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,77 @@
1
+
2
+ require 'lore'
3
+ require 'lore/cache/abstract_entity_cache'
4
+ begin
5
+ require 'mmap'
6
+ require 'activesupport'
7
+ require 'active_support/cache/memory_store'
8
+ rescue LoadError
9
+ Lore.log { 'Mmap or ActiveSupport for Ruby could not be loaded. You won\'t be able to use Mmap_Entity_Cache. ' }
10
+ module ActiveSupport
11
+ module Cache
12
+ class MemoryStore
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module Lore
19
+ module Cache
20
+
21
+
22
+ # Implementation of entity cache using MMapped PStor files.
23
+ # Derived from Abstract_Entity_Cache.
24
+ # Uses Lore::Cache::Cache_Helpers for generating PStor files.
25
+ class Memory_Entity_Cache < Abstract_Entity_Cache
26
+ extend Cache_Helpers
27
+
28
+ @@logger = Logger.new('/tmp/lore_cache.log')
29
+
30
+ @@store = ActiveSupport::Cache::MemoryStore.new()
31
+
32
+
33
+ def self.flush(accessor)
34
+ index = accessor.table_name
35
+ return unless Lore.cache_enabled?
36
+ Dir.glob("/tmp/lore_cache__#{index}*").each { |cache_file|
37
+ @@logger.debug('Clearing cache file ' << cache_file.to_s.split('_').last)
38
+ begin
39
+ @@store.delete(cache_file.to_s.split('_').last)
40
+ rescue ::Exception => excep
41
+ # Another process already killed this file
42
+ end
43
+ }
44
+ end
45
+
46
+ def self.read(accessor, query_obj)
47
+ @@logger.debug { 'Loading from cache: ' << index_for(query_obj[:query]) }
48
+ store = @@store.read("#{accessor.table_name}--#{index_for(query_obj[:query])}")
49
+ return [] unless store
50
+
51
+ return store[:values]
52
+ # result << accessor.new(r, joined_models, :cached)
53
+ end
54
+
55
+ def self.create(accessor, query_object, result)
56
+ entry = query_object.update({ :values => result })
57
+ @@store.write("#{accessor.table_name}--#{index_for(query_object[:query])}", entry)
58
+ end
59
+
60
+ def self.include?(accessor, query_obj)
61
+ hit = @@store.exist?("#{accessor.table_name}--#{index_for(query_obj[:query])}")
62
+ @@logger.debug { 'Cache miss: ' << index_for(query_obj[:query]) } unless hit
63
+ @@logger.info { 'CACHE MISS on ' << query_obj[:query] } unless hit
64
+ return hit
65
+ end
66
+
67
+ def self.delete(index)
68
+ @@logger.debug { "Deleting index #{index}" }
69
+ @@store.delete(index)
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+
@@ -34,9 +34,9 @@ module Cache
34
34
  end
35
35
 
36
36
  def self.read(accessor, query_obj)
37
- @@logger.debug('Loading from cache: ' << index_for(query_obj[:query]))
37
+ Lore.log { 'Loading from cache: ' << index_for(query_obj[:query]) }
38
38
  store = Mmap.new(storefile_of(accessor.table_name, query_obj[:query]))
39
- @@logger.debug('STORE: ' << store.inspect)
39
+ Lore.log { 'STORE: ' << store.inspect }
40
40
  return [] unless store
41
41
  result = Marshal::load(store)
42
42
  return result['dump']
