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/lib/og/adapters/filesys.rb
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: filesys.rb 17 2005-04-14 16:03:40Z gmosx $
|
4
|
-
|
5
|
-
require 'fileutils'
|
6
|
-
|
7
|
-
require 'og/adapters/base'
|
8
|
-
|
9
|
-
module Og
|
10
|
-
|
11
|
-
# The Filesys adapter. This adapter stores Og objectes in
|
12
|
-
# the filesystem. This adapter is a proof of concept and
|
13
|
-
# at the moment severely limited. For extra documentation
|
14
|
-
# see lib/og/adapter.rb
|
15
|
-
|
16
|
-
class FilesysAdapter < Adapter
|
17
|
-
|
18
|
-
# Creates the name of the database root dir.
|
19
|
-
|
20
|
-
def self.dir(database)
|
21
|
-
"#{database}_db"
|
22
|
-
end
|
23
|
-
|
24
|
-
def create_db(database, user = nil, password = nil)
|
25
|
-
begin
|
26
|
-
FileUtils.mkdir_p(self.class.dir(database))
|
27
|
-
rescue
|
28
|
-
Logger.error "Cannot create '#{database}'!"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def drop_db(database, user = nil, password = nil)
|
33
|
-
begin
|
34
|
-
FileUtils.rmdir(self.class.dir(database))
|
35
|
-
super
|
36
|
-
rescue
|
37
|
-
Logger.error "Cannot drop '#{database}'!"
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def insert_code(klass, db)
|
42
|
-
props = props_for_insert(klass)
|
43
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
44
|
-
|
45
|
-
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
46
|
-
|
47
|
-
%{
|
48
|
-
conn.store.query("#{sql}").close
|
49
|
-
@oid = conn.store.last_insert_row_id
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
def new_connection(db)
|
54
|
-
return FilesysConnection.new(db)
|
55
|
-
end
|
56
|
-
|
57
|
-
# A 'table' is emulated by using a directory to store
|
58
|
-
# one file per table row. Each file contains an object
|
59
|
-
# marshaled in YAML format. A special file called
|
60
|
-
# '_sequence' stores the sequence for this 'table'.
|
61
|
-
|
62
|
-
def create_table(klass, db)
|
63
|
-
class_dir = File.join(self.class.dir(db.config[:database]), klass::DBTABLE)
|
64
|
-
FileUtils.mkdir_p(class_dir)
|
65
|
-
|
66
|
-
seq_file = File.join(class_dir, 'seq')
|
67
|
-
File.open(seq_file, 'w') { |f| f << '1' }
|
68
|
-
rescue => ex
|
69
|
-
Logger.error "Cannot create directory to store '#{klass}' classes!"
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
# The Filesys adapter connection.
|
75
|
-
|
76
|
-
class FilesysConnection < Connection
|
77
|
-
|
78
|
-
def initialize(db)
|
79
|
-
super
|
80
|
-
config = db.config
|
81
|
-
|
82
|
-
begin
|
83
|
-
raise unless File.directory?(FilesysAdapter.dir(config[:database]))
|
84
|
-
rescue => ex
|
85
|
-
if true # ex.to_s =~ /database .* does not exist/i
|
86
|
-
Logger.info "Database '#{config[:database]}' not found!"
|
87
|
-
@db.adapter.create_db(config[:database], config[:user])
|
88
|
-
retry
|
89
|
-
end
|
90
|
-
raise
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def save(obj)
|
95
|
-
seq = nil
|
96
|
-
class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), obj.class::DBTABLE)
|
97
|
-
File.open(File.join(class_dir, 'seq'), 'r') { |f| seq = f.read.to_i }
|
98
|
-
obj.oid = seq
|
99
|
-
File.open(File.join(class_dir, "#{seq}.yml"), 'w') { |f| f << obj.to_yaml }
|
100
|
-
seq += 1
|
101
|
-
File.open(File.join(class_dir, 'seq'), 'w') { |f| f << seq }
|
102
|
-
end
|
103
|
-
|
104
|
-
def load(oid, klass)
|
105
|
-
obj = nil
|
106
|
-
class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), klass::DBTABLE)
|
107
|
-
File.open(File.join(class_dir, "#{oid}.yml"), 'r') { |f| obj = YAML::load(f.read) }
|
108
|
-
return obj
|
109
|
-
end
|
110
|
-
|
111
|
-
def close
|
112
|
-
Logger.debug "Closed DB connection." if $DBG
|
113
|
-
end
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
data/lib/og/adapters/mysql.rb
DELETED
@@ -1,350 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: mysql.rb 17 2005-04-14 16:03:40Z gmosx $
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'mysql'
|
7
|
-
rescue Object => ex
|
8
|
-
Logger.error 'Ruby-Mysql bindings are not installed!'
|
9
|
-
Logger.error ex
|
10
|
-
end
|
11
|
-
|
12
|
-
require 'og/adapters/base'
|
13
|
-
|
14
|
-
module Og
|
15
|
-
|
16
|
-
# The MySQL adapter. This adapter communicates with
|
17
|
-
# an MySQL rdbms. For extra documentation see
|
18
|
-
# lib/og/adapter.rb
|
19
|
-
|
20
|
-
class MysqlAdapter < Adapter
|
21
|
-
|
22
|
-
def initialize
|
23
|
-
super
|
24
|
-
@typemap.update(TrueClass => 'tinyint')
|
25
|
-
@typecast.update(TrueClass => "#\{:s: ? \"1\" : 'NULL' \}")
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.escape(str)
|
29
|
-
return nil unless str
|
30
|
-
return Mysql.quote(str)
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.timestamp(time = Time.now)
|
34
|
-
return nil unless time
|
35
|
-
return time.strftime("%Y%m%d%H%M%S")
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.date(date)
|
39
|
-
return nil unless date
|
40
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
41
|
-
end
|
42
|
-
|
43
|
-
def write_prop(p)
|
44
|
-
if p.klass.ancestors.include?(Integer)
|
45
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
46
|
-
elsif p.klass.ancestors.include?(Float)
|
47
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
48
|
-
elsif p.klass.ancestors.include?(String)
|
49
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
|
50
|
-
elsif p.klass.ancestors.include?(Time)
|
51
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
52
|
-
elsif p.klass.ancestors.include?(Date)
|
53
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
54
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
55
|
-
return "#\{@#{p.symbol} ? 1 : 0 \}"
|
56
|
-
else
|
57
|
-
# gmosx: keep the '' for nil symbols.
|
58
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def read_prop(p, idx)
|
63
|
-
if p.klass.ancestors.include?(Integer)
|
64
|
-
return "res[#{idx}].to_i"
|
65
|
-
elsif p.klass.ancestors.include?(Float)
|
66
|
-
return "res[#{idx}].to_f"
|
67
|
-
elsif p.klass.ancestors.include?(String)
|
68
|
-
return "res[#{idx}]"
|
69
|
-
elsif p.klass.ancestors.include?(Time)
|
70
|
-
return "#{self.class}.parse_timestamp(res[#{idx}])"
|
71
|
-
elsif p.klass.ancestors.include?(Date)
|
72
|
-
return "#{self.class}.parse_date(res[#{idx}])"
|
73
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
74
|
-
return "('0' != res[#{idx}])"
|
75
|
-
else
|
76
|
-
return "YAML::load(res[#{idx}])"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def create_db(database, user = nil, password = nil)
|
81
|
-
# gmosx: system is used to avoid shell expansion.
|
82
|
-
system 'mysqladmin', '-f', "--user=#{user}", "--password=#{password}", 'create', database
|
83
|
-
super
|
84
|
-
end
|
85
|
-
|
86
|
-
def drop_db(database, user = nil, password = nil)
|
87
|
-
system 'mysqladmin', '-f', "--user=#{user}", "--password=#{password}", 'drop', database
|
88
|
-
super
|
89
|
-
end
|
90
|
-
|
91
|
-
def props_for_insert(klass)
|
92
|
-
klass.__props.reject { |p| :oid == p.symbol }
|
93
|
-
end
|
94
|
-
|
95
|
-
def insert_code(klass, db)
|
96
|
-
props = props_for_insert(klass)
|
97
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
98
|
-
|
99
|
-
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
100
|
-
|
101
|
-
%{
|
102
|
-
conn.store.query_with_result = false
|
103
|
-
conn.store.query "#{sql}"
|
104
|
-
@oid = conn.store.insert_id()
|
105
|
-
}
|
106
|
-
end
|
107
|
-
|
108
|
-
def new_connection(db)
|
109
|
-
return MysqlConnection.new(db)
|
110
|
-
end
|
111
|
-
|
112
|
-
def calc_field_index(klass, db)
|
113
|
-
res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
|
114
|
-
meta = db.managed_classes[klass]
|
115
|
-
|
116
|
-
for idx in (0...res.num_fields)
|
117
|
-
meta.field_index[res.fetch_field.name] = idx
|
118
|
-
end
|
119
|
-
|
120
|
-
ensure
|
121
|
-
res.free if res
|
122
|
-
end
|
123
|
-
|
124
|
-
def create_fields(klass)
|
125
|
-
fields = []
|
126
|
-
|
127
|
-
klass.__props.each do |p|
|
128
|
-
klass.sql_index(p.symbol) if p.meta[:sql_index]
|
129
|
-
|
130
|
-
field = "#{p.symbol}"
|
131
|
-
|
132
|
-
if p.meta and p.meta[:sql]
|
133
|
-
field << " #{p.meta[:sql]}"
|
134
|
-
else
|
135
|
-
field << " #{@typemap[p.klass]}"
|
136
|
-
|
137
|
-
if p.meta
|
138
|
-
# set default value (gmosx: not that useful in the
|
139
|
-
# current implementation).
|
140
|
-
if default = p.meta[:default]
|
141
|
-
field << " DEFAULT #{default.inspect} NOT NULL"
|
142
|
-
end
|
143
|
-
|
144
|
-
# set unique
|
145
|
-
# FIXME: correctly handle UNIQUE constrain.
|
146
|
-
# field << " UNIQUE" if p.meta[:unique]
|
147
|
-
|
148
|
-
# attach extra sql
|
149
|
-
if extra_sql = p.meta[:extra_sql]
|
150
|
-
field << " #{extra_sql}"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
fields << field
|
156
|
-
end
|
157
|
-
|
158
|
-
return fields
|
159
|
-
end
|
160
|
-
|
161
|
-
def create_table(klass, db)
|
162
|
-
conn = db.get_connection
|
163
|
-
|
164
|
-
fields = create_fields(klass)
|
165
|
-
|
166
|
-
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
167
|
-
|
168
|
-
conn.store.query_with_result = true
|
169
|
-
|
170
|
-
# Create table constrains
|
171
|
-
|
172
|
-
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
173
|
-
sql << ", #{constrains.join(', ')}"
|
174
|
-
end
|
175
|
-
|
176
|
-
sql << ');'
|
177
|
-
|
178
|
-
begin
|
179
|
-
conn.store.query(sql)
|
180
|
-
Logger.info "Created table '#{klass::DBTABLE}'."
|
181
|
-
rescue => ex
|
182
|
-
if ex.errno == 1050 # table already exists.
|
183
|
-
Logger.debug "Table already exists" if $DBG
|
184
|
-
return
|
185
|
-
else
|
186
|
-
raise
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# Create indices
|
191
|
-
|
192
|
-
if klass.__meta and indices = klass.__meta[:sql_index]
|
193
|
-
for data in indices
|
194
|
-
idx, options = *data
|
195
|
-
idx = idx.to_s
|
196
|
-
pre_sql, post_sql = options[:pre], options[:post]
|
197
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
198
|
-
conn.store.query("CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})")
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Create join tables if needed. Join tables are used in
|
203
|
-
# 'many_to_many' relations.
|
204
|
-
|
205
|
-
if klass.__meta and joins = klass.__meta[:sql_join]
|
206
|
-
for data in joins
|
207
|
-
# the class to join to and some options.
|
208
|
-
join_name, join_class, options = *data
|
209
|
-
|
210
|
-
# gmosx: dont use DBTABLE here, perhaps the join class
|
211
|
-
# is not managed yet.
|
212
|
-
join_table = "#{self.class.join_table(klass, join_class, join_name)}"
|
213
|
-
join_src = "#{self.class.encode(klass)}_oid"
|
214
|
-
join_dst = "#{self.class.encode(join_class)}_oid"
|
215
|
-
begin
|
216
|
-
conn.store.query("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )")
|
217
|
-
conn.store.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)")
|
218
|
-
conn.store.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)")
|
219
|
-
rescue => ex
|
220
|
-
if ex.errno == 1050 # table already exists.
|
221
|
-
Logger.debug "Join table already exists" if $DBG
|
222
|
-
else
|
223
|
-
raise
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
ensure
|
230
|
-
db.put_connection
|
231
|
-
end
|
232
|
-
|
233
|
-
def eval_og_oid(klass)
|
234
|
-
klass.class_eval %{
|
235
|
-
prop_accessor :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
|
236
|
-
}
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# The MySQL connection.
|
241
|
-
|
242
|
-
class MysqlConnection < Connection
|
243
|
-
|
244
|
-
def initialize(db)
|
245
|
-
super
|
246
|
-
|
247
|
-
config = db.config
|
248
|
-
|
249
|
-
@store = Mysql.connect(
|
250
|
-
config[:address] || 'localhost',
|
251
|
-
config[:user],
|
252
|
-
config[:password],
|
253
|
-
config[:database]
|
254
|
-
)
|
255
|
-
rescue => ex
|
256
|
-
if ex.errno == 1049 # database does not exist.
|
257
|
-
Logger.info "Database '#{config[:database]}' not found!"
|
258
|
-
@db.adapter.create_db(config[:database], config[:user], config[:password])
|
259
|
-
retry
|
260
|
-
end
|
261
|
-
raise
|
262
|
-
end
|
263
|
-
|
264
|
-
def close
|
265
|
-
@store.close
|
266
|
-
super
|
267
|
-
end
|
268
|
-
|
269
|
-
def prepare(sql)
|
270
|
-
raise 'Not implemented!'
|
271
|
-
end
|
272
|
-
|
273
|
-
def query(sql)
|
274
|
-
Logger.debug sql if $DBG
|
275
|
-
begin
|
276
|
-
@store.query_with_result = true
|
277
|
-
return @store.query(sql)
|
278
|
-
rescue => ex
|
279
|
-
handle_db_exception(ex, sql)
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
def exec(sql)
|
284
|
-
Logger.debug sql if $DBG
|
285
|
-
begin
|
286
|
-
@store.query_with_result = false
|
287
|
-
@store.query(sql)
|
288
|
-
rescue => ex
|
289
|
-
handle_db_exception(ex, sql)
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
def start
|
294
|
-
# @store.transaction
|
295
|
-
end
|
296
|
-
|
297
|
-
def commit
|
298
|
-
# @store.commit
|
299
|
-
end
|
300
|
-
|
301
|
-
def rollback
|
302
|
-
# @store.rollback
|
303
|
-
end
|
304
|
-
|
305
|
-
def valid_res?(res)
|
306
|
-
return !(res.nil? or 0 == res.num_rows)
|
307
|
-
end
|
308
|
-
|
309
|
-
def read_one(res, klass)
|
310
|
-
return nil unless valid_res?(res)
|
311
|
-
|
312
|
-
row = res.fetch_row
|
313
|
-
obj = klass.allocate
|
314
|
-
obj.og_read(row)
|
315
|
-
|
316
|
-
res.free
|
317
|
-
return obj
|
318
|
-
end
|
319
|
-
|
320
|
-
def read_all(res, klass)
|
321
|
-
return [] unless valid_res?(res)
|
322
|
-
|
323
|
-
objects = []
|
324
|
-
|
325
|
-
for tuple in (0...res.num_rows)
|
326
|
-
row = res.fetch_row
|
327
|
-
|
328
|
-
obj = klass.allocate
|
329
|
-
obj.og_read(row)
|
330
|
-
|
331
|
-
objects << obj
|
332
|
-
end
|
333
|
-
|
334
|
-
res.free
|
335
|
-
return objects
|
336
|
-
end
|
337
|
-
|
338
|
-
def read_int(res, idx = 0)
|
339
|
-
val = res.fetch_row[idx].to_i
|
340
|
-
res.free
|
341
|
-
return val
|
342
|
-
end
|
343
|
-
|
344
|
-
def get_row(res)
|
345
|
-
res.fetch_row
|
346
|
-
end
|
347
|
-
|
348
|
-
end
|
349
|
-
|
350
|
-
end
|