Package Version
0.5.0 (also 0.6.x-dev @ e037464a and the 0.6.x head)
Php Version
8.4
Database Engine
MySQL
Basic Information
Sql\Ddl\Constraint\Check documents its expression as string|ExpressionInterface (property docblock Check.php#L16, constructor docblock #L21 — the parameter at #L23 is untyped) but always wraps it in new Literal($this->expression) (#L43), and Argument\Literal::__construct() takes string. So an Expression is accepted at construction and blows up at render.
Three problems:
- An
ExpressionInterface expression throws TypeError at render.
- An empty expression renders a bare
CONSTRAINT chk`` header — the !== '' guard at L41 quietly drops the `CHECK (%s)` part while the name guard at L36 still emits `CONSTRAINT %s`.
__construct($expression, ?string $name) gives $name no default, so an unnamed check has to be written new Check('id > 0', null). PrimaryKey and UniqueKey default the name (ForeignKey requires it, as a non-nullable first parameter); Check is the odd one out in being required yet nullable.
test/unit/Sql/Ddl/Constraint/CheckTest.php only ever exercises new Check('id>0', 'foo').
Steps to Reproduce
new Check(new Expression('a > ?', [1]), 'chk') added to a CreateTable, then getSqlString($mysqlPlatform).
new Check('', 'chk') added to a CreateTable with one column, then getSqlString($mysqlPlatform).
new Check('id > 0') with no second argument.
Expected Behavior
- When the expression is an
ExpressionInterface, merge its getExpressionData() into the check's — spec CHECK (<inner spec>), values appended — so values and identifiers inside it are quoted by the platform instead of being flattened to a literal. I have prototyped this: it renders CHECK (a > '1') and CHECK (a>b), and MySQL accepts both. A plain string stays a Literal; it is an expression slot by design and should be documented as such.
- Throw
PhpDb\Sql\Exception\InvalidArgumentException for an empty string at construction — the same exception Sql\Expression::setExpression() already throws for an empty string. Nothing under Sql\Ddl validates anything today, so that is the precedent.
?string $name = null.
There is no prepare path to worry about: DDL objects extend AbstractSql, not AbstractPreparableSql, and MySQL itself rejects a parameter marker inside a CHECK at execute time (ERROR 3815). Values in a check are always inlined.
Test plan:
new Check(new Expression('a > ?', [1]), 'chk') renders CONSTRAINT `chk` CHECK (a > '1') via getSqlString() on the MySQL platform.
new Check('') throws InvalidArgumentException.
new Check('id > 0') compiles and renders CHECK (id > 0).
- Column-level use still renders inline:
`age` INTEGER NOT NULL CONSTRAINT `chk_age` CHECK (age >= 0) (accepted by MySQL 8.4.10, reproduced on 8.0.46).
CheckTest covers all four. If this gets an integration test: CHECK names are schema-wide in MySQL (3822 on a duplicate), so use unique names per test.
Actual behavior?
TypeError: PhpDb\Sql\Argument\Literal::__construct(): Argument #1 ($literal) must be of type string, PhpDb\Sql\Expression given
- (MySQL
AdapterPlatform)
CREATE TABLE `x` (
`a` INTEGER NOT NULL ,
CONSTRAINT `chk`
)
ArgumentCountError — too few arguments.
Additional Info
Reproduced on 0.5.x, e037464a and the 0.6.x head. From an internal DDL audit (not published — the finding is reproduced above). The Check expression slot also appears in the literal-slots RFC (#181) as a documented by-design Literal.
Package Version
0.5.0 (also
0.6.x-dev@e037464aand the0.6.xhead)Php Version
8.4
Database Engine
MySQL
Basic Information
Sql\Ddl\Constraint\Checkdocuments its expression asstring|ExpressionInterface(property docblock Check.php#L16, constructor docblock #L21 — the parameter at #L23 is untyped) but always wraps it innew Literal($this->expression)(#L43), andArgument\Literal::__construct()takesstring. So anExpressionis accepted at construction and blows up at render.Three problems:
ExpressionInterfaceexpression throwsTypeErrorat render.CONSTRAINTchk`` header — the!== ''guard at L41 quietly drops the `CHECK (%s)` part while the name guard at L36 still emits `CONSTRAINT %s`.__construct($expression, ?string $name)gives$nameno default, so an unnamed check has to be writtennew Check('id > 0', null).PrimaryKeyandUniqueKeydefault the name (ForeignKeyrequires it, as a non-nullable first parameter);Checkis the odd one out in being required yet nullable.test/unit/Sql/Ddl/Constraint/CheckTest.phponly ever exercisesnew Check('id>0', 'foo').Steps to Reproduce
new Check(new Expression('a > ?', [1]), 'chk')added to aCreateTable, thengetSqlString($mysqlPlatform).new Check('', 'chk')added to aCreateTablewith one column, thengetSqlString($mysqlPlatform).new Check('id > 0')with no second argument.Expected Behavior
ExpressionInterface, merge itsgetExpressionData()into the check's — specCHECK (<inner spec>), values appended — so values and identifiers inside it are quoted by the platform instead of being flattened to a literal. I have prototyped this: it rendersCHECK (a > '1')andCHECK (a>b), and MySQL accepts both. A plain string stays aLiteral; it is an expression slot by design and should be documented as such.PhpDb\Sql\Exception\InvalidArgumentExceptionfor an empty string at construction — the same exceptionSql\Expression::setExpression()already throws for an empty string. Nothing underSql\Ddlvalidates anything today, so that is the precedent.?string $name = null.There is no prepare path to worry about: DDL objects extend
AbstractSql, notAbstractPreparableSql, and MySQL itself rejects a parameter marker inside a CHECK at execute time (ERROR 3815). Values in a check are always inlined.Test plan:
new Check(new Expression('a > ?', [1]), 'chk')rendersCONSTRAINT `chk` CHECK (a > '1')viagetSqlString()on the MySQL platform.new Check('')throwsInvalidArgumentException.new Check('id > 0')compiles and rendersCHECK (id > 0).`age` INTEGER NOT NULL CONSTRAINT `chk_age` CHECK (age >= 0)(accepted by MySQL 8.4.10, reproduced on 8.0.46).CheckTestcovers all four. If this gets an integration test: CHECK names are schema-wide in MySQL (3822 on a duplicate), so use unique names per test.Actual behavior?
AdapterPlatform)ArgumentCountError— too few arguments.Additional Info
Reproduced on
0.5.x,e037464aand the0.6.xhead. From an internal DDL audit (not published — the finding is reproduced above). TheCheckexpression slot also appears in the literal-slots RFC (#181) as a documented by-designLiteral.