@@ -0,0 +1,86 @@
1
+
2
+ require 'lore'
3
+ require 'lore/cache/abstract_entity_cache'
4
+ begin
5
+ require 'mmap'
6
+ rescue LoadError
7
+ Lore.log { 'Mmap for Ruby could not be found. You won\'t be able to use Mmap_Entity_Cache. ' }
8
+ end
9
+
10
+ module Lore
11
+ module Cache
12
+
13
+
14
+ # Implementation of entity cache using MMapped PStor files.
15
+ # Derived from Abstract_Entity_Cache.
16
+ # Uses Lore::Cache::Cache_Helpers for generating PStor files.
17
+ class Mmap_Entity_Cache < Abstract_Entity_Cache
18
+ extend Cache_Helpers
19
+
20
+ @@logger = Logger.new('/tmp/lore_cache.log')
21
+
22
+
23
+ def self.flush(accessor)
24
+ index = accessor.table_name
25
+ return unless Lore.cache_enabled?
26
+ Dir.glob("/tmp/lore_cache__#{index}*").each { |cache_file|
27
+ @@logger.debug('Clearing cache file ' << cache_file)
28
+ begin
29
+ File.unlink(cache_file)
30
+ rescue ::Exception => excep
31
+ # Another process already killed this file
32
+ end
33
+ }
34
+ end
35
+
36
+ def self.read(accessor, query_obj)
37
+ @@logger.debug { 'Loading from cache: ' << index_for(query_obj[:query]) }
38
+ store = Mmap.new(storefile_of(accessor.table_name, query_obj[:query]))
39
+ return [] unless store
40
+ result = Marshal::load(store)
41
+ return result
42
+
43
+ vector = []
44
+ joined_models = result['dump'][:joined_models]
45
+ joined_models ||= []
46
+ result['dump'][:values].each { |r|
47
+ vector << accessor.new(r, joined_models, true)
48
+ }
49
+ return vector
50
+
51
+ #
52
+ # return result['dump']
53
+ end
54
+
55
+ def self.create(accessor, query_object, result)
56
+ storefile = create_mmap(accessor, query_object, result)
57
+ end
58
+ def self.create_mmap(accessor, query_object, result)
59
+ storefile = storefile_of(accessor.table_name, query_object[:query])
60
+ store = create_store(storefile)
61
+ store.transaction do
62
+ # store['dump'] = { :values => result,
63
+ # :joined_models => query_object[:joined_models] }
64
+ store['dump'] = result
65
+ end
66
+ @@logger.debug('Creating cache entry for ' << storefile )
67
+ Mmap.new(storefile)
68
+ return storefile
69
+ end
70
+
71
+ def self.include?(accessor, query_obj)
72
+ hit = FileTest.exist?(storefile_of(accessor.table_name, query_obj[:query]))
73
+ @@logger.debug { 'Cache miss: ' << index_for(query_obj[:query]) } unless hit
74
+ return hit
75
+ end
76
+
77
+ def self.delete(index)
78
+ File.rm(storefile_of(accessor.table_name, query_obj[:query]))
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
85
+
86
+
@@ -1,8 +1,9 @@
1
1
 
2
- require('lore/table_accessor')
2
+ require('lore/model/table_accessor')
3
3
  require('lore/query_shortcuts') # So far, a forward-declaration could do here as well
4
4
 
5
5
  class String
6
+ # Poor man's SQL injection prevention ...
6
7
  def lore_escape
7
8
  self.gsub!("'","X")
8
9
  self.gsub!('"','X')
@@ -10,6 +11,59 @@ class String
10
11
  end
11
12
  end
12
13
 
14
+ class Symbol
15
+ # {{{
16
+
17
+ # Do not overload Symbol#==
18
+ def eq(other)
19
+ Lore::Clause.new(self.to_s)==(other)
20
+ end
21
+ alias is eq
22
+ def >=(other)
23
+ Lore::Clause.new(self.to_s)>=(other)
24
+ end
25
+ def <=(other)
26
+ Lore::Clause.new(self.to_s)<=(other)
27
+ end
28
+ def >(other)
29
+ Lore::Clause.new(self.to_s)>(other)
30
+ end
31
+ def <(other)
32
+ Lore::Clause.new(self.to_s)<=(other)
33
+ end
34
+ def <=>(other)
35
+ Lore::Clause.new(self.to_s)<=>(other)
36
+ end
37
+ def not(other)
38
+ Lore::Clause.new(self.to_s)<=>(other)
39
+ end
40
+ def like(other)
41
+ Lore::Clause.new(self.to_s).like(other)
42
+ end
43
+ def ilike(other)
44
+ Lore::Clause.new(self.to_s).ilike(other)
45
+ end
46
+ def has_element(other)
47
+ Lore::Clause.new(self.to_s).has_element(other)
48
+ end
49
+ def has_element_like(other)
50
+ Lore::Clause.new(self.to_s).has_element_like(other)
51
+ end
52
+ def has_element_ilike(other)
53
+ Lore::Clause.new(self.to_s).has_element_ilike(other)
54
+ end
55
+ def in(other)
56
+ Lore::Clause.new(self.to_s).in(other)
57
+ end
58
+ def not_in(other)
59
+ Lore::Clause.new(self.to_s).not_in(other)
60
+ end
61
+ def between(s,e)
62
+ Lore::Clause.new(self.to_s).between(s,e)
63
+ end
64
+
65
+ end # }}}
66
+
13
67
  module Lore
