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.
- data/LICENSE +19 -0
- data/README +74 -0
- data/aspect.rb +80 -0
- data/behaviours/lockable.rb +41 -0
- data/behaviours/movable.rb +54 -0
- data/behaviours/versioned.rb +24 -0
- data/benchmark.rb +193 -0
- data/bits.rb +52 -0
- data/cache/abstract_entity_cache.rb +82 -0
- data/cache/bits.rb +22 -0
- data/cache/cacheable.rb +202 -0
- data/cache/cached_entities.rb +116 -0
- data/cache/file_index.rb +35 -0
- data/cache/mmap_entity_cache.rb +67 -0
- data/clause.rb +528 -0
- data/connection.rb +155 -0
- data/custom_functions.sql +14 -0
- data/exception/ambiguous_attribute.rb +14 -0
- data/exception/cache_exception.rb +30 -0
- data/exception/invalid_klass_parameters.rb +63 -0
- data/exception/invalid_parameter.rb +42 -0
- data/exception/unknown_typecode.rb +19 -0
- data/file_index.sql +56 -0
- data/gui/erb_template.rb +79 -0
- data/gui/erb_template_helpers.rhtml +19 -0
- data/gui/form.rb +314 -0
- data/gui/form_element.rb +676 -0
- data/gui/form_generator.rb +151 -0
- data/gui/templates/button.rhtml +2 -0
- data/gui/templates/checkbox.rhtml +3 -0
- data/gui/templates/checkbox_row.rhtml +1 -0
- data/gui/templates/file.rhtml +2 -0
- data/gui/templates/file_readonly.rhtml +3 -0
- data/gui/templates/form_element.rhtml +5 -0
- data/gui/templates/form_element_horizontal.rhtml +3 -0
- data/gui/templates/form_element_listed.rhtml +8 -0
- data/gui/templates/form_table.rhtml +3 -0
- data/gui/templates/form_table_blank.rhtml +3 -0
- data/gui/templates/form_table_horizontal.rhtml +8 -0
- data/gui/templates/password.rhtml +2 -0
- data/gui/templates/password_readonly.rhtml +3 -0
- data/gui/templates/radio.rhtml +1 -0
- data/gui/templates/radio_row.rhtml +1 -0
- data/gui/templates/select.rhtml +23 -0
- data/gui/templates/text.rhtml +2 -0
- data/gui/templates/text_readonly.rhtml +3 -0
- data/gui/templates/textarea.rhtml +3 -0
- data/gui/templates/textarea_readonly.rhtml +4 -0
- data/lore.gemspec +40 -0
- data/lore.rb +94 -0
- data/migration.rb +48 -0
- data/model.rb +139 -0
- data/model_factory.rb +202 -0
- data/model_shortcuts.rb +16 -0
- data/query_shortcuts.rb +367 -0
- data/reserved_methods.txt +3 -0
- data/result.rb +100 -0
- data/symbol.rb +58 -0
- data/table_accessor.rb +1926 -0
- data/table_deleter.rb +115 -0
- data/table_inserter.rb +168 -0
- data/table_instance.rb +384 -0
- data/table_selector.rb +314 -0
- data/table_updater.rb +155 -0
- data/test/README +31 -0
- data/test/env.rb +5 -0
- data/test/lore_test.log +8218 -0
- data/test/model.rb +142 -0
- data/test/prepare.rb +37 -0
- data/test/tc_aspect.rb +58 -0
- data/test/tc_cache.rb +80 -0
- data/test/tc_clause.rb +104 -0
- data/test/tc_deep_inheritance.rb +49 -0
- data/test/tc_factory.rb +57 -0
- data/test/tc_filter.rb +37 -0
- data/test/tc_form.rb +32 -0
- data/test/tc_model.rb +86 -0
- data/test/tc_prepare.rb +45 -0
- data/test/tc_refined_query.rb +88 -0
- data/test/tc_table_accessor.rb +265 -0
- data/test/test.log +181 -0
- data/test/test_db.sql +400 -0
- data/test/ts_lore.rb +49 -0
- data/types.rb +55 -0
- data/validation/message.rb +60 -0
- data/validation/parameter_validator.rb +104 -0
- data/validation/reason.rb +54 -0
- data/validation/type_validator.rb +91 -0
- data/validation.rb +65 -0
- 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
|