[12.x] Support Check Constraints #56929
Draft
+729
−30
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Closes #56523
Fixes #56151
Related to #51373, #45487
Previous attempts #46512, #46883, #56000, #41078
This PR adds support for inspecting, adding, and dropping check constraints on all database drivers (Yes, even SQLite!).
Why?
Check constraints are a valuable feature at the database layer, and adding support for them is now straightforward (after #51373). Managing check constraints manually can be cumbersome, especially in SQLite.
We currently rely on check constraints for enum column types (except on MySQL and MariaDB, which have a native enum type). Without proper support, modifying enum columns has been difficult. This PR addresses that by allowing developers to:
Usage
Create a table with check constraints
Modify a table with check constraints
Drop check constraints of a table
Inspect check constraints of a table
name
(?string
): Name of the constraintcolumns
(string[]
): Array of constrained columnsdefinition
(string
): Constraint definition expressionNotes
Similar to other implemented constraints (e.g., foreign keys, unique, etc.), check constraints are always added through separate commands rather than at the column level.
A check constraint defined on a column (e.g.
$table->integer('price')->check('price > 0')
) will be assigned a default name following the same convention as other constraints:table_column_check
.However, a check constraint defined at the table level (e.g.
$table->check('price > 0')
) will not receive a default name (hard to generate a random one using the expression!)It is highly recommended to assign explicit names to your constraints. You can do this:
$table->check('price > 0', 'orders_price_check')
$table->check('price > 0', ['price'])
On PostgreSQL, SQL Server, and SQLite, the enum column type is defined as a string with a check constraint (i.e.
$table->enum('foo', ['a', 'b', 'c'])
was equivalent tofoo varchar check (foo in ('a', 'b', 'c'))
) . As a result, modifying or redefining an enum column was not previously supported on these DB drivers.This PR resolves that. You can now drop the previous check constraint of an enum column and redefine it as needed:
Previously, this check constraint of an enum column did not have a default name. As a result, PostgreSQL and SQL Server automatically assigned their own names to the constraint. This PR now explicitly assigns names (as explained in Note 2). The assigned name matches PostgreSQL’s behavior but differs from SQL Server’s default.
Q: Why do I have to manually drop the constraint? Why doesn't the
change
modifier handle it automatically?A: As a rule of thumb, the
change
modifier only modifies column attributes (e.g., type, default, nullable, comment, etc.) and does not affect indexes, foreign keys, or check constraints. Therefore, constraints must be dropped manually when needed.You can drop check constraints either by name or by columns. Dropping by columns is possible thanks to the table inspection functionality:
$table->dropCheck(['c1'])
), the command will drop every constraint that involves onlyc1
. That means a constraints that include bothc1
andc2
columns will not be dropped.$table->dropCheck(['c1', 'c2'])
and$table->dropCheck(['c2', 'c1'])
are equivalent and will drop any constraints that have both of these columns only.$table->dropCheck('orders_price_check')
), SQLite will drop all constraints with that name.