Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[RFC]: Replace caller-supplied Literal slots in Sql\Ddl with backed enums and typed arguments

Aperta
#181 0 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@simon-mundy ci sta già lavorando.

Dal 4/9/2026.

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

enhancement
Proposed Version

0.6.0 for the enums, typed arguments and string-compat setters; the string-getter retype waits for the next major.

Basic Information

Every place under src/Sql/Ddl/ where a caller-supplied string becomes an Argument\Literal or is concatenated into a spec string gets a backed enum (closed value sets) or a typed int (numeric slots), with a one-minor string-compat path. This is the core counterpart of phpdb-mysql#79.

Background

The DDL layer renders three kinds of argument: Argument\Identifier (quoted as an identifier), Argument\Value (quoted or bound) and Argument\Literal (inserted verbatim). Going through every getExpressionData() under src/Sql/Ddl/ (and processTableOptions() for table options), these are the places where a Literal, or a bare string concatenated into the spec, comes from the caller rather than a class constant:

Class Slot Origin Rendered probe
Index column prefix length untyped array $lengths, concatenated into the spec INDEX `i`(`email`(20) INJECT))
Index USING type setType(string) -> Literal USING HASH; DROP
ForeignKey ON DELETE / ON UPDATE rule string setters -> Literal ON DELETE CASCADE; DROP
Integer display width options['length'] (untyped via the constructor array, bool|string via setOption()), concatenated INTEGER NOT NULL (11) (also a positional bug, #178)
Check expression untyped constructor with a string|ExpressionInterface docblock -> Literal an expression slot by design; an Expression is accepted at construction and fails at render (#177)
CreateTable / AlterTable processTableOptions() table option key setOption(string $name, ...), strtoupper() only ENGINE = INNODB; DROP TABLE Y; -- = 'x'

Example: setOption('row_format', 'DYNAMIC'), 'algorithm' and 'lock' with a plain string render a quoted value MySQL rejects with 1064. Only a Literal works, and nothing tells the caller that.

Safe by construction and not proposed to change: Column::$type (protected, set by the subclass), AbstractLengthColumn / AbstractPrecisionColumn lengths (built from ?int), AbstractTimestampColumn's fixed ON UPDATE CURRENT_TIMESTAMP, all identifier slots, Column defaults given as an Argument\Literal, and table-option values given as a Sql\Literal. Those last two are raw by definition.

Considerations
  • Docs bug. Only Argument\Literal itself says a Literal default is raw. Column::setDefault() and the DDL docs don't, and docs/book/sql-ddl/columns.md:340-346, alter-drop.md, examples.md and intro.md all show setDefault('CURRENT_TIMESTAMP') producing an unquoted default. It actually renders DEFAULT 'CURRENT_TIMESTAMP', which MySQL rejects (1067). Worth its own issue; listed here because it is the other side of the same invariant.
  • Getters. getOnDeleteRule(): string, getOnUpdateRule(): string and Index::getType(): ?string are asserted as strings in ForeignKeyTest.php:106-132 and IndexTest.php:67. Retyping them in a minor would be a BC break, so they stay until the next major.
  • SET DEFAULT is valid grammar that InnoDB doesn't honour (on 8.0.46 the DDL is accepted and RESTRICT is enforced at DML time). Keep it in the enum, document the caveat.
  • Index prefix length: MySQL rejects 0 with 1391, so positive-int is exactly the server's bound.
  • Integer display width is itself deprecated since 8.0.17; keeping it as a typed int is for the other platforms, and the MySQL decorator will drop it.
  • Overlaps: #169 (typing the option arrays), phpdb-mysql#79 (the adapter side), the statements umbrella, #179 (RowFormat/Algorithm/Lock and key validation live there).
Proposal(s)
  1. Sql\Ddl\Constraint\ReferentialAction: string { NoAction = 'NO ACTION'; Restrict = 'RESTRICT'; Cascade = 'CASCADE'; SetNull = 'SET NULL'; SetDefault = 'SET DEFAULT' } for ForeignKey::setOnDeleteRule() / setOnUpdateRule() and the constructor.
  2. Sql\Ddl\Index\IndexType: string { BTree = 'BTREE'; Hash = 'HASH' } for Index::setType().
  3. Index::$lengths typed list<positive-int> (validated in the constructor) and rendered as Literal((string) $int) in the values array, not in the spec.
  4. Integer length as int (see #178).
  5. Table option keys validated against a pattern or an enum of known options; string values keep going through quoteTrustedValue(), keyword values through RowFormat / Algorithm / Lock enums (#179).

Compatibility:

  • Setters keep accepting string for one minor, resolved with Enum::tryFrom(strtoupper(trim($value))) and rejected with InvalidArgumentException when unknown. Passing a string is deprecated in the docblock and removed at the next major.
  • Getters keep returning string for the same period. Add enum-returning siblings now (getOnDeleteAction(): ReferentialAction, getOnUpdateAction(): ReferentialAction, Index::getIndexType(): ?IndexType) and retype the string getters at the next major.

Test plan:

  • No getExpressionData() under src/Sql/Ddl/ constructs an Argument\Literal from a caller string except Check (documented) and Column defaults (documented in setDefault() and the docs, with the CURRENT_TIMESTAMP examples corrected).
  • No getExpressionData() or processTableOptions() concatenates a caller value into the spec string.
  • Each enum has a unit test for accepted values, the string-compat path and rejection.
  • The probe strings above render quoted/escaped or throw; none reaches SQL verbatim.
  • Existing string-getter tests pass unchanged.
Appendix/Additional Info
  • #91 (backed enums for metadata constants), #169 (untyped option arrays), phpdb-mysql#79 (the same fix on the adapter side).
  • From an internal DDL audit (not published). The full slot table there includes the adapter side; the core rows are reproduced above.
Lingua principale
PHP
Stelle
18
Fork
8
Merge medio
6g 11h
PR unite (30g)
14

Preparare l'ambiente

  • Include un Dockerfile o un file Docker Compose
  • Nessun modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di php-db/phpdb

Tutte le issue di php-db/phpdb

Issue simili

Altre issue su PHP

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.