From 1f487da9d87fed6484a07153be88e2384e929f8d Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Mon, 19 Feb 2024 14:03:33 +0100 Subject: [PATCH] Add MariaDB driver --- config/database.php | 2 +- .../Database/Connectors/ConnectionFactory.php | 3 + .../Database/Connectors/MariaDbConnector.php | 20 + .../Console/DatabaseInspectionCommand.php | 2 + src/Illuminate/Database/Console/DbCommand.php | 13 +- .../Console/Migrations/MigrateCommand.php | 2 +- src/Illuminate/Database/DatabaseManager.php | 4 +- src/Illuminate/Database/MariaDbConnection.php | 94 ++ .../Query/Grammars/MariaDbGrammar.php | 19 + .../Query/Processors/MariaDbProcessor.php | 8 + .../Schema/Grammars/MariaDbGrammar.php | 76 + .../Database/Schema/MariaDbBuilder.php | 8 + .../Database/Schema/MariaDbSchemaState.php | 18 + tests/Database/DatabaseMariaDbBuilderTest.php | 48 + .../Database/DatabaseMariaDbProcessorTest.php | 32 + .../DatabaseMariaDbQueryGrammarTest.php | 31 + .../DatabaseMariaDbSchemaBuilderTest.php | 52 + .../DatabaseMariaDbSchemaGrammarTest.php | 1467 +++++++++++++++++ .../DatabaseMariaDbSchemaStateTest.php | 87 + .../Integration/Database/DatabaseTestCase.php | 5 + ...DatabaseEloquentMariaDbIntegrationTest.php | 84 + ...seEmulatePreparesMariaDbConnectionTest.php | 21 + .../MariaDb/DatabaseMariaDbConnectionTest.php | 147 ++ ...aDbSchemaBuilderAlterTableWithEnumTest.php | 68 + .../DatabaseMariaDbSchemaBuilderTest.php | 32 + .../Database/MariaDb/EloquentCastTest.php | 237 +++ .../Database/MariaDb/EscapeTest.php | 71 + .../Database/MariaDb/FulltextTest.php | 69 + .../Database/MariaDb/MariaDbTestCase.php | 15 + .../Database/SchemaBuilderTest.php | 16 +- .../Database/TimestampTypeTest.php | 8 +- 31 files changed, 2742 insertions(+), 17 deletions(-) create mode 100755 src/Illuminate/Database/Connectors/MariaDbConnector.php create mode 100755 src/Illuminate/Database/MariaDbConnection.php create mode 100755 src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php create mode 100644 src/Illuminate/Database/Query/Processors/MariaDbProcessor.php create mode 100755 src/Illuminate/Database/Schema/Grammars/MariaDbGrammar.php create mode 100755 src/Illuminate/Database/Schema/MariaDbBuilder.php create mode 100644 src/Illuminate/Database/Schema/MariaDbSchemaState.php create mode 100644 tests/Database/DatabaseMariaDbBuilderTest.php create mode 100644 tests/Database/DatabaseMariaDbProcessorTest.php create mode 100755 tests/Database/DatabaseMariaDbQueryGrammarTest.php create mode 100755 tests/Database/DatabaseMariaDbSchemaBuilderTest.php create mode 100755 tests/Database/DatabaseMariaDbSchemaGrammarTest.php create mode 100644 tests/Database/DatabaseMariaDbSchemaStateTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseEloquentMariaDbIntegrationTest.php create mode 100755 tests/Integration/Database/MariaDb/DatabaseEmulatePreparesMariaDbConnectionTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbConnectionTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderAlterTableWithEnumTest.php create mode 100644 tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderTest.php create mode 100644 tests/Integration/Database/MariaDb/EloquentCastTest.php create mode 100644 tests/Integration/Database/MariaDb/EscapeTest.php create mode 100644 tests/Integration/Database/MariaDb/FulltextTest.php create mode 100644 tests/Integration/Database/MariaDb/MariaDbTestCase.php diff --git a/config/database.php b/config/database.php index bbd50b07880c..f29b3eae5233 100644 --- a/config/database.php +++ b/config/database.php @@ -59,7 +59,7 @@ ], 'mariadb' => [ - 'driver' => 'mysql', + 'driver' => 'mariadb', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), diff --git a/src/Illuminate/Database/Connectors/ConnectionFactory.php b/src/Illuminate/Database/Connectors/ConnectionFactory.php index 80b25d0223a6..f63123652747 100755 --- a/src/Illuminate/Database/Connectors/ConnectionFactory.php +++ b/src/Illuminate/Database/Connectors/ConnectionFactory.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Database\Connection; +use Illuminate\Database\MariaDbConnection; use Illuminate\Database\MySqlConnection; use Illuminate\Database\PostgresConnection; use Illuminate\Database\SQLiteConnection; @@ -241,6 +242,7 @@ public function createConnector(array $config) return match ($config['driver']) { 'mysql' => new MySqlConnector, + 'mariadb' => new MariaDbConnector, 'pgsql' => new PostgresConnector, 'sqlite' => new SQLiteConnector, 'sqlsrv' => new SqlServerConnector, @@ -268,6 +270,7 @@ protected function createConnection($driver, $connection, $database, $prefix = ' return match ($driver) { 'mysql' => new MySqlConnection($connection, $database, $prefix, $config), + 'mariadb' => new MariaDbConnection($connection, $database, $prefix, $config), 'pgsql' => new PostgresConnection($connection, $database, $prefix, $config), 'sqlite' => new SQLiteConnection($connection, $database, $prefix, $config), 'sqlsrv' => new SqlServerConnection($connection, $database, $prefix, $config), diff --git a/src/Illuminate/Database/Connectors/MariaDbConnector.php b/src/Illuminate/Database/Connectors/MariaDbConnector.php new file mode 100755 index 000000000000..50b8ac5d1055 --- /dev/null +++ b/src/Illuminate/Database/Connectors/MariaDbConnector.php @@ -0,0 +1,20 @@ +isMaria() => 'MariaDB', $connection instanceof MySqlConnection => 'MySQL', + $connection instanceof MariaDbConnection => 'MariaDB', $connection instanceof PostgresConnection => 'PostgreSQL', $connection instanceof SQLiteConnection => 'SQLite', $connection instanceof SqlServerConnection => 'SQL Server', diff --git a/src/Illuminate/Database/Console/DbCommand.php b/src/Illuminate/Database/Console/DbCommand.php index c9d3b5290907..c4267c6b6727 100644 --- a/src/Illuminate/Database/Console/DbCommand.php +++ b/src/Illuminate/Database/Console/DbCommand.php @@ -130,7 +130,7 @@ public function commandEnvironment(array $connection) public function getCommand(array $connection) { return [ - 'mysql' => 'mysql', + 'mysql', 'mariadb' => 'mysql', 'pgsql' => 'psql', 'sqlite' => 'sqlite3', 'sqlsrv' => 'sqlcmd', @@ -156,6 +156,17 @@ protected function getMysqlArguments(array $connection) ], $connection), [$connection['database']]); } + /** + * Get the arguments for the MariaDB CLI. + * + * @param array $connection + * @return array + */ + protected function getMariaDbArguments(array $connection) + { + return $this->getMysqlArguments($connection); + } + /** * Get the arguments for the Postgres CLI. * diff --git a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php index 58dc91a9e6e0..5f7e1c87b10f 100755 --- a/src/Illuminate/Database/Console/Migrations/MigrateCommand.php +++ b/src/Illuminate/Database/Console/Migrations/MigrateCommand.php @@ -148,7 +148,7 @@ protected function repositoryExists() if ( $e->getPrevious() instanceof PDOException && $e->getPrevious()->getCode() === 1049 && - $connection->getDriverName() === 'mysql') { + in_array($connection->getDriverName(), ['mysql', 'mariadb'])) { return $this->createMissingMysqlDatabase($connection); } diff --git a/src/Illuminate/Database/DatabaseManager.php b/src/Illuminate/Database/DatabaseManager.php index 76680ff2f2d4..9239fca06f90 100755 --- a/src/Illuminate/Database/DatabaseManager.php +++ b/src/Illuminate/Database/DatabaseManager.php @@ -361,13 +361,13 @@ public function setDefaultConnection($name) } /** - * Get all of the support drivers. + * Get all of the supported drivers. * * @return string[] */ public function supportedDrivers() { - return ['mysql', 'pgsql', 'sqlite', 'sqlsrv']; + return ['mysql', 'mariadb', 'pgsql', 'sqlite', 'sqlsrv']; } /** diff --git a/src/Illuminate/Database/MariaDbConnection.php b/src/Illuminate/Database/MariaDbConnection.php new file mode 100755 index 000000000000..58e124d50c10 --- /dev/null +++ b/src/Illuminate/Database/MariaDbConnection.php @@ -0,0 +1,94 @@ +setConnection($this); + + return $this->withTablePrefix($grammar); + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\MariaDbBuilder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { + $this->useDefaultSchemaGrammar(); + } + + return new MariaDbBuilder($this); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\MariaDbGrammar + */ + protected function getDefaultSchemaGrammar() + { + ($grammar = new SchemaGrammar)->setConnection($this); + + return $this->withTablePrefix($grammar); + } + + /** + * Get the schema state for the connection. + * + * @param \Illuminate\Filesystem\Filesystem|null $files + * @param callable|null $processFactory + * @return \Illuminate\Database\Schema\MariaDbSchemaState + */ + public function getSchemaState(Filesystem $files = null, callable $processFactory = null) + { + return new MariaDbSchemaState($this, $files, $processFactory); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\MariaDbProcessor + */ + protected function getDefaultPostProcessor() + { + return new MariaDbProcessor; + } +} diff --git a/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php b/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php new file mode 100755 index 000000000000..37d2cc015431 --- /dev/null +++ b/src/Illuminate/Database/Query/Grammars/MariaDbGrammar.php @@ -0,0 +1,19 @@ +getServerVersion(), '10.5.2', '<')) { + $column = collect($connection->getSchemaBuilder()->getColumns($blueprint->getTable())) + ->firstWhere('name', $command->from); + + $modifiers = $this->addModifiers($column['type'], $blueprint, new ColumnDefinition([ + 'change' => true, + 'type' => match ($column['type_name']) { + 'bigint' => 'bigInteger', + 'int' => 'integer', + 'mediumint' => 'mediumInteger', + 'smallint' => 'smallInteger', + 'tinyint' => 'tinyInteger', + default => $column['type_name'], + }, + 'nullable' => $column['nullable'], + 'default' => $column['default'] && str_starts_with(strtolower($column['default']), 'current_timestamp') + ? new Expression($column['default']) + : $column['default'], + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + ])); + + return sprintf('alter table %s change %s %s %s', + $this->wrapTable($blueprint), + $this->wrap($command->from), + $this->wrap($command->to), + $modifiers + ); + } + + return parent::compileRenameColumn($blueprint, $command, $connection); + } + + /** + * 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 : '' + ); + } +} diff --git a/src/Illuminate/Database/Schema/MariaDbBuilder.php b/src/Illuminate/Database/Schema/MariaDbBuilder.php new file mode 100755 index 000000000000..012befe3802e --- /dev/null +++ b/src/Illuminate/Database/Schema/MariaDbBuilder.php @@ -0,0 +1,8 @@ +connectionString().' --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc --column-statistics=0'; + + return $command.' "${:LARAVEL_LOAD_DATABASE}"'; + } +} diff --git a/tests/Database/DatabaseMariaDbBuilderTest.php b/tests/Database/DatabaseMariaDbBuilderTest.php new file mode 100644 index 000000000000..9f26f35ed6a3 --- /dev/null +++ b/tests/Database/DatabaseMariaDbBuilderTest.php @@ -0,0 +1,48 @@ +shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8mb4'); + $connection->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8mb4_unicode_ci'); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('statement')->once()->with( + 'create database `my_temporary_database` default character set `utf8mb4` default collate `utf8mb4_unicode_ci`' + )->andReturn(true); + + $builder = new MariaDbBuilder($connection); + $builder->createDatabase('my_temporary_database'); + } + + public function testDropDatabaseIfExists() + { + $grammar = new MariaDbGrammar; + + $connection = m::mock(Connection::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('statement')->once()->with( + 'drop database if exists `my_database_a`' + )->andReturn(true); + + $builder = new MariaDbBuilder($connection); + + $builder->dropDatabaseIfExists('my_database_a'); + } +} diff --git a/tests/Database/DatabaseMariaDbProcessorTest.php b/tests/Database/DatabaseMariaDbProcessorTest.php new file mode 100644 index 000000000000..ecd3cae703a8 --- /dev/null +++ b/tests/Database/DatabaseMariaDbProcessorTest.php @@ -0,0 +1,32 @@ + 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => 'YES', 'default' => '', 'extra' => 'auto_increment', 'comment' => 'bar'], + ['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => 'NO', 'default' => 'foo', 'extra' => '', 'comment' => ''], + ['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => 'YES', 'default' => 'NULL', 'extra' => 'on update CURRENT_TIMESTAMP', 'comment' => 'NULL'], + ]; + $expected = [ + ['name' => 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => true, 'default' => '', 'auto_increment' => true, 'comment' => 'bar'], + ['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => false, 'default' => 'foo', 'auto_increment' => false, 'comment' => ''], + ['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => true, 'default' => 'NULL', 'auto_increment' => false, 'comment' => 'NULL'], + ]; + $this->assertEquals($expected, $processor->processColumns($listing)); + + // convert listing to objects to simulate PDO::FETCH_CLASS + foreach ($listing as &$row) { + $row = (object) $row; + } + + $this->assertEquals($expected, $processor->processColumns($listing)); + } +} diff --git a/tests/Database/DatabaseMariaDbQueryGrammarTest.php b/tests/Database/DatabaseMariaDbQueryGrammarTest.php new file mode 100755 index 000000000000..f5a5a4dc8975 --- /dev/null +++ b/tests/Database/DatabaseMariaDbQueryGrammarTest.php @@ -0,0 +1,31 @@ +shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); + $grammar = new MariaDbGrammar; + $grammar->setConnection($connection); + + $query = $grammar->substituteBindingsIntoRawSql( + 'select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = ?', + ['foo'], + ); + + $this->assertSame('select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = \'foo\'', $query); + } +} diff --git a/tests/Database/DatabaseMariaDbSchemaBuilderTest.php b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php new file mode 100755 index 000000000000..a49345a6c14b --- /dev/null +++ b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php @@ -0,0 +1,52 @@ +shouldReceive('getDatabaseName')->andReturn('db'); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $builder = new MariaDbBuilder($connection); + $grammar->shouldReceive('compileTables')->once()->andReturn('sql'); + $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]); + $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); + $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'prefix_table']]); + + $this->assertTrue($builder->hasTable('table')); + } + + public function testGetColumnListing() + { + $connection = m::mock(Connection::class); + $grammar = m::mock(MariaDbGrammar::class); + $processor = m::mock(MariaDbProcessor::class); + $connection->shouldReceive('getDatabaseName')->andReturn('db'); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $grammar->shouldReceive('compileColumns')->with('db', 'prefix_table')->once()->andReturn('sql'); + $processor->shouldReceive('processColumns')->once()->andReturn([['name' => 'column']]); + $builder = new MariaDbBuilder($connection); + $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); + $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'column']]); + + $this->assertEquals(['column'], $builder->getColumnListing('table')); + } +} diff --git a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php new file mode 100755 index 000000000000..601e7479c568 --- /dev/null +++ b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php @@ -0,0 +1,1467 @@ +create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->increments('id'); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key, add `email` varchar(255) not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->uuid('id')->primary(); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('create table `users` (`id` char(36) not null, primary key (`id`))', $statements[0]); + } + + public function testAutoIncrementStartingValue() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id')->startingValue(1000); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(2, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); + $this->assertSame('alter table `users` auto_increment = 1000', $statements[1]); + } + + public function testAddColumnsWithMultipleAutoIncrementStartingValue() + { + $blueprint = new Blueprint('users'); + $blueprint->id()->from(100); + $blueprint->string('name')->from(200); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertEquals([ + 'alter table `users` add `id` bigint unsigned not null auto_increment primary key, add `name` varchar(255) not null', + 'alter table `users` auto_increment = 100', + ], $statements); + } + + public function testEngineCreateTable() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + $blueprint->engine('InnoDB'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn('InnoDB'); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8 collate 'utf8_unicode_ci' engine = InnoDB", $statements[0]); + } + + public function testCharsetCollationCreateTable() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + $blueprint->charset('utf8mb4'); + $blueprint->collation('utf8mb4_unicode_ci'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email')->charset('utf8mb4')->collation('utf8mb4_unicode_ci'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) character set utf8mb4 collate 'utf8mb4_unicode_ci' not null) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); + } + + public function testBasicCreateTableWithPrefix() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->increments('id'); + $blueprint->string('email'); + $grammar = $this->getGrammar(); + $grammar->setTablePrefix('prefix_'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $grammar); + + $this->assertCount(1, $statements); + $this->assertSame('create table `prefix_users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); + } + + public function testCreateTemporaryTable() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->temporary(); + $blueprint->increments('id'); + $blueprint->string('email'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('create temporary table `users` (`id` int unsigned not null auto_increment primary key, `email` varchar(255) not null)', $statements[0]); + } + + public function testDropTable() + { + $blueprint = new Blueprint('users'); + $blueprint->drop(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('drop table `users`', $statements[0]); + } + + public function testDropTableIfExists() + { + $blueprint = new Blueprint('users'); + $blueprint->dropIfExists(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('drop table if exists `users`', $statements[0]); + } + + public function testDropColumn() + { + $blueprint = new Blueprint('users'); + $blueprint->dropColumn('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop `foo`', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->dropColumn(['foo', 'bar']); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->dropColumn('foo', 'bar'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop `foo`, drop `bar`', $statements[0]); + } + + public function testDropPrimary() + { + $blueprint = new Blueprint('users'); + $blueprint->dropPrimary(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop primary key', $statements[0]); + } + + public function testDropUnique() + { + $blueprint = new Blueprint('users'); + $blueprint->dropUnique('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop index `foo`', $statements[0]); + } + + public function testDropIndex() + { + $blueprint = new Blueprint('users'); + $blueprint->dropIndex('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop index `foo`', $statements[0]); + } + + public function testDropSpatialIndex() + { + $blueprint = new Blueprint('geo'); + $blueprint->dropSpatialIndex(['coordinates']); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` drop index `geo_coordinates_spatialindex`', $statements[0]); + } + + public function testDropForeign() + { + $blueprint = new Blueprint('users'); + $blueprint->dropForeign('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop foreign key `foo`', $statements[0]); + } + + public function testDropTimestamps() + { + $blueprint = new Blueprint('users'); + $blueprint->dropTimestamps(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); + } + + public function testDropTimestampsTz() + { + $blueprint = new Blueprint('users'); + $blueprint->dropTimestampsTz(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` drop `created_at`, drop `updated_at`', $statements[0]); + } + + public function testDropMorphs() + { + $blueprint = new Blueprint('photos'); + $blueprint->dropMorphs('imageable'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(2, $statements); + $this->assertSame('alter table `photos` drop index `photos_imageable_type_imageable_id_index`', $statements[0]); + $this->assertSame('alter table `photos` drop `imageable_type`, drop `imageable_id`', $statements[1]); + } + + public function testRenameTable() + { + $blueprint = new Blueprint('users'); + $blueprint->rename('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('rename table `users` to `foo`', $statements[0]); + } + + public function testRenameIndex() + { + $blueprint = new Blueprint('users'); + $blueprint->renameIndex('foo', 'bar'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` rename index `foo` to `bar`', $statements[0]); + } + + public function testAddingPrimaryKey() + { + $blueprint = new Blueprint('users'); + $blueprint->primary('foo', 'bar'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add primary key (`foo`)', $statements[0]); + } + + public function testAddingPrimaryKeyWithAlgorithm() + { + $blueprint = new Blueprint('users'); + $blueprint->primary('foo', 'bar', 'hash'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add primary key using hash(`foo`)', $statements[0]); + } + + public function testAddingUniqueKey() + { + $blueprint = new Blueprint('users'); + $blueprint->unique('foo', 'bar'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add unique `bar`(`foo`)', $statements[0]); + } + + public function testAddingIndex() + { + $blueprint = new Blueprint('users'); + $blueprint->index(['foo', 'bar'], 'baz'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add index `baz`(`foo`, `bar`)', $statements[0]); + } + + public function testAddingIndexWithAlgorithm() + { + $blueprint = new Blueprint('users'); + $blueprint->index(['foo', 'bar'], 'baz', 'hash'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add index `baz` using hash(`foo`, `bar`)', $statements[0]); + } + + public function testAddingFulltextIndex() + { + $blueprint = new Blueprint('users'); + $blueprint->fulltext('body'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add fulltext `users_body_fulltext`(`body`)', $statements[0]); + } + + public function testAddingSpatialIndex() + { + $blueprint = new Blueprint('geo'); + $blueprint->spatialIndex('coordinates'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[0]); + } + + public function testAddingFluentSpatialIndex() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'point')->spatialIndex(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(2, $statements); + $this->assertSame('alter table `geo` add spatial index `geo_coordinates_spatialindex`(`coordinates`)', $statements[1]); + } + + public function testAddingRawIndex() + { + $blueprint = new Blueprint('users'); + $blueprint->rawIndex('(function(column))', 'raw_index'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add index `raw_index`((function(column)))', $statements[0]); + } + + public function testAddingForeignKey() + { + $blueprint = new Blueprint('users'); + $blueprint->foreign('foo_id')->references('id')->on('orders'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`)', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnDelete(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on delete cascade', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add constraint `users_foo_id_foreign` foreign key (`foo_id`) references `orders` (`id`) on update cascade', $statements[0]); + } + + public function testAddingIncrementingID() + { + $blueprint = new Blueprint('users'); + $blueprint->increments('id'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key', $statements[0]); + } + + public function testAddingSmallIncrementingID() + { + $blueprint = new Blueprint('users'); + $blueprint->smallIncrements('id'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `id` smallint unsigned not null auto_increment primary key', $statements[0]); + } + + public function testAddingID() + { + $blueprint = new Blueprint('users'); + $blueprint->id(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->id('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` bigint unsigned not null auto_increment primary key', $statements[0]); + } + + public function testAddingForeignID() + { + $blueprint = new Blueprint('users'); + $foreignId = $blueprint->foreignId('foo'); + $blueprint->foreignId('company_id')->constrained(); + $blueprint->foreignId('laravel_idea_id')->constrained(); + $blueprint->foreignId('team_id')->references('id')->on('teams'); + $blueprint->foreignId('team_column_id')->constrained('teams'); + + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); + $this->assertSame([ + 'alter table `users` add `foo` bigint unsigned not null, add `company_id` bigint unsigned not null, add `laravel_idea_id` bigint unsigned not null, add `team_id` bigint unsigned not null, add `team_column_id` bigint unsigned not null', + 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', + ], $statements); + } + + public function testAddingForeignIdSpecifyingIndexNameInConstraint() + { + $blueprint = new Blueprint('users'); + $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertSame([ + 'alter table `users` add `company_id` bigint unsigned not null', + 'alter table `users` add constraint `my_index` foreign key (`company_id`) references `companies` (`id`)', + ], $statements); + } + + public function testAddingBigIncrementingID() + { + $blueprint = new Blueprint('users'); + $blueprint->bigIncrements('id'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); + } + + public function testAddingColumnInTableFirst() + { + $blueprint = new Blueprint('users'); + $blueprint->string('name')->first(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `name` varchar(255) not null first', $statements[0]); + } + + public function testAddingColumnAfterAnotherColumn() + { + $blueprint = new Blueprint('users'); + $blueprint->string('name')->after('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `name` varchar(255) not null after `foo`', $statements[0]); + } + + public function testAddingMultipleColumnsAfterAnotherColumn() + { + $blueprint = new Blueprint('users'); + $blueprint->after('foo', function ($blueprint) { + $blueprint->string('one'); + $blueprint->string('two'); + }); + $blueprint->string('three'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `one` varchar(255) not null after `foo`, add `two` varchar(255) not null after `one`, add `three` varchar(255) not null', $statements[0]); + } + + public function testAddingGeneratedColumn() + { + $blueprint = new Blueprint('products'); + $blueprint->integer('price'); + $blueprint->integer('discounted_virtual')->virtualAs('price - 5'); + $blueprint->integer('discounted_stored')->storedAs('price - 5'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + + $blueprint = new Blueprint('products'); + $blueprint->integer('price'); + $blueprint->integer('discounted_virtual')->virtualAs('price - 5')->nullable(false); + $blueprint->integer('discounted_stored')->storedAs('price - 5')->nullable(false); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5) not null, add `discounted_stored` int as (price - 5) stored not null', $statements[0]); + } + + public function testAddingGeneratedColumnWithCharset() + { + $blueprint = new Blueprint('links'); + $blueprint->string('url', 2083)->charset('ascii'); + $blueprint->string('url_hash_virtual', 64)->virtualAs('sha2(url, 256)')->charset('ascii'); + $blueprint->string('url_hash_stored', 64)->storedAs('sha2(url, 256)')->charset('ascii'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `links` add `url` varchar(2083) character set ascii not null, add `url_hash_virtual` varchar(64) character set ascii as (sha2(url, 256)), add `url_hash_stored` varchar(64) character set ascii as (sha2(url, 256)) stored', $statements[0]); + } + + public function testAddingGeneratedColumnByExpression() + { + $blueprint = new Blueprint('products'); + $blueprint->integer('price'); + $blueprint->integer('discounted_virtual')->virtualAs(new Expression('price - 5')); + $blueprint->integer('discounted_stored')->storedAs(new Expression('price - 5')); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + } + + public function testAddingInvisibleColumn() + { + $blueprint = new Blueprint('users'); + $blueprint->string('secret', 64)->nullable(false)->invisible(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `secret` varchar(64) not null invisible', $statements[0]); + } + + public function testAddingString() + { + $blueprint = new Blueprint('users'); + $blueprint->string('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(255) not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->string('foo', 100); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(100) not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->string('foo', 100)->nullable()->default('bar'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->string('foo', 100)->nullable()->default(new Expression('CURRENT TIMESTAMP')); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(100) null default CURRENT TIMESTAMP', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->string('foo', 100)->nullable()->default(Foo::BAR); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(100) null default \'bar\'', $statements[0]); + } + + public function testAddingText() + { + $blueprint = new Blueprint('users'); + $blueprint->text('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` text not null', $statements[0]); + } + + public function testAddingBigInteger() + { + $blueprint = new Blueprint('users'); + $blueprint->bigInteger('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` bigint not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->bigInteger('foo', true); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` bigint not null auto_increment primary key', $statements[0]); + } + + public function testAddingInteger() + { + $blueprint = new Blueprint('users'); + $blueprint->integer('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` int not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->integer('foo', true); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` int not null auto_increment primary key', $statements[0]); + } + + public function testAddingIncrementsWithStartingValues() + { + $blueprint = new Blueprint('users'); + $blueprint->id()->startingValue(1000); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(2, $statements); + $this->assertSame('alter table `users` add `id` bigint unsigned not null auto_increment primary key', $statements[0]); + $this->assertSame('alter table `users` auto_increment = 1000', $statements[1]); + } + + public function testAddingMediumInteger() + { + $blueprint = new Blueprint('users'); + $blueprint->mediumInteger('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` mediumint not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->mediumInteger('foo', true); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` mediumint not null auto_increment primary key', $statements[0]); + } + + public function testAddingSmallInteger() + { + $blueprint = new Blueprint('users'); + $blueprint->smallInteger('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` smallint not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->smallInteger('foo', true); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` smallint not null auto_increment primary key', $statements[0]); + } + + public function testAddingTinyInteger() + { + $blueprint = new Blueprint('users'); + $blueprint->tinyInteger('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` tinyint not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->tinyInteger('foo', true); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` tinyint not null auto_increment primary key', $statements[0]); + } + + public function testAddingFloat() + { + $blueprint = new Blueprint('users'); + $blueprint->float('foo', 5); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` float(5) not null', $statements[0]); + } + + public function testAddingDouble() + { + $blueprint = new Blueprint('users'); + $blueprint->double('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` double not null', $statements[0]); + } + + public function testAddingDecimal() + { + $blueprint = new Blueprint('users'); + $blueprint->decimal('foo', 5, 2); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` decimal(5, 2) not null', $statements[0]); + } + + public function testAddingBoolean() + { + $blueprint = new Blueprint('users'); + $blueprint->boolean('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` tinyint(1) not null', $statements[0]); + } + + public function testAddingEnum() + { + $blueprint = new Blueprint('users'); + $blueprint->enum('role', ['member', 'admin']); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `role` enum(\'member\', \'admin\') not null', $statements[0]); + } + + public function testAddingSet() + { + $blueprint = new Blueprint('users'); + $blueprint->set('role', ['member', 'admin']); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `role` set(\'member\', \'admin\') not null', $statements[0]); + } + + public function testAddingJson() + { + $blueprint = new Blueprint('users'); + $blueprint->json('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); + } + + public function testAddingJsonb() + { + $blueprint = new Blueprint('users'); + $blueprint->jsonb('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` json not null', $statements[0]); + } + + public function testAddingDate() + { + $blueprint = new Blueprint('users'); + $blueprint->date('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` date not null', $statements[0]); + } + + public function testAddingYear() + { + $blueprint = new Blueprint('users'); + $blueprint->year('birth_year'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `birth_year` year not null', $statements[0]); + } + + public function testAddingDateTime() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); + } + + public function testAddingDateTimeWithDefaultCurrent() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo')->useCurrent(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP', $statements[0]); + } + + public function testAddingDateTimeWithOnUpdateCurrent() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo')->useCurrentOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime not null on update CURRENT_TIMESTAMP', $statements[0]); + } + + public function testAddingDateTimeWithDefaultCurrentAndOnUpdateCurrent() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo')->useCurrent()->useCurrentOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP', $statements[0]); + } + + public function testAddingDateTimeWithDefaultCurrentOnUpdateCurrentAndPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTime('foo', 3)->useCurrent()->useCurrentOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime(3) not null default CURRENT_TIMESTAMP(3) on update CURRENT_TIMESTAMP(3)', $statements[0]); + } + + public function testAddingDateTimeTz() + { + $blueprint = new Blueprint('users'); + $blueprint->dateTimeTz('foo', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime(1) not null', $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->dateTimeTz('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` datetime not null', $statements[0]); + } + + public function testAddingTime() + { + $blueprint = new Blueprint('users'); + $blueprint->time('created_at'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); + } + + public function testAddingTimeWithPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->time('created_at', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); + } + + public function testAddingTimeTz() + { + $blueprint = new Blueprint('users'); + $blueprint->timeTz('created_at'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` time not null', $statements[0]); + } + + public function testAddingTimeTzWithPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timeTz('created_at', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` time(1) not null', $statements[0]); + } + + public function testAddingTimestamp() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); + } + + public function testAddingTimestampWithPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); + } + + public function testAddingTimestampWithDefault() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at')->default('2015-07-22 11:43:17'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); + } + + public function testAddingTimestampWithDefaultCurrentSpecifyingPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at', 1)->useCurrent(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1)', $statements[0]); + } + + public function testAddingTimestampWithOnUpdateCurrentSpecifyingPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at', 1)->useCurrentOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null on update CURRENT_TIMESTAMP(1)', $statements[0]); + } + + public function testAddingTimestampWithDefaultCurrentAndOnUpdateCurrentSpecifyingPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamp('created_at', 1)->useCurrent()->useCurrentOnUpdate(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1) on update CURRENT_TIMESTAMP(1)', $statements[0]); + } + + public function testAddingTimestampTz() + { + $blueprint = new Blueprint('users'); + $blueprint->timestampTz('created_at'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp not null', $statements[0]); + } + + public function testAddingTimestampTzWithPrecision() + { + $blueprint = new Blueprint('users'); + $blueprint->timestampTz('created_at', 1); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null', $statements[0]); + } + + public function testAddingTimeStampTzWithDefault() + { + $blueprint = new Blueprint('users'); + $blueprint->timestampTz('created_at')->default('2015-07-22 11:43:17'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame("alter table `users` add `created_at` timestamp not null default '2015-07-22 11:43:17'", $statements[0]); + } + + public function testAddingTimestamps() + { + $blueprint = new Blueprint('users'); + $blueprint->timestamps(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + } + + public function testAddingTimestampsTz() + { + $blueprint = new Blueprint('users'); + $blueprint->timestampsTz(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + } + + public function testAddingRememberToken() + { + $blueprint = new Blueprint('users'); + $blueprint->rememberToken(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `remember_token` varchar(100) null', $statements[0]); + } + + public function testAddingBinary() + { + $blueprint = new Blueprint('users'); + $blueprint->binary('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` blob not null', $statements[0]); + } + + public function testAddingUuid() + { + $blueprint = new Blueprint('users'); + $blueprint->uuid('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` char(36) not null', $statements[0]); + } + + public function testAddingUuidDefaultsColumnName() + { + $blueprint = new Blueprint('users'); + $blueprint->uuid(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `uuid` char(36) not null', $statements[0]); + } + + public function testAddingForeignUuid() + { + $blueprint = new Blueprint('users'); + $foreignUuid = $blueprint->foreignUuid('foo'); + $blueprint->foreignUuid('company_id')->constrained(); + $blueprint->foreignUuid('laravel_idea_id')->constrained(); + $blueprint->foreignUuid('team_id')->references('id')->on('teams'); + $blueprint->foreignUuid('team_column_id')->constrained('teams'); + + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); + $this->assertSame([ + 'alter table `users` add `foo` char(36) not null, add `company_id` char(36) not null, add `laravel_idea_id` char(36) not null, add `team_id` char(36) not null, add `team_column_id` char(36) not null', + 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', + ], $statements); + } + + public function testAddingIpAddress() + { + $blueprint = new Blueprint('users'); + $blueprint->ipAddress('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(45) not null', $statements[0]); + } + + public function testAddingIpAddressDefaultsColumnName() + { + $blueprint = new Blueprint('users'); + $blueprint->ipAddress(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `ip_address` varchar(45) not null', $statements[0]); + } + + public function testAddingMacAddress() + { + $blueprint = new Blueprint('users'); + $blueprint->macAddress('foo'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `foo` varchar(17) not null', $statements[0]); + } + + public function testAddingMacAddressDefaultsColumnName() + { + $blueprint = new Blueprint('users'); + $blueprint->macAddress(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `users` add `mac_address` varchar(17) not null', $statements[0]); + } + + public function testAddingGeometry() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` geometry not null', $statements[0]); + } + + public function testAddingGeography() + { + $blueprint = new Blueprint('geo'); + $blueprint->geography('coordinates'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` geometry ref_system_id=4326 not null', $statements[0]); + } + + public function testAddingPoint() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'point'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` point not null', $statements[0]); + } + + public function testAddingPointWithSrid() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'point', 4326); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` point ref_system_id=4326 not null', $statements[0]); + } + + public function testAddingPointWithSridColumn() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'point', 4326)->after('id'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` point ref_system_id=4326 not null after `id`', $statements[0]); + } + + public function testAddingLineString() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'linestring'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` linestring not null', $statements[0]); + } + + public function testAddingPolygon() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'polygon'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` polygon not null', $statements[0]); + } + + public function testAddingGeometryCollection() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'geometrycollection'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` geometrycollection not null', $statements[0]); + } + + public function testAddingMultiPoint() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'multipoint'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` multipoint not null', $statements[0]); + } + + public function testAddingMultiLineString() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'multilinestring'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` multilinestring not null', $statements[0]); + } + + public function testAddingMultiPolygon() + { + $blueprint = new Blueprint('geo'); + $blueprint->geometry('coordinates', 'multipolygon'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame('alter table `geo` add `coordinates` multipolygon not null', $statements[0]); + } + + public function testAddingComment() + { + $blueprint = new Blueprint('users'); + $blueprint->string('foo')->comment("Escape ' when using words like it's"); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("alter table `users` add `foo` varchar(255) not null comment 'Escape \\' when using words like it\\'s'", $statements[0]); + } + + public function testCreateDatabase() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_foo'); + $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_foo'); + + $statement = $this->getGrammar()->compileCreateDatabase('my_database_a', $connection); + + $this->assertSame( + 'create database `my_database_a` default character set `utf8mb4_foo` default collate `utf8mb4_unicode_ci_foo`', + $statement + ); + + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->once()->once()->with('charset')->andReturn('utf8mb4_bar'); + $connection->shouldReceive('getConfig')->once()->once()->with('collation')->andReturn('utf8mb4_unicode_ci_bar'); + + $statement = $this->getGrammar()->compileCreateDatabase('my_database_b', $connection); + + $this->assertSame( + 'create database `my_database_b` default character set `utf8mb4_bar` default collate `utf8mb4_unicode_ci_bar`', + $statement + ); + } + + public function testCreateTableWithVirtualAsColumn() + { + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_column'); + $blueprint->string('my_other_column')->virtualAs('my_column'); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column)) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_json_column'); + $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))))", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_json_column'); + $blueprint->string('my_other_column')->virtualAsJson('my_json_column->some_attribute->nested'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))))", $statements[0]); + } + + public function testCreateTableWithVirtualAsColumnWhenJsonColumnHasArrayKey() + { + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_json_column')->virtualAsJson('my_json_column->foo[0][1]'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_json_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"foo\"[0][1]'))))", $statements[0]); + } + + public function testCreateTableWithStoredAsColumn() + { + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->once()->with('charset')->andReturn('utf8'); + $conn->shouldReceive('getConfig')->once()->with('collation')->andReturn('utf8_unicode_ci'); + $conn->shouldReceive('getConfig')->once()->with('engine')->andReturn(null); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_column'); + $blueprint->string('my_other_column')->storedAs('my_column'); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_column` varchar(255) not null, `my_other_column` varchar(255) as (my_column) stored) default character set utf8 collate 'utf8_unicode_ci'", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_json_column'); + $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\"'))) stored)", $statements[0]); + + $blueprint = new Blueprint('users'); + $blueprint->create(); + $blueprint->string('my_json_column'); + $blueprint->string('my_other_column')->storedAsJson('my_json_column->some_attribute->nested'); + + $conn = $this->getConnection(); + $conn->shouldReceive('getConfig')->andReturn(null); + + $statements = $blueprint->toSql($conn, $this->getGrammar()); + + $this->assertCount(1, $statements); + $this->assertSame("create table `users` (`my_json_column` varchar(255) not null, `my_other_column` varchar(255) as (json_unquote(json_extract(`my_json_column`, '$.\"some_attribute\".\"nested\"'))) stored)", $statements[0]); + } + + public function testDropDatabaseIfExists() + { + $statement = $this->getGrammar()->compileDropDatabaseIfExists('my_database_a'); + + $this->assertSame( + 'drop database if exists `my_database_a`', + $statement + ); + + $statement = $this->getGrammar()->compileDropDatabaseIfExists('my_database_b'); + + $this->assertSame( + 'drop database if exists `my_database_b`', + $statement + ); + } + + public function testDropAllTables() + { + $statement = $this->getGrammar()->compileDropAllTables(['alpha', 'beta', 'gamma']); + + $this->assertSame('drop table `alpha`,`beta`,`gamma`', $statement); + } + + public function testDropAllViews() + { + $statement = $this->getGrammar()->compileDropAllViews(['alpha', 'beta', 'gamma']); + + $this->assertSame('drop view `alpha`,`beta`,`gamma`', $statement); + } + + public function testGrammarsAreMacroable() + { + // compileReplace macro. + $this->getGrammar()::macro('compileReplace', function () { + return true; + }); + + $c = $this->getGrammar()::compileReplace(); + + $this->assertTrue($c); + } + + protected function getConnection() + { + return m::mock(Connection::class); + } + + public function getGrammar() + { + return new MariaDbGrammar; + } +} diff --git a/tests/Database/DatabaseMariaDbSchemaStateTest.php b/tests/Database/DatabaseMariaDbSchemaStateTest.php new file mode 100644 index 000000000000..fff911093b5b --- /dev/null +++ b/tests/Database/DatabaseMariaDbSchemaStateTest.php @@ -0,0 +1,87 @@ +createMock(MariaDbConnection::class); + $connection->method('getConfig')->willReturn($dbConfig); + + $schemaState = new MariaDbSchemaState($connection); + + // test connectionString + $method = new ReflectionMethod(get_class($schemaState), 'connectionString'); + $connString = $method->invoke($schemaState); + + self::assertEquals($expectedConnectionString, $connString); + + // test baseVariables + $method = new ReflectionMethod(get_class($schemaState), 'baseVariables'); + $variables = $method->invoke($schemaState, $dbConfig); + + self::assertEquals($expectedVariables, $variables); + } + + public static function provider(): Generator + { + yield 'default' => [ + ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}"', [ + 'LARAVEL_LOAD_SOCKET' => '', + 'LARAVEL_LOAD_HOST' => '127.0.0.1', + 'LARAVEL_LOAD_PORT' => '', + 'LARAVEL_LOAD_USER' => 'root', + 'LARAVEL_LOAD_PASSWORD' => '', + 'LARAVEL_LOAD_DATABASE' => 'forge', + 'LARAVEL_LOAD_SSL_CA' => '', + ], [ + 'username' => 'root', + 'host' => '127.0.0.1', + 'database' => 'forge', + ], + ]; + + yield 'ssl_ca' => [ + ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}" --host="${:LARAVEL_LOAD_HOST}" --port="${:LARAVEL_LOAD_PORT}" --ssl-ca="${:LARAVEL_LOAD_SSL_CA}"', [ + 'LARAVEL_LOAD_SOCKET' => '', + 'LARAVEL_LOAD_HOST' => '', + 'LARAVEL_LOAD_PORT' => '', + 'LARAVEL_LOAD_USER' => 'root', + 'LARAVEL_LOAD_PASSWORD' => '', + 'LARAVEL_LOAD_DATABASE' => 'forge', + 'LARAVEL_LOAD_SSL_CA' => 'ssl.ca', + ], [ + 'username' => 'root', + 'database' => 'forge', + 'options' => [ + \PDO::MYSQL_ATTR_SSL_CA => 'ssl.ca', + ], + ], + ]; + + yield 'unix socket' => [ + ' --user="${:LARAVEL_LOAD_USER}" --password="${:LARAVEL_LOAD_PASSWORD}" --socket="${:LARAVEL_LOAD_SOCKET}"', [ + 'LARAVEL_LOAD_SOCKET' => '/tmp/mysql.sock', + 'LARAVEL_LOAD_HOST' => '', + 'LARAVEL_LOAD_PORT' => '', + 'LARAVEL_LOAD_USER' => 'root', + 'LARAVEL_LOAD_PASSWORD' => '', + 'LARAVEL_LOAD_DATABASE' => 'forge', + 'LARAVEL_LOAD_SSL_CA' => '', + ], [ + 'username' => 'root', + 'database' => 'forge', + 'unix_socket' => '/tmp/mysql.sock', + ], + ]; + } +} diff --git a/tests/Integration/Database/DatabaseTestCase.php b/tests/Integration/Database/DatabaseTestCase.php index 14f78bd71e0a..31ec210d5699 100644 --- a/tests/Integration/Database/DatabaseTestCase.php +++ b/tests/Integration/Database/DatabaseTestCase.php @@ -32,5 +32,10 @@ protected function defineEnvironment($app) $connection = $app['config']->get('database.default'); $this->driver = $app['config']->get("database.connections.$connection.driver"); + + // TODO: Adjust orchestra/testbench-core/laravel/config/database.php + if ($connection === 'mariadb') { + $this->driver = 'mariadb'; + } } } diff --git a/tests/Integration/Database/MariaDb/DatabaseEloquentMariaDbIntegrationTest.php b/tests/Integration/Database/MariaDb/DatabaseEloquentMariaDbIntegrationTest.php new file mode 100644 index 000000000000..e58574bf4be2 --- /dev/null +++ b/tests/Integration/Database/MariaDb/DatabaseEloquentMariaDbIntegrationTest.php @@ -0,0 +1,84 @@ +id(); + $table->string('name')->nullable(); + $table->string('email')->unique(); + $table->timestamps(); + }); + } + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('database_eloquent_mariadb_integration_users'); + } + + public function testCreateOrFirst() + { + $user1 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); + + $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertNull($user1->name); + + $user2 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + + $user3 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst( + ['email' => 'abigailotwell@gmail.com'], + ['name' => 'Abigail Otwell'] + ); + + $this->assertNotEquals($user3->id, $user1->id); + $this->assertSame('abigailotwell@gmail.com', $user3->email); + $this->assertSame('Abigail Otwell', $user3->name); + + $user4 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst( + ['name' => 'Dries Vints'], + ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] + ); + + $this->assertSame('Nuno Maduro', $user4->name); + } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentMariaDbIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } +} + +class DatabaseEloquentMariaDbIntegrationUser extends Model +{ + protected $table = 'database_eloquent_mariadb_integration_users'; + + protected $guarded = []; +} diff --git a/tests/Integration/Database/MariaDb/DatabaseEmulatePreparesMariaDbConnectionTest.php b/tests/Integration/Database/MariaDb/DatabaseEmulatePreparesMariaDbConnectionTest.php new file mode 100755 index 000000000000..8c155518dcb5 --- /dev/null +++ b/tests/Integration/Database/MariaDb/DatabaseEmulatePreparesMariaDbConnectionTest.php @@ -0,0 +1,21 @@ +set('database.connections.mariadb.options', [ + PDO::ATTR_EMULATE_PREPARES => true, + ]); + } +} diff --git a/tests/Integration/Database/MariaDb/DatabaseMariaDbConnectionTest.php b/tests/Integration/Database/MariaDb/DatabaseMariaDbConnectionTest.php new file mode 100644 index 000000000000..d1d164e9bef2 --- /dev/null +++ b/tests/Integration/Database/MariaDb/DatabaseMariaDbConnectionTest.php @@ -0,0 +1,147 @@ +json(self::JSON_COL)->nullable(); + $table->float(self::FLOAT_COL)->nullable(); + }); + } + } + + protected function destroyDatabaseMigrations() + { + Schema::drop(self::TABLE); + } + + #[DataProvider('floatComparisonsDataProvider')] + public function testJsonFloatComparison($value, $operator, $shouldMatch) + { + DB::table(self::TABLE)->insert([self::JSON_COL => '{"rank":'.self::FLOAT_VAL.'}']); + + $this->assertSame( + $shouldMatch, + DB::table(self::TABLE)->where(self::JSON_COL.'->rank', $operator, $value)->exists(), + self::JSON_COL.'->rank should '.($shouldMatch ? '' : 'not ')."be $operator $value" + ); + } + + public static function floatComparisonsDataProvider() + { + return [ + [0.2, '=', true], + [0.2, '>', false], + [0.2, '<', false], + [0.1, '=', false], + [0.1, '<', false], + [0.1, '>', true], + [0.3, '=', false], + [0.3, '<', true], + [0.3, '>', false], + ]; + } + + public function testFloatValueStoredCorrectly() + { + DB::table(self::TABLE)->insert([self::FLOAT_COL => self::FLOAT_VAL]); + + $this->assertEquals(self::FLOAT_VAL, DB::table(self::TABLE)->value(self::FLOAT_COL)); + } + + #[DataProvider('jsonWhereNullDataProvider')] + public function testJsonWhereNull($expected, $key, array $value = ['value' => 123]) + { + DB::table(self::TABLE)->insert([self::JSON_COL => json_encode($value)]); + + $this->assertSame($expected, DB::table(self::TABLE)->whereNull(self::JSON_COL.'->'.$key)->exists()); + } + + #[DataProvider('jsonWhereNullDataProvider')] + public function testJsonWhereNotNull($expected, $key, array $value = ['value' => 123]) + { + DB::table(self::TABLE)->insert([self::JSON_COL => json_encode($value)]); + + $this->assertSame(! $expected, DB::table(self::TABLE)->whereNotNull(self::JSON_COL.'->'.$key)->exists()); + } + + public static function jsonWhereNullDataProvider() + { + return [ + 'key not exists' => [true, 'invalid'], + 'key exists and null' => [true, 'value', ['value' => null]], + 'key exists and "null"' => [false, 'value', ['value' => 'null']], + 'key exists and not null' => [false, 'value', ['value' => false]], + 'nested key not exists' => [true, 'nested->invalid'], + 'nested key exists and null' => [true, 'nested->value', ['nested' => ['value' => null]]], + 'nested key exists and "null"' => [false, 'nested->value', ['nested' => ['value' => 'null']]], + 'nested key exists and not null' => [false, 'nested->value', ['nested' => ['value' => false]]], + 'array index not exists' => [false, '[0]', [1 => 'invalid']], + 'array index exists and null' => [true, '[0]', [null]], + 'array index exists and "null"' => [false, '[0]', ['null']], + 'array index exists and not null' => [false, '[0]', [false]], + 'nested array index not exists' => [false, 'nested[0]', ['nested' => [1 => 'nested->invalid']]], + 'nested array index exists and null' => [true, 'nested->value[1]', ['nested' => ['value' => [0, null]]]], + 'nested array index exists and "null"' => [false, 'nested->value[1]', ['nested' => ['value' => [0, 'null']]]], + 'nested array index exists and not null' => [false, 'nested->value[1]', ['nested' => ['value' => [0, false]]]], + ]; + } + + public function testJsonPathUpdate() + { + DB::table(self::TABLE)->insert([ + [self::JSON_COL => '{"foo":["bar"]}'], + [self::JSON_COL => '{"foo":["baz"]}'], + ]); + $updatedCount = DB::table(self::TABLE)->where(self::JSON_COL.'->foo[0]', 'baz')->update([ + self::JSON_COL.'->foo[0]' => 'updated', + ]); + $this->assertSame(1, $updatedCount); + } + + #[DataProvider('jsonContainsKeyDataProvider')] + public function testWhereJsonContainsKey($count, $column) + { + DB::table(self::TABLE)->insert([ + ['json_col' => '{"foo":{"bar":["baz"]}}'], + ['json_col' => '{"foo":{"bar":false}}'], + ['json_col' => '{"foo":{}}'], + ['json_col' => '{"foo":[{"bar":"bar"},{"baz":"baz"}]}'], + ['json_col' => '{"bar":null}'], + ]); + + $this->assertSame($count, DB::table(self::TABLE)->whereJsonContainsKey($column)->count()); + } + + public static function jsonContainsKeyDataProvider() + { + return [ + 'string key' => [4, 'json_col->foo'], + 'nested key exists' => [2, 'json_col->foo->bar'], + 'string key missing' => [0, 'json_col->none'], + 'integer key with arrow ' => [0, 'json_col->foo->bar->0'], + 'integer key with braces' => [2, 'json_col->foo->bar[0]'], + 'integer key missing' => [0, 'json_col->foo->bar[1]'], + 'mixed keys' => [1, 'json_col->foo[1]->baz'], + 'null value' => [1, 'json_col->bar'], + ]; + } +} diff --git a/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderAlterTableWithEnumTest.php b/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderAlterTableWithEnumTest.php new file mode 100644 index 000000000000..37a05623a754 --- /dev/null +++ b/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderAlterTableWithEnumTest.php @@ -0,0 +1,68 @@ +integer('id'); + $table->string('name'); + $table->string('age'); + $table->enum('color', ['red', 'blue']); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('users'); + } + + public function testRenameColumnOnTableWithEnum() + { + Schema::table('users', function (Blueprint $table) { + $table->renameColumn('name', 'username'); + }); + + $this->assertTrue(Schema::hasColumn('users', 'username')); + } + + public function testChangeColumnOnTableWithEnum() + { + Schema::table('users', function (Blueprint $table) { + $table->unsignedInteger('age')->change(); + }); + + $this->assertSame('int', Schema::getColumnType('users', 'age')); + } + + public function testGetTablesAndColumnListing() + { + $tables = Schema::getTables(); + + $this->assertCount(2, $tables); + $this->assertEquals(['migrations', 'users'], array_column($tables, 'name')); + + $columns = Schema::getColumnListing('users'); + + foreach (['id', 'name', 'age', 'color'] as $column) { + $this->assertContains($column, $columns); + } + + Schema::create('posts', function (Blueprint $table) { + $table->integer('id'); + $table->string('title'); + }); + $tables = Schema::getTables(); + $this->assertCount(3, $tables); + Schema::drop('posts'); + } +} diff --git a/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderTest.php b/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderTest.php new file mode 100644 index 000000000000..c018f2956707 --- /dev/null +++ b/tests/Integration/Database/MariaDb/DatabaseMariaDbSchemaBuilderTest.php @@ -0,0 +1,32 @@ +id(); + $table->comment('This is a comment'); + }); + + $tableInfo = DB::table('information_schema.tables') + ->where('table_schema', $this->app['config']->get('database.connections.mariadb.database')) + ->where('table_name', 'users') + ->select('table_comment as table_comment') + ->first(); + + $this->assertEquals('This is a comment', $tableInfo->table_comment); + + Schema::drop('users'); + } +} diff --git a/tests/Integration/Database/MariaDb/EloquentCastTest.php b/tests/Integration/Database/MariaDb/EloquentCastTest.php new file mode 100644 index 000000000000..704edaf81167 --- /dev/null +++ b/tests/Integration/Database/MariaDb/EloquentCastTest.php @@ -0,0 +1,237 @@ +increments('id'); + $table->string('email')->unique(); + $table->integer('created_at'); + $table->integer('updated_at'); + }); + + Schema::create('users_nullable_timestamps', function ($table) { + $table->increments('id'); + $table->string('email')->unique(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('updated_at')->nullable(); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('users'); + } + + public function testItCastTimestampsCreatedByTheBuilderWhenTimeHasNotPassed() + { + Carbon::setTestNow(now()); + $createdAt = now()->timestamp; + + $castUser = UserWithIntTimestampsViaCasts::create([ + 'email' => fake()->unique()->email, + ]); + $attributeUser = UserWithIntTimestampsViaAttribute::create([ + 'email' => fake()->unique()->email, + ]); + $mutatorUser = UserWithIntTimestampsViaMutator::create([ + 'email' => fake()->unique()->email, + ]); + + $this->assertSame($createdAt, $castUser->created_at->timestamp); + $this->assertSame($createdAt, $castUser->updated_at->timestamp); + $this->assertSame($createdAt, $attributeUser->created_at->timestamp); + $this->assertSame($createdAt, $attributeUser->updated_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->created_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->updated_at->timestamp); + + $castUser->update([ + 'email' => fake()->unique()->email, + ]); + $attributeUser->update([ + 'email' => fake()->unique()->email, + ]); + $mutatorUser->update([ + 'email' => fake()->unique()->email, + ]); + + $this->assertSame($createdAt, $castUser->created_at->timestamp); + $this->assertSame($createdAt, $castUser->updated_at->timestamp); + $this->assertSame($createdAt, $castUser->fresh()->updated_at->timestamp); + $this->assertSame($createdAt, $attributeUser->created_at->timestamp); + $this->assertSame($createdAt, $attributeUser->updated_at->timestamp); + $this->assertSame($createdAt, $attributeUser->fresh()->updated_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->created_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->updated_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->fresh()->updated_at->timestamp); + } + + public function testItCastTimestampsCreatedByTheBuilderWhenTimeHasPassed() + { + Carbon::setTestNow(now()); + $createdAt = now()->timestamp; + + $castUser = UserWithIntTimestampsViaCasts::create([ + 'email' => fake()->unique()->email, + ]); + $attributeUser = UserWithIntTimestampsViaAttribute::create([ + 'email' => fake()->unique()->email, + ]); + $mutatorUser = UserWithIntTimestampsViaMutator::create([ + 'email' => fake()->unique()->email, + ]); + + $this->assertSame($createdAt, $castUser->created_at->timestamp); + $this->assertSame($createdAt, $castUser->updated_at->timestamp); + $this->assertSame($createdAt, $attributeUser->created_at->timestamp); + $this->assertSame($createdAt, $attributeUser->updated_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->created_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->updated_at->timestamp); + + Carbon::setTestNow(now()->addSecond()); + $updatedAt = now()->timestamp; + + $castUser->update([ + 'email' => fake()->unique()->email, + ]); + $attributeUser->update([ + 'email' => fake()->unique()->email, + ]); + $mutatorUser->update([ + 'email' => fake()->unique()->email, + ]); + + $this->assertSame($createdAt, $castUser->created_at->timestamp); + $this->assertSame($updatedAt, $castUser->updated_at->timestamp); + $this->assertSame($updatedAt, $castUser->fresh()->updated_at->timestamp); + $this->assertSame($createdAt, $attributeUser->created_at->timestamp); + $this->assertSame($updatedAt, $attributeUser->updated_at->timestamp); + $this->assertSame($updatedAt, $attributeUser->fresh()->updated_at->timestamp); + $this->assertSame($createdAt, $mutatorUser->created_at->timestamp); + $this->assertSame($updatedAt, $mutatorUser->updated_at->timestamp); + $this->assertSame($updatedAt, $mutatorUser->fresh()->updated_at->timestamp); + } + + public function testItCastTimestampsUpdatedByAMutator() + { + Carbon::setTestNow(now()); + + $mutatorUser = UserWithUpdatedAtViaMutator::create([ + 'email' => fake()->unique()->email, + ]); + + $this->assertNull($mutatorUser->updated_at); + + Carbon::setTestNow(now()->addSecond()); + $updatedAt = now()->timestamp; + + $mutatorUser->update([ + 'email' => fake()->unique()->email, + ]); + + $this->assertSame($updatedAt, $mutatorUser->updated_at->timestamp); + $this->assertSame($updatedAt, $mutatorUser->fresh()->updated_at->timestamp); + } +} + +class UserWithIntTimestampsViaCasts extends Model +{ + protected $table = 'users'; + + protected $fillable = ['email']; + + protected $casts = [ + 'created_at' => UnixTimeStampToCarbon::class, + 'updated_at' => UnixTimeStampToCarbon::class, + ]; +} + +class UnixTimeStampToCarbon implements CastsAttributes +{ + public function get($model, string $key, $value, array $attributes) + { + return Carbon::parse($value); + } + + public function set($model, string $key, $value, array $attributes) + { + return Carbon::parse($value)->timestamp; + } +} + +class UserWithIntTimestampsViaAttribute extends Model +{ + protected $table = 'users'; + + protected $fillable = ['email']; + + protected function updatedAt(): Attribute + { + return Attribute::make( + get: fn ($value) => Carbon::parse($value), + set: fn ($value) => Carbon::parse($value)->timestamp, + ); + } + + protected function createdAt(): Attribute + { + return Attribute::make( + get: fn ($value) => Carbon::parse($value), + set: fn ($value) => Carbon::parse($value)->timestamp, + ); + } +} + +class UserWithIntTimestampsViaMutator extends Model +{ + protected $table = 'users'; + + protected $fillable = ['email']; + + protected function getUpdatedAtAttribute($value) + { + return Carbon::parse($value); + } + + protected function setUpdatedAtAttribute($value) + { + $this->attributes['updated_at'] = Carbon::parse($value)->timestamp; + } + + protected function getCreatedAtAttribute($value) + { + return Carbon::parse($value); + } + + protected function setCreatedAtAttribute($value) + { + $this->attributes['created_at'] = Carbon::parse($value)->timestamp; + } +} + +class UserWithUpdatedAtViaMutator extends Model +{ + protected $table = 'users_nullable_timestamps'; + + protected $fillable = ['email', 'updated_at']; + + public function setUpdatedAtAttribute($value) + { + if (! $this->id) { + return; + } + + $this->updated_at = $value; + } +} diff --git a/tests/Integration/Database/MariaDb/EscapeTest.php b/tests/Integration/Database/MariaDb/EscapeTest.php new file mode 100644 index 000000000000..bb015188bda3 --- /dev/null +++ b/tests/Integration/Database/MariaDb/EscapeTest.php @@ -0,0 +1,71 @@ +assertSame('42', $this->app['db']->escape(42)); + $this->assertSame('-6', $this->app['db']->escape(-6)); + } + + public function testEscapeFloat() + { + $this->assertSame('3.14159', $this->app['db']->escape(3.14159)); + $this->assertSame('-3.14159', $this->app['db']->escape(-3.14159)); + } + + public function testEscapeBool() + { + $this->assertSame('1', $this->app['db']->escape(true)); + $this->assertSame('0', $this->app['db']->escape(false)); + } + + public function testEscapeNull() + { + $this->assertSame('null', $this->app['db']->escape(null)); + $this->assertSame('null', $this->app['db']->escape(null, true)); + } + + public function testEscapeBinary() + { + $this->assertSame("x'dead00beef'", $this->app['db']->escape(hex2bin('dead00beef'), true)); + } + + public function testEscapeString() + { + $this->assertSame("'2147483647'", $this->app['db']->escape('2147483647')); + $this->assertSame("'true'", $this->app['db']->escape('true')); + $this->assertSame("'false'", $this->app['db']->escape('false')); + $this->assertSame("'null'", $this->app['db']->escape('null')); + $this->assertSame("'Hello\'World'", $this->app['db']->escape("Hello'World")); + } + + public function testEscapeStringInvalidUtf8() + { + $this->expectException(RuntimeException::class); + + $this->app['db']->escape("I am hiding an invalid \x80 utf-8 continuation byte"); + } + + public function testEscapeStringNullByte() + { + $this->expectException(RuntimeException::class); + + $this->app['db']->escape("I am hiding a \00 byte"); + } + + public function testEscapeArray() + { + $this->expectException(RuntimeException::class); + + $this->app['db']->escape(['a', 'b']); + } +} diff --git a/tests/Integration/Database/MariaDb/FulltextTest.php b/tests/Integration/Database/MariaDb/FulltextTest.php new file mode 100644 index 000000000000..e5ed54016358 --- /dev/null +++ b/tests/Integration/Database/MariaDb/FulltextTest.php @@ -0,0 +1,69 @@ +id('id'); + $table->string('title', 200); + $table->text('body'); + $table->fulltext(['title', 'body']); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('articles'); + } + + protected function setUp(): void + { + parent::setUp(); + + DB::table('articles')->insert([ + ['title' => 'MariaDB Tutorial', 'body' => 'DBMS stands for DataBase ...'], + ['title' => 'How To Use MariaDB Well', 'body' => 'After you went through a ...'], + ['title' => 'Optimizing MariaDB', 'body' => 'In this tutorial, we show ...'], + ['title' => '1001 MariaDB Tricks', 'body' => '1. Never run mariadbd as root. 2. ...'], + ['title' => 'MariaDB vs. YourSQL', 'body' => 'In the following database comparison ...'], + ['title' => 'MariaDB Security', 'body' => 'When configured properly, MariaDB ...'], + ]); + } + + /** @link https://mariadb.com/kb/en/full-text-index-overview/#in-natural-language-mode */ + public function testWhereFulltext() + { + $articles = DB::table('articles')->whereFulltext(['title', 'body'], 'database')->get(); + + $this->assertCount(2, $articles); + $this->assertSame('MariaDB Tutorial', $articles[0]->title); + $this->assertSame('MariaDB vs. YourSQL', $articles[1]->title); + } + + /** @link https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode */ + public function testWhereFulltextWithBooleanMode() + { + $articles = DB::table('articles')->whereFulltext(['title', 'body'], '+MariaDB -YourSQL', ['mode' => 'boolean'])->get(); + + $this->assertCount(5, $articles); + } + + /** @link https://mariadb.com/kb/en/full-text-index-overview/#with-query-expansion */ + public function testWhereFulltextWithExpandedQuery() + { + $articles = DB::table('articles')->whereFulltext(['title', 'body'], 'database', ['expanded' => true])->get(); + + $this->assertCount(6, $articles); + } +} diff --git a/tests/Integration/Database/MariaDb/MariaDbTestCase.php b/tests/Integration/Database/MariaDb/MariaDbTestCase.php new file mode 100644 index 000000000000..c99c90d2978b --- /dev/null +++ b/tests/Integration/Database/MariaDb/MariaDbTestCase.php @@ -0,0 +1,15 @@ +driver !== 'mariadb') { + $this->markTestSkipped('Test requires a MariaDB connection.'); + } + } +} diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index ae3b41dea4a7..2abe2ceb8f02 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -63,8 +63,8 @@ public function testChangeToTinyInteger() public function testChangeToTextColumn() { - if ($this->driver !== 'mysql') { - $this->markTestSkipped('Test requires a MySQL connection.'); + if (! in_array($this->driver, ['mysql', 'mariadb'])) { + $this->markTestSkipped('Test requires a MySQL or a MariaDB connection.'); } Schema::create('test', function (Blueprint $table) { @@ -88,8 +88,8 @@ public function testChangeToTextColumn() public function testChangeTextColumnToTextColumn() { - if ($this->driver !== 'mysql') { - $this->markTestSkipped('Test requires a MySQL connection.'); + if (! in_array($this->driver, ['mysql', 'mariadb'])) { + $this->markTestSkipped('Test requires a MySQL or a MariaDB connection.'); } Schema::create('test', static function (Blueprint $table) { @@ -227,7 +227,7 @@ public function testGetTables() $this->assertEmpty(array_diff(['foo', 'bar', 'baz'], array_column($tables, 'name'))); - if (in_array($this->driver, ['mysql', 'pgsql'])) { + if (in_array($this->driver, ['mysql', 'mariadb', 'pgsql'])) { $this->assertNotEmpty(array_filter($tables, function ($table) { return $table['name'] === 'foo' && $table['comment'] === 'This is a comment'; })); @@ -386,8 +386,8 @@ public function testGetIndexesWithCompositeKeys() public function testGetFullTextIndexes() { - if (! in_array($this->driver, ['pgsql', 'mysql'])) { - $this->markTestSkipped('Test requires a MySQL or a PostgreSQL connection.'); + if (! in_array($this->driver, ['mysql', 'mariadb', 'pgsql'])) { + $this->markTestSkipped('Test requires a MySQL, a MariaDB, or a PostgreSQL connection.'); } Schema::create('articles', function (Blueprint $table) { @@ -503,7 +503,7 @@ public function testAlteringTableWithForeignKeyConstraintsEnabled() public function testSystemVersionedTables() { - if ($this->driver !== 'mysql' || ! $this->getConnection()->isMaria()) { + if ($this->driver !== 'mariadb') { $this->markTestSkipped('Test requires a MariaDB connection.'); } diff --git a/tests/Integration/Database/TimestampTypeTest.php b/tests/Integration/Database/TimestampTypeTest.php index 05e57638c0e1..210de93546ec 100644 --- a/tests/Integration/Database/TimestampTypeTest.php +++ b/tests/Integration/Database/TimestampTypeTest.php @@ -18,10 +18,10 @@ public function testChangeDatetimeColumnToTimestampColumn() }); $this->assertTrue(Schema::hasColumn('test', 'datetime_to_timestamp')); - // Only Postgres and MySQL actually have a timestamp type + // Only MySQL, MariaDB, and PostgreSQL actually have a timestamp type $this->assertSame( match ($this->driver) { - 'mysql', 'pgsql' => 'timestamp', + 'mysql', 'mariadb', 'pgsql' => 'timestamp', default => 'datetime', }, Schema::getColumnType('test', 'datetime_to_timestamp') @@ -51,8 +51,8 @@ public function testChangeTimestampColumnToDatetimeColumn() public function testChangeStringColumnToTimestampColumn() { - if ($this->driver !== 'mysql') { - $this->markTestSkipped('Test requires a MySQL connection.'); + if (! in_array($this->driver, ['mysql', 'mariadb'])) { + $this->markTestSkipped('Test requires a MySQL or a MariaDB connection.'); } Schema::create('test', function (Blueprint $table) {