terrazine 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d761b9fb0ee69306c68a075791804e9394be557a
|
4
|
+
data.tar.gz: 7413cc5841e648c59fee3163ca2841273efecbc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15259c4c4f582b13fcbcbfeca042398d97cfe554abd96ae291f8e38c523e188a0b7954666c6940c4254ee92e8886071647551a6b52b759d9891f045f7d6c3113
|
7
|
+
data.tar.gz: 032c017527f3bdf2b44a7ee2a6b69c4ae5a62306cdc5a3f49fa43e2b224271d6668f75bf1d62419a32fc8fdf0e4fa5db1608260db0cc839a0105266a7ae3d2e5
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -14,20 +14,51 @@ Get result and access any returned data rails like syntax.
|
|
14
14
|
|
15
15
|
#### Realization
|
16
16
|
This is my first gem and first close meeting with OOP... I would appreciate any help =)
|
17
|
-
And sorry for my English =(
|
17
|
+
And sorry for my English =(
|
18
|
+
|
19
|
+
#### Readiness
|
20
|
+
Terrazine is not finished yet. Now it has allmost full SELECT builder, but with some limitations like:
|
21
|
+
- awfull where syntax
|
22
|
+
- bad join syntax
|
23
|
+
- not all SQL functions supported
|
24
|
+
|
25
|
+
And now it supports only Postgresql.
|
18
26
|
|
19
27
|
## Detailed description
|
20
28
|
|
21
29
|
### Usage
|
22
|
-
|
30
|
+
#### Initialization
|
31
|
+
Add this line to the Gemfile
|
32
|
+
```ruby
|
33
|
+
gem 'terrazine', '0.0.2'
|
34
|
+
```
|
35
|
+
After server initialization set `Terrazine.config`. Now config accepts only `:connection` option. In the bright future will be added `:adapter` option support.
|
36
|
+
In rails you can set config with [after_initialize](https://apidock.com/rails/Rails/Configuration/after_initialize) and it will looks like:
|
37
|
+
|
38
|
+
UPD: On production, rails closing `PG::Connection` from `after_initialize`, as fast fix connection now can be `Proc` object which must return `PG::Connection`. Later i'll try to find better solution
|
39
|
+
```ruby
|
40
|
+
# file config/application.rb
|
41
|
+
module Name
|
42
|
+
class Application < Rails::Application
|
43
|
+
# ....
|
44
|
+
config.after_initialize do
|
45
|
+
Terrazine.config connection: -> { ActiveRecord::Base.connection.raw_connection }
|
46
|
+
end
|
47
|
+
# ....
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
#### Workflow
|
52
|
+
- Describe whole data structure, or create `Constructor` instance and combine parts of data by it instance methods.
|
53
|
+
- Send result to `Terrazine.send_request(structure||constructor, params = {})`
|
54
|
+
- Rejoice at the `::Result`
|
23
55
|
|
24
56
|
### Constructor
|
25
57
|
You can create Constructor instance by calling `Terrazine.new_constructor`. It optional accepts data structure.
|
26
|
-
|
27
58
|
```ruby
|
28
59
|
constructor = Terrazine.new_constructor
|
29
60
|
constructor_2 = Terrazine.new_constructor from: :calls
|
30
|
-
```
|
61
|
+
```
|
31
62
|
#### Instance methods
|
32
63
|
Instance methods write or combine data inside constructor instance.
|
33
64
|
Not finished methods - just rewrites structure without combination with existing data.
|
@@ -42,90 +73,209 @@ Not finished methods - just rewrites structure without combination with existing
|
|
42
73
|
- [x] build_sql
|
43
74
|
|
44
75
|
### Data Structures
|
76
|
+
You can take a look on more detailed examples in `spec/constructor_spec.rb`
|
45
77
|
|
46
|
-
####
|
47
|
-
|
78
|
+
#### Common patterns
|
79
|
+
###### SQL Function
|
80
|
+
Structure:
|
81
|
+
- `Array`
|
82
|
+
- first element - `Symbol` that begins from _ - `:_nullif`
|
83
|
+
- arguments
|
84
|
+
- [columns](#columns)
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
[:_count, [:_nullif, :row, [:_params, 'mrgl']]] # TODO: param?
|
88
|
+
# => ['COUNT(NULLIF(row, $1))', ['mrgl']]
|
89
|
+
```
|
90
|
+
[Detailed Functions description](#sql-functions).
|
91
|
+
###### Columns
|
92
|
+
Possible structures:
|
93
|
+
- `String`
|
94
|
+
- if it locted in the `Hash` with table alias, table alias will be added to it
|
95
|
+
- if there is no table alias it will be returned to the builder as it is.
|
96
|
+
- `Symbol` - just parsed to string
|
97
|
+
- `Hash`
|
98
|
+
- key - table alias||name
|
99
|
+
- value - [columns](#columns)
|
100
|
+
- [SQL function](#sql-function)
|
101
|
+
- `Array` - holder of any possible structures
|
102
|
+
```ruby
|
103
|
+
['name', {u: ['role', 'u.phone, m.rating', :field]}]
|
104
|
+
# => 'name, u.role, u.phone, m.rating, u.field'
|
105
|
+
```
|
106
|
+
[Detailed Select description](#select)
|
107
|
+
###### Tables
|
108
|
+
Possible structures:
|
48
109
|
- `String` || `Symbol`
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
110
|
+
- [SQL function](#sql-function)
|
111
|
+
- `Array`
|
112
|
+
- if there is no `Array` inside it will be joined `structure.join ' '`
|
113
|
+
- otherwise it will be recursive mapped
|
52
114
|
```ruby
|
115
|
+
['users u', [:_values, ...], [:masters, :m]]
|
116
|
+
'users u, (VALUES...), masters m'
|
117
|
+
```
|
118
|
+
###### Conditions
|
119
|
+
Not finished yet...
|
120
|
+
Column can be described as `:u__name => 'u.name'` or `:name`
|
121
|
+
Possible structures:
|
122
|
+
- `String` passes as it is
|
123
|
+
- `Hash` represent sql `=` or `IN` if value is `Array`. TODO: `IS` in case of `nil` or `false`
|
124
|
+
- `Symbol` - column name
|
125
|
+
- `Array` - only as value! will be placed in querry params `($1)`.
|
126
|
+
- `String` - will be placed in querry params
|
127
|
+
- `Array`
|
128
|
+
- first element - `Symbol` operator representation, by default `:and`
|
129
|
+
- `eq` - `=`
|
130
|
+
- `or`, `and`
|
131
|
+
- `in`
|
132
|
+
- `not`
|
133
|
+
- `like`, `ilike`
|
134
|
+
- `reg` - `~`, `reg_i` - `~*`, `reg_f` - `!~`, `reg_fi` - `!~*`
|
135
|
+
- arguments
|
136
|
+
- `Array` - holder of any possible structures
|
137
|
+
```ruby
|
138
|
+
[[:not, 'z = 13'],
|
139
|
+
[:or, 'mrgl = 2', 'rgl = 22'],
|
140
|
+
[:or, 'rgl = 12', 'zgl = lol']]
|
141
|
+
# => 'NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = lol)'
|
142
|
+
[{ role: 'manager', id: [0, 1, 153] },
|
143
|
+
[:not, [:like, :u__name, 'Aeonax']]]
|
144
|
+
#=> 'role = $1 AND id IN ($2) AND NOT u.name LIKE $3', ['manager', [0, 1, 153], 'Aeonax']
|
145
|
+
```
|
146
|
+
###### Sub Querry
|
147
|
+
Possible structures:
|
148
|
+
- `Constructor` instance
|
149
|
+
- `Hash` with `:select` value
|
150
|
+
|
151
|
+
#### Select
|
152
|
+
Possible structures:
|
153
|
+
- [columns](#columns)
|
154
|
+
- [sub querry](#sub-query)
|
155
|
+
- [SQL function](#sql-function)
|
156
|
+
- `Array` with combination of possible structures.
|
157
|
+
```ruby
|
158
|
+
# String
|
53
159
|
constructor.select "name, email"
|
160
|
+
# Symbol
|
54
161
|
constructor.select :birthdate
|
162
|
+
# Array as columns
|
163
|
+
constructor.select [:phone, 'role']
|
164
|
+
# Array as SQL function
|
165
|
+
constructor.select [:_nullif, :row, :value]
|
166
|
+
# Hash with column alias(`AS`) as key and any available for `select` value
|
167
|
+
constructor.select _missed_calls_count:
|
168
|
+
{ select: [:_count, [:_nullif, :connected, :true]],
|
169
|
+
from: [:calls, :c],
|
170
|
+
where: ['c.client_id = u.id',
|
171
|
+
['direction = ?', 0]]}
|
172
|
+
# Hash with table alias as key and any available for `select` values
|
55
173
|
constructor.select m: [:common_rating, :work_rating, { _master_id: :id }]
|
56
|
-
|
57
|
-
from: [:calls, :c],
|
58
|
-
where: ['c.client_id = u.id',
|
59
|
-
['direction = ?', 0]]} }
|
174
|
+
# You can take a look of resulted data structure. In future, perhaps, Constructor will be more complicated and it will merge hashes...
|
60
175
|
constructor.structure
|
61
|
-
# => { select: ['name, email', :birthdate,
|
62
|
-
#
|
176
|
+
# => { select: ['name, email', :birthdate, :phone, 'role',
|
177
|
+
# [:_nullif, :row, :value],
|
63
178
|
# { _missed_calls_count: { select: [:_count, [:_nullif, :connected, :true]],
|
64
179
|
# from: [:calls, :c],
|
65
180
|
# where: ['c.client_id = u.id',
|
66
|
-
# ['direction = ?', 0]]} }] }
|
181
|
+
# ['direction = ?', 0]]} }] },
|
182
|
+
# { m: [:common_rating, :work_rating, { _master_id: :id }] }
|
67
183
|
|
68
184
|
constructor.build_sql
|
69
|
-
# => ['SELECT name, email, birthdate, m.common_rating, m.work_rating, m.id AS master_id,
|
185
|
+
# => ['SELECT name, email, birthdate, phone, role, NULLIF(row, value), m.common_rating, m.work_rating, m.id AS master_id,
|
70
186
|
# (SELECT COUNT(NULLIF(connected, TRUE))
|
71
187
|
# FROM calls c
|
72
188
|
# WHERE c.client_id = u.id AND direction = $1) AS missed_calls_count',
|
73
189
|
# 0]
|
74
|
-
```
|
190
|
+
```
|
191
|
+
|
192
|
+
##### Distinct Select
|
193
|
+
To specify distinct select you should add to your data structure `:distinct` value:
|
194
|
+
- `true`
|
195
|
+
- [columns](#columns)
|
196
|
+
Or with `Constructor` instance methods:
|
197
|
+
- `.distinct`
|
198
|
+
- distinct structure - optional
|
199
|
+
- `.distinct_select`
|
200
|
+
- [select](#select) structure
|
201
|
+
- distinct structure - optional
|
202
|
+
In constructor methods `distinct: true` passed by default
|
203
|
+
```ruby
|
204
|
+
# as data
|
205
|
+
distinct: true, select: true
|
206
|
+
# => 'SELECT DISTINCT * '
|
207
|
+
# OR via constructor
|
208
|
+
constructor.distinct_select([:id, :name]).build_sql # => 'SELECT DISTINCT id, name'
|
209
|
+
# OR
|
210
|
+
constructor.distinct_select([:id, :name], :phone).build_sql # => 'SELECT DISTINCT ON(phone) id, name '
|
211
|
+
```
|
75
212
|
|
76
213
|
#### From
|
77
|
-
|
78
|
-
-
|
79
|
-
-
|
214
|
+
Possible structures:
|
215
|
+
- [table representation](#tables)
|
216
|
+
- [SQL functions](#sql-functions)
|
217
|
+
- `Array` with combination of possible structures.
|
80
218
|
```ruby
|
81
|
-
from 'table_name table_alias' || :table_name
|
82
|
-
from [:table_name, :table_alias]
|
83
|
-
|
84
|
-
from
|
85
|
-
|
219
|
+
from: 'table_name table_alias' || :table_name
|
220
|
+
from: [:table_name, :table_alias]
|
221
|
+
# => 'FROM table_name table_alias '
|
222
|
+
from: [:_values, [1, 2], :rgl, [:zgl, :gl]]
|
223
|
+
# => 'FROM (VALUES(1, 2)) AS rgl (zgl, gl)'
|
224
|
+
from: [[:table_name, :table_alias], [:_values, [1, 2], :values_name, [*values_column_names]]]
|
225
|
+
# => 'FROM table_name table_alias, (VALUES(1, 2)) AS values_name (v_c_1, v_c_2)'
|
226
|
+
```
|
86
227
|
I do not like the `from` syntax, but how it can be made more convenient...?
|
87
228
|
|
88
229
|
#### Join
|
89
|
-
|
90
|
-
- `String`
|
91
|
-
- `Array
|
92
|
-
|
93
|
-
-
|
94
|
-
|
95
|
-
|
96
|
-
`Array`
|
97
|
-
```ruby
|
98
|
-
join 'users u ON u.id = m.user_id'
|
99
|
-
join ['users u ON u.id = m.user_id',
|
100
|
-
|
101
|
-
join [[:user, :u], { on: 'rgl = 123' }]
|
102
|
-
|
103
|
-
|
104
|
-
|
230
|
+
Possible structures:
|
231
|
+
- `String` - just passed in to `JOIN #{structure} `
|
232
|
+
- `Array` with values(same order):
|
233
|
+
- [table representation](#tables)
|
234
|
+
- `Hash`
|
235
|
+
- `:on` - [conditions](#conditions)
|
236
|
+
- `:option` - optional - contains `Symbol` or `String` of join type... rename to type?
|
237
|
+
- `Array` with combination of possible structures.
|
238
|
+
```ruby
|
239
|
+
join: 'users u ON u.id = m.user_id'
|
240
|
+
join: ['users u ON u.id = m.user_id',
|
241
|
+
'skills s ON u.id = s.user_id']
|
242
|
+
join: [[:user, :u], { on: 'rgl = 123' }]
|
243
|
+
# => 'JOIN users u ON rgl = 123'
|
244
|
+
join: [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
|
245
|
+
[:master, { on: ['z = 12', 'mrgl = 12'] }]]
|
246
|
+
# => 'FULL JOIN user u ON mrgl = 2 OR rgl = 22 JOIN master ON z = 12 AND mrgl = 12'
|
247
|
+
```
|
105
248
|
|
106
|
-
####
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
# => '
|
120
|
-
|
249
|
+
#### Order
|
250
|
+
Possible structures:
|
251
|
+
- `String`, `Symbol` just insert it in `"ORDER BY #{structure} "`
|
252
|
+
- [SQL function](#sql-function)
|
253
|
+
- `Hash`
|
254
|
+
- key - previsious possible structures.
|
255
|
+
- value - options representation
|
256
|
+
- `Symbol` - `:last || :first || :asc || :desc`
|
257
|
+
- `String` - `'<' || '>'` or smthng else that passed in to `USING`
|
258
|
+
- `Array` - with symbols inside
|
259
|
+
- `Array` - any possible structures
|
260
|
+
```ruby
|
261
|
+
order: 'z.amount DESC' || :name
|
262
|
+
# => 'ORDER BY z.amount DESC ' || 'ORDER BY name '
|
263
|
+
order: [:name, [:_case ...], { amount: [:first, :desc] }]
|
264
|
+
# => 'ORDER BY name, CASE ..., amount DESC NULLS FIRST '
|
265
|
+
```
|
121
266
|
|
122
267
|
#### With
|
123
268
|
```ruby
|
124
|
-
with [:alias_name, { select: true, from: :users}]
|
125
|
-
with [[:alias_name, { select: true, from: :users}],
|
126
|
-
|
269
|
+
with: [:alias_name, { select: true, from: :users}]
|
270
|
+
with: [[:alias_name, { select: true, from: :users}],
|
271
|
+
[:alias_name_2, { select: {u: [:name, :email]},
|
127
272
|
from: :rgl}]]
|
128
|
-
|
273
|
+
# => 'WITH alias_name (SELECT * FROM users ), alias_name_2 (...) '
|
274
|
+
# OR
|
275
|
+
with name: { select: true },
|
276
|
+
another_name: { select: :mrgl }
|
277
|
+
# => 'WITH name AS (SELECT * ), another_name AS (SELECT mrgl ) '
|
278
|
+
```
|
129
279
|
|
130
280
|
#### Union
|
131
281
|
```ruby
|
@@ -133,8 +283,27 @@ union: [{ select: true, from: [:o_list, [:_values, [1], :al, [:master]]] },
|
|
133
283
|
{ select: true, from: [:co_list, [:_values, [0, :FALSE, :TRUE, 0],
|
134
284
|
:al, [:rating, :rejected,
|
135
285
|
:payment, :master]]] }]
|
286
|
+
'SELECT ... UNION SELECT ...'
|
287
|
+
```
|
288
|
+
|
289
|
+
#### SQL Functions
|
290
|
+
##### Params
|
291
|
+
Pass argument as params to adapter
|
292
|
+
```ruby
|
293
|
+
[:_values, [:_params, 'mrgl', true, 'rgl'], :z, [:f_1, :f_2, :f_3]]
|
294
|
+
['(VALUES($1, $2, $3) AS z (f_1, f_2, f_3))', ['mrgl', true, 'rgl']]
|
136
295
|
```
|
137
296
|
|
297
|
+
##### Values
|
298
|
+
Second and third arguments are nesessary right now, but in furure i'll do them optional.
|
299
|
+
Arguments:
|
300
|
+
- array of values, can be nested
|
301
|
+
- `AS` name
|
302
|
+
- column names
|
303
|
+
```ruby
|
304
|
+
[:_values, [{u: [:name, :phone]}, :role, [:_params, 'rgl']], :z, [:n, :p, :r, :m]]
|
305
|
+
# => '(VALUES(u.name, u.phone, role, $1) AS z (n, p, r, m))'
|
306
|
+
```
|
138
307
|
### Result representation
|
139
308
|
#### ::Row
|
140
309
|
Result row - allow accessing data by field name via method - `row.name # => "mrgl"` or get hash representation with `row.to_h`
|
@@ -156,7 +325,7 @@ After initialize `PG::Result` cleared
|
|
156
325
|
- `:presenter_options`
|
157
326
|
|
158
327
|
#### ::Presenter
|
159
|
-
Used in `result.present(options = {})`
|
328
|
+
Used in `result.present(options = {})` for data representation as `Hash` or `Array`. Options are merged with `result.options[:presenter_options]`
|
160
329
|
Data will be presented as `Array` if `rows > 1` or `options[:array]` present.
|
161
330
|
##### Available options
|
162
331
|
- `array` - if querry returns only one row, but on client you await for array of data.
|
@@ -164,19 +333,38 @@ Data will be presented as `Array` if `rows > 1` or `options[:array]` present.
|
|
164
333
|
- `Proc` - it will call proc with row as argument, and! then pass it to modifier_presentation again
|
165
334
|
- `::Result` - it will call `modifier.present`
|
166
335
|
- any else will be returned without changes
|
336
|
+
- `delete` - (will be soon) - Symbol, String or Array representing keys that must be deleted from result data.
|
167
337
|
|
168
338
|
## TODO:
|
169
|
-
|
170
|
-
- [
|
171
|
-
- [
|
339
|
+
Except this todo's there is a lot commented todo's inside project.-_-
|
340
|
+
- [x] Parse data like arrays, booleans, nil to SQL. (:_params function -\_-)
|
341
|
+
- [x] Relocate functions builder in to class, finally I found how it can be done nice=))
|
172
342
|
- [ ] should I bother with extra spaces?
|
343
|
+
- [ ] logger in config
|
344
|
+
- [ ] Insert
|
345
|
+
- [ ] Update
|
346
|
+
- [ ] Delete
|
173
347
|
|
174
348
|
### Tests
|
349
|
+
- [ ] Normal structure!!!!
|
175
350
|
- [ ] Constructor + Builder
|
176
351
|
- [ ] Result
|
177
352
|
- [ ] Request
|
178
353
|
|
179
|
-
###
|
354
|
+
### Meditate
|
355
|
+
- [ ] builder structure... another possibility to split it?
|
180
356
|
- [ ] from
|
181
357
|
- [ ] join !!!
|
182
|
-
- [
|
358
|
+
- [x] where !!!!!! Supporting rails like syntax with hash?
|
359
|
+
- [ ] supporting another databases
|
360
|
+
|
361
|
+
## Updates:
|
362
|
+
#### 0.0.3
|
363
|
+
- Expand predicates syntax
|
364
|
+
- added support of multiple rows for `VALUES`
|
365
|
+
- `ORDER` structure
|
366
|
+
- scary tests-_-
|
367
|
+
|
368
|
+
## Contact
|
369
|
+
You can write me your suggestions for improving the syntax, wishes, things that you think are missing here.
|
370
|
+
My [email](mailto:aeonax.liar@gmail.com), [Ruby On Rails slack](https://rubyonrails-link.slack.com/messages/D8W1WSRAP)
|
data/circle.yml
ADDED
data/lib/terrazine.rb
CHANGED
@@ -28,12 +28,19 @@ module Terrazine
|
|
28
28
|
Constructor.new structure
|
29
29
|
end
|
30
30
|
|
31
|
-
def self.build_sql(structure)
|
31
|
+
def self.build_sql(structure, options = {})
|
32
32
|
case structure
|
33
|
-
when Hash
|
34
|
-
new_constructor(structure).build_sql
|
33
|
+
when Hash # , Array
|
34
|
+
new_constructor(structure).build_sql options
|
35
35
|
when Constructor
|
36
|
-
structure.build_sql
|
36
|
+
structure.build_sql options
|
37
|
+
when Array
|
38
|
+
# TODO!!!!!
|
39
|
+
if structure.first.is_a?(String) && structure.second.is_a?(Array)
|
40
|
+
structure
|
41
|
+
else
|
42
|
+
new_constructor(structure).build_sql options
|
43
|
+
end
|
37
44
|
when String
|
38
45
|
structure
|
39
46
|
else
|