torque-postgresql 3.4.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/torque/function_generator.rb +13 -0
- data/lib/generators/torque/templates/function.sql.erb +4 -0
- data/lib/generators/torque/templates/type.sql.erb +2 -0
- data/lib/generators/torque/templates/view.sql.erb +3 -0
- data/lib/generators/torque/type_generator.rb +13 -0
- data/lib/generators/torque/view_generator.rb +16 -0
- data/lib/torque/postgresql/adapter/database_statements.rb +111 -94
- data/lib/torque/postgresql/adapter/oid/array.rb +17 -0
- data/lib/torque/postgresql/adapter/oid/line.rb +2 -6
- data/lib/torque/postgresql/adapter/oid/range.rb +4 -4
- data/lib/torque/postgresql/adapter/oid.rb +1 -23
- data/lib/torque/postgresql/adapter/quoting.rb +13 -7
- data/lib/torque/postgresql/adapter/schema_creation.rb +7 -28
- data/lib/torque/postgresql/adapter/schema_definitions.rb +58 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +136 -34
- data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
- data/lib/torque/postgresql/adapter/schema_statements.rb +109 -49
- data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
- data/lib/torque/postgresql/arel/nodes.rb +16 -2
- data/lib/torque/postgresql/arel/operations.rb +7 -1
- data/lib/torque/postgresql/arel/visitors.rb +7 -9
- data/lib/torque/postgresql/associations/association_scope.rb +23 -31
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +25 -0
- data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +16 -0
- data/lib/torque/postgresql/attributes/builder/enum.rb +12 -9
- data/lib/torque/postgresql/attributes/builder/full_text_search.rb +109 -0
- data/lib/torque/postgresql/attributes/builder/period.rb +21 -21
- data/lib/torque/postgresql/attributes/builder.rb +49 -11
- data/lib/torque/postgresql/attributes/enum.rb +7 -7
- data/lib/torque/postgresql/attributes/enum_set.rb +7 -7
- data/lib/torque/postgresql/attributes/full_text_search.rb +19 -0
- data/lib/torque/postgresql/attributes/period.rb +2 -2
- data/lib/torque/postgresql/attributes.rb +0 -4
- data/lib/torque/postgresql/auxiliary_statement/recursive.rb +3 -3
- data/lib/torque/postgresql/base.rb +5 -11
- data/lib/torque/postgresql/collector.rb +1 -1
- data/lib/torque/postgresql/config.rb +129 -5
- data/lib/torque/postgresql/function.rb +94 -0
- data/lib/torque/postgresql/inheritance.rb +52 -36
- data/lib/torque/postgresql/predicate_builder/arel_attribute_handler.rb +33 -0
- data/lib/torque/postgresql/predicate_builder/array_handler.rb +47 -0
- data/lib/torque/postgresql/predicate_builder/enumerator_lazy_handler.rb +37 -0
- data/lib/torque/postgresql/predicate_builder/regexp_handler.rb +21 -0
- data/lib/torque/postgresql/predicate_builder.rb +35 -0
- data/lib/torque/postgresql/railtie.rb +137 -30
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +12 -44
- data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -0
- data/lib/torque/postgresql/reflection/has_many_reflection.rb +4 -0
- data/lib/torque/postgresql/reflection/runtime_reflection.rb +1 -1
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -2
- data/lib/torque/postgresql/relation/buckets.rb +124 -0
- data/lib/torque/postgresql/relation/distinct_on.rb +7 -2
- data/lib/torque/postgresql/relation/inheritance.rb +22 -15
- data/lib/torque/postgresql/relation/join_series.rb +112 -0
- data/lib/torque/postgresql/relation/merger.rb +17 -3
- data/lib/torque/postgresql/relation.rb +24 -38
- data/lib/torque/postgresql/schema_cache.rb +6 -12
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql/versioned_commands/command_migration.rb +146 -0
- data/lib/torque/postgresql/versioned_commands/generator.rb +57 -0
- data/lib/torque/postgresql/versioned_commands/migration_context.rb +83 -0
- data/lib/torque/postgresql/versioned_commands/migrator.rb +39 -0
- data/lib/torque/postgresql/versioned_commands/schema_table.rb +101 -0
- data/lib/torque/postgresql/versioned_commands.rb +161 -0
- data/lib/torque/postgresql.rb +2 -1
- data/spec/fixtures/migrations/20250101000001_create_users.rb +0 -0
- data/spec/fixtures/migrations/20250101000002_create_function_count_users_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000003_create_internal_users.rb +0 -0
- data/spec/fixtures/migrations/20250101000004_update_function_count_users_v2.sql +0 -0
- data/spec/fixtures/migrations/20250101000005_create_view_all_users_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000006_create_type_user_id_v1.sql +0 -0
- data/spec/fixtures/migrations/20250101000007_remove_function_count_users_v2.sql +0 -0
- data/spec/initialize.rb +67 -0
- data/spec/mocks/cache_query.rb +21 -21
- data/spec/mocks/create_table.rb +6 -26
- data/spec/schema.rb +17 -12
- data/spec/spec_helper.rb +11 -2
- data/spec/tests/arel_spec.rb +32 -7
- data/spec/tests/auxiliary_statement_spec.rb +3 -3
- data/spec/tests/belongs_to_many_spec.rb +72 -5
- data/spec/tests/enum_set_spec.rb +12 -11
- data/spec/tests/enum_spec.rb +4 -2
- data/spec/tests/full_text_seach_test.rb +280 -0
- data/spec/tests/function_spec.rb +42 -0
- data/spec/tests/has_many_spec.rb +21 -8
- data/spec/tests/interval_spec.rb +1 -7
- data/spec/tests/period_spec.rb +61 -61
- data/spec/tests/predicate_builder_spec.rb +132 -0
- data/spec/tests/relation_spec.rb +229 -0
- data/spec/tests/schema_spec.rb +6 -9
- data/spec/tests/table_inheritance_spec.rb +25 -26
- data/spec/tests/versioned_commands_spec.rb +513 -0
- metadata +64 -39
@@ -4,6 +4,23 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
module Adapter
|
6
6
|
module SchemaDumper
|
7
|
+
SEARCH_VECTOR_SCANNER = /
|
8
|
+
to_tsvector\(
|
9
|
+
('[^']+'|[a-z][a-z0-9_]*)[^,]*,[^\(]*
|
10
|
+
\(?coalesce\(([a-z][a-z0-9_]*)[^\)]*\)\)?
|
11
|
+
(?:::[^\)]*\))?
|
12
|
+
(?:\s*,\s*'([A-D])')?
|
13
|
+
/ix
|
14
|
+
|
15
|
+
def initialize(*)
|
16
|
+
super
|
17
|
+
|
18
|
+
if with_versioned_commands?
|
19
|
+
@versioned_commands = VersionedCommands::SchemaTable.new(@connection.pool)
|
20
|
+
@ignore_tables << @versioned_commands.table_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
7
24
|
def dump(stream) # :nodoc:
|
8
25
|
@connection.dump_mode!
|
9
26
|
super
|
@@ -12,17 +29,14 @@ module Torque
|
|
12
29
|
stream
|
13
30
|
end
|
14
31
|
|
15
|
-
|
16
|
-
super
|
17
|
-
user_defined_schemas(stream)
|
18
|
-
end
|
32
|
+
private
|
19
33
|
|
20
|
-
|
21
|
-
|
22
|
-
column.type == :enum_set ? :enum : super
|
23
|
-
end
|
34
|
+
def types(stream) # :nodoc:
|
35
|
+
super
|
24
36
|
|
25
|
-
|
37
|
+
versioned_commands(stream, :type)
|
38
|
+
versioned_commands(stream, :function)
|
39
|
+
end
|
26
40
|
|
27
41
|
def tables(stream) # :nodoc:
|
28
42
|
around_tables(stream) { dump_tables(stream) }
|
@@ -32,6 +46,7 @@ module Torque
|
|
32
46
|
functions(stream) if fx_functions_position == :beginning
|
33
47
|
|
34
48
|
yield
|
49
|
+
versioned_commands(stream, :view, true)
|
35
50
|
|
36
51
|
functions(stream) if fx_functions_position == :end
|
37
52
|
triggers(stream) if defined?(::Fx::SchemaDumper::Trigger)
|
@@ -39,41 +54,46 @@ module Torque
|
|
39
54
|
|
40
55
|
def dump_tables(stream)
|
41
56
|
inherited_tables = @connection.inherited_tables
|
42
|
-
sorted_tables = (@connection.tables - @connection.views).
|
43
|
-
table_name.split(/(?:public)?\./).reverse
|
44
|
-
|
57
|
+
sorted_tables = (@connection.tables - @connection.views).filter_map do |table_name|
|
58
|
+
name_parts = table_name.split(/(?:public)?\./).reverse.compact_blank
|
59
|
+
next if ignored?(table_name) || ignored?(name_parts.join('.'))
|
60
|
+
|
61
|
+
[table_name, name_parts]
|
62
|
+
end.sort_by(&:last).to_h
|
63
|
+
|
64
|
+
postponed = []
|
45
65
|
|
46
66
|
stream.puts " # These are the common tables"
|
47
|
-
|
48
|
-
table
|
67
|
+
sorted_tables.each do |table, (table_name, _)|
|
68
|
+
next postponed << table if inherited_tables.key?(table_name)
|
69
|
+
|
70
|
+
table(table, stream)
|
71
|
+
stream.puts # Ideally we would not do this in the last one
|
49
72
|
end
|
50
73
|
|
51
|
-
if
|
74
|
+
if postponed.present?
|
52
75
|
stream.puts " # These are tables that have inheritance"
|
53
|
-
|
54
|
-
next if ignored?(table_name)
|
55
|
-
|
76
|
+
postponed.each do |table|
|
56
77
|
sub_stream = StringIO.new
|
57
|
-
table(
|
58
|
-
|
59
|
-
|
60
|
-
sub_stream.rewind
|
61
|
-
inherits.map! { |parent| parent.to_s.sub(/\Apublic\./, '') }
|
62
|
-
inherits = inherits.first if inherits.size === 1
|
63
|
-
inherits = ", inherits: #{inherits.inspect} do |t|"
|
64
|
-
table_dump = sub_stream.read.gsub(/ do \|t\|$/, inherits)
|
65
|
-
|
66
|
-
# Ensure bodyless definitions
|
67
|
-
table_dump.gsub!(/do \|t\|\n end/, '')
|
68
|
-
stream.print table_dump
|
78
|
+
table(table, sub_stream)
|
79
|
+
stream.puts sub_stream.string.sub(/do \|t\|\n end/, '')
|
80
|
+
stream.puts
|
69
81
|
end
|
70
82
|
end
|
71
83
|
|
72
|
-
#
|
84
|
+
# Fixes double new lines to single new lines
|
85
|
+
stream.pos -= 1
|
86
|
+
|
87
|
+
# dump foreign keys at the end to make sure all dependent tables exist.
|
73
88
|
if @connection.supports_foreign_keys?
|
74
|
-
|
75
|
-
|
89
|
+
foreign_keys_stream = StringIO.new
|
90
|
+
sorted_tables.each do |(tbl, *)|
|
91
|
+
foreign_keys(tbl, foreign_keys_stream)
|
76
92
|
end
|
93
|
+
|
94
|
+
foreign_keys_string = foreign_keys_stream.string
|
95
|
+
stream.puts if foreign_keys_string.length > 0
|
96
|
+
stream.print foreign_keys_string
|
77
97
|
end
|
78
98
|
end
|
79
99
|
|
@@ -83,7 +103,8 @@ module Torque
|
|
83
103
|
end
|
84
104
|
|
85
105
|
# Dump user defined schemas
|
86
|
-
def
|
106
|
+
def schemas(stream)
|
107
|
+
return super if !PostgreSQL.config.schemas.enabled
|
87
108
|
return if (list = (@connection.user_defined_schemas - ['public'])).empty?
|
88
109
|
|
89
110
|
stream.puts " # Custom schemas defined in this database."
|
@@ -91,6 +112,87 @@ module Torque
|
|
91
112
|
stream.puts
|
92
113
|
end
|
93
114
|
|
115
|
+
# Adjust the schema type for search vector
|
116
|
+
def schema_type_with_virtual(column)
|
117
|
+
column.virtual? && column.type == :tsvector ? :search_vector : super
|
118
|
+
end
|
119
|
+
|
120
|
+
# Adjust the schema type for search language
|
121
|
+
def schema_type(column)
|
122
|
+
column.sql_type == 'regconfig' ? :search_language : super
|
123
|
+
end
|
124
|
+
|
125
|
+
# Adjust table options to make the dump more readable
|
126
|
+
def prepare_column_options(column)
|
127
|
+
options = super
|
128
|
+
parse_search_vector_options(column, options) if column.type == :tsvector
|
129
|
+
options
|
130
|
+
end
|
131
|
+
|
132
|
+
# Parse the search vector operation into a readable format
|
133
|
+
def parse_search_vector_options(column, options)
|
134
|
+
settings = options[:as].scan(SEARCH_VECTOR_SCANNER)
|
135
|
+
return if settings.empty?
|
136
|
+
|
137
|
+
languages = settings.map(&:shift).uniq
|
138
|
+
return if languages.many?
|
139
|
+
|
140
|
+
language = languages.first
|
141
|
+
language = language[0] == "'" ? language[1..-2] : language.to_sym
|
142
|
+
columns = parse_search_vector_columns(settings)
|
143
|
+
|
144
|
+
options.except!(:as, :type)
|
145
|
+
options.merge!(language: language.inspect, columns: columns)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Simplify the whole columns configuration to make it more manageable
|
149
|
+
def parse_search_vector_columns(settings)
|
150
|
+
return ":#{settings.first.first}" if settings.one?
|
151
|
+
|
152
|
+
settings = settings.sort_by(&:last)
|
153
|
+
weights = %w[A B C D]
|
154
|
+
|
155
|
+
columns = settings.each.with_index.reduce([]) do |acc, (setting, index)|
|
156
|
+
column, weight = setting
|
157
|
+
break if (weights[index] || 'D') != weight
|
158
|
+
|
159
|
+
acc << column
|
160
|
+
acc
|
161
|
+
end
|
162
|
+
|
163
|
+
return columns.map(&:to_sym).inspect if columns
|
164
|
+
settings.to_h.transform_values(&:inspect)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Simply add all versioned commands to the stream
|
168
|
+
def versioned_commands(stream, type, add_newline = false)
|
169
|
+
return unless with_versioned_commands?
|
170
|
+
|
171
|
+
list = @versioned_commands.versions_of(type.to_s)
|
172
|
+
return if list.empty?
|
173
|
+
|
174
|
+
existing = list_existing_versioned_commands(type)
|
175
|
+
|
176
|
+
stream.puts if add_newline
|
177
|
+
stream.puts " # These are #{type.to_s.pluralize} managed by versioned commands"
|
178
|
+
list.each do |(name, version)|
|
179
|
+
next if existing.exclude?(name)
|
180
|
+
|
181
|
+
stream.puts " create_#{type} \"#{name}\", version: #{version}"
|
182
|
+
end
|
183
|
+
stream.puts unless add_newline
|
184
|
+
end
|
185
|
+
|
186
|
+
def list_existing_versioned_commands(type)
|
187
|
+
@connection.list_versioned_commands(type).each_with_object(Set.new) do |entry, set|
|
188
|
+
set << (entry.first == 'public' ? entry.last : entry.join('_'))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def with_versioned_commands?
|
193
|
+
PostgreSQL.config.versioned_commands.enabled
|
194
|
+
end
|
195
|
+
|
94
196
|
def fx_functions_position
|
95
197
|
return unless defined?(::Fx::SchemaDumper::Function)
|
96
198
|
Fx.configuration.dump_functions_at_beginning_of_schema ? :beginning : :end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module Adapter
|
6
|
+
module SchemaOverrides
|
7
|
+
# This adds better support for handling the quotation of table names
|
8
|
+
def quote_table_name(name)
|
9
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting::QUOTED_TABLE_NAMES.then do |m|
|
10
|
+
m[name] ||= quote_identifier_name(name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
%i[
|
15
|
+
table_exists? indexes index_exists? columns column_exists? primary_key
|
16
|
+
create_table change_table add_column add_columns remove_columns remove_column
|
17
|
+
change_column change_column_default change_column_null rename_column
|
18
|
+
add_index remove_index rename_index index_name_exists? foreign_keys
|
19
|
+
add_timestamps remove_timestamps change_table_comment change_column_comment
|
20
|
+
bulk_change_table
|
21
|
+
|
22
|
+
rename_table add_foreign_key remove_foreign_key foreign_key_exists?
|
23
|
+
].each do |method_name|
|
24
|
+
define_method(method_name) do |table_name, *args, **options, &block|
|
25
|
+
table_name = sanitize_name_with_schema(table_name, options)
|
26
|
+
super(table_name, *args, **options, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def drop_table(*table_names, **options)
|
31
|
+
table_names = table_names.map { |name| sanitize_name_with_schema(name, options.dup) }
|
32
|
+
super(*table_names, **options)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate_table_length!(table_name)
|
38
|
+
super(table_name.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
include SchemaOverrides
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -4,42 +4,55 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
module Adapter
|
6
6
|
module SchemaStatements
|
7
|
-
|
8
|
-
TableDefinition = ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition
|
9
|
-
|
10
|
-
# Create a new schema
|
11
|
-
def create_schema(name, options = {})
|
12
|
-
drop_schema(name, options) if options[:force]
|
13
|
-
|
14
|
-
check = 'IF NOT EXISTS' if options.fetch(:check, true)
|
15
|
-
execute("CREATE SCHEMA #{check} #{quote_schema_name(name.to_s)}")
|
16
|
-
end
|
17
|
-
|
18
|
-
# Drop an existing schema
|
19
|
-
def drop_schema(name, options = {})
|
20
|
-
force = options.fetch(:force, '').upcase
|
21
|
-
check = 'IF EXISTS' if options.fetch(:check, true)
|
22
|
-
execute("DROP SCHEMA #{check} #{quote_schema_name(name.to_s)} #{force}")
|
23
|
-
end
|
24
|
-
|
25
|
-
# Drops a type.
|
7
|
+
# Drops a type
|
26
8
|
def drop_type(name, options = {})
|
27
9
|
force = options.fetch(:force, '').upcase
|
28
10
|
check = 'IF EXISTS' if options.fetch(:check, true)
|
29
|
-
|
11
|
+
name = sanitize_name_with_schema(name, options)
|
12
|
+
|
13
|
+
internal_exec_query(<<-SQL.squish).tap { reload_type_map }
|
30
14
|
DROP TYPE #{check}
|
31
|
-
#{quote_type_name(name
|
15
|
+
#{quote_type_name(name)} #{force}
|
32
16
|
SQL
|
33
17
|
end
|
34
18
|
|
35
|
-
# Renames a type
|
19
|
+
# Renames a type
|
36
20
|
def rename_type(type_name, new_name, options = {})
|
37
|
-
|
38
|
-
|
21
|
+
type_name = sanitize_name_with_schema(type_name, options)
|
22
|
+
internal_exec_query(<<-SQL.squish).tap { reload_type_map }
|
23
|
+
ALTER TYPE #{quote_type_name(type_name)}
|
39
24
|
RENAME TO #{Quoting::Name.new(nil, new_name.to_s).quoted}
|
40
25
|
SQL
|
41
26
|
end
|
42
27
|
|
28
|
+
# Creates a column that stores the underlying language of the record so
|
29
|
+
# that a search vector can be created dynamically based on it. It uses
|
30
|
+
# a `regconfig` type, so string conversions are mandatory
|
31
|
+
def add_search_language(table, name, options = {})
|
32
|
+
add_column(table, name, :regconfig, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a column and setup a search vector as a virtual column. The
|
36
|
+
# options are dev-friendly and controls how the vector function will be
|
37
|
+
# defined
|
38
|
+
#
|
39
|
+
# === Options
|
40
|
+
# [:columns]
|
41
|
+
# The list of columns that will be used to create the search vector.
|
42
|
+
# It can be a single column, an array of columns, or a hash as a
|
43
|
+
# combination of column name and weight (A, B, C, or D).
|
44
|
+
# [:language]
|
45
|
+
# Specify the language config to be used for the search vector. If a
|
46
|
+
# string is provided, then the value will be statically embedded. If a
|
47
|
+
# symbol is provided, then it will reference another column.
|
48
|
+
# [:stored]
|
49
|
+
# Specify if the value should be stored in the database. As of now,
|
50
|
+
# PostgreSQL only supports `true`, which will create a stored column.
|
51
|
+
def add_search_vector(table, name, columns, options = {})
|
52
|
+
options = Builder.search_vector_options(columns: columns, **options)
|
53
|
+
add_column(table, name, options.delete(:type), options)
|
54
|
+
end
|
55
|
+
|
43
56
|
# Changes the enumerator by adding new values
|
44
57
|
#
|
45
58
|
# Example:
|
@@ -48,6 +61,7 @@ module Torque
|
|
48
61
|
# add_enum_values 'status', ['baz'], after: 'foo'
|
49
62
|
# add_enum_values 'status', ['baz'], prepend: true
|
50
63
|
def add_enum_values(name, values, options = {})
|
64
|
+
name = sanitize_name_with_schema(name, options)
|
51
65
|
before = options.fetch(:before, false)
|
52
66
|
after = options.fetch(:after, false)
|
53
67
|
|
@@ -59,7 +73,7 @@ module Torque
|
|
59
73
|
reference = "BEFORE #{before}" unless before == false
|
60
74
|
reference = "AFTER #{after}" unless after == false
|
61
75
|
execute <<-SQL.squish
|
62
|
-
ALTER TYPE #{quote_type_name(name
|
76
|
+
ALTER TYPE #{quote_type_name(name)}
|
63
77
|
ADD VALUE #{value} #{reference}
|
64
78
|
SQL
|
65
79
|
|
@@ -77,34 +91,26 @@ module Torque
|
|
77
91
|
SQL
|
78
92
|
end
|
79
93
|
|
80
|
-
# Rewrite the method that creates tables to easily accept extra options
|
81
|
-
def create_table(table_name, **options, &block)
|
82
|
-
table_name = "#{options[:schema]}.#{table_name}" if options[:schema].present?
|
83
|
-
|
84
|
-
options[:id] = false if options[:inherits].present? &&
|
85
|
-
options[:primary_key].blank? && options[:id].blank?
|
86
94
|
|
87
|
-
|
88
|
-
|
95
|
+
# Add the schema option when extracting table options
|
96
|
+
def table_options(table_name)
|
97
|
+
options = super
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
99
|
+
if PostgreSQL.config.schemas.enabled
|
100
|
+
table, schema = table_name.split('.').reverse
|
101
|
+
if table.present? && schema.present? && schema != current_schema
|
102
|
+
options[:schema] = schema
|
103
|
+
end
|
104
|
+
end
|
95
105
|
|
96
|
-
|
97
|
-
|
98
|
-
table_name = "#{options[:schema]}.#{table_name}" if options[:schema].present?
|
99
|
-
super table_name, **options
|
100
|
-
end
|
106
|
+
if options[:options]&.start_with?('INHERITS (')
|
107
|
+
options.delete(:options)
|
101
108
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
return super unless parts.size == 2 && parts[1] != 'public'
|
109
|
+
tables = inherited_table_names(table_name)
|
110
|
+
options[:inherits] = tables.one? ? tables.first : tables
|
111
|
+
end
|
106
112
|
|
107
|
-
|
113
|
+
options
|
108
114
|
end
|
109
115
|
|
110
116
|
# When dumping the schema we need to add all schemas, not only those
|
@@ -112,7 +118,10 @@ module Torque
|
|
112
118
|
def quoted_scope(name = nil, type: nil)
|
113
119
|
return super unless name.nil?
|
114
120
|
|
115
|
-
|
121
|
+
scope = super
|
122
|
+
global = scope[:schema].start_with?('ANY (')
|
123
|
+
scope[:schema] = "ANY ('{#{user_defined_schemas.join(',')}}')"
|
124
|
+
scope
|
116
125
|
end
|
117
126
|
|
118
127
|
# Fix the query to include the schema on tables names when dumping
|
@@ -128,6 +137,51 @@ module Torque
|
|
128
137
|
super + [:schema, :inherits]
|
129
138
|
end
|
130
139
|
|
140
|
+
# Add proper support for schema load when using versioned commands
|
141
|
+
def assume_migrated_upto_version(version)
|
142
|
+
return super unless PostgreSQL.config.versioned_commands.enabled
|
143
|
+
return super if (commands = pool.migration_context.migration_commands).empty?
|
144
|
+
|
145
|
+
version = version.to_i
|
146
|
+
migration_context = pool.migration_context
|
147
|
+
migrated = migration_context.get_all_versions
|
148
|
+
versions = migration_context.migrations.map(&:version)
|
149
|
+
|
150
|
+
inserting = (versions - migrated).select { |v| v < version }
|
151
|
+
inserting << version unless migrated.include?(version)
|
152
|
+
return if inserting.empty?
|
153
|
+
|
154
|
+
duplicated = inserting.tally.filter_map { |v, count| v if count > 1 }
|
155
|
+
raise <<~MSG.squish if duplicated.present?
|
156
|
+
Duplicate migration #{duplicated.first}.
|
157
|
+
Please renumber your migrations to resolve the conflict.
|
158
|
+
MSG
|
159
|
+
|
160
|
+
VersionedCommands::SchemaTable.new(pool).create_table
|
161
|
+
execute insert_versions_sql(inserting)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Add proper support for schema load when using versioned commands
|
165
|
+
def insert_versions_sql(versions)
|
166
|
+
return super unless PostgreSQL.config.versioned_commands.enabled
|
167
|
+
|
168
|
+
commands = pool.migration_context.migration_commands.select do |migration|
|
169
|
+
versions.include?(migration.version)
|
170
|
+
end
|
171
|
+
|
172
|
+
return super if commands.empty?
|
173
|
+
|
174
|
+
table = quote_table_name(VersionedCommands::SchemaTable.new(pool).table_name)
|
175
|
+
|
176
|
+
sql = super(versions - commands.map(&:version))
|
177
|
+
sql << "\nINSERT INTO #{table} (version, type, object_name) VALUES\n"
|
178
|
+
sql << commands.map do |m|
|
179
|
+
+"(#{quote(m.version)}, #{quote(m.type)}, #{quote(m.object_name)})"
|
180
|
+
end.join(",\n")
|
181
|
+
sql << ";"
|
182
|
+
sql
|
183
|
+
end
|
184
|
+
|
131
185
|
private
|
132
186
|
|
133
187
|
# Remove the schema from the sequence name
|
@@ -135,6 +189,12 @@ module Torque
|
|
135
189
|
super(table_name.split('.').last, column_name, suffix)
|
136
190
|
end
|
137
191
|
|
192
|
+
# Helper for supporting schema name in several methods
|
193
|
+
def sanitize_name_with_schema(name, options)
|
194
|
+
return name if (schema = options&.delete(:schema)).blank?
|
195
|
+
Quoting::Name.new(schema.to_s, name.to_s)
|
196
|
+
end
|
197
|
+
|
138
198
|
def quote_enum_values(name, values, options)
|
139
199
|
prefix = options[:prefix]
|
140
200
|
prefix = name if prefix === true
|
@@ -3,39 +3,26 @@
|
|
3
3
|
module Torque
|
4
4
|
module PostgreSQL
|
5
5
|
module Arel
|
6
|
-
nodes = ::Arel::Nodes
|
7
|
-
inflix = nodes::InfixOperation
|
8
|
-
visitors = ::Arel::Visitors::PostgreSQL
|
9
|
-
default_alias = :visit_Arel_Nodes_InfixOperation
|
10
|
-
|
11
6
|
Math = Module.new
|
12
|
-
INFLIX_OPERATION = {
|
13
|
-
'Overlaps' => :'&&',
|
14
|
-
'Contains' => :'@>',
|
15
|
-
'ContainedBy' => :'<@',
|
16
|
-
'HasKey' => :'?',
|
17
|
-
'HasAllKeys' => :'?&',
|
18
|
-
'HasAnyKeys' => :'?|',
|
19
|
-
'StrictlyLeft' => :'<<',
|
20
|
-
'StrictlyRight' => :'>>',
|
21
|
-
'DoesntRightExtend' => :'&<',
|
22
|
-
'DoesntLeftExtend' => :'&>',
|
23
|
-
'AdjacentTo' => :'-|-',
|
24
|
-
}.freeze
|
25
7
|
|
26
|
-
|
27
|
-
|
8
|
+
def self.build_operations(operations)
|
9
|
+
default_alias = :visit_Arel_Nodes_InfixOperation
|
10
|
+
|
11
|
+
operations&.each do |name, operator|
|
12
|
+
klass_name = name.to_s.camelize
|
13
|
+
next if ::Arel::Nodes.const_defined?(klass_name)
|
28
14
|
|
29
|
-
|
30
|
-
|
15
|
+
klass = Class.new(::Arel::Nodes::InfixOperation)
|
16
|
+
operator = (-operator).to_sym
|
17
|
+
klass.send(:define_method, :initialize) { |*args| super(operator, *args) }
|
31
18
|
|
32
|
-
|
33
|
-
|
19
|
+
::Arel::Nodes.const_set(klass_name, klass)
|
20
|
+
visitor = :"visit_Arel_Nodes_#{klass_name}"
|
21
|
+
::Arel::Visitors::PostgreSQL.send(:alias_method, visitor, default_alias)
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
klass.new(self, other)
|
23
|
+
# Don't worry about quoting here, if the right side is something that
|
24
|
+
# doesn't need quoting, it will leave it as it is
|
25
|
+
Math.send(:define_method, klass_name.underscore) { |other| klass.new(self, other) }
|
39
26
|
end
|
40
27
|
end
|
41
28
|
|
@@ -13,18 +13,32 @@ module Torque
|
|
13
13
|
include ::Arel::Math
|
14
14
|
|
15
15
|
def initialize(left, right, array = false)
|
16
|
-
right = right.to_s
|
16
|
+
right = +right.to_s
|
17
17
|
right << '[]' if array
|
18
18
|
super left, right
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
class Ref < ::Arel::Nodes::Unary
|
23
|
+
attr_reader :reference
|
24
|
+
alias to_s expr
|
25
|
+
|
26
|
+
def initialize(expr, reference = nil)
|
27
|
+
@reference = reference
|
28
|
+
super expr
|
29
|
+
end
|
30
|
+
|
31
|
+
def as(other)
|
32
|
+
@reference&.as(other) || super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
22
36
|
end
|
23
37
|
|
24
38
|
::Arel.define_singleton_method(:array) do |*values, cast: nil|
|
25
39
|
values = values.first if values.size.eql?(1) && values.first.is_a?(::Enumerable)
|
26
40
|
result = ::Arel::Nodes.build_quoted(values)
|
27
|
-
result = result.
|
41
|
+
result = result.pg_cast(cast, true) if cast.present?
|
28
42
|
result
|
29
43
|
end
|
30
44
|
|
@@ -6,10 +6,16 @@ module Torque
|
|
6
6
|
module Operations
|
7
7
|
|
8
8
|
# Create a cast operation
|
9
|
-
def
|
9
|
+
def pg_cast(type, array = false)
|
10
10
|
Nodes::Cast.new(self, type, array)
|
11
11
|
end
|
12
12
|
|
13
|
+
# Make sure to add proper support over AR's own +cast+ method while
|
14
|
+
# still allow attributes to be casted
|
15
|
+
def cast(type, array = false)
|
16
|
+
defined?(super) && !array ? super(type) : pg_cast(type, array)
|
17
|
+
end
|
18
|
+
|
13
19
|
end
|
14
20
|
|
15
21
|
::Arel::Attributes::Attribute.include(Operations)
|
@@ -4,13 +4,6 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
module Arel
|
6
6
|
module Visitors
|
7
|
-
# Enclose select manager with parenthesis
|
8
|
-
# :TODO: Remove when checking the new version of Arel
|
9
|
-
def visit_Arel_SelectManager(o, collector)
|
10
|
-
collector << '('
|
11
|
-
visit(o.ast, collector) << ')'
|
12
|
-
end
|
13
|
-
|
14
7
|
# Add ONLY modifier to query
|
15
8
|
def visit_Arel_Nodes_JoinSource(o, collector)
|
16
9
|
collector << 'ONLY ' if o.only?
|
@@ -26,11 +19,16 @@ module Torque
|
|
26
19
|
# Allow quoted arrays to get here
|
27
20
|
def visit_Arel_Nodes_Casted(o, collector)
|
28
21
|
value = o.value_for_database
|
29
|
-
|
30
|
-
|
22
|
+
klass = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array::Data
|
23
|
+
return super unless value.is_a?(klass)
|
24
|
+
quote_array(value.values, collector)
|
31
25
|
end
|
32
26
|
|
33
27
|
## TORQUE VISITORS
|
28
|
+
def visit_Torque_PostgreSQL_Arel_Nodes_Ref(o, collector)
|
29
|
+
collector << quote_table_name(o.expr)
|
30
|
+
end
|
31
|
+
|
34
32
|
# Allow casting any node
|
35
33
|
def visit_Torque_PostgreSQL_Arel_Nodes_Cast(o, collector)
|
36
34
|
visit(o.left, collector) << '::' << o.right
|