lore 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/LICENSE +19 -0
  2. data/README +74 -0
  3. data/aspect.rb +80 -0
  4. data/behaviours/lockable.rb +41 -0
  5. data/behaviours/movable.rb +54 -0
  6. data/behaviours/versioned.rb +24 -0
  7. data/benchmark.rb +193 -0
  8. data/bits.rb +52 -0
  9. data/cache/abstract_entity_cache.rb +82 -0
  10. data/cache/bits.rb +22 -0
  11. data/cache/cacheable.rb +202 -0
  12. data/cache/cached_entities.rb +116 -0
  13. data/cache/file_index.rb +35 -0
  14. data/cache/mmap_entity_cache.rb +67 -0
  15. data/clause.rb +528 -0
  16. data/connection.rb +155 -0
  17. data/custom_functions.sql +14 -0
  18. data/exception/ambiguous_attribute.rb +14 -0
  19. data/exception/cache_exception.rb +30 -0
  20. data/exception/invalid_klass_parameters.rb +63 -0
  21. data/exception/invalid_parameter.rb +42 -0
  22. data/exception/unknown_typecode.rb +19 -0
  23. data/file_index.sql +56 -0
  24. data/gui/erb_template.rb +79 -0
  25. data/gui/erb_template_helpers.rhtml +19 -0
  26. data/gui/form.rb +314 -0
  27. data/gui/form_element.rb +676 -0
  28. data/gui/form_generator.rb +151 -0
  29. data/gui/templates/button.rhtml +2 -0
  30. data/gui/templates/checkbox.rhtml +3 -0
  31. data/gui/templates/checkbox_row.rhtml +1 -0
  32. data/gui/templates/file.rhtml +2 -0
  33. data/gui/templates/file_readonly.rhtml +3 -0
  34. data/gui/templates/form_element.rhtml +5 -0
  35. data/gui/templates/form_element_horizontal.rhtml +3 -0
  36. data/gui/templates/form_element_listed.rhtml +8 -0
  37. data/gui/templates/form_table.rhtml +3 -0
  38. data/gui/templates/form_table_blank.rhtml +3 -0
  39. data/gui/templates/form_table_horizontal.rhtml +8 -0
  40. data/gui/templates/password.rhtml +2 -0
  41. data/gui/templates/password_readonly.rhtml +3 -0
  42. data/gui/templates/radio.rhtml +1 -0
  43. data/gui/templates/radio_row.rhtml +1 -0
  44. data/gui/templates/select.rhtml +23 -0
  45. data/gui/templates/text.rhtml +2 -0
  46. data/gui/templates/text_readonly.rhtml +3 -0
  47. data/gui/templates/textarea.rhtml +3 -0
  48. data/gui/templates/textarea_readonly.rhtml +4 -0
  49. data/lore.gemspec +40 -0
  50. data/lore.rb +94 -0
  51. data/migration.rb +48 -0
  52. data/model.rb +139 -0
  53. data/model_factory.rb +202 -0
  54. data/model_shortcuts.rb +16 -0
  55. data/query_shortcuts.rb +367 -0
  56. data/reserved_methods.txt +3 -0
  57. data/result.rb +100 -0
  58. data/symbol.rb +58 -0
  59. data/table_accessor.rb +1926 -0
  60. data/table_deleter.rb +115 -0
  61. data/table_inserter.rb +168 -0
  62. data/table_instance.rb +384 -0
  63. data/table_selector.rb +314 -0
  64. data/table_updater.rb +155 -0
  65. data/test/README +31 -0
  66. data/test/env.rb +5 -0
  67. data/test/lore_test.log +8218 -0
  68. data/test/model.rb +142 -0
  69. data/test/prepare.rb +37 -0
  70. data/test/tc_aspect.rb +58 -0
  71. data/test/tc_cache.rb +80 -0
  72. data/test/tc_clause.rb +104 -0
  73. data/test/tc_deep_inheritance.rb +49 -0
  74. data/test/tc_factory.rb +57 -0
  75. data/test/tc_filter.rb +37 -0
  76. data/test/tc_form.rb +32 -0
  77. data/test/tc_model.rb +86 -0
  78. data/test/tc_prepare.rb +45 -0
  79. data/test/tc_refined_query.rb +88 -0
  80. data/test/tc_table_accessor.rb +265 -0
  81. data/test/test.log +181 -0
  82. data/test/test_db.sql +400 -0
  83. data/test/ts_lore.rb +49 -0
  84. data/types.rb +55 -0
  85. data/validation/message.rb +60 -0
  86. data/validation/parameter_validator.rb +104 -0
  87. data/validation/reason.rb +54 -0
  88. data/validation/type_validator.rb +91 -0
  89. data/validation.rb +65 -0
  90. metadata +170 -0
