i_dig_sql 2.0.0 → 3.0.0

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