torque-postgresql 2.3.0 → 2.4.1
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 +13 -14
- data/lib/torque/postgresql/adapter/schema_statements.rb +5 -0
- data/lib/torque/postgresql/auxiliary_statement/recursive.rb +149 -0
- data/lib/torque/postgresql/auxiliary_statement/settings.rb +75 -22
- data/lib/torque/postgresql/auxiliary_statement.rb +39 -40
- data/lib/torque/postgresql/base.rb +10 -0
- data/lib/torque/postgresql/config.rb +5 -1
- data/lib/torque/postgresql/inheritance.rb +3 -1
- data/lib/torque/postgresql/railtie.rb +5 -1
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +28 -15
- data/lib/torque/postgresql/version.rb +1 -1
- data/spec/models/category.rb +2 -0
- data/spec/schema.rb +7 -1
- data/spec/tests/auxiliary_statement_spec.rb +374 -35
- data/spec/tests/schema_spec.rb +9 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e36ced2b0986c132e095bbc1799e4fab1f7bd6f320e83d74f8387c9ad23b798
|
4
|
+
data.tar.gz: 4807f5dc5146805a349a5495829e0b4aac83df495982b08a35c9c593d8323d42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84cc6db0f08fcb8c6d0616784382f77ebca1440a396df0a06efa7c2b0e1e0ac6685d54c4b4f6ac5163a7ba42cbeb4b2f81fcb197e4836d894819e0f76d9545c4
|
7
|
+
data.tar.gz: 3624ca914923f61c0bf23a7eed17c8c7d71270b8d0853d3d1445b7a1e513a97d066d8a7041111e504a7b1e6aaaab391cc2502d8fb42e69052c46b97377f4dfb4
|
@@ -185,21 +185,20 @@ module Torque
|
|
185
185
|
|
186
186
|
# Get the list of columns, and their definition, but only from the
|
187
187
|
# actual table, does not include columns that comes from inherited table
|
188
|
-
def column_definitions(table_name)
|
189
|
-
|
188
|
+
def column_definitions(table_name)
|
189
|
+
local = 'AND a.attislocal' if @_dump_mode
|
190
|
+
|
190
191
|
query(<<-SQL, 'SCHEMA')
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
#{local_condition}
|
202
|
-
ORDER BY a.attnum
|
192
|
+
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
193
|
+
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
194
|
+
c.collname, col_description(a.attrelid, a.attnum) AS comment
|
195
|
+
FROM pg_attribute a
|
196
|
+
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
197
|
+
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
198
|
+
LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
|
199
|
+
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
|
200
|
+
AND a.attnum > 0 AND NOT a.attisdropped #{local}
|
201
|
+
ORDER BY a.attnum
|
203
202
|
SQL
|
204
203
|
end
|
205
204
|
|
@@ -127,6 +127,11 @@ module Torque
|
|
127
127
|
|
128
128
|
private
|
129
129
|
|
130
|
+
# Remove the schema from the sequence name
|
131
|
+
def sequence_name_from_parts(table_name, column_name, suffix)
|
132
|
+
super(table_name.split('.').last, column_name, suffix)
|
133
|
+
end
|
134
|
+
|
130
135
|
def quote_enum_values(name, values, options)
|
131
136
|
prefix = options[:prefix]
|
132
137
|
prefix = name if prefix === true
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
class AuxiliaryStatement
|
6
|
+
class Recursive < AuxiliaryStatement
|
7
|
+
# Setup any additional option in the recursive mode
|
8
|
+
def initialize(*, **options)
|
9
|
+
super
|
10
|
+
|
11
|
+
@connect = options[:connect]&.to_a&.first
|
12
|
+
@union_all = options[:union_all]
|
13
|
+
@sub_query = options[:sub_query]
|
14
|
+
|
15
|
+
if options.key?(:with_depth)
|
16
|
+
@depth = options[:with_depth].values_at(:name, :start, :as)
|
17
|
+
@depth[0] ||= 'depth'
|
18
|
+
end
|
19
|
+
|
20
|
+
if options.key?(:with_path)
|
21
|
+
@path = options[:with_path].values_at(:name, :source, :as)
|
22
|
+
@path[0] ||= 'path'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Build the string or arel query
|
29
|
+
def build_query(base)
|
30
|
+
# Expose columns and get the list of the ones for select
|
31
|
+
columns = expose_columns(base, @query.try(:arel_table))
|
32
|
+
sub_columns = columns.dup
|
33
|
+
type = @union_all.present? ? 'all' : ''
|
34
|
+
|
35
|
+
# Build any extra columns that are dynamic and from the recursion
|
36
|
+
extra_columns(base, columns, sub_columns)
|
37
|
+
|
38
|
+
# Prepare the query depending on its type
|
39
|
+
if @query.is_a?(String) && @sub_query.is_a?(String)
|
40
|
+
args = @args.each_with_object({}) { |h, (k, v)| h[k] = base.connection.quote(v) }
|
41
|
+
::Arel.sql("(#{@query} UNION #{type.upcase} #{@sub_query})" % args)
|
42
|
+
elsif relation_query?(@query)
|
43
|
+
@query = @query.where(@where) if @where.present?
|
44
|
+
@bound_attributes.concat(@query.send(:bound_attributes))
|
45
|
+
|
46
|
+
if relation_query?(@sub_query)
|
47
|
+
@bound_attributes.concat(@sub_query.send(:bound_attributes))
|
48
|
+
|
49
|
+
sub_query = @sub_query.select(*sub_columns).arel
|
50
|
+
sub_query.from([@sub_query.arel_table, table])
|
51
|
+
else
|
52
|
+
sub_query = ::Arel.sql(@sub_query)
|
53
|
+
end
|
54
|
+
|
55
|
+
@query.select(*columns).arel.union(type, sub_query)
|
56
|
+
else
|
57
|
+
raise ArgumentError, <<-MSG.squish
|
58
|
+
Only String and ActiveRecord::Base objects are accepted as query and sub query
|
59
|
+
objects, #{@query.class.name} given for #{self.class.name}.
|
60
|
+
MSG
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Setup the statement using the class configuration
|
65
|
+
def prepare(base, settings)
|
66
|
+
super
|
67
|
+
|
68
|
+
prepare_sub_query(base, settings)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Make sure that both parts of the union are ready
|
72
|
+
def prepare_sub_query(base, settings)
|
73
|
+
@union_all = settings.union_all if @union_all.nil?
|
74
|
+
@sub_query ||= settings.sub_query
|
75
|
+
@depth ||= settings.depth
|
76
|
+
@path ||= settings.path
|
77
|
+
|
78
|
+
# Collect the connection
|
79
|
+
@connect ||= settings.connect || begin
|
80
|
+
key = base.primary_key
|
81
|
+
[key.to_sym, :"parent_#{key}"] unless key.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
raise ArgumentError, <<-MSG.squish if @sub_query.nil? && @query.is_a?(String)
|
85
|
+
Unable to generate sub query from a string query. Please provide a `sub_query`
|
86
|
+
property on the "#{table_name}" settings.
|
87
|
+
MSG
|
88
|
+
|
89
|
+
if @sub_query.nil?
|
90
|
+
raise ArgumentError, <<-MSG.squish if @connect.blank?
|
91
|
+
Unable to generate sub query without setting up a proper way to connect it
|
92
|
+
with the main query. Please provide a `connect` property on the "#{table_name}"
|
93
|
+
settings.
|
94
|
+
MSG
|
95
|
+
|
96
|
+
left, right = @connect.map(&:to_s)
|
97
|
+
condition = @query.arel_table[right].eq(table[left])
|
98
|
+
|
99
|
+
if @query.where_values_hash.key?(right)
|
100
|
+
@sub_query = @query.unscope(where: right.to_sym).where(condition)
|
101
|
+
else
|
102
|
+
@sub_query = @query.where(condition)
|
103
|
+
@query = @query.where(right => nil)
|
104
|
+
end
|
105
|
+
elsif @sub_query.respond_to?(:call)
|
106
|
+
# Call a proc to get the real sub query
|
107
|
+
call_args = @sub_query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
|
108
|
+
@sub_query = @sub_query.call(*call_args)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add depth and path if they were defined in settings
|
113
|
+
def extra_columns(base, columns, sub_columns)
|
114
|
+
return if @query.is_a?(String) || @sub_query.is_a?(String)
|
115
|
+
|
116
|
+
# Add the connect attribute to the query
|
117
|
+
if defined?(@connect)
|
118
|
+
columns.unshift(@query.arel_table[@connect[0]])
|
119
|
+
sub_columns.unshift(@sub_query.arel_table[@connect[0]])
|
120
|
+
end
|
121
|
+
|
122
|
+
# Build a column to represent the depth of the recursion
|
123
|
+
if @depth.present?
|
124
|
+
name, start, as = @depth
|
125
|
+
col = table[name]
|
126
|
+
base.select_extra_values += [col.as(as)] unless as.nil?
|
127
|
+
|
128
|
+
columns << ::Arel.sql(start.to_s).as(name)
|
129
|
+
sub_columns << (col + ::Arel.sql('1')).as(name)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Build a column to represent the path of the record access
|
133
|
+
if @path.present?
|
134
|
+
name, source, as = @path
|
135
|
+
source = @query.arel_table[source || @connect[0]]
|
136
|
+
|
137
|
+
col = table[name]
|
138
|
+
base.select_extra_values += [col.as(as)] unless as.nil?
|
139
|
+
parts = [col, source.cast(:varchar)]
|
140
|
+
|
141
|
+
columns << ::Arel.array([source]).cast(:varchar, true).as(name)
|
142
|
+
sub_columns << ::Arel::Nodes::NamedFunction.new('array_append', parts).as(name)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -4,9 +4,9 @@ module Torque
|
|
4
4
|
module PostgreSQL
|
5
5
|
class AuxiliaryStatement
|
6
6
|
class Settings < Collector.new(:attributes, :join, :join_type, :query, :requires,
|
7
|
-
:polymorphic, :through)
|
7
|
+
:polymorphic, :through, :union_all, :connect)
|
8
8
|
|
9
|
-
attr_reader :base, :source
|
9
|
+
attr_reader :base, :source, :depth, :path
|
10
10
|
alias_method :select, :attributes
|
11
11
|
alias_method :cte, :source
|
12
12
|
|
@@ -14,9 +14,10 @@ module Torque
|
|
14
14
|
delegate :table, :table_name, to: :@source
|
15
15
|
delegate :sql, to: ::Arel
|
16
16
|
|
17
|
-
def initialize(base, source)
|
17
|
+
def initialize(base, source, recursive = false)
|
18
18
|
@base = base
|
19
19
|
@source = source
|
20
|
+
@recursive = recursive
|
20
21
|
end
|
21
22
|
|
22
23
|
def base_name
|
@@ -27,6 +28,39 @@ module Torque
|
|
27
28
|
@base.arel_table
|
28
29
|
end
|
29
30
|
|
31
|
+
|
32
|
+
def recursive?
|
33
|
+
@recursive
|
34
|
+
end
|
35
|
+
|
36
|
+
def depth?
|
37
|
+
defined?(@depth)
|
38
|
+
end
|
39
|
+
|
40
|
+
def path?
|
41
|
+
defined?(@path)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add an attribute to the result showing the depth of each iteration
|
45
|
+
def with_depth(name = 'depth', start: 0, as: nil)
|
46
|
+
@depth = [name.to_s, start, as&.to_s] if recursive?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Add an attribute to the result showing the path of each record
|
50
|
+
def with_path(name = 'path', source: nil, as: nil)
|
51
|
+
@path = [name.to_s, source&.to_s, as&.to_s] if recursive?
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set recursive operation to use union all
|
55
|
+
def union_all!
|
56
|
+
@union_all = true if recursive?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add both depth and path to the result
|
60
|
+
def with_depth_and_path
|
61
|
+
with_depth && with_path
|
62
|
+
end
|
63
|
+
|
30
64
|
# Get the arel version of the table set on the query
|
31
65
|
def query_table
|
32
66
|
raise StandardError, 'The query is not defined yet' if query.nil?
|
@@ -41,36 +75,55 @@ module Torque
|
|
41
75
|
|
42
76
|
alias column col
|
43
77
|
|
44
|
-
# There are
|
78
|
+
# There are three ways of setting the query:
|
45
79
|
# - A simple relation based on a Model
|
46
80
|
# - A Arel-based select manager
|
47
|
-
# - A string or a proc
|
81
|
+
# - A string or a proc
|
48
82
|
def query(value = nil, command = nil)
|
49
83
|
return @query if value.nil?
|
50
|
-
return @query = value if relation_query?(value)
|
51
84
|
|
52
|
-
|
53
|
-
|
54
|
-
@query_table = value.source.left.name
|
55
|
-
return
|
56
|
-
end
|
85
|
+
@query = sanitize_query(value, command)
|
86
|
+
end
|
57
87
|
|
58
|
-
|
88
|
+
# Same as query, but for the second part of the union for recursive cte
|
89
|
+
def sub_query(value = nil, command = nil)
|
90
|
+
return unless recursive?
|
91
|
+
return @sub_query if value.nil?
|
59
92
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
93
|
+
@sub_query = sanitize_query(value, command)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Assume `parent_` as the other part if provided a Symbol or String
|
97
|
+
def connect(value = nil)
|
98
|
+
return @connect if value.nil?
|
64
99
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
MSG
|
100
|
+
value = [value.to_sym, :"parent_#{value}"] \
|
101
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
102
|
+
value = value.to_a.first if value.is_a?(Hash)
|
69
103
|
|
70
|
-
@
|
71
|
-
@query_table = ::Arel::Table.new(value)
|
104
|
+
@connect = value
|
72
105
|
end
|
73
106
|
|
107
|
+
alias connect= connect
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Get the query and table from the params
|
112
|
+
def sanitize_query(value, command = nil)
|
113
|
+
return value if relation_query?(value)
|
114
|
+
return value if value.is_a?(::Arel::SelectManager)
|
115
|
+
|
116
|
+
command = value if command.nil? # For compatibility purposes
|
117
|
+
valid_type = command.respond_to?(:call) || command.is_a?(String)
|
118
|
+
|
119
|
+
raise ArgumentError, <<-MSG.squish unless valid_type
|
120
|
+
Only relation, string and proc are valid object types for query,
|
121
|
+
#{command.inspect} given.
|
122
|
+
MSG
|
123
|
+
|
124
|
+
command
|
125
|
+
end
|
126
|
+
|
74
127
|
end
|
75
128
|
end
|
76
129
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'auxiliary_statement/settings'
|
4
|
+
require_relative 'auxiliary_statement/recursive'
|
4
5
|
|
5
6
|
module Torque
|
6
7
|
module PostgreSQL
|
@@ -8,17 +9,20 @@ module Torque
|
|
8
9
|
TABLE_COLUMN_AS_STRING = /\A(?:"?(\w+)"?\.)?"?(\w+)"?\z/.freeze
|
9
10
|
|
10
11
|
class << self
|
11
|
-
attr_reader :config
|
12
|
+
attr_reader :config, :table_name
|
12
13
|
|
13
14
|
# Find or create the class that will handle statement
|
14
15
|
def lookup(name, base)
|
15
16
|
const = name.to_s.camelize << '_' << self.name.demodulize
|
16
17
|
return base.const_get(const, false) if base.const_defined?(const, false)
|
17
|
-
|
18
|
+
|
19
|
+
base.const_set(const, Class.new(self)).tap do |klass|
|
20
|
+
klass.instance_variable_set(:@table_name, name.to_s)
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
# Create a new instance of an auxiliary statement
|
21
|
-
def instantiate(statement, base, options
|
25
|
+
def instantiate(statement, base, **options)
|
22
26
|
klass = while base < ActiveRecord::Base
|
23
27
|
list = base.auxiliary_statements_list
|
24
28
|
break list[statement] if list.present? && list.key?(statement)
|
@@ -26,15 +30,15 @@ module Torque
|
|
26
30
|
base = base.superclass
|
27
31
|
end
|
28
32
|
|
29
|
-
return klass.new(options) unless klass.nil?
|
33
|
+
return klass.new(**options) unless klass.nil?
|
30
34
|
raise ArgumentError, <<-MSG.squish
|
31
35
|
There's no '#{statement}' auxiliary statement defined for #{base.class.name}.
|
32
36
|
MSG
|
33
37
|
end
|
34
38
|
|
35
39
|
# Fast access to statement build
|
36
|
-
def build(statement, base,
|
37
|
-
klass = instantiate(statement, base, options)
|
40
|
+
def build(statement, base, bound_attributes = [], join_sources = [], **options)
|
41
|
+
klass = instantiate(statement, base, **options)
|
38
42
|
result = klass.build(base)
|
39
43
|
|
40
44
|
bound_attributes.concat(klass.bound_attributes)
|
@@ -56,7 +60,7 @@ module Torque
|
|
56
60
|
# A way to create auxiliary statements outside of models configurations,
|
57
61
|
# being able to use on extensions
|
58
62
|
def create(table_or_settings, &block)
|
59
|
-
klass = Class.new(
|
63
|
+
klass = Class.new(self)
|
60
64
|
|
61
65
|
if block_given?
|
62
66
|
klass.instance_variable_set(:@table_name, table_or_settings)
|
@@ -89,7 +93,8 @@ module Torque
|
|
89
93
|
def configure(base, instance)
|
90
94
|
return @config unless @config.respond_to?(:call)
|
91
95
|
|
92
|
-
|
96
|
+
recursive = self < AuxiliaryStatement::Recursive
|
97
|
+
settings = Settings.new(base, instance, recursive)
|
93
98
|
settings.instance_exec(settings, &@config)
|
94
99
|
settings
|
95
100
|
end
|
@@ -98,11 +103,6 @@ module Torque
|
|
98
103
|
def table
|
99
104
|
@table ||= ::Arel::Table.new(table_name)
|
100
105
|
end
|
101
|
-
|
102
|
-
# Get the name of the table of the configurated statement
|
103
|
-
def table_name
|
104
|
-
@table_name ||= self.name.demodulize.split('_').first.underscore
|
105
|
-
end
|
106
106
|
end
|
107
107
|
|
108
108
|
delegate :config, :table, :table_name, :relation, :configure, :relation_query?,
|
@@ -111,15 +111,14 @@ module Torque
|
|
111
111
|
attr_reader :bound_attributes, :join_sources
|
112
112
|
|
113
113
|
# Start a new auxiliary statement giving extra options
|
114
|
-
def initialize(
|
115
|
-
options = args.extract_options!
|
114
|
+
def initialize(*, **options)
|
116
115
|
args_key = Torque::PostgreSQL.config.auxiliary_statement.send_arguments_key
|
117
116
|
|
118
117
|
@join = options.fetch(:join, {})
|
119
118
|
@args = options.fetch(args_key, {})
|
120
119
|
@where = options.fetch(:where, {})
|
121
120
|
@select = options.fetch(:select, {})
|
122
|
-
@join_type = options
|
121
|
+
@join_type = options[:join_type]
|
123
122
|
|
124
123
|
@bound_attributes = []
|
125
124
|
@join_sources = []
|
@@ -131,7 +130,7 @@ module Torque
|
|
131
130
|
@join_sources.clear
|
132
131
|
|
133
132
|
# Prepare all the data for the statement
|
134
|
-
prepare(base)
|
133
|
+
prepare(base, configure(base, self))
|
135
134
|
|
136
135
|
# Add the join condition to the list
|
137
136
|
@join_sources << build_join(base)
|
@@ -142,8 +141,7 @@ module Torque
|
|
142
141
|
|
143
142
|
private
|
144
143
|
# Setup the statement using the class configuration
|
145
|
-
def prepare(base)
|
146
|
-
settings = configure(base, self)
|
144
|
+
def prepare(base, settings)
|
147
145
|
requires = Array.wrap(settings.requires).flatten.compact
|
148
146
|
@dependencies = ensure_dependencies(requires, base).flatten.compact
|
149
147
|
|
@@ -151,14 +149,13 @@ module Torque
|
|
151
149
|
@query = settings.query
|
152
150
|
|
153
151
|
# Call a proc to get the real query
|
154
|
-
if @query.
|
152
|
+
if @query.respond_to?(:call)
|
155
153
|
call_args = @query.try(:arity) === 0 ? [] : [OpenStruct.new(@args)]
|
156
154
|
@query = @query.call(*call_args)
|
157
155
|
@args = []
|
158
156
|
end
|
159
157
|
|
160
|
-
#
|
161
|
-
@query_table = settings.query_table unless relation_query?(@query)
|
158
|
+
# Merge select attributes provided on the instance creation
|
162
159
|
@select = settings.attributes.merge(@select) if settings.attributes.present?
|
163
160
|
|
164
161
|
# Merge join settings
|
@@ -168,7 +165,7 @@ module Torque
|
|
168
165
|
@association = settings.through.to_s
|
169
166
|
elsif relation_query?(@query)
|
170
167
|
@association = base.reflections.find do |name, reflection|
|
171
|
-
break name if @query.klass.eql?
|
168
|
+
break name if @query.klass.eql?(reflection.klass)
|
172
169
|
end
|
173
170
|
end
|
174
171
|
end
|
@@ -234,15 +231,6 @@ module Torque
|
|
234
231
|
as a query object on #{self.class.name}.
|
235
232
|
MSG
|
236
233
|
|
237
|
-
# Expose join columns
|
238
|
-
if relation_query?(@query)
|
239
|
-
query_table = @query.arel_table
|
240
|
-
conditions.children.each do |item|
|
241
|
-
@query.select_values += [query_table[item.left.name]] \
|
242
|
-
if item.left.relation.eql?(table)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
234
|
# Build the join based on the join type
|
247
235
|
arel_join.new(table, table.create_on(conditions))
|
248
236
|
end
|
@@ -263,21 +251,31 @@ module Torque
|
|
263
251
|
|
264
252
|
# Mount the list of selected attributes
|
265
253
|
def expose_columns(base, query_table = nil)
|
266
|
-
# Add
|
267
|
-
@select
|
268
|
-
|
269
|
-
|
254
|
+
# Add the columns necessary for the join
|
255
|
+
list = @join_sources.each_with_object(@select) do |join, hash|
|
256
|
+
join.right.expr.children.each do |item|
|
257
|
+
hash[item.left.name] = nil if item.left.relation.eql?(table)
|
258
|
+
end
|
270
259
|
end
|
260
|
+
|
261
|
+
# Add select columns to the query and get exposed columns
|
262
|
+
list.map do |left, right|
|
263
|
+
base.select_extra_values += [table[right.to_s]] unless right.nil?
|
264
|
+
next unless query_table
|
265
|
+
|
266
|
+
col = project(left, query_table)
|
267
|
+
right.nil? ? col : col.as(right.to_s)
|
268
|
+
end.compact
|
271
269
|
end
|
272
270
|
|
273
271
|
# Ensure that all the dependencies are loaded in the base relation
|
274
272
|
def ensure_dependencies(list, base)
|
275
273
|
with_options = list.extract_options!.to_a
|
276
|
-
(list + with_options).map do |
|
277
|
-
dependent_klass = base.model.auxiliary_statements_list[
|
274
|
+
(list + with_options).map do |name, options|
|
275
|
+
dependent_klass = base.model.auxiliary_statements_list[name]
|
278
276
|
|
279
277
|
raise ArgumentError, <<-MSG.squish if dependent_klass.nil?
|
280
|
-
The '#{
|
278
|
+
The '#{name}' auxiliary statement dependency can't found on
|
281
279
|
#{self.class.name}.
|
282
280
|
MSG
|
283
281
|
|
@@ -285,7 +283,8 @@ module Torque
|
|
285
283
|
cte.is_a?(dependent_klass)
|
286
284
|
end
|
287
285
|
|
288
|
-
|
286
|
+
options ||= {}
|
287
|
+
AuxiliaryStatement.build(name, base, bound_attributes, join_sources, **options)
|
289
288
|
end
|
290
289
|
end
|
291
290
|
|
@@ -309,6 +309,16 @@ module Torque
|
|
309
309
|
klass.configurator(block)
|
310
310
|
end
|
311
311
|
alias cte auxiliary_statement
|
312
|
+
|
313
|
+
# Creates a new recursive auxiliary statement (CTE) under the base
|
314
|
+
# Very similar to the regular auxiliary statement, but with two-part
|
315
|
+
# query where one is executed first and the second recursively
|
316
|
+
def recursive_auxiliary_statement(table, &block)
|
317
|
+
klass = AuxiliaryStatement::Recursive.lookup(table, self)
|
318
|
+
auxiliary_statements_list[table.to_sym] = klass
|
319
|
+
klass.configurator(block)
|
320
|
+
end
|
321
|
+
alias recursive_cte recursive_auxiliary_statement
|
312
322
|
end
|
313
323
|
end
|
314
324
|
|
@@ -69,10 +69,14 @@ module Torque
|
|
69
69
|
# arguments to format string or send on a proc
|
70
70
|
cte.send_arguments_key = :args
|
71
71
|
|
72
|
-
# Estipulate a class name (which may contain namespace) that
|
72
|
+
# Estipulate a class name (which may contain namespace) that exposes the
|
73
73
|
# auxiliary statement in order to perform detached CTEs
|
74
74
|
cte.exposed_class = 'TorqueCTE'
|
75
75
|
|
76
|
+
# Estipulate a class name (which may contain namespace) that exposes the
|
77
|
+
# recursive auxiliary statement in order to perform detached CTEs
|
78
|
+
cte.exposed_recursive_class = 'TorqueRecursiveCTE'
|
79
|
+
|
76
80
|
end
|
77
81
|
|
78
82
|
# Configure ENUM features
|
@@ -55,7 +55,9 @@ module Torque
|
|
55
55
|
|
56
56
|
# Check if the model's table depends on any inheritance
|
57
57
|
def physically_inherited?
|
58
|
-
@physically_inherited
|
58
|
+
return @physically_inherited if defined?(@physically_inherited)
|
59
|
+
|
60
|
+
@physically_inherited = connection.schema_cache.dependencies(
|
59
61
|
defined?(@table_name) ? @table_name : decorated_table_name,
|
60
62
|
).present?
|
61
63
|
rescue ActiveRecord::ConnectionNotEstablished
|
@@ -30,11 +30,15 @@ module Torque
|
|
30
30
|
Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
|
31
31
|
end
|
32
32
|
|
33
|
-
# Define the exposed constant for auxiliary statements
|
33
|
+
# Define the exposed constant for both types of auxiliary statements
|
34
34
|
if torque_config.auxiliary_statement.exposed_class.present?
|
35
35
|
*ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
|
36
36
|
base = ns.present? ? Object.const_get(ns.join('::')) : Object
|
37
37
|
base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
|
38
|
+
|
39
|
+
*ns, name = torque_config.auxiliary_statement.exposed_recursive_class.split('::')
|
40
|
+
base = ns.present? ? Object.const_get(ns.join('::')) : Object
|
41
|
+
base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement::Recursive)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|