og 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +485 -0
  2. data/README +35 -12
  3. data/Rakefile +4 -7
  4. data/benchmark/bench.rb +1 -1
  5. data/doc/AUTHORS +3 -3
  6. data/doc/RELEASES +153 -2
  7. data/doc/config.txt +0 -7
  8. data/doc/tutorial.txt +7 -0
  9. data/examples/README +5 -0
  10. data/examples/mysql_to_psql.rb +25 -50
  11. data/examples/run.rb +62 -77
  12. data/install.rb +1 -1
  13. data/lib/og.rb +45 -106
  14. data/lib/og/collection.rb +156 -0
  15. data/lib/og/entity.rb +131 -0
  16. data/lib/og/errors.rb +10 -15
  17. data/lib/og/manager.rb +115 -0
  18. data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
  19. data/lib/og/{mixins → mixin}/orderable.rb +35 -35
  20. data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
  21. data/lib/og/{mixins → mixin}/tree.rb +0 -4
  22. data/lib/og/relation.rb +178 -0
  23. data/lib/og/relation/belongs_to.rb +14 -0
  24. data/lib/og/relation/has_many.rb +62 -0
  25. data/lib/og/relation/has_one.rb +17 -0
  26. data/lib/og/relation/joins_many.rb +69 -0
  27. data/lib/og/relation/many_to_many.rb +17 -0
  28. data/lib/og/relation/refers_to.rb +31 -0
  29. data/lib/og/store.rb +223 -0
  30. data/lib/og/store/filesys.rb +113 -0
  31. data/lib/og/store/madeleine.rb +4 -0
  32. data/lib/og/store/memory.rb +291 -0
  33. data/lib/og/store/mysql.rb +283 -0
  34. data/lib/og/store/psql.rb +238 -0
  35. data/lib/og/store/sql.rb +599 -0
  36. data/lib/og/store/sqlite.rb +190 -0
  37. data/lib/og/store/sqlserver.rb +262 -0
  38. data/lib/og/types.rb +19 -0
  39. data/lib/og/validation.rb +0 -4
  40. data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
  41. data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
  42. data/test/og/mixin/tc_timestamped.rb +38 -0
  43. data/test/og/store/tc_filesys.rb +71 -0
  44. data/test/og/tc_relation.rb +36 -0
  45. data/test/og/tc_store.rb +290 -0
  46. data/test/og/tc_types.rb +21 -0
  47. metadata +54 -40
  48. data/examples/mock_example.rb +0 -50
  49. data/lib/og/adapters/base.rb +0 -706
  50. data/lib/og/adapters/filesys.rb +0 -117
  51. data/lib/og/adapters/mysql.rb +0 -350
  52. data/lib/og/adapters/oracle.rb +0 -368
  53. data/lib/og/adapters/psql.rb +0 -272
  54. data/lib/og/adapters/sqlite.rb +0 -265
  55. data/lib/og/adapters/sqlserver.rb +0 -356
  56. data/lib/og/database.rb +0 -290
  57. data/lib/og/enchant.rb +0 -149
  58. data/lib/og/meta.rb +0 -407
  59. data/lib/og/testing/mock.rb +0 -165
  60. data/lib/og/typemacros.rb +0 -24
  61. data/test/og/adapters/tc_filesys.rb +0 -83
  62. data/test/og/adapters/tc_sqlite.rb +0 -86
  63. data/test/og/adapters/tc_sqlserver.rb +0 -96
  64. data/test/og/tc_automanage.rb +0 -46
  65. data/test/og/tc_lifecycle.rb +0 -105
  66. data/test/og/tc_many_to_many.rb +0 -61
  67. data/test/og/tc_meta.rb +0 -55
  68. data/test/og/tc_validation.rb +0 -89
  69. data/test/tc_og.rb +0 -364
