activerecord-spanner-adapter 1.8.0 → 2.1.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/.github/workflows/acceptance-tests-on-emulator.yaml +4 -6
- data/.github/workflows/ci.yaml +4 -6
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -6
- data/.github/workflows/nightly-unit-tests.yaml +4 -6
- data/.github/workflows/rubocop.yaml +1 -1
- data/.github/workflows/samples.yaml +30 -0
- data/.kokoro/release.cfg +2 -2
- data/.kokoro/release.sh +1 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +28 -0
- data/Gemfile +6 -5
- data/README.md +26 -22
- data/acceptance/cases/migration/command_recorder_test.rb +7 -38
- data/acceptance/cases/migration/references_index_test.rb +2 -11
- data/acceptance/cases/models/binary_identifiers.rb +97 -0
- data/acceptance/cases/models/default_value_test.rb +2 -2
- data/acceptance/cases/tasks/database_tasks_test.rb +71 -74
- data/acceptance/cases/transactions/read_write_transactions_test.rb +10 -4
- 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 +22 -8
- data/activerecord-spanner-adapter.gemspec +3 -3
- data/benchmarks/application.rb +3 -7
- data/examples/snippets/Rakefile +28 -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/auto-generated-primary-key/README.md +140 -0
- data/examples/snippets/auto-generated-primary-key/Rakefile +13 -0
- data/examples/snippets/auto-generated-primary-key/application.rb +86 -0
- data/examples/snippets/auto-generated-primary-key/config/database.yml +10 -0
- data/examples/snippets/auto-generated-primary-key/db/migrate/01_create_tables.rb +29 -0
- data/examples/snippets/auto-generated-primary-key/db/seeds.rb +31 -0
- data/examples/snippets/auto-generated-primary-key/models/album.rb +11 -0
- data/examples/snippets/auto-generated-primary-key/models/singer.rb +11 -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 +7 -3
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
- data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +20 -11
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +22 -51
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +10 -8
- data/lib/active_record/connection_adapters/spanner_adapter.rb +42 -7
- 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/base.rb +59 -32
- 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 +5 -3
- data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
- data/lib/activerecord_spanner_adapter/table/column.rb +16 -4
- 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 +23 -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
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,38 @@
|
|
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 "auto-generated-primary-key" if less_than_7_1
|
36
|
+
samples.delete "query-logs" if less_than_7_1
|
37
|
+
samples.each do |sample|
|
38
|
+
run_sample sample
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
23
42
|
desc "Runs a simple ActiveRecord tutorial on a Spanner emulator."
|
24
43
|
task :run, [:sample] do |_t, args|
|
25
44
|
sample = args[:sample]
|
@@ -28,7 +47,11 @@ task :run, [:sample] do |_t, args|
|
|
28
47
|
puts ""
|
29
48
|
sample = "quickstart"
|
30
49
|
end
|
50
|
+
run_sample sample
|
51
|
+
end
|
31
52
|
|
53
|
+
def run_sample sample
|
54
|
+
puts "Running #{sample}"
|
32
55
|
puts "Downloading Spanner emulator image..."
|
33
56
|
Docker::Image.create "fromImage" => "gcr.io/cloud-spanner-emulator/emulator:latest"
|
34
57
|
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
|
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Sample - Auto-generated Primary Keys
|
2
|
+
|
3
|
+
This example shows how to use an IDENTITY column to generate the primary key of a model.
|
4
|
+
|
5
|
+
See https://cloud.google.com/spanner/docs/primary-key-default-value#identity-columns for more information
|
6
|
+
about IDENTITY columns in Cloud Spanner.
|
7
|
+
|
8
|
+
## Requirements
|
9
|
+
Using IDENTITY for generating primary key values in ActiveRecord has the following requirements:
|
10
|
+
1. You must use __ActiveRecord version 7.1 or higher__.
|
11
|
+
2. Your database configuration must include a `default_sequence_kind` value like this:
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
development:
|
15
|
+
adapter: spanner
|
16
|
+
emulator_host: localhost:9010
|
17
|
+
project: test-project
|
18
|
+
instance: test-instance
|
19
|
+
database: testdb
|
20
|
+
default_sequence_kind: BIT_REVERSED_POSITIVE
|
21
|
+
pool: 5
|
22
|
+
timeout: 5000
|
23
|
+
schema_dump: false
|
24
|
+
```
|
25
|
+
|
26
|
+
## Creating Tables with IDENTITY in ActiveRecord
|
27
|
+
You can create an IDENTITY column using migrations in ActiveRecord by using the `:primary_key` type
|
28
|
+
for the column. Note that this only generates an IDENTITY column if you have included the option
|
29
|
+
`default_sequence_kind: BIT_REVERSED_POSITIVE` as shown above.
|
30
|
+
|
31
|
+
Any default `id` column that is added by ActiveRecord will also use the `:primary_key` type and use
|
32
|
+
an IDENTITY column.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# The default `id` column that is added by ActiveRecord will automatically use
|
36
|
+
# an IDENTITY column.
|
37
|
+
create_table :singers do |t|
|
38
|
+
t.string :first_name
|
39
|
+
t.string :last_name
|
40
|
+
end
|
41
|
+
|
42
|
+
# You can also explicitly create a primary key column with a different name. Use
|
43
|
+
# the :primary_key type to generate an IDENTITY column.
|
44
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
45
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
46
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
47
|
+
t.string :first_name
|
48
|
+
t.string :last_name
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
## Example Data Model
|
53
|
+
This example uses the following table schema:
|
54
|
+
|
55
|
+
```sql
|
56
|
+
CREATE TABLE `singers` (
|
57
|
+
`singerid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
|
58
|
+
`first_name` STRING(MAX),
|
59
|
+
`last_name` STRING(MAX)
|
60
|
+
) PRIMARY KEY (`singerid`)
|
61
|
+
|
62
|
+
CREATE TABLE `albums` (
|
63
|
+
`singerid` INT64 NOT NULL,
|
64
|
+
`albumid` INT64 NOT NULL GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE),
|
65
|
+
`title` STRING(MAX)
|
66
|
+
) PRIMARY KEY (`singerid`, `albumid`), INTERLEAVE IN PARENT `singers`
|
67
|
+
```
|
68
|
+
|
69
|
+
This schema can be created in ActiveRecord 7.1 and later as follows:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# Execute the entire migration as one DDL batch.
|
73
|
+
connection.ddl_batch do
|
74
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
75
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
76
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
77
|
+
t.string :first_name
|
78
|
+
t.string :last_name
|
79
|
+
end
|
80
|
+
|
81
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
82
|
+
# Interleave the `albums` table in the parent table `singers`.
|
83
|
+
t.interleave_in :singers
|
84
|
+
t.integer :singerid, null: false
|
85
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
86
|
+
t.column :albumid, :primary_key, null: false
|
87
|
+
t.string :title
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
## Auto-generated Primary Keys and Mutations
|
93
|
+
|
94
|
+
Using primary keys that are auto-generated by an IDENTITY column is not
|
95
|
+
supported in combination with mutations, because Spanner must return the
|
96
|
+
generated primary key value after a row was inserted using a `THEN RETURN`
|
97
|
+
clause. This is not supported for mutations.
|
98
|
+
|
99
|
+
A workaround for this is to explicitly set a value for the primary key of
|
100
|
+
a row before inserting it:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
ActiveRecord::Base.transaction isolation: :buffered_mutations do
|
104
|
+
# Assign the singer a random id value. This value will be included in the insert mutation.
|
105
|
+
singer = Singer.create id: SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF,
|
106
|
+
first_name: "Melissa", last_name: "Garcia"
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
You can also instruct the Spanner ActiveRecord provider to automatically include a
|
111
|
+
primary key value when you use mutations. The primary key value assignment can then
|
112
|
+
be omitted from your code. Add `use_client_side_id_for_mutations: true` to your
|
113
|
+
configuration for this:
|
114
|
+
|
115
|
+
```yaml
|
116
|
+
development:
|
117
|
+
adapter: spanner
|
118
|
+
emulator_host: localhost:9010
|
119
|
+
project: test-project
|
120
|
+
instance: test-instance
|
121
|
+
database: testdb
|
122
|
+
default_sequence_kind: BIT_REVERSED_POSITIVE
|
123
|
+
use_client_side_id_for_mutations: true
|
124
|
+
pool: 5
|
125
|
+
timeout: 5000
|
126
|
+
schema_dump: false
|
127
|
+
```
|
128
|
+
|
129
|
+
## Running the Sample
|
130
|
+
|
131
|
+
The sample will automatically start a Spanner Emulator in a docker container and execute the sample
|
132
|
+
against that emulator. The emulator will automatically be stopped when the application finishes.
|
133
|
+
|
134
|
+
Run the application with the following commands:
|
135
|
+
|
136
|
+
```bash
|
137
|
+
export AR_VERSION="~> 7.1.2"
|
138
|
+
bundle install
|
139
|
+
bundle exec rake run
|
140
|
+
```
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require_relative "../config/environment"
|
8
|
+
require "sinatra/activerecord/rake"
|
9
|
+
|
10
|
+
desc "Sample showing how to work with auto-generated-primary keys."
|
11
|
+
task :run do
|
12
|
+
Dir.chdir("..") { sh "bundle exec rake run[auto-generated-primary-key]" }
|
13
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Copyright 2025 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require "io/console"
|
8
|
+
require_relative "../config/environment"
|
9
|
+
require_relative "models/singer"
|
10
|
+
require_relative "models/album"
|
11
|
+
|
12
|
+
class Application
|
13
|
+
def self.run
|
14
|
+
# Create a new singer.
|
15
|
+
singer = create_new_singer
|
16
|
+
|
17
|
+
# Create a new album.
|
18
|
+
album = create_new_album singer
|
19
|
+
|
20
|
+
# Verify that the album exists.
|
21
|
+
find_album singer.singerid, album.albumid
|
22
|
+
|
23
|
+
# List all singers, albums and tracks.
|
24
|
+
list_singers_albums
|
25
|
+
|
26
|
+
# Create a new singer using mutations.
|
27
|
+
# This requires us to set a primary key value manually.
|
28
|
+
# You can also instruct the Spanner Ruby ActiveRecord provider to that automatically
|
29
|
+
# by including this in your database configuration:
|
30
|
+
# use_client_side_id_for_mutations: true
|
31
|
+
create_new_singer_using_mutations
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_album singerid, albumid
|
35
|
+
album = Album.find [singerid, albumid]
|
36
|
+
puts "Found album: #{album.title}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.list_singers_albums
|
40
|
+
puts ""
|
41
|
+
puts "Listing all singers with corresponding albums"
|
42
|
+
Singer.all.order("last_name, first_name").each do |singer|
|
43
|
+
puts "#{singer.first_name} #{singer.last_name} has #{singer.albums.count} albums:"
|
44
|
+
singer.albums.order("title").each do |album|
|
45
|
+
puts " #{album.title}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_new_singer
|
51
|
+
# Create a new singer. The singerid is generated by an IDENTITY column in the database and returned.
|
52
|
+
puts ""
|
53
|
+
singer = Singer.create first_name: "Melissa", last_name: "Garcia"
|
54
|
+
puts "Created a new singer '#{singer.first_name} #{singer.last_name}' with id #{singer.singerid}"
|
55
|
+
|
56
|
+
singer
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.create_new_album singer
|
60
|
+
# Create a new album.
|
61
|
+
puts ""
|
62
|
+
puts "Creating a new album for #{singer.first_name} #{singer.last_name}"
|
63
|
+
# The albumid is not generated by the database.
|
64
|
+
album = singer.albums.build albumid: 1, title: "New Title"
|
65
|
+
album.save!
|
66
|
+
|
67
|
+
album
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.create_new_singer_using_mutations
|
71
|
+
# Create a new singer using mutations. Mutations do not support THEN RETURN clauses,
|
72
|
+
# so we must specify a value for the primary key before inserting the row.
|
73
|
+
puts ""
|
74
|
+
singer = nil
|
75
|
+
ActiveRecord::Base.transaction isolation: :buffered_mutations do
|
76
|
+
# Assign the singer a random id value. This value will be included in the insert mutation.
|
77
|
+
singer = Singer.create id: SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF,
|
78
|
+
first_name: "Melissa", last_name: "Garcia"
|
79
|
+
end
|
80
|
+
puts "Inserted a new singer '#{singer.first_name} #{singer.last_name}' using mutations with id #{singer.singerid}"
|
81
|
+
|
82
|
+
singer
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Application.run
|
@@ -0,0 +1,29 @@
|
|
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
|
+
class CreateTables < ActiveRecord::Migration[7.1]
|
8
|
+
def change
|
9
|
+
connection.default_sequence_kind = "BIT_REVERSED_POSITIVE"
|
10
|
+
# Execute the entire migration as one DDL batch.
|
11
|
+
connection.ddl_batch do
|
12
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
13
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
14
|
+
t.column :singerid, :primary_key, primary_key: true, null: false
|
15
|
+
t.string :first_name
|
16
|
+
t.string :last_name
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
20
|
+
# Interleave the `albums` table in the parent table `singers`.
|
21
|
+
t.interleave_in :singers
|
22
|
+
t.integer :singerid, null: false
|
23
|
+
# Use the ':primary_key' data type to create an auto-generated primary key column.
|
24
|
+
t.column :albumid, :primary_key, null: false
|
25
|
+
t.string :title
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2023 Google LLC
|
2
|
+
#
|
3
|
+
# Use of this source code is governed by an MIT-style
|
4
|
+
# license that can be found in the LICENSE file or at
|
5
|
+
# https://opensource.org/licenses/MIT.
|
6
|
+
|
7
|
+
require_relative "../../config/environment"
|
8
|
+
require_relative "../models/singer"
|
9
|
+
require_relative "../models/album"
|
10
|
+
|
11
|
+
first_names = %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
|
+
# NOTE: We do not use mutations to insert these rows, because letting the database generate the primary key means that
|
18
|
+
# we rely on a `THEN RETURN id` clause in the insert statement. This is only supported for DML statements, and not for
|
19
|
+
# mutations.
|
20
|
+
ActiveRecord::Base.transaction do
|
21
|
+
singers = []
|
22
|
+
5.times do
|
23
|
+
singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
|
24
|
+
end
|
25
|
+
|
26
|
+
albums = []
|
27
|
+
20.times do
|
28
|
+
singer = singers.sample
|
29
|
+
albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
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
|
+
class Album < ActiveRecord::Base
|
8
|
+
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
9
|
+
# The primary key of `singers` is `singerid`.
|
10
|
+
belongs_to :singer, foreign_key: :singerid
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
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
|
+
class Singer < ActiveRecord::Base
|
8
|
+
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
9
|
+
# The primary key of `albums` is (`singerid`, `albumid`).
|
10
|
+
has_many :albums, foreign_key: :singerid
|
11
|
+
end
|
@@ -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]
|
@@ -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[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
|