data/clause.rb ADDED
@@ -0,0 +1,528 @@
1
+
2
+ require('lore/table_accessor')
3
+ require('lore/query_shortcuts') # So far, a forward-declaration could do here as well
4
+
5
+ class String
6
+ def lore_escape
7
+ self.gsub!("'","X")
8
+ self.gsub!('"','X')
9
+ self
10
+ end
11
+ end
12
+
13
+ module Lore
14
+
15
+ def self.parse_field_value(value)
16
+ if value.instance_of? Clause then
17
+ value = value.field_name
18
+ else
19
+ value = value.to_s.lore_escape
20
+ value = '\'' << value << '\''
21
+ end
22
+ value
23
+ end
24
+
25
+ # Usage:
26
+ #
27
+ # Some_Klass.select { |k|
28
+ # k.join(Other_Klass).on(Some_Klass.foo == Other_Klass.bar) { |o|
29
+ # k.where(
30
+ # (o['foo'] == 1) & (o['bar'] <=> 2)
31
+ # )
32
+ # k.limit(10)
33
+ # }
34
+ #
35
+ class Join # :nodoc:
36
+ # {{{
37
+
38
+ def initialize(clause, join_klass, type=:natural)
39
+ @type = type
40
+ @clause_parser = clause
41
+ @join_klass = join_klass
42
+ @string = ''
43
+ # By joining with another klass, new attributes are added
44
+ # we have to include in the AS part of the query:
45
+ new_attributes = ''
46
+ join_klass.get_attributes.each { |attr_set|
47
+ table = attr_set[0]
48
+ attr_set[1].each { |attrib_name|
49
+ new_attributes += ', ' << "\n"
50
+ new_attributes += table + '.' << attrib_name
51
+ new_attributes += ' AS '
52
+ new_attributes += '"' << table << '.' << attrib_name << '" '
53
+ }
54
+ }
55
+ implicit_joins = ''
56
+ @clause_parser.add_as(new_attributes)
57
+
58
+ @implicit_joins = Table_Selector.build_joined_query(join_klass)
59
+
60
+ # @clause_parser.add_join(self)
61
+ end
62
+
63
+ def implicit_joins
64
+ @implicit_joins
65
+ end
66
+
67
+ def plan_name
68
+ @plan_name
69
+ end
70
+
71
+ def string
72
+ @string
73
+ end
74
+
75
+ def using(key, &block)
76
+ if @type == :natural then cmd = 'JOIN '
77
+ elsif @type == :left then cmd = 'LEFT JOIN '
78
+ elsif @type == :right then cmd = 'RIGHT JOIN '
79
+ end
80
+ @string = "\n" << cmd << @join_klass.table_name << ' USING (' << key.to_s << ') '
81
+ @clause_parser.append_join(self)
82
+ yield @clause_parser # use extended clause parser for inner block argument
83
+ end
84
+
85
+ def on(clause, &block)
86
+ if @type == :natural then cmd = 'JOIN '
87
+ elsif @type == :left then cmd = 'LEFT JOIN '
88
+ elsif @type == :right then cmd = 'RIGHT JOIN '
89
+ end
90
+ @string = cmd << @join_klass.table_name << ' ON (' << clause.to_sql << ') '
91
+ @clause_parser.append_join(self)
92
+ yield @clause_parser # use extended clause parser for inner block argument
93
+ end
94
+
95
+ end # class }}}
96
+
97
+ class Clause < String # :nodoc:
98
+ # {{{
99
+
100
+ attr_reader :field_name, :value_string, :left_side, :plan
101
+
102
+ def initialize(field_name, left_side='', value_string='', plan={})
103
+ @value_string = value_string
104
+ @left_side = left_side
105
+ @field_name = field_name
106
+ end
107
+
108
+ def self.for(accessor)
109
+ Clause_Parser.new(accessor.table_name, accessor.get_attributes)
110
+ end
111
+
112
+ def not_in(nested_query_string)
113
+ # Check for Refined_Select needed for functionality of
114
+ # Refined_Select. Example:
115
+ #
116
+ # User.all.with(User.user_id.in( Admin.all(:user_id) ))
117
+ #
118
+ if(nested_query_string.instance_of? Refined_Select) then
119
+ nested_query_string.to_inner_select
120
+ else
121
+ @value_string = @field_name << ' NOT IN (' << "\n" << nested_query_string.to_s << ') '
122
+ Clause.new(@value_string, @left_side+@value_string)
123
+ end
124
+ end
125
+
126
+ def in(nested_query_string)
127
+ # Check for Refined_Select needed for functionality of
128
+ # Refined_Select. Example:
129
+ #
130
+ # User.all.with(User.user_id.in( Admin.all(:user_id) ))
131
+ #
132
+ if(nested_query_string.instance_of? Refined_Select) then
133
+ nested_query_string = nested_query_string.to_select
134
+ elsif nested_query_string.instance_of? Array then
135
+ raise ::Exception.new('IN field expects at least one element. ') if nested_query_string.length == 0
136
+ nested_query_string = nested_query_string.join(',')
137
+ elsif nested_query_string.instance_of? Range then
138
+ return between(nested_query_string.first, nested_query_string.last)
139
+ end
140
+ @value_string = @field_name << ' IN (' << "\n" << nested_query_string.to_s << ') '
141
+ Clause.new(@value_string, @left_side+@value_string)
142
+
143
+ end
144
+
145
+ def between(range_begin, range_end)
146
+ @value_string = @field_name << " BETWEEN #{range_begin} AND #{range_end} "
147
+ Clause.new(@value_string, @left_side+@value_string)
148
+ end
149
+
150
+ def +(value)
151
+ if value.instance_of? String then
152
+ value = '\''+value.lore_escape+'\'::text'
153
+ else
154
+ value = value.to_s.lore_escape
155
+ end
156
+ @value_string = @field_name.to_s + '+'+value
157
+ return Clause.new(@value_string, @left_side.to_s+@value_string.to_s)
158
+ end
159
+ def -(value)
160
+ if value.instance_of? String then
161
+ value = '\''+value.lore_escape+'\'::text'
162
+ else
163
+ value = value.to_s.lore_escape
164
+ end
165
+ @value_string = @field_name + '-'+ value
166
+ return Clause.new(@value_string, @left_side+@value_string)
167
+ end
168
+
169
+ def is_null()
170
+ @value_string = @field_name + ' IS NULL'
171
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
172
+ end
173
+
174
+ def is_not_null()
175
+ @value_string = @field_name + ' IS NOT NULL'
176
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
177
+ end
178
+
179
+ def <(value)
180
+ @value_string = @field_name + ' < ' << Lore.parse_field_value(value)
181
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
182
+ end
183
+ def <=(value)
184
+ @value_string = @field_name + ' <= ' << Lore.parse_field_value(value)
185
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
186
+ end
187
+ def >(value)
188
+ @value_string = @field_name + ' > ' << Lore.parse_field_value(value)
189
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
190
+ end
191
+ def >=(value)
192
+ @value_string = @field_name + ' >= ' << Lore.parse_field_value(value)
193
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
194
+ end
195
+ def like(value)
196
+ value = value.to_s.lore_escape
197
+ @value_string = @field_name + ' LIKE ' << Lore.parse_field_value(value)
198
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
199
+ end
200
+ def ilike(value)
201
+ value = value.to_s.lore_escape
202
+ @value_string = @field_name + ' ILIKE ' << Lore.parse_field_value(value)
203
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
204
+ end
205
+ def posreg_like(value)
206
+ value = value.to_s.lore_escape
207
+ @value_string = @field_name + ' ~* ' << Lore.parse_field_value(value)
208
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
209
+ end
210
+ def ==(value)
211
+ if value.instance_of? Clause then
212
+ value = value.field_name
213
+ else
214
+ value = value.to_s.lore_escape
215
+ value = '\'' << value << '\''
216
+ end
217
+ if(value != :NULL)
218
+ @value_string = @field_name << ' = ' << value
219
+ else
220
+ @value_string = @field_name << ' IS NULL'
221
+ end
222
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
223
+ end
224
+
225
+ def <=>(value)
226
+ if(value != :NULL)
227
+ @value_string = @field_name << ' != ' << Lore.parse_field_value(value)
228
+ else
229
+ @value_string = @field_name << ' NOT NULL '
230
+ end
231
+ Clause.new(@field_name, @left_side+@value_string, '', @plan)
232
+ end
233
+ def |(value)
234
+ @value_string = ' OR ' << value.left_side
235
+ Clause.new(@value_string, '(' << @left_side+@value_string + ')', '', @plan)
236
+ end
237
+ alias or |
238
+ def &(value)
239
+ return unless value
240
+ if @left_side.gsub(' ','') != '' then
241
+ @value_string = ' AND ' << value.left_side
242
+ else
243
+ @value_string = value.left_side
244
+ end
245
+ Clause.new(@field_name, @left_side+@value_string, '', @plan)
246
+ end
247
+ alias and &
248
+
249
+ def has_element(element)
250
+ element = element.to_s if element.kind_of? Clause
251
+ element = '\''+element.lore_escape+'\'' if element.kind_of? String
252
+ @value_string = element + ' = ANY (' << @field_name+ ')'
253
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
254
+ end
255
+
256
+ # This query requires a custom operator, defined by:
257
+ #
258
+ # create function rlike(text,text) returns bool as 'select $2 like $1' language sql strict immutable;
259
+ # create operator ~~~ (procedure = rlike, leftarg = text, rightarg = text, commutator = ~~);
260
+ #
261
+ def has_element_like(element)
262
+ element = element.to_s if element.kind_of? Clause
263
+ element = '\''+element.lore_escape+'\'' if element.kind_of? String
264
+ @value_string = element + ' ~~~ ANY (' << @field_name+ ')'
265
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
266
+ end
267
+
268
+ # This query requires a custom operator, defined by:
269
+ #
270
+ # create function irlike(text,text) returns bool as 'select $2 ilike $1' language sql strict immutable;
271
+ # create operator ~~~~ (procedure = irlike, leftarg = text, rightarg = text, commutator = ~~);
272
+ #
273
+ def has_element_ilike(element)
274
+ element = element.to_s if element.kind_of? Clause
275
+ element = '\'' + element.lore_escape + '\'' if element.kind_of? String
276
+ @value_string = element + ' ~~~~ ANY (' << @field_name+ ')'
277
+ Clause.new(@value_string, @left_side+@value_string, '', @plan)
278
+ end
279
+
280
+ # Important to return @field_name here, as
281
+ # Table_Accessor.attribute should return
282
+ # schema.table_name.attribute.
283
+ # See Table_Accessor.load_attribute_fields
284
+ def to_s
285
+ @field_name.to_s
286
+ end
287
+
288
+ def tag
289
+ @field_name.gsub('.','--')
290
+ end
291
+
292
+ def inspect
293
+ return 'Clause('+to_s+')'
294
+ end
295
+
296
+ def to_sql
297
+ @left_side + @value_string
298
+ end
299
+
300
+ def plan_name
301
+
302
+ end
303
+
304
+ end # class }}}
305
+
306
+ # parses / builds WHERE, GROUP BY, ORDER BY, LIMIT, ...
307
+ # part of the query:
308
+ class Clause_Parser # :nodoc
309
+ # {{{
310
+
311
+ def initialize(base_table, *table_fields)
312
+
313
+ @clause = Hash.new
314
+ @clause[:limit] = ''
315
+ @clause[:offset] = ''
316
+ @clause[:group_by] = ''
317
+ @clause[:order_by] = ''
318
+ @clause[:join] = ''
319
+ @clause[:as] = ''
320
+ @clause[:set] = ''
321
+ @clause[:filter] = ''
322
+ @clause[:where] = 't'
323
+ @base_table = base_table
324
+
325
+ end # def
326
+
327
+ def self.for(accessor)
328
+ Clause_Parser.new(accessor.table_name, accessor.get_attributes)
329
+ end
330
+
331
+ def parts
332
+ @clause
333
+ end
334
+
335
+ def where_part
336
+ @clause[:where]
337
+ end
338
+ def as_part
339
+ @clause[:as]
340
+ end
341
+ def join_part
342
+ @clause[:join]
343
+ end
344
+ def having_part
345
+ @clause[:having]
346
+ end
347
+
348
+ def filter_part
349
+ return @clause[:order_by] << @clause[:limit] << @clause[:offset] << @clause[:group_by] << @clause[:having]
350
+ end
351
+ def set_part
352
+ @clause[:set].chomp!(',')
353
+ return ' SET ' << @clause[:set]
354
+ end
355
+
356
+ def plan_args
357
+ @clause[:plan_args]
358
+ end
359
+
360
+ def plan_types
361
+ @clause[:plan_types]
362
+ end
363
+
364
+ def plan_values
365
+ @clause[:plan_values]
366
+ end
367
+
368
+ def plan_name
369
+ @clause[:plan_name]
370
+ end
371
+
372
+ def set(attrib_value_hash)
373
+
374
+ attrib_value_hash.each_pair { |attr_name, value|
375
+
376
+ attr_name = attr_name.to_s unless attr_name.instance_of? String
377
+ if value.instance_of? Clause then
378
+ value = value.to_s.lore_escape
379
+ else
380
+ value = '\'' << value.to_s.lore_escape+'\''
381
+ end
382
+ # Postgres disallows full attribute names in UPDATE
383
+ # queries, so use field name only:
384
+ Lore.log { 'Set Attrib: ' << attr_name.to_s }
385
+ Lore.log { 'Set Value: ' << value.to_s }
386
+ @clause[:set] << attr_name.to_s.split('.').at(-1).to_s + ' = ' << value.to_s + ','
387
+ }
388
+ return self
389
+
390
+ end
391
+
392
+ def to_sql
393
+ clause = ''
394
+ clause << @clause[:join].to_s
395
+ clause << @clause[:where].to_s
396
+ clause << @clause[:group_by].to_s
397
+ clause << @clause[:order_by].to_s
398
+ clause << @clause[:limit].to_s
399
+ end # def
400
+
401
+ def where(where_clause)
402
+ if where_clause.instance_of? Clause then
403
+ where_clause = where_clause.to_sql
404
+ elsif where_clause.instance_of? TrueClass then
405
+ where_clause = '\'t\''
406
+ elsif where_clause.instance_of? FalseClass then
407
+ where_clause = '\'f\''
408
+ end
409
+ @clause[:where] = ' WHERE ' << where_clause.to_s
410
+ return self
411
+ end # def
412
+
413
+ # For usage in class Join only
414
+ def add_join(join)
415
+ @clause[:final_join] = join.implicit_joins
416
+ end
417
+
418
+ # TODO: Check if this is ever needed at all. Currently unused and untested.
419
+ def prepend_join(join)
420
+ @clause[:join] = join.string << @clause[:join]
421
+ @clause[:join] << join.implicit_joins
422
+
423
+ end
424
+ def append_join(join)
425
+ @clause[:join] << join.string
426
+ @clause[:join] << join.implicit_joins
427
+ end
428
+
429
+ def add_as(string)
430
+ @clause[:as] << string
431
+ end
432
+
433
+ def join(join_klass)
434
+ # this Join instance also will update this Clause_Parser's
435
+ # as_part (so passing self is crucial):
436
+ j = Join.new(self, join_klass)
437
+ return j
438
+ end
439
+ def left_join(join_klass)
440
+ # this Join instance also will update this Clause_Parser's
441
+ # as_part (so passing self is crucial):
442
+ j = Join.new(self, join_klass, :left)
443
+ return j
444
+ end
445
+ def right_join(join_klass)
446
+ # this Join instance also will update this Clause_Parser's
447
+ # as_part (so passing self is crucial):
448
+ j = Join.new(self, join_klass, :right)
449
+ return j
450
+ end
451
+
452
+ def limit(limit_val, offset_val=0)
453
+ @clause[:limit] = ' LIMIT ' << limit_val.to_s
454
+ @clause[:offset] = ' OFFSET ' << offset_val.to_s
455
+
456
+ return self
457
+ end # def
458
+
459
+ def having(having_clause)
460
+ @clause[:having] = ' HAVING ' << having_clause.to_sql
461
+ return self
462
+ end
463
+
464
+ def group_by(*absolute_field_names)
465
+ absolute_field_names.map! { |field|
466
+ if field.instance_of? Clause then
467
+ field = field.field_name.to_s
468
+ else
469
+ field = field.to_s
470
+ end
471
+ }
472
+ @clause[:group_by] = ' GROUP BY ' << absolute_field_names.join(',')
473
+ p @clause
474
+ return self
475
+ end # def
476
+
477
+ def order_by(order_field, dir=:asc)
478
+ (dir == :desc)? dir_s = 'DESC' : dir_s = 'ASC'
479
+ if @clause[:order_by] == '' then
480
+ @clause[:order_by] = ' ORDER BY '
481
+ else
482
+ @clause[:order_by] << ', '
483
+ end
484
+ @clause[:order_by] << order_field.to_s + ' ' << dir_s
485
+ return self
486
+ end # def
487
+
488
+ def [](absolute_field_name)
489
+ if absolute_field_name.instance_of? Clause then
490
+ field_name = absolute_field_name.field_name.to_s
491
+ else
492
+ field_name = absolute_field_name.to_s
493
+ end
494
+
495
+ return Clause.new(field_name)
496
+ end # def
497
+
498
+ def max(absolute_field_name)
499
+ if absolute_field_name.instance_of? Clause then
500
+ field_name = absolute_field_name.field_name.to_s
501
+ else
502
+ field_name = absolute_field_name.to_s
503
+ end
504
+ return Clause.new("max(#{field_name})")
505
+ end
506
+
507
+ def method_missing(absolute_field_name)
508
+ if absolute_field_name.instance_of? Clause then
509
+ field_name = absolute_field_name.field_name.to_s
510
+ else
511
+ field_name = absolute_field_name.to_s
512
+ end
513
+
514
+ return Clause.new("#{@base_table}.#{field_name}")
515
+ end
516
+
517
+ def id()
518
+ return Clause.new("#{@base_table}.id")
519
+ end
520
+
521
+ def perform
522
+
523
+ end
524
+
525
+ # }}}
526
+ end # class
527
+
528
+ end # module
data/connection.rb ADDED
@@ -0,0 +1,155 @@
1
+
2
+ $:.push('/opt/local/lib/ruby/1.8/postgres')
3
+
4
+ require('postgres')
5
+ require('logger')
6
+ require('lore/lore')
7
+ require('lore/result')
8
+
9
+ module Lore
10
+
11
+ class Connection_Error < ::Exception
12
+
13
+ end
14
+
15
+ class Context
16
+
17
+ @@logger = Lore.logger
18
+ @@context_stack = Array.new
19
+
20
+ def self.get_connection()
21
+ if @@context_stack.empty? then
22
+ raise ::Exception.new('No context given. ')
23
+ end
24
+ return Connection_Pool.get_connection(@@context_stack.last)
25
+ end
26
+
27
+ def self.inspect
28
+ @@context_stack.inspect
29
+ end
30
+
31
+ def self.enter(context_name)
32
+ @@logger.debug('Entering context ' + context_name.to_s)
33
+ @@context_stack.push(context_name)
34
+ end
35
+
36
+ def self.leave()
37
+ context_name = @@context_stack.pop
38
+ @@logger.debug('Leaving context ' << context_name.to_s)
39
+ end
40
+
41
+ def self.clear()
42
+ @@context_stack = Array.new
43
+ $cb__project = nil
44
+ end
45
+
46
+ def self.switch_to(project)
47
+ clear()
48
+ enter(project.context)
49
+ $cb__project = project
50
+ end
51
+
52
+ end
53
+
54
+ class Connection_Pool
55
+
56
+ @@pool = Hash.new
57
+
58
+ def self.get_connection(db_name)
59
+
60
+ db_name = db_name.intern unless db_name.instance_of? Symbol
61
+
62
+ # If requested connection is not in pool yet:
63
+ if !@@pool.has_key? db_name then
64
+ # Try to establish connection
65
+ begin
66
+ connection = PGconn.connect(Lore.pg_server, Lore.pg_port, '', '', db_name.to_s, Lore.user_for(db_name), Lore.pass_for(db_name.to_s))
67
+ connection.exec(Lore.on_connect_commands)
68
+ @@pool[db_name] = connection
69
+ # Handle errors
70
+ rescue PGError => pge
71
+ raise Lore::Connection_Error.new("Could not establish connection to database '#{db_name.to_s}': " << pge.message)
72
+ rescue ::Exception => excep
73
+ raise Lore::Connection_Error.new("Could not establish connection to database '#{db_name.to_s}': " << excep.message)
74
+ end
75
+ end
76
+
77
+ # Return requested connection
78
+ return @@pool[db_name]
79
+
80
+ end
81
+
82
+ end
83
+
84
+ class Connection # :nodoc
85
+ #include Singleton
86
+
87
+ @@logger = Lore.logger
88
+ @@query_count = 0
89
+ @@result_row_count = 0
90
+
91
+ def initialize
92
+ end
93
+
94
+ def self.reset_query_count
95
+ @@query_count = 0
96
+ end
97
+ def self.reset_result_row_count
98
+ @@result_row_count = 0
99
+ end
100
+ def self.query_count
101
+ @@query_count
102
+ end
103
+ def self.result_row_count
104
+ @@result_row_count
105
+ end
106
+
107
+ def self.perform_cacheable(query)
108
+
109
+ if Lore::Cache::Cached_Entities.include?(query) then
110
+ result = Lore::Cache::Cached_Entities[query]
111
+ else
112
+ result = perform(query)
113
+ Lore::Cache::Cached_Entities[query] = result
114
+ end
115
+ return result
116
+ end
117
+
118
+ def self.perform(query)
119
+
120
+ begin
121
+
122
+ connection = Context.get_connection
123
+
124
+ @@query_count += 1
125
+ result = connection.exec(query)
126
+ @@result_row_count += result.num_tuples
127
+
128
+ # Log database query on low level:
129
+
130
+ if Lore.log_queries? then
131
+ query = query.gsub('WHERE', "\nWHERE").gsub('FROM', "\n}}} \nFROM").gsub('JOIN', "\nJOIN")
132
+ query = query.gsub('SELECT', "\nSELECT \n{{{").gsub("\n\n","\n")
133
+ query = query.gsub('SET', "SET\n").gsub(' , ', ", \n")
134
+ query.split("\n").each { |line|
135
+ @@logger.debug { line }
136
+ }
137
+ end
138
+
139
+ rescue ::Exception => pge
140
+
141
+ pge.message << "\n" << query
142
+ @@logger.error { pge.message }
143
+ @@logger.error { 'Context: ' << Context.inspect }
144
+ @@logger.error { 'Query: ' << "\n" << query }
145
+ raise pge
146
+
147
+ end
148
+
149
+ return Result.new(query, result)
150
+
151
+ end
152
+
153
+ end # class Connection
154
+
155
+ end # module Lore
@@ -0,0 +1,14 @@
1
+
2
+ -- These functions are needed for exotic operations Lore offers, so far
3
+ -- LIKE-operator and ILIKE-operators on array elements.
4
+ -- If you intend to use custom SQL functions that have to be defined in
5
+ -- your database, this is where they should be placed.
6
+
7
+ -- Provide LIKE-operator for arrays: (Model.an_array_attribute.has_element_like('%'+foo)
8
+ --
9
+ create function rlike(text,text) returns bool as 'select $2 like $1' language sql strict immutable;
10
+ create operator ~~~ (procedure = rlike, leftarg = text, rightarg = text, commutator = ~~);
11
+ create function irlike(text,text) returns bool as 'select $2 ilike $1' language sql strict immutable;
12
+ create operator ~~~~ (procedure = irlike, leftarg = text, rightarg = text, commutator = ~~);
13
+
14
+
@@ -0,0 +1,14 @@
1
+
2
+ module Lore
3
+ module Exception
4
+
5
+ class Ambiguous_Attribute < ::Exception
6
+
7
+ def initialize(table_a, table_b, attribute)
8
+ @message = 'Ambiguous attribute: '+attribute+ ' exists in '+table_a+' and '+table_b+'. '
9
+ end
10
+
11
+ end # class
12
+
13
+ end # module
14
+ end # module