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
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
|