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/oracle.rb
DELETED
@@ -1,368 +0,0 @@
|
|
1
|
-
# * Matt Bowen <matt.bowen@farweststeel.com>
|
2
|
-
# * George Moschovitis <gm@navel.gr>
|
3
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
4
|
-
# $Id: oracle.rb 17 2005-04-14 16:03:40Z gmosx $
|
5
|
-
|
6
|
-
begin
|
7
|
-
require 'oracle'
|
8
|
-
rescue
|
9
|
-
Logger.error 'Ruby-Oracle bindings are not installed!'
|
10
|
-
Logger.error ex
|
11
|
-
end
|
12
|
-
|
13
|
-
require 'og/adapters/base'
|
14
|
-
|
15
|
-
module Og
|
16
|
-
|
17
|
-
# The Oracle adapter. This adapter communicates with
|
18
|
-
# an Oracle rdbms. For extra documentation see
|
19
|
-
# lib/og/adapter.rb
|
20
|
-
|
21
|
-
class OracleAdapter < Adapter
|
22
|
-
|
23
|
-
def initialize
|
24
|
-
super
|
25
|
-
@typemap.update(
|
26
|
-
Integer => 'number',
|
27
|
-
Fixnum => 'number',
|
28
|
-
String => 'varchar2(512)',
|
29
|
-
TrueClass => 'number',
|
30
|
-
Object => 'varchar2(1024)',
|
31
|
-
Array => 'varchar2(1024)',
|
32
|
-
Hash => 'varchar2(1024)'
|
33
|
-
)
|
34
|
-
@typecast.update(TrueClass => "#\{:s: ? \"1\" : 'NULL' \}")
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.timestamp(time = Time.now)
|
38
|
-
return nil unless time
|
39
|
-
return time.strftime("%Y-%m-%d %H:%M:%S")
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.date(date)
|
43
|
-
return nil unless date
|
44
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def write_prop(p)
|
48
|
-
if p.klass.ancestors.include?(Integer)
|
49
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
50
|
-
elsif p.klass.ancestors.include?(Float)
|
51
|
-
return "#\{@#{p.symbol} || 'NULL'\}"
|
52
|
-
elsif p.klass.ancestors.include?(String)
|
53
|
-
return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
|
54
|
-
elsif p.klass.ancestors.include?(Time)
|
55
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
56
|
-
elsif p.klass.ancestors.include?(Date)
|
57
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
58
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
59
|
-
return "#\{@#{p.symbol} ? \"1\" : 'NULL' \}"
|
60
|
-
else
|
61
|
-
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
#--
|
66
|
-
# mcb:
|
67
|
-
# Unlike MySQL or Postgres, Oracle database/schema creation is a big deal.
|
68
|
-
# I don't know how to do it from the command line. I use Oracle's Database
|
69
|
-
# Configuration Assistant utility (dbca). I takes 30min - 1hr to create
|
70
|
-
# a full blown schema. So, your FIXME comments are fine. I'm thinking you
|
71
|
-
# won't be able to do this via Og, but once created, Og will be able to create
|
72
|
-
# tables, indexes, and other objects.
|
73
|
-
#++
|
74
|
-
|
75
|
-
def create_db(database, user = nil, password = nil)
|
76
|
-
# FIXME: what is appropriate for oracle?
|
77
|
-
# `createdb #{database} -U #{user}`
|
78
|
-
super
|
79
|
-
end
|
80
|
-
|
81
|
-
def drop_db(database, user = nil, password = nil)
|
82
|
-
# FIXME: what is appropriate for oracle?
|
83
|
-
# `dropdb #{database} -U #{user}`
|
84
|
-
super
|
85
|
-
end
|
86
|
-
|
87
|
-
def insert_code(klass, db)
|
88
|
-
props = props_for_insert(klass)
|
89
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
90
|
-
|
91
|
-
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
92
|
-
|
93
|
-
%{
|
94
|
-
res = conn.store.exec("SELECT #{klass::DBSEQ}.nextval FROM DUAL")
|
95
|
-
@oid = res.fetch[0].to_i
|
96
|
-
res.close
|
97
|
-
conn.exec "#{sql}"
|
98
|
-
}
|
99
|
-
end
|
100
|
-
|
101
|
-
def new_connection(db)
|
102
|
-
return OracleConnection.new(db)
|
103
|
-
end
|
104
|
-
|
105
|
-
def calc_field_index(klass, db)
|
106
|
-
# gmosx: This is incredible!!! argh!
|
107
|
-
# res = db.query "SELECT * FROM #{klass::DBTABLE} # LIMIT 1"
|
108
|
-
res = db.query "SELECT * FROM (SELECT * FROM #{klass::DBTABLE}) WHERE ROWNUM <= 1"
|
109
|
-
meta = db.managed_classes[klass]
|
110
|
-
|
111
|
-
columns = res.getColNames
|
112
|
-
|
113
|
-
for idx in (0...columns.size)
|
114
|
-
# mcb: Oracle will return column names in uppercase.
|
115
|
-
meta.field_index[columns[idx].downcase] = idx
|
116
|
-
end
|
117
|
-
|
118
|
-
ensure
|
119
|
-
res.close if res
|
120
|
-
end
|
121
|
-
|
122
|
-
def create_table(klass, db)
|
123
|
-
conn = db.get_connection
|
124
|
-
|
125
|
-
fields = create_fields(klass)
|
126
|
-
|
127
|
-
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
128
|
-
|
129
|
-
# Create table constrains.
|
130
|
-
|
131
|
-
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
132
|
-
sql << ", #{constrains.join(', ')}"
|
133
|
-
end
|
134
|
-
|
135
|
-
# mcb: Oracle driver chokes on semicolon.
|
136
|
-
sql << ")"
|
137
|
-
|
138
|
-
# mcb:
|
139
|
-
# Oracle driver appears to have problems executing multiple SQL
|
140
|
-
# statements in single exec() call. Chokes with or without semicolon
|
141
|
-
# delimiter. Solution: make separate calls for each statement.
|
142
|
-
|
143
|
-
begin
|
144
|
-
conn.store.exec(sql).close
|
145
|
-
Logger.info "Created table '#{klass::DBTABLE}'."
|
146
|
-
|
147
|
-
# Create indices.
|
148
|
-
|
149
|
-
if klass.__meta and indices = klass.__meta[:sql_index]
|
150
|
-
for data in indices
|
151
|
-
idx, options = *data
|
152
|
-
idx = idx.to_s
|
153
|
-
pre_sql, post_sql = options[:pre], options[:post]
|
154
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
155
|
-
sql = " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})"
|
156
|
-
conn.store.exec(sql).close
|
157
|
-
Logger.info "Created index '#{klass::DBTABLE}_#{idxname}_idx'."
|
158
|
-
end
|
159
|
-
end
|
160
|
-
rescue Exception => ex
|
161
|
-
# gmosx: any idea how to better test this?
|
162
|
-
if ex.to_s =~ /ORA-00955/i
|
163
|
-
Logger.debug 'Table or index already exists' if $DBG
|
164
|
-
return
|
165
|
-
else
|
166
|
-
raise
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# Create the sequence for this table.
|
171
|
-
begin
|
172
|
-
conn.store.exec("CREATE SEQUENCE #{klass::DBSEQ}").close
|
173
|
-
Logger.info "Created sequence '#{klass::DBSEQ}'."
|
174
|
-
rescue Exception => ex
|
175
|
-
# gmosx: any idea how to better test this?
|
176
|
-
if ex.to_s =~ /ORA-00955/i
|
177
|
-
Logger.debug "Sequence already exists" if $DBG
|
178
|
-
else
|
179
|
-
raise
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# Create join tables if needed. Join tables are used in
|
184
|
-
# 'many_to_many' relations.
|
185
|
-
|
186
|
-
if klass.__meta and joins = klass.__meta[:sql_join]
|
187
|
-
for data in joins
|
188
|
-
# the class to join to and some options.
|
189
|
-
join_name, join_class, options = *data
|
190
|
-
|
191
|
-
# gmosx: dont use DBTABLE here, perhaps the join class
|
192
|
-
# is not managed yet.
|
193
|
-
join_table = "#{self.class.join_table(klass, join_class, join_name)}"
|
194
|
-
join_src = "#{self.class.encode(klass)}_oid"
|
195
|
-
join_dst = "#{self.class.encode(join_class)}_oid"
|
196
|
-
begin
|
197
|
-
conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").close
|
198
|
-
conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
|
199
|
-
conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
|
200
|
-
rescue Exception => ex
|
201
|
-
# gmosx: any idea how to better test this?
|
202
|
-
if ex.to_s =~ /ORA-00955/i
|
203
|
-
Logger.debug "Join table already exists" if $DBG
|
204
|
-
else
|
205
|
-
raise
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
ensure
|
212
|
-
db.put_connection
|
213
|
-
end
|
214
|
-
|
215
|
-
def drop_table(klass)
|
216
|
-
super
|
217
|
-
exec "DROP SEQUENCE #{klass::DBSEQ}"
|
218
|
-
end
|
219
|
-
|
220
|
-
# Generate the property for oid.
|
221
|
-
|
222
|
-
#--
|
223
|
-
# mcb:
|
224
|
-
# Oracle doesn't have a "serial" datatype. Replace with
|
225
|
-
# integer (which is probably just a synonym for NUMBER(38,0))
|
226
|
-
# A sequence is created automatically by Og.
|
227
|
-
#++
|
228
|
-
|
229
|
-
def eval_og_oid(klass)
|
230
|
-
klass.class_eval %{
|
231
|
-
prop_accessor :oid, Fixnum, :sql => 'integer PRIMARY KEY'
|
232
|
-
}
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
# The Oracle connection.
|
237
|
-
|
238
|
-
class OracleConnection < Connection
|
239
|
-
|
240
|
-
# mcb:
|
241
|
-
# The database connection details are tucked away in a
|
242
|
-
# TNS entry (Transparent Network Substrate) which specifies host,
|
243
|
-
# port, protocol, and database instance. Here is a sample TNS
|
244
|
-
# entry:
|
245
|
-
#
|
246
|
-
# File: tns_names.ora
|
247
|
-
#
|
248
|
-
# KBSID =
|
249
|
-
# (DESCRIPTION =
|
250
|
-
# (ADDRESS_LIST =
|
251
|
-
# (ADDRESS = (PROTOCOL = TCP)(HOST = keebler.farweststeel.com)(PORT = 1521))
|
252
|
-
# )
|
253
|
-
# (CONNECT_DATA =
|
254
|
-
# (SID = KBSID)
|
255
|
-
# )
|
256
|
-
# )
|
257
|
-
|
258
|
-
def initialize(db)
|
259
|
-
super
|
260
|
-
config = db.config
|
261
|
-
|
262
|
-
begin
|
263
|
-
# FIXME: how to pass address etc?
|
264
|
-
@store = Oracle.new(config[:user], config[:password], config[:database])
|
265
|
-
# gmosx: better use this???
|
266
|
-
# @store = Oracle.new(config[:tns])
|
267
|
-
|
268
|
-
# gmosx: does this work?
|
269
|
-
@store.autocommit = true
|
270
|
-
rescue Exception => ex
|
271
|
-
# mcb:
|
272
|
-
# Oracle will raise a ORA-01017 if username, password, or
|
273
|
-
# SID aren't valid. I verified this for all three.
|
274
|
-
# irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
|
275
|
-
# /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017: invalid username/password; logon denied (OCIError)
|
276
|
-
# gmosx:
|
277
|
-
# any idea how to better test this? an integer error id?
|
278
|
-
if ex.to_s =~ /ORA-01017/i
|
279
|
-
Logger.info "Database '#{config[:database]}' not found!"
|
280
|
-
@db.adapter.create_db(config[:database], config[:user])
|
281
|
-
retry
|
282
|
-
end
|
283
|
-
raise
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def close
|
288
|
-
@store.logoff
|
289
|
-
super
|
290
|
-
end
|
291
|
-
|
292
|
-
def query(sql)
|
293
|
-
Logger.debug sql if $DBG
|
294
|
-
begin
|
295
|
-
return @store.exec(sql)
|
296
|
-
rescue Exception => ex
|
297
|
-
handle_db_exception(ex, sql)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
def exec(sql)
|
302
|
-
Logger.debug sql if $DBG
|
303
|
-
begin
|
304
|
-
@store.exec(sql)
|
305
|
-
rescue Exception => ex
|
306
|
-
handle_db_exception(ex, sql)
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
def start
|
311
|
-
@store.autocommit = false
|
312
|
-
end
|
313
|
-
|
314
|
-
def commit
|
315
|
-
@store.commit
|
316
|
-
ensure
|
317
|
-
@store.autocommit = true
|
318
|
-
end
|
319
|
-
|
320
|
-
def rollback
|
321
|
-
@store.rollback
|
322
|
-
ensure
|
323
|
-
@store.autocommit = true
|
324
|
-
end
|
325
|
-
|
326
|
-
def valid_res?(res)
|
327
|
-
return !(res.nil?)
|
328
|
-
end
|
329
|
-
|
330
|
-
def read_one(res, klass)
|
331
|
-
return nil unless valid_res?(res)
|
332
|
-
|
333
|
-
row = res.fetch
|
334
|
-
return nil unless row
|
335
|
-
|
336
|
-
obj = klass.allocate
|
337
|
-
obj.og_read(row)
|
338
|
-
|
339
|
-
res.close
|
340
|
-
return obj
|
341
|
-
end
|
342
|
-
|
343
|
-
def read_all(res, klass)
|
344
|
-
return [] unless valid_res?(res)
|
345
|
-
objects = []
|
346
|
-
|
347
|
-
while row = res.fetch
|
348
|
-
obj = klass.allocate
|
349
|
-
obj.og_read(row)
|
350
|
-
objects << obj
|
351
|
-
end
|
352
|
-
|
353
|
-
res.close
|
354
|
-
return objects
|
355
|
-
end
|
356
|
-
|
357
|
-
def read_int(res, idx = 0)
|
358
|
-
val = res.fetch[idx].to_i
|
359
|
-
res.close
|
360
|
-
return val
|
361
|
-
end
|
362
|
-
|
363
|
-
def get_row(res)
|
364
|
-
res.fetch
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
end
|
data/lib/og/adapters/psql.rb
DELETED
@@ -1,272 +0,0 @@
|
|
1
|
-
# * George Moschovitis <gm@navel.gr>
|
2
|
-
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: psql.rb 17 2005-04-14 16:03:40Z gmosx $
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'postgres'
|
7
|
-
rescue Object => ex
|
8
|
-
Logger.error 'Ruby-Postgresql bindings are not installed!'
|
9
|
-
Logger.error ex
|
10
|
-
end
|
11
|
-
|
12
|
-
require 'og/adapters/base'
|
13
|
-
|
14
|
-
module Og
|
15
|
-
|
16
|
-
# The PostgreSQL adapter. This adapter communicates with
|
17
|
-
# an PostgreSQL rdbms. For extra documentation see
|
18
|
-
# lib/og/adapter.rb
|
19
|
-
|
20
|
-
class PsqlAdapter < Adapter
|
21
|
-
|
22
|
-
def self.escape(str)
|
23
|
-
return nil unless str
|
24
|
-
return PGconn.escape(str)
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.timestamp(time = Time.now)
|
28
|
-
return nil unless time
|
29
|
-
return time.strftime("%Y-%m-%d %H:%M:%S")
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.date(date)
|
33
|
-
return nil unless date
|
34
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
35
|
-
end
|
36
|
-
|
37
|
-
def read_prop(p, idx)
|
38
|
-
if p.klass.ancestors.include?(Integer)
|
39
|
-
return "res.getvalue(tuple, #{idx}).to_i()"
|
40
|
-
elsif p.klass.ancestors.include?(Float)
|
41
|
-
return "res.getvalue(tuple, #{idx}).to_f()"
|
42
|
-
elsif p.klass.ancestors.include?(String)
|
43
|
-
return "res.getvalue(tuple, #{idx})"
|
44
|
-
elsif p.klass.ancestors.include?(Time)
|
45
|
-
return "#{self.class}.parse_timestamp(res.getvalue(tuple, #{idx}))"
|
46
|
-
elsif p.klass.ancestors.include?(Date)
|
47
|
-
return "#{self.class}.parse_date(res.getvalue(tuple, #{idx}))"
|
48
|
-
elsif p.klass.ancestors.include?(TrueClass)
|
49
|
-
return %|('t' == res.getvalue(tuple, #{idx}))|
|
50
|
-
else
|
51
|
-
return "YAML::load(res.getvalue(tuple, #{idx}))"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def create_db(database, user = nil, password = nil)
|
56
|
-
# gmosx: system is used to avoid shell expansion.
|
57
|
-
system 'createdb', database, '-U', user
|
58
|
-
super
|
59
|
-
end
|
60
|
-
|
61
|
-
def drop_db(database, user = nil, password = nil)
|
62
|
-
system 'dropdb', database, '-U', user
|
63
|
-
super
|
64
|
-
end
|
65
|
-
|
66
|
-
def insert_code(klass, db)
|
67
|
-
props = props_for_insert(klass)
|
68
|
-
values = props.collect { |p| write_prop(p) }.join(',')
|
69
|
-
|
70
|
-
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
71
|
-
|
72
|
-
%{
|
73
|
-
res = conn.store.exec("SELECT nextval('#{klass::DBSEQ}')")
|
74
|
-
@oid = res.getvalue(0, 0).to_i
|
75
|
-
res.clear
|
76
|
-
conn.exec "#{sql}"
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
def new_connection(db)
|
81
|
-
return PsqlConnection.new(db)
|
82
|
-
end
|
83
|
-
|
84
|
-
def calc_field_index(klass, db)
|
85
|
-
res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
|
86
|
-
meta = db.managed_classes[klass]
|
87
|
-
|
88
|
-
for field in res.fields
|
89
|
-
meta.field_index[field] = res.fieldnum(field)
|
90
|
-
end
|
91
|
-
|
92
|
-
ensure
|
93
|
-
res.clear if res
|
94
|
-
end
|
95
|
-
|
96
|
-
def create_table(klass, db)
|
97
|
-
conn = db.get_connection
|
98
|
-
|
99
|
-
fields = create_fields(klass)
|
100
|
-
|
101
|
-
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
102
|
-
|
103
|
-
# Create table constrains.
|
104
|
-
|
105
|
-
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
106
|
-
sql << ", #{constrains.join(', ')}"
|
107
|
-
end
|
108
|
-
|
109
|
-
sql << ") WITHOUT OIDS;"
|
110
|
-
|
111
|
-
# Create indices.
|
112
|
-
|
113
|
-
if klass.__meta and indices = klass.__meta[:sql_index]
|
114
|
-
for data in indices
|
115
|
-
idx, options = *data
|
116
|
-
idx = idx.to_s
|
117
|
-
pre_sql, post_sql = options[:pre], options[:post]
|
118
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
119
|
-
sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
begin
|
124
|
-
conn.store.exec(sql).clear
|
125
|
-
Logger.info "Created table '#{klass::DBTABLE}'."
|
126
|
-
rescue => ex
|
127
|
-
# gmosx: any idea how to better test this?
|
128
|
-
if ex.to_s =~ /relation .* already exists/i
|
129
|
-
Logger.debug 'Table already exists' if $DBG
|
130
|
-
return
|
131
|
-
else
|
132
|
-
raise
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Create join tables if needed. Join tables are used in
|
137
|
-
# 'many_to_many' relations.
|
138
|
-
|
139
|
-
if klass.__meta and joins = klass.__meta[:sql_join]
|
140
|
-
for data in joins
|
141
|
-
# the class to join to and some options.
|
142
|
-
join_name, join_class, options = *data
|
143
|
-
|
144
|
-
# gmosx: dont use DBTABLE here, perhaps the join class
|
145
|
-
# is not managed yet.
|
146
|
-
join_table = "#{self.class.join_table(klass, join_class, join_name)}"
|
147
|
-
join_src = "#{self.class.encode(klass)}_oid"
|
148
|
-
join_dst = "#{self.class.encode(join_class)}_oid"
|
149
|
-
begin
|
150
|
-
conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").clear
|
151
|
-
conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").clear
|
152
|
-
conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").clear
|
153
|
-
rescue => ex
|
154
|
-
# gmosx: any idea how to better test this?
|
155
|
-
if ex.to_s =~ /relation .* already exists/i
|
156
|
-
Logger.debug "Join table already exists" if $DBG
|
157
|
-
else
|
158
|
-
raise
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
ensure
|
165
|
-
db.put_connection
|
166
|
-
end
|
167
|
-
|
168
|
-
def drop_table(klass)
|
169
|
-
super
|
170
|
-
exec "DROP SEQUENCE #{klass::DBSEQ}"
|
171
|
-
end
|
172
|
-
|
173
|
-
# Generate the property for oid.
|
174
|
-
|
175
|
-
def eval_og_oid(klass)
|
176
|
-
klass.class_eval %{
|
177
|
-
prop_accessor :oid, Fixnum, :sql => 'serial PRIMARY KEY'
|
178
|
-
}
|
179
|
-
end
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
# The PostgreSQL connection.
|
184
|
-
|
185
|
-
class PsqlConnection < Connection
|
186
|
-
|
187
|
-
def initialize(db)
|
188
|
-
super
|
189
|
-
|
190
|
-
config = db.config
|
191
|
-
|
192
|
-
begin
|
193
|
-
@store = PGconn.connect(
|
194
|
-
config[:address],
|
195
|
-
config[:port],
|
196
|
-
nil,
|
197
|
-
nil,
|
198
|
-
config[:database],
|
199
|
-
config[:user].to_s,
|
200
|
-
config[:password].to_s
|
201
|
-
)
|
202
|
-
rescue => ex
|
203
|
-
# gmosx: any idea how to better test this?
|
204
|
-
if ex.to_s =~ /database .* does not exist/i
|
205
|
-
Logger.info "Database '#{config[:database]}' not found!"
|
206
|
-
@db.adapter.create_db(config[:database], config[:user])
|
207
|
-
retry
|
208
|
-
end
|
209
|
-
raise
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def close
|
214
|
-
@store.close
|
215
|
-
super
|
216
|
-
end
|
217
|
-
|
218
|
-
def query(sql)
|
219
|
-
Logger.debug sql if $DBG
|
220
|
-
begin
|
221
|
-
return @store.exec(sql)
|
222
|
-
rescue => ex
|
223
|
-
handle_db_exception(ex, sql)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def exec(sql)
|
228
|
-
Logger.debug sql if $DBG
|
229
|
-
begin
|
230
|
-
@store.exec(sql).clear
|
231
|
-
rescue => ex
|
232
|
-
handle_db_exception(ex, sql)
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def valid_res?(res)
|
237
|
-
return !(res.nil? or 0 == res.num_tuples)
|
238
|
-
end
|
239
|
-
|
240
|
-
def read_one(res, klass)
|
241
|
-
return nil unless valid_res?(res)
|
242
|
-
|
243
|
-
obj = klass.allocate
|
244
|
-
obj.og_read(res, 0)
|
245
|
-
|
246
|
-
res.clear
|
247
|
-
return obj
|
248
|
-
end
|
249
|
-
|
250
|
-
def read_all(res, klass)
|
251
|
-
return [] unless valid_res?(res)
|
252
|
-
objects = []
|
253
|
-
|
254
|
-
for tuple in (0...res.num_tuples)
|
255
|
-
obj = klass.allocate
|
256
|
-
obj.og_read(res, tuple)
|
257
|
-
objects << obj
|
258
|
-
end
|
259
|
-
|
260
|
-
res.clear
|
261
|
-
return objects
|
262
|
-
end
|
263
|
-
|
264
|
-
def read_int(res, idx = 0)
|
265
|
-
val = res.getvalue(0, idx).to_i
|
266
|
-
res.clear
|
267
|
-
return val
|
268
|
-
end
|
269
|
-
|
270
|
-
end
|
271
|
-
|
272
|
-
end
|