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 +4 -4
- data/.toys/release.rb +2 -2
- data/CHANGELOG.md +7 -0
- data/acceptance/cases/models/query_test.rb +24 -0
- data/examples/rails/README.md +1 -1
- data/examples/snippets/generated-column/db/schema.rb +3 -3
- data/examples/snippets/hints/README.md +19 -0
- data/examples/snippets/hints/Rakefile +13 -0
- data/examples/snippets/hints/application.rb +47 -0
- data/examples/snippets/hints/config/database.yml +8 -0
- data/examples/snippets/hints/db/migrate/01_create_tables.rb +23 -0
- data/examples/snippets/hints/db/schema.rb +28 -0
- data/examples/snippets/hints/db/seeds.rb +29 -0
- data/examples/snippets/hints/models/album.rb +9 -0
- data/examples/snippets/hints/models/singer.rb +9 -0
- data/examples/snippets/partitioned-dml/README.md +16 -0
- data/examples/snippets/partitioned-dml/Rakefile +13 -0
- data/examples/snippets/partitioned-dml/application.rb +48 -0
- data/examples/snippets/partitioned-dml/config/database.yml +8 -0
- data/examples/snippets/partitioned-dml/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/partitioned-dml/db/schema.rb +26 -0
- data/examples/snippets/partitioned-dml/db/seeds.rb +29 -0
- data/examples/snippets/partitioned-dml/models/album.rb +9 -0
- data/examples/snippets/partitioned-dml/models/singer.rb +9 -0
- data/lib/active_record/tasks/spanner_database_tasks.rb +1 -1
- data/lib/activerecord_spanner_adapter/base.rb +31 -10
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +39 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8f379074a93053e240a8dc13c74b3a3f85cf2b767830901fffd7bcf3bf2b3ba
|
4
|
+
data.tar.gz: f5ac8e18ed5cc732b753c40b36add02b164a39ea14396fa720371b7d65645684
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
18
|
-
path: "
|
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
|
data/examples/rails/README.md
CHANGED
@@ -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'
|
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,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,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,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
|
@@ -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::
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
-
|
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 =
|
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
|
@@ -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.
|
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-
|
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
|