@@ -0,0 +1,21 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+
3
+ require 'test/unit'
4
+
5
+ require 'og'
6
+
7
+ class TestCaseOgTypes < Test::Unit::TestCase # :nodoc: all
8
+ include Og
9
+
10
+ class User
11
+ property :name, Og::VarChar(16), :unique => true
12
+
13
+ def initialize(name = nil)
14
+ @name = name
15
+ end
16
+ end
17
+
18
+ def test_all
19
+ assert_equal [String, {:sql=>"VARCHAR(16)"}], Og::VarChar(16)
20
+ end
21
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: og
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.16.0
7
- date: 2005-04-18
6
+ version: 0.17.0
7
+ date: 2005-05-16
8
8
  summary: Og (ObjectGraph)
9
9
  require_paths:
10
10
  - lib
@@ -27,62 +27,66 @@ platform: ruby
27
27
  authors:
28
28
  - George Moschovitis
29
29
  files:
30
+ - README
30
31
  - CHANGELOG
31
- - Rakefile
32
32
  - INSTALL
33
- - README
33
+ - Rakefile
34
34
  - install.rb
35
35
  - benchmark/bench.rb
36
36
  - benchmark/sqlite-no-prepare.1.txt
37
37
  - benchmark/sqlite-no-prepare.2.txt
38
38
  - benchmark/sqlite-prepare.1.txt
39
39
  - benchmark/sqlite-prepare.2.txt
40
- - examples/mock_example.rb
41
40
  - examples/run.rb
42
41
  - examples/mysql_to_psql.rb
43
42
  - examples/README
44
43
  - doc/tutorial.txt
45
44
  - doc/LICENSE
46
- - doc/config.txt
47
45
  - doc/RELEASES
46
+ - doc/config.txt
48
47
  - doc/AUTHORS
49
48
  - lib/og
50
49
  - lib/og.rb
51
- - lib/og/adapters
52
- - lib/og/mixins
53
- - lib/og/testing
54
- - lib/og/typemacros.rb
55
- - lib/og/enchant.rb
56
- - lib/og/database.rb
57
- - lib/og/meta.rb
58
- - lib/og/validation.rb
50
+ - lib/og/collection.rb
51
+ - lib/og/entity.rb
52
+ - lib/og/relation.rb
53
+ - lib/og/relation
54
+ - lib/og/mixin
55
+ - lib/og/store
56
+ - lib/og/manager.rb
59
57
  - lib/og/errors.rb
60
- - lib/og/adapters/filesys.rb
61
- - lib/og/adapters/psql.rb
62
- - lib/og/adapters/sqlite.rb
63
- - lib/og/adapters/mysql.rb
64
- - lib/og/adapters/oracle.rb
65
- - lib/og/adapters/sqlserver.rb
66
- - lib/og/adapters/base.rb
67
- - lib/og/mixins/hierarchical.rb
68
- - lib/og/mixins/tree.rb
69
- - lib/og/mixins/orderable.rb
70
- - lib/og/mixins/timestamped.rb
71
- - lib/og/testing/mock.rb
58
+ - lib/og/store.rb
59
+ - lib/og/validation.rb
60
+ - lib/og/testing
61
+ - lib/og/types.rb
62
+ - lib/og/relation/belongs_to.rb
63
+ - lib/og/relation/has_many.rb
64
+ - lib/og/relation/has_one.rb
65
+ - lib/og/relation/refers_to.rb
66
+ - lib/og/relation/many_to_many.rb
67
+ - lib/og/relation/joins_many.rb
68
+ - lib/og/mixin/hierarchical.rb
69
+ - lib/og/mixin/orderable.rb
70
+ - lib/og/mixin/timestamped.rb
71
+ - lib/og/mixin/tree.rb
72
+ - lib/og/store/filesys.rb
73
+ - lib/og/store/psql.rb
74
+ - lib/og/store/sql.rb
75
+ - lib/og/store/mysql.rb
76
+ - lib/og/store/madeleine.rb
77
+ - lib/og/store/sqlserver.rb
78
+ - lib/og/store/sqlite.rb
79
+ - lib/og/store/memory.rb
72
80
  - test/og
