activerecord-spanner-adapter 1.5.0 → 1.6.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 +1 -1
- data/.github/workflows/acceptance-tests-on-production.yaml +5 -3
- data/.github/workflows/ci.yaml +1 -1
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +1 -1
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +5 -3
- data/.github/workflows/nightly-unit-tests.yaml +1 -1
- data/.github/workflows/release-please-label.yml +1 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile +5 -2
- data/README.md +10 -10
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +6 -0
- data/acceptance/cases/migration/change_schema_test.rb +19 -3
- data/acceptance/cases/migration/schema_dumper_test.rb +10 -1
- data/acceptance/cases/models/insert_all_test.rb +22 -0
- data/acceptance/cases/models/interleave_test.rb +6 -0
- data/acceptance/cases/tasks/database_tasks_test.rb +340 -2
- data/acceptance/cases/transactions/optimistic_locking_test.rb +6 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +24 -0
- data/acceptance/models/table_with_sequence.rb +10 -0
- data/acceptance/schema/schema.rb +65 -19
- data/acceptance/test_helper.rb +1 -1
- data/activerecord-spanner-adapter.gemspec +1 -1
- data/examples/snippets/bit-reversed-sequence/README.md +103 -0
- data/examples/snippets/bit-reversed-sequence/Rakefile +13 -0
- data/examples/snippets/bit-reversed-sequence/application.rb +68 -0
- data/examples/snippets/bit-reversed-sequence/config/database.yml +8 -0
- data/examples/snippets/bit-reversed-sequence/db/migrate/01_create_tables.rb +33 -0
- data/examples/snippets/bit-reversed-sequence/db/schema.rb +31 -0
- data/examples/snippets/bit-reversed-sequence/db/seeds.rb +31 -0
- data/examples/snippets/bit-reversed-sequence/models/album.rb +11 -0
- data/examples/snippets/bit-reversed-sequence/models/singer.rb +15 -0
- data/examples/snippets/interleaved-tables/README.md +44 -53
- data/examples/snippets/interleaved-tables/Rakefile +2 -2
- data/examples/snippets/interleaved-tables/application.rb +2 -2
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +12 -18
- data/examples/snippets/interleaved-tables/db/schema.rb +9 -7
- data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/models/album.rb +3 -7
- data/examples/snippets/interleaved-tables/models/singer.rb +1 -1
- data/examples/snippets/interleaved-tables/models/track.rb +6 -7
- data/examples/snippets/interleaved-tables-before-7.1/README.md +167 -0
- data/examples/snippets/interleaved-tables-before-7.1/Rakefile +13 -0
- data/examples/snippets/interleaved-tables-before-7.1/application.rb +126 -0
- data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +37 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/album.rb +20 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/singer.rb +18 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/track.rb +28 -0
- data/examples/snippets/query-logs/README.md +43 -0
- data/examples/snippets/query-logs/Rakefile +13 -0
- data/examples/snippets/query-logs/application.rb +63 -0
- data/examples/snippets/query-logs/config/database.yml +8 -0
- data/examples/snippets/query-logs/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/query-logs/db/schema.rb +31 -0
- data/examples/snippets/query-logs/db/seeds.rb +24 -0
- data/examples/snippets/query-logs/models/album.rb +9 -0
- data/examples/snippets/query-logs/models/singer.rb +9 -0
- data/lib/active_record/connection_adapters/spanner/column.rb +13 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +144 -35
- data/lib/active_record/connection_adapters/spanner/schema_cache.rb +3 -21
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +11 -0
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +3 -2
- data/lib/active_record/connection_adapters/spanner_adapter.rb +28 -9
- data/lib/activerecord_spanner_adapter/base.rb +58 -21
- data/lib/activerecord_spanner_adapter/information_schema.rb +33 -24
- data/lib/activerecord_spanner_adapter/primary_key.rb +1 -1
- data/lib/activerecord_spanner_adapter/table/column.rb +4 -9
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +3 -1
- metadata +33 -4
@@ -0,0 +1,68 @@
|
|
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 "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
|
+
puts ""
|
27
|
+
puts "Press any key to end the application"
|
28
|
+
STDIN.getch
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.find_album singerid, albumid
|
32
|
+
album = Album.find [singerid, albumid]
|
33
|
+
puts "Found album: #{album.title}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.list_singers_albums
|
37
|
+
puts ""
|
38
|
+
puts "Listing all singers with corresponding albums"
|
39
|
+
Singer.all.order("last_name, first_name").each do |singer|
|
40
|
+
puts "#{singer.first_name} #{singer.last_name} has #{singer.albums.count} albums:"
|
41
|
+
singer.albums.order("title").each do |album|
|
42
|
+
puts " #{album.title}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.create_new_singer
|
48
|
+
# Create a new singer. The singerid is generated by the bit-reversed sequence in the database and returned.
|
49
|
+
puts ""
|
50
|
+
singer = Singer.create first_name: "Melissa", last_name: "Garcia"
|
51
|
+
puts "Created a new singer '#{singer.first_name} #{singer.last_name}' with id #{singer.singerid}"
|
52
|
+
|
53
|
+
singer
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.create_new_album singer
|
57
|
+
# Create a new album.
|
58
|
+
puts ""
|
59
|
+
puts "Creating a new album for #{singer.first_name} #{singer.last_name}"
|
60
|
+
# The albumid is not generated by a sequence in the database.
|
61
|
+
album = singer.albums.build albumid: 1, title: "New Title"
|
62
|
+
album.save!
|
63
|
+
|
64
|
+
album
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
Application.run
|
@@ -0,0 +1,33 @@
|
|
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
|
+
class CreateTables < ActiveRecord::Migration[7.1]
|
8
|
+
def change
|
9
|
+
# Execute the entire migration as one DDL batch.
|
10
|
+
connection.ddl_batch do
|
11
|
+
# TODO: Uncomment when bit-reversed sequences are supported in the emulator.
|
12
|
+
# connection.execute "create sequence singer_sequence OPTIONS (sequence_kind = 'bit_reversed_positive')"
|
13
|
+
|
14
|
+
# Explicitly define the primary key.
|
15
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
16
|
+
# TODO: Uncomment when bit-reversed sequences are supported in the emulator.
|
17
|
+
# t.integer :singerid, primary_key: true, null: false,
|
18
|
+
# default: -> { "GET_NEXT_SEQUENCE_VALUE(SEQUENCE singer_sequence)" }
|
19
|
+
t.integer :singerid, primary_key: true, null: false, default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }
|
20
|
+
t.string :first_name
|
21
|
+
t.string :last_name
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
25
|
+
# Interleave the `albums` table in the parent table `singers`.
|
26
|
+
t.interleave_in :singers
|
27
|
+
t.integer :singerid, null: false
|
28
|
+
t.integer :albumid, null: false
|
29
|
+
t.string :title
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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[7.1].define(version: 1) do
|
14
|
+
connection.start_batch_ddl
|
15
|
+
|
16
|
+
create_table "albums", primary_key: ["singerid", "albumid"], force: :cascade do |t|
|
17
|
+
t.integer "singerid", limit: 8, null: false
|
18
|
+
t.integer "albumid", limit: 8, null: false
|
19
|
+
t.string "title"
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table "singers", primary_key: "singerid", default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }, force: :cascade do |t|
|
23
|
+
t.string "first_name"
|
24
|
+
t.string "last_name"
|
25
|
+
end
|
26
|
+
|
27
|
+
connection.run_batch
|
28
|
+
rescue
|
29
|
+
abort_batch
|
30
|
+
raise
|
31
|
+
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.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
|
+
# 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 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
|
+
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,15 @@
|
|
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
|
+
class Singer < ActiveRecord::Base
|
8
|
+
# Set the sequence name so the ActiveRecord provider knows that it should let the database generate the primary key
|
9
|
+
# value and return it using a `THEN RETURN id` clause.
|
10
|
+
self.sequence_name = :singer_sequence
|
11
|
+
|
12
|
+
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
13
|
+
# The primary key of `albums` is (`singerid`, `albumid`).
|
14
|
+
has_many :albums, foreign_key: :singerid
|
15
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
# Sample - Interleaved Tables
|
2
2
|
|
3
|
-
This example
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
__NOTE__: This example requires Rails 7.1 or later.
|
4
|
+
|
5
|
+
This example shows how to use interleaved tables with the Spanner ActiveRecord adapter in Rails 7.1 and later.
|
6
|
+
Interleaved tables use composite primary keys. This is only supported by Rails 7.1 and later. For older versions,
|
7
|
+
you need to use the third-party gem `composite_primary_key` (https://github.com/composite-primary-keys/composite_primary_keys).
|
7
8
|
|
8
9
|
See https://cloud.google.com/spanner/docs/schema-and-data-model#creating-interleaved-tables for more information
|
9
10
|
on interleaved tables if you are not familiar with this concept.
|
@@ -13,8 +14,6 @@ You can create interleaved tables using migrations in ActiveRecord by using the
|
|
13
14
|
methods that are defined on `TableDefinition`:
|
14
15
|
* `interleave_in`: Specifies which parent table a child table should be interleaved in and optionally whether
|
15
16
|
deletes of a parent record should automatically cascade delete all child records.
|
16
|
-
* `parent_key`: Creates a column that is a reference to (a part of) the primary key of the parent table. Each child
|
17
|
-
table must include all the primary key columns of the parent table as a `parent_key`.
|
18
17
|
|
19
18
|
Cloud Spanner requires a child table to include the exact same primary key columns as the parent table in addition to
|
20
19
|
the primary key column(s) of the child table. This means that the default `id` primary key column of ActiveRecord is
|
@@ -32,62 +31,55 @@ CREATE TABLE singers (
|
|
32
31
|
) PRIMARY KEY (singerid);
|
33
32
|
|
34
33
|
CREATE TABLE albums (
|
35
|
-
albumid INT64 NOT NULL,
|
36
34
|
singerid INT64 NOT NULL,
|
35
|
+
albumid INT64 NOT NULL,
|
37
36
|
title STRING(MAX)
|
38
37
|
) PRIMARY KEY (singerid, albumid), INTERLEAVE IN PARENT singers;
|
39
38
|
|
40
39
|
CREATE TABLE tracks (
|
41
|
-
trackid INT64 NOT NULL,
|
42
40
|
singerid INT64 NOT NULL,
|
43
41
|
albumid INT64 NOT NULL,
|
42
|
+
trackid INT64 NOT NULL,
|
44
43
|
title STRING(MAX),
|
45
44
|
duration NUMERIC
|
46
45
|
) PRIMARY KEY (singerid, albumid, trackid), INTERLEAVE IN PARENT albums ON DELETE CASCADE;
|
47
46
|
```
|
48
47
|
|
49
|
-
This schema can be created in ActiveRecord as follows:
|
48
|
+
This schema can be created in ActiveRecord 7.1 and later as follows:
|
50
49
|
|
51
50
|
```ruby
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
51
|
+
# Execute the entire migration as one DDL batch.
|
52
|
+
connection.ddl_batch do
|
53
|
+
# Explicitly define the primary key.
|
54
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
55
|
+
t.integer :singerid
|
56
|
+
t.string :first_name
|
57
|
+
t.string :last_name
|
58
|
+
end
|
58
59
|
|
59
|
-
create_table :albums, id: false do |t|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
t.parent_key :singerid
|
67
|
-
t.string :title
|
68
|
-
end
|
60
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
61
|
+
# Interleave the `albums` table in the parent table `singers`.
|
62
|
+
t.interleave_in :singers
|
63
|
+
t.integer :singerid
|
64
|
+
t.integer :albumid
|
65
|
+
t.string :title
|
66
|
+
end
|
69
67
|
|
70
|
-
create_table :tracks, id: false do |t|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
t.parent_key :albumid
|
81
|
-
t.string :title
|
82
|
-
t.numeric :duration
|
68
|
+
create_table :tracks, primary_key: [:singerid, :albumid, :trackid], id: false do |t|
|
69
|
+
# Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
|
70
|
+
# album when an album is deleted.
|
71
|
+
t.interleave_in :albums, :cascade
|
72
|
+
t.integer :singerid
|
73
|
+
t.integer :albumid
|
74
|
+
t.integer :trackid
|
75
|
+
t.string :title
|
76
|
+
t.numeric :duration
|
77
|
+
end
|
83
78
|
end
|
84
79
|
```
|
85
80
|
|
86
81
|
## Models for Interleaved Tables
|
87
|
-
|
88
|
-
function from the `composite_primary_keys` gem.
|
89
|
-
|
90
|
-
An interleaved table parent/child relationship must be modelled as a `belongs_to`/`has_many` association in
|
82
|
+
An interleaved table parent/child relationship can be modelled as a `belongs_to`/`has_many` association in
|
91
83
|
ActiveRecord. As the columns that are used to reference a parent record use a custom column name, it is required to also
|
92
84
|
include the custom column name(s) in the `belongs_to` and `has_many` definitions.
|
93
85
|
|
@@ -108,7 +100,7 @@ class Singer < ActiveRecord::Base
|
|
108
100
|
has_many :albums, foreign_key: :singerid
|
109
101
|
|
110
102
|
# `tracks` is defined as INTERLEAVE IN PARENT `albums`.
|
111
|
-
# The primary key of `tracks` is
|
103
|
+
# The primary key of `tracks` is [`singerid`, `albumid`, `trackid`].
|
112
104
|
# The `singerid` column can be used to associate tracks with a singer without the need to go through albums.
|
113
105
|
# Note also that the inclusion of `singerid` as a column in `tracks` is required in order to make `tracks` a child
|
114
106
|
# table of `albums` which has primary key (`singerid`, `albumid`).
|
@@ -116,24 +108,21 @@ class Singer < ActiveRecord::Base
|
|
116
108
|
end
|
117
109
|
|
118
110
|
class Album < ActiveRecord::Base
|
119
|
-
# Use the `composite_primary_key` gem to create a composite primary key definition for the model.
|
120
|
-
self.primary_keys = :singerid, :albumid
|
121
|
-
|
122
111
|
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
123
112
|
# The primary key of `singers` is `singerid`.
|
124
113
|
belongs_to :singer, foreign_key: :singerid
|
125
114
|
|
126
115
|
# `tracks` is defined as INTERLEAVE IN PARENT `albums`.
|
127
116
|
# The primary key of `albums` is (`singerid`, `albumid`).
|
128
|
-
|
117
|
+
# Rails 7.1 requires using query_constraints to define a composite foreign key.
|
118
|
+
has_many :tracks, query_constraints: [:singerid, :albumid]
|
129
119
|
end
|
130
120
|
|
131
121
|
class Track < ActiveRecord::Base
|
132
|
-
#
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
belongs_to :album, foreign_key: [:singerid, :albumid]
|
122
|
+
# `tracks` is defined as INTERLEAVE IN PARENT `albums`.
|
123
|
+
# The primary key of `albums` is (`singerid`, `albumid`).
|
124
|
+
# Rails 7.1 requires a composite primary key in a belongs_to relationship to be specified as query_constraints.
|
125
|
+
belongs_to :album, query_constraints: [:singerid, :albumid]
|
137
126
|
|
138
127
|
# `tracks` also has a `singerid` column that can be used to associate a Track with a Singer.
|
139
128
|
belongs_to :singer, foreign_key: :singerid
|
@@ -157,8 +146,10 @@ end
|
|
157
146
|
The sample will automatically start a Spanner Emulator in a docker container and execute the sample
|
158
147
|
against that emulator. The emulator will automatically be stopped when the application finishes.
|
159
148
|
|
160
|
-
Run the application with the
|
149
|
+
Run the application with the following commands:
|
161
150
|
|
162
151
|
```bash
|
152
|
+
export AR_VERSION="~> 7.1.2"
|
153
|
+
bundle install
|
163
154
|
bundle exec rake run
|
164
155
|
```
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Google LLC
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -7,7 +7,7 @@
|
|
7
7
|
require_relative "../config/environment"
|
8
8
|
require "sinatra/activerecord/rake"
|
9
9
|
|
10
|
-
desc "Sample showing how to work with interleaved tables in ActiveRecord."
|
10
|
+
desc "Sample showing how to work with interleaved tables in ActiveRecord 7.1 and later."
|
11
11
|
task :run do
|
12
12
|
Dir.chdir("..") { sh "bundle exec rake run[interleaved-tables]" }
|
13
13
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Google LLC
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
@@ -49,7 +49,7 @@ class Application
|
|
49
49
|
puts "Found album: #{album.title}"
|
50
50
|
end
|
51
51
|
|
52
|
-
def self.
|
52
|
+
def self.list_singers_albums
|
53
53
|
puts ""
|
54
54
|
puts "Listing all singers with corresponding albums and tracks"
|
55
55
|
Singer.all.order("last_name, first_name").each do |singer|
|
@@ -1,41 +1,35 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Google LLC
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
-
class CreateTables < ActiveRecord::Migration[
|
7
|
+
class CreateTables < ActiveRecord::Migration[7.1]
|
8
8
|
def change
|
9
9
|
# Execute the entire migration as one DDL batch.
|
10
10
|
connection.ddl_batch do
|
11
|
-
|
12
|
-
|
13
|
-
t.
|
11
|
+
# Explicitly define the primary key.
|
12
|
+
create_table :singers, id: false, primary_key: :singerid do |t|
|
13
|
+
t.integer :singerid
|
14
14
|
t.string :first_name
|
15
15
|
t.string :last_name
|
16
16
|
end
|
17
17
|
|
18
|
-
create_table :albums, id: false do |t|
|
18
|
+
create_table :albums, primary_key: [:singerid, :albumid], id: false do |t|
|
19
19
|
# Interleave the `albums` table in the parent table `singers`.
|
20
20
|
t.interleave_in :singers
|
21
|
-
t.
|
22
|
-
|
23
|
-
# it is not presented to ActiveRecord as part of the primary key, to prevent ActiveRecord from considering this
|
24
|
-
# to be an entity with a composite primary key (which is not supported by ActiveRecord).
|
25
|
-
t.parent_key :singerid
|
21
|
+
t.integer :singerid
|
22
|
+
t.integer :albumid
|
26
23
|
t.string :title
|
27
24
|
end
|
28
25
|
|
29
|
-
create_table :tracks, id: false do |t|
|
26
|
+
create_table :tracks, primary_key: [:singerid, :albumid, :trackid], id: false do |t|
|
30
27
|
# Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
|
31
28
|
# album when an album is deleted.
|
32
29
|
t.interleave_in :albums, :cascade
|
33
|
-
|
34
|
-
t.
|
35
|
-
|
36
|
-
# database, but are presented as parent keys to ActiveRecord.
|
37
|
-
t.parent_key :singerid
|
38
|
-
t.parent_key :albumid
|
30
|
+
t.integer :singerid
|
31
|
+
t.integer :albumid
|
32
|
+
t.integer :trackid
|
39
33
|
t.string :title
|
40
34
|
t.numeric :duration
|
41
35
|
end
|
@@ -10,22 +10,24 @@
|
|
10
10
|
#
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
|
-
ActiveRecord::Schema.define(version: 1) do
|
13
|
+
ActiveRecord::Schema[7.1].define(version: 1) do
|
14
14
|
connection.start_batch_ddl
|
15
15
|
|
16
|
-
create_table "albums", primary_key: "
|
17
|
-
t.integer "singerid", limit: 8
|
16
|
+
create_table "albums", primary_key: ["singerid", "albumid"], force: :cascade do |t|
|
17
|
+
t.integer "singerid", limit: 8
|
18
|
+
t.integer "albumid", limit: 8
|
18
19
|
t.string "title"
|
19
20
|
end
|
20
21
|
|
21
|
-
create_table "singers", primary_key: "singerid",
|
22
|
+
create_table "singers", primary_key: "singerid", force: :cascade do |t|
|
22
23
|
t.string "first_name"
|
23
24
|
t.string "last_name"
|
24
25
|
end
|
25
26
|
|
26
|
-
create_table "tracks", primary_key: "
|
27
|
-
t.integer "singerid", limit: 8
|
28
|
-
t.integer "albumid", limit: 8
|
27
|
+
create_table "tracks", primary_key: ["singerid", "albumid", "trackid"], force: :cascade do |t|
|
28
|
+
t.integer "singerid", limit: 8
|
29
|
+
t.integer "albumid", limit: 8
|
30
|
+
t.integer "trackid", limit: 8
|
29
31
|
t.string "title"
|
30
32
|
t.decimal "duration"
|
31
33
|
end
|
@@ -1,20 +1,16 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Google LLC
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
-
require "composite_primary_keys"
|
8
|
-
|
9
7
|
class Album < ActiveRecord::Base
|
10
|
-
# Use the `composite_primary_key` gem to create a composite primary key definition for the model.
|
11
|
-
self.primary_keys = :singerid, :albumid
|
12
|
-
|
13
8
|
# `albums` is defined as INTERLEAVE IN PARENT `singers`.
|
14
9
|
# The primary key of `singers` is `singerid`.
|
15
10
|
belongs_to :singer, foreign_key: :singerid
|
16
11
|
|
17
12
|
# `tracks` is defined as INTERLEAVE IN PARENT `albums`.
|
18
13
|
# The primary key of `albums` is (`singerid`, `albumid`).
|
19
|
-
|
14
|
+
# Rails 7.1 requires using query_constraints to define a composite foreign key.
|
15
|
+
has_many :tracks, query_constraints: [:singerid, :albumid]
|
20
16
|
end
|
@@ -1,17 +1,16 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2023 Google LLC
|
2
2
|
#
|
3
3
|
# Use of this source code is governed by an MIT-style
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
7
|
class Track < ActiveRecord::Base
|
8
|
-
#
|
9
|
-
|
8
|
+
# `tracks` is defined as INTERLEAVE IN PARENT `albums`.
|
9
|
+
# The primary key of `albums` is (`singerid`, `albumid`).
|
10
|
+
# Rails 7.1 requires a composite primary key in a belongs_to relationship to be specified as query_constraints.
|
11
|
+
belongs_to :album, query_constraints: [:singerid, :albumid]
|
10
12
|
|
11
|
-
# `tracks`
|
12
|
-
belongs_to :album, foreign_key: [:singerid, :albumid]
|
13
|
-
|
14
|
-
# `tracks` also has a `singerid` column should be used to associate a Track with a Singer.
|
13
|
+
# `tracks` also has a `singerid` column that can be used to associate a Track with a Singer.
|
15
14
|
belongs_to :singer, foreign_key: :singerid
|
16
15
|
|
17
16
|
# Override the default initialize method to automatically set the singer attribute when an album is given.
|