activerecord-spanner-adapter 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55f265a14ddd7a176378a49090cbb3277aae66ce2c74913978ec9614289759fc
4
- data.tar.gz: ae48998a381c0142063ac4499c8a0c13ece58bded34a72cbcc0371541ee02231
3
+ metadata.gz: f8f379074a93053e240a8dc13c74b3a3f85cf2b767830901fffd7bcf3bf2b3ba
4
+ data.tar.gz: f5ac8e18ed5cc732b753c40b36add02b164a39ea14396fa720371b7d65645684
5
5
  SHA512:
6
- metadata.gz: fa104b38f89edc91eb78d05b5f89e232fb50847953061eae4e3c9eb12bf307f9a0cf69545eb06a0682c92b2ac7bce065b8d88f8fa148570b4720b47ccccad0d5
7
- data.tar.gz: 3de12a8b087940d6234ecd902ea7b109a8319960700dd323c363da266aa299f211b003e50fb01e24ff5e4632e4372b5451e0d129168cacfa3541771263dc2703
6
+ metadata.gz: 39062a1c09f09cc4da8c1dd7d592905a61ae4b6e914aa88060572c9bda1dbe2626808ab92efcdef21ca18c61c7c116c3a4f55903c5829ff5044984e72e69763c
7
+ data.tar.gz: 41a26004516514ec6573e554ba664824105a5f0687d4a831057ff69a92ba9d329fbb185d310d9e5e2d6f1449470aaf573873ffddb038063166aee64c360f10d1
data/.toys/release.rb CHANGED
@@ -14,5 +14,5 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
- load_git remote: "https://github.com/googleapis/google-cloud-ruby.git",
18
- path: ".toys/release"
17
+ load_git remote: "https://github.com/googleapis/ruby-common-tools.git",
18
+ path: "toys/release"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0](https://www.github.com/googleapis/ruby-spanner-activerecord/compare/activerecord-spanner-adapter/v0.6.0...activerecord-spanner-adapter/v0.7.0) (2021-10-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * add support for query hints ([#134](https://www.github.com/googleapis/ruby-spanner-activerecord/issues/134)) ([f4b5b1e](https://www.github.com/googleapis/ruby-spanner-activerecord/commit/f4b5b1e5b959d43756258e84f95f26f375b7fba8))
9
+
3
10
  ## [0.6.0](https://www.github.com/googleapis/ruby-spanner-activerecord/compare/activerecord-spanner-adapter/v0.5.0...activerecord-spanner-adapter/v0.6.0) (2021-09-09)
4
11
 
5
12
 
@@ -142,6 +142,30 @@ module ActiveRecord
142
142
 
143
143
  assert_equal [post], posts.to_a
144
144
  end
145
+
146
+ def test_statement_hint
147
+ post = Post.optimizer_hints("statement_hint: @{USE_ADDITIONAL_PARALLELISM=TRUE}")
148
+ .select(:title).to_a.first
149
+
150
+ assert_nil post.id
151
+ assert_equal "Title - 1", post.title
152
+ end
153
+
154
+ def test_table_hint
155
+ post = Post.optimizer_hints("table_hint: posts@{FORCE_INDEX=_BASE_TABLE}")
156
+ .select(:title).to_a.first
157
+
158
+ assert_nil post.id
159
+ assert_equal "Title - 1", post.title
160
+ end
161
+
162
+ def test_join_hint
163
+ post = Post.joins("inner join @{JOIN_TYPE=HASH_JOIN} comments on posts.id=comments.post_id")
164
+ .select(:title).to_a.first
165
+
166
+ assert_nil post.id
167
+ assert_equal "Title - 1", post.title
168
+ end
145
169
  end
146
170
  end
147
171
  end
@@ -99,7 +99,7 @@ If you are not familiar with Active Record, you can read more about it on [Ruby
99
99
  ### Use Cloud Spanner adapter in Gemfile
100
100
  1. Edit the Gemfile file of the `blog` app and add the `activerecord-spanner-adapter` gem:
101
101
  ```ruby
102
- gem 'activerecord-spanner-adapter', git: 'https://github.com/googleapis/ruby-spanner-activerecord.git'
102
+ gem 'activerecord-spanner-adapter'
103
103
  ```
104
104
  1. Install gems:
105
105
 
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
@@ -12,7 +12,7 @@
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
14
 
15
- create_table "singers", force: :cascade do |t|
15
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
16
16
  t.string "first_name", limit: 100
17
17
  t.string "last_name", limit: 200, null: false
18
18
  t.string "full_name", limit: 300, null: false
@@ -0,0 +1,19 @@
1
+ # Sample - Query Hints
2
+
3
+ This example shows how to use query hints Spanner ActiveRecord adapter. Statement hints and
4
+ table hints can be specified using the optimizer_hints method. Join hints must be specified
5
+ in a join string.
6
+
7
+ See https://cloud.google.com/spanner/docs/query-syntax#sql_syntax for more information on
8
+ the supported query hints.
9
+
10
+ ## Running the Sample
11
+
12
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
13
+ against that emulator. The emulator will automatically be stopped when the application finishes.
14
+
15
+ Run the application with the command
16
+
17
+ ```bash
18
+ bundle exec rake run
19
+ ```
@@ -0,0 +1,13 @@
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 "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to work with generated columns in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[hints]" }
13
+ end
@@ -0,0 +1,47 @@
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 "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
14
+ puts ""
15
+ puts "Listing all singers using additional parallelism:"
16
+ # A statement hint must be prefixed with 'statement_hint:'
17
+ Singer.optimizer_hints("statement_hint: @{USE_ADDITIONAL_PARALLELISM=TRUE}")
18
+ .order("last_name, first_name").each do |singer|
19
+ puts singer.full_name
20
+ end
21
+
22
+ puts ""
23
+ puts "Listing all singers using the index on full_name:"
24
+ # All table hints must be prefixed with 'table_hint:'.
25
+ # Queries may contain multiple table hints.
26
+ Singer.optimizer_hints("table_hint: singers@{FORCE_INDEX=index_singers_on_full_name}")
27
+ .order("full_name").each do |singer|
28
+ puts singer.full_name
29
+ end
30
+
31
+ puts ""
32
+ puts "Listing all singers with at least one album that starts with 'blue':"
33
+ # Join hints cannot be specified using an optimizer_hint. Instead, the join can
34
+ # be specified using a string that includes the join hint.
35
+ Singer.joins("INNER JOIN @{JOIN_METHOD=HASH_JOIN} albums " \
36
+ "on singers.id=albums.singer_id AND albums.title LIKE 'blue%'")
37
+ .distinct.order("last_name, first_name").each do |singer|
38
+ puts singer.full_name
39
+ end
40
+
41
+ puts ""
42
+ puts "Press any key to end the application"
43
+ STDIN.getch
44
+ end
45
+ end
46
+
47
+ Application.run
@@ -0,0 +1,8 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ pool: 5
8
+ timeout: 5000
@@ -0,0 +1,23 @@
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, limit: 100
12
+ t.string :last_name, limit: 200, null: false
13
+ t.string :full_name, limit: 300, null: false, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
14
+ t.index [:full_name], name: "index_singers_on_full_name"
15
+ end
16
+
17
+ create_table :albums do |t|
18
+ t.string :title
19
+ t.references :singer, index: false, foreign_key: true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 1) do
14
+
15
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
+ t.string "title"
17
+ t.integer "singer_id", limit: 8
18
+ end
19
+
20
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
21
+ t.string "first_name", limit: 100
22
+ t.string "last_name", limit: 200, null: false
23
+ t.string "full_name", limit: 300, null: false
24
+ t.index ["full_name"], name: "index_singers_on_full_name", order: { full_name: :asc }
25
+ end
26
+
27
+ add_foreign_key "albums", "singers"
28
+ end
@@ -0,0 +1,29 @@
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.rb"
8
+ require_relative "../models/singer"
9
+ require_relative "../models/album"
10
+
11
+ first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
12
+ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
13
+
14
+ adjectives = %w[daily happy blue generous cooked bad open]
15
+ nouns = %w[windows potatoes bank street tree glass bottle]
16
+
17
+ # This ensures all the records are inserted using one read/write transaction that will use mutations instead of DML.
18
+ ActiveRecord::Base.transaction isolation: :buffered_mutations do
19
+ singers = []
20
+ 5.times do
21
+ singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
22
+ end
23
+
24
+ albums = []
25
+ 20.times do
26
+ singer = singers.sample
27
+ albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
28
+ end
29
+ 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
@@ -0,0 +1,16 @@
1
+ # Sample - Partitioned DML
2
+
3
+ This example shows how to use Partitioned DML with the Spanner ActiveRecord adapter.
4
+
5
+ See https://cloud.google.com/spanner/docs/dml-partitioned for more information on Partitioned DML.
6
+
7
+ ## Running the Sample
8
+
9
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
10
+ against that emulator. The emulator will automatically be stopped when the application finishes.
11
+
12
+ Run the application with the command
13
+
14
+ ```bash
15
+ bundle exec rake run
16
+ ```
@@ -0,0 +1,13 @@
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 "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to work with Partitioned DML in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[partitioned-dml]" }
13
+ end
@@ -0,0 +1,48 @@
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 "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
14
+ singer_count = Singer.all.count
15
+ album_count = Album.all.count
16
+ puts ""
17
+ puts "Singers in the database: #{singer_count}"
18
+ puts "Albums in the database: #{album_count}"
19
+
20
+ puts ""
21
+ puts "Deleting all albums in the database using Partitioned DML"
22
+ # Note that a Partitioned DML transaction can contain ONLY ONE DML statement.
23
+ # If we want to delete all data in two different tables, we need to do so in two different PDML transactions.
24
+ Album.transaction isolation: :pdml do
25
+ count = Album.delete_all
26
+ puts "Deleted #{count} albums"
27
+ end
28
+
29
+ puts ""
30
+ puts "Deleting all singers in the database using Partitioned DML"
31
+ Singer.transaction isolation: :pdml do
32
+ count = Singer.delete_all
33
+ puts "Deleted #{count} singers"
34
+ end
35
+
36
+ singer_count = Singer.all.count
37
+ album_count = Album.all.count
38
+ puts ""
39
+ puts "Singers in the database: #{singer_count}"
40
+ puts "Albums in the database: #{album_count}"
41
+
42
+ puts ""
43
+ puts "Press any key to end the application"
44
+ STDIN.getch
45
+ end
46
+ end
47
+
48
+ Application.run
@@ -0,0 +1,8 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ pool: 5
8
+ timeout: 5000
@@ -0,0 +1,21 @@
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, limit: 100
12
+ t.string :last_name, limit: 200, null: false
13
+ end
14
+
15
+ create_table :albums do |t|
16
+ t.string :title
17
+ t.references :singer, index: false, foreign_key: true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 1) do
14
+
15
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
+ t.string "title"
17
+ t.integer "singer_id", limit: 8
18
+ end
19
+
20
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
21
+ t.string "first_name", limit: 100
22
+ t.string "last_name", limit: 200, null: false
23
+ end
24
+
25
+ add_foreign_key "albums", "singers"
26
+ end
@@ -0,0 +1,29 @@
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.rb"
8
+ require_relative "../models/singer"
9
+ require_relative "../models/album"
10
+
11
+ first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
12
+ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
13
+
14
+ adjectives = %w[daily happy blue generous cooked bad open]
15
+ nouns = %w[windows potatoes bank street tree glass bottle]
16
+
17
+ # This ensures all the records are inserted using one read/write transaction that will use mutations instead of DML.
18
+ ActiveRecord::Base.transaction isolation: :buffered_mutations do
19
+ singers = []
20
+ 5.times do
21
+ singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
22
+ end
23
+
24
+ albums = []
25
+ 20.times do
26
+ singer = singers.sample
27
+ albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
28
+ end
29
+ 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
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  @connection.create_database
19
19
  rescue Google::Cloud::Error => error
