activerecord-spanner-adapter 1.6.3 → 2.0.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 +3 -7
- data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/ci.yaml +3 -7
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -33
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/nightly-unit-tests.yaml +5 -33
- data/.github/workflows/rubocop.yaml +1 -1
- data/.github/workflows/samples.yaml +30 -0
- data/.kokoro/populate-secrets.sh +5 -1
- data/.kokoro/release.cfg +22 -12
- data/.kokoro/release.sh +1 -3
- data/.kokoro/trampoline_v2.sh +19 -11
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -2
- data/.trampolinerc +6 -1
- data/CHANGELOG.md +37 -0
- data/Gemfile +7 -5
- data/README.md +11 -9
- data/Rakefile +2 -2
- data/acceptance/cases/migration/command_recorder_test.rb +7 -38
- data/acceptance/cases/migration/references_index_test.rb +2 -11
- data/acceptance/cases/migration/schema_dumper_test.rb +21 -9
- data/acceptance/cases/models/binary_identifiers.rb +97 -0
- data/acceptance/cases/models/insert_all_test.rb +22 -7
- data/acceptance/cases/sessions/session_not_found_test.rb +2 -0
- data/acceptance/cases/tasks/database_tasks_test.rb +1 -0
- 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 +6 -1
- data/activerecord-spanner-adapter.gemspec +3 -3
- data/benchmarks/application.rb +3 -7
- data/examples/snippets/Rakefile +27 -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/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 +3 -3
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +37 -23
- data/lib/active_record/connection_adapters/spanner/quoting.rb +19 -6
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -9
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +28 -46
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
- data/lib/active_record/connection_adapters/spanner_adapter.rb +54 -27
- 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.rb +5 -1
- data/lib/activerecord_spanner_adapter/base.rb +58 -30
- 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 +1 -1
- data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
- data/lib/activerecord_spanner_adapter/table/column.rb +12 -3
- 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 +15 -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
|
@@ -21,15 +21,15 @@ module ActiveRecord
|
|
|
21
21
|
internal_execute sql, name, binds
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def internal_exec_query sql, name = "SQL", binds = [], prepare: false, async: false
|
|
25
|
-
result = internal_execute sql, name, binds, prepare: prepare, async: async
|
|
24
|
+
def internal_exec_query sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false
|
|
25
|
+
result = internal_execute sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry
|
|
26
26
|
ActiveRecord::Result.new(
|
|
27
27
|
result.fields.keys.map(&:to_s), result.rows.map(&:values)
|
|
28
28
|
)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def internal_execute sql, name = "SQL", binds = [],
|
|
32
|
-
prepare: false, async: false # rubocop:disable Lint/UnusedMethodArgument
|
|
32
|
+
prepare: false, async: false, allow_retry: false # rubocop:disable Lint/UnusedMethodArgument, /
|
|
33
33
|
statement_type = sql_statement_type sql
|
|
34
34
|
# Call `transform` to invoke any query transformers that might have been registered.
|
|
35
35
|
sql = transform sql
|
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
log_args = [sql, name]
|
|
68
|
-
log_args.
|
|
68
|
+
log_args.push binds, type_casted_binds(binds) if log_statement_binds
|
|
69
69
|
|
|
70
70
|
log(*log_args) do
|
|
71
71
|
types, params = to_types_and_params binds
|
|
@@ -83,12 +83,16 @@ module ActiveRecord
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def append_request_tag_from_query_logs sql, binds
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
possible_prefixes = [
|
|
87
|
+
"/*request_tag:true,",
|
|
88
|
+
"/*_request_tag='true',",
|
|
89
|
+
"/*_request_tag:true,",
|
|
90
|
+
"/*_request_tag='true',"
|
|
91
|
+
]
|
|
92
|
+
possible_prefixes.each do |prefix|
|
|
93
|
+
if sql.start_with? prefix
|
|
94
|
+
append_request_tag_from_query_logs_with_format sql, binds, prefix
|
|
95
|
+
end
|
|
92
96
|
end
|
|
93
97
|
end
|
|
94
98
|
|
|
@@ -101,7 +105,7 @@ module ActiveRecord
|
|
|
101
105
|
if options.request_tag == ""
|
|
102
106
|
options.request_tag = request_tag
|
|
103
107
|
else
|
|
104
|
-
options.request_tag += "
|
|
108
|
+
options.request_tag += ",#{request_tag}"
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
binds.append options
|
|
@@ -192,7 +196,7 @@ module ActiveRecord
|
|
|
192
196
|
# and this RPC can return multiple partial result sets for DML as well. Only the last partial
|
|
193
197
|
# result set will contain the statistics. Although there will never be any rows, this makes
|
|
194
198
|
# sure that the stream is fully consumed.
|
|
195
|
-
result.rows.each { |_| }
|
|
199
|
+
result.rows.each { |_| } # rubocop:disable Lint/EmptyBlock
|
|
196
200
|
return result.row_count if result.row_count
|
|
197
201
|
|
|
198
202
|
raise ActiveRecord::StatementInvalid.new(
|
|
@@ -282,12 +286,12 @@ module ActiveRecord
|
|
|
282
286
|
#
|
|
283
287
|
def begin_isolated_db_transaction isolation
|
|
284
288
|
if isolation.is_a? Hash
|
|
285
|
-
raise "Unsupported isolation level: #{isolation}" unless
|
|
289
|
+
raise "Unsupported isolation level: #{isolation}" unless
|
|
286
290
|
isolation[:timestamp] || isolation[:staleness] || isolation[:strong]
|
|
287
291
|
raise "Only one option is supported. It must be one of `timestamp`, `staleness` or `strong`." \
|
|
288
292
|
if isolation.count != 1
|
|
289
293
|
else
|
|
290
|
-
raise "Unsupported isolation level: #{isolation}" unless
|
|
294
|
+
raise "Unsupported isolation level: #{isolation}" unless
|
|
291
295
|
[:serializable, :read_only, :buffered_mutations, :pdml].include? isolation
|
|
292
296
|
end
|
|
293
297
|
|
|
@@ -312,24 +316,35 @@ module ActiveRecord
|
|
|
312
316
|
|
|
313
317
|
# Translates binds to Spanner types and params.
|
|
314
318
|
def to_types_and_params binds
|
|
315
|
-
types = binds
|
|
319
|
+
types = to_types binds
|
|
320
|
+
params = to_params binds
|
|
321
|
+
[types, params]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def to_types binds
|
|
325
|
+
binds.enum_for(:each_with_index).to_h do |bind, i|
|
|
316
326
|
type = :INT64
|
|
317
327
|
if bind.respond_to? :type
|
|
318
328
|
type = ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
|
|
319
329
|
.convert_active_model_type_to_spanner(bind.type)
|
|
320
|
-
elsif bind.
|
|
330
|
+
elsif bind.instance_of? Symbol
|
|
321
331
|
# This ensures that for example :environment is sent as the string 'environment' to Cloud Spanner.
|
|
322
332
|
type = :STRING
|
|
333
|
+
elsif bind.instance_of?(TrueClass) || bind.instance_of?(FalseClass)
|
|
334
|
+
type = :BOOL
|
|
323
335
|
end
|
|
324
336
|
[
|
|
325
337
|
# Generates binds for named parameters in the format `@p1, @p2, ...`
|
|
326
338
|
"p#{i + 1}", type
|
|
327
339
|
]
|
|
328
|
-
end
|
|
329
|
-
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def to_params binds
|
|
344
|
+
binds.enum_for(:each_with_index).to_h do |bind, i|
|
|
330
345
|
type = if bind.respond_to? :type
|
|
331
346
|
bind.type
|
|
332
|
-
elsif bind.
|
|
347
|
+
elsif bind.instance_of? Symbol
|
|
333
348
|
# This ensures that for example :environment is sent as the string 'environment' to Cloud Spanner.
|
|
334
349
|
:STRING
|
|
335
350
|
else
|
|
@@ -341,8 +356,7 @@ module ActiveRecord
|
|
|
341
356
|
.serialize_with_transaction_isolation_level(type, bind_value, :dml)
|
|
342
357
|
|
|
343
358
|
["p#{i + 1}", value]
|
|
344
|
-
end
|
|
345
|
-
[types, params]
|
|
359
|
+
end
|
|
346
360
|
end
|
|
347
361
|
|
|
348
362
|
# An insert/update/delete statement could use mutations in some specific circumstances.
|
|
@@ -351,7 +365,7 @@ module ActiveRecord
|
|
|
351
365
|
def should_use_mutation arel
|
|
352
366
|
!@connection.current_transaction.nil? \
|
|
353
367
|
&& @connection.current_transaction.isolation == :buffered_mutations \
|
|
354
|
-
&& can_use_mutation(arel)
|
|
368
|
+
&& can_use_mutation(arel)
|
|
355
369
|
end
|
|
356
370
|
|
|
357
371
|
def can_use_mutation arel
|
|
@@ -381,7 +395,7 @@ module ActiveRecord
|
|
|
381
395
|
)
|
|
382
396
|
end
|
|
383
397
|
|
|
384
|
-
COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m
|
|
398
|
+
COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m \
|
|
385
399
|
unless defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
386
400
|
COMMENT_REGEX = ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX \
|
|
387
401
|
if defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
@@ -30,20 +30,33 @@
|
|
|
30
30
|
|
|
31
31
|
module ActiveRecord
|
|
32
32
|
module ConnectionAdapters
|
|
33
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
|
34
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
|
35
|
+
|
|
36
|
+
module Quoting
|
|
37
|
+
module ClassMethods
|
|
38
|
+
# This is used for ActiveRecord v8 and higher.
|
|
39
|
+
def quote_column_name name
|
|
40
|
+
QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub '`', '``'}`".freeze
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def quote_table_name name
|
|
44
|
+
QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub '.', '`.`'}`".freeze
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
33
49
|
module Spanner
|
|
34
50
|
module Quoting
|
|
35
|
-
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
|
36
|
-
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
|
37
|
-
|
|
38
51
|
def quote_column_name name
|
|
39
|
-
QUOTED_COLUMN_NAMES[name] ||= "`#{
|
|
52
|
+
QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub '`', '``'}`".freeze
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
def quote_table_name name
|
|
43
|
-
QUOTED_TABLE_NAMES[name] ||=
|
|
56
|
+
QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub '.', '`.`'}`".freeze
|
|
44
57
|
end
|
|
45
58
|
|
|
46
|
-
STR_ESCAPE_REGX = /[\n\r'\\]
|
|
59
|
+
STR_ESCAPE_REGX = /[\n\r'\\]/
|
|
47
60
|
STR_ESCAPE_VALUES = {
|
|
48
61
|
"\n" => "\\n", "\r" => "\\r", "'" => "\\'", "\\" => "\\\\"
|
|
49
62
|
}.freeze
|
|
@@ -44,10 +44,7 @@ module ActiveRecord
|
|
|
44
44
|
else
|
|
45
45
|
[o.options[:primary_key]]
|
|
46
46
|
end
|
|
47
|
-
pk_names =
|
|
48
|
-
columns.each do |c|
|
|
49
|
-
pk_names.append c.to_s
|
|
50
|
-
end
|
|
47
|
+
pk_names = columns.map(&:to_s)
|
|
51
48
|
PrimaryKeyDefinition.new pk_names
|
|
52
49
|
else
|
|
53
50
|
pk_names = o.columns.each_with_object [] do |c, r|
|
|
@@ -93,8 +90,8 @@ module ActiveRecord
|
|
|
93
90
|
end
|
|
94
91
|
|
|
95
92
|
def visit_DropColumnDefinition o
|
|
96
|
-
"ALTER TABLE #{quote_table_name o.table_name} DROP" \
|
|
97
|
-
|
|
93
|
+
"ALTER TABLE #{quote_table_name o.table_name} DROP " \
|
|
94
|
+
"COLUMN #{quote_column_name o.name}"
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
def visit_ChangeColumnDefinition o
|
|
@@ -143,7 +140,7 @@ module ActiveRecord
|
|
|
143
140
|
|
|
144
141
|
if !options[:allow_commit_timestamp].nil? &&
|
|
145
142
|
options[:column].sql_type == "TIMESTAMP"
|
|
146
|
-
sql << " OPTIONS (allow_commit_timestamp = "\
|
|
143
|
+
sql << " OPTIONS (allow_commit_timestamp = " \
|
|
147
144
|
"#{options[:allow_commit_timestamp]})"
|
|
148
145
|
end
|
|
149
146
|
|
|
@@ -153,8 +150,9 @@ module ActiveRecord
|
|
|
153
150
|
sql << " STORED" if options[:stored]
|
|
154
151
|
unless options[:stored]
|
|
155
152
|
raise ArgumentError, "" \
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
"Cloud Spanner currently does not support generated columns" \
|
|
154
|
+
"without the STORED option." \
|
|
155
|
+
"Specify 'stored: true' option for `#{options[:column].name}`"
|
|
158
156
|
end
|
|
159
157
|
end
|
|
160
158
|
|
|
@@ -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|
|
|
@@ -23,6 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
module SchemaStatements
|
|
24
24
|
VERSION_6_1_0 = Gem::Version.create "6.1.0"
|
|
25
25
|
VERSION_6_0_3 = Gem::Version.create "6.0.3"
|
|
26
|
+
VERSION_7_2 = Gem::Version.create "7.2.0"
|
|
26
27
|
|
|
27
28
|
def current_database
|
|
28
29
|
@connection.database_id
|
|
@@ -108,8 +109,7 @@ module ActiveRecord
|
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
def rename_table _table_name, _new_name
|
|
111
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
|
112
|
-
"rename_table is not implemented"
|
|
112
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_table is not implemented"
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# Column
|
|
@@ -167,20 +167,14 @@ module ActiveRecord
|
|
|
167
167
|
execute_schema_statements statements
|
|
168
168
|
end
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
_remove_columns table_name, *column_names
|
|
173
|
-
end
|
|
174
|
-
else
|
|
175
|
-
def remove_columns table_name, *column_names, _type: nil, **_options
|
|
176
|
-
_remove_columns table_name, *column_names
|
|
177
|
-
end
|
|
170
|
+
def remove_columns table_name, *column_names, _type: nil, **_options
|
|
171
|
+
_remove_columns table_name, *column_names
|
|
178
172
|
end
|
|
179
173
|
|
|
180
174
|
def _remove_columns table_name, *column_names
|
|
181
175
|
if column_names.empty?
|
|
182
|
-
raise ArgumentError, "You must specify at least one column name. "\
|
|
183
|
-
|
|
176
|
+
raise ArgumentError, "You must specify at least one column name. " \
|
|
177
|
+
"Example: remove_columns(:people, :first_name)"
|
|
184
178
|
end
|
|
185
179
|
|
|
186
180
|
statements = []
|
|
@@ -192,14 +186,8 @@ module ActiveRecord
|
|
|
192
186
|
execute_schema_statements statements
|
|
193
187
|
end
|
|
194
188
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
_change_column table_name, column_name, type, **options
|
|
198
|
-
end
|
|
199
|
-
else
|
|
200
|
-
def change_column table_name, column_name, type, **options
|
|
201
|
-
_change_column table_name, column_name, type, **options
|
|
202
|
-
end
|
|
189
|
+
def change_column table_name, column_name, type, **options
|
|
190
|
+
_change_column table_name, column_name, type, **options
|
|
203
191
|
end
|
|
204
192
|
|
|
205
193
|
def change_column_null table_name, column_name, null, _default = nil
|
|
@@ -207,14 +195,12 @@ module ActiveRecord
|
|
|
207
195
|
end
|
|
208
196
|
|
|
209
197
|
def change_column_default _table_name, _column_name, _default_or_changes
|
|
210
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
|
211
|
-
"change column with default value not supported."
|
|
198
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "change column with default value not supported."
|
|
212
199
|
end
|
|
213
200
|
|
|
214
201
|
def rename_column table_name, column_name, new_column_name
|
|
215
202
|
if ActiveRecord::Base.connection.ddl_batch?
|
|
216
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
|
217
|
-
"rename_column in a DDL Batch is not supported."
|
|
203
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_column in a DDL Batch is not supported."
|
|
218
204
|
end
|
|
219
205
|
column = information_schema do |i|
|
|
220
206
|
i.table_column table_name, column_name
|
|
@@ -279,16 +265,9 @@ module ActiveRecord
|
|
|
279
265
|
execute_schema_statements schema_creation.accept(id)
|
|
280
266
|
end
|
|
281
267
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
execute "DROP INDEX #{quote_table_name index_name}"
|
|
286
|
-
end
|
|
287
|
-
else
|
|
288
|
-
def remove_index table_name, column_name = nil, **options
|
|
289
|
-
index_name = index_name_for_remove table_name, column_name, options
|
|
290
|
-
execute "DROP INDEX #{quote_table_name index_name}"
|
|
291
|
-
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}"
|
|
292
271
|
end
|
|
293
272
|
|
|
294
273
|
def rename_index table_name, old_name, new_name
|
|
@@ -357,14 +336,8 @@ module ActiveRecord
|
|
|
357
336
|
end
|
|
358
337
|
end
|
|
359
338
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
_add_foreign_key from_table, to_table, **options
|
|
363
|
-
end
|
|
364
|
-
else
|
|
365
|
-
def add_foreign_key from_table, to_table, **options
|
|
366
|
-
_add_foreign_key from_table, to_table, **options
|
|
367
|
-
end
|
|
339
|
+
def add_foreign_key from_table, to_table, **options
|
|
340
|
+
_add_foreign_key from_table, to_table, **options
|
|
368
341
|
end
|
|
369
342
|
|
|
370
343
|
def _add_foreign_key from_table, to_table, **options
|
|
@@ -401,6 +374,16 @@ module ActiveRecord
|
|
|
401
374
|
information_schema { |i| i.check_constraints table_name }
|
|
402
375
|
end
|
|
403
376
|
|
|
377
|
+
if ActiveRecord.gem_version >= VERSION_7_2
|
|
378
|
+
def migration_context
|
|
379
|
+
pool.migration_context
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def schema_migration
|
|
383
|
+
pool.schema_migration
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
404
387
|
def assume_migrated_upto_version version
|
|
405
388
|
version = version.to_i
|
|
406
389
|
sm_table = quote_table_name schema_migration.table_name
|
|
@@ -497,8 +480,8 @@ module ActiveRecord
|
|
|
497
480
|
type ||= column.type
|
|
498
481
|
options[:null] = column.null unless options.key? :null
|
|
499
482
|
|
|
500
|
-
if ["STRING", "BYTES"].include?
|
|
501
|
-
options[:limit] = column.limit
|
|
483
|
+
if ["STRING", "BYTES"].include?(type) && !options.key?(:limit)
|
|
484
|
+
options[:limit] = column.limit
|
|
502
485
|
end
|
|
503
486
|
|
|
504
487
|
# Only timestamp type can set commit timestamp
|
|
@@ -631,8 +614,7 @@ module ActiveRecord
|
|
|
631
614
|
end
|
|
632
615
|
|
|
633
616
|
def information_schema
|
|
634
|
-
info_schema =
|
|
635
|
-
ActiveRecordSpannerAdapter::Connection.information_schema @config
|
|
617
|
+
info_schema = ActiveRecordSpannerAdapter::Connection.information_schema @config
|
|
636
618
|
|
|
637
619
|
return info_schema unless block_given?
|
|
638
620
|
|
|
@@ -14,7 +14,9 @@ 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
|
|
18
20
|
|
|
19
21
|
def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil
|
|
20
22
|
super type_metadata
|
|
@@ -33,11 +35,7 @@ module ActiveRecord
|
|
|
33
35
|
alias eql? ==
|
|
34
36
|
|
|
35
37
|
def hash
|
|
36
|
-
TypeMetadata.hash
|
|
37
|
-
__getobj__.hash ^
|
|
38
|
-
ordinal_position.hash ^
|
|
39
|
-
allow_commit_timestamp.hash ^
|
|
40
|
-
generated.hash
|
|
38
|
+
[TypeMetadata.name, __getobj__, ordinal_position, allow_commit_timestamp, generated].hash
|
|
41
39
|
end
|
|
42
40
|
|
|
43
41
|
private
|
|
@@ -28,16 +28,18 @@ require "activerecord_spanner_adapter/primary_key"
|
|
|
28
28
|
require "activerecord_spanner_adapter/transaction"
|
|
29
29
|
|
|
30
30
|
module ActiveRecord
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
if ActiveRecord.version < Gem::Version.new("7.2")
|
|
32
|
+
module ConnectionHandling # :nodoc:
|
|
33
|
+
def spanner_connection config
|
|
34
|
+
connection = ActiveRecordSpannerAdapter::Connection.new config
|
|
35
|
+
connection.connect!
|
|
36
|
+
ConnectionAdapters::SpannerAdapter.new connection, logger, nil, config
|
|
37
|
+
rescue Google::Cloud::Error => error
|
|
38
|
+
if error.instance_of? Google::Cloud::NotFoundError
|
|
39
|
+
raise ActiveRecord::NoDatabaseError
|
|
40
|
+
end
|
|
41
|
+
raise error
|
|
42
|
+
end
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -69,11 +71,21 @@ module ActiveRecord
|
|
|
69
71
|
# Determines whether or not to log query binds when executing statements
|
|
70
72
|
class_attribute :log_statement_binds, instance_writer: false, default: false
|
|
71
73
|
|
|
72
|
-
def initialize
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
def initialize config_or_deprecated_connection, deprecated_logger = nil,
|
|
75
|
+
deprecated_connection_options = nil, deprecated_config = nil
|
|
76
|
+
if config_or_deprecated_connection.is_a? Hash
|
|
77
|
+
@connection = ActiveRecordSpannerAdapter::Connection.new config_or_deprecated_connection
|
|
78
|
+
@connection.connect!
|
|
79
|
+
super config_or_deprecated_connection
|
|
80
|
+
@raw_connection ||= @connection
|
|
81
|
+
else
|
|
82
|
+
@connection = config_or_deprecated_connection
|
|
83
|
+
@connection_options = deprecated_connection_options
|
|
84
|
+
super config_or_deprecated_connection, deprecated_logger, deprecated_config
|
|
85
|
+
@raw_connection ||= config_or_deprecated_connection
|
|
86
|
+
end
|
|
87
|
+
# Spanner does not support unprepared statements
|
|
88
|
+
@prepared_statements = true
|
|
77
89
|
end
|
|
78
90
|
|
|
79
91
|
def max_identifier_length
|
|
@@ -197,12 +209,14 @@ module ActiveRecord
|
|
|
197
209
|
raise "ActiveRecordSpannerAdapter does not support insert_sql with buffered_mutations transaction."
|
|
198
210
|
end
|
|
199
211
|
|
|
200
|
-
if insert.skip_duplicates? || insert.update_duplicates?
|
|
201
|
-
raise NotImplementedError, "CloudSpanner does not support skip_duplicates and update_duplicates."
|
|
202
|
-
end
|
|
203
|
-
|
|
204
212
|
values_list, = insert.values_list
|
|
205
|
-
|
|
213
|
+
prefix = "INSERT"
|
|
214
|
+
if insert.update_duplicates?
|
|
215
|
+
prefix += " OR UPDATE"
|
|
216
|
+
elsif insert.skip_duplicates?
|
|
217
|
+
prefix += " OR IGNORE"
|
|
218
|
+
end
|
|
219
|
+
"#{prefix} #{insert.into} #{values_list}"
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
module TypeMapBuilder
|
|
@@ -262,7 +276,9 @@ module ActiveRecord
|
|
|
262
276
|
end
|
|
263
277
|
|
|
264
278
|
def transform sql
|
|
265
|
-
if ActiveRecord::VERSION::MAJOR >=
|
|
279
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
|
280
|
+
preprocess_query sql
|
|
281
|
+
elsif ActiveRecord::VERSION::MAJOR == 7
|
|
266
282
|
transform_query sql
|
|
267
283
|
else
|
|
268
284
|
sql
|
|
@@ -270,12 +286,23 @@ module ActiveRecord
|
|
|
270
286
|
end
|
|
271
287
|
|
|
272
288
|
# Overwrite the standard log method to be able to translate exceptions.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
289
|
+
# sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
|
|
290
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
|
291
|
+
def log sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
|
|
292
|
+
super
|
|
293
|
+
rescue ActiveRecord::StatementInvalid
|
|
294
|
+
raise
|
|
295
|
+
rescue StandardError => e
|
|
296
|
+
raise translate_exception_class(e, sql, binds)
|
|
297
|
+
end
|
|
298
|
+
else
|
|
299
|
+
def log sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, *args
|
|
300
|
+
super
|
|
301
|
+
rescue ActiveRecord::StatementInvalid
|
|
302
|
+
raise
|
|
303
|
+
rescue StandardError => e
|
|
304
|
+
raise translate_exception_class(e, sql, binds)
|
|
305
|
+
end
|
|
279
306
|
end
|
|
280
307
|
|
|
281
308
|
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
|
|
|
@@ -15,7 +15,11 @@ if defined?(Rails)
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
ActiveSupport.on_load :active_record do
|
|
18
|
-
|
|
18
|
+
if Rails.version >= "7.2.0"
|
|
19
|
+
ActiveRecord::ConnectionAdapters.register("spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter")
|
|
20
|
+
else
|
|
21
|
+
require "active_record/connection_adapters/spanner_adapter"
|
|
22
|
+
end
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|