activerecord-spanner-adapter 1.8.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) 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.cfg +2 -2
  9. data/.kokoro/release.sh +1 -4
  10. data/.release-please-manifest.json +1 -1
  11. data/.rubocop.yml +2 -2
  12. data/CHANGELOG.md +28 -0
  13. data/Gemfile +6 -5
  14. data/README.md +26 -22
  15. data/acceptance/cases/migration/command_recorder_test.rb +7 -38
  16. data/acceptance/cases/migration/references_index_test.rb +2 -11
  17. data/acceptance/cases/models/binary_identifiers.rb +97 -0
  18. data/acceptance/cases/models/default_value_test.rb +2 -2
  19. data/acceptance/cases/tasks/database_tasks_test.rb +71 -74
  20. data/acceptance/cases/transactions/read_write_transactions_test.rb +10 -4
  21. data/acceptance/models/binary_project.rb +20 -0
  22. data/acceptance/models/string_io.rb +28 -0
  23. data/acceptance/models/user.rb +20 -0
  24. data/acceptance/test_helper.rb +22 -8
  25. data/activerecord-spanner-adapter.gemspec +3 -3
  26. data/benchmarks/application.rb +3 -7
  27. data/examples/snippets/Rakefile +28 -5
  28. data/examples/snippets/array-data-type/application.rb +1 -5
  29. data/examples/snippets/array-data-type/config/database.yml +1 -0
  30. data/examples/snippets/auto-generated-primary-key/README.md +140 -0
  31. data/examples/snippets/auto-generated-primary-key/Rakefile +13 -0
  32. data/examples/snippets/auto-generated-primary-key/application.rb +86 -0
  33. data/examples/snippets/auto-generated-primary-key/config/database.yml +10 -0
  34. data/examples/snippets/auto-generated-primary-key/db/migrate/01_create_tables.rb +29 -0
  35. data/examples/snippets/auto-generated-primary-key/db/seeds.rb +31 -0
  36. data/examples/snippets/auto-generated-primary-key/models/album.rb +11 -0
  37. data/examples/snippets/auto-generated-primary-key/models/singer.rb +11 -0
  38. data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
  39. data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
  40. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
  41. data/examples/snippets/bulk-insert/application.rb +1 -5
  42. data/examples/snippets/bulk-insert/config/database.yml +1 -0
  43. data/examples/snippets/commit-timestamp/application.rb +0 -4
  44. data/examples/snippets/commit-timestamp/config/database.yml +1 -0
  45. data/examples/snippets/config/environment.rb +5 -0
  46. data/examples/snippets/create-records/application.rb +1 -5
  47. data/examples/snippets/create-records/config/database.yml +1 -0
  48. data/examples/snippets/date-data-type/application.rb +1 -5
  49. data/examples/snippets/date-data-type/config/database.yml +1 -0
  50. data/examples/snippets/date-data-type/db/seeds.rb +1 -1
  51. data/examples/snippets/generated-column/application.rb +0 -4
  52. data/examples/snippets/generated-column/config/database.yml +1 -0
  53. data/examples/snippets/generated-column/db/seeds.rb +1 -1
  54. data/examples/snippets/hints/application.rb +0 -4
  55. data/examples/snippets/hints/config/database.yml +1 -0
  56. data/examples/snippets/hints/db/seeds.rb +1 -1
  57. data/examples/snippets/interleaved-tables/application.rb +1 -5
  58. data/examples/snippets/interleaved-tables/config/database.yml +1 -0
  59. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  60. data/examples/snippets/interleaved-tables/models/album.rb +6 -2
  61. data/examples/snippets/interleaved-tables/models/track.rb +5 -1
  62. data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
  63. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
  64. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
  65. data/examples/snippets/migrations/application.rb +0 -4
  66. data/examples/snippets/migrations/config/database.yml +1 -0
  67. data/examples/snippets/mutations/application.rb +1 -5
  68. data/examples/snippets/mutations/config/database.yml +1 -0
  69. data/examples/snippets/mutations/db/seeds.rb +1 -1
  70. data/examples/snippets/optimistic-locking/application.rb +0 -4
  71. data/examples/snippets/optimistic-locking/config/database.yml +1 -0
  72. data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
  73. data/examples/snippets/partitioned-dml/application.rb +0 -4
  74. data/examples/snippets/partitioned-dml/config/database.yml +1 -0
  75. data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
  76. data/examples/snippets/query-logs/application.rb +15 -13
  77. data/examples/snippets/query-logs/config/database.yml +1 -0
  78. data/examples/snippets/query-logs/db/seeds.rb +1 -1
  79. data/examples/snippets/quickstart/application.rb +0 -4
  80. data/examples/snippets/quickstart/config/database.yml +1 -0
  81. data/examples/snippets/quickstart/db/seeds.rb +1 -1
  82. data/examples/snippets/read-only-transactions/application.rb +0 -4
  83. data/examples/snippets/read-only-transactions/config/database.yml +1 -0
  84. data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
  85. data/examples/snippets/read-write-transactions/application.rb +2 -6
  86. data/examples/snippets/read-write-transactions/config/database.yml +1 -0
  87. data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
  88. data/examples/snippets/stale-reads/application.rb +0 -4
  89. data/examples/snippets/stale-reads/config/database.yml +1 -0
  90. data/examples/snippets/stale-reads/db/seeds.rb +1 -1
  91. data/examples/snippets/tags/application.rb +0 -4
  92. data/examples/snippets/tags/config/database.yml +1 -0
  93. data/examples/snippets/tags/db/seeds.rb +1 -1
  94. data/examples/snippets/timestamp-data-type/application.rb +0 -4
  95. data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
  96. data/lib/active_record/connection_adapters/spanner/column.rb +7 -3
  97. data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
  98. data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
  99. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +20 -11
  100. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
  101. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +22 -51
  102. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +10 -8
  103. data/lib/active_record/connection_adapters/spanner_adapter.rb +42 -7
  104. data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
  105. data/lib/active_record/type/spanner/array.rb +4 -0
  106. data/lib/active_record/type/spanner/bytes.rb +10 -0
  107. data/lib/activerecord_spanner_adapter/base.rb +59 -32
  108. data/lib/activerecord_spanner_adapter/connection.rb +9 -5
  109. data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
  110. data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
  111. data/lib/activerecord_spanner_adapter/index.rb +10 -2
  112. data/lib/activerecord_spanner_adapter/information_schema.rb +5 -3
  113. data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
  114. data/lib/activerecord_spanner_adapter/table/column.rb +16 -4
  115. data/lib/activerecord_spanner_adapter/table.rb +8 -2
  116. data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
  117. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  118. data/lib/arel/visitors/spanner.rb +16 -11
  119. data/lib/spanner_client_ext.rb +4 -3
  120. metadata +23 -34
  121. data/examples/snippets/array-data-type/db/schema.rb +0 -31
  122. data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
  123. data/examples/snippets/bulk-insert/db/schema.rb +0 -31
  124. data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
  125. data/examples/snippets/create-records/db/schema.rb +0 -31
  126. data/examples/snippets/date-data-type/db/schema.rb +0 -26
  127. data/examples/snippets/generated-column/db/schema.rb +0 -26
  128. data/examples/snippets/hints/db/schema.rb +0 -33
  129. data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
  130. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
  131. data/examples/snippets/migrations/db/schema.rb +0 -38
  132. data/examples/snippets/mutations/db/schema.rb +0 -32
  133. data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
  134. data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
  135. data/examples/snippets/query-logs/db/schema.rb +0 -31
  136. data/examples/snippets/quickstart/db/schema.rb +0 -31
  137. data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
  138. data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
  139. data/examples/snippets/stale-reads/db/schema.rb +0 -31
  140. data/examples/snippets/tags/db/schema.rb +0 -31
  141. data/examples/snippets/timestamp-data-type/db/schema.rb +0 -26
