diff --git a/README.md b/README.md index 6057783..f21c1a1 100644 --- a/README.md +++ b/README.md @@ -110,9 +110,20 @@ For more detailed instructions, see `Colopl\Spanner\Tests\Eloquent\ModelTest`. ### Migrations -Since Cloud Spanner does not support AUTO_INCREMENT attribute, `Blueprint::increments` (and all of its variants) will -create a column of type `STRING(36) DEFAULT (GENERATE_UUID())` to generate and fill the column with a UUID -and flag it as a primary key. +Since Spanner recommends using UUID as a primary key, `Blueprint::increments` (and all of its variants) will create a +column of type `STRING(36) DEFAULT (GENERATE_UUID())` to generate and fill the column with a UUID +and flag it as a primary key. If you want to use `AUTO_INCREMENT`, you can do so by specifying it directly like this: + +```php +// `default_sequence_kind` must be set in order to use auto increment +$schemaBuilder->setDatabaseOptions([ + 'default_sequence_kind' => 'bit_reversed_positive', +]); + +$schemaBuilder->create('user', function (Blueprint $table) { + $table->integer('id')->primary()->autoIncrement(); +}); +``` ### Transactions Google Cloud Spanner sometimes requests transaction retries (e.g. `UNAVAILABLE`, and `ABORTED`), even if the logic is correct. For that reason, please do not manage transactions manually. diff --git a/compose.yaml b/compose.yaml index 72405f0..60be13c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,4 +8,4 @@ services: depends_on: - emulator emulator: - image: "gcr.io/cloud-spanner-emulator/emulator:1.5.30" + image: "gcr.io/cloud-spanner-emulator/emulator:1.5.33" diff --git a/src/Schema/Blueprint.php b/src/Schema/Blueprint.php index e5f106b..48a8511 100644 --- a/src/Schema/Blueprint.php +++ b/src/Schema/Blueprint.php @@ -21,7 +21,6 @@ use Illuminate\Database\Schema\Blueprint as BaseBlueprint; use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Support\Fluent; -use LogicException; /** * @method IndexDefinition index(string|string[] $columns, string|null $name = null) @@ -37,7 +36,7 @@ class Blueprint extends BaseBlueprint */ public function bigInteger($column, $autoIncrement = false, $unsigned = false): IntColumnDefinition { - $definition = new IntColumnDefinition($this, ['type' => __FUNCTION__, 'name' => $column]); + $definition = new IntColumnDefinition($this, ['type' => __FUNCTION__, 'name' => $column, 'autoIncrement' => $autoIncrement]); $this->addColumnDefinition($definition); return $definition; } diff --git a/src/Schema/Builder.php b/src/Schema/Builder.php index 87bcc10..128a77c 100644 --- a/src/Schema/Builder.php +++ b/src/Schema/Builder.php @@ -20,6 +20,8 @@ use Closure; use Colopl\Spanner\Connection; use Illuminate\Database\Schema\Builder as BaseBuilder; +use Illuminate\Support\Arr; +use Illuminate\Support\Str; /** * @property Grammar $grammar @@ -40,6 +42,23 @@ class Builder extends BaseBuilder */ public static $defaultMorphKeyType = 'uuid'; + /** + * @param array $options + * @return void + */ + public function setDatabaseOptions(array $options): void + { + $connection = $this->connection; + $name = Str::afterLast($connection->getDatabaseName(), '/'); + $line = implode(', ', Arr::map($options, fn($v, $k) => "$k = " . match (true) { + is_null($v) => 'null', + is_bool($v) => $v ? 'true' : 'false', + is_string($v) => $this->grammar->quoteString($v), + default => $v, + })); + $connection->statement("ALTER DATABASE `{$name}` SET OPTIONS ({$line})"); + } + /** * @deprecated Use Blueprint::dropIndex() instead. Will be removed in v10.0. * diff --git a/src/Schema/ColumnDefinition.php b/src/Schema/ColumnDefinition.php index 1eafae3..7317b8d 100644 --- a/src/Schema/ColumnDefinition.php +++ b/src/Schema/ColumnDefinition.php @@ -34,10 +34,13 @@ * @property int|null $precision * @property int|null $scale * @property bool|null $useCurrent + * @property bool|null $autoIncrement * @property string|Expression|true|null $generatedAs * @property string|null $virtualAs * @property bool|null $storedAs * @property int|null $startingValue + * @property bool|null $primary + * @property bool|null $change */ class ColumnDefinition extends BaseColumnDefinition { diff --git a/src/Schema/Grammar.php b/src/Schema/Grammar.php index 25bb57e..e9797a9 100644 --- a/src/Schema/Grammar.php +++ b/src/Schema/Grammar.php @@ -20,7 +20,6 @@ use Colopl\Spanner\Concerns\SharedGrammarCalls; use DateTimeInterface; use Illuminate\Contracts\Database\Query\Expression as ExpressionContract; -use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Grammars\Grammar as BaseGrammar; @@ -37,7 +36,7 @@ class Grammar extends BaseGrammar /** * @inheritdoc */ - protected $modifiers = ['Nullable', 'Default', 'GeneratedAs', 'Invisible', 'UseSequence']; + protected $modifiers = ['Nullable', 'Default', 'GeneratedAs', 'Invisible', 'Increment', 'UseSequence']; /** * Compile the query to determine the tables. @@ -884,6 +883,24 @@ protected function modifyInvisible(Blueprint $blueprint, Fluent $column) : null; } + /** + * @param Blueprint $blueprint + * @param ColumnDefinition $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if ($column->type !== 'bigInteger') { + return null; + } + + if ($column->autoIncrement !== true) { + return null; + } + + return ' auto_increment'; + } + /** * Get the SQL for an identity column modifier. * diff --git a/src/Schema/IntColumnDefinition.php b/src/Schema/IntColumnDefinition.php index 9a0d1ba..a4a2f70 100644 --- a/src/Schema/IntColumnDefinition.php +++ b/src/Schema/IntColumnDefinition.php @@ -20,8 +20,6 @@ namespace Colopl\Spanner\Schema; -use Illuminate\Database\Schema\ColumnDefinition; - /** * @property string $name * @property string|null $useSequence diff --git a/tests/Schema/BlueprintTest.php b/tests/Schema/BlueprintTest.php index 090710e..1400d30 100644 --- a/tests/Schema/BlueprintTest.php +++ b/tests/Schema/BlueprintTest.php @@ -96,6 +96,33 @@ public function test_create_with_all_valid_types(): void ], $statements); } + public function test_create_with_autoIncrement(): void + { + $conn = $this->getDefaultConnection(); + $tableName = $this->generateTableName(); + + $blueprint = new Blueprint($conn, $tableName, function (Blueprint $table) { + $table->bigInteger('id', true)->primary(); + $table->string('name'); + }); + $blueprint->create(); + + $queries = $blueprint->toSql(); + $this->assertSame( + 'create table `' . $tableName . '` (' . implode(', ', [ + '`id` int64 not null auto_increment', + '`name` string(255) not null', + ]) . ') primary key (`id`)', + $queries[0], + ); + + $conn->runDdlBatch($queries); + $conn->table($tableName)->insert(['name' => 't']); + $row = $conn->table($tableName)->first(); + $this->assertIsInt($row['id']); + $this->assertSame('t', $row['name']); + } + public function test_create_with_generateUuid(): void { $conn = $this->getDefaultConnection(); diff --git a/tests/TestCase.php b/tests/TestCase.php index ff5af0d..e5d21ac 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -144,7 +144,13 @@ protected function setUpDatabaseOnce(Connection $conn): void } if (!$conn->databaseExists()) { $conn->createDatabase($this->getTestDatabaseDDLs()); + + // Configure database to use bit-reversed sequences for auto-increment columns + $conn->getSchemaBuilder()->setDatabaseOptions([ + 'default_sequence_kind' => 'bit_reversed_positive', + ]); } + $this->beforeApplicationDestroyed(fn() => $this->cleanupDatabase($conn)); }