activerecord-spanner-adapter 1.2.2 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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 +22 -0
  6. data/README.md +4 -0
  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.2".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.2
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