activerecord-spanner-adapter 1.6.2 → 1.8.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +5 -7
  3. data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
  4. data/.github/workflows/ci.yaml +5 -7
  5. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +6 -33
  6. data/.github/workflows/nightly-acceptance-tests-on-production.yaml +1 -1
  7. data/.github/workflows/nightly-unit-tests.yaml +7 -33
  8. data/.kokoro/populate-secrets.sh +5 -1
  9. data/.kokoro/release.cfg +22 -12
  10. data/.kokoro/trampoline_v2.sh +19 -11
  11. data/.release-please-manifest.json +1 -1
  12. data/.trampolinerc +6 -1
  13. data/CHANGELOG.md +28 -0
  14. data/Gemfile +5 -4
  15. data/README.md +1 -0
  16. data/Rakefile +2 -2
  17. data/acceptance/cases/migration/schema_dumper_test.rb +33 -7
  18. data/acceptance/cases/models/insert_all_test.rb +22 -7
  19. data/acceptance/cases/sessions/session_not_found_test.rb +2 -0
  20. data/acceptance/cases/tasks/database_tasks_test.rb +5 -0
  21. data/acceptance/schema/schema.rb +1 -0
  22. data/acceptance/test_helper.rb +5 -1
  23. data/activerecord-spanner-adapter.gemspec +3 -1
  24. data/examples/snippets/bit-reversed-sequence/README.md +6 -0
  25. data/examples/snippets/bit-reversed-sequence/db/migrate/01_create_tables.rb +3 -6
  26. data/examples/snippets/bit-reversed-sequence/db/schema.rb +1 -1
  27. data/lib/active_record/connection_adapters/spanner/database_statements.rb +5 -3
  28. data/lib/active_record/connection_adapters/spanner/quoting.rb +17 -5
  29. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +3 -1
  30. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +11 -0
  31. data/lib/active_record/connection_adapters/spanner_adapter.rb +34 -20
  32. data/lib/activerecord-spanner-adapter.rb +5 -1
  33. data/lib/activerecord_spanner_adapter/base.rb +46 -12
  34. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  35. metadata +21 -9
  36. data/.github/workflows/release-please-label.yml +0 -25
  37. data/.github/workflows/release-please.yml +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2daeef21393399532473625549d008d1a4cfcf791be1a347c98909c3e3973dde
4
- data.tar.gz: 16caacb8cde7a9dfd2d2e22a8500d9a25b21f22fe392bb3c2631af892d35168a
3
+ metadata.gz: e521009bd7ee056c90e4147edd3e6dfe012e7808abf8dcf2b9a0145f380d669d
4
+ data.tar.gz: 9be31f9f418a9ca5f80531924bc299a75c64826d09989c7318aff88eaccf8a41
5
5
  SHA512:
6
- metadata.gz: da334e1c10da6bbd60c6ececee046889b33e045df31838b8a0939f56cf93240b44c20002aa2b61bbcb6b961907b1f1ed73aa0fc9de8b1283faef427db84fce94
7
- data.tar.gz: 52c05cfca494e311aa7049591a2258acf93880434d548834667742440ba9a7ddc283183dedb16227fc5b469fbc2b1cd50ed333c30f86e6f2fda0bf3ae1473e72
6
+ metadata.gz: a529d5691fdcecc54e6a090a807c5b5ff561aa2a355d268a605c91b54705a9a093544129da76df2bb5341953a55dfac792be77079aa1b63121260eb8ed9559c3
7
+ data.tar.gz: 0f6be5f7de6113d16ce2f5e0474924ee4af6bdf3599c84050382f5c47a64f24876b656522e57ebb1e35742e6154221174b0a1e7487fa82034a5a4a5acfa527c2
@@ -18,16 +18,14 @@ jobs:
18
18
  strategy:
19
19
  max-parallel: 4
20
20
  matrix:
21
- ruby: ["2.7", "3.0", "3.1", "3.2"]
22
- ar: ["~> 6.0.6", "~> 6.1.7", "~> 7.0.4", "~> 7.1.0"]
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"]
23
23
  # Exclude combinations that are not supported.
24
24
  exclude:
25
+ - ruby: "2.7"
26
+ ar: "~> 7.2.0"
25
27
  - ruby: "3.0"
26
- ar: "~> 6.0.6"
27
- - ruby: "3.1"
28
- ar: "~> 6.0.6"
29
- - ruby: "3.2"
30
- ar: "~> 6.0.6"
28
+ ar: "~> 7.2.0"
31
29
  env:
32
30
  AR_VERSION: ${{ matrix.ar }}
33
31
  steps:
@@ -24,7 +24,7 @@ jobs:
24
24
  strategy:
25
25
  max-parallel: 4
26
26
  matrix:
27
- ruby: [3.0]
27
+ ruby: [3.3]
28
28
  steps:
29
29
  - uses: actions/checkout@v4
30
30
  - name: Set up Ruby
@@ -10,16 +10,14 @@ jobs:
10
10
  strategy:
11
11
  max-parallel: 4
12
12
  matrix:
13
- ruby: ["2.7", "3.0", "3.1", "3.2"]
14
- ar: ["~> 6.0.6", "~> 6.1.7", "~> 7.0.4", "~> 7.1.0"]
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"]
15
15
  # Exclude combinations that are not supported.
16
16
  exclude:
17
+ - ruby: "2.7"
18
+ ar: "~> 7.2.0"
17
19
  - ruby: "3.0"
18
- ar: "~> 6.0.6"
19
- - ruby: "3.1"
20
- ar: "~> 6.0.6"
21
- - ruby: "3.2"
22
- ar: "~> 6.0.6"
20
+ ar: "~> 7.2.0"
23
21
  env:
24
22
  AR_VERSION: ${{ matrix.ar }}
25
23
  steps:
@@ -18,41 +18,14 @@ jobs:
18
18
  strategy:
19
19
  max-parallel: 4
20
20
  matrix:
21
- # Run acceptance tests all supported combinations of Ruby and ActiveRecord.
22
- ruby: [2.7, 3.0, 3.1, 3.2]
23
- ar: [6.0.0, 6.0.1, 6.0.2.2, 6.0.3.7, 6.0.4, 6.1.3.2, 6.1.4.7, 6.1.5.1, 6.1.6.1, 7.0.2.4, 7.0.3.1, 7.0.4, 7.0.5, 7.0.6, 7.0.7, 7.1.0, 7.1.1, 7.1.2]
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"]
24
23
  # Exclude combinations that are not supported.
