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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/blunderbuss.yml +1 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +22 -0
- data/README.md +4 -0
- data/acceptance/cases/migration/schema_dumper_test.rb +69 -0
- data/acceptance/cases/models/generated_column_test.rb +21 -7
- data/acceptance/cases/models/interleave_test.rb +36 -0
- data/acceptance/cases/models/logging_test.rb +53 -0
- data/acceptance/cases/models/query_test.rb +6 -1
- data/acceptance/cases/tasks/database_tasks_test.rb +407 -0
- data/acceptance/models/album_partial_disabled.rb +17 -0
- data/acceptance/schema/schema.rb +139 -134
- data/acceptance/test_helper.rb +2 -0
- data/examples/snippets/array-data-type/db/schema.rb +8 -3
- data/examples/snippets/bulk-insert/db/schema.rb +9 -4
- data/examples/snippets/commit-timestamp/db/schema.rb +11 -6
- data/examples/snippets/create-records/db/schema.rb +9 -4
- data/examples/snippets/date-data-type/db/schema.rb +8 -3
- data/examples/snippets/generated-column/db/schema.rb +6 -1
- data/examples/snippets/hints/db/schema.rb +6 -1
- data/examples/snippets/interleaved-tables/README.md +2 -2
- data/examples/snippets/interleaved-tables/db/schema.rb +5 -0
- data/examples/snippets/migrations/db/schema.rb +10 -5
- data/examples/snippets/mutations/db/schema.rb +9 -4
- data/examples/snippets/optimistic-locking/db/schema.rb +9 -4
- data/examples/snippets/partitioned-dml/db/schema.rb +5 -0
- data/examples/snippets/quickstart/db/schema.rb +9 -4
- data/examples/snippets/read-only-transactions/db/schema.rb +5 -0
- data/examples/snippets/read-write-transactions/db/schema.rb +9 -4
- data/examples/snippets/stale-reads/db/schema.rb +5 -0
- data/examples/snippets/timestamp-data-type/db/schema.rb +8 -3
- data/lib/active_record/connection_adapters/spanner/column.rb +23 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +7 -4
- data/lib/active_record/connection_adapters/spanner/quoting.rb +9 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +17 -4
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +11 -2
- data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +56 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +56 -9
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +19 -4
- data/lib/active_record/connection_adapters/spanner_adapter.rb +11 -0
- data/lib/active_record/tasks/spanner_database_tasks.rb +18 -4
- data/lib/active_record/type/spanner/spanner_active_record_converter.rb +10 -0
- data/lib/active_record/type/spanner/time.rb +10 -3
- data/lib/activerecord_spanner_adapter/base.rb +41 -27
- data/lib/activerecord_spanner_adapter/connection.rb +8 -3
- data/lib/activerecord_spanner_adapter/information_schema.rb +52 -3
- data/lib/activerecord_spanner_adapter/table/column.rb +7 -2
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +8 -2
- 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
|
-
|
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
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
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
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
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, :
|
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
|
|
@@ -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
|
-
|
36
|
-
|
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.
|
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:
|
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
|