activerecord-spanner-adapter 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +7 -0
- data/.github/sync-repo-settings.yaml +16 -0
- data/.github/workflows/acceptance-tests-on-emulator.yaml +45 -0
- data/.github/workflows/acceptance-tests-on-production.yaml +36 -0
- data/.github/workflows/ci.yaml +33 -0
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +52 -0
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +35 -0
- data/.github/workflows/nightly-unit-tests.yaml +40 -0
- data/.github/workflows/release-please-label.yml +25 -0
- data/.github/workflows/release-please.yml +39 -0
- data/.github/workflows/rubocop.yaml +31 -0
- data/.gitignore +67 -5
- data/.kokoro/populate-secrets.sh +77 -0
- data/.kokoro/release.cfg +33 -0
- data/.kokoro/release.sh +15 -0
- data/.kokoro/trampoline_v2.sh +489 -0
- data/.rubocop.yml +46 -0
- data/.toys/release.rb +18 -0
- data/.trampolinerc +48 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +9 -4
- data/LICENSE +6 -6
- data/README.md +67 -30
- data/Rakefile +79 -3
- data/SECURITY.md +7 -0
- data/acceptance/cases/associations/has_many_associations_test.rb +119 -0
- data/acceptance/cases/associations/has_many_through_associations_test.rb +63 -0
- data/acceptance/cases/associations/has_one_associations_test.rb +79 -0
- data/acceptance/cases/associations/has_one_through_associations_test.rb +98 -0
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +211 -0
- data/acceptance/cases/migration/change_schema_test.rb +433 -0
- data/acceptance/cases/migration/change_table_test.rb +115 -0
- data/acceptance/cases/migration/column_attributes_test.rb +122 -0
- data/acceptance/cases/migration/column_positioning_test.rb +48 -0
- data/acceptance/cases/migration/columns_test.rb +201 -0
- data/acceptance/cases/migration/command_recorder_test.rb +406 -0
- data/acceptance/cases/migration/create_join_table_test.rb +216 -0
- data/acceptance/cases/migration/ddl_batching_test.rb +80 -0
- data/acceptance/cases/migration/foreign_key_test.rb +297 -0
- data/acceptance/cases/migration/index_test.rb +211 -0
- data/acceptance/cases/migration/references_foreign_key_test.rb +259 -0
- data/acceptance/cases/migration/references_index_test.rb +135 -0
- data/acceptance/cases/migration/references_statements_test.rb +166 -0
- data/acceptance/cases/migration/rename_column_test.rb +96 -0
- data/acceptance/cases/models/calculation_query_test.rb +128 -0
- data/acceptance/cases/models/generated_column_test.rb +126 -0
- data/acceptance/cases/models/mutation_test.rb +122 -0
- data/acceptance/cases/models/query_test.rb +147 -0
- data/acceptance/cases/sessions/session_not_found_test.rb +121 -0
- data/acceptance/cases/transactions/optimistic_locking_test.rb +141 -0
- data/acceptance/cases/transactions/read_only_transactions_test.rb +67 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
- data/acceptance/cases/type/all_types_test.rb +152 -0
- data/acceptance/cases/type/binary_test.rb +59 -0
- data/acceptance/cases/type/boolean_test.rb +31 -0
- data/acceptance/cases/type/date_test.rb +32 -0
- data/acceptance/cases/type/date_time_test.rb +30 -0
- data/acceptance/cases/type/float_test.rb +27 -0
- data/acceptance/cases/type/integer_test.rb +44 -0
- data/acceptance/cases/type/numeric_test.rb +27 -0
- data/acceptance/cases/type/string_test.rb +79 -0
- data/acceptance/cases/type/text_test.rb +30 -0
- data/acceptance/cases/type/time_test.rb +87 -0
- data/acceptance/models/account.rb +13 -0
- data/acceptance/models/address.rb +9 -0
- data/acceptance/models/album.rb +12 -0
- data/acceptance/models/all_types.rb +8 -0
- data/acceptance/models/author.rb +11 -0
- data/acceptance/models/club.rb +12 -0
- data/acceptance/models/comment.rb +9 -0
- data/acceptance/models/customer.rb +9 -0
- data/acceptance/models/department.rb +9 -0
- data/acceptance/models/firm.rb +10 -0
- data/acceptance/models/member.rb +13 -0
- data/acceptance/models/member_type.rb +9 -0
- data/acceptance/models/membership.rb +10 -0
- data/acceptance/models/organization.rb +9 -0
- data/acceptance/models/post.rb +10 -0
- data/acceptance/models/singer.rb +10 -0
- data/acceptance/models/track.rb +20 -0
- data/acceptance/models/transaction.rb +9 -0
- data/acceptance/schema/schema.rb +143 -0
- data/acceptance/test_helper.rb +260 -0
- data/activerecord-spanner-adapter.gemspec +32 -17
- data/assets/solidus-db.png +0 -0
- data/benchmarks/README.md +17 -0
- data/benchmarks/Rakefile +14 -0
- data/benchmarks/application.rb +308 -0
- data/benchmarks/config/database.yml +8 -0
- data/benchmarks/config/environment.rb +12 -0
- data/benchmarks/db/migrate/01_create_tables.rb +25 -0
- data/benchmarks/db/schema.rb +29 -0
- data/benchmarks/models/album.rb +9 -0
- data/benchmarks/models/singer.rb +9 -0
- data/bin/console +6 -7
- data/examples/rails/README.md +262 -0
- data/examples/snippets/README.md +29 -0
- data/examples/snippets/Rakefile +57 -0
- data/examples/snippets/array-data-type/README.md +45 -0
- data/examples/snippets/array-data-type/Rakefile +13 -0
- data/examples/snippets/array-data-type/application.rb +45 -0
- data/examples/snippets/array-data-type/config/database.yml +8 -0
- data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
- data/examples/snippets/array-data-type/db/schema.rb +26 -0
- data/examples/snippets/array-data-type/db/seeds.rb +5 -0
- data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
- data/examples/snippets/bin/create_emulator_instance.rb +18 -0
- data/examples/snippets/bulk-insert/README.md +21 -0
- data/examples/snippets/bulk-insert/Rakefile +13 -0
- data/examples/snippets/bulk-insert/application.rb +64 -0
- data/examples/snippets/bulk-insert/config/database.yml +8 -0
- data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/bulk-insert/db/schema.rb +26 -0
- data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
- data/examples/snippets/bulk-insert/models/album.rb +9 -0
- data/examples/snippets/bulk-insert/models/singer.rb +9 -0
- data/examples/snippets/commit-timestamp/README.md +18 -0
- data/examples/snippets/commit-timestamp/Rakefile +13 -0
- data/examples/snippets/commit-timestamp/application.rb +53 -0
- data/examples/snippets/commit-timestamp/config/database.yml +8 -0
- data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
- data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
- data/examples/snippets/commit-timestamp/models/album.rb +9 -0
- data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
- data/examples/snippets/config/environment.rb +21 -0
- data/examples/snippets/create-records/README.md +12 -0
- data/examples/snippets/create-records/Rakefile +13 -0
- data/examples/snippets/create-records/application.rb +42 -0
- data/examples/snippets/create-records/config/database.yml +8 -0
- data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/create-records/db/schema.rb +26 -0
- data/examples/snippets/create-records/db/seeds.rb +5 -0
- data/examples/snippets/create-records/models/album.rb +9 -0
- data/examples/snippets/create-records/models/singer.rb +9 -0
- data/examples/snippets/date-data-type/README.md +19 -0
- data/examples/snippets/date-data-type/Rakefile +13 -0
- data/examples/snippets/date-data-type/application.rb +35 -0
- data/examples/snippets/date-data-type/config/database.yml +8 -0
- data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
- data/examples/snippets/date-data-type/db/schema.rb +21 -0
- data/examples/snippets/date-data-type/db/seeds.rb +16 -0
- data/examples/snippets/date-data-type/models/singer.rb +8 -0
- data/examples/snippets/generated-column/README.md +41 -0
- data/examples/snippets/generated-column/Rakefile +13 -0
- data/examples/snippets/generated-column/application.rb +37 -0
- data/examples/snippets/generated-column/config/database.yml +8 -0
- data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
- data/examples/snippets/generated-column/db/schema.rb +21 -0
- data/examples/snippets/generated-column/db/seeds.rb +18 -0
- data/examples/snippets/generated-column/models/singer.rb +8 -0
- data/examples/snippets/interleaved-tables/README.md +152 -0
- data/examples/snippets/interleaved-tables/Rakefile +13 -0
- data/examples/snippets/interleaved-tables/application.rb +109 -0
- data/examples/snippets/interleaved-tables/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables/models/album.rb +15 -0
- data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
- data/examples/snippets/interleaved-tables/models/track.rb +25 -0
- data/examples/snippets/migrations/README.md +43 -0
- data/examples/snippets/migrations/Rakefile +13 -0
- data/examples/snippets/migrations/application.rb +26 -0
- data/examples/snippets/migrations/config/database.yml +8 -0
- data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
- data/examples/snippets/migrations/db/schema.rb +33 -0
- data/examples/snippets/migrations/db/seeds.rb +5 -0
- data/examples/snippets/migrations/models/album.rb +10 -0
- data/examples/snippets/migrations/models/singer.rb +10 -0
- data/examples/snippets/migrations/models/track.rb +9 -0
- data/examples/snippets/mutations/README.md +34 -0
- data/examples/snippets/mutations/Rakefile +13 -0
- data/examples/snippets/mutations/application.rb +47 -0
- data/examples/snippets/mutations/config/database.yml +8 -0
- data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/mutations/db/schema.rb +27 -0
- data/examples/snippets/mutations/db/seeds.rb +25 -0
- data/examples/snippets/mutations/models/album.rb +9 -0
- data/examples/snippets/mutations/models/singer.rb +9 -0
- data/examples/snippets/optimistic-locking/README.md +12 -0
- data/examples/snippets/optimistic-locking/Rakefile +13 -0
- data/examples/snippets/optimistic-locking/application.rb +48 -0
- data/examples/snippets/optimistic-locking/config/database.yml +8 -0
- data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
- data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
- data/examples/snippets/optimistic-locking/models/album.rb +9 -0
- data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
- data/examples/snippets/quickstart/README.md +26 -0
- data/examples/snippets/quickstart/Rakefile +13 -0
- data/examples/snippets/quickstart/application.rb +51 -0
- data/examples/snippets/quickstart/config/database.yml +8 -0
- data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/quickstart/db/schema.rb +26 -0
- data/examples/snippets/quickstart/db/seeds.rb +24 -0
- data/examples/snippets/quickstart/models/album.rb +9 -0
- data/examples/snippets/quickstart/models/singer.rb +9 -0
- data/examples/snippets/read-only-transactions/README.md +13 -0
- data/examples/snippets/read-only-transactions/Rakefile +13 -0
- data/examples/snippets/read-only-transactions/application.rb +49 -0
- data/examples/snippets/read-only-transactions/config/database.yml +8 -0
- data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
- data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
- data/examples/snippets/read-only-transactions/models/album.rb +9 -0
- data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
- data/examples/snippets/read-write-transactions/README.md +12 -0
- data/examples/snippets/read-write-transactions/Rakefile +13 -0
- data/examples/snippets/read-write-transactions/application.rb +39 -0
- data/examples/snippets/read-write-transactions/config/database.yml +8 -0
- data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
- data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
- data/examples/snippets/read-write-transactions/models/album.rb +9 -0
- data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
- data/examples/snippets/timestamp-data-type/README.md +17 -0
- data/examples/snippets/timestamp-data-type/Rakefile +13 -0
- data/examples/snippets/timestamp-data-type/application.rb +42 -0
- data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
- data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
- data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
- data/examples/solidus/README.md +172 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +224 -269
- data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
- data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
- data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/spanner_adapter.rb +182 -78
- data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
- data/lib/active_record/type/spanner/array.rb +32 -0
- data/lib/active_record/type/spanner/bytes.rb +26 -0
- data/lib/active_record/type/spanner/spanner_active_record_converter.rb +32 -0
- data/lib/active_record/type/spanner/time.rb +37 -0
- data/lib/activerecord-spanner-adapter.rb +23 -0
- data/lib/activerecord_spanner_adapter/base.rb +217 -0
- data/lib/activerecord_spanner_adapter/connection.rb +324 -0
- data/lib/activerecord_spanner_adapter/errors.rb +13 -0
- data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
- data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
- data/lib/activerecord_spanner_adapter/index.rb +80 -0
- data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
- data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
- data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
- data/lib/activerecord_spanner_adapter/table.rb +61 -0
- data/lib/activerecord_spanner_adapter/transaction.rb +113 -0
- data/lib/activerecord_spanner_adapter/version.rb +9 -0
- data/lib/arel/visitors/spanner.rb +35 -0
- data/lib/spanner_client_ext.rb +82 -0
- data/renovate.json +5 -0
- metadata +387 -34
- data/.travis.yml +0 -5
- data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
- data/lib/active_record/connection_adapters/spanner.rb +0 -10
- data/lib/activerecord-spanner-adapter/version.rb +0 -3
|
@@ -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
|
-
|