73
- - test/tc_og.rb
74
- - test/og/adapters
75
- - test/og/mixins
76
- - test/og/tc_many_to_many.rb
77
- - test/og/tc_validation.rb
78
- - test/og/tc_lifecycle.rb
79
- - test/og/tc_automanage.rb
80
- - test/og/tc_meta.rb
81
- - test/og/adapters/tc_filesys.rb
82
- - test/og/adapters/tc_sqlite.rb
83
- - test/og/adapters/tc_sqlserver.rb
84
- - test/og/mixins/tc_hierarchical.rb
85
- - test/og/mixins/tc_orderable.rb
81
+ - test/og/store
82
+ - test/og/mixin
83
+ - test/og/tc_types.rb
84
+ - test/og/tc_relation.rb
85
+ - test/og/tc_store.rb
86
+ - test/og/store/tc_filesys.rb
87
+ - test/og/mixin/tc_hierarchical.rb
88
+ - test/og/mixin/tc_orderable.rb
89
+ - test/og/mixin/tc_timestamped.rb
86
90
  test_files: []
87
91
  rdoc_options:
88
92
  - "--main"
@@ -108,5 +112,15 @@ dependencies:
108
112
  -
109
113
  - "="
110
114
  - !ruby/object:Gem::Version
111
- version: 0.16.0
115
+ version: 0.17.0
116
+ version:
117
+ - !ruby/object:Gem::Dependency
118
+ name: facets
119
+ version_requirement:
120
+ version_requirements: !ruby/object:Gem::Version::Requirement
121
+ requirements:
122
+ -
123
+ - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 0.7.1
112
126
  version:
