activerecord-spanner-adapter 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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