[RFC]: Replace caller-supplied Literal slots in Sql\Ddl with backed enums and typed arguments
メンテナーはふだん 1 日以内に返信
@simon-mundy がすでに取り組んでいます。
2026年9月4日 から。
評価
この issue はまだ評価されていません。
説明
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\Literalitself says aLiteraldefault is raw.Column::setDefault()and the DDL docs don't, anddocs/book/sql-ddl/columns.md:340-346,alter-drop.md,examples.mdandintro.mdall showsetDefault('CURRENT_TIMESTAMP')producing an unquoted default. It actually rendersDEFAULT '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(): stringandIndex::getType(): ?stringare asserted as strings inForeignKeyTest.php:106-132andIndexTest.php:67. Retyping them in a minor would be a BC break, so they stay until the next major. SET DEFAULTis 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-intis exactly the server's bound. - Integer display width is itself deprecated since 8.0.17; keeping it as a typed
intis 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/Lockand key validation live there).
Proposal(s)
Sql\Ddl\Constraint\ReferentialAction: string { NoAction = 'NO ACTION'; Restrict = 'RESTRICT'; Cascade = 'CASCADE'; SetNull = 'SET NULL'; SetDefault = 'SET DEFAULT' }forForeignKey::setOnDeleteRule()/setOnUpdateRule()and the constructor.Sql\Ddl\Index\IndexType: string { BTree = 'BTREE'; Hash = 'HASH' }forIndex::setType().Index::$lengthstypedlist<positive-int>(validated in the constructor) and rendered asLiteral((string) $int)in the values array, not in the spec.Integerlength asint(see #178).- Table option keys validated against a pattern or an enum of known options; string values keep going through
quoteTrustedValue(), keyword values throughRowFormat/Algorithm/Lockenums (#179).
Compatibility:
- Setters keep accepting
stringfor one minor, resolved withEnum::tryFrom(strtoupper(trim($value)))and rejected withInvalidArgumentExceptionwhen unknown. Passing a string is deprecated in the docblock and removed at the next major. - Getters keep returning
stringfor 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()undersrc/Sql/Ddl/constructs anArgument\Literalfrom a caller string exceptCheck(documented) andColumndefaults (documented insetDefault()and the docs, with theCURRENT_TIMESTAMPexamples corrected). - No
getExpressionData()orprocessTableOptions()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.
- 主要言語
- PHP
- スター
- 18
- フォーク
- 8
- 平均マージ
- 6日 11時間
- マージ済み PR(30日)
- 14
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
php-db/phpdb のほかの issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 1 日以内に返信
-
RFC
難易度 3/5 1〜2日 初心者へのやさしさ 66/100
メンテナーはふだん 1 日以内に返信
-
RFC
難易度 5/5 1週間以上 初心者へのやさしさ 38/100
メンテナーはふだん 1 日以内に返信
-
RFC
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
RFC
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
extension/Commercial needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 2 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 90/100
crazy-goat/rabbit-stream#753 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
opensourcepos/opensourcepos#4743 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
OpenConext/OpenConext-engineblock#2129 ·
メンテナーはふだん 3 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 82/100
grokability/snipe-it#19786 ·
メンテナーはふだん 1 日以内に返信