activerecord-spanner-adapter 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +49 -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 +55 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +9 -4
- data/LICENSE +6 -6
- data/README.md +66 -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 +171 -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 +130 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
- data/acceptance/cases/type/all_types_test.rb +172 -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/json_test.rb +34 -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 +147 -0
- data/acceptance/test_helper.rb +261 -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/hints/README.md +19 -0
- data/examples/snippets/hints/Rakefile +13 -0
- data/examples/snippets/hints/application.rb +47 -0
- data/examples/snippets/hints/config/database.yml +8 -0
- data/examples/snippets/hints/db/migrate/01_create_tables.rb +23 -0
- data/examples/snippets/hints/db/schema.rb +28 -0
- data/examples/snippets/hints/db/seeds.rb +29 -0
- data/examples/snippets/hints/models/album.rb +9 -0
- data/examples/snippets/hints/models/singer.rb +9 -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/partitioned-dml/README.md +16 -0
- data/examples/snippets/partitioned-dml/Rakefile +13 -0
- data/examples/snippets/partitioned-dml/application.rb +48 -0
- data/examples/snippets/partitioned-dml/config/database.yml +8 -0
- data/examples/snippets/partitioned-dml/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/partitioned-dml/db/schema.rb +26 -0
- data/examples/snippets/partitioned-dml/db/seeds.rb +29 -0
- data/examples/snippets/partitioned-dml/models/album.rb +9 -0
- data/examples/snippets/partitioned-dml/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 +77 -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/stale-reads/README.md +27 -0
- data/examples/snippets/stale-reads/Rakefile +13 -0
- data/examples/snippets/stale-reads/application.rb +63 -0
- data/examples/snippets/stale-reads/config/database.yml +8 -0
- data/examples/snippets/stale-reads/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/stale-reads/db/schema.rb +26 -0
- data/examples/snippets/stale-reads/db/seeds.rb +24 -0
- data/examples/snippets/stale-reads/models/album.rb +9 -0
- data/examples/snippets/stale-reads/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 +244 -266
- 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 +185 -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 +33 -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 +238 -0
- data/lib/activerecord_spanner_adapter/connection.rb +350 -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 +262 -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 +154 -0
- data/lib/activerecord_spanner_adapter/version.rb +9 -0
- data/lib/arel/visitors/spanner.rb +111 -0
- data/lib/spanner_client_ext.rb +107 -0
- data/renovate.json +5 -0
- metadata +405 -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,80 @@
|
|
|
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/index/column"
|
|
8
|
+
|
|
9
|
+
module ActiveRecordSpannerAdapter
|
|
10
|
+
class Index
|
|
11
|
+
attr_accessor :table, :name, :columns, :type, :unique, :null_filtered,
|
|
12
|
+
:interleave_in, :storing, :state
|
|
13
|
+
|
|
14
|
+
def initialize \
|
|
15
|
+
table,
|
|
16
|
+
name,
|
|
17
|
+
columns,
|
|
18
|
+
type: nil,
|
|
19
|
+
unique: false,
|
|
20
|
+
null_filtered: false,
|
|
21
|
+
interleave_in: nil,
|
|
22
|
+
storing: nil,
|
|
23
|
+
state: nil
|
|
24
|
+
@table = table.to_s
|
|
25
|
+
@name = name.to_s
|
|
26
|
+
@columns = Array(columns)
|
|
27
|
+
@type = type
|
|
28
|
+
@unique = unique
|
|
29
|
+
@null_filtered = null_filtered
|
|
30
|
+
@interleave_in = interleave_in unless interleave_in.to_s.empty?
|
|
31
|
+
@storing = storing || []
|
|
32
|
+
@state = state
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def primary?
|
|
36
|
+
@type == "PRIMARY_KEY"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def columns_by_position
|
|
40
|
+
@columns.select(&:ordinal_position).sort do |c1, c2|
|
|
41
|
+
c1.ordinal_position <=> c2.ordinal_position
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def column_names
|
|
46
|
+
columns_by_position.map(&:name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def orders
|
|
50
|
+
columns_by_position.each_with_object({}) do |c, r|
|
|
51
|
+
r[c.name] = c.desc? ? :desc : :asc
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def options
|
|
56
|
+
{
|
|
57
|
+
name: name,
|
|
58
|
+
order: orders,
|
|
59
|
+
unique: unique,
|
|
60
|
+
interleave_in: interleave_in,
|
|
61
|
+
null_filtered: null_filtered,
|
|
62
|
+
storing: storing
|
|
63
|
+
}.delete_if { |_, v| v.nil? }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def rename_column_options old_column, new_column
|
|
67
|
+
opts = options
|
|
68
|
+
|
|
69
|
+
opts[:order].transform_keys do |key|
|
|
70
|
+
key.to_s == new_column.to_s
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
columns = column_names.map do |c|
|
|
74
|
+
c.to_s == old_column.to_s ? new_column : c
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
{ options: opts, columns: columns }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
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 "active_record/connection_adapters/abstract/quoting"
|
|
8
|
+
require "activerecord_spanner_adapter/information_schema"
|
|
9
|
+
require "activerecord_spanner_adapter/table"
|
|
10
|
+
require "activerecord_spanner_adapter/index"
|
|
11
|
+
require "activerecord_spanner_adapter/foreign_key"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
module ActiveRecordSpannerAdapter
|
|
15
|
+
class InformationSchema
|
|
16
|
+
include ActiveRecord::ConnectionAdapters::Quoting
|
|
17
|
+
|
|
18
|
+
attr_reader :connection
|
|
19
|
+
|
|
20
|
+
def initialize connection
|
|
21
|
+
@connection = connection
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tables table_name: nil, schema_name: nil, view: nil
|
|
26
|
+
sql = +"SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, PARENT_TABLE_NAME, ON_DELETE_ACTION"
|
|
27
|
+
sql << " FROM INFORMATION_SCHEMA.TABLES"
|
|
28
|
+
sql << " WHERE TABLE_SCHEMA=%<schema_name>s"
|
|
29
|
+
sql << " AND TABLE_NAME=%<table_name>s" if table_name
|
|
30
|
+
|
|
31
|
+
rows = execute_query(
|
|
32
|
+
sql,
|
|
33
|
+
schema_name: (schema_name || ""), table_name: table_name
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
rows.map do |row|
|
|
37
|
+
table = Table.new(
|
|
38
|
+
row["TABLE_NAME"],
|
|
39
|
+
parent_table: row["PARENT_TABLE_NAME"],
|
|
40
|
+
on_delete: row["ON_DELETE_ACTION"],
|
|
41
|
+
schema_name: row["TABLE_SCHEMA"],
|
|
42
|
+
catalog: row["TABLE_CATALOG"]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if [:full, :columns].include? view
|
|
46
|
+
table.columns = table_columns table.name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if [:full, :indexes].include? view
|
|
50
|
+
table.indexes = indexes table.name
|
|
51
|
+
end
|
|
52
|
+
table
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def table table_name, schema_name: nil, view: nil
|
|
57
|
+
tables(
|
|
58
|
+
table_name: table_name,
|
|
59
|
+
schema_name: schema_name,
|
|
60
|
+
view: view
|
|
61
|
+
).first
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def table_columns table_name, column_name: nil
|
|
65
|
+
sql = +"SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE,"
|
|
66
|
+
sql << " CAST(COLUMN_DEFAULT AS STRING) AS COLUMN_DEFAULT, ORDINAL_POSITION"
|
|
67
|
+
sql << " FROM INFORMATION_SCHEMA.COLUMNS"
|
|
68
|
+
sql << " WHERE TABLE_NAME=%<table_name>s"
|
|
69
|
+
sql << " AND COLUMN_NAME=%<column_name>s" if column_name
|
|
70
|
+
sql << " ORDER BY ORDINAL_POSITION ASC"
|
|
71
|
+
|
|
72
|
+
execute_query(
|
|
73
|
+
sql,
|
|
74
|
+
table_name: table_name,
|
|
75
|
+
column_name: column_name
|
|
76
|
+
).map do |row|
|
|
77
|
+
type, limit = parse_type_and_limit row["SPANNER_TYPE"]
|
|
78
|
+
Table::Column.new \
|
|
79
|
+
table_name,
|
|
80
|
+
row["COLUMN_NAME"],
|
|
81
|
+
type,
|
|
82
|
+
limit: limit,
|
|
83
|
+
ordinal_position: row["ORDINAL_POSITION"],
|
|
84
|
+
nullable: row["IS_NULLABLE"] == "YES",
|
|
85
|
+
default: row["COLUMN_DEFAULT"]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def table_column table_name, column_name
|
|
90
|
+
table_columns(table_name, column_name: column_name).first
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the primary key columns of the given table. By default it will only return the columns that are not part
|
|
94
|
+
# of the primary key of the parent table (if any). These are the columns that are considered the primary key by
|
|
95
|
+
# ActiveRecord. The parent primary key columns are filtered out by default to allow interleaved tables to be
|
|
96
|
+
# considered as tables with a single-column primary key by ActiveRecord. The actual primary key of the table will
|
|
97
|
+
# include both the parent primary key columns and the 'own' primary key columns of a table.
|
|
98
|
+
def table_primary_keys table_name, include_parent_keys = false
|
|
99
|
+
sql = +"WITH TABLE_PK_COLS AS ( "
|
|
100
|
+
sql << "SELECT C.TABLE_NAME, C.COLUMN_NAME, C.INDEX_NAME, C.COLUMN_ORDERING, C.ORDINAL_POSITION "
|
|
101
|
+
sql << "FROM INFORMATION_SCHEMA.INDEX_COLUMNS C "
|
|
102
|
+
sql << "WHERE C.INDEX_TYPE = 'PRIMARY_KEY' "
|
|
103
|
+
sql << "AND TABLE_CATALOG = '' "
|
|
104
|
+
sql << "AND TABLE_SCHEMA = '') "
|
|
105
|
+
sql << "SELECT INDEX_NAME, COLUMN_NAME, COLUMN_ORDERING, ORDINAL_POSITION "
|
|
106
|
+
sql << "FROM TABLE_PK_COLS "
|
|
107
|
+
sql << "INNER JOIN INFORMATION_SCHEMA.TABLES T USING (TABLE_NAME) "
|
|
108
|
+
sql << "WHERE TABLE_NAME = %<table_name>s "
|
|
109
|
+
sql << "AND TABLE_CATALOG = '' "
|
|
110
|
+
sql << "AND TABLE_SCHEMA = '' "
|
|
111
|
+
unless include_parent_keys
|
|
112
|
+
sql << "AND (T.PARENT_TABLE_NAME IS NULL OR COLUMN_NAME NOT IN ( "
|
|
113
|
+
sql << " SELECT COLUMN_NAME "
|
|
114
|
+
sql << " FROM TABLE_PK_COLS "
|
|
115
|
+
sql << " WHERE TABLE_NAME = T.PARENT_TABLE_NAME "
|
|
116
|
+
sql << ")) "
|
|
117
|
+
end
|
|
118
|
+
sql << "ORDER BY ORDINAL_POSITION"
|
|
119
|
+
execute_query(
|
|
120
|
+
sql,
|
|
121
|
+
table_name: table_name
|
|
122
|
+
).map do |row|
|
|
123
|
+
Index::Column.new \
|
|
124
|
+
table_name,
|
|
125
|
+
row["INDEX_NAME"],
|
|
126
|
+
row["COLUMN_NAME"],
|
|
127
|
+
order: row["COLUMN_ORDERING"],
|
|
128
|
+
ordinal_position: row["ORDINAL_POSITION"]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def indexes table_name, index_name: nil, index_type: nil
|
|
133
|
+
table_indexes_columns = index_columns(
|
|
134
|
+
table_name,
|
|
135
|
+
index_name: index_name
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
sql = +"SELECT INDEX_NAME, INDEX_TYPE, IS_UNIQUE, IS_NULL_FILTERED, PARENT_TABLE_NAME, INDEX_STATE"
|
|
139
|
+
sql << " FROM INFORMATION_SCHEMA.INDEXES"
|
|
140
|
+
sql << " WHERE TABLE_NAME=%<table_name>s"
|
|
141
|
+
sql << " AND TABLE_CATALOG = ''"
|
|
142
|
+
sql << " AND TABLE_SCHEMA = ''"
|
|
143
|
+
sql << " AND INDEX_NAME=%<index_name>s" if index_name
|
|
144
|
+
sql << " AND INDEX_TYPE=%<index_type>s" if index_type
|
|
145
|
+
sql << " AND SPANNER_IS_MANAGED=FALSE"
|
|
146
|
+
|
|
147
|
+
execute_query(
|
|
148
|
+
sql,
|
|
149
|
+
table_name: table_name,
|
|
150
|
+
index_name: index_name,
|
|
151
|
+
index_type: index_type
|
|
152
|
+
).map do |row|
|
|
153
|
+
columns = []
|
|
154
|
+
storing = []
|
|
155
|
+
table_indexes_columns.each do |c|
|
|
156
|
+
next unless c.index_name == row["INDEX_NAME"]
|
|
157
|
+
if c.ordinal_position
|
|
158
|
+
columns << c
|
|
159
|
+
else
|
|
160
|
+
storing << c.name
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
Index.new \
|
|
165
|
+
table_name,
|
|
166
|
+
row["INDEX_NAME"],
|
|
167
|
+
columns,
|
|
168
|
+
type: row["INDEX_TYPE"],
|
|
169
|
+
unique: row["IS_UNIQUE"],
|
|
170
|
+
null_filtered: row["IS_NULL_FILTERED"],
|
|
171
|
+
interleave_in: row["PARENT_TABLE_NAME"],
|
|
172
|
+
storing: storing,
|
|
173
|
+
state: row["INDEX_STATE"]
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def index table_name, index_name
|
|
178
|
+
indexes(table_name, index_name: index_name).first
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def index_columns table_name, index_name: nil
|
|
182
|
+
sql = +"SELECT INDEX_NAME, COLUMN_NAME, COLUMN_ORDERING, ORDINAL_POSITION"
|
|
183
|
+
sql << " FROM INFORMATION_SCHEMA.INDEX_COLUMNS"
|
|
184
|
+
sql << " WHERE TABLE_NAME=%<table_name>s"
|
|
185
|
+
sql << " AND TABLE_CATALOG = ''"
|
|
186
|
+
sql << " AND TABLE_SCHEMA = ''"
|
|
187
|
+
sql << " AND INDEX_NAME=%<index_name>s" if index_name
|
|
188
|
+
sql << " ORDER BY ORDINAL_POSITION ASC"
|
|
189
|
+
|
|
190
|
+
execute_query(
|
|
191
|
+
sql,
|
|
192
|
+
table_name: table_name, index_name: index_name
|
|
193
|
+
).map do |row|
|
|
194
|
+
Index::Column.new \
|
|
195
|
+
table_name,
|
|
196
|
+
row["INDEX_NAME"],
|
|
197
|
+
row["COLUMN_NAME"],
|
|
198
|
+
order: row["COLUMN_ORDERING"],
|
|
199
|
+
ordinal_position: row["ORDINAL_POSITION"]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def indexes_by_columns table_name, column_names
|
|
204
|
+
column_names = Array(column_names).map(&:to_s)
|
|
205
|
+
|
|
206
|
+
indexes(table_name).select do |index|
|
|
207
|
+
index.columns.any? { |c| column_names.include? c.name }
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def foreign_keys table_name
|
|
212
|
+
sql = <<~SQL
|
|
213
|
+
SELECT cc.table_name AS to_table,
|
|
214
|
+
cc.column_name AS primary_key,
|
|
215
|
+
fk.column_name as column,
|
|
216
|
+
fk.constraint_name AS name,
|
|
217
|
+
rc.update_rule AS on_update,
|
|
218
|
+
rc.delete_rule AS on_delete
|
|
219
|
+
FROM information_schema.referential_constraints rc
|
|
220
|
+
INNER JOIN information_schema.key_column_usage fk ON rc.constraint_name = fk.constraint_name
|
|
221
|
+
INNER JOIN information_schema.constraint_column_usage cc ON rc.constraint_name = cc.constraint_name
|
|
222
|
+
WHERE fk.table_name = %<table_name>s
|
|
223
|
+
AND fk.constraint_schema = %<constraint_schema>s
|
|
224
|
+
SQL
|
|
225
|
+
|
|
226
|
+
rows = execute_query(
|
|
227
|
+
sql, table_name: table_name, constraint_schema: ""
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
rows.map do |row|
|
|
231
|
+
ForeignKey.new(
|
|
232
|
+
table_name,
|
|
233
|
+
row["name"],
|
|
234
|
+
row["column"],
|
|
235
|
+
row["to_table"],
|
|
236
|
+
row["primary_key"],
|
|
237
|
+
on_delete: row["on_delete"],
|
|
238
|
+
on_update: row["on_update"]
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def parse_type_and_limit value
|
|
244
|
+
matched = /^([A-Z]*)\((.*)\)/.match value
|
|
245
|
+
return [value] unless matched
|
|
246
|
+
|
|
247
|
+
limit = matched[2]
|
|
248
|
+
limit = limit.to_i unless limit == "MAX"
|
|
249
|
+
|
|
250
|
+
[matched[1], limit]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
private
|
|
254
|
+
|
|
255
|
+
def execute_query sql, params = {}
|
|
256
|
+
params = params.transform_values { |v| quote v }
|
|
257
|
+
sql = format sql, params
|
|
258
|
+
|
|
259
|
+
@connection.execute_query(sql).rows
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
module ActiveRecord
|
|
8
|
+
module AttributeMethods
|
|
9
|
+
module PrimaryKey
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def primary_and_parent_key
|
|
12
|
+
reset_primary_and_parent_key unless defined? @primary_and_parent_key
|
|
13
|
+
@primary_and_parent_key
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset_primary_and_parent_key
|
|
17
|
+
self.primary_and_parent_key = base_class? ? fetch_primary_and_parent_key : base_class.primary_and_parent_key
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def fetch_primary_and_parent_key
|
|
21
|
+
return connection.schema_cache.primary_and_parent_keys table_name \
|
|
22
|
+
if ActiveRecord::Base != self && table_exists?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def primary_and_parent_key= value
|
|
26
|
+
@primary_and_parent_key = value
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
module ActiveRecordSpannerAdapter
|
|
8
|
+
class Table
|
|
9
|
+
class Column
|
|
10
|
+
attr_accessor :table_name, :name, :type, :limit, :ordinal_position,
|
|
11
|
+
:allow_commit_timestamp, :default, :primary_key
|
|
12
|
+
attr_writer :nullable
|
|
13
|
+
|
|
14
|
+
def initialize \
|
|
15
|
+
table_name,
|
|
16
|
+
name,
|
|
17
|
+
type,
|
|
18
|
+
limit: nil,
|
|
19
|
+
ordinal_position: nil,
|
|
20
|
+
nullable: true,
|
|
21
|
+
allow_commit_timestamp: nil,
|
|
22
|
+
default: nil
|
|
23
|
+
@table_name = table_name.to_s
|
|
24
|
+
@name = name.to_s
|
|
25
|
+
@type = type
|
|
26
|
+
@limit = limit
|
|
27
|
+
@nullable = nullable != false
|
|
28
|
+
@ordinal_position = ordinal_position
|
|
29
|
+
@allow_commit_timestamp = allow_commit_timestamp
|
|
30
|
+
@default = default
|
|
31
|
+
@primary_key = false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def nullable
|
|
35
|
+
return false if primary_key
|
|
36
|
+
@nullable
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def spanner_type
|
|
40
|
+
return "#{type}(#{limit || 'MAX'})" if limit_allowed?
|
|
41
|
+
type
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def options
|
|
45
|
+
{
|
|
46
|
+
limit: limit,
|
|
47
|
+
null: nullable,
|
|
48
|
+
allow_commit_timestamp: allow_commit_timestamp
|
|
49
|
+
}.delete_if { |_, v| v.nil? }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def limit_allowed?
|
|
55
|
+
["BYTES", "STRING"].include? type
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# The MIT License (MIT)
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2020 Google LLC.
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
|
13
|
+
# all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
# THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
# Copyright 2020 Google LLC
|
|
24
|
+
#
|
|
25
|
+
# Use of this source code is governed by an MIT-style
|
|
26
|
+
# license that can be found in the LICENSE file or at
|
|
27
|
+
# https://opensource.org/licenses/MIT.
|
|
28
|
+
|
|
29
|
+
require "activerecord_spanner_adapter/table/column"
|
|
30
|
+
|
|
31
|
+
module ActiveRecordSpannerAdapter
|
|
32
|
+
class Table
|
|
33
|
+
attr_accessor :name, :on_delete, :parent_table, :schema_name, :catalog,
|
|
34
|
+
:indexes, :columns, :foreign_keys
|
|
35
|
+
|
|
36
|
+
# parent_table == interleave_in
|
|
37
|
+
def initialize \
|
|
38
|
+
name,
|
|
39
|
+
parent_table: nil,
|
|
40
|
+
on_delete: nil,
|
|
41
|
+
schema_name: nil,
|
|
42
|
+
catalog: nil
|
|
43
|
+
@name = name.to_s
|
|
44
|
+
@parent_table = parent_table.to_s if parent_table
|
|
45
|
+
@on_delete = on_delete
|
|
46
|
+
@schema_name = schema_name
|
|
47
|
+
@catalog = catalog
|
|
48
|
+
@columns = []
|
|
49
|
+
@indexes = []
|
|
50
|
+
@foreign_keys = []
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def primary_keys
|
|
54
|
+
columns.select(&:primary_key).map(&:name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def cascade?
|
|
58
|
+
@on_delete == "CASCADE"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
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 ActiveRecordSpannerAdapter
|
|
8
|
+
class Transaction
|
|
9
|
+
attr_reader :state
|
|
10
|
+
|
|
11
|
+
def initialize connection, isolation
|
|
12
|
+
@connection = connection
|
|
13
|
+
@isolation = isolation
|
|
14
|
+
@committable = ![:read_only, :pdml].include?(isolation) && !isolation.is_a?(Hash)
|
|
15
|
+
@state = :INITIALIZED
|
|
16
|
+
@sequence_number = 0
|
|
17
|
+
@mutations = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def active?
|
|
21
|
+
@state == :STARTED
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def isolation
|
|
25
|
+
return nil unless active?
|
|
26
|
+
@isolation
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def buffer mutation
|
|
30
|
+
@mutations << mutation
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Begins the transaction.
|
|
34
|
+
#
|
|
35
|
+
# Read-only and PDML transactions are started by executing a BeginTransaction RPC.
|
|
36
|
+
# Read/write transactions are not really started by this method, and instead a
|
|
37
|
+
# transaction selector is prepared that will be included with the first statement
|
|
38
|
+
# on the transaction.
|
|
39
|
+
def begin
|
|
40
|
+
raise "Nested transactions are not allowed" if @state != :INITIALIZED
|
|
41
|
+
begin
|
|
42
|
+
case @isolation
|
|
43
|
+
when Hash
|
|
44
|
+
if @isolation[:timestamp]
|
|
45
|
+
@grpc_transaction = @connection.session.create_snapshot timestamp: @isolation[:timestamp]
|
|
46
|
+
elsif @isolation[:staleness]
|
|
47
|
+
@grpc_transaction = @connection.session.create_snapshot staleness: @isolation[:staleness]
|
|
48
|
+
elsif @isolation[:strong]
|
|
49
|
+
@grpc_transaction = @connection.session.create_snapshot strong: true
|
|
50
|
+
else
|
|
51
|
+
raise "Invalid snapshot argument: #{@isolation}"
|
|
52
|
+
end
|
|
53
|
+
when :read_only
|
|
54
|
+
@grpc_transaction = @connection.session.create_snapshot strong: true
|
|
55
|
+
when :pdml
|
|
56
|
+
@grpc_transaction = @connection.session.create_pdml
|
|
57
|
+
else
|
|
58
|
+
@begin_transaction_selector = Google::Spanner::V1::TransactionSelector.new \
|
|
59
|
+
begin: Google::Spanner::V1::TransactionOptions.new(
|
|
60
|
+
read_write: Google::Spanner::V1::TransactionOptions::ReadWrite.new
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
@state = :STARTED
|
|
65
|
+
rescue Google::Cloud::NotFoundError => e
|
|
66
|
+
if @connection.session_not_found? e
|
|
67
|
+
@connection.reset!
|
|
68
|
+
retry
|
|
69
|
+
end
|
|
70
|
+
@state = :FAILED
|
|
71
|
+
raise
|
|
72
|
+
rescue StandardError
|
|
73
|
+
@state = :FAILED
|
|
74
|
+
raise
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Forces a BeginTransaction RPC for a read/write transaction. This is used by a
|
|
79
|
+
# connection if the first statement of a transaction failed.
|
|
80
|
+
def force_begin_read_write
|
|
81
|
+
@grpc_transaction = @connection.session.create_transaction
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def next_sequence_number
|
|
85
|
+
@sequence_number += 1 if @committable
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def commit
|
|
89
|
+
raise "This transaction is not active" unless active?
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
# Start a transaction with an explicit BeginTransaction RPC if the transaction only contains mutations.
|
|
93
|
+
force_begin_read_write if @committable && !@mutations.empty? && !@grpc_transaction
|
|
94
|
+
|
|
95
|
+
@connection.session.commit_transaction @grpc_transaction, @mutations if @committable && @grpc_transaction
|
|
96
|
+
@state = :COMMITTED
|
|
97
|
+
rescue Google::Cloud::NotFoundError => e
|
|
98
|
+
if @connection.session_not_found? e
|
|
99
|
+
shoot_and_forget_rollback
|
|
100
|
+
@connection.reset!
|
|
101
|
+
@connection.raise_aborted_err
|
|
102
|
+
end
|
|
103
|
+
@state = :FAILED
|
|
104
|
+
raise
|
|
105
|
+
rescue StandardError
|
|
106
|
+
@state = :FAILED
|
|
107
|
+
raise
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def rollback
|
|
112
|
+
# Allow rollback after abort and/or a failed commit.
|
|
113
|
+
raise "This transaction is not active" unless active? || @state == :FAILED || @state == :ABORTED
|
|
114
|
+
if active? && @grpc_transaction
|
|
115
|
+
# We do a shoot-and-forget rollback here, as the error that caused the transaction to be rolled back could
|
|
116
|
+
# also have invalidated the transaction (e.g. `Session not found`). If the rollback fails for any other
|
|
117
|
+
# reason, we also do not need to retry it or propagate the error to the application, as the transaction will
|
|
118
|
+
# automatically be aborted by Cloud Spanner after 10 seconds anyways.
|
|
119
|
+
shoot_and_forget_rollback
|
|
120
|
+
end
|
|
121
|
+
@state = :ROLLED_BACK
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def shoot_and_forget_rollback
|
|
125
|
+
@connection.session.rollback @grpc_transaction.transaction_id if @committable
|
|
126
|
+
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
|
127
|
+
# Ignored
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def mark_aborted
|
|
131
|
+
@state = :ABORTED
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Sets the underlying gRPC transaction to use for this Transaction.
|
|
135
|
+
# This is used for queries/DML statements that inlined the BeginTransaction option and returned
|
|
136
|
+
# a transaction in the metadata.
|
|
137
|
+
def grpc_transaction= grpc
|
|
138
|
+
@grpc_transaction = Google::Cloud::Spanner::Transaction.from_grpc grpc, @connection.session
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def transaction_selector
|
|
142
|
+
return unless active?
|
|
143
|
+
|
|
144
|
+
# Use the transaction that has been started by a BeginTransaction RPC or returned by a
|
|
145
|
+
# statement, if present.
|
|
146
|
+
return Google::Spanner::V1::TransactionSelector.new id: @grpc_transaction.transaction_id \
|
|
147
|
+
if @grpc_transaction
|
|
148
|
+
|
|
149
|
+
# Return a transaction selector that will instruct the statement to also start a transaction
|
|
150
|
+
# and return its id as a side effect.
|
|
151
|
+
@begin_transaction_selector
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|