terrazine 0.0.2 → 0.0.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/Gemfile +7 -0
- data/README.md +255 -67
- data/circle.yml +11 -0
- data/lib/terrazine.rb +11 -4
- data/lib/terrazine/builder.rb +40 -221
- data/lib/terrazine/builders/clauses.rb +145 -0
- data/lib/terrazine/builders/expressions.rb +72 -0
- data/lib/terrazine/builders/operators.rb +63 -0
- data/lib/terrazine/builders/params.rb +18 -0
- data/lib/terrazine/builders/predicates.rb +160 -0
- data/lib/terrazine/config.rb +3 -1
- data/lib/terrazine/constructor.rb +28 -74
- data/lib/version.rb +1 -1
- data/spec/constructor_spec.rb +102 -12
- data/terrazine.gemspec +1 -1
- metadata +9 -3
data/lib/terrazine/builder.rb
CHANGED
@@ -1,251 +1,70 @@
|
|
1
|
+
require_relative 'builders/operators'
|
2
|
+
require_relative 'builders/predicates'
|
3
|
+
require_relative 'builders/expressions'
|
4
|
+
require_relative 'builders/clauses'
|
5
|
+
require_relative 'builders/params'
|
6
|
+
|
1
7
|
module Terrazine
|
2
|
-
#
|
8
|
+
# builds structures in to sql string
|
9
|
+
# TODO: SPLIT!!! But how-_-
|
10
|
+
# Operators(sql_functions), Predicates, Clauses(select, from...), Expressions(columns, tables), Params...
|
11
|
+
# they are mixed... everything can contain everything and they must communicate with each other.
|
12
|
+
# And how it can be splitted?
|
3
13
|
class Builder
|
4
|
-
|
14
|
+
# https://6ftdan.com/allyourdev/2015/05/02/private-module-methods-in-ruby/
|
15
|
+
# TODO: all methods private except get_sql, get_partial_sql ?
|
5
16
|
|
6
|
-
def initialize
|
7
|
-
@constructor = constructor
|
17
|
+
def initialize
|
8
18
|
@params = []
|
9
19
|
end
|
10
20
|
|
11
|
-
#
|
21
|
+
# get complete sql structure for constructor.
|
22
|
+
def get_sql(structure, options)
|
23
|
+
# get_partial_sql structure, key: 'sql'
|
24
|
+
wrap_result send("build_#{options[:key] || 'sql'}", structure)
|
25
|
+
end
|
26
|
+
|
27
|
+
# def get_partial_sql(structure, options)
|
28
|
+
# wrap_result send("build_#{options[:key]}", structure)
|
29
|
+
# end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# TODO: update, delete, insert, group.....
|
12
34
|
def build_sql(structure)
|
13
35
|
structure = structure.is_a?(Constructor) ? structure.structure : structure
|
14
36
|
sql = ''
|
15
|
-
|
16
|
-
|
17
|
-
[:union, :select, :insert, :update, :delete, :set, :from, :join, :where,
|
18
|
-
:group, :order, :limit, :offset].each do |i|
|
37
|
+
[:with, :union, :select, :insert, :update, :delete, :set, :from,
|
38
|
+
:join, :where, :returning, :group, :order, :limit, :offset].each do |i|
|
19
39
|
next unless structure[i]
|
20
|
-
sql += send("build_#{i}"
|
40
|
+
sql += send("build_#{i}", structure[i], structure)
|
21
41
|
end
|
22
42
|
sql
|
23
43
|
end
|
24
44
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
res
|
31
|
-
end
|
32
|
-
|
33
|
-
def build_with(structure)
|
34
|
-
if structure.second.is_a? Hash
|
35
|
-
"#{structure.first} AS (#{build_sql(structure.last)})"
|
45
|
+
def method_missing(name, *args)
|
46
|
+
/(?<type>[^_]+)_(?<action>\w+)/ =~ name
|
47
|
+
# arguments = [action]
|
48
|
+
if type && respond_to?("#{type}_missing", true)
|
49
|
+
send "#{type}_missing", action, *args
|
36
50
|
else
|
37
|
-
|
51
|
+
super
|
38
52
|
end
|
39
53
|
end
|
40
54
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# sql += build_select(structure[:select], structure[:distinct]) if structure[:select]
|
44
|
-
# [:from, :join, :where, :order, :limit, :offset].each do |i|
|
45
|
-
# sql += send("build_#{i}", structure[i]) if structure[i]
|
46
|
-
# end
|
55
|
+
# TODO
|
56
|
+
# def construct_as(field, name)
|
47
57
|
# end
|
48
58
|
|
49
|
-
def build_union(structure)
|
50
|
-
structure.map { |i| build_sql(i) }.join ' UNION '
|
51
|
-
end
|
52
|
-
|
53
|
-
def build_distinct_select(distinct)
|
54
|
-
case distinct
|
55
|
-
when Array
|
56
|
-
"DISTINCT ON(#{build_columns fields}) "
|
57
|
-
when true
|
58
|
-
'DISTINCT '
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def build_select(structure, distinct = nil)
|
63
|
-
# puts "build_select, structure #{structure}"
|
64
|
-
"SELECT #{build_distinct_select distinct}#{build_columns structure} "
|
65
|
-
end
|
66
|
-
|
67
|
-
def build_tables(structure)
|
68
|
-
case structure
|
69
|
-
when Array
|
70
|
-
if check_alias(structure.first) # VALUES function or ...?
|
71
|
-
build_function(structure)
|
72
|
-
# if it's a array with strings/values
|
73
|
-
elsif structure.select { |i| i.is_a? Array }.empty? # array of table_name and alias
|
74
|
-
structure.join ' '
|
75
|
-
else # array of tables/values
|
76
|
-
structure.map { |i| i.is_a?(Array) ? build_tables(i) : i }.join(', ')
|
77
|
-
end
|
78
|
-
when String, Symbol
|
79
|
-
structure
|
80
|
-
else
|
81
|
-
raise "Undefined structure for FROM - #{structure}"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def build_from(structure)
|
86
|
-
"FROM #{build_tables(structure)} "
|
87
|
-
end
|
88
|
-
|
89
|
-
def conditions_constructor(structure, joiner = :and, level = nil)
|
90
|
-
case structure
|
91
|
-
when Array
|
92
|
-
key = structure.first
|
93
|
-
# AND, OR support
|
94
|
-
if key.is_a? Symbol
|
95
|
-
res = structure.drop(1).map { |i| conditions_constructor(i) }.join " #{key} ".upcase
|
96
|
-
level ? res : "(#{res})"
|
97
|
-
# Sub Queries support - ['rgl IN ?', {...}]
|
98
|
-
elsif key =~ /\?/
|
99
|
-
if [Hash, Constructor].include?(structure.second.class)
|
100
|
-
key.sub(/\?/, "(#{build_sql(structure.second)})")
|
101
|
-
else
|
102
|
-
key.sub(/\?/, build_param(structure.second))
|
103
|
-
end
|
104
|
-
else
|
105
|
-
res = structure.map { |i| conditions_constructor(i) }.join " #{joiner} ".upcase
|
106
|
-
level ? res : "(#{res})"
|
107
|
-
end
|
108
|
-
when String
|
109
|
-
structure
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
# TODO? conditions like [:eq :name :Aeonax]
|
114
|
-
def build_conditions(structure)
|
115
|
-
conditions_constructor(structure, :and, true) + ' '
|
116
|
-
end
|
117
|
-
|
118
|
-
# TODO: -_-
|
119
|
-
def build_join(structure)
|
120
|
-
if structure.is_a? Array
|
121
|
-
# TODO: hash is sux here -_- !!!!!!
|
122
|
-
if structure.second.is_a? Hash
|
123
|
-
name = build_tables structure.first # (name.is_a?(Array) ? name.join(' ') : name)
|
124
|
-
v = structure.second
|
125
|
-
"#{v[:option].to_s.upcase + ' ' if v[:option]}JOIN #{name} ON #{build_conditions v[:on]}"
|
126
|
-
else
|
127
|
-
structure.map { |i| build_join(i) }.join
|
128
|
-
end
|
129
|
-
else
|
130
|
-
structure =~ /join/i ? structure : "JOIN #{structure} "
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def build_where(structure)
|
135
|
-
"WHERE #{build_conditions(structure)} "
|
136
|
-
end
|
137
|
-
|
138
|
-
# TODO!
|
139
|
-
def build_order(structure)
|
140
|
-
"ORDER BY #{structure} "
|
141
|
-
end
|
142
|
-
|
143
|
-
def build_limit(limit)
|
144
|
-
"LIMIT #{limit || 8} "
|
145
|
-
end
|
146
|
-
|
147
|
-
def build_offset(offset)
|
148
|
-
"OFFSET #{offset || 0} "
|
149
|
-
end
|
150
|
-
|
151
|
-
private
|
152
|
-
|
153
|
-
def build_param(value)
|
154
|
-
# no need for injections check - pg gem will check it
|
155
|
-
@params << value
|
156
|
-
"$#{@params.count}"
|
157
|
-
end
|
158
|
-
|
159
59
|
# all functions and column aliases begins from _
|
160
60
|
def check_alias(val)
|
161
61
|
val.to_s =~ /^_/
|
162
62
|
end
|
163
63
|
|
164
|
-
def iterate_hash(data)
|
64
|
+
def iterate_hash(data, join = true)
|
165
65
|
iterations = []
|
166
66
|
data.each { |k, v| iterations << yield(k, v) }
|
167
|
-
iterations.join
|
168
|
-
end
|
169
|
-
|
170
|
-
def build_as(field, name)
|
171
|
-
"#{field} AS #{name.to_s.sub(/^_/, '')}" # update ruby for delete_prefix? =)
|
172
|
-
end
|
173
|
-
|
174
|
-
def build_columns(structure, prefix = nil)
|
175
|
-
case structure
|
176
|
-
when Array
|
177
|
-
# SQL function - in format: "_#{fn}"
|
178
|
-
if check_alias(structure.first)
|
179
|
-
build_function structure, prefix
|
180
|
-
else
|
181
|
-
structure.map { |i| build_columns i, prefix }.join ', '
|
182
|
-
end
|
183
|
-
when Hash
|
184
|
-
# sub_query
|
185
|
-
if structure[:select]
|
186
|
-
"(#{build_sql(structure)})"
|
187
|
-
# colum OR table alias
|
188
|
-
else
|
189
|
-
iterate_hash(structure) do |k, v|
|
190
|
-
if check_alias(k)
|
191
|
-
build_as(build_columns(v, prefix), k)
|
192
|
-
else
|
193
|
-
build_columns(v, k.to_s)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
when Symbol, String
|
198
|
-
structure = structure.to_s
|
199
|
-
if prefix && structure !~ /, |\./
|
200
|
-
"#{prefix}.#{structure}"
|
201
|
-
else
|
202
|
-
structure
|
203
|
-
end
|
204
|
-
when Constructor
|
205
|
-
"(#{build_sql structure.structure})"
|
206
|
-
when true # choose everything -_-
|
207
|
-
build_columns('*', prefix)
|
208
|
-
else # TODO: values from value passing here... -_-
|
209
|
-
structure
|
210
|
-
# raise "Undefined class: #{structure.class} of #{structure}" # TODO: ERRORS class
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
# TODO!!!!!!! Relocate in class FunctionsBuilder? and send function name in it.
|
215
|
-
def build_function(structure, prefix = nil)
|
216
|
-
function = structure.first.to_s.sub(/^_/, '')
|
217
|
-
arguments = structure.drop(1)
|
218
|
-
case function.to_sym
|
219
|
-
when :param
|
220
|
-
build_param arguments.first
|
221
|
-
when :count # TODO? alias support on this lvl
|
222
|
-
if arguments.count > 1
|
223
|
-
arguments.map { |i| "COUNT(#{build_columns(i, prefix)})" }.join ','
|
224
|
-
else
|
225
|
-
"COUNT(#{build_columns(arguments.first, prefix)})"
|
226
|
-
end
|
227
|
-
when :nullif
|
228
|
-
# TODO? querry for value
|
229
|
-
"NULLIF(#{build_columns(arguments.first, prefix)}, #{arguments[1]})"
|
230
|
-
when :array # TODO? build_columns support
|
231
|
-
if [Hash, Constructor].include?(arguments.first.class)
|
232
|
-
"ARRAY(#{build_sql arguments.first})"
|
233
|
-
else # TODO? condition and error case
|
234
|
-
"ARRAY[#{arguments.join ', '}]"
|
235
|
-
end
|
236
|
-
when :avg
|
237
|
-
"AVG(#{build_columns(arguments.first, prefix)})"
|
238
|
-
when :values
|
239
|
-
"(VALUES(#{build_columns arguments.first, prefix})) AS #{structure[2]} (#{build_columns arguments.last})"
|
240
|
-
when :case
|
241
|
-
else_val = "ELSE #{arguments.pop} " unless arguments.last.is_a? Array
|
242
|
-
conditions = arguments.map { |i| "WHEN #{i.first} THEN #{i.last}" }.join ' '
|
243
|
-
"CASE #{conditions} #{else_val}END"
|
244
|
-
when :coalesce
|
245
|
-
"COALESCE(#{build_columns(arguments, prefix)})"
|
246
|
-
else
|
247
|
-
raise "Unknown function #{function}" # TODO: errors-_-
|
248
|
-
end
|
67
|
+
join ? iterations.join(', ') : iterations
|
249
68
|
end
|
250
69
|
end
|
251
70
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Terrazine
|
2
|
+
class Builder
|
3
|
+
# it use Predicate, expressions
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
# TODO: :with_recursive
|
8
|
+
def build_with(structure, _)
|
9
|
+
"WITH #{construct_with(structure)} "
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_union(structure, _)
|
13
|
+
structure.map { |i| build_sql(i) }.join ' UNION '
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_select(structure, common_structure)
|
17
|
+
distinct = construct_distinct common_structure[:distinct]
|
18
|
+
"SELECT #{distinct}#{build_columns structure} "
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_from(structure, _)
|
22
|
+
"FROM #{build_tables(structure)} "
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: -_-
|
26
|
+
def build_join(structure, _)
|
27
|
+
if structure.is_a? Array
|
28
|
+
# TODO: hash is sux here -_- !!!!!!
|
29
|
+
if structure.second.is_a? Hash
|
30
|
+
name = build_tables structure.first # (name.is_a?(Array) ? name.join(' ') : name)
|
31
|
+
v = structure.second
|
32
|
+
"#{v[:option].to_s.upcase + ' ' if v[:option]}JOIN #{name} ON #{build_predicates v[:on]} "
|
33
|
+
else
|
34
|
+
structure.map { |i| build_join(i, nil) }.join
|
35
|
+
end
|
36
|
+
else
|
37
|
+
structure =~ /join/i ? structure : "JOIN #{structure} "
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO!
|
42
|
+
def build_update(structure, _)
|
43
|
+
"UPDATE #{construct_update structure} "
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_returning(structure, _)
|
47
|
+
"RETURNING #{build_columns structure}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_where(structure, _)
|
51
|
+
"WHERE #{build_predicates(structure)} "
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO!
|
55
|
+
def build_order(structure, _)
|
56
|
+
"ORDER BY #{construct_order structure} "
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_limit(limit, _)
|
60
|
+
"LIMIT #{limit || 8} "
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_offset(offset, _)
|
64
|
+
"OFFSET #{offset || 0} "
|
65
|
+
end
|
66
|
+
|
67
|
+
def construct_with(structure)
|
68
|
+
case structure
|
69
|
+
when Array
|
70
|
+
if structure.second.is_a? Hash
|
71
|
+
"#{structure.first} AS (#{build_sql(structure.last)})"
|
72
|
+
else
|
73
|
+
structure.map { |v| construct_with(v) }.join ', '
|
74
|
+
end
|
75
|
+
when Hash
|
76
|
+
iterate_hash(structure) { |k, v| "#{k} AS (#{build_sql v})" }
|
77
|
+
else
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def construct_distinct(structure)
|
83
|
+
return unless structure
|
84
|
+
if structure == true
|
85
|
+
'DISTINCT '
|
86
|
+
else
|
87
|
+
"DISTINCT ON(#{build_columns structure}) "
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def construct_update(structure)
|
92
|
+
case structure
|
93
|
+
when Array
|
94
|
+
table = build_tables structure.first
|
95
|
+
"#{table} SET #{construct_set structure.last}"
|
96
|
+
when String
|
97
|
+
structure
|
98
|
+
else
|
99
|
+
raise "Undefined structure for `UPDATE`: #{structure}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# TODO: (..., ...) = (..., ...)
|
104
|
+
def construct_set(structure)
|
105
|
+
case structure
|
106
|
+
when Hash
|
107
|
+
iterate_hash(structure) { |k, v| "#{build_columns k} = #{build_columns v}" }
|
108
|
+
when String
|
109
|
+
structure
|
110
|
+
else
|
111
|
+
raise "Undefined structure for `UPDATE`: #{structure}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# { name: :asc, email: [:desc, :last] }
|
116
|
+
# [:name, :email, { phone: :last }]
|
117
|
+
def construct_order(structure)
|
118
|
+
case structure
|
119
|
+
when Array # function or values for order
|
120
|
+
if check_alias structure.first
|
121
|
+
build_operator structure
|
122
|
+
else
|
123
|
+
structure.map { |i| construct_order i }.join ', '
|
124
|
+
end
|
125
|
+
when Hash
|
126
|
+
iterate_hash(structure) { |k, v| "#{construct_order k} #{construct_order_options v}" }
|
127
|
+
else
|
128
|
+
structure
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def construct_order_options(option)
|
133
|
+
case option
|
134
|
+
when Array
|
135
|
+
option.sort.map { |i| construct_order_options i }.join ' '
|
136
|
+
when :last, :first
|
137
|
+
"nulls #{option}".upcase
|
138
|
+
when :asc, :desc
|
139
|
+
option.to_s.upcase
|
140
|
+
else
|
141
|
+
"USING#{option}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Terrazine
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
# doesn't use Predicates
|
5
|
+
# use Operators, Expressions
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# TODO: split
|
10
|
+
def build_tables(structure)
|
11
|
+
case structure
|
12
|
+
when Array
|
13
|
+
if check_alias(structure.first) # VALUES function or ...?
|
14
|
+
build_operator(structure)
|
15
|
+
# if it's a array with strings/values || array of tables/values
|
16
|
+
else
|
17
|
+
joiner = structure.select { |i| i.is_a? Array }.empty? ? ' ' : ', '
|
18
|
+
structure.map { |i| build_tables i }.join joiner
|
19
|
+
end
|
20
|
+
when Hash
|
21
|
+
"(#{build_sql structure})"
|
22
|
+
when String, Symbol
|
23
|
+
structure
|
24
|
+
else
|
25
|
+
raise "Undefined structure for FROM - #{structure}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO: split
|
30
|
+
def build_columns(structure, prefix = nil)
|
31
|
+
case structure
|
32
|
+
when Array
|
33
|
+
# SQL function - in format: "_#{fn}"
|
34
|
+
if check_alias(structure.first)
|
35
|
+
build_operator structure, prefix
|
36
|
+
else
|
37
|
+
structure.map { |i| build_columns i, prefix }.join ', '
|
38
|
+
end
|
39
|
+
when Hash
|
40
|
+
# sub_query
|
41
|
+
if structure[:select]
|
42
|
+
"(#{build_sql(structure)})"
|
43
|
+
# colum OR table alias
|
44
|
+
else
|
45
|
+
iterate_hash(structure) do |k, v|
|
46
|
+
if check_alias(k)
|
47
|
+
# update ruby for delete_prefix? =)
|
48
|
+
"#{build_columns(v, prefix)} AS #{k.to_s.sub(/^_/, '')}"
|
49
|
+
# construct_as(build_columns(v, prefix), k)
|
50
|
+
else
|
51
|
+
build_columns(v, k.to_s)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
when Symbol, String, Integer
|
56
|
+
structure = structure.to_s
|
57
|
+
if prefix && structure !~ /, |\.|\(/
|
58
|
+
"#{prefix}.#{structure}"
|
59
|
+
else
|
60
|
+
structure
|
61
|
+
end
|
62
|
+
when Constructor
|
63
|
+
"(#{build_sql structure.structure})"
|
64
|
+
when true # choose everything -_-
|
65
|
+
build_columns('*', prefix)
|
66
|
+
else # TODO: values from value passing here... -_-
|
67
|
+
structure
|
68
|
+
# raise "Undefined class: #{structure.class} of #{structure}" # TODO: ERRORS class
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|