@@ -8,6 +8,11 @@ module ActiveRecord
8
8
  module ConnectionAdapters
9
9
  module Spanner
10
10
  class SchemaCreation < SchemaCreation
11
+ def initialize connection
12
+ super
13
+ @connection = connection
14
+ end
15
+
11
16
  private
12
17
 
13
18
  # rubocop:disable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -44,10 +49,7 @@ module ActiveRecord
44
49
  else
45
50
  [o.options[:primary_key]]
46
51
  end
47
- pk_names = []
48
- columns.each do |c|
49
- pk_names.append c.to_s
50
- end
52
+ pk_names = columns.map(&:to_s)
51
53
  PrimaryKeyDefinition.new pk_names
52
54
  else
53
55
  pk_names = o.columns.each_with_object [] do |c, r|
@@ -93,8 +95,8 @@ module ActiveRecord
93
95
  end
94
96
 
95
97
  def visit_DropColumnDefinition o
96
- "ALTER TABLE #{quote_table_name o.table_name} DROP" \
97
- " COLUMN #{quote_column_name o.name}"
98
+ "ALTER TABLE #{quote_table_name o.table_name} DROP " \
99
+ "COLUMN #{quote_column_name o.name}"
98
100
  end
99
101
 
100
102
  def visit_ChangeColumnDefinition o