@@ -1,50 +0,0 @@
1
- # = Og Mocking Example
2
- #
3
- # A simple example to demonstrate how to mock Og.
4
- # Very useful in test units.
5
- #
6
- # * George Moschovitis <gm@navel.gr>
7
- # (c) 2004-2005 Navel, all rights reserved.
8
- # $Id: mock_example.rb 1 2005-04-11 11:04:30Z gmosx $
9
-
10
- require 'rubygems'
11
- require 'flexmock'
12
- require 'og'
13
- require 'og/mock'
14
-
15
- class Article
16
- prop_accessor :body, String
17
-
18
- def initialize(body = nil)
19
- @body = body
20
- end
21
- end
22
-
23
- class SimpleTest < Test::Unit::TestCase
24
-
25
- def setup
26
- @og = Og::MockDatabase.new
27
- end
28
-
29
- def teardown
30
- @og = nil
31
- end
32
-
33
- def test_me
34
- mocks = [
35
- Article.new('body1'),
36
- Article.new('body2'),
37
- Article.new('body3')
38
- ]
39
- @og.mock_handle(:load_all) { |klass, extrasql| mocks }
40
-
41
- # differnt ways to call the mocked method...
42
- puts 'Here are the articles:', Article.all
43
- puts 'Here are the articles:', Article.load_all
44
- puts 'Here are the articles:', @og.load_all(Article)
45
-
46
- # 3 times called
47
- assert_equal(3, @og.mock_count(:load_all))
48
- end
49
-
50
- end
@@ -1,706 +0,0 @@
1
- # * George Moschovitis <gm@navel.gr>
2
- # (c) 2004-2005 Navel, all rights reserved.
3
- # $Id: base.rb 17 2005-04-14 16:03:40Z gmosx $
4
-
5
- require 'yaml'
6
- require 'singleton'
7
-
8
- require 'glue/property'
9
- require 'glue/array'
10
- require 'glue/time'
11
- require 'glue/attribute'
12
-
13
- require 'og'
14
- require 'og/errors'
15
-
16
- module Og
17
-
18
- # An adapter communicates with the backend datastore.
19
- # The adapters for all supported datastores extend this
20
- # class. Typically, an RDBMS is used to implement a
21
- # datastore.
22
- #
23
- # This is the base adapter implementation. Adapters for
24
- # well known RDBMS systems and other stores inherit
25
- # from this class.
26
-
27
- class Adapter
28
- include Singleton
29
-
30
- # A mapping between Ruby and backend Datastore types.
31
-
32
- attr_accessor :typemap
33
-
34
- # A map for casting Ruby types to SQL safe textual
35
- # representations.
36
-
37
- attr_accessor :typecast
38
-
39
- # Lookup the adapter instance from the adapter name.
40
-
41
- def self.for_name(name)
42
- # gmosx: RDoc complains about this, so lets use an
43
- # eval, AAAAAAAARGH!
44
- # require "og/adapters/#{name}"
45
- eval %{
46
- require 'og/adapters/#{name}'
47
- return #{name.capitalize}Adapter.instance
48
- }
49
- end
50
-
51
- def initialize
52
- # The default mappings, should be valid for most
53
- # RDBMS.
54
-
55
- @typemap = {
56
- Integer => 'integer',
57
- Fixnum => 'integer',
58
- Float => 'float',
59
- String => 'text',
60
- Time => 'timestamp',
61
- Date => 'date',
62
- TrueClass => 'boolean',
63
- Object => 'text',
64
- Array => 'text',
65
- Hash => 'text'
66
- }
67
-
68
- # The :s: is a marker that will be replaced with the
69
- # actual value to be casted. The default parameter of
70
- # the Hash handles all other types (Object, Array, etc)
71
-
72
- @typecast = Hash.new("'#\{#{self.class}.escape(:s:.to_yaml)\}'").update(
73
- Integer => "\#\{:s:\}",
74
- Float => "\#\{:s:\}",
75
- String => "'#\{#{self.class}.escape(:s:)\}'",
76
- Time => "'#\{#{self.class}.timestamp(:s:)\}'",
77
- Date => "'#\{#{self.class}.date(:s:)\}'",
78
- TrueClass => "#\{:s: ? \"'t'\" : 'NULL' \}"
79
- )
80
- end
81
-
82
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
83
- # :section: Utilities
84
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
85
-
86
- # Escape an SQL string
87
-
88
- def self.escape(str)
89
- return nil unless str
90
- return str.gsub( /'/, "''" )
91
- end
92
-
93
- # Convert a ruby time to an sql timestamp.
94
- # TODO: Optimize this
95
-
96
- def self.timestamp(time = Time.now)
97
- return nil unless time
98
- return time.strftime("%Y-%m-%d %H:%M:%S")
99
- end
100
-
101
- # Output YYY-mm-dd
102
- # TODO: Optimize this
103
-
104
- def self.date(date)
105
- return nil unless date
106
- return "#{date.year}-#{date.month}-#{date.mday}"
107
- end
108
-
109
- # Parse sql datetime
110
- # TODO: Optimize this
111
-
112
- def self.parse_timestamp(str)
113
- return nil unless str
114
- return Time.parse(str)
115
- end
116
-
117
- # Input YYYY-mm-dd
118
- # TODO: Optimize this
119
-
120
- def self.parse_date(str)
121
- return nil unless str
122
- return Date.strptime(str)
123
- end
124
-
125
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
126
- # :section: Database methods
127
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
128
-
129
- # Create the database.
130
-
131
- def create_db(database, user = nil, password = nil)
132
- Logger.info "Creating database '#{database}'."
133
- Og.create_schema = true
134
- end
135
-
136
- # Drop the database.
137
-
138
- def drop_db(database, user = nil, password = nil)
139
- Logger.info "Dropping database '#{database}'."
140
- end
141
-
142
- # Create a new connection to the backend.
143
-
144
- def new_connection(db)
145
- return Connection.new(db)
146
- end
147
-
148
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149
- # :section: O->R mapping methods and utilities.
150
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151
-
152
- # Encode the name of the klass as an sql safe string.
153
- # The Module separators are replaced with _ and NOT stripped
154
- # out so that we can convert back to the original notation if
155
- # needed. The leading module if available is removed.
156
-
157
- def self.encode(klass)
158
- "#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
159
- end
160
-
161
- # The name of the SQL table where objects of this class
162
- # are stored. A prefix is needed to avoid colision with
163
- # reserved prefices (for example User maps to user which
164
- # is reserved in postgresql). The prefix should start
165
- # with an alphanumeric character to be compatible with
166
- # all RDBMS (most notable Oracle).
167
- #
168
- # You may want to override this method to map an existing
169
- # database schema using Og.
170
-
171
- def self.table(klass)
172
- "og_#{Og.table_prefix}#{encode(klass)}"
173
- end
174
-
175
- # The name of the join table for the two given classes.
176
- # A prefix is needed to avoid colision with reserved
177
- # prefices (for example User maps to user which
178
- # is reserved in postgresql). The prefix should start
179
- # with an alphanumeric character to be compatible with
180
- # all RDBMS (most notable Oracle).
181
- #
182
- # You may want to override this method to map an existing
183
- # database schema using Og.
184
-
185
- def self.join_table(klass1, klass2, field)
186
- "og_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}_#{field}"
187
- end
188
-
189
- # Return an sql string evaluator for the property.
190
- # No need to optimize this, used only to precalculate code.
191
- # YAML is used to store general Ruby objects to be more
192
- # portable.
193
- #--
194
- # FIXME: add extra handling for float.
195
- #++
196
-
197
- def write_prop(p)
198
- if p.klass.ancestors.include?(Integer)
199
- return "#\{@#{p.symbol} || 'NULL'\}"
200
- elsif p.klass.ancestors.include?(Float)
201
- return "#\{@#{p.symbol} || 'NULL'\}"
202
- elsif p.klass.ancestors.include?(String)
203
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
204
- elsif p.klass.ancestors.include?(Time)
205
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
206
- elsif p.klass.ancestors.include?(Date)
207
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
208
- elsif p.klass.ancestors.include?(TrueClass)
209
- return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
210
- else
211
- # gmosx: keep the '' for nil symbols.
212
- return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
213
- end
214
- end
215
-
216
- # Return an evaluator for reading the property.
217
- # No need to optimize this, used only to precalculate code.
218
-
219
- def read_prop(p, idx)
220
- if p.klass.ancestors.include?(Integer)
221
- return "res[#{idx}].to_i"
222
- elsif p.klass.ancestors.include?(Float)
223
- return "res[#{idx}].to_f"
224
- elsif p.klass.ancestors.include?(String)
225
- return "res[#{idx}]"
226
- elsif p.klass.ancestors.include?(Time)
227
- return "#{self.class}.parse_timestamp(res[#{idx}])"
228
- elsif p.klass.ancestors.include?(Date)
229
- return "#{self.class}.parse_date(res[#{idx}])"
230
- elsif p.klass.ancestors.include?(TrueClass)
231
- return "('0' != res[#{idx}])"
232
- else
233
- return "YAML::load(res[#{idx}])"
234
- end
235
- end
236
-
237
- # Create the fields that correpsond to the klass properties.
238
- # The generated fields array is used in create_table.
239
- # If the property has an :sql metadata this overrides the
240
- # default mapping. If the property has an :extra_sql metadata
241
- # the extra sql is appended after the default mapping.
242
-
243
- def create_fields(klass)
244
- fields = []
245
-
246
- klass.__props.each do |p|
247
- klass.sql_index(p.symbol) if p.meta[:sql_index]
248
-
249
- field = "#{p.symbol}"
250
-
251
- if p.meta and p.meta[:sql]
252
- field << " #{p.meta[:sql]}"
253
- else
254
- field << " #{@typemap[p.klass]}"
255
-
256
- if p.meta
257
- # set default value (gmosx: not that useful in the
258
- # current implementation).
259
- if default = p.meta[:default]
260
- field << " DEFAULT #{default.inspect} NOT NULL"
261
- end
262
-
263
- # set unique
264
- field << " UNIQUE" if p.meta[:unique]
265
-
266
- # attach extra sql
267
- if extra_sql = p.meta[:extra_sql]
268
- field << " #{extra_sql}"
269
- end
270
- end
271
- end
272
-
273
- fields << field
274
- end
275
-
276
- return fields
277
- end
278
-
279
- # Create the managed object table. The properties of the
280
- # object are mapped to the table columns. Additional sql relations
281
- # and constrains are created (indicices, sequences, etc).
282
-
283
- def create_table(klass)
284
- raise 'Not implemented!'
285
- end
286
-
287
- # Returns the props that will be included in the insert query.
288
- # For some backends the oid should be stripped.
289
-
290
- def props_for_insert(klass)
291
- klass.__props
292
- end
293
-
294
- # Returns the code that actually inserts the object into the
295
- # database. Returns the code as String.
296
-
297
- def insert_code(klass, sql, pre_cb, post_cb)
298
- raise 'Not implemented!'
299
- end
300
-
301
- # Generate the mapping of the database fields to the
302
- # object properties.
303
-
304
- def calc_field_index(klass, og)
305
- # Implement if needed.
306
- end
307
-
308
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
309
- # :section: Precompile lifecycle methods.
310
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311
-
312
- # Precompile some code that gets executed all the time.
313
- # Deletion code is not precompiled, because it is not used
314
- # as frequently.
315
-
316
- def eval_lifecycle_methods(klass, db)
317
- eval_og_insert(klass, db)
318
- eval_og_update(klass, db)
319
- eval_og_read(klass, db)
320
- end
321
-
322
- # Generate the property for oid.
323
-
324
- def eval_og_oid(klass)
325
- klass.class_eval %{
326
- prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
327
- }
328
- end
329
-
330
- # Precompile the insert code for the given class.
331
- # The generated code sets the oid when inserting!
332
-
333
- def eval_og_insert(klass, db)
334
- klass.class_eval %{
335
- def og_insert(conn)
336
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
337
- #{insert_code(klass, db)}
338
- #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
339
- end
340
- }
341
- end
342
-
343
- # Precompile the update code for the given class.
344
- # Ignore the oid when updating!
345
-
346
- def eval_og_update(klass, db)
347
- props = klass.__props.reject { |p| :oid == p.symbol }
348
-
349
- updates = props.collect { |p|
350
- "#{p.name}=#{write_prop(p)}"
351
- }
352
-
353
- sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
354
-
355
- klass.class_eval %{
356
- def og_update(conn)
357
- #{Aspects.gen_advice_code(:og_update, klass.advices, :pre) if klass.respond_to?(:advices)}
358
- conn.exec "#{sql}"
359
- #{Aspects.gen_advice_code(:og_update, klass.advices, :post) if klass.respond_to?(:advices)}
360
- end
361
- }
362
- end
363
-
364
- # Precompile the code to read (deserialize) objects of the
365
- # given class from the backend. In order to allow for changing
366
- # field/attribute orders we have to use a field mapping hash.
367
-
368
- def eval_og_read(klass, db)
369
- calc_field_index(klass, db)
370
-
371
- props = klass.__props
372
- code = []
373
-
374
- props.each do |p|
375
- if idx = db.managed_classes[klass].field_index[p.name]
376
- # more fault tolerant if a new field is added and it
377
- # doesnt exist in the database.
378
- code << "@#{p.name} = #{read_prop(p, idx)}"
379
- end
380
- end
381
-
382
- klass.class_eval %{
383
- def og_read(res, tuple = 0)
384
- #{Aspects.gen_advice_code(:og_read, klass.advices, :pre) if klass.respond_to?(:advices)}
385
- #{code.join('; ')}
386
- #{Aspects.gen_advice_code(:og_read, klass.advices, :post) if klass.respond_to?(:advices)}
387
- end
388
- }
389
- end
390
-
391
- end
392
-
393
- # A Connection to the Database. This file defines the skeleton
394
- # functionality. A store specific implementation file (adapter)
395
- # implements all methods.
396
- #--
397
- # - support caching (memoize).
398
- # - use prepared statements.
399
- #++
400
-
401
- class Connection
402
-
403
- # The Og database object.
404
-
405
- attr_reader :db
406
-
407
- # The actual connection to the backend store.
408
-
409
- attr_accessor :store
410
-
411
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
412
- # :section: Backend connection methods.
413
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
414
-
415
- # Initialize a connection to the database.
416
-
417
- def initialize(db)
418
- @db = db
419
- Logger.debug "Created DB connection." if $DBG
420
- end
421
-
422
- # Close the connection to the database.
423
-
424
- def close
425
- Logger.debug "Closed DB connection." if $DBG
426
- end
427
-
428
- # Create the managed object table. The properties of the
429
- # object are mapped to the table columns. Additional sql relations
430
- # and constrains are created (indicices, sequences, etc).
431
-
432
- def create_table(klass)
433
- raise 'Not implemented!'
434
- end
435
-
436
- # Drop the managed object table.
437
-
438
- def drop_table(klass)
439
- exec "DROP TABLE #{klass::DBTABLE}"
440
- end
441
-
442
- # Prepare an sql statement.
443
-
444
- def prepare(sql)
445
- raise 'Not implemented!'
446
- end
447
-
448
- # Execute an SQL query and return the result.
449
-
450
- def query(sql)
451
- raise 'Not implemented!'
452
- end
453
-
454
- # Execute an SQL query, no result returned.
455
-
456
- def exec(sql)
457
- raise 'Not implemented!'
458
- end
459
- alias_method :execute, :exec
460
-
461
- # Start a new transaction.
462
-
463
- def start
464
- exec 'START TRANSACTION'
465
- end
466
-
467
- # Commit a transaction.
468
-
469
- def commit
470
- exec 'COMMIT'
471
- end
472
-
473
- # Rollback a transaction.
474
-
475
- def rollback
476
- exec 'ROLLBACK'
477
- end
478
-
479
- # Transaction helper. In the transaction block use
480
- # the db pointer to the backend.
481
-
482
- def transaction(&block)
483
- begin
484
- start
485
- yield(self)
486
- commit
487
- rescue => ex
488
- Logger.error "DB Error: ERROR IN TRANSACTION"
489
- Logger.error "#{ex}"
490
- Logger.error "#{ex.backtrace}"
491
- rollback
492
- end
493
- end
494
-
495
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
496
- # :section: Deserialization methods.
497
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
498
-
499
- # Is the given resultset valid?
500
-
501
- def valid_res?(res)
502
- return !(res.nil?)
503
- end
504
-
505
- # Read (deserialize) one row of the resultset.
506
-
507
- def read_one(res, klass)
508
- raise 'Not implemented!'
509
- end
510
-
511
- # Read (deserialize) all rows of the resultset.
512
-
513
- def read_all(res, klass)
514
- raise 'Not implemented!'
515
- end
516
-
517
- # Read the first column of the resultset as an Integer.
518
-
519
- def read_int(res, idx = 0)
520
- raise 'Not implemented!'
521
- end
522
-
523
- # Get a row from the resultset.
524
-
525
- def get_row(res)
526
- return res
527
- end
528
-
529
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
530
- # :section: Managed object methods.
531
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
532
-
533
- # Save an object to the database. Insert if this is a new object or
534
- # update if this is already stored in the database.
535
-
536
- def save(obj)
537
- if obj.oid
538
- # object allready inserted, update!
539
- obj.og_update(self)
540
- else
541
- # not in the database, insert!
542
- obj.og_insert(self)
543
- end
544
- end
545
- alias_method :<<, :save
546
- alias_method :put, :save
547
-
548
- # Force insertion of managed object.
549
-
550
- def insert(obj)
551
- obj.og_insert(self)
552
- end
553
-
554
- # Force update of managed object.
555
-
556
- def update(obj)
557
- obj.og_update(self)
558
- end
559
-
560
- # Update only specific fields of the managed object.
561
- #
562
- # Input:
563
- # sql = the sql code to updated the properties.
564
- #
565
- # WARNING: the object in memory is not updated.
566
- #--
567
- # TODO: should update the object in memory.
568
- #++
569
-
570
- def update_properties(update_sql, obj_or_oid, klass = nil)
571
- oid = obj_or_oid.to_i
572
- klass = obj_or_oid.class unless klass
573
-
574
- exec "UPDATE #{klass::DBTABLE} SET #{update_sql} WHERE oid=#{oid}"
575
- end
576
- alias_method :pupdate, :update_properties
577
- alias_method :update_propery, :update_properties
578
-
579
- # Load an object from the database.
580
- #
581
- # Input:
582
- # oid = the object oid, OR the object name.
583
-
584
- def load(oid, klass)
585
- if oid.to_i > 0 # a valid Fixnum ?
586
- load_by_oid(oid, klass)
587
- else
588
- load_by_name(oid, klass)
589
- end
590
- end
591
- alias_method :get, :load
592
-
593
- # Load an object by oid.
594
-
595
- def load_by_oid(oid, klass)
596
- res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}"
597
- read_one(res, klass)
598
- end
599
- alias_method :get_by_oid, :load_by_oid
600
-
601
- # Load an object by name.
602
-
603
- def load_by_name(name, klass)
604
- res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'"
605
- read_one(res, klass)
606
- end
607
- alias_method :get_by_name, :load_by_name
608
-
609
- # Load all objects of the given klass.
610
- # Used to be called 'collect' in an earlier version.
611
-
612
- def load_all(klass, extrasql = nil)
613
- res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}"
614
- read_all(res, klass)
615
- end
616
- alias_method :get_all, :load_all
617
-
618
- # Perform a standard SQL query to the database. Deserializes the
619
- # results.
620
-
621
- def select(sql, klass)
622
- unless sql =~ /SELECT/i
623
- sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
624
- end
625
-
626
- res = query(sql)
627
- read_all(res, klass)
628
- end
629
-
630
- # Optimized for one result.
631
-
632
- def select_one(sql, klass)
633
- unless sql =~ /SELECT/i
634
- sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
635
- end
636
-
637
- res = query(sql)
638
- read_one(res, klass)
639
- end
640
-
641
- # Perform a count query.
642
-
643
- def count(sql, klass = nil)
644
- unless sql =~ /SELECT/i
645
- sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}"
646
- end
647
-
648
- res = query(sql)
649
- return read_int(res)
650
- end
651
-
652
- # Delete an object from the database. Allways perform a deep delete.
653
- #
654
- # No need to optimize here with pregenerated code. Deletes are
655
- # not used as much as reads or writes.
656
- #
657
- # Input:
658
- #
659
- # obj_or_oid = Object or oid to delete.
660
- # klass = Class of object (can be nil if an object is passed)
661
- #
662
- #--
663
- # TODO: pre evaluate for symmetry to the other methods
664
- #++
665
-
666
- def delete(obj_or_oid, klass = nil, cascade = true)
667
- oid = obj_or_oid.to_i
668
- klass = obj_or_oid.class unless klass
669
-
670
- # this is a class callback!
671
-
672
- if klass.respond_to?(:og_pre_delete)
673
- klass.og_pre_delete(self, obj_or_oid)
674
- end
675
-
676
- # TODO: implement this as stored procedure? naaah.
677
- # TODO: also handle many_to_many relations.
678
-
679
- transaction do |tx|
680
- tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}"
681
- if cascade and klass.__meta.include?(:descendants)
682
- klass.__meta[:descendants].each do |dclass, linkback|
683
- tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}"
684
- end
685
- end
686
- end
687
- end
688
- alias_method :delete!, :delete
689
-
690
- protected
691
-
692
- # Handles an adapter exception.
693
-
694
- def handle_db_exception(ex, sql = nil)
695
- Logger.error "DB error #{ex}, [#{sql}]"
696
- Logger.error ex.backtrace.join("\n")
697
- raise SqlException.new(ex, sql) if Og.raise_db_exceptions
698
-
699
- # FIXME: should return :error or something.
700
-
701
- return nil
702
- end
703
-
704
- end
705
-
706
- end