activerecord-spanner-adapter 2.1.0 → 2.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +8 -2
  3. data/.github/workflows/ci.yaml +8 -2
  4. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +7 -1
  5. data/.github/workflows/nightly-unit-tests.yaml +7 -1
  6. data/.github/workflows/samples.yaml +7 -1
  7. data/.kokoro/trampoline_v2.sh +0 -0
  8. data/.release-please-manifest.json +1 -1
  9. data/CHANGELOG.md +13 -0
  10. data/Gemfile +1 -1
  11. data/acceptance/cases/tasks/database_tasks_test.rb +1 -1
  12. data/acceptance/test_helper.rb +12 -0
  13. data/activerecord-spanner-adapter.gemspec +3 -4
  14. data/examples/snippets/isolation-level/README.md +39 -0
  15. data/examples/snippets/isolation-level/Rakefile +13 -0
  16. data/examples/snippets/isolation-level/application.rb +36 -0
  17. data/examples/snippets/isolation-level/config/database.yml +10 -0
  18. data/examples/snippets/isolation-level/db/migrate/01_create_tables.rb +22 -0
  19. data/examples/snippets/isolation-level/db/seeds.rb +25 -0
  20. data/examples/snippets/isolation-level/models/album.rb +9 -0
  21. data/examples/snippets/isolation-level/models/singer.rb +9 -0
  22. data/lib/active_record/connection_adapters/spanner/database_statements.rb +4 -2
  23. data/lib/active_record/connection_adapters/spanner_adapter.rb +6 -1
  24. data/lib/activerecord_spanner_adapter/base.rb +2 -2
  25. data/lib/activerecord_spanner_adapter/connection.rb +6 -1
  26. data/lib/activerecord_spanner_adapter/transaction.rb +12 -2
  27. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  28. data/lib/arel/visitors/spanner.rb +11 -4
  29. metadata +20 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 539c47de17139481981cfaaaf2b65d43d8f0b27642ad68663658123d0053a8fb
4
- data.tar.gz: c4eb45d075689d08da773ebc11bd1e24df9c6cb7b6ab79a2a352c5c4f4442fbf
3
+ metadata.gz: 35016d190d27c935b9a004570e326d7cdc75af064f877bd619334ea967cf4d7a
4
+ data.tar.gz: 1cf91b818edc4d594fcf623f0a3218927406b47523931641fb6e844c88b578ff
5
5
  SHA512:
6
- metadata.gz: a7d32fa8e2aeeb7785a35f4d11c2dd1ae2db7298d030b6d0da3f08ef5acb2c19833f4f80bc4b5e4b3b174ff9a34121fe97f50ddae902a3af1505eacec311b790
7
- data.tar.gz: 1ec5e7a0370d3bbcc3dd8b0c2b7412f81a57f03703784a0b20476d20d9016d409efcd4f3dc8ecd234e64d1cbedcfe804497fd6b65fd277d7feca014393627edf
6
+ metadata.gz: 96dfb48e59b278093e118006e393e03b8981fd370605664830a73ad09873ead412a726a838407595fb903194e64f7b368dc940a443d774847679c316742eefc3
7
+ data.tar.gz: 5aa61e971ad3b21f9a9dee3476e7ce9fe5b52f080c2bd507e39cd2aa90719802d0ba5bcd024c3fa521feabc1e8756a249757dda5d3d432c67111ea3479d50e17
@@ -18,12 +18,18 @@ jobs:
18
18
  strategy:
19
19
  max-parallel: 4
20
20
  matrix:
21
- ruby: ["3.1", "3.2", "3.3"]
21
+ ruby: ["3.1", "3.2", "3.3", "3.4"]
22
22
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
23
23
  # Exclude combinations that are not supported.
24
24
  exclude:
25
25
  - ruby: "3.1"
26
26
  ar: "~> 8.0.0"
27
+ - ruby: "3.4"
28
+ ar: "~> 7.0.0"
29
+ - ruby: "3.4"
30
+ ar: "~> 7.1.0"
31
+ - ruby: "3.4"
32
+ ar: "~> 7.2.0"
27
33
  env:
28
34
  AR_VERSION: ${{ matrix.ar }}
29
35
  steps:
@@ -38,7 +44,7 @@ jobs:
38
44
  - name: Install dependencies
39
45
  run: bundle install
40
46
  - name: Run acceptance tests on emulator
