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,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
  # }}}