querybuilder 0.5.5 → 0.5.6
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -6
- data/Manifest.txt +2 -1
- data/README.rdoc +1 -1
- data/Rakefile +2 -2
- data/lib/{QueryBuilder.rb → query_builder.rb} +101 -101
- data/lib/querybuilder.rb +1 -0
- metadata +15 -8
data/History.txt
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
== 0.5.
|
2
|
-
|
3
|
-
*
|
4
|
-
* Added 'use_name' to 'add_table' so you can add tables under a different name
|
1
|
+
== 0.5.6 2009-10-15
|
2
|
+
* 1 minor enhancement
|
3
|
+
* Fixed library name (was not loaded on case sensitive systems)
|
5
4
|
|
6
5
|
== 0.5.4 2009-04-09
|
7
6
|
|
@@ -18,12 +17,12 @@
|
|
18
17
|
* 1 minor enhancement:
|
19
18
|
* Added support for main_table option in custom queries
|
20
19
|
* More tests for custom queries
|
21
|
-
|
20
|
+
|
22
21
|
== 0.5.1 2009-03-03
|
23
22
|
|
24
23
|
* 1 minor enhancement:
|
25
24
|
* Added support for glob directories in load_custom_queries
|
26
|
-
|
25
|
+
|
27
26
|
== 0.5.0 2009-01-23
|
28
27
|
|
29
28
|
* 1 major enhancement:
|
data/Manifest.txt
CHANGED
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
%w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
2
|
-
require File.dirname(__FILE__) + '/lib/
|
2
|
+
require File.dirname(__FILE__) + '/lib/query_builder'
|
3
3
|
|
4
4
|
# Generate all the Rake tasks
|
5
5
|
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
@@ -13,7 +13,7 @@ $hoe = Hoe.new('querybuilder', QueryBuilder::VERSION) do |p|
|
|
13
13
|
p.extra_dev_deps = [
|
14
14
|
['newgem', ">= #{::Newgem::VERSION}"]
|
15
15
|
]
|
16
|
-
|
16
|
+
|
17
17
|
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
18
18
|
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
19
19
|
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
@@ -10,24 +10,24 @@ Syntax of a query is "RELATION [where ...|] [in ...|from SUB_QUERY|]".
|
|
10
10
|
=end
|
11
11
|
class QueryBuilder
|
12
12
|
attr_reader :tables, :where, :errors, :join_tables, :distinct, :final_parser, :page_size
|
13
|
-
VERSION = '0.5.
|
14
|
-
|
13
|
+
VERSION = '0.5.6'
|
14
|
+
|
15
15
|
@@main_table = {}
|
16
16
|
@@main_class = {}
|
17
17
|
@@custom_queries = {}
|
18
|
-
|
18
|
+
|
19
19
|
class << self
|
20
20
|
# This is the table name of the main class.
|
21
21
|
def set_main_table(table_name)
|
22
22
|
@@main_table[self] = table_name.to_s
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# This is the class of the returned elements if there is no class change in the query. This
|
26
26
|
# should correspond to the class used to build call "Foo.find_by_sql(...)" (Foo).
|
27
27
|
def set_main_class(main_class)
|
28
28
|
@@main_class[self] = main_class.to_s
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# Load prepared SQL definitions from a set of directories. If the file does not contain "host" or "hosts" keys,
|
32
32
|
# the filename is used as host.
|
33
33
|
#
|
@@ -87,11 +87,11 @@ class QueryBuilder
|
|
87
87
|
rescue NameError => err
|
88
88
|
raise ArgumentError.new("invalid class for CustomQueries (#{klass})")
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
# Return the parser built from the query. The class of the returned object can be different
|
92
92
|
# from the class used to call "new". For example: NodeQuery.new("comments from nodes in project") would
|
93
93
|
# return a CommentQuery since that is the final fetched objects (final_parser).
|
94
|
-
#
|
94
|
+
#
|
95
95
|
# ==== Parameters
|
96
96
|
# query<String>:: Pseudo sql query string.
|
97
97
|
# opts<Hash>:: List of options.
|
@@ -111,7 +111,7 @@ class QueryBuilder
|
|
111
111
|
obj.final_parser
|
112
112
|
end
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
# Build a new query from a pseudo sql string. See QueryBuilder::new for details.
|
116
116
|
def initialize(query, opts = {})
|
117
117
|
if opts[:pre_query]
|
@@ -119,10 +119,10 @@ class QueryBuilder
|
|
119
119
|
else
|
120
120
|
init_with_query(query, opts)
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
parse_elements(@elements)
|
124
124
|
end
|
125
|
-
|
125
|
+
|
126
126
|
# Convert query object to a string. This string should then be evaluated.
|
127
127
|
#
|
128
128
|
# ==== Parameters
|
@@ -147,7 +147,7 @@ class QueryBuilder
|
|
147
147
|
statement, bind_values = build_statement(type)
|
148
148
|
bind_values.empty? ? "\"#{statement}\"" : "[#{[["\"#{statement}\""] + bind_values].join(', ')}]"
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
# Convert the query object into an SQL query.
|
152
152
|
#
|
153
153
|
# ==== Parameters
|
@@ -171,8 +171,8 @@ class QueryBuilder
|
|
171
171
|
connection = get_connection(bindings)
|
172
172
|
statement.gsub('?') { eval_bound_value(bind_values.shift, connection, bindings) }
|
173
173
|
end
|
174
|
-
|
175
|
-
|
174
|
+
|
175
|
+
|
176
176
|
# Test query validity
|
177
177
|
#
|
178
178
|
# ==== Returns
|
@@ -180,7 +180,7 @@ class QueryBuilder
|
|
180
180
|
def valid?
|
181
181
|
@errors == []
|
182
182
|
end
|
183
|
-
|
183
|
+
|
184
184
|
# Name of the pagination key when 'paginate' is used.
|
185
185
|
#
|
186
186
|
# ==== Parameters
|
@@ -195,7 +195,7 @@ class QueryBuilder
|
|
195
195
|
def pagination_key
|
196
196
|
@offset_limit_order_group[:paginate]
|
197
197
|
end
|
198
|
-
|
198
|
+
|
199
199
|
# Main class for the query (useful when queries move from class to class)
|
200
200
|
#
|
201
201
|
# ==== Returns
|
@@ -207,22 +207,22 @@ class QueryBuilder
|
|
207
207
|
def main_class
|
208
208
|
Module.const_get(@@main_class[self.class])
|
209
209
|
end
|
210
|
-
|
210
|
+
|
211
211
|
protected
|
212
|
-
|
212
|
+
|
213
213
|
def current_table
|
214
214
|
@current_table || main_table
|
215
215
|
end
|
216
|
-
|
216
|
+
|
217
217
|
def main_table
|
218
218
|
@main_table || @@main_table[self.class]
|
219
219
|
end
|
220
|
-
|
220
|
+
|
221
221
|
def parse_part(part, is_last)
|
222
|
-
|
222
|
+
|
223
223
|
rest, context = part.split(' in ')
|
224
224
|
clause, filters = rest.split(/\s+where\s+/)
|
225
|
-
|
225
|
+
|
226
226
|
if @just_changed_class
|
227
227
|
# just changed class: parse filters && context
|
228
228
|
parse_filters(filters) if filters
|
@@ -243,7 +243,7 @@ class QueryBuilder
|
|
243
243
|
return nil
|
244
244
|
end
|
245
245
|
end
|
246
|
-
|
246
|
+
|
247
247
|
def parse_filters(clause)
|
248
248
|
# TODO: add 'match' parameter (#105)
|
249
249
|
rest = clause.strip
|
@@ -261,15 +261,15 @@ class QueryBuilder
|
|
261
261
|
res << " "
|
262
262
|
elsif rest[0..0] == '('
|
263
263
|
unless allowed.include?(:par_open)
|
264
|
-
@errors << clause_error(clause, rest, res)
|
264
|
+
@errors << clause_error(clause, rest, res)
|
265
265
|
return
|
266
266
|
end
|
267
267
|
res << '('
|
268
268
|
rest = rest[1..-1]
|
269
269
|
par_count += 1
|
270
|
-
elsif rest[0..0] == ')'
|
270
|
+
elsif rest[0..0] == ')'
|
271
271
|
unless allowed.include?(:par_close)
|
272
|
-
@errors << clause_error(clause, rest, res)
|
272
|
+
@errors << clause_error(clause, rest, res)
|
273
273
|
return
|
274
274
|
end
|
275
275
|
res << ')'
|
@@ -282,7 +282,7 @@ class QueryBuilder
|
|
282
282
|
allowed = [:op, :bool_op]
|
283
283
|
elsif rest =~ /\A((>=|<=|<>|\!=|<|=|>)|((not\s+like|like|lt|le|eq|ne|ge|gt)\s+))/
|
284
284
|
unless allowed.include?(:op)
|
285
|
-
@errors << clause_error(clause, rest, res)
|
285
|
+
@errors << clause_error(clause, rest, res)
|
286
286
|
return
|
287
287
|
end
|
288
288
|
op = $1.strip
|
@@ -290,9 +290,9 @@ class QueryBuilder
|
|
290
290
|
op = {'lt' => '<', 'le' => '<=', 'eq' => '=', 'ne' => '<>', '!=' => '<>', 'ge' => '>=', 'gt' => '>', 'like' => 'LIKE', 'not like' => 'NOT LIKE'}[op] || $1
|
291
291
|
res << op
|
292
292
|
allowed = [:value, :par_open]
|
293
|
-
elsif rest =~ /\A("|')([^\1]*?)\1/
|
293
|
+
elsif rest =~ /\A("|')([^\1]*?)\1/
|
294
294
|
unless allowed.include?(:value)
|
295
|
-
@errors << clause_error(clause, rest, res)
|
295
|
+
@errors << clause_error(clause, rest, res)
|
296
296
|
return
|
297
297
|
end
|
298
298
|
rest = rest[$&.size..-1]
|
@@ -300,7 +300,7 @@ class QueryBuilder
|
|
300
300
|
allowed = after_value
|
301
301
|
elsif rest =~ /\A(\d+|[\w:]+)\s+(second|minute|hour|day|week|month|year)s?/
|
302
302
|
unless allowed.include?(:value)
|
303
|
-
@errors << clause_error(clause, rest, res)
|
303
|
+
@errors << clause_error(clause, rest, res)
|
304
304
|
return
|
305
305
|
end
|
306
306
|
rest = rest[$&.size..-1]
|
@@ -311,9 +311,9 @@ class QueryBuilder
|
|
311
311
|
end
|
312
312
|
res << "INTERVAL #{field} #{type.upcase}"
|
313
313
|
allowed = after_value
|
314
|
-
elsif rest =~ /\A(-?\d+)/
|
314
|
+
elsif rest =~ /\A(-?\d+)/
|
315
315
|
unless allowed.include?(:value)
|
316
|
-
@errors << clause_error(clause, rest, res)
|
316
|
+
@errors << clause_error(clause, rest, res)
|
317
317
|
return
|
318
318
|
end
|
319
319
|
rest = rest[$&.size..-1]
|
@@ -321,23 +321,23 @@ class QueryBuilder
|
|
321
321
|
allowed = after_value
|
322
322
|
elsif rest =~ /\A(is\s+not\s+null|is\s+null)/
|
323
323
|
unless allowed.include?(:bool_op)
|
324
|
-
@errors << clause_error(clause, rest, res)
|
324
|
+
@errors << clause_error(clause, rest, res)
|
325
325
|
return
|
326
326
|
end
|
327
327
|
rest = rest[$&.size..-1]
|
328
328
|
res << $1.upcase
|
329
329
|
allowed = [:par_close, :bool_op]
|
330
|
-
elsif rest[0..7] == 'REF_DATE'
|
330
|
+
elsif rest[0..7] == 'REF_DATE'
|
331
331
|
unless allowed.include?(:value)
|
332
|
-
@errors << clause_error(clause, rest, res)
|
332
|
+
@errors << clause_error(clause, rest, res)
|
333
333
|
return
|
334
334
|
end
|
335
335
|
rest = rest[8..-1]
|
336
336
|
res << @ref_date
|
337
337
|
allowed = after_value
|
338
|
-
elsif rest =~ /\A(\+|\-)/
|
338
|
+
elsif rest =~ /\A(\+|\-)/
|
339
339
|
unless allowed.include?(:op)
|
340
|
-
@errors << clause_error(clause, rest, res)
|
340
|
+
@errors << clause_error(clause, rest, res)
|
341
341
|
return
|
342
342
|
end
|
343
343
|
rest = rest[$&.size..-1]
|
@@ -345,7 +345,7 @@ class QueryBuilder
|
|
345
345
|
allowed = [:value, :par_open]
|
346
346
|
elsif rest =~ /\A(and|or)/
|
347
347
|
unless allowed.include?(:bool_op)
|
348
|
-
@errors << clause_error(clause, rest, res)
|
348
|
+
@errors << clause_error(clause, rest, res)
|
349
349
|
return
|
350
350
|
end
|
351
351
|
rest = rest[$&.size..-1]
|
@@ -354,7 +354,7 @@ class QueryBuilder
|
|
354
354
|
allowed = [:par_open, :value]
|
355
355
|
elsif rest =~ /\A[\w:]+/
|
356
356
|
unless allowed.include?(:value)
|
357
|
-
@errors << clause_error(clause, rest, res)
|
357
|
+
@errors << clause_error(clause, rest, res)
|
358
358
|
return
|
359
359
|
end
|
360
360
|
rest = rest[$&.size..-1]
|
@@ -365,12 +365,12 @@ class QueryBuilder
|
|
365
365
|
end
|
366
366
|
res << field
|
367
367
|
allowed = after_value
|
368
|
-
else
|
368
|
+
else
|
369
369
|
@errors << clause_error(clause, rest, res)
|
370
370
|
return
|
371
371
|
end
|
372
372
|
end
|
373
|
-
|
373
|
+
|
374
374
|
if par_count > 0
|
375
375
|
@errors << "invalid clause #{clause.inspect}: missing closing ')'"
|
376
376
|
elsif allowed.include?(:value)
|
@@ -379,11 +379,11 @@ class QueryBuilder
|
|
379
379
|
@where << (has_or ? "(#{res})" : res)
|
380
380
|
end
|
381
381
|
end
|
382
|
-
|
382
|
+
|
383
383
|
def parse_order_clause(order)
|
384
384
|
return @order unless order
|
385
385
|
res = []
|
386
|
-
|
386
|
+
|
387
387
|
order.split(',').each do |clause|
|
388
388
|
if clause == 'random'
|
389
389
|
res << "RAND()"
|
@@ -403,11 +403,11 @@ class QueryBuilder
|
|
403
403
|
end
|
404
404
|
res == [] ? nil : " ORDER BY #{res.join(', ')}"
|
405
405
|
end
|
406
|
-
|
406
|
+
|
407
407
|
def parse_group_clause(group)
|
408
408
|
return @group unless group
|
409
409
|
res = []
|
410
|
-
|
410
|
+
|
411
411
|
group.split(',').each do |field|
|
412
412
|
if fld = map_field(field, table, :group)
|
413
413
|
res << fld
|
@@ -417,7 +417,7 @@ class QueryBuilder
|
|
417
417
|
end
|
418
418
|
res == [] ? nil : " GROUP BY #{res.join(', ')}"
|
419
419
|
end
|
420
|
-
|
420
|
+
|
421
421
|
def parse_limit_clause(limit)
|
422
422
|
return @limit unless limit
|
423
423
|
if limit.kind_of?(Fixnum)
|
@@ -432,7 +432,7 @@ class QueryBuilder
|
|
432
432
|
nil
|
433
433
|
end
|
434
434
|
end
|
435
|
-
|
435
|
+
|
436
436
|
def parse_paginate_clause(paginate)
|
437
437
|
return @offset unless paginate
|
438
438
|
if !@limit
|
@@ -447,7 +447,7 @@ class QueryBuilder
|
|
447
447
|
nil
|
448
448
|
end
|
449
449
|
end
|
450
|
-
|
450
|
+
|
451
451
|
def parse_offset_clause(offset)
|
452
452
|
return @offset unless offset
|
453
453
|
if !@limit
|
@@ -461,7 +461,7 @@ class QueryBuilder
|
|
461
461
|
nil
|
462
462
|
end
|
463
463
|
end
|
464
|
-
|
464
|
+
|
465
465
|
def add_table(use_name, table_name = nil)
|
466
466
|
table_name ||= use_name
|
467
467
|
if !@table_counter[use_name]
|
@@ -471,12 +471,12 @@ class QueryBuilder
|
|
471
471
|
else
|
472
472
|
@tables << table_name
|
473
473
|
end
|
474
|
-
else
|
474
|
+
else
|
475
475
|
@table_counter[use_name] += 1
|
476
476
|
@tables << "#{table_name} AS #{table(use_name)}"
|
477
477
|
end
|
478
478
|
end
|
479
|
-
|
479
|
+
|
480
480
|
# return a unique table name for the current sub-query context, adding the table when necessary
|
481
481
|
def needs_table(table1, table2, filter)
|
482
482
|
@needed_tables[table2] ||= {}
|
@@ -486,16 +486,16 @@ class QueryBuilder
|
|
486
486
|
table(table2)
|
487
487
|
end
|
488
488
|
end
|
489
|
-
|
489
|
+
|
490
490
|
# versions LEFT JOIN dyn_attributes ON ...
|
491
491
|
def needs_join_table(table1, type, table2, clause, join_name = nil)
|
492
492
|
join_name ||= "#{table1}=#{type}=#{table2}"
|
493
493
|
@needed_join_tables[join_name] ||= {}
|
494
494
|
@needed_join_tables[join_name][table] ||= begin
|
495
495
|
# define join for this part ('table' = unique for each part)
|
496
|
-
|
496
|
+
|
497
497
|
first_table = table(table1)
|
498
|
-
|
498
|
+
|
499
499
|
if !@table_counter[table2]
|
500
500
|
@table_counter[table2] = 0
|
501
501
|
second_table = table2
|
@@ -508,22 +508,22 @@ class QueryBuilder
|
|
508
508
|
table(table2)
|
509
509
|
end
|
510
510
|
end
|
511
|
-
|
511
|
+
|
512
512
|
def table_counter(table_name)
|
513
513
|
@table_counter[table_name] || 0
|
514
514
|
end
|
515
|
-
|
515
|
+
|
516
516
|
def table_at(table_name, index)
|
517
517
|
if index < 0
|
518
518
|
return nil # no table at this address
|
519
519
|
end
|
520
520
|
index == 0 ? table_name : "#{table_name[0..1]}#{index}"
|
521
521
|
end
|
522
|
-
|
522
|
+
|
523
523
|
def table(table_name=main_table, index=0)
|
524
524
|
table_at(table_name, table_counter(table_name) + index)
|
525
525
|
end
|
526
|
-
|
526
|
+
|
527
527
|
def merge_alternate_queries(alt_queries)
|
528
528
|
counter = 1
|
529
529
|
if valid?
|
@@ -539,13 +539,13 @@ class QueryBuilder
|
|
539
539
|
end
|
540
540
|
counter = 0
|
541
541
|
end
|
542
|
-
|
542
|
+
|
543
543
|
if @where.compact == []
|
544
544
|
where_list = []
|
545
545
|
else
|
546
546
|
where_list = [@where.compact.reverse.join(' AND ')]
|
547
547
|
end
|
548
|
-
|
548
|
+
|
549
549
|
alt_queries.each do |query|
|
550
550
|
next unless query.main_class == self.main_class # no mixed class target !
|
551
551
|
@errors += query.errors unless @ignore_warnings
|
@@ -557,13 +557,13 @@ class QueryBuilder
|
|
557
557
|
@distinct ||= query.distinct
|
558
558
|
where_list << query.where.reverse.join(' AND ')
|
559
559
|
end
|
560
|
-
|
560
|
+
|
561
561
|
@where_list = where_list
|
562
|
-
|
562
|
+
|
563
563
|
@tables.uniq!
|
564
|
-
|
564
|
+
|
565
565
|
fix_where_list(where_list)
|
566
|
-
|
566
|
+
|
567
567
|
if counter > 1
|
568
568
|
@distinct = @tables.size > 1
|
569
569
|
@where = ["((#{where_list.join(') OR (')}))"]
|
@@ -571,7 +571,7 @@ class QueryBuilder
|
|
571
571
|
@where = where_list
|
572
572
|
end
|
573
573
|
end
|
574
|
-
|
574
|
+
|
575
575
|
def merge_tables(sub_query)
|
576
576
|
@tables += sub_query.tables
|
577
577
|
sub_query.join_tables.each do |k,v|
|
@@ -579,7 +579,7 @@ class QueryBuilder
|
|
579
579
|
@join_tables[k] << v
|
580
580
|
end
|
581
581
|
end
|
582
|
-
|
582
|
+
|
583
583
|
def prepare_custom_query_arguments(key, value)
|
584
584
|
if value.kind_of?(Array)
|
585
585
|
value.map {|e| parse_custom_query_argument(key, e)}
|
@@ -595,7 +595,7 @@ class QueryBuilder
|
|
595
595
|
parse_custom_query_argument(key, value)
|
596
596
|
end
|
597
597
|
end
|
598
|
-
|
598
|
+
|
599
599
|
# Map a field to be used inside a query. An attr is a field from table at index 0 = @node attribute.
|
600
600
|
def field_or_attr(fld, table_name = table, context = nil)
|
601
601
|
if fld =~ /^\d+$/
|
@@ -612,7 +612,7 @@ class QueryBuilder
|
|
612
612
|
map_attr(fld)
|
613
613
|
end
|
614
614
|
end
|
615
|
-
|
615
|
+
|
616
616
|
def build_statement(type = :find)
|
617
617
|
statement = type == :find ? find_statement : count_statement
|
618
618
|
|
@@ -624,7 +624,7 @@ class QueryBuilder
|
|
624
624
|
end
|
625
625
|
[statement, bind_values]
|
626
626
|
end
|
627
|
-
|
627
|
+
|
628
628
|
def find_statement
|
629
629
|
table_list = []
|
630
630
|
@tables.each do |t|
|
@@ -644,7 +644,7 @@ class QueryBuilder
|
|
644
644
|
|
645
645
|
"SELECT #{@select.join(',')} FROM #{table_list.flatten.join(',')}" + (@where == [] ? '' : " WHERE #{@where.reverse.join(' AND ')}") + group.to_s + @order.to_s + @limit.to_s + @offset.to_s
|
646
646
|
end
|
647
|
-
|
647
|
+
|
648
648
|
def count_statement
|
649
649
|
table_list = []
|
650
650
|
@tables.each do |t|
|
@@ -668,7 +668,7 @@ class QueryBuilder
|
|
668
668
|
|
669
669
|
"SELECT #{count_on} FROM #{table_list.flatten.join(',')}" + (@where == [] ? '' : " WHERE #{@where.reverse.join(' AND ')}")
|
670
670
|
end
|
671
|
-
|
671
|
+
|
672
672
|
# Adapted from Rail's ActiveRecord code. We need "eval" because
|
673
673
|
# QueryBuilder is a compiler and it has absolutely no knowledge
|
674
674
|
# of the running context.
|
@@ -693,47 +693,47 @@ class QueryBuilder
|
|
693
693
|
def class_from_table(table_name)
|
694
694
|
Object
|
695
695
|
end
|
696
|
-
|
696
|
+
|
697
697
|
def default_context_filter
|
698
698
|
raise NameError.new("default_context_filter not defined for class #{self.class}")
|
699
699
|
end
|
700
|
-
|
700
|
+
|
701
701
|
# Default sort order
|
702
702
|
def default_order_clause
|
703
703
|
nil
|
704
704
|
end
|
705
|
-
|
705
|
+
|
706
706
|
def after_parse
|
707
707
|
# do nothing
|
708
708
|
end
|
709
|
-
|
709
|
+
|
710
710
|
def parse_change_class(rel, is_last)
|
711
711
|
nil
|
712
712
|
end
|
713
|
-
|
713
|
+
|
714
714
|
def parse_relation(clause, context)
|
715
715
|
return nil
|
716
716
|
end
|
717
|
-
|
717
|
+
|
718
718
|
def context_filter_fields(clause, is_last = false)
|
719
719
|
nil
|
720
720
|
end
|
721
|
-
|
721
|
+
|
722
722
|
def parse_context(clause, is_last = false)
|
723
|
-
|
723
|
+
|
724
724
|
if fields = context_filter_fields(clause, is_last)
|
725
725
|
@where << "#{field_or_attr(fields[0])} = #{field_or_attr(fields[1], table(main_table,-1))}" if fields != :void
|
726
726
|
else
|
727
727
|
@errors << "invalid context '#{clause}'"
|
728
728
|
end
|
729
729
|
end
|
730
|
-
|
730
|
+
|
731
731
|
# Map a litteral value to be used inside a query
|
732
732
|
def map_literal(value, env = :sql)
|
733
733
|
env == :sql ? insert_bind(value.inspect) : value
|
734
734
|
end
|
735
|
-
|
736
|
-
|
735
|
+
|
736
|
+
|
737
737
|
# Overwrite this and take car to check for valid fields.
|
738
738
|
def map_field(fld, table_name, context = nil)
|
739
739
|
if fld == 'id'
|
@@ -742,11 +742,11 @@ class QueryBuilder
|
|
742
742
|
# TODO: error, raise / ignore ?
|
743
743
|
end
|
744
744
|
end
|
745
|
-
|
745
|
+
|
746
746
|
def map_attr(fld)
|
747
747
|
insert_bind(fld.to_s)
|
748
748
|
end
|
749
|
-
|
749
|
+
|
750
750
|
# ******** And maybe overwrite these **********
|
751
751
|
def parse_custom_query_argument(key, value)
|
752
752
|
return nil unless value
|
@@ -760,19 +760,19 @@ class QueryBuilder
|
|
760
760
|
value
|
761
761
|
end
|
762
762
|
end
|
763
|
-
|
763
|
+
|
764
764
|
def extract_custom_query(list)
|
765
765
|
list[-1].split(' ').first
|
766
766
|
end
|
767
|
-
|
767
|
+
|
768
768
|
private
|
769
|
-
|
769
|
+
|
770
770
|
def parse_elements(elements)
|
771
771
|
# "final_parser" is the parser who will respond to 'to_sql'. It might be a sub-parser for another class.
|
772
772
|
@final_parser = self
|
773
|
-
|
774
|
-
if @@custom_queries[self.class] &&
|
775
|
-
@@custom_queries[self.class][@opts[:custom_query_group]] &&
|
773
|
+
|
774
|
+
if @@custom_queries[self.class] &&
|
775
|
+
@@custom_queries[self.class][@opts[:custom_query_group]] &&
|
776
776
|
custom_query = @@custom_queries[self.class][@opts[:custom_query_group]][extract_custom_query(elements)]
|
777
777
|
custom_query.each do |k,v|
|
778
778
|
instance_variable_set("@#{k}", prepare_custom_query_arguments(k.to_sym, v))
|
@@ -787,21 +787,21 @@ class QueryBuilder
|
|
787
787
|
clause, filters = elements[-1].split(/\s+where\s+/)
|
788
788
|
|
789
789
|
parse_filters(filters) if filters
|
790
|
-
|
790
|
+
|
791
791
|
@limit = parse_limit_clause(@offset_limit_order_group[:limit])
|
792
792
|
if @offset_limit_order_group[:paginate]
|
793
793
|
@offset = parse_paginate_clause(@offset_limit_order_group[:paginate])
|
794
794
|
else
|
795
795
|
@offset = parse_offset_clause(@offset_limit_order_group[:offset])
|
796
796
|
end
|
797
|
-
|
797
|
+
|
798
798
|
@order = parse_order_clause(@offset_limit_order_group[:order])
|
799
799
|
else
|
800
800
|
i, new_class = 0, nil
|
801
801
|
elements.each_index do |i|
|
802
802
|
break if new_class = parse_part(elements[i], i == 0) # yes, is_last is first (parsing reverse)
|
803
803
|
end
|
804
|
-
|
804
|
+
|
805
805
|
if new_class
|
806
806
|
# move to another parser class
|
807
807
|
@final_parser = new_class.new(nil, :pre_query => self, :elements => elements[i..-1])
|
@@ -829,10 +829,10 @@ class QueryBuilder
|
|
829
829
|
@where.compact!
|
830
830
|
end
|
831
831
|
end
|
832
|
-
|
832
|
+
|
833
833
|
def init_with_query(query, opts)
|
834
834
|
@opts = opts
|
835
|
-
|
835
|
+
|
836
836
|
if query.kind_of?(Array)
|
837
837
|
@query = query[0]
|
838
838
|
if query.size > 1
|
@@ -841,8 +841,8 @@ class QueryBuilder
|
|
841
841
|
else
|
842
842
|
@query = query
|
843
843
|
end
|
844
|
-
|
845
|
-
|
844
|
+
|
845
|
+
|
846
846
|
@offset_limit_order_group = {}
|
847
847
|
if @query == nil || @query == ''
|
848
848
|
elements = [main_table]
|
@@ -855,11 +855,11 @@ class QueryBuilder
|
|
855
855
|
last_element, @offset_limit_order_group[:order] = last_element.split(' order by ')
|
856
856
|
elements[-1], @offset_limit_order_group[:group] = last_element.split(' group by ')
|
857
857
|
end
|
858
|
-
|
858
|
+
|
859
859
|
@offset_limit_order_group[:limit] = opts[:limit] || @offset_limit_order_group[:limit]
|
860
860
|
# In order to know the table names of the dependencies, we need to parse it backwards.
|
861
861
|
# We first find the closest elements, then the final ones. For example, "pages from project" we need
|
862
|
-
# project information before getting 'pages'.
|
862
|
+
# project information before getting 'pages'.
|
863
863
|
@elements = elements.reverse
|
864
864
|
|
865
865
|
@tables = []
|
@@ -888,15 +888,15 @@ class QueryBuilder
|
|
888
888
|
@just_changed_class = true
|
889
889
|
@elements = elements
|
890
890
|
end
|
891
|
-
|
891
|
+
|
892
892
|
def clause_error(clause, rest, res)
|
893
893
|
"invalid clause #{clause.inspect} near \"#{res[-2..-1]}#{rest[0..1]}\""
|
894
894
|
end
|
895
|
-
|
895
|
+
|
896
896
|
def insert_bind(str)
|
897
897
|
"[[#{str}]]"
|
898
898
|
end
|
899
|
-
|
899
|
+
|
900
900
|
# Make sure all clauses are compatible (where_list is a list of strings, not arrays)
|
901
901
|
def fix_where_list(where_list)
|
902
902
|
# do nothing
|
data/lib/querybuilder.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'query_builder'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: querybuilder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gaspard Bucher
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-10-15 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -42,7 +42,12 @@ dependencies:
|
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: 1.8.0
|
44
44
|
version:
|
45
|
-
description:
|
45
|
+
description: |-
|
46
|
+
QueryBuilder is an interpreter for the "pseudo sql" language. This language
|
47
|
+
can be used for two purposes:
|
48
|
+
|
49
|
+
1. protect your database from illegal SQL by securing queries
|
50
|
+
2. ease writing complex relational queries by abstracting table internals
|
46
51
|
email:
|
47
52
|
- gaspard@teti.ch
|
48
53
|
executables: []
|
@@ -58,11 +63,11 @@ files:
|
|
58
63
|
- Manifest.txt
|
59
64
|
- README.rdoc
|
60
65
|
- Rakefile
|
61
|
-
- lib/
|
66
|
+
- lib/query_builder.rb
|
67
|
+
- lib/querybuilder.rb
|
62
68
|
- script/console
|
63
69
|
- script/destroy
|
64
70
|
- script/generate
|
65
|
-
- test/mock/custom_queries
|
66
71
|
- test/mock/custom_queries/test.yml
|
67
72
|
- test/mock/dummy_query.rb
|
68
73
|
- test/mock/user_query.rb
|
@@ -74,7 +79,9 @@ files:
|
|
74
79
|
- test/test_helper.rb
|
75
80
|
- test/test_QueryBuilder.rb
|
76
81
|
has_rdoc: true
|
77
|
-
homepage: http://
|
82
|
+
homepage: http://github.com/zena/querybuilder/tree/master
|
83
|
+
licenses: []
|
84
|
+
|
78
85
|
post_install_message:
|
79
86
|
rdoc_options:
|
80
87
|
- --main
|
@@ -96,9 +103,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
103
|
requirements: []
|
97
104
|
|
98
105
|
rubyforge_project: querybuilder
|
99
|
-
rubygems_version: 1.3.
|
106
|
+
rubygems_version: 1.3.5
|
100
107
|
signing_key:
|
101
|
-
specification_version:
|
108
|
+
specification_version: 3
|
102
109
|
summary: QueryBuilder is an interpreter for the "pseudo sql" language
|
103
110
|
test_files:
|
104
111
|
- test/test_helper.rb
|