diff --git a/src/Illuminate/Database/Connectors/SQLiteConnector.php b/src/Illuminate/Database/Connectors/SQLiteConnector.php index 8ffdd81aa1c3..d68b42718044 100755 --- a/src/Illuminate/Database/Connectors/SQLiteConnector.php +++ b/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -11,32 +11,118 @@ class SQLiteConnector extends Connector implements ConnectorInterface * * @param array $config * @return \PDO - * - * @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException */ public function connect(array $config) { $options = $this->getOptions($config); + $path = $this->parseDatabasePath($config['database']); + + $connection = $this->createConnection("sqlite:{$path}", $config, $options); + + $this->configureForeignKeyConstraints($connection, $config); + $this->configureBusyTimeout($connection, $config); + $this->configureJournalMode($connection, $config); + $this->configureSynchronous($connection, $config); + + return $connection; + } + + /** + * Get the absolute database path. + * + * @param string $path + * @return string + * + * @throws \Illuminate\Database\SQLiteDatabaseDoesNotExistException + */ + protected function parseDatabasePath(string $path): string + { // SQLite supports "in-memory" databases that only last as long as the owning // connection does. These are useful for tests or for short lifetime store // querying. In-memory databases shall be anonymous (:memory:) or named. - if ($config['database'] === ':memory:' || - str_contains($config['database'], '?mode=memory') || - str_contains($config['database'], '&mode=memory') + if ($path === ':memory:' || + str_contains($path, '?mode=memory') || + str_contains($path, '&mode=memory') ) { - return $this->createConnection('sqlite:'.$config['database'], $config, $options); + return $path; } - $path = realpath($config['database']) ?: realpath(base_path($config['database'])); + $path = realpath($path) ?: realpath(base_path($path)); // Here we'll verify that the SQLite database exists before going any further // as the developer probably wants to know if the database exists and this // SQLite driver will not throw any exception if it does not by default. if ($path === false) { - throw new SQLiteDatabaseDoesNotExistException($config['database']); + throw new SQLiteDatabaseDoesNotExistException($path); + } + + return $path; + } + + /** + * Enable or disable foreign key constraints if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureForeignKeyConstraints($connection, array $config): void + { + if (! isset($config['foreign_key_constraints'])) { + return; + } + + $foreignKeys = $config['foreign_key_constraints'] ? 1 : 0; + + $connection->prepare("pragma foreign_keys = {$foreignKeys}")->execute(); + } + + /** + * Set the busy timeout if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureBusyTimeout($connection, array $config): void + { + if (! isset($config['busy_timeout'])) { + return; + } + + $connection->prepare("pragma busy_timeout = {$config['busy_timeout']}")->execute(); + } + + /** + * Set the journal mode if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureJournalMode($connection, array $config): void + { + if (! isset($config['journal_mode'])) { + return; + } + + $connection->prepare("pragma journal_mode = {$config['journal_mode']}")->execute(); + } + + /** + * Set the synchronous mode if configured. + * + * @param \PDO $connection + * @param array $config + * @return void + */ + protected function configureSynchronous($connection, array $config): void + { + if (! isset($config['synchronous'])) { + return; } - return $this->createConnection("sqlite:{$path}", $config, $options); + $connection->prepare("pragma synchronous = {$config['synchronous']}")->execute(); } } diff --git a/src/Illuminate/Database/SQLiteConnection.php b/src/Illuminate/Database/SQLiteConnection.php index 997afb3934e6..53cbfad42c9d 100755 --- a/src/Illuminate/Database/SQLiteConnection.php +++ b/src/Illuminate/Database/SQLiteConnection.php @@ -12,25 +12,6 @@ class SQLiteConnection extends Connection { - /** - * Create a new database connection instance. - * - * @param \PDO|\Closure $pdo - * @param string $database - * @param string $tablePrefix - * @param array $config - * @return void - */ - public function __construct($pdo, $database = '', $tablePrefix = '', array $config = []) - { - parent::__construct($pdo, $database, $tablePrefix, $config); - - $this->configureForeignKeyConstraints(); - $this->configureBusyTimeout(); - $this->configureJournalMode(); - $this->configureSynchronous(); - } - /** * {@inheritdoc} */ @@ -39,98 +20,6 @@ public function getDriverTitle() return 'SQLite'; } - /** - * Enable or disable foreign key constraints if configured. - * - * @return void - */ - protected function configureForeignKeyConstraints(): void - { - $enableForeignKeyConstraints = $this->getConfig('foreign_key_constraints'); - - if ($enableForeignKeyConstraints === null) { - return; - } - - $schemaBuilder = $this->getSchemaBuilder(); - - try { - $enableForeignKeyConstraints - ? $schemaBuilder->enableForeignKeyConstraints() - : $schemaBuilder->disableForeignKeyConstraints(); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the busy timeout if configured. - * - * @return void - */ - protected function configureBusyTimeout(): void - { - $milliseconds = $this->getConfig('busy_timeout'); - - if ($milliseconds === null) { - return; - } - - try { - $this->getSchemaBuilder()->setBusyTimeout($milliseconds); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the journal mode if configured. - * - * @return void - */ - protected function configureJournalMode(): void - { - $mode = $this->getConfig('journal_mode'); - - if ($mode === null) { - return; - } - - try { - $this->getSchemaBuilder()->setJournalMode($mode); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - - /** - * Set the synchronous mode if configured. - * - * @return void - */ - protected function configureSynchronous(): void - { - $mode = $this->getConfig('synchronous'); - - if ($mode === null) { - return; - } - - try { - $this->getSchemaBuilder()->setSynchronous($mode); - } catch (QueryException $e) { - if (! $e->getPrevious() instanceof SQLiteDatabaseDoesNotExistException) { - throw $e; - } - } - } - /** * Escape a binary value for safe SQL embedding. * diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 6f4b7c49217f..624d98c8b800 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -354,7 +354,7 @@ public function compileAlter(Blueprint $blueprint, Fluent $command) $table = $this->wrapTable($blueprint); $columnNames = implode(', ', $columnNames); - $foreignKeyConstraintsEnabled = $this->connection->scalar('pragma foreign_keys'); + $foreignKeyConstraintsEnabled = $this->connection->scalar($this->pragma('foreign_keys')); return array_filter(array_merge([ $foreignKeyConstraintsEnabled ? $this->compileDisableForeignKeyConstraints() : null, @@ -511,11 +511,14 @@ public function compileDropAllViews($schema = null) /** * Compile the SQL needed to rebuild the database. * + * @param string|null $schema * @return string */ - public function compileRebuild() + public function compileRebuild($schema = null) { - return 'vacuum'; + return sprintf('vacuum %s', + $this->wrapValue($schema ?? 'main') + ); } /** @@ -672,7 +675,7 @@ public function compileRenameIndex(Blueprint $blueprint, Fluent $command) */ public function compileEnableForeignKeyConstraints() { - return $this->pragma('foreign_keys', 'ON'); + return $this->pragma('foreign_keys', 1); } /** @@ -682,72 +685,22 @@ public function compileEnableForeignKeyConstraints() */ public function compileDisableForeignKeyConstraints() { - return $this->pragma('foreign_keys', 'OFF'); - } - - /** - * Compile the command to set the busy timeout. - * - * @param int $milliseconds - * @return string - */ - public function compileSetBusyTimeout($milliseconds) - { - return $this->pragma('busy_timeout', $milliseconds); - } - - /** - * Compile the command to set the journal mode. - * - * @param string $mode - * @return string - */ - public function compileSetJournalMode($mode) - { - return $this->pragma('journal_mode', $mode); - } - - /** - * Compile the command to set the synchronous mode. - * - * @param string $mode - * @return string - */ - public function compileSetSynchronous($mode) - { - return $this->pragma('synchronous', $mode); - } - - /** - * Compile the SQL needed to enable a writable schema. - * - * @return string - */ - public function compileEnableWriteableSchema() - { - return $this->pragma('writable_schema', 1); - } - - /** - * Compile the SQL needed to disable a writable schema. - * - * @return string - */ - public function compileDisableWriteableSchema() - { - return $this->pragma('writable_schema', 0); + return $this->pragma('foreign_keys', 0); } /** - * Get the SQL to set a PRAGMA value. + * Get the SQL to get or set a PRAGMA value. * - * @param string $name + * @param string $key * @param mixed $value * @return string */ - protected function pragma(string $name, mixed $value): string + public function pragma(string $key, mixed $value = null): string { - return sprintf('PRAGMA %s = %s;', $name, $value); + return sprintf('pragma %s%s', + $key, + is_null($value) ? '' : ' = '.$value + ); } /** diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 6e818c37db9c..a473768414d2 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -111,24 +111,26 @@ public function getColumns($table) */ public function dropAllTables() { - $database = $this->connection->getDatabaseName(); + 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') - ) { - return $this->refreshDatabaseFile(); - } + if ($database !== ':memory:' && + ! str_contains($database, '?mode=memory') && + ! str_contains($database, '&mode=memory') + ) { + $this->refreshDatabaseFile($database); + } else { + $this->pragma('writable_schema', 1); - $this->connection->select($this->grammar->compileEnableWriteableSchema()); + $this->connection->statement($this->grammar->compileDropAllTables($schema)); - foreach ($this->getCurrentSchemaListing() as $schema) { - $this->connection->select($this->grammar->compileDropAllTables($schema)); - } + $this->pragma('writable_schema', 0); - $this->connection->select($this->grammar->compileDisableWriteableSchema()); - - $this->connection->select($this->grammar->compileRebuild()); + $this->connection->statement($this->grammar->compileRebuild($schema)); + } + } } /** @@ -138,64 +140,40 @@ public function dropAllTables() */ public function dropAllViews() { - $this->connection->select($this->grammar->compileEnableWriteableSchema()); - foreach ($this->getCurrentSchemaListing() as $schema) { - $this->connection->select($this->grammar->compileDropAllViews($schema)); - } + $this->pragma('writable_schema', 1); - $this->connection->select($this->grammar->compileDisableWriteableSchema()); + $this->connection->statement($this->grammar->compileDropAllViews($schema)); - $this->connection->select($this->grammar->compileRebuild()); - } + $this->pragma('writable_schema', 0); - /** - * Set the busy timeout. - * - * @param int $milliseconds - * @return bool - */ - public function setBusyTimeout($milliseconds) - { - return $this->connection->statement( - $this->grammar->compileSetBusyTimeout($milliseconds) - ); - } - - /** - * Set the journal mode. - * - * @param string $mode - * @return bool - */ - public function setJournalMode($mode) - { - return $this->connection->statement( - $this->grammar->compileSetJournalMode($mode) - ); + $this->connection->statement($this->grammar->compileRebuild($schema)); + } } /** - * Set the synchronous mode. + * Get the value for the given pragma name or set the given value. * - * @param int $mode - * @return bool + * @param string $key + * @param mixed $value + * @return mixed */ - public function setSynchronous($mode) + public function pragma($key, $value = null) { - return $this->connection->statement( - $this->grammar->compileSetSynchronous($mode) - ); + 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() + public function refreshDatabaseFile($path = null) { - file_put_contents($this->connection->getDatabaseName(), ''); + file_put_contents($path ?? $this->connection->getDatabaseName(), ''); } /** diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 29a9cb9ff346..7bbc69abdbb1 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -2523,7 +2523,9 @@ public function testClone() public function testCloneModelMakesAFreshCopyOfTheModel() { - $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $query = new BaseBuilder($connection, new Grammar($connection), m::mock(Processor::class)); $builder = (new Builder($query))->setModel(new EloquentBuilderTestStub); $builder->select('*')->from('users'); diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 85c32aec98fa..6fca5a006116 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -792,18 +792,6 @@ public function testAddAndDropPrimaryOnSqlite() $this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique')); } - #[RequiresDatabase('sqlite')] - public function testSetJournalModeOnSqlite() - { - file_put_contents(DB::connection('sqlite')->getConfig('database'), ''); - - $this->assertSame('delete', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode); - - Schema::connection('sqlite')->setJournalMode('WAL'); - - $this->assertSame('wal', DB::connection('sqlite')->select('PRAGMA journal_mode')[0]->journal_mode); - } - public function testAddingMacros() { Schema::macro('foo', fn () => 'foo'); diff --git a/tests/Integration/Database/Sqlite/ConnectorTest.php b/tests/Integration/Database/Sqlite/ConnectorTest.php new file mode 100644 index 000000000000..04ead4ba1ff9 --- /dev/null +++ b/tests/Integration/Database/Sqlite/ConnectorTest.php @@ -0,0 +1,61 @@ +databasePath = database_path('secondary.sqlite')); + } + + protected function destroyDatabaseMigrations() + { + Schema::dropDatabaseIfExists($this->databasePath); + } + + public function testConnectionConfigurations() + { + $schema = DB::build([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ])->getSchemaBuilder(); + + $this->assertSame(0, $schema->pragma('foreign_keys')); + $this->assertSame(60000, $schema->pragma('busy_timeout')); + $this->assertSame('memory', $schema->pragma('journal_mode')); + $this->assertSame(2, $schema->pragma('synchronous')); + + $schema = DB::build([ + 'driver' => 'sqlite', + 'database' => $this->databasePath, + 'foreign_key_constraints' => true, + 'busy_timeout' => 12345, + 'journal_mode' => 'wal', + 'synchronous' => 'normal', + ])->getSchemaBuilder(); + + $this->assertSame(1, $schema->pragma('foreign_keys')); + $this->assertSame(12345, $schema->pragma('busy_timeout')); + $this->assertSame('wal', $schema->pragma('journal_mode')); + $this->assertSame(1, $schema->pragma('synchronous')); + + $schema->pragma('foreign_keys', 0); + $schema->pragma('busy_timeout', 54321); + $schema->pragma('journal_mode', 'delete'); + $schema->pragma('synchronous', 0); + + $this->assertSame(0, $schema->pragma('foreign_keys')); + $this->assertSame(54321, $schema->pragma('busy_timeout')); + $this->assertSame('delete', $schema->pragma('journal_mode')); + $this->assertSame(0, $schema->pragma('synchronous')); + } +}