20
20
  if error.instance_of? Google::Cloud::AlreadyExistsError
21
- raise ActiveRecord::Tasks::DatabaseAlreadyExists
21
+ raise ActiveRecord::DatabaseAlreadyExists
22
22
  end
23
23
 
24
24
  raise error
@@ -15,7 +15,17 @@ module ActiveRecord
15
15
  # Creates an object (or multiple objects) and saves it to the database. This method will use mutations instead
16
16
  # of DML if there is no active transaction, or if the active transaction has been created with the option
17
17
  # isolation: :buffered_mutations.
18
+ def self.create! attributes = nil, &block
19
+ return super unless spanner_adapter?
20
+ return super if active_transaction?
21
+
22
+ transaction isolation: :buffered_mutations do
23
+ return super
24
+ end
25
+ end
26
+
18
27
  def self.create attributes = nil, &block
28
+ return super unless spanner_adapter?
19
29
  return super if active_transaction?
20
30
 
21
31
  transaction isolation: :buffered_mutations do
@@ -23,8 +33,16 @@ module ActiveRecord
23
33
  end
24
34
  end
25
35
 
36
+ def self.spanner_adapter?
37
+ connection.adapter_name == "spanner"
38
+ end
39
+
40
+ def self.buffered_mutations?
41
+ spanner_adapter? && connection&.current_spanner_transaction&.isolation == :buffered_mutations
42
+ end
43
+
26
44
  def self._insert_record values