@@ -131,19 +133,23 @@ module ActiveRecord
131
133
  sql
132
134
  end
133
135
 
134
- # rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
135
-
136
136
  def add_column_options! column, sql, options
137
137
  if options[:null] == false || options[:primary_key] == true
138
138
  sql << " NOT NULL"
139
139
  end
140
140
  if options.key? :default
141
141
  sql << " DEFAULT (#{quote_default_expression options[:default], column})"
142
+ elsif column.type == :primary_key
143
+ if @connection.use_auto_increment?
144
+ sql << " AUTO_INCREMENT"
145
+ elsif @connection.use_identity?
146
+ sql << " GENERATED BY DEFAULT AS IDENTITY (#{@connection.default_sequence_kind})"
147
+ end
142
148
  end
143
149
 
144
150
  if !options[:allow_commit_timestamp].nil? &&
145
151
  options[:column].sql_type == "TIMESTAMP"
146
- sql << " OPTIONS (allow_commit_timestamp = "\
152
+ sql << " OPTIONS (allow_commit_timestamp = " \
147
153
  "#{options[:allow_commit_timestamp]})"
148
154
  end
149
155
 
@@ -153,13 +159,16 @@ module ActiveRecord
153
159
  sql << " STORED" if options[:stored]
154
160
  unless options[:stored]
155
161
  raise ArgumentError, "" \
156
- "Cloud Spanner currently does not support generated columns without the STORED option." \
157
- "Specify 'stored: true' option for `#{options[:column].name}`"
162
+ "Cloud Spanner currently does not support generated columns" \
163
+ "without the STORED option." \
164
+ "Specify 'stored: true' option for `#{options[:column].name}`"
158
165
  end
159
166
  end
160
167
 
161
168
  sql
162
169
  end
170
+
171
+ # rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
163
172
  end
164
173
  end
165
174
  end
@@ -5,7 +5,7 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecord
8
- module ConnectionAdapters #: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|
@@ -21,8 +21,6 @@ module ActiveRecord
21
21
  # [Schema Doc](https://cloud.google.com/spanner/docs/information-schema)
22
22
  #
23
23
  module SchemaStatements
24
- VERSION_6_1_0 = Gem::Version.create "6.1.0"
25
- VERSION_6_0_3 = Gem::Version.create "6.0.3"
26
24
  VERSION_7_2 = Gem::Version.create "7.2.0"
27
25
 
28
26
  def current_database
@@ -109,8 +107,7 @@ module ActiveRecord
109
107
  end
110
108
 
111
109
  def rename_table _table_name, _new_name
112
- raise ActiveRecordSpannerAdapter::NotSupportedError, \
113
- "rename_table is not implemented"
110
+ raise ActiveRecordSpannerAdapter::NotSupportedError, "rename_table is not implemented"
114
111
  end
115
112
 
116
113
  # Column
@@ -126,18 +123,20 @@ module ActiveRecord
126
123
  fetch_type_metadata(field.spanner_type,
127
124
  field.ordinal_position,
128
125
  field.allow_commit_timestamp,
129
- field.generated),
126
+ field.generated,
127
+ is_identity: field.is_identity),
130
128
  field.nullable,
131
129
  field.default_function,
132
130
  primary_key: field.primary_key
133
131
  end
134
132
 
135
- def fetch_type_metadata sql_type, ordinal_position = nil, allow_commit_timestamp = nil, generated = nil
133
+ def fetch_type_metadata sql_type, ordinal_position = nil, allow_commit_timestamp = nil, generated = nil,
134
+ is_identity: false
136
135
  Spanner::TypeMetadata.new \
137
136
  super(sql_type),
