activerecord-spanner-adapter 0.1.0 → 0.7.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 +42 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +9 -5
- data/LICENSE +6 -6
- data/README.md +67 -30
- data/Rakefile +74 -2
- 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/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/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 -251
- 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 +129 -7
- 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 -141
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/spanner_adapter.rb +188 -70
- 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 +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 +123 -0
- data/lib/activerecord_spanner_adapter/version.rb +9 -0
- data/lib/arel/visitors/spanner.rb +111 -0
- data/lib/spanner_client_ext.rb +103 -0
- data/renovate.json +5 -0
- metadata +417 -36
- data/.gitmodules +0 -3
- data/.travis.yml +0 -5
- data/lib/active_record/connection_adapters/spanner.rb +0 -10
- data/lib/activerecord-spanner-adapter/version.rb +0 -3
|
@@ -1,320 +1,313 @@
|
|
|
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
|
+
|
|
1
9
|
module ActiveRecord
|
|
2
10
|
module ConnectionAdapters
|
|
3
11
|
module Spanner
|
|
4
12
|
module DatabaseStatements
|
|
5
|
-
|
|
13
|
+
# DDL, DML and DQL Statements
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
collector.add_bind(o) {|bind_idx| "@p#{bind_idx}" }
|
|
10
|
-
end
|
|
11
|
-
end
|
|
15
|
+
def execute sql, name = nil, binds = []
|
|
16
|
+
statement_type = sql_statement_type sql
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
if preventing_writes? && [:dml, :ddl].include?(statement_type)
|
|
19
|
+
raise ActiveRecord::ReadOnlyError(
|
|
20
|
+
"Write query attempted while in readonly mode: #{sql}"
|
|
21
|
+
)
|
|
17
22
|
end
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
if statement_type == :ddl
|
|
25
|
+
execute_ddl sql
|
|
26
|
+
else
|
|
27
|
+
transaction_required = statement_type == :dml
|
|
28
|
+
materialize_transactions
|
|
29
|
+
|
|
30
|
+
# First process and remove any hints in the binds that indicate that
|
|
31
|
+
# a different read staleness should be used than the default.
|
|
32
|
+
staleness_hint = binds.find { |b| b.is_a? Arel::Visitors::StalenessHint }
|
|
33
|
+
if staleness_hint
|
|
34
|
+
selector = Google::Cloud::Spanner::Session.single_use_transaction staleness_hint.value
|
|
35
|
+
binds.delete staleness_hint
|
|
29
36
|
end
|
|
30
|
-
end
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
log sql, name do
|
|
39
|
+
types, params = to_types_and_params binds
|
|
40
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
41
|
+
if transaction_required
|
|
42
|
+
transaction do
|
|
43
|
+
@connection.execute_query sql, params: params, types: types
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
@connection.execute_query sql, params: params, types: types, single_use_selector: selector
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
36
50
|
end
|
|
37
51
|
end
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
include RightValueResolveable
|
|
43
|
-
|
|
44
|
-
def initialize(schema_reader, binds)
|
|
45
|
-
super()
|
|
46
|
-
@schema_reader = schema_reader
|
|
47
|
-
@binds = binds
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
attr_reader :binds
|
|
51
|
-
private :binds
|
|
52
|
-
|
|
53
|
-
def visit_Arel_Nodes_InsertStatement(o)
|
|
54
|
-
raise NotImplementedError, 'INSERT INTO SELECT statement is not supported' if o.select
|
|
55
|
-
table = o.relation.name
|
|
56
|
-
columns = if o.columns.any?
|
|
57
|
-
o.columns.map(&:name)
|
|
58
|
-
else
|
|
59
|
-
columns(table).map(&:name)
|
|
60
|
-
end
|
|
61
|
-
values = o.values ? accept(o.values) : []
|
|
62
|
-
|
|
63
|
-
[table, columns, values]
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def visit_Arel_Nodes_UpdateStatement(o)
|
|
67
|
-
table = o.relation.name
|
|
68
|
-
values = accept(o.values)
|
|
69
|
-
pk = @schema_reader.primary_key(table)
|
|
53
|
+
def query sql, name = nil
|
|
54
|
+
exec_query sql, name
|
|
55
|
+
end
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
57
|
+
def exec_query sql, name = "SQL", binds = [], prepare: false # rubocop:disable Lint/UnusedMethodArgument
|
|
58
|
+
result = execute sql, name, binds
|
|
59
|
+
ActiveRecord::Result.new(
|
|
60
|
+
result.fields.keys.map(&:to_s), result.rows.map(&:values)
|
|
61
|
+
)
|
|
62
|
+
end
|
|
74
63
|
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
def exec_mutation mutation
|
|
65
|
+
@connection.current_transaction.buffer mutation
|
|
66
|
+
end
|
|
77
67
|
|
|
78
|
-
|
|
68
|
+
def update arel, name = nil, binds = []
|
|
69
|
+
# Add a `WHERE TRUE` if it is an update_all or delete_all call that uses DML.
|
|
70
|
+
if !should_use_mutation(arel) && arel.respond_to?(:ast) && arel.ast.wheres.empty?
|
|
71
|
+
arel.ast.wheres << Arel::Nodes::SqlLiteral.new("TRUE")
|
|
79
72
|
end
|
|
73
|
+
return super unless should_use_mutation arel
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
table = o.relation.name
|
|
75
|
+
raise "Unsupported update for use with mutations: #{arel}" unless arel.is_a? Arel::DeleteManager
|
|
83
76
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
105
|
-
end
|
|
77
|
+
exec_mutation create_delete_all_mutation arel if arel.is_a? Arel::DeleteManager
|
|
78
|
+
0 # Affected rows (unknown)
|
|
79
|
+
end
|
|
80
|
+
alias delete update
|
|
81
|
+
|
|
82
|
+
def exec_update sql, name = "SQL", binds = []
|
|
83
|
+
result = execute sql, name, binds
|
|
84
|
+
# Make sure that we consume the entire result stream before trying to get the stats.
|
|
85
|
+
# This is required because the ExecuteStreamingSql RPC is also used for (Partitioned) DML,
|
|
86
|
+
# and this RPC can return multiple partial result sets for DML as well. Only the last partial
|
|
87
|
+
# result set will contain the statistics. Although there will never be any rows, this makes
|
|
88
|
+
# sure that the stream is fully consumed.
|
|
89
|
+
result.rows.each { |_| }
|
|
90
|
+
return result.row_count if result.row_count
|
|
91
|
+
|
|
92
|
+
raise ActiveRecord::StatementInvalid.new(
|
|
93
|
+
"DML statement is invalid.", sql: sql
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
alias exec_delete exec_update
|
|
106
97
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
raise NotImplementedError, "mutation with SQL literal is not supported"
|
|
112
|
-
else
|
|
113
|
-
accept(value)
|
|
114
|
-
end
|
|
98
|
+
def truncate table_name, name = nil
|
|
99
|
+
Array(table_name).each do |t|
|
|
100
|
+
log "TRUNCATE #{t}", name do
|
|
101
|
+
@connection.truncate t
|
|
115
102
|
end
|
|
116
103
|
end
|
|
117
|
-
|
|
118
|
-
def visit_Arel_Nodes_Assignment(o)
|
|
119
|
-
[accept(o.left), accept(o.right)]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def visit_Arel_Nodes_UnqualifiedColumn(o)
|
|
123
|
-
o.name
|
|
124
|
-
end
|
|
125
104
|
end
|
|
126
105
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
include RightValueResolveable
|
|
131
|
-
|
|
132
|
-
NOT_SIMPLE = nil
|
|
106
|
+
def write_query? sql
|
|
107
|
+
sql_statement_type(sql) == :dml
|
|
108
|
+
end
|
|
133
109
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
110
|
+
def execute_ddl statements
|
|
111
|
+
log "MIGRATION", "SCHEMA" do
|
|
112
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
113
|
+
@connection.execute_ddl statements
|
|
114
|
+
end
|
|
139
115
|
end
|
|
116
|
+
rescue Google::Cloud::Error => error
|
|
117
|
+
raise ActiveRecord::StatementInvalid, error
|
|
118
|
+
end
|
|
140
119
|
|
|
141
|
-
|
|
142
|
-
private :binds
|
|
120
|
+
# Transaction
|
|
143
121
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
else
|
|
148
|
-
NOT_SIMPLE
|
|
149
|
-
end
|
|
122
|
+
def transaction requires_new: nil, isolation: nil, joinable: true
|
|
123
|
+
if !requires_new && current_transaction.joinable?
|
|
124
|
+
return super
|
|
150
125
|
end
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
127
|
+
backoff = 0.2
|
|
128
|
+
begin
|
|
129
|
+
super
|
|
130
|
+
rescue ActiveRecord::StatementInvalid => err
|
|
131
|
+
if err.cause.is_a? Google::Cloud::AbortedError
|
|
132
|
+
sleep(delay_from_aborted(err) || backoff *= 1.3)
|
|
133
|
+
retry
|
|
157
134
|
end
|
|
135
|
+
raise
|
|
158
136
|
end
|
|
137
|
+
end
|
|
159
138
|
|
|
160
|
-
|
|
161
|
-
|
|
139
|
+
def transaction_isolation_levels
|
|
140
|
+
{
|
|
141
|
+
read_uncommitted: "READ UNCOMMITTED",
|
|
142
|
+
read_committed: "READ COMMITTED",
|
|
143
|
+
repeatable_read: "REPEATABLE READ",
|
|
144
|
+
serializable: "SERIALIZABLE",
|
|
145
|
+
|
|
146
|
+
# These are not really isolation levels, but it is the only (best) way to pass in additional
|
|
147
|
+
# transaction options to the connection.
|
|
148
|
+
read_only: "READ_ONLY",
|
|
149
|
+
buffered_mutations: "BUFFERED_MUTATIONS"
|
|
150
|
+
}
|
|
151
|
+
end
|
|
162
152
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
accept(o.right)
|
|
167
|
-
end
|
|
153
|
+
def begin_db_transaction
|
|
154
|
+
log "BEGIN" do
|
|
155
|
+
@connection.begin_transaction
|
|
168
156
|
end
|
|
157
|
+
end
|
|
169
158
|
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
# Begins a transaction on the database with the specified isolation level. Cloud Spanner only supports
|
|
160
|
+
# isolation level :serializable, but also defines three additional 'isolation levels' that can be used
|
|
161
|
+
# to start specific types of Spanner transactions:
|
|
162
|
+
# * :read_only: Starts a read-only snapshot transaction using a strong timestamp bound.
|
|
163
|
+
# * :buffered_mutations: Starts a read/write transaction that will use mutations instead of DML for single-row
|
|
164
|
+
# inserts/updates/deletes. Mutations are buffered locally until the transaction is
|
|
165
|
+
# committed, and any changes during a transaction cannot be read by the application.
|
|
166
|
+
# * :pdml: Starts a Partitioned DML transaction. Executing multiple DML statements in one PDML transaction
|
|
167
|
+
# block is NOT supported A PDML transaction is not guaranteed to be atomic.
|
|
168
|
+
# See https://cloud.google.com/spanner/docs/dml-partitioned for more information.
|
|
169
|
+
#
|
|
170
|
+
# In addition to the above, a Hash containing read-only snapshot options may be used to start a specific
|
|
171
|
+
# read-only snapshot:
|
|
172
|
+
# * { timestamp: Time } Starts a read-only snapshot at the given timestamp.
|
|
173
|
+
# * { staleness: Integer } Starts a read-only snapshot with the given staleness in seconds.
|
|
174
|
+
# * { strong: <any value>} Starts a read-only snapshot with strong timestamp bound
|
|
175
|
+
# (this is the same as :read_only)
|
|
176
|
+
#
|
|
177
|
+
def begin_isolated_db_transaction isolation
|
|
178
|
+
if isolation.is_a? Hash
|
|
179
|
+
raise "Unsupported isolation level: #{isolation}" unless \
|
|
180
|
+
isolation[:timestamp] || isolation[:staleness] || isolation[:strong]
|
|
181
|
+
raise "Only one option is supported. It must be one of `timestamp`, `staleness` or `strong`." \
|
|
182
|
+
if isolation.count != 1
|
|
183
|
+
else
|
|
184
|
+
raise "Unsupported isolation level: #{isolation}" unless \
|
|
185
|
+
[:serializable, :read_only, :buffered_mutations, :pdml].include? isolation
|
|
172
186
|
end
|
|
173
187
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
alias visit_Arel_Nodes_Or unsupported
|
|
177
|
-
alias visit_Arel_Nodes_NotEqual unsupported
|
|
178
|
-
alias visit_Arel_Nodes_Case unsupported
|
|
179
|
-
alias visit_Arel_Nodes_Between unsupported
|
|
180
|
-
alias visit_Arel_Nodes_GreaterThanOrEqual unsupported
|
|
181
|
-
alias visit_Arel_Nodes_GreaterThan unsupported
|
|
182
|
-
alias visit_Arel_Nodes_LessThanOrEqual unsupported
|
|
183
|
-
alias visit_Arel_Nodes_LessThan unsupported
|
|
184
|
-
alias visit_Arel_Nodes_Matches unsupported
|
|
185
|
-
alias visit_Arel_Nodes_DoesNotMatch unsupported
|
|
186
|
-
|
|
187
|
-
private
|
|
188
|
-
def pk_cond?(o)
|
|
189
|
-
o.left.kind_of?(Arel::Attributes::Attribute) &&
|
|
190
|
-
o.left.relation == @relation &&
|
|
191
|
-
o.left.name == @pk
|
|
188
|
+
log "BEGIN #{isolation}" do
|
|
189
|
+
@connection.begin_transaction isolation
|
|
192
190
|
end
|
|
193
191
|
end
|
|
194
192
|
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
type_casted_binds = binds.map {|attr| type_cast(attr.value_for_database) }
|
|
199
|
-
table, columns, values = MutationVisitor.new(self, type_casted_binds).accept(arel.ast)
|
|
200
|
-
fake_sql = <<~"SQL"
|
|
201
|
-
INSERT INTO #{table}(#{columns.join(", ")}) VALUES (#{values.join(", ")})
|
|
202
|
-
SQL
|
|
203
|
-
|
|
204
|
-
row = columns.zip(values).inject({}) {|out, (col, value)|
|
|
205
|
-
out[col] = value
|
|
206
|
-
out
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
log(fake_sql, name) do
|
|
210
|
-
session.commit do |c|
|
|
211
|
-
c.insert table, row
|
|
212
|
-
end
|
|
193
|
+
def commit_db_transaction
|
|
194
|
+
log "COMMIT" do
|
|
195
|
+
@connection.commit_transaction
|
|
213
196
|
end
|
|
214
|
-
|
|
215
|
-
id_value
|
|
216
197
|
end
|
|
217
198
|
|
|
218
|
-
def
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
type_casted_binds = binds.map {|attr| type_cast(attr.value_for_database) }
|
|
222
|
-
table, target, values = MutationVisitor.new(self, type_casted_binds.dup).accept(arel.ast)
|
|
223
|
-
|
|
224
|
-
fake_set = values.map {|col, val|
|
|
225
|
-
"#{quote_column_name(col)} = #{quote(val)}"
|
|
226
|
-
}.join(', ')
|
|
227
|
-
|
|
228
|
-
pk = primary_key(table)
|
|
229
|
-
if target.size > 1
|
|
230
|
-
fake_sql = "UPDATE #{quote_column_name(table)} SET #{fake_set} WHERE #{pk} IN ?"
|
|
231
|
-
else
|
|
232
|
-
fake_sql = "UPDATE #{quote_column_name(table)} SET #{fake_set} WHERE #{pk} = ?"
|
|
199
|
+
def rollback_db_transaction
|
|
200
|
+
log "ROLLBACK" do
|
|
201
|
+
@connection.rollback_transaction
|
|
233
202
|
end
|
|
203
|
+
end
|
|
234
204
|
|
|
235
|
-
|
|
236
|
-
rows = target.map {|id| row.merge(pk => id) }
|
|
205
|
+
private
|
|
237
206
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
207
|
+
# Translates binds to Spanner types and params.
|
|
208
|
+
def to_types_and_params binds
|
|
209
|
+
types = binds.enum_for(:each_with_index).map do |bind, i|
|
|
210
|
+
type = :INT64
|
|
211
|
+
if bind.respond_to? :type
|
|
212
|
+
type = ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
|
|
213
|
+
.convert_active_model_type_to_spanner(bind.type)
|
|
241
214
|
end
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
215
|
+
[
|
|
216
|
+
# Generates binds for named parameters in the format `@p1, @p2, ...`
|
|
217
|
+
"p#{i + 1}", type
|
|
218
|
+
]
|
|
219
|
+
end.to_h
|
|
220
|
+
params = binds.enum_for(:each_with_index).map do |bind, i|
|
|
221
|
+
type = bind.respond_to?(:type) ? bind.type : ActiveModel::Type::Integer
|
|
222
|
+
value = bind
|
|
223
|
+
value = type.serialize bind.value, :dml if type.respond_to?(:serialize) && type.method(:serialize).arity < 0
|
|
224
|
+
value = type.serialize bind.value if type.respond_to?(:serialize) && type.method(:serialize).arity >= 0
|
|
225
|
+
|
|
226
|
+
["p#{i + 1}", value]
|
|
227
|
+
end.to_h
|
|
228
|
+
[types, params]
|
|
245
229
|
end
|
|
246
230
|
|
|
247
|
-
|
|
248
|
-
|
|
231
|
+
# An insert/update/delete statement could use mutations in some specific circumstances.
|
|
232
|
+
# This method returns an indication whether a specific operation should use mutations instead of DML
|
|
233
|
+
# based on the operation itself, and the current transaction.
|
|
234
|
+
def should_use_mutation arel
|
|
235
|
+
!@connection.current_transaction.nil? \
|
|
236
|
+
&& @connection.current_transaction.isolation == :buffered_mutations \
|
|
237
|
+
&& can_use_mutation(arel) \
|
|
238
|
+
end
|
|
249
239
|
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
def can_use_mutation arel
|
|
241
|
+
return true if arel.is_a?(Arel::DeleteManager) && arel.respond_to?(:ast) && arel.ast.wheres.empty?
|
|
242
|
+
false
|
|
243
|
+
end
|
|
252
244
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
where_clause = visitor.accept(wheres, collector).compile(binds.dup, self)
|
|
257
|
-
# TODO(yugui) keep consistency with transaction
|
|
258
|
-
target = select_values(<<~"SQL", name, binds)
|
|
259
|
-
SELECT #{quote_column_name(pk)} FROM #{quote_table_name(table)} WHERE #{where_clause}
|
|
260
|
-
SQL
|
|
245
|
+
def create_delete_all_mutation arel
|
|
246
|
+
unless arel.is_a? Arel::DeleteManager
|
|
247
|
+
raise "A delete mutation can only be created from a DeleteManager"
|
|
261
248
|
end
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
fake_sql = "DELETE FROM #{quote_column_name(table)}"
|
|
266
|
-
elsif target.size > 1
|
|
267
|
-
fake_sql = "DELETE FROM #{quote_column_name(table)} WHERE (primary-key) IN ?"
|
|
268
|
-
keyset = target
|
|
269
|
-
else
|
|
270
|
-
fake_sql = "DELETE FROM #{quote_column_name(table)} WHERE (primary-key) = ?"
|
|
271
|
-
keyset = target
|
|
249
|
+
# Check if it is a delete_all operation.
|
|
250
|
+
unless arel.ast.wheres.empty?
|
|
251
|
+
raise "A delete mutation can only be created without a WHERE clause"
|
|
272
252
|
end
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
end
|
|
253
|
+
table_name = arel.ast.relation.name if arel.ast.relation.is_a? Arel::Table
|
|
254
|
+
table_name = arel.ast.relation.left.name if arel.ast.relation.is_a? Arel::Nodes::JoinSource
|
|
255
|
+
unless table_name
|
|
256
|
+
raise "Could not find table for delete mutation"
|
|
278
257
|
end
|
|
279
258
|
|
|
280
|
-
|
|
259
|
+
Google::Cloud::Spanner::V1::Mutation.new(
|
|
260
|
+
delete: Google::Cloud::Spanner::V1::Mutation::Delete.new(
|
|
261
|
+
table: table_name,
|
|
262
|
+
key_set: { all: true }
|
|
263
|
+
)
|
|
264
|
+
)
|
|
281
265
|
end
|
|
282
266
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
267
|
+
COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m.freeze \
|
|
268
|
+
unless defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
269
|
+
COMMENT_REGEX = ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX \
|
|
270
|
+
if defined? ActiveRecord::ConnectionAdapters::AbstractAdapter::COMMENT_REGEX
|
|
271
|
+
|
|
272
|
+
private_class_method def self.build_sql_statement_regexp *parts # :nodoc:
|
|
273
|
+
parts = parts.map { |part| /#{part}/i }
|
|
274
|
+
/\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
|
|
290
275
|
end
|
|
291
276
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
277
|
+
DDL_REGX = build_sql_statement_regexp(:create, :alter, :drop).freeze
|
|
278
|
+
|
|
279
|
+
DML_REGX = build_sql_statement_regexp(:insert, :delete, :update).freeze
|
|
280
|
+
|
|
281
|
+
def sql_statement_type sql
|
|
282
|
+
case sql
|
|
283
|
+
when DDL_REGX
|
|
284
|
+
:ddl
|
|
285
|
+
when DML_REGX
|
|
286
|
+
:dml
|
|
300
287
|
else
|
|
301
|
-
|
|
302
|
-
b["p#{i+1}"] = type_cast(attr.value_for_database)
|
|
303
|
-
b
|
|
304
|
-
}
|
|
288
|
+
:dql
|
|
305
289
|
end
|
|
290
|
+
end
|
|
306
291
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
292
|
+
##
|
|
293
|
+
# Retrieves the delay value from Google::Cloud::AbortedError or
|
|
294
|
+
# GRPC::Aborted
|
|
295
|
+
def delay_from_aborted err
|
|
296
|
+
return nil if err.nil?
|
|
297
|
+
if err.respond_to?(:metadata) && err.metadata["google.rpc.retryinfo-bin"]
|
|
298
|
+
retry_info = Google::Rpc::RetryInfo.decode err.metadata["google.rpc.retryinfo-bin"]
|
|
299
|
+
seconds = retry_info["retry_delay"].seconds
|
|
300
|
+
nanos = retry_info["retry_delay"].nanos
|
|
301
|
+
return seconds if nanos.zero?
|
|
302
|
+
return seconds + (nanos / 1_000_000_000.0)
|
|
314
303
|
end
|
|
304
|
+
# No metadata? Try the inner error
|
|
305
|
+
delay_from_aborted err.cause
|
|
306
|
+
rescue StandardError
|
|
307
|
+
# Any error indicates the backoff should be handled elsewhere
|
|
308
|
+
nil
|
|
315
309
|
end
|
|
316
310
|
end
|
|
317
311
|
end
|
|
318
312
|
end
|
|
319
313
|
end
|
|
320
|
-
|