14
68
 
15
69
  def self.parse_field_value(value)
@@ -32,7 +86,7 @@ module Lore
32
86
  # k.limit(10)
33
87
  # }
34
88
  #
35
- class Join # :nodoc:
89
+ class Join
36
90
  # {{{
37
91
 
38
92
  def initialize(clause, base_klass, join_klass, type=:natural)
@@ -44,21 +98,10 @@ module Lore
44
98
  # By joining with another klass, new attributes are added
45
99
  # we have to include in the AS part of the query:
46
100
  new_attributes = ''
47
- # join_klass.get_attributes.each { |attr_set|
48
- # table = attr_set[0]
49
- # attr_set[1].each { |attrib_name|
50
- # new_attributes += ', ' << "\n"
51
- # new_attributes += table + '.' << attrib_name
52
- # new_attributes += ' AS '
53
- # new_attributes += '"' << table << '.' << attrib_name << '" '
54
- # }
55
- # }
56
101
  implicit_joins = ''
57
102
  @clause_parser.add_as(new_attributes)
58
103
 
59
- @implicit_joins = Table_Selector.build_joined_query(join_klass)
60
-
61
- # @clause_parser.add_join(self)
104
+ @implicit_joins = Table_Select.build_joined_query(join_klass)
62
105
  end
63
106
 
64
107
  def implicit_joins
@@ -99,12 +142,26 @@ module Lore
99
142
 
100
143
  end # class }}}
101
144
 
102
- class Clause < String # :nodoc:
145
+ # Clause objects are responsible for operands on Model attributes,
146
+ # as in WHERE parts of a query.
147
+ # Model klass methods named like one if its (possibly inherited)
148
+ # field names return a preconfigured Clause object on that field.
149
+ #
150
+ # Example:
151
+ #
152
+ # (Car.num_seats > 100).to_sql --> "public.vehicle.num_seats > '100'"
153
+ #
154
+ class Clause < String
103
155
  # {{{
104
156
 
105
157
  attr_reader :field_name, :value_string, :left_side, :plan
106
158
 
107
159
  def initialize(field_name='', left_side='', value_string='', plan={})
160
+ if field_name.instance_of?(TrueClass) then
161
+ return (Clause.new('1') == '1')
162
+ elsif field_name.instance_of?(FalseClass) then
163
+ return (Clause.new('1') == '0')
164
+ end
108
165
  @value_string = value_string
109
166
  @left_side = left_side
110
167
  @field_name = field_name
@@ -114,31 +171,32 @@ module Lore
114
171
  Clause_Parser.new(accessor)
115
172
  end
116
173
 
174
+ # Check for Refined_Select needed for functionality of
175
+ # Refined_Select. Example:
176
+ #
177
+ # User.all.with(User.user_id.in( Admin.all(:user_id) ))
178
+ #
117
179
  def not_in(nested_query_string)
118
- # Check for Refined_Select needed for functionality of
119
- # Refined_Select. Example:
120
- #
121
- # User.all.with(User.user_id.in( Admin.all(:user_id) ))
122
- #
123
180
  if(nested_query_string.instance_of? Refined_Select) then
124
181
  nested_query_string.to_inner_select
125
182
  else
183
+ nested_query_string = nested_query_string.join(',') if nested_query_string.is_a?(Array)
126
184
  @value_string = @field_name << ' NOT IN (' << "\n" << nested_query_string.to_s << ') '
127
185
  Clause.new(@value_string, @left_side+@value_string)
128
186
  end
129
187
  end
130
188
 
189
+ # Check for Refined_Select needed for functionality of
190
+ # Refined_Select. Example:
191
+ #
192
+ # User.all.with(User.user_id.in( Admin.all(:user_id) ))
193
+ #
131
194
  def in(nested_query_string)
132
- # Check for Refined_Select needed for functionality of
133
- # Refined_Select. Example:
134
- #
135
- # User.all.with(User.user_id.in( Admin.all(:user_id) ))
136
- #
137
195
  if(nested_query_string.instance_of? Refined_Select) then
138
196
  nested_query_string = nested_query_string.to_select
139
197
  elsif nested_query_string.instance_of? Array then
140
- raise ::Exception.new('IN field expects at least one element. ') if nested_query_string.length == 0
141
198
  nested_query_string = nested_query_string.join(',')
