lore 0.4.8 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +16 -7
- data/README.rdoc +91 -0
- data/benchmark/benchmark.sql +11 -0
- data/benchmark/results.txt +28 -0
- data/benchmark/select.rb +352 -0
- data/lib/lore.rb +22 -8
- data/lib/lore/adapters/context.rb +64 -0
- data/lib/lore/adapters/postgres-pr.rb +6 -0
- data/lib/lore/adapters/postgres-pr/connection.rb +93 -0
- data/lib/lore/adapters/postgres-pr/result.rb +63 -0
- data/lib/lore/{types.rb → adapters/postgres-pr/types.rb} +36 -0
- data/lib/lore/adapters/postgres.rb +24 -0
- data/lib/lore/adapters/postgres/connection.rb +81 -0
- data/lib/lore/adapters/postgres/result.rb +82 -0
- data/lib/lore/adapters/postgres/types.rb +91 -0
- data/lib/lore/bits.rb +18 -0
- data/lib/lore/cache/abstract_entity_cache.rb +2 -1
- data/lib/lore/cache/cacheable.rb +12 -177
- data/lib/lore/cache/memcache_entity_cache.rb +89 -0
- data/lib/lore/cache/memory_entity_cache.rb +77 -0
- data/lib/lore/cache/mmap_entity_cache.rb +2 -2
- data/lib/lore/cache/mmap_entity_cache_bork.rb +86 -0
- data/lib/lore/clause.rb +107 -35
- data/lib/lore/{exception → exceptions}/ambiguous_attribute.rb +2 -2
- data/lib/lore/{exception → exceptions}/cache_exception.rb +1 -1
- data/lib/lore/exceptions/database_exception.rb +16 -0
- data/lib/lore/{exception/invalid_parameter.rb → exceptions/invalid_field.rb} +7 -4
- data/lib/lore/exceptions/unknown_type.rb +18 -0
- data/lib/lore/exceptions/validation_failure.rb +71 -0
- data/lib/lore/gui/form_generator.rb +109 -60
- data/lib/lore/gui/lore_model_select_field.rb +1 -0
- data/lib/lore/migration.rb +84 -25
- data/lib/lore/model.rb +3 -18
- data/lib/lore/{aspect.rb → model/aspect.rb} +0 -0
- data/lib/lore/model/associations.rb +225 -0
- data/lib/lore/model/attribute_settings.rb +233 -0
- data/lib/lore/model/filters.rb +34 -0
- data/lib/lore/model/mockable.rb +62 -0
- data/lib/lore/{model_factory.rb → model/model_factory.rb} +68 -39
- data/lib/lore/model/model_instance.rb +382 -0
- data/lib/lore/{model_shortcuts.rb → model/model_shortcuts.rb} +7 -0
- data/lib/lore/model/polymorphic.rb +53 -0
- data/lib/lore/model/prepare.rb +97 -0
- data/lib/lore/model/table_accessor.rb +1016 -0
- data/lib/lore/query.rb +71 -0
- data/lib/lore/query_shortcuts.rb +43 -11
- data/lib/lore/strategies/table_delete.rb +115 -0
- data/lib/lore/strategies/table_insert.rb +146 -0
- data/lib/lore/strategies/table_select.rb +299 -0
- data/lib/lore/strategies/table_update.rb +155 -0
- data/lib/lore/validation/parameter_validator.rb +85 -26
- data/lib/lore/validation/type_validator.rb +34 -78
- data/{custom_models.rb → lore-0.9.2.gem} +0 -0
- data/lore.gemspec +26 -17
- data/spec/clause.rb +37 -0
- data/spec/fixtures/blank_models.rb +37 -0
- data/{test/model.rb → spec/fixtures/models.rb} +64 -41
- data/spec/fixtures/polymorphic_models.rb +68 -0
- data/spec/model_associations.rb +86 -0
- data/spec/model_create.rb +47 -0
- data/spec/model_definition.rb +151 -0
- data/spec/model_delete.rb +31 -0
- data/spec/model_inheritance.rb +50 -0
- data/spec/model_polymorphic.rb +85 -0
- data/spec/model_select.rb +101 -0
- data/spec/model_select_eager.rb +42 -0
- data/spec/model_union_select.rb +33 -0
- data/spec/model_update.rb +45 -0
- data/spec/model_validation.rb +20 -0
- data/spec/spec_db.sql +808 -0
- data/spec/spec_env.rb +19 -0
- data/spec/spec_helpers.rb +77 -0
- metadata +93 -82
- data/lib/lore/README.txt +0 -84
- data/lib/lore/behaviours/lockable.rb +0 -55
- data/lib/lore/behaviours/movable.rb +0 -72
- data/lib/lore/behaviours/paginated.rb +0 -31
- data/lib/lore/behaviours/versioned.rb +0 -36
- data/lib/lore/connection.rb +0 -152
- data/lib/lore/exception/invalid_klass_parameters.rb +0 -63
- data/lib/lore/exception/unknown_typecode.rb +0 -19
- data/lib/lore/result.rb +0 -119
- data/lib/lore/symbol.rb +0 -58
- data/lib/lore/table_accessor.rb +0 -1790
- data/lib/lore/table_deleter.rb +0 -116
- data/lib/lore/table_inserter.rb +0 -170
- data/lib/lore/table_instance.rb +0 -389
- data/lib/lore/table_selector.rb +0 -285
- data/lib/lore/table_updater.rb +0 -157
- data/lib/lore/validation.rb +0 -65
- data/lib/lore/validation/message.rb +0 -60
- data/lib/lore/validation/reason.rb +0 -52
- data/lore_test.log +0 -2366
- data/test/README +0 -31
- data/test/custom_models.rb +0 -18
- data/test/env.rb +0 -5
- data/test/prepare.rb +0 -37
- data/test/tc_aspect.rb +0 -58
- data/test/tc_cache.rb +0 -83
- data/test/tc_clause.rb +0 -104
- data/test/tc_deep_inheritance.rb +0 -49
- data/test/tc_factory.rb +0 -57
- data/test/tc_filter.rb +0 -37
- data/test/tc_form.rb +0 -32
- data/test/tc_model.rb +0 -140
- data/test/tc_prepare.rb +0 -44
- data/test/tc_refined_query.rb +0 -88
- data/test/tc_table_accessor.rb +0 -267
- data/test/tc_thread.rb +0 -100
- data/test/test_db.sql +0 -400
- data/test/test_lore.rb +0 -50
data/lib/lore/query.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
|
4
|
+
# Proxy class for Select queries.
|
5
|
+
#
|
6
|
+
# Model.select { |x| ... } --> Select.new(Model, clause_parser)
|
7
|
+
#
|
8
|
+
class Select_Query < Array
|
9
|
+
|
10
|
+
attr_reader :model, :clause_parser
|
11
|
+
|
12
|
+
def initialize(model, what=nil, polymorphic=false, &block)
|
13
|
+
@model = model
|
14
|
+
@strategy = model.__select_strategy__
|
15
|
+
@what = what
|
16
|
+
@polymorphic = polymorphic
|
17
|
+
@clause_parser = yield(Clause_Parser.new(@model))
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_sql
|
21
|
+
@strategy.select_query(@what, @clause_parser, @polymorphic)[:query]
|
22
|
+
end
|
23
|
+
alias sql to_sql
|
24
|
+
|
25
|
+
def perform
|
26
|
+
@strategy.select_cached(@what, @clause_parser, @polymorphic)
|
27
|
+
end
|
28
|
+
alias to_a perform
|
29
|
+
alias exec perform
|
30
|
+
alias result perform
|
31
|
+
|
32
|
+
def first
|
33
|
+
perform.first
|
34
|
+
end
|
35
|
+
def last
|
36
|
+
perform.last
|
37
|
+
end
|
38
|
+
def each(&block)
|
39
|
+
perform.each(&block)
|
40
|
+
end
|
41
|
+
def each_with_index(&block)
|
42
|
+
perform.each_with_index(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def union(other)
|
46
|
+
@clause_parser.union(other)
|
47
|
+
return self
|
48
|
+
end
|
49
|
+
|
50
|
+
def +(other)
|
51
|
+
perform + other.to_a
|
52
|
+
end
|
53
|
+
|
54
|
+
# Any undefined method is interpreted as
|
55
|
+
# kicker call.
|
56
|
+
def method_missing(meth, *args)
|
57
|
+
perform.__send__(meth, *args)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
class Delete_Query
|
63
|
+
end
|
64
|
+
|
65
|
+
class Update_Query
|
66
|
+
end
|
67
|
+
|
68
|
+
class Insert_Query
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/lore/query_shortcuts.rb
CHANGED
@@ -36,6 +36,11 @@ module Lore
|
|
36
36
|
end
|
37
37
|
alias where with
|
38
38
|
|
39
|
+
def or(condition)
|
40
|
+
@condition = ((@condition) | condition)
|
41
|
+
return self
|
42
|
+
end
|
43
|
+
|
39
44
|
# Adds HAVING statement to query. Examples:
|
40
45
|
#
|
41
46
|
# User.all.having(User.user_id.in( Admin.all(:user_id) ))
|
@@ -60,7 +65,7 @@ module Lore
|
|
60
65
|
# -> Highest car_id as integer, e.g. 14223
|
61
66
|
#
|
62
67
|
def method_missing(method, attribute_name)
|
63
|
-
@what = method.to_s
|
68
|
+
@what = "#{method.to_s}(#{attribute_name.to_s}) "
|
64
69
|
return self
|
65
70
|
end
|
66
71
|
|
@@ -69,6 +74,7 @@ module Lore
|
|
69
74
|
class Refined_Select < Refined_Query
|
70
75
|
|
71
76
|
def initialize(accessor, statements={})
|
77
|
+
@polymorphic = false
|
72
78
|
super(accessor, statements)
|
73
79
|
end
|
74
80
|
|
@@ -103,6 +109,7 @@ module Lore
|
|
103
109
|
entities.first
|
104
110
|
end
|
105
111
|
|
112
|
+
|
106
113
|
# When requesting a single value, #to_i can be used to
|
107
114
|
# retreive the result as string instead of calling #entity:
|
108
115
|
#
|
@@ -167,6 +174,13 @@ module Lore
|
|
167
174
|
return self
|
168
175
|
end
|
169
176
|
|
177
|
+
# Execute as polymorphic query, joining all concrete base models
|
178
|
+
# of this polymorhpic model.
|
179
|
+
def polymorphic
|
180
|
+
@polymorphic = true
|
181
|
+
return self
|
182
|
+
end
|
183
|
+
|
170
184
|
# Handy wrapper for
|
171
185
|
# <request>.entities.each { |e| ... }
|
172
186
|
def each(&block)
|
@@ -221,16 +235,29 @@ module Lore
|
|
221
235
|
#
|
222
236
|
def entities
|
223
237
|
if @what.nil? then
|
224
|
-
|
225
|
-
entity
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
238
|
+
if @polymorphic then
|
239
|
+
result = @accessor.polymorphic_select { |entity|
|
240
|
+
entity.where(@condition)
|
241
|
+
entity.limit(@limit, @offset) unless @limit.nil?
|
242
|
+
@order_by.each { |o|
|
243
|
+
entity.order_by(o[0], o[1])
|
244
|
+
}
|
245
|
+
entity.group_by(@group_by) unless @group_by.nil?
|
246
|
+
entity.having(@having) unless @having.nil?
|
247
|
+
entity
|
248
|
+
}.to_a
|
249
|
+
else
|
250
|
+
result = @accessor.select { |entity|
|
251
|
+
entity.where(@condition)
|
252
|
+
entity.limit(@limit, @offset) unless @limit.nil?
|
253
|
+
@order_by.each { |o|
|
254
|
+
entity.order_by(o[0], o[1])
|
255
|
+
}
|
256
|
+
entity.group_by(@group_by) unless @group_by.nil?
|
257
|
+
entity.having(@having) unless @having.nil?
|
258
|
+
entity
|
259
|
+
}.to_a
|
260
|
+
end
|
234
261
|
else
|
235
262
|
result = Array.new
|
236
263
|
@accessor.select_values(@what) { |entity|
|
@@ -247,6 +274,8 @@ module Lore
|
|
247
274
|
result << row.values['value']
|
248
275
|
elsif row.kind_of? String then
|
249
276
|
result << row
|
277
|
+
else
|
278
|
+
result = row
|
250
279
|
end
|
251
280
|
}
|
252
281
|
end
|
@@ -254,6 +283,9 @@ module Lore
|
|
254
283
|
return result
|
255
284
|
end
|
256
285
|
alias perform entities
|
286
|
+
alias to_a entities
|
287
|
+
alias result entities
|
288
|
+
alias value entity
|
257
289
|
|
258
290
|
end # class
|
259
291
|
|
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
require('lore/bits')
|
3
|
+
|
4
|
+
module Lore
|
5
|
+
|
6
|
+
class Table_Delete
|
7
|
+
|
8
|
+
@logger = Lore.logger
|
9
|
+
|
10
|
+
public
|
11
|
+
|
12
|
+
def initialize(accessor)
|
13
|
+
@accessor = accessor
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def self.atomic_delete_query(table_name, primary_keys, value_keys)
|
19
|
+
# {{{
|
20
|
+
query_string = 'DELETE FROM '+table_name+' WHERE '
|
21
|
+
|
22
|
+
field_counter=0
|
23
|
+
primary_keys.each { |field|
|
24
|
+
|
25
|
+
query_string << "#{field} ='"
|
26
|
+
value = value_keys[field].to_s
|
27
|
+
if value == '' then
|
28
|
+
value = value_keys["#{table_name}.#{internal_attribute_name}"].to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
query_string << "#{value}' "
|
32
|
+
if field_counter < primary_keys.length-1
|
33
|
+
query_string += 'AND '
|
34
|
+
end
|
35
|
+
field_counter += 1
|
36
|
+
}
|
37
|
+
query_string << '; '
|
38
|
+
|
39
|
+
query_string
|
40
|
+
|
41
|
+
end # }}}
|
42
|
+
|
43
|
+
public
|
44
|
+
|
45
|
+
def block_delete(&block)
|
46
|
+
# {{{
|
47
|
+
query_string = "DELETE FROM #{@accessor.table_name} "
|
48
|
+
|
49
|
+
if block_given? then
|
50
|
+
yield_obj = Lore::Clause_Parser.new(@accessor.table_name)
|
51
|
+
clause = yield *yield_obj
|
52
|
+
end
|
53
|
+
|
54
|
+
query_string += clause.where_part
|
55
|
+
|
56
|
+
Lore::Context.enter(@accessor.get_context) if @accessor.get_context
|
57
|
+
begin
|
58
|
+
Lore::Connection.perform(query_string)
|
59
|
+
ensure
|
60
|
+
Lore::Context.leave if @accessor.get_context
|
61
|
+
end
|
62
|
+
end # }}}
|
63
|
+
|
64
|
+
def self.delete_query(table_name,
|
65
|
+
is_a_hierarchy,
|
66
|
+
primary_keys,
|
67
|
+
value_keys,
|
68
|
+
query_string='')
|
69
|
+
# {{{
|
70
|
+
query_string += atomic_delete_query(table_name,
|
71
|
+
primary_keys[table_name],
|
72
|
+
value_keys
|
73
|
+
).to_s
|
74
|
+
is_a_hierarchy.each_pair { |table, base_tables|
|
75
|
+
|
76
|
+
# pass base tables afterwards, recursively, as IS_A-based deletion has
|
77
|
+
# to be done top-down (derived tabled first):
|
78
|
+
query_string = delete_query(table,
|
79
|
+
base_tables,
|
80
|
+
|
81
|
+
primary_keys,
|
82
|
+
value_keys,
|
83
|
+
|
84
|
+
query_string
|
85
|
+
).to_s
|
86
|
+
}
|
87
|
+
return query_string
|
88
|
+
end # }}}
|
89
|
+
|
90
|
+
public
|
91
|
+
|
92
|
+
def perform_delete(value_keys)
|
93
|
+
# {{{
|
94
|
+
query_string = self.class.delete_query(@accessor.table_name,
|
95
|
+
@accessor.__associations__.base_klasses_tree,
|
96
|
+
@accessor.__associations__.primary_keys,
|
97
|
+
value_keys)
|
98
|
+
|
99
|
+
Lore::Context.enter(@accessor.get_context) if @accessor.get_context
|
100
|
+
begin
|
101
|
+
Lore::Connection.perform("BEGIN;\n#{query_string}\nCOMMIT;")
|
102
|
+
rescue ::Exception => excep
|
103
|
+
Lore::Connection.perform("ROLLBACK;")
|
104
|
+
raise excep
|
105
|
+
ensure
|
106
|
+
Lore::Context.leave if @accessor.get_context
|
107
|
+
end
|
108
|
+
@accessor.flush_entity_cache()
|
109
|
+
|
110
|
+
end # }}}
|
111
|
+
|
112
|
+
end # class
|
113
|
+
|
114
|
+
end # module
|
115
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
|
2
|
+
module Lore
|
3
|
+
|
4
|
+
class Table_Insert
|
5
|
+
|
6
|
+
public
|
7
|
+
|
8
|
+
def initialize(accessor)
|
9
|
+
@accessor = accessor
|
10
|
+
@aggregates = accessor.__associations__.aggregate_klasses
|
11
|
+
@is_a = accessor.__associations__.base_klasses_tree
|
12
|
+
@foreign_keys = accessor.__associations__.foreign_keys
|
13
|
+
@base_table = accessor.table_name
|
14
|
+
@fields = accessor.__attributes__.fields
|
15
|
+
@sequences = accessor.__attributes__.sequences
|
16
|
+
@sequence_values = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform_insert(value_keys)
|
20
|
+
# {{{
|
21
|
+
@aggregates.keys.each { |table|
|
22
|
+
@is_a.delete(table)
|
23
|
+
}
|
24
|
+
|
25
|
+
Context.enter(@accessor.get_context) if @accessor.get_context
|
26
|
+
Lore.logger.info { 'PERFORM INSERT on '+@accessor.to_s }
|
27
|
+
|
28
|
+
@sequence_values = load_sequence_values(@sequences)
|
29
|
+
|
30
|
+
# Parse sequence_values into value_keys where entries match the exact table:
|
31
|
+
# (thus, there is an explicit call like primary_key :this_field, :this_sequence)
|
32
|
+
@sequence_values.each_pair { |table, fields|
|
33
|
+
value_keys[table].update(@sequence_values[table])
|
34
|
+
}
|
35
|
+
# Parse sequence_values into value_keys where a table depends from a
|
36
|
+
# sequence field by IS_A
|
37
|
+
value_keys = update_sequence_values_deps(@base_table,
|
38
|
+
@is_a,
|
39
|
+
value_keys)
|
40
|
+
query_string = insert_query(@base_table,
|
41
|
+
@is_a,
|
42
|
+
value_keys)
|
43
|
+
|
44
|
+
begin
|
45
|
+
Lore::Connection.perform("BEGIN;\n#{query_string}\nCOMMIT;")
|
46
|
+
rescue ::Exception => excep
|
47
|
+
Lore::Connection.perform("ROLLBACK;")
|
48
|
+
raise excep
|
49
|
+
ensure
|
50
|
+
Lore::Context.leave if @accessor.get_context
|
51
|
+
end
|
52
|
+
@accessor.flush_entity_cache()
|
53
|
+
|
54
|
+
# value_keys now are extended by sequence values:
|
55
|
+
return value_keys
|
56
|
+
end # }}}
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def load_sequence_values(sequences)
|
61
|
+
# {{{
|
62
|
+
sequence_values = Hash.new
|
63
|
+
|
64
|
+
sequences.each_pair { |table_name, field|
|
65
|
+
field.each_pair { |field_name, sequence_name|
|
66
|
+
|
67
|
+
pure_schema_name = table_name.split('.')[0]
|
68
|
+
pure_table_name = table_name.split('.')[1]
|
69
|
+
temp_sequence_name = "#{sequence_name}_temp"
|
70
|
+
|
71
|
+
sequence_query_string = "SELECT nextval('#{pure_schema_name}.#{sequence_name}'::text) as #{temp_sequence_name}; "
|
72
|
+
sequence_value_result = Lore::Connection.perform(sequence_query_string)
|
73
|
+
|
74
|
+
sequence_values[table_name] = Hash.new if sequence_values[table_name] == nil
|
75
|
+
sequence_values[table_name][field_name.to_sym] = sequence_value_result.get_field_value(0, temp_sequence_name)
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
return sequence_values
|
80
|
+
end # }}}
|
81
|
+
|
82
|
+
def atomic_insert_query(table_name, value_keys)
|
83
|
+
# {{{
|
84
|
+
query_string = "\nINSERT INTO #{table_name} "
|
85
|
+
|
86
|
+
value_string = String.new
|
87
|
+
field_string = String.new
|
88
|
+
key_counter = 0
|
89
|
+
value_keys.each_pair { |field, value|
|
90
|
+
|
91
|
+
field_string << "#{field}"
|
92
|
+
value_string << "'#{value}'"
|
93
|
+
if key_counter < value_keys.length-1
|
94
|
+
field_string += ', '
|
95
|
+
value_string += ', '
|
96
|
+
end
|
97
|
+
key_counter += 1
|
98
|
+
}
|
99
|
+
query_string += "(#{field_string}) \n VALUES (#{value_string}); "
|
100
|
+
return query_string
|
101
|
+
end # }}}
|
102
|
+
|
103
|
+
def insert_query(table_name,
|
104
|
+
is_a,
|
105
|
+
value_keys,
|
106
|
+
query_string='')
|
107
|
+
# {{{
|
108
|
+
is_a.each_pair { |table, base_tables|
|
109
|
+
|
110
|
+
# pass base tables first, recursively, as IS_A-based creation has
|
111
|
+
# to be done bottom-up:
|
112
|
+
query_string << insert_query(table, base_tables, value_keys).to_s
|
113
|
+
}
|
114
|
+
# finally, add query string for this table:
|
115
|
+
if(value_keys[table_name] != nil)
|
116
|
+
query_string << atomic_insert_query(table_name, value_keys[table_name]).to_s
|
117
|
+
else
|
118
|
+
Lore.logger.debug { "No initial attribute for IS_A related table #{table_name} given. " }
|
119
|
+
end
|
120
|
+
query_string
|
121
|
+
end # }}}
|
122
|
+
|
123
|
+
def update_sequence_values_deps(table, is_a, value_keys)
|
124
|
+
# {{{
|
125
|
+
is_a.each_pair { |base_table, next_base_tables|
|
126
|
+
# extend each value_key with primary keys of its base table:
|
127
|
+
if @sequence_values.has_key?(base_table) then
|
128
|
+
# For sequenced foreign keys, only one attribute is allowed!
|
129
|
+
own_fkey = @foreign_keys[table][base_table].first.first
|
130
|
+
if own_fkey
|
131
|
+
then
|
132
|
+
foreign_pkey = @foreign_keys[table][base_table].at(1).first
|
133
|
+
seq_val = @sequence_values[base_table][foreign_pkey]
|
134
|
+
value_keys[table][own_fkey] = seq_val
|
135
|
+
end
|
136
|
+
end
|
137
|
+
value_keys = update_sequence_values_deps(base_table,
|
138
|
+
next_base_tables,
|
139
|
+
value_keys)
|
140
|
+
}
|
141
|
+
return value_keys
|
142
|
+
end # }}}
|
143
|
+
|
144
|
+
end # class
|
145
|
+
|
146
|
+
end # module
|
@@ -0,0 +1,299 @@
|
|
1
|
+
|
2
|
+
require('lore/cache/cached_entities')
|
3
|
+
require('lore/clause')
|
4
|
+
|
5
|
+
module Lore
|
6
|
+
|
7
|
+
class Table_Select
|
8
|
+
|
9
|
+
@@logger = Lore.logger
|
10
|
+
|
11
|
+
public
|
12
|
+
|
13
|
+
def initialize(accessor)
|
14
|
+
@accessor = accessor
|
15
|
+
end
|
16
|
+
|
17
|
+
# Extracted, recursive method for building the JOIN-part of
|
18
|
+
# a SELECT query.
|
19
|
+
def self.build_joined_query(accessor,
|
20
|
+
join_type='JOIN',
|
21
|
+
query_string='',
|
22
|
+
joined_tables=[])
|
23
|
+
# {{{
|
24
|
+
associations = accessor.__associations__
|
25
|
+
top_table = accessor.table_name
|
26
|
+
is_a_hierarchy = associations.joins()
|
27
|
+
own_primary_keys = associations.primary_keys()
|
28
|
+
own_foreign_keys = associations.foreign_keys()
|
29
|
+
joined_models = associations.joined_models
|
30
|
+
|
31
|
+
# predefine
|
32
|
+
own_p_keys = Hash.new
|
33
|
+
foreign_p_keys = Hash.new
|
34
|
+
on_string = String.new
|
35
|
+
field_counter = 0
|
36
|
+
|
37
|
+
is_a_hierarchy.each_pair { |foreign_table, foreign_base_tables|
|
38
|
+
# Ensure no table is joined twice
|
39
|
+
if !(joined_tables.include?(foreign_table)) then
|
40
|
+
# [ [ a_id_f, a_id ], [ b_id_f, b_id ] ]
|
41
|
+
mapping = own_foreign_keys[top_table][foreign_table]
|
42
|
+
own_f_keys = mapping.at(0)
|
43
|
+
foreign_p_keys = mapping.at(1)
|
44
|
+
|
45
|
+
if own_f_keys then
|
46
|
+
joined_tables << foreign_table
|
47
|
+
query_string << "\n #{join_type} #{foreign_table} ON ("
|
48
|
+
on_string = ''
|
49
|
+
foreign_p_keys.uniq.each_with_index { |foreign_field, field_counter|
|
50
|
+
# base.table.foreign_field = this.table.own_field
|
51
|
+
on_string << "#{foreign_table}.#{foreign_field} = #{top_table}.#{own_f_keys[field_counter]}"
|
52
|
+
query_string << ", " if field_counter > 0
|
53
|
+
query_string << on_string
|
54
|
+
}
|
55
|
+
query_string << ')'
|
56
|
+
end
|
57
|
+
|
58
|
+
# sub-joins of joined table:
|
59
|
+
query_string = build_joined_query(joined_models[foreign_table].first,
|
60
|
+
join_type,
|
61
|
+
query_string,
|
62
|
+
joined_tables)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
return query_string
|
66
|
+
end # }}}
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def build_select_query(value_keys)
|
71
|
+
# {{{
|
72
|
+
raise ::Exception.new("This method is deprecated. ")
|
73
|
+
|
74
|
+
query_string = "SELECT * FROM #{base_table} #{self.class.build_joined_query(@accessor)} WHERE "
|
75
|
+
query_string << "\n WHERE "
|
76
|
+
|
77
|
+
operator = '='
|
78
|
+
field = String.new
|
79
|
+
value_keys[0].each_pair { |field, value|
|
80
|
+
# Filtering values is not necessary when feeded with
|
81
|
+
# Lore::Attributes instance
|
82
|
+
field = field.to_s
|
83
|
+
if value.instance_of? Hash then
|
84
|
+
value.each_pair { |attrib_name, attrib_value|
|
85
|
+
query_string << "#{field}.#{attrib_name} #{operator} '#{attrib_value.to_s.lore_escape}' AND "
|
86
|
+
}
|
87
|
+
else
|
88
|
+
query_string << "#{base_table}." if field.split('.')[2].nil?
|
89
|
+
query_string << "#{field} #{operator} '#{value.to_s.lore_escape}' AND "
|
90
|
+
end
|
91
|
+
}
|
92
|
+
# remove trailing AND:
|
93
|
+
query_string.chomp!(' AND ')
|
94
|
+
|
95
|
+
return query_string
|
96
|
+
end # }}}
|
97
|
+
|
98
|
+
def plan_select_query(what, &block)
|
99
|
+
# {{{
|
100
|
+
auto_plan = Lore::Plan.new(clause, select_query(what, &block), @accessor)
|
101
|
+
end # }}}
|
102
|
+
|
103
|
+
public
|
104
|
+
|
105
|
+
def select_query(what=nil, clause_parser=nil, polymorphic=false, &block)
|
106
|
+
# {{{
|
107
|
+
# Example:
|
108
|
+
# select(Car.name) -> SELECT max(id)
|
109
|
+
if what.instance_of? Clause then
|
110
|
+
what = what.to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
if(what.nil? || what == '*' || what == '') then
|
114
|
+
query_as_part = '*'
|
115
|
+
else
|
116
|
+
query_as_part = what.to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
if block_given? then
|
120
|
+
yield_obj = Clause_Parser.new(@accessor)
|
121
|
+
clause_parser = yield *yield_obj
|
122
|
+
end
|
123
|
+
|
124
|
+
query_string = 'SELECT '
|
125
|
+
|
126
|
+
query_parts = clause_parser.parts
|
127
|
+
query_parts[:what] = query_as_part
|
128
|
+
query_parts[:from] = "FROM #{@accessor.table_name}"
|
129
|
+
# Add JOIN part for system defined type (user defined
|
130
|
+
# joins will be set in Clause_Parser object in later
|
131
|
+
# yield):
|
132
|
+
if polymorphic && @accessor.is_polymorphic? then
|
133
|
+
query_parts[:all_joins] = self.class.build_polymorphic_joined_query(@accessor) << query_parts[:join]
|
134
|
+
else
|
135
|
+
query_parts[:all_joins] = self.class.build_joined_query(@accessor) << query_parts[:join]
|
136
|
+
end
|
137
|
+
query_string << [ :what, :from, :all_joins, :where,
|
138
|
+
:group_by, :having, :filter,
|
139
|
+
:order_by, :limit, :offset ].map { |part|
|
140
|
+
query_parts[part]
|
141
|
+
}.join(' ')
|
142
|
+
|
143
|
+
# Attaching UNION selects:
|
144
|
+
if clause_parser.unions then
|
145
|
+
clause_parser.unions.each { |select_query_obj|
|
146
|
+
query_string << "\nUNION\n"
|
147
|
+
union_sql = select_query_obj.sql
|
148
|
+
query_string << union_sql
|
149
|
+
}
|
150
|
+
end
|
151
|
+
|
152
|
+
return { :query => query_string, :joined_models => clause_parser.parts[:joined] }
|
153
|
+
end # }}}
|
154
|
+
|
155
|
+
def self.build_polymorphic_joined_query(accessor)
|
156
|
+
# {{{
|
157
|
+
# Generates full outer join on all concrete submodels of this
|
158
|
+
# (abstract) polymorphic model.
|
159
|
+
|
160
|
+
# Correct query for this is:
|
161
|
+
# select * from asset
|
162
|
+
# ---- BEGIN IMPLICIT JOINS
|
163
|
+
# -- full outer join on concrete model's base table
|
164
|
+
# full outer join document_asset on (asset.asset_id = document_asset.asset_id)
|
165
|
+
# -- left join on concrete model's implicitly joined tables
|
166
|
+
# left join document_asset_info on (document_asset_info.document_asset_id = document_asset.id)
|
167
|
+
# -- full outer join on next concrete base table
|
168
|
+
# full outer join media_asset on (asset.asset_id = media_asset.asset_id)
|
169
|
+
# -- left join on concrete model's implicitly joined tables
|
170
|
+
# left join media_asset_info on (media_asset_info.media_asset_id = media_asset.id)
|
171
|
+
# ---- END IMPLICIT JOINS
|
172
|
+
# ---- BEGIN EXPLICIT JOINS
|
173
|
+
# join Asset_Comments using (asset_id)
|
174
|
+
# ---- END EXPLICIT JOINS
|
175
|
+
# where ...
|
176
|
+
# order by model;
|
177
|
+
concrete_models = accessor.__associations__.concrete_models
|
178
|
+
own_table_name = accessor.table_name
|
179
|
+
implicit_joins = build_joined_query(accessor)
|
180
|
+
own_pkeys = accessor.__associations__.primary_keys[own_table_name]
|
181
|
+
concrete_models.each { |concrete_model|
|
182
|
+
join_constraints = []
|
183
|
+
concrete_model.__associations__.foreign_keys[concrete_model.table_name][own_table_name].first.each_with_index { |fkey,index|
|
184
|
+
join_constraints << "#{own_table_name}.#{own_pkeys[index]} = #{concrete_model.table_name}.#{fkey}"
|
185
|
+
}
|
186
|
+
implicit_joins << "\nFULL OUTER JOIN #{concrete_model.table_name} ON (#{join_constraints.join(' AND ')}) "
|
187
|
+
# Attach the concrete model's own implicit joins (is_a and aggregates),
|
188
|
+
# but don't join polymorphic base table (own_table_name) again:
|
189
|
+
implicit_joins << build_joined_query(concrete_model, ' LEFT JOIN', '', [own_table_name])
|
190
|
+
}
|
191
|
+
implicit_joins
|
192
|
+
end # }}}
|
193
|
+
|
194
|
+
def select(what, polymorphic=false, &block)
|
195
|
+
# {{{
|
196
|
+
query_string = select_query(what, nil, polymorphic, &block)
|
197
|
+
return perform_select(query_string[:query])
|
198
|
+
end # }}}
|
199
|
+
|
200
|
+
def select_cached(what, clause_parser=nil, polymorphic=false, &block)
|
201
|
+
# {{{
|
202
|
+
joined_models = []
|
203
|
+
query_string = nil
|
204
|
+
query = nil
|
205
|
+
if clause_parser.nil? && block_given? then
|
206
|
+
query = select_query(what, nil, polymorphic, &block)
|
207
|
+
query_string = query[:query]
|
208
|
+
joined_models = query[:joined_models]
|
209
|
+
elsif !block_given? && clause_parser then
|
210
|
+
query = select_query(what, clause_parser, polymorphic)
|
211
|
+
query_string = query[:query]
|
212
|
+
joined_models = query[:joined_models]
|
213
|
+
else
|
214
|
+
query_string = what.to_s
|
215
|
+
end
|
216
|
+
what = false if what.empty?
|
217
|
+
|
218
|
+
result = Array.new
|
219
|
+
if Lore.cache_enabled? &&
|
220
|
+
@accessor.entity_cache &&
|
221
|
+
@accessor.entity_cache.include?(@accessor, query) then
|
222
|
+
result = @accessor.entity_cache.read(@accessor, query)
|
223
|
+
result = [] if result.to_s == ''
|
224
|
+
else
|
225
|
+
Context.enter(@accessor.get_context) if @accessor.get_context
|
226
|
+
begin
|
227
|
+
result = Lore::Connection.perform(query_string).get_rows()
|
228
|
+
if polymorphic && !what && @accessor.is_polymorphic? then
|
229
|
+
result.map! { |row|
|
230
|
+
Lore.logger.debug { "Polymorphic select returned: #{row.inspect}" }
|
231
|
+
row = @accessor.new_polymorphic(row, joined_models)
|
232
|
+
}
|
233
|
+
else
|
234
|
+
result.map! { |row|
|
235
|
+
row = (@accessor.new(row, joined_models))
|
236
|
+
}
|
237
|
+
end
|
238
|
+
rescue PGError => pge
|
239
|
+
raise pge
|
240
|
+
ensure
|
241
|
+
Context.leave if @accessor.get_context
|
242
|
+
end
|
243
|
+
if Lore.cache_enabled? && @accessor.entity_cache then
|
244
|
+
@accessor.entity_cache.create(@accessor, query, result)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
return result
|
248
|
+
end # }}}
|
249
|
+
|
250
|
+
def prepare(plan_name, args, &block)
|
251
|
+
# {{{
|
252
|
+
args_string = ''
|
253
|
+
args.map { |a| a = Lore::TYPE_NAMES[a] }
|
254
|
+
if args.to_s != '' && args.length > 0 then args_string = "(#{args.join(',')})" end
|
255
|
+
query_string = "PREPARE #{@accessor.table_name.gsub('.','_')}__#{plan_name.to_s}#{args_string} AS " << select_query(&block)[:query]
|
256
|
+
begin
|
257
|
+
Context.enter(@accessor.get_context) if @accessor.get_context
|
258
|
+
result = Lore::Connection.perform(query_string)
|
259
|
+
rescue ::Exception => excep
|
260
|
+
@@logger.debug("Exception when preparing #{plan_name.to_s}: #{excep.message}")
|
261
|
+
ensure
|
262
|
+
Context.leave if @accessor.get_context
|
263
|
+
end
|
264
|
+
end # }}}
|
265
|
+
|
266
|
+
def select_prepared(plan_name, *args)
|
267
|
+
# {{{
|
268
|
+
args_string = ''
|
269
|
+
if args.to_s != '' && args.length > 0 then args_string = "(#{args.join(',')})" end
|
270
|
+
query_string = "EXECUTE #{plan_name.to_s} #{args_string}; "
|
271
|
+
return select_cached(query_string)
|
272
|
+
end # }}}
|
273
|
+
|
274
|
+
def deallocate(plan_name)
|
275
|
+
# {{{
|
276
|
+
begin
|
277
|
+
query_string = "DEALLOCATE #{@accessor.table_name.gsub('.','_')}__#{plan_name.to_s}; "
|
278
|
+
result = Lore::Connection.perform(query_string)
|
279
|
+
rescue ::Exception => excep
|
280
|
+
end
|
281
|
+
end # }}}
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
def perform_select(query_string)
|
286
|
+
# {{{
|
287
|
+
Context.enter(@accessor.get_context) if @accessor.get_context
|
288
|
+
begin
|
289
|
+
return Lore::Connection.perform(query_string)
|
290
|
+
rescue PGError => pge
|
291
|
+
raise pge
|
292
|
+
ensure
|
293
|
+
Context.leave if @accessor.get_context
|
294
|
+
end
|
295
|
+
end # }}}
|
296
|
+
|
297
|
+
end # class
|
298
|
+
|
299
|
+
end # module
|