activerecord-spanner-adapter 1.6.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/acceptance-tests-on-emulator.yaml +5 -7
- data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/ci.yaml +5 -7
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +6 -33
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +1 -1
- data/.github/workflows/nightly-unit-tests.yaml +7 -33
- data/.kokoro/populate-secrets.sh +5 -1
- data/.kokoro/release.cfg +22 -12
- data/.kokoro/trampoline_v2.sh +19 -11
- data/.release-please-manifest.json +1 -1
- data/.trampolinerc +6 -1
- data/CHANGELOG.md +28 -0
- data/Gemfile +5 -4
- data/README.md +1 -0
- data/Rakefile +2 -2
- data/acceptance/cases/migration/schema_dumper_test.rb +33 -7
- data/acceptance/cases/models/insert_all_test.rb +22 -7
- data/acceptance/cases/sessions/session_not_found_test.rb +2 -0
- data/acceptance/cases/tasks/database_tasks_test.rb +5 -0
- data/acceptance/schema/schema.rb +1 -0
- data/acceptance/test_helper.rb +5 -1
- data/activerecord-spanner-adapter.gemspec +3 -1
- data/examples/snippets/bit-reversed-sequence/README.md +6 -0
- data/examples/snippets/bit-reversed-sequence/db/migrate/01_create_tables.rb +3 -6
- data/examples/snippets/bit-reversed-sequence/db/schema.rb +1 -1
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +5 -3
- data/lib/active_record/connection_adapters/spanner/quoting.rb +17 -5
- data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +3 -1
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +11 -0
- data/lib/active_record/connection_adapters/spanner_adapter.rb +34 -20
- data/lib/activerecord-spanner-adapter.rb +5 -1
- data/lib/activerecord_spanner_adapter/base.rb +46 -12
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- metadata +21 -9
- data/.github/workflows/release-please-label.yml +0 -25
- data/.github/workflows/release-please.yml +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e521009bd7ee056c90e4147edd3e6dfe012e7808abf8dcf2b9a0145f380d669d
|
4
|
+
data.tar.gz: 9be31f9f418a9ca5f80531924bc299a75c64826d09989c7318aff88eaccf8a41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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: "~>
|
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:
|
data/.github/workflows/ci.yaml
CHANGED
@@ -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
|
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: "~>
|
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
|
-
|
22
|
-
|
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:
|
27
|
-
ar:
|
28
|
-
- ruby: 3.0
|
29
|
-
ar:
|
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,41 +10,15 @@ jobs:
|
|
10
10
|
strategy:
|
11
11
|
max-parallel: 4
|
12
12
|
matrix:
|
13
|
-
# Run
|
14
|
-
ruby: [2.7, 3.0, 3.1, 3.2]
|
15
|
-
ar: [
|
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:
|
19
|
-
ar:
|
20
|
-
- ruby: 3.0
|
21
|
-
ar:
|
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:
|
data/.kokoro/populate-secrets.sh
CHANGED
@@ -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
|
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: "
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
48
|
+
define_artifacts {
|
49
|
+
regex: "github/ruby-spanner-activerecord/pkg/*.gem"
|
50
|
+
strip_prefix: "github"
|
51
|
+
}
|
52
|
+
}
|
data/.kokoro/trampoline_v2.sh
CHANGED
@@ -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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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.
|
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"
|
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", "~>
|
7
|
-
gem "
|
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
|
13
|
+
gem 'sqlite3', '~> 1.4'
|
13
14
|
|
14
15
|
# Required for samples and testing.
|
15
|
-
install_if -> { ENV.fetch("AR_VERSION", "~>
|
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["
|
60
|
-
ENV["
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
140
|
-
|
141
|
-
Author.upsert_all(values)
|
142
|
-
end
|
150
|
+
ActiveRecord::Base.transaction do
|
151
|
+
Author.upsert_all(values)
|
143
152
|
end
|
144
|
-
|
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,
|
data/acceptance/schema/schema.rb
CHANGED
@@ -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|
|
data/acceptance/test_helper.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
17
|
-
|
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: -> { "
|
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] ||= "`#{
|
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] ||=
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
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.
|
52
|
-
|
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
|
115
|
-
|
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
|
-
|
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
|
-
|
162
|
+
_internal_insert_record record
|
132
163
|
end
|
133
164
|
end
|
134
165
|
end
|
135
166
|
end
|
136
167
|
|
137
|
-
def self.
|
168
|
+
def self.upsert attributes, returning: nil, **kwargs
|
138
169
|
return super unless spanner_adapter?
|
139
|
-
if active_transaction? && !buffered_mutations?
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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.
|
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.
|
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-
|
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.
|
47
|
+
version: '6.1'
|
34
48
|
- - "<"
|
35
49
|
- !ruby/object:Gem::Version
|
36
|
-
version: '7.
|
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.
|
57
|
+
version: '6.1'
|
44
58
|
- - "<"
|
45
59
|
- !ruby/object:Gem::Version
|
46
|
-
version: '7.
|
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.
|
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 }}
|