i_dig_sql 2.0.0 → 3.0.0

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: cc217b63c8a06244155e7007377bca783c611b36
4
- data.tar.gz: c284841ef57010e523b15405072726c7d5f6125f
3
+ metadata.gz: 5a72cee2c11ff760fcb5227b539fa976a39dfb56
4
+ data.tar.gz: a418aba79878b9361f980f137c7be1b108e3ee74
5
5
  SHA512:
6
- metadata.gz: afd3204cc8130e91f844b192641cd44829ba544d92cec68fd6b7766d688f11564de76ecaa3ae16aa5472515234f6c1962d06447e0b448d69869c0ef01a54e1fd
7
- data.tar.gz: e403fe4f10c7b8ec7e9b323fa8c3907f216682ac4fd5159af99505e92f0cf5ee0140df7acb42896e7148c1e4aee99efca0785f6964f155efa74b96f5647a224b
6
+ metadata.gz: 1e7ef6053d12c991ab562da0c882bc510babae4c315e108e7bb34c67855e998106ea51ccc1f5c4384a88d9f5c0fdbaf8b2f183539d1c3a40b208b4be73431de5
7
+ data.tar.gz: 43c7bca10bbb494ab492e7ad1852ecbd992ba80b96832056bf6760b7f8c2a533023faeb8df91ab595bf30e04c38081d927367ad522f269245b1f6b5c8f113aae
data/README.md CHANGED
@@ -1,29 +1,19 @@
1
1
  # I\_Dig\_Sql
2
2
 
3
- My way of managing SQL fragments using Ruby.
4
3
 
5
- # Warning:
4
+ I'm still learning how to write decent SQL queries
5
+ in Postgresql 9.4. I'm using this to manage
6
+ sub-queries in complicated SQL queries.
6
7
 
7
- You will hate using this.
8
- Instead, use:
8
+ You won't find this useful and I am too lazy/busy
9
+ to write decent documentation for something
10
+ no one but me will use for esoteric purposes.
11
+
12
+ Instead, this to for SQL + Ruby:
9
13
 