27
- return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
45
+ return super unless buffered_mutations?
28
46
 
29
47
  primary_key = self.primary_key
30
48
  primary_key_value = nil
@@ -48,7 +66,7 @@ module ActiveRecord
48
66
  values: [grpc_values.list_value]
49
67
  )
50
68
  )
51
- Base.connection.current_spanner_transaction.buffer mutation
69
+ connection.current_spanner_transaction.buffer mutation
52
70
 
53
71
  primary_key_value
54
72
  end
@@ -56,6 +74,7 @@ module ActiveRecord
56
74
  # Deletes all records of this class. This method will use mutations instead of DML if there is no active
57
75
  # transaction, or if the active transaction has been created with the option isolation: :buffered_mutations.
58
76
  def self.delete_all
77
+ return super unless spanner_adapter?
59
78
  return super if active_transaction?
60
79
 
61
80
  transaction isolation: :buffered_mutations do
@@ -72,7 +91,8 @@ module ActiveRecord
72
91
  # of DML if there is no active transaction, or if the active transaction has been created with the option
73
92
  # isolation: :buffered_mutations.
74
93
  def update attributes
75
- return super if Base.active_transaction?
94
+ return super unless self.class.spanner_adapter?
95
+ return super if self.class.active_transaction?
76
96
 
