querybuilder 0.5.5 → 0.5.6
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.
- 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
|