wrap($type), $this->wrap(str_replace('.', '__', $name)) ); } /** * Compile the query to determine if the dbstat table is available. * * @return string */ public function compileDbstatExists() { return "select exists (select 1 from pragma_compile_options where compile_options = 'ENABLE_DBSTAT_VTAB') as enabled"; } /** * Compile the query to determine the tables. * * @param bool $withSize * @return string */ public function compileTables($withSize = false) { return $withSize ? 'select m.tbl_name as name, sum(s.pgsize) as size from sqlite_master as m ' .'join dbstat as s on s.name = m.name ' ."where m.type in ('table', 'index') and m.tbl_name not like 'sqlite_%' " .'group by m.tbl_name ' .'order by m.tbl_name' : "select name from sqlite_master where type = 'table' and name not like 'sqlite_%' order by name"; } /** * Compile the query to determine the views. * * @return string */ public function compileViews() { return "select name, sql as definition from sqlite_master where type = 'view' order by name"; } /** * Compile the query to determine the columns. * * @param string $table * @return string */ public function compileColumns($table) { return sprintf( 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary", hidden as "extra" ' .'from pragma_table_xinfo(%s) order by cid asc', $this->wrap(str_replace('.', '__', $table)) ); } /** * Compile the query to determine the indexes. * * @param string $table * @return string */ public function compileIndexes($table) { return sprintf( 'select "primary" as name, group_concat(col) as columns, 1 as "unique", 1 as "primary" ' .'from (select name as col from pragma_table_info(%s) where pk > 0 order by pk, cid) group by name ' .'union select name, group_concat(col) as columns, "unique", origin = "pk" as "primary" ' .'from (select il.*, ii.name as col from pragma_index_list(%s) il, pragma_index_info(il.name) ii order by il.seq, ii.seqno) ' .'group by name, "unique", "primary"', $table = $this->wrap(str_replace('.', '__', $table)), $table ); } /** * Compile the query to determine the foreign keys. * * @param string $table * @return string */ public function compileForeignKeys($table) { return sprintf( 'select group_concat("from") as columns, "table" as foreign_table, ' .'group_concat("to") as foreign_columns, on_update, on_delete ' .'from (select * from pragma_foreign_key_list(%s) order by id desc, seq) ' .'group by id, "table", on_update, on_delete', $this->wrap(str_replace('.', '__', $table)) ); } /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command) { return sprintf('%s table %s (%s%s%s)', $blueprint->temporary ? 'create temporary' : 'create', $this->wrapTable($blueprint), implode(', ', $this->getColumns($blueprint)), $this->addForeignKeys($this->getCommandsByName($blueprint, 'foreign')), $this->addPrimaryKeys($this->getCommandByName($blueprint, 'primary')) ); } /** * Get the foreign key syntax for a table creation statement. * * @param \Illuminate\Database\Schema\ForeignKeyDefinition[] $foreignKeys * @return string|null */ protected function addForeignKeys($foreignKeys) { return collect($foreignKeys)->reduce(function ($sql, $foreign) { // Once we have all the foreign key commands for the table creation statement // we'll loop through each of them and add them to the create table SQL we // are building, since SQLite needs foreign keys on the tables creation. return $sql.$this->getForeignKey($foreign); }, ''); } /** * Get the SQL for the foreign key. * * @param \Illuminate\Support\Fluent $foreign * @return string */ protected function getForeignKey($foreign) { // We need to columnize the columns that the foreign key is being defined for // so that it is a properly formatted list. Once we have done this, we can // return the foreign key SQL declaration to the calling method for use. $sql = sprintf(', foreign key(%s) references %s(%s)', $this->columnize($foreign->columns), $this->wrapTable($foreign->on), $this->columnize((array) $foreign->references) ); if (! is_null($foreign->onDelete)) { $sql .= " on delete {$foreign->onDelete}"; } // If this foreign key specifies the action to be taken on update we will add // that to the statement here. We'll append it to this SQL and then return // this SQL so we can keep adding any other foreign constraints to this. if (! is_null($foreign->onUpdate)) { $sql .= " on update {$foreign->onUpdate}"; } return $sql; } /** * Get the primary key syntax for a table creation statement. * * @param \Illuminate\Support\Fluent|null $primary * @return string|null */ protected function addPrimaryKeys($primary) { if (! is_null($primary)) { return ", primary key ({$this->columnize($primary->columns)})"; } } /** * Compile alter table commands for adding columns. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return array */ public function compileAdd(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); return collect($columns)->map(function ($column) use ($blueprint) { return 'alter table '.$this->wrapTable($blueprint).' '.$column; })->all(); } /** * Compile a change column command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array|string * * @throws \RuntimeException */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { $schema = $connection->getSchemaBuilder(); $table = $blueprint->getTable(); $changedColumns = collect($blueprint->getChangedColumns()); $columnNames = []; $autoIncrementColumn = null; $columns = collect($schema->getColumns($table)) ->map(function ($column) use ($blueprint, $changedColumns, &$columnNames, &$autoIncrementColumn) { $column = $changedColumns->first(fn ($col) => $col->name === $column['name'], $column); if ($column instanceof Fluent) { $name = $this->wrap($column); $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs) && is_null($column->storedAsJson)) { $columnNames[] = $name; } return $this->addModifiers($name.' '.$this->getType($column), $blueprint, $column); } else { $name = $this->wrap($column['name']); $autoIncrementColumn = $column['auto_increment'] ? $column['name'] : $autoIncrementColumn; $isGenerated = ! is_null($column['generation']); if (! $isGenerated) { $columnNames[] = $name; } return $this->addModifiers($name.' '.$column['type'], $blueprint, new ColumnDefinition([ 'change' => true, 'type' => $column['type_name'], 'nullable' => $column['nullable'], 'default' => $column['default'] ? new Expression($column['default']) : null, 'autoIncrement' => $column['auto_increment'], 'collation' => $column['collation'], 'comment' => $column['comment'], 'virtualAs' => $isGenerated && $column['generation']['type'] === 'virtual' ? $column['generation']['expression'] : null, 'storedAs' => $isGenerated && $column['generation']['type'] === 'stored' ? $column['generation']['expression'] : null, ]) ); } })->all(); $foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ 'columns' => $foreignKey['columns'], 'on' => $foreignKey['foreign_table'], 'references' => $foreignKey['foreign_columns'], 'onUpdate' => $foreignKey['on_update'], 'onDelete' => $foreignKey['on_delete'], ]))->all(); [$primary, $indexes] = collect($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'); $indexes = collect($indexes)->reject(fn ($index) => str_starts_with('sqlite_', $index->index))->map( fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index) )->all(); $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$table); $table = $this->wrapTable($blueprint); $columnNames = implode(', ', $columnNames); $foreignKeyConstraintsEnabled = $connection->scalar('pragma foreign_keys'); return array_filter(array_merge([ $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, sprintf('create table %s (%s%s%s)', $tempTable, implode(', ', $columns), $this->addForeignKeys($foreignKeys), $autoIncrementColumn ? '' : $this->addPrimaryKeys($primary->first()) ), sprintf('insert into %s (%s) select %s from %s', $tempTable, $columnNames, $columnNames, $table), sprintf('drop table %s', $table), sprintf('alter table %s rename to %s', $tempTable, $table), ], $indexes, [$foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null])); } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { return sprintf('create unique index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { return sprintf('create index %s on %s (%s)', $this->wrap($command->index), $this->wrapTable($blueprint), $this->columnize($command->columns) ); } /** * Compile a spatial index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return void * * @throws \RuntimeException */ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) { throw new RuntimeException('The database driver in use does not support spatial indexes.'); } /** * Compile a foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string|null */ public function compileForeign(Blueprint $blueprint, Fluent $command) { // Handled on table creation... } /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table '.$this->wrapTable($blueprint); } /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { return 'drop table if exists '.$this->wrapTable($blueprint); } /** * Compile the SQL needed to drop all tables. * * @return string */ public function compileDropAllTables() { return "delete from sqlite_master where type in ('table', 'index', 'trigger')"; } /** * Compile the SQL needed to drop all views. * * @return string */ public function compileDropAllViews() { return "delete from sqlite_master where type in ('view')"; } /** * Compile the SQL needed to rebuild the database. * * @return string */ public function compileRebuild() { return 'vacuum'; } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array */ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { $table = $this->wrapTable($blueprint); $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column)->all(); } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index}"; } /** * Compile a drop spatial index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return void * * @throws \RuntimeException */ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) { throw new RuntimeException('The database driver in use does not support spatial indexes.'); } /** * Compile a rename table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileRename(Blueprint $blueprint, Fluent $command) { $from = $this->wrapTable($blueprint); return "alter table {$from} rename to ".$this->wrapTable($command->to); } /** * Compile a rename index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection * @return array * * @throws \RuntimeException */ public function compileRenameIndex(Blueprint $blueprint, Fluent $command, Connection $connection) { $indexes = $connection->getSchemaBuilder()->getIndexes($blueprint->getTable()); $index = Arr::first($indexes, fn ($index) => $index['name'] === $command->from); if (! $index) { throw new RuntimeException("Index [{$command->from}] does not exist."); } if ($index['primary']) { throw new RuntimeException('SQLite does not support altering primary keys.'); } if ($index['unique']) { return [ $this->compileDropUnique($blueprint, new IndexDefinition(['index' => $index['name']])), $this->compileUnique($blueprint, new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) ), ]; } return [ $this->compileDropIndex($blueprint, new IndexDefinition(['index' => $index['name']])), $this->compileIndex($blueprint, new IndexDefinition(['index' => $command->to, 'columns' => $index['columns']]) ), ]; } /** * Compile the command to enable foreign key constraints. * * @return string */ public function compileEnableForeignKeyConstraints() { return 'PRAGMA foreign_keys = ON;'; } /** * Compile the command to disable foreign key constraints. * * @return string */ public function compileDisableForeignKeyConstraints() { return 'PRAGMA foreign_keys = OFF;'; } /** * Compile the SQL needed to enable a writable schema. * * @return string */ public function compileEnableWriteableSchema() { return 'PRAGMA writable_schema = 1;'; } /** * Compile the SQL needed to disable a writable schema. * * @return string */ public function compileDisableWriteableSchema() { return 'PRAGMA writable_schema = 0;'; } /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { return 'varchar'; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { return 'varchar'; } /** * Create the column definition for a tiny text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyText(Fluent $column) { return 'text'; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'text'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'text'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'text'; } /** * Create the column definition for an integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return 'integer'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return 'float'; } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { return 'double'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return 'numeric'; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'tinyint(1)'; } /** * Create the column definition for an enumeration type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { return sprintf( 'varchar check ("%s" in (%s))', $column->name, $this->quoteString($column->allowed) ); } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'text'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'text'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a date-time (with time zone) type. * * Note: "SQLite does not have a storage class set aside for storing dates and/or times." * * @link https://www.sqlite.org/datatype3.html * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return $this->typeDateTime($column); } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return 'time'; } /** * Create the column definition for a time (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return $this->typeTime($column); } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { if ($column->useCurrent) { $column->default(new Expression('CURRENT_TIMESTAMP')); } return 'datetime'; } /** * Create the column definition for a timestamp (with time zone) type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { return $this->typeTimestamp($column); } /** * Create the column definition for a year type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeYear(Fluent $column) { return $this->typeInteger($column); } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'blob'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'varchar'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'varchar'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'varchar'; } /** * Create the column definition for a spatial Geometry type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeGeometry(Fluent $column) { return 'geometry'; } /** * Create the column definition for a spatial Geography type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeGeography(Fluent $column) { return $this->typeGeometry($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 requires a type, see the virtualAs / storedAs modifiers.'); } /** * Get the SQL for a generated virtual column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) { if (! is_null($virtualAs = $column->virtualAsJson)) { if ($this->isJsonSelector($virtualAs)) { $virtualAs = $this->wrapJsonSelector($virtualAs); } return " as ({$virtualAs})"; } if (! is_null($virtualAs = $column->virtualAs)) { return " as ({$this->getValue($virtualAs)})"; } } /** * Get the SQL for a generated stored column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) { if (! is_null($storedAs = $column->storedAsJson)) { if ($this->isJsonSelector($storedAs)) { $storedAs = $this->wrapJsonSelector($storedAs); } return " as ({$storedAs}) stored"; } if (! is_null($storedAs = $column->storedAs)) { return " as ({$this->getValue($column->storedAs)}) stored"; } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs) && is_null($column->storedAsJson)) { return $column->nullable ? '' : ' not null'; } if ($column->nullable === false) { return ' not null'; } } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if (! is_null($column->default) && is_null($column->virtualAs) && is_null($column->virtualAsJson) && is_null($column->storedAs)) { return ' default '.$this->getDefaultValue($column->default); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (in_array($column->type, $this->serials) && $column->autoIncrement) { return ' primary key autoincrement'; } } /** * Get the SQL for a collation column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCollate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->collation)) { return " collate '{$column->collation}'"; } } /** * Wrap the given JSON selector. * * @param string $value * @return string */ protected function wrapJsonSelector($value) { [$field, $path] = $this->wrapJsonFieldAndPath($value); return 'json_extract('.$field.$path.')'; } }