41
- run: bundle exec rake acceptance
47
+ run: bundle exec rake acceptance TESTOPTS="-v"
42
48
  env:
43
49
  SPANNER_EMULATOR_HOST: localhost:9010
44
50
  SPANNER_TEST_PROJECT: test-project
@@ -10,12 +10,18 @@ jobs:
10
10
  strategy:
11
11
  max-parallel: 4
12
12
  matrix:
13
- ruby: ["3.1", "3.2", "3.3"]
13
+ ruby: ["3.1", "3.2", "3.3", "3.4"]
14
14
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
15
15
  # Exclude combinations that are not supported.
16
16
  exclude:
17
17
  - ruby: "3.1"
18
18
  ar: "~> 8.0.0"
19
+ - ruby: "3.4"
20
+ ar: "~> 7.0.0"
21
+ - ruby: "3.4"
22
+ ar: "~> 7.1.0"
23
+ - ruby: "3.4"
24
+ ar: "~> 7.2.0"
19
25
  env:
20
26
  AR_VERSION: ${{ matrix.ar }}
21
27
  steps:
@@ -30,4 +36,4 @@ jobs:
30
36
  - name: Install dependencies
31
37
  run: bundle install
32
38
  - name: Run tests
33
- run: bundle exec rake test
39
+ run: bundle exec rake test --trace TESTOPTS="-v"
@@ -18,12 +18,18 @@ jobs:
18
18
  strategy:
19
19
  max-parallel: 4
20
20
  matrix:
21
- ruby: ["3.1", "3.2", "3.3"]
21
+ ruby: ["3.1", "3.2", "3.3", "3.4"]
22
22
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
23
23
  # Exclude combinations that are not supported.
24
24
  exclude:
25
25
  - ruby: "3.1"
26
26
  ar: "~> 8.0.0"
27
+ - ruby: "3.4"
28
+ ar: "~> 7.0.0"
29
+ - ruby: "3.4"
30
+ ar: "~> 7.1.0"
31
+ - ruby: "3.4"
32
+ ar: "~> 7.2.0"
27
33
  env:
28
34
  AR_VERSION: ${{ matrix.ar }}
29
35
  steps:
@@ -11,12 +11,18 @@ jobs:
11
11
  max-parallel: 4
12
12
  matrix:
13
13
  # Run acceptance tests all supported combinations of Ruby and ActiveRecord.
14
- ruby: ["3.1", "3.2", "3.3"]
14
+ ruby: ["3.1", "3.2", "3.3", "3.4"]
15
15
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
16
16
  # Exclude combinations that are not supported.
17
17
  exclude:
18
18
  - ruby: "3.1"
19
19
  ar: "~> 8.0.0"
20
+ - ruby: "3.4"
21
+ ar: "~> 7.0.0"
22
+ - ruby: "3.4"
23
+ ar: "~> 7.1.0"
24
+ - ruby: "3.4"
25
+ ar: "~> 7.2.0"
20
26
  env:
21
27
  AR_VERSION: ${{ matrix.ar }}
22
28
  steps:
@@ -8,12 +8,18 @@ jobs:
8
8
  strategy:
9
9
  max-parallel: 4
10
10
  matrix:
11
- ruby: ["3.1", "3.2", "3.3"]
11
+ ruby: ["3.1", "3.2", "3.3", "3.4"]
12
12
  ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
13
13
  # Exclude combinations that are not supported.
14
14
  exclude:
15
15
  - ruby: "3.1"
16
16
  ar: "~> 8.0.0"
17
+ - ruby: "3.4"
18
+ ar: "~> 7.0.0"
19
+ - ruby: "3.4"
20
+ ar: "~> 7.1.0"
21
+ - ruby: "3.4"
22
+ ar: "~> 7.2.0"
17
23
  env:
18
24
  AR_VERSION: ${{ matrix.ar }}
19
25
  steps:
