Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Query/Processors/Processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,23 @@ public function processForeignKeys($results)
{
return $results;
}

/**
* Process the results of a check constraints query.
*
* @param list<array<string, mixed>> $results
* @return list<array{name: string|null, columns: list<string>, definition: string}>
*/
public function processCheckConstraints($results)
{
return array_map(function ($result) {
$result = (object) $result;

return [
'name' => $result->name ?? null,
'columns' => explode(',', $result->columns ?? ''),
'definition' => $result->definition,
];
}, $results);
}
}
24 changes: 24 additions & 0 deletions src/Illuminate/Database/Query/Processors/SQLiteProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,28 @@ public function processForeignKeys($results)
];
}, $results);
}

/**
* Process the results of a check constraints query.
*
* @param list<array<string, mixed>> $results
* @param string $sql
* @param list<string> $columns
* @return list<array{name: string|null, columns: list<string>, definition: string}>
*/
public function processCheckConstraints($results, ?string $sql = '', array $columns = [])
{
preg_match_all(
'/(?:constraint\s+"?([^"\s]+)"?)?\s+check\s*(\((?:[^()]+|\((?:[^()]+|\([^()]*\))*\))*\))/i',
$sql ?? '',
$matches,
PREG_SET_ORDER | PREG_UNMATCHED_AS_NULL
);

return array_map(fn (array $match) => [
'name' => $match[1],
'columns' => array_values(array_filter($columns, fn (string $column) => str_contains($match[2], $column))),
'definition' => $match[2],
], $matches);
}
}
61 changes: 61 additions & 0 deletions src/Illuminate/Database/Schema/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
namespace Illuminate\Database\Schema;

use Closure;
use Illuminate\Contracts\Database\Query\Expression as ExpressionContract;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Grammars\Grammar;
use Illuminate\Database\Schema\Grammars\MySqlGrammar;
use Illuminate\Database\Schema\Grammars\PostgresGrammar;
use Illuminate\Database\Schema\Grammars\SQLiteGrammar;
use Illuminate\Database\Schema\Grammars\SqlServerGrammar;
use Illuminate\Support\Collection;
use Illuminate\Support\Fluent;
use Illuminate\Support\Traits\Macroable;
Expand Down Expand Up @@ -193,6 +196,7 @@ protected function commandsNamed(array $names)
protected function addImpliedCommands()
{
$this->addFluentIndexes();
$this->addFluentCheckConstraints();
$this->addFluentCommands();

if (! $this->creating()) {
Expand Down Expand Up @@ -256,6 +260,36 @@ protected function addFluentIndexes()
}
}

/**
* Add the check constraint commands fluently specified on columns.
*
* @return void
*/
protected function addFluentCheckConstraints()
{
foreach ($this->columns as $column) {
if ($column->type === 'enum' && (
$this->grammar instanceof PostgresGrammar ||
$this->grammar instanceof SQLiteGrammar ||
$this->grammar instanceof SqlServerGrammar
)) {
$column->check = sprintf('%s in (%s)',
$this->grammar->wrap($column->name),
$this->grammar->quoteString($column->allowed)
);
}

if ($column->check) {
$this->check(
$column->check,
$this->createIndexName('check', [$column->name])
);

$column->check = null;
}
}
}

/**
* Add the fluent commands specified on any columns.
*
Expand Down Expand Up @@ -540,6 +574,17 @@ public function dropConstrainedForeignIdFor($model, $column = null)
return $this->dropConstrainedForeignId($column ?: $model->getForeignKey());
}

/**
* Indicate that the given check constraint should be dropped.
*
* @param string|string[] $nameOrColumns
* @return \Illuminate\Support\Fluent
*/
public function dropCheck(string|array $nameOrColumns): Fluent
{
return $this->addCommand('dropCheck', [(is_array($nameOrColumns) ? 'columns' : 'constraint') => $nameOrColumns]);
}

/**
* Indicate that the given indexes should be renamed.
*
Expand Down Expand Up @@ -724,6 +769,22 @@ public function foreign($columns, $name = null)
return $command;
}

/**
* Specify a check constraint for the table.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $expression
* @param string|string[]|null $nameOrColumns
* @return \Illuminate\Support\Fluent
*/
public function check(ExpressionContract|string $expression, string|array|null $nameOrColumns = null): Fluent
{
if (is_array($nameOrColumns)) {
$nameOrColumns = $this->createIndexName('check', $nameOrColumns);
}

return $this->addCommand('check', ['expression' => $expression, 'constraint' => $nameOrColumns]);
}

/**
* Create a new auto-incrementing big integer (8-byte) column on the table.
*
Expand Down
54 changes: 54 additions & 0 deletions src/Illuminate/Database/Schema/BlueprintState.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ class BlueprintState
*/
private $foreignKeys;

/**
* The check constraints.
*
* @var \Illuminate\Support\Fluent[]
*/
private $checkConstraints;

