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/table_selector.rb ADDED
@@ -0,0 +1,314 @@
1
+
2
+ require('lore/cache/cached_entities')
3
+ require('lore/connection')
4
+ require('lore/clause')
5
+
6
+ module Lore
7
+
8
+ module Table_Selector # :nodoc:
9
+
10
+ @@logger = Lore.logger
11
+
12
+ public
13
+
14
+ # Extracted, recursive method for building the JOIN-part of
15
+ # a SELECT query.
16
+ def self.build_joined_query(accessor, query_string='', joined_tables=[])
17
+
18
+ top_table = accessor.table_name
19
+ is_a_hierarchy = accessor.get_joins()
20
+ own_primary_keys = accessor.get_primary_keys()
21
+ own_foreign_keys = accessor.get_foreign_keys()
22
+
23
+ joined_accessors = (accessor.get_is_a_klasses).dup
24
+ joined_accessors.update(accessor.get_aggregate_klasses)
25
+
26
+ # predefine
27
+ own_p_keys = Hash.new
28
+ foreign_p_keys = Hash.new
29
+ on_string = String.new
30
+ field_counter = 0
31
+
32
+ is_a_hierarchy.each_pair { |foreign_table, foreign_base_tables|
33
+ # JOIN base.table ON (
34
+ if !(joined_tables.include?(foreign_table)) then
35
+
36
+ foreign_p_keys = own_primary_keys[foreign_table]
37
+
38
+ own_p_keys = own_foreign_keys[foreign_table]
39
+
40
+ if !own_p_keys.nil? then
41
+
42
+ joined_tables << foreign_table
43
+ query_string << "\n JOIN #{foreign_table} on ("
44
+
45
+ field_counter = 0
46
+ on_string = ''
47
+ foreign_p_keys.uniq.each { |foreign_field|
48
+
49
+ # base.table.foreign_field =
50
+ on_string << "#{foreign_table}.#{foreign_field} = #{top_table}.#{own_p_keys[field_counter]}"
51
+ # this.table.own_field
52
+
53
+ if field_counter > 0 then query_string << ", #{on_string}"
54
+ else query_string << on_string
55
+ end
56
+
57
+ field_counter += 1
58
+ }
59
+
60
+ query_string << ')'
61
+
62
+ end
63
+
64
+ # sub-joins of joined table:
65
+ query_string = build_joined_query(joined_accessors["#{top_table}.#{own_p_keys.first}"],
66
+ query_string,
67
+ joined_tables)
68
+ end
69
+ }
70
+ return query_string
71
+ end # def
72
+
73
+ public
74
+
75
+ # Extracted, recursive method for building the AS-part of
76
+ # a SELECT query.
77
+ def self.build_as_query(table_fields,
78
+ query_string='')
79
+
80
+ return '*'
81
+
82
+ first = query_string == ''
83
+
84
+ table_fields.each_pair { |table_name, fields|
85
+
86
+ fields.each { |foreign_field|
87
+
88
+ # base.table.foreign_field =
89
+ query_string << ', ' unless first
90
+ query_string << "\n #{table_name}.#{foreign_field} AS "
91
+ query_string << "\"#{table_name}.#{foreign_field}\""
92
+
93
+ first = false
94
+ }
95
+
96
+ }
97
+
98
+ query_string
99
+
100
+ end #def
101
+
102
+ protected
103
+
104
+ def self.build_select_query(accessor,
105
+ value_keys)
106
+ # this is to be wrapped in an adapter to provide DB abstraction:
107
+
108
+ table_name = accessor.get_table_name
109
+ query_string = 'SELECT '
110
+ query_string << build_as_query(accessor.get_attributes)
111
+ query_string << ' FROM ' << table_name + ' '
112
+ query_string << build_joined_query(accessor)
113
+ query_string << "\n WHERE "
114
+
115
+ operator = '='
116
+ field = String.new
117
+ value_keys[0].each_pair { |field, value|
118
+ # Filtering values is not necessary when feeded with
119
+ # Lore::Attributes instance
120
+ field = field.to_s
121
+ if value.instance_of? Hash then
122
+ value.each_pair { |attrib_name, attrib_value|
123
+ query_string << "#{field}.#{attrib_name} #{operator} '#{attrib_value.to_s.lore_escape}' AND "
124
+ }
125
+ else
126
+ query_string << "#{table_name}." if field.split('.')[2].nil?
127
+ query_string << "#{field} #{operator} '#{value.to_s.lore_escape}' AND "
128
+ end
129
+ }
130
+ # remove trailing AND:
131
+ query_string.chomp!(' AND ')
132
+
133
+ return query_string
134
+
135
+ end # def
136
+
137
+
138
+ protected
139
+
140
+ def self.select_query(what,
141
+ accessor,
142
+ clause = nil,
143
+ &block)
144
+
145
+ query_string = 'SELECT '
146
+
147
+ # Example:
148
+ # select(Car.name) -> SELECT public.car.name AS "value"
149
+ if what.instance_of? Lore::Clause then
150
+ what = what.to_s + ' AS "value" '
151
+ end
152
+
153
+ if(what.nil? || what == '*' || what == '') then
154
+ query_as_part = build_as_query(accessor.get_attributes)
155
+ else
156
+ query_as_part = what.to_s
157
+ end
158
+ query_from_part = " FROM #{accessor.get_table_name} "
159
+ # Add JOIN part for system defined type (user defined
160
+ # joins will be set in Clause_Parser object in later
161
+ # yield):
162
+ query_join_part = build_joined_query(accessor)
163
+
164
+ clause_string = ''
165
+ if block_given? then
166
+ yield_obj = Lore::Clause_Parser.new(accessor.table_name, *(accessor.get_attributes))
167
+ clause = yield *yield_obj
168
+ end
169
+ # Extend AS part by attributes that have been added in clause
170
+ query_parts = clause.parts
171
+ query_as_part << ' ' << query_parts[:as].to_s
172
+ # Extend JOIN part by joins that have been added in clause
173
+ query_join_part << ' ' << query_parts[:join].to_s
174
+ # Set WHERE part to conditions defined in clause
175
+ query_where_part = ' ' << query_parts[:where].to_s
176
+ # Set GROUP BY part
177
+ query_group_by_part = ' ' << query_parts[:group_by].to_s
178
+ # Set HAVING part
179
+ query_having_part = ' ' << query_parts[:having].to_s
180
+ # Set LIMIT, OFFSET
181
+ query_filter_part = ' ' << query_parts[:filter].to_s
182
+ query_order_by_part = ' ' << query_parts[:order_by].to_s
183
+ query_limit_part = ' ' << query_parts[:limit].to_s
184
+ query_limit_part << ' ' << query_parts[:offset].to_s
185
+
186
+ query_string << query_as_part << query_from_part << query_join_part << query_where_part << query_group_by_part << query_having_part << query_filter_part << query_order_by_part << query_limit_part
187
+
188
+ # TODO:
189
+ # Implement class Plan_Clause, offering exactly the same methods as Clause,
190
+ # but generating a Plan instead the query.
191
+ # Pass block& to Plan_Clause, too, only in case a plan is needed.
192
+
193
+ return query_string
194
+
195
+ end
196
+
197
+ def self.plan_select_query(what, accessor, &block)
198
+
199
+ auto_plan = Lore::Plan.new(clause, select_query(what, accessor, &block), accessor)
200
+
201
+ end
202
+
203
+ protected
204
+
205
+ def self.select(what,
206
+ accessor,
207
+ &block)
208
+
209
+ query_string = select_query(what,
210
+ accessor,
211
+ &block)
212
+
213
+ return perform_select(accessor, query_string)
214
+
215
+ end # def
216
+
217
+ def self.select_cached(what,
218
+ accessor,
219
+ &block)
220
+
221
+ query_string = select_query(what,
222
+ accessor,
223
+ &block)
224
+
225
+ @@logger.debug { 'Select on ' << accessor.to_s }
226
+
227
+ result = Array.new
228
+ if Lore.cache_enabled? && accessor.entity_cache && accessor.entity_cache.include?(accessor, query_string) then
229
+ result = accessor.entity_cache.read(accessor, query_string)
230
+ result = nil if result == ''
231
+ @@logger.debug { "cache contents for #{accessor.table_name} found: #{result.to_s != ''}" }
232
+ else
233
+ db_result = perform_select(accessor, query_string)
234
+ db_result.get_rows[:values].each { |row|
235
+ result.push(accessor.new(row))
236
+ }
237
+ if Lore.cache_enabled? && accessor.entity_cache then
238
+ accessor.entity_cache.create(accessor, query_string, result)
239
+ end
240
+ end
241
+ return result
242
+ end # def
243
+
244
+ def self.select_on_keys(accessor,
245
+ value_keys)
246
+
247
+ query_string = build_select_query(accessor,
248
+ value_keys)
249
+
250
+ return perform_select(accessor, query_string)
251
+
252
+ end #def
253
+
254
+ def self.prepare(plan_name, accessor, args, &block)
255
+ args_string = ''
256
+ args.map { |a| a = Lore::TYPE_NAMES[a] }
257
+ if args.to_s != '' && args.length > 0 then args_string = "(#{args.join(',')})" end
258
+ query_string = "PREPARE #{accessor.table_name.gsub('.','_')}__#{plan_name.to_s}#{args_string} AS " << select_query(nil, accessor, &block)
259
+ begin
260
+ result = Lore::Connection.perform(query_string)
261
+ rescue ::Exception => excep
262
+ @@logger.debug("Exception when preparing #{plan_name.to_s}: #{excep.message}. Already prepared before?")
263
+ end
264
+ end
265
+
266
+ def self.select_prepared(plan_name, accessor, *args)
267
+ args_string = ''
268
+ if args.to_s != '' && args.length > 0 then args_string = "(#{args.join(',')})" end
269
+ query_string = "EXECUTE #{plan_name.to_s} #{args_string}; "
270
+ result = Array.new
271
+ if Lore.cache_enabled? && accessor.entity_cache && accessor.entity_cache.include?(accessor, query_string) then
272
+ result = accessor.read_entity_cache(query_string)
273
+ result = nil if result == ''
274
+ @@logger.debug { "cache contents for prepared #{accessor.table_name}: #{result.to_s.inspect}" }
275
+ else
276
+ db_result = perform_select(accessor, query_string)
277
+ db_result.get_rows[:values].each { |row|
278
+ result.push(accessor.new(row))
279
+ }
280
+ if Lore.cache_enabled? && accessor.entity_cache then
281
+ accessor.create_entity_cache(query_string, result)
282
+ end
283
+ end
284
+ return result
285
+ end
286
+
287
+
288
+ def self.deallocate(plan_name, accessor)
289
+ begin
290
+ query_string = "DEALLOCATE #{accessor.table_name.gsub('.','_')}__#{plan_name.to_s}; "
291
+ result = Lore::Connection.perform(query_string)
292
+ rescue ::Exception => excep
293
+ end
294
+ end
295
+
296
+ private
297
+
298
+ def self.perform_select(accessor, query_string)
299
+
300
+ Context.enter(accessor.get_context) unless accessor.get_context.nil?
301
+ begin
302
+ result = Lore::Connection.perform(query_string)
303
+ return result
304
+ rescue PGError => pge
305
+ raise pge
306
+ ensure
307
+ Context.leave unless accessor.get_context.nil?
308
+ GC.start
309
+ end
310
+ end
311
+
312
+
313
+ end # module
314
+ end # module
data/table_updater.rb ADDED
@@ -0,0 +1,155 @@
1
+
2
+ require('lore/connection')
3
+ require('lore/bits')
4
+
5
+ module Lore
6
+
7
+ module Table_Updater # :nodoc:
8
+
9
+ @logger = Logger.new(Lore.logfile)
10
+
11
+ def self.atomic_update_query(table_name,
12
+ attributes,
13
+ primary_key_values,
14
+ value_keys,
15
+ explicit_fields)
16
+
17
+ @logger.debug('EXPLICIT FIELDS: ' << explicit_fields.inspect)
18
+
19
+ query_string = "\n"
20
+ query_string += 'UPDATE '+table_name+' SET '
21
+
22
+ set_string = String.new
23
+
24
+ key_counter = 0
25
+ attributes.each { |attribute_name|
26
+
27
+ internal_attribute_name = attribute_name[0..24]
28
+ value = value_keys[internal_attribute_name].to_s
29
+ if value == '' then
30
+ value = value_keys[table_name+'.'+internal_attribute_name].to_s
31
+ end
32
+
33
+ # attrib = Lore.resolve_passed_value(value_keys, table_name, attribute_name)
34
+ # value = attrib[:value]
35
+ # internal_attribute_name = attrib[:field]
36
+
37
+ # only include attribute to update query if
38
+ # this attribute is not marked for explicit updating or
39
+ # marked as explicit but non-empty:
40
+ if(
41
+ !(explicit_fields && explicit_fields.include?(internal_attribute_name) && value.empty?) &&
42
+ !(primary_key_values[attribute_name] && value.empty?)
43
+ )
44
+
45
+ if key_counter > 0
46
+ set_string += ', '
47
+ end # if
48
+ set_string += attribute_name + '=\'' + value.to_s + '\' '
49
+
50
+ key_counter = 1
51
+
52
+ end # if
53
+ }
54
+ query_string += set_string
55
+
56
+ query_string += 'WHERE '
57
+
58
+ field_counter=0
59
+ primary_key_values.each_pair { |field, value|
60
+ query_string += field + '=\'' + value.to_s + '\' '
61
+ if field_counter < primary_key_values.keys.length-1
62
+ query_string += 'AND '
63
+ end
64
+ field_counter += 1
65
+ }
66
+ query_string += ';'
67
+
68
+ query_string
69
+
70
+ end #def
71
+
72
+ def self.block_update(accessor,
73
+ &block)
74
+
75
+ query_string = 'UPDATE '+accessor.get_table_name
76
+
77
+ if block_given? then
78
+ yield_obj = Lore::Clause_Parser.new(accessor.table_name)
79
+ clause = yield *yield_obj
80
+ end
81
+
82
+ query_string += clause.set_part
83
+ query_string += clause.where_part
84
+
85
+ Lore::Context.enter(accessor.get_context) unless accessor.get_context.nil?
86
+
87
+ begin
88
+ Lore::Connection.perform(query_string)
89
+ ensure
90
+ Lore::Context.leave unless accessor.get_context.nil?
91
+ end
92
+
93
+ end
94
+
95
+ # :nodoc
96
+ def self.update_query(table_name,
97
+ is_a_hierarchy,
98
+
99
+ attributes,
100
+ primary_key_values,
101
+ value_keys,
102
+ explicit_fields,
103
+
104
+ query_string='')
105
+
106
+ is_a_hierarchy.each_pair { |table, base_tables|
107
+
108
+ # pass base tables first, recursively, as IS_A-based creation has
109
+ # to be done bottom-up:
110
+ query_string += update_query(table,
111
+ base_tables,
112
+
113
+ attributes,
114
+ primary_key_values,
115
+ value_keys,
116
+ explicit_fields
117
+ ).to_s
118
+ }
119
+ # finally, add query string for this table:
120
+ query_string += atomic_update_query(table_name,
121
+ attributes[table_name],
122
+ primary_key_values[table_name],
123
+ value_keys[table_name],
124
+ # value_keys[table_name],
125
+ explicit_fields[table_name]
126
+ ).to_s
127
+
128
+ query_string
129
+
130
+ end #def
131
+
132
+ # :nodoc
133
+ def self.perform_update(accessor, accessor_instance)
134
+
135
+ query_string = update_query(accessor.get_table_name,
136
+ accessor.get_is_a,
137
+ accessor.get_attributes,
138
+ accessor_instance.get_primary_key_values,
139
+ accessor_instance.get_attribute_values,
140
+ accessor.get_explicit)
141
+
142
+ Context.enter(accessor.get_context) unless accessor.get_context.nil?
143
+ begin
144
+ Lore::Connection.perform("BEGIN;\n#{query_string}\nCOMMIT;")
145
+ ensure
146
+ Context.leave unless accessor.get_context.nil?
147
+ end
148
+
149
+ accessor.flush_entity_cache()
150
+
151
+ end #def
152
+
153
+ end # module
154
+
155
+ end # module
data/test/README ADDED
@@ -0,0 +1,31 @@
1
+
2
+ How to run tests
3
+
4
+ - Create (empty) database test:
5
+
6
+ $ createdb test
7
+
8
+ - Create user 'lore' with password 'lore23'
9
+ (change this in trunk/lore.rb, if you want to)
10
+
11
+ $shell: createuser lore
12
+ $shell: ...
13
+ $shell: psql test
14
+ $psql/test: ALTER USER lore WITH PASSWORD 'lore23';
15
+ $psql/test: \q
16
+
17
+ - Insert test schema into database:
18
+ $shell: psql test < test/test_db.sql
19
+
20
+ - Run tests. No test should fail except those
21
+ saying they are supposed to (printing something like
22
+ 'This will fail')
23
+
24
+ It's best to redirect STDERR to a file.
25
+
26
+ $shell: ruby test/ts_lore.rb 2>test.log
27
+
28
+ - Check the logfile in case you are interested in
29
+ the kinky details.
30
+
31
+
data/test/env.rb ADDED
@@ -0,0 +1,5 @@
1
+
2
+ require('lore')
3
+
4
+ Lore.add_login_data 'test' => ['lore','lore23']
5
+ Lore::Context.enter :test