File without changes
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "2.1.0"
2
+ ".": "2.3.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ### 2.3.0 (2025-05-30)
4
+
5
+ #### Features
6
+
7
+ * Add optimizer hint syntax to set a priority in request options ([#363](https://github.com/googleapis/ruby-spanner-activerecord/issues/363))
8
+ * support ruby 3.4 ([#359](https://github.com/googleapis/ruby-spanner-activerecord/issues/359))
9
+
10
+ ### 2.2.0 (2025-04-03)
11
+
12
+ #### Features
13
+
14
+ * transaction isolation level ([#355](https://github.com/googleapis/ruby-spanner-activerecord/issues/355))
15
+
3
16
  ### 2.1.0 (2025-03-17)
4
17
 
5
18
  #### Features
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gem "ostruct"
9
9
  gem "minitest", "~> 5.25.0"
10
10
  gem "minitest-rg", "~> 5.3.0"
11
11
  gem "pry", "~> 0.14.2"
12
- gem "pry-byebug", "~> 3.10.1"
12
+ gem "pry-byebug", "~> 3.11.0"
13
13
  # Add sqlite3 for testing for compatibility with other adapters.
14
14
  gem 'sqlite3'
15
15
 
@@ -65,9 +65,9 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def drop_database
68
+ spanner_instance.database(@database_id)&.drop
68
69
  ActiveRecord::Base.connection_pool.disconnect!
69
70
  ActiveRecordSpannerAdapter::Connection.reset_information_schemas!
70
- spanner_instance.database(@database_id)&.drop
71
71
  end
72
72
 
73
73
  def test_structure_dump_and_load
@@ -188,6 +188,7 @@ module SpannerAdapter
188
188
  unless @skip_test_table_create
189
189
  connection.drop_table :test_models, if_exists: true
190
190
  end
191
+ ActiveRecord::Base.connection_pool.disconnect!
191
192
 
192
193
  super
193
194
  end
@@ -273,8 +274,19 @@ module SpannerAdapter
273
274
  ActiveSupport::Notifications.subscribe("sql.active_record", SQLCounter.new)
274
275
  end
275
276
 
277
+ module Kernel
278
+ # Monkey-patch Kernel.exit to call exit! instead.
279
+ # This prevents the tests from getting stuck after running (probably) due to
280
+ # gRPC connections that have not been closed yet.
281
+ def exit status = true
282
+ exit! status
283
+ end
284
+ end
285
+
276
286
  Minitest.after_run do
277
287
  drop_test_database
288
+ ActiveRecord::Base.connection_pool.disconnect!
289
+ ActiveRecordSpannerAdapter::Connection.reset_information_schemas!
278
290
  end
279
291
 
280
292
  create_test_database
@@ -24,14 +24,13 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = ">= 3.1"
26
26
 
27
- spec.add_dependency "google-cloud-spanner", "~> 2.18"
28
- # Pin gRPC to 1.64.3, as 1.65 and 1.66 cause test runs to hang randomly.
29
- spec.add_dependency "grpc", "1.64.3"
27
+ spec.add_dependency "google-cloud-spanner", "~> 2.25"
28
+ spec.add_dependency "google-cloud-spanner-v1", "~> 1.7"
30
29
  spec.add_runtime_dependency "activerecord", [">= 7.0", "< 9"]
31
30
 
32
31
  spec.add_development_dependency "autotest-suffix", "~> 1.1"
33
32
  spec.add_development_dependency "bundler", "~> 2.0"
34
- spec.add_development_dependency "google-style", "~> 1.30.1"
33
+ spec.add_development_dependency "google-style", "~> 1.31.0"
35
34
  spec.add_development_dependency "minitest", "~> 5.10"
36
35
  spec.add_development_dependency "minitest-autotest", "~> 1.0"
37
36
  spec.add_development_dependency "minitest-focus", "~> 1.1"
@@ -0,0 +1,39 @@
1
+ # Sample - Isolation Level
2
+
3
+ This example shows how to use a specific isolation level for read/write transactions
4
+ using the Spanner ActiveRecord adapter.
5
+
6
+ You can specify the isolation level in two ways:
7
+
8
+ 1. Set a default in the database configuration:
9
+
10
+ ```yaml
11
+ development:
12
+ adapter: spanner
13
+ emulator_host: localhost:9010
14
+ project: test-project
15
+ instance: test-instance
16
+ database: testdb
17
+ isolation_level: :serializable,
18
+ pool: 5
19
+ timeout: 5000
20
+ schema_dump: false
21
+ ```
22
+
23
+ 2. Specify the isolation level for a specific transaction. This will override any
24
+ default that is set in the database configuration.
25
+
26
+ ```ruby
27
+ ActiveRecord::Base.transaction isolation: :repeatable_read do
28
+ # Execute transaction code...
29
+ end
30
+ ```
31
+
32
+ The sample will automatically start a Spanner Emulator in a Docker container and execute the sample
33
+ against that emulator. The emulator will automatically be stopped when the application finishes.
34
+
35
+ Run the application with the command
36
+
37
+ ```bash
38
+ bundle exec rake run
39
+ ```
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 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
+ require_relative "../config/environment"
8
+ require "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to specify a transaction isolation level for Spanner with ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[isolation-level]" }
13
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright 2025 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
+ require "io/console"
8
+ require_relative "../config/environment"
9
+ require_relative "models/singer"
10
+ require_relative "models/album"
11
+
12
+ class Application
13
+ def self.run # rubocop:disable Metrics/AbcSize
14
+ from_album = nil
15
+ to_album = nil
16
+ # Execute a read/write transaction using isolation level repeatable read.
17
+ puts "Executing a read/write transaction using isolation level repeatable read"
18
+ ActiveRecord::Base.transaction isolation: :repeatable_read do
19
+ # Transfer a marketing budget of 10,000 from one album to another.
20
+ from_album = Album.all.sample
21
+ to_album = Album.where.not(id: from_album.id).sample
22
+
23
+ puts ""
24
+ puts "Transferring 10,000 marketing budget from #{from_album.title} (#{from_album.marketing_budget}) " \
25
+ "to #{to_album.title} (#{to_album.marketing_budget})"
26
+ from_album.update marketing_budget: from_album.marketing_budget - 10000
27
+ to_album.update marketing_budget: to_album.marketing_budget + 10000
28
+ end
29
+ puts ""
30
+ puts "Budgets after update:"
31
+ puts "Marketing budget #{from_album.title}: #{from_album.reload.marketing_budget}"
32
+ puts "Marketing budget #{to_album.title}: #{to_album.reload.marketing_budget}"
33
+ end
34
+ end
35
+
36
+ Application.run
@@ -0,0 +1,10 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ isolation_level: :serializable,
8
+ pool: 5
9
+ timeout: 5000
10
+ schema_dump: false
@@ -0,0 +1,22 @@
1
+ # Copyright 2021 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
+ class CreateTables < ActiveRecord::Migration[6.0]
8
+ def change
9
+ connection.ddl_batch do
10
+ create_table :singers do |t|
11
+ t.string :first_name
12
+ t.string :last_name
13
+ end
14
+
15
+ create_table :albums do |t|
16
+ t.string :title
17
+ t.numeric :marketing_budget
18
+ t.references :singer, index: false, foreign_key: true
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright 2021 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
+ require_relative "../../config/environment"
8
+ require_relative "../models/singer"
9
+ require_relative "../models/album"
10
+
11
+ first_names = ["Pete", "Alice", "John", "Ethel", "Trudy", "Naomi", "Wendy", "Ruben", "Thomas", "Elly"]
12
+ last_names = ["Wendelson", "Allison", "Peterson", "Johnson", "Henderson", "Ericsson", "Aronson", "Tennet", "Courtou"]
13
+
14
+ adjectives = ["daily", "happy", "blue", "generous", "cooked", "bad", "open"]
15
+ nouns = ["windows", "potatoes", "bank", "street", "tree", "glass", "bottle"]
16
+ budgets = [15000, 25000, 10000, 20000, 30000, 12000, 13000]
17
+
18
+ 5.times do
19
+ Singer.create first_name: first_names.sample, last_name: last_names.sample
20
+ end
21
+
22
+ 20.times do
23
+ singer_id = Singer.all.sample.id
24
+ Album.create title: "#{adjectives.sample} #{nouns.sample}", marketing_budget: budgets.sample, singer_id: singer_id
25
+ end
@@ -0,0 +1,9 @@
1
+ # Copyright 2021 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
+ class Album < ActiveRecord::Base
8
+ belongs_to :singer
9
+ end
@@ -0,0 +1,9 @@
1
+ # Copyright 2021 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
+ class Singer < ActiveRecord::Base
8
+ has_many :albums
9
+ end
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  end
30
30
 
31
31
  def internal_execute sql, name = "SQL", binds = [],
32
- prepare: false, async: false, allow_retry: false # rubocop:disable Lint/UnusedMethodArgument, /
32
+ prepare: false, async: false, allow_retry: false # rubocop:disable Lint/UnusedMethodArgument
33
33
  statement_type = sql_statement_type sql
34
34
  # Call `transform` to invoke any query transformers that might have been registered.
35
35
  sql = transform sql
@@ -292,7 +292,7 @@ module ActiveRecord
292
292
  if isolation.count != 1
293
293
  else
294
294
  raise "Unsupported isolation level: #{isolation}" unless
295
- [:serializable, :read_only, :buffered_mutations, :pdml].include? isolation
295
+ [:serializable, :repeatable_read, :read_only, :buffered_mutations, :pdml].include? isolation
296
296
  end
297
297
 
298
298
  log "BEGIN #{isolation}" do
@@ -406,8 +406,10 @@ module ActiveRecord
406
406
  end
407
407
 
408
408
  DDL_REGX = build_sql_statement_regexp(:create, :alter, :drop).freeze
409
+ private_constant :DDL_REGX
409
410
 
410
411
  DML_REGX = build_sql_statement_regexp(:insert, :delete, :update).freeze
412
+ private_constant :DML_REGX
411
413
 
412
414
  def sql_statement_type sql
413
415
  case sql
@@ -150,7 +150,8 @@ module ActiveRecord
150
150
  end
151
151
 
152
152
  # Spanner Connection API
153
- delegate :ddl_batch, :ddl_batch?, :start_batch_ddl, :abort_batch, :run_batch, to: :@connection
153
+ delegate :ddl_batch, :ddl_batch?, :start_batch_ddl, :abort_batch, :run_batch,
154
+ :isolation_level, :isolation_level=, to: :@connection
154
155
 
155
156
  def current_spanner_transaction
156
157
  @connection.current_transaction
@@ -170,6 +171,10 @@ module ActiveRecord
170
171
  false
171
172
  end
172
173
 
174
+ def supports_transaction_isolation?
175
+ true
176
+ end
177
+
173
178
  def supports_foreign_keys?
174
179
  true
175
180
  end
@@ -21,7 +21,7 @@ module ActiveRecord
21
21
  # Creates an object (or multiple objects) and saves it to the database. This method will use mutations instead
22
22
  # of DML if there is no active transaction, or if the active transaction has been created with the option
23
23
  # isolation: :buffered_mutations.
24
- def self.create! attributes = nil, &block
24
+ def self.create!(attributes = nil, &)
25
25
  return super unless spanner_adapter?
26
26
  return super if active_transaction?
27
27
 
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  end
31
31
  end
32
32
 
33
- def self.create attributes = nil, &block
33
+ def self.create(attributes = nil, &)
34
34
  return super unless spanner_adapter?
35
35
  return super if active_transaction?
36
36
 
@@ -14,10 +14,12 @@ module ActiveRecordSpannerAdapter
14
14
  attr_reader :database_id
15
15
  attr_reader :spanner
16
16
  attr_accessor :current_transaction
17
+ attr_accessor :isolation_level
17
18
 
18
19
  def initialize config
19
20
  @instance_id = config[:instance]
20
21
  @database_id = config[:database]
22
+ @isolation_level = config[:isolation_level]
21
23
  @spanner = self.class.spanners config
22
24
  end
23
25
 
@@ -42,6 +44,9 @@ module ActiveRecordSpannerAdapter
42
44
  # Call this method if you drop and recreate a database with the same name
43
45
  # to prevent the cached information to be used for the new database.
44
46
  def self.reset_information_schemas!
47
+ @information_schemas.each_value do |info_schema|
48
+ info_schema.connection.disconnect!
49
+ end
45
50
  @information_schemas = {}
46
51
  end
47
52
 
@@ -271,7 +276,7 @@ module ActiveRecordSpannerAdapter
271
276
 
272
277
  def begin_transaction isolation = nil
273
278
  raise "Nested transactions are not allowed" if current_transaction&.active?
274
- self.current_transaction = Transaction.new self, isolation
279
+ self.current_transaction = Transaction.new self, isolation || @isolation_level
275
280
  current_transaction.begin
276
281
  current_transaction
277
282
  end
@@ -55,11 +55,12 @@ module ActiveRecordSpannerAdapter
55
55
  when :pdml
56
56
  @grpc_transaction = @connection.session.create_pdml
57
57
  else
58
+ grpc_isolation = _transaction_isolation_level_to_grpc @isolation
58
59
  @begin_transaction_selector = Google::Cloud::Spanner::V1::TransactionSelector.new \
59
60
  begin: Google::Cloud::Spanner::V1::TransactionOptions.new(
60
- read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new
61
+ read_write: Google::Cloud::Spanner::V1::TransactionOptions::ReadWrite.new,
62
+ isolation_level: grpc_isolation
61
63
  )
62
-
63
64
  end
64
65
  @state = :STARTED
65
66
  rescue Google::Cloud::NotFoundError => e
@@ -75,6 +76,15 @@ module ActiveRecordSpannerAdapter
75
76
  end
76
77
  end
77
78
 
79
+ def _transaction_isolation_level_to_grpc isolation
80
+ case isolation
81
+ when :serializable
82
+ Google::Cloud::Spanner::V1::TransactionOptions::IsolationLevel::SERIALIZABLE
83
+ when :repeatable_read
84
+ Google::Cloud::Spanner::V1::TransactionOptions::IsolationLevel::REPEATABLE_READ
85
+ end
86
+ end
87
+
78
88
  # Forces a BeginTransaction RPC for a read/write transaction. This is used by a
79
89
  # connection if the first statement of a transaction failed.
80
90
  def force_begin_read_write
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "2.1.0".freeze
8
+ VERSION = "2.3.0".freeze
9
9
  end
@@ -91,10 +91,17 @@ module Arel # :nodoc: all
91
91
  StalenessHint.new min_read_timestamp: time
92
92
  next
93
93
  end
94
- next unless v.start_with? "read_timestamp:"
95
- time = Time.xmlschema v.delete_prefix("read_timestamp:")
96
- collector.hints[:staleness] =
97
- StalenessHint.new read_timestamp: time
94
+ if v.start_with? "read_timestamp:"
95
+ time = Time.xmlschema v.delete_prefix("read_timestamp:")
96
+ collector.hints[:staleness] =
97
+ StalenessHint.new read_timestamp: time
98
+ next
99
+ end
100
+ next unless v.start_with? "priority:"
101
+ priority = v.delete_prefix("priority:").strip.to_sym
102
+ collector.hints[:request_options] ||=
103
+ Google::Cloud::Spanner::V1::RequestOptions.new
104
+ collector.hints[:request_options].priority = priority
98
105
  end
99
106
  collector
100
107
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-spanner-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: google-cloud-spanner
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.18'
18
+ version: '2.25'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.18'
25
+ version: '2.25'
26
26
  - !ruby/object:Gem::Dependency
27
- name: grpc
27
+ name: google-cloud-spanner-v1
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - '='
30
+ - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 1.64.3
32
+ version: '1.7'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - '='
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 1.64.3
39
+ version: '1.7'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: activerecord
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -91,14 +91,14 @@ dependencies:
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: 1.30.1
94
+ version: 1.31.0
95
95
  type: :development
96
96
  prerelease: false
97
97
  version_requirements: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - "~>"
100
100
  - !ruby/object:Gem::Version
101
- version: 1.30.1
101
+ version: 1.31.0
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: minitest
104
104
  requirement: !ruby/object:Gem::Requirement
@@ -439,6 +439,14 @@ files:
439
439
  - examples/snippets/interleaved-tables/models/album.rb
440
440
  - examples/snippets/interleaved-tables/models/singer.rb
441
441
  - examples/snippets/interleaved-tables/models/track.rb
442
+ - examples/snippets/isolation-level/README.md
443
+ - examples/snippets/isolation-level/Rakefile
444
+ - examples/snippets/isolation-level/application.rb
445
+ - examples/snippets/isolation-level/config/database.yml
446
+ - examples/snippets/isolation-level/db/migrate/01_create_tables.rb
447
+ - examples/snippets/isolation-level/db/seeds.rb
448
+ - examples/snippets/isolation-level/models/album.rb
449
+ - examples/snippets/isolation-level/models/singer.rb
442
450
  - examples/snippets/migrations/README.md
443
451
  - examples/snippets/migrations/Rakefile
444
452
  - examples/snippets/migrations/application.rb
@@ -579,7 +587,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
579
587
  - !ruby/object:Gem::Version
580
588
  version: '0'
581
589
  requirements: []
582
- rubygems_version: 3.6.5
590
+ rubygems_version: 3.6.9
583
591
  specification_version: 4
584
592
  summary: Rails ActiveRecord connector for Google Spanner Database
585
593
  test_files: []