activerecord-spanner-adapter 1.8.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/.github/workflows/acceptance-tests-on-emulator.yaml +4 -6
- data/.github/workflows/ci.yaml +4 -6
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -6
- data/.github/workflows/nightly-unit-tests.yaml +4 -6
- data/.github/workflows/rubocop.yaml +1 -1
- data/.github/workflows/samples.yaml +30 -0
- data/.kokoro/release.cfg +2 -2
- data/.kokoro/release.sh +1 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +28 -0
- data/Gemfile +6 -5
- data/README.md +26 -22
- data/acceptance/cases/migration/command_recorder_test.rb +7 -38
- data/acceptance/cases/migration/references_index_test.rb +2 -11
- data/acceptance/cases/models/binary_identifiers.rb +97 -0
- 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/models/binary_project.rb +20 -0
- data/acceptance/models/string_io.rb +28 -0
- data/acceptance/models/user.rb +20 -0
- data/acceptance/test_helper.rb +22 -8
- data/activerecord-spanner-adapter.gemspec +3 -3
- data/benchmarks/application.rb +3 -7
- data/examples/snippets/Rakefile +28 -5
- data/examples/snippets/array-data-type/application.rb +1 -5
- data/examples/snippets/array-data-type/config/database.yml +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/examples/snippets/bit-reversed-sequence/application.rb +0 -4
- data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
- data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
- data/examples/snippets/bulk-insert/application.rb +1 -5
- data/examples/snippets/bulk-insert/config/database.yml +1 -0
- data/examples/snippets/commit-timestamp/application.rb +0 -4
- data/examples/snippets/commit-timestamp/config/database.yml +1 -0
- data/examples/snippets/config/environment.rb +5 -0
- data/examples/snippets/create-records/application.rb +1 -5
- data/examples/snippets/create-records/config/database.yml +1 -0
- data/examples/snippets/date-data-type/application.rb +1 -5
- data/examples/snippets/date-data-type/config/database.yml +1 -0
- data/examples/snippets/date-data-type/db/seeds.rb +1 -1
- data/examples/snippets/generated-column/application.rb +0 -4
- data/examples/snippets/generated-column/config/database.yml +1 -0
- data/examples/snippets/generated-column/db/seeds.rb +1 -1
- data/examples/snippets/hints/application.rb +0 -4
- data/examples/snippets/hints/config/database.yml +1 -0
- data/examples/snippets/hints/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/application.rb +1 -5
- data/examples/snippets/interleaved-tables/config/database.yml +1 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/models/album.rb +6 -2
- data/examples/snippets/interleaved-tables/models/track.rb +5 -1
- data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
- data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
- data/examples/snippets/migrations/application.rb +0 -4
- data/examples/snippets/migrations/config/database.yml +1 -0
- data/examples/snippets/mutations/application.rb +1 -5
- data/examples/snippets/mutations/config/database.yml +1 -0
- data/examples/snippets/mutations/db/seeds.rb +1 -1
- data/examples/snippets/optimistic-locking/application.rb +0 -4
- data/examples/snippets/optimistic-locking/config/database.yml +1 -0
- data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
- data/examples/snippets/partitioned-dml/application.rb +0 -4
- data/examples/snippets/partitioned-dml/config/database.yml +1 -0
- data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
- data/examples/snippets/query-logs/application.rb +15 -13
- data/examples/snippets/query-logs/config/database.yml +1 -0
- data/examples/snippets/query-logs/db/seeds.rb +1 -1
- data/examples/snippets/quickstart/application.rb +0 -4
- data/examples/snippets/quickstart/config/database.yml +1 -0
- data/examples/snippets/quickstart/db/seeds.rb +1 -1
- data/examples/snippets/read-only-transactions/application.rb +0 -4
- data/examples/snippets/read-only-transactions/config/database.yml +1 -0
- data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
- data/examples/snippets/read-write-transactions/application.rb +2 -6
- data/examples/snippets/read-write-transactions/config/database.yml +1 -0
- data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
- data/examples/snippets/stale-reads/application.rb +0 -4
- data/examples/snippets/stale-reads/config/database.yml +1 -0
- data/examples/snippets/stale-reads/db/seeds.rb +1 -1
- data/examples/snippets/tags/application.rb +0 -4
- data/examples/snippets/tags/config/database.yml +1 -0
- data/examples/snippets/tags/db/seeds.rb +1 -1
- data/examples/snippets/timestamp-data-type/application.rb +0 -4
- data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
- data/lib/active_record/connection_adapters/spanner/column.rb +7 -3
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
- data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +20 -11
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +22 -51
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +10 -8
- data/lib/active_record/connection_adapters/spanner_adapter.rb +42 -7
- data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
- data/lib/active_record/type/spanner/array.rb +4 -0
- data/lib/active_record/type/spanner/bytes.rb +10 -0
- data/lib/activerecord_spanner_adapter/base.rb +59 -32
- data/lib/activerecord_spanner_adapter/connection.rb +9 -5
- data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
- data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
- data/lib/activerecord_spanner_adapter/index.rb +10 -2
- data/lib/activerecord_spanner_adapter/information_schema.rb +5 -3
- data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
- data/lib/activerecord_spanner_adapter/table/column.rb +16 -4
- data/lib/activerecord_spanner_adapter/table.rb +8 -2
- data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +16 -11
- data/lib/spanner_client_ext.rb +4 -3
- metadata +23 -34
- data/examples/snippets/array-data-type/db/schema.rb +0 -31
- data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
- data/examples/snippets/bulk-insert/db/schema.rb +0 -31
- data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
- data/examples/snippets/create-records/db/schema.rb +0 -31
- data/examples/snippets/date-data-type/db/schema.rb +0 -26
- data/examples/snippets/generated-column/db/schema.rb +0 -26
- data/examples/snippets/hints/db/schema.rb +0 -33
- data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
- data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
- data/examples/snippets/migrations/db/schema.rb +0 -38
- data/examples/snippets/mutations/db/schema.rb +0 -32
- data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
- data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
- data/examples/snippets/query-logs/db/schema.rb +0 -31
- data/examples/snippets/quickstart/db/schema.rb +0 -31
- data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
- data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
- data/examples/snippets/stale-reads/db/schema.rb +0 -31
- data/examples/snippets/tags/db/schema.rb +0 -31
- data/examples/snippets/timestamp-data-type/db/schema.rb +0 -26
@@ -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
|
@@ -44,10 +49,7 @@ module ActiveRecord
|
|
44
49
|
else
|
45
50
|
[o.options[:primary_key]]
|
46
51
|
end
|
47
|
-
pk_names =
|
48
|
-
columns.each do |c|
|
49
|
-
pk_names.append c.to_s
|
50
|
-
end
|
52
|
+
pk_names = columns.map(&:to_s)
|
51
53
|
PrimaryKeyDefinition.new pk_names
|
52
54
|
else
|
53
55
|
pk_names = o.columns.each_with_object [] do |c, r|
|
@@ -93,8 +95,8 @@ module ActiveRecord
|
|
93
95
|
end
|
94
96
|
|
95
97
|
def visit_DropColumnDefinition o
|
96
|
-
"ALTER TABLE #{quote_table_name o.table_name} DROP" \
|
97
|
-
|
98
|
+
"ALTER TABLE #{quote_table_name o.table_name} DROP " \
|
99
|
+
"COLUMN #{quote_column_name o.name}"
|
98
100
|
end
|
99
101
|
|
100
102
|
def visit_ChangeColumnDefinition o
|
@@ -131,19 +133,23 @@ module ActiveRecord
|
|
131
133
|
sql
|
132
134
|
end
|
133
135
|
|
134
|
-
# rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
135
|
-
|
136
136
|
def add_column_options! column, sql, options
|
137
137
|
if options[:null] == false || options[:primary_key] == true
|
138
138
|
sql << " NOT NULL"
|
139
139
|
end
|
140
140
|
if options.key? :default
|
141
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
|
142
148
|
end
|
143
149
|
|
144
150
|
if !options[:allow_commit_timestamp].nil? &&
|
145
151
|
options[:column].sql_type == "TIMESTAMP"
|
146
|
-
sql << " OPTIONS (allow_commit_timestamp = "\
|
152
|
+
sql << " OPTIONS (allow_commit_timestamp = " \
|
147
153
|
"#{options[:allow_commit_timestamp]})"
|
148
154
|
end
|
149
155
|
|
@@ -153,13 +159,16 @@ module ActiveRecord
|
|
153
159
|
sql << " STORED" if options[:stored]
|
154
160
|
unless options[:stored]
|
155
161
|
raise ArgumentError, "" \
|
156
|
-
|
157
|
-
|
162
|
+
"Cloud Spanner currently does not support generated columns" \
|
163
|
+
"without the STORED option." \
|
164
|
+
"Specify 'stored: true' option for `#{options[:column].name}`"
|
158
165
|
end
|
159
166
|
end
|
160
167
|
|
161
168
|
sql
|
162
169
|
end
|
170
|
+
|
171
|
+
# rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
163
172
|
end
|
164
173
|
end
|
165
174
|
end
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
7
|
module ActiveRecord
|
8
|
-
module ConnectionAdapters
|
8
|
+
module ConnectionAdapters # :nodoc:
|
9
9
|
module Spanner
|
10
10
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
11
11
|
attr_reader :interleave_in_parent
|
@@ -62,6 +62,9 @@ module ActiveRecord
|
|
62
62
|
DropIndexDefinition = Struct.new :name
|
63
63
|
|
64
64
|
class ReferenceDefinition < ActiveRecord::ConnectionAdapters::ReferenceDefinition
|
65
|
+
# This constructor intentionally does not call super to prevent ActiveRecord
|
66
|
+
# from creating an additional secondary index for the foreign key.
|
67
|
+
# rubocop:disable Lint/MissingSuper
|
65
68
|
def initialize \
|
66
69
|
name,
|
67
70
|
polymorphic: false,
|
@@ -81,6 +84,7 @@ module ActiveRecord
|
|
81
84
|
return unless polymorphic && foreign_key
|
82
85
|
raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
|
83
86
|
end
|
87
|
+
# rubocop:enable Lint/MissingSuper
|
84
88
|
|
85
89
|
private
|
86
90
|
|
@@ -96,8 +100,13 @@ module ActiveRecord
|
|
96
100
|
end
|
97
101
|
|
98
102
|
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
99
|
-
attr_reader :null_filtered
|
103
|
+
attr_reader :null_filtered
|
104
|
+
attr_reader :interleave_in
|
105
|
+
attr_reader :storing
|
106
|
+
attr_reader :orders
|
100
107
|
|
108
|
+
# This constructor intentionally does not call super.
|
109
|
+
# rubocop:disable Lint/MissingSuper
|
101
110
|
def initialize \
|
102
111
|
table_name,
|
103
112
|
name,
|
@@ -123,6 +132,7 @@ module ActiveRecord
|
|
123
132
|
|
124
133
|
@orders = @orders.symbolize_keys
|
125
134
|
end
|
135
|
+
# rubocop:enable Lint/MissingSuper
|
126
136
|
|
127
137
|
def columns_with_order
|
128
138
|
columns.each_with_object({}) do |c, result|
|
@@ -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
|
@@ -109,8 +107,7 @@ module ActiveRecord
|
|
109
107
|
end
|
110
108
|
|
111
109
|
def rename_table _table_name, _new_name
|
112
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
113
|
-
"rename_table is not implemented"
|
110
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_table is not implemented"
|
114
111
|
end
|
115
112
|
|
116
113
|
# Column
|
@@ -126,18 +123,20 @@ module ActiveRecord
|
|
126
123
|
fetch_type_metadata(field.spanner_type,
|
127
124
|
field.ordinal_position,
|
128
125
|
field.allow_commit_timestamp,
|
129
|
-
field.generated
|
126
|
+
field.generated,
|
127
|
+
is_identity: field.is_identity),
|
130
128
|
field.nullable,
|
131
129
|
field.default_function,
|
132
130
|
primary_key: field.primary_key
|
133
131
|
end
|
134
132
|
|
135
|
-
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
|
136
135
|
Spanner::TypeMetadata.new \
|
137
136
|
super(sql_type),
|
138
137
|
ordinal_position: ordinal_position,
|
139
138
|
allow_commit_timestamp: allow_commit_timestamp,
|
140
|
-
generated: generated
|
139
|
+
generated: generated, is_identity: is_identity
|
141
140
|
end
|
142
141
|
|
143
142
|
def add_column table_name, column_name, type, **options
|
@@ -168,20 +167,14 @@ module ActiveRecord
|
|
168
167
|
execute_schema_statements statements
|
169
168
|
end
|
170
169
|
|
171
|
-
|
172
|
-
|
173
|
-
_remove_columns table_name, *column_names
|
174
|
-
end
|
175
|
-
else
|
176
|
-
def remove_columns table_name, *column_names, _type: nil, **_options
|
177
|
-
_remove_columns table_name, *column_names
|
178
|
-
end
|
170
|
+
def remove_columns table_name, *column_names, _type: nil, **_options
|
171
|
+
_remove_columns table_name, *column_names
|
179
172
|
end
|
180
173
|
|
181
174
|
def _remove_columns table_name, *column_names
|
182
175
|
if column_names.empty?
|
183
|
-
raise ArgumentError, "You must specify at least one column name. "\
|
184
|
-
|
176
|
+
raise ArgumentError, "You must specify at least one column name. " \
|
177
|
+
"Example: remove_columns(:people, :first_name)"
|
185
178
|
end
|
186
179
|
|
187
180
|
statements = []
|
@@ -193,14 +186,8 @@ module ActiveRecord
|
|
193
186
|
execute_schema_statements statements
|
194
187
|
end
|
195
188
|
|
196
|
-
|
197
|
-
|
198
|
-
_change_column table_name, column_name, type, **options
|
199
|
-
end
|
200
|
-
else
|
201
|
-
def change_column table_name, column_name, type, **options
|
202
|
-
_change_column table_name, column_name, type, **options
|
203
|
-
end
|
189
|
+
def change_column table_name, column_name, type, **options
|
190
|
+
_change_column table_name, column_name, type, **options
|
204
191
|
end
|
205
192
|
|
206
193
|
def change_column_null table_name, column_name, null, _default = nil
|
@@ -208,14 +195,12 @@ module ActiveRecord
|
|
208
195
|
end
|
209
196
|
|
210
197
|
def change_column_default _table_name, _column_name, _default_or_changes
|
211
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
212
|
-
"change column with default value not supported."
|
198
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "change column with default value not supported."
|
213
199
|
end
|
214
200
|
|
215
201
|
def rename_column table_name, column_name, new_column_name
|
216
202
|
if ActiveRecord::Base.connection.ddl_batch?
|
217
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
218
|
-
"rename_column in a DDL Batch is not supported."
|
203
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_column in a DDL Batch is not supported."
|
219
204
|
end
|
220
205
|
column = information_schema do |i|
|
221
206
|
i.table_column table_name, column_name
|
@@ -280,16 +265,9 @@ module ActiveRecord
|
|
280
265
|
execute_schema_statements schema_creation.accept(id)
|
281
266
|
end
|
282
267
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
execute "DROP INDEX #{quote_table_name index_name}"
|
287
|
-
end
|
288
|
-
else
|
289
|
-
def remove_index table_name, column_name = nil, **options
|
290
|
-
index_name = index_name_for_remove table_name, column_name, options
|
291
|
-
execute "DROP INDEX #{quote_table_name index_name}"
|
292
|
-
end
|
268
|
+
def remove_index table_name, column_name = nil, **options
|
269
|
+
index_name = index_name_for_remove table_name, column_name, options
|
270
|
+
execute "DROP INDEX #{quote_table_name index_name}"
|
293
271
|
end
|
294
272
|
|
295
273
|
def rename_index table_name, old_name, new_name
|
@@ -358,14 +336,8 @@ module ActiveRecord
|
|
358
336
|
end
|
359
337
|
end
|
360
338
|
|
361
|
-
|
362
|
-
|
363
|
-
_add_foreign_key from_table, to_table, **options
|
364
|
-
end
|
365
|
-
else
|
366
|
-
def add_foreign_key from_table, to_table, **options
|
367
|
-
_add_foreign_key from_table, to_table, **options
|
368
|
-
end
|
339
|
+
def add_foreign_key from_table, to_table, **options
|
340
|
+
_add_foreign_key from_table, to_table, **options
|
369
341
|
end
|
370
342
|
|
371
343
|
def _add_foreign_key from_table, to_table, **options
|
@@ -508,8 +480,8 @@ module ActiveRecord
|
|
508
480
|
type ||= column.type
|
509
481
|
options[:null] = column.null unless options.key? :null
|
510
482
|
|
511
|
-
if ["STRING", "BYTES"].include?
|
512
|
-
options[:limit] = column.limit
|
483
|
+
if ["STRING", "BYTES"].include?(type) && !options.key?(:limit)
|
484
|
+
options[:limit] = column.limit
|
513
485
|
end
|
514
486
|
|
515
487
|
# Only timestamp type can set commit timestamp
|
@@ -642,8 +614,7 @@ module ActiveRecord
|
|
642
614
|
end
|
643
615
|
|
644
616
|
def information_schema
|
645
|
-
info_schema =
|
646
|
-
ActiveRecordSpannerAdapter::Connection.information_schema @config
|
617
|
+
info_schema = ActiveRecordSpannerAdapter::Connection.information_schema @config
|
647
618
|
|
648
619
|
return info_schema unless block_given?
|
649
620
|
|
@@ -14,13 +14,18 @@ module ActiveRecord
|
|
14
14
|
|
15
15
|
include Deduplicable if defined?(Deduplicable)
|
16
16
|
|
17
|
-
attr_reader :ordinal_position
|
17
|
+
attr_reader :ordinal_position
|
18
|
+
attr_reader :allow_commit_timestamp
|
19
|
+
attr_reader :generated
|
20
|
+
attr_reader :is_identity
|
18
21
|
|
19
|
-
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
|
20
24
|
super type_metadata
|
21
25
|
@ordinal_position = ordinal_position
|
22
26
|
@allow_commit_timestamp = allow_commit_timestamp
|
23
27
|
@generated = generated
|
28
|
+
@is_identity = is_identity
|
24
29
|
end
|
25
30
|
|
26
31
|
def == other
|
@@ -28,16 +33,13 @@ module ActiveRecord
|
|
28
33
|
__getobj__ == other.__getobj__ &&
|
29
34
|
ordinal_position == other.ordinal_position &&
|
30
35
|
allow_commit_timestamp == other.allow_commit_timestamp &&
|
31
|
-
generated == other.generated
|
36
|
+
generated == other.generated &&
|
37
|
+
is_identity == other.is_identity
|
32
38
|
end
|
33
39
|
alias eql? ==
|
34
40
|
|
35
41
|
def hash
|
36
|
-
TypeMetadata.hash
|
37
|
-
__getobj__.hash ^
|
38
|
-
ordinal_position.hash ^
|
39
|
-
allow_commit_timestamp.hash ^
|
40
|
-
generated.hash
|
42
|
+
[TypeMetadata.name, __getobj__, ordinal_position, allow_commit_timestamp, generated, is_identity].hash
|
41
43
|
end
|
42
44
|
|
43
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
|
@@ -276,7 +298,9 @@ module ActiveRecord
|
|
276
298
|
end
|
277
299
|
|
278
300
|
def transform sql
|
279
|
-
if ActiveRecord::VERSION::MAJOR >=
|
301
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
302
|
+
preprocess_query sql
|
303
|
+
elsif ActiveRecord::VERSION::MAJOR == 7
|
280
304
|
transform_query sql
|
281
305
|
else
|
282
306
|
sql
|
@@ -284,12 +308,23 @@ module ActiveRecord
|
|
284
308
|
end
|
285
309
|
|
286
310
|
# Overwrite the standard log method to be able to translate exceptions.
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
311
|
+
# sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
|
312
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
313
|
+
def log sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
|
314
|
+
super
|
315
|
+
rescue ActiveRecord::StatementInvalid
|
316
|
+
raise
|
317
|
+
rescue StandardError => e
|
318
|
+
raise translate_exception_class(e, sql, binds)
|
319
|
+
end
|
320
|
+
else
|
321
|
+
def log sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, *args
|
322
|
+
super
|
323
|
+
rescue ActiveRecord::StatementInvalid
|
324
|
+
raise
|
325
|
+
rescue StandardError => e
|
326
|
+
raise translate_exception_class(e, sql, binds)
|
327
|
+
end
|
293
328
|
end
|
294
329
|
|
295
330
|
def translate_exception exception, message:, sql:, binds:
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
def purge
|
32
32
|
begin
|
33
33
|
drop
|
34
|
-
rescue ActiveRecord::NoDatabaseError
|
34
|
+
rescue ActiveRecord::NoDatabaseError
|
35
35
|
# ignored; create the database
|
36
36
|
end
|
37
37
|
|
@@ -51,8 +51,8 @@ module ActiveRecord
|
|
51
51
|
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
|
52
52
|
|
53
53
|
if ignore_tables.any?
|
54
|
-
index_regx = /^CREATE(.*)INDEX(.*)ON (#{ignore_tables.join
|
55
|
-
table_regx = /^CREATE TABLE (#{ignore_tables.join
|
54
|
+
index_regx = /^CREATE(.*)INDEX(.*)ON (#{ignore_tables.join '|'})\(/
|
55
|
+
table_regx = /^CREATE TABLE (#{ignore_tables.join '|'})/
|
56
56
|
end
|
57
57
|
|
58
58
|
@connection.database.ddl(force: true).each do |statement|
|
@@ -66,7 +66,7 @@ module ActiveRecord
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def structure_load filename, _extra_flags
|
69
|
-
statements = File.read(filename).split(
|
69
|
+
statements = File.read(filename).split(";").map(&:strip).reject(&:empty?)
|
70
70
|
ddls = statements.select { |s| s =~ /^(CREATE|ALTER|DROP|GRANT|REVOKE|ANALYZE)/ }
|
71
71
|
@connection.execute_ddl ddls
|
72
72
|
|
@@ -9,11 +9,15 @@ module ActiveRecord
|
|
9
9
|
module Spanner
|
10
10
|
class Array < Type::Value
|
11
11
|
attr_reader :element_type
|
12
|
+
|
12
13
|
delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :element_type
|
13
14
|
|
15
|
+
# This constructor intentionally does not call super.
|
16
|
+
# rubocop:disable Lint/MissingSuper
|
14
17
|
def initialize element_type
|
15
18
|
@element_type = element_type
|
16
19
|
end
|
20
|
+
# rubocop:enable Lint/MissingSuper
|
17
21
|
|
18
22
|
def cast value
|
19
23
|
return super if value.nil?
|
@@ -10,6 +10,16 @@ module ActiveRecord
|
|
10
10
|
module Type
|
11
11
|
module Spanner
|
12
12
|
class Bytes < ActiveRecord::Type::Binary
|
13
|
+
def deserialize value
|
14
|
+
# Set this environment variable to disable de-serializing BYTES
|
15
|
+
# to a StringIO instance.
|
16
|
+
return super if ENV["SPANNER_BYTES_DESERIALIZE_DISABLED"]
|
17
|
+
|
18
|
+
return super value if value.nil?
|
19
|
+
return StringIO.new Base64.strict_decode64(value) if value.is_a? ::String
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
13
23
|
def serialize value
|
14
24
|
return super value if value.nil?
|
15
25
|
|
@@ -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
|
|
@@ -118,12 +136,10 @@ module ActiveRecord
|
|
118
136
|
keys = returning || primary_key
|
119
137
|
return primary_key_value if keys == primary_key
|
120
138
|
|
121
|
-
primary_key_values_hash =
|
122
|
-
|
123
|
-
|
124
|
-
values.append primary_key_values_hash[column]
|
139
|
+
primary_key_values_hash = primary_key.zip(primary_key_value).to_h
|
140
|
+
keys.map do |column|
|
141
|
+
primary_key_values_hash[column]
|
125
142
|
end
|
126
|
-
values
|
127
143
|
end
|
128
144
|
|
129
145
|
def self._upsert_record values, returning
|
@@ -194,9 +210,9 @@ module ActiveRecord
|
|
194
210
|
def self._buffer_record values, method, returning
|
195
211
|
primary_key_value =
|
196
212
|
if primary_key.is_a? Array
|
197
|
-
_set_composite_primary_key_values primary_key, values
|
213
|
+
_set_composite_primary_key_values primary_key, values, true
|
198
214
|
else
|
199
|
-
_set_single_primary_key_value primary_key, values
|
215
|
+
_set_single_primary_key_value primary_key, values, true
|
200
216
|
end
|
201
217
|
|
202
218
|
metadata = TableMetadata.new self, arel_table
|
@@ -215,15 +231,25 @@ module ActiveRecord
|
|
215
231
|
_convert_primary_key primary_key_value, returning
|
216
232
|
end
|
217
233
|
|
218
|
-
def self.
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
247
|
+
primary_key.map do |col|
|
248
|
+
_set_composite_primary_key_value col, values, is_mutation
|
222
249
|
end
|
223
|
-
primary_key_value
|
224
250
|
end
|
225
251
|
|
226
|
-
def self._set_composite_primary_key_value primary_key, values
|
252
|
+
def self._set_composite_primary_key_value primary_key, values, is_mutation
|
227
253
|
value = values[primary_key]
|
228
254
|
type = ActiveModel::Type::BigInteger.new
|
229
255
|
|
@@ -232,6 +258,8 @@ module ActiveRecord
|
|
232
258
|
value = value.value
|
233
259
|
end
|
234
260
|
|
261
|
+
return value if is_auto_generated?(primary_key) \
|
262
|
+
&& !(is_mutation && connection.use_client_side_id_for_mutations)
|
235
263
|
return value unless prefetch_primary_key?
|
236
264
|
|
237
265
|
if value.nil?
|
@@ -248,10 +276,11 @@ module ActiveRecord
|
|
248
276
|
value
|
249
277
|
end
|
250
278
|
|
251
|
-
def self._set_single_primary_key_value primary_key, values
|
279
|
+
def self._set_single_primary_key_value primary_key, values, is_mutation
|
252
280
|
primary_key_value = values[primary_key] || values[primary_key.to_sym]
|
253
281
|
|
254
|
-
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)
|
255
284
|
return primary_key_value unless prefetch_primary_key?
|
256
285
|
|
257
286
|
if primary_key_value.nil?
|
@@ -419,14 +448,12 @@ module ActiveRecord
|
|
419
448
|
end
|
420
449
|
|
421
450
|
def serialize_keys metadata, keys
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
:mutation)
|
451
|
+
keys.map do |key|
|
452
|
+
ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
|
453
|
+
.serialize_with_transaction_isolation_level(metadata.type(key),
|
454
|
+
attribute_in_database(key),
|
455
|
+
:mutation)
|
428
456
|
end
|
429
|
-
serialized_values
|
430
457
|
end
|
431
458
|
|
432
459
|
def _execute_version_check attempted_action # rubocop:disable Metrics/AbcSize
|
@@ -456,7 +483,7 @@ module ActiveRecord
|
|
456
483
|
|
457
484
|
# We need to check the version using a SELECT query, as a mutation cannot include a WHERE clause.
|
458
485
|
sql = "SELECT 1 FROM `#{self.class.arel_table.name}` " \
|
459
|
-
|
486
|
+
"WHERE #{pk_sql} AND `#{locking_column}` = @lock_version"
|
460
487
|
locked_row = self.class.connection.raw_connection.execute_query sql, params: params, types: param_types
|
461
488
|
raise ActiveRecord::StaleObjectError.new(self, attempted_action) unless locked_row.rows.any?
|
462
489
|
end
|