og 0.16.0 → 0.17.0

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