activerecord-spanner-adapter 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -1,66 +1,58 @@
|
|
1
|
-
#
|
2
|
-
|
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
|
+
# frozen_string_literal: true
|
3
30
|
|
4
31
|
module ActiveRecord
|
5
32
|
module ConnectionAdapters
|
6
33
|
module Spanner
|
7
34
|
module Quoting
|
8
|
-
|
9
|
-
|
10
|
-
def quote_identifier(name)
|
11
|
-
# https://cloud.google.com/spanner/docs/data-definition-language?hl=ja#ddl_syntax
|
12
|
-
# raise ArgumentError, "invalid table name #{name}" unless IDENTIFIERS_PATTERN =~ name
|
13
|
-
"`#{name}`"
|
35
|
+
def quote_column_name name
|
36
|
+
self.class.quoted_column_names[name] ||= "`#{super.gsub '`', '``'}`".freeze
|
14
37
|
end
|
15
38
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
private
|
20
|
-
def _type_cast(value)
|
21
|
-
# NOTE: Spanner APIs are strongly typed unlike typical SQL interfaces.
|
22
|
-
# So we don't want to serialize the value into string unlike other adapters.
|
23
|
-
case value
|
24
|
-
when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
|
25
|
-
value.to_s
|
26
|
-
else
|
27
|
-
value
|
28
|
-
end
|
39
|
+
def quote_table_name name
|
40
|
+
self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
|
29
41
|
end
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when true
|
36
|
-
quoted_true
|
37
|
-
when false
|
38
|
-
quoted_false
|
39
|
-
when nil
|
40
|
-
'NULL'
|
41
|
-
when Numeric, ActiveSupport::Duration
|
42
|
-
value.to_s
|
43
|
-
when Type::Time::Value
|
44
|
-
%Q["#{quoted_time(value)}"]
|
45
|
-
when Date, Time
|
46
|
-
%Q["#{quoted_date(value)}"]
|
47
|
-
else
|
48
|
-
raise TypeError, "can't quote #{value.class.name}"
|
49
|
-
end
|
50
|
-
end
|
43
|
+
STR_ESCAPE_REGX = /[\n\r'\\]/.freeze
|
44
|
+
STR_ESCAPE_VALUES = {
|
45
|
+
"\n" => "\\n", "\r" => "\\r", "'" => "\\'", "\\" => "\\\\"
|
46
|
+
}.freeze
|
51
47
|
|
52
|
-
|
53
|
-
# Not sure but string-escape syntax in SELECT statements in Spanner
|
54
|
-
# looks to be the one in JSON by observation.
|
55
|
-
JSON.generate(value)
|
56
|
-
end
|
48
|
+
private_constant :STR_ESCAPE_REGX, :STR_ESCAPE_VALUES
|
57
49
|
|
58
|
-
def
|
59
|
-
|
50
|
+
def quote_string s
|
51
|
+
s.gsub STR_ESCAPE_REGX, STR_ESCAPE_VALUES
|
60
52
|
end
|
61
53
|
|
62
|
-
def
|
63
|
-
'
|
54
|
+
def quoted_binary value
|
55
|
+
"b'#{value}'"
|
64
56
|
end
|
65
57
|
end
|
66
58
|
end
|
@@ -0,0 +1,43 @@
|
|
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 ConnectionAdapters
|
9
|
+
class SpannerSchemaCache < SchemaCache
|
10
|
+
def initialize conn
|
11
|
+
@primary_and_parent_keys = {}
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize_dup other
|
16
|
+
@primary_and_parent_keys = @primary_and_parent_keys.dup
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def encode_with coder
|
21
|
+
coder["primary_and_parent_keys"] = @primary_and_parent_keys
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def init_with coder
|
26
|
+
@primary_and_parent_keys = coder["primary_and_parent_keys"]
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def primary_and_parent_keys table_name
|
31
|
+
@primary_and_parent_keys[table_name] ||=
|
32
|
+
if data_source_exists? table_name
|
33
|
+
connection.primary_and_parent_keys table_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear!
|
38
|
+
@primary_and_parent_keys.clear
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,22 +1,138 @@
|
|
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
|
+
|
1
7
|
module ActiveRecord
|
2
8
|
module ConnectionAdapters
|
3
9
|
module Spanner
|
4
|
-
class
|
10
|
+
class SchemaCreation < SchemaCreation
|
11
|
+
private
|
12
|
+
|
13
|
+
# rubocop:disable Naming/MethodName, Metrics/AbcSize
|
14
|
+
|
15
|
+
def visit_TableDefinition o
|
16
|
+
create_sql = +"CREATE TABLE #{quote_table_name o.name} "
|
17
|
+
statements = o.columns.map { |c| accept c }
|
18
|
+
|
19
|
+
o.foreign_keys.each do |to_table, options|
|
20
|
+
statements << foreign_key_in_create(o.name, to_table, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
create_sql << "(#{statements.join ', '}) " if statements.any?
|
24
|
+
|
25
|
+
primary_keys = if o.primary_keys
|
26
|
+
o.primary_keys
|
27
|
+
else
|
28
|
+
pk_names = o.columns.each_with_object [] do |c, r|
|
29
|
+
if c.type == :primary_key || c.primary_key?
|
30
|
+
r << c.name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
PrimaryKeyDefinition.new pk_names
|
34
|
+
end
|
35
|
+
|
36
|
+
if o.interleave_in?
|
37
|
+
parent_names = o.columns.each_with_object [] do |c, r|
|
38
|
+
if c.type == :parent_key
|
39
|
+
r << c.name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
primary_keys.name = parent_names.concat primary_keys.name
|
43
|
+
create_sql << accept(primary_keys)
|
44
|
+
create_sql << ", INTERLEAVE IN PARENT #{quote_table_name o.interleave_in_parent}"
|
45
|
+
create_sql << " ON DELETE #{o.on_delete}" if o.on_delete
|
46
|
+
else
|
47
|
+
create_sql << accept(primary_keys)
|
48
|
+
end
|
49
|
+
|
50
|
+
create_sql
|
51
|
+
end
|
52
|
+
|
53
|
+
def visit_DropTableDefinition o
|
54
|
+
"DROP TABLE #{quote_table_name o.name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def visit_ColumnDefinition o
|
58
|
+
o.sql_type = type_to_sql o.type, **o.options
|
59
|
+
column_sql = +"#{quote_column_name o.name} #{o.sql_type}"
|
60
|
+
add_column_options! column_sql, column_options(o)
|
61
|
+
column_sql
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_AddColumnDefinition o
|
65
|
+
# Overridden to add the optional COLUMN keyword. The keyword is only optional
|
66
|
+
# on real Cloud Spanner, the emulator requires the COLUMN keyword to be included.
|
67
|
+
+"ADD COLUMN #{accept o.column}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_DropColumnDefinition o
|
71
|
+
"ALTER TABLE #{quote_table_name o.table_name} DROP" \
|
72
|
+
" COLUMN #{quote_column_name o.name}"
|
73
|
+
end
|
5
74
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
DDL.new(ddl)
|
75
|
+
def visit_ChangeColumnDefinition o
|
76
|
+
sql = +"ALTER TABLE #{quote_table_name o.table_name} ALTER COLUMN "
|
77
|
+
sql << accept(o.column)
|
78
|
+
sql
|
11
79
|
end
|
12
80
|
|
13
|
-
def
|
14
|
-
|
81
|
+
def visit_DropIndexDefinition o
|
82
|
+
"DROP INDEX #{quote_table_name o.name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_IndexDefinition o
|
86
|
+
sql = +"CREATE"
|
87
|
+
sql << " UNIQUE" if o.unique
|
88
|
+
sql << " NULL_FILTERED" if o.null_filtered
|
89
|
+
sql << " INDEX #{quote_table_name o.name} "
|
90
|
+
|
91
|
+
columns_sql = o.columns_with_order.map do |c, order|
|
92
|
+
order_sql = +quote_column_name(c)
|
93
|
+
order_sql << " DESC" if order == "DESC"
|
94
|
+
order_sql
|
95
|
+
end
|
96
|
+
|
97
|
+
sql << "ON #{quote_table_name o.table} (#{columns_sql.join ', '})"
|
98
|
+
|
99
|
+
if o.storing.any?
|
100
|
+
storing = o.storing.map { |s| quote_column_name s }
|
101
|
+
sql << " STORING (#{storing.join ', '})"
|
102
|
+
end
|
103
|
+
if o.interleave_in
|
104
|
+
sql << ", INTERLEAVE IN #{quote_table_name o.interleave_in}"
|
105
|
+
end
|
106
|
+
sql
|
107
|
+
end
|
108
|
+
|
109
|
+
# rubocop:enable Naming/MethodName, Metrics/AbcSize
|
110
|
+
|
111
|
+
def add_column_options! sql, options
|
112
|
+
if options[:null] == false || options[:primary_key] == true
|
15
113
|
sql << " NOT NULL"
|
16
114
|
end
|
115
|
+
|
116
|
+
if !options[:allow_commit_timestamp].nil? &&
|
117
|
+
options[:column].sql_type == "TIMESTAMP"
|
118
|
+
sql << " OPTIONS (allow_commit_timestamp = "\
|
119
|
+
"#{options[:allow_commit_timestamp]})"
|
120
|
+
end
|
121
|
+
|
122
|
+
if (as = options[:as])
|
123
|
+
sql << " AS (#{as})"
|
124
|
+
|
125
|
+
sql << " STORED" if options[:stored]
|
126
|
+
unless options[:stored]
|
127
|
+
raise ArgumentError, "" \
|
128
|
+
"Cloud Spanner currently does not support generated columns without the STORED option." \
|
129
|
+
"Specify 'stored: true' option for `#{options[:column].name}`"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
sql
|
17
134
|
end
|
18
135
|
end
|
19
136
|
end
|
20
137
|
end
|
21
138
|
end
|
22
|
-
|
@@ -0,0 +1,122 @@
|
|
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 ConnectionAdapters #:nodoc:
|
9
|
+
module Spanner
|
10
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
11
|
+
attr_reader :interleave_in_parent
|
12
|
+
|
13
|
+
def interleave_in parent, on_delete = nil
|
14
|
+
@interleave_in_parent = parent
|
15
|
+
@on_delete = on_delete
|
16
|
+
end
|
17
|
+
|
18
|
+
def parent_key name
|
19
|
+
column name, :parent_key, null: false
|
20
|
+
end
|
21
|
+
|
22
|
+
def interleave_in?
|
23
|
+
@interleave_in_parent != nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_delete
|
27
|
+
"CASCADE" if @on_delete == :cascade
|
28
|
+
end
|
29
|
+
|
30
|
+
def references *args, **options
|
31
|
+
args.each do |ref_name|
|
32
|
+
Spanner::ReferenceDefinition.new(ref_name, **options).add_to(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
alias belongs_to references
|
36
|
+
end
|
37
|
+
|
38
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
39
|
+
def primary_key name, type = :primary_key, **options
|
40
|
+
type = :string # rubocop:disable Lint/ShadowedArgument
|
41
|
+
options.merge primary_key: true
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
DropTableDefinition = Struct.new :name, :options
|
47
|
+
DropColumnDefinition = Struct.new :table_name, :name
|
48
|
+
ChangeColumnDefinition = Struct.new :table_name, :column, :name
|
49
|
+
DropIndexDefinition = Struct.new :name
|
50
|
+
|
51
|
+
class ReferenceDefinition < ActiveRecord::ConnectionAdapters::ReferenceDefinition
|
52
|
+
def initialize \
|
53
|
+
name,
|
54
|
+
polymorphic: false,
|
55
|
+
index: true,
|
56
|
+
foreign_key: false,
|
57
|
+
type: :integer,
|
58
|
+
**options
|
59
|
+
@name = name
|
60
|
+
@polymorphic = polymorphic
|
61
|
+
@foreign_key = foreign_key
|
62
|
+
# Only add an index if there is no foreign key, as Cloud Spanner will automatically add a managed index when
|
63
|
+
# a foreign key is added.
|
64
|
+
@index = index unless foreign_key
|
65
|
+
@type = type
|
66
|
+
@options = options
|
67
|
+
|
68
|
+
return unless polymorphic && foreign_key
|
69
|
+
raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def columns
|
75
|
+
result = [[column_name, type, options]]
|
76
|
+
|
77
|
+
if polymorphic
|
78
|
+
type_options = polymorphic_options.merge limit: 255
|
79
|
+
result.unshift ["#{name}_type", :string, type_options]
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
86
|
+
attr_reader :null_filtered, :interleave_in, :storing, :orders
|
87
|
+
|
88
|
+
def initialize \
|
89
|
+
table_name,
|
90
|
+
name,
|
91
|
+
columns,
|
92
|
+
unique: false,
|
93
|
+
null_filtered: false,
|
94
|
+
interleave_in: nil,
|
95
|
+
storing: nil,
|
96
|
+
orders: nil
|
97
|
+
@table = table_name
|
98
|
+
@name = name
|
99
|
+
@unique = unique
|
100
|
+
@null_filtered = null_filtered
|
101
|
+
@interleave_in = interleave_in
|
102
|
+
@storing = Array(storing)
|
103
|
+
columns = columns.split(/\W/) if columns.is_a? String
|
104
|
+
@columns = Array(columns).map(&:to_s)
|
105
|
+
@orders = orders || {}
|
106
|
+
|
107
|
+
unless @orders.is_a? Hash
|
108
|
+
@orders = columns.each_with_object({}) { |c, r| r[c] = orders }
|
109
|
+
end
|
110
|
+
|
111
|
+
@orders = @orders.symbolize_keys
|
112
|
+
end
|
113
|
+
|
114
|
+
def columns_with_order
|
115
|
+
columns.each_with_object({}) do |c, result|
|
116
|
+
result[c] = orders[c.to_sym].to_s.upcase
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,19 @@
|
|
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 ConnectionAdapters
|
11
|
+
module Spanner
|
12
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
13
|
+
def default_primary_key? column
|
14
|
+
schema_type(column) == :integer
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,173 +1,587 @@
|
|
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
|
+
require "active_record/connection_adapters/spanner/schema_creation"
|
10
|
+
require "active_record/connection_adapters/spanner/schema_dumper"
|
11
|
+
|
1
12
|
module ActiveRecord
|
2
13
|
module ConnectionAdapters
|
3
14
|
module Spanner
|
15
|
+
#
|
16
|
+
# # SchemaStatements
|
17
|
+
#
|
18
|
+
# Collection of methods to handle database schema.
|
19
|
+
#
|
20
|
+
# [Schema Doc](https://cloud.google.com/spanner/docs/information-schema)
|
21
|
+
#
|
4
22
|
module SchemaStatements
|
5
|
-
|
6
|
-
|
7
|
-
NATIVE_DATABASE_TYPES = {
|
8
|
-
primary_key: 'STRING(36)',
|
9
|
-
string: { name: 'STRING', limit: 255 },
|
10
|
-
text: { name: 'STRING', limit: 'MAX' },
|
11
|
-
integer: { name: 'INT64' },
|
12
|
-
float: { name: 'FLOAT64' },
|
13
|
-
datetime: { name: 'TIMESTAMP' },
|
14
|
-
date: { name: 'DATE' },
|
15
|
-
binary: { name: 'BYTES', limit: 'MAX' },
|
16
|
-
boolean: { name: 'BOOL' },
|
17
|
-
}
|
18
|
-
|
19
|
-
def native_database_types # :nodoc:
|
20
|
-
NATIVE_DATABASE_TYPES
|
21
|
-
end
|
22
|
-
|
23
|
-
def tables
|
24
|
-
# https://cloud.google.com/spanner/docs/information-schema
|
25
|
-
select_values(<<-SQL, 'SCHEMA')
|
26
|
-
SELECT
|
27
|
-
t.table_name
|
28
|
-
FROM
|
29
|
-
information_schema.tables AS t
|
30
|
-
WHERE
|
31
|
-
t.table_catalog = '' AND t.table_schema = ''
|
32
|
-
SQL
|
33
|
-
end
|
34
|
-
|
35
|
-
def views
|
36
|
-
[]
|
37
|
-
end
|
38
|
-
|
39
|
-
def indexes(table, name = :ignored)
|
40
|
-
params = {table: table}
|
41
|
-
results = exec_query(<<-"SQL", 'SCHEMA', params, prepare: false)
|
42
|
-
SELECT
|
43
|
-
idx.index_name,
|
44
|
-
idx.index_type,
|
45
|
-
idx.parent_table_name,
|
46
|
-
idx.is_unique,
|
47
|
-
idx.is_null_filtered
|
48
|
-
FROM
|
49
|
-
information_schema.indexes AS idx
|
50
|
-
WHERE
|
51
|
-
idx.table_catalog = '' AND
|
52
|
-
idx.table_schema = '' AND
|
53
|
-
idx.table_name = @table
|
54
|
-
SQL
|
55
|
-
|
56
|
-
results.map do |row|
|
57
|
-
col_params = { table: table, index: row['index_name'] }
|
58
|
-
col_results = exec_query(<<-"SQL", 'SCHEMA', col_params, prepare: false)
|
59
|
-
SELECT
|
60
|
-
col.column_name,
|
61
|
-
col.column_ordering
|
62
|
-
FROM
|
63
|
-
information_schema.index_columns AS col
|
64
|
-
WHERE
|
65
|
-
col.table_catalog = '' AND
|
66
|
-
col.table_schema = '' AND
|
67
|
-
col.table_name = @table AND
|
68
|
-
col.index_name = @index
|
69
|
-
ORDER BY
|
70
|
-
col.ordinal_position
|
71
|
-
SQL
|
23
|
+
VERSION_6_1_0 = Gem::Version.create "6.1.0"
|
24
|
+
VERSION_6_0_3 = Gem::Version.create "6.0.3"
|
72
25
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
26
|
+
def current_database
|
27
|
+
@connection.database_id
|
28
|
+
end
|
29
|
+
|
30
|
+
# Table
|
31
|
+
|
32
|
+
def data_sources
|
33
|
+
information_schema { |i| i.tables.map(&:name) }
|
34
|
+
end
|
35
|
+
alias tables data_sources
|
36
|
+
|
37
|
+
def table_exists? table_name
|
38
|
+
information_schema { |i| i.table table_name }.present?
|
39
|
+
end
|
40
|
+
alias data_source_exists? table_exists?
|
41
|
+
|
42
|
+
def create_table table_name, **options
|
43
|
+
td = create_table_definition table_name, options
|
44
|
+
|
45
|
+
if options[:id] != false
|
46
|
+
pk = options.fetch :primary_key do
|
47
|
+
Base.get_primary_key table_name.to_s.singularize
|
48
|
+
end
|
49
|
+
|
50
|
+
if pk.is_a? Array
|
51
|
+
td.primary_keys pk
|
52
|
+
else
|
53
|
+
td.primary_key pk, options.fetch(:id, :primary_key), **{}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
yield td if block_given?
|
58
|
+
|
59
|
+
statements = []
|
60
|
+
|
61
|
+
if options[:force]
|
62
|
+
statements.concat drop_table_with_indexes_sql(table_name, options)
|
63
|
+
end
|
64
|
+
|
65
|
+
statements << schema_creation.accept(td)
|
66
|
+
|
67
|
+
td.indexes.each do |column_name, index_options|
|
68
|
+
id = create_index_definition table_name, column_name, **index_options
|
69
|
+
statements << schema_creation.accept(id)
|
70
|
+
end
|
71
|
+
|
72
|
+
execute_schema_statements statements
|
73
|
+
end
|
74
|
+
|
75
|
+
def drop_table table_name, options = {}
|
76
|
+
statements = drop_table_with_indexes_sql table_name, options
|
77
|
+
execute_schema_statements statements
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates a join table that uses all the columns in the table as the primary key by default, unless
|
81
|
+
# an explicit primary key has been defined for the table. ActiveRecord will by default generate join
|
82
|
+
# tables without a primary key. Cloud Spanner however requires all tables to have a primary key.
|
83
|
+
# Instead of adding an additional column to the table only for the purpose of being the primary key,
|
84
|
+
# the Spanner ActiveRecord adapter defines a primary key that contains all the columns in the join
|
85
|
+
# table, as all values in the table should be unique anyways.
|
86
|
+
def create_join_table table_1, table_2, column_options: {}, **options
|
87
|
+
super do |td|
|
88
|
+
unless td.columns.any?(&:primary_key?)
|
89
|
+
td.columns.each do |col|
|
90
|
+
def col.primary_key?
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
yield td if block_given?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def rename_table _table_name, _new_name
|
100
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, \
|
101
|
+
"rename_table is not implemented"
|
102
|
+
end
|
103
|
+
|
104
|
+
# Column
|
105
|
+
|
106
|
+
def column_definitions table_name
|
107
|
+
information_schema { |i| i.table_columns table_name }
|
108
|
+
end
|
109
|
+
|
110
|
+
def new_column_from_field _table_name, field
|
111
|
+
ConnectionAdapters::Column.new \
|
112
|
+
field.name,
|
113
|
+
field.default,
|
114
|
+
fetch_type_metadata(field.spanner_type, field.ordinal_position),
|
115
|
+
field.nullable
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_type_metadata sql_type, ordinal_position = nil
|
119
|
+
Spanner::TypeMetadata.new \
|
120
|
+
super(sql_type), ordinal_position: ordinal_position
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_column table_name, column_name, type, **options
|
124
|
+
# Add column with NOT NULL not supported by spanner.
|
125
|
+
# It is currently un-implemented state in spanner service.
|
126
|
+
nullable = options.delete(:null) == false
|
127
|
+
|
128
|
+
at = create_alter_table table_name
|
129
|
+
at.add_column column_name, type, **options
|
130
|
+
|
131
|
+
statements = [schema_creation.accept(at)]
|
132
|
+
|
133
|
+
# Alter NOT NULL
|
134
|
+
if nullable
|
135
|
+
cd = at.adds.first.column
|
136
|
+
cd.null = false
|
137
|
+
ccd = Spanner::ChangeColumnDefinition.new(
|
138
|
+
table_name, cd, column_name
|
82
139
|
)
|
140
|
+
statements << schema_creation.accept(ccd)
|
141
|
+
end
|
142
|
+
|
143
|
+
execute_schema_statements statements
|
144
|
+
end
|
145
|
+
|
146
|
+
def remove_column table_name, column_name, _type = nil, _options = {}
|
147
|
+
statements = drop_column_sql table_name, column_name
|
148
|
+
execute_schema_statements statements
|
149
|
+
end
|
150
|
+
|
151
|
+
if ActiveRecord.gem_version < VERSION_6_1_0
|
152
|
+
def remove_columns table_name, *column_names
|
153
|
+
_remove_columns table_name, *column_names
|
154
|
+
end
|
155
|
+
else
|
156
|
+
def remove_columns table_name, *column_names, _type: nil, **_options
|
157
|
+
_remove_columns table_name, *column_names
|
83
158
|
end
|
84
159
|
end
|
85
160
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
161
|
+
def _remove_columns table_name, *column_names
|
162
|
+
if column_names.empty?
|
163
|
+
raise ArgumentError, "You must specify at least one column name. "\
|
164
|
+
"Example: remove_columns(:people, :first_name)"
|
165
|
+
end
|
166
|
+
|
167
|
+
statements = []
|
168
|
+
|
169
|
+
column_names.each do |column_name|
|
170
|
+
statements.concat drop_column_sql(table_name, column_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
execute_schema_statements statements
|
174
|
+
end
|
175
|
+
|
176
|
+
if ActiveRecord.gem_version < VERSION_6_1_0
|
177
|
+
def change_column table_name, column_name, type, options = {}
|
178
|
+
_change_column table_name, column_name, type, **options
|
179
|
+
end
|
180
|
+
else
|
181
|
+
def change_column table_name, column_name, type, **options
|
182
|
+
_change_column table_name, column_name, type, **options
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def change_column_null table_name, column_name, null, _default = nil
|
187
|
+
change_column table_name, column_name, nil, null: null
|
188
|
+
end
|
189
|
+
|
190
|
+
def change_column_default _table_name, _column_name, _default_or_changes
|
191
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, \
|
192
|
+
"change column with default value not supported."
|
193
|
+
end
|
194
|
+
|
195
|
+
def rename_column table_name, column_name, new_column_name
|
196
|
+
if ActiveRecord::Base.connection.ddl_batch?
|
197
|
+
raise ActiveRecordSpannerAdapter::NotSupportedError, \
|
198
|
+
"rename_column in a DDL Batch is not supported."
|
199
|
+
end
|
200
|
+
column = information_schema do |i|
|
201
|
+
i.table_column table_name, column_name
|
202
|
+
end
|
203
|
+
|
204
|
+
unless column
|
205
|
+
raise ArgumentError,
|
206
|
+
"Column '#{column_name}' not exist for table '#{table_name}'"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Add Column
|
210
|
+
cast_type = lookup_cast_type column.spanner_type
|
211
|
+
add_column table_name, new_column_name, cast_type.type, **column.options
|
212
|
+
|
213
|
+
# Copy data
|
214
|
+
copy_data table_name, column_name, new_column_name
|
215
|
+
|
216
|
+
# Recreate Indexes
|
217
|
+
recreate_indexes table_name, column_name, new_column_name
|
218
|
+
|
219
|
+
# Recreate Foreign keys
|
220
|
+
recreate_foreign_keys table_name, column_name, new_column_name
|
221
|
+
|
222
|
+
# Drop Indexes, Drop Foreign keys and columns
|
223
|
+
remove_column table_name, column_name
|
224
|
+
end
|
225
|
+
|
226
|
+
# Index
|
227
|
+
|
228
|
+
def indexes table_name
|
229
|
+
result = information_schema do |i|
|
230
|
+
i.indexes table_name, index_type: "INDEX"
|
231
|
+
end
|
232
|
+
|
233
|
+
result.map do |index|
|
234
|
+
IndexDefinition.new(
|
235
|
+
index.table,
|
236
|
+
index.name,
|
237
|
+
index.columns.map(&:name),
|
238
|
+
unique: index.unique,
|
239
|
+
null_filtered: index.null_filtered,
|
240
|
+
interleave_in: index.interleave_in,
|
241
|
+
storing: index.storing,
|
242
|
+
orders: index.orders
|
111
243
|
)
|
112
244
|
end
|
113
245
|
end
|
114
246
|
|
115
|
-
def
|
116
|
-
|
117
|
-
index.type == 'PRIMARY_KEY'
|
118
|
-
}.columns
|
247
|
+
def index_name_exists? table_name, index_name
|
248
|
+
information_schema { |i| i.index table_name, index_name }.present?
|
119
249
|
end
|
120
250
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
251
|
+
def add_index table_name, column_name, options = {}
|
252
|
+
id = create_index_definition table_name, column_name, **options
|
253
|
+
|
254
|
+
if data_source_exists?(table_name) &&
|
255
|
+
index_name_exists?(table_name, id.name)
|
256
|
+
raise ArgumentError, "Index name '#{id.name}' on table" \
|
257
|
+
"'#{table_name}' already exists"
|
258
|
+
end
|
259
|
+
|
260
|
+
execute_schema_statements schema_creation.accept(id)
|
126
261
|
end
|
127
262
|
|
128
|
-
|
129
|
-
|
263
|
+
if ActiveRecord.gem_version < VERSION_6_1_0
|
264
|
+
def remove_index table_name, options = {}
|
265
|
+
index_name = index_name_for_remove table_name, options
|
266
|
+
execute "DROP INDEX #{quote_table_name index_name}"
|
267
|
+
end
|
268
|
+
else
|
269
|
+
def remove_index table_name, column_name = nil, **options
|
270
|
+
index_name = index_name_for_remove table_name, column_name, options
|
271
|
+
execute "DROP INDEX #{quote_table_name index_name}"
|
272
|
+
end
|
130
273
|
end
|
131
274
|
|
132
|
-
def
|
133
|
-
|
134
|
-
raise NotImplementedError, 'force in drop_table' if options[:force]
|
275
|
+
def rename_index table_name, old_name, new_name
|
276
|
+
validate_index_length! table_name, new_name
|
135
277
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
278
|
+
old_index = information_schema { |i| i.index table_name, old_name }
|
279
|
+
return unless old_index
|
280
|
+
|
281
|
+
statements = [
|
282
|
+
schema_creation.accept(DropIndexDefinition.new(old_name))
|
283
|
+
]
|
284
|
+
|
285
|
+
id = IndexDefinition.new \
|
286
|
+
old_index.table,
|
287
|
+
new_name,
|
288
|
+
old_index.columns.map(&:name),
|
289
|
+
unique: old_index.unique,
|
290
|
+
null_filtered: old_index.null_filtered,
|
291
|
+
interleave_in: old_index.interleave_in,
|
292
|
+
storing: old_index.storing,
|
293
|
+
orders: old_index.orders
|
294
|
+
|
295
|
+
statements << schema_creation.accept(id)
|
296
|
+
execute_schema_statements statements
|
297
|
+
end
|
298
|
+
|
299
|
+
# Primary Keys
|
300
|
+
|
301
|
+
def primary_keys table_name
|
302
|
+
columns = information_schema do |i|
|
303
|
+
i.table_primary_keys table_name
|
304
|
+
end
|
305
|
+
|
306
|
+
columns.map(&:name)
|
307
|
+
end
|
308
|
+
|
309
|
+
def primary_and_parent_keys table_name
|
310
|
+
columns = information_schema do |i|
|
311
|
+
i.table_primary_keys table_name, true
|
312
|
+
end
|
141
313
|
|
142
|
-
|
143
|
-
execute_ddl(*ddls)
|
314
|
+
columns.map(&:name)
|
144
315
|
end
|
145
316
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
317
|
+
# Foreign Keys
|
318
|
+
|
319
|
+
def foreign_keys table_name, column: nil
|
320
|
+
raise ArgumentError if table_name.blank?
|
321
|
+
|
322
|
+
result = information_schema { |i| i.foreign_keys table_name }
|
323
|
+
|
324
|
+
if column
|
325
|
+
result = result.select { |fk| fk.columns.include? column.to_s }
|
326
|
+
end
|
327
|
+
|
328
|
+
result.map do |fk|
|
329
|
+
options = {
|
330
|
+
column: fk.columns.first,
|
331
|
+
name: fk.name,
|
332
|
+
primary_key: fk.ref_columns.first,
|
333
|
+
on_delete: fk.on_update,
|
334
|
+
on_update: fk.on_update
|
335
|
+
}
|
336
|
+
|
337
|
+
ForeignKeyDefinition.new table_name, fk.ref_table, options
|
338
|
+
end
|
155
339
|
end
|
156
340
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
341
|
+
if ActiveRecord.gem_version < VERSION_6_0_3
|
342
|
+
def add_foreign_key from_table, to_table, options = {}
|
343
|
+
_add_foreign_key from_table, to_table, **options
|
344
|
+
end
|
345
|
+
else
|
346
|
+
def add_foreign_key from_table, to_table, **options
|
347
|
+
_add_foreign_key from_table, to_table, **options
|
162
348
|
end
|
163
349
|
end
|
164
350
|
|
351
|
+
def _add_foreign_key from_table, to_table, **options
|
352
|
+
options = foreign_key_options from_table, to_table, options
|
353
|
+
at = create_alter_table from_table
|
354
|
+
at.add_foreign_key to_table, options
|
355
|
+
|
356
|
+
execute_schema_statements schema_creation.accept(at)
|
357
|
+
end
|
358
|
+
|
359
|
+
def remove_foreign_key from_table, to_table = nil, **options
|
360
|
+
fk_name_to_delete = foreign_key_for!(
|
361
|
+
from_table, to_table: to_table, **options
|
362
|
+
).name
|
363
|
+
|
364
|
+
at = create_alter_table from_table
|
365
|
+
at.drop_foreign_key fk_name_to_delete
|
366
|
+
|
367
|
+
execute_schema_statements schema_creation.accept(at)
|
368
|
+
end
|
369
|
+
|
370
|
+
# Reference Column
|
371
|
+
|
372
|
+
def add_reference table_name, ref_name, **options
|
373
|
+
ReferenceDefinition.new(ref_name, **options).add_to(
|
374
|
+
update_table_definition(table_name, self)
|
375
|
+
)
|
376
|
+
end
|
377
|
+
alias add_belongs_to add_reference
|
378
|
+
|
379
|
+
def quoted_scope name = nil, type: nil
|
380
|
+
scope = { schema: quote("") }
|
381
|
+
scope[:name] = quote name if name
|
382
|
+
scope[:type] = quote type if type
|
383
|
+
scope
|
384
|
+
end
|
385
|
+
|
386
|
+
def create_schema_dumper options
|
387
|
+
SchemaDumper.create self, options
|
388
|
+
end
|
389
|
+
|
390
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
391
|
+
def type_to_sql type, limit: nil, precision: nil, scale: nil, **opts
|
392
|
+
type = type.to_sym if type
|
393
|
+
native = native_database_types[type]
|
394
|
+
|
395
|
+
return type.to_s unless native
|
396
|
+
|
397
|
+
sql_type = (native.is_a?(Hash) ? native[:name] : native).dup
|
398
|
+
|
399
|
+
sql_type = "#{sql_type}(#{limit || native[:limit]})" if [:string, :text, :binary].include? type
|
400
|
+
sql_type = "ARRAY<#{sql_type}>" if opts[:array]
|
401
|
+
|
402
|
+
sql_type
|
403
|
+
end
|
404
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
405
|
+
|
165
406
|
private
|
166
|
-
|
167
|
-
|
407
|
+
|
408
|
+
def schema_creation
|
409
|
+
SchemaCreation.new self
|
410
|
+
end
|
411
|
+
|
412
|
+
def create_table_definition *args
|
413
|
+
TableDefinition.new self, args[0], options: args[1]
|
414
|
+
end
|
415
|
+
|
416
|
+
def able_to_ddl_batch? table_name
|
417
|
+
[ActiveRecord::InternalMetadata.table_name, ActiveRecord::SchemaMigration.table_name].exclude? table_name.to_s
|
418
|
+
end
|
419
|
+
|
420
|
+
def _change_column table_name, column_name, type, **options # rubocop:disable Metrics/AbcSize
|
421
|
+
column = information_schema do |i|
|
422
|
+
i.table_column table_name, column_name
|
423
|
+
end
|
424
|
+
|
425
|
+
unless column
|
426
|
+
raise ArgumentError,
|
427
|
+
"Column '#{column_name}' not exist for table '#{table_name}'"
|
428
|
+
end
|
429
|
+
|
430
|
+
indexes = information_schema do |i|
|
431
|
+
i.indexes_by_columns table_name, column_name
|
432
|
+
end
|
433
|
+
|
434
|
+
statements = indexes.map do |index|
|
435
|
+
schema_creation.accept DropIndexDefinition.new(index.name)
|
436
|
+
end
|
437
|
+
|
438
|
+
column = new_column_from_field table_name, column
|
439
|
+
|
440
|
+
type ||= column.type
|
441
|
+
options[:null] = column.null unless options.key? :null
|
442
|
+
|
443
|
+
if ["STRING", "BYTES"].include? type
|
444
|
+
options[:limit] = column.limit unless options.key? :limit
|
445
|
+
end
|
446
|
+
|
447
|
+
# Only timestamp type can set commit timestamp
|
448
|
+
if type == "TIMESTAMP" && options.key?(:allow_commit_timestamp) == false
|
449
|
+
options[:allow_commit_timestamp] = column.allow_commit_timestamp
|
450
|
+
end
|
451
|
+
|
452
|
+
td = create_table_definition table_name
|
453
|
+
cd = td.new_column_definition column.name, type, **options
|
454
|
+
|
455
|
+
ccd = Spanner::ChangeColumnDefinition.new table_name, cd, column.name
|
456
|
+
statements << schema_creation.accept(ccd)
|
457
|
+
|
458
|
+
# Recreate indexes
|
459
|
+
indexes.each do |index|
|
460
|
+
id = create_index_definition(
|
461
|
+
table_name,
|
462
|
+
index.column_names,
|
463
|
+
**index.options
|
464
|
+
)
|
465
|
+
statements << schema_creation.accept(id)
|
466
|
+
end
|
467
|
+
|
468
|
+
execute_schema_statements statements
|
469
|
+
end
|
470
|
+
|
471
|
+
def copy_data table_name, src_column_name, dest_column_name
|
472
|
+
sql = "UPDATE %<table>s SET %<dest_column_name>s = %<src_column_name>s WHERE true"
|
473
|
+
values = {
|
474
|
+
table: table_name,
|
475
|
+
dest_column_name: quote_column_name(dest_column_name),
|
476
|
+
src_column_name: quote_column_name(src_column_name)
|
477
|
+
}
|
478
|
+
ActiveRecord::Base.connection.transaction isolation: :pdml do
|
479
|
+
execute sql % values
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def recreate_indexes table_name, column_name, new_column_name
|
484
|
+
indexes = information_schema.indexes_by_columns table_name, column_name
|
485
|
+
indexes.each do |index|
|
486
|
+
remove_index table_name, name: index.name
|
487
|
+
options = index.rename_column_options column_name, new_column_name
|
488
|
+
options[:options][:name] = options[:options][:name].to_s.gsub(
|
489
|
+
column_name.to_s, new_column_name.to_s
|
490
|
+
)
|
491
|
+
add_index table_name, options[:columns], **options[:options]
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def recreate_foreign_keys table_name, column_name, new_column_name
|
496
|
+
fkeys = foreign_keys table_name, column: column_name
|
497
|
+
fkeys.each do |fk|
|
498
|
+
remove_foreign_key table_name, name: fk.name
|
499
|
+
options = fk.options.except :column, :name
|
500
|
+
options[:column] = new_column_name
|
501
|
+
add_foreign_key table_name, fk.to_table, **options
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def create_index_definition table_name, column_name, **options
|
506
|
+
column_names = index_column_names column_name
|
507
|
+
|
508
|
+
options.assert_valid_keys(
|
509
|
+
:unique, :order, :name, :where, :length, :internal, :using,
|
510
|
+
:algorithm, :type, :opclass, :interleave_in, :storing,
|
511
|
+
:null_filtered
|
512
|
+
)
|
513
|
+
|
514
|
+
index_name = options[:name].to_s if options.key? :name
|
515
|
+
index_name ||= index_name table_name, column_names
|
516
|
+
|
517
|
+
validate_index_length! table_name, index_name
|
518
|
+
|
519
|
+
IndexDefinition.new \
|
520
|
+
table_name,
|
521
|
+
index_name,
|
522
|
+
column_names,
|
523
|
+
unique: options[:unique],
|
524
|
+
null_filtered: options[:null_filtered],
|
525
|
+
interleave_in: options[:interleave_in],
|
526
|
+
storing: options[:storing],
|
527
|
+
orders: options[:order]
|
528
|
+
end
|
529
|
+
|
530
|
+
def drop_table_with_indexes_sql table_name, options
|
531
|
+
statements = []
|
532
|
+
|
533
|
+
table = information_schema { |i| i.table table_name, view: :indexes }
|
534
|
+
return statements unless table
|
535
|
+
|
536
|
+
table.indexes.each do |index|
|
537
|
+
next if index.primary?
|
538
|
+
|
539
|
+
statements << schema_creation.accept(
|
540
|
+
DropIndexDefinition.new(index.name)
|
541
|
+
)
|
542
|
+
end
|
543
|
+
|
544
|
+
statements << schema_creation.accept(
|
545
|
+
DropTableDefinition.new(table_name, options)
|
546
|
+
)
|
547
|
+
statements
|
548
|
+
end
|
549
|
+
|
550
|
+
def drop_column_sql table_name, column_name
|
551
|
+
indexes = information_schema do |i|
|
552
|
+
i.indexes_by_columns table_name, column_name
|
553
|
+
end
|
554
|
+
|
555
|
+
statements = indexes.map do |index|
|
556
|
+
schema_creation.accept DropIndexDefinition.new(index.name)
|
557
|
+
end
|
558
|
+
|
559
|
+
foreign_keys(table_name, column: column_name).each do |fk|
|
560
|
+
at = create_alter_table table_name
|
561
|
+
at.drop_foreign_key fk.name
|
562
|
+
statements << schema_creation.accept(at)
|
563
|
+
end
|
564
|
+
|
565
|
+
statements << schema_creation.accept(
|
566
|
+
DropColumnDefinition.new(table_name, column_name)
|
567
|
+
)
|
568
|
+
|
569
|
+
statements
|
570
|
+
end
|
571
|
+
|
572
|
+
def execute_schema_statements statements
|
573
|
+
execute_ddl statements
|
574
|
+
end
|
575
|
+
|
576
|
+
def information_schema
|
577
|
+
info_schema = \
|
578
|
+
ActiveRecordSpannerAdapter::Connection.information_schema @config
|
579
|
+
|
580
|
+
return info_schema unless block_given?
|
581
|
+
|
582
|
+
yield info_schema
|
168
583
|
end
|
169
584
|
end
|
170
585
|
end
|
171
586
|
end
|
172
587
|
end
|
173
|
-
|