77
97
  transaction isolation: :buffered_mutations do
78
98
  return super
@@ -83,7 +103,8 @@ module ActiveRecord
83
103
  # of DML if there is no active transaction, or if the active transaction has been created with the option
84
104
  # isolation: :buffered_mutations.
85
105
  def destroy
86
- return super if Base.active_transaction?
106
+ return super unless self.class.spanner_adapter?
107
+ return super if self.class.active_transaction?
87
108
 
88
109
  transaction isolation: :buffered_mutations do
89
110
  return super
@@ -110,7 +131,7 @@ module ActiveRecord
110
131
  private_class_method :_create_grpc_values_for_insert
111
132
 
112
133
  def _update_row attribute_names, attempted_action = "update"
113
- return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
134
+ return super unless self.class.buffered_mutations?
114
135
 
115
136
  if locking_enabled?
116
137
  _execute_version_check attempted_action
@@ -129,7 +150,7 @@ module ActiveRecord
129
150
  values: [grpc_values.list_value]
130
151
  )
131
152
  )
132
- Base.connection.current_spanner_transaction.buffer mutation
153
+ self.class.connection.current_spanner_transaction.buffer mutation
133
154
  1 # Affected rows
134
155
  end
135
156
 
@@ -161,13 +182,13 @@ module ActiveRecord
161
182
  end
162
183
 
163
184
  def destroy_row
164
- return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
185
+ return super unless self.class.buffered_mutations?
165
186
 
166
187
  _delete_row
167
188
  end
168
189
 
169
190
  def _delete_row
170
- return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
191
+ return super unless self.class.buffered_mutations?
171
192
  if locking_enabled?
172
193
  _execute_version_check "destroy"
173
194
  end
@@ -186,7 +207,7 @@ module ActiveRecord
186
207
  key_set: { keys: [list_value] }
187
208
  )
188
209
  )
189
- Base.connection.current_spanner_transaction.buffer mutation
210
+ self.class.connection.current_spanner_transaction.buffer mutation
190
211
  1 # Affected rows
191
212
  end
192
213
 
@@ -210,7 +231,7 @@ module ActiveRecord
210
231
  "WHERE `#{self.class.primary_key}` = @id AND `#{locking_column}` = @lock_version"
211
232
  params = { "id" => id_in_database, "lock_version" => previous_lock_value }
212
233
  param_types = { "id" => :INT64, "lock_version" => :INT64 }
213
- locked_row = Base.connection.raw_connection.execute_query sql, params: params, types: param_types
234
+ locked_row = self.class.connection.raw_connection.execute_query sql, params: params, types: param_types
214
235
  raise ActiveRecord::StaleObjectError.new(self, attempted_action) unless locked_row.rows.any?
215
236
  end
216
237
  end
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "0.6.0".freeze
8
+ VERSION = "0.7.0".freeze
9
9
  end
@@ -14,11 +14,24 @@ module Arel # :nodoc: all
14
14
  end
15
15
  end
16
16
 
17
+ class StatementHint
18
+ attr_reader :value
19
+
20
+ def initialize value
21
+ @value = value
22
+ end
23
+ end
24
+
17
25
  class Spanner < Arel::Visitors::ToSql
18
26
  def compile node, collector = Arel::Collectors::SQLString.new
19
27
  collector.class.module_eval { attr_accessor :hints }
28
+ collector.class.module_eval { attr_accessor :table_hints }
29
+ collector.class.module_eval { attr_accessor :join_hints }
20
30
  collector.hints = {}
