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.
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