activerecord-spanner-adapter 1.2.1 → 1.3.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/blunderbuss.yml +1 -1
  4. data/.release-please-manifest.json +1 -1
  5. data/CHANGELOG.md +29 -0
  6. data/README.md +7 -2
  7. data/acceptance/cases/migration/schema_dumper_test.rb +69 -0
  8. data/acceptance/cases/models/generated_column_test.rb +21 -7
  9. data/acceptance/cases/models/interleave_test.rb +36 -0
  10. data/acceptance/cases/models/logging_test.rb +53 -0
  11. data/acceptance/cases/models/query_test.rb +6 -1
  12. data/acceptance/cases/tasks/database_tasks_test.rb +407 -0
  13. data/acceptance/models/album_partial_disabled.rb +17 -0
  14. data/acceptance/schema/schema.rb +139 -134
  15. data/acceptance/test_helper.rb +2 -0
  16. data/examples/snippets/array-data-type/db/schema.rb +8 -3
  17. data/examples/snippets/bulk-insert/db/schema.rb +9 -4
  18. data/examples/snippets/commit-timestamp/db/schema.rb +11 -6
  19. data/examples/snippets/create-records/db/schema.rb +9 -4
  20. data/examples/snippets/date-data-type/db/schema.rb +8 -3
  21. data/examples/snippets/generated-column/db/schema.rb +6 -1
  22. data/examples/snippets/hints/db/schema.rb +6 -1
  23. data/examples/snippets/interleaved-tables/README.md +2 -2
  24. data/examples/snippets/interleaved-tables/db/schema.rb +5 -0
  25. data/examples/snippets/migrations/db/schema.rb +10 -5
  26. data/examples/snippets/mutations/db/schema.rb +9 -4
  27. data/examples/snippets/optimistic-locking/db/schema.rb +9 -4
  28. data/examples/snippets/partitioned-dml/db/schema.rb +5 -0
  29. data/examples/snippets/quickstart/db/schema.rb +9 -4
  30. data/examples/snippets/read-only-transactions/db/schema.rb +5 -0
  31. data/examples/snippets/read-write-transactions/db/schema.rb +9 -4
  32. data/examples/snippets/stale-reads/db/schema.rb +5 -0
  33. data/examples/snippets/timestamp-data-type/db/schema.rb +8 -3
  34. data/lib/active_record/connection_adapters/spanner/column.rb +23 -0
  35. data/lib/active_record/connection_adapters/spanner/database_statements.rb +7 -4
  36. data/lib/active_record/connection_adapters/spanner/quoting.rb +9 -0
  37. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +17 -4
  38. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +11 -2
  39. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +56 -0
  40. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +56 -9
  41. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +19 -4
  42. data/lib/active_record/connection_adapters/spanner_adapter.rb +11 -0
  43. data/lib/active_record/tasks/spanner_database_tasks.rb +18 -4
  44. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +10 -0
  45. data/lib/active_record/type/spanner/time.rb +10 -3
  46. data/lib/activerecord_spanner_adapter/base.rb +41 -27
  47. data/lib/activerecord_spanner_adapter/connection.rb +8 -3
  48. data/lib/activerecord_spanner_adapter/information_schema.rb +52 -3
  49. data/lib/activerecord_spanner_adapter/table/column.rb +7 -2
  50. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  51. data/lib/arel/visitors/spanner.rb +8 -2
  52. metadata +8 -2
@@ -141,29 +141,41 @@ module ActiveRecord
141
141
  def self._set_composite_primary_key_values primary_key, values
142
142
  primary_key_value = []
143
143
  primary_key.each do |col|
144
- value = values[col]
145
-
146
- if !value && prefetch_primary_key?
147
- value =
148
- if ActiveRecord::VERSION::MAJOR >= 7
149
- ActiveModel::Attribute.from_database col, next_sequence_value, ActiveModel::Type::BigInteger.new
150
- else
151
- next_sequence_value
152
- end
153
- values[col] = value
154
- end
155
- if value.is_a? ActiveModel::Attribute
156
- value = value.value
157
- end
158
- primary_key_value.append value
144
+ primary_key_value.append _set_composite_primary_key_value col, values
159
145
  end
160
146
  primary_key_value
161
147
  end
162
148
 