199
+ nested_query_string = 'NULL' if nested_query_string.length == 0
142
200
  elsif nested_query_string.instance_of? Range then
143
201
  return between(nested_query_string.first, nested_query_string.last)
144
202
  end
@@ -226,6 +284,7 @@ module Lore
226
284
  end
227
285
  Clause.new(@value_string, @left_side+@value_string, '', @plan)
228
286
  end
287
+ alias is ==
229
288
 
230
289
  def <=>(value)
231
290
  if(value != :NULL)
@@ -235,19 +294,25 @@ module Lore
235
294
  end
236
295
  Clause.new(@field_name, @left_side+@value_string, '', @plan)
237
296
  end
297
+ alias is_not <=>
298
+
238
299
  def |(value)
239
- @value_string = ' OR ' << value.left_side
240
- Clause.new(@value_string, '(' << @left_side+@value_string + ')', '', @plan)
300
+ return Clause.new('1') == '1' if value.instance_of?(TrueClass)
301
+ return Clause.new('1') == '0' if value.instance_of?(FalseClass)
302
+ @value_string = " OR #{value.left_side}"
303
+ Clause.new(@value_string, "(#{@left_side+@value_string})", '', @plan)
241
304
  end
242
305
  alias or |
243
306
  def &(value)
244
307
  return unless value
308
+ return Clause.new('1') == '1' if value.instance_of?(TrueClass)
309
+ return Clause.new('1') == '0' if value.instance_of?(FalseClass)
245
310
  if @left_side.gsub(' ','') != '' then
246
- @value_string = ' AND ' << value.left_side
311
+ @value_string = " AND #{value.left_side}"
247
312
  else
248
313
  @value_string = value.left_side
249
314
  end
250
- Clause.new(@field_name, @left_side+@value_string, '', @plan)
315
+ Clause.new(@field_name, "(#{@left_side+@value_string})", '', @plan)
251
316
  end
252
317
  alias and &
253
318
 
@@ -306,8 +371,10 @@ module Lore
306
371
 
307
372
  # parses / builds WHERE, GROUP BY, ORDER BY, LIMIT, ...
308
373
  # part of the query:
309
- class Clause_Parser # :nodoc
374
+ class Clause_Parser
310
375
  # {{{
376
+
377
+ attr_reader :unions
311
378
 
312
379
  def initialize(base_accessor)
313
380
 
@@ -322,6 +389,7 @@ module Lore
322
389
  @clause[:filter] = ''
323
390
  @clause[:where] = 't'
324
391
  @clause[:joined] = []
392
+ @unions = false
325
393
  @base_accessor = base_accessor
326
394
 
327
395
  end # def
@@ -398,6 +466,7 @@ module Lore
398
466
  clause << @clause[:group_by].to_s
399
467
  clause << @clause[:order_by].to_s
400
468
  clause << @clause[:limit].to_s
469
+ return clause
401
470
  end # def
402
471
 
403
472
  def where(where_clause)
@@ -408,7 +477,7 @@ module Lore
408
477
  elsif where_clause.instance_of? FalseClass then
409
478
  where_clause = '\'f\''
410
479
  end
411
- @clause[:where] = ' WHERE ' << where_clause.to_s
480
+ @clause[:where] = "\nWHERE #{where_clause.to_s}"
412
481
  return self
413
482
  end # def
414
483
 
@@ -452,10 +521,15 @@ module Lore
452
521
  return j
453
522
  end
454
523
 
524
+ def union(select_query)
525
+ @unions ||= []
526
+ @unions << select_query
527
+ return self
528
+ end
529
+
455
530
  def limit(limit_val, offset_val=0)
456
531
  @clause[:limit] = ' LIMIT ' << limit_val.to_s
457
532
  @clause[:offset] = ' OFFSET ' << offset_val.to_s
458
-
459
533
  return self
460
534
  end # def
461
535
 
@@ -473,7 +547,6 @@ module Lore
473
547
  end
474
548
  }
475
549
  @clause[:group_by] = ' GROUP BY ' << absolute_field_names.join(',')
476
- p @clause
477
550
  return self
478
551
  end # def
479
552
 
@@ -523,7 +596,6 @@ module Lore
523
596
  end
524
597
 
525
598
  def perform
526
-
527
599
  end
528
600
 
529
601
  # }}}