lore 0.4.2

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