activerecord-spanner-adapter 1.8.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +4 -6
  3. data/.github/workflows/ci.yaml +4 -6
  4. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -6
  5. data/.github/workflows/nightly-unit-tests.yaml +4 -6
  6. data/.github/workflows/rubocop.yaml +1 -1
  7. data/.github/workflows/samples.yaml +30 -0
  8. data/.kokoro/release.sh +1 -3
  9. data/.release-please-manifest.json +1 -1
  10. data/.rubocop.yml +2 -2
  11. data/CHANGELOG.md +18 -0
  12. data/Gemfile +6 -5
  13. data/README.md +11 -9
  14. data/acceptance/cases/migration/command_recorder_test.rb +7 -38
  15. data/acceptance/cases/migration/references_index_test.rb +2 -11
  16. data/acceptance/cases/models/binary_identifiers.rb +97 -0
  17. data/acceptance/models/binary_project.rb +20 -0
  18. data/acceptance/models/string_io.rb +28 -0
  19. data/acceptance/models/user.rb +20 -0
  20. data/acceptance/test_helper.rb +1 -0
  21. data/activerecord-spanner-adapter.gemspec +3 -3
  22. data/benchmarks/application.rb +3 -7
  23. data/examples/snippets/Rakefile +27 -5
  24. data/examples/snippets/array-data-type/application.rb +1 -5
  25. data/examples/snippets/array-data-type/config/database.yml +1 -0
  26. data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
  27. data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
  28. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
  29. data/examples/snippets/bulk-insert/application.rb +1 -5
  30. data/examples/snippets/bulk-insert/config/database.yml +1 -0
  31. data/examples/snippets/commit-timestamp/application.rb +0 -4
  32. data/examples/snippets/commit-timestamp/config/database.yml +1 -0
  33. data/examples/snippets/config/environment.rb +5 -0
  34. data/examples/snippets/create-records/application.rb +1 -5
  35. data/examples/snippets/create-records/config/database.yml +1 -0
  36. data/examples/snippets/date-data-type/application.rb +1 -5
  37. data/examples/snippets/date-data-type/config/database.yml +1 -0
  38. data/examples/snippets/date-data-type/db/seeds.rb +1 -1
  39. data/examples/snippets/generated-column/application.rb +0 -4
  40. data/examples/snippets/generated-column/config/database.yml +1 -0
  41. data/examples/snippets/generated-column/db/seeds.rb +1 -1
  42. data/examples/snippets/hints/application.rb +0 -4
  43. data/examples/snippets/hints/config/database.yml +1 -0
  44. data/examples/snippets/hints/db/seeds.rb +1 -1
  45. data/examples/snippets/interleaved-tables/application.rb +1 -5
  46. data/examples/snippets/interleaved-tables/config/database.yml +1 -0
  47. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  48. data/examples/snippets/interleaved-tables/models/album.rb +6 -2
  49. data/examples/snippets/interleaved-tables/models/track.rb +5 -1
  50. data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
  51. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
  52. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
  53. data/examples/snippets/migrations/application.rb +0 -4
  54. data/examples/snippets/migrations/config/database.yml +1 -0
  55. data/examples/snippets/mutations/application.rb +1 -5
  56. data/examples/snippets/mutations/config/database.yml +1 -0
  57. data/examples/snippets/mutations/db/seeds.rb +1 -1
  58. data/examples/snippets/optimistic-locking/application.rb +0 -4
  59. data/examples/snippets/optimistic-locking/config/database.yml +1 -0
  60. data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
  61. data/examples/snippets/partitioned-dml/application.rb +0 -4
  62. data/examples/snippets/partitioned-dml/config/database.yml +1 -0
  63. data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
  64. data/examples/snippets/query-logs/application.rb +15 -13
  65. data/examples/snippets/query-logs/config/database.yml +1 -0
  66. data/examples/snippets/query-logs/db/seeds.rb +1 -1
  67. data/examples/snippets/quickstart/application.rb +0 -4
  68. data/examples/snippets/quickstart/config/database.yml +1 -0
  69. data/examples/snippets/quickstart/db/seeds.rb +1 -1
  70. data/examples/snippets/read-only-transactions/application.rb +0 -4
  71. data/examples/snippets/read-only-transactions/config/database.yml +1 -0
  72. data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
  73. data/examples/snippets/read-write-transactions/application.rb +2 -6
  74. data/examples/snippets/read-write-transactions/config/database.yml +1 -0
  75. data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
  76. data/examples/snippets/stale-reads/application.rb +0 -4
  77. data/examples/snippets/stale-reads/config/database.yml +1 -0
  78. data/examples/snippets/stale-reads/db/seeds.rb +1 -1
  79. data/examples/snippets/tags/application.rb +0 -4
  80. data/examples/snippets/tags/config/database.yml +1 -0
  81. data/examples/snippets/tags/db/seeds.rb +1 -1
  82. data/examples/snippets/timestamp-data-type/application.rb +0 -4
  83. data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
  84. data/lib/active_record/connection_adapters/spanner/column.rb +3 -3
  85. data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
  86. data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
  87. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -9
  88. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
  89. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +17 -46
  90. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
  91. data/lib/active_record/connection_adapters/spanner_adapter.rb +20 -7
  92. data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
  93. data/lib/active_record/type/spanner/array.rb +4 -0
  94. data/lib/active_record/type/spanner/bytes.rb +10 -0
  95. data/lib/activerecord_spanner_adapter/base.rb +12 -18
  96. data/lib/activerecord_spanner_adapter/connection.rb +9 -5
  97. data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
  98. data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
  99. data/lib/activerecord_spanner_adapter/index.rb +10 -2
  100. data/lib/activerecord_spanner_adapter/information_schema.rb +1 -1
  101. data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
  102. data/lib/activerecord_spanner_adapter/table/column.rb +12 -3
  103. data/lib/activerecord_spanner_adapter/table.rb +8 -2
  104. data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
  105. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  106. data/lib/arel/visitors/spanner.rb +16 -11
  107. data/lib/spanner_client_ext.rb +4 -3
  108. metadata +13 -32
  109. data/examples/snippets/array-data-type/db/schema.rb +0 -31
  110. data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
  111. data/examples/snippets/bulk-insert/db/schema.rb +0 -31
  112. data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
  113. data/examples/snippets/create-records/db/schema.rb +0 -31
  114. data/examples/snippets/date-data-type/db/schema.rb +0 -26
  115. data/examples/snippets/generated-column/db/schema.rb +0 -26
  116. data/examples/snippets/hints/db/schema.rb +0 -33
  117. data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
  118. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
  119. data/examples/snippets/migrations/db/schema.rb +0 -38
  120. data/examples/snippets/mutations/db/schema.rb +0 -32
  121. data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
  122. data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
  123. data/examples/snippets/query-logs/db/schema.rb +0 -31
  124. data/examples/snippets/quickstart/db/schema.rb +0 -31
  125. data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
  126. data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
  127. data/examples/snippets/stale-reads/db/schema.rb +0 -31
  128. data/examples/snippets/tags/db/schema.rb +0 -31
  129. 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 # rubocop:disable Metrics/AbcSize
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
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -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.rb"
7
+ require_relative "../../config/environment"
8
8
  require_relative "../models/singer"