25
24
  exclude:
26
- - ruby: 3.0
27
- ar: 6.0.0
28
- - ruby: 3.0
29
- ar: 6.0.1
30
- - ruby: 3.0
31
- ar: 6.0.2.2
32
- - ruby: 3.0
33
- ar: 6.0.3.7
34
- - ruby: 3.0
35
- ar: 6.0.4
36
- - ruby: 3.1
37
- ar: 6.0.0
38
- - ruby: 3.1
39
- ar: 6.0.1
40
- - ruby: 3.1
41
- ar: 6.0.2.2
42
- - ruby: 3.1
43
- ar: 6.0.3.7
44
- - ruby: 3.1
45
- ar: 6.0.4
46
- - ruby: 3.2
47
- ar: 6.0.0
48
- - ruby: 3.2
49
- ar: 6.0.1
50
- - ruby: 3.2
51
- ar: 6.0.2.2
52
- - ruby: 3.2
53
- ar: 6.0.3.7
54
- - ruby: 3.2
55
- ar: 6.0.4
25
+ - ruby: "2.7"
26
+ ar: "~> 7.2.0"
27
+ - ruby: "3.0"
28
+ ar: "~> 7.2.0"
56
29
  env:
57
30
  AR_VERSION: ${{ matrix.ar }}
58
31
  steps:
@@ -10,7 +10,7 @@ jobs:
10
10
  strategy:
11
11
  max-parallel: 4
12
12
  matrix:
13
- ruby: [3.0]
13
+ ruby: [3.3]
14
14
  steps:
15
15
  - uses: actions/checkout@v4
16
16
  - name: Set up Ruby
@@ -10,41 +10,15 @@ jobs:
10
10
  strategy:
11
11
  max-parallel: 4
12
12
  matrix:
13
- # Run unit tests all supported combinations of Ruby and ActiveRecord.
14
- ruby: [2.7, 3.0, 3.1, 3.2]
15
- ar: [6.0.0, 6.0.1, 6.0.2.2, 6.0.3.7, 6.0.4, 6.1.3.2, 6.1.4.7, 6.1.5.1, 6.1.6.1, 7.0.2.4, 7.0.3.1, 7.0.4, 7.0.5, 7.1.0, 7.1.1, 7.1.2]
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"]
16
16
  # Exclude combinations that are not supported.
17
17
  exclude:
18
- - ruby: 3.0
19
- ar: 6.0.0
20
- - ruby: 3.0
21
- ar: 6.0.1
22
- - ruby: 3.0
23
- ar: 6.0.2.2
24
- - ruby: 3.0
25
- ar: 6.0.3.7
26
- - ruby: 3.0
27
- ar: 6.0.4
28
- - ruby: 3.1
29
- ar: 6.0.0
30
- - ruby: 3.1
31
- ar: 6.0.1
32
- - ruby: 3.1
33
- ar: 6.0.2.2
34
- - ruby: 3.1
35
- ar: 6.0.3.7
36
- - ruby: 3.1
37
- ar: 6.0.4
38
- - ruby: 3.2
39
- ar: 6.0.0
40
- - ruby: 3.2
41
- ar: 6.0.1
42
- - ruby: 3.2
43
- ar: 6.0.2.2
44
- - ruby: 3.2
45
- ar: 6.0.3.7
46
- - ruby: 3.2
47
- ar: 6.0.4
18
+ - ruby: "2.7"
19
+ ar: "~> 7.2.0"
20
+ - ruby: "3.0"
21
+ ar: "~> 7.2.0"
48
22
  env:
49
23
  AR_VERSION: ${{ matrix.ar }}
50
24
  steps:
@@ -24,6 +24,10 @@ function msg { println "$*" >&2 ;}
24
24
  function println { printf '%s\n' "$(now) $*" ;}
25
25
 
26
26
  # Populates requested secrets set in SECRET_MANAGER_KEYS
27
+ if [[ -z "${SECRET_MANAGER_PROJECT_ID-}" ]]; then
28
+ msg "SECRET_MANAGER_PROJECT_ID is not set in environment variables, using default"
29
+ SECRET_MANAGER_PROJECT_ID="cloud-devrel-kokoro-resources"
30
+ fi
27
31
 
28
32
  # In Kokoro CI builds, we use the service account attached to the
29
33
  # Kokoro VM. This means we need to setup auth on other CI systems.
@@ -65,7 +69,7 @@ do
65
69
  msg "Retrieving secret ${key}"
66
70
  "${GCLOUD_COMMANDS[@]}" \
67
71
  secrets versions access latest \
68
- --project cloud-devrel-kokoro-resources \
72
+ --project "${SECRET_MANAGER_PROJECT_ID}" \
69
73
  --secret $key > \
70
74
  "$SECRET_LOCATION/$key"
71
75
  if [[ $? == 0 ]]; then
data/.kokoro/release.cfg CHANGED
@@ -7,19 +7,13 @@ action {
7
7
  }
8
8
  }
9
9
 
10
- # Download resources for system tests (service account key, etc.)
11
- gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-ruby"
12
-
13
- # Download trampoline resources.
14
- gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
15
-
16
10
  # Use the trampoline script to run in docker.
17
11
  build_file: "ruby-spanner-activerecord/.kokoro/trampoline_v2.sh"
18
12
 
19
13
  # Configure the docker image for kokoro-trampoline.
20
14
  env_vars: {
21
15
  key: "TRAMPOLINE_IMAGE"
22
- value: "gcr.io/cloud-devrel-kokoro-resources/yoshi-ruby/release"
16
+ value: "us-central1-docker.pkg.dev/cloud-sdk-release-custom-pool/release-images/ruby-multi"
23
17
  }
24
18
 
