activerecord-spanner-adapter 1.5.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/acceptance-tests-on-emulator.yaml +1 -1
- data/.github/workflows/acceptance-tests-on-production.yaml +5 -3
- data/.github/workflows/ci.yaml +1 -1
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +1 -1
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +5 -3
- data/.github/workflows/nightly-unit-tests.yaml +1 -1
- data/.github/workflows/release-please-label.yml +1 -1
- data/.github/workflows/rubocop.yaml +1 -1
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile +7 -2
- data/README.md +10 -10
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +6 -0
- data/acceptance/cases/migration/change_schema_test.rb +19 -3
- data/acceptance/cases/migration/schema_dumper_test.rb +10 -1
- data/acceptance/cases/models/interleave_test.rb +6 -0
- data/acceptance/cases/tasks/database_tasks_test.rb +340 -2
- data/acceptance/cases/transactions/optimistic_locking_test.rb +6 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +24 -0
- data/acceptance/models/table_with_sequence.rb +10 -0
- data/acceptance/schema/schema.rb +65 -19
- data/acceptance/test_helper.rb +1 -1
- data/activerecord-spanner-adapter.gemspec +1 -1
- data/benchmarks/application.rb +1 -1
- data/examples/snippets/bit-reversed-sequence/README.md +103 -0
- data/examples/snippets/bit-reversed-sequence/Rakefile +13 -0
- data/examples/snippets/bit-reversed-sequence/application.rb +68 -0
- data/examples/snippets/bit-reversed-sequence/config/database.yml +8 -0
- data/examples/snippets/bit-reversed-sequence/db/migrate/01_create_tables.rb +33 -0
- data/examples/snippets/bit-reversed-sequence/db/schema.rb +31 -0
- data/examples/snippets/bit-reversed-sequence/db/seeds.rb +31 -0
- data/examples/snippets/bit-reversed-sequence/models/album.rb +11 -0
- data/examples/snippets/bit-reversed-sequence/models/singer.rb +15 -0
- data/examples/snippets/interleaved-tables/README.md +44 -53
- data/examples/snippets/interleaved-tables/Rakefile +2 -2
- data/examples/snippets/interleaved-tables/application.rb +2 -2
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +12 -18
- data/examples/snippets/interleaved-tables/db/schema.rb +9 -7
- data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
- data/examples/snippets/interleaved-tables/models/album.rb +3 -7
- data/examples/snippets/interleaved-tables/models/singer.rb +1 -1
- data/examples/snippets/interleaved-tables/models/track.rb +6 -7
- data/examples/snippets/interleaved-tables-before-7.1/README.md +167 -0
- data/examples/snippets/interleaved-tables-before-7.1/Rakefile +13 -0
- data/examples/snippets/interleaved-tables-before-7.1/application.rb +126 -0
- data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +37 -0
- data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/album.rb +20 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/singer.rb +18 -0
- data/examples/snippets/interleaved-tables-before-7.1/models/track.rb +28 -0
- data/examples/snippets/query-logs/README.md +43 -0
- data/examples/snippets/query-logs/Rakefile +13 -0
- data/examples/snippets/query-logs/application.rb +63 -0
- data/examples/snippets/query-logs/config/database.yml +8 -0
- data/examples/snippets/query-logs/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/query-logs/db/schema.rb +31 -0
- data/examples/snippets/query-logs/db/seeds.rb +24 -0
- data/examples/snippets/query-logs/models/album.rb +9 -0
- data/examples/snippets/query-logs/models/singer.rb +9 -0
- data/examples/snippets/read-only-transactions/application.rb +1 -1
- data/lib/active_record/connection_adapters/spanner/column.rb +13 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +144 -35
- data/lib/active_record/connection_adapters/spanner/schema_cache.rb +3 -21
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +11 -2
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +12 -2
- data/lib/active_record/connection_adapters/spanner_adapter.rb +28 -9
- data/lib/activerecord_spanner_adapter/base.rb +64 -20
- data/lib/activerecord_spanner_adapter/foreign_key.rb +6 -2
- data/lib/activerecord_spanner_adapter/index/column.rb +3 -1
- data/lib/activerecord_spanner_adapter/index.rb +4 -2
- data/lib/activerecord_spanner_adapter/information_schema.rb +124 -72
- data/lib/activerecord_spanner_adapter/primary_key.rb +1 -1
- data/lib/activerecord_spanner_adapter/table/column.rb +7 -10
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- data/lib/arel/visitors/spanner.rb +3 -1
- data/lib/spanner_client_ext.rb +6 -1
- metadata +34 -5
@@ -4,6 +4,7 @@
|
|
4
4
|
# license that can be found in the LICENSE file or at
|
5
5
|
# https://opensource.org/licenses/MIT.
|
6
6
|
|
7
|
+
require "active_record/gem_version"
|
7
8
|
require "activerecord_spanner_adapter/relation"
|
8
9
|
|
9
10
|
module ActiveRecord
|
@@ -14,6 +15,8 @@ module ActiveRecord
|
|
14
15
|
end
|
15
16
|
|
16
17
|
class Base
|
18
|
+
VERSION_7_1 = Gem::Version.create "7.1.0"
|
19
|
+
|
17
20
|
# Creates an object (or multiple objects) and saves it to the database. This method will use mutations instead
|
18
21
|
# of DML if there is no active transaction, or if the active transaction has been created with the option
|
19
22
|
# isolation: :buffered_mutations.
|
@@ -30,7 +33,9 @@ module ActiveRecord
|
|
30
33
|
return super unless spanner_adapter?
|
31
34
|
return super if active_transaction?
|
32
35
|
|
33
|
-
|
36
|
+
# Only use mutations to create new records if the primary key is generated client-side.
|
37
|
+
isolation = sequence_name ? nil : :buffered_mutations
|
38
|
+
transaction isolation: isolation do
|
34
39
|
return super
|
35
40
|
end
|
36
41
|
end
|
@@ -43,35 +48,74 @@ module ActiveRecord
|
|
43
48
|
spanner_adapter? && connection&.current_spanner_transaction&.isolation == :buffered_mutations
|
44
49
|
end
|
45
50
|
|
46
|
-
def self._insert_record values
|
47
|
-
|
51
|
+
def self._insert_record values, returning = []
|
52
|
+
if !(buffered_mutations? || (primary_key && values.is_a?(Hash))) || !spanner_adapter?
|
53
|
+
return super values if ActiveRecord.gem_version < VERSION_7_1
|
54
|
+
return super
|
55
|
+
end
|
48
56
|
|
49
|
-
|
57
|
+
# Mutations cannot be used in combination with a sequence, as mutations do not support a THEN RETURN clause.
|
58
|
+
if buffered_mutations? && sequence_name
|
59
|
+
raise StatementInvalid, "Mutations cannot be used to create records that use a sequence " \
|
60
|
+
"to generate the primary key. #{self} uses #{sequence_name}."
|
61
|
+
end
|
50
62
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
63
|
+
return _buffer_record values, :insert, returning if buffered_mutations?
|
64
|
+
|
65
|
+
_insert_record_dml values, returning
|
66
|
+
end
|
67
|
+
|
68
|
+
def self._insert_record_dml values, returning
|
69
|
+
primary_key_value = _set_primary_key_value values
|
57
70
|
if ActiveRecord::VERSION::MAJOR >= 7
|
58
71
|
im = Arel::InsertManager.new arel_table
|
59
72
|
im.insert(values.transform_keys { |name| arel_table[name] })
|
60
73
|
else
|
61
74
|
im = arel_table.compile_insert _substitute_values(values)
|
62
75
|
end
|
63
|
-
connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
|
76
|
+
result = connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
|
77
|
+
|
78
|
+
_convert_primary_key result, returning
|
79
|
+
end
|
80
|
+
|
81
|
+
def self._set_primary_key_value values
|
82
|
+
if primary_key.is_a? Array
|
83
|
+
_set_composite_primary_key_values primary_key, values
|
84
|
+
else
|
85
|
+
_set_single_primary_key_value primary_key, values
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self._convert_primary_key primary_key_value, returning
|
90
|
+
# Rails 7.1 and higher supports composite primary keys, and therefore require the provider to return an array
|
91
|
+
# instead of a single value in all cases. The order of the values should be equal to the order of the returning
|
92
|
+
# columns (or the primary key if no returning columns were specified).
|
93
|
+
return primary_key_value if ActiveRecord.gem_version < VERSION_7_1
|
94
|
+
return primary_key_value if primary_key_value.is_a?(Array) && primary_key_value.length == 1
|
95
|
+
return [primary_key_value] unless primary_key_value.is_a? Array
|
96
|
+
|
97
|
+
# Re-order the returned values according to the returning columns if it is not equal to the primary key of the
|
98
|
+
# table.
|
99
|
+
keys = returning || primary_key
|
100
|
+
return primary_key_value if keys == primary_key
|
101
|
+
|
102
|
+
primary_key_values_hash = Hash[primary_key.zip(primary_key_value)]
|
103
|
+
values = []
|
104
|
+
keys.each do |column|
|
105
|
+
values.append primary_key_values_hash[column]
|
106
|
+
end
|
107
|
+
values
|
64
108
|
end
|
65
109
|
|
66
|
-
def self._upsert_record values
|
67
|
-
_buffer_record values, :insert_or_update
|
110
|
+
def self._upsert_record values, returning
|
111
|
+
_buffer_record values, :insert_or_update, returning
|
68
112
|
end
|
69
113
|
|
70
114
|
def self.insert_all _attributes, **_kwargs
|
71
115
|
raise NotImplementedError, "Cloud Spanner does not support skip_duplicates. Use insert! or upsert instead."
|
72
116
|
end
|
73
117
|
|
74
|
-
def self.insert_all! attributes, **_kwargs
|
118
|
+
def self.insert_all! attributes, returning: nil, **_kwargs
|
75
119
|
return super unless spanner_adapter?
|
76
120
|
return super if active_transaction? && !buffered_mutations?
|
77
121
|
|
@@ -90,7 +134,7 @@ module ActiveRecord
|
|
90
134
|
end
|
91
135
|
end
|
92
136
|
|
93
|
-
def self.upsert_all attributes, **_kwargs
|
137
|
+
def self.upsert_all attributes, returning: nil, unique_by: nil, **_kwargs
|
94
138
|
return super unless spanner_adapter?
|
95
139
|
if active_transaction? && !buffered_mutations?
|
96
140
|
raise NotImplementedError, "Cloud Spanner does not support upsert using DML. " \
|
@@ -102,18 +146,18 @@ module ActiveRecord
|
|
102
146
|
# The mutations will be sent as one batch when the transaction is committed.
|
103
147
|
if active_transaction?
|
104
148
|
attributes.each do |record|
|
105
|
-
_upsert_record record
|
149
|
+
_upsert_record record, returning
|
106
150
|
end
|
107
151
|
else
|
108
152
|
transaction isolation: :buffered_mutations do
|
109
153
|
attributes.each do |record|
|
110
|
-
_upsert_record record
|
154
|
+
_upsert_record record, returning
|
111
155
|
end
|
112
156
|
end
|
113
157
|
end
|
114
158
|
end
|
115
159
|
|
116
|
-
def self._buffer_record values, method
|
160
|
+
def self._buffer_record values, method, returning
|
117
161
|
primary_key_value =
|
118
162
|
if primary_key.is_a? Array
|
119
163
|
_set_composite_primary_key_values primary_key, values
|
@@ -132,10 +176,9 @@ module ActiveRecord
|
|
132
176
|
mutation = Google::Cloud::Spanner::V1::Mutation.new(
|
133
177
|
"#{method}": write
|
134
178
|
)
|
135
|
-
|
136
179
|
connection.current_spanner_transaction.buffer mutation
|
137
180
|
|
138
|
-
primary_key_value
|
181
|
+
_convert_primary_key primary_key_value, returning
|
139
182
|
end
|
140
183
|
|
141
184
|
def self._set_composite_primary_key_values primary_key, values
|
@@ -174,6 +217,7 @@ module ActiveRecord
|
|
174
217
|
def self._set_single_primary_key_value primary_key, values
|
175
218
|
primary_key_value = values[primary_key] || values[primary_key.to_sym]
|
176
219
|
|
220
|
+
return primary_key_value if sequence_name
|
177
221
|
return primary_key_value unless prefetch_primary_key?
|
178
222
|
|
179
223
|
if primary_key_value.nil?
|
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
module ActiveRecordSpannerAdapter
|
8
8
|
class ForeignKey
|
9
|
-
attr_accessor :table_name, :name, :columns, :ref_table, :ref_columns,
|
9
|
+
attr_accessor :table_schema, :table_name, :name, :columns, :ref_schema, :ref_table, :ref_columns,
|
10
10
|
:on_delete, :on_update
|
11
11
|
|
12
12
|
def initialize \
|
@@ -16,10 +16,14 @@ module ActiveRecordSpannerAdapter
|
|
16
16
|
ref_table,
|
17
17
|
ref_columns,
|
18
18
|
on_delete: nil,
|
19
|
-
on_update: nil
|
19
|
+
on_update: nil,
|
20
|
+
table_schema: "",
|
21
|
+
ref_schema: ""
|
22
|
+
@table_schema = table_schema
|
20
23
|
@table_name = table_name
|
21
24
|
@name = name
|
22
25
|
@columns = Array(columns)
|
26
|
+
@ref_schema = ref_schema
|
23
27
|
@ref_table = ref_table
|
24
28
|
@ref_columns = Array(ref_columns)
|
25
29
|
@on_delete = on_delete unless on_delete == "NO ACTION"
|
@@ -7,16 +7,18 @@
|
|
7
7
|
module ActiveRecordSpannerAdapter
|
8
8
|
class Index
|
9
9
|
class Column
|
10
|
-
attr_accessor :table_name, :index_name, :name, :order, :ordinal_position
|
10
|
+
attr_accessor :table_name, :schema_name, :index_name, :name, :order, :ordinal_position
|
11
11
|
|
12
12
|
def initialize \
|
13
13
|
table_name,
|
14
14
|
index_name,
|
15
15
|
name,
|
16
|
+
schema_name: "",
|
16
17
|
order: nil,
|
17
18
|
ordinal_position: nil
|
18
19
|
@table_name = table_name.to_s
|
19
20
|
@index_name = index_name.to_s
|
21
|
+
@schema_name = schema_name.to_s
|
20
22
|
@name = name.to_s
|
21
23
|
@order = order.to_s.upcase if order
|
22
24
|
@ordinal_position = ordinal_position
|
@@ -8,7 +8,7 @@ require "activerecord_spanner_adapter/index/column"
|
|
8
8
|
|
9
9
|
module ActiveRecordSpannerAdapter
|
10
10
|
class Index
|
11
|
-
attr_accessor :table, :name, :columns, :type, :unique, :null_filtered,
|
11
|
+
attr_accessor :schema, :table, :name, :columns, :type, :unique, :null_filtered,
|
12
12
|
:interleave_in, :storing, :state
|
13
13
|
|
14
14
|
def initialize \
|
@@ -20,7 +20,9 @@ module ActiveRecordSpannerAdapter
|
|
20
20
|
null_filtered: false,
|
21
21
|
interleave_in: nil,
|
22
22
|
storing: nil,
|
23
|
-
state: nil
|
23
|
+
state: nil,
|
24
|
+
schema: ""
|
25
|
+
@schema = schema.to_s
|
24
26
|
@table = table.to_s
|
25
27
|
@name = name.to_s
|
26
28
|
@columns = Array(columns)
|
@@ -15,6 +15,8 @@ module ActiveRecordSpannerAdapter
|
|
15
15
|
class InformationSchema
|
16
16
|
include ActiveRecord::ConnectionAdapters::Quoting
|
17
17
|
|
18
|
+
IsRails71OrLater = ActiveRecord.gem_version >= Gem::Version.create("7.1.0")
|
19
|
+
|
18
20
|
attr_reader :connection
|
19
21
|
|
20
22
|
def initialize connection
|
@@ -22,7 +24,7 @@ module ActiveRecordSpannerAdapter
|
|
22
24
|
@mutex = Mutex.new
|
23
25
|
end
|
24
26
|
|
25
|
-
def tables table_name: nil, schema_name:
|
27
|
+
def tables table_name: nil, schema_name: "", view: nil
|
26
28
|
sql = +"SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, PARENT_TABLE_NAME, ON_DELETE_ACTION"
|
27
29
|
sql << " FROM INFORMATION_SCHEMA.TABLES"
|
28
30
|
sql << " WHERE TABLE_SCHEMA=%<schema_name>s"
|
@@ -43,17 +45,17 @@ module ActiveRecordSpannerAdapter
|
|
43
45
|
)
|
44
46
|
|
45
47
|
if [:full, :columns].include? view
|
46
|
-
table.columns = table_columns table.name
|
48
|
+
table.columns = table_columns table.name, schema_name: schema_name
|
47
49
|
end
|
48
50
|
|
49
51
|
if [:full, :indexes].include? view
|
50
|
-
table.indexes = indexes table.name
|
52
|
+
table.indexes = indexes table.name, schema_name: table.schema_name
|
51
53
|
end
|
52
54
|
table
|
53
55
|
end
|
54
56
|
end
|
55
57
|
|
56
|
-
def table table_name, schema_name:
|
58
|
+
def table table_name, schema_name: "", view: nil
|
57
59
|
tables(
|
58
60
|
table_name: table_name,
|
59
61
|
schema_name: schema_name,
|
@@ -61,52 +63,62 @@ module ActiveRecordSpannerAdapter
|
|
61
63
|
).first
|
62
64
|
end
|
63
65
|
|
64
|
-
def table_columns table_name, column_name: nil
|
66
|
+
def table_columns table_name, column_name: nil, schema_name: ""
|
67
|
+
primary_keys = table_primary_keys(table_name).map(&:name)
|
65
68
|
sql = +"SELECT COLUMN_NAME, SPANNER_TYPE, IS_NULLABLE, GENERATION_EXPRESSION,"
|
66
69
|
sql << " CAST(COLUMN_DEFAULT AS STRING) AS COLUMN_DEFAULT, ORDINAL_POSITION"
|
67
70
|
sql << " FROM INFORMATION_SCHEMA.COLUMNS"
|
68
71
|
sql << " WHERE TABLE_NAME=%<table_name>s"
|
72
|
+
sql << " AND TABLE_SCHEMA=%<schema_name>s"
|
69
73
|
sql << " AND COLUMN_NAME=%<column_name>s" if column_name
|
70
74
|
sql << " ORDER BY ORDINAL_POSITION ASC"
|
71
75
|
|
72
|
-
column_options = column_options table_name, column_name
|
76
|
+
column_options = column_options table_name, column_name, schema_name: schema_name
|
73
77
|
execute_query(
|
74
78
|
sql,
|
75
79
|
table_name: table_name,
|
76
|
-
column_name: column_name
|
80
|
+
column_name: column_name,
|
81
|
+
schema_name: schema_name
|
77
82
|
).map do |row|
|
78
|
-
|
79
|
-
|
80
|
-
|
83
|
+
_create_column table_name, row, primary_keys, column_options, schema_name: schema_name
|
84
|
+
end
|
85
|
+
end
|
81
86
|
|
82
|
-
|
83
|
-
|
87
|
+
def _create_column table_name, row, primary_keys, column_options, schema_name: ""
|
88
|
+
type, limit = parse_type_and_limit row["SPANNER_TYPE"]
|
89
|
+
column_name = row["COLUMN_NAME"]
|
90
|
+
options = column_options[column_name]
|
91
|
+
primary_key = primary_keys.include? column_name
|
84
92
|
|
85
|
-
|
86
|
-
|
87
|
-
default = nil
|
88
|
-
end
|
93
|
+
default = row["COLUMN_DEFAULT"]
|
94
|
+
default_function = row["GENERATION_EXPRESSION"]
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
if default && default.length < 200 && /\w+\(.*\)/.match?(default)
|
97
|
+
default_function ||= default
|
98
|
+
default = nil
|
99
|
+
end
|
93
100
|
|
94
|
-
|
95
|
-
|
96
|
-
column_name,
|
97
|
-
type,
|
98
|
-
limit: limit,
|
99
|
-
allow_commit_timestamp: options["allow_commit_timestamp"],
|
100
|
-
ordinal_position: row["ORDINAL_POSITION"],
|
101
|
-
nullable: row["IS_NULLABLE"] == "YES",
|
102
|
-
default: default,
|
103
|
-
default_function: default_function,
|
104
|
-
generated: row["GENERATION_EXPRESSION"].present?
|
101
|
+
if default && type == "STRING"
|
102
|
+
default = unquote_string default
|
105
103
|
end
|
104
|
+
|
105
|
+
Table::Column.new \
|
106
|
+
table_name,
|
107
|
+
column_name,
|
108
|
+
type,
|
109
|
+
schema_name: schema_name,
|
110
|
+
limit: limit,
|
111
|
+
allow_commit_timestamp: options["allow_commit_timestamp"],
|
112
|
+
ordinal_position: row["ORDINAL_POSITION"],
|
113
|
+
nullable: row["IS_NULLABLE"] == "YES",
|
114
|
+
default: default,
|
115
|
+
default_function: default_function,
|
116
|
+
generated: row["GENERATION_EXPRESSION"].present?,
|
117
|
+
primary_key: primary_key
|
106
118
|
end
|
107
119
|
|
108
|
-
def table_column table_name, column_name
|
109
|
-
table_columns(table_name, column_name: column_name).first
|
120
|
+
def table_column table_name, column_name, schema_name: ""
|
121
|
+
table_columns(table_name, column_name: column_name, schema_name: schema_name).first
|
110
122
|
end
|
111
123
|
|
112
124
|
# Returns the primary key columns of the given table. By default it will only return the columns that are not part
|
@@ -114,43 +126,49 @@ module ActiveRecordSpannerAdapter
|
|
114
126
|
# ActiveRecord. The parent primary key columns are filtered out by default to allow interleaved tables to be
|
115
127
|
# considered as tables with a single-column primary key by ActiveRecord. The actual primary key of the table will
|
116
128
|
# include both the parent primary key columns and the 'own' primary key columns of a table.
|
117
|
-
def table_primary_keys table_name, include_parent_keys =
|
129
|
+
def table_primary_keys table_name, include_parent_keys = IsRails71OrLater, schema_name: ""
|
118
130
|
sql = +"WITH TABLE_PK_COLS AS ( "
|
119
|
-
sql << "SELECT C.
|
131
|
+
sql << "SELECT C.TABLE_CATALOG, C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, "
|
132
|
+
sql << "C.INDEX_NAME, C.COLUMN_ORDERING, C.ORDINAL_POSITION "
|
120
133
|
sql << "FROM INFORMATION_SCHEMA.INDEX_COLUMNS C "
|
121
134
|
sql << "WHERE C.INDEX_TYPE = 'PRIMARY_KEY' "
|
122
135
|
sql << "AND TABLE_CATALOG = '' "
|
123
136
|
sql << "AND TABLE_SCHEMA = '') "
|
124
137
|
sql << "SELECT INDEX_NAME, COLUMN_NAME, COLUMN_ORDERING, ORDINAL_POSITION "
|
125
138
|
sql << "FROM TABLE_PK_COLS "
|
126
|
-
sql << "INNER JOIN INFORMATION_SCHEMA.TABLES T USING (TABLE_NAME) "
|
139
|
+
sql << "INNER JOIN INFORMATION_SCHEMA.TABLES T USING (TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME) "
|
127
140
|
sql << "WHERE TABLE_NAME = %<table_name>s "
|
128
141
|
sql << "AND TABLE_CATALOG = '' "
|
129
|
-
sql << "AND TABLE_SCHEMA =
|
142
|
+
sql << "AND TABLE_SCHEMA = %<schema_name>s "
|
130
143
|
unless include_parent_keys
|
131
144
|
sql << "AND (T.PARENT_TABLE_NAME IS NULL OR COLUMN_NAME NOT IN ( "
|
132
145
|
sql << " SELECT COLUMN_NAME "
|
133
146
|
sql << " FROM TABLE_PK_COLS "
|
134
|
-
sql << " WHERE
|
147
|
+
sql << " WHERE TABLE_CATALOG = T.TABLE_CATALOG "
|
148
|
+
sql << " AND TABLE_SCHEMA=T.TABLE_SCHEMA "
|
149
|
+
sql << " AND TABLE_NAME = T.PARENT_TABLE_NAME "
|
135
150
|
sql << ")) "
|
136
151
|
end
|
137
152
|
sql << "ORDER BY ORDINAL_POSITION"
|
138
153
|
execute_query(
|
139
154
|
sql,
|
140
|
-
table_name: table_name
|
155
|
+
table_name: table_name,
|
156
|
+
schema_name: schema_name
|
141
157
|
).map do |row|
|
142
158
|
Index::Column.new \
|
143
159
|
table_name,
|
144
160
|
row["INDEX_NAME"],
|
145
161
|
row["COLUMN_NAME"],
|
162
|
+
schema_name: schema_name,
|
146
163
|
order: row["COLUMN_ORDERING"],
|
147
164
|
ordinal_position: row["ORDINAL_POSITION"]
|
148
165
|
end
|
149
166
|
end
|
150
167
|
|
151
|
-
def indexes table_name, index_name: nil, index_type: nil
|
168
|
+
def indexes table_name, schema_name: "", index_name: nil, index_type: nil
|
152
169
|
table_indexes_columns = index_columns(
|
153
170
|
table_name,
|
171
|
+
schema_name: schema_name,
|
154
172
|
index_name: index_name
|
155
173
|
)
|
156
174
|
|
@@ -158,7 +176,7 @@ module ActiveRecordSpannerAdapter
|
|
158
176
|
sql << " FROM INFORMATION_SCHEMA.INDEXES"
|
159
177
|
sql << " WHERE TABLE_NAME=%<table_name>s"
|
160
178
|
sql << " AND TABLE_CATALOG = ''"
|
161
|
-
sql << " AND TABLE_SCHEMA =
|
179
|
+
sql << " AND TABLE_SCHEMA = %<schema_name>s"
|
162
180
|
sql << " AND INDEX_NAME=%<index_name>s" if index_name
|
163
181
|
sql << " AND INDEX_TYPE=%<index_type>s" if index_type
|
164
182
|
sql << " AND SPANNER_IS_MANAGED=FALSE"
|
@@ -166,6 +184,7 @@ module ActiveRecordSpannerAdapter
|
|
166
184
|
execute_query(
|
167
185
|
sql,
|
168
186
|
table_name: table_name,
|
187
|
+
schema_name: schema_name,
|
169
188
|
index_name: index_name,
|
170
189
|
index_type: index_type
|
171
190
|
).map do |row|
|
@@ -189,89 +208,120 @@ module ActiveRecordSpannerAdapter
|
|
189
208
|
null_filtered: row["IS_NULL_FILTERED"],
|
190
209
|
interleave_in: row["PARENT_TABLE_NAME"],
|
191
210
|
storing: storing,
|
192
|
-
state: row["INDEX_STATE"]
|
211
|
+
state: row["INDEX_STATE"],
|
212
|
+
schema: schema_name
|
193
213
|
end
|
194
214
|
end
|
195
215
|
|
196
|
-
def index table_name, index_name
|
197
|
-
indexes(table_name, index_name: index_name).first
|
216
|
+
def index table_name, index_name, schema_name: ""
|
217
|
+
indexes(table_name, index_name: index_name, schema_name: schema_name).first
|
198
218
|
end
|
199
219
|
|
200
|
-
def index_columns table_name, index_name: nil
|
220
|
+
def index_columns table_name, schema_name: "", index_name: nil
|
201
221
|
sql = +"SELECT INDEX_NAME, COLUMN_NAME, COLUMN_ORDERING, ORDINAL_POSITION"
|
202
222
|
sql << " FROM INFORMATION_SCHEMA.INDEX_COLUMNS"
|
203
223
|
sql << " WHERE TABLE_NAME=%<table_name>s"
|
204
224
|
sql << " AND TABLE_CATALOG = ''"
|
205
|
-
sql << " AND TABLE_SCHEMA =
|
225
|
+
sql << " AND TABLE_SCHEMA = %<schema_name>s"
|
206
226
|
sql << " AND INDEX_NAME=%<index_name>s" if index_name
|
207
227
|
sql << " ORDER BY ORDINAL_POSITION ASC"
|
208
228
|
|
209
229
|
execute_query(
|
210
230
|
sql,
|
211
|
-
table_name: table_name, index_name: index_name
|
231
|
+
table_name: table_name, schema_name: schema_name, index_name: index_name
|
212
232
|
).map do |row|
|
213
233
|
Index::Column.new \
|
214
234
|
table_name,
|
215
235
|
row["INDEX_NAME"],
|
216
236
|
row["COLUMN_NAME"],
|
237
|
+
schema_name: schema_name,
|
217
238
|
order: row["COLUMN_ORDERING"],
|
218
239
|
ordinal_position: row["ORDINAL_POSITION"]
|
219
240
|
end
|
220
241
|
end
|
221
242
|
|
222
|
-
def indexes_by_columns table_name, column_names
|
243
|
+
def indexes_by_columns table_name, column_names, schema_name: ""
|
223
244
|
column_names = Array(column_names).map(&:to_s)
|
224
245
|
|
225
|
-
indexes(table_name).select do |index|
|
246
|
+
indexes(table_name, schema_name: schema_name).select do |index|
|
226
247
|
index.columns.any? { |c| column_names.include? c.name }
|
227
248
|
end
|
228
249
|
end
|
229
250
|
|
230
|
-
def foreign_keys
|
251
|
+
def foreign_keys from_table_name, from_schema_name: ""
|
231
252
|
sql = <<~SQL
|
232
|
-
SELECT
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
253
|
+
SELECT CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, UPDATE_RULE, DELETE_RULE,
|
254
|
+
FK_CATALOG, FK_SCHEMA, FK_TABLE,
|
255
|
+
PK_CATALOG, PK_SCHEMA, PK_TABLE,
|
256
|
+
ARRAY_AGG(FK_COLUMN) AS FK_COLUMNS, ARRAY_AGG(PK_COLUMN) AS PK_COLUMNS
|
257
|
+
FROM (SELECT CONSTRAINTS.CONSTRAINT_CATALOG,
|
258
|
+
CONSTRAINTS.CONSTRAINT_SCHEMA,
|
259
|
+
CONSTRAINTS.CONSTRAINT_NAME,
|
260
|
+
CONSTRAINTS.UPDATE_RULE,
|
261
|
+
CONSTRAINTS.DELETE_RULE,
|
262
|
+
CHILD.TABLE_CATALOG AS FK_CATALOG,
|
263
|
+
CHILD.TABLE_SCHEMA AS FK_SCHEMA,
|
264
|
+
CHILD.TABLE_NAME AS FK_TABLE,
|
265
|
+
CHILD.COLUMN_NAME AS FK_COLUMN,
|
266
|
+
PARENT.TABLE_CATALOG AS PK_CATALOG,
|
267
|
+
PARENT.TABLE_SCHEMA AS PK_SCHEMA,
|
268
|
+
PARENT.TABLE_NAME AS PK_TABLE,
|
269
|
+
PARENT.COLUMN_NAME AS PK_COLUMN
|
270
|
+
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS CONSTRAINTS
|
271
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CHILD
|
272
|
+
ON CONSTRAINTS.CONSTRAINT_CATALOG = CHILD.CONSTRAINT_CATALOG
|
273
|
+
AND CONSTRAINTS.CONSTRAINT_SCHEMA = CHILD.CONSTRAINT_SCHEMA
|
274
|
+
AND CONSTRAINTS.CONSTRAINT_NAME = CHILD.CONSTRAINT_NAME
|
275
|
+
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE PARENT
|
276
|
+
ON CONSTRAINTS.UNIQUE_CONSTRAINT_CATALOG = PARENT.CONSTRAINT_CATALOG
|
277
|
+
AND CONSTRAINTS.UNIQUE_CONSTRAINT_SCHEMA = PARENT.CONSTRAINT_SCHEMA
|
278
|
+
AND CONSTRAINTS.UNIQUE_CONSTRAINT_NAME = PARENT.CONSTRAINT_NAME
|
279
|
+
AND PARENT.ORDINAL_POSITION = CHILD.POSITION_IN_UNIQUE_CONSTRAINT
|
280
|
+
ORDER BY CHILD.TABLE_CATALOG, CHILD.TABLE_SCHEMA, CHILD.TABLE_NAME, CHILD.POSITION_IN_UNIQUE_CONSTRAINT
|
281
|
+
) FOREIGN_KEYS
|
282
|
+
WHERE FK_TABLE = %<table_name>s
|
283
|
+
AND FK_SCHEMA = %<constraint_schema>s
|
284
|
+
GROUP BY CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, UPDATE_RULE, DELETE_RULE,
|
285
|
+
FK_CATALOG, FK_SCHEMA, FK_TABLE,
|
286
|
+
PK_CATALOG, PK_SCHEMA, PK_TABLE
|
243
287
|
SQL
|
244
288
|
|
245
289
|
rows = execute_query(
|
246
|
-
sql, table_name:
|
290
|
+
sql, table_name: from_table_name, constraint_schema: from_schema_name
|
247
291
|
)
|
248
292
|
|
249
293
|
rows.map do |row|
|
250
294
|
ForeignKey.new(
|
251
|
-
|
252
|
-
row["
|
253
|
-
row["
|
254
|
-
row["
|
255
|
-
row["
|
256
|
-
on_delete: row["
|
257
|
-
on_update: row["
|
295
|
+
from_table_name,
|
296
|
+
row["CONSTRAINT_NAME"],
|
297
|
+
row["FK_COLUMNS"],
|
298
|
+
row["PK_TABLE"],
|
299
|
+
row["PK_COLUMNS"],
|
300
|
+
on_delete: row["DELETE_RULE"],
|
301
|
+
on_update: row["UPDATE_RULE"],
|
302
|
+
table_schema: from_schema_name,
|
303
|
+
ref_schema: row["PK_SCHEMA"]
|
258
304
|
)
|
259
305
|
end
|
260
306
|
end
|
261
307
|
|
262
|
-
def check_constraints table_name
|
308
|
+
def check_constraints table_name, schema_name: ""
|
263
309
|
sql = <<~SQL.squish
|
264
310
|
SELECT tc.TABLE_NAME,
|
265
311
|
tc.CONSTRAINT_NAME,
|
266
312
|
cc.CHECK_CLAUSE
|
267
313
|
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
268
|
-
INNER JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
|
314
|
+
INNER JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
|
315
|
+
ON tc.CONSTRAINT_CATALOG = cc.CONSTRAINT_CATALOG
|
316
|
+
AND tc.CONSTRAINT_SCHEMA = cc.CONSTRAINT_SCHEMA
|
317
|
+
AND tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
|
269
318
|
WHERE tc.TABLE_NAME = %<table_name>s
|
319
|
+
AND tc.CONSTRAINT_SCHEMA = %<schema_name>s
|
270
320
|
AND tc.CONSTRAINT_TYPE = 'CHECK'
|
271
321
|
AND NOT (tc.CONSTRAINT_NAME LIKE 'CK_IS_NOT_NULL_%%' AND cc.CHECK_CLAUSE LIKE '%%IS NOT NULL')
|
272
322
|
SQL
|
273
323
|
|
274
|
-
rows = execute_query sql, table_name: table_name
|
324
|
+
rows = execute_query sql, table_name: table_name, schema_name: schema_name
|
275
325
|
|
276
326
|
rows.map do |row|
|
277
327
|
ActiveRecord::ConnectionAdapters::CheckConstraintDefinition.new(
|
@@ -354,16 +404,18 @@ module ActiveRecordSpannerAdapter
|
|
354
404
|
[value[start...(start + length)].to_i(base)].pack "U"
|
355
405
|
end
|
356
406
|
|
357
|
-
def column_options table_name, column_name
|
407
|
+
def column_options table_name, column_name, schema_name: ""
|
358
408
|
sql = +"SELECT COLUMN_NAME, OPTION_NAME, OPTION_TYPE, OPTION_VALUE"
|
359
409
|
sql << " FROM INFORMATION_SCHEMA.COLUMN_OPTIONS"
|
360
410
|
sql << " WHERE TABLE_NAME=%<table_name>s"
|
411
|
+
sql << " AND TABLE_SCHEMA=%<schema_name>s"
|
361
412
|
sql << " AND COLUMN_NAME=%<column_name>s" if column_name
|
362
413
|
|
363
414
|
column_options = Hash.new { |h, k| h[k] = {} }
|
364
415
|
execute_query(
|
365
416
|
sql,
|
366
417
|
table_name: table_name,
|
418
|
+
schema_name: schema_name,
|
367
419
|
column_name: column_name
|
368
420
|
).each_with_object(column_options) do |row, options|
|
369
421
|
next unless row["OPTION_TYPE"] == "BOOL"
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def fetch_primary_and_parent_key
|
21
|
-
return connection.
|
21
|
+
return connection.spanner_schema_cache.primary_and_parent_keys table_name \
|
22
22
|
if ActiveRecord::Base != self && table_exists?
|
23
23
|
end
|
24
24
|
|
@@ -7,22 +7,24 @@
|
|
7
7
|
module ActiveRecordSpannerAdapter
|
8
8
|
class Table
|
9
9
|
class Column
|
10
|
-
attr_accessor :table_name, :name, :type, :limit, :ordinal_position,
|
10
|
+
attr_accessor :schema_name, :table_name, :name, :type, :limit, :ordinal_position,
|
11
11
|
:allow_commit_timestamp, :default, :default_function, :generated,
|
12
|
-
:primary_key
|
13
|
-
attr_writer :nullable
|
12
|
+
:primary_key, :nullable
|
14
13
|
|
15
14
|
def initialize \
|
16
15
|
table_name,
|
17
16
|
name,
|
18
17
|
type,
|
18
|
+
schema_name: "",
|
19
19
|
limit: nil,
|
20
20
|
ordinal_position: nil,
|
21
21
|
nullable: true,
|
22
22
|
allow_commit_timestamp: nil,
|
23
23
|
default: nil,
|
24
24
|
default_function: nil,
|
25
|
-
generated: nil
|
25
|
+
generated: nil,
|
26
|
+
primary_key: false
|
27
|
+
@schema_name = schema_name.to_s
|
26
28
|
@table_name = table_name.to_s
|
27
29
|
@name = name.to_s
|
28
30
|
@type = type
|
@@ -33,12 +35,7 @@ module ActiveRecordSpannerAdapter
|
|
33
35
|
@default = default
|
34
36
|
@default_function = default_function
|
35
37
|
@generated = generated == true
|
36
|
-
@primary_key =
|
37
|
-
end
|
38
|
-
|
39
|
-
def nullable
|
40
|
-
return false if primary_key
|
41
|
-
@nullable
|
38
|
+
@primary_key = primary_key
|
42
39
|
end
|
43
40
|
|
44
41
|
def spanner_type
|