activerecord-spanner-adapter 1.0.1 → 1.2.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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/sync-repo-settings.yaml +2 -2
- data/.github/workflows/acceptance-tests-on-emulator.yaml +10 -6
- data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/ci.yaml +10 -8
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +14 -5
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +2 -2
- data/.github/workflows/nightly-unit-tests.yaml +14 -5
- data/.github/workflows/release-please.yml +2 -2
- data/.github/workflows/rubocop.yaml +3 -3
- data/.kokoro/release.sh +1 -3
- data/.release-please-manifest.json +1 -1
- data/.toys/release.rb +8 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +5 -1
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +12 -8
- data/acceptance/cases/models/insert_all_test.rb +150 -0
- data/acceptance/cases/transactions/optimistic_locking_test.rb +5 -0
- data/acceptance/cases/type/all_types_test.rb +10 -13
- data/acceptance/cases/type/json_test.rb +0 -2
- data/acceptance/models/album.rb +7 -2
- data/acceptance/models/singer.rb +2 -2
- data/acceptance/models/track.rb +5 -2
- data/acceptance/schema/schema.rb +2 -4
- data/acceptance/test_helper.rb +1 -1
- data/activerecord-spanner-adapter.gemspec +1 -1
- data/examples/snippets/interleaved-tables/README.md +164 -0
- data/examples/snippets/interleaved-tables/Rakefile +13 -0
- data/examples/snippets/interleaved-tables/application.rb +126 -0
- data/examples/snippets/interleaved-tables/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables/models/album.rb +20 -0
- data/examples/snippets/interleaved-tables/models/singer.rb +18 -0
- data/examples/snippets/interleaved-tables/models/track.rb +28 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +10 -4
- data/lib/active_record/connection_adapters/spanner_adapter.rb +64 -31
- data/lib/activerecord_spanner_adapter/base.rb +150 -17
- data/lib/activerecord_spanner_adapter/connection.rb +1 -1
- data/lib/activerecord_spanner_adapter/relation.rb +21 -0
- data/lib/activerecord_spanner_adapter/transaction.rb +4 -4
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +10 -0
- metadata +25 -7
@@ -8,6 +8,7 @@ require "securerandom"
|
|
8
8
|
require "google/cloud/spanner"
|
9
9
|
require "spanner_client_ext"
|
10
10
|
require "active_record/connection_adapters/abstract_adapter"
|
11
|
+
require "active_record/connection_adapters/abstract/connection_pool"
|
11
12
|
require "active_record/connection_adapters/spanner/database_statements"
|
12
13
|
require "active_record/connection_adapters/spanner/schema_statements"
|
13
14
|
require "active_record/connection_adapters/spanner/schema_cache"
|
@@ -43,9 +44,9 @@ module ActiveRecord
|
|
43
44
|
module ConnectionAdapters
|
44
45
|
module AbstractPool
|
45
46
|
def get_schema_cache connection
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
self.schema_cache ||= SpannerSchemaCache.new connection
|
48
|
+
schema_cache.connection = connection
|
49
|
+
schema_cache
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
@@ -178,41 +179,73 @@ module ActiveRecord
|
|
178
179
|
Arel::Visitors::Spanner.new self
|
179
180
|
end
|
180
181
|
|
181
|
-
|
182
|
+
def build_insert_sql insert
|
183
|
+
if current_spanner_transaction&.isolation == :buffered_mutations
|
184
|
+
raise "ActiveRecordSpannerAdapter does not support insert_sql with buffered_mutations transaction."
|
185
|
+
end
|
182
186
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
m, %r{^BYTES}i, ActiveRecord::Type::Spanner::Bytes
|
187
|
-
)
|
188
|
-
m.register_type "DATE", Type::Date.new
|
189
|
-
m.register_type "FLOAT64", Type::Float.new
|
190
|
-
m.register_type "NUMERIC", Type::Decimal.new
|
191
|
-
m.register_type "INT64", Type::Integer.new(limit: 8)
|
192
|
-
register_class_with_limit m, %r{^STRING}i, Type::String
|
193
|
-
m.register_type "TIMESTAMP", ActiveRecord::Type::Spanner::Time.new
|
194
|
-
m.register_type "JSON", ActiveRecord::Type::Json.new
|
187
|
+
if insert.skip_duplicates? || insert.update_duplicates?
|
188
|
+
raise NotImplementedError, "CloudSpanner does not support skip_duplicates and update_duplicates."
|
189
|
+
end
|
195
190
|
|
196
|
-
|
191
|
+
values_list, = insert.values_list
|
192
|
+
"INSERT #{insert.into} #{values_list}"
|
197
193
|
end
|
198
194
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
m
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
195
|
+
module TypeMapBuilder
|
196
|
+
private
|
197
|
+
|
198
|
+
def initialize_type_map m = type_map
|
199
|
+
m.register_type "BOOL", Type::Boolean.new
|
200
|
+
register_class_with_limit(
|
201
|
+
m, %r{^BYTES}i, ActiveRecord::Type::Spanner::Bytes
|
202
|
+
)
|
203
|
+
m.register_type "DATE", Type::Date.new
|
204
|
+
m.register_type "FLOAT64", Type::Float.new
|
205
|
+
m.register_type "NUMERIC", Type::Decimal.new
|
206
|
+
m.register_type "INT64", Type::Integer.new(limit: 8)
|
207
|
+
register_class_with_limit m, %r{^STRING}i, Type::String
|
208
|
+
m.register_type "TIMESTAMP", ActiveRecord::Type::Spanner::Time.new
|
209
|
+
m.register_type "JSON", ActiveRecord::Type::Json.new
|
210
|
+
|
211
|
+
register_array_types m
|
212
|
+
end
|
213
|
+
|
214
|
+
def register_array_types m
|
215
|
+
m.register_type %r{^ARRAY<BOOL>}i, Type::Spanner::Array.new(Type::Boolean.new)
|
216
|
+
m.register_type %r{^ARRAY<BYTES\((MAX|d+)\)>}i,
|
217
|
+
Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Bytes.new)
|
218
|
+
m.register_type %r{^ARRAY<DATE>}i, Type::Spanner::Array.new(Type::Date.new)
|
219
|
+
m.register_type %r{^ARRAY<FLOAT64>}i, Type::Spanner::Array.new(Type::Float.new)
|
220
|
+
m.register_type %r{^ARRAY<NUMERIC>}i, Type::Spanner::Array.new(Type::Decimal.new)
|
221
|
+
m.register_type %r{^ARRAY<INT64>}i, Type::Spanner::Array.new(Type::Integer.new(limit: 8))
|
222
|
+
m.register_type %r{^ARRAY<STRING\((MAX|d+)\)>}i, Type::Spanner::Array.new(Type::String.new)
|
223
|
+
m.register_type %r{^ARRAY<TIMESTAMP>}i, Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Time.new)
|
224
|
+
m.register_type %r{^ARRAY<JSON>}i, Type::Spanner::Array.new(ActiveRecord::Type::Json.new)
|
225
|
+
end
|
226
|
+
|
227
|
+
def extract_limit sql_type
|
228
|
+
value = /\((.*)\)/.match sql_type
|
229
|
+
return unless value
|
230
|
+
|
231
|
+
value[1] == "MAX" ? "MAX" : value[1].to_i
|
232
|
+
end
|
209
233
|
end
|
210
234
|
|
211
|
-
|
212
|
-
|
213
|
-
|
235
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
236
|
+
class << self
|
237
|
+
include TypeMapBuilder
|
238
|
+
end
|
239
|
+
|
240
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map m }
|
241
|
+
|
242
|
+
private
|
214
243
|
|
215
|
-
|
244
|
+
def type_map
|
245
|
+
TYPE_MAP
|
246
|
+
end
|
247
|
+
else
|
248
|
+
include TypeMapBuilder
|
216
249
|
end
|
217
250
|
|
218
251
|
def translate_exception exception, message:, sql:, binds:
|
@@ -4,6 +4,8 @@
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
+
require "activerecord_spanner_adapter/relation"
|
8
|
+
|
7
9
|
module ActiveRecord
|
8
10
|
class TableMetadata # :nodoc:
|
9
11
|
# This attr_reader is private in ActiveRecord 6.0.x and public in 6.1.x. This makes sure it is always available in
|
@@ -42,35 +44,137 @@ module ActiveRecord
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def self._insert_record values
|
45
|
-
return super unless buffered_mutations?
|
47
|
+
return super unless buffered_mutations? || (primary_key && values.is_a?(Hash))
|
46
48
|
|
47
|
-
|
48
|
-
primary_key_value = nil
|
49
|
+
return _buffer_record values, :insert if buffered_mutations?
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
primary_key_value =
|
52
|
+
if primary_key.is_a? Array
|
53
|
+
_set_composite_primary_key_values primary_key, values
|
54
|
+
else
|
55
|
+
_set_single_primary_key_value primary_key, values
|
56
|
+
end
|
57
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
58
|
+
im = Arel::InsertManager.new arel_table
|
59
|
+
im.insert(values.transform_keys { |name| arel_table[name] })
|
60
|
+
else
|
61
|
+
im = arel_table.compile_insert _substitute_values(values)
|
62
|
+
end
|
63
|
+
connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
|
64
|
+
end
|
52
65
|
|
53
|
-
|
54
|
-
|
55
|
-
|
66
|
+
def self._upsert_record values
|
67
|
+
_buffer_record values, :insert_or_update
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.insert_all _attributes, _returning: nil, _unique_by: nil
|
71
|
+
raise NotImplementedError, "Cloud Spanner does not support skip_duplicates."
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.insert_all! attributes, returning: nil
|
75
|
+
return super unless spanner_adapter?
|
76
|
+
return super if active_transaction? && !buffered_mutations?
|
77
|
+
|
78
|
+
# This might seem inefficient, but is actually not, as it is only buffering a mutation locally.
|
79
|
+
# The mutations will be sent as one batch when the transaction is committed.
|
80
|
+
if active_transaction?
|
81
|
+
attributes.each do |record|
|
82
|
+
_insert_record record
|
83
|
+
end
|
84
|
+
else
|
85
|
+
transaction isolation: :buffered_mutations do
|
86
|
+
attributes.each do |record|
|
87
|
+
_insert_record record
|
88
|
+
end
|
56
89
|
end
|
57
90
|
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.upsert_all attributes, returning: nil, unique_by: nil
|
94
|
+
return super unless spanner_adapter?
|
95
|
+
if active_transaction? && !buffered_mutations?
|
96
|
+
raise NotImplementedError, "Cloud Spanner does not support upsert using DML. " \
|
97
|
+
"Use upsert outside a transaction block or in a transaction " \
|
98
|
+
"block with isolation: :buffered_mutations"
|
99
|
+
end
|
100
|
+
|
101
|
+
# This might seem inefficient, but is actually not, as it is only buffering a mutation locally.
|
102
|
+
# The mutations will be sent as one batch when the transaction is committed.
|
103
|
+
if active_transaction?
|
104
|
+
attributes.each do |record|
|
105
|
+
_upsert_record record
|
106
|
+
end
|
107
|
+
else
|
108
|
+
transaction isolation: :buffered_mutations do
|
109
|
+
attributes.each do |record|
|
110
|
+
_upsert_record record
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self._buffer_record values, method
|
117
|
+
primary_key_value =
|
118
|
+
if primary_key.is_a? Array
|
119
|
+
_set_composite_primary_key_values primary_key, values
|
120
|
+
else
|
121
|
+
_set_single_primary_key_value primary_key, values
|
122
|
+
end
|
58
123
|
|
59
124
|
metadata = TableMetadata.new self, arel_table
|
60
125
|
columns, grpc_values = _create_grpc_values_for_insert metadata, values
|
61
126
|
|
127
|
+
write = Google::Cloud::Spanner::V1::Mutation::Write.new(
|
128
|
+
table: arel_table.name,
|
129
|
+
columns: columns,
|
130
|
+
values: [grpc_values.list_value]
|
131
|
+
)
|
62
132
|
mutation = Google::Cloud::Spanner::V1::Mutation.new(
|
63
|
-
|
64
|
-
table: arel_table.name,
|
65
|
-
columns: columns,
|
66
|
-
values: [grpc_values.list_value]
|
67
|
-
)
|
133
|
+
"#{method}": write
|
68
134
|
)
|
135
|
+
|
69
136
|
connection.current_spanner_transaction.buffer mutation
|
70
137
|
|
71
138
|
primary_key_value
|
72
139
|
end
|
73
140
|
|
141
|
+
def self._set_composite_primary_key_values primary_key, values
|
142
|
+
primary_key_value = []
|
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
|
159
|
+
end
|
160
|
+
primary_key_value
|
161
|
+
end
|
162
|
+
|
163
|
+
def self._set_single_primary_key_value primary_key, values
|
164
|
+
primary_key_value = values[primary_key] || values[primary_key.to_sym]
|
165
|
+
|
166
|
+
if !primary_key_value && prefetch_primary_key?
|
167
|
+
primary_key_value = next_sequence_value
|
168
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
169
|
+
values[primary_key] = ActiveModel::Attribute.from_database primary_key, primary_key_value,
|
170
|
+
ActiveModel::Type::BigInteger.new
|
171
|
+
else
|
172
|
+
values[primary_key] = primary_key_value
|
173
|
+
end
|
174
|
+
end
|
175
|
+
primary_key_value
|
176
|
+
end
|
177
|
+
|
74
178
|
# Deletes all records of this class. This method will use mutations instead of DML if there is no active
|
75
179
|
# transaction, or if the active transaction has been created with the option isolation: :buffered_mutations.
|
76
180
|
def self.delete_all
|
@@ -87,6 +191,14 @@ module ActiveRecord
|
|
87
191
|
!(current_transaction.nil? || current_transaction.is_a?(ConnectionAdapters::NullTransaction))
|
88
192
|
end
|
89
193
|
|
194
|
+
def self.unwrap_attribute attr_or_value
|
195
|
+
if attr_or_value.is_a? ActiveModel::Attribute
|
196
|
+
attr_or_value.value
|
197
|
+
else
|
198
|
+
attr_or_value
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
90
202
|
# Updates the given attributes of the object in the database. This method will use mutations instead
|
91
203
|
# of DML if there is no active transaction, or if the active transaction has been created with the option
|
92
204
|
# isolation: :buffered_mutations.
|
@@ -117,6 +229,7 @@ module ActiveRecord
|
|
117
229
|
serialized_values = []
|
118
230
|
columns = []
|
119
231
|
values.each_pair do |k, v|
|
232
|
+
v = unwrap_attribute v
|
120
233
|
type = metadata.type k
|
121
234
|
serialized_values << (type.method(:serialize).arity < 0 ? type.serialize(v, :mutation) : type.serialize(v))
|
122
235
|
columns << metadata.arel_table[k].name
|
@@ -168,6 +281,7 @@ module ActiveRecord
|
|
168
281
|
all_values.each do |h|
|
169
282
|
h.each_pair do |k, v|
|
170
283
|
type = metadata.type k
|
284
|
+
v = self.class.unwrap_attribute v
|
171
285
|
has_serialize_options = type.method(:serialize).arity < 0
|
172
286
|
all_serialized_values << (has_serialize_options ? type.serialize(v, :mutation) : type.serialize(v))
|
173
287
|
all_columns << metadata.arel_table[k].name
|
@@ -222,15 +336,34 @@ module ActiveRecord
|
|
222
336
|
serialized_values
|
223
337
|
end
|
224
338
|
|
225
|
-
def _execute_version_check attempted_action
|
339
|
+
def _execute_version_check attempted_action # rubocop:disable Metrics/AbcSize
|
226
340
|
locking_column = self.class.locking_column
|
227
341
|
previous_lock_value = read_attribute_before_type_cast locking_column
|
228
342
|
|
343
|
+
primary_key = self.class.primary_key
|
344
|
+
if primary_key.is_a? Array
|
345
|
+
pk_sql = ""
|
346
|
+
params = {}
|
347
|
+
param_types = {}
|
348
|
+
id = id_in_database
|
349
|
+
primary_key.each_with_index do |col, idx|
|
350
|
+
pk_sql.concat "`#{col}`=@id#{idx}"
|
351
|
+
pk_sql.concat " AND " if idx < primary_key.length - 1
|
352
|
+
|
353
|
+
params["id#{idx}"] = id[idx]
|
354
|
+
param_types["id#{idx}"] = :INT64
|
355
|
+
end
|
356
|
+
params["lock_version"] = previous_lock_value
|
357
|
+
param_types["lock_version"] = :INT64
|
358
|
+
else
|
359
|
+
pk_sql = "`#{self.class.primary_key}` = @id"
|
360
|
+
params = { "id" => id_in_database, "lock_version" => previous_lock_value }
|
361
|
+
param_types = { "id" => :INT64, "lock_version" => :INT64 }
|
362
|
+
end
|
363
|
+
|
229
364
|
# We need to check the version using a SELECT query, as a mutation cannot include a WHERE clause.
|
230
365
|
sql = "SELECT 1 FROM `#{self.class.arel_table.name}` " \
|
231
|
-
"WHERE
|
232
|
-
params = { "id" => id_in_database, "lock_version" => previous_lock_value }
|
233
|
-
param_types = { "id" => :INT64, "lock_version" => :INT64 }
|
366
|
+
"WHERE #{pk_sql} AND `#{locking_column}` = @lock_version"
|
234
367
|
locked_row = self.class.connection.raw_connection.execute_query sql, params: params, types: param_types
|
235
368
|
raise ActiveRecord::StaleObjectError.new(self, attempted_action) unless locked_row.rows.any?
|
236
369
|
end
|
@@ -251,7 +251,7 @@ module ActiveRecordSpannerAdapter
|
|
251
251
|
# transaction fails, as that also means that no transaction id was returned.
|
252
252
|
def create_transaction_after_failed_first_statement original_error
|
253
253
|
transaction = current_transaction.force_begin_read_write
|
254
|
-
Google::Spanner::V1::TransactionSelector.new id: transaction.transaction_id
|
254
|
+
Google::Cloud::Spanner::V1::TransactionSelector.new id: transaction.transaction_id
|
255
255
|
rescue Google::Cloud::Error
|
256
256
|
# Raise the original error if the BeginTransaction RPC also fails.
|
257
257
|
raise original_error
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2022 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module CpkExtension
|
9
|
+
def cpk_subquery stmt
|
10
|
+
return super unless spanner_adapter?
|
11
|
+
# The composite_primary_key gem will by default generate WHERE clauses using an IN clause with a multi-column
|
12
|
+
# sub select, e.g.: SELECT * FROM my_table WHERE (id1, id2) IN (SELECT id1, id2 FROM my_table WHERE ...).
|
13
|
+
# This is not supported in Cloud Spanner. Instead, composite_primary_key should generate an EXISTS clause.
|
14
|
+
cpk_exists_subquery stmt
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Relation
|
19
|
+
prepend CpkExtension
|
20
|
+
end
|
21
|
+
end
|
@@ -55,9 +55,9 @@ module ActiveRecordSpannerAdapter
|
|
55
55
|
when :pdml
|
56
56
|
@grpc_transaction = @connection.session.create_pdml
|
57
57
|
else
|
58
|
-
@begin_transaction_selector = Google::Spanner::V1::TransactionSelector.new \
|
59
|
-
begin: Google::Spanner::V1::TransactionOptions.new(
|
60
|
-
read_write: Google::Spanner::V1::TransactionOptions::ReadWrite.new
|
58
|
+
@begin_transaction_selector = Google::Cloud::Spanner::V1::TransactionSelector.new \
|
59
|
+
begin: Google::Cloud::Spanner::V1::TransactionOptions.new(
|
60
|
+
read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new
|
61
61
|
)
|
62
62
|
|
63
63
|
end
|
@@ -143,7 +143,7 @@ module ActiveRecordSpannerAdapter
|
|
143
143
|
|
144
144
|
# Use the transaction that has been started by a BeginTransaction RPC or returned by a
|
145
145
|
# statement, if present.
|
146
|
-
return Google::Spanner::V1::TransactionSelector.new id: @grpc_transaction.transaction_id \
|
146
|
+
return Google::Cloud::Spanner::V1::TransactionSelector.new id: @grpc_transaction.transaction_id \
|
147
147
|
if @grpc_transaction
|
148
148
|
|
149
149
|
# Return a transaction selector that will instruct the statement to also start a transaction
|
@@ -98,6 +98,16 @@ module Arel # :nodoc: all
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
# For ActiveRecord 7.0
|
102
|
+
def visit_ActiveModel_Attribute o, collector
|
103
|
+
# Do not generate a query parameter if the value should be set to the PENDING_COMMIT_TIMESTAMP(), as that is
|
104
|
+
# not supported as a parameter value by Cloud Spanner.
|
105
|
+
return collector << "PENDING_COMMIT_TIMESTAMP()" \
|
106
|
+
if o.type.is_a?(ActiveRecord::Type::Spanner::Time) && o.value == :commit_timestamp
|
107
|
+
collector.add_bind(o, &bind_block)
|
108
|
+
end
|
109
|
+
|
110
|
+
# For ActiveRecord 6.x
|
101
111
|
def visit_Arel_Nodes_BindParam o, collector
|
102
112
|
# Do not generate a query parameter if the value should be set to the PENDING_COMMIT_TIMESTAMP(), as that is
|
103
113
|
# not supported as a parameter value by Cloud Spanner.
|
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.2.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-
|
11
|
+
date: 2022-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-cloud-spanner
|
@@ -28,16 +28,22 @@ dependencies:
|
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 6.0.0
|
34
|
+
- - "<"
|
32
35
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
36
|
+
version: '7.1'
|
34
37
|
type: :runtime
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 6.0.0
|
44
|
+
- - "<"
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
46
|
+
version: '7.1'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: autotest-suffix
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -264,6 +270,7 @@ files:
|
|
264
270
|
- acceptance/cases/migration/rename_column_test.rb
|
265
271
|
- acceptance/cases/models/calculation_query_test.rb
|
266
272
|
- acceptance/cases/models/generated_column_test.rb
|
273
|
+
- acceptance/cases/models/insert_all_test.rb
|
267
274
|
- acceptance/cases/models/mutation_test.rb
|
268
275
|
- acceptance/cases/models/query_test.rb
|
269
276
|
- acceptance/cases/sessions/session_not_found_test.rb
|
@@ -380,6 +387,16 @@ files:
|
|
380
387
|
- examples/snippets/hints/db/seeds.rb
|
381
388
|
- examples/snippets/hints/models/album.rb
|
382
389
|
- examples/snippets/hints/models/singer.rb
|
390
|
+
- examples/snippets/interleaved-tables/README.md
|
391
|
+
- examples/snippets/interleaved-tables/Rakefile
|
392
|
+
- examples/snippets/interleaved-tables/application.rb
|
393
|
+
- examples/snippets/interleaved-tables/config/database.yml
|
394
|
+
- examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb
|
395
|
+
- examples/snippets/interleaved-tables/db/schema.rb
|
396
|
+
- examples/snippets/interleaved-tables/db/seeds.rb
|
397
|
+
- examples/snippets/interleaved-tables/models/album.rb
|
398
|
+
- examples/snippets/interleaved-tables/models/singer.rb
|
399
|
+
- examples/snippets/interleaved-tables/models/track.rb
|
383
400
|
- examples/snippets/migrations/README.md
|
384
401
|
- examples/snippets/migrations/Rakefile
|
385
402
|
- examples/snippets/migrations/application.rb
|
@@ -485,6 +502,7 @@ files:
|
|
485
502
|
- lib/activerecord_spanner_adapter/index/column.rb
|
486
503
|
- lib/activerecord_spanner_adapter/information_schema.rb
|
487
504
|
- lib/activerecord_spanner_adapter/primary_key.rb
|
505
|
+
- lib/activerecord_spanner_adapter/relation.rb
|
488
506
|
- lib/activerecord_spanner_adapter/table.rb
|
489
507
|
- lib/activerecord_spanner_adapter/table/column.rb
|
490
508
|
- lib/activerecord_spanner_adapter/transaction.rb
|
@@ -512,7 +530,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
512
530
|
- !ruby/object:Gem::Version
|
513
531
|
version: '0'
|
514
532
|
requirements: []
|
515
|
-
rubygems_version: 3.3.
|
533
|
+
rubygems_version: 3.3.14
|
516
534
|
signing_key:
|
517
535
|
specification_version: 4
|
518
536
|
summary: Rails ActiveRecord connector for Google Spanner Database
|