activerecord-spanner-adapter 0.3.0 → 0.5.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.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +7 -0
- data/.github/sync-repo-settings.yaml +16 -0
- data/.github/workflows/acceptance-tests-on-emulator.yaml +45 -0
- data/.github/workflows/acceptance-tests-on-production.yaml +36 -0
- data/.github/workflows/ci.yaml +33 -0
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +52 -0
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +35 -0
- data/.github/workflows/nightly-unit-tests.yaml +40 -0
- data/.github/workflows/release-please-label.yml +25 -0
- data/.github/workflows/release-please.yml +39 -0
- data/.github/workflows/rubocop.yaml +31 -0
- data/.gitignore +67 -5
- data/.kokoro/populate-secrets.sh +77 -0
- data/.kokoro/release.cfg +33 -0
- data/.kokoro/release.sh +15 -0
- data/.kokoro/trampoline_v2.sh +489 -0
- data/.rubocop.yml +46 -0
- data/.toys/release.rb +18 -0
- data/.trampolinerc +48 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +9 -4
- data/LICENSE +6 -6
- data/README.md +67 -30
- data/Rakefile +79 -3
- data/SECURITY.md +7 -0
- data/acceptance/cases/associations/has_many_associations_test.rb +119 -0
- data/acceptance/cases/associations/has_many_through_associations_test.rb +63 -0
- data/acceptance/cases/associations/has_one_associations_test.rb +79 -0
- data/acceptance/cases/associations/has_one_through_associations_test.rb +98 -0
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +211 -0
- data/acceptance/cases/migration/change_schema_test.rb +433 -0
- data/acceptance/cases/migration/change_table_test.rb +115 -0
- data/acceptance/cases/migration/column_attributes_test.rb +122 -0
- data/acceptance/cases/migration/column_positioning_test.rb +48 -0
- data/acceptance/cases/migration/columns_test.rb +201 -0
- data/acceptance/cases/migration/command_recorder_test.rb +406 -0
- data/acceptance/cases/migration/create_join_table_test.rb +216 -0
- data/acceptance/cases/migration/ddl_batching_test.rb +80 -0
- data/acceptance/cases/migration/foreign_key_test.rb +297 -0
- data/acceptance/cases/migration/index_test.rb +211 -0
- data/acceptance/cases/migration/references_foreign_key_test.rb +259 -0
- data/acceptance/cases/migration/references_index_test.rb +135 -0
- data/acceptance/cases/migration/references_statements_test.rb +166 -0
- data/acceptance/cases/migration/rename_column_test.rb +96 -0
- data/acceptance/cases/models/calculation_query_test.rb +128 -0
- data/acceptance/cases/models/generated_column_test.rb +126 -0
- data/acceptance/cases/models/mutation_test.rb +122 -0
- data/acceptance/cases/models/query_test.rb +147 -0
- data/acceptance/cases/sessions/session_not_found_test.rb +121 -0
- data/acceptance/cases/transactions/optimistic_locking_test.rb +141 -0
- data/acceptance/cases/transactions/read_only_transactions_test.rb +67 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
- data/acceptance/cases/type/all_types_test.rb +152 -0
- data/acceptance/cases/type/binary_test.rb +59 -0
- data/acceptance/cases/type/boolean_test.rb +31 -0
- data/acceptance/cases/type/date_test.rb +32 -0
- data/acceptance/cases/type/date_time_test.rb +30 -0
- data/acceptance/cases/type/float_test.rb +27 -0
- data/acceptance/cases/type/integer_test.rb +44 -0
- data/acceptance/cases/type/numeric_test.rb +27 -0
- data/acceptance/cases/type/string_test.rb +79 -0
- data/acceptance/cases/type/text_test.rb +30 -0
- data/acceptance/cases/type/time_test.rb +87 -0
- data/acceptance/models/account.rb +13 -0
- data/acceptance/models/address.rb +9 -0
- data/acceptance/models/album.rb +12 -0
- data/acceptance/models/all_types.rb +8 -0
- data/acceptance/models/author.rb +11 -0
- data/acceptance/models/club.rb +12 -0
- data/acceptance/models/comment.rb +9 -0
- data/acceptance/models/customer.rb +9 -0
- data/acceptance/models/department.rb +9 -0
- data/acceptance/models/firm.rb +10 -0
- data/acceptance/models/member.rb +13 -0
- data/acceptance/models/member_type.rb +9 -0
- data/acceptance/models/membership.rb +10 -0
- data/acceptance/models/organization.rb +9 -0
- data/acceptance/models/post.rb +10 -0
- data/acceptance/models/singer.rb +10 -0
- data/acceptance/models/track.rb +20 -0
- data/acceptance/models/transaction.rb +9 -0
- data/acceptance/schema/schema.rb +143 -0
- data/acceptance/test_helper.rb +260 -0
- data/activerecord-spanner-adapter.gemspec +32 -17
- data/assets/solidus-db.png +0 -0
- data/benchmarks/README.md +17 -0
- data/benchmarks/Rakefile +14 -0
- data/benchmarks/application.rb +308 -0
- data/benchmarks/config/database.yml +8 -0
- data/benchmarks/config/environment.rb +12 -0
- data/benchmarks/db/migrate/01_create_tables.rb +25 -0
- data/benchmarks/db/schema.rb +29 -0
- data/benchmarks/models/album.rb +9 -0
- data/benchmarks/models/singer.rb +9 -0
- data/bin/console +6 -7
- data/examples/rails/README.md +262 -0
- data/examples/snippets/README.md +29 -0
- data/examples/snippets/Rakefile +57 -0
- data/examples/snippets/array-data-type/README.md +45 -0
- data/examples/snippets/array-data-type/Rakefile +13 -0
- data/examples/snippets/array-data-type/application.rb +45 -0
- data/examples/snippets/array-data-type/config/database.yml +8 -0
- data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
- data/examples/snippets/array-data-type/db/schema.rb +26 -0
- data/examples/snippets/array-data-type/db/seeds.rb +5 -0
- data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
- data/examples/snippets/bin/create_emulator_instance.rb +18 -0
- data/examples/snippets/bulk-insert/README.md +21 -0
- data/examples/snippets/bulk-insert/Rakefile +13 -0
- data/examples/snippets/bulk-insert/application.rb +64 -0
- data/examples/snippets/bulk-insert/config/database.yml +8 -0
- data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/bulk-insert/db/schema.rb +26 -0
- data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
- data/examples/snippets/bulk-insert/models/album.rb +9 -0
- data/examples/snippets/bulk-insert/models/singer.rb +9 -0
- data/examples/snippets/commit-timestamp/README.md +18 -0
- data/examples/snippets/commit-timestamp/Rakefile +13 -0
- data/examples/snippets/commit-timestamp/application.rb +53 -0
- data/examples/snippets/commit-timestamp/config/database.yml +8 -0
- data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
- data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
- data/examples/snippets/commit-timestamp/models/album.rb +9 -0
- data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
- data/examples/snippets/config/environment.rb +21 -0
- data/examples/snippets/create-records/README.md +12 -0
- data/examples/snippets/create-records/Rakefile +13 -0
- data/examples/snippets/create-records/application.rb +42 -0
- data/examples/snippets/create-records/config/database.yml +8 -0
- data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/create-records/db/schema.rb +26 -0
- data/examples/snippets/create-records/db/seeds.rb +5 -0
- data/examples/snippets/create-records/models/album.rb +9 -0
- data/examples/snippets/create-records/models/singer.rb +9 -0
- data/examples/snippets/date-data-type/README.md +19 -0
- data/examples/snippets/date-data-type/Rakefile +13 -0
- data/examples/snippets/date-data-type/application.rb +35 -0
- data/examples/snippets/date-data-type/config/database.yml +8 -0
- data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
- data/examples/snippets/date-data-type/db/schema.rb +21 -0
- data/examples/snippets/date-data-type/db/seeds.rb +16 -0
- data/examples/snippets/date-data-type/models/singer.rb +8 -0
- data/examples/snippets/generated-column/README.md +41 -0
- data/examples/snippets/generated-column/Rakefile +13 -0
- data/examples/snippets/generated-column/application.rb +37 -0
- data/examples/snippets/generated-column/config/database.yml +8 -0
- data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
- data/examples/snippets/generated-column/db/schema.rb +21 -0
- data/examples/snippets/generated-column/db/seeds.rb +18 -0
- data/examples/snippets/generated-column/models/singer.rb +8 -0
- data/examples/snippets/interleaved-tables/README.md +152 -0
- data/examples/snippets/interleaved-tables/Rakefile +13 -0
- data/examples/snippets/interleaved-tables/application.rb +109 -0
- data/examples/snippets/interleaved-tables/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables/models/album.rb +15 -0
- data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
- data/examples/snippets/interleaved-tables/models/track.rb +25 -0
- data/examples/snippets/migrations/README.md +43 -0
- data/examples/snippets/migrations/Rakefile +13 -0
- data/examples/snippets/migrations/application.rb +26 -0
- data/examples/snippets/migrations/config/database.yml +8 -0
- data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
- data/examples/snippets/migrations/db/schema.rb +33 -0
- data/examples/snippets/migrations/db/seeds.rb +5 -0
- data/examples/snippets/migrations/models/album.rb +10 -0
- data/examples/snippets/migrations/models/singer.rb +10 -0
- data/examples/snippets/migrations/models/track.rb +9 -0
- data/examples/snippets/mutations/README.md +34 -0
- data/examples/snippets/mutations/Rakefile +13 -0
- data/examples/snippets/mutations/application.rb +47 -0
- data/examples/snippets/mutations/config/database.yml +8 -0
- data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/mutations/db/schema.rb +27 -0
- data/examples/snippets/mutations/db/seeds.rb +25 -0
- data/examples/snippets/mutations/models/album.rb +9 -0
- data/examples/snippets/mutations/models/singer.rb +9 -0
- data/examples/snippets/optimistic-locking/README.md +12 -0
- data/examples/snippets/optimistic-locking/Rakefile +13 -0
- data/examples/snippets/optimistic-locking/application.rb +48 -0
- data/examples/snippets/optimistic-locking/config/database.yml +8 -0
- data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
- data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
- data/examples/snippets/optimistic-locking/models/album.rb +9 -0
- data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
- data/examples/snippets/quickstart/README.md +26 -0
- data/examples/snippets/quickstart/Rakefile +13 -0
- data/examples/snippets/quickstart/application.rb +51 -0
- data/examples/snippets/quickstart/config/database.yml +8 -0
- data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/quickstart/db/schema.rb +26 -0
- data/examples/snippets/quickstart/db/seeds.rb +24 -0
- data/examples/snippets/quickstart/models/album.rb +9 -0
- data/examples/snippets/quickstart/models/singer.rb +9 -0
- data/examples/snippets/read-only-transactions/README.md +13 -0
- data/examples/snippets/read-only-transactions/Rakefile +13 -0
- data/examples/snippets/read-only-transactions/application.rb +49 -0
- data/examples/snippets/read-only-transactions/config/database.yml +8 -0
- data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
- data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
- data/examples/snippets/read-only-transactions/models/album.rb +9 -0
- data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
- data/examples/snippets/read-write-transactions/README.md +12 -0
- data/examples/snippets/read-write-transactions/Rakefile +13 -0
- data/examples/snippets/read-write-transactions/application.rb +39 -0
- data/examples/snippets/read-write-transactions/config/database.yml +8 -0
- data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
- data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
- data/examples/snippets/read-write-transactions/models/album.rb +9 -0
- data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
- data/examples/snippets/timestamp-data-type/README.md +17 -0
- data/examples/snippets/timestamp-data-type/Rakefile +13 -0
- data/examples/snippets/timestamp-data-type/application.rb +42 -0
- data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
- data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
- data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
- data/examples/solidus/README.md +172 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +224 -269
- data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
- data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
- data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/spanner_adapter.rb +182 -78
- data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
- data/lib/active_record/type/spanner/array.rb +32 -0
- data/lib/active_record/type/spanner/bytes.rb +26 -0
- data/lib/active_record/type/spanner/spanner_active_record_converter.rb +32 -0
- data/lib/active_record/type/spanner/time.rb +37 -0
- data/lib/activerecord-spanner-adapter.rb +23 -0
- data/lib/activerecord_spanner_adapter/base.rb +217 -0
- data/lib/activerecord_spanner_adapter/connection.rb +324 -0
- data/lib/activerecord_spanner_adapter/errors.rb +13 -0
- data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
- data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
- data/lib/activerecord_spanner_adapter/index.rb +80 -0
- data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
- data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
- data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
- data/lib/activerecord_spanner_adapter/table.rb +61 -0
- data/lib/activerecord_spanner_adapter/transaction.rb +113 -0
- data/lib/activerecord_spanner_adapter/version.rb +9 -0
- data/lib/arel/visitors/spanner.rb +35 -0
- data/lib/spanner_client_ext.rb +82 -0
- data/renovate.json +5 -0
- metadata +387 -34
- data/.travis.yml +0 -5
- data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
- data/lib/active_record/connection_adapters/spanner.rb +0 -10
- data/lib/activerecord-spanner-adapter/version.rb +0 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright 2020 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
|
+
module ActiveRecord
|
|
10
|
+
module Type
|
|
11
|
+
module Spanner
|
|
12
|
+
class Bytes < ActiveRecord::Type::Binary
|
|
13
|
+
def serialize value
|
|
14
|
+
return super value if value.nil?
|
|
15
|
+
|
|
16
|
+
if value.respond_to?(:read) && value.respond_to?(:rewind)
|
|
17
|
+
value.rewind
|
|
18
|
+
value = value.read
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright 2021 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
|
+
module ActiveRecord
|
|
10
|
+
module Type
|
|
11
|
+
module Spanner
|
|
12
|
+
class SpannerActiveRecordConverter
|
|
13
|
+
##
|
|
14
|
+
# Converts an ActiveModel::Type to a Spanner type code.
|
|
15
|
+
def self.convert_active_model_type_to_spanner type # rubocop:disable Metrics/CyclomaticComplexity
|
|
16
|
+
case type
|
|
17
|
+
when NilClass then nil
|
|
18
|
+
when ActiveModel::Type::Integer, ActiveModel::Type::BigInteger then :INT64
|
|
19
|
+
when ActiveModel::Type::Boolean then :BOOL
|
|
20
|
+
when ActiveModel::Type::String, ActiveModel::Type::ImmutableString then :STRING
|
|
21
|
+
when ActiveModel::Type::Binary, ActiveRecord::Type::Spanner::Bytes then :BYTES
|
|
22
|
+
when ActiveModel::Type::Float then :FLOAT64
|
|
23
|
+
when ActiveModel::Type::Decimal then :NUMERIC
|
|
24
|
+
when ActiveModel::Type::DateTime, ActiveModel::Type::Time, ActiveRecord::Type::Spanner::Time then :TIMESTAMP
|
|
25
|
+
when ActiveModel::Type::Date then :DATE
|
|
26
|
+
when ActiveRecord::Type::Spanner::Array then [convert_active_model_type_to_spanner(type.element_type)]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright 2020 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
|
+
module ActiveRecord
|
|
10
|
+
module Type
|
|
11
|
+
module Spanner
|
|
12
|
+
class Time < ActiveRecord::Type::Time
|
|
13
|
+
def serialize value, *options
|
|
14
|
+
return "PENDING_COMMIT_TIMESTAMP()" if value == :commit_timestamp && options.length && options[0] == :dml
|
|
15
|
+
return "spanner.commit_timestamp()" if value == :commit_timestamp && options.length && options[0] == :mutation
|
|
16
|
+
val = super value
|
|
17
|
+
val.acts_like?(:time) ? val.utc.rfc3339(9) : val
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def user_input_in_time_zone value
|
|
21
|
+
return value.in_time_zone if value.is_a? ::Time
|
|
22
|
+
super value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def cast_value value
|
|
28
|
+
if value.is_a? ::String
|
|
29
|
+
value = value.empty? ? nil : ::Time.parse(value)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
value
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright 2020 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
|
+
require "activerecord_spanner_adapter/version"
|
|
8
|
+
|
|
9
|
+
if defined?(Rails)
|
|
10
|
+
module ActiveRecord
|
|
11
|
+
module ConnectionAdapters
|
|
12
|
+
class SpannerRailtie < ::Rails::Railtie
|
|
13
|
+
rake_tasks do
|
|
14
|
+
require "active_record/tasks/spanner_database_tasks"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ActiveSupport.on_load :active_record do
|
|
18
|
+
require "active_record/connection_adapters/spanner_adapter"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Copyright 2021 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
|
+
module ActiveRecord
|
|
8
|
+
class TableMetadata # :nodoc:
|
|
9
|
+
# This attr_reader is private in ActiveRecord 6.0.x and public in 6.1.x. This makes sure it is always available in
|
|
10
|
+
# the Spanner adapter.
|
|
11
|
+
attr_reader :arel_table
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Base
|
|
15
|
+
# Creates an object (or multiple objects) and saves it to the database. This method will use mutations instead
|
|
16
|
+
# of DML if there is no active transaction, or if the active transaction has been created with the option
|
|
17
|
+
# isolation: :buffered_mutations.
|
|
18
|
+
def self.create attributes = nil, &block
|
|
19
|
+
return super if active_transaction?
|
|
20
|
+
|
|
21
|
+
transaction isolation: :buffered_mutations do
|
|
22
|
+
return super
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self._insert_record values
|
|
27
|
+
return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
|
|
28
|
+
|
|
29
|
+
primary_key = self.primary_key
|
|
30
|
+
primary_key_value = nil
|
|
31
|
+
|
|
32
|
+
if primary_key && values.is_a?(Hash)
|
|
33
|
+
primary_key_value = values[primary_key]
|
|
34
|
+
|
|
35
|
+
if !primary_key_value && prefetch_primary_key?
|
|
36
|
+
primary_key_value = next_sequence_value
|
|
37
|
+
values[primary_key] = primary_key_value
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
metadata = TableMetadata.new self, arel_table
|
|
42
|
+
columns, grpc_values = _create_grpc_values_for_insert metadata, values
|
|
43
|
+
|
|
44
|
+
mutation = Google::Cloud::Spanner::V1::Mutation.new(
|
|
45
|
+
insert: Google::Cloud::Spanner::V1::Mutation::Write.new(
|
|
46
|
+
table: arel_table.name,
|
|
47
|
+
columns: columns,
|
|
48
|
+
values: [grpc_values.list_value]
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
Base.connection.current_spanner_transaction.buffer mutation
|
|
52
|
+
|
|
53
|
+
primary_key_value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Deletes all records of this class. This method will use mutations instead of DML if there is no active
|
|
57
|
+
# transaction, or if the active transaction has been created with the option isolation: :buffered_mutations.
|
|
58
|
+
def self.delete_all
|
|
59
|
+
return super if active_transaction?
|
|
60
|
+
|
|
61
|
+
transaction isolation: :buffered_mutations do
|
|
62
|
+
return super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.active_transaction?
|
|
67
|
+
current_transaction = connection.current_transaction
|
|
68
|
+
!(current_transaction.nil? || current_transaction.is_a?(ConnectionAdapters::NullTransaction))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Updates the given attributes of the object in the database. This method will use mutations instead
|
|
72
|
+
# of DML if there is no active transaction, or if the active transaction has been created with the option
|
|
73
|
+
# isolation: :buffered_mutations.
|
|
74
|
+
def update attributes
|
|
75
|
+
return super if Base.active_transaction?
|
|
76
|
+
|
|
77
|
+
transaction isolation: :buffered_mutations do
|
|
78
|
+
return super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Deletes the object in the database. This method will use mutations instead
|
|
83
|
+
# of DML if there is no active transaction, or if the active transaction has been created with the option
|
|
84
|
+
# isolation: :buffered_mutations.
|
|
85
|
+
def destroy
|
|
86
|
+
return super if Base.active_transaction?
|
|
87
|
+
|
|
88
|
+
transaction isolation: :buffered_mutations do
|
|
89
|
+
return super
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def self._create_grpc_values_for_insert metadata, values
|
|
96
|
+
serialized_values = []
|
|
97
|
+
columns = []
|
|
98
|
+
values.each_pair do |k, v|
|
|
99
|
+
type = metadata.type k
|
|
100
|
+
serialized_values << (type.method(:serialize).arity < 0 ? type.serialize(v, :mutation) : type.serialize(v))
|
|
101
|
+
columns << metadata.arel_table[k].name
|
|
102
|
+
end
|
|
103
|
+
[columns, Google::Protobuf::Value.new(list_value:
|
|
104
|
+
Google::Protobuf::ListValue.new(
|
|
105
|
+
values: serialized_values.map do |value|
|
|
106
|
+
Google::Cloud::Spanner::Convert.object_to_grpc_value value
|
|
107
|
+
end
|
|
108
|
+
))]
|
|
109
|
+
end
|
|
110
|
+
private_class_method :_create_grpc_values_for_insert
|
|
111
|
+
|
|
112
|
+
def _update_row attribute_names, attempted_action = "update"
|
|
113
|
+
return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
|
|
114
|
+
|
|
115
|
+
if locking_enabled?
|
|
116
|
+
_execute_version_check attempted_action
|
|
117
|
+
attribute_names << self.class.locking_column
|
|
118
|
+
self[self.class.locking_column] += 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
metadata = TableMetadata.new self.class, self.class.arel_table
|
|
122
|
+
values = attributes_with_values attribute_names
|
|
123
|
+
columns, grpc_values = _create_grpc_values_for_update metadata, values
|
|
124
|
+
|
|
125
|
+
mutation = Google::Cloud::Spanner::V1::Mutation.new(
|
|
126
|
+
update: Google::Cloud::Spanner::V1::Mutation::Write.new(
|
|
127
|
+
table: self.class.arel_table.name,
|
|
128
|
+
columns: columns,
|
|
129
|
+
values: [grpc_values.list_value]
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
Base.connection.current_spanner_transaction.buffer mutation
|
|
133
|
+
1 # Affected rows
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def _create_grpc_values_for_update metadata, values
|
|
137
|
+
constraints = {}
|
|
138
|
+
keys = self.class.primary_and_parent_key
|
|
139
|
+
keys.each do |key|
|
|
140
|
+
constraints[key] = attribute_in_database key
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Use both the where values + the values that are actually set.
|
|
144
|
+
all_values = [constraints, values]
|
|
145
|
+
all_serialized_values = []
|
|
146
|
+
all_columns = []
|
|
147
|
+
all_values.each do |h|
|
|
148
|
+
h.each_pair do |k, v|
|
|
149
|
+
type = metadata.type k
|
|
150
|
+
has_serialize_options = type.method(:serialize).arity < 0
|
|
151
|
+
all_serialized_values << (has_serialize_options ? type.serialize(v, :mutation) : type.serialize(v))
|
|
152
|
+
all_columns << metadata.arel_table[k].name
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
[all_columns, Google::Protobuf::Value.new(list_value:
|
|
156
|
+
Google::Protobuf::ListValue.new(
|
|
157
|
+
values: all_serialized_values.map do |value|
|
|
158
|
+
Google::Cloud::Spanner::Convert.object_to_grpc_value value
|
|
159
|
+
end
|
|
160
|
+
))]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def destroy_row
|
|
164
|
+
return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
|
|
165
|
+
|
|
166
|
+
_delete_row
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def _delete_row
|
|
170
|
+
return super unless Base.connection&.current_spanner_transaction&.isolation == :buffered_mutations
|
|
171
|
+
if locking_enabled?
|
|
172
|
+
_execute_version_check "destroy"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
metadata = TableMetadata.new self.class, self.class.arel_table
|
|
176
|
+
keys = self.class.primary_and_parent_key
|
|
177
|
+
serialized_values = serialize_keys metadata, keys
|
|
178
|
+
list_value = Google::Protobuf::ListValue.new(
|
|
179
|
+
values: serialized_values.map do |value|
|
|
180
|
+
Google::Cloud::Spanner::Convert.object_to_grpc_value value
|
|
181
|
+
end
|
|
182
|
+
)
|
|
183
|
+
mutation = Google::Cloud::Spanner::V1::Mutation.new(
|
|
184
|
+
delete: Google::Cloud::Spanner::V1::Mutation::Delete.new(
|
|
185
|
+
table: self.class.arel_table.name,
|
|
186
|
+
key_set: { keys: [list_value] }
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
Base.connection.current_spanner_transaction.buffer mutation
|
|
190
|
+
1 # Affected rows
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def serialize_keys metadata, keys
|
|
194
|
+
serialized_values = []
|
|
195
|
+
keys.each do |key|
|
|
196
|
+
type = metadata.type key
|
|
197
|
+
has_serialize_options = type.method(:serialize).arity < 0
|
|
198
|
+
serialized_values << type.serialize(attribute_in_database(key), :mutation) if has_serialize_options
|
|
199
|
+
serialized_values << type.serialize(attribute_in_database(key)) unless has_serialize_options
|
|
200
|
+
end
|
|
201
|
+
serialized_values
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def _execute_version_check attempted_action
|
|
205
|
+
locking_column = self.class.locking_column
|
|
206
|
+
previous_lock_value = read_attribute_before_type_cast locking_column
|
|
207
|
+
|
|
208
|
+
# We need to check the version using a SELECT query, as a mutation cannot include a WHERE clause.
|
|
209
|
+
sql = "SELECT 1 FROM `#{self.class.arel_table.name}` " \
|
|
210
|
+
"WHERE `#{self.class.primary_key}` = @id AND `#{locking_column}` = @lock_version"
|
|
211
|
+
params = { "id" => id_in_database, "lock_version" => previous_lock_value }
|
|
212
|
+
param_types = { "id" => :INT64, "lock_version" => :INT64 }
|
|
213
|
+
locked_row = Base.connection.raw_connection.execute_query sql, params: params, types: param_types
|
|
214
|
+
raise ActiveRecord::StaleObjectError.new(self, attempted_action) unless locked_row.rows.any?
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Copyright 2020 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
|
+
require "google/cloud/spanner"
|
|
8
|
+
require "spanner_client_ext"
|
|
9
|
+
require "activerecord_spanner_adapter/information_schema"
|
|
10
|
+
|
|
11
|
+
module ActiveRecordSpannerAdapter
|
|
12
|
+
class Connection
|
|
13
|
+
attr_reader :instance_id, :database_id, :spanner
|
|
14
|
+
attr_accessor :current_transaction
|
|
15
|
+
|
|
16
|
+
def initialize config
|
|
17
|
+
@instance_id = config[:instance]
|
|
18
|
+
@database_id = config[:database]
|
|
19
|
+
@spanner = self.class.spanners config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.spanners config
|
|
23
|
+
config = config.symbolize_keys
|
|
24
|
+
@spanners ||= {}
|
|
25
|
+
@mutex ||= Mutex.new
|
|
26
|
+
@mutex.synchronize do
|
|
27
|
+
@spanners[database_path(config)] ||= Google::Cloud::Spanner.new(
|
|
28
|
+
project_id: config[:project],
|
|
29
|
+
credentials: config[:credentials],
|
|
30
|
+
emulator_host: config[:emulator_host],
|
|
31
|
+
scope: config[:scope],
|
|
32
|
+
timeout: config[:timeout],
|
|
33
|
+
lib_name: "spanner-activerecord-adapter",
|
|
34
|
+
lib_version: ActiveRecordSpannerAdapter::VERSION
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.information_schema config
|
|
40
|
+
@information_schemas ||= {}
|
|
41
|
+
@information_schemas[database_path(config)] ||= \
|
|
42
|
+
ActiveRecordSpannerAdapter::InformationSchema.new new(config)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def session
|
|
46
|
+
@last_used = Time.current
|
|
47
|
+
@session ||= spanner.create_session instance_id, database_id
|
|
48
|
+
end
|
|
49
|
+
alias connect! session
|
|
50
|
+
|
|
51
|
+
def active?
|
|
52
|
+
# This method should not initialize a session.
|
|
53
|
+
unless @session
|
|
54
|
+
return false
|
|
55
|
+
end
|
|
56
|
+
# Assume that it is still active if it has been used in the past 50 minutes.
|
|
57
|
+
if ((Time.current - @last_used) / 60).round < 50
|
|
58
|
+
return true
|
|
59
|
+
end
|
|
60
|
+
session.execute_query "SELECT 1"
|
|
61
|
+
true
|
|
62
|
+
rescue StandardError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def disconnect!
|
|
67
|
+
session.release!
|
|
68
|
+
true
|
|
69
|
+
ensure
|
|
70
|
+
@session = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def reset!
|
|
74
|
+
disconnect!
|
|
75
|
+
session
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Database Operations
|
|
80
|
+
|
|
81
|
+
def create_database
|
|
82
|
+
job = spanner.create_database instance_id, database_id
|
|
83
|
+
job.wait_until_done!
|
|
84
|
+
raise Google::Cloud::Error.from_error job.error if job.error?
|
|
85
|
+
job.database
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def database
|
|
89
|
+
@database ||= begin
|
|
90
|
+
database = spanner.database instance_id, database_id
|
|
91
|
+
unless database
|
|
92
|
+
raise ActiveRecord::NoDatabaseError(
|
|
93
|
+
"#{spanner.project}/#{instance_id}/#{database_id}"
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
database
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# DDL Statements
|
|
101
|
+
|
|
102
|
+
# @params [Array<String>, String] sql Single or list of statements
|
|
103
|
+
def execute_ddl statements, operation_id: nil, wait_until_done: true
|
|
104
|
+
raise "DDL cannot be executed during a transaction" if current_transaction&.active?
|
|
105
|
+
self.current_transaction = nil
|
|
106
|
+
|
|
107
|
+
statements = Array statements
|
|
108
|
+
return unless statements.any?
|
|
109
|
+
|
|
110
|
+
# If a DDL batch is active we only buffer the statements on the connection until the batch is run.
|
|
111
|
+
if @ddl_batch
|
|
112
|
+
@ddl_batch.push(*statements)
|
|
113
|
+
return true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
execute_ddl_statements statements, operation_id, wait_until_done
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# DDL Batching
|
|
120
|
+
|
|
121
|
+
##
|
|
122
|
+
# Executes a set of DDL statements as one batch. This method raises an error if no block is given.
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
# connection.ddl_batch do
|
|
126
|
+
# connection.execute_ddl "CREATE TABLE `Users` (Id INT64, Name STRING(MAX)) PRIMARY KEY (Id)"
|
|
127
|
+
# connection.execute_ddl "CREATE INDEX Idx_Users_Name ON `Users` (Name)"
|
|
128
|
+
# end
|
|
129
|
+
def ddl_batch
|
|
130
|
+
raise Google::Cloud::FailedPreconditionError, "No block given for the DDL batch" unless block_given?
|
|
131
|
+
begin
|
|
132
|
+
start_batch_ddl
|
|
133
|
+
yield
|
|
134
|
+
run_batch
|
|
135
|
+
rescue StandardError
|
|
136
|
+
abort_batch
|
|
137
|
+
raise
|
|
138
|
+
ensure
|
|
139
|
+
@ddl_batch = nil
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Returns true if this connection is currently executing a DDL batch, and otherwise false.
|
|
145
|
+
def ddl_batch?
|
|
146
|
+
return true if @ddl_batch
|
|
147
|
+
false
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
# Starts a manual DDL batch. The batch must be ended by calling either run_batch or abort_batch.
|
|
152
|
+
#
|
|
153
|
+
# @example
|
|
154
|
+
# begin
|
|
155
|
+
# connection.start_batch_ddl
|
|
156
|
+
# connection.execute_ddl "CREATE TABLE `Users` (Id INT64, Name STRING(MAX)) PRIMARY KEY (Id)"
|
|
157
|
+
# connection.execute_ddl "CREATE INDEX Idx_Users_Name ON `Users` (Name)"
|
|
158
|
+
# connection.run_batch
|
|
159
|
+
# rescue StandardError
|
|
160
|
+
# connection.abort_batch
|
|
161
|
+
# raise
|
|
162
|
+
# end
|
|
163
|
+
def start_batch_ddl
|
|
164
|
+
if @ddl_batch
|
|
165
|
+
raise Google::Cloud::FailedPreconditionError, "A DDL batch is already active on this connection"
|
|
166
|
+
end
|
|
167
|
+
@ddl_batch = []
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
##
|
|
171
|
+
# Aborts the current batch on this connection. This is a no-op if there is no batch on this connection.
|
|
172
|
+
#
|
|
173
|
+
# @see start_batch_ddl
|
|
174
|
+
def abort_batch
|
|
175
|
+
@ddl_batch = nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
# Runs the current batch on this connection. This will raise a FailedPreconditionError if there is no
|
|
180
|
+
# active batch on this connection.
|
|
181
|
+
#
|
|
182
|
+
# @see start_batch_ddl
|
|
183
|
+
def run_batch
|
|
184
|
+
unless @ddl_batch
|
|
185
|
+
raise Google::Cloud::FailedPreconditionError, "There is no batch active on this connection"
|
|
186
|
+
end
|
|
187
|
+
# Just return if the batch is empty.
|
|
188
|
+
return true if @ddl_batch.empty?
|
|
189
|
+
begin
|
|
190
|
+
execute_ddl_statements @ddl_batch, nil, true
|
|
191
|
+
ensure
|
|
192
|
+
@ddl_batch = nil
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# DQL, DML Statements
|
|
197
|
+
|
|
198
|
+
def execute_query sql, params: nil, types: nil
|
|
199
|
+
if params
|
|
200
|
+
converted_params, types = \
|
|
201
|
+
Google::Cloud::Spanner::Convert.to_input_params_and_types(
|
|
202
|
+
params, types
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Clear the transaction from the previous statement.
|
|
207
|
+
unless current_transaction&.active?
|
|
208
|
+
self.current_transaction = nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
begin
|
|
212
|
+
session.execute_query \
|
|
213
|
+
sql,
|
|
214
|
+
params: converted_params,
|
|
215
|
+
types: types,
|
|
216
|
+
transaction: transaction_selector,
|
|
217
|
+
seqno: (current_transaction&.next_sequence_number)
|
|
218
|
+
rescue Google::Cloud::AbortedError
|
|
219
|
+
# Mark the current transaction as aborted to prevent any unnecessary further requests on the transaction.
|
|
220
|
+
current_transaction&.mark_aborted
|
|
221
|
+
raise
|
|
222
|
+
rescue Google::Cloud::NotFoundError => e
|
|
223
|
+
if session_not_found?(e) || transaction_not_found?(e)
|
|
224
|
+
reset!
|
|
225
|
+
# Force a retry of the entire transaction if this statement was executed as part of a transaction.
|
|
226
|
+
# Otherwise, just retry the statement itself.
|
|
227
|
+
raise_aborted_err if current_transaction&.active?
|
|
228
|
+
retry
|
|
229
|
+
end
|
|
230
|
+
raise
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Transactions
|
|
235
|
+
|
|
236
|
+
def begin_transaction isolation = nil
|
|
237
|
+
raise "Nested transactions are not allowed" if current_transaction&.active?
|
|
238
|
+
self.current_transaction = Transaction.new self, isolation
|
|
239
|
+
current_transaction.begin
|
|
240
|
+
current_transaction
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def commit_transaction
|
|
244
|
+
raise "This connection does not have a transaction" unless current_transaction
|
|
245
|
+
current_transaction.commit
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def rollback_transaction
|
|
249
|
+
raise "This connection does not have a transaction" unless current_transaction
|
|
250
|
+
current_transaction.rollback
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def transaction_selector
|
|
254
|
+
return current_transaction&.transaction_selector if current_transaction&.active?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def truncate table_name
|
|
258
|
+
session.delete table_name
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def self.database_path config
|
|
262
|
+
"#{config[:emulator_host]}/#{config[:project]}/#{config[:instance]}/#{config[:database]}"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def session_not_found? err
|
|
266
|
+
if err.respond_to?(:metadata) && err.metadata["google.rpc.resourceinfo-bin"]
|
|
267
|
+
resource_info = Google::Rpc::ResourceInfo.decode err.metadata["google.rpc.resourceinfo-bin"]
|
|
268
|
+
type = resource_info["resource_type"]
|
|
269
|
+
return "type.googleapis.com/google.spanner.v1.Session".eql? type
|
|
270
|
+
end
|
|
271
|
+
false
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def transaction_not_found? err
|
|
275
|
+
if err.respond_to?(:metadata) && err.metadata["google.rpc.resourceinfo-bin"]
|
|
276
|
+
resource_info = Google::Rpc::ResourceInfo.decode err.metadata["google.rpc.resourceinfo-bin"]
|
|
277
|
+
type = resource_info["resource_type"]
|
|
278
|
+
return "type.googleapis.com/google.spanner.v1.Transaction".eql? type
|
|
279
|
+
end
|
|
280
|
+
false
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def raise_aborted_err
|
|
284
|
+
retry_info = Google::Rpc::RetryInfo.new retry_delay: Google::Protobuf::Duration.new(seconds: 0, nanos: 1)
|
|
285
|
+
begin
|
|
286
|
+
raise GRPC::BadStatus.new(
|
|
287
|
+
GRPC::Core::StatusCodes::ABORTED,
|
|
288
|
+
"Transaction aborted",
|
|
289
|
+
"google.rpc.retryinfo-bin": Google::Rpc::RetryInfo.encode(retry_info)
|
|
290
|
+
)
|
|
291
|
+
rescue GRPC::BadStatus
|
|
292
|
+
raise Google::Cloud::AbortedError
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
private
|
|
297
|
+
|
|
298
|
+
def execute_ddl_statements statements, operation_id, wait_until_done
|
|
299
|
+
job = database.update statements: statements, operation_id: operation_id
|
|
300
|
+
job.wait_until_done! if wait_until_done
|
|
301
|
+
raise Google::Cloud::Error.from_error job.error if job.error?
|
|
302
|
+
job.done?
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
##
|
|
306
|
+
# Retrieves the delay value from Google::Cloud::AbortedError or
|
|
307
|
+
# GRPC::Aborted
|
|
308
|
+
def delay_from_aborted err
|
|
309
|
+
return nil if err.nil?
|
|
310
|
+
if err.respond_to?(:metadata) && err.metadata["google.rpc.retryinfo-bin"]
|
|
311
|
+
retry_info = Google::Rpc::RetryInfo.decode err.metadata["google.rpc.retryinfo-bin"]
|
|
312
|
+
seconds = retry_info["retry_delay"].seconds
|
|
313
|
+
nanos = retry_info["retry_delay"].nanos
|
|
314
|
+
return seconds if nanos.zero?
|
|
315
|
+
return seconds + (nanos / 1_000_000_000.0)
|
|
316
|
+
end
|
|
317
|
+
# No metadata? Try the inner error
|
|
318
|
+
delay_from_aborted err.cause
|
|
319
|
+
rescue StandardError
|
|
320
|
+
# Any error indicates the backoff should be handled elsewhere
|
|
321
|
+
nil
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|