activerecord-spanner-adapter 1.6.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/acceptance-tests-on-emulator.yaml +3 -7
- data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/ci.yaml +3 -7
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -33
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/nightly-unit-tests.yaml +5 -33
- data/.github/workflows/rubocop.yaml +1 -1
- data/.github/workflows/samples.yaml +30 -0
- data/.kokoro/populate-secrets.sh +5 -1
- data/.kokoro/release.cfg +22 -12
- data/.kokoro/release.sh +1 -3
- data/.kokoro/trampoline_v2.sh +19 -11
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -2
- data/.trampolinerc +6 -1
- data/CHANGELOG.md +37 -0
- data/Gemfile +7 -5
- data/README.md +11 -9
- data/Rakefile +2 -2
- data/acceptance/cases/migration/command_recorder_test.rb +7 -38
- data/acceptance/cases/migration/references_index_test.rb +2 -11
- data/acceptance/cases/migration/schema_dumper_test.rb +21 -9
- data/acceptance/cases/models/binary_identifiers.rb +97 -0
- data/acceptance/cases/models/insert_all_test.rb +22 -7
- data/acceptance/cases/sessions/session_not_found_test.rb +2 -0
- data/acceptance/cases/tasks/database_tasks_test.rb +1 -0
- data/acceptance/models/binary_project.rb +20 -0
- data/acceptance/models/string_io.rb +28 -0
- data/acceptance/models/user.rb +20 -0
- data/acceptance/test_helper.rb +6 -1
- data/activerecord-spanner-adapter.gemspec +3 -3
- data/benchmarks/application.rb +3 -7
- data/examples/snippets/Rakefile +27 -5
- data/examples/snippets/array-data-type/application.rb +1 -5
- data/examples/snippets/array-data-type/config/database.yml +1 -0
- data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
- data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
- data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
- data/examples/snippets/bulk-insert/application.rb +1 -5
- data/examples/snippets/bulk-insert/config/database.yml +1 -0
- data/examples/snippets/commit-timestamp/application.rb +0 -4
- data/examples/snippets/commit-timestamp/config/database.yml +1 -0
- data/examples/snippets/config/environment.rb +5 -0
- data/examples/snippets/create-records/application.rb +1 -5
- data/examples/snippets/create-records/config/database.yml +1 -0
- data/examples/snippets/date-data-type/application.rb +1 -5
- data/examples/snippets/date-data-type/config/database.yml +1 -0
- data/examples/snippets/date-data-type/db/seeds.rb +1 -1
- data/examples/snippets/generated-column/application.rb +0 -4
- data/examples/snippets/generated-column/config/database.yml +1 -0
- data/examples/snippets/generated-column/db/seeds.rb +1 -1
- data/examples/snippets/hints/application.rb +0 -4
- data/examples/snippets/hints/config/database.yml +1 -0
- data/examples/snippets/hints/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/application.rb +1 -5
- data/examples/snippets/interleaved-tables/config/database.yml +1 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/models/album.rb +6 -2
- data/examples/snippets/interleaved-tables/models/track.rb +5 -1
- data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
- data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
- data/examples/snippets/migrations/application.rb +0 -4
- data/examples/snippets/migrations/config/database.yml +1 -0
- data/examples/snippets/mutations/application.rb +1 -5
- data/examples/snippets/mutations/config/database.yml +1 -0
- data/examples/snippets/mutations/db/seeds.rb +1 -1
- data/examples/snippets/optimistic-locking/application.rb +0 -4
- data/examples/snippets/optimistic-locking/config/database.yml +1 -0
- data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
- data/examples/snippets/partitioned-dml/application.rb +0 -4
- data/examples/snippets/partitioned-dml/config/database.yml +1 -0
- data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
- data/examples/snippets/query-logs/application.rb +15 -13
- data/examples/snippets/query-logs/config/database.yml +1 -0
- data/examples/snippets/query-logs/db/seeds.rb +1 -1
- data/examples/snippets/quickstart/application.rb +0 -4
- data/examples/snippets/quickstart/config/database.yml +1 -0
- data/examples/snippets/quickstart/db/seeds.rb +1 -1
- data/examples/snippets/read-only-transactions/application.rb +0 -4
- data/examples/snippets/read-only-transactions/config/database.yml +1 -0
- data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
- data/examples/snippets/read-write-transactions/application.rb +2 -6
- data/examples/snippets/read-write-transactions/config/database.yml +1 -0
- data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
- data/examples/snippets/stale-reads/application.rb +0 -4
- data/examples/snippets/stale-reads/config/database.yml +1 -0
- data/examples/snippets/stale-reads/db/seeds.rb +1 -1
- data/examples/snippets/tags/application.rb +0 -4
- data/examples/snippets/tags/config/database.yml +1 -0
- data/examples/snippets/tags/db/seeds.rb +1 -1
- data/examples/snippets/timestamp-data-type/application.rb +0 -4
- data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
- data/lib/active_record/connection_adapters/spanner/column.rb +3 -3
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +37 -23
- data/lib/active_record/connection_adapters/spanner/quoting.rb +19 -6
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -9
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +28 -46
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
- data/lib/active_record/connection_adapters/spanner_adapter.rb +54 -27
- data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
- data/lib/active_record/type/spanner/array.rb +4 -0
- data/lib/active_record/type/spanner/bytes.rb +10 -0
- data/lib/activerecord-spanner-adapter.rb +5 -1
- data/lib/activerecord_spanner_adapter/base.rb +58 -30
- data/lib/activerecord_spanner_adapter/connection.rb +9 -5
- data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
- data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
- data/lib/activerecord_spanner_adapter/index.rb +10 -2
- data/lib/activerecord_spanner_adapter/information_schema.rb +1 -1
- data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
- data/lib/activerecord_spanner_adapter/table/column.rb +12 -3
- data/lib/activerecord_spanner_adapter/table.rb +8 -2
- data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +16 -11
- data/lib/spanner_client_ext.rb +4 -3
- metadata +15 -34
- data/examples/snippets/array-data-type/db/schema.rb +0 -31
- data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
- data/examples/snippets/bulk-insert/db/schema.rb +0 -31
- data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
- data/examples/snippets/create-records/db/schema.rb +0 -31
- data/examples/snippets/date-data-type/db/schema.rb +0 -26
- data/examples/snippets/generated-column/db/schema.rb +0 -26
- data/examples/snippets/hints/db/schema.rb +0 -33
- data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
- data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
- data/examples/snippets/migrations/db/schema.rb +0 -38
- data/examples/snippets/mutations/db/schema.rb +0 -32
- data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
- data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
- data/examples/snippets/query-logs/db/schema.rb +0 -31
- data/examples/snippets/quickstart/db/schema.rb +0 -31
- data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
- data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
- data/examples/snippets/stale-reads/db/schema.rb +0 -31
- data/examples/snippets/tags/db/schema.rb +0 -31
- data/examples/snippets/timestamp-data-type/db/schema.rb +0 -26
@@ -17,15 +17,27 @@ module ActiveRecord
|
|
17
17
|
ActiveRecord::gem_version >= Gem::Version.create('7.1.0')
|
18
18
|
end
|
19
19
|
|
20
|
+
def is_7_2_or_higher?
|
21
|
+
ActiveRecord::gem_version >= Gem::Version.create('7.2.0')
|
22
|
+
end
|
23
|
+
|
24
|
+
def pool_or_connection
|
25
|
+
if is_7_2_or_higher?
|
26
|
+
ActiveRecord::Base.connection_pool
|
27
|
+
else
|
28
|
+
ActiveRecord::Base.connection
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
20
32
|
def test_dump_schema_contains_start_batch_ddl
|
21
|
-
connection =
|
33
|
+
connection = pool_or_connection
|
22
34
|
schema = StringIO.new
|
23
35
|
ActiveRecord::SchemaDumper.dump connection, schema
|
24
36
|
assert schema.string.include?("connection.start_batch_ddl")
|
25
37
|
end
|
26
38
|
|
27
39
|
def test_dump_schema_contains_run_batch
|
28
|
-
connection =
|
40
|
+
connection = pool_or_connection
|
29
41
|
schema = StringIO.new
|
30
42
|
ActiveRecord::SchemaDumper.dump connection, schema
|
31
43
|
assert schema.string.include?(" connection.run_batch\n"\
|
@@ -35,7 +47,7 @@ module ActiveRecord
|
|
35
47
|
end
|
36
48
|
|
37
49
|
def test_dump_schema_contains_albums_table
|
38
|
-
connection =
|
50
|
+
connection = pool_or_connection
|
39
51
|
schema = StringIO.new
|
40
52
|
ActiveRecord::SchemaDumper.dump connection, schema
|
41
53
|
sql = schema.string
|
@@ -47,42 +59,42 @@ module ActiveRecord
|
|
47
59
|
end
|
48
60
|
|
49
61
|
def test_dump_schema_contains_interleaved_index
|
50
|
-
connection =
|
62
|
+
connection = pool_or_connection
|
51
63
|
schema = StringIO.new
|
52
64
|
ActiveRecord::SchemaDumper.dump connection, schema
|
53
65
|
assert schema.string.include?("t.index [\"singerid\", \"albumid\", \"title\"], name: \"index_tracks_on_singerid_and_albumid_and_title\", order: { singerid: :asc, albumid: :asc, title: :asc }, null_filtered: true, interleave_in: \"albums\""), schema.string
|
54
66
|
end
|
55
67
|
|
56
68
|
def test_dump_schema_should_not_contain_id_limit
|
57
|
-
connection =
|
69
|
+
connection = pool_or_connection
|
58
70
|
schema = StringIO.new
|
59
71
|
ActiveRecord::SchemaDumper.dump connection, schema
|
60
72
|
assert !schema.string.include?("id: { limit: 8 }")
|
61
73
|
end
|
62
74
|
|
63
75
|
def test_dump_schema_contains_commit_timestamp
|
64
|
-
connection =
|
76
|
+
connection = pool_or_connection
|
65
77
|
schema = StringIO.new
|
66
78
|
ActiveRecord::SchemaDumper.dump connection, schema
|
67
79
|
assert schema.string.include?("t.time \"last_updated\", allow_commit_timestamp: true"), schema.string
|
68
80
|
end
|
69
81
|
|
70
82
|
def test_dump_schema_contains_virtual_column
|
71
|
-
connection =
|
83
|
+
connection = pool_or_connection
|
72
84
|
schema = StringIO.new
|
73
85
|
ActiveRecord::SchemaDumper.dump connection, schema
|
74
86
|
assert schema.string.include?("t.virtual \"full_name\", type: :string, as: \"COALESCE(first_name || ' ', '') || last_name\", stored: true"), schema.string
|
75
87
|
end
|
76
88
|
|
77
89
|
def test_dump_schema_contains_string_array
|
78
|
-
connection =
|
90
|
+
connection = pool_or_connection
|
79
91
|
schema = StringIO.new
|
80
92
|
ActiveRecord::SchemaDumper.dump connection, schema
|
81
93
|
assert schema.string.include?("t.string \"col_array_string\", array: true"), schema.string
|
82
94
|
end
|
83
95
|
|
84
96
|
def test_dump_schema_index_storing
|
85
|
-
connection =
|
97
|
+
connection = pool_or_connection
|
86
98
|
schema = StringIO.new
|
87
99
|
ActiveRecord::SchemaDumper.dump connection, schema
|
88
100
|
assert schema.string.include?("t.index [\"last_name\"], name: \"index_singers_on_last_name\", order: { last_name: :asc }, storing: [\"first_name\", \"tracks_count\"]"), schema.string
|
@@ -0,0 +1,97 @@
|
|
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
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require "test_helper"
|
10
|
+
require "test_helpers/with_separate_database"
|
11
|
+
require_relative "../../models/user"
|
12
|
+
require_relative "../../models/binary_project"
|
13
|
+
|
14
|
+
module Models
|
15
|
+
class DefaultValueTest < SpannerAdapter::TestCase
|
16
|
+
include TestHelpers::WithSeparateDatabase
|
17
|
+
|
18
|
+
def setup
|
19
|
+
super
|
20
|
+
|
21
|
+
connection.create_table :users, id: :binary do |t|
|
22
|
+
t.string :email, null: false
|
23
|
+
t.string :full_name, null: false
|
24
|
+
end
|
25
|
+
connection.create_table :binary_projects, id: :binary do |t|
|
26
|
+
t.string :name, null: false
|
27
|
+
t.string :description, null: false
|
28
|
+
t.binary :owner_id, null: false
|
29
|
+
t.foreign_key :users, column: :owner_id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_includes_works
|
34
|
+
user = User.create!(
|
35
|
+
email: "test@example.com",
|
36
|
+
full_name: "Test User"
|
37
|
+
)
|
38
|
+
3.times do |i|
|
39
|
+
Project.create!(
|
40
|
+
name: "Project #{i}",
|
41
|
+
description: "Description #{i}",
|
42
|
+
owner: user
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
# First verify the association works without includes
|
47
|
+
projects = Project.all
|
48
|
+
assert_equal 3, projects.count
|
49
|
+
|
50
|
+
# Compare the base64 content instead of the StringIO objects
|
51
|
+
first_project = projects.first
|
52
|
+
assert_equal to_base64(user.id), to_base64(first_project.owner_id)
|
53
|
+
|
54
|
+
# Now verify includes is working
|
55
|
+
query_count = count_queries do
|
56
|
+
loaded_projects = Project.includes(:owner).to_a
|
57
|
+
loaded_projects.each do |project|
|
58
|
+
# Access the owner to ensure it's preloaded
|
59
|
+
assert_equal user.full_name, project.owner.full_name
|
60
|
+
end
|
61
|
+
assert_equal 3, loaded_projects.count
|
62
|
+
end
|
63
|
+
|
64
|
+
# Spanner should execute 2 queries: one for projects and one for users
|
65
|
+
assert_equal 2, query_count
|
66
|
+
|
67
|
+
user = User.all.includes(:projects).first
|
68
|
+
assert user
|
69
|
+
project_count = 0
|
70
|
+
user.projects.each do |_|
|
71
|
+
project_count += 1
|
72
|
+
end
|
73
|
+
assert_equal 3, project_count
|
74
|
+
assert_equal 3, user.projects.count
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def to_base64 buffer
|
80
|
+
buffer.rewind
|
81
|
+
value = buffer.read
|
82
|
+
Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
|
83
|
+
end
|
84
|
+
|
85
|
+
def count_queries(&block)
|
86
|
+
count = 0
|
87
|
+
counter_fn = ->(name, started, finished, unique_id, payload) {
|
88
|
+
unless %w[CACHE SCHEMA].include?(payload[:name])
|
89
|
+
count += 1
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
ActiveSupport::Notifications.subscribed(counter_fn, "sql.active_record", &block)
|
94
|
+
count
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -30,13 +30,24 @@ module ActiveRecord
|
|
30
30
|
{ id: Author.next_sequence_value, name: "Carol" },
|
31
31
|
]
|
32
32
|
|
33
|
-
|
33
|
+
Author.insert_all(values)
|
34
|
+
|
35
|
+
authors = Author.all.order(:name)
|
36
|
+
|
37
|
+
assert_equal "Alice", authors[0].name
|
38
|
+
assert_equal "Bob", authors[1].name
|
39
|
+
assert_equal "Carol", authors[2].name
|
34
40
|
end
|
35
41
|
|
36
42
|
def test_insert
|
37
43
|
value = { id: Author.next_sequence_value, name: "Alice" }
|
38
44
|
|
39
|
-
|
45
|
+
Author.insert(value)
|
46
|
+
|
47
|
+
authors = Author.all.order(:name)
|
48
|
+
|
49
|
+
assert_equal 1, authors.length
|
50
|
+
assert_equal "Alice", authors[0].name
|
40
51
|
end
|
41
52
|
|
42
53
|
def test_insert_all!
|
@@ -136,12 +147,16 @@ module ActiveRecord
|
|
136
147
|
{ id: Author.next_sequence_value, name: "Carol" },
|
137
148
|
]
|
138
149
|
|
139
|
-
|
140
|
-
|
141
|
-
Author.upsert_all(values)
|
142
|
-
end
|
150
|
+
ActiveRecord::Base.transaction do
|
151
|
+
Author.upsert_all(values)
|
143
152
|
end
|
144
|
-
|
153
|
+
|
154
|
+
authors = Author.all.order(:name)
|
155
|
+
|
156
|
+
assert_equal 3, authors.length
|
157
|
+
assert_equal "Alice", authors[0].name
|
158
|
+
assert_equal "Bob", authors[1].name
|
159
|
+
assert_equal "Carol", authors[2].name
|
145
160
|
end
|
146
161
|
|
147
162
|
def test_upsert_all_with_buffered_mutation_transaction
|
@@ -51,6 +51,8 @@ module ActiveRecord
|
|
51
51
|
sessions.each do |session|
|
52
52
|
client.delete_session Google::Cloud::Spanner::V1::DeleteSessionRequest.new name: session.name
|
53
53
|
end
|
54
|
+
# Wait a bit to ensure that the sessions are really deleted.
|
55
|
+
sleep 5 unless ENV["SPANNER_EMULATOR_HOST"]
|
54
56
|
end
|
55
57
|
|
56
58
|
def test_single_read
|
@@ -0,0 +1,20 @@
|
|
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
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require_relative 'string_io'
|
10
|
+
|
11
|
+
class BinaryProject < ActiveRecord::Base
|
12
|
+
belongs_to :owner, class_name: 'User'
|
13
|
+
|
14
|
+
before_create :set_uuid
|
15
|
+
private
|
16
|
+
|
17
|
+
def set_uuid
|
18
|
+
self.id ||= StringIO.new(SecureRandom.random_bytes(16))
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
# Add equality and hash functions to StringIO to use it for sets.
|
10
|
+
class StringIO
|
11
|
+
def ==(o)
|
12
|
+
o.class == self.class && self.to_base64 == o.to_base64
|
13
|
+
end
|
14
|
+
|
15
|
+
def eql?(o)
|
16
|
+
self == o
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
to_base64.hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_base64
|
24
|
+
self.rewind
|
25
|
+
value = self.read
|
26
|
+
Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require_relative 'string_io'
|
10
|
+
|
11
|
+
class User < ActiveRecord::Base
|
12
|
+
has_many :binary_projects, foreign_key: :owner_id
|
13
|
+
|
14
|
+
before_create :set_uuid
|
15
|
+
private
|
16
|
+
|
17
|
+
def set_uuid
|
18
|
+
self.id ||= StringIO.new(SecureRandom.random_bytes(16))
|
19
|
+
end
|
20
|
+
end
|
data/acceptance/test_helper.rb
CHANGED
@@ -5,9 +5,11 @@
|
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
7
|
gem "minitest"
|
8
|
+
require "logger" # https://github.com/rails/rails/issues/54260
|
8
9
|
require "minitest/autorun"
|
9
10
|
require "minitest/focus"
|
10
11
|
require "minitest/rg"
|
12
|
+
require "ostruct"
|
11
13
|
require "active_support"
|
12
14
|
require "google/cloud/spanner"
|
13
15
|
require "active_record"
|
@@ -18,7 +20,10 @@ require "securerandom"
|
|
18
20
|
require "composite_primary_keys" if ActiveRecord::gem_version < Gem::Version.create('7.1.0')
|
19
21
|
|
20
22
|
# rubocop:disable Style/GlobalVars
|
21
|
-
|
23
|
+
#
|
24
|
+
if ActiveRecord.gem_version >= Gem::Version.create("7.2.0")
|
25
|
+
ActiveRecord::ConnectionAdapters.register("spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter")
|
26
|
+
end
|
22
27
|
$spanner_test_database = "ar-test-#{SecureRandom.hex 4}"
|
23
28
|
|
24
29
|
def connector_config
|
@@ -22,16 +22,16 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename f }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.required_ruby_version = ">=
|
25
|
+
spec.required_ruby_version = ">= 3.1"
|
26
26
|
|
27
27
|
spec.add_dependency "google-cloud-spanner", "~> 2.18"
|
28
28
|
# Pin gRPC to 1.64.3, as 1.65 and 1.66 cause test runs to hang randomly.
|
29
29
|
spec.add_dependency "grpc", "1.64.3"
|
30
|
-
spec.add_runtime_dependency "activerecord", [">= 6.
|
30
|
+
spec.add_runtime_dependency "activerecord", [">= 6.1", "< 9"]
|
31
31
|
|
32
32
|
spec.add_development_dependency "autotest-suffix", "~> 1.1"
|
33
33
|
spec.add_development_dependency "bundler", "~> 2.0"
|
34
|
-
spec.add_development_dependency "google-style", "~> 1.
|
34
|
+
spec.add_development_dependency "google-style", "~> 1.30.1"
|
35
35
|
spec.add_development_dependency "minitest", "~> 5.10"
|
36
36
|
spec.add_development_dependency "minitest-autotest", "~> 1.0"
|
37
37
|
spec.add_development_dependency "minitest-focus", "~> 1.1"
|
data/benchmarks/application.rb
CHANGED
@@ -63,7 +63,7 @@ class Application
|
|
63
63
|
|
64
64
|
puts ""
|
65
65
|
puts "Press any key to end the application"
|
66
|
-
|
66
|
+
$stdin.getch
|
67
67
|
end
|
68
68
|
|
69
69
|
def self.execute_individual_benchmarks singer, client
|
@@ -120,9 +120,7 @@ class Application
|
|
120
120
|
sql = "SELECT * FROM Singers WHERE id=@id"
|
121
121
|
params = { id: singer[:id] }
|
122
122
|
param_types = { id: :INT64 }
|
123
|
-
client.execute(sql, params: params, types: param_types).rows.
|
124
|
-
return row
|
125
|
-
end
|
123
|
+
client.execute(sql, params: params, types: param_types).rows.first(1)
|
126
124
|
else
|
127
125
|
Singer.find singer.id
|
128
126
|
end
|
@@ -134,9 +132,7 @@ class Application
|
|
134
132
|
sql = "SELECT * FROM Singers WHERE id=@id"
|
135
133
|
params = { id: singer[:id] }
|
136
134
|
param_types = { id: :INT64 }
|
137
|
-
client.execute(sql, params: params, types: param_types).rows.
|
138
|
-
return row
|
139
|
-
end
|
135
|
+
client.execute(sql, params: params, types: param_types).rows.first(1)
|
140
136
|
else
|
141
137
|
singer.reload
|
142
138
|
end
|
data/examples/snippets/Rakefile
CHANGED
@@ -7,19 +7,37 @@
|
|
7
7
|
require_relative "config/environment"
|
8
8
|
require "docker"
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
samples = Dir.entries(".").select do |entry|
|
10
|
+
def fetch_samples
|
11
|
+
Dir.entries(".").select do |entry|
|
13
12
|
File.directory?(File.join(".", entry)) \
|
14
|
-
|
15
|
-
|
13
|
+
&& !%w[. ..].include?(entry) \
|
14
|
+
&& File.exist?(File.join(".", entry, "application.rb"))
|
16
15
|
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Lists all available samples."
|
19
|
+
task :list do
|
20
|
+
samples = fetch_samples
|
17
21
|
puts "Available samples: "
|
18
22
|
samples.sort.each { |dir| puts " #{dir}" }
|
19
23
|
puts ""
|
20
24
|
puts "Run a sample with the command `bundle exec rake run\\[<sample-name>\\]`"
|
21
25
|
end
|
22
26
|
|
27
|
+
desc "Runs all samples."
|
28
|
+
task :all do
|
29
|
+
samples = fetch_samples
|
30
|
+
ar_version = ENV.fetch "AR_VERSION", "~> 7.1.0"
|
31
|
+
less_than_7_1 = ar_version.dup.to_s.sub("~>", "").strip < "7.1.0"
|
32
|
+
samples.delete "interleaved-tables" if less_than_7_1
|
33
|
+
samples.delete "interleaved-tables-before-7.1" unless less_than_7_1
|
34
|
+
samples.delete "bit-reversed-sequence" if less_than_7_1
|
35
|
+
samples.delete "query-logs" if less_than_7_1
|
36
|
+
samples.each do |sample|
|
37
|
+
run_sample sample
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
23
41
|
desc "Runs a simple ActiveRecord tutorial on a Spanner emulator."
|
24
42
|
task :run, [:sample] do |_t, args|
|
25
43
|
sample = args[:sample]
|
@@ -28,7 +46,11 @@ task :run, [:sample] do |_t, args|
|
|
28
46
|
puts ""
|
29
47
|
sample = "quickstart"
|
30
48
|
end
|
49
|
+
run_sample sample
|
50
|
+
end
|
31
51
|
|
52
|
+
def run_sample sample
|
53
|
+
puts "Running #{sample}"
|
32
54
|
puts "Downloading Spanner emulator image..."
|
33
55
|
Docker::Image.create "fromImage" => "gcr.io/cloud-spanner-emulator/emulator:latest"
|
34
56
|
puts "Creating Spanner emulator container..."
|
@@ -9,7 +9,7 @@ require_relative "../config/environment"
|
|
9
9
|
require_relative "models/entity_with_array_types"
|
10
10
|
|
11
11
|
class Application
|
12
|
-
def self.run
|
12
|
+
def self.run
|
13
13
|
# Create a record with all array types.
|
14
14
|
record = EntityWithArrayTypes.create \
|
15
15
|
col_array_string: ["value1", "value2", "value3"],
|
@@ -35,10 +35,6 @@ class Application
|
|
35
35
|
puts "Bytes array: #{record.col_array_bytes.map(&:read)}"
|
36
36
|
puts "Date array: #{record.col_array_date}"
|
37
37
|
puts "Timestamp array: #{record.col_array_timestamp}"
|
38
|
-
|
39
|
-
puts ""
|
40
|
-
puts "Press any key to end the application"
|
41
|
-
STDIN.getch
|
42
38
|
end
|
43
39
|
end
|
44
40
|
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
-
require_relative "../../config/environment
|
7
|
+
require_relative "../../config/environment"
|
8
8
|
require_relative "../models/singer"
|
9
9
|
require_relative "../models/album"
|
10
10
|
|
@@ -14,7 +14,7 @@ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Te
|
|
14
14
|
adjectives = %w[daily happy blue generous cooked bad open]
|
15
15
|
nouns = %w[windows potatoes bank street tree glass bottle]
|
16
16
|
|
17
|
-
#
|
17
|
+
# NOTE: We do not use mutations to insert these rows, because letting the database generate the primary key means that
|
18
18
|
# we rely on a `THEN RETURN id` clause in the insert statement. This is only supported for DML statements, and not for
|
19
19
|
# mutations.
|
20
20
|
ActiveRecord::Base.transaction do
|
@@ -46,7 +46,7 @@ class Application
|
|
46
46
|
]
|
47
47
|
end
|
48
48
|
puts ""
|
49
|
-
puts "Created a batch of #{singers.length} singers and #{albums.length} "\
|
49
|
+
puts "Created a batch of #{singers.length} singers and #{albums.length} " \
|
50
50
|
"albums using a transaction with buffered mutations:"
|
51
51
|
singers.each do |s|
|
52
52
|
puts " Created singer #{s.first_name} #{s.last_name} with id #{s.id}"
|
@@ -54,10 +54,6 @@ class Application
|
|
54
54
|
puts " with album #{a.title} with id #{a.id}"
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
58
|
-
puts ""
|
59
|
-
puts "Press any key to end the application"
|
60
|
-
STDIN.getch
|
61
57
|
end
|
62
58
|
end
|
63
59
|
|
@@ -43,10 +43,6 @@ class Application
|
|
43
43
|
puts "Singer and album updated:"
|
44
44
|
puts "#{singer.first_name} #{singer.last_name} (Last updated: #{singer.last_updated.strftime format})"
|
45
45
|
puts " #{album.title} (Last updated: #{album.last_updated.strftime format})"
|
46
|
-
|
47
|
-
puts ""
|
48
|
-
puts "Press any key to end the application"
|
49
|
-
STDIN.getch
|
50
46
|
end
|
51
47
|
end
|
52
48
|
|
@@ -4,11 +4,16 @@
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
+
require "logger" # https://github.com/rails/rails/issues/54260
|
7
8
|
require "active_record"
|
8
9
|
require "bundler"
|
9
10
|
|
10
11
|
Dir["../../lib/*.rb"].each { |file| require file }
|
11
12
|
|
13
|
+
if ActiveRecord.version >= Gem::Version.create("7.2.0")
|
14
|
+
ActiveRecord::ConnectionAdapters.register "spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter"
|
15
|
+
end
|
16
|
+
|
12
17
|
Bundler.require
|
13
18
|
|
14
19
|
ActiveRecord::Base.establish_connection(
|
@@ -13,7 +13,7 @@ class Application
|
|
13
13
|
def self.run
|
14
14
|
# Creating a single record without an explicit transaction will automatically save it to the database.
|
15
15
|
# It is not recommended to call Entity.create repeatedly to insert multiple records, as each call will
|
16
|
-
# use a separate Spanner transaction. Instead multiple records should be created by passing an array of
|
16
|
+
# use a separate Spanner transaction. Instead, multiple records should be created by passing an array of
|
17
17
|
# entities to the Entity.create method.
|
18
18
|
singer = Singer.create first_name: "Dave", last_name: "Allison"
|
19
19
|
puts ""
|
@@ -32,10 +32,6 @@ class Application
|
|
32
32
|
singers.each do |s|
|
33
33
|
puts " Created singer #{s.first_name} #{s.last_name} with id #{s.id}"
|
34
34
|
end
|
35
|
-
|
36
|
-
puts ""
|
37
|
-
puts "Press any key to end the application"
|
38
|
-
STDIN.getch
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
@@ -17,7 +17,7 @@ class Application
|
|
17
17
|
puts "#{"#{singer.first_name} #{singer.last_name}".ljust 30}#{singer.birth_date}"
|
18
18
|
end
|
19
19
|
|
20
|
-
# Update the
|
20
|
+
# Update the birthdate of a random singer using the current system time. Any time and timezone information will be
|
21
21
|
# lost after saving the record as a DATE only contains the year, month and day-of-month information.
|
22
22
|
singer = Singer.all.sample
|
23
23
|
singer.update birth_date: Time.now
|
@@ -25,10 +25,6 @@ class Application
|
|
25
25
|
puts ""
|
26
26
|
puts "Updated birth date to current system time:"
|
27
27
|
puts "#{"#{singer.first_name} #{singer.last_name}".ljust 30}#{singer.birth_date}"
|
28
|
-
|
29
|
-
puts ""
|
30
|
-
puts "Press any key to end the application"
|
31
|
-
STDIN.getch
|
32
28
|
end
|
33
29
|
end
|
34
30
|
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
#
|
7
|
-
require_relative "../../config/environment
|
7
|
+
require_relative "../../config/environment"
|
8
8
|
require_relative "../models/singer"
|
9
9
|
|
10
10
|
first_names = %w[Nelson Todd William Alex Dominique Adenoid Steve Nathan Beverly Annie Amy Norma Diana Regan Phyllis]
|