9
9
  require_relative "../models/album"
10
10
 
@@ -53,10 +53,6 @@ class Application
53
53
  puts ""
54
54
  puts "Updated album title: #{album.title}"
55
55
  puts "Title somewhere during the last 10 seconds: #{album_max_staleness&.title}"
56
-
57
- puts ""
58
- puts "Press any key to end the application"
59
- STDIN.getch
60
56
  end
61
57
  end
62
58
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -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.rb"
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
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -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.rb"
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
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -10,16 +10,16 @@ module ActiveRecord
10
10
  module ConnectionAdapters
11
11
  module Spanner
12
12
  class Column < ConnectionAdapters::Column
13
- # rubocop:disable Style/MethodDefParentheses
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/MethodDefParentheses
17
+ # rubocop:enable Style/OptionalBooleanParameter
18
18
  super
19
19
  @primary_key = primary_key
20
20
  end
21
21
 
22
- def has_default? # rubocop:disable Naming/PredicateName
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, Metrics/LineLength
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.concat [binds, type_casted_binds(binds)] if log_statement_binds
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
- legacy_formatter_prefix = "/*request_tag:true,"
87
- sql_commenter_prefix = "/*request_tag='true',"
88
- if sql.start_with? legacy_formatter_prefix
89
- append_request_tag_from_query_logs_with_format sql, binds, legacy_formatter_prefix
90
- elsif sql.start_with? sql_commenter_prefix
91
- append_request_tag_from_query_logs_with_format sql, binds, sql_commenter_prefix
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 += "," + 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.enum_for(:each_with_index).map do |bind, i|
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.class == Symbol
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.class == TrueClass || bind.class == FalseClass
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.to_h
331
- params = binds.enum_for(:each_with_index).map do |bind, i|
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.class == Symbol
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.to_h
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.freeze \
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'\\]/.freeze
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
- " COLUMN #{quote_column_name o.name}"
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
- "Cloud Spanner currently does not support generated columns without the STORED option." \
157
- "Specify 'stored: true' option for `#{options[:column].name}`"
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 #:nodoc:
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, :interleave_in, :storing, :orders
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
- if ActiveRecord.gem_version < VERSION_6_1_0
172
- def remove_columns table_name, *column_names
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
- "Example: remove_columns(:people, :first_name)"
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
- if ActiveRecord.gem_version < VERSION_6_1_0
197
- def change_column table_name, column_name, type, options = {}
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
- if ActiveRecord.gem_version < VERSION_6_1_0
284
- def remove_index table_name, options = {}
285
- index_name = index_name_for_remove table_name, options
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
- if ActiveRecord.gem_version < VERSION_6_0_3
362
- def add_foreign_key from_table, to_table, options = {}
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? type
512
- options[:limit] = column.limit unless options.key? :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, :allow_commit_timestamp, :generated
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 >= 7
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
- def log sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, *args
288
- super
289
- rescue ActiveRecord::StatementInvalid
290
- raise
291
- rescue StandardError => e
292
- raise translate_exception_class(e, sql, binds)
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 # rubocop:disable Lint/HandleExceptions
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(/;/).map(&:strip).reject(&:empty?)
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