149
+ def self._set_composite_primary_key_value primary_key, values
150
+ value = values[primary_key]
151
+
152
+ if value.is_a? ActiveModel::Attribute
153
+ value = value.value
154
+ end
155
+
156
+ return value unless prefetch_primary_key?
157
+
158
+ if value.nil?
159
+ value = next_sequence_value
160
+ end
161
+
162
+ values[primary_key] =
163
+ if ActiveRecord::VERSION::MAJOR >= 7
164
+ ActiveModel::Attribute.from_database primary_key, value,
165
+ ActiveModel::Type::BigInteger.new
166
+ else
167
+ value
168
+ end
169
+
170
+ value
171
+ end
172
+
163
173
  def self._set_single_primary_key_value primary_key, values
164
174
  primary_key_value = values[primary_key] || values[primary_key.to_sym]
165
175
 
166
- if !primary_key_value && prefetch_primary_key?
176
+ return primary_key_value unless prefetch_primary_key?
177
+
178
+ if primary_key_value.nil?
167
179
  primary_key_value = next_sequence_value
168
180
  if ActiveRecord::VERSION::MAJOR >= 7
169
181
  values[primary_key] = ActiveModel::Attribute.from_database primary_key, primary_key_value,
@@ -172,6 +184,7 @@ module ActiveRecord
172
184
  values[primary_key] = primary_key_value
173
185
  end
174
186
  end
187
+
175
188
  primary_key_value
176
189
  end
177
190
 
@@ -229,9 +242,10 @@ module ActiveRecord
229
242
  serialized_values = []
230
243
  columns = []
231
244
  values.each_pair do |k, v|
232
- v = unwrap_attribute v
233
- type = metadata.type k
234
- serialized_values << (type.method(:serialize).arity < 0 ? type.serialize(v, :mutation) : type.serialize(v))
245
+ serialized_values << ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
246
+ .serialize_with_transaction_isolation_level(metadata.type(k),
247
+ unwrap_attribute(v),
248
+ :mutation)
235
249
  columns << metadata.arel_table[k].name
236
250
  end
