activerecord-spanner-adapter 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.kokoro/release.cfg +2 -2
- data/.kokoro/release.sh +1 -2
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +10 -0
- data/README.md +25 -23
- data/acceptance/cases/models/default_value_test.rb +2 -2
- data/acceptance/cases/tasks/database_tasks_test.rb +71 -74
- data/acceptance/cases/transactions/read_write_transactions_test.rb +10 -4
- data/acceptance/test_helper.rb +21 -8
- data/activerecord-spanner-adapter.gemspec +1 -1
- data/examples/snippets/Rakefile +1 -0
- data/examples/snippets/auto-generated-primary-key/README.md +140 -0
- data/examples/snippets/auto-generated-primary-key/Rakefile +13 -0
- data/examples/snippets/auto-generated-primary-key/application.rb +86 -0
- data/examples/snippets/auto-generated-primary-key/config/database.yml +10 -0
- data/examples/snippets/auto-generated-primary-key/db/migrate/01_create_tables.rb +29 -0
- data/examples/snippets/auto-generated-primary-key/db/seeds.rb +31 -0
- data/examples/snippets/auto-generated-primary-key/models/album.rb +11 -0
- data/examples/snippets/auto-generated-primary-key/models/singer.rb +11 -0
- data/lib/active_record/connection_adapters/spanner/column.rb +4 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +13 -2
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +5 -5
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +7 -3
- data/lib/active_record/connection_adapters/spanner_adapter.rb +22 -0
- data/lib/activerecord_spanner_adapter/base.rb +49 -16
- data/lib/activerecord_spanner_adapter/information_schema.rb +4 -2
- data/lib/activerecord_spanner_adapter/table/column.rb +4 -1
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- metadata +13 -5
@@ -0,0 +1,140 @@
|
|
1
|
+
# Sample - Auto-generated Primary Keys
|
2
|
+
|
3
|
+
This example shows how to use an IDENTITY column to generate the primary key of a model.
|
4
|
+
|
5
|
+
See https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns for more information
|
6
|
+
about IDENTITY columns in Cloud Spanner.
|
7
|
+
|
8
|
+
## Requirements
|
9
|
+
Using IDENTITY for generating primary key values in ActiveRecord has the following requirements:
|
10
|
+
1. You must use __ActiveRecord version 7.1 or higher__.
|
11
|
+
2. Your database configuration must include a `default_sequence_kind` value like this:
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
development:
|
15
|
+
adapter: spanner
|
16
|
+
emulator_host: localhost:9010
|
17
|
+
project: test-project
|
18
|
+
instance: test-instance
|
19
|
+
database: testdb
|
20
|
+
default_sequence_kind: BIT_REVERSED_POSITIVE
|
21
|
+
pool: 5
|
22
|
+
timeout: 5000
|
23
|
+
schema_dump: false
|
24
|
+
```
|
25
|
+
|
26
|
+
## Creating Tables with IDENTITY in ActiveRecord
|
27
|
+
You can create an IDENTITY column using migrations in ActiveRecord by using the `:primary_key` type
|
28
|
+
for the column. Note that this only generates an IDENTITY column if you have included the option
|
29
|
+
`default_sequence_kind: BIT_REVERSED_POSITIVE` as shown above.
|
30
|
+
|
31
|
+
Any default `id` column that is added by ActiveRecord will also use the `:primary_key` type and use
|
32
|
+
an IDENTITY column.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# The default `id` column that is added by ActiveRecord will automatically use
|
36
|
+
# an IDENTITY column.
|
37
|
+
create_table :singers do |t|
|
38
|
+
t.string :first_name
|
39
|
+
t.string :last_name
|
40
|
+
end
|
41
|
+
|
42
|
+
# You can also explicitly create a primary key column with a different name. Use
|
43
|
+
# the :primary_key type to generate an IDENTITY column.
|
44
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
45
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
46
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
47
|
+
t.string :first_name
|
48
|
+
t.string :last_name
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
## Example Data Model
|
53
|
+
This example uses the following table schema:
|
54
|
+
|
55
|
+
```sql
|
56
|
+
CREATE TABLE `singers` (
|
57
|
+
`singerid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
|
58
|
+
`first_name` STRING(MAX),
|
59
|
+
`last_name` STRING(MAX)
|
60
|
+
) PRIMARY KEY (`singerid`)
|
61
|
+
|
62
|
+
CREATE TABLE `albums` (
|
63
|
+
`singerid` INT64 NOT NULL,
|
64
|
+
`albumid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
|
65
|
+
`title` STRING(MAX)
|
66
|
+
) PRIMARY KEY (`singerid`, `albumid`), INTERLEAVE IN PARENT `singers`
|
67
|
+
```
|
68
|
+
|
69
|
+
This schema can be created in ActiveRecord 7.1 and later as follows:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# Execute the entire migration as one DDL batch.
|
73
|
+
connection.ddl_batch do
|
74
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
75
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
76
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
77
|
+
t.string :first_name
|
78
|
+
t.string :last_name
|
79
|
+
end
|
80
|
+
|
81
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
82
|
+
# Interleave the `albums` table in the parent table `singers`.
|
83
|
+
t.interleave_in :singers
|
84
|
+
t.integer :singerid, null: false
|
85
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
86
|
+
t.column :albumid, :primary_key, null: false
|
87
|
+
t.string :title
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
## Auto-generated Primary Keys and Mutations
|
93
|
+
|
94
|
+
Using primary keys that are auto-generated by an IDENTITY column is not
|
95
|
+
supported in combination with mutations, because Spanner must return the
|
96
|
+
generated primary key value after a row was inserted using a `THEN RETURN`
|
97
|
+
clause. This is not supported for mutations.
|
98
|
+
|
99
|
+
A workaround for this is to explicitly set a value for the primary key of
|
100
|
+
a row before inserting it:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
ActiveRecord::Base.transaction isolation: :buffered_mutations do
|
104
|
+
# Assign the singer a random id value. This value will be included in the insert mutation.
|
105
|
+
singer = Singer.create id: SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF,
|
106
|
+
first_name: "Melissa", last_name: "Garcia"
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
You can also instruct the Spanner ActiveRecord provider to automatically include a
|
111
|
+
primary key value when you use mutations. The primary key value assignment can then
|
112
|
+
be omitted from your code. Add `use_client_side_id_for_mutations: true` to your
|
113
|
+
configuration for this:
|
114
|
+
|
115
|
+
```yaml
|
116
|
+
development:
|
117
|
+
adapter: spanner
|
118
|
+
emulator_host: localhost:9010
|
119
|
+
project: test-project
|
120
|
+
instance: test-instance
|
121
|
+
database: testdb
|
122
|
+
default_sequence_kind: BIT_REVERSED_POSITIVE
|
123
|
+
use_client_side_id_for_mutations: true
|
124
|
+
pool: 5
|
125
|
+
timeout: 5000
|
126
|
+
schema_dump: false
|
127
|
+
```
|
128
|
+
|
129
|
+
## Running the Sample
|
130
|
+
|
131
|
+
The sample will automatically start a Spanner Emulator in a docker container and execute the sample
|
132
|
+
against that emulator. The emulator will automatically be stopped when the application finishes.
|
133
|
+
|
134
|
+
Run the application with the following commands:
|
135
|
+
|
136
|
+
```bash
|
137
|
+
export AR_VERSION="~> 7.1.2"
|
138
|
+
bundle install
|
139
|
+
bundle exec rake run
|
140
|
+
```
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require_relative "../config/environment"
|
8
|
+
require "sinatra/activerecord/rake"
|
9
|
+
|
10
|
+
desc "Sample showing how to work with auto-generated-primary keys."
|
11
|
+
task :run do
|
12
|
+
Dir.chdir("..") { sh "bundle exec rake run[auto-generated-primary-key]" }
|
13
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require "io/console"
|
8
|
+
require_relative "../config/environment"
|
9
|
+
require_relative "models/singer"
|
10
|
+
require_relative "models/album"
|
11
|
+
|
12
|
+
class Application
|
13
|
+
def self.run
|
14
|
+
# Create a new singer.
|
15
|
+
singer = create_new_singer
|
16
|
+
|
17
|
+
# Create a new album.
|
18
|
+
album = create_new_album singer
|
19
|
+
|
20
|
+
# Verify that the album exists.
|
21
|
+
find_album singer.singerid, album.albumid
|
22
|
+
|
23
|
+
# List all singers, albums and tracks.
|
24
|
+
list_singers_albums
|
25
|
+
|
26
|
+
# Create a new singer using mutations.
|
27
|
+
# This requires us to set a primary key value manually.
|
28
|
+
# You can also instruct the Spanner Ruby ActiveRecord provider to that automatically
|
29
|
+
# by including this in your database configuration:
|
30
|
+
# use_client_side_id_for_mutations: true
|
31
|
+
create_new_singer_using_mutations
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_album singerid, albumid
|
35
|
+
album = Album.find [singerid, albumid]
|
36
|
+
puts "Found album: #{album.title}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.list_singers_albums
|
40
|
+
puts ""
|
41
|
+
puts "Listing all singers with corresponding albums"
|
42
|
+
Singer.all.order("last_name, first_name").each do |singer|
|
43
|
+
puts "#{singer.first_name} #{singer.last_name} has #{singer.albums.count} albums:"
|
44
|
+
singer.albums.order("title").each do |album|
|
45
|
+
puts " #{album.title}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_new_singer
|
51
|
+
# Create a new singer. The singerid is generated by an IDENTITY column in the database and returned.
|
52
|
+
puts ""
|
53
|
+
singer = Singer.create first_name: "Melissa", last_name: "Garcia"
|
54
|
+
puts "Created a new singer '#{singer.first_name} #{singer.last_name}' with id #{singer.singerid}"
|
55
|
+
|
56
|
+
singer
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.create_new_album singer
|
60
|
+
# Create a new album.
|
61
|
+
puts ""
|
62
|
+
puts "Creating a new album for #{singer.first_name} #{singer.last_name}"
|
63
|
+
# The albumid is not generated by the database.
|
64
|
+
album = singer.albums.build albumid: 1, title: "New Title"
|
65
|
+
album.save!
|
66
|
+
|
67
|
+
album
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.create_new_singer_using_mutations
|
71
|
+
# Create a new singer using mutations. Mutations do not support THEN RETURN clauses,
|
72
|
+
# so we must specify a value for the primary key before inserting the row.
|
73
|
+
puts ""
|
74
|
+
singer = nil
|
75
|
+
ActiveRecord::Base.transaction isolation: :buffered_mutations do
|
76
|
+
# Assign the singer a random id value. This value will be included in the insert mutation.
|
77
|
+
singer = Singer.create id: SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF,
|
78
|
+
first_name: "Melissa", last_name: "Garcia"
|
79
|
+
end
|
80
|
+
puts "Inserted a new singer '#{singer.first_name} #{singer.last_name}' using mutations with id #{singer.singerid}"
|
81
|
+
|
82
|
+
singer
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Application.run
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
class CreateTables < ActiveRecord::Migration[7.1]
|
8
|
+
def change
|
9
|
+
connection.default_sequence_kind = "BIT_REVERSED_POSITIVE"
|
10
|
+
# Execute the entire migration as one DDL batch.
|
11
|
+
connection.ddl_batch do
|
12
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
13
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
14
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
15
|
+
t.string :first_name
|
16
|
+
t.string :last_name
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
20
|
+
# Interleave the `albums` table in the parent table `singers`.
|
21
|
+
t.interleave_in :singers
|
22
|
+
t.integer :singerid, null: false
|
23
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
24
|
+
t.column :albumid, :primary_key, null: false
|
25
|
+
t.string :title
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2023 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require_relative "../../config/environment"
|
8
|
+
require_relative "../models/singer"
|
9
|
+
require_relative "../models/album"
|
10
|
+
|
11
|
+
first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
|
12
|
+
last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
|
13
|
+
|
14
|
+
adjectives = %w[daily happy blue generous cooked bad open]
|
15
|
+
nouns = %w[windows potatoes bank street tree glass bottle]
|
16
|
+
|
17
|
+
# NOTE: We do not use mutations to insert these rows, because letting the database generate the primary key means that
|
18
|
+
# we rely on a `THEN RETURN id` clause in the insert statement. This is only supported for DML statements, and not for
|
19
|
+
# mutations.
|
20
|
+
ActiveRecord::Base.transaction do
|
21
|
+
singers = []
|
22
|
+
5.times do
|
23
|
+
singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
|
24
|
+
end
|
25
|
+
|
26
|
+
albums = []
|
27
|
+
20.times do
|
28
|
+
singer = singers.sample
|
29
|
+
albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
class Album < ActiveRecord::Base
|
8
|
+
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
9
|
+
# The primary key of `singers` is `singerid`.
|
10
|
+
belongs_to :singer, foreign_key: :singerid
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
class Singer < ActiveRecord::Base
|
8
|
+
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
9
|
+
# The primary key of `albums` is (`singerid`, `albumid`).
|
10
|
+
has_many :albums, foreign_key: :singerid
|
11
|
+
end
|
@@ -8,6 +8,11 @@ module ActiveRecord
|
|
8
8
|
module ConnectionAdapters
|
9
9
|
module Spanner
|
10
10
|
class SchemaCreation < SchemaCreation
|
11
|
+
def initialize connection
|
12
|
+
super
|
13
|
+
@connection = connection
|
14
|
+
end
|
15
|
+
|
11
16
|
private
|
12
17
|
|
13
18
|
# rubocop:disable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
@@ -128,14 +133,18 @@ module ActiveRecord
|
|
128
133
|
sql
|
129
134
|
end
|
130
135
|
|
131
|
-
# rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
132
|
-
|
133
136
|
def add_column_options! column, sql, options
|
134
137
|
if options[:null] == false || options[:primary_key] == true
|
135
138
|
sql << " NOT NULL"
|
136
139
|
end
|
137
140
|
if options.key? :default
|
138
141
|
sql << " DEFAULT (#{quote_default_expression options[:default], column})"
|
142
|
+
elsif column.type == :primary_key
|
143
|
+
if @connection.use_auto_increment?
|
144
|
+
sql << " AUTO_INCREMENT"
|
145
|
+
elsif @connection.use_identity?
|
146
|
+
sql << " GENERATED BY DEFAULT AS IDENTITY (#{@connection.default_sequence_kind})"
|
147
|
+
end
|
139
148
|
end
|
140
149
|
|
141
150
|
if !options[:allow_commit_timestamp].nil? &&
|
@@ -158,6 +167,8 @@ module ActiveRecord
|
|
158
167
|
|
159
168
|
sql
|
160
169
|
end
|
170
|
+
|
171
|
+
# rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
161
172
|
end
|
162
173
|
end
|
163
174
|
end
|
@@ -21,8 +21,6 @@ module ActiveRecord
|
|
21
21
|
# [Schema Doc](https://cloud.google.com/spanner/docs/information-schema)
|
22
22
|
#
|
23
23
|
module SchemaStatements
|
24
|
-
VERSION_6_1_0 = Gem::Version.create "6.1.0"
|
25
|
-
VERSION_6_0_3 = Gem::Version.create "6.0.3"
|
26
24
|
VERSION_7_2 = Gem::Version.create "7.2.0"
|
27
25
|
|
28
26
|
def current_database
|
@@ -125,18 +123,20 @@ module ActiveRecord
|
|
125
123
|
fetch_type_metadata(field.spanner_type,
|
126
124
|
field.ordinal_position,
|
127
125
|
field.allow_commit_timestamp,
|
128
|
-
field.generated
|
126
|
+
field.generated,
|
127
|
+
is_identity: field.is_identity),
|
129
128
|
field.nullable,
|
130
129
|
field.default_function,
|
131
130
|
primary_key: field.primary_key
|
132
131
|
end
|
133
132
|
|
134
|
-
def fetch_type_metadata sql_type, ordinal_position = nil, allow_commit_timestamp = nil, generated = nil
|
133
|
+
def fetch_type_metadata sql_type, ordinal_position = nil, allow_commit_timestamp = nil, generated = nil,
|
134
|
+
is_identity: false
|
135
135
|
Spanner::TypeMetadata.new \
|
136
136
|
super(sql_type),
|
137
137
|
ordinal_position: ordinal_position,
|
138
138
|
allow_commit_timestamp: allow_commit_timestamp,
|
139
|
-
generated: generated
|
139
|
+
generated: generated, is_identity: is_identity
|
140
140
|
end
|
141
141
|
|
142
142
|
def add_column table_name, column_name, type, **options
|
@@ -17,12 +17,15 @@ module ActiveRecord
|
|
17
17
|
attr_reader :ordinal_position
|
18
18
|
attr_reader :allow_commit_timestamp
|
19
19
|
attr_reader :generated
|
20
|
+
attr_reader :is_identity
|
20
21
|
|
21
|
-
def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil
|
22
|
+
def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil,
|
23
|
+
is_identity: false
|
22
24
|
super type_metadata
|
23
25
|
@ordinal_position = ordinal_position
|
24
26
|
@allow_commit_timestamp = allow_commit_timestamp
|
25
27
|
@generated = generated
|
28
|
+
@is_identity = is_identity
|
26
29
|
end
|
27
30
|
|
28
31
|
def == other
|
@@ -30,12 +33,13 @@ module ActiveRecord
|
|
30
33
|
__getobj__ == other.__getobj__ &&
|
31
34
|
ordinal_position == other.ordinal_position &&
|
32
35
|
allow_commit_timestamp == other.allow_commit_timestamp &&
|
33
|
-
generated == other.generated
|
36
|
+
generated == other.generated &&
|
37
|
+
is_identity == other.is_identity
|
34
38
|
end
|
35
39
|
alias eql? ==
|
36
40
|
|
37
41
|
def hash
|
38
|
-
[TypeMetadata.name, __getobj__, ordinal_position, allow_commit_timestamp, generated].hash
|
42
|
+
[TypeMetadata.name, __getobj__, ordinal_position, allow_commit_timestamp, generated, is_identity].hash
|
39
43
|
end
|
40
44
|
|
41
45
|
private
|
@@ -71,6 +71,9 @@ module ActiveRecord
|
|
71
71
|
# Determines whether or not to log query binds when executing statements
|
72
72
|
class_attribute :log_statement_binds, instance_writer: false, default: false
|
73
73
|
|
74
|
+
attr_accessor :default_sequence_kind
|
75
|
+
attr_accessor :use_client_side_id_for_mutations
|
76
|
+
|
74
77
|
def initialize config_or_deprecated_connection, deprecated_logger = nil,
|
75
78
|
deprecated_connection_options = nil, deprecated_config = nil
|
76
79
|
if config_or_deprecated_connection.is_a? Hash
|
@@ -86,6 +89,25 @@ module ActiveRecord
|
|
86
89
|
end
|
87
90
|
# Spanner does not support unprepared statements
|
88
91
|
@prepared_statements = true
|
92
|
+
# The default for default_sequence_kind will be changed to BIT_REVERSED_POSITIVE
|
93
|
+
# in the next major version. The default value is currently DISABLED to prevent
|
94
|
+
# breaking changes to existing code.
|
95
|
+
@default_sequence_kind = @config.fetch :default_sequence_kind, "DISABLED"
|
96
|
+
@use_auto_increment = @default_sequence_kind&.casecmp? "AUTO_INCREMENT"
|
97
|
+
@auto_increment_disabled = @default_sequence_kind&.casecmp? "DISABLED"
|
98
|
+
@use_client_side_id_for_mutations = self.class.type_cast_config_to_boolean(
|
99
|
+
@config.fetch(:use_client_side_id_for_mutations, false)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
def use_auto_increment?
|
104
|
+
"AUTO_INCREMENT".casecmp?(@default_sequence_kind || "")
|
105
|
+
end
|
106
|
+
|
107
|
+
def use_identity?
|
108
|
+
!use_auto_increment? \
|
109
|
+
&& @default_sequence_kind \
|
110
|
+
&& !@default_sequence_kind.casecmp?("DISABLED")
|
89
111
|
end
|
90
112
|
|
91
113
|
def max_identifier_length
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
35
35
|
return super if active_transaction?
|
36
36
|
|
37
37
|
# Only use mutations to create new records if the primary key is generated client-side.
|
38
|
-
isolation =
|
38
|
+
isolation = has_auto_generated_primary_key? ? nil : :buffered_mutations
|
39
39
|
transaction isolation: isolation do
|
40
40
|
return super
|
41
41
|
end
|
@@ -53,6 +53,21 @@ module ActiveRecord
|
|
53
53
|
!(buffered_mutations? || (primary_key && values.is_a?(Hash))) || !spanner_adapter?
|
54
54
|
end
|
55
55
|
|
56
|
+
def self.has_auto_generated_primary_key?
|
57
|
+
return true if sequence_name
|
58
|
+
pk = primary_key
|
59
|
+
if pk.is_a? Array
|
60
|
+
return pk.any? do |col|
|
61
|
+
columns_hash[col].auto_incremented_by_db?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
columns_hash[pk].auto_incremented_by_db?
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.is_auto_generated? col
|
68
|
+
columns_hash[col]&.auto_incremented_by_db?
|
69
|
+
end
|
70
|
+
|
56
71
|
def self._internal_insert_record values
|
57
72
|
if ActiveRecord.gem_version < VERSION_7_2
|
58
73
|
_insert_record values
|
@@ -73,10 +88,13 @@ module ActiveRecord
|
|
73
88
|
return super
|
74
89
|
end
|
75
90
|
|
76
|
-
# Mutations cannot be used in combination with
|
77
|
-
|
78
|
-
|
79
|
-
|
91
|
+
# Mutations cannot be used in combination with an auto-generated primary key,
|
92
|
+
# as mutations do not support a THEN RETURN clause.
|
93
|
+
if buffered_mutations? \
|
94
|
+
&& has_auto_generated_primary_key? \
|
95
|
+
&& !_has_all_primary_key_values?(primary_key, values) \
|
96
|
+
&& !connection.use_client_side_id_for_mutations
|
97
|
+
raise StatementInvalid, "Mutations cannot be used to create records that use an auto-generated primary key."
|
80
98
|
end
|
81
99
|
|
82
100
|
return _buffer_record values, :insert, returning if buffered_mutations?
|
@@ -85,7 +103,7 @@ module ActiveRecord
|
|
85
103
|
end
|
86
104
|
|
87
105
|
def self._insert_record_dml values, returning
|
88
|
-
primary_key_value = _set_primary_key_value values
|
106
|
+
primary_key_value = _set_primary_key_value values, false
|
89
107
|
if ActiveRecord::VERSION::MAJOR >= 7
|
90
108
|
im = Arel::InsertManager.new arel_table
|
91
109
|
im.insert(values.transform_keys { |name| arel_table[name] })
|
@@ -97,11 +115,11 @@ module ActiveRecord
|
|
97
115
|
_convert_primary_key result, returning
|
98
116
|
end
|
99
117
|
|
100
|
-
def self._set_primary_key_value values
|
118
|
+
def self._set_primary_key_value values, is_mutation
|
101
119
|
if primary_key.is_a? Array
|
102
|
-
_set_composite_primary_key_values primary_key, values
|
120
|
+
_set_composite_primary_key_values primary_key, values, is_mutation
|
103
121
|
else
|
104
|
-
_set_single_primary_key_value primary_key, values
|
122
|
+
_set_single_primary_key_value primary_key, values, is_mutation
|
105
123
|
end
|
106
124
|
end
|
107
125
|
|
@@ -192,9 +210,9 @@ module ActiveRecord
|
|
192
210
|
def self._buffer_record values, method, returning
|
193
211
|
primary_key_value =
|
194
212
|
if primary_key.is_a? Array
|
195
|
-
_set_composite_primary_key_values primary_key, values
|
213
|
+
_set_composite_primary_key_values primary_key, values, true
|
196
214
|
else
|
197
|
-
_set_single_primary_key_value primary_key, values
|
215
|
+
_set_single_primary_key_value primary_key, values, true
|
198
216
|
end
|
199
217
|
|
200
218
|
metadata = TableMetadata.new self, arel_table
|
@@ -213,13 +231,25 @@ module ActiveRecord
|
|
213
231
|
_convert_primary_key primary_key_value, returning
|
214
232
|
end
|
215
233
|
|
216
|
-
def self.
|
234
|
+
def self._has_all_primary_key_values? primary_key, values
|
235
|
+
if primary_key.is_a? Array
|
236
|
+
all = TrueClass
|
237
|
+
primary_key.each do |key|
|
238
|
+
all &&= values.key? key
|
239
|
+
end
|
240
|
+
all
|
241
|
+
else
|
242
|
+
values.key? primary_key
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def self._set_composite_primary_key_values primary_key, values, is_mutation
|
217
247
|
primary_key.map do |col|
|
218
|
-
_set_composite_primary_key_value col, values
|
248
|
+
_set_composite_primary_key_value col, values, is_mutation
|
219
249
|
end
|
220
250
|
end
|
221
251
|
|
222
|
-
def self._set_composite_primary_key_value primary_key, values
|
252
|
+
def self._set_composite_primary_key_value primary_key, values, is_mutation
|
223
253
|
value = values[primary_key]
|
224
254
|
type = ActiveModel::Type::BigInteger.new
|
225
255
|
|
@@ -228,6 +258,8 @@ module ActiveRecord
|
|
228
258
|
value = value.value
|
229
259
|
end
|
230
260
|
|
261
|
+
return value if is_auto_generated?(primary_key) \
|
262
|
+
&& !(is_mutation && connection.use_client_side_id_for_mutations)
|
231
263
|
return value unless prefetch_primary_key?
|
232
264
|
|
233
265
|
if value.nil?
|
@@ -244,10 +276,11 @@ module ActiveRecord
|
|
244
276
|
value
|
245
277
|
end
|
246
278
|
|
247
|
-
def self._set_single_primary_key_value primary_key, values
|
279
|
+
def self._set_single_primary_key_value primary_key, values, is_mutation
|
248
280
|
primary_key_value = values[primary_key] || values[primary_key.to_sym]
|
249
281
|
|
250
|
-
return primary_key_value if
|
282
|
+
return primary_key_value if has_auto_generated_primary_key? \
|
283
|
+
&& !(is_mutation && connection.use_client_side_id_for_mutations)
|
251
284
|
return primary_key_value unless prefetch_primary_key?
|
252
285
|
|
253
286
|
if primary_key_value.nil?
|
@@ -66,7 +66,8 @@ module ActiveRecordSpannerAdapter
|
|
66
66
|
def table_columns table_name, column_name: nil, schema_name: ""
|
67
67
|
primary_keys = table_primary_keys(table_name).map(&:name)
|
68
68
|
sql = +"SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE, GENERATION_EXPRESSION,"
|
69
|
-
sql << " CAST(COLUMN_DEFAULT AS STRING) AS COLUMN_DEFAULT, ORDINAL_POSITION"
|
69
|
+
sql << " CAST(COLUMN_DEFAULT AS STRING) AS COLUMN_DEFAULT, ORDINAL_POSITION,"
|
70
|
+
sql << " IS_IDENTITY"
|
70
71
|
sql << " FROM INFORMATION_SCHEMA.COLUMNS"
|
71
72
|
sql << " WHERE TABLE_NAME=%<table_name>s"
|
72
73
|
sql << " AND TABLE_SCHEMA=%<schema_name>s"
|
@@ -114,7 +115,8 @@ module ActiveRecordSpannerAdapter
|
|
114
115
|
default: default,
|
115
116
|
default_function: default_function,
|
116
117
|
generated: row["GENERATION_EXPRESSION"].present?,
|
117
|
-
primary_key: primary_key
|
118
|
+
primary_key: primary_key,
|
119
|
+
is_identity: row["IS_IDENTITY"] == "YES"
|
118
120
|
end
|
119
121
|
|
120
122
|
def table_column table_name, column_name, schema_name: ""
|
@@ -19,6 +19,7 @@ module ActiveRecordSpannerAdapter
|
|
19
19
|
attr_accessor :generated
|
20
20
|
attr_accessor :primary_key
|
21
21
|
attr_accessor :nullable
|
22
|
+
attr_accessor :is_identity
|
22
23
|
|
23
24
|
def initialize \
|
24
25
|
table_name,
|
@@ -32,7 +33,8 @@ module ActiveRecordSpannerAdapter
|
|
32
33
|
default: nil,
|
33
34
|
default_function: nil,
|
34
35
|
generated: nil,
|
35
|
-
primary_key: false
|
36
|
+
primary_key: false,
|
37
|
+
is_identity: false
|
36
38
|
@schema_name = schema_name.to_s
|
37
39
|
@table_name = table_name.to_s
|
38
40
|
@name = name.to_s
|
@@ -45,6 +47,7 @@ module ActiveRecordSpannerAdapter
|
|
45
47
|
@default_function = default_function
|
46
48
|
@generated = generated == true
|
47
49
|
@primary_key = primary_key
|
50
|
+
@is_identity = is_identity
|
48
51
|
end
|
49
52
|
|
50
53
|
def spanner_type
|