lafcadio 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,572 +0,0 @@
1
- # = Overview
2
- # By passing a block to ObjectStore, you can write complex, ad-hoc queries in
3
- # Ruby. This involves a few more keystrokes than writing raw SQL, but also makes
4
- # it easier to change queries at runtime, and these queries can also be fully
5
- # tested against the MockObjectStore.
6
- # big_invoices = object_store.getInvoices { |inv| inv.rate.gt( 50 ) }
7
- # # => "select * from invoices where rate > 50"
8
- # This a full-fledged block, so you can pass in values from the calling context.
9
- # date = Date.new( 2004, 1, 1 )
10
- # recent_invoices = object_store.getInvoices { |inv| inv.date.gt( date ) }
11
- # # => "select * from invoices where date > '2004-01-01'"
12
- #
13
- # = Query operators
14
- # You can compare fields either to simple values, or to other fields in the same
15
- # table.
16
- # paid_immediately = object_store.getInvoices { |inv|
17
- # inv.date.equals( inv.paid )
18
- # }
19
- # # => "select * from invoices where date = paid"
20
- #
21
- # == Numerical comparisons: +lt+, +lte+, +gte+, +gt+
22
- # +lt+, +lte+, +gte+, and +gt+ stand for "less than", "less than or equal",
23
- # "greater than or equal", and "greater than", respectively.
24
- # tiny_invoices = object_store.getInvoices { |inv| inv.rate.lte( 25 ) }
25
- # # => "select * from invoices where rate <= 25"
26
- # These comparators work on fields that contain numbers, dates, and even
27
- # references to other domain objects.
28
- # for_1st_ten_clients = object_store.getInvoices { |inv|
29
- # inv.client.lte( 10 )
30
- # }
31
- # # => "select * from invoices where client <= 10"
32
- #
33
- # == Equality: +equals+
34
- # full_week_invs = object_store.getInvoices { |inv| inv.hours.equals( 40 ) }
35
- # # => "select * from invoices where hours = 40"
36
- # If you're comparing to a domain object you should pass in the object itself.
37
- # client = object_store.getClient( 99 )
38
- # invoices = object_store.getInvoices { |inv| inv.client.equals( client ) }
39
- # # => "select * from invoices where client = 99"
40
- #
41
- # == Inclusion: +in+
42
- # first_three_invs = object_store.getInvoices { |inv| inv.pk_id.in( 1, 2, 3 ) }
43
- # # => "select * from invoices where pk_id in ( 1, 2, 3 )"
44
- #
45
- # == Text comparison: +like+
46
- # fname_starts_with_a = object_store.getUsers { |user|
47
- # user.fname.like( /^a/ )
48
- # }
49
- # # => "select * from users where fname like 'a%'"
50
- # fname_ends_with_a = object_store.getUsers { |user|
51
- # user.fname.like( /a$/ )
52
- # }
53
- # # => "select * from users where fname like '%a'"
54
- # fname_contains_a = object_store.getUsers { |user|
55
- # user.fname.like( /a/ )
56
- # }
57
- # # => "select * from users where fname like '%a%'"
58
- # Please note that although we're using the Regexp operators here, these aren't
59
- # full-fledged regexps. Only ^ and $ work for this.
60
- #
61
- # == Compound conditions: <tt>Query.And</tt> and <tt>Query.Or</tt>
62
- # invoices = object_store.getInvoices { |inv|
63
- # Query.And( inv.hours.equals( 40 ), inv.rate.equals( 50 ) )
64
- # }
65
- # # => "select * from invoices where (hours = 40 and rate = 50)"
66
- # client99 = object_store.getClient( 99 )
67
- # invoices = object_store.getInvoices { |inv|
68
- # Query.Or( inv.hours.equals( 40 ),
69
- # inv.rate.equals( 50 ),
70
- # inv.client.equals( client99 ) )
71
- # }
72
- # # => "select * from invoices where (hours = 40 or rate = 50 or client = 99)"
73
- # Note that both compound operators can take 2 or more arguments. Also, they can
74
- # be nested:
75
- # invoices = object_store.getInvoices { |inv|
76
- # Query.And( inv.hours.equals( 40 ),
77
- # Query.Or( inv.rate.equals( 50 ),
78
- # inv.client.equals( client99 ) ) )
79
- # }
80
- # # => "select * from invoices where (hours = 40 and
81
- # # (rate = 50 or client = 99))"
82
- #
83
- # == Negation: +not+
84
- # invoices = object_store.getInvoices { |inv| inv.rate.equals( 50 ).not }
85
- # # => "select * from invoices where rate != 50"
86
-
87
- module Lafcadio
88
- class Query
89
- def self.And( *conditions ); CompoundCondition.new( *conditions ); end
90
-
91
- def self.infer( domain_class, &action )
92
- inferrer = Query::Inferrer.new( domain_class ) { |obj|
93
- action.call( obj )
94
- }
95
- inferrer.execute
96
- end
97
-
98
- def self.Or( *conditions )
99
- conditions << CompoundCondition::OR
100
- CompoundCondition.new( *conditions)
101
- end
102
-
103
- ASC = 1
104
- DESC = 2
105
-
106
- attr_reader :object_type, :condition
107
- attr_accessor :order_by, :order_by_order, :limit
108
-
109
- def initialize(object_type, pk_idOrCondition = nil)
110
- @object_type = object_type
111
- ( @condition, @order_by, @limit ) = [ nil, nil, nil ]
112
- if pk_idOrCondition
113
- if pk_idOrCondition.class <= Condition
114
- @condition = pk_idOrCondition
115
- else
116
- @condition = Query::Equals.new( object_type.sql_primary_key_name,
117
- pk_idOrCondition, object_type )
118
- end
119
- end
120
- @order_by_order = ASC
121
- end
122
-
123
- def and( &action ); compound( CompoundCondition::AND, action ); end
124
-
125
- def compound( comp_type, action )
126
- rquery = Query.infer( @object_type ) { |dobj| action.call( dobj ) }
127
- comp_cond = Query::CompoundCondition.new( @condition, rquery.condition,
128
- comp_type )
129
- comp_cond.query
130
- end
131
-
132
- def eql?( other ); other.class <= Query && other.to_sql == to_sql; end
133
-
134
- def fields; '*'; end
135
-
136
- def hash; to_sql.hash; end
137
-
138
- def limit_clause
139
- "limit #{ @limit.begin }, #{ @limit.end - @limit.begin + 1 }" if @limit
140
- end
141
-
142
- def or( &action ); compound( CompoundCondition::OR, action ); end
143
-
144
- def order_clause
145
- if @order_by
146
- clause = "order by #{ @order_by } "
147
- clause += @order_by_order == ASC ? 'asc' : 'desc'
148
- clause
149
- end
150
- end
151
-
152
- def sql_primary_key_field(object_type)
153
- "#{ object_type.table_name }.#{ object_type.sql_primary_key_name }"
154
- end
155
-
156
- def tables
157
- concrete_classes = object_type.self_and_concrete_superclasses.reverse
158
- table_names = concrete_classes.collect { |domain_class|
159
- domain_class.table_name
160
- }
161
- table_names.join( ', ' )
162
- end
163
-
164
- def to_sql
165
- clauses = [ "select #{ fields }", "from #{ tables }" ]
166
- clauses << where_clause if where_clause
167
- clauses << order_clause if order_clause
168
- clauses << limit_clause if limit_clause
169
- clauses.join ' '
170
- end
171
-
172
- def where_clause
173
- concrete_classes = object_type.self_and_concrete_superclasses.reverse
174
- where_clauses = []
175
- concrete_classes.each_with_index { |domain_class, i|
176
- if i < concrete_classes.size - 1
177
- join_clause = sql_primary_key_field( domain_class ) + ' = ' +
178
- sql_primary_key_field( concrete_classes[i+1] )
179
- where_clauses << join_clause
180
- else
181
- where_clauses << @condition.to_sql if @condition
182
- end
183
- }
184
- where_clauses.size > 0 ? 'where ' + where_clauses.join( ' and ' ) : nil
185
- end
186
-
187
- class Condition #:nodoc:
188
- def Condition.search_term_type
189
- Object
190
- end
191
-
192
- attr_reader :object_type
193
-
194
- def initialize(fieldName, searchTerm, object_type)
195
- @fieldName = fieldName
196
- @searchTerm = searchTerm
197
- unless @searchTerm.class <= self.class.search_term_type
198
- raise "Incorrect searchTerm type #{ searchTerm.class }"
199
- end
200
- @object_type = object_type
201
- if @object_type
202
- unless @object_type <= DomainObject
203
- raise "Incorrect object type #{ @object_type.to_s }"
204
- end
205
- end
206
- end
207
-
208
- def db_field_name; get_field.db_table_and_field_name; end
209
-
210
- def get_field
211
- anObjectType = @object_type
212
- field = nil
213
- while (anObjectType < DomainObject || anObjectType < DomainObject) &&
214
- !field
215
- field = anObjectType.get_class_field( @fieldName ) ||
216
- anObjectType.get_class_field_by_db_name( @fieldName )
217
- anObjectType = anObjectType.superclass
218
- end
219
- if field
220
- field
221
- else
222
- errStr = "Couldn't find field \"#{ @fieldName }\" in " +
223
- "#{ @object_type } domain class"
224
- raise( MissingError, errStr, caller )
225
- end
226
- end
227
-
228
- def query; Query.new( @object_type, self ); end
229
-
230
- def not
231
- Query::Not.new( self )
232
- end
233
-
234
- def primary_key_field?
235
- [ @object_type.sql_primary_key_name, 'pk_id' ].include?( @fieldName )
236
- end
237
- end
238
-
239
- class Compare < Condition #:nodoc:
240
- LESS_THAN = 1
241
- LESS_THAN_OR_EQUAL = 2
242
- GREATER_THAN_OR_EQUAL = 3
243
- GREATER_THAN = 4
244
-
245
- @@comparators = {
246
- LESS_THAN => '<',
247
- LESS_THAN_OR_EQUAL => '<=',
248
- GREATER_THAN_OR_EQUAL => '>=',
249
- GREATER_THAN => '>'
250
- }
251
-
252
- @@mockComparators = {
253
- LESS_THAN => Proc.new { |d1, d2| d1 < d2 },
254
- LESS_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 <= d2 },
255
- GREATER_THAN_OR_EQUAL => Proc.new { |d1, d2| d1 >= d2 },
256
- GREATER_THAN => Proc.new { |d1, d2| d1 > d2 }
257
- }
258
-
259
- def initialize(fieldName, searchTerm, object_type, compareType)
260
- super fieldName, searchTerm, object_type
261
- @compareType = compareType
262
- end
263
-
264
- def to_sql
265
- if ( get_field.kind_of?( LinkField ) &&
266
- !@searchTerm.respond_to?( :pk_id ) )
267
- search_val = @searchTerm.to_s
268
- else
269
- search_val = get_field.value_for_sql( @searchTerm ).to_s
270
- end
271
- "#{ db_field_name } #{ @@comparators[@compareType] } " + search_val
272
- end
273
-
274
- def object_meets(anObj)
275
- value = anObj.send @fieldName
276
- value = value.pk_id if value.class <= DomainObject
277
- if value
278
- @@mockComparators[@compareType].call(value, @searchTerm)
279
- else
280
- false
281
- end
282
- end
283
- end
284
-
285
- class CompoundCondition < Condition #:nodoc:
286
- AND = 1
287
- OR = 2
288
-
289
- def initialize(*conditions)
290
- if( [ AND, OR ].index(conditions.last) )
291
- @compoundType = conditions.last
292
- conditions.pop
293
- else
294
- @compoundType = AND
295
- end
296
- @conditions = conditions
297
- @object_type = conditions[0].object_type
298
- end
299
-
300
- def object_meets(anObj)
301
- if @compoundType == AND
302
- @conditions.inject( true ) { |result, cond|
303
- result && cond.object_meets( anObj )
304
- }
305
- else
306
- @conditions.inject( false ) { |result, cond|
307
- result || cond.object_meets( anObj )
308
- }
309
- end
310
- end
311
-
312
- def to_sql
313
- booleanString = @compoundType == AND ? 'and' : 'or'
314
- subSqlStrings = @conditions.collect { |cond| cond.to_sql }
315
- "(#{ subSqlStrings.join(" #{ booleanString } ") })"
316
- end
317
- end
318
-
319
- class DomainObjectImpostor #:nodoc:
320
- attr_reader :domain_class
321
-
322
- def initialize( domain_class )
323
- @domain_class = domain_class
324
- end
325
-
326
- def method_missing( methId, *args )
327
- fieldName = methId.id2name
328
- begin
329
- classField = @domain_class.get_field( fieldName )
330
- ObjectFieldImpostor.new( self, classField )
331
- rescue MissingError
332
- super( methId, *args )
333
- end
334
- end
335
- end
336
-
337
- class Equals < Condition #:nodoc:
338
- def r_val_string
339
- field = get_field
340
- if @searchTerm.class <= ObjectField
341
- @searchTerm.db_table_and_field_name
342
- else
343
- field.value_for_sql(@searchTerm).to_s
344
- end
345
- end
346
-
347
- def object_meets(anObj)
348
- if @searchTerm.class <= ObjectField
349
- compare_value = anObj.send( @searchTerm.name )
350
- else
351
- compare_value = @searchTerm
352
- end
353
- compare_value == anObj.send( @fieldName )
354
- end
355
-
356
- def to_sql
357
- sql = "#{ db_field_name } "
358
- unless @searchTerm.nil?
359
- sql += "= " + r_val_string
360
- else
361
- sql += "is null"
362
- end
363
- sql
364
- end
365
- end
366
-
367
- class In < Condition #:nodoc:
368
- def self.search_term_type
369
- Array
370
- end
371
-
372
- def object_meets(anObj)
373
- value = anObj.send @fieldName
374
- @searchTerm.index(value) != nil
375
- end
376
-
377
- def to_sql
378
- "#{ db_field_name } in (#{ @searchTerm.join(', ') })"
379
- end
380
- end
381
-
382
- class Inferrer #:nodoc:
383
- def initialize( domain_class, &action )
384
- @domain_class = domain_class; @action = action
385
- end
386
-
387
- def execute
388
- impostor = DomainObjectImpostor.new( @domain_class )
389
- condition = @action.call( impostor )
390
- query = Query.new( @domain_class, condition )
391
- end
392
- end
393
-
394
- class Like < Condition #:nodoc:
395
- PRE_AND_POST = 1
396
- PRE_ONLY = 2
397
- POST_ONLY = 3
398
-
399
- def initialize(
400
- fieldName, searchTerm, object_type, matchType = PRE_AND_POST)
401
- super fieldName, searchTerm, object_type
402
- @matchType = matchType
403
- end
404
-
405
- def get_regexp
406
- if @matchType == PRE_AND_POST
407
- Regexp.new(@searchTerm)
408
- elsif @matchType == PRE_ONLY
409
- Regexp.new(@searchTerm.to_s + "$")
410
- elsif @matchType == POST_ONLY
411
- Regexp.new("^" + @searchTerm)
412
- end
413
- end
414
-
415
- def object_meets(anObj)
416
- value = anObj.send @fieldName
417
- if value.class <= DomainObject || value.class == DomainObjectProxy
418
- value = value.pk_id.to_s
419
- end
420
- if value.class <= Array
421
- (value.index(@searchTerm) != nil)
422
- else
423
- get_regexp.match(value) != nil
424
- end
425
- end
426
-
427
- def to_sql
428
- withWildcards = @searchTerm
429
- if @matchType == PRE_AND_POST
430
- withWildcards = "%" + withWildcards + "%"
431
- elsif @matchType == PRE_ONLY
432
- withWildcards = "%" + withWildcards
433
- elsif @matchType == POST_ONLY
434
- withWildcards += "%"
435
- end
436
- "#{ db_field_name } like '#{ withWildcards }'"
437
- end
438
- end
439
-
440
- class Link < Condition #:nodoc:
441
- def initialize( fieldName, searchTerm, object_type )
442
- if searchTerm.pk_id.nil?
443
- raise ArgumentError,
444
- "Can't query using an uncommitted domain object as a search term",
445
- caller
446
- else
447
- super( fieldName, searchTerm, object_type )
448
- end
449
- end
450
-
451
- def self.search_term_type
452
- DomainObject
453
- end
454
-
455
- def object_meets(anObj)
456
- value = anObj.send @fieldName
457
- value ? value.pk_id == @searchTerm.pk_id : false
458
- end
459
-
460
- def to_sql
461
- "#{ db_field_name } = #{ @searchTerm.pk_id }"
462
- end
463
- end
464
-
465
- class Max < Query #:nodoc:
466
- attr_reader :field_name
467
-
468
- def initialize( object_type, field_name = 'pk_id' )
469
- super( object_type )
470
- @field_name = field_name
471
- end
472
-
473
- def collect( coll )
474
- max = coll.inject( nil ) { |max, d_obj|
475
- a_value = d_obj.send( @field_name )
476
- ( max.nil? || a_value > max ) ? a_value : max
477
- }
478
- [ max ]
479
- end
480
-
481
- def fields
482
- "max(#{ @object_type.get_field( @field_name ).db_field_name })"
483
- end
484
- end
485
-
486
- class Not < Condition #:nodoc:
487
- def initialize(unCondition)
488
- @unCondition = unCondition
489
- end
490
-
491
- def object_meets(obj)
492
- !@unCondition.object_meets(obj)
493
- end
494
-
495
- def object_type; @unCondition.object_type; end
496
-
497
- def to_sql
498
- "!(#{ @unCondition.to_sql })"
499
- end
500
- end
501
-
502
- class ObjectFieldImpostor #:nodoc:
503
- def self.comparators
504
- {
505
- 'lt' => Compare::LESS_THAN, 'lte' => Compare::LESS_THAN_OR_EQUAL,
506
- 'gte' => Compare::GREATER_THAN_OR_EQUAL,
507
- 'gt' => Compare::GREATER_THAN
508
- }
509
- end
510
-
511
- attr_reader :class_field
512
-
513
- def initialize( domainObjectImpostor, class_field_or_name )
514
- @domainObjectImpostor = domainObjectImpostor
515
- if class_field_or_name == 'pk_id'
516
- @db_field_name = 'pk_id'
517
- else
518
- @class_field = class_field_or_name
519
- @db_field_name = class_field_or_name.db_field_name
520
- end
521
- end
522
-
523
- def method_missing( methId, *args )
524
- methodName = methId.id2name
525
- if !ObjectFieldImpostor.comparators.keys.index( methodName ).nil?
526
- register_compare_condition( methodName, *args )
527
- else
528
- super( methId, *args )
529
- end
530
- end
531
-
532
- def register_compare_condition( compareStr, searchTerm)
533
- compareVal = ObjectFieldImpostor.comparators[compareStr]
534
- Compare.new( @db_field_name, searchTerm,
535
- @domainObjectImpostor.domain_class, compareVal )
536
- end
537
-
538
- def equals( searchTerm )
539
- Equals.new( @db_field_name, field_or_field_name( searchTerm ),
540
- @domainObjectImpostor.domain_class )
541
- end
542
-
543
- def field_or_field_name( search_term )
544
- if search_term.class == ObjectFieldImpostor
545
- search_term.class_field
546
- else
547
- search_term
548
- end
549
- end
550
-
551
- def like( regexp )
552
- if regexp.source =~ /^\^(.*)/
553
- searchTerm = $1
554
- matchType = Query::Like::POST_ONLY
555
- elsif regexp.source =~ /(.*)\$$/
556
- searchTerm = $1
557
- matchType = Query::Like::PRE_ONLY
558
- else
559
- searchTerm = regexp.source
560
- matchType = Query::Like::PRE_AND_POST
561
- end
562
- Query::Like.new( @db_field_name, searchTerm,
563
- @domainObjectImpostor.domain_class, matchType )
564
- end
565
-
566
- def in( *searchTerms )
567
- Query::In.new( @db_field_name, searchTerms,
568
- @domainObjectImpostor.domain_class )
569
- end
570
- end
571
- end
572
- end