237
251
  [columns, Google::Protobuf::Value.new(list_value:
@@ -280,10 +294,10 @@ module ActiveRecord
280
294
  all_columns = []
281
295
  all_values.each do |h|
282
296
  h.each_pair do |k, v|
283
- type = metadata.type k
284
- v = self.class.unwrap_attribute v
285
- has_serialize_options = type.method(:serialize).arity < 0
286
- all_serialized_values << (has_serialize_options ? type.serialize(v, :mutation) : type.serialize(v))
297
+ all_serialized_values << ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
298
+ .serialize_with_transaction_isolation_level(metadata.type(k),
299
+ self.class.unwrap_attribute(v),
300
+ :mutation)
287
301
  all_columns << metadata.arel_table[k].name
288
302
  end
289
303
  end
@@ -328,10 +342,10 @@ module ActiveRecord
328
342
  def serialize_keys metadata, keys
329
343
  serialized_values = []
330
344
  keys.each do |key|
331
- type = metadata.type key
332
- has_serialize_options = type.method(:serialize).arity < 0
333
- serialized_values << type.serialize(attribute_in_database(key), :mutation) if has_serialize_options
334
- serialized_values << type.serialize(attribute_in_database(key)) unless has_serialize_options
345
+ serialized_values << ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
346
+ .serialize_with_transaction_isolation_level(metadata.type(key),
347
+ attribute_in_database(key),
348
+ :mutation)
335
349
  end
336
350
  serialized_values
337
351
  end
@@ -36,6 +36,13 @@ module ActiveRecordSpannerAdapter
36
36
  end
37
37
  end
38
38
 
39
+ # Clears the cached information about the underlying information schemas.
40
+ # Call this method if you drop and recreate a database with the same name
41
+ # to prevent the cached information to be used for the new database.
42
+ def self.reset_information_schemas!
43
+ @information_schemas = {}
44
+ end
45
+
39
46
  def self.information_schema config
40
47
  @information_schemas ||= {}
41
48
  @information_schemas[database_path(config)] ||= \
@@ -89,9 +96,7 @@ module ActiveRecordSpannerAdapter
89
96
  @database ||= begin
90
97
  database = spanner.database instance_id, database_id
91
98
  unless database
92
- raise ActiveRecord::NoDatabaseError(
93
- "#{spanner.project}/#{instance_id}/#{database_id}"
94
- )
99
+ raise ActiveRecord::NoDatabaseError, "#{spanner.project}/#{instance_id}/#{database_id}"
95
100
  end
96
101
  database
97
102
  end
@@ -62,27 +62,34 @@ module ActiveRecordSpannerAdapter
62
62
  end
63
63
 
64
64
  def table_columns table_name, column_name: nil
65
- sql = +"SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE,"
65
+ sql = +"SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE, GENERATION_EXPRESSION,"
66
66
  sql << " CAST(COLUMN_DEFAULT AS STRING) AS COLUMN_DEFAULT, ORDINAL_POSITION"
67
67
  sql << " FROM INFORMATION_SCHEMA.COLUMNS"
68
68
  sql << " WHERE TABLE_NAME=%<table_name>s"
69
69
  sql << " AND COLUMN_NAME=%<column_name>s" if column_name
70
70
  sql << " ORDER BY ORDINAL_POSITION ASC"
71
71
 
72
+ column_options = column_options table_name, column_name
72
73
  execute_query(
73
74
  sql,
74
75
  table_name: table_name,
75
76
  column_name: column_name
76
77
  ).map do |row|
77
78
  type, limit = parse_type_and_limit row["SPANNER_TYPE"]
79
+ column_name = row["COLUMN_NAME"]
80
+ options = column_options[column_name]
81
+
78
82
  Table::Column.new \
79
83
  table_name,
80
- row["COLUMN_NAME"],
84
+ column_name,
81
85
  type,
82
86
  limit: limit,
87
+ allow_commit_timestamp: options["allow_commit_timestamp"],
83
88
  ordinal_position: row["ORDINAL_POSITION"],
84
89
  nullable: row["IS_NULLABLE"] == "YES",
85
- default: row["COLUMN_DEFAULT"]
90
+ default: row["COLUMN_DEFAULT"],
91
+ default_function: row["GENERATION_EXPRESSION"],
92
+ generated: row["GENERATION_EXPRESSION"].present?
86
93
  end
87
94
  end
88
95
 
@@ -240,6 +247,27 @@ module ActiveRecordSpannerAdapter
240
247
  end
241
248
  end
242
249
 
250
+ def check_constraints table_name
251
+ sql = <<~SQL.squish
252
+ SELECT tc.TABLE_NAME,
253
+ tc.CONSTRAINT_NAME,
254
+ cc.CHECK_CLAUSE
255
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
256
+ INNER JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
257
+ WHERE tc.TABLE_NAME = %<table_name>s
258
+ AND tc.CONSTRAINT_TYPE = 'CHECK'
259
+ AND NOT (tc.CONSTRAINT_NAME LIKE 'CK_IS_NOT_NULL_%%' AND cc.CHECK_CLAUSE LIKE '%%IS NOT NULL')
260
+ SQL
261
+
262
+ rows = execute_query sql, table_name: table_name
263
+
264
+ rows.map do |row|
265
+ ActiveRecord::ConnectionAdapters::CheckConstraintDefinition.new(
266
+ table_name, row["CHECK_CLAUSE"], name: row["CONSTRAINT_NAME"]
267
+ )
268
+ end
269
+ end
270
+
243
271
  def parse_type_and_limit value
244
272
  matched = /^([A-Z]*)\((.*)\)/.match value
245
273
  return [value] unless matched
@@ -252,6 +280,27 @@ module ActiveRecordSpannerAdapter
252
280
 
253
281
  private
254
282
 
283
+ def column_options table_name, column_name
284
+ sql = +"SELECT COLUMN_NAME, OPTION_NAME, OPTION_TYPE, OPTION_VALUE"
285
+ sql << " FROM INFORMATION_SCHEMA.COLUMN_OPTIONS"
286
+ sql << " WHERE TABLE_NAME=%<table_name>s"
287
+ sql << " AND COLUMN_NAME=%<column_name>s" if column_name
288
+
289
+ column_options = Hash.new { |h, k| h[k] = {} }
290
+ execute_query(
291
+ sql,
292
+ table_name: table_name,
293
+ column_name: column_name
294
+ ).each_with_object(column_options) do |row, options|
295
+ next unless row["OPTION_TYPE"] == "BOOL"
296
+
297
+ col = row["COLUMN_NAME"]
298
+ opt = row["OPTION_NAME"]
299
+ value = row["OPTION_VALUE"] == "TRUE"
300
+ options[col][opt] = value
301
+ end
302
+ end
303
+
255
304
  def execute_query sql, params = {}
256
305
  params = params.transform_values { |v| quote v }
257
306
  sql = format sql, params
@@ -8,7 +8,8 @@ module ActiveRecordSpannerAdapter
8
8
  class Table
9
9
  class Column
10
10
  attr_accessor :table_name, :name, :type, :limit, :ordinal_position,
11
- :allow_commit_timestamp, :default, :primary_key
11
+ :allow_commit_timestamp, :default, :default_function, :generated,
12
+ :primary_key
12
13
  attr_writer :nullable
13
14
 
14
15
  def initialize \
@@ -19,7 +20,9 @@ module ActiveRecordSpannerAdapter
19
20
  ordinal_position: nil,
20
21
  nullable: true,
21
22
  allow_commit_timestamp: nil,
22
- default: nil
23
+ default: nil,
24
+ default_function: nil,
25
+ generated: nil
23
26
  @table_name = table_name.to_s
24
27
  @name = name.to_s
25
28
  @type = type
@@ -28,6 +31,8 @@ module ActiveRecordSpannerAdapter
28
31
  @ordinal_position = ordinal_position
29
32
  @allow_commit_timestamp = allow_commit_timestamp
30
33
  @default = default
34
+ @default_function = default_function
35
+ @generated = generated == true
31
36
  @primary_key = false
32
37
  end
33
38
 
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "1.2.1".freeze
8
+ VERSION = "1.3.1".freeze
9
9
  end
@@ -30,10 +30,16 @@ module Arel # :nodoc: all
30
30
  collector.hints = {}
31
31
  collector.table_hints = {}
32
32
  collector.join_hints = {}
33
+
33
34
  sql, binds = accept(node, collector).value
34
35
  sql = collector.hints[:statement_hint].value + sql if collector.hints[:statement_hint]
35
- binds << collector.hints[:staleness] if collector.hints[:staleness]
36
- [sql, binds]
36
+
37
+ if binds
38
+ binds << collector.hints[:staleness] if collector.hints[:staleness]
39
+ [sql, binds]
40
+ else
41
+ sql
42
+ end
37
43
  end
38
44
 
39
45
  private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-spanner-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-29 00:00:00.000000000 Z
11
+ date: 2023-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-cloud-spanner
@@ -268,12 +268,16 @@ files:
268
268
  - acceptance/cases/migration/references_index_test.rb
269
269
  - acceptance/cases/migration/references_statements_test.rb
270
270
  - acceptance/cases/migration/rename_column_test.rb
271
+ - acceptance/cases/migration/schema_dumper_test.rb
271
272
  - acceptance/cases/models/calculation_query_test.rb
272
273
  - acceptance/cases/models/generated_column_test.rb
273
274
  - acceptance/cases/models/insert_all_test.rb
275
+ - acceptance/cases/models/interleave_test.rb
276
+ - acceptance/cases/models/logging_test.rb
274
277
  - acceptance/cases/models/mutation_test.rb
275
278
  - acceptance/cases/models/query_test.rb
276
279
  - acceptance/cases/sessions/session_not_found_test.rb
280
+ - acceptance/cases/tasks/database_tasks_test.rb
277
281
  - acceptance/cases/transactions/optimistic_locking_test.rb
278
282
  - acceptance/cases/transactions/read_only_transactions_test.rb
279
283
  - acceptance/cases/transactions/read_write_transactions_test.rb
@@ -292,6 +296,7 @@ files:
292
296
  - acceptance/models/account.rb
293
297
  - acceptance/models/address.rb
294
298
  - acceptance/models/album.rb
299
+ - acceptance/models/album_partial_disabled.rb
295
300
  - acceptance/models/all_types.rb
296
301
  - acceptance/models/author.rb
297
302
  - acceptance/models/club.rb
@@ -479,6 +484,7 @@ files:
479
484
  - examples/snippets/timestamp-data-type/db/seeds.rb
480
485
  - examples/snippets/timestamp-data-type/models/meeting.rb
481
486
  - examples/solidus/README.md
487
+ - lib/active_record/connection_adapters/spanner/column.rb
482
488
  - lib/active_record/connection_adapters/spanner/database_statements.rb
483
489
  - lib/active_record/connection_adapters/spanner/quoting.rb
484
490
  - lib/active_record/connection_adapters/spanner/schema_cache.rb