31
+ collector.table_hints = {}
32
+ collector.join_hints = {}
21
33
  sql, binds = accept(node, collector).value
34
+ sql = collector.hints[:statement_hint].value + sql if collector.hints[:statement_hint]
22
35
  binds << collector.hints[:staleness] if collector.hints[:staleness]
23
36
  [sql, binds]
24
37
  end
@@ -32,9 +45,25 @@ module Arel # :nodoc: all
32
45
  BIND_BLOCK
33
46
  end
34
47
 
48
+ def visit_table_hint v, collector
49
+ value = v.delete_prefix("table_hint:").strip
50
+ # TODO: This does not support FORCE_INDEX hints that reference an index that contains '@{' in the name.
51
+ start_of_hint_index = value.rindex "@{"
52
+ table_name = value[0, start_of_hint_index]
53
+ table_hint = value[start_of_hint_index, value.length]
54
+ collector.table_hints[table_name] = table_hint if table_name && table_hint
55
+ end
56
+
57
+ def visit_statement_hint v, collector
58
+ collector.hints[:statement_hint] = \
59
+ StatementHint.new v.delete_prefix("statement_hint:")
60
+ end
61
+
35
62
  # rubocop:disable Naming/MethodName
36
63
  def visit_Arel_Nodes_OptimizerHints o, collector
37
64
  o.expr.each do |v|
65
+ visit_table_hint v, collector if v.start_with? "table_hint:"
66
+ visit_statement_hint v, collector if v.start_with? "statement_hint:"
38
67
  if v.start_with? "max_staleness:"
39
68
  collector.hints[:staleness] = \
40
69
  StalenessHint.new max_staleness: v.delete_prefix("max_staleness:").to_f
@@ -59,6 +88,16 @@ module Arel # :nodoc: all
59
88
  collector
60
89
  end
61
90
 
91
+ def visit_Arel_Table o, collector
92
+ return super unless collector.table_hints[o.name]
93
+ if o.table_alias
94
+ collector << quote_table_name(o.name) << collector.table_hints[o.name] \
95
+ << " " << quote_table_name(o.table_alias)
96
+ else
97
+ collector << quote_table_name(o.name) << collector.table_hints[o.name]
98
+ end
99
+ end
100
+
62
101
  def visit_Arel_Nodes_BindParam o, collector
63
102
  # Do not generate a query parameter if the value should be set to the PENDING_COMMIT_TIMESTAMP(), as that is
64
103
  # 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: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-10 00:00:00.000000000 Z
11
+ date: 2021-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-cloud-spanner
@@ -369,6 +369,15 @@ files:
369
369
  - examples/snippets/generated-column/db/schema.rb
370
370
  - examples/snippets/generated-column/db/seeds.rb
371
371
  - examples/snippets/generated-column/models/singer.rb
372
+ - examples/snippets/hints/README.md
373
+ - examples/snippets/hints/Rakefile
374
+ - examples/snippets/hints/application.rb
375
+ - examples/snippets/hints/config/database.yml
376
+ - examples/snippets/hints/db/migrate/01_create_tables.rb
377
+ - examples/snippets/hints/db/schema.rb
378
+ - examples/snippets/hints/db/seeds.rb
379
+ - examples/snippets/hints/models/album.rb
380
+ - examples/snippets/hints/models/singer.rb
372
381
  - examples/snippets/interleaved-tables/README.md
373
382
  - examples/snippets/interleaved-tables/Rakefile
374
383
  - examples/snippets/interleaved-tables/application.rb
@@ -407,6 +416,15 @@ files:
407
416
  - examples/snippets/optimistic-locking/db/seeds.rb
408
417
  - examples/snippets/optimistic-locking/models/album.rb
409
418
  - examples/snippets/optimistic-locking/models/singer.rb
419
+ - examples/snippets/partitioned-dml/README.md
420
+ - examples/snippets/partitioned-dml/Rakefile
421
+ - examples/snippets/partitioned-dml/application.rb
422
+ - examples/snippets/partitioned-dml/config/database.yml
423
+ - examples/snippets/partitioned-dml/db/migrate/01_create_tables.rb
424
+ - examples/snippets/partitioned-dml/db/schema.rb
425
+ - examples/snippets/partitioned-dml/db/seeds.rb
426
+ - examples/snippets/partitioned-dml/models/album.rb
427
+ - examples/snippets/partitioned-dml/models/singer.rb
410
428
  - examples/snippets/quickstart/README.md
411
429
  - examples/snippets/quickstart/Rakefile
412
430
  - examples/snippets/quickstart/application.rb