This commit is contained in:
2026-05-01 23:40:14 +08:00
commit b8f599a617
3867 changed files with 478663 additions and 0 deletions
+1983
View File
File diff suppressed because it is too large Load Diff
+247
View File
@@ -0,0 +1,247 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Collection;
use Illuminate\Support\Fluent;
use Illuminate\Support\Str;
class BlueprintState
{
/**
* The blueprint instance.
*
* @var \Illuminate\Database\Schema\Blueprint
*/
protected $blueprint;
/**
* The connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The columns.
*
* @var \Illuminate\Database\Schema\ColumnDefinition[]
*/
private $columns;
/**
* The primary key.
*
* @var \Illuminate\Database\Schema\IndexDefinition|null
*/
private $primaryKey;
/**
* The indexes.
*
* @var \Illuminate\Database\Schema\IndexDefinition[]
*/
private $indexes;
/**
* The foreign keys.
*
* @var \Illuminate\Database\Schema\ForeignKeyDefinition[]
*/
private $foreignKeys;
/**
* Create a new blueprint state instance.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Database\Connection $connection
*/
public function __construct(Blueprint $blueprint, Connection $connection)
{
$this->blueprint = $blueprint;
$this->connection = $connection;
$schema = $connection->getSchemaBuilder();
$table = $blueprint->getTable();
$this->columns = (new Collection($schema->getColumns($table)))->map(fn ($column) => new ColumnDefinition([
'name' => $column['name'],
'type' => $column['type_name'],
'full_type_definition' => $column['type'],
'nullable' => $column['nullable'],
'default' => is_null($column['default']) ? null : new Expression(Str::wrap($column['default'], '(', ')')),
'autoIncrement' => $column['auto_increment'],
'collation' => $column['collation'],
'comment' => $column['comment'],
'virtualAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'virtual'
? $column['generation']['expression']
: null,
'storedAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'stored'
? $column['generation']['expression']
: null,
]))->all();
[$primary, $indexes] = (new Collection($schema->getIndexes($table)))->map(fn ($index) => new IndexDefinition([
'name' => match (true) {
$index['primary'] => 'primary',
$index['unique'] => 'unique',
default => 'index',
},
'index' => $index['name'],
'columns' => $index['columns'],
]))->partition(fn ($index) => $index->name === 'primary');
$this->indexes = $indexes->all();
$this->primaryKey = $primary->first();
$this->foreignKeys = (new Collection($schema->getForeignKeys($table)))->map(fn ($foreignKey) => new ForeignKeyDefinition([
'columns' => $foreignKey['columns'],
'on' => new Expression($foreignKey['foreign_table']),
'references' => $foreignKey['foreign_columns'],
'onUpdate' => $foreignKey['on_update'],
'onDelete' => $foreignKey['on_delete'],
]))->all();
}
/**
* Get the primary key.
*
* @return \Illuminate\Database\Schema\IndexDefinition|null
*/
public function getPrimaryKey()
{
return $this->primaryKey;
}
/**
* Get the columns.
*
* @return \Illuminate\Database\Schema\ColumnDefinition[]
*/
public function getColumns()
{
return $this->columns;
}
/**
* Get the indexes.
*
* @return \Illuminate\Database\Schema\IndexDefinition[]
*/
public function getIndexes()
{
return $this->indexes;
}
/**
* Get the foreign keys.
*
* @return \Illuminate\Database\Schema\ForeignKeyDefinition[]
*/
public function getForeignKeys()
{
return $this->foreignKeys;
}
/*
* Update the blueprint's state.
*
* @param \Illuminate\Support\Fluent $command
* @return void
*/
public function update(Fluent $command)
{
switch ($command->name) {
case 'alter':
// Already handled...
break;
case 'add':
$this->columns[] = $command->column;
break;
case 'change':
foreach ($this->columns as &$column) {
if ($column->name === $command->column->name) {
$column = $command->column;
break;
}
}
break;
case 'renameColumn':
foreach ($this->columns as $column) {
if ($column->name === $command->from) {
$column->name = $command->to;
break;
}
}
if ($this->primaryKey) {
$this->primaryKey->columns = str_replace($command->from, $command->to, $this->primaryKey->columns);
}
foreach ($this->indexes as $index) {
$index->columns = str_replace($command->from, $command->to, $index->columns);
}
foreach ($this->foreignKeys as $foreignKey) {
$foreignKey->columns = str_replace($command->from, $command->to, $foreignKey->columns);
}
break;
case 'dropColumn':
$this->columns = array_values(
array_filter($this->columns, fn ($column) => ! in_array($column->name, $command->columns))
);
break;
case 'primary':
$this->primaryKey = $command;
break;
case 'unique':
case 'index':
$this->indexes[] = $command;
break;
case 'renameIndex':
foreach ($this->indexes as $index) {
if ($index->index === $command->from) {
$index->index = $command->to;
break;
}
}
break;
case 'foreign':
$this->foreignKeys[] = $command;
break;
case 'dropPrimary':
$this->primaryKey = null;
break;
case 'dropIndex':
case 'dropUnique':
$this->indexes = array_values(
array_filter($this->indexes, fn ($index) => $index->index !== $command->index)
);
break;
case 'dropForeign':
$this->foreignKeys = array_values(
array_filter($this->foreignKeys, fn ($fk) => $fk->columns !== $command->columns)
);
break;
}
}
}
+782
View File
@@ -0,0 +1,782 @@
<?php
namespace Illuminate\Database\Schema;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Database\Connection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
class Builder
{
use Macroable;
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The schema grammar instance.
*
* @var \Illuminate\Database\Schema\Grammars\Grammar
*/
protected $grammar;
/**
* The Blueprint resolver callback.
*
* @var \Closure(\Illuminate\Database\Connection, string, \Closure|null): \Illuminate\Database\Schema\Blueprint
*/
protected $resolver;
/**
* The default string length for migrations.
*
* @var non-negative-int|null
*/
public static $defaultStringLength = 255;
/**
* The default time precision for migrations.
*/
public static ?int $defaultTimePrecision = 0;
/**
* The default relationship morph key type.
*
* @var 'int'|'uuid'|'ulid'
*/
public static $defaultMorphKeyType = 'int';
/**
* Create a new database Schema manager.
*
* @param \Illuminate\Database\Connection $connection
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->grammar = $connection->getSchemaGrammar();
}
/**
* Set the default string length for migrations.
*
* @param non-negative-int $length
* @return void
*/
public static function defaultStringLength($length)
{
static::$defaultStringLength = $length;
}
/**
* Set the default time precision for migrations.
*/
public static function defaultTimePrecision(?int $precision): void
{
static::$defaultTimePrecision = $precision;
}
/**
* Set the default morph key type for migrations.
*
* @param string $type
* @return void
*
* @throws \InvalidArgumentException
*/
public static function defaultMorphKeyType(string $type)
{
if (! in_array($type, ['int', 'uuid', 'ulid'])) {
throw new InvalidArgumentException("Morph key type must be 'int', 'uuid', or 'ulid'.");
}
static::$defaultMorphKeyType = $type;
}
/**
* Set the default morph key type for migrations to UUIDs.
*
* @return void
*/
public static function morphUsingUuids()
{
static::defaultMorphKeyType('uuid');
}
/**
* Set the default morph key type for migrations to ULIDs.
*
* @return void
*/
public static function morphUsingUlids()
{
static::defaultMorphKeyType('ulid');
}
/**
* Create a database in the schema.
*
* @param string $name
* @return bool
*/
public function createDatabase($name)
{
return $this->connection->statement(
$this->grammar->compileCreateDatabase($name)
);
}
/**
* Drop a database from the schema if the database exists.
*
* @param string $name
* @return bool
*/
public function dropDatabaseIfExists($name)
{
return $this->connection->statement(
$this->grammar->compileDropDatabaseIfExists($name)
);
}
/**
* Get the schemas that belong to the connection.
*
* @return list<array{name: string, path: string|null, default: bool}>
*/
public function getSchemas()
{
return $this->connection->getPostProcessor()->processSchemas(
$this->connection->selectFromWriteConnection($this->grammar->compileSchemas())
);
}
/**
* Determine if the given table exists.
*
* @param string $table
* @return bool
*/
public function hasTable($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);
$table = $this->connection->getTablePrefix().$table;
if ($sql = $this->grammar->compileTableExists($schema, $table)) {
return (bool) $this->connection->scalar($sql);
}
foreach ($this->getTables($schema ?? $this->getCurrentSchemaName()) as $value) {
if (strtolower($table) === strtolower($value['name'])) {
return true;
}
}
return false;
}
/**
* Determine if the given view exists.
*
* @param string $view
* @return bool
*/
public function hasView($view)
{
[$schema, $view] = $this->parseSchemaAndTable($view);
$view = $this->connection->getTablePrefix().$view;
foreach ($this->getViews($schema ?? $this->getCurrentSchemaName()) as $value) {
if (strtolower($view) === strtolower($value['name'])) {
return true;
}
}
return false;
}
/**
* Get the tables that belong to the connection.
*
* @param string|string[]|null $schema
* @return list<array{name: string, schema: string|null, schema_qualified_name: string, size: int|null, comment: string|null, collation: string|null, engine: string|null}>
*/
public function getTables($schema = null)
{
return $this->connection->getPostProcessor()->processTables(
$this->connection->selectFromWriteConnection($this->grammar->compileTables($schema))
);
}
/**
* Get the names of the tables that belong to the connection.
*
* @param string|string[]|null $schema
* @param bool $schemaQualified
* @return list<string>
*/
public function getTableListing($schema = null, $schemaQualified = true)
{
return array_column(
$this->getTables($schema),
$schemaQualified ? 'schema_qualified_name' : 'name'
);
}
/**
* Get the views that belong to the connection.
*
* @param string|string[]|null $schema
* @return list<array{name: string, schema: string|null, schema_qualified_name: string, definition: string}>
*/
public function getViews($schema = null)
{
return $this->connection->getPostProcessor()->processViews(
$this->connection->selectFromWriteConnection($this->grammar->compileViews($schema))
);
}
/**
* Get the user-defined types that belong to the connection.
*
* @param string|string[]|null $schema
* @return list<array{name: string, schema: string, type: string, type: string, category: string, implicit: bool}>
*/
public function getTypes($schema = null)
{
return $this->connection->getPostProcessor()->processTypes(
$this->connection->selectFromWriteConnection($this->grammar->compileTypes($schema))
);
}
/**
* Determine if the given table has a given column.
*
* @param string $table
* @param string $column
* @return bool
*/
public function hasColumn($table, $column)
{
return in_array(
strtolower($column), array_map(strtolower(...), $this->getColumnListing($table))
);
}
/**
* Determine if the given table has given columns.
*
* @param string $table
* @param array<string> $columns
* @return bool
*/
public function hasColumns($table, array $columns)
{
$tableColumns = array_map(strtolower(...), $this->getColumnListing($table));
foreach ($columns as $column) {
if (! in_array(strtolower($column), $tableColumns)) {
return false;
}
}
return true;
}
/**
* Execute a table builder callback if the given table has a given column.
*
* @param string $table
* @param string $column
* @param \Closure $callback
* @return void
*/
public function whenTableHasColumn(string $table, string $column, Closure $callback)
{
if ($this->hasColumn($table, $column)) {
$this->table($table, fn (Blueprint $table) => $callback($table));
}
}
/**
* Execute a table builder callback if the given table doesn't have a given column.
*
* @param string $table
* @param string $column
* @param \Closure $callback
* @return void
*/
public function whenTableDoesntHaveColumn(string $table, string $column, Closure $callback)
{
if (! $this->hasColumn($table, $column)) {
$this->table($table, fn (Blueprint $table) => $callback($table));
}
}
/**
* Execute a table builder callback if the given table has a given index.
*
* @param string $table
* @param string|array $index
* @param \Closure $callback
* @param string|null $type
* @return void
*/
public function whenTableHasIndex(string $table, string|array $index, Closure $callback, ?string $type = null)
{
if ($this->hasIndex($table, $index, $type)) {
$this->table($table, fn (Blueprint $table) => $callback($table));
}
}
/**
* Execute a table builder callback if the given table doesn't have a given index.
*
* @param string $table
* @param string|array $index
* @param \Closure $callback
* @param string|null $type
* @return void
*/
public function whenTableDoesntHaveIndex(string $table, string|array $index, Closure $callback, ?string $type = null)
{
if (! $this->hasIndex($table, $index, $type)) {
$this->table($table, fn (Blueprint $table) => $callback($table));
}
}
/**
* Get the data type for the given column name.
*
* @param string $table
* @param string $column
* @param bool $fullDefinition
* @return string
*
* @throws \InvalidArgumentException
*/
public function getColumnType($table, $column, $fullDefinition = false)
{
$columns = $this->getColumns($table);
foreach ($columns as $value) {
if (strtolower($value['name']) === strtolower($column)) {
return $fullDefinition ? $value['type'] : $value['type_name'];
}
}
throw new InvalidArgumentException("There is no column with name '$column' on table '$table'.");
}
/**
* Get the column listing for a given table.
*
* @param string $table
* @return list<string>
*/
public function getColumnListing($table)
{
return array_column($this->getColumns($table), 'name');
}
/**
* Get the columns for a given table.
*
* @param string $table
* @return list<array{name: string, type: string, type_name: string, nullable: bool, default: mixed, auto_increment: bool, comment: string|null, generation: array{type: string, expression: string|null}|null}>
*/
public function getColumns($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);
$table = $this->connection->getTablePrefix().$table;
return $this->connection->getPostProcessor()->processColumns(
$this->connection->selectFromWriteConnection(
$this->grammar->compileColumns($schema, $table)
)
);
}
/**
* Get the indexes for a given table.
*
* @param string $table
* @return list<array{name: string, columns: list<string>, type: string, unique: bool, primary: bool}>
*/
public function getIndexes($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);
$table = $this->connection->getTablePrefix().$table;
return $this->connection->getPostProcessor()->processIndexes(
$this->connection->selectFromWriteConnection(
$this->grammar->compileIndexes($schema, $table)
)
);
}
/**
* Get the names of the indexes for a given table.
*
* @param string $table
* @return list<string>
*/
public function getIndexListing($table)
{
return array_column($this->getIndexes($table), 'name');
}
/**
* Determine if the given table has a given index.
*
* @param string $table
* @param string|array $index
* @param string|null $type
* @return bool
*/
public function hasIndex($table, $index, $type = null)
{
$type = is_null($type) ? $type : strtolower($type);
foreach ($this->getIndexes($table) as $value) {
$typeMatches = is_null($type)
|| ($type === 'primary' && $value['primary'])
|| ($type === 'unique' && $value['unique'])
|| $type === $value['type'];
if (($value['name'] === $index || $value['columns'] === $index) && $typeMatches) {
return true;
}
}
return false;
}
/**
* Get the foreign keys for a given table.
*
* @param string $table
* @return array
*/
public function getForeignKeys($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);
$table = $this->connection->getTablePrefix().$table;
return $this->connection->getPostProcessor()->processForeignKeys(
$this->connection->selectFromWriteConnection(
$this->grammar->compileForeignKeys($schema, $table)
)
);
}
/**
* Modify a table on the schema.
*
* @param string $table
* @param \Closure $callback
* @return void
*/
public function table($table, Closure $callback)
{
$this->build($this->createBlueprint($table, $callback));
}
/**
* Create a new table on the schema.
*
* @param string $table
* @param \Closure $callback
* @return void
*/
public function create($table, Closure $callback)
{
$this->build(tap($this->createBlueprint($table), function ($blueprint) use ($callback) {
$blueprint->create();
$callback($blueprint);
}));
}
/**
* Drop a table from the schema.
*
* @param string $table
* @return void
*/
public function drop($table)
{
$this->build(tap($this->createBlueprint($table), function ($blueprint) {
$blueprint->drop();
}));
}
/**
* Drop a table from the schema if it exists.
*
* @param string $table
* @return void
*/
public function dropIfExists($table)
{
$this->build(tap($this->createBlueprint($table), function ($blueprint) {
$blueprint->dropIfExists();
}));
}
/**
* Drop columns from a table schema.
*
* @param string $table
* @param string|array<string> $columns
* @return void
*/
public function dropColumns($table, $columns)
{
$this->table($table, function (Blueprint $blueprint) use ($columns) {
$blueprint->dropColumn($columns);
});
}
/**
* Drop all tables from the database.
*
* @return void
*
* @throws \LogicException
*/
public function dropAllTables()
{
throw new LogicException('This database driver does not support dropping all tables.');
}
/**
* Drop all views from the database.
*
* @return void
*
* @throws \LogicException
*/
public function dropAllViews()
{
throw new LogicException('This database driver does not support dropping all views.');
}
/**
* Drop all types from the database.
*
* @return void
*
* @throws \LogicException
*/
public function dropAllTypes()
{
throw new LogicException('This database driver does not support dropping all types.');
}
/**
* Rename a table on the schema.
*
* @param string $from
* @param string $to
* @return void
*/
public function rename($from, $to)
{
$this->build(tap($this->createBlueprint($from), function ($blueprint) use ($to) {
$blueprint->rename($to);
}));
}
/**
* Enable foreign key constraints.
*
* @return bool
*/
public function enableForeignKeyConstraints()
{
return $this->connection->statement(
$this->grammar->compileEnableForeignKeyConstraints()
);
}
/**
* Disable foreign key constraints.
*
* @return bool
*/
public function disableForeignKeyConstraints()
{
return $this->connection->statement(
$this->grammar->compileDisableForeignKeyConstraints()
);
}
/**
* Disable foreign key constraints during the execution of a callback.
*
* @template TReturn
*
* @param (\Closure(): TReturn) $callback
* @return TReturn
*/
public function withoutForeignKeyConstraints(Closure $callback)
{
$this->disableForeignKeyConstraints();
try {
return $callback();
} finally {
$this->enableForeignKeyConstraints();
}
}
/**
* Create the vector extension on the schema if it does not exist.
*
* @param string|null $schema
* @return void
*/
public function ensureVectorExtensionExists($schema = null)
{
$this->ensureExtensionExists('vector', $schema);
}
/**
* Create a new extension on the schema if it does not exist.
*
* @param string $name
* @param string|null $schema
* @return void
*
* @throws \RuntimeException
*/
public function ensureExtensionExists($name, $schema = null)
{
if (! $this->getConnection() instanceof PostgresConnection) {
throw new RuntimeException('Extensions are only supported by Postgres.');
}
$name = $this->getConnection()->getSchemaGrammar()->wrap($name);
$this->getConnection()->statement(match (filled($schema)) {
true => "create extension if not exists {$name} schema {$this->getConnection()->getSchemaGrammar()->wrap($schema)}",
false => "create extension if not exists {$name}",
});
}
/**
* Execute the blueprint to build / modify the table.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return void
*/
protected function build(Blueprint $blueprint)
{
$blueprint->build();
}
/**
* Create a new command set with a Closure.
*
* @param string $table
* @param \Closure|null $callback
* @return \Illuminate\Database\Schema\Blueprint
*/
protected function createBlueprint($table, ?Closure $callback = null)
{
$connection = $this->connection;
if (isset($this->resolver)) {
return call_user_func($this->resolver, $connection, $table, $callback);
}
return Container::getInstance()->make(Blueprint::class, compact('connection', 'table', 'callback'));
}
/**
* Get the names of the current schemas for the connection.
*
* @return string[]|null
*/
public function getCurrentSchemaListing()
{
return null;
}
/**
* Get the default schema name for the connection.
*
* @return string|null
*/
public function getCurrentSchemaName()
{
return $this->getCurrentSchemaListing()[0] ?? null;
}
/**
* Parse the given database object reference and extract the schema and table.
*
* @param string $reference
* @param string|bool|null $withDefaultSchema
* @return array{string|null, string}
*
* @throws \InvalidArgumentException
*/
public function parseSchemaAndTable($reference, $withDefaultSchema = null)
{
$segments = explode('.', $reference);
if (count($segments) > 2) {
throw new InvalidArgumentException(
"Using three-part references is not supported, you may use `Schema::connection('{$segments[0]}')` instead."
);
}
$table = $segments[1] ?? $segments[0];
$schema = match (true) {
isset($segments[1]) => $segments[0],
is_string($withDefaultSchema) => $withDefaultSchema,
$withDefaultSchema => $this->getCurrentSchemaName(),
default => null,
};
return [$schema, $table];
}
/**
* Get the database connection instance.
*
* @return \Illuminate\Database\Connection
*/
public function getConnection()
{
return $this->connection;
}
/**
* Set the Schema Blueprint resolver callback.
*
* @param \Closure(\Illuminate\Database\Connection, string, \Closure|null): \Illuminate\Database\Schema\Blueprint $resolver
* @return void
*/
public function blueprintResolver(Closure $resolver)
{
$this->resolver = $resolver;
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Support\Fluent;
/**
* @method $this after(string $column) Place the column "after" another column (MySQL)
* @method $this always(bool $value = true) Used as a modifier for generatedAs() (PostgreSQL)
* @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 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
* @method $this first() Place the column "first" in the table (MySQL)
* @method $this from(int $startingValue) Set the starting value of an auto-incrementing field (MySQL/PostgreSQL)
* @method $this fulltext(bool|string $indexName = null) Add a fulltext index
* @method $this generatedAs(string|\Illuminate\Contracts\Database\Query\Expression $expression = null) Create a SQL compliant identity column (PostgreSQL)
* @method $this instant() Specify that algorithm=instant should be used for the column operation (MySQL)
* @method $this index(bool|string $indexName = null) Add an index
* @method $this invisible() Specify that the column should be invisible to "SELECT *" (MySQL)
* @method $this lock(('none'|'shared'|'default'|'exclusive') $value) Specify the DDL lock mode for the column operation (MySQL)
* @method $this nullable(bool $value = true) Allow NULL values to be inserted into the column
* @method $this persisted() Mark the computed generated column as persistent (SQL Server)
* @method $this primary(bool $value = true) Add a primary index
* @method $this spatialIndex(bool|string $indexName = null) Add a spatial index
* @method $this vectorIndex(bool|string $indexName = null) Add a vector index
* @method $this startingValue(int $startingValue) Set the starting value of an auto-incrementing field (MySQL/PostgreSQL)
* @method $this storedAs(string|\Illuminate\Contracts\Database\Query\Expression $expression) Create a stored generated column (MySQL/PostgreSQL/SQLite)
* @method $this type(string $type) Specify a type for the column
* @method $this unique(bool|string $indexName = null) Add a unique index
* @method $this unsigned() Set the INTEGER column as UNSIGNED (MySQL)
* @method $this useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value
* @method $this useCurrentOnUpdate() Set the TIMESTAMP column to use CURRENT_TIMESTAMP when updating (MySQL)
* @method $this virtualAs(string|\Illuminate\Contracts\Database\Query\Expression $expression) Create a virtual generated column (MySQL/PostgreSQL/SQLite)
*/
class ColumnDefinition extends Fluent
{
//
}
@@ -0,0 +1,56 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Support\Stringable;
class ForeignIdColumnDefinition extends ColumnDefinition
{
/**
* The schema builder blueprint instance.
*
* @var \Illuminate\Database\Schema\Blueprint
*/
protected $blueprint;
/**
* Create a new foreign ID column definition.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param array $attributes
*/
public function __construct(Blueprint $blueprint, $attributes = [])
{
parent::__construct($attributes);
$this->blueprint = $blueprint;
}
/**
* Create a foreign key constraint on this column referencing the "id" column of the conventionally related table.
*
* @param string|null $table
* @param string|null $column
* @param string|null $indexName
* @return \Illuminate\Database\Schema\ForeignKeyDefinition
*/
public function constrained($table = null, $column = null, $indexName = null)
{
$table ??= $this->table;
$column ??= $this->referencesModelColumn ?? 'id';
return $this->references($column, $indexName)->on($table ?? (new Stringable($this->name))->beforeLast('_'.$column)->plural());
}
/**
* Specify which column this foreign ID references on another table.
*
* @param string $column
* @param string|null $indexName
* @return \Illuminate\Database\Schema\ForeignKeyDefinition
*/
public function references($column, $indexName = null)
{
return $this->blueprint->foreign($this->name, $indexName)->references($column);
}
}
@@ -0,0 +1,97 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Support\Fluent;
/**
* @method ForeignKeyDefinition deferrable(bool $value = true) Set the foreign key as deferrable (PostgreSQL)
* @method ForeignKeyDefinition initiallyImmediate(bool $value = true) Set the default time to check the constraint (PostgreSQL)
* @method ForeignKeyDefinition lock(('none'|'shared'|'default'|'exclusive') $value) Specify the DDL lock mode for the foreign key operation (MySQL)
* @method ForeignKeyDefinition on(string $table) Specify the referenced table
* @method ForeignKeyDefinition onDelete(('cascade'|'restrict'|'set null'|'no action') $action) Add an ON DELETE action
* @method ForeignKeyDefinition onUpdate(('cascade'|'restrict'|'set null'|'no action') $action) Add an ON UPDATE action
* @method ForeignKeyDefinition references(string|string[] $columns) Specify the referenced column(s)
*/
class ForeignKeyDefinition extends Fluent
{
/**
* Indicate that updates should cascade.
*
* @return $this
*/
public function cascadeOnUpdate()
{
return $this->onUpdate('cascade');
}
/**
* Indicate that updates should be restricted.
*
* @return $this
*/
public function restrictOnUpdate()
{
return $this->onUpdate('restrict');
}
/**
* Indicate that updates should set the foreign key value to null.
*
* @return $this
*/
public function nullOnUpdate()
{
return $this->onUpdate('set null');
}
/**
* Indicate that updates should have "no action".
*
* @return $this
*/
public function noActionOnUpdate()
{
return $this->onUpdate('no action');
}
/**
* Indicate that deletes should cascade.
*
* @return $this
*/
public function cascadeOnDelete()
{
return $this->onDelete('cascade');
}
/**
* Indicate that deletes should be restricted.
*
* @return $this
*/
public function restrictOnDelete()
{
return $this->onDelete('restrict');
}
/**
* Indicate that deletes should set the foreign key value to null.
*
* @return $this
*/
public function nullOnDelete()
{
return $this->onDelete('set null');
}
/**
* Indicate that deletes should have "no action".
*
* @return $this
*/
public function noActionOnDelete()
{
return $this->onDelete('no action');
}
}
+541
View File
@@ -0,0 +1,541 @@
<?php
namespace Illuminate\Database\Schema\Grammars;
use Illuminate\Contracts\Database\Query\Expression;
use Illuminate\Database\Concerns\CompilesJsonPaths;
use Illuminate\Database\Grammar as BaseGrammar;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Fluent;
use RuntimeException;
use UnitEnum;
use function Illuminate\Support\enum_value;
abstract class Grammar extends BaseGrammar
{
use CompilesJsonPaths;
/**
* The possible column modifiers.
*
* @var string[]
*/
protected $modifiers = [];
/**
* If this Grammar supports schema changes wrapped in a transaction.
*
* @var bool
*/
protected $transactions = false;
/**
* The commands to be executed outside of create or alter command.
*
* @var array
*/
protected $fluentCommands = [];
/**
* Compile a create database command.
*
* @param string $name
* @return string
*/
public function compileCreateDatabase($name)
{
return sprintf('create database %s',
$this->wrapValue($name),
);
}
/**
* Compile a drop database if exists command.
*
* @param string $name
* @return string
*/
public function compileDropDatabaseIfExists($name)
{
return sprintf('drop database if exists %s',
$this->wrapValue($name)
);
}
/**
* Compile the query to determine the schemas.
*
* @return string
*
* @throws \RuntimeException
*/
public function compileSchemas()
{
throw new RuntimeException('This database driver does not support retrieving schemas.');
}
/**
* Compile the query to determine if the given table exists.
*
* @param string|null $schema
* @param string $table
* @return string|null
*/
public function compileTableExists($schema, $table)
{
//
}
/**
* Compile the query to determine the tables.
*
* @param string|string[]|null $schema
* @return string
*
* @throws \RuntimeException
*/
public function compileTables($schema)
{
throw new RuntimeException('This database driver does not support retrieving tables.');
}
/**
* Compile the query to determine the views.
*
* @param string|string[]|null $schema
* @return string
*
* @throws \RuntimeException
*/
public function compileViews($schema)
{
throw new RuntimeException('This database driver does not support retrieving views.');
}
/**
* Compile the query to determine the user-defined types.
*
* @param string|string[]|null $schema
* @return string
*
* @throws \RuntimeException
*/
public function compileTypes($schema)
{
throw new RuntimeException('This database driver does not support retrieving user-defined types.');
}
/**
* Compile the query to determine the columns.
*
* @param string|null $schema
* @param string $table
* @return string
*
* @throws \RuntimeException
*/
public function compileColumns($schema, $table)
{
throw new RuntimeException('This database driver does not support retrieving columns.');
}
/**
* Compile the query to determine the indexes.
*
* @param string|null $schema
* @param string $table
* @return string
*
* @throws \RuntimeException
*/
public function compileIndexes($schema, $table)
{
throw new RuntimeException('This database driver does not support retrieving indexes.');
}
/**
* Compile a vector index key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return void
*
* @throws \RuntimeException
*/
public function compileVectorIndex(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('The database driver in use does not support vector indexes.');
}
/**
* Compile the query to determine the foreign keys.
*
* @param string|null $schema
* @param string $table
* @return string
*
* @throws \RuntimeException
*/
public function compileForeignKeys($schema, $table)
{
throw new RuntimeException('This database driver does not support retrieving foreign keys.');
}
/**
* Compile a rename column command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return list<string>|string
*/
public function compileRenameColumn(Blueprint $blueprint, Fluent $command)
{
return sprintf('alter table %s rename column %s to %s',
$this->wrapTable($blueprint),
$this->wrap($command->from),
$this->wrap($command->to)
);
}
/**
* Compile a change column command into a series of SQL statements.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return list<string>|string
*
* @throws \RuntimeException
*/
public function compileChange(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support modifying columns.');
}
/**
* Compile a fulltext index key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*
* @throws \RuntimeException
*/
public function compileFulltext(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support fulltext index creation.');
}
/**
* Compile a drop fulltext index command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*
* @throws \RuntimeException
*/
public function compileDropFullText(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support fulltext index removal.');
}
/**
* Compile a foreign key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*/
public function compileForeign(Blueprint $blueprint, Fluent $command)
{
// We need to prepare several of the elements of the foreign key definition
// before we can create the SQL, such as wrapping the tables and convert
// an array of columns to comma-delimited strings for the SQL queries.
$sql = sprintf('alter table %s add constraint %s ',
$this->wrapTable($blueprint),
$this->wrap($command->index)
);
// Once we have the initial portion of the SQL statement we will add on the
// key name, table name, and referenced columns. These will complete the
// main portion of the SQL statement and this SQL will almost be done.
$sql .= sprintf('foreign key (%s) references %s (%s)',
$this->columnize($command->columns),
$this->wrapTable($command->on),
$this->columnize((array) $command->references)
);
// Once we have the basic foreign key creation statement constructed we can
// build out the syntax for what should happen on an update or delete of
// the affected columns, which will get something like "cascade", etc.
if (! is_null($command->onDelete)) {
$sql .= " on delete {$command->onDelete}";
}
if (! is_null($command->onUpdate)) {
$sql .= " on update {$command->onUpdate}";
}
return $sql;
}
/**
* Compile a drop foreign key command.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $command
* @return string
*
* @throws \RuntimeException
*/
public function compileDropForeign(Blueprint $blueprint, Fluent $command)
{
throw new RuntimeException('This database driver does not support dropping foreign keys.');
}
/**
* Compile the blueprint's added column definitions.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @return array
*/
protected function getColumns(Blueprint $blueprint)
{
$columns = [];
foreach ($blueprint->getAddedColumns() as $column) {
$columns[] = $this->getColumn($blueprint, $column);
}
return $columns;
}
/**
* Compile the column definition.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Database\Schema\ColumnDefinition $column
* @return string
*/
protected function getColumn(Blueprint $blueprint, $column)
{
// Each of the column types has their own compiler functions, which are tasked
// with turning the column definition into its SQL format for this platform
// used by the connection. The column's modifiers are compiled and added.
$sql = $this->wrap($column).' '.$this->getType($column);
return $this->addModifiers($sql, $blueprint, $column);
}
/**
* Get the SQL for the column data type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function getType(Fluent $column)
{
return $this->{'type'.ucfirst($column->type)}($column);
}
/**
* Create the column definition for a generated, computed column type.
*
* @param \Illuminate\Support\Fluent $column
* @return void
*
* @throws \RuntimeException
*/
protected function typeComputed(Fluent $column)
{
throw new RuntimeException('This database driver does not support the computed type.');
}
/**
* Create the column definition for a vector type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*
* @throws \RuntimeException
*/
protected function typeVector(Fluent $column)
{
throw new RuntimeException('This database driver does not support the vector type.');
}
/**
* Create the column definition for a tsvector type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*
* @throws \RuntimeException
*/
protected function typeTsvector(Fluent $column)
{
throw new RuntimeException('This database driver does not support the tsvector type.');
}
/**
* Create the column definition for a raw column type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function typeRaw(Fluent $column)
{
return $column->offsetGet('definition');
}
/**
* Add the column modifiers to the definition.
*
* @param string $sql
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function addModifiers($sql, Blueprint $blueprint, Fluent $column)
{
foreach ($this->modifiers as $modifier) {
if (method_exists($this, $method = "modify{$modifier}")) {
$sql .= $this->{$method}($blueprint, $column);
}
}
return $sql;
}
/**
* Get the command with a given name if it exists on the blueprint.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param string $name
* @return \Illuminate\Support\Fluent|null
*/
protected function getCommandByName(Blueprint $blueprint, $name)
{
$commands = $this->getCommandsByName($blueprint, $name);
if (count($commands) > 0) {
return array_first($commands);
}
}
/**
* Get all of the commands with a given name.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param string $name
* @return array
*/
protected function getCommandsByName(Blueprint $blueprint, $name)
{
return array_filter($blueprint->getCommands(), function ($value) use ($name) {
return $value->name == $name;
});
}
/*
* Determine if a command with a given name exists on the blueprint.
*
* @param \Illuminate\Database\Schema\Blueprint $blueprint
* @param string $name
* @return bool
*/
protected function hasCommand(Blueprint $blueprint, $name)
{
foreach ($blueprint->getCommands() as $command) {
if ($command->name === $name) {
return true;
}
}
return false;
}
/**
* Add a prefix to an array of values.
*
* @param string $prefix
* @param array<string> $values
* @return array<string>
*/
public function prefixArray($prefix, array $values)
{
return array_map(function ($value) use ($prefix) {
return $prefix.' '.$value;
}, $values);
}
/**
* Wrap a table in keyword identifiers.
*
* @param mixed $table
* @param string|null $prefix
* @return string
*/
public function wrapTable($table, $prefix = null)
{
return parent::wrapTable(
$table instanceof Blueprint ? $table->getTable() : $table,
$prefix
);
}
/**
* Wrap a value in keyword identifiers.
*
* @param \Illuminate\Support\Fluent|\Illuminate\Contracts\Database\Query\Expression|string $value
* @return string
*/
public function wrap($value)
{
return parent::wrap(
$value instanceof Fluent ? $value->name : $value,
);
}
/**
* Format a value so that it can be used in "default" clauses.
*
* @param mixed $value
* @return string
*/
protected function getDefaultValue($value)
{
if ($value instanceof Expression) {
return $this->getValue($value);
}
if ($value instanceof UnitEnum) {
return "'".str_replace("'", "''", enum_value($value))."'";
}
return is_bool($value)
? "'".(int) $value."'"
: "'".str_replace("'", "''", $value)."'";
}
/**
* Get the fluent commands for the grammar.
*
* @return array
*/
public function getFluentCommands()
{
return $this->fluentCommands;
}
/**
* Check if this Grammar supports schema changes wrapped in a transaction.
*
* @return bool
*/
public function supportsSchemaTransactions()
{
return $this->transactions;
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace Illuminate\Database\Schema\Grammars;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Fluent;
class MariaDbGrammar extends MySqlGrammar
{
/** @inheritDoc */
public function compileRenameColumn(Blueprint $blueprint, Fluent $command)
{
if (version_compare($this->connection->getServerVersion(), '10.5.2', '<')) {
return $this->compileLegacyRenameColumn($blueprint, $command);
}
return parent::compileRenameColumn($blueprint, $command);
}
/**
* Create the column definition for a uuid type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function typeUuid(Fluent $column)
{
if (version_compare($this->connection->getServerVersion(), '10.7.0', '<')) {
return 'char(36)';
}
return 'uuid';
}
/**
* Create the column definition for a spatial Geometry type.
*
* @param \Illuminate\Support\Fluent $column
* @return string
*/
protected function typeGeometry(Fluent $column)
{
$subtype = $column->subtype ? strtolower($column->subtype) : null;
if (! in_array($subtype, ['point', 'linestring', 'polygon', 'geometrycollection', 'multipoint', 'multilinestring', 'multipolygon'])) {
$subtype = null;
}
return sprintf('%s%s',
$subtype ?? 'geometry',
$column->srid ? ' ref_system_id='.$column->srid : ''
);
}
/**
* Wrap the given JSON selector.
*
* @param string $value
* @return string
*/
protected function wrapJsonSelector($value)
{
[$field, $path] = $this->wrapJsonFieldAndPath($value);
return 'json_value('.$field.$path.')';
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Support\Fluent;
/**
* @method $this algorithm(string $algorithm) Specify an algorithm for the index (MySQL/PostgreSQL)
* @method $this deferrable(bool $value = true) Specify that the unique index is deferrable (PostgreSQL)
* @method $this initiallyImmediate(bool $value = true) Specify the default time to check the unique index constraint (PostgreSQL)
* @method $this language(string $language) Specify a language for the full text index (PostgreSQL)
* @method $this lock(('none'|'shared'|'default'|'exclusive') $value) Specify the DDL lock mode for the index operation (MySQL)
* @method $this nullsNotDistinct(bool $value = true) Specify that the null values should not be treated as distinct (PostgreSQL)
* @method $this online(bool $value = true) Specify that index creation should not lock the table (PostgreSQL/SqlServer)
*/
class IndexDefinition extends Fluent
{
//
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace Illuminate\Database\Schema;
class MariaDbBuilder extends MySqlBuilder
{
//
}
@@ -0,0 +1,66 @@
<?php
namespace Illuminate\Database\Schema;
use Symfony\Component\Process\Exception\ProcessFailedException;
class MariaDbSchemaState extends MySqlSchemaState
{
/**
* Load the given schema file into the database.
*
* @param string $path
* @return void
*/
public function load($path)
{
$versionInfo = $this->detectClientVersion();
$command = 'mariadb '.$this->connectionString($versionInfo).' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"';
$process = $this->makeProcess($command)->setTimeout(null);
$process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
}
/**
* Get the base dump command arguments for MariaDB as a string.
*
* @return string
*/
protected function baseDumpCommand()
{
$versionInfo = $this->detectClientVersion();
$command = 'mariadb-dump '.$this->connectionString($versionInfo).' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc';
return $command.' "${:LARAVEL_LOAD_DATABASE}"';
}
/**
* Detect the MariaDB client version.
*
* @return array{version: string, isMariaDb: bool}
*/
protected function detectClientVersion(): array
{
// Minimum version of MariaDB that supports the mariadb command...
$version = '10.5.2';
try {
$versionOutput = $this->makeProcess('mariadb --version')->mustRun()->getOutput();
if (preg_match('/(\d+\.\d+\.\d+)/', $versionOutput, $matches)) {
$version = $matches[1];
}
} catch (ProcessFailedException) {
}
return [
'isMariaDb' => true,
'version' => $version,
];
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace Illuminate\Database\Schema;
class MySqlBuilder extends Builder
{
/**
* Drop all tables from the database.
*
* @return void
*/
public function dropAllTables()
{
$tables = $this->getTableListing($this->getCurrentSchemaListing());
if (empty($tables)) {
return;
}
$this->disableForeignKeyConstraints();
try {
$this->connection->statement(
$this->grammar->compileDropAllTables($tables)
);
} finally {
$this->enableForeignKeyConstraints();
}
}
/**
* Drop all views from the database.
*
* @return void
*/
public function dropAllViews()
{
$views = array_column($this->getViews($this->getCurrentSchemaListing()), 'schema_qualified_name');
if (empty($views)) {
return;
}
$this->connection->statement(
$this->grammar->compileDropAllViews($views)
);
}
/**
* Get the names of current schemas for the connection.
*
* @return string[]|null
*/
public function getCurrentSchemaListing()
{
return [$this->connection->getDatabaseName()];
}
}
+231
View File
@@ -0,0 +1,231 @@
<?php
namespace Illuminate\Database\Schema;
use Exception;
use Illuminate\Database\Connection;
use Illuminate\Support\Str;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class MySqlSchemaState extends SchemaState
{
/**
* Dump the database's schema into a file.
*
* @param \Illuminate\Database\Connection $connection
* @param string $path
* @return void
*/
public function dump(Connection $connection, $path)
{
$this->executeDumpProcess($this->makeProcess(
$this->baseDumpCommand().' --routines --result-file="${:LARAVEL_LOAD_PATH}" --no-data'
), $this->output, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
$this->removeAutoIncrementingState($path);
if ($this->hasMigrationTable()) {
$this->appendMigrationData($path);
}
}
/**
* Remove the auto-incrementing state from the given schema dump.
*
* @param string $path
* @return void
*/
protected function removeAutoIncrementingState(string $path)
{
$this->files->put($path, preg_replace(
'/\s+AUTO_INCREMENT=[0-9]+/iu',
'',
$this->files->get($path)
));
}
/**
* Append the migration data to the schema dump.
*
* @param string $path
* @return void
*/
protected function appendMigrationData(string $path)
{
$process = $this->executeDumpProcess($this->makeProcess(
$this->baseDumpCommand().' '.$this->getMigrationTable().' --no-create-info --skip-extended-insert --skip-routines --compact --complete-insert'
), null, array_merge($this->baseVariables($this->connection->getConfig()), [
//
]));
$this->files->append($path, $process->getOutput());
}
/**
* Load the given schema file into the database.
*
* @param string $path
* @return void
*/
public function load($path)
{
$versionInfo = $this->detectClientVersion();
$command = 'mysql '.$this->connectionString($versionInfo).' --database="${:LARAVEL_LOAD_DATABASE}" < "${:LARAVEL_LOAD_PATH}"';
$process = $this->makeProcess($command)->setTimeout(null);
$process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
}
/**
* Get the base dump command arguments for MySQL as a string.
*
* @return string
*/
protected function baseDumpCommand()
{
$versionInfo = $this->detectClientVersion();
$command = 'mysqldump '.$this->connectionString($versionInfo).' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0';
if (! $this->connection->isMaria()) {
$command .= ' --set-gtid-purged=OFF';
}
return $command.' "${:LARAVEL_LOAD_DATABASE}"';
}
/**
* Generate a basic connection string (--socket, --host, --port, --user, --password) for the database.
*
* @param array{version: string, isMariaDb: bool} $versionInfo
* @return string
*/
protected function connectionString(array $versionInfo)
{
$value = ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}"';
$config = $this->connection->getConfig();
$value .= $config['unix_socket'] ?? false
? ' --socket="${:LARAVEL_LOAD_SOCKET}"'
: ' --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"';
if (isset($config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA])) {
$value .= ' --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"';
}
if (isset($config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CERT : \PDO::MYSQL_ATTR_SSL_CERT])) {
$value .= ' --ssl-cert="${:LARAVEL_LOAD_SSL_CERT}"';
}
if (isset($config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_KEY : \PDO::MYSQL_ATTR_SSL_KEY])) {
$value .= ' --ssl-key="${:LARAVEL_LOAD_SSL_KEY}"';
}
$verifyCertOption = PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_VERIFY_SERVER_CERT : \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT;
if (isset($config['options'][$verifyCertOption]) && $config['options'][$verifyCertOption] === false) {
if (version_compare($versionInfo['version'], '5.7.11', '>=') && ! $versionInfo['isMariaDb']) {
$value .= ' --ssl-mode=DISABLED';
} else {
$value .= ' --ssl=off';
}
}
return $value;
}
/**
* Get the base variables for a dump / load command.
*
* @param array $config
* @return array
*/
protected function baseVariables(array $config)
{
$config['host'] ??= '';
return [
'LARAVEL_LOAD_SOCKET' => $config['unix_socket'] ?? '',
'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'],
'LARAVEL_LOAD_PORT' => $config['port'] ?? '',
'LARAVEL_LOAD_USER' => $config['username'],
'LARAVEL_LOAD_PASSWORD' => $config['password'] ?? '',
'LARAVEL_LOAD_DATABASE' => $config['database'],
'LARAVEL_LOAD_SSL_CA' => $config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA] ?? '',
'LARAVEL_LOAD_SSL_CERT' => $config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CERT : \PDO::MYSQL_ATTR_SSL_CERT] ?? '',
'LARAVEL_LOAD_SSL_KEY' => $config['options'][PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_KEY : \PDO::MYSQL_ATTR_SSL_KEY] ?? '',
];
}
/**
* Execute the given dump process.
*
* @param \Symfony\Component\Process\Process $process
* @param callable $output
* @param array $variables
* @param int $depth
* @return \Symfony\Component\Process\Process
*
* @throws \Throwable
*/
protected function executeDumpProcess(Process $process, $output, array $variables, int $depth = 0)
{
if ($depth > 30) {
throw new Exception('Dump execution exceeded maximum depth of 30.');
}
try {
$process->setTimeout(null)->mustRun($output, $variables);
} catch (Exception $e) {
if (Str::contains($e->getMessage(), ['column-statistics', 'column_statistics'])) {
return $this->executeDumpProcess(Process::fromShellCommandLine(
str_replace(' --column-statistics=0', '', $process->getCommandLine())
), $output, $variables, $depth + 1);
}
if (str_contains($e->getMessage(), 'set-gtid-purged')) {
return $this->executeDumpProcess(Process::fromShellCommandLine(
str_replace(' --set-gtid-purged=OFF', '', $process->getCommandLine())
), $output, $variables, $depth + 1);
}
throw $e;
}
return $process;
}
/**
* Detect the MySQL client version.
*
* @return array{version: string, isMariaDb: bool}
*/
protected function detectClientVersion(): array
{
[$version, $isMariaDb] = ['8.0.0', false];
try {
$versionOutput = $this->makeProcess('mysql --version')->mustRun()->getOutput();
if (preg_match('/(\d+\.\d+\.\d+)/', $versionOutput, $matches)) {
$version = $matches[1];
}
$isMariaDb = stripos($versionOutput, 'mariadb') !== false;
} catch (ProcessFailedException) {
}
return [
'version' => $version,
'isMariaDb' => $isMariaDb,
];
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\Concerns\ParsesSearchPath;
class PostgresBuilder extends Builder
{
use ParsesSearchPath;
/**
* Drop all tables from the database.
*
* @return void
*/
public function dropAllTables()
{
$tables = [];
$excludedTables = $this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys'];
foreach ($this->getTables($this->getCurrentSchemaListing()) as $table) {
if (empty(array_intersect([$table['name'], $table['schema_qualified_name']], $excludedTables))) {
$tables[] = $table['schema_qualified_name'];
}
}
if (empty($tables)) {
return;
}
$this->connection->statement(
$this->grammar->compileDropAllTables($tables)
);
}
/**
* Drop all views from the database.
*
* @return void
*/
public function dropAllViews()
{
$views = array_column($this->getViews($this->getCurrentSchemaListing()), 'schema_qualified_name');
if (empty($views)) {
return;
}
$this->connection->statement(
$this->grammar->compileDropAllViews($views)
);
}
/**
* Drop all types from the database.
*
* @return void
*/
public function dropAllTypes()
{
$types = [];
$domains = [];
foreach ($this->getTypes($this->getCurrentSchemaListing()) as $type) {
if (! $type['implicit']) {
if ($type['type'] === 'domain') {
$domains[] = $type['schema_qualified_name'];
} else {
$types[] = $type['schema_qualified_name'];
}
}
}
if (! empty($types)) {
$this->connection->statement($this->grammar->compileDropAllTypes($types));
}
if (! empty($domains)) {
$this->connection->statement($this->grammar->compileDropAllDomains($domains));
}
}
/**
* Get the current schemas for the connection.
*
* @return string[]
*/
public function getCurrentSchemaListing()
{
return array_map(
fn ($schema) => $schema === '$user' ? $this->connection->getConfig('username') : $schema,
$this->parseSearchPath(
$this->connection->getConfig('search_path')
?: $this->connection->getConfig('schema')
?: 'public'
)
);
}
}
@@ -0,0 +1,95 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\Connection;
use Illuminate\Support\Collection;
class PostgresSchemaState extends SchemaState
{
/**
* Dump the database's schema into a file.
*
* @param \Illuminate\Database\Connection $connection
* @param string $path
* @return void
*/
public function dump(Connection $connection, $path)
{
$commands = new Collection([
$this->baseDumpCommand().' --schema-only > '.$path,
]);
if ($this->hasMigrationTable()) {
$commands->push($this->baseDumpCommand().' -t '.$this->getMigrationTable().' --data-only >> '.$path);
}
$commands->map(function ($command, $path) {
$this->makeProcess($command)->mustRun($this->output, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
});
}
/**
* Load the given schema file into the database.
*
* @param string $path
* @return void
*/
public function load($path)
{
$command = 'pg_restore --no-owner --no-acl --clean --if-exists --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}" "${:LARAVEL_LOAD_PATH}"';
if (str_ends_with($path, '.sql')) {
$command = 'psql --file="${:LARAVEL_LOAD_PATH}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"';
}
$process = $this->makeProcess($command);
$process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
}
/**
* Get the name of the application's migration table.
*
* @return string
*/
protected function getMigrationTable(): string
{
[$schema, $table] = $this->connection->getSchemaBuilder()->parseSchemaAndTable($this->migrationTable, withDefaultSchema: true);
return $schema.'.'.$this->connection->getTablePrefix().$table;
}
/**
* Get the base dump command arguments for PostgreSQL as a string.
*
* @return string
*/
protected function baseDumpCommand()
{
return 'pg_dump --no-owner --no-acl --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --username="${:LARAVEL_LOAD_USER}" --dbname="${:LARAVEL_LOAD_DATABASE}"';
}
/**
* Get the base variables for a dump / load command.
*
* @param array $config
* @return array
*/
protected function baseVariables(array $config)
{
$config['host'] ??= '';
return [
'LARAVEL_LOAD_HOST' => is_array($config['host']) ? $config['host'][0] : $config['host'],
'LARAVEL_LOAD_PORT' => $config['port'] ?? '',
'LARAVEL_LOAD_USER' => $config['username'],
'PGPASSWORD' => $config['password'],
'LARAVEL_LOAD_DATABASE' => $config['database'],
];
}
}
+173
View File
@@ -0,0 +1,173 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\QueryException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
class SQLiteBuilder extends Builder
{
/**
* Create a database in the schema.
*
* @param string $name
* @return bool
*/
public function createDatabase($name)
{
return File::put($name, '') !== false;
}
/**
* Drop a database from the schema if the database exists.
*
* @param string $name
* @return bool
*/
public function dropDatabaseIfExists($name)
{
return ! File::exists($name) || File::delete($name);
}
/** @inheritDoc */
public function getTables($schema = null)
{
try {
$withSize = $this->connection->scalar($this->grammar->compileDbstatExists());
} catch (QueryException) {
$withSize = false;
}
if (version_compare($this->connection->getServerVersion(), '3.37.0', '<')) {
$schema ??= array_column($this->getSchemas(), 'name');
$tables = [];
foreach (Arr::wrap($schema) as $name) {
$tables = array_merge($tables, $this->connection->selectFromWriteConnection(
$this->grammar->compileLegacyTables($name, $withSize)
));
}
return $this->connection->getPostProcessor()->processTables($tables);
}
return $this->connection->getPostProcessor()->processTables(
$this->connection->selectFromWriteConnection(
$this->grammar->compileTables($schema)
)
);
}
/** @inheritDoc */
public function getViews($schema = null)
{
$schema ??= array_column($this->getSchemas(), 'name');
$views = [];
foreach (Arr::wrap($schema) as $name) {
$views = array_merge($views, $this->connection->selectFromWriteConnection(
$this->grammar->compileViews($name)
));
}
return $this->connection->getPostProcessor()->processViews($views);
}
/** @inheritDoc */
public function getColumns($table)
{
[$schema, $table] = $this->parseSchemaAndTable($table);
$table = $this->connection->getTablePrefix().$table;
return $this->connection->getPostProcessor()->processColumns(
$this->connection->selectFromWriteConnection($this->grammar->compileColumns($schema, $table)),
$this->connection->scalar($this->grammar->compileSqlCreateStatement($schema, $table))
);
}
/**
* Drop all tables from the database.
*
* @return void
*/
public function dropAllTables()
{
foreach ($this->getCurrentSchemaListing() as $schema) {
$database = $schema === 'main'
? $this->connection->getDatabaseName()
: (array_column($this->getSchemas(), 'path', 'name')[$schema] ?: ':memory:');
if ($database !== ':memory:' &&
! str_contains($database, '?mode=memory') &&
! str_contains($database, '&mode=memory')
) {
$this->refreshDatabaseFile($database);
} else {
$this->pragma('writable_schema', 1);
$this->connection->statement($this->grammar->compileDropAllTables($schema));
$this->pragma('writable_schema', 0);
$this->connection->statement($this->grammar->compileRebuild($schema));
}
}
}
/**
* Drop all views from the database.
*
* @return void
*/
public function dropAllViews()
{
foreach ($this->getCurrentSchemaListing() as $schema) {
$this->pragma('writable_schema', 1);
$this->connection->statement($this->grammar->compileDropAllViews($schema));
$this->pragma('writable_schema', 0);
$this->connection->statement($this->grammar->compileRebuild($schema));
}
}
/**
* Get the value for the given pragma name or set the given value.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
public function pragma($key, $value = null)
{
return is_null($value)
? $this->connection->scalar($this->grammar->pragma($key))
: $this->connection->statement($this->grammar->pragma($key, $value));
}
/**
* Empty the database file.
*
* @param string|null $path
* @return void
*/
public function refreshDatabaseFile($path = null)
{
file_put_contents($path ?? $this->connection->getDatabaseName(), '');
}
/**
* Get the names of current schemas for the connection.
*
* @return string[]|null
*/
public function getCurrentSchemaListing()
{
return ['main'];
}
}
+141
View File
@@ -0,0 +1,141 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\Connection;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
abstract class SchemaState
{
/**
* The connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The filesystem instance.
*
* @var \Illuminate\Filesystem\Filesystem
*/
protected $files;
/**
* The name of the application's migration table.
*
* @var string
*/
protected $migrationTable = 'migrations';
/**
* The process factory callback.
*
* @var callable
*/
protected $processFactory;
/**
* The output callable instance.
*
* @var callable
*/
protected $output;
/**
* Create a new dumper instance.
*
* @param \Illuminate\Database\Connection $connection
* @param \Illuminate\Filesystem\Filesystem|null $files
* @param callable|null $processFactory
*/
public function __construct(Connection $connection, ?Filesystem $files = null, ?callable $processFactory = null)
{
$this->connection = $connection;
$this->files = $files ?: new Filesystem;
$this->processFactory = $processFactory ?: function (...$arguments) {
return Process::fromShellCommandline(...$arguments)->setTimeout(null);
};
$this->handleOutputUsing(function () {
//
});
}
/**
* Dump the database's schema into a file.
*
* @param \Illuminate\Database\Connection $connection
* @param string $path
* @return void
*/
abstract public function dump(Connection $connection, $path);
/**
* Load the given schema file into the database.
*
* @param string $path
* @return void
*/
abstract public function load($path);
/**
* Create a new process instance.
*
* @param mixed ...$arguments
* @return \Symfony\Component\Process\Process
*/
public function makeProcess(...$arguments)
{
return call_user_func($this->processFactory, ...$arguments);
}
/**
* Determine if the current connection has a migration table.
*
* @return bool
*/
public function hasMigrationTable(): bool
{
return $this->connection->getSchemaBuilder()->hasTable($this->migrationTable);
}
/**
* Get the name of the application's migration table.
*
* @return string
*/
protected function getMigrationTable(): string
{
return $this->connection->getTablePrefix().$this->migrationTable;
}
/**
* Specify the name of the application's migration table.
*
* @param string $table
* @return $this
*/
public function withMigrationTable(string $table)
{
$this->migrationTable = $table;
return $this;
}
/**
* Specify the callback that should be used to handle process output.
*
* @param callable $output
* @return $this
*/
public function handleOutputUsing(callable $output)
{
$this->output = $output;
return $this;
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Support\Arr;
class SqlServerBuilder extends Builder
{
/**
* Drop all tables from the database.
*
* @return void
*/
public function dropAllTables()
{
$this->connection->statement($this->grammar->compileDropAllForeignKeys());
$this->connection->statement($this->grammar->compileDropAllTables());
}
/**
* Drop all views from the database.
*
* @return void
*/
public function dropAllViews()
{
$this->connection->statement($this->grammar->compileDropAllViews());
}
/**
* Get the default schema name for the connection.
*
* @return string|null
*/
public function getCurrentSchemaName()
{
return Arr::first($this->getSchemas(), fn ($schema) => $schema['default'])['name'];
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Illuminate\Database\Schema;
use Illuminate\Database\Connection;
use Illuminate\Support\Collection;
class SqliteSchemaState extends SchemaState
{
/**
* Dump the database's schema into a file.
*
* @param \Illuminate\Database\Connection $connection
* @param string $path
* @return void
*/
public function dump(Connection $connection, $path)
{
$process = $this->makeProcess($this->baseCommand().' ".schema --indent"')
->setTimeout(null)
->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
//
]));
$migrations = preg_replace('/CREATE TABLE sqlite_.+?\);[\r\n]+/is', '', $process->getOutput());
$this->files->put($path, $migrations.PHP_EOL);
if ($this->hasMigrationTable()) {
$this->appendMigrationData($path);
}
}
/**
* Append the migration data to the schema dump.
*
* @param string $path
* @return void
*/
protected function appendMigrationData(string $path)
{
$process = $this->makeProcess(
$this->baseCommand().' ".dump \''.$this->getMigrationTable().'\'"'
)->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
//
]));
$migrations = (new Collection(preg_split("/\r\n|\n|\r/", $process->getOutput())))
->filter(fn ($line) => preg_match('/^\s*(--|INSERT\s)/iu', $line) === 1 && $line !== '')
->all();
$this->files->append($path, implode(PHP_EOL, $migrations).PHP_EOL);
}
/**
* Load the given schema file into the database.
*
* @param string $path
* @return void
*/
public function load($path)
{
$database = $this->connection->getDatabaseName();
if ($database === ':memory:' ||
str_contains($database, '?mode=memory') ||
str_contains($database, '&mode=memory')
) {
$this->connection->getPdo()->exec($this->files->get($path));
return;
}
$process = $this->makeProcess($this->baseCommand().' < "${:LARAVEL_LOAD_PATH}"');
$process->mustRun(null, array_merge($this->baseVariables($this->connection->getConfig()), [
'LARAVEL_LOAD_PATH' => $path,
]));
}
/**
* Get the base sqlite command arguments as a string.
*
* @return string
*/
protected function baseCommand()
{
return 'sqlite3 "${:LARAVEL_LOAD_DATABASE}"';
}
/**
* Get the base variables for a dump / load command.
*
* @param array $config
* @return array
*/
protected function baseVariables(array $config)
{
return [
'LARAVEL_LOAD_DATABASE' => $config['database'],
];
}
}