lore 0.4.8 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/Manifest.txt +16 -7
  2. data/README.rdoc +91 -0
  3. data/benchmark/benchmark.sql +11 -0
  4. data/benchmark/results.txt +28 -0
  5. data/benchmark/select.rb +352 -0
  6. data/lib/lore.rb +22 -8
  7. data/lib/lore/adapters/context.rb +64 -0
  8. data/lib/lore/adapters/postgres-pr.rb +6 -0
  9. data/lib/lore/adapters/postgres-pr/connection.rb +93 -0
  10. data/lib/lore/adapters/postgres-pr/result.rb +63 -0
  11. data/lib/lore/{types.rb → adapters/postgres-pr/types.rb} +36 -0
  12. data/lib/lore/adapters/postgres.rb +24 -0
  13. data/lib/lore/adapters/postgres/connection.rb +81 -0
  14. data/lib/lore/adapters/postgres/result.rb +82 -0
  15. data/lib/lore/adapters/postgres/types.rb +91 -0
  16. data/lib/lore/bits.rb +18 -0
  17. data/lib/lore/cache/abstract_entity_cache.rb +2 -1
  18. data/lib/lore/cache/cacheable.rb +12 -177
  19. data/lib/lore/cache/memcache_entity_cache.rb +89 -0
  20. data/lib/lore/cache/memory_entity_cache.rb +77 -0
  21. data/lib/lore/cache/mmap_entity_cache.rb +2 -2
  22. data/lib/lore/cache/mmap_entity_cache_bork.rb +86 -0
  23. data/lib/lore/clause.rb +107 -35
  24. data/lib/lore/{exception → exceptions}/ambiguous_attribute.rb +2 -2
  25. data/lib/lore/{exception → exceptions}/cache_exception.rb +1 -1
  26. data/lib/lore/exceptions/database_exception.rb +16 -0
  27. data/lib/lore/{exception/invalid_parameter.rb → exceptions/invalid_field.rb} +7 -4
  28. data/lib/lore/exceptions/unknown_type.rb +18 -0
  29. data/lib/lore/exceptions/validation_failure.rb +71 -0
  30. data/lib/lore/gui/form_generator.rb +109 -60
  31. data/lib/lore/gui/lore_model_select_field.rb +1 -0
  32. data/lib/lore/migration.rb +84 -25
  33. data/lib/lore/model.rb +3 -18
  34. data/lib/lore/{aspect.rb → model/aspect.rb} +0 -0
  35. data/lib/lore/model/associations.rb +225 -0
  36. data/lib/lore/model/attribute_settings.rb +233 -0
  37. data/lib/lore/model/filters.rb +34 -0
  38. data/lib/lore/model/mockable.rb +62 -0
  39. data/lib/lore/{model_factory.rb → model/model_factory.rb} +68 -39
  40. data/lib/lore/model/model_instance.rb +382 -0
  41. data/lib/lore/{model_shortcuts.rb → model/model_shortcuts.rb} +7 -0
  42. data/lib/lore/model/polymorphic.rb +53 -0
  43. data/lib/lore/model/prepare.rb +97 -0
  44. data/lib/lore/model/table_accessor.rb +1016 -0
  45. data/lib/lore/query.rb +71 -0
  46. data/lib/lore/query_shortcuts.rb +43 -11
  47. data/lib/lore/strategies/table_delete.rb +115 -0
  48. data/lib/lore/strategies/table_insert.rb +146 -0
  49. data/lib/lore/strategies/table_select.rb +299 -0
  50. data/lib/lore/strategies/table_update.rb +155 -0
  51. data/lib/lore/validation/parameter_validator.rb +85 -26
  52. data/lib/lore/validation/type_validator.rb +34 -78
  53. data/{custom_models.rb → lore-0.9.2.gem} +0 -0
  54. data/lore.gemspec +26 -17
  55. data/spec/clause.rb +37 -0
  56. data/spec/fixtures/blank_models.rb +37 -0
  57. data/{test/model.rb → spec/fixtures/models.rb} +64 -41
  58. data/spec/fixtures/polymorphic_models.rb +68 -0
  59. data/spec/model_associations.rb +86 -0
  60. data/spec/model_create.rb +47 -0
  61. data/spec/model_definition.rb +151 -0
  62. data/spec/model_delete.rb +31 -0
  63. data/spec/model_inheritance.rb +50 -0
  64. data/spec/model_polymorphic.rb +85 -0
  65. data/spec/model_select.rb +101 -0
  66. data/spec/model_select_eager.rb +42 -0
  67. data/spec/model_union_select.rb +33 -0
  68. data/spec/model_update.rb +45 -0
  69. data/spec/model_validation.rb +20 -0
  70. data/spec/spec_db.sql +808 -0
  71. data/spec/spec_env.rb +19 -0
  72. data/spec/spec_helpers.rb +77 -0
  73. metadata +93 -82
  74. data/lib/lore/README.txt +0 -84
  75. data/lib/lore/behaviours/lockable.rb +0 -55
  76. data/lib/lore/behaviours/movable.rb +0 -72
  77. data/lib/lore/behaviours/paginated.rb +0 -31
  78. data/lib/lore/behaviours/versioned.rb +0 -36
  79. data/lib/lore/connection.rb +0 -152
  80. data/lib/lore/exception/invalid_klass_parameters.rb +0 -63
  81. data/lib/lore/exception/unknown_typecode.rb +0 -19
  82. data/lib/lore/result.rb +0 -119
  83. data/lib/lore/symbol.rb +0 -58
  84. data/lib/lore/table_accessor.rb +0 -1790
  85. data/lib/lore/table_deleter.rb +0 -116
  86. data/lib/lore/table_inserter.rb +0 -170
  87. data/lib/lore/table_instance.rb +0 -389
  88. data/lib/lore/table_selector.rb +0 -285
  89. data/lib/lore/table_updater.rb +0 -157
  90. data/lib/lore/validation.rb +0 -65
  91. data/lib/lore/validation/message.rb +0 -60
  92. data/lib/lore/validation/reason.rb +0 -52
  93. data/lore_test.log +0 -2366
  94. data/test/README +0 -31
  95. data/test/custom_models.rb +0 -18
  96. data/test/env.rb +0 -5
  97. data/test/prepare.rb +0 -37
  98. data/test/tc_aspect.rb +0 -58
  99. data/test/tc_cache.rb +0 -83
  100. data/test/tc_clause.rb +0 -104
  101. data/test/tc_deep_inheritance.rb +0 -49
  102. data/test/tc_factory.rb +0 -57
  103. data/test/tc_filter.rb +0 -37
  104. data/test/tc_form.rb +0 -32
  105. data/test/tc_model.rb +0 -140
  106. data/test/tc_prepare.rb +0 -44
  107. data/test/tc_refined_query.rb +0 -88
  108. data/test/tc_table_accessor.rb +0 -267
  109. data/test/tc_thread.rb +0 -100
  110. data/test/test_db.sql +0 -400
  111. data/test/test_lore.rb +0 -50
@@ -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
@@ -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 << '(' << attribute_name.to_s << ') AS "value"'
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
- result = @accessor.select { |entity|
225
- entity.where(@condition)
226
- entity.limit(@limit, @offset) unless @limit.nil?
227
- @order_by.each { |o|
228
- entity.order_by(o[0], o[1])
229
- }
230
- entity.group_by(@group_by) unless @group_by.nil?
231
- entity.having(@having) unless @having.nil?
232
- entity
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