138
137
  ordinal_position: ordinal_position,
139
138
  allow_commit_timestamp: allow_commit_timestamp,
140
- generated: generated
139
+ generated: generated, is_identity: is_identity
141
140
  end
142
141
 
143
142
  def add_column table_name, column_name, type, **options
@@ -168,20 +167,14 @@ module ActiveRecord
168
167
  execute_schema_statements statements
169
168
  end
170
169
 
171
- 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,13 +14,18 @@ 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
20
+ attr_reader :is_identity
18
21
 
19
- def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil
22
+ def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil,
23
+ is_identity: false
20
24
  super type_metadata
21
25
  @ordinal_position = ordinal_position
22
26
  @allow_commit_timestamp = allow_commit_timestamp
23
27
  @generated = generated
28
+ @is_identity = is_identity
24
29
  end
25
30
 
26
31
  def == other
@@ -28,16 +33,13 @@ module ActiveRecord
28
33
  __getobj__ == other.__getobj__ &&
29
34
  ordinal_position == other.ordinal_position &&
30
35
  allow_commit_timestamp == other.allow_commit_timestamp &&
31
- generated == other.generated
36
+ generated == other.generated &&
37
+ is_identity == other.is_identity
32
38
  end
33
39
  alias eql? ==
34
40
 
35
41
  def hash
36
- TypeMetadata.hash ^
37
- __getobj__.hash ^
38
- ordinal_position.hash ^
39
- allow_commit_timestamp.hash ^
40
- generated.hash
42
+ [TypeMetadata.name, __getobj__, ordinal_position, allow_commit_timestamp, generated, is_identity].hash
41
43
  end
42
44
 
43
45
  private
@@ -71,6 +71,9 @@ module ActiveRecord
71
71
  # Determines whether or not to log query binds when executing statements
72
72
  class_attribute :log_statement_binds, instance_writer: false, default: false
73
73
 
74
+ attr_accessor :default_sequence_kind
75
+ attr_accessor :use_client_side_id_for_mutations
76
+
74
77
  def initialize config_or_deprecated_connection, deprecated_logger = nil,
75
78
  deprecated_connection_options = nil, deprecated_config = nil
76
79
  if config_or_deprecated_connection.is_a? Hash
@@ -86,6 +89,25 @@ module ActiveRecord
86
89
  end
87
90
  # Spanner does not support unprepared statements
88
91
  @prepared_statements = true
92
+ # The default for default_sequence_kind will be changed to BIT_REVERSED_POSITIVE
93
+ # in the next major version. The default value is currently DISABLED to prevent
94
+ # breaking changes to existing code.
95
+ @default_sequence_kind = @config.fetch :default_sequence_kind, "DISABLED"
96
+ @use_auto_increment = @default_sequence_kind&.casecmp? "AUTO_INCREMENT"
97
+ @auto_increment_disabled = @default_sequence_kind&.casecmp? "DISABLED"
98
+ @use_client_side_id_for_mutations = self.class.type_cast_config_to_boolean(
99
+ @config.fetch(:use_client_side_id_for_mutations, false)
100
+ )
101
+ end
102
+
103
+ def use_auto_increment?
104
+ "AUTO_INCREMENT".casecmp?(@default_sequence_kind || "")
105
+ end
106
+
107
+ def use_identity?
108
+ !use_auto_increment? \
109
+ && @default_sequence_kind \
110
+ && !@default_sequence_kind.casecmp?("DISABLED")
89
111
  end
90
112
 
91
113
  def max_identifier_length
@@ -276,7 +298,9 @@ module ActiveRecord
276
298
  end
277
299
 
278
300
  def transform sql
279
- if ActiveRecord::VERSION::MAJOR >= 7
301
+ if ActiveRecord::VERSION::MAJOR >= 8
302
+ preprocess_query sql
303
+ elsif ActiveRecord::VERSION::MAJOR == 7
280
304
  transform_query sql
281
305
  else
282
306
  sql
@@ -284,12 +308,23 @@ module ActiveRecord
284
308
  end
285
309
 
286
310
  # Overwrite the standard log method to be able to translate exceptions.