25
19
  env_vars: {
@@ -27,16 +21,32 @@ env_vars: {
27
21
  value: ".kokoro/release.sh"
28
22
  }
29
23
 
24
+ env_vars: {
25
+ key: "SECRET_MANAGER_PROJECT_ID"
26
+ value: "cloud-sdk-release-custom-pool"
27
+ }
28
+
30
29
  env_vars: {
31
30
  key: "SECRET_MANAGER_KEYS"
32
31
  value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem,docuploader_service_account"
33
32
  }
34
33
 
34
+ # Pick up Rubygems key from internal keystore
35
+ before_action {
36
+ fetch_keystore {
37
+ keystore_resource {
38
+ keystore_config_id: 73713
39
+ keyname: "rubygems-publish-key"
40
+ backend: "blade:keystore-fastconfigpush"
41
+ }
42
+ }
43
+ }
44
+
35
45
  # Store the packages uploaded to rubygems.org, which
36
46
  # we can later use to generate SBOMs and attestations.
37
47
  action {
38
- define_artifacts {
39
- regex: "github/ruby-spanner-activerecord/pkg/*.gem"
40
- strip_prefix: "github"
41
- }
42
- }
48
+ define_artifacts {
49
+ regex: "github/ruby-spanner-activerecord/pkg/*.gem"
50
+ strip_prefix: "github"
51
+ }
52
+ }
@@ -138,18 +138,26 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then
138
138
  RUNNING_IN_CI="true"
139
139
  TRAMPOLINE_CI="kokoro"
140
140
  if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then
141
- if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then
142
- log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting."
143
- exit 1
144
- fi
145
- # This service account will be activated later.
146
- TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json"
141
+ if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then
142
+ log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting."
143
+ exit 1
144
+ fi
145
+ # This service account will be activated later.
146
+ TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json"
147
147
  else
148
- if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
149
- gcloud auth list
150
- fi
151
- log_yellow "Configuring Container Registry access"
152
- gcloud auth configure-docker --quiet
148
+ if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
149
+ gcloud auth list
150
+ fi
151
+ log_yellow "Configuring Container Registry access"
152
+ TRAMPOLINE_HOST=$(echo "${TRAMPOLINE_IMAGE}" | cut -d/ -f1)
153
+ if [[ ! "${TRAMPOLINE_HOST}" =~ "gcr.io" ]]; then
154
+ # If you need to specificy a host other than gcr.io, you have to run on an update version of gcloud.
155
+ echo "TRAMPOLINE_HOST: ${TRAMPOLINE_HOST}"
156
+ gcloud components update
157
+ gcloud auth configure-docker "${TRAMPOLINE_HOST}"
158
+ else
159
+ gcloud auth configure-docker --quiet
160
+ fi
153
161
  fi
154
162
  pass_down_envvars+=(
155
163
  # KOKORO dynamic variables.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.6.2"
2
+ ".": "1.8.0"
3
3
  }
