lore 0.4.8 → 0.9.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/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
|