activerecord-spanner-adapter 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Rakefile +3 -7
- data/activerecord-spanner-adapter.gemspec +2 -2
- data/lib/active_record/connection_adapters/spanner/client.rb +190 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +33 -18
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -1
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +3 -5
- data/lib/active_record/connection_adapters/spanner_adapter.rb +25 -14
- data/lib/activerecord-spanner-adapter/version.rb +1 -1
- metadata +11 -11
- data/.gitmodules +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98b65b0b5292ed6027d2dd1305a9794565438374
|
4
|
+
data.tar.gz: ff0fc0355000390592dd1c916d8eaedfdb4c24e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25ec93d1aa400524c5feb62efbb0bf1dce21468fdee84c5ba8d701cdeedd55b91880ff27880e46c6a19f5bd6d5791d289458a623a675d1089b1c99e1b892b92c
|
7
|
+
data.tar.gz: 83cd0c3ecec42157a2f0675c1c77900045c74dee42ba83a44474ce966e0dab83b1b5f8fbfb57a8d9eab60b0bf8ebdac3d1177ba4a81faf2734ebb9e62c20142d
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require "
|
2
|
+
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
t.libs << "lib"
|
7
|
-
t.test_files = FileList['test/**/*_test.rb']
|
8
|
-
end
|
4
|
+
RSpec::Core::RakeTask.new("spec")
|
5
|
+
task :default => :spec
|
9
6
|
|
10
|
-
task :default => :test
|
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency 'activerecord', "~> 5.0"
|
22
|
-
spec.add_dependency 'google-cloud-spanner'
|
22
|
+
spec.add_dependency 'google-cloud-spanner', "~> 0.23"
|
23
23
|
spec.add_dependency 'google-gax', '~> 0.8'
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.14"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
-
spec.add_development_dependency "
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.6.0"
|
27
27
|
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Spanner
|
4
|
+
# raised on unsupported operations in a certain transaction state.
|
5
|
+
class TransactionStateError < ActiveRecordError
|
6
|
+
end
|
7
|
+
|
8
|
+
class InitialPhaseClient
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
@next = self
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :next
|
15
|
+
|
16
|
+
delegate :insert, :update, :delete, :execute, :close, to: :client
|
17
|
+
|
18
|
+
def begin_transaction
|
19
|
+
@next = ReadWriteTransactionClient.begin(client)
|
20
|
+
end
|
21
|
+
|
22
|
+
def begin_snapshot(**options)
|
23
|
+
@next = SnapshotClient.begin(client, **options)
|
24
|
+
end
|
25
|
+
|
26
|
+
%w[ commit rollback ].each do |name|
|
27
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
28
|
+
def #{name}
|
29
|
+
raise TransactionStateError, 'not in a transaction'
|
30
|
+
end
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
attr_reader :client
|
36
|
+
end
|
37
|
+
|
38
|
+
class ReadWriteTransactionClient
|
39
|
+
def initialize(client, tx, commit_op, rollback_op)
|
40
|
+
@next = self
|
41
|
+
@client = client
|
42
|
+
@tx = tx
|
43
|
+
|
44
|
+
@commit_op = commit_op
|
45
|
+
@rollback_op = rollback_op
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def begin(client)
|
50
|
+
enum = enum_for(:transaction, client, deadline: 0)
|
51
|
+
closer = ->{
|
52
|
+
begin
|
53
|
+
enum.next
|
54
|
+
rescue StopIteration => stop
|
55
|
+
stop.result
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
begin
|
60
|
+
tx, rollback_op = enum.next
|
61
|
+
on_rollback = ->{
|
62
|
+
begin
|
63
|
+
rollback_op.call
|
64
|
+
ensure
|
65
|
+
closer.call
|
66
|
+
end
|
67
|
+
}
|
68
|
+
return ReadPhaseClient.new(client, tx, closer, on_rollback)
|
69
|
+
rescue
|
70
|
+
closer.call
|
71
|
+
raise
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def transaction(client, *args)
|
77
|
+
client.transaction(*args) do |tx|
|
78
|
+
rollbacked = false
|
79
|
+
yield tx, ->{ rollbacked = true }
|
80
|
+
raise Google::Cloud::Spanner::Rollback if rollbacked
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :next
|
86
|
+
|
87
|
+
delegate :close, to: :client
|
88
|
+
delegate :execute, :insert, :update, :delete, to: :tx
|
89
|
+
|
90
|
+
def commit
|
91
|
+
result = @commit_op.call
|
92
|
+
result.tap do
|
93
|
+
@next = InitialPhaseClient.new(@client)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def rollback
|
98
|
+
@rollback_op.call.tap do
|
99
|
+
@next = InitialPhaseClient.new(@client)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
%w[ begin_transaction begin_snapshot ].each do |name|
|
104
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
105
|
+
def #{name}(*args)
|
106
|
+
raise TransactionStateError, 'already in a transaction'
|
107
|
+
end
|
108
|
+
EOS
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
attr_reader :client, :tx # :nodoc:
|
113
|
+
end
|
114
|
+
|
115
|
+
class ReadPhaseClient < ReadWriteTransactionClient
|
116
|
+
%w[ insert update delete ].each do |name|
|
117
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
118
|
+
def #{name}(*args)
|
119
|
+
super.tap { transit_to_write_phase }
|
120
|
+
end
|
121
|
+
EOS
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def transit_to_write_phase
|
126
|
+
@next = WritePhaseClient.new(@client, @tx, @commit_op, @rollback_op)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class WritePhaseClient < ReadWriteTransactionClient
|
131
|
+
def execute(*args)
|
132
|
+
raise TransactionStateError, "cannot read after write within a transaction"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class SnapshotClient
|
137
|
+
def initialize(client, **options)
|
138
|
+
@client = client
|
139
|
+
|
140
|
+
enum = client.enum_for(:snapshot, **options)
|
141
|
+
@snapshot, @closer = enum.next, ->{ loop { enum.next } }
|
142
|
+
@next = self
|
143
|
+
end
|
144
|
+
|
145
|
+
class << self
|
146
|
+
alias begin new
|
147
|
+
end
|
148
|
+
|
149
|
+
attr_reader :next
|
150
|
+
|
151
|
+
delegate :close, to: :client
|
152
|
+
delegate :execute, to: :snapshot
|
153
|
+
|
154
|
+
%w[ insert update delete ].each do |name|
|
155
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
156
|
+
def #{name}(*)
|
157
|
+
raise TransactionStateError, "cannot write within a read-only transaction"
|
158
|
+
end
|
159
|
+
EOS
|
160
|
+
end
|
161
|
+
|
162
|
+
%w[ commit rollback ].each do |name|
|
163
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
164
|
+
def #{name}
|
165
|
+
terminate_phase
|
166
|
+
true
|
167
|
+
end
|
168
|
+
EOS
|
169
|
+
end
|
170
|
+
|
171
|
+
%w[ begin_transaction begin_snapshot ].each do |name|
|
172
|
+
class_eval(<<-"EOS", __FILE__, __LINE__+1)
|
173
|
+
def #{name}(*args)
|
174
|
+
raise TransactionStateError, 'already in a transaction'
|
175
|
+
end
|
176
|
+
EOS
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
attr_reader :client, :snapshot
|
181
|
+
|
182
|
+
def terminate_phase
|
183
|
+
@next = InitialPhaseClient.new(@client)
|
184
|
+
ensure
|
185
|
+
@closer.call
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -207,16 +207,14 @@ module ActiveRecord
|
|
207
207
|
}
|
208
208
|
|
209
209
|
log(fake_sql, name) do
|
210
|
-
|
211
|
-
c.insert table, row
|
212
|
-
end
|
210
|
+
with_phase_transition {|client| client.insert(table, row) }
|
213
211
|
end
|
214
212
|
|
215
213
|
id_value
|
216
214
|
end
|
217
215
|
|
218
216
|
def update(arel, name = nil, binds = [])
|
219
|
-
raise NotImplementedError, "
|
217
|
+
raise NotImplementedError, "UPDATE statement in raw SQL is not supported" unless arel.respond_to?(:ast)
|
220
218
|
|
221
219
|
type_casted_binds = binds.map {|attr| type_cast(attr.value_for_database) }
|
222
220
|
table, target, values = MutationVisitor.new(self, type_casted_binds.dup).accept(arel.ast)
|
@@ -236,16 +234,13 @@ module ActiveRecord
|
|
236
234
|
rows = target.map {|id| row.merge(pk => id) }
|
237
235
|
|
238
236
|
log(fake_sql, name, binds) do
|
239
|
-
|
240
|
-
c.update(table, rows)
|
241
|
-
end
|
237
|
+
with_phase_transition {|client| client.update(table, rows) }
|
242
238
|
end
|
243
|
-
|
244
|
-
true
|
239
|
+
target.size
|
245
240
|
end
|
246
241
|
|
247
242
|
def delete(arel, name, binds)
|
248
|
-
raise NotImplementedError, "DELETE in raw SQL is not supported" unless arel.respond_to?(:ast)
|
243
|
+
raise NotImplementedError, "DELETE statement in raw SQL is not supported" unless arel.respond_to?(:ast)
|
249
244
|
|
250
245
|
type_casted_binds = binds.map {|attr| type_cast(attr.value_for_database) }
|
251
246
|
table, target, wheres = MutationVisitor.new(self, type_casted_binds.dup).accept(arel.ast)
|
@@ -272,9 +267,7 @@ module ActiveRecord
|
|
272
267
|
end
|
273
268
|
|
274
269
|
log(fake_sql, name, binds) do
|
275
|
-
|
276
|
-
c.delete(table, keyset)
|
277
|
-
end
|
270
|
+
with_phase_transition {|client| client.delete(table, keyset) }
|
278
271
|
end
|
279
272
|
|
280
273
|
keyset.size
|
@@ -305,14 +298,36 @@ module ActiveRecord
|
|
305
298
|
end
|
306
299
|
|
307
300
|
log(sql, name, binds) do
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
301
|
+
executor = client
|
302
|
+
executor = raw_client if name == 'SCHEMA'
|
303
|
+
results = executor.execute(sql, params: spanner_binds)
|
304
|
+
columns = results.fields.keys
|
305
|
+
rows = results.rows.map(&:to_a)
|
313
306
|
ActiveRecord::Result.new(columns.map(&:to_s), rows)
|
314
307
|
end
|
315
308
|
end
|
309
|
+
|
310
|
+
def begin_db_transaction
|
311
|
+
with_phase_transition {|client| client.begin_transaction }
|
312
|
+
end
|
313
|
+
|
314
|
+
def begin_isolated_db_transaction(isolation)
|
315
|
+
with_phase_transition {|client| client.begin_snapshot(**isolation) }
|
316
|
+
end
|
317
|
+
|
318
|
+
def commit_db_transaction
|
319
|
+
with_phase_transition {|client| client.commit }
|
320
|
+
end
|
321
|
+
|
322
|
+
def exec_rollback_db_transaction
|
323
|
+
with_phase_transition {|client| client.rollback }
|
324
|
+
end
|
325
|
+
|
326
|
+
def with_phase_transition
|
327
|
+
result = yield client
|
328
|
+
@client = client.next
|
329
|
+
result
|
330
|
+
end
|
316
331
|
end
|
317
332
|
end
|
318
333
|
end
|
@@ -5,10 +5,16 @@ module ActiveRecord
|
|
5
5
|
|
6
6
|
class SchemaCreation < AbstractAdapter::SchemaCreation
|
7
7
|
def visit_TableDefinition(o)
|
8
|
-
pk = o.columns.find {|c| c.
|
8
|
+
pk = o.columns.find {|c| c.options[:primary_key] }
|
9
9
|
ddl = "#{super} PRIMARY KEY (#{quote_column_name(pk.name)})"
|
10
10
|
DDL.new(ddl)
|
11
11
|
end
|
12
|
+
|
13
|
+
def add_column_options!(sql, options)
|
14
|
+
if options[:null] == false
|
15
|
+
sql << " NOT NULL"
|
16
|
+
end
|
17
|
+
end
|
12
18
|
end
|
13
19
|
end
|
14
20
|
end
|
@@ -119,16 +119,14 @@ module ActiveRecord
|
|
119
119
|
end
|
120
120
|
|
121
121
|
def create_database(name, instance_id: nil, statements: [])
|
122
|
-
|
123
|
-
|
124
|
-
statements: statements)
|
122
|
+
job = conn.create_database(instance_id || @instance_id, name,
|
123
|
+
statements: statements)
|
125
124
|
job.wait_until_done! unless job.done?
|
126
125
|
raise_on_error(job)
|
127
126
|
end
|
128
127
|
|
129
128
|
def drop_database(name, instance_id: nil)
|
130
|
-
service
|
131
|
-
service.drop_database(instance_id || instance.instance_id, name)
|
129
|
+
conn.service.drop_database(instance_id || @instance_id, name)
|
132
130
|
end
|
133
131
|
|
134
132
|
def drop_table(name, options = {})
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'google/cloud/spanner'
|
2
2
|
|
3
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
4
|
+
require 'active_record/connection_adapters/spanner/client'
|
4
5
|
require 'active_record/connection_adapters/spanner/database_statements'
|
5
6
|
require 'active_record/connection_adapters/spanner/schema_creation'
|
6
7
|
require 'active_record/connection_adapters/spanner/schema_statements'
|
7
8
|
require 'active_record/connection_adapters/spanner/quoting'
|
9
|
+
require 'grpc'
|
8
10
|
|
9
11
|
module ActiveRecord
|
10
12
|
module ConnectionHandling
|
@@ -28,9 +30,9 @@ module ActiveRecord
|
|
28
30
|
include Spanner::Quoting
|
29
31
|
|
30
32
|
def initialize(connection, logger, config)
|
31
|
-
super(connection, logger, config)
|
32
33
|
conn_params = config.symbolize_keys.slice(*ADAPTER_OPTS)
|
33
34
|
connect(conn_params)
|
35
|
+
super(connection, logger, config)
|
34
36
|
end
|
35
37
|
|
36
38
|
def schema_creation # :nodoc:
|
@@ -42,19 +44,22 @@ module ActiveRecord
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def active?
|
45
|
-
|
46
|
-
|
47
|
+
[
|
48
|
+
::GRPC::Core::ConnectivityStates::IDLE,
|
49
|
+
::GRPC::Core::ConnectivityStates::READY,
|
50
|
+
].include?(@conn.service.channel.connectivity_state)
|
47
51
|
end
|
48
52
|
|
49
53
|
def connect(params)
|
50
54
|
client_params = params.slice(*CLIENT_PARAMS)
|
51
|
-
@
|
55
|
+
@conn = Google::Cloud::Spanner.new(**client_params)
|
52
56
|
@instance_id = params[:instance]
|
53
57
|
@database_id = params[:database]
|
54
58
|
end
|
55
59
|
|
56
60
|
def disconnect!
|
57
|
-
|
61
|
+
super
|
62
|
+
release_client!
|
58
63
|
end
|
59
64
|
|
60
65
|
def prefetch_primary_key?(table_name = nil)
|
@@ -67,7 +72,7 @@ module ActiveRecord
|
|
67
72
|
end
|
68
73
|
|
69
74
|
private
|
70
|
-
attr_reader :
|
75
|
+
attr_reader :conn
|
71
76
|
|
72
77
|
def initialize_type_map(m) # :nodoc:
|
73
78
|
register_class_with_limit m, %r(STRING)i, Type::String
|
@@ -82,9 +87,8 @@ module ActiveRecord
|
|
82
87
|
# TODO(yugui) Support array and struct
|
83
88
|
end
|
84
89
|
|
85
|
-
|
86
90
|
def instance
|
87
|
-
@instance ||=
|
91
|
+
@instance ||= conn.instance(@instance_id)
|
88
92
|
raise ActiveRecord::NoDatabaseError unless @instance
|
89
93
|
|
90
94
|
@instance
|
@@ -101,15 +105,22 @@ module ActiveRecord
|
|
101
105
|
@db
|
102
106
|
end
|
103
107
|
|
104
|
-
def
|
105
|
-
@
|
108
|
+
def raw_client
|
109
|
+
@raw_client ||= conn.client(@instance_id, @database_id)
|
106
110
|
end
|
107
111
|
|
108
|
-
def
|
109
|
-
@
|
110
|
-
@session = nil
|
112
|
+
def client
|
113
|
+
@client ||= ConnectionAdapters::Spanner::InitialPhaseClient.new(raw_client)
|
111
114
|
end
|
115
|
+
|
116
|
+
def release_client!
|
117
|
+
@client&.close
|
118
|
+
@client = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
#def reset_transaction
|
122
|
+
# # @transaction_manager = Spanner::TransactionManager.new(self, client)
|
123
|
+
#end
|
112
124
|
end
|
113
125
|
end
|
114
126
|
end
|
115
|
-
|
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: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yuki Yugui Sonoda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03
|
11
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: google-cloud-spanner
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '0.23'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '0.23'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: google-gax
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,19 +81,19 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '10.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rspec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 3.6.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 3.6.0
|
97
97
|
description: Connection Adapter of Google Cloud Spanner to ActiveRecord O/R mapper
|
98
98
|
library
|
99
99
|
email:
|
@@ -103,7 +103,6 @@ extensions: []
|
|
103
103
|
extra_rdoc_files: []
|
104
104
|
files:
|
105
105
|
- ".gitignore"
|
106
|
-
- ".gitmodules"
|
107
106
|
- ".travis.yml"
|
108
107
|
- Gemfile
|
109
108
|
- LICENSE
|
@@ -113,6 +112,7 @@ files:
|
|
113
112
|
- bin/console
|
114
113
|
- bin/setup
|
115
114
|
- lib/active_record/connection_adapters/spanner.rb
|
115
|
+
- lib/active_record/connection_adapters/spanner/client.rb
|
116
116
|
- lib/active_record/connection_adapters/spanner/database_statements.rb
|
117
117
|
- lib/active_record/connection_adapters/spanner/quoting.rb
|
118
118
|
- lib/active_record/connection_adapters/spanner/schema_creation.rb
|
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
138
|
version: '0'
|
139
139
|
requirements: []
|
140
140
|
rubyforge_project:
|
141
|
-
rubygems_version: 2.6.
|
141
|
+
rubygems_version: 2.6.10
|
142
142
|
signing_key:
|
143
143
|
specification_version: 4
|
144
144
|
summary: Adapts Google Cloud Spanner to ActiveRecord
|
data/.gitmodules
DELETED