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.
- data/CHANGELOG +485 -0
- data/README +35 -12
- data/Rakefile +4 -7
- data/benchmark/bench.rb +1 -1
- data/doc/AUTHORS +3 -3
- data/doc/RELEASES +153 -2
- data/doc/config.txt +0 -7
- data/doc/tutorial.txt +7 -0
- data/examples/README +5 -0
- data/examples/mysql_to_psql.rb +25 -50
- data/examples/run.rb +62 -77
- data/install.rb +1 -1
- data/lib/og.rb +45 -106
- data/lib/og/collection.rb +156 -0
- data/lib/og/entity.rb +131 -0
- data/lib/og/errors.rb +10 -15
- data/lib/og/manager.rb +115 -0
- data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
- data/lib/og/{mixins → mixin}/orderable.rb +35 -35
- data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
- data/lib/og/{mixins → mixin}/tree.rb +0 -4
- data/lib/og/relation.rb +178 -0
- data/lib/og/relation/belongs_to.rb +14 -0
- data/lib/og/relation/has_many.rb +62 -0
- data/lib/og/relation/has_one.rb +17 -0
- data/lib/og/relation/joins_many.rb +69 -0
- data/lib/og/relation/many_to_many.rb +17 -0
- data/lib/og/relation/refers_to.rb +31 -0
- data/lib/og/store.rb +223 -0
- data/lib/og/store/filesys.rb +113 -0
- data/lib/og/store/madeleine.rb +4 -0
- data/lib/og/store/memory.rb +291 -0
- data/lib/og/store/mysql.rb +283 -0
- data/lib/og/store/psql.rb +238 -0
- data/lib/og/store/sql.rb +599 -0
- data/lib/og/store/sqlite.rb +190 -0
- data/lib/og/store/sqlserver.rb +262 -0
- data/lib/og/types.rb +19 -0
- data/lib/og/validation.rb +0 -4
- data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
- data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
- data/test/og/mixin/tc_timestamped.rb +38 -0
- data/test/og/store/tc_filesys.rb +71 -0
- data/test/og/tc_relation.rb +36 -0
- data/test/og/tc_store.rb +290 -0
- data/test/og/tc_types.rb +21 -0
- metadata +54 -40
- data/examples/mock_example.rb +0 -50
- data/lib/og/adapters/base.rb +0 -706
- data/lib/og/adapters/filesys.rb +0 -117
- data/lib/og/adapters/mysql.rb +0 -350
- data/lib/og/adapters/oracle.rb +0 -368
- data/lib/og/adapters/psql.rb +0 -272
- data/lib/og/adapters/sqlite.rb +0 -265
- data/lib/og/adapters/sqlserver.rb +0 -356
- data/lib/og/database.rb +0 -290
- data/lib/og/enchant.rb +0 -149
- data/lib/og/meta.rb +0 -407
- data/lib/og/testing/mock.rb +0 -165
- data/lib/og/typemacros.rb +0 -24
- data/test/og/adapters/tc_filesys.rb +0 -83
- data/test/og/adapters/tc_sqlite.rb +0 -86
- data/test/og/adapters/tc_sqlserver.rb +0 -96
- data/test/og/tc_automanage.rb +0 -46
- data/test/og/tc_lifecycle.rb +0 -105
- data/test/og/tc_many_to_many.rb +0 -61
- data/test/og/tc_meta.rb +0 -55
- data/test/og/tc_validation.rb +0 -89
- data/test/tc_og.rb +0 -364
data/test/og/tc_types.rb
ADDED
@@ -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.
|
7
|
-
date: 2005-
|
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
|
-
-
|
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/
|
52
|
-
- lib/og/
|
53
|
-
- lib/og/
|
54
|
-
- lib/og/
|
55
|
-
- lib/og/
|
56
|
-
- lib/og/
|
57
|
-
- lib/og/
|
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/
|
61
|
-
- lib/og/
|
62
|
-
- lib/og/
|
63
|
-
- lib/og/
|
64
|
-
- lib/og/
|
65
|
-
- lib/og/
|
66
|
-
- lib/og/
|
67
|
-
- lib/og/
|
68
|
-
- lib/og/
|
69
|
-
- lib/og/
|
70
|
-
- lib/og/
|
71
|
-
- lib/og/
|
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/
|
74
|
-
- test/og/
|
75
|
-
- test/og/
|
76
|
-
- test/og/
|
77
|
-
- test/og/
|
78
|
-
- test/og/
|
79
|
-
- test/og/
|
80
|
-
- test/og/
|
81
|
-
- test/og/
|
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.
|
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:
|
data/examples/mock_example.rb
DELETED
@@ -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
|
data/lib/og/adapters/base.rb
DELETED
@@ -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
|