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
@@ -0,0 +1,63 @@
|
|
1
|
+
module Terrazine
|
2
|
+
class Builder
|
3
|
+
private
|
4
|
+
|
5
|
+
# now it doesnt use Predicates
|
6
|
+
|
7
|
+
def build_operator(structure, prefix = nil)
|
8
|
+
operator = structure.first.to_s.sub(/^_/, '')
|
9
|
+
arguments = structure.drop(1)
|
10
|
+
# puts operator
|
11
|
+
send("operator_#{operator}", arguments, prefix)
|
12
|
+
end
|
13
|
+
|
14
|
+
def operator_missing(name, arguments, prefix)
|
15
|
+
"#{name}(#{build_columns arguments, prefix})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def operator_params(arguments, _)
|
19
|
+
if arguments.count > 1
|
20
|
+
arguments.map { |i| build_param i }
|
21
|
+
else
|
22
|
+
build_param arguments.first
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# without arguments smthng like this - "COUNT(#{prefix + '.'}*)"
|
27
|
+
def operator_count(arguments, prefix)
|
28
|
+
if arguments.count > 1
|
29
|
+
arguments.map { |i| "COUNT(#{build_columns(i, prefix)})" }.join ', '
|
30
|
+
else
|
31
|
+
"COUNT(#{build_columns(arguments.first, prefix)})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def operator_nullif(arguments, prefix)
|
36
|
+
"NULLIF(#{build_columns(arguments.first, prefix)}, #{arguments[1]})"
|
37
|
+
end
|
38
|
+
|
39
|
+
def operator_array(arguments, prefix)
|
40
|
+
if [Hash, Constructor].include?(arguments.first.class)
|
41
|
+
"ARRAY(#{build_sql arguments.first})"
|
42
|
+
else # TODO? condition and error case
|
43
|
+
"ARRAY[#{build_columns arguments, prefix}]"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def operator_avg(arguments, prefix)
|
48
|
+
"AVG(#{build_columns(arguments.first, prefix)})"
|
49
|
+
end
|
50
|
+
|
51
|
+
def operator_values(arguments, _)
|
52
|
+
values = arguments.first.first.is_a?(Array) ? arguments.first : [arguments.first]
|
53
|
+
values.map! { |i| "(#{build_columns i})" }
|
54
|
+
"(VALUES#{values.join ', '}) AS #{arguments[1]} (#{build_columns arguments.last})"
|
55
|
+
end
|
56
|
+
|
57
|
+
def operator_case(arguments, _)
|
58
|
+
else_val = "ELSE #{arguments.pop} " unless arguments.last.is_a? Array
|
59
|
+
conditions = arguments.map { |i| "WHEN #{i.first} THEN #{i.last}" }.join ' '
|
60
|
+
"CASE #{conditions} #{else_val}END"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Terrazine
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def build_param(value)
|
7
|
+
# no need for injections check - pg gem will check it
|
8
|
+
@params << value
|
9
|
+
"$#{@params.count}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def wrap_result(sql)
|
13
|
+
res = @params.count.positive? ? [sql, @params] : sql
|
14
|
+
@params = []
|
15
|
+
res
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
module Terrazine
|
2
|
+
class Builder
|
3
|
+
# TODO: :between, :!=, :>, :<, :>=, :<=
|
4
|
+
# Done: :eq, :not, :or, :and, :like, :ilike, :reg_#{type}, :in
|
5
|
+
|
6
|
+
# now it doesn't use Operators
|
7
|
+
# uses Expressions
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# TODO? conditions like [:eq :name :Aeonax]
|
12
|
+
def build_predicates(structure)
|
13
|
+
construct_condition(structure, true)
|
14
|
+
end
|
15
|
+
|
16
|
+
# [:or, { u__name: 'Aeonax', u__role: 'liar'}, # same as [[:eq, :u__name, 'Aeonax'], ...]
|
17
|
+
# [:not, [:in, :id, [1, 2, 531]]]]
|
18
|
+
def construct_condition(structure, first_level = nil)
|
19
|
+
case structure
|
20
|
+
when Array
|
21
|
+
key = structure.first
|
22
|
+
return construct_condition(key) if structure.size < 2
|
23
|
+
if key.is_a? Symbol
|
24
|
+
parentizer send("condition_#{key}", structure.drop(1)), first_level, key
|
25
|
+
elsif key.is_a?(String) && key =~ /\?/
|
26
|
+
if [Hash, Constructor].include?(structure.second.class)
|
27
|
+
key.sub(/\?/, "(#{build_sql(structure.second)})")
|
28
|
+
else
|
29
|
+
key.sub(/\?/, build_param(structure.second))
|
30
|
+
end
|
31
|
+
else
|
32
|
+
parentizer condition_and(structure), first_level, :and
|
33
|
+
end
|
34
|
+
when Hash
|
35
|
+
res = condition_eq structure
|
36
|
+
first_level ? condition_and(res) : res
|
37
|
+
when Symbol
|
38
|
+
condition_column(structure)
|
39
|
+
when String
|
40
|
+
structure
|
41
|
+
else
|
42
|
+
raise "Unknow structure #{structure} class #{structure.class} for condition"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# common
|
47
|
+
|
48
|
+
def condition_column(structure)
|
49
|
+
structure.to_s.sub(/__/, '.')
|
50
|
+
end
|
51
|
+
|
52
|
+
def construct_condition_value(structure)
|
53
|
+
case structure
|
54
|
+
when Symbol
|
55
|
+
condition_column(structure)
|
56
|
+
when TrueClass, FalseClass
|
57
|
+
structure.to_s.upcase
|
58
|
+
when nil
|
59
|
+
'NULL'
|
60
|
+
else
|
61
|
+
build_param structure
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parentizer(sql, first_level, key)
|
66
|
+
if first_level || ![:or, :and].include?(key)
|
67
|
+
sql
|
68
|
+
else
|
69
|
+
"(#{sql})"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#### Condition builders
|
74
|
+
|
75
|
+
# supporting eq if there is no array inside
|
76
|
+
def condition_not(structure)
|
77
|
+
value = if structure.count == 1
|
78
|
+
construct_condition structure.flatten(1)
|
79
|
+
else
|
80
|
+
condition_eq structure
|
81
|
+
end
|
82
|
+
"NOT #{value}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def conditions_joiner(structure, joiner)
|
86
|
+
structure.map { |i| construct_condition i }.flatten.join(" #{joiner} ".upcase)
|
87
|
+
end
|
88
|
+
|
89
|
+
def condition_and(structure)
|
90
|
+
conditions_joiner structure, 'and'
|
91
|
+
end
|
92
|
+
|
93
|
+
def condition_or(structure)
|
94
|
+
conditions_joiner structure, 'or'
|
95
|
+
end
|
96
|
+
|
97
|
+
def condition_in(structure)
|
98
|
+
values = case structure.second
|
99
|
+
when Hash, Constructor
|
100
|
+
build_sql structure.second
|
101
|
+
else
|
102
|
+
build_param structure.second
|
103
|
+
end
|
104
|
+
"#{construct_condition_value structure.first} IN (#{values})"
|
105
|
+
end
|
106
|
+
|
107
|
+
def conditions_construct_eq(column, value)
|
108
|
+
return condition_in([column, value]) if value.is_a? Array
|
109
|
+
"#{construct_condition_value column} = #{construct_condition_value value}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def condition_is(structure)
|
113
|
+
"#{construct_condition_value structure.first} IS #{construct_condition_value structure.second}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def condition_eq(structure)
|
117
|
+
case structure
|
118
|
+
when Array
|
119
|
+
conditions_construct_eq structure.first, structure.second
|
120
|
+
when Hash
|
121
|
+
iterate_hash(structure, false) { |k, v| conditions_construct_eq k, v }
|
122
|
+
else
|
123
|
+
raise "Undefinded structure: #{structure} for equality condition builder"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def condition_between(structure)
|
128
|
+
"BETWEEN #{construct_condition structure}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def condition_pattern(structure, pattern)
|
132
|
+
"#{construct_condition_value structure.first} #{pattern.upcase} " \
|
133
|
+
"#{construct_condition_value structure.second}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def condition_like(structure)
|
137
|
+
condition_pattern structure, :like
|
138
|
+
end
|
139
|
+
|
140
|
+
def condition_ilike(structure)
|
141
|
+
condition_pattern structure, :ilike
|
142
|
+
end
|
143
|
+
|
144
|
+
def condition_reg(structure)
|
145
|
+
condition_pattern structure, '~'
|
146
|
+
end
|
147
|
+
|
148
|
+
def condition_reg_i(structure)
|
149
|
+
condition_pattern structure, '~*'
|
150
|
+
end
|
151
|
+
|
152
|
+
def condition_reg_f(structure)
|
153
|
+
condition_pattern structure, '!~'
|
154
|
+
end
|
155
|
+
|
156
|
+
def condition_reg_fi(structure)
|
157
|
+
condition_pattern structure, '!~*'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/terrazine/config.rb
CHANGED
@@ -8,7 +8,9 @@ module Terrazine
|
|
8
8
|
|
9
9
|
def connection(conn = nil)
|
10
10
|
@@connection ||= conn
|
11
|
-
conn || @@connection
|
11
|
+
c = conn || @@connection
|
12
|
+
# Proc because of closing PG::Connection by rails on production -_-
|
13
|
+
c.is_a?(Proc) ? c.call : c
|
12
14
|
end
|
13
15
|
|
14
16
|
def connection!(conn = nil)
|
@@ -3,12 +3,10 @@ module Terrazine
|
|
3
3
|
attr_reader :structure, :params
|
4
4
|
def initialize(structure = {})
|
5
5
|
@structure = structure
|
6
|
-
# @params = []
|
7
|
-
@builder = Builder.new(self)
|
8
6
|
end
|
9
7
|
|
10
8
|
# TODO? join hash inside array?
|
11
|
-
# TODO!! join values of existing keys
|
9
|
+
# TODO!! join values of existing keys on hashes merge
|
12
10
|
def structure_constructor(structure, modifier)
|
13
11
|
return modifier unless structure
|
14
12
|
|
@@ -28,51 +26,18 @@ module Terrazine
|
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
|
-
# just string
|
32
|
-
### select "name, email"
|
33
|
-
|
34
|
-
# array of strings or symbols
|
35
|
-
### select [*selectable_fields]
|
36
|
-
|
37
|
-
# hash with column aliases
|
38
|
-
### select _field_alias: :field
|
39
|
-
### => 'SELECT field AS field_alias '
|
40
|
-
|
41
|
-
# array of fields and aliases - order doesnt matter
|
42
|
-
### select [{ _user_id: :id, _user_name: :name }, :password]
|
43
|
-
### => 'SELECT id AS user_id, name AS user_name, password '
|
44
|
-
|
45
|
-
# functions - array with first value - function name with underscore as symbol
|
46
|
-
### select [:_nullif, :row, :value]
|
47
|
-
|
48
|
-
# table alias/name
|
49
|
-
### select t_a: [{ _user_id: :id }, :field_2, [:_nullif, :row, :value]]
|
50
|
-
### => 'SELECT t_a.id AS user_id, t_a.password, NULLIF(t_a.row, value) '
|
51
|
-
|
52
|
-
# any nesting and sub queries as new SQLConstructor or hash structure
|
53
|
-
### select u: [{ _some_count: [:_count, [:_nullif, :row, :value]] },
|
54
|
-
### :name, :email],
|
55
|
-
### _u_count: (another_constructor || another_structure)
|
56
|
-
### => 'SELECT COUNT(NULLIF(u,row, value)) AS some_count, u.name, u.email, (SELECT ...) AS u_count '
|
57
|
-
|
58
|
-
# construct it
|
59
|
-
### constructor = SQLConstructor.new from: [:users, :u],
|
60
|
-
### join [[:mrgl, :m], { on: 'm.user_id = u.id'}]
|
61
|
-
### constructor.select :name
|
62
|
-
### constructor.select [{u: :id, _some_count: [:_count, another_constructor]}] if smthng
|
63
|
-
### constructor.select [{r: :rgl}, :zgl] if another_smthng
|
64
|
-
### constructor.build_sql
|
65
|
-
### => 'SELECT name, u.id, COUNT(SELECT ...) AS some_count, r.rgl, zgl FROM ...'
|
66
29
|
def select(structure)
|
67
30
|
@structure[:select] = structure_constructor(@structure[:select], structure)
|
68
31
|
self
|
69
32
|
end
|
70
33
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
34
|
+
def distinct(fields = true)
|
35
|
+
@structure[:distinct] = fields
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def distinct_select(structure, fields = true)
|
40
|
+
@structure[:distinct] = fields
|
76
41
|
select structure
|
77
42
|
self
|
78
43
|
end
|
@@ -87,34 +52,11 @@ module Terrazine
|
|
87
52
|
end
|
88
53
|
|
89
54
|
# TODO: join constructor AND better syntax
|
90
|
-
# join 'users u ON u.id = m.user_id'
|
91
|
-
# join ['users u ON u.id = m.user_id',
|
92
|
-
# 'skills s ON u.id = s.user_id']
|
93
|
-
# join [[:user, :u], { on: 'rgl = 123' }]
|
94
|
-
# join [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
|
95
|
-
# [:master, { on: ['z = 12', 'mrgl = 12'] }]]
|
96
55
|
def join(structure)
|
97
56
|
@structure[:join] = structure
|
98
|
-
# puts @structure[:join]
|
99
57
|
self
|
100
58
|
end
|
101
59
|
|
102
|
-
# conditions 'mrgl = 12'
|
103
|
-
# conditions ['z = 12', 'mrgl = 12']
|
104
|
-
# conditions ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']]
|
105
|
-
# conditions [:or, ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']],
|
106
|
-
# [:or, 'rgl = 12', 'zgl = fuck']]
|
107
|
-
# conditions [['NOT z = 13',
|
108
|
-
# [:or, 'mrgl = 2', 'rgl = 22']],
|
109
|
-
# [:or, 'rgl = 12', 'zgl = fuck']]
|
110
|
-
# => 'NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = fuck)'
|
111
|
-
# conditions ['NOT z = 13', [:or, 'mrgl = 2',
|
112
|
-
# ['rgl IN ?', {select: true, from: :users}]]]
|
113
|
-
|
114
|
-
# constructor.where ['u.categories_cache ~ ?',
|
115
|
-
# { select: :path, from: :categories,
|
116
|
-
# where: ['id = ?', s_params[:category_id]] }]
|
117
|
-
# constructor.where('m.cashless IS TRUE')
|
118
60
|
def where(structure)
|
119
61
|
w = @structure[:where]
|
120
62
|
if w.is_a?(Array) && w.first.is_a?(Array)
|
@@ -127,22 +69,34 @@ module Terrazine
|
|
127
69
|
self
|
128
70
|
end
|
129
71
|
|
130
|
-
# TODO: with -_-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
72
|
+
# TODO: with constructor -_-
|
73
|
+
def with(structure)
|
74
|
+
@structure[:with] = structure
|
75
|
+
self
|
76
|
+
end
|
135
77
|
|
78
|
+
# TODO: order constructor -_-
|
79
|
+
def order(structure)
|
80
|
+
@structure[:order] = structure
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: default per used here and in builder...-_-
|
136
85
|
def limit(per)
|
137
86
|
@structure[:limit] = (per || 8).to_i
|
138
87
|
self
|
139
88
|
end
|
140
89
|
|
90
|
+
# same as limit =(
|
91
|
+
def offset(offset)
|
92
|
+
@structure[:offset] = offset || 0
|
93
|
+
end
|
94
|
+
|
141
95
|
# TODO: serve - return count of all rows
|
142
96
|
# params - hash with keys :per, :page
|
143
97
|
def paginate(params)
|
144
98
|
limit params[:per]
|
145
|
-
|
99
|
+
offset((params.fetch(:page, 1).to_i - 1) * @structure[:limit])
|
146
100
|
self
|
147
101
|
end
|
148
102
|
|
@@ -156,8 +110,8 @@ module Terrazine
|
|
156
110
|
# constructor.build_sql
|
157
111
|
# => 'SELECT .... FROM ...'
|
158
112
|
# => ['SELECT .... FROM .... WHERE id = $1', [22]]
|
159
|
-
def build_sql
|
160
|
-
|
113
|
+
def build_sql(options = {})
|
114
|
+
Builder.new.get_sql @structure, options
|
161
115
|
end
|
162
116
|
end
|
163
117
|
end
|
data/lib/version.rb
CHANGED
data/spec/constructor_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require_relative 'spec_helper'
|
2
2
|
|
3
3
|
# TODO.... -_-
|
4
|
+
# May be store structures with string representation? Because tests sux right now=(
|
4
5
|
describe Terrazine::Constructor do
|
5
6
|
before :each do
|
6
7
|
@constructor = Terrazine.new_constructor
|
@@ -13,7 +14,26 @@ describe Terrazine::Constructor do
|
|
13
14
|
expect(@constructor.class).to eql Terrazine::Constructor
|
14
15
|
end
|
15
16
|
|
16
|
-
context '`
|
17
|
+
context '`WITH`' do
|
18
|
+
it 'build array like syntax' do
|
19
|
+
@constructor.with [:name, { select: true }]
|
20
|
+
expect(@constructor.build_sql).to eq 'WITH name AS (SELECT * ) '
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'build nested array like syntax' do
|
24
|
+
@constructor.with [[:name, { select: true }],
|
25
|
+
[:another_name, { select: :mrgl }]]
|
26
|
+
expect(@constructor.build_sql).to eq 'WITH name AS (SELECT * ), another_name AS (SELECT mrgl ) '
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'build hash like syntax' do
|
30
|
+
@constructor.with name: { select: true },
|
31
|
+
another_name: { select: :mrgl }
|
32
|
+
expect(@constructor.build_sql).to eq 'WITH name AS (SELECT * ), another_name AS (SELECT mrgl ) '
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context '`SELECT`' do
|
17
37
|
it 'build simple structure' do
|
18
38
|
@constructor.select(:name)
|
19
39
|
@constructor.select('phone')
|
@@ -32,22 +52,37 @@ describe Terrazine::Constructor do
|
|
32
52
|
@constructor.select select: [:_count, [:_nullif, :connected, true]],
|
33
53
|
from: [:calls, :c],
|
34
54
|
where: 'u.id = c.user_id'
|
35
|
-
expect(@constructor.build_sql).to eq 'SELECT (SELECT COUNT(NULLIF(connected, true)) FROM calls c WHERE u.id = c.user_id
|
55
|
+
expect(@constructor.build_sql).to eq 'SELECT (SELECT COUNT(NULLIF(connected, true)) FROM calls c WHERE u.id = c.user_id ) '
|
36
56
|
end
|
37
57
|
|
38
58
|
it 'build big structures' do
|
39
59
|
@permanent_c.select _calls_count: { select: [:_count, [:_nullif, :connected, true]],
|
40
60
|
from: [:calls, :c],
|
41
|
-
where:
|
61
|
+
where: { u__id: :c__user_id } },
|
42
62
|
u: [:name, :phone, { _master: [:_nullif, :role, "'master'"] },
|
43
63
|
'u.abilities, u.id', 'birthdate']
|
44
64
|
@permanent_c.select o: :client_name
|
45
65
|
@permanent_c.select :secure_id
|
46
|
-
expect(@permanent_c.build_sql).to eq "SELECT (SELECT COUNT(NULLIF(connected, true)) FROM calls c WHERE u.id = c.user_id
|
66
|
+
expect(@permanent_c.build_sql).to eq "SELECT (SELECT COUNT(NULLIF(connected, true)) FROM calls c WHERE u.id = c.user_id ) AS calls_count, u.name, u.phone, NULLIF(u.role, 'master') AS master, u.abilities, u.id, u.birthdate, o.client_name, secure_id "
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'build DISTINCT' do
|
70
|
+
@constructor.distinct_select([:id, :name, :phone])
|
71
|
+
expect(@constructor.build_sql).to eq 'SELECT DISTINCT id, name, phone '
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'build DISTINCT ON field' do
|
75
|
+
@constructor.distinct_select([:id, :name, :phone], :id)
|
76
|
+
expect(@constructor.build_sql).to eq 'SELECT DISTINCT ON(id) id, name, phone '
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'build DISTINCT ON array of field' do
|
80
|
+
@constructor.distinct_select([:id, :name, :phone], [:id, :phone])
|
81
|
+
expect(@constructor.build_sql).to eq 'SELECT DISTINCT ON(id, phone) id, name, phone '
|
47
82
|
end
|
48
83
|
end
|
49
84
|
|
50
|
-
context '`
|
85
|
+
context '`FROM`' do
|
51
86
|
it 'build simple data structures' do
|
52
87
|
@constructor.from :users
|
53
88
|
expect(@constructor.build_sql).to eq 'FROM users '
|
@@ -55,19 +90,25 @@ describe Terrazine::Constructor do
|
|
55
90
|
expect(@permanent_c.build_sql).to match 'o.client_name, secure_id FROM users u $'
|
56
91
|
end
|
57
92
|
|
58
|
-
it 'build
|
59
|
-
@constructor.from [:_values, [:
|
93
|
+
it 'build VALUES' do
|
94
|
+
@constructor.from [:_values, [:_params, 'mrgl'], :r, ['type']]
|
60
95
|
expect(@constructor.build_sql).to eq ['FROM (VALUES($1)) AS r (type) ', ['mrgl']]
|
61
96
|
end
|
62
97
|
|
63
|
-
it 'build
|
98
|
+
it 'build VALUES and tables' do
|
64
99
|
@constructor.from [[:mrgl, :m], [:_values, [1, 2], :rgl, [:zgl, :gl]]]
|
65
100
|
expect(@constructor.build_sql).to eq 'FROM mrgl m, (VALUES(1, 2)) AS rgl (zgl, gl) '
|
66
101
|
end
|
102
|
+
|
103
|
+
it 'build VALUES with many rows' do
|
104
|
+
@constructor.from [:_values, [[:_params, 'mrgl'], [:_params, 'rgl']], :r, ['type']]
|
105
|
+
expect(@constructor.build_sql).to eq ['FROM (VALUES($1), ($2)) AS r (type) ',
|
106
|
+
['mrgl', 'rgl']]
|
107
|
+
end
|
67
108
|
end
|
68
109
|
|
69
|
-
context '`
|
70
|
-
it 'build simple
|
110
|
+
context '`JOIN`' do
|
111
|
+
it 'build simple structure' do
|
71
112
|
@constructor.join 'users u ON u.id = m.user_id'
|
72
113
|
expect(@constructor.build_sql).to eq 'JOIN users u ON u.id = m.user_id '
|
73
114
|
@constructor.join ['users u ON u.id = m.user_id',
|
@@ -84,7 +125,56 @@ describe Terrazine::Constructor do
|
|
84
125
|
end
|
85
126
|
end
|
86
127
|
|
87
|
-
context '`
|
88
|
-
|
128
|
+
context '`ORDER`' do
|
129
|
+
it 'build string structre' do
|
130
|
+
@constructor.order 'name ASC'
|
131
|
+
expect(@constructor.build_sql).to eq 'ORDER BY name ASC '
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'build array structure' do
|
135
|
+
@constructor.order [:name, :email]
|
136
|
+
expect(@constructor.build_sql).to eq 'ORDER BY name, email '
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'build hash structure' do
|
140
|
+
@constructor.order name: :asc, phone: [:first, :desc]
|
141
|
+
expect(@constructor.build_sql).to eq 'ORDER BY name ASC, phone DESC NULLS FIRST '
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'build complicated structure' do
|
145
|
+
@constructor.order [:role, { name: :asc, phone: [:last, :desc], amount: '<' }]
|
146
|
+
expect(@constructor.build_sql).to eq 'ORDER BY role, name ASC, phone DESC NULLS LAST, amount USING< '
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context '`WHERE`' do
|
151
|
+
it 'build simple structure' do
|
152
|
+
@constructor.where [[:not, 'z = 13'],
|
153
|
+
[:or, 'mrgl = 2', 'rgl = 22'],
|
154
|
+
[:or, 'rgl = 12', 'zgl = lol']]
|
155
|
+
expect(@constructor.build_sql).to eq 'WHERE NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = lol) '
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'build intemidate structure' do
|
159
|
+
@constructor.where [{ role: 'manager', id: [0, 1, 153] },
|
160
|
+
[:not, [:like, :u__name, 'Aeonax']]]
|
161
|
+
expect(@constructor.build_sql).to eq ['WHERE role = $1 AND id IN ($2) AND NOT u.name LIKE $3 ', ['manager', [0, 1, 153], 'Aeonax']]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'Operators' do
|
166
|
+
it 'build missing operator' do
|
167
|
+
sql = Terrazine.build_sql([:_to_json,
|
168
|
+
[:_array_agg,
|
169
|
+
{ select: [:id, :title,
|
170
|
+
{ _copies_in_stock:
|
171
|
+
{ select: [:_count, :id],
|
172
|
+
from: [:book_copies, :b_c],
|
173
|
+
where: ['b_c.shop_id = s.id',
|
174
|
+
'b_c.book_id = b.id',
|
175
|
+
'NOT b_c.sold = TRUE'] } }] }]],
|
176
|
+
key: 'operator')
|
177
|
+
expect(sql).to eq 'to_json(array_agg((SELECT id, title, (SELECT COUNT(id) FROM book_copies b_c WHERE b_c.shop_id = s.id AND b_c.book_id = b.id AND NOT b_c.sold = TRUE ) AS copies_in_stock )))'
|
178
|
+
end
|
89
179
|
end
|
90
180
|
end
|