activerecord-spanner-adapter 1.8.0 → 2.0.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 (129) 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.sh +1 -3
  9. data/.release-please-manifest.json +1 -1
  10. data/.rubocop.yml +2 -2
  11. data/CHANGELOG.md +18 -0
  12. data/Gemfile +6 -5
  13. data/README.md +11 -9
  14. data/acceptance/cases/migration/command_recorder_test.rb +7 -38
  15. data/acceptance/cases/migration/references_index_test.rb +2 -11
  16. data/acceptance/cases/models/binary_identifiers.rb +97 -0
  17. data/acceptance/models/binary_project.rb +20 -0
  18. data/acceptance/models/string_io.rb +28 -0
  19. data/acceptance/models/user.rb +20 -0
  20. data/acceptance/test_helper.rb +1 -0
  21. data/activerecord-spanner-adapter.gemspec +3 -3
  22. data/benchmarks/application.rb +3 -7
  23. data/examples/snippets/Rakefile +27 -5
  24. data/examples/snippets/array-data-type/application.rb +1 -5
  25. data/examples/snippets/array-data-type/config/database.yml +1 -0
  26. data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
  27. data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
  28. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
  29. data/examples/snippets/bulk-insert/application.rb +1 -5
  30. data/examples/snippets/bulk-insert/config/database.yml +1 -0
  31. data/examples/snippets/commit-timestamp/application.rb +0 -4
  32. data/examples/snippets/commit-timestamp/config/database.yml +1 -0
  33. data/examples/snippets/config/environment.rb +5 -0
  34. data/examples/snippets/create-records/application.rb +1 -5
  35. data/examples/snippets/create-records/config/database.yml +1 -0
  36. data/examples/snippets/date-data-type/application.rb +1 -5
  37. data/examples/snippets/date-data-type/config/database.yml +1 -0
  38. data/examples/snippets/date-data-type/db/seeds.rb +1 -1
  39. data/examples/snippets/generated-column/application.rb +0 -4
  40. data/examples/snippets/generated-column/config/database.yml +1 -0
  41. data/examples/snippets/generated-column/db/seeds.rb +1 -1
  42. data/examples/snippets/hints/application.rb +0 -4
  43. data/examples/snippets/hints/config/database.yml +1 -0
  44. data/examples/snippets/hints/db/seeds.rb +1 -1
  45. data/examples/snippets/interleaved-tables/application.rb +1 -5
  46. data/examples/snippets/interleaved-tables/config/database.yml +1 -0
  47. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  48. data/examples/snippets/interleaved-tables/models/album.rb +6 -2
  49. data/examples/snippets/interleaved-tables/models/track.rb +5 -1
  50. data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
  51. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
  52. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
  53. data/examples/snippets/migrations/application.rb +0 -4
  54. data/examples/snippets/migrations/config/database.yml +1 -0
  55. data/examples/snippets/mutations/application.rb +1 -5
  56. data/examples/snippets/mutations/config/database.yml +1 -0
  57. data/examples/snippets/mutations/db/seeds.rb +1 -1
  58. data/examples/snippets/optimistic-locking/application.rb +0 -4
  59. data/examples/snippets/optimistic-locking/config/database.yml +1 -0
  60. data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
  61. data/examples/snippets/partitioned-dml/application.rb +0 -4
  62. data/examples/snippets/partitioned-dml/config/database.yml +1 -0
  63. data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
  64. data/examples/snippets/query-logs/application.rb +15 -13
  65. data/examples/snippets/query-logs/config/database.yml +1 -0
  66. data/examples/snippets/query-logs/db/seeds.rb +1 -1
  67. data/examples/snippets/quickstart/application.rb +0 -4
  68. data/examples/snippets/quickstart/config/database.yml +1 -0
  69. data/examples/snippets/quickstart/db/seeds.rb +1 -1
  70. data/examples/snippets/read-only-transactions/application.rb +0 -4
  71. data/examples/snippets/read-only-transactions/config/database.yml +1 -0
  72. data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
  73. data/examples/snippets/read-write-transactions/application.rb +2 -6
  74. data/examples/snippets/read-write-transactions/config/database.yml +1 -0
  75. data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
  76. data/examples/snippets/stale-reads/application.rb +0 -4
  77. data/examples/snippets/stale-reads/config/database.yml +1 -0
  78. data/examples/snippets/stale-reads/db/seeds.rb +1 -1
  79. data/examples/snippets/tags/application.rb +0 -4
  80. data/examples/snippets/tags/config/database.yml +1 -0
  81. data/examples/snippets/tags/db/seeds.rb +1 -1
  82. data/examples/snippets/timestamp-data-type/application.rb +0 -4
  83. data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
  84. data/lib/active_record/connection_adapters/spanner/column.rb +3 -3
  85. data/lib/active_record/connection_adapters/spanner/database_statements.rb +34 -22
  86. data/lib/active_record/connection_adapters/spanner/quoting.rb +2 -1
  87. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -9
  88. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
  89. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +17 -46
  90. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
  91. data/lib/active_record/connection_adapters/spanner_adapter.rb +20 -7
  92. data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
  93. data/lib/active_record/type/spanner/array.rb +4 -0
  94. data/lib/active_record/type/spanner/bytes.rb +10 -0
  95. data/lib/activerecord_spanner_adapter/base.rb +12 -18
  96. data/lib/activerecord_spanner_adapter/connection.rb +9 -5
  97. data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
  98. data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
  99. data/lib/activerecord_spanner_adapter/index.rb +10 -2
  100. data/lib/activerecord_spanner_adapter/information_schema.rb +1 -1
  101. data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
  102. data/lib/activerecord_spanner_adapter/table/column.rb +12 -3
  103. data/lib/activerecord_spanner_adapter/table.rb +8 -2
  104. data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
  105. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  106. data/lib/arel/visitors/spanner.rb +16 -11
  107. data/lib/spanner_client_ext.rb +4 -3
  108. metadata +13 -32
  109. data/examples/snippets/array-data-type/db/schema.rb +0 -31
  110. data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
  111. data/examples/snippets/bulk-insert/db/schema.rb +0 -31
  112. data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
  113. data/examples/snippets/create-records/db/schema.rb +0 -31
  114. data/examples/snippets/date-data-type/db/schema.rb +0 -26
  115. data/examples/snippets/generated-column/db/schema.rb +0 -26
  116. data/examples/snippets/hints/db/schema.rb +0 -33
  117. data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
  118. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
  119. data/examples/snippets/migrations/db/schema.rb +0 -38
  120. data/examples/snippets/mutations/db/schema.rb +0 -32
  121. data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
  122. data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
  123. data/examples/snippets/query-logs/db/schema.rb +0 -31
  124. data/examples/snippets/quickstart/db/schema.rb +0 -31
  125. data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
  126. data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
  127. data/examples/snippets/stale-reads/db/schema.rb +0 -31
  128. data/examples/snippets/tags/db/schema.rb +0 -31
  129. 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: df28341210b0da8f86475dc621e5c4113ad00a72b23fa6bc3e224a0064c7e267
4
+ data.tar.gz: 00eed36443cb2cd7c99869a6e2ff3821c16fd2a16a3d937dd831c3226743ef19
5
5
  SHA512:
6
- metadata.gz: a529d5691fdcecc54e6a090a807c5b5ff561aa2a355d268a605c91b54705a9a093544129da76df2bb5341953a55dfac792be77079aa1b63121260eb8ed9559c3
7
- data.tar.gz: 0f6be5f7de6113d16ce2f5e0474924ee4af6bdf3599c84050382f5c47a64f24876b656522e57ebb1e35742e6154221174b0a1e7487fa82034a5a4a5acfa527c2
6
+ metadata.gz: a53ee5f7ab4d576a969631ce6125557a6171f91dd372f71c03c34c62b67b9363e0898d4b8d7ff3836d58efd73fd2a71dbf108668c5e837634864e017d129b8ca
7
+ data.tar.gz: 665f4e837ee3dee167c2d5e4160dfe03fb3008903f3be50dbae50dd07fcea8b01b7b561b683c825c460a3462d0f729c5ace143c4285d9eb05b2871776086cd94
@@ -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.sh CHANGED
@@ -8,6 +8,4 @@ export GEM_HOME=$HOME/.gem
8
8
  export PATH=$GEM_HOME/bin:$PATH
9
9
 