10
14
  * [Arel](http://github.com/rails/arel).
11
15
  * K'bam [https://github.com/vilnius-leopold/kbam](https://github.com/vilnius-leopold/kbam)
12
-
13
- # History
14
-
15
- I had trouble maintaining BIG sql queries.
16
-
17
- I tried many things.
18
-
19
- The best way (within my preferences)
20
- was to use sub-queries, CTEs, avoid joins as much as possible,
21
- and this gem to manage SQL fragments and CTEs.
22
-
23
- Naturally, you would want to use prepared statements, compiled wat-cha-me-call-its,
24
- functions, views, thing-ma-jig-ers, and other tools available in your RDBMS.
25
-
26
- So this gem is for lazy, stupid people like me.
16
+ * Sequel [http://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html](http://sequel.jeremyevans.net/rdoc/files/doc/querying_rdoc.html)
27
17
 
28
18
  # Usage
29
19
 
@@ -52,6 +42,7 @@ Please note that none of this is ready yet.
52
42
  ^
53
43
 
54
44
  sql.to_sql
45
+
55
46
  ```
56
47
 
57
48
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 3.0.0
data/bin/test CHANGED
@@ -2,9 +2,46 @@
2
2
  # -*- bash -*-
3
3
  #
4
4
  #
5
+ x="$1"
5
6
  set -u -e -o pipefail
6
7
 
8
+ if [[ "$x" == "watch" ]]; then
9
+ shift
7
10
 
8
- bundle exec bacon -rpry -ri_dig_sql specs/i_dig_sql.rb "$@"
11
+ echo ""
12
+ echo "=== Watching: $@"
13
+
14
+ echo -e "\n=== Running test:"
15
+ bin/test "$@" || echo ""
16
+
17
+ inotifywait -q -m -e close_write,close --exclude .git/ -r . | while read CHANGE
18
+ do
19
+
20
+ dir=$(echo "$CHANGE" | cut -d' ' -f 1)
21
+ op=$(echo "$CHANGE" | cut -d' ' -f 2)
22
+ file=$(echo "$CHANGE" | cut -d' ' -f 3)
23
+ path="${dir}$file"
24
+
25
+ if [[ "$op" == *CLOSE_WRITE* && $file == *.rb* ]]; then
26
+ echo -e "\n=== Running test:"
27
+ bin/test "$@" || echo ""
28
+ fi
29
+
30
+ done # === do
31
+
32
+ echo ""
33
+ exit 0
34
+ fi # === if watch
35
+
36
+ files="$(echo specs/*-$x.rb)"
37
+ if [[ -f "$files" ]]; then
38
+ shift
39
+ else
40
+ files="$(echo specs/*-*.rb)"
41
+ fi
42
+
43
+
44
+
45
+ bundle exec bacon -rpry -ri_dig_sql specs/helpers.rb $files "$@"
9
46
 
10
47
 
@@ -21,8 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_development_dependency "bundler" , "~> 1.5"
25
- spec.add_development_dependency "bacon" , '~> 1.0'
26
- spec.add_development_dependency "Bacon_Colored" , '~> 0'
27
- spec.add_development_dependency "pry" , '~> 0'
24
+ spec.add_development_dependency "bundler" , "> 1.5"
25
+ spec.add_development_dependency "bacon" , '> 1.0'
26
+ spec.add_development_dependency "Bacon_Colored" , '> 0'
27
+ spec.add_development_dependency "pry" , '> 0'
28
+ spec.add_development_dependency "awesome_print" , '> 0'
29
+ spec.add_development_dependency "anbt-sql-formatter" , '> 0'
30
+ spec.add_development_dependency "rouge" , '> 0.0.0'
31
+ spec.add_development_dependency "unindent" , '> 0.0.0'
28
32
  end
@@ -1,124 +1,447 @@
1
1
 
2
+ require "i_dig_sql/H"
3
+
2
4
  class I_Dig_Sql
3
5
 
4
6
  include Enumerable
7
+
5
8
  HAS_VAR = /(\{\{|\<\<)[^\}\>]+(\}\}|\>\>)/
9
+ SELECT_FROM_REG = /SELECT.+FROM.+/
10
+
11
+
12
+ ALL_UNDERSCORE = /\A[_]+\Z/
13
+ COMBO_LEFT_RIGHT = [:left, :left, :right, :right]
14
+ COMBO_OUT_IN = [:out, :in, :out, :in]
6
15
 
7
16
  Duplicate = Class.new RuntimeError
8
17
 
9
18
  class << self
10
19
  end # === class self ===
11
20
 
12
- class H < Hash
21
+ attr_reader :digs, :WITHS, :data
22
+ def initialize *args
23
+ @WITH = nil
24
+ @FRAGMENT = nil
25
+ @SQL = nil
26
+ @WITHS = []
13
27
 
14
- def [] name
15
- fail ArgumentError, "Unknown key: #{name.inspect}" unless has_key?(name)
16
- super
17
- end
28
+ @digs = []
29
+ @data = H.new(:allow_update)
30
+ .merge!(
31
+ :name => nil,
32
+ :raw => nil,
33
+ :vars => H.new,
34
+ :procs => H.new
35
+ )
18
36
 
19
- def []= name, val
20
- if has_key?(name) && self[name] != val
21
- fail ArgumentError, "Key already set: #{name.inspect}"
22
- end
37
+ args.each { |a|
23
38
 
24
- super
25
- end
39
+ case a
26
40
 
27
- def merge_these *args
28
- args.each { |h|
29
- h.each { |k,v|
30
- self[k] = v
31
- }
32
- }
33
- self
34
- end
41
+ when Symbol
42
+ @data[:name] = a
35
43
 
36
- end # === class H
44
+ when I_Dig_Sql
45
+ @digs << a
46
+
47
+ when String
48
+ @data.merge!(:raw=>a)
49
+
50
+ when Hash, H
51
+ if a.has_key?(:raw)
52
+ @data.merge! a
53
+ else
54
+ @data[:vars].merge! a
55
+ end
56
+
57
+ else
58
+ fail ArgumentError, "Unknown arg: #{a.inspect}"
59
+
60
+ end # === case
37
61
 
38
- attr_reader :sqls, :vars
39
- def initialize *args
40
- @digs = args.select { |a|
41
- a.is_a? I_Dig_Sql
42
62
  }
43
63
 
44
- @sqls = H.new
45
- @vars = H.new
64
+ end # === def initialize
65
+
66
+ %w{name raw vars}.each { |k|
67
+ eval <<-EOF.strip, nil, __FILE__, __LINE__
68
+ def #{k}
69
+ @data[:#{k}]
70
+ end
71
+ EOF
72
+ }
73
+
74
+ def vars!
75
+ vars = H.new.merge!(@data[:vars])
76
+
77
+ @digs.reverse.inject(vars) { |memo, dig|
78
+ if dig != self
79
+ memo.merge! dig.vars
80
+ end
81
+ memo
82
+ }
83
+ end
84
+
85
+ def has_key? name
86
+ !!(search name)
87
+ end
46
88
 
47
- @sqls.merge_these *(@digs.map(&:sqls))
48
- @vars.merge_these *(@digs.map(&:vars))
89
+ def search name
90
+ fail ArgumentError, "No name specified: #{name.inspect}" if !name
91
+ return self if self.name == name
92
+ found = false
93
+ if @data[:procs].has_key?(name)
94
+ return @data[:procs][name]
95
+ end
49
96
 
50
- @string = args.select { |s| s.is_a? String }.join("\n")
97
+ @digs.reverse.detect { |d|
98
+ found = if d.name == name
99
+ d
100
+ elsif d.data[:procs].has_key?(name)
101
+ d.data[:procs][name]
102
+ else
103
+ d.digs.detect { |deep|
104
+ found = deep if deep.name == name
105
+ found = deep.data[:procs][name] if deep.data[:procs].has_key?(name)
106
+ found
107
+ }
108
+ end
109
+ }
110
+ found
51
111
  end
52
112
 
53
113
  def [] name
54
- @sqls[name]
114
+ found = search(name)
115
+ fail ArgumentError, "SQL not found for: #{name.inspect}" unless found
116
+ found
55
117
  end
56
118
 
57
119
  def []= name, val
58
- @sqls[name] = val
120
+ fail ArgumentError, "Name already taken: #{name.inspect}" if has_key?(name)
121
+
122
+ case val
123
+ when String
124
+ @digs << I_Dig_Sql.new(self, name, val)
125
+
126
+ when Hash, H
127
+ case
128
+ when val[:name] == :DEFAULT
129
+ @digs << I_Dig_Sql.new(:DEFAULT, val)
130
+ when val[:raw]
131
+ @data.merge! val
132
+ else
133
+ @digs << I_Dig_Sql.new(self, name, val)
134
+ end
135
+
136
+ when I_Dig_Sql
137
+ @digs << val
138
+
139
+ when Proc
140
+ @data[:procs][name] = val
141
+
142
+ else
143
+ fail ArgumentError, "Unknown class: #{name.inspect} -> #{val.class}"
144
+
145
+ end # === case
59
146
  end
60
147
 
61
148
  def each
149
+ digs = @digs.reverse
150
+ digs.unshift self
62
151
  if block_given?
63
- @sqls.each { |k, v| yield k, v }
152
+ digs.each { |d| yield d.name, d }
64
153
  else
65
- @sqls.each
154
+ digs.each
66
155
  end
67
156
  end
68
157
 
158
+
69
159
  def << str
70
- @string << (
71
- if @string.empty?
72
- str
73
- else
74
- "\n" << str
75
- end
76
- )
160
+ (@data[:raw] ||= "") << str
161
+ end
162
+
163
+ def has_raw?
164
+ !!(@data[:raw] && !@data[:raw].strip.empty?)
77
165
  end
78
166
 
79
167
  def to_pair
80
- [to_sql, vars]
168
+ [to_sql, vars!]
169
+ end
170
+
171
+ def to_meta *args
172
+ compile *args
173
+ end
174
+
175
+ def to_sql *args
176
+ compile(*args)[:SQL]
177
+ end
178
+
179
+ %w{ FRAGMENT SQL }.each { |k|
180
+ eval <<-EOF.strip, nil, __FILE__, __LINE__ + 1
181
+ def #{k}
182
+ return @#{k} if @SQL
183
+ to_meta[:#{k}]
184
+ end
185
+ EOF
186
+ }
187
+
188
+ private # ==========================================
189
+
190
+ def prefix_raw sym
191
+ "raw_#{sym.to_s.split('_').first}".to_sym
81
192
  end
82
193
 
83
- def to_sql
84
- s = @string.dup
85
- ctes = []
194
+ def prefix sym
195
+ sym.to_s.split('_').first.to_sym
196
+ end
197
+
198
+ protected(
199
+ def table_name one, two = nil
200
+ if two
201
+ k = two
202
+ link_name = one
203
+ else
204
+ k = one
205
+ link_name = nil
206
+ end
207
+
208
+ case
209
+ when [:out_ftable, :in_ftable].include?(k)
210
+ "#{real_table}_#{ meta[prefix(k)] }_#{meta[k]}"
211
+
212
+ when link? && link_name && @data[link_name][:inner_join].include?(k)
213
+ "#{self.name}_#{@data[link_name][:name]}_#{k}"
214
+
215
+ else
216
+ fail ArgumentError, "Unknown key for table name: #{k.inspect}"
217
+ end
218
+ end
219
+ ) # === protected
220
+
221
+ #
222
+ # Examples:
223
+ #
224
+ # field :in, :owner_id
225
+ # field :screen_name, :screen_name
226
+ # field :in
227
+ # field :raw_in
228
+ #
229
+ protected(
230
+ def field *args
231
+
232
+ case args
233
+ when [:out], [:in]
234
+ "#{name}.#{data[args.last][:name]}"
235
+ when [:raw, :out], [:raw, :in]
236
+ "#{name}.#{self[:DEFAULT].data[args.last]}"
237
+ when [:owner_id], [:type_id]
238
+ "#{name}.#{args.first}"
239
+ else
240
+ fail ArgumentError, "Unknown args: #{args.inspect}"
241
+ end # === case
242
+
243
+ end # === def field
244
+ )
86
245
 
87
- while s[HAS_VAR]
246
+ protected def compile target = nil
247
+ return(self[target].compile) if target && target != name
248
+
249
+ if !@SQL
250
+ compile_raw
251
+ end
252
+
253
+ {FRAGMENT: @FRAGMENT, SQL: @SQL, WITH: @WITH, VARS: vars!}
254
+ end # === def to_sql
255
+
256
+ def compile_raw
257
+ @data[:raw].freeze
258
+
259
+ s = @FRAGMENT = @data[:raw].dup
260
+
261
+ while s[HAS_VAR]
88
262
  s.gsub!(/\{\{\s?([a-zA-Z0-9\_]+)\s?\}\}/) do |match|
89
263
  key = $1.to_sym
90
- ctes << key
264
+ @WITHS << key
91
265
  key
92
266
  end
93
267
 
94
268
  s.gsub!(/\<\<\s?([a-zA-Z0-9\_\-\ \*]+)\s?\>\>/) do |match|
95
269
  tokens = $1.split
96
- key = tokens.pop.to_sym
97
- field = tokens.empty? ? nil : tokens.join(' ')
98
270
 
99
- case
100
- when field
101
- ctes << key
102
- "SELECT #{field} FROM #{key}"
271
+ key = tokens.last.to_sym
272
+
273
+ if has_key?(key)
274
+
275
+ tokens.pop
276
+ target = self[key]
277
+
278
+ if target.is_a?(Proc)
279
+ target.call self, *tokens
280
+ else
281
+ field = tokens.empty? ? nil : tokens.join(' ')
282
+
283
+ if field
284
+ @WITHS << key
285
+ tokens.pop
286
+ "SELECT #{field} FROM #{key}"
287
+ else
288
+ target.to_sql
289
+ end
290
+ end
291
+
292
+ elsif has_key?(tokens.first.to_sym)
293
+ self[tokens.shift.to_sym].call self, *tokens
294
+
103
295
  else
104
- self[key]
105
- end
296
+ fail ArgumentError, "Not found: #{$1}"
297
+
298
+ end # === if has_key?
106
299
  end
300
+ end # === while s HAS_VAR
301
+
302
+ @WITH = if @WITHS.empty?
303
+ ""
304
+ else
305
+ %^WITH\n #{WITH()}\n\n^
306
+ end
307
+
308
+ @SQL = (@WITH + @FRAGMENT).strip
309
+ end # === fragments_to_raw
310
+
311
+ def WITH
312
+ withs = @WITHS.dup
313
+ maps = []
314
+ done = {}
315
+ while name = withs.shift
316
+ next if done[name]
317
+ if name == :DEFAULT || !self.has_key?(name)
318
+ done[name] = true
319
+ next
320
+ end
321
+
322
+ fragment = self[name].FRAGMENT
323
+ fragment.gsub!(/^/, " ") if ENV['IS_DEV']
324
+ maps << "#{name} AS (\n#{fragment}\n )"
325
+ withs.concat self[name].WITHS
326
+ done[name] = true
327
+ end # === while name
328
+
329
+ maps.join ",\n "
330
+ end
331
+
332
+ def WHERE
333
+ wheres = @data[:WHERE].dup
334
+
335
+ if link? && name != :block
336
+ wheres << "#{field :type_id} = :#{name.to_s.upcase}_TYPE_ID"
337
+
338
+ pattern = [ data[:out][:inner_join], data[:in][:inner_join] ]
339
+ left, mid = data[:out][:inner_join]
340
+ right, _ = *data[:in][:inner_join]
341
+
342
+
343
+ case
344
+ when [[:screen_name], [:screen_name]], [[:screen_name, :computer],[:screen_name]]
345
+ # do nothing
346
+ else
347
+ fail "Programmer Error: Permissions not implemented for: #{pattern.inspect}"
348
+ end # === case
349
+
350
+ if left == :screen_name && right == :screen_name
351
+ default = self[:DEFAULT]
352
+ block = self[:block]
353
+ blocked = block.table_name(:out, :screen_name)
354
+ victim = block.table_name(:in, :screen_name)
355
+ f_in = table_name :in, :screen_name
356
+ f_out = table_name :out, :screen_name
357
+
358
+ wheres << %^
359
+ NOT EXISTS (
360
+ SELECT 1
361
+ FROM #{block.real_table} AS block
362
+ WHERE
363
+ (
364
+ block.type_id = :BLOCK_SCREEN_TYPE_ID
365
+ AND (
366
+ (
367
+ #{f_out}.owner_id = #{block.field :out}
368
+ AND
369
+ #{f_in}.owner_id = #{victim}.owner_id
370
+ )
371
+ OR
372
+ (
373
+ #{field :raw, :in} = #{block.field :out}
374
+ AND
375
+ #{blocked}.owner_id = #{victim}.owner_id
376
+ )
377
+ )
378
+ )
379
+ OR
380
+ (
381
+ block.type_id = :BLOCK_OWNER_TYPE_ID
382
+ AND (
383
+ (
384
+ #{f_out}.owner_id = #{blocked}.owner_id
385
+ AND
386
+ #{f_in}.owner_id = #{victim}.owner_id
387
+ )
388
+ OR
389
+ (
390
+ #{f_in}.owner_id = #{blocked}.owner_id
391
+ AND
392
+ #{f_out}.owner_id = #{victim}.owner_id
393
+ )
394
+ ) -- AND
395
+ )
396
+ ) -- NOT EXISTS
397
+ ^
398
+ end # === if :screen_name, :screen_name
399
+
400
+ if mid == :computer
401
+ asql(wheres.last)
402
+ fail "COMPUTER not ready"
403
+ end
404
+
405
+ end # === if link?
406
+
407
+ if @data.has_key?(:OF)
408
+ table = self[@data[:FROM].first]
409
+ wheres << "#{table.field(:out)} = #{@data[:OF].first}"
107
410
  end
108
411
 
109
- return s if ctes.empty?
110
-
111
- %^
112
- WITH
113
- #{ctes.uniq.map { |k| "#{k} AS (
114
- #{self[k]}
115
- )" }.join "
116
- ,
117
- "}
118
- #{s}
119
- ^
412
+ if false && @data[:NOT_EXISTS]
413
+ block = self[meta[:not_exists]]
414
+ w = ""
415
+ w << %^ NOT EXISTS (\n^
416
+ w << %^ SELECT 1\n^
417
+ w << %^ FROM #{meta[:not_exists]}\n^
418
+ w << %^ WHERE\n^
419
+
420
+ conds = []
421
+ block[:where].each { |block_meta|
422
+ type_id = block_meta.first
423
+ c = ""
424
+ c << %^ (\n^
425
+ c << " #{field meta, block_meta[1][1], block_meta[1][2]} = #{field block, block_meta[2][1], block_meta[2][2]}\n"
426
+ c << " AND\n"
427
+ c << " #{meta[:not_exists]}.type_id = :#{type_id}_TYPE_ID\n"
428
+ c << " AND\n"
429
+ c << " #{field meta, block_meta[3][1], block_meta[3][2]} = #{field block, block_meta[4][1], block_meta[4][2]}\n"
430
+ c << %^ )\n^
431
+ conds << c
432
+ }
433
+
434
+ w << conds.join(" OR\n")
435
+ sql[:WHERE] << w
436
+ end
437
+
438
+
439
+ return nil if wheres.empty?
440
+ wheres.join " AND "
120
441
  end
121
442
 
443
+ # === END: Rendering DSL ==========================================
444
+
122
445
  end # === class I_Dig_Sql ===
123
446
 
124
447