activerecord-spanner-adapter 1.8.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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