Skip to content

Conversation

dbakan
Copy link
Contributor

@dbakan dbakan commented Feb 17, 2022

This pull requests adds support for table check constraints (Documentation: MySQL | PostrgeSQL | SQLite | SQL Server).

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->unsignedInteger('age');

    $table->check('age>=21');
});

Schema::create('events', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->datetime('starts_at');
    $table->datetime('ends_at')->nullable();
    $table->unsignedInteger('price');
    $table->unsignedInteger('discounted_price');

    $table->check('ends_at IS NULL OR ends_at>starts_at');
    $table->check('discounted_price <= price', 'check_valid_discounts');
});

In our use case, a Booking has many BookingItems, which morph to a contractor, who can either be a Hotel or a Flight. In case of a Hotel we need to known the room_id, for any other contractor_typethe room_id must be null.

While we will continue to validate this in the application, this PR allows us to add another layer of integrity for the database:

Schema::create('booking_items', function (Blueprint $table) {
    $table->id();
    $table->morphs('contractor');
    $table->foreignId('room_id')->constraint()->nullable();

    $table->check('contractor_type!="hotel" OR room_id IS NOT NULL');
});

All the default database drivers have support for check constraints (edit: For MySQL 5.7 The CHECK clause is parsed but ignored by all storage engines.). SQLite comes with a number of limitions:

  • SQLite cannot add check constraints to existing tables.
  • SQLite cannot drop check constraints.

Some similar limitations are currently covered in Blueprint::ensureCommandsAreValid() (link), while more recent PRs throw exceptions inside of the corresponding methods of SQLiteGrammar (e.g. compileSpatialIndex (link). I followed the later approach for now.

Note that the expression is considered "raw", meaning that it is not wrapped or parsed in any way. As long as it is valid SQL, the underlying database should handle it well.

I was torn between $table->check(...) and $table->checkConstraint(...) and I'm of course open for suggestions.
Please let me know if there's anything to be changed, improved or clarified.

If this gets merged, I'd be happy to look into inline column checks next as in $table->integer('age)->check('age>=21').

@taylorotwell
Copy link
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If possible, please consider releasing your code as a package so that the community can still take advantage of your contributions!

If you feel absolutely certain that this code corrects a bug in the framework, please "@" mention me in a follow-up comment with further explanation so that GitHub will send me a notification of your response.

@hafezdivandari
Copy link
Contributor

#56929

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants