torque-postgresql 3.4.1 → 4.0.0.rc1
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/torque/postgresql/adapter/database_statements.rb +63 -84
- 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 +36 -0
- data/lib/torque/postgresql/adapter/schema_dumper.rb +90 -34
- data/lib/torque/postgresql/adapter/schema_overrides.rb +45 -0
- data/lib/torque/postgresql/adapter/schema_statements.rb +64 -49
- data/lib/torque/postgresql/arel/infix_operation.rb +15 -28
- data/lib/torque/postgresql/arel/nodes.rb +2 -2
- data/lib/torque/postgresql/arel/operations.rb +7 -1
- data/lib/torque/postgresql/arel/visitors.rb +3 -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 +121 -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 +3 -10
- data/lib/torque/postgresql/collector.rb +1 -1
- data/lib/torque/postgresql/config.rb +95 -5
- data/lib/torque/postgresql/function.rb +61 -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 +112 -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/inheritance.rb +4 -7
- data/lib/torque/postgresql/relation.rb +6 -10
- data/lib/torque/postgresql/schema_cache.rb +6 -12
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql.rb +2 -1
- data/spec/initialize.rb +58 -0
- data/spec/mocks/cache_query.rb +21 -21
- data/spec/mocks/create_table.rb +6 -26
- data/spec/schema.rb +19 -12
- data/spec/spec_helper.rb +5 -1
- 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 +252 -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/schema_spec.rb +2 -8
- data/spec/tests/table_inheritance_spec.rb +25 -26
- metadata +34 -39
@@ -20,15 +20,14 @@ module Torque
|
|
20
20
|
klass.find(self.id)
|
21
21
|
end
|
22
22
|
|
23
|
-
|
23
|
+
class_methods do
|
24
24
|
delegate :_auto_cast_attribute, :_record_class_attribute, to: ActiveRecord::Relation
|
25
25
|
|
26
26
|
# Get a full list of all attributes from a model and all its dependents
|
27
27
|
def inheritance_merged_attributes
|
28
28
|
@inheritance_merged_attributes ||= begin
|
29
|
-
|
30
|
-
|
31
|
-
list.flatten.uniq.freeze
|
29
|
+
children = casted_dependents.values.flat_map(&:attribute_names)
|
30
|
+
attribute_names.to_set.merge(children).to_a.freeze
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
@@ -45,11 +44,11 @@ module Torque
|
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
48
|
-
result = types.
|
49
|
-
|
50
|
-
end
|
47
|
+
result = types.filter_map do |attribute, types|
|
48
|
+
attribute if types.each_with_object(types.shift).all?(&:==)
|
49
|
+
end
|
51
50
|
|
52
|
-
result.freeze
|
51
|
+
(attribute_names + result).freeze
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
@@ -111,22 +110,19 @@ module Torque
|
|
111
110
|
# For all main purposes, physical inherited classes should have
|
112
111
|
# base_class as their own
|
113
112
|
def base_class
|
114
|
-
|
115
|
-
self
|
113
|
+
physically_inherited? ? self : super
|
116
114
|
end
|
117
115
|
|
118
116
|
# Primary key is one exception when getting information about the class,
|
119
117
|
# it must returns the superclass PK
|
120
118
|
def primary_key
|
121
|
-
|
122
|
-
superclass.primary_key
|
119
|
+
physically_inherited? ? superclass.primary_key : super
|
123
120
|
end
|
124
121
|
|
125
122
|
# Add an additional check to return the name of the table even when the
|
126
123
|
# class is inherited, but only if it is a physical inheritance
|
127
124
|
def compute_table_name
|
128
|
-
|
129
|
-
decorated_table_name
|
125
|
+
physically_inherited? ? decorated_table_name : super
|
130
126
|
end
|
131
127
|
|
132
128
|
# Raises an error message saying that the giver record class was not
|
@@ -142,37 +138,57 @@ module Torque
|
|
142
138
|
|
143
139
|
private
|
144
140
|
|
145
|
-
|
141
|
+
# If the class is physically inherited, the klass needs to be properly
|
142
|
+
# changed before moving forward
|
143
|
+
def instantiate_instance_of(klass, attributes, types = {}, &block)
|
146
144
|
return super unless klass.physically_inheritances?
|
147
145
|
|
148
|
-
|
149
|
-
|
150
|
-
return super unless attributes.key?(record_class) &&
|
151
|
-
attributes.delete(auto_cast) && attributes[record_class] != table_name
|
152
|
-
|
153
|
-
klass = casted_dependents[attributes[record_class]]
|
154
|
-
raise_unable_to_cast(attributes[record_class]) if klass.nil?
|
155
|
-
filter_attributes_for_cast(attributes, klass)
|
146
|
+
real_class = torque_discriminate_class_for_record(klass, attributes)
|
147
|
+
return super if real_class.nil?
|
156
148
|
|
157
|
-
|
149
|
+
attributes, types = sanitize_attributes(real_class, attributes, types)
|
150
|
+
super(real_class, attributes, types, &block)
|
158
151
|
end
|
159
152
|
|
160
|
-
#
|
161
|
-
#
|
162
|
-
def
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
153
|
+
# Unwrap the attributes and column types from the given class when
|
154
|
+
# there are unmergeable attributes
|
155
|
+
def sanitize_attributes(real_class, attributes, types)
|
156
|
+
skip = (inheritance_merged_attributes - real_class.attribute_names).to_set
|
157
|
+
skip.merge(real_class.attribute_names - inheritance_mergeable_attributes)
|
158
|
+
return [attributes, types] if skip.empty?
|
159
|
+
|
160
|
+
dropped = 0
|
161
|
+
new_types = {}
|
162
|
+
|
163
|
+
row = attributes.instance_variable_get(:@row).dup
|
164
|
+
indexes = attributes.instance_variable_get(:@column_indexes).dup
|
165
|
+
indexes = indexes.each_with_object({}) do |(column, index), new_indexes|
|
166
|
+
attribute, prefix = column.split('__', 2).reverse
|
167
|
+
current_index = index - dropped
|
168
|
+
|
169
|
+
if prefix != table_name && skip.include?(attribute)
|
170
|
+
row.delete_at(current_index)
|
171
|
+
dropped += 1
|
172
|
+
else
|
173
|
+
new_types.merge!(types.slice(attribute))
|
174
|
+
new_types[current_index] = types[index]
|
175
|
+
new_indexes[attribute] = current_index
|
176
|
+
end
|
169
177
|
end
|
170
178
|
|
171
|
-
|
172
|
-
new_record.merge!(record.slice(*(record.keys - inheritance_merged_attributes)))
|
173
|
-
record.replace(new_record)
|
179
|
+
[ActiveRecord::Result::IndexedRow.new(indexes, row), new_types]
|
174
180
|
end
|
175
181
|
|
182
|
+
# Get the real class when handling physical inheritances and casting
|
183
|
+
# the record when existing properly is present
|
184
|
+
def torque_discriminate_class_for_record(klass, record)
|
185
|
+
return if record[_auto_cast_attribute.to_s] == false
|
186
|
+
|
187
|
+
embedded_type = record[_record_class_attribute.to_s]
|
188
|
+
return if embedded_type.blank? || embedded_type == table_name
|
189
|
+
|
190
|
+
casted_dependents[embedded_type] || raise_unable_to_cast(embedded_type)
|
191
|
+
end
|
176
192
|
end
|
177
193
|
end
|
178
194
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class ArelAttributeHandler
|
7
|
+
# Shortcut
|
8
|
+
def self.call(*args)
|
9
|
+
new.call(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(*)
|
13
|
+
# There is no need to use or save the predicate builder here
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(attribute, value)
|
17
|
+
case
|
18
|
+
when array_typed?(attribute) && array_typed?(value) then attribute.overlaps(value)
|
19
|
+
when array_typed?(attribute) then value.eq(FN.any(attribute))
|
20
|
+
when array_typed?(value) then attribute.eq(FN.any(value))
|
21
|
+
else attribute.eq(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def array_typed?(attribute)
|
28
|
+
attribute.able_to_type_cast? && attribute.type_caster.is_a?(ARRAY_OID)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
module ArrayHandler
|
7
|
+
def call(attribute, value)
|
8
|
+
return super unless array_attribute?(attribute) &&
|
9
|
+
PostgreSQL.config.predicate_builder.handle_array_attributes
|
10
|
+
|
11
|
+
call_for_array(attribute, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_for_array(attribute, value)
|
15
|
+
if !value.is_a?(::Array)
|
16
|
+
call_with_value(attribute, value)
|
17
|
+
elsif value.any?
|
18
|
+
call_with_array(attribute, value)
|
19
|
+
else
|
20
|
+
call_with_empty(attribute)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def call_with_value(attribute, value)
|
27
|
+
FN.infix(:"=", FN.bind_with(attribute, value), FN.any(attribute))
|
28
|
+
end
|
29
|
+
|
30
|
+
def call_with_array(attribute, value)
|
31
|
+
attribute.overlaps(FN.bind_with(attribute, value))
|
32
|
+
end
|
33
|
+
|
34
|
+
def call_with_empty(attribute)
|
35
|
+
FN.cardinality(attribute).eq(0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def array_attribute?(attribute)
|
39
|
+
attribute.type_caster.is_a?(ARRAY_OID)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
::ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ArrayHandler)
|
44
|
+
::ActiveRecord::PredicateBuilder::BasicObjectHandler.prepend(ArrayHandler)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class EnumeratorLazyHandler < ::ActiveRecord::PredicateBuilder::ArrayHandler
|
7
|
+
Timeout = Class.new(::Timeout::Error)
|
8
|
+
|
9
|
+
def call(attribute, value)
|
10
|
+
with_timeout do
|
11
|
+
super(attribute, limit.nil? ? value.force : value.first(limit))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def with_timeout
|
18
|
+
return yield if timeout.nil?
|
19
|
+
|
20
|
+
begin
|
21
|
+
::Timeout.timeout(timeout) { yield }
|
22
|
+
rescue ::Timeout::Error
|
23
|
+
raise Timeout, "Lazy predicate builder timed out after #{timeout} seconds"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def timeout
|
28
|
+
PostgreSQL.config.predicate_builder.lazy_timeout
|
29
|
+
end
|
30
|
+
|
31
|
+
def limit
|
32
|
+
PostgreSQL.config.predicate_builder.lazy_limit
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module PredicateBuilder
|
6
|
+
class RegexpHandler
|
7
|
+
def initialize(predicate_builder)
|
8
|
+
@predicate_builder = predicate_builder
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(attribute, value)
|
12
|
+
operator = value.casefold? ? :"~*" : :"~"
|
13
|
+
FN.infix(operator, attribute, FN.bind_with(attribute, value.source))
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :predicate_builder
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'predicate_builder/array_handler'
|
4
|
+
|
5
|
+
require_relative 'predicate_builder/regexp_handler'
|
6
|
+
require_relative 'predicate_builder/arel_attribute_handler'
|
7
|
+
require_relative 'predicate_builder/enumerator_lazy_handler'
|
8
|
+
|
9
|
+
module Torque
|
10
|
+
module PostgreSQL
|
11
|
+
module PredicateBuilder
|
12
|
+
ARRAY_OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array
|
13
|
+
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
|
17
|
+
handlers = Array.wrap(PostgreSQL.config.predicate_builder.enabled).inquiry
|
18
|
+
|
19
|
+
if handlers.regexp?
|
20
|
+
register_handler(Regexp, RegexpHandler.new(self))
|
21
|
+
end
|
22
|
+
|
23
|
+
if handlers.enumerator_lazy?
|
24
|
+
register_handler(Enumerator::Lazy, EnumeratorLazyHandler.new(self))
|
25
|
+
end
|
26
|
+
|
27
|
+
if handlers.arel_attribute?
|
28
|
+
register_handler(::Arel::Attributes::Attribute, ArelAttributeHandler.new(self))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
::ActiveRecord::PredicateBuilder.prepend(PredicateBuilder)
|
34
|
+
end
|
35
|
+
end
|
@@ -4,41 +4,123 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
# = Torque PostgreSQL Railtie
|
6
6
|
class Railtie < Rails::Railtie # :nodoc:
|
7
|
-
|
8
7
|
# Get information from the running rails app
|
9
8
|
initializer 'torque-postgresql' do |app|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# initializer
|
15
|
-
Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
|
16
|
-
Torque::PostgreSQL::Attributes::EnumSet.include_on(ActiveRecord::Base)
|
17
|
-
Torque::PostgreSQL::Attributes::Period.include_on(ActiveRecord::Base)
|
18
|
-
|
19
|
-
# Setup belongs_to_many association
|
20
|
-
ActiveRecord::Base.belongs_to_many_required_by_default = torque_config.associations
|
21
|
-
.belongs_to_many_required_by_default
|
22
|
-
|
23
|
-
# Define a method to find enumaerators based on the namespace
|
24
|
-
torque_config.enum.namespace.define_singleton_method(:const_missing) do |name|
|
25
|
-
Torque::PostgreSQL::Attributes::Enum.lookup(name)
|
26
|
-
end
|
9
|
+
ActiveSupport.on_load(:active_record_postgresqladapter) do
|
10
|
+
ActiveSupport.on_load(:active_record) do
|
11
|
+
torque_config = Torque::PostgreSQL.config
|
12
|
+
torque_config.eager_load = app.config.eager_load
|
27
13
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
14
|
+
# TODO: Only load files that have their features enabled, like CTE
|
15
|
+
|
16
|
+
ar_type = ActiveRecord::Type
|
17
|
+
|
18
|
+
# Setup belongs_to_many association
|
19
|
+
ActiveRecord::Base.belongs_to_many_required_by_default =
|
20
|
+
torque_config.associations.belongs_to_many_required_by_default
|
21
|
+
|
22
|
+
## Schemas Enabled Setup
|
23
|
+
if (config = torque_config.schemas).enabled
|
24
|
+
require_relative 'adapter/schema_overrides'
|
25
|
+
end
|
26
|
+
|
27
|
+
## CTE Enabled Setup
|
28
|
+
if (config = torque_config.auxiliary_statement).enabled
|
29
|
+
require_relative 'auxiliary_statement'
|
30
|
+
require_relative 'relation/auxiliary_statement'
|
31
|
+
Relation.include(Relation::AuxiliaryStatement)
|
32
|
+
|
33
|
+
# Define the exposed constant for both types of auxiliary statements
|
34
|
+
if config.exposed_class.present?
|
35
|
+
*ns, name = config.exposed_class.split('::')
|
36
|
+
base = ns.present? ? ::Object.const_get(ns.join('::')) : ::Object
|
37
|
+
base.const_set(name, AuxiliaryStatement)
|
38
|
+
|
39
|
+
*ns, name = config.exposed_recursive_class.split('::')
|
40
|
+
base = ns.present? ? ::Object.const_get(ns.join('::')) : ::Object
|
41
|
+
base.const_set(name, AuxiliaryStatement::Recursive)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
## Enum Enabled Setup
|
46
|
+
if (config = torque_config.enum).enabled
|
47
|
+
require_relative 'adapter/oid/enum'
|
48
|
+
require_relative 'adapter/oid/enum_set'
|
49
|
+
|
50
|
+
require_relative 'attributes/enum'
|
51
|
+
require_relative 'attributes/enum_set'
|
52
|
+
|
53
|
+
Attributes::Enum.include_on(ActiveRecord::Base)
|
54
|
+
Attributes::EnumSet.include_on(ActiveRecord::Base)
|
55
|
+
|
56
|
+
ar_type.register(:enum, Adapter::OID::Enum, adapter: :postgresql)
|
57
|
+
ar_type.register(:enum_set, Adapter::OID::EnumSet, adapter: :postgresql)
|
58
|
+
|
59
|
+
if config.namespace == false
|
60
|
+
# TODO: Allow enum classes to exist without a namespace
|
61
|
+
config.namespace = PostgreSQL.const_set('Enum', Module.new)
|
62
|
+
else
|
63
|
+
config.namespace ||= ::Object.const_set('Enum', Module.new)
|
64
|
+
|
65
|
+
# Define a method to find enumerators based on the namespace
|
66
|
+
config.namespace.define_singleton_method(:const_missing) do |name|
|
67
|
+
Attributes::Enum.lookup(name)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Define a helper method to get a sample value
|
71
|
+
config.namespace.define_singleton_method(:sample) do |name|
|
72
|
+
Attributes::Enum.lookup(name).sample
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
## Geometry Enabled Setup
|
78
|
+
if (config = torque_config.geometry).enabled
|
79
|
+
require_relative 'adapter/oid/box'
|
80
|
+
require_relative 'adapter/oid/circle'
|
81
|
+
require_relative 'adapter/oid/line'
|
82
|
+
require_relative 'adapter/oid/segment'
|
83
|
+
|
84
|
+
ar_type.register(:box, Adapter::OID::Box, adapter: :postgresql)
|
85
|
+
ar_type.register(:circle, Adapter::OID::Circle, adapter: :postgresql)
|
86
|
+
ar_type.register(:line, Adapter::OID::Line, adapter: :postgresql)
|
87
|
+
ar_type.register(:segment, Adapter::OID::Segment, adapter: :postgresql)
|
88
|
+
end
|
89
|
+
|
90
|
+
## Period Enabled Setup
|
91
|
+
if (config = torque_config.period).enabled
|
92
|
+
require_relative 'attributes/period'
|
93
|
+
Attributes::Period.include_on(ActiveRecord::Base)
|
94
|
+
end
|
95
|
+
|
96
|
+
## Interval Enabled Setup
|
97
|
+
if (config = torque_config.interval).enabled
|
98
|
+
require_relative 'adapter/oid/interval'
|
99
|
+
ar_type.register(:interval, Adapter::OID::Interval, adapter: :postgresql)
|
100
|
+
end
|
101
|
+
|
102
|
+
## Full Text Search Enabled Setup
|
103
|
+
if (config = torque_config.full_text_search).enabled
|
104
|
+
require_relative 'attributes/full_text_search'
|
105
|
+
Attributes::FullTextSearch.include_on(ActiveRecord::Base)
|
106
|
+
end
|
107
|
+
|
108
|
+
## Arel Setup
|
109
|
+
PostgreSQL::Arel.build_operations(torque_config.arel.infix_operators)
|
110
|
+
if (mod = torque_config.arel.expose_function_helper_on&.to_s)
|
111
|
+
parent, _, name = mod.rpartition('::')
|
112
|
+
parent.constantize.const_set(name, PostgreSQL::FN)
|
113
|
+
end
|
32
114
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
115
|
+
# Make sure to load all the types that are handled by this gem on
|
116
|
+
# each individual PG connection
|
117
|
+
adapter = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
118
|
+
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
119
|
+
next unless pool.db_config.adapter_class.is_a?(adapter)
|
38
120
|
|
39
|
-
|
40
|
-
|
41
|
-
|
121
|
+
pool.with_connection { |conn| conn.torque_load_additional_types }
|
122
|
+
end
|
123
|
+
end
|
42
124
|
end
|
43
125
|
end
|
44
126
|
end
|
@@ -5,29 +5,25 @@ module Torque
|
|
5
5
|
module Reflection
|
6
6
|
module AbstractReflection
|
7
7
|
AREL_ATTR = ::Arel::Attributes::Attribute
|
8
|
-
|
9
|
-
ARR_NO_CAST = 'bigint'
|
10
|
-
ARR_CAST = 'bigint[]'
|
8
|
+
AREL_NODE = ::Arel::Nodes::Node
|
11
9
|
|
12
10
|
# Check if the foreign key actually exists
|
13
11
|
def connected_through_array?
|
14
12
|
false
|
15
13
|
end
|
16
14
|
|
17
|
-
#
|
18
|
-
#
|
15
|
+
# Connection through an array-like attribute is more complex then just
|
16
|
+
# a simple eq. This needs to go through the channel that handles larger
|
17
|
+
# situations
|
19
18
|
def join_scope(table, foreign_table, foreign_klass)
|
20
19
|
return super unless connected_through_array?
|
21
20
|
|
22
|
-
|
21
|
+
table_md = ActiveRecord::TableMetadata.new(klass, table)
|
22
|
+
predicate_builder = klass.predicate_builder.with(table_md)
|
23
23
|
scope_chain_items = join_scopes(table, predicate_builder)
|
24
24
|
klass_scope = klass_join_scope(table, predicate_builder)
|
25
25
|
|
26
26
|
klass_scope.where!(build_id_constraint_between(table, foreign_table))
|
27
|
-
klass_scope.where!(type => foreign_klass.polymorphic_name) if type
|
28
|
-
klass_scope.where!(klass.send(:type_condition, table)) \
|
29
|
-
if klass.finder_needs_type_condition?
|
30
|
-
|
31
27
|
scope_chain_items.inject(klass_scope, &:merge!)
|
32
28
|
end
|
33
29
|
|
@@ -40,43 +36,15 @@ module Torque
|
|
40
36
|
result
|
41
37
|
end
|
42
38
|
|
43
|
-
# Build the id constraint checking if both types are perfect matching.
|
44
|
-
# The klass attribute (left side) will always be a column attribute
|
45
|
-
def build_id_constraint(klass_attr, source_attr)
|
46
|
-
return klass_attr.eq(source_attr) unless connected_through_array?
|
47
|
-
|
48
|
-
# Klass and key are associated with the reflection Class
|
49
|
-
klass_type = klass.columns_hash[join_keys.key.to_s]
|
50
|
-
|
51
|
-
# Apply an ANY operation which checks if the single value on the left
|
52
|
-
# side exists in the array on the right side
|
53
|
-
if source_attr.is_a?(AREL_ATTR)
|
54
|
-
any_value = [klass_attr, source_attr]
|
55
|
-
any_value.reverse! if klass_type.try(:array?)
|
56
|
-
return any_value.shift.eq(::Arel::Nodes::NamedFunction.new('ANY', any_value))
|
57
|
-
end
|
58
|
-
|
59
|
-
# If the left side is not an array, just use the IN condition
|
60
|
-
return klass_attr.in(source_attr) unless klass_type.try(:array)
|
61
|
-
|
62
|
-
# Build the overlap condition (array && array) ensuring that the right
|
63
|
-
# side has the same type as the left side
|
64
|
-
source_attr = ::Arel::Nodes.build_quoted(Array.wrap(source_attr))
|
65
|
-
klass_attr.overlaps(source_attr.cast(klass_type.sql_type_metadata.sql_type))
|
66
|
-
end
|
67
|
-
|
68
|
-
# TODO: Deprecate this method
|
69
|
-
def join_keys
|
70
|
-
OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key)
|
71
|
-
end
|
72
|
-
|
73
39
|
private
|
74
40
|
|
41
|
+
# This one is a lot simpler, now that we have a predicate builder that
|
42
|
+
# knows exactly what to do with 2 array-like attributes
|
75
43
|
def build_id_constraint_between(table, foreign_table)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
44
|
+
PredicateBuilder::ArelAttributeHandler.call(
|
45
|
+
table[join_primary_key],
|
46
|
+
foreign_table[join_foreign_key],
|
47
|
+
)
|
80
48
|
end
|
81
49
|
end
|
82
50
|
|
@@ -5,7 +5,7 @@ module Torque
|
|
5
5
|
module Reflection
|
6
6
|
module RuntimeReflection
|
7
7
|
delegate :klass, :active_record, :connected_through_array?, :macro, :name,
|
8
|
-
:
|
8
|
+
:array_attribute, to: :@reflection
|
9
9
|
end
|
10
10
|
|
11
11
|
::ActiveRecord::Reflection::RuntimeReflection.include(RuntimeReflection)
|
@@ -5,8 +5,6 @@ module Torque
|
|
5
5
|
module Relation
|
6
6
|
module Inheritance
|
7
7
|
|
8
|
-
# REGCLASS = ::Arel.sql('tableoid').cast('regclass')
|
9
|
-
|
10
8
|
# :nodoc:
|
11
9
|
def cast_records_value; get_value(:cast_records); end
|
12
10
|
# :nodoc:
|
@@ -46,7 +44,7 @@ module Torque
|
|
46
44
|
|
47
45
|
# Like #cast_records, but modifies relation in place
|
48
46
|
def cast_records!(*types, **options)
|
49
|
-
where!(regclass.
|
47
|
+
where!(regclass.pg_cast(:varchar).in(types.map(&:table_name))) if options[:filter]
|
50
48
|
self.select_extra_values += [regclass.as(_record_class_attribute.to_s)]
|
51
49
|
self.cast_records_value = (types.present? ? types : model.casted_dependents.values)
|
52
50
|
self
|
@@ -73,8 +71,7 @@ module Torque
|
|
73
71
|
next arel_tables.first[column] if arel_tables.size == 1
|
74
72
|
|
75
73
|
if mergeable.include?(column)
|
76
|
-
|
77
|
-
::Arel::Nodes::NamedFunction.new('COALESCE', list).as(column)
|
74
|
+
FN.coalesce(*arel_tables.each_with_object(column).map(&:[])).as(column)
|
78
75
|
else
|
79
76
|
arel_tables.map { |table| table[column].as("#{table.left.name}__#{column}") }
|
80
77
|
end
|
@@ -105,12 +102,12 @@ module Torque
|
|
105
102
|
end
|
106
103
|
|
107
104
|
def build_auto_caster_marker(arel, types)
|
108
|
-
attribute = regclass.
|
105
|
+
attribute = regclass.pg_cast(:varchar).in(types.map(&:table_name))
|
109
106
|
attribute.as(self.class._auto_cast_attribute.to_s)
|
110
107
|
end
|
111
108
|
|
112
109
|
def regclass
|
113
|
-
arel_table['tableoid'].
|
110
|
+
arel_table['tableoid'].pg_cast(:regclass)
|
114
111
|
end
|
115
112
|
|
116
113
|
end
|