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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f1a24d3310a23b60eb51584c7fdcec61a03c2b58
4
- data.tar.gz: 3be3456371d57ae8b67918d5d3b09a6447ff9194
3
+ metadata.gz: d761b9fb0ee69306c68a075791804e9394be557a
4
+ data.tar.gz: 7413cc5841e648c59fee3163ca2841273efecbc1
5
5
  SHA512:
6
- metadata.gz: c5a940bf57c92d76934211d9f6bbcca8ec9ed0bd8e42f6f788feb817309c6dcf3edb1d3f567718d1c847c051efc992a85e89ff985b86969d2ae3a114ab80a58a
7
- data.tar.gz: 92462094a13958703b45b35305b5cc1107db055119a0847cfd2a5a34bf59d63525be94d3309e4972985e24ad5926ab2b29addb916fd1e7732eebf2f7b457abed
6
+ metadata.gz: 15259c4c4f582b13fcbcbfeca042398d97cfe554abd96ae291f8e38c523e188a0b7954666c6940c4254ee92e8886071647551a6b52b759d9891f045f7d6c3113
7
+ data.tar.gz: 032c017527f3bdf2b44a7ee2a6b69c4ae5a62306cdc5a3f49fa43e2b224271d6668f75bf1d62419a32fc8fdf0e4fa5db1608260db0cc839a0105266a7ae3d2e5
data/Gemfile CHANGED
@@ -1 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
1
3
  gem 'pg-hstore', '1.2.0'
4
+
5
+ group :test do
6
+ gem 'pg', '1.0.0'
7
+ gem 'rspec'
8
+ end
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
- Describe whole data structure, or create `Constructor` instance and combine parts of data by it instance methods. Then send result to `Terrazine.send_request(structure||constructor, params = {})` and it will return you `Terrazine::Result` instance. (description will be soon)
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
- #### Select
47
- Accepts
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
- - `Hash` represents column alias - 'AS' (if key begins from `_`) OR table alias that will join to the values table prefix OR another data structure(present keyword `:select`).
50
- - Another `Constructor` or `Hash` representing data structure
51
- - `Array` can contain all of the above structures OR in case of first symbol/string begins from `_` it will represent SQL function
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
- constructor.select { _missed_calls_count: { select: [:_count, [:_nullif, :connected, :true]],
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
- # { m: [:common_rating, :work_rating, { _master_id: :id }] },
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
- Accepts
78
- - `String` || `Symbol`
79
- - `Array` can contains table_name and table_alias OR `VALUES` OR both
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
- from [[:table_name, :table_alias], [:_values, [1, 2], :values_name, [*values_column_names]]]
84
- from [:mrgl, [:_values, [1, 2], :rgl, [:zgl, :gl]]]
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
- Accpets
90
- - `String`
91
- - `Array`:
92
- First element same as `from` first element - table name or `Array` of table_name and table_alias, then `Hash` with keys:
93
- - on - conditions(description will be bellow)
94
- - options - optional contains `Symbol` or `String` of join type... rename to type?
95
-
96
- `Array` can be nested
97
- ```ruby
98
- join 'users u ON u.id = m.user_id'
99
- join ['users u ON u.id = m.user_id',
100
- 'skills s ON u.id = s.user_id']
101
- join [[:user, :u], { on: 'rgl = 123' }]
102
- join [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
103
- [:master, { on: ['z = 12', 'mrgl = 12'] }]]
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
- #### Conditions
107
- Current conditions implementation is sux... -_- Soon i'll change it.
108
- Now it accepts `String` or `Array`.
109
- First element of array is `Symbol` representation of join condition - `:or || :and` or by default `:and`.
110
- ```ruby
111
- conditions 'mrgl = 12'
112
- conditions ['z = 12', 'mrgl = 12']
113
- conditions ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']]
114
- conditions [:or, ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']],
115
- [:or, 'rgl = 12', 'zgl = lol']]
116
- conditions [['NOT z = 13',
117
- [:or, 'mrgl = 2', 'rgl = 22']],
118
- [:or, 'rgl = 12', 'zgl = lol']]
119
- # => 'NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = lol)'
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
- [:alias_name_2, { select: {u: [:name, :email]},
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 = {})` - it represents data as `Hash` or `Array`. options are merged with `result.options[:presenter_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
- - [ ] Parse data like arrays, booleans, nil to SQL
170
- - [ ] Relocate functions builder in to class, finally I found how it can be done nice=))
171
- - [ ] meditate about structure supporting another databases(now supports only postgress)
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
- ### Think of a better data structure for
354
+ ### Meditate
355
+ - [ ] builder structure... another possibility to split it?
180
356
  - [ ] from
181
357
  - [ ] join !!!
182
- - [ ] where !!!!!! Support for rails like syntax with hash?
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)
@@ -0,0 +1,11 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.3.1
4
+
5
+ checkout:
6
+ post:
7
+ - bundle
8
+
9
+ test:
10
+ override:
11
+ - bundle exec rspec
@@ -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