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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 539c47de17139481981cfaaaf2b65d43d8f0b27642ad68663658123d0053a8fb
|
4
|
+
data.tar.gz: c4eb45d075689d08da773ebc11bd1e24df9c6cb7b6ab79a2a352c5c4f4442fbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7d32fa8e2aeeb7785a35f4d11c2dd1ae2db7298d030b6d0da3f08ef5acb2c19833f4f80bc4b5e4b3b174ff9a34121fe97f50ddae902a3af1505eacec311b790
|
7
|
+
data.tar.gz: 1ec5e7a0370d3bbcc3dd8b0c2b7412f81a57f03703784a0b20476d20d9016d409efcd4f3dc8ecd234e64d1cbedcfe804497fd6b65fd277d7feca014393627edf
|
@@ -18,14 +18,12 @@ jobs:
|
|
18
18
|
strategy:
|
19
19
|
max-parallel: 4
|
20
20
|
matrix:
|
21
|
-
ruby: ["
|
22
|
-
ar: ["~>
|
21
|
+
ruby: ["3.1", "3.2", "3.3"]
|
22
|
+
ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
|
23
23
|
# Exclude combinations that are not supported.
|
24
24
|
exclude:
|
25
|
-
- ruby: "
|
26
|
-
ar: "~>
|
27
|
-
- ruby: "3.0"
|
28
|
-
ar: "~> 7.2.0"
|
25
|
+
- ruby: "3.1"
|
26
|
+
ar: "~> 8.0.0"
|
29
27
|
env:
|
30
28
|
AR_VERSION: ${{ matrix.ar }}
|
31
29
|
steps:
|
data/.github/workflows/ci.yaml
CHANGED
@@ -10,14 +10,12 @@ jobs:
|
|
10
10
|
strategy:
|
11
11
|
max-parallel: 4
|
12
12
|
matrix:
|
13
|
-
ruby: ["
|
14
|
-
ar: ["~>
|
13
|
+
ruby: ["3.1", "3.2", "3.3"]
|
14
|
+
ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
|
15
15
|
# Exclude combinations that are not supported.
|
16
16
|
exclude:
|
17
|
-
- ruby: "
|
18
|
-
ar: "~>
|
19
|
-
- ruby: "3.0"
|
20
|
-
ar: "~> 7.2.0"
|
17
|
+
- ruby: "3.1"
|
18
|
+
ar: "~> 8.0.0"
|
21
19
|
env:
|
22
20
|
AR_VERSION: ${{ matrix.ar }}
|
23
21
|
steps:
|
@@ -18,14 +18,12 @@ jobs:
|
|
18
18
|
strategy:
|
19
19
|
max-parallel: 4
|
20
20
|
matrix:
|
21
|
-
ruby: ["
|
22
|
-
ar: ["~>
|
21
|
+
ruby: ["3.1", "3.2", "3.3"]
|
22
|
+
ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
|
23
23
|
# Exclude combinations that are not supported.
|
24
24
|
exclude:
|
25
|
-
- ruby: "
|
26
|
-
ar: "~>
|
27
|
-
- ruby: "3.0"
|
28
|
-
ar: "~> 7.2.0"
|
25
|
+
- ruby: "3.1"
|
26
|
+
ar: "~> 8.0.0"
|
29
27
|
env:
|
30
28
|
AR_VERSION: ${{ matrix.ar }}
|
31
29
|
steps:
|
@@ -11,14 +11,12 @@ jobs:
|
|
11
11
|
max-parallel: 4
|
12
12
|
matrix:
|
13
13
|
# Run acceptance tests all supported combinations of Ruby and ActiveRecord.
|
14
|
-
ruby: ["
|
15
|
-
ar: ["~>
|
14
|
+
ruby: ["3.1", "3.2", "3.3"]
|
15
|
+
ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
|
16
16
|
# Exclude combinations that are not supported.
|
17
17
|
exclude:
|
18
|
-
- ruby: "
|
19
|
-
ar: "~>
|
20
|
-
- ruby: "3.0"
|
21
|
-
ar: "~> 7.2.0"
|
18
|
+
- ruby: "3.1"
|
19
|
+
ar: "~> 8.0.0"
|
22
20
|
env:
|
23
21
|
AR_VERSION: ${{ matrix.ar }}
|
24
22
|
steps:
|
@@ -0,0 +1,30 @@
|
|
1
|
+
on:
|
2
|
+
pull_request:
|
3
|
+
name: samples
|
4
|
+
jobs:
|
5
|
+
test:
|
6
|
+
runs-on: ubuntu-latest
|
7
|
+
|
8
|
+
strategy:
|
9
|
+
max-parallel: 4
|
10
|
+
matrix:
|
11
|
+
ruby: ["3.1", "3.2", "3.3"]
|
12
|
+
ar: ["~> 7.0.0", "~> 7.1.0", "~> 7.2.0", "~> 8.0.0"]
|
13
|
+
# Exclude combinations that are not supported.
|
14
|
+
exclude:
|
15
|
+
- ruby: "3.1"
|
16
|
+
ar: "~> 8.0.0"
|
17
|
+
env:
|
18
|
+
AR_VERSION: ${{ matrix.ar }}
|
19
|
+
steps:
|
20
|
+
- uses: actions/checkout@v4
|
21
|
+
- name: Set up Ruby
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
bundler-cache: false
|
25
|
+
ruby-version: ${{ matrix.ruby }}
|
26
|
+
- name: Install dependencies
|
27
|
+
run: bundle install
|
28
|
+
- name: Run samples
|
29
|
+
run: bundle exec rake all
|
30
|
+
working-directory: examples/snippets
|
data/.kokoro/release.cfg
CHANGED
@@ -13,7 +13,7 @@ build_file: "ruby-spanner-activerecord/.kokoro/trampoline_v2.sh"
|
|
13
13
|
# Configure the docker image for kokoro-trampoline.
|
14
14
|
env_vars: {
|
15
15
|
key: "TRAMPOLINE_IMAGE"
|
16
|
-
value: "us-central1-docker.pkg.dev/cloud-sdk-release-custom-pool/release-images/ruby-
|
16
|
+
value: "us-central1-docker.pkg.dev/cloud-sdk-release-custom-pool/release-images/ruby-release"
|
17
17
|
}
|
18
18
|
|
19
19
|
env_vars: {
|
@@ -28,7 +28,7 @@ env_vars: {
|
|
28
28
|
|
29
29
|
env_vars: {
|
30
30
|
key: "SECRET_MANAGER_KEYS"
|
31
|
-
value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem
|
31
|
+
value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem"
|
32
32
|
}
|
33
33
|
|
34
34
|
# Pick up Rubygems key from internal keystore
|
data/.kokoro/release.sh
CHANGED
@@ -7,7 +7,4 @@ set -eo pipefail
|
|
7
7
|
export GEM_HOME=$HOME/.gem
|
8
8
|
export PATH=$GEM_HOME/bin:$PATH
|
9
9
|
|
10
|
-
|
11
|
-
toys release install-python-tools -v
|
12
|
-
python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script
|
13
|
-
toys release perform -v --enable-docs < /dev/null
|
10
|
+
toys release perform -v --reporter-org=googleapis --force-republish --enable-rad < /dev/null
|
data/.rubocop.yml
CHANGED
@@ -16,7 +16,7 @@ AllCops:
|
|
16
16
|
|
17
17
|
Documentation:
|
18
18
|
Enabled: false
|
19
|
-
Layout/
|
19
|
+
Layout/HashAlignment:
|
20
20
|
Enabled: false
|
21
21
|
Metrics/ClassLength:
|
22
22
|
Enabled: false
|
@@ -24,7 +24,7 @@ Metrics/ModuleLength:
|
|
24
24
|
Enabled: false
|
25
25
|
Naming/RescuedExceptionsVariableName:
|
26
26
|
Enabled: false
|
27
|
-
Naming/
|
27
|
+
Naming/MethodParameterName:
|
28
28
|
Enabled: false
|
29
29
|
Style/EmptyMethod:
|
30
30
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 2.1.0 (2025-03-17)
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* support IDENTITY columns for auto-generated primary keys ([#352](https://github.com/googleapis/ruby-spanner-activerecord/issues/352))
|
8
|
+
#### Documentation
|
9
|
+
|
10
|
+
* add a test to verify that FOR UPDATE can be used ([#348](https://github.com/googleapis/ruby-spanner-activerecord/issues/348))
|
11
|
+
* update README with the correct supported versions ([#349](https://github.com/googleapis/ruby-spanner-activerecord/issues/349))
|
12
|
+
|
13
|
+
### 2.0.0 (2025-01-23)
|
14
|
+
|
15
|
+
### ⚠ BREAKING CHANGES
|
16
|
+
|
17
|
+
* drop support for Rails 6.1 ([#346](https://github.com/googleapis/ruby-spanner-activerecord/issues/346))
|
18
|
+
* deserialize BYTES to StringIO ([#343](https://github.com/googleapis/ruby-spanner-activerecord/issues/343))
|
19
|
+
|
20
|
+
#### Features
|
21
|
+
|
22
|
+
* drop support for Rails 6.1 ([#346](https://github.com/googleapis/ruby-spanner-activerecord/issues/346))
|
23
|
+
* support Rails 8.0 ([#331](https://github.com/googleapis/ruby-spanner-activerecord/issues/331))
|
24
|
+
#### Bug Fixes
|
25
|
+
|
26
|
+
* deserialize BYTES to StringIO ([#343](https://github.com/googleapis/ruby-spanner-activerecord/issues/343))
|
27
|
+
#### Documentation
|
28
|
+
|
29
|
+
* add rails dbconsole to list of limitations ([#224](https://github.com/googleapis/ruby-spanner-activerecord/issues/224))
|
30
|
+
|
3
31
|
### 1.8.0 (2024-12-12)
|
4
32
|
|
5
33
|
#### Features
|
data/Gemfile
CHANGED
@@ -3,17 +3,18 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in activerecord-spanner.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
6
|
+
ar_version = ENV.fetch("AR_VERSION", "~> 7.1.0")
|
7
|
+
gem "activerecord", ar_version
|
7
8
|
gem "ostruct"
|
8
9
|
gem "minitest", "~> 5.25.0"
|
9
10
|
gem "minitest-rg", "~> 5.3.0"
|
10
|
-
gem "pry", "~> 0.
|
11
|
-
gem "pry-byebug", "~> 3.
|
11
|
+
gem "pry", "~> 0.14.2"
|
12
|
+
gem "pry-byebug", "~> 3.10.1"
|
12
13
|
# Add sqlite3 for testing for compatibility with other adapters.
|
13
|
-
gem 'sqlite3'
|
14
|
+
gem 'sqlite3'
|
14
15
|
|
15
16
|
# Required for samples and testing.
|
16
|
-
install_if -> {
|
17
|
+
install_if -> { ar_version.dup.to_s.sub("~>", "").strip < "7.1.0" && !ENV["SKIP_COMPOSITE_PK"] } do
|
17
18
|
gem "composite_primary_keys"
|
18
19
|
end
|
19
20
|
|
data/README.md
CHANGED
@@ -4,16 +4,17 @@
|
|
4
4
|
|
5
5
|

|
6
6
|
|
7
|
-
__This adapter only supports GoogleSQL-dialect
|
7
|
+
__This adapter only supports GoogleSQL-dialect Spanner databases. PostgreSQL-dialect
|
8
8
|
databases are not supported. You can use the standard PostgreSQL ActiveRecord adapter in
|
9
9
|
[combination with PGAdapter](https://github.com/GoogleCloudPlatform/pgadapter/blob/-/samples/ruby/activerecord)
|
10
|
-
for
|
10
|
+
for Spanner PostgreSQL-dialect databases.__
|
11
11
|
|
12
|
-
This project provides a
|
12
|
+
This project provides a Spanner adapter for ActiveRecord. It supports the following versions:
|
13
13
|
|
14
|
-
- ActiveRecord
|
15
|
-
- ActiveRecord
|
16
|
-
- ActiveRecord 7.
|
14
|
+
- ActiveRecord 7.0.x with Ruby 3.1 and higher.
|
15
|
+
- ActiveRecord 7.1.x with Ruby 3.1 and higher.
|
16
|
+
- ActiveRecord 7.2.x with Ruby 3.1 and higher.
|
17
|
+
- ActiveRecord 8.0.x with Ruby 3.2 and higher.
|
17
18
|
|
18
19
|
Known limitations are listed in the [Limitations](#limitations) section.
|
19
20
|
Please report any problems that you might encounter by [creating a new issue](https://github.com/googleapis/ruby-spanner-activerecord/issues/new).
|
@@ -41,7 +42,7 @@ And then execute:
|
|
41
42
|
### Migrations
|
42
43
|
__Use DDL batching when executing migrations for the best possible performance.__
|
43
44
|
|
44
|
-
Executing multiple schema changes on
|
45
|
+
Executing multiple schema changes on Spanner can take a long time. It is therefore
|
45
46
|
strongly recommended that you limit the number of schema change operations. You can do
|
46
47
|
this by using DDL batching in your migrations. See [the migrations examples](examples/snippets/migrations)
|
47
48
|
for how to do this.
|
@@ -62,8 +63,8 @@ development:
|
|
62
63
|
To get started with Rails, read the tutorial under {file:examples/rails/README.md examples/rails/README.md}.
|
63
64
|
|
64
65
|
You can also find a list of short self-contained code examples that show how
|
65
|
-
to use ActiveRecord with
|
66
|
-
database, as all samples will automatically start a
|
66
|
+
to use ActiveRecord with Spanner under the directory [examples/snippets](examples/snippets). Each example is directly runnable without the need to setup a Spanner
|
67
|
+
database, as all samples will automatically start a Spanner emulator in a Docker container and execute the sample
|
67
68
|
code against that emulator. All samples can be executed by navigating to the sample directory on your local machine and
|
68
69
|
then executing the command `bundle exec rake run`. Example:
|
69
70
|
|
@@ -76,28 +77,31 @@ __NOTE__: You do need to have [Docker](https://docs.docker.com/get-docker/) inst
|
|
76
77
|
|
77
78
|
Some noteworthy examples in the snippets directory:
|
78
79
|
- [quickstart](examples/snippets/quickstart): A simple application that shows how to create and query a simple database containing two tables.
|
79
|
-
- [migrations](examples/snippets/migrations): Shows a best-practice for executing migrations on
|
80
|
+
- [migrations](examples/snippets/migrations): Shows a best-practice for executing migrations on Spanner.
|
81
|
+
- [auto-generated-primary-key](examples/snippets/auto-generated-primary-key): Shows how to use IDENTITY columns for primary keys.
|
80
82
|
- [bit-reversed-sequences](examples/snippets/bit-reversed-sequence): Shows how to use bit-reversed sequences for primary keys.
|
81
|
-
- [read-write-transactions](examples/snippets/read-write-transactions): Shows how to execute transactions on
|
82
|
-
- [read-only-transactions](examples/snippets/read-only-transactions): Shows how to execute read-only transactions on
|
83
|
+
- [read-write-transactions](examples/snippets/read-write-transactions): Shows how to execute transactions on Spanner.
|
84
|
+
- [read-only-transactions](examples/snippets/read-only-transactions): Shows how to execute read-only transactions on Spanner.
|
83
85
|
- [bulk-insert](examples/snippets/bulk-insert): Shows the best way to insert a large number of new records.
|
84
86
|
- [mutations](examples/snippets/mutations): Shows how you can use [mutations instead of DML](https://cloud.google.com/spanner/docs/dml-versus-mutations)
|
85
|
-
for inserting, updating and deleting data in a
|
87
|
+
for inserting, updating and deleting data in a Spanner database. Mutations can have a significant performance
|
86
88
|
advantage compared to DML statements, but do not allow read-your-writes semantics during a transaction.
|
87
89
|
- [array-data-type](examples/snippets/array-data-type): Shows how to work with `ARRAY` data types.
|
88
90
|
- [interleaved-tables](examples/snippets/interleaved-tables-before-7.1): Shows how to work with [Interleaved Tables](https://cloud.google.com/spanner/docs/schema-and-data-model#create-interleaved-tables).
|
89
91
|
|
90
92
|
## Limitations
|
91
93
|
|
92
|
-
| Limitation
|
93
|
-
|
94
|
-
| Interleaved tables require composite primary keys
|
95
|
-
| Lack of sequential IDs
|
96
|
-
| Table without Primary Key
|
97
|
-
| Table names CANNOT have spaces within them whether back-ticked or not
|
98
|
-
| Table names CANNOT have punctuation marks and MUST contain valid UTF-8
|
99
|
-
| Index with fields length [add_index](https://apidock.com/rails/v5.2.3/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index)
|
100
|
-
| Only GoogleSQL-dialect databases
|
94
|
+
| Limitation | Comment | Resolution |
|
95
|
+
|------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
|
96
|
+
| Interleaved tables require composite primary keys | Spanner requires composite primary keys for interleaved tables. See {file:examples/snippets/interleaved-tables/README.md this example} for an example on how to use interleaved tables with ActiveRecord | Use composite primary keys. |
|
97
|
+
| Lack of sequential IDs | Spanner uses either bit-reversed IDENTITY columns or sequences to generated primary keys to avoid [hotspotting](https://cloud.google.com/spanner/docs/schema-design#uuid_primary_key) so you SHOULD NOT rely on IDs being sorted | Use either IDENTITY columns or bit-reversed sequences to automatically generate primary keys. |
|
98
|
+
| Table without Primary Key | Spanner support does not support tables without a primary key. | Always define a primary key for your table. |
|
99
|
+
| Table names CANNOT have spaces within them whether back-ticked or not | Spanner DOES NOT support tables with spaces in them for example `Entity ID` | Ensure that your table names don't contain spaces. |
|
100
|
+
| Table names CANNOT have punctuation marks and MUST contain valid UTF-8 | Spanner DOES NOT support punctuation marks e.g. periods ".", question marks "?" in table names | Ensure that your table names don't contain punctuation marks. |
|
101
|
+
| Index with fields length [add_index](https://apidock.com/rails/v5.2.3/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index) | Spanner does not support index with fields length | Ensure that your database definition does not include index definitions with field lengths. |
|
102
|
+
| Only GoogleSQL-dialect databases | Spanner supports both GoogleSQL- and PostgreSQL-dialect databases. This adapter only supports GoogleSQL-dialect databases. You can use the [PostgreSQL ActiveRecord provider in combination with PGAdapter](https://github.com/GoogleCloudPlatform/pgadapter/tree/postgresql-dialect/samples/ruby/activerecord) for Spanner PostgreSQL databases. | |
|
103
|
+
| `rails dbconsole` is not supported. | The `rails dbconsole` is not supported for Spanner databases. | |
|
104
|
+
|
101
105
|
|
102
106
|
## Contributing
|
103
107
|
|
@@ -13,9 +13,6 @@ module ActiveRecord
|
|
13
13
|
class CommandRecorderTest < SpannerAdapter::TestCase
|
14
14
|
include SpannerAdapter::Migration::TestHelper
|
15
15
|
|
16
|
-
VERSION_6_1_0 = Gem::Version.create "6.1.0"
|
17
|
-
VERSION_6_0_3 = Gem::Version.create "6.0.3"
|
18
|
-
|
19
16
|
def setup
|
20
17
|
skip_test_table_create!
|
21
18
|
super
|
@@ -203,20 +200,12 @@ module ActiveRecord
|
|
203
200
|
|
204
201
|
def test_invert_add_index
|
205
202
|
remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
|
206
|
-
|
207
|
-
assert_equal [:remove_index, [:table, { column: [:one, :two] }]], remove
|
208
|
-
else
|
209
|
-
assert_equal [:remove_index, [:table, [:one, :two]], nil], remove
|
210
|
-
end
|
203
|
+
assert_equal [:remove_index, [:table, [:one, :two]], nil], remove
|
211
204
|
end
|
212
205
|
|
213
206
|
def test_invert_add_index_with_name
|
214
207
|
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
|
215
|
-
|
216
|
-
assert_equal [:remove_index, [:table, { name: "new_index" }]], remove
|
217
|
-
else
|
218
|
-
assert_equal [:remove_index, [:table, [:one, :two], {:name=>"new_index"}], nil], remove
|
219
|
-
end
|
208
|
+
assert_equal [:remove_index, [:table, [:one, :two], {:name=>"new_index"}], nil], remove
|
220
209
|
end
|
221
210
|
|
222
211
|
def test_invert_remove_index
|
@@ -226,11 +215,7 @@ module ActiveRecord
|
|
226
215
|
|
227
216
|
def test_invert_remove_index_with_options
|
228
217
|
add = @recorder.inverse_of :remove_index, [:table, { column: :one }]
|
229
|
-
|
230
|
-
assert_equal [:add_index, [:table, :one, {} ]], add
|
231
|
-
else
|
232
|
-
assert_equal [:add_index, [:table, :one ]], add
|
233
|
-
end
|
218
|
+
assert_equal [:add_index, [:table, :one ]], add
|
234
219
|
end
|
235
220
|
|
236
221
|
def test_invert_remove_index_with_positional_column
|
@@ -250,11 +235,7 @@ module ActiveRecord
|
|
250
235
|
|
251
236
|
def test_invert_remove_index_with_no_special_options
|
252
237
|
add = @recorder.inverse_of :remove_index, [:table, { column: [:one, :two] }]
|
253
|
-
|
254
|
-
assert_equal [:add_index, [:table, [:one, :two], {}]], add
|
255
|
-
else
|
256
|
-
assert_equal [:add_index, [:table, [:one, :two]]], add
|
257
|
-
end
|
238
|
+
assert_equal [:add_index, [:table, [:one, :two]]], add
|
258
239
|
end
|
259
240
|
|
260
241
|
def test_invert_remove_index_with_no_column
|
@@ -315,11 +296,7 @@ module ActiveRecord
|
|
315
296
|
|
316
297
|
def test_invert_add_foreign_key
|
317
298
|
enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people]
|
318
|
-
|
319
|
-
assert_equal [:remove_foreign_key, [:dogs, :people]], enable
|
320
|
-
else
|
321
|
-
assert_equal [:remove_foreign_key, [:dogs, :people], nil], enable
|
322
|
-
end
|
299
|
+
assert_equal [:remove_foreign_key, [:dogs, :people], nil], enable
|
323
300
|
end
|
324
301
|
|
325
302
|
def test_invert_remove_foreign_key
|
@@ -329,11 +306,7 @@ module ActiveRecord
|
|
329
306
|
|
330
307
|
def test_invert_add_foreign_key_with_column
|
331
308
|
enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id"]
|
332
|
-
|
333
|
-
assert_equal [:remove_foreign_key, [:dogs, column: "owner_id"]], enable
|
334
|
-
else
|
335
|
-
assert_equal [:remove_foreign_key, [:dogs, :people, column: "owner_id"], nil], enable
|
336
|
-
end
|
309
|
+
assert_equal [:remove_foreign_key, [:dogs, :people, column: "owner_id"], nil], enable
|
337
310
|
end
|
338
311
|
|
339
312
|
def test_invert_remove_foreign_key_with_column
|
@@ -343,11 +316,7 @@ module ActiveRecord
|
|
343
316
|
|
344
317
|
def test_invert_add_foreign_key_with_column_and_name
|
345
318
|
enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"]
|
346
|
-
|
347
|
-
assert_equal [:remove_foreign_key, [:dogs, { name: "fk" }]], enable
|
348
|
-
else
|
349
|
-
assert_equal [:remove_foreign_key, [:dogs, :people, { column: "owner_id", name: "fk" }], nil], enable
|
350
|
-
end
|
319
|
+
assert_equal [:remove_foreign_key, [:dogs, :people, { column: "owner_id", name: "fk" }], nil], enable
|
351
320
|
end
|
352
321
|
|
353
322
|
def test_invert_remove_foreign_key_with_column_and_name
|
@@ -76,12 +76,7 @@ module ActiveRecord
|
|
76
76
|
t.references :foo, polymorphic: true, index: true
|
77
77
|
end
|
78
78
|
end
|
79
|
-
|
80
|
-
if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
|
81
|
-
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id)
|
82
|
-
else
|
83
|
-
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo)
|
84
|
-
end
|
79
|
+
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo)
|
85
80
|
end
|
86
81
|
|
87
82
|
def test_creates_index_for_existing_table
|
@@ -124,11 +119,7 @@ module ActiveRecord
|
|
124
119
|
end
|
125
120
|
end
|
126
121
|
|
127
|
-
|
128
|
-
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo_type_and_foo_id)
|
129
|
-
else
|
130
|
-
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo)
|
131
|
-
end
|
122
|
+
assert connection.index_exists?(table_name, [:foo_type, :foo_id], name: :index_testings_on_foo)
|
132
123
|
end
|
133
124
|
end
|
134
125
|
end
|
@@ -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
|
@@ -31,7 +31,7 @@ module Models
|
|
31
31
|
t.column :col_timestamp, :datetime, default: default.col_timestamp
|
32
32
|
end
|
33
33
|
|
34
|
-
item = LiteralValue.new
|
34
|
+
item = LiteralValue.new id: connection.next_sequence_value(nil)
|
35
35
|
default.each_pair { |col, expected| assert_equal(expected, item[col]) }
|
36
36
|
item.save!
|
37
37
|
default.each_pair { |col, expected| assert_equal(expected, item[col]) }
|
@@ -45,7 +45,7 @@ module Models
|
|
45
45
|
t.column :col_timestamp, :datetime, default: -> { "CURRENT_TIMESTAMP()" }
|
46
46
|
end
|
47
47
|
|
48
|
-
item = ExpressionValue.create!
|
48
|
+
item = ExpressionValue.create! id: connection.next_sequence_value(nil)
|
49
49
|
item.reload
|
50
50
|
assert_equal(BigDecimal("1.23"), item.col_numeric)
|
51
51
|
assert(item.col_timestamp)
|