10
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
11
+ toys release perform -v --reporter-org=googleapis --enable-docs --force-republish --enable-docs --enable-rad < /dev/null
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.8.0"
2
+ ".": "2.0.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ### 2.0.0 (2025-01-23)
4
+
5
+ ### ⚠ BREAKING CHANGES
6
+
7
+ * drop support for Rails 6.1 ([#346](https://github.com/googleapis/ruby-spanner-activerecord/issues/346))
8
+ * deserialize BYTES to StringIO ([#343](https://github.com/googleapis/ruby-spanner-activerecord/issues/343))
9
+
10
+ #### Features
11
+
12
+ * drop support for Rails 6.1 ([#346](https://github.com/googleapis/ruby-spanner-activerecord/issues/346))
13
+ * support Rails 8.0 ([#331](https://github.com/googleapis/ruby-spanner-activerecord/issues/331))
14
+ #### Bug Fixes
15
+
16
+ * deserialize BYTES to StringIO ([#343](https://github.com/googleapis/ruby-spanner-activerecord/issues/343))
17
+ #### Documentation
18
+
19
+ * add rails dbconsole to list of limitations ([#224](https://github.com/googleapis/ruby-spanner-activerecord/issues/224))
20
+
3
21
  ### 1.8.0 (2024-12-12)
4
22
 
5
23
  #### 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
@@ -89,15 +89,17 @@ Some noteworthy examples in the snippets directory:
89
89
 
90
90
  ## Limitations
91
91
 
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. |
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. 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. | |
101
+ | `rails dbconsole` is not supported. | The `rails dbconsole` is not supported for Spanner databases. | |
102
+
101
103
 
102
104
  ## Contributing
103
105
 
@@ -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
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require_relative 'string_io'
10
+
11
+ class BinaryProject < ActiveRecord::Base
12
+ belongs_to :owner, class_name: 'User'
13
+
14
+ before_create :set_uuid
15
+ private
16
+
17
+ def set_uuid
18
+ self.id ||= StringIO.new(SecureRandom.random_bytes(16))
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ # frozen_string_literal: true
8
+
9
+ # Add equality and hash functions to StringIO to use it for sets.
10
+ class StringIO
11
+ def ==(o)
12
+ o.class == self.class && self.to_base64 == o.to_base64
13
+ end
14
+
15
+ def eql?(o)
16
+ self == o
17
+ end
18
+
19
+ def hash
20
+ to_base64.hash
21
+ end
22
+
23
+ def to_base64
24
+ self.rewind
25
+ value = self.read
26
+ Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require_relative 'string_io'
10
+
11
+ class User < ActiveRecord::Base
12
+ has_many :binary_projects, foreign_key: :owner_id
13
+
14
+ before_create :set_uuid
15
+ private
16
+
17
+ def set_uuid
18
+ self.id ||= StringIO.new(SecureRandom.random_bytes(16))
19
+ end
20
+ end
@@ -5,6 +5,7 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  gem "minitest"
8
+ require "logger" # https://github.com/rails/rails/issues/54260
8
9
  require "minitest/autorun"
9
10
  require "minitest/focus"
10
11
  require "minitest/rg"
@@ -22,16 +22,16 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename f }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = ">= 2.7"
25
+ spec.required_ruby_version = ">= 3.1"
26
26
 
27
27
  spec.add_dependency "google-cloud-spanner", "~> 2.18"
28
28
  # Pin gRPC to 1.64.3, as 1.65 and 1.66 cause test runs to hang randomly.
29
29
  spec.add_dependency "grpc", "1.64.3"
30
- spec.add_runtime_dependency "activerecord", [">= 6.1", "< 7.3"]
30
+ spec.add_runtime_dependency "activerecord", [">= 6.1", "< 9"]
31
31
 
32
32
  spec.add_development_dependency "autotest-suffix", "~> 1.1"
33
33
  spec.add_development_dependency "bundler", "~> 2.0"
34
- spec.add_development_dependency "google-style", "~> 1.24.0"
34
+ spec.add_development_dependency "google-style", "~> 1.30.1"
35
35
  spec.add_development_dependency "minitest", "~> 5.10"
36
36
  spec.add_development_dependency "minitest-autotest", "~> 1.0"
37
37
  spec.add_development_dependency "minitest-focus", "~> 1.1"
@@ -63,7 +63,7 @@ class Application
63
63
 
64
64
  puts ""
65
65
  puts "Press any key to end the application"
66
- STDIN.getch
66
+ $stdin.getch
67
67
  end
68
68
 
69
69
  def self.execute_individual_benchmarks singer, client
@@ -120,9 +120,7 @@ class Application
120
120
  sql = "SELECT * FROM Singers WHERE id=@id"
121
121
  params = { id: singer[:id] }
122
122
  param_types = { id: :INT64 }
123
- client.execute(sql, params: params, types: param_types).rows.each do |row|
124
- return row
125
- end
123
+ client.execute(sql, params: params, types: param_types).rows.first(1)
126
124
  else
127
125
  Singer.find singer.id
128
126
  end
@@ -134,9 +132,7 @@ class Application
134
132
  sql = "SELECT * FROM Singers WHERE id=@id"
135
133
  params = { id: singer[:id] }
136
134
  param_types = { id: :INT64 }
137
- client.execute(sql, params: params, types: param_types).rows.each do |row|
138
- return row
139
- end
135
+ client.execute(sql, params: params, types: param_types).rows.first(1)
140
136
  else
141
137
  singer.reload
142
138
  end