activerecord-spanner-adapter 1.8.0 → 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 +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.sh +1 -3
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +6 -5
- data/README.md +11 -9
- 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/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 +1 -0
- 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 +34 -22
- data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
- 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 +17 -46
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
- data/lib/active_record/connection_adapters/spanner_adapter.rb +20 -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 +12 -18
- 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 +13 -32
- 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
|
@@ -10,7 +10,7 @@ require_relative "models/singer"
|
|
|
10
10
|
require_relative "models/album"
|
|
11
11
|
|
|
12
12
|
class Application
|
|
13
|
-
def self.run
|
|
13
|
+
def self.run
|
|
14
14
|
from_album = nil
|
|
15
15
|
to_album = nil
|
|
16
16
|
# Use a read/write transaction to execute multiple statements as an atomic unit.
|
|
@@ -20,7 +20,7 @@ class Application
|
|
|
20
20
|
to_album = Album.where.not(id: from_album.id).sample
|
|
21
21
|
|
|
22
22
|
puts ""
|
|
23
|
-
puts "Transferring 10,000 marketing budget from #{from_album.title} (#{from_album.marketing_budget}) "\
|
|
23
|
+
puts "Transferring 10,000 marketing budget from #{from_album.title} (#{from_album.marketing_budget}) " \
|
|
24
24
|
"to #{to_album.title} (#{to_album.marketing_budget})"
|
|
25
25
|
from_album.update marketing_budget: from_album.marketing_budget - 10000
|
|
26
26
|
to_album.update marketing_budget: to_album.marketing_budget + 10000
|
|
@@ -29,10 +29,6 @@ class Application
|
|
|
29
29
|
puts "Budgets after update:"
|
|
30
30
|
puts "Marketing budget #{from_album.title}: #{from_album.reload.marketing_budget}"
|
|
31
31
|
puts "Marketing budget #{to_album.title}: #{to_album.reload.marketing_budget}"
|
|
32
|
-
|
|
33
|
-
puts ""
|
|
34
|
-
puts "Press any key to end the application"
|
|
35
|
-
STDIN.getch
|
|
36
32
|
end
|
|
37
33
|
end
|
|
38
34
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
|
5
5
|
# https://opensource.org/licenses/MIT.
|
|
6
6
|
|
|
7
|
-
require_relative "../../config/environment
|
|
7
|
+
require_relative "../../config/environment"
|
|
8
8
|
require_relative "../models/singer"
|
|
9
9
|
require_relative "../models/album"
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
|
5
5
|
# https://opensource.org/licenses/MIT.
|
|
6
6
|
|
|
7
|
-
require_relative "../../config/environment
|
|
7
|
+
require_relative "../../config/environment"
|
|
8
8
|
require_relative "../models/singer"
|
|
9
9
|
require_relative "../models/album"
|
|
10
10
|
|
|
@@ -22,10 +22,6 @@ class Application
|
|
|
22
22
|
albums = Album.annotate("request_tag: query-all-albums", "transaction_tag: sample-transaction").all
|
|
23
23
|
puts "Queried #{albums.length} albums using a request and a transaction tag"
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
puts ""
|
|
27
|
-
puts "Press any key to end the application"
|
|
28
|
-
STDIN.getch
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
|
5
5
|
# https://opensource.org/licenses/MIT.
|
|
6
6
|
|
|
7
|
-
require_relative "../../config/environment
|
|
7
|
+
require_relative "../../config/environment"
|
|
8
8
|
require_relative "../models/singer"
|
|
9
9
|
require_relative "../models/album"
|
|
10
10
|
|
|
@@ -32,10 +32,6 @@ class Application
|
|
|
32
32
|
# Simulate that the application is now running in the timezone America/Los_Angeles.
|
|
33
33
|
Time.zone = "America/Los_Angeles"
|
|
34
34
|
puts "#{'Meeting time in the local timezone (America/Los_Angeles):'.ljust 60} #{meeting.local_meeting_time}"
|
|
35
|
-
|
|
36
|
-
puts ""
|
|
37
|
-
puts "Press any key to end the application"
|
|
38
|
-
STDIN.getch
|
|
39
35
|
end
|
|
40
36
|
end
|
|
41
37
|
|
|
@@ -10,16 +10,16 @@ module ActiveRecord
|
|
|
10
10
|
module ConnectionAdapters
|
|
11
11
|
module Spanner
|
|
12
12
|
class Column < ConnectionAdapters::Column
|
|
13
|
-
# rubocop:disable Style/
|
|
13
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
14
14
|
def initialize(name, default, sql_type_metadata = nil, null = true,
|
|
15
15
|
default_function = nil, collation: nil, comment: nil,
|
|
16
16
|
primary_key: false, **)
|
|
17
|
-
# rubocop:enable Style/
|
|
17
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
18
18
|
super
|
|
19
19
|
@primary_key = primary_key
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def has_default?
|
|
22
|
+
def has_default?
|
|
23
23
|
super && !virtual?
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def internal_execute sql, name = "SQL", binds = [],
|
|
32
|
-
prepare: false, async: false, allow_retry: 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,26 +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
|
|
323
|
-
elsif bind.
|
|
333
|
+
elsif bind.instance_of?(TrueClass) || bind.instance_of?(FalseClass)
|
|
324
334
|
type = :BOOL
|
|
325
335
|
end
|
|
326
336
|
[
|
|
327
337
|
# Generates binds for named parameters in the format `@p1, @p2, ...`
|
|
328
338
|
"p#{i + 1}", type
|
|
329
339
|
]
|
|
330
|
-
end
|
|
331
|
-
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def to_params binds
|
|
344
|
+
binds.enum_for(:each_with_index).to_h do |bind, i|
|
|
332
345
|
type = if bind.respond_to? :type
|
|
333
346
|
bind.type
|
|
334
|
-
elsif bind.
|
|
347
|
+
elsif bind.instance_of? Symbol
|
|
335
348
|
# This ensures that for example :environment is sent as the string 'environment' to Cloud Spanner.
|
|
336
349
|
:STRING
|
|
337
350
|
else
|
|
@@ -343,8 +356,7 @@ module ActiveRecord
|
|
|
343
356
|
.serialize_with_transaction_isolation_level(type, bind_value, :dml)
|
|
344
357
|
|
|
345
358
|
["p#{i + 1}", value]
|
|
346
|
-
end
|
|
347
|
-
[types, params]
|
|
359
|
+
end
|
|
348
360
|
end
|
|
349
361
|
|
|
350
362
|
# An insert/update/delete statement could use mutations in some specific circumstances.
|
|
@@ -353,7 +365,7 @@ module ActiveRecord
|
|
|
353
365
|
def should_use_mutation arel
|
|
354
366
|
!@connection.current_transaction.nil? \
|
|
355
367
|
&& @connection.current_transaction.isolation == :buffered_mutations \
|
|
356
|
-
&& can_use_mutation(arel)
|
|
368
|
+
&& can_use_mutation(arel)
|
|
357
369
|
end
|
|
358
370
|
|
|
359
371
|
def can_use_mutation arel
|
|
@@ -383,7 +395,7 @@ module ActiveRecord
|
|
|
383
395
|
)
|
|
384
396
|
end
|
|
385
397
|
|
|
386
|
-
COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m
|
|
398
|
+
COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m \
|
|
387
399
|
unless defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
388
400
|
COMMENT_REGEX = ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX \
|
|
389
401
|
if defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
@@ -45,6 +45,7 @@ module ActiveRecord
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
+
|
|
48
49
|
module Spanner
|
|
49
50
|
module Quoting
|
|
50
51
|
def quote_column_name name
|
|
@@ -55,7 +56,7 @@ module ActiveRecord
|
|
|
55
56
|
QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub '.', '`.`'}`".freeze
|
|
56
57
|
end
|
|
57
58
|
|
|
58
|
-
STR_ESCAPE_REGX = /[\n\r'\\]
|
|
59
|
+
STR_ESCAPE_REGX = /[\n\r'\\]/
|
|
59
60
|
STR_ESCAPE_VALUES = {
|
|
60
61
|
"\n" => "\\n", "\r" => "\\r", "'" => "\\'", "\\" => "\\\\"
|
|
61
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|
|
|
@@ -109,8 +109,7 @@ module ActiveRecord
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def rename_table _table_name, _new_name
|
|
112
|
-
raise ActiveRecordSpannerAdapter::NotSupportedError,
|
|
113
|
-
"rename_table is not implemented"
|
|
112
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_table is not implemented"
|
|
114
113
|
end
|
|
115
114
|
|
|
116
115
|
# Column
|
|
@@ -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,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
|
|
@@ -276,7 +276,9 @@ module ActiveRecord
|
|
|
276
276
|
end
|
|
277
277
|
|
|
278
278
|
def transform sql
|
|
279
|
-
if ActiveRecord::VERSION::MAJOR >=
|
|
279
|
+
if ActiveRecord::VERSION::MAJOR >= 8
|
|
280
|
+
preprocess_query sql
|
|
281
|
+
elsif ActiveRecord::VERSION::MAJOR == 7
|
|
280
282
|
transform_query sql
|
|
281
283
|
else
|
|
282
284
|
sql
|
|
@@ -284,12 +286,23 @@ module ActiveRecord
|
|
|
284
286
|
end
|
|
285
287
|
|
|
286
288
|
# Overwrite the standard log method to be able to translate exceptions.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
293
306
|
end
|
|
294
307
|
|
|
295
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
|
|