arel_toolkit 0.2.0 → 0.4.3
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/.codeclimate.yml +3 -0
- data/.github/workflows/develop.yml +90 -0
- data/.github/workflows/master.yml +67 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +13 -2
- data/Appraisals +13 -0
- data/CHANGELOG.md +132 -6
- data/Gemfile +5 -0
- data/Gemfile.lock +92 -12
- data/Guardfile +23 -12
- data/README.md +104 -6
- data/Rakefile +18 -0
- data/arel_toolkit.gemspec +19 -4
- data/benchmark.rb +54 -0
- data/bin/console +1 -0
- data/ext/pg_result_init/extconf.rb +52 -0
- data/ext/pg_result_init/pg_result_init.c +138 -0
- data/ext/pg_result_init/pg_result_init.h +6 -0
- data/gemfiles/active_record_6.gemfile +7 -0
- data/gemfiles/active_record_6.gemfile.lock +210 -0
- data/gemfiles/arel_gems.gemfile +10 -0
- data/gemfiles/arel_gems.gemfile.lock +284 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/default.gemfile.lock +208 -0
- data/lib/arel/enhance.rb +17 -0
- data/lib/arel/enhance/context_enhancer/arel_table.rb +92 -0
- data/lib/arel/enhance/node.rb +232 -0
- data/lib/arel/enhance/path.rb +38 -0
- data/lib/arel/enhance/path_node.rb +26 -0
- data/lib/arel/enhance/query.rb +38 -0
- data/lib/arel/enhance/query_methods.rb +23 -0
- data/lib/arel/enhance/visitor.rb +97 -0
- data/lib/arel/extensions.rb +55 -3
- data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
- data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
- data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
- data/lib/arel/extensions/active_record_type_caster_map.rb +7 -0
- data/lib/arel/extensions/array.rb +2 -9
- data/lib/arel/extensions/assignment.rb +22 -0
- data/lib/arel/extensions/at_time_zone.rb +37 -0
- data/lib/arel/extensions/attributes_attribute.rb +47 -0
- data/lib/arel/extensions/binary.rb +7 -0
- data/lib/arel/extensions/bind_param.rb +15 -0
- data/lib/arel/extensions/bit_string.rb +2 -9
- data/lib/arel/extensions/case.rb +17 -0
- data/lib/arel/extensions/coalesce.rb +17 -3
- data/lib/arel/extensions/conflict.rb +9 -0
- data/lib/arel/extensions/contained_within_equals.rb +10 -0
- data/lib/arel/extensions/contains.rb +27 -5
- data/lib/arel/extensions/contains_equals.rb +10 -0
- data/lib/arel/extensions/current_catalog.rb +4 -0
- data/lib/arel/extensions/current_date.rb +4 -0
- data/lib/arel/extensions/current_of_expression.rb +2 -9
- data/lib/arel/extensions/current_role.rb +4 -0
- data/lib/arel/extensions/current_row.rb +7 -0
- data/lib/arel/extensions/current_schema.rb +4 -0
- data/lib/arel/extensions/current_user.rb +4 -0
- data/lib/arel/extensions/dealocate.rb +31 -0
- data/lib/arel/extensions/default_values.rb +4 -0
- data/lib/arel/extensions/delete_manager.rb +25 -0
- data/lib/arel/extensions/delete_statement.rb +32 -8
- data/lib/arel/extensions/distinct_from.rb +3 -16
- data/lib/arel/extensions/dot.rb +11 -0
- data/lib/arel/extensions/equality.rb +2 -4
- data/lib/arel/extensions/exists.rb +59 -0
- data/lib/arel/extensions/extract_from.rb +25 -0
- data/lib/arel/extensions/factorial.rb +10 -2
- data/lib/arel/extensions/false.rb +7 -0
- data/lib/arel/extensions/function.rb +44 -14
- data/lib/arel/extensions/greatest.rb +17 -3
- data/lib/arel/extensions/indirection.rb +3 -12
- data/lib/arel/extensions/infer.rb +7 -7
- data/lib/arel/extensions/infix_operation.rb +17 -0
- data/lib/arel/extensions/insert_manager.rb +21 -0
- data/lib/arel/extensions/insert_statement.rb +35 -9
- data/lib/arel/extensions/into.rb +21 -0
- data/lib/arel/extensions/json_get_field.rb +10 -0
- data/lib/arel/extensions/json_get_object.rb +10 -0
- data/lib/arel/extensions/json_path_get_field.rb +10 -0
- data/lib/arel/extensions/json_path_get_object.rb +10 -0
- data/lib/arel/extensions/jsonb_all_key_exists.rb +10 -0
- data/lib/arel/extensions/jsonb_any_key_exists.rb +10 -0
- data/lib/arel/extensions/jsonb_key_exists.rb +10 -0
- data/lib/arel/extensions/least.rb +17 -3
- data/lib/arel/extensions/named_argument.rb +24 -0
- data/lib/arel/extensions/named_function.rb +7 -0
- data/lib/arel/extensions/node.rb +10 -0
- data/lib/arel/extensions/not_distinct_from.rb +3 -16
- data/lib/arel/extensions/not_equal.rb +2 -4
- data/lib/arel/extensions/ordering.rb +21 -6
- data/lib/arel/extensions/overlap.rb +1 -1
- data/lib/arel/extensions/overlaps.rb +49 -0
- data/lib/arel/extensions/overlay.rb +53 -0
- data/lib/arel/extensions/position.rb +27 -0
- data/lib/arel/extensions/prepare.rb +39 -0
- data/lib/arel/extensions/range_function.rb +10 -2
- data/lib/arel/extensions/row.rb +3 -8
- data/lib/arel/extensions/select_core.rb +73 -0
- data/lib/arel/extensions/select_manager.rb +25 -0
- data/lib/arel/extensions/select_statement.rb +31 -9
- data/lib/arel/extensions/session_user.rb +4 -0
- data/lib/arel/extensions/set_to_default.rb +4 -0
- data/lib/arel/extensions/substring.rb +46 -0
- data/lib/arel/extensions/table.rb +43 -10
- data/lib/arel/extensions/time_with_precision.rb +6 -0
- data/lib/arel/extensions/to_sql.rb +27 -0
- data/lib/arel/extensions/top.rb +8 -0
- data/lib/arel/extensions/transaction.rb +45 -0
- data/lib/arel/extensions/tree_manager.rb +15 -0
- data/lib/arel/extensions/trim.rb +44 -0
- data/lib/arel/extensions/true.rb +7 -0
- data/lib/arel/extensions/type_cast.rb +11 -0
- data/lib/arel/extensions/unary.rb +7 -0
- data/lib/arel/extensions/unary_operation.rb +16 -0
- data/lib/arel/extensions/unknown.rb +4 -0
- data/lib/arel/extensions/update_manager.rb +25 -0
- data/lib/arel/extensions/update_statement.rb +31 -6
- data/lib/arel/extensions/user.rb +4 -0
- data/lib/arel/extensions/values_list.rb +15 -0
- data/lib/arel/extensions/variable_set.rb +55 -0
- data/lib/arel/extensions/variable_show.rb +26 -0
- data/lib/arel/middleware.rb +27 -0
- data/lib/arel/middleware/active_record_extension.rb +13 -0
- data/lib/arel/middleware/cache_accessor.rb +35 -0
- data/lib/arel/middleware/chain.rb +172 -0
- data/lib/arel/middleware/database_executor.rb +77 -0
- data/lib/arel/middleware/no_op_cache.rb +9 -0
- data/lib/arel/middleware/postgresql_adapter.rb +62 -0
- data/lib/arel/middleware/railtie.rb +25 -0
- data/lib/arel/middleware/result.rb +170 -0
- data/lib/arel/middleware/to_sql_executor.rb +15 -0
- data/lib/arel/middleware/to_sql_middleware.rb +33 -0
- data/lib/arel/sql_to_arel.rb +8 -4
- data/lib/arel/sql_to_arel/error.rb +6 -0
- data/lib/arel/sql_to_arel/pg_query_visitor.rb +324 -76
- data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +112 -0
- data/lib/arel/sql_to_arel/result.rb +30 -0
- data/lib/arel/transformer.rb +8 -0
- data/lib/arel/transformer/prefix_schema_name.rb +183 -0
- data/lib/arel/transformer/remove_active_record_info.rb +40 -0
- data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
- data/lib/arel_toolkit.rb +16 -1
- data/lib/arel_toolkit/version.rb +1 -1
- metadata +278 -25
- data/.travis.yml +0 -21
- data/lib/arel/extensions/generate_series.rb +0 -9
- data/lib/arel/extensions/rank.rb +0 -9
- data/lib/arel/sql_to_arel/frame_options.rb +0 -110
- data/lib/arel/sql_to_arel/unbound_column_reference.rb +0 -5
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
class DatabaseExecutor
|
|
4
|
+
attr_reader :middleware
|
|
5
|
+
|
|
6
|
+
attr_accessor :index
|
|
7
|
+
attr_reader :context
|
|
8
|
+
attr_reader :final_block
|
|
9
|
+
|
|
10
|
+
def initialize(middleware)
|
|
11
|
+
@middleware = middleware
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(arel, context, final_block)
|
|
15
|
+
@index = 0
|
|
16
|
+
@context = context
|
|
17
|
+
@final_block = final_block
|
|
18
|
+
|
|
19
|
+
result = call(arel)
|
|
20
|
+
check_return_type result
|
|
21
|
+
result
|
|
22
|
+
ensure
|
|
23
|
+
@index = 0
|
|
24
|
+
@context = nil
|
|
25
|
+
@final_block = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(next_arel)
|
|
29
|
+
check_argument_type next_arel
|
|
30
|
+
|
|
31
|
+
current_middleware = middleware[index]
|
|
32
|
+
|
|
33
|
+
return execute_sql(next_arel) if current_middleware.nil?
|
|
34
|
+
|
|
35
|
+
self.index += 1
|
|
36
|
+
|
|
37
|
+
case current_middleware.method(:call).arity
|
|
38
|
+
when 2
|
|
39
|
+
current_middleware.call(next_arel, self)
|
|
40
|
+
else
|
|
41
|
+
current_middleware.call(next_arel, self, context.dup)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def execute_sql(next_arel)
|
|
48
|
+
sql, binds = next_arel.to_sql_and_binds
|
|
49
|
+
|
|
50
|
+
context[:cache_accessor].write(
|
|
51
|
+
transformed_sql: sql,
|
|
52
|
+
transformed_binds: binds,
|
|
53
|
+
original_sql: context[:original_sql],
|
|
54
|
+
original_binds: context[:original_binds],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
sql_result = final_block.call(sql, binds)
|
|
58
|
+
|
|
59
|
+
check_return_type sql_result
|
|
60
|
+
sql_result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def check_argument_type(next_arel)
|
|
64
|
+
return if next_arel.is_a?(Arel::Enhance::Node)
|
|
65
|
+
|
|
66
|
+
raise "Only `Arel::Enhance::Node` is valid for middleware, passed `#{next_arel.class}`"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def check_return_type(return_object)
|
|
70
|
+
return if return_object.is_a?(Arel::Middleware::Result)
|
|
71
|
+
|
|
72
|
+
raise 'Object returned from middleware needs to be wrapped in `Arel::Middleware::Result`' \
|
|
73
|
+
" for object `#{return_object}`"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
module PostgreSQLAdapter
|
|
4
|
+
def initialize(*args)
|
|
5
|
+
Arel.middleware.none do
|
|
6
|
+
super(*args)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute(sql, name = nil)
|
|
11
|
+
super(sql, name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
alias parent_execute execute
|
|
15
|
+
|
|
16
|
+
# rubocop:disable Lint/DuplicateMethods
|
|
17
|
+
def execute(sql, name = nil)
|
|
18
|
+
Arel::Middleware.current_chain.execute(sql) do |processed_sql|
|
|
19
|
+
Arel::Middleware::Result.create(
|
|
20
|
+
data: parent_execute(processed_sql, name),
|
|
21
|
+
from: Arel::Middleware::PGResult,
|
|
22
|
+
to: Arel::Middleware::PGResult,
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
# rubocop:enable Lint/DuplicateMethods
|
|
27
|
+
|
|
28
|
+
def query(sql, name = nil)
|
|
29
|
+
Arel::Middleware.current_chain.execute(sql) do |processed_sql|
|
|
30
|
+
# NOTE: we're not calling `super` here, but execute.
|
|
31
|
+
# The `query` super does not return the columns, like the other methods.
|
|
32
|
+
# As we want the result objects to be the same, we call execute instead.
|
|
33
|
+
Arel::Middleware::Result.create(
|
|
34
|
+
data: parent_execute(processed_sql, name),
|
|
35
|
+
from: Arel::Middleware::PGResult,
|
|
36
|
+
to: Arel::Middleware::ArrayResult,
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def exec_no_cache(sql, name, binds)
|
|
42
|
+
Arel::Middleware.current_chain.execute(sql, binds) do |processed_sql, processed_binds|
|
|
43
|
+
Arel::Middleware::Result.create(
|
|
44
|
+
data: super(processed_sql, name, processed_binds),
|
|
45
|
+
from: Arel::Middleware::PGResult,
|
|
46
|
+
to: Arel::Middleware::PGResult,
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def exec_cache(sql, name, binds)
|
|
52
|
+
Arel::Middleware.current_chain.execute(sql, binds) do |processed_sql, processed_binds|
|
|
53
|
+
Arel::Middleware::Result.create(
|
|
54
|
+
data: super(processed_sql, name, processed_binds),
|
|
55
|
+
from: Arel::Middleware::PGResult,
|
|
56
|
+
to: Arel::Middleware::PGResult,
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
if defined? Rails::Railtie
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer 'arel.middleware.insert' do
|
|
6
|
+
ActiveSupport.on_load :active_record do
|
|
7
|
+
Arel::Middleware::Railtie.insert
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Railtie
|
|
14
|
+
def self.insert
|
|
15
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(
|
|
16
|
+
Arel::Middleware::PostgreSQLAdapter,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
ActiveRecord::Base.singleton_class.prepend(
|
|
20
|
+
Arel::Middleware::ActiveRecordExtension,
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
class Column
|
|
4
|
+
attr_reader :name
|
|
5
|
+
attr_reader :metadata
|
|
6
|
+
|
|
7
|
+
def initialize(name, metadata)
|
|
8
|
+
@name = name
|
|
9
|
+
@metadata = metadata
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Class is very similar to ActiveRecord::Result
|
|
14
|
+
# activerecord/lib/active_record/result.rb
|
|
15
|
+
class Result
|
|
16
|
+
attr_reader :original_data
|
|
17
|
+
|
|
18
|
+
def self.create(from:, to:, data:)
|
|
19
|
+
Result.new from, to, data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(from_caster, to_caster, original_data)
|
|
23
|
+
@from_caster = from_caster
|
|
24
|
+
@to_caster = to_caster
|
|
25
|
+
@original_data = original_data
|
|
26
|
+
@modified = false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def columns
|
|
30
|
+
@columns ||= column_objects.map(&:name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def column_objects
|
|
34
|
+
@column_objects ||= from_caster.column_objects(original_data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def rows
|
|
38
|
+
@rows ||= from_caster.rows(original_data)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def remove_column(column_name)
|
|
42
|
+
column_index = columns.index(column_name)
|
|
43
|
+
raise "Unknown column `#{column_name}`. Existing columns: `#{columns}`" if column_index.nil?
|
|
44
|
+
|
|
45
|
+
@hash_rows = nil
|
|
46
|
+
@columns = nil
|
|
47
|
+
@modified = true
|
|
48
|
+
|
|
49
|
+
column_objects.delete_at(column_index)
|
|
50
|
+
deleted_rows = []
|
|
51
|
+
|
|
52
|
+
rows.map! do |row|
|
|
53
|
+
deleted_rows << row.delete_at(column_index)
|
|
54
|
+
row
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
deleted_rows
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def hash_rows
|
|
61
|
+
@hash_rows ||=
|
|
62
|
+
begin
|
|
63
|
+
rows.map do |row|
|
|
64
|
+
hash = {}
|
|
65
|
+
|
|
66
|
+
index = 0
|
|
67
|
+
length = columns.length
|
|
68
|
+
|
|
69
|
+
while index < length
|
|
70
|
+
hash[columns[index]] = row[index]
|
|
71
|
+
index += 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
hash
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_casted_result
|
|
80
|
+
to_caster.cast_to(self)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def modified?
|
|
84
|
+
@modified
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
attr_reader :to_caster, :from_caster
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class PGResult
|
|
93
|
+
class << self
|
|
94
|
+
def column_objects(pg_result)
|
|
95
|
+
pg_result.fields.each_with_index.map do |field, index|
|
|
96
|
+
Column.new(
|
|
97
|
+
field,
|
|
98
|
+
tableid: pg_result.ftable(index),
|
|
99
|
+
columnid: pg_result.ftablecol(index),
|
|
100
|
+
format: pg_result.fformat(index),
|
|
101
|
+
typid: pg_result.ftype(index),
|
|
102
|
+
typlen: pg_result.fsize(index),
|
|
103
|
+
atttypmod: pg_result.fmod(index),
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def rows(data)
|
|
109
|
+
data.values
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def cast_to(result)
|
|
113
|
+
return result.original_data unless result.modified?
|
|
114
|
+
|
|
115
|
+
pg_columns = result_to_columns(result)
|
|
116
|
+
conn = ActiveRecord::Base.connection.raw_connection
|
|
117
|
+
new_result = PgResultInit.create(conn, result.original_data, pg_columns, result.rows)
|
|
118
|
+
result.original_data.clear
|
|
119
|
+
new_result
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def result_to_columns(result)
|
|
125
|
+
result.column_objects.map do |column|
|
|
126
|
+
{
|
|
127
|
+
name: column.name,
|
|
128
|
+
tableid: column.metadata.fetch(:tableid, 0),
|
|
129
|
+
columnid: column.metadata.fetch(:columnid, 0),
|
|
130
|
+
format: column.metadata.fetch(:format, 0),
|
|
131
|
+
typid: column.metadata.fetch(:typid),
|
|
132
|
+
typlen: column.metadata.fetch(:typlen),
|
|
133
|
+
atttypmod: column.metadata.fetch(:atttypmod, -1),
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class EmptyPGResult < PGResult
|
|
141
|
+
class << self
|
|
142
|
+
def cast_to(_result)
|
|
143
|
+
ActiveRecord::Base.connection.raw_connection.make_empty_pgresult(2)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
class ArrayResult
|
|
149
|
+
def self.cast_to(result)
|
|
150
|
+
result.rows
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class StringResult
|
|
155
|
+
class << self
|
|
156
|
+
def column_objects(_string)
|
|
157
|
+
[]
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def rows(_string)
|
|
161
|
+
[]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cast_to(result)
|
|
165
|
+
result.original_data
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
class ToSqlExecutor < DatabaseExecutor
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
def execute_sql(next_arel)
|
|
7
|
+
Arel::Middleware::Result.create(
|
|
8
|
+
data: next_arel.to_sql,
|
|
9
|
+
from: Arel::Middleware::StringResult,
|
|
10
|
+
to: Arel::Middleware::EmptyPGResult,
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Middleware
|
|
3
|
+
class ToSqlMiddleware
|
|
4
|
+
attr_reader :sql, :type, :query_class
|
|
5
|
+
|
|
6
|
+
def initialize(type)
|
|
7
|
+
@sql = []
|
|
8
|
+
@type = type
|
|
9
|
+
@query_class = class_from_type
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(next_arel, next_middleware)
|
|
13
|
+
sql << next_arel.to_sql unless next_arel.query(class: query_class).empty?
|
|
14
|
+
next_middleware.call(next_arel)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def class_from_type
|
|
20
|
+
case type
|
|
21
|
+
when :insert
|
|
22
|
+
Arel::Nodes::InsertStatement
|
|
23
|
+
when :select
|
|
24
|
+
Arel::Nodes::SelectStatement
|
|
25
|
+
when :update
|
|
26
|
+
Arel::Nodes::UpdateStatement
|
|
27
|
+
when :delete
|
|
28
|
+
Arel::Nodes::DeleteStatement
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/arel/sql_to_arel.rb
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
require_relative './sql_to_arel/result'
|
|
2
|
+
require_relative './sql_to_arel/error'
|
|
3
|
+
require_relative './sql_to_arel/pg_query_visitor'
|
|
3
4
|
|
|
4
5
|
module Arel
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
module SqlToArel
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.sql_to_arel(sql, binds: [])
|
|
10
|
+
SqlToArel::PgQueryVisitor.new.accept(sql, binds)
|
|
7
11
|
end
|
|
8
12
|
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# rubocop:disable Metrics/ParameterLists
|
|
7
7
|
|
|
8
8
|
require 'pg_query'
|
|
9
|
-
require_relative './frame_options'
|
|
9
|
+
require_relative './pg_query_visitor/frame_options'
|
|
10
10
|
|
|
11
11
|
module Arel
|
|
12
12
|
module SqlToArel
|
|
@@ -15,13 +15,24 @@ module Arel
|
|
|
15
15
|
MIN_MAX_EXPR = 'MinMaxExpr'.freeze
|
|
16
16
|
|
|
17
17
|
attr_reader :object
|
|
18
|
+
attr_reader :binds
|
|
19
|
+
attr_reader :sql
|
|
18
20
|
|
|
19
|
-
def accept(sql)
|
|
21
|
+
def accept(sql, binds = [])
|
|
20
22
|
tree = PgQuery.parse(sql).tree
|
|
21
|
-
raise 'https://github.com/mvgijssel/arel_toolkit/issues/33' if tree.length > 1
|
|
22
23
|
|
|
23
|
-
@object = tree
|
|
24
|
-
|
|
24
|
+
@object = tree
|
|
25
|
+
@binds = binds
|
|
26
|
+
@sql = sql
|
|
27
|
+
|
|
28
|
+
Result.new visit(object, :top)
|
|
29
|
+
rescue ::PgQuery::ParseError => e
|
|
30
|
+
new_error = ::PgQuery::ParseError.new(e.message, __FILE__, __LINE__, -1)
|
|
31
|
+
raise new_error, e.message, e.backtrace
|
|
32
|
+
rescue ::StandardError => e
|
|
33
|
+
raise e.class, e.message, e.backtrace if e.is_a?(Arel::SqlToArel::Error)
|
|
34
|
+
|
|
35
|
+
boom e.message, e.backtrace
|
|
25
36
|
end
|
|
26
37
|
|
|
27
38
|
private
|
|
@@ -72,7 +83,7 @@ module Arel
|
|
|
72
83
|
Arel::Nodes::NullIf.new(left, right)
|
|
73
84
|
|
|
74
85
|
when PgQuery::AEXPR_OF
|
|
75
|
-
|
|
86
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/34'
|
|
76
87
|
|
|
77
88
|
when PgQuery::AEXPR_IN
|
|
78
89
|
left = visit(lexpr)
|
|
@@ -91,7 +102,7 @@ module Arel
|
|
|
91
102
|
escape = nil
|
|
92
103
|
|
|
93
104
|
if right.is_a?(Array)
|
|
94
|
-
|
|
105
|
+
boom "Don't know how to handle length `#{right.length}`" if right.length != 2
|
|
95
106
|
|
|
96
107
|
right, escape = right
|
|
97
108
|
end
|
|
@@ -110,7 +121,7 @@ module Arel
|
|
|
110
121
|
escape = nil
|
|
111
122
|
|
|
112
123
|
if right.is_a?(Array)
|
|
113
|
-
|
|
124
|
+
boom "Don't know how to handle length `#{right.length}`" if right.length != 2
|
|
114
125
|
|
|
115
126
|
right, escape = right
|
|
116
127
|
end
|
|
@@ -129,7 +140,7 @@ module Arel
|
|
|
129
140
|
escape = nil
|
|
130
141
|
|
|
131
142
|
if right.is_a?(Array)
|
|
132
|
-
|
|
143
|
+
boom "Don't know how to handle length `#{right.length}`" if right.length != 2
|
|
133
144
|
|
|
134
145
|
right, escape = right
|
|
135
146
|
end
|
|
@@ -164,10 +175,10 @@ module Arel
|
|
|
164
175
|
Arel::Nodes::NotBetweenSymmetric.new left, Arel::Nodes::And.new(right)
|
|
165
176
|
|
|
166
177
|
when PgQuery::AEXPR_PAREN
|
|
167
|
-
|
|
178
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/35'
|
|
168
179
|
|
|
169
180
|
else
|
|
170
|
-
|
|
181
|
+
boom "Unknown Expr type `#{kind}`"
|
|
171
182
|
end
|
|
172
183
|
end
|
|
173
184
|
|
|
@@ -176,7 +187,7 @@ module Arel
|
|
|
176
187
|
end
|
|
177
188
|
|
|
178
189
|
def visit_A_Indirection(arg:, indirection:)
|
|
179
|
-
Arel::Nodes::Indirection.new(visit(arg
|
|
190
|
+
Arel::Nodes::Indirection.new(visit(arg), visit(indirection, :operator))
|
|
180
191
|
end
|
|
181
192
|
|
|
182
193
|
def visit_A_Star
|
|
@@ -184,7 +195,7 @@ module Arel
|
|
|
184
195
|
end
|
|
185
196
|
|
|
186
197
|
def visit_Alias(aliasname:)
|
|
187
|
-
aliasname
|
|
198
|
+
Arel.sql visit_String(nil, str: aliasname)
|
|
188
199
|
end
|
|
189
200
|
|
|
190
201
|
def visit_BitString(str:)
|
|
@@ -205,7 +216,7 @@ module Arel
|
|
|
205
216
|
Arel::Nodes::Not.new(args)
|
|
206
217
|
|
|
207
218
|
else
|
|
208
|
-
|
|
219
|
+
boom "? Boolop -> #{boolop}"
|
|
209
220
|
end
|
|
210
221
|
|
|
211
222
|
if context
|
|
@@ -238,7 +249,7 @@ module Arel
|
|
|
238
249
|
Arel::Nodes::NotEqual.new(arg, Arel::Nodes::Unknown.new)
|
|
239
250
|
|
|
240
251
|
else
|
|
241
|
-
|
|
252
|
+
boom '?'
|
|
242
253
|
end
|
|
243
254
|
end
|
|
244
255
|
|
|
@@ -269,8 +280,21 @@ module Arel
|
|
|
269
280
|
Arel::Nodes::Coalesce.new args
|
|
270
281
|
end
|
|
271
282
|
|
|
272
|
-
def visit_ColumnRef(
|
|
273
|
-
|
|
283
|
+
def visit_ColumnRef(fields:)
|
|
284
|
+
fields = fields.reverse
|
|
285
|
+
column = visit(fields[0], :operator)
|
|
286
|
+
table = visit(fields[1], :operator) if fields[1]
|
|
287
|
+
schema_name = visit(fields[2], :operator) if fields[2]
|
|
288
|
+
database = visit(fields[3], :operator) if fields[3]
|
|
289
|
+
|
|
290
|
+
table = Arel::Table.new(table) if table
|
|
291
|
+
attribute = Arel::Attribute.new(table, column)
|
|
292
|
+
attribute.schema_name = schema_name
|
|
293
|
+
attribute.database = database
|
|
294
|
+
|
|
295
|
+
return attribute if table
|
|
296
|
+
|
|
297
|
+
Arel::Nodes::UnqualifiedColumn.new Arel::Attribute.new(nil, column)
|
|
274
298
|
end
|
|
275
299
|
|
|
276
300
|
def visit_CommonTableExpr(ctename:, ctequery:)
|
|
@@ -283,6 +307,15 @@ module Arel
|
|
|
283
307
|
Arel::Nodes::CurrentOfExpression.new(cursor_name)
|
|
284
308
|
end
|
|
285
309
|
|
|
310
|
+
def visit_DefElem(defname:, arg:, defaction:)
|
|
311
|
+
case defname
|
|
312
|
+
when 'savepoint_name'
|
|
313
|
+
visit(arg)
|
|
314
|
+
else
|
|
315
|
+
boom "Unknown defname `#{defname}` with defaction `#{defaction}`"
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
286
319
|
def visit_DeleteStmt(
|
|
287
320
|
relation:,
|
|
288
321
|
using_clause: nil,
|
|
@@ -332,15 +365,9 @@ module Arel
|
|
|
332
365
|
when ['sum']
|
|
333
366
|
Arel::Nodes::Sum.new args
|
|
334
367
|
|
|
335
|
-
when ['rank']
|
|
336
|
-
Arel::Nodes::Rank.new args
|
|
337
|
-
|
|
338
368
|
when ['count']
|
|
339
369
|
Arel::Nodes::Count.new args
|
|
340
370
|
|
|
341
|
-
when ['generate_series']
|
|
342
|
-
Arel::Nodes::GenerateSeries.new args
|
|
343
|
-
|
|
344
371
|
when ['max']
|
|
345
372
|
Arel::Nodes::Max.new args
|
|
346
373
|
|
|
@@ -356,10 +383,55 @@ module Arel
|
|
|
356
383
|
when [PG_CATALOG, 'similar_escape']
|
|
357
384
|
args
|
|
358
385
|
|
|
359
|
-
|
|
360
|
-
|
|
386
|
+
when [PG_CATALOG, 'date_part']
|
|
387
|
+
field, expression = args
|
|
388
|
+
[Arel::Nodes::ExtractFrom.new(expression, field)]
|
|
389
|
+
|
|
390
|
+
when [PG_CATALOG, 'timezone']
|
|
391
|
+
timezone, expression = args
|
|
392
|
+
|
|
393
|
+
[Arel::Nodes::AtTimeZone.new(maybe_add_grouping(expression), timezone)]
|
|
394
|
+
|
|
395
|
+
# https://www.postgresql.org/docs/10/functions-string.html
|
|
396
|
+
when [PG_CATALOG, 'position']
|
|
397
|
+
string, substring = args
|
|
398
|
+
[Arel::Nodes::Position.new(substring, string)]
|
|
399
|
+
|
|
400
|
+
when [PG_CATALOG, 'overlay']
|
|
401
|
+
string, substring, start, length = args
|
|
402
|
+
[Arel::Nodes::Overlay.new(string, substring, start, length)]
|
|
403
|
+
|
|
404
|
+
when [PG_CATALOG, 'ltrim']
|
|
405
|
+
string, substring = args
|
|
406
|
+
[Arel::Nodes::Trim.new('leading', substring, string)]
|
|
407
|
+
|
|
408
|
+
when [PG_CATALOG, 'rtrim']
|
|
409
|
+
string, substring = args
|
|
410
|
+
[Arel::Nodes::Trim.new('trailing', substring, string)]
|
|
411
|
+
|
|
412
|
+
when [PG_CATALOG, 'btrim']
|
|
413
|
+
string, substring = args
|
|
414
|
+
[Arel::Nodes::Trim.new('both', substring, string)]
|
|
415
|
+
|
|
416
|
+
when [PG_CATALOG, 'substring']
|
|
417
|
+
string, pattern, escape = args
|
|
418
|
+
[Arel::Nodes::Substring.new(string, pattern, escape)]
|
|
419
|
+
|
|
420
|
+
when [PG_CATALOG, 'overlaps']
|
|
421
|
+
start1, end1, start2, end2 = args
|
|
422
|
+
[Arel::Nodes::Overlaps.new(start1, end1, start2, end2)]
|
|
361
423
|
|
|
362
|
-
|
|
424
|
+
else
|
|
425
|
+
case function_names.length
|
|
426
|
+
when 2
|
|
427
|
+
func = Arel::Nodes::NamedFunction.new(function_names.last, args)
|
|
428
|
+
func.schema_name = function_names.first
|
|
429
|
+
func
|
|
430
|
+
when 1
|
|
431
|
+
Arel::Nodes::NamedFunction.new(function_names.first, args)
|
|
432
|
+
else
|
|
433
|
+
boom "Don't know how to handle function names length `#{function_names.length}`"
|
|
434
|
+
end
|
|
363
435
|
end
|
|
364
436
|
|
|
365
437
|
func.distinct = (agg_distinct.nil? ? false : true) unless func.is_a?(::Array)
|
|
@@ -376,17 +448,16 @@ module Arel
|
|
|
376
448
|
end
|
|
377
449
|
|
|
378
450
|
def visit_InferClause(conname: nil, index_elems: nil)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
infer
|
|
451
|
+
left = Arel.sql(conname) if conname
|
|
452
|
+
right = visit(index_elems) if index_elems
|
|
453
|
+
Arel::Nodes::Infer.new left, right
|
|
383
454
|
end
|
|
384
455
|
|
|
385
456
|
def visit_IndexElem(name:, ordering:, nulls_ordering:)
|
|
386
|
-
|
|
387
|
-
|
|
457
|
+
boom "Unknown ordering `#{ordering}`" unless ordering.zero?
|
|
458
|
+
boom "Unknown nulls ordering `#{ordering}`" unless nulls_ordering.zero?
|
|
388
459
|
|
|
389
|
-
Arel.sql(name)
|
|
460
|
+
Arel.sql visit_String(str: name)
|
|
390
461
|
end
|
|
391
462
|
|
|
392
463
|
def visit_InsertStmt(
|
|
@@ -418,7 +489,7 @@ module Arel
|
|
|
418
489
|
end
|
|
419
490
|
|
|
420
491
|
insert_statement.returning = visit(returning_list, :select)
|
|
421
|
-
insert_statement.
|
|
492
|
+
insert_statement.conflict = visit(on_conflict_clause) if on_conflict_clause
|
|
422
493
|
insert_manager
|
|
423
494
|
end
|
|
424
495
|
|
|
@@ -426,6 +497,12 @@ module Arel
|
|
|
426
497
|
ival
|
|
427
498
|
end
|
|
428
499
|
|
|
500
|
+
def visit_IntoClause(rel:, on_commit:)
|
|
501
|
+
raise "Unknown on_commit `#{on_commit}`" unless on_commit.zero?
|
|
502
|
+
|
|
503
|
+
Arel::Nodes::Into.new(visit(rel))
|
|
504
|
+
end
|
|
505
|
+
|
|
429
506
|
def visit_JoinExpr(jointype:, is_natural: nil, larg:, rarg:, quals: nil)
|
|
430
507
|
join_class = case jointype
|
|
431
508
|
when 0
|
|
@@ -463,12 +540,12 @@ module Arel
|
|
|
463
540
|
1 => 'FOR KEY SHARE',
|
|
464
541
|
2 => 'FOR SHARE',
|
|
465
542
|
3 => 'FOR NO KEY UPDATE',
|
|
466
|
-
4 => 'FOR UPDATE'
|
|
543
|
+
4 => 'FOR UPDATE',
|
|
467
544
|
}.fetch(strength)
|
|
468
545
|
wait_policy_clause = {
|
|
469
546
|
0 => '',
|
|
470
547
|
1 => ' SKIP LOCKED',
|
|
471
|
-
2 => ' NOWAIT'
|
|
548
|
+
2 => ' NOWAIT',
|
|
472
549
|
}.fetch(wait_policy)
|
|
473
550
|
|
|
474
551
|
Arel::Nodes::Lock.new Arel.sql("#{strength_clause}#{wait_policy_clause}")
|
|
@@ -481,10 +558,17 @@ module Arel
|
|
|
481
558
|
when 1
|
|
482
559
|
Arel::Nodes::Least.new visit(args)
|
|
483
560
|
else
|
|
484
|
-
|
|
561
|
+
boom "Unknown Op -> #{op}"
|
|
485
562
|
end
|
|
486
563
|
end
|
|
487
564
|
|
|
565
|
+
def visit_NamedArgExpr(arg:, name:, argnumber:)
|
|
566
|
+
arg = visit(arg)
|
|
567
|
+
boom '' unless argnumber == -1
|
|
568
|
+
|
|
569
|
+
Arel::Nodes::NamedArgument.new(name, arg)
|
|
570
|
+
end
|
|
571
|
+
|
|
488
572
|
def visit_Null(**_)
|
|
489
573
|
Arel.sql 'NULL'
|
|
490
574
|
end
|
|
@@ -509,29 +593,36 @@ module Arel
|
|
|
509
593
|
conflict
|
|
510
594
|
end
|
|
511
595
|
|
|
512
|
-
def visit_ParamRef(
|
|
513
|
-
|
|
514
|
-
end
|
|
596
|
+
def visit_ParamRef(number: nil)
|
|
597
|
+
value = (binds[number - 1] unless binds.empty?)
|
|
515
598
|
|
|
516
|
-
|
|
517
|
-
|
|
599
|
+
Arel::Nodes::BindParam.new(value)
|
|
600
|
+
end
|
|
518
601
|
|
|
602
|
+
def visit_RangeFunction(
|
|
603
|
+
is_rowsfrom: nil,
|
|
604
|
+
functions:,
|
|
605
|
+
lateral: false,
|
|
606
|
+
ordinality: false,
|
|
607
|
+
aliaz: nil
|
|
608
|
+
)
|
|
519
609
|
functions = functions.map do |function_array|
|
|
520
610
|
function, empty_value = function_array
|
|
521
|
-
|
|
611
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/37' unless empty_value.nil?
|
|
522
612
|
|
|
523
613
|
visit(function)
|
|
524
614
|
end
|
|
525
615
|
|
|
526
|
-
node = Arel::Nodes::RangeFunction.new functions
|
|
616
|
+
node = Arel::Nodes::RangeFunction.new functions, is_rowsfrom: is_rowsfrom
|
|
527
617
|
node = lateral ? Arel::Nodes::Lateral.new(node) : node
|
|
528
|
-
ordinality ? Arel::Nodes::WithOrdinality.new(node) : node
|
|
618
|
+
node = ordinality ? Arel::Nodes::WithOrdinality.new(node) : node
|
|
619
|
+
aliaz.nil? ? node : Arel::Nodes::As.new(node, visit(aliaz))
|
|
529
620
|
end
|
|
530
621
|
|
|
531
622
|
def visit_RangeSubselect(aliaz:, subquery:, lateral: false)
|
|
532
623
|
aliaz = visit(aliaz)
|
|
533
624
|
subquery = visit(subquery)
|
|
534
|
-
node = Arel::Nodes::
|
|
625
|
+
node = Arel::Nodes::As.new(Arel::Nodes::Grouping.new(subquery), aliaz)
|
|
535
626
|
lateral ? Arel::Nodes::Lateral.new(node) : node
|
|
536
627
|
end
|
|
537
628
|
|
|
@@ -545,8 +636,8 @@ module Arel
|
|
|
545
636
|
)
|
|
546
637
|
end
|
|
547
638
|
|
|
548
|
-
def visit_RawStmt(context,
|
|
549
|
-
visit(stmt, context)
|
|
639
|
+
def visit_RawStmt(context, **args)
|
|
640
|
+
visit(args.fetch(:stmt), context)
|
|
550
641
|
end
|
|
551
642
|
|
|
552
643
|
def visit_ResTarget(context, val: nil, name: nil)
|
|
@@ -555,19 +646,21 @@ module Arel
|
|
|
555
646
|
val = visit(val)
|
|
556
647
|
|
|
557
648
|
if name
|
|
558
|
-
|
|
649
|
+
aliaz = visit_Alias(aliasname: name)
|
|
650
|
+
Arel::Nodes::As.new(val, aliaz)
|
|
559
651
|
else
|
|
560
652
|
val
|
|
561
653
|
end
|
|
562
654
|
when :insert
|
|
563
655
|
name
|
|
564
656
|
when :update
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
657
|
+
relation = nil
|
|
658
|
+
column = Arel::Attribute.new(relation, name)
|
|
659
|
+
value = visit(val)
|
|
660
|
+
|
|
661
|
+
Nodes::Assignment.new(Nodes::UnqualifiedColumn.new(column), value)
|
|
569
662
|
else
|
|
570
|
-
|
|
663
|
+
boom "Unknown context `#{context}`"
|
|
571
664
|
end
|
|
572
665
|
end
|
|
573
666
|
|
|
@@ -591,6 +684,7 @@ module Arel
|
|
|
591
684
|
op:,
|
|
592
685
|
window_clause: nil,
|
|
593
686
|
values_lists: nil,
|
|
687
|
+
into_clause: nil,
|
|
594
688
|
all: nil,
|
|
595
689
|
larg: nil,
|
|
596
690
|
rarg: nil
|
|
@@ -600,14 +694,32 @@ module Arel
|
|
|
600
694
|
select_statement = select_manager.ast
|
|
601
695
|
|
|
602
696
|
froms, join_sources = generate_sources(from_clause)
|
|
697
|
+
if froms
|
|
698
|
+
froms = froms.first if froms.length == 1
|
|
699
|
+
select_core.froms = froms
|
|
700
|
+
end
|
|
701
|
+
|
|
603
702
|
select_core.from = froms if froms
|
|
604
703
|
select_core.source.right = join_sources
|
|
605
704
|
|
|
606
705
|
select_core.projections = visit(target_list, :select) if target_list
|
|
607
|
-
|
|
706
|
+
|
|
707
|
+
if where_clause
|
|
708
|
+
where_clause = visit(where_clause)
|
|
709
|
+
where_clause = if where_clause.is_a?(Arel::Nodes::And)
|
|
710
|
+
where_clause
|
|
711
|
+
else
|
|
712
|
+
Arel::Nodes::And.new([where_clause])
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
select_core.wheres = [where_clause]
|
|
716
|
+
end
|
|
717
|
+
|
|
608
718
|
select_core.groups = visit(group_clause) if group_clause
|
|
609
719
|
select_core.havings = [visit(having_clause)] if having_clause
|
|
610
720
|
select_core.windows = visit(window_clause) if window_clause
|
|
721
|
+
select_core.into = visit(into_clause) if into_clause
|
|
722
|
+
select_core.top = ::Arel::Nodes::Top.new visit(limit_count) if limit_count
|
|
611
723
|
|
|
612
724
|
if distinct_clause == [nil]
|
|
613
725
|
select_core.set_quantifier = Arel::Nodes::Distinct.new
|
|
@@ -616,7 +728,7 @@ module Arel
|
|
|
616
728
|
elsif distinct_clause.nil?
|
|
617
729
|
select_core.set_quantifier = nil
|
|
618
730
|
else
|
|
619
|
-
|
|
731
|
+
boom "Unknown distinct clause `#{distinct_clause}`"
|
|
620
732
|
end
|
|
621
733
|
|
|
622
734
|
select_statement.limit = ::Arel::Nodes::Limit.new visit(limit_count) if limit_count
|
|
@@ -632,12 +744,14 @@ module Arel
|
|
|
632
744
|
value
|
|
633
745
|
when Integer
|
|
634
746
|
Arel.sql(value.to_s)
|
|
635
|
-
when Arel::Nodes::TypeCast
|
|
747
|
+
when Arel::Nodes::TypeCast, Arel::Nodes::UnqualifiedColumn
|
|
636
748
|
Arel.sql(value.to_sql)
|
|
637
749
|
when Arel::Nodes::BindParam
|
|
638
750
|
value
|
|
751
|
+
when Arel::Nodes::Quoted
|
|
752
|
+
value.value
|
|
639
753
|
else
|
|
640
|
-
|
|
754
|
+
boom "Unknown value `#{value}`"
|
|
641
755
|
end
|
|
642
756
|
end
|
|
643
757
|
end
|
|
@@ -667,7 +781,7 @@ module Arel
|
|
|
667
781
|
end
|
|
668
782
|
else
|
|
669
783
|
# https://www.postgresql.org/docs/10/queries-union.html
|
|
670
|
-
|
|
784
|
+
boom "Unknown combining queries op `#{op}`"
|
|
671
785
|
end
|
|
672
786
|
|
|
673
787
|
unless union.nil?
|
|
@@ -723,7 +837,7 @@ module Arel
|
|
|
723
837
|
when :operator
|
|
724
838
|
str
|
|
725
839
|
when :const
|
|
726
|
-
Arel.
|
|
840
|
+
Arel::Nodes.build_quoted str
|
|
727
841
|
else
|
|
728
842
|
"\"#{str}\""
|
|
729
843
|
end
|
|
@@ -735,7 +849,7 @@ module Arel
|
|
|
735
849
|
operator = if oper_name
|
|
736
850
|
operator = visit(oper_name, :operator)
|
|
737
851
|
if operator.length > 1
|
|
738
|
-
|
|
852
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/39'
|
|
739
853
|
end
|
|
740
854
|
|
|
741
855
|
operator.first
|
|
@@ -744,24 +858,51 @@ module Arel
|
|
|
744
858
|
generate_sublink(sub_link_type, subselect, testexpr, operator)
|
|
745
859
|
end
|
|
746
860
|
|
|
861
|
+
def visit_TransactionStmt(kind:, options: nil)
|
|
862
|
+
Arel::Nodes::Transaction.new(
|
|
863
|
+
kind,
|
|
864
|
+
(visit(options) if options),
|
|
865
|
+
)
|
|
866
|
+
end
|
|
867
|
+
|
|
747
868
|
def visit_TypeCast(arg:, type_name:)
|
|
748
869
|
arg = visit(arg)
|
|
749
870
|
type_name = visit(type_name)
|
|
750
871
|
|
|
751
|
-
Arel::Nodes::TypeCast.new(arg, type_name)
|
|
872
|
+
Arel::Nodes::TypeCast.new(maybe_add_grouping(arg), type_name)
|
|
752
873
|
end
|
|
753
874
|
|
|
754
|
-
def visit_TypeName(names:, typemod:)
|
|
875
|
+
def visit_TypeName(names:, typemod:, array_bounds: [])
|
|
876
|
+
array_bounds = visit(array_bounds)
|
|
877
|
+
|
|
755
878
|
names = names.map do |name|
|
|
756
879
|
visit(name, :operator)
|
|
757
880
|
end
|
|
758
881
|
|
|
759
882
|
names = names.reject { |name| name == PG_CATALOG }
|
|
760
883
|
|
|
761
|
-
|
|
762
|
-
|
|
884
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/40' if typemod != -1
|
|
885
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/41' if names.length > 1
|
|
886
|
+
if array_bounds != [] && array_bounds != [-1]
|
|
887
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/86'
|
|
888
|
+
end
|
|
763
889
|
|
|
764
|
-
names.first
|
|
890
|
+
type_name = names.first
|
|
891
|
+
type_name = case type_name
|
|
892
|
+
when 'int4'
|
|
893
|
+
'integer'
|
|
894
|
+
when 'float4'
|
|
895
|
+
'real'
|
|
896
|
+
when 'float8'
|
|
897
|
+
'double precision'
|
|
898
|
+
when 'timestamptz'
|
|
899
|
+
'timestamp with time zone'
|
|
900
|
+
else
|
|
901
|
+
type_name
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
type_name << '[]' if array_bounds == [-1]
|
|
905
|
+
type_name
|
|
765
906
|
end
|
|
766
907
|
|
|
767
908
|
def visit_UpdateStmt(
|
|
@@ -786,6 +927,19 @@ module Arel
|
|
|
786
927
|
update_manager
|
|
787
928
|
end
|
|
788
929
|
|
|
930
|
+
def visit_VariableSetStmt(kind:, name:, args: [], is_local: false)
|
|
931
|
+
Arel::Nodes::VariableSet.new(
|
|
932
|
+
kind,
|
|
933
|
+
visit(args),
|
|
934
|
+
name,
|
|
935
|
+
is_local,
|
|
936
|
+
)
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
def visit_VariableShowStmt(name:)
|
|
940
|
+
Arel::Nodes::VariableShow.new(name)
|
|
941
|
+
end
|
|
942
|
+
|
|
789
943
|
def visit_WindowDef(
|
|
790
944
|
partition_clause: [],
|
|
791
945
|
order_clause: [],
|
|
@@ -794,8 +948,11 @@ module Arel
|
|
|
794
948
|
start_offset: nil,
|
|
795
949
|
end_offset: nil
|
|
796
950
|
)
|
|
797
|
-
|
|
951
|
+
if name.present? && partition_clause.empty? && order_clause.empty?
|
|
952
|
+
return Arel::Nodes::SqlLiteral.new(name)
|
|
953
|
+
end
|
|
798
954
|
|
|
955
|
+
instance = name.nil? ? Arel::Nodes::Window.new : Arel::Nodes::NamedWindow.new(name)
|
|
799
956
|
instance.tap do |window|
|
|
800
957
|
window.orders = visit order_clause
|
|
801
958
|
window.partitions = visit partition_clause
|
|
@@ -819,13 +976,20 @@ module Arel
|
|
|
819
976
|
end
|
|
820
977
|
|
|
821
978
|
def generate_operator(left, right, operator)
|
|
979
|
+
left = maybe_add_grouping(left)
|
|
980
|
+
right = maybe_add_grouping(right)
|
|
981
|
+
|
|
822
982
|
case operator
|
|
823
983
|
|
|
824
984
|
# https://www.postgresql.org/docs/10/functions-math.html
|
|
825
985
|
when '+'
|
|
826
986
|
Arel::Nodes::Addition.new(left, right)
|
|
827
987
|
when '-'
|
|
828
|
-
|
|
988
|
+
if left.nil?
|
|
989
|
+
Arel::Nodes::UnaryOperation.new(:'-', right)
|
|
990
|
+
else
|
|
991
|
+
Arel::Nodes::Subtraction.new(left, right)
|
|
992
|
+
end
|
|
829
993
|
when '*'
|
|
830
994
|
Arel::Nodes::Multiplication.new(left, right)
|
|
831
995
|
when '/'
|
|
@@ -849,9 +1013,17 @@ module Arel
|
|
|
849
1013
|
when '|'
|
|
850
1014
|
Arel::Nodes::BitwiseOr.new(left, right)
|
|
851
1015
|
when '#'
|
|
852
|
-
|
|
1016
|
+
if left.nil?
|
|
1017
|
+
Arel::Nodes::UnaryOperation.new(:'#', right)
|
|
1018
|
+
else
|
|
1019
|
+
Arel::Nodes::BitwiseXor.new(left, right)
|
|
1020
|
+
end
|
|
853
1021
|
when '~'
|
|
854
|
-
|
|
1022
|
+
if left.nil?
|
|
1023
|
+
Arel::Nodes::BitwiseNot.new(right)
|
|
1024
|
+
else
|
|
1025
|
+
Arel::Nodes::Regexp.new(left, right, true)
|
|
1026
|
+
end
|
|
855
1027
|
when '<<'
|
|
856
1028
|
Arel::Nodes::BitwiseShiftLeft.new(left, right)
|
|
857
1029
|
when '>>'
|
|
@@ -881,11 +1053,59 @@ module Arel
|
|
|
881
1053
|
when '||'
|
|
882
1054
|
Arel::Nodes::Concat.new(left, right)
|
|
883
1055
|
|
|
1056
|
+
# https://www.postgresql.org/docs/9.3/functions-net.html
|
|
1057
|
+
when '<<='
|
|
1058
|
+
Arel::Nodes::ContainedWithinEquals.new(left, right)
|
|
1059
|
+
when '>>='
|
|
1060
|
+
Arel::Nodes::ContainsEquals.new(left, right)
|
|
1061
|
+
|
|
1062
|
+
# https://www.postgresql.org/docs/9.4/functions-json.html
|
|
1063
|
+
when '->'
|
|
1064
|
+
Arel::Nodes::JsonGetObject.new(left, right)
|
|
1065
|
+
when '->>'
|
|
1066
|
+
Arel::Nodes::JsonGetField.new(left, right)
|
|
1067
|
+
when '#>'
|
|
1068
|
+
Arel::Nodes::JsonPathGetObject.new(left, right)
|
|
1069
|
+
when '#>>'
|
|
1070
|
+
Arel::Nodes::JsonPathGetField.new(left, right)
|
|
1071
|
+
|
|
1072
|
+
# https://www.postgresql.org/docs/9.4/functions-json.html#FUNCTIONS-JSONB-OP-TABLE
|
|
1073
|
+
when '?'
|
|
1074
|
+
Arel::Nodes::JsonbKeyExists.new(left, right)
|
|
1075
|
+
when '?|'
|
|
1076
|
+
if left.nil?
|
|
1077
|
+
Arel::Nodes::UnaryOperation.new(:'?|', right)
|
|
1078
|
+
else
|
|
1079
|
+
Arel::Nodes::JsonbAnyKeyExists.new(left, right)
|
|
1080
|
+
end
|
|
1081
|
+
when '?&'
|
|
1082
|
+
Arel::Nodes::JsonbAllKeyExists.new(left, right)
|
|
1083
|
+
|
|
1084
|
+
# https://www.postgresql.org/docs/9.3/functions-matching.html#FUNCTIONS-POSIX-TABLE
|
|
1085
|
+
when '~*'
|
|
1086
|
+
Arel::Nodes::Regexp.new(left, right, false)
|
|
1087
|
+
when '!~'
|
|
1088
|
+
Arel::Nodes::NotRegexp.new(left, right, true)
|
|
1089
|
+
when '!~*'
|
|
1090
|
+
Arel::Nodes::NotRegexp.new(left, right, false)
|
|
1091
|
+
|
|
884
1092
|
else
|
|
885
|
-
|
|
1093
|
+
if left.nil?
|
|
1094
|
+
Arel::Nodes::UnaryOperation.new(operator, right)
|
|
1095
|
+
else
|
|
1096
|
+
Arel::Nodes::InfixOperation.new(operator, left, right)
|
|
1097
|
+
end
|
|
886
1098
|
end
|
|
887
1099
|
end
|
|
888
1100
|
|
|
1101
|
+
def visit_DeallocateStmt(name: nil)
|
|
1102
|
+
Arel::Nodes::Dealocate.new name
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
def visit_PrepareStmt(name:, argtypes: nil, query:)
|
|
1106
|
+
Arel::Nodes::Prepare.new name, argtypes && visit(argtypes), visit(query)
|
|
1107
|
+
end
|
|
1108
|
+
|
|
889
1109
|
def visit(attribute, context = nil)
|
|
890
1110
|
return attribute.map { |attr| visit(attr, context) } if attribute.is_a? Array
|
|
891
1111
|
|
|
@@ -972,27 +1192,55 @@ module Arel
|
|
|
972
1192
|
generate_operator(testexpr, Arel::Nodes::All.new(subselect), operator)
|
|
973
1193
|
|
|
974
1194
|
when PgQuery::SUBLINK_TYPE_ANY
|
|
975
|
-
|
|
1195
|
+
if operator.nil?
|
|
1196
|
+
Arel::Nodes::In.new(testexpr, subselect)
|
|
1197
|
+
else
|
|
1198
|
+
generate_operator(testexpr, Arel::Nodes::Any.new(subselect), operator)
|
|
1199
|
+
end
|
|
976
1200
|
|
|
977
1201
|
when PgQuery::SUBLINK_TYPE_ROWCOMPARE
|
|
978
|
-
|
|
1202
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/42'
|
|
979
1203
|
|
|
980
1204
|
when PgQuery::SUBLINK_TYPE_EXPR
|
|
981
1205
|
Arel::Nodes::Grouping.new(subselect)
|
|
982
1206
|
|
|
983
1207
|
when PgQuery::SUBLINK_TYPE_MULTIEXPR
|
|
984
|
-
|
|
1208
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/43'
|
|
985
1209
|
|
|
986
1210
|
when PgQuery::SUBLINK_TYPE_ARRAY
|
|
987
1211
|
Arel::Nodes::ArraySubselect.new(subselect)
|
|
988
1212
|
|
|
989
1213
|
when PgQuery::SUBLINK_TYPE_CTE
|
|
990
|
-
|
|
1214
|
+
boom 'https://github.com/mvgijssel/arel_toolkit/issues/44'
|
|
991
1215
|
|
|
992
1216
|
else
|
|
993
|
-
|
|
1217
|
+
boom "Unknown sublinktype: #{type}"
|
|
994
1218
|
end
|
|
995
1219
|
end
|
|
1220
|
+
|
|
1221
|
+
def maybe_add_grouping(node)
|
|
1222
|
+
case node
|
|
1223
|
+
when Arel::Nodes::Binary
|
|
1224
|
+
Arel::Nodes::Grouping.new(node)
|
|
1225
|
+
else
|
|
1226
|
+
node
|
|
1227
|
+
end
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
def boom(message, backtrace = nil)
|
|
1231
|
+
new_message = <<~STRING
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
SQL: #{sql}
|
|
1235
|
+
BINDS: #{binds}
|
|
1236
|
+
message: #{message}
|
|
1237
|
+
|
|
1238
|
+
STRING
|
|
1239
|
+
|
|
1240
|
+
raise(Arel::SqlToArel::Error, new_message, backtrace) if backtrace
|
|
1241
|
+
|
|
1242
|
+
raise Arel::SqlToArel::Error, new_message
|
|
1243
|
+
end
|
|
996
1244
|
end
|
|
997
1245
|
end
|
|
998
1246
|
end
|