data/.trampolinerc CHANGED
@@ -18,7 +18,12 @@ required_envvars+=(
18
18
 
19
19
  # Add env vars which are passed down into the container here.
20
20
  pass_down_envvars+=(
21
- "AUTORELEASE_PR" "RELEASE_DRY_RUN" "RELEASE_PACKAGE" "KOKORO_GIT_COMMIT" "RUBY_VERSIONS" "EXTRA_CI_ARGS"
21
+ "AUTORELEASE_PR"
22
+ "EXTRA_CI_ARGS"
23
+ "KOKORO_GIT_COMMIT"
24
+ "RELEASE_DRY_RUN"
25
+ "RELEASE_PACKAGE"
26
+ "RUBY_VERSIONS"
22
27
  )
23
28
 
24
29
  # Prevent unintentional override on the default image.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ### 1.8.0 (2024-12-12)
4
+
5
+ #### Features
6
+
7
+ * INSERT OR [IGNORE|UPDATE] ([#332](https://github.com/googleapis/ruby-spanner-activerecord/issues/332))
8
+ #### Bug Fixes
9
+
10
+ * Fixed incorrect argument handling. ([#333](https://github.com/googleapis/ruby-spanner-activerecord/issues/333))
11
+
12
+ ### 1.7.0 (2024-12-11)
13
+
14
+ #### Features
15
+
16
+ * support Rails 7.2.0 ([#328](https://github.com/googleapis/ruby-spanner-activerecord/issues/328))
17
+ #### Bug Fixes
18
+
19
+ * `SpannerAdapter` requires prepared statements to be enabled ([#323](https://github.com/googleapis/ruby-spanner-activerecord/issues/323))
20
+ * local emulator test ([#320](https://github.com/googleapis/ruby-spanner-activerecord/issues/320))
21
+
22
+ ### 1.6.3 (2024-08-31)
23
+
24
+ #### Bug Fixes
25
+
26
+ * a few Ruby DSL schema dump bug fixes ([#308](https://github.com/googleapis/ruby-spanner-activerecord/issues/308))
27
+ #### Documentation
28
+
29
+ * update bit-reversed sequence sample ([#303](https://github.com/googleapis/ruby-spanner-activerecord/issues/303))
30
+
3
31
  ### 1.6.2 (2024-02-19)
4
32
 
5
33
  #### Bug Fixes
data/Gemfile CHANGED
@@ -3,16 +3,17 @@ 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", "~> 6.1.6.1")
7
- gem "minitest", "~> 5.20.0"
6
+ gem "activerecord", ENV.fetch("AR_VERSION", "~> 7.1.0")
7
+ gem "ostruct"
8
+ gem "minitest", "~> 5.25.0"
8
9
  gem "minitest-rg", "~> 5.3.0"
9
10
  gem "pry", "~> 0.13.0"
10
11
  gem "pry-byebug", "~> 3.9.0"
11
12
  # Add sqlite3 for testing for compatibility with other adapters.
12
- gem "sqlite3"
13
+ gem 'sqlite3', '~> 1.4'
13
14
 
14
15
  # Required for samples and testing.
15
- install_if -> { ENV.fetch("AR_VERSION", "~> 6.1.6.1").dup.to_s.sub("~>", "").strip < "7.1.0" && !ENV["SKIP_COMPOSITE_PK"] } do
16
+ install_if -> { ENV.fetch("AR_VERSION", "~> 7.1.0").dup.to_s.sub("~>", "").strip < "7.1.0" && !ENV["SKIP_COMPOSITE_PK"] } do
16
17
  gem "composite_primary_keys"
17
18
  end
18
19
 
data/README.md CHANGED
@@ -77,6 +77,7 @@ __NOTE__: You do need to have [Docker](https://docs.docker.com/get-docker/) inst
77
77
  Some noteworthy examples in the snippets directory:
78
78
  - [quickstart](examples/snippets/quickstart): A simple application that shows how to create and query a simple database containing two tables.
79
79
  - [migrations](examples/snippets/migrations): Shows a best-practice for executing migrations on Cloud Spanner.
80
+ - [bit-reversed-sequences](examples/snippets/bit-reversed-sequence): Shows how to use bit-reversed sequences for primary keys.
80
81
  - [read-write-transactions](examples/snippets/read-write-transactions): Shows how to execute transactions on Cloud Spanner.
81
82
  - [read-only-transactions](examples/snippets/read-only-transactions): Shows how to execute read-only transactions on Cloud Spanner.
82
83
  - [bulk-insert](examples/snippets/bulk-insert): Shows the best way to insert a large number of new records.
data/Rakefile CHANGED
@@ -56,8 +56,8 @@ task :acceptance, [:project, :keyfile, :instance, :tests] do |t, args|
56
56
  tests ||= "**"
57
57
 
58
58
  # always overwrite when running tests
59
- ENV["SPANNER_PROJECT"] = project
60
- ENV["SPANNER_KEYFILE_JSON"] = keyfile
59
+ ENV["SPANNER_TEST_PROJECT"] = project
60
+ ENV["SPANNER_TEST_KEYFILE_JSON"] = keyfile
61
61
  ENV["SPANNER_TEST_INSTANCE"] = instance
62
62
  ENV["SPANNER_EMULATOR_HOST"] = emulator_host
63
63
 
@@ -17,15 +17,27 @@ module ActiveRecord
17
17
  ActiveRecord::gem_version >= Gem::Version.create('7.1.0')
18
18
  end
19
19
 
20
+ def is_7_2_or_higher?
21
+ ActiveRecord::gem_version >= Gem::Version.create('7.2.0')
22
+ end
23
+
24
+ def pool_or_connection
25
+ if is_7_2_or_higher?
26
+ ActiveRecord::Base.connection_pool
27
+ else
28
+ ActiveRecord::Base.connection
29
+ end
30
+ end
31
+
20
32
  def test_dump_schema_contains_start_batch_ddl
21
- connection = ActiveRecord::Base.connection
33
+ connection = pool_or_connection
22
34
  schema = StringIO.new
23
35
  ActiveRecord::SchemaDumper.dump connection, schema
24
36
  assert schema.string.include?("connection.start_batch_ddl")
25
37
  end
26
38
 
27
39
  def test_dump_schema_contains_run_batch
28
- connection = ActiveRecord::Base.connection
40
+ connection = pool_or_connection
29
41
  schema = StringIO.new
30
42
  ActiveRecord::SchemaDumper.dump connection, schema
31
43
  assert schema.string.include?(" connection.run_batch\n"\
@@ -35,7 +47,7 @@ module ActiveRecord
35
47
  end
36
48
 
37
49
  def test_dump_schema_contains_albums_table
38
- connection = ActiveRecord::Base.connection
50
+ connection = pool_or_connection
39
51
  schema = StringIO.new
40
52
  ActiveRecord::SchemaDumper.dump connection, schema
41
53
  sql = schema.string
@@ -47,32 +59,46 @@ module ActiveRecord
47
59
  end
48
60
 
49
61
  def test_dump_schema_contains_interleaved_index
50
- connection = ActiveRecord::Base.connection
62
+ connection = pool_or_connection
51
63
  schema = StringIO.new
52
64
  ActiveRecord::SchemaDumper.dump connection, schema
53
65
  assert schema.string.include?("t.index [\"singerid\", \"albumid\", \"title\"], name: \"index_tracks_on_singerid_and_albumid_and_title\", order: { singerid: :asc, albumid: :asc, title: :asc }, null_filtered: true, interleave_in: \"albums\""), schema.string
54
66
  end
55
67
 
56
68
  def test_dump_schema_should_not_contain_id_limit
57
- connection = ActiveRecord::Base.connection
69
+ connection = pool_or_connection
58
70
  schema = StringIO.new
59
71
  ActiveRecord::SchemaDumper.dump connection, schema
60
72
  assert !schema.string.include?("id: { limit: 8 }")
61
73
  end
62
74
 
63
75
  def test_dump_schema_contains_commit_timestamp
64
- connection = ActiveRecord::Base.connection
76
+ connection = pool_or_connection
65
77
  schema = StringIO.new
66
78
  ActiveRecord::SchemaDumper.dump connection, schema
67
79
  assert schema.string.include?("t.time \"last_updated\", allow_commit_timestamp: true"), schema.string
68
80
  end
69
81
 
70
82
  def test_dump_schema_contains_virtual_column
71
- connection = ActiveRecord::Base.connection
83
+ connection = pool_or_connection
72
84
  schema = StringIO.new
73
85
  ActiveRecord::SchemaDumper.dump connection, schema
74
86
  assert schema.string.include?("t.virtual \"full_name\", type: :string, as: \"COALESCE(first_name || ' ', '') || last_name\", stored: true"), schema.string
75
87
  end
88
+
89
+ def test_dump_schema_contains_string_array
90
+ connection = pool_or_connection
91
+ schema = StringIO.new
92
+ ActiveRecord::SchemaDumper.dump connection, schema
93
+ assert schema.string.include?("t.string \"col_array_string\", array: true"), schema.string
94
+ end
95
+
96
+ def test_dump_schema_index_storing
97
+ connection = pool_or_connection
98
+ schema = StringIO.new
99
+ ActiveRecord::SchemaDumper.dump connection, schema
100
+ assert schema.string.include?("t.index [\"last_name\"], name: \"index_singers_on_last_name\", order: { last_name: :asc }, storing: [\"first_name\", \"tracks_count\"]"), schema.string
101
+ end
76
102
  end
77
103
  end
78
104
  end
@@ -30,13 +30,24 @@ module ActiveRecord
30
30
  { id: Author.next_sequence_value, name: "Carol" },
31
31
  ]
32
32
 
33
- assert_raise(NotImplementedError) { Author.insert_all(values) }
33
+ Author.insert_all(values)
34
+
35
+ authors = Author.all.order(:name)
36
+
37
+ assert_equal "Alice", authors[0].name
38
+ assert_equal "Bob", authors[1].name
39
+ assert_equal "Carol", authors[2].name
34
40
  end
35
41
 
36
42
  def test_insert
37
43
  value = { id: Author.next_sequence_value, name: "Alice" }
38
44
 
39
- assert_raise(NotImplementedError) { Author.insert(value) }
45
+ Author.insert(value)
46
+
47
+ authors = Author.all.order(:name)
48
+
49
+ assert_equal 1, authors.length
50
+ assert_equal "Alice", authors[0].name
40
51
  end
41
52
 
42
53
  def test_insert_all!
@@ -136,12 +147,16 @@ module ActiveRecord
136
147
  { id: Author.next_sequence_value, name: "Carol" },
137
148
  ]
138
149
 
139
- err = assert_raise(NotImplementedError) do
140
- ActiveRecord::Base.transaction do
141
- Author.upsert_all(values)
142
- end
150
+ ActiveRecord::Base.transaction do
151
+ Author.upsert_all(values)
143
152
  end
144
- assert_match "Use upsert outside a transaction block", err.message
153
+
154
+ authors = Author.all.order(:name)
155
+
156
+ assert_equal 3, authors.length
157
+ assert_equal "Alice", authors[0].name
158
+ assert_equal "Bob", authors[1].name
159
+ assert_equal "Carol", authors[2].name
145
160
  end
146
161
 
147
162
  def test_upsert_all_with_buffered_mutation_transaction
@@ -51,6 +51,8 @@ module ActiveRecord
51
51
  sessions.each do |session|
52
52
  client.delete_session Google::Cloud::Spanner::V1::DeleteSessionRequest.new name: session.name
53
53
  end
54
+ # Wait a bit to ensure that the sessions are really deleted.
55
+ sleep 5 unless ENV["SPANNER_EMULATOR_HOST"]
54
56
  end
55
57
 
56
58
  def test_single_read
@@ -48,6 +48,7 @@ module ActiveRecord
48
48
  end
49
49
 
50
50
  def teardown
51
+ drop_database
51
52
  ActiveRecord::Base.connection_pool.disconnect!
52
53
  FileUtils.rm_rf ActiveRecord::Tasks::DatabaseTasks.db_dir
53
54
  ActiveRecord::Tasks::DatabaseTasks.db_dir = @original_db_dir
@@ -226,6 +227,7 @@ CREATE TABLE singers (
226
227
  lock_version INT64,
227
228
  full_name STRING(MAX) AS (COALESCE(first_name || ' ', '') || last_name) STORED,
228
229
  ) PRIMARY KEY(singerid);
230
+ CREATE INDEX index_singers_on_last_name ON singers(last_name) STORING (tracks_count, first_name);
229
231
  CREATE TABLE albums (
230
232
  albumid INT64 NOT NULL,
231
233
  singerid INT64 NOT NULL,
@@ -383,6 +385,7 @@ CREATE TABLE singers (
383
385
  lock_version INT64,
384
386
  full_name STRING(MAX) AS (COALESCE(first_name || ' ', '') || last_name) STORED,
385
387
  ) PRIMARY KEY(singerid);
388
+ CREATE INDEX index_singers_on_last_name ON singers(last_name) STORING (tracks_count, first_name);
386
389
  CREATE TABLE albums (
387
390
  singerid INT64 NOT NULL,
388
391
  albumid INT64 NOT NULL,
@@ -547,6 +550,7 @@ CREATE TABLE singers (
547
550
  lock_version INT64,
548
551
  full_name STRING(MAX) AS (COALESCE(first_name || ' ', '') || last_name) STORED,
549
552
  ) PRIMARY KEY(singerid);
553
+ CREATE INDEX index_singers_on_last_name ON singers(last_name) STORING (tracks_count, first_name);
550
554
  CREATE TABLE albums (
551
555
  albumid INT64 NOT NULL,
552
556
  singerid INT64 NOT NULL,
@@ -707,6 +711,7 @@ CREATE TABLE singers (
707
711
  lock_version INT64,
708
712
  full_name STRING(MAX) AS (COALESCE(first_name || ' ', '') || last_name) STORED,
709
713
  ) PRIMARY KEY(singerid);
714
+ CREATE INDEX index_singers_on_last_name ON singers(last_name) STORING (tracks_count, first_name);
710
715
  CREATE TABLE albums (
711
716
  singerid INT64 NOT NULL,
712
717
  albumid INT64 NOT NULL,
@@ -126,6 +126,7 @@ def create_tables_in_test_schema
126
126
  t.integer :lock_version
127
127
  t.virtual :full_name, type: :string, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
128
128
  end
129
+ add_index :singers, :last_name, storing: %i[tracks_count first_name]
129
130
 
130
131
  if is_7_1_or_higher?
131
132
  create_table :albums, primary_key: [:singerid, :albumid] do |t|
@@ -8,6 +8,7 @@ gem "minitest"
8
8
  require "minitest/autorun"
9
9
  require "minitest/focus"
10
10
  require "minitest/rg"
11
+ require "ostruct"
11
12
  require "active_support"
12
13
  require "google/cloud/spanner"
13
14
  require "active_record"
@@ -18,7 +19,10 @@ require "securerandom"
18
19
  require "composite_primary_keys" if ActiveRecord::gem_version < Gem::Version.create('7.1.0')
19
20
 
20
21
  # rubocop:disable Style/GlobalVars
21
-
22
+ #
23
+ if ActiveRecord.gem_version >= Gem::Version.create("7.2.0")
24
+ ActiveRecord::ConnectionAdapters.register("spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter")
25
+ end
22
26
  $spanner_test_database = "ar-test-#{SecureRandom.hex 4}"
23
27
 
24
28
  def connector_config
@@ -25,7 +25,9 @@ Gem::Specification.new do |spec|
25
25
  spec.required_ruby_version = ">= 2.7"
26
26
 
27
27
  spec.add_dependency "google-cloud-spanner", "~> 2.18"
28
- spec.add_runtime_dependency "activerecord", [">= 6.0.0", "< 7.2"]
28
+ # Pin gRPC to 1.64.3, as 1.65 and 1.66 cause test runs to hang randomly.
29
+ spec.add_dependency "grpc", "1.64.3"
30
+ spec.add_runtime_dependency "activerecord", [">= 6.1", "< 7.3"]
29
31
 
30
32
  spec.add_development_dependency "autotest-suffix", "~> 1.1"
31
33
  spec.add_development_dependency "bundler", "~> 2.0"
@@ -5,6 +5,12 @@ This example shows how to use a bit-reversed sequence to generate the primary ke
5
5
  See https://cloud.google.com/spanner/docs/primary-key-default-value#bit-reversed-sequence for more information
6
6
  about bit-reversed sequences in Cloud Spanner.
7
7
 
8
+ ## Requirements
9
+ Using bit-reversed sequences for generating primary key values in ActiveRecord has the following requirements:
10
+ 1. You must use __ActiveRecord version 7.1 or higher__.
11
+ 2. Your models must include a sequence name like this: `self.sequence_name = :singer_sequence`
12
+ 3. You must create the bit-reversed sequence using a SQL statement in your migrations.
13
+
8
14
  ## Creating Tables with Bit-Reversed Sequences in ActiveRecord
9
15
  You can create bit-reversed sequences using migrations in ActiveRecord by executing a SQL statement using the underlying
10
16
  connection.
@@ -8,15 +8,12 @@ class CreateTables < ActiveRecord::Migration[7.1]
8
8
  def change
9
9
  # Execute the entire migration as one DDL batch.
10
10
  connection.ddl_batch do
11
- # TODO: Uncomment when bit-reversed sequences are supported in the emulator.
12
- # connection.execute "create sequence singer_sequence OPTIONS (sequence_kind = 'bit_reversed_positive')"
11
+ connection.execute "create sequence singer_sequence OPTIONS (sequence_kind = 'bit_reversed_positive')"
13
12
 
14
13
  # Explicitly define the primary key.
15
14
  create_table :singers, id: false, primary_key: :singerid do |t|
16
- # TODO: Uncomment when bit-reversed sequences are supported in the emulator.
17
- # t.integer :singerid, primary_key: true, null: false,
18
- # default: -> { "GET_NEXT_SEQUENCE_VALUE(SEQUENCE singer_sequence)" }
19
- t.integer :singerid, primary_key: true, null: false, default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }
15
+ t.integer :singerid, primary_key: true, null: false,
16
+ default: -> { "GET_NEXT_SEQUENCE_VALUE(SEQUENCE singer_sequence)" }
20
17
  t.string :first_name
21
18
  t.string :last_name
22
19
  end
@@ -19,7 +19,7 @@ ActiveRecord::Schema[7.1].define(version: 1) do
19
19
  t.string "title"
20
20
  end
21
21
 
22
- create_table "singers", primary_key: "singerid", default: -> { "FARM_FINGERPRINT(GENERATE_UUID())" }, force: :cascade do |t|
22
+ create_table "singers", primary_key: "singerid", default: -> { "GET_NEXT_SEQUENCE_VALUE(SEQUENCE singer_sequence)" }, force: :cascade do |t|
23
23
  t.string "first_name"
24
24
  t.string "last_name"
25
25
  end
@@ -21,15 +21,15 @@ module ActiveRecord
21
21
  internal_execute sql, name, binds
22
22
  end
23
23
 
24
- def internal_exec_query sql, name = "SQL", binds = [], prepare: false, async: false
25
- result = internal_execute sql, name, binds, prepare: prepare, async: async
24
+ def internal_exec_query sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false
25
+ result = internal_execute sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry
26
26
  ActiveRecord::Result.new(
27
27
  result.fields.keys.map(&:to_s), result.rows.map(&:values)
28
28
  )
29
29
  end
30
30
 
31
31
  def internal_execute sql, name = "SQL", binds = [],
32
- prepare: false, async: false # rubocop:disable Lint/UnusedMethodArgument
32
+ prepare: false, async: false, allow_retry: false # rubocop:disable Lint/UnusedMethodArgument, Metrics/LineLength
33
33
  statement_type = sql_statement_type sql
34
34
  # Call `transform` to invoke any query transformers that might have been registered.
35
35
  sql = transform sql
@@ -320,6 +320,8 @@ module ActiveRecord
320
320
  elsif bind.class == Symbol
321
321
  # This ensures that for example :environment is sent as the string 'environment' to Cloud Spanner.
322
322
  type = :STRING
323
+ elsif bind.class == TrueClass || bind.class == FalseClass
324
+ type = :BOOL
323
325
  end
324
326
  [
325
327
  # Generates binds for named parameters in the format `@p1, @p2, ...`
@@ -30,17 +30,29 @@
30
30
 
31
31
  module ActiveRecord
32
32
  module ConnectionAdapters
33
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
34
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
35
+
36
+ module Quoting
37
+ module ClassMethods
38
+ # This is used for ActiveRecord v8 and higher.
39
+ def quote_column_name name
40
+ QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub '`', '``'}`".freeze
41
+ end
42
+
43
+ def quote_table_name name
44
+ QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub '.', '`.`'}`".freeze
45
+ end
46
+ end
47
+ end
33
48
  module Spanner
34
49
  module Quoting
35
- QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
36
- QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
37
-
38
50
  def quote_column_name name
39
- QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub '`', '``'}`".freeze
51
+ QUOTED_COLUMN_NAMES[name] ||= "`#{name.to_s.gsub '`', '``'}`".freeze
40
52
  end
41
53
 
42
54
  def quote_table_name name
43
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
55
+ QUOTED_TABLE_NAMES[name] ||= "`#{name.to_s.gsub '.', '`.`'}`".freeze
44
56
  end
45
57
 
46
58
  STR_ESCAPE_REGX = /[\n\r'\\]/.freeze
@@ -27,6 +27,8 @@ module ActiveRecord
27
27
  spec = { type: schema_type(column).inspect }.merge! spec
28
28
  end
29
29
 
30
+ spec[:array] = true if column.sql_type.start_with? "ARRAY<"
31
+
30
32
  spec
31
33
  end
32
34
 
@@ -54,7 +56,7 @@ module ActiveRecord
54
56
  index_parts = super
55
57
  index_parts << "null_filtered: #{index.null_filtered.inspect}" if index.null_filtered
56
58
  index_parts << "interleave_in: #{index.interleave_in.inspect}" if index.interleave_in
57
- index_parts << "storing: #{format_index_parts index.storing}" if index.storing.present?
59
+ index_parts << "storing: #{format_index_parts index.storing.sort}" if index.storing.present?
58
60
  index_parts
59
61
  end
60
62
 
@@ -23,6 +23,7 @@ module ActiveRecord
23
23
  module SchemaStatements
24
24
  VERSION_6_1_0 = Gem::Version.create "6.1.0"
25
25
  VERSION_6_0_3 = Gem::Version.create "6.0.3"
26
+ VERSION_7_2 = Gem::Version.create "7.2.0"
26
27
 
27
28
  def current_database
28
29
  @connection.database_id
@@ -401,6 +402,16 @@ module ActiveRecord
401
402
  information_schema { |i| i.check_constraints table_name }
402
403
  end
403
404
 
405
+ if ActiveRecord.gem_version >= VERSION_7_2
406
+ def migration_context
407
+ pool.migration_context
408
+ end
409
+
410
+ def schema_migration
411
+ pool.schema_migration
412
+ end
413
+ end
414
+
404
415
  def assume_migrated_upto_version version
405
416
  version = version.to_i
406
417
  sm_table = quote_table_name schema_migration.table_name
@@ -28,16 +28,18 @@ require "activerecord_spanner_adapter/primary_key"
28
28
  require "activerecord_spanner_adapter/transaction"
29
29
 
30
30
  module ActiveRecord
31
- module ConnectionHandling # :nodoc:
32
- def spanner_connection config
33
- connection = ActiveRecordSpannerAdapter::Connection.new config
34
- connection.connect!
35
- ConnectionAdapters::SpannerAdapter.new connection, logger, nil, config
36
- rescue Google::Cloud::Error => error
37
- if error.instance_of? Google::Cloud::NotFoundError
38
- raise ActiveRecord::NoDatabaseError
39
- end
40
- raise error
31
+ if ActiveRecord.version < Gem::Version.new("7.2")
32
+ module ConnectionHandling # :nodoc:
33
+ def spanner_connection config
34
+ connection = ActiveRecordSpannerAdapter::Connection.new config
35
+ connection.connect!
36
+ ConnectionAdapters::SpannerAdapter.new connection, logger, nil, config
37
+ rescue Google::Cloud::Error => error
38
+ if error.instance_of? Google::Cloud::NotFoundError
39
+ raise ActiveRecord::NoDatabaseError
40
+ end
41
+ raise error
42
+ end
41
43
  end
42
44
  end
43
45
 
@@ -69,11 +71,21 @@ module ActiveRecord
69
71
  # Determines whether or not to log query binds when executing statements
70
72
  class_attribute :log_statement_binds, instance_writer: false, default: false
71
73
 
72
- def initialize connection, logger, connection_options, config
73
- @connection = connection
74
- @connection_options = connection_options
75
- super connection, logger, config
76
- @raw_connection ||= connection
74
+ def initialize config_or_deprecated_connection, deprecated_logger = nil,
75
+ deprecated_connection_options = nil, deprecated_config = nil
76
+ if config_or_deprecated_connection.is_a? Hash
77
+ @connection = ActiveRecordSpannerAdapter::Connection.new config_or_deprecated_connection
78
+ @connection.connect!
79
+ super config_or_deprecated_connection
80
+ @raw_connection ||= @connection
81
+ else
82
+ @connection = config_or_deprecated_connection
83
+ @connection_options = deprecated_connection_options
84
+ super config_or_deprecated_connection, deprecated_logger, deprecated_config
85
+ @raw_connection ||= config_or_deprecated_connection
86
+ end
87
+ # Spanner does not support unprepared statements
88
+ @prepared_statements = true
77
89
  end
78
90
 
79
91
  def max_identifier_length
@@ -197,12 +209,14 @@ module ActiveRecord
197
209
  raise "ActiveRecordSpannerAdapter does not support insert_sql with buffered_mutations transaction."
198
210
  end
199
211
 
200
- if insert.skip_duplicates? || insert.update_duplicates?
201
- raise NotImplementedError, "CloudSpanner does not support skip_duplicates and update_duplicates."
202
- end
203
-
204
212
  values_list, = insert.values_list
205
- "INSERT #{insert.into} #{values_list}"
213
+ prefix = "INSERT"
214
+ if insert.update_duplicates?
215
+ prefix += " OR UPDATE"
216
+ elsif insert.skip_duplicates?
217
+ prefix += " OR IGNORE"
218
+ end
219
+ "#{prefix} #{insert.into} #{values_list}"
206
220
  end
207
221
 
208
222
  module TypeMapBuilder
@@ -15,7 +15,11 @@ if defined?(Rails)
15
15
  end
16
16
 
17
17
  ActiveSupport.on_load :active_record do
18
- require "active_record/connection_adapters/spanner_adapter"
18
+ if Rails.version >= "7.2.0"
19
+ ActiveRecord::ConnectionAdapters.register("spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter")
20
+ else
21
+ require "active_record/connection_adapters/spanner_adapter"
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -16,6 +16,7 @@ module ActiveRecord
16
16
 
17
17
  class Base
18
18
  VERSION_7_1 = Gem::Version.create "7.1.0"
19
+ VERSION_7_2 = Gem::Version.create "7.2.0"
19
20
 
20
21
  # Creates an object (or multiple objects) and saves it to the database. This method will use mutations instead
21
22
  # of DML if there is no active transaction, or if the active transaction has been created with the option
@@ -48,8 +49,26 @@ module ActiveRecord
48
49
  spanner_adapter? && connection&.current_spanner_transaction&.isolation == :buffered_mutations
49
50
  end
50
51
 
51
- def self._insert_record values, returning = []
52
- if !(buffered_mutations? || (primary_key && values.is_a?(Hash))) || !spanner_adapter?
52
+ def self._should_use_standard_insert_record? values
53
+ !(buffered_mutations? || (primary_key && values.is_a?(Hash))) || !spanner_adapter?
54
+ end
55
+
56
+ def self._internal_insert_record values
57
+ if ActiveRecord.gem_version < VERSION_7_2
58
+ _insert_record values
59
+ else
60
+ _insert_record nil, values
61
+ end
62
+ end
63
+
64
+ def self._insert_record *args
65
+ if ActiveRecord.gem_version < VERSION_7_2
66
+ values, returning = args
67
+ else
68
+ _connection, values, returning = args
69
+ end
70
+
71
+ if _should_use_standard_insert_record? values
53
72
  return super values if ActiveRecord.gem_version < VERSION_7_1
54
73
  return super
55
74
  end
@@ -111,8 +130,20 @@ module ActiveRecord
111
130
  _buffer_record values, :insert_or_update, returning
112
131
  end
113
132
 
114
- def self.insert_all _attributes, **_kwargs
115
- raise NotImplementedError, "Cloud Spanner does not support skip_duplicates. Use insert! or upsert instead."
133
+ def self.insert_all attributes, returning: nil, **_kwargs
134
+ if active_transaction? && buffered_mutations?
135
+ raise NotImplementedError,
136
+ "Spanner does not support skip_duplicates for mutations. " \
137
+ "Use a transaction that uses DML, or use insert! or upsert instead."
138
+ end
139
+ super
140
+ end
141
+
142
+ def self.insert! attributes, returning: nil, **kwargs
143
+ return super unless spanner_adapter?
144
+ return super if active_transaction? && !buffered_mutations?
145
+
146
+ insert_all! [attributes], returning: returning, **kwargs
116
147
  end
117
148
 
118
149
  def self.insert_all! attributes, returning: nil, **_kwargs
@@ -123,24 +154,27 @@ module ActiveRecord
123
154
  # The mutations will be sent as one batch when the transaction is committed.
124
155
  if active_transaction?
125
156
  attributes.each do |record|
126
- _insert_record record
157
+ _internal_insert_record record
127
158
  end
128
159
  else
129
160
  transaction isolation: :buffered_mutations do
130
161
  attributes.each do |record|
131
- _insert_record record
162
+ _internal_insert_record record
132
163
  end
133
164
  end
134
165
  end
135
166
  end
136
167
 
137
- def self.upsert_all attributes, returning: nil, unique_by: nil, **_kwargs
168
+ def self.upsert attributes, returning: nil, **kwargs
138
169
  return super unless spanner_adapter?
139
- if active_transaction? && !buffered_mutations?
140
- raise NotImplementedError, "Cloud Spanner does not support upsert using DML. " \
141
- "Use upsert outside a transaction block or in a transaction " \
142
- "block with isolation: :buffered_mutations"
143
- end
170
+ return super if active_transaction? && !buffered_mutations?
171
+
172
+ upsert_all [attributes], returning: returning, **kwargs
173
+ end
174
+
175
+ def self.upsert_all attributes, returning: nil, unique_by: nil, **kwargs
176
+ return super unless spanner_adapter?
177
+ return super if active_transaction? && !buffered_mutations?
144
178
 
145
179
  # This might seem inefficient, but is actually not, as it is only buffering a mutation locally.
146
180
  # The mutations will be sent as one batch when the transaction is committed.
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "1.6.2".freeze
8
+ VERSION = "1.8.0".freeze
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-spanner-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-19 00:00:00.000000000 Z
11
+ date: 2024-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-cloud-spanner
@@ -24,26 +24,40 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.18'
27
+ - !ruby/object:Gem::Dependency
28
+ name: grpc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.64.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.64.3
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: activerecord
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: 6.0.0
47
+ version: '6.1'
34
48
  - - "<"
35
49
  - !ruby/object:Gem::Version
36
- version: '7.2'
50
+ version: '7.3'
37
51
  type: :runtime
38
52
  prerelease: false
39
53
  version_requirements: !ruby/object:Gem::Requirement
40
54
  requirements:
41
55
  - - ">="
42
56
  - !ruby/object:Gem::Version
43
- version: 6.0.0
57
+ version: '6.1'
44
58
  - - "<"
45
59
  - !ruby/object:Gem::Version
46
- version: '7.2'
60
+ version: '7.3'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: autotest-suffix
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -230,8 +244,6 @@ files:
230
244
  - ".github/workflows/nightly-acceptance-tests-on-emulator.yaml"
231
245
  - ".github/workflows/nightly-acceptance-tests-on-production.yaml"
232
246
  - ".github/workflows/nightly-unit-tests.yaml"
233
- - ".github/workflows/release-please-label.yml"
234
- - ".github/workflows/release-please.yml"
235
247
  - ".github/workflows/rubocop.yaml"
236
248
  - ".gitignore"
237
249
  - ".kokoro/populate-secrets.sh"
@@ -577,7 +589,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
577
589
  - !ruby/object:Gem::Version
578
590
  version: '0'
579
591
  requirements: []
580
- rubygems_version: 3.5.3
592
+ rubygems_version: 3.5.23
581
593
  signing_key:
582
594
  specification_version: 4
583
595
  summary: Rails ActiveRecord connector for Google Spanner Database
@@ -1,25 +0,0 @@
1
- name: release-please-label
2
- on:
3
- pull_request_target:
4
- branches:
5
- - main
6
- types:
7
- - opened
8
- jobs:
9
- release-please-label:
10
- if: "${{ github.event.sender.login == 'yoshi-code-bot' && startsWith(github.event.pull_request.title, 'chore(main): release ') }}"
11
- runs-on: ubuntu-latest
12
- steps:
13
- - name: ReleaseLabel
14
- uses: actions/github-script@v7
15
- with:
16
- github-token: ${{secrets.YOSHI_APPROVER_TOKEN}}
17
- script: |
18
- core.info('Labeling release');
19
- await github.rest.issues.addLabels({
20
- owner: context.repo.owner,
21
- repo: context.repo.repo,
22
- issue_number: context.payload.pull_request.number,
23
- labels: ['autorelease: pending']
24
- });
25
- core.info('Labeled');
@@ -1,38 +0,0 @@
1
- name: Release-Please
2
- on:
3
- workflow_dispatch:
4
- inputs:
5
- gem:
6
- description: "Name of single gem to release. Leave blank to check all gems."
7
- required: false
8
- args:
9
- description: "Extra command line arguments."
10
- required: false
11
-
12
- jobs:
13
- release-please:
14
- if: ${{ github.repository == 'googleapis/ruby-spanner-activerecord' }}
15
- runs-on: ubuntu-latest
16
- env:
17
- GITHUB_TOKEN: ${{ secrets.YOSHI_CODE_BOT_TOKEN }}
18
- RELEASE_PLEASE_DISABLE: ${{ secrets.RELEASE_PLEASE_DISABLE }}
19
- steps:
20
- - name: Checkout repo
21
- uses: actions/checkout@v4
22
- - name: Install Ruby 3.0
23
- uses: ruby/setup-ruby@v1
24
- with:
25
- ruby-version: "3.0"
26
- - name: Install NodeJS 16.x
27
- uses: actions/setup-node@v4
28
- with:
29
- node-version: "16.x"
30
- - name: Install tools
31
- run: "gem install --no-document toys"
32
- - name: execute
33
- run: |
34
- toys release manifest -v \
35
- --fork --skip-labeling \
36
- --github-event-name=${{ github.event_name }} \
37
- ${{ github.event.inputs.args }} \
38
- -- ${{ github.event.inputs.gem }}