/**
* Create a new blueprint state instance.
*
Expand Down Expand Up @@ -103,6 +110,12 @@ public function __construct(Blueprint $blueprint, Connection $connection)
'onUpdate' => $foreignKey['on_update'],
'onDelete' => $foreignKey['on_delete'],
]))->all();

$this->checkConstraints = (new Collection($schema->getCheckConstraints($table)))->map(fn ($constraint) => new Fluent([
'constraint' => $constraint['name'],
'columns' => $constraint['columns'],
'expression' => new Expression(Str::unwrap($constraint['definition'], '(', ')')),
]))->all();
}

/**
Expand Down Expand Up @@ -145,6 +158,16 @@ public function getForeignKeys()
return $this->foreignKeys;
}

/**
* Get the check constraints.
*
* @return \Illuminate\Support\Fluent[]
*/
public function getCheckConstraints()
{
return $this->checkConstraints;
}

/*
* Update the blueprint's state.
*
Expand Down Expand Up @@ -192,6 +215,10 @@ public function update(Fluent $command)
$foreignKey->columns = str_replace($command->from, $command->to, $foreignKey->columns);
}

foreach ($this->checkConstraints as $checkConstraint) {
$checkConstraint->columns = str_replace($command->from, $command->to, $checkConstraint->columns);
}

break;

case 'dropColumn':
Expand Down Expand Up @@ -224,6 +251,10 @@ public function update(Fluent $command)
$this->foreignKeys[] = $command;
break;

case 'check':
$this->checkConstraints[] = $command;
break;

case 'dropPrimary':
$this->primaryKey = null;
break;
Expand All @@ -241,6 +272,29 @@ public function update(Fluent $command)
array_filter($this->foreignKeys, fn ($fk) => $fk->columns !== $command->columns)
);

break;

case 'dropCheck':
if ($command->constraint) {
$this->checkConstraints = array_values(
array_filter($this->checkConstraints, fn ($constraint) => $constraint->constraint !== $command->constraint)
);

break;
}

$commandColumns = $command->columns;
sort($commandColumns);

$this->checkConstraints = array_values(
array_filter($this->checkConstraints, function ($constraint) use ($commandColumns) {
$constraintColumns = $constraint->columns;
sort($constraintColumns);

return $constraintColumns !== $commandColumns;
})
);

break;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/Illuminate/Database/Schema/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,25 @@ public function getForeignKeys($table)
);
}

/**
* Get the check constraints for a given table.
*
* @param string $table
* @return array
*/
public function getCheckConstraints($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);

$table = $this->connection->getTablePrefix().$table;

return $this->connection->getPostProcessor()->processCheckConstraints(
$this->connection->selectFromWriteConnection(
$this->grammar->compileCheckConstraints($schema, $table)
)
);
}

/**
* Modify a table on the schema.
*
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Database/Schema/ColumnDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @method $this autoIncrement() Set INTEGER columns as auto-increment (primary key)
* @method $this change() Change the column
* @method $this charset(string $charset) Specify a character set for the column (MySQL)
* @method $this check(\Illuminate\Contracts\Database\Query\Expression|string $expression) Specify a check constraint for the column
* @method $this collation(string $collation) Specify a collation for the column
* @method $this comment(string $comment) Add a comment to the column (MySQL/PostgreSQL)
* @method $this default(mixed $value) Specify a "default" value for the column
Expand Down
76 changes: 76 additions & 0 deletions src/Illuminate/Database/Schema/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Concerns\CompilesJsonPaths;
use Illuminate\Database\Grammar as BaseGrammar;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Collection;
use Illuminate\Support\Fluent;
use RuntimeException;

Expand Down Expand Up @@ -164,6 +165,20 @@ public function compileForeignKeys($schema, $table)
throw new RuntimeException('This database driver does not support retrieving foreign keys.');
}

/**
* Compile the query to determine the check constraints.
*
* @param string|null $schema
* @param string $table
* @return string
*
* @throws \RuntimeException
*/
public function compileCheckConstraints($schema, $table)
{
throw new RuntimeException('This database driver does not support retrieving check constraints.');
}

/**
* Compile a rename column command.
*
Expand Down Expand Up @@ -274,6 +289,67 @@ public function compileDropForeign(Blueprint $blueprint, Fluent $command)
throw new RuntimeException('This database driver does not support dropping foreign keys.');
}

/**
* Compile a check constraint command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string|null
*/
public function compileCheck(Blueprint $blueprint, Fluent $command)
{
return sprintf('alter table %s add%s check (%s)',
$this->wrapTable($blueprint),
$command->constraint ? ' constraint '.$this->wrap($command->constraint) : '',
$this->getValue($command->expression)
);
}

/**
* Compile a drop check constraint command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string|null
*/
public function compileDropCheck(Blueprint $blueprint, Fluent $command)
{
if ($command->constraint) {
$command->constraints = [$command->constraint];
$command->constraint = null;

return $this->compileDropConstraints($blueprint, $command);
}

$columns = $command->columns;
$command->columns = null;
sort($columns);

$command->constraints = (new Collection($this->connection->getSchemaBuilder()->getCheckConstraints($blueprint->getTable())))
->filter(function (array $checkConstraint) use ($columns) {
sort($checkConstraint['columns']);

return $checkConstraint['columns'] === $columns;
})->pluck('name')->all();

return $this->compileDropConstraints($blueprint, $command);
}

/**
* Compile a drop constraints command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string|null
*/
public function compileDropConstraints(Blueprint $blueprint, Fluent $command)
{
return sprintf('alter table %s %s',
$this->wrapTable($blueprint),
implode(', ', $this->prefixArray('drop constraint', $this->wrapArray($command->constraints)))
);
}

/**
* Compile the blueprint's added column definitions.
*
Expand Down
Loading