287
- 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)
311
+ # sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
312
+ if ActiveRecord::VERSION::MAJOR >= 8
313
+ def log sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block
314
+ super
315
+ rescue ActiveRecord::StatementInvalid
316
+ raise
317
+ rescue StandardError => e
318
+ raise translate_exception_class(e, sql, binds)
319
+ end
320
+ else
321
+ def log sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, *args
322
+ super
323
+ rescue ActiveRecord::StatementInvalid
324
+ raise
325
+ rescue StandardError => e
326
+ raise translate_exception_class(e, sql, binds)
327
+ end
293
328
  end
294
329
 
295
330
  def translate_exception exception, message:, sql:, binds:
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  def purge
32
32
  begin
33
33
  drop
34
- rescue ActiveRecord::NoDatabaseError # 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
 
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  return super if active_transaction?
36
36
 
37
37
  # Only use mutations to create new records if the primary key is generated client-side.
38
- isolation = sequence_name ? nil : :buffered_mutations
38
+ isolation = has_auto_generated_primary_key? ? nil : :buffered_mutations
39
39
  transaction isolation: isolation do
40
40
  return super
41
41
  end
@@ -53,6 +53,21 @@ module ActiveRecord
53
53
  !(buffered_mutations? || (primary_key && values.is_a?(Hash))) || !spanner_adapter?
54
54
  end
55
55
 
56
+ def self.has_auto_generated_primary_key?
57
+ return true if sequence_name
58
+ pk = primary_key
59
+ if pk.is_a? Array
60
+ return pk.any? do |col|
61
+ columns_hash[col].auto_incremented_by_db?
62
+ end
63
+ end
64
+ columns_hash[pk].auto_incremented_by_db?
65
+ end
66
+
67
+ def self.is_auto_generated? col
68
+ columns_hash[col]&.auto_incremented_by_db?
69
+ end
70
+
56
71
  def self._internal_insert_record values
57
72
  if ActiveRecord.gem_version < VERSION_7_2
58
73
  _insert_record values
@@ -73,10 +88,13 @@ module ActiveRecord
73
88
  return super
74
89
  end
75
90
 
76
- # Mutations cannot be used in combination with a sequence, as mutations do not support a THEN RETURN clause.
77
- if buffered_mutations? && sequence_name
78
- raise StatementInvalid, "Mutations cannot be used to create records that use a sequence " \
79
- "to generate the primary key. #{self} uses #{sequence_name}."
91
+ # Mutations cannot be used in combination with an auto-generated primary key,
92
+ # as mutations do not support a THEN RETURN clause.
93
+ if buffered_mutations? \
94
+ && has_auto_generated_primary_key? \
95
+ && !_has_all_primary_key_values?(primary_key, values) \
96
+ && !connection.use_client_side_id_for_mutations
97
+ raise StatementInvalid, "Mutations cannot be used to create records that use an auto-generated primary key."
80
98
  end
81
99
 
82
100
  return _buffer_record values, :insert, returning if buffered_mutations?
@@ -85,7 +103,7 @@ module ActiveRecord
85
103
  end
86
104
 
87
105
  def self._insert_record_dml values, returning
88
- primary_key_value = _set_primary_key_value values
106
+ primary_key_value = _set_primary_key_value values, false
89
107
  if ActiveRecord::VERSION::MAJOR >= 7
90
108
  im = Arel::InsertManager.new arel_table
91
109
  im.insert(values.transform_keys { |name| arel_table[name] })
@@ -97,11 +115,11 @@ module ActiveRecord
97
115
  _convert_primary_key result, returning
98
116
  end
99
117
 
100
- def self._set_primary_key_value values
118
+ def self._set_primary_key_value values, is_mutation
101
119
  if primary_key.is_a? Array
102
- _set_composite_primary_key_values primary_key, values
120
+ _set_composite_primary_key_values primary_key, values, is_mutation
103
121
  else
104
- _set_single_primary_key_value primary_key, values
122
+ _set_single_primary_key_value primary_key, values, is_mutation
105
123
  end
106
124
  end
107
125
 
@@ -118,12 +136,10 @@ module ActiveRecord
118
136
  keys = returning || primary_key
119
137
  return primary_key_value if keys == primary_key
120
138
 
121
- primary_key_values_hash = Hash[primary_key.zip(primary_key_value)]
122
- values = []
123
- keys.each do |column|
124
- values.append primary_key_values_hash[column]
139
+ primary_key_values_hash = primary_key.zip(primary_key_value).to_h
140
+ keys.map do |column|
141
+ primary_key_values_hash[column]
125
142
  end
126
- values
127
143
  end
128
144
 
129
145
  def self._upsert_record values, returning
@@ -194,9 +210,9 @@ module ActiveRecord
194
210
  def self._buffer_record values, method, returning
195
211
  primary_key_value =
196
212
  if primary_key.is_a? Array
197
- _set_composite_primary_key_values primary_key, values
213
+ _set_composite_primary_key_values primary_key, values, true
198
214
  else
199
- _set_single_primary_key_value primary_key, values
215
+ _set_single_primary_key_value primary_key, values, true
200
216
  end
201
217
 
202
218
  metadata = TableMetadata.new self, arel_table
@@ -215,15 +231,25 @@ module ActiveRecord
215
231
  _convert_primary_key primary_key_value, returning
216
232
  end
217
233
 
218
- def self._set_composite_primary_key_values primary_key, values
219
- primary_key_value = []
220
- primary_key.each do |col|
221
- primary_key_value.append _set_composite_primary_key_value col, values
234
+ def self._has_all_primary_key_values? primary_key, values
235
+ if primary_key.is_a? Array
236
+ all = TrueClass
237
+ primary_key.each do |key|
238
+ all &&= values.key? key
239
+ end
240
+ all
241
+ else
242
+ values.key? primary_key
243
+ end
244
+ end
245
+
246
+ def self._set_composite_primary_key_values primary_key, values, is_mutation
247
+ primary_key.map do |col|
248
+ _set_composite_primary_key_value col, values, is_mutation
222
249
  end
223
- primary_key_value
224
250
  end
225
251
 
226
- def self._set_composite_primary_key_value primary_key, values
252
+ def self._set_composite_primary_key_value primary_key, values, is_mutation
227
253
  value = values[primary_key]
228
254
  type = ActiveModel::Type::BigInteger.new
229
255
 
@@ -232,6 +258,8 @@ module ActiveRecord
232
258
  value = value.value
233
259
  end
234
260
 
261
+ return value if is_auto_generated?(primary_key) \
262
+ && !(is_mutation && connection.use_client_side_id_for_mutations)
235
263
  return value unless prefetch_primary_key?
236
264
 
237
265
  if value.nil?
@@ -248,10 +276,11 @@ module ActiveRecord
248
276
  value
249
277
  end
250
278
 
251
- def self._set_single_primary_key_value primary_key, values
279
+ def self._set_single_primary_key_value primary_key, values, is_mutation
252
280
  primary_key_value = values[primary_key] || values[primary_key.to_sym]
253
281
 
254
- return primary_key_value if sequence_name
282
+ return primary_key_value if has_auto_generated_primary_key? \
283
+ && !(is_mutation && connection.use_client_side_id_for_mutations)
255
284
  return primary_key_value unless prefetch_primary_key?
256
285
 
257
286
  if primary_key_value.nil?
@@ -419,14 +448,12 @@ module ActiveRecord
419
448
  end
420
449
 
421
450
  def serialize_keys metadata, keys
422
- serialized_values = []
423
- keys.each do |key|
424
- serialized_values << ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
425
- .serialize_with_transaction_isolation_level(metadata.type(key),
426
- attribute_in_database(key),
427
- :mutation)
451
+ keys.map do |key|
452
+ ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
453
+ .serialize_with_transaction_isolation_level(metadata.type(key),
454
+ attribute_in_database(key),
455
+ :mutation)
428
456
  end
429
- serialized_values
430
457
  end
431
458
 
432
459
  def _execute_version_check attempted_action # rubocop:disable Metrics/AbcSize
@@ -456,7 +483,7 @@ module ActiveRecord
456
483
 
457
484
  # We need to check the version using a SELECT query, as a mutation cannot include a WHERE clause.
458
485
  sql = "SELECT 1 FROM `#{self.class.arel_table.name}` " \
459
- "WHERE #{pk_sql} AND `#{locking_column}` = @lock_version"
486
+ "WHERE #{pk_sql} AND `#{locking_column}` = @lock_version"
460
487
  locked_row = self.class.connection.raw_connection.execute_query sql, params: params, types: param_types
461
488
  raise ActiveRecord::StaleObjectError.new(self, attempted_action) unless locked_row.rows.any?
462
489
  end