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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +4 -6
  3. data/.github/workflows/ci.yaml +4 -6
  4. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -6
  5. data/.github/workflows/nightly-unit-tests.yaml +4 -6
  6. data/.github/workflows/rubocop.yaml +1 -1
  7. data/.github/workflows/samples.yaml +30 -0
  8. data/.kokoro/release.cfg +2 -2
  9. data/.kokoro/release.sh +1 -4
  10. data/.release-please-manifest.json +1 -1
  11. data/.rubocop.yml +2 -2
  12. data/CHANGELOG.md +28 -0
  13. data/Gemfile +6 -5
  14. data/README.md +26 -22
  15. data/acceptance/cases/migration/command_recorder_test.rb +7 -38
  16. data/acceptance/cases/migration/references_index_test.rb +2 -11
  17. data/acceptance/cases/models/binary_identifiers.rb +97 -0
  18. data/acceptance/cases/models/default_value_test.rb +2 -2
  19. data/acceptance/cases/tasks/database_tasks_test.rb +71 -74
  20. data/acceptance/cases/transactions/read_write_transactions_test.rb +10 -4
  21. data/acceptance/models/binary_project.rb +20 -0
  22. data/acceptance/models/string_io.rb +28 -0
  23. data/acceptance/models/user.rb +20 -0
  24. data/acceptance/test_helper.rb +22 -8
  25. data/activerecord-spanner-adapter.gemspec +3 -3
  26. data/benchmarks/application.rb +3 -7
  27. data/examples/snippets/Rakefile +28 -5
  28. data/examples/snippets/array-data-type/application.rb +1 -5
  29. data/examples/snippets/array-data-type/config/database.yml +1 -0
  30. data/examples/snippets/auto-generated-primary-key/README.md +140 -0
  31. data/examples/snippets/auto-generated-primary-key/Rakefile +13 -0
  32. data/examples/snippets/auto-generated-primary-key/application.rb +86 -0
  33. data/examples/snippets/auto-generated-primary-key/config/database.yml +10 -0
  34. data/examples/snippets/auto-generated-primary-key/db/migrate/01_create_tables.rb +29 -0
  35. data/examples/snippets/auto-generated-primary-key/db/seeds.rb +31 -0
  36. data/examples/snippets/auto-generated-primary-key/models/album.rb +11 -0
  37. data/examples/snippets/auto-generated-primary-key/models/singer.rb +11 -0
  38. data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
  39. data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
  40. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
  41. data/examples/snippets/bulk-insert/application.rb +1 -5
  42. data/examples/snippets/bulk-insert/config/database.yml +1 -0
  43. data/examples/snippets/commit-timestamp/application.rb +0 -4
  44. data/examples/snippets/commit-timestamp/config/database.yml +1 -0
  45. data/examples/snippets/config/environment.rb +5 -0
  46. data/examples/snippets/create-records/application.rb +1 -5
  47. data/examples/snippets/create-records/config/database.yml +1 -0
  48. data/examples/snippets/date-data-type/application.rb +1 -5
  49. data/examples/snippets/date-data-type/config/database.yml +1 -0
  50. data/examples/snippets/date-data-type/db/seeds.rb +1 -1
  51. data/examples/snippets/generated-column/application.rb +0 -4
  52. data/examples/snippets/generated-column/config/database.yml +1 -0
  53. data/examples/snippets/generated-column/db/seeds.rb +1 -1
  54. data/examples/snippets/hints/application.rb +0 -4
  55. data/examples/snippets/hints/config/database.yml +1 -0
  56. data/examples/snippets/hints/db/seeds.rb +1 -1
  57. data/examples/snippets/interleaved-tables/application.rb +1 -5
  58. data/examples/snippets/interleaved-tables/config/database.yml +1 -0
  59. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  60. data/examples/snippets/interleaved-tables/models/album.rb +6 -2
  61. data/examples/snippets/interleaved-tables/models/track.rb +5 -1
  62. data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
  63. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
  64. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
  65. data/examples/snippets/migrations/application.rb +0 -4
  66. data/examples/snippets/migrations/config/database.yml +1 -0
  67. data/examples/snippets/mutations/application.rb +1 -5
  68. data/examples/snippets/mutations/config/database.yml +1 -0
  69. data/examples/snippets/mutations/db/seeds.rb +1 -1
  70. data/examples/snippets/optimistic-locking/application.rb +0 -4
  71. data/examples/snippets/optimistic-locking/config/database.yml +1 -0
  72. data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
  73. data/examples/snippets/partitioned-dml/application.rb +0 -4
  74. data/examples/snippets/partitioned-dml/config/database.yml +1 -0
  75. data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
  76. data/examples/snippets/query-logs/application.rb +15 -13
  77. data/examples/snippets/query-logs/config/database.yml +1 -0
  78. data/examples/snippets/query-logs/db/seeds.rb +1 -1
  79. data/examples/snippets/quickstart/application.rb +0 -4
  80. data/examples/snippets/quickstart/config/database.yml +1 -0
  81. data/examples/snippets/quickstart/db/seeds.rb +1 -1
  82. data/examples/snippets/read-only-transactions/application.rb +0 -4
  83. data/examples/snippets/read-only-transactions/config/database.yml +1 -0
  84. data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
  85. data/examples/snippets/read-write-transactions/application.rb +2 -6
  86. data/examples/snippets/read-write-transactions/config/database.yml +1 -0
  87. data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
  88. data/examples/snippets/stale-reads/application.rb +0 -4
  89. data/examples/snippets/stale-reads/config/database.yml +1 -0
  90. data/examples/snippets/stale-reads/db/seeds.rb +1 -1
  91. data/examples/snippets/tags/application.rb +0 -4
  92. data/examples/snippets/tags/config/database.yml +1 -0
  93. data/examples/snippets/tags/db/seeds.rb +1 -1
  94. data/examples/snippets/timestamp-data-type/application.rb +0 -4
  95. data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
  96. data/lib/active_record/connection_adapters/spanner/column.rb +7 -3
  97. data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
  98. data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
  99. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +20 -11
  100. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
  101. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +22 -51
  102. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +10 -8
  103. data/lib/active_record/connection_adapters/spanner_adapter.rb +42 -7
  104. data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
  105. data/lib/active_record/type/spanner/array.rb +4 -0
  106. data/lib/active_record/type/spanner/bytes.rb +10 -0
  107. data/lib/activerecord_spanner_adapter/base.rb +59 -32
  108. data/lib/activerecord_spanner_adapter/connection.rb +9 -5
  109. data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
  110. data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
  111. data/lib/activerecord_spanner_adapter/index.rb +10 -2
  112. data/lib/activerecord_spanner_adapter/information_schema.rb +5 -3
  113. data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
  114. data/lib/activerecord_spanner_adapter/table/column.rb +16 -4
  115. data/lib/activerecord_spanner_adapter/table.rb +8 -2
  116. data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
  117. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  118. data/lib/arel/visitors/spanner.rb +16 -11
  119. data/lib/spanner_client_ext.rb +4 -3
  120. metadata +23 -34
  121. data/examples/snippets/array-data-type/db/schema.rb +0 -31
  122. data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
  123. data/examples/snippets/bulk-insert/db/schema.rb +0 -31
  124. data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
  125. data/examples/snippets/create-records/db/schema.rb +0 -31
  126. data/examples/snippets/date-data-type/db/schema.rb +0 -26
  127. data/examples/snippets/generated-column/db/schema.rb +0 -26
  128. data/examples/snippets/hints/db/schema.rb +0 -33
  129. data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
  130. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
  131. data/examples/snippets/migrations/db/schema.rb +0 -38
  132. data/examples/snippets/mutations/db/schema.rb +0 -32
  133. data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
  134. data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
  135. data/examples/snippets/query-logs/db/schema.rb +0 -31
  136. data/examples/snippets/quickstart/db/schema.rb +0 -31
  137. data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
  138. data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
  139. data/examples/snippets/stale-reads/db/schema.rb +0 -31
  140. data/examples/snippets/tags/db/schema.rb +0 -31
  141. 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: e521009bd7ee056c90e4147edd3e6dfe012e7808abf8dcf2b9a0145f380d669d
4
- data.tar.gz: 9be31f9f418a9ca5f80531924bc299a75c64826d09989c7318aff88eaccf8a41
3
+ metadata.gz: 539c47de17139481981cfaaaf2b65d43d8f0b27642ad68663658123d0053a8fb
4
+ data.tar.gz: c4eb45d075689d08da773ebc11bd1e24df9c6cb7b6ab79a2a352c5c4f4442fbf
5
5
  SHA512:
6
- metadata.gz: a529d5691fdcecc54e6a090a807c5b5ff561aa2a355d268a605c91b54705a9a093544129da76df2bb5341953a55dfac792be77079aa1b63121260eb8ed9559c3
7
- data.tar.gz: 0f6be5f7de6113d16ce2f5e0474924ee4af6bdf3599c84050382f5c47a64f24876b656522e57ebb1e35742e6154221174b0a1e7487fa82034a5a4a5acfa527c2
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: ["2.7", "3.0", "3.1", "3.2", "3.3"]
22
- ar: ["~> 6.1.0", "~> 7.0.0", "~> 7.1.0", "~> 7.2.0"]
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: "2.7"
26
- ar: "~> 7.2.0"
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:
@@ -10,14 +10,12 @@ jobs:
10
10
  strategy:
11
11
  max-parallel: 4
12
12
  matrix:
13
- ruby: ["2.7", "3.0", "3.1", "3.2", "3.3"]
14
- ar: ["~> 6.1.0", "~> 7.0.0", "~> 7.1.0", "~> 7.2.0"]
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: "2.7"
18
- ar: "~> 7.2.0"
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: ["2.7", "3.0", "3.1", "3.2", "3.3"]
22
- ar: ["~> 6.1.0", "~> 7.0.0", "~> 7.1.0", "~> 7.2.0"]
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: "2.7"
26
- ar: "~> 7.2.0"
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: ["2.7", "3.0", "3.1", "3.2", "3.3"]
15
- ar: ["~> 6.1.0", "~> 7.0.0", "~> 7.1.0", "~> 7.2.0"]
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: "2.7"
19
- ar: "~> 7.2.0"
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:
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: setup ruby
17
17
  uses: ruby/setup-ruby@v1
18
18
  with:
19
- ruby-version: '2.7'
19
+ ruby-version: '3.3'
20
20
  - name: cache gems
21
21
  uses: actions/cache@v4
22
22
  with:
@@ -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-multi"
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,docuploader_service_account"
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
- gem install --no-document toys
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
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.8.0"
2
+ ".": "2.1.0"
3
3
  }
data/.rubocop.yml CHANGED
@@ -16,7 +16,7 @@ AllCops:
16
16
 
17
17
  Documentation:
18
18
  Enabled: false
19
- Layout/AlignHash:
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/UncommunicativeMethodParamName:
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
- gem "activerecord", ENV.fetch("AR_VERSION", "~> 7.1.0")
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.13.0"
11
- gem "pry-byebug", "~> 3.9.0"
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', '~> 1.4'
14
+ gem 'sqlite3'
14
15
 
15
16
  # Required for samples and testing.
16
- install_if -> { ENV.fetch("AR_VERSION", "~> 7.1.0").dup.to_s.sub("~>", "").strip < "7.1.0" && !ENV["SKIP_COMPOSITE_PK"] } do
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
  ![rubocop](https://github.com/googleapis/ruby-spanner-activerecord/workflows/rubocop/badge.svg)
6
6
 
7
- __This adapter only supports GoogleSQL-dialect Cloud Spanner databases. PostgreSQL-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 Cloud Spanner PostgreSQL-dialect databases.__
10
+ for Spanner PostgreSQL-dialect databases.__
11
11
 
12
- This project provides a Cloud Spanner adapter for ActiveRecord. It supports the following versions:
12
+ This project provides a Spanner adapter for ActiveRecord. It supports the following versions:
13
13
 
14
- - ActiveRecord 6.0.x with Ruby 2.7.
15
- - ActiveRecord 6.1.x with Ruby 2.7 and higher.
16
- - ActiveRecord 7.0.x with Ruby 2.7 and higher.
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 Cloud Spanner can take a long time. It is therefore
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 Cloud Spanner under the directory [examples/snippets](examples/snippets). Each example is directly runnable without the need to setup a Cloud Spanner
66
- database, as all samples will automatically start a Cloud Spanner emulator in a Docker container and execute the sample
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 Cloud Spanner.
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 Cloud Spanner.
82
- - [read-only-transactions](examples/snippets/read-only-transactions): Shows how to execute read-only transactions on Cloud Spanner.
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 Cloud Spanner database. Mutations can have a significant performance
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 | Comment | Resolution |
93
- |-----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
94
- | Interleaved tables require composite primary keys | Cloud 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. |
95
- | Lack of sequential IDs | Cloud Spanner uses either using bit-reversed sequences or UUID4 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 UUID4s or bit-reversed sequences to automatically generate primary keys. |
96
- | Table without Primary Key | Cloud Spanner support does not support tables without a primary key. | Always define a primary key for your table. |
97
- | Table names CANNOT have spaces within them whether back-ticked or not | Cloud Spanner DOES NOT support tables with spaces in them for example `Entity ID` | Ensure that your table names don't contain spaces. |
98
- | Table names CANNOT have punctuation marks and MUST contain valid UTF-8 | Cloud Spanner DOES NOT support punctuation marks e.g. periods ".", question marks "?" in table names | Ensure that your table names don't contain punctuation marks. |
99
- | Index with fields length [add_index](https://apidock.com/rails/v5.2.3/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index) | Cloud Spanner does not support index with fields length | Ensure that your database definition does not include index definitions with field lengths. |
100
- | Only GoogleSQL-dialect databases | Cloud Spanner supports both GoogleSQL- and PostgreSQL-dialect databases. This adapter only supports GoogleSQL-dialect databases. | Ensure that your database uses the GoogleSQL dialect. |
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
- if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
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
- if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
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
- if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
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
- if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
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
- if ActiveRecord.gem_version < VERSION_6_0_3
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
- if ActiveRecord.gem_version < VERSION_6_0_3
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
- if ActiveRecord.gem_version < VERSION_6_0_3
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
- if ActiveRecord::gem_version < Gem::Version.create('6.1.0')
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)