og 0.10.0 → 0.11.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/AUTHORS +4 -1
- data/ChangeLog +290 -7
- data/README.og +3 -4
- data/RELEASES.og +50 -0
- data/Rakefile +7 -7
- data/examples/og/run.rb +1 -1
- data/lib/glue/array.rb +14 -33
- data/lib/glue/hash.rb +32 -53
- data/lib/glue/pool.rb +9 -12
- data/lib/glue/property.rb +31 -9
- data/lib/og.rb +20 -12
- data/lib/og/adapter.rb +40 -13
- data/lib/og/adapters/filesys.rb +121 -0
- data/lib/og/adapters/mysql.rb +10 -5
- data/lib/og/adapters/oracle.rb +374 -0
- data/lib/og/adapters/psql.rb +10 -23
- data/lib/og/adapters/sqlite.rb +3 -3
- data/lib/og/backend.rb +2 -2
- data/lib/og/connection.rb +6 -6
- data/lib/og/database.rb +5 -5
- data/lib/og/enchant.rb +6 -2
- data/lib/og/meta.rb +56 -26
- data/lib/og/mock.rb +1 -1
- data/lib/og/typemacros.rb +23 -0
- data/test/og/tc_filesys.rb +83 -0
- data/test/og/tc_meta.rb +55 -0
- data/test/tc_og.rb +115 -36
- metadata +8 -4
- data/ChangeLog.1 +0 -2344
data/lib/og/adapter.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: adapter.rb
|
3
|
+
# $Id: adapter.rb 266 2005-02-28 14:50:48Z gmosx $
|
4
4
|
|
5
5
|
require 'yaml'
|
6
6
|
require 'singleton'
|
7
7
|
|
8
8
|
require 'og/connection'
|
9
9
|
|
10
|
-
|
10
|
+
module Og
|
11
11
|
|
12
12
|
# An adapter communicates with the backend datastore.
|
13
13
|
# The adapters for all supported datastores extend this
|
@@ -74,6 +74,7 @@ class Adapter
|
|
74
74
|
# TODO: Optimize this
|
75
75
|
|
76
76
|
def self.parse_timestamp(str)
|
77
|
+
return nil unless str
|
77
78
|
return Time.parse(str)
|
78
79
|
end
|
79
80
|
|
@@ -108,7 +109,7 @@ class Adapter
|
|
108
109
|
end
|
109
110
|
|
110
111
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
111
|
-
# :section:
|
112
|
+
# :section: O->R mapping methods and utilities.
|
112
113
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
113
114
|
|
114
115
|
# Encode the name of the klass as an sql safe string.
|
@@ -121,24 +122,40 @@ class Adapter
|
|
121
122
|
end
|
122
123
|
|
123
124
|
# The name of the SQL table where objects of this class
|
124
|
-
# are stored.
|
125
|
+
# are stored. A prefix is needed to avoid colision with
|
126
|
+
# reserved prefices (for example User maps to user which
|
127
|
+
# is reserved in postgresql). The prefix should start
|
128
|
+
# with an alphanumeric character to be compatible with
|
129
|
+
# all RDBMS (most notable Oracle).
|
130
|
+
#
|
131
|
+
# You may want to override this method to map an existing
|
132
|
+
# database schema using Og.
|
125
133
|
|
126
134
|
def self.table(klass)
|
127
|
-
"
|
135
|
+
"og_#{Og.table_prefix}#{encode(klass)}"
|
128
136
|
end
|
129
137
|
|
130
138
|
# The name of the join table for the two given classes.
|
139
|
+
# A prefix is needed to avoid colision with reserved
|
140
|
+
# prefices (for example User maps to user which
|
141
|
+
# is reserved in postgresql). The prefix should start
|
142
|
+
# with an alphanumeric character to be compatible with
|
143
|
+
# all RDBMS (most notable Oracle).
|
144
|
+
#
|
145
|
+
# You may want to override this method to map an existing
|
146
|
+
# database schema using Og.
|
131
147
|
|
132
148
|
def self.join_table(klass1, klass2)
|
133
|
-
"
|
149
|
+
"og_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
|
134
150
|
end
|
135
|
-
|
151
|
+
|
136
152
|
# Return an sql string evaluator for the property.
|
137
153
|
# No need to optimize this, used only to precalculate code.
|
138
154
|
# YAML is used to store general Ruby objects to be more
|
139
155
|
# portable.
|
140
|
-
|
156
|
+
#--
|
141
157
|
# FIXME: add extra handling for float.
|
158
|
+
#++
|
142
159
|
|
143
160
|
def write_prop(p)
|
144
161
|
if p.klass.ancestors.include?(Integer)
|
@@ -146,7 +163,7 @@ class Adapter
|
|
146
163
|
elsif p.klass.ancestors.include?(Float)
|
147
164
|
return "#\{@#{p.symbol} || 'NULL'\}"
|
148
165
|
elsif p.klass.ancestors.include?(String)
|
149
|
-
return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
|
166
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
|
150
167
|
elsif p.klass.ancestors.include?(Time)
|
151
168
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
152
169
|
elsif p.klass.ancestors.include?(Date)
|
@@ -154,6 +171,7 @@ class Adapter
|
|
154
171
|
elsif p.klass.ancestors.include?(TrueClass)
|
155
172
|
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
156
173
|
else
|
174
|
+
# gmosx: keep the '' for nil symbols.
|
157
175
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
158
176
|
end
|
159
177
|
end
|
@@ -197,9 +215,18 @@ class Adapter
|
|
197
215
|
field << " #{p.meta[:sql]}"
|
198
216
|
else
|
199
217
|
field << " #{@typemap[p.klass]}"
|
200
|
-
|
201
|
-
if p.meta
|
202
|
-
|
218
|
+
|
219
|
+
if p.meta
|
220
|
+
# set default value (gmosx: not that useful in the
|
221
|
+
# current implementation).
|
222
|
+
if default = p.meta[:default]
|
223
|
+
field << " DEFAULT #{default.inspect} NOT NULL"
|
224
|
+
end
|
225
|
+
|
226
|
+
# attach extra sql
|
227
|
+
if extra_sql = p.meta[:extra_sql]
|
228
|
+
field << " #{extra_sql}"
|
229
|
+
end
|
203
230
|
end
|
204
231
|
end
|
205
232
|
|
@@ -235,7 +262,7 @@ class Adapter
|
|
235
262
|
# object properties.
|
236
263
|
|
237
264
|
def calc_field_index(klass, og)
|
238
|
-
|
265
|
+
# Implement if needed.
|
239
266
|
end
|
240
267
|
|
241
268
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: filesys.rb 264 2005-02-23 13:46:55Z gmosx $
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
require 'og/adapter'
|
8
|
+
require 'og/connection'
|
9
|
+
require 'glue/attribute'
|
10
|
+
|
11
|
+
module Og
|
12
|
+
|
13
|
+
# The Filesys adapter. This adapter stores Og objectes in
|
14
|
+
# the filesystem. This adapter is a proof of concept and
|
15
|
+
# at the moment severely limited. For extra documentation
|
16
|
+
# see lib/og/adapter.rb
|
17
|
+
|
18
|
+
class FilesysAdapter < Adapter
|
19
|
+
|
20
|
+
# Creates the name of the database root dir.
|
21
|
+
|
22
|
+
def self.dir(database)
|
23
|
+
"#{database}_db"
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_db(database, user = nil, password = nil)
|
27
|
+
begin
|
28
|
+
FileUtils.mkdir_p(self.class.dir(database))
|
29
|
+
rescue
|
30
|
+
Logger.error "Cannot create '#{database}'!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def drop_db(database, user = nil, password = nil)
|
35
|
+
begin
|
36
|
+
FileUtils.rmdir(self.class.dir(database))
|
37
|
+
super
|
38
|
+
rescue
|
39
|
+
Logger.error "Cannot drop '#{database}'!"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_code(klass, db, pre_cb, post_cb)
|
44
|
+
props = props_for_insert(klass)
|
45
|
+
values = props.collect { |p| write_prop(p) }.join(',')
|
46
|
+
|
47
|
+
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
48
|
+
|
49
|
+
%{
|
50
|
+
#{pre_cb}
|
51
|
+
conn.store.query("#{sql}").close
|
52
|
+
@oid = conn.store.last_insert_row_id
|
53
|
+
#{post_cb}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_connection(db)
|
58
|
+
return FilesysConnection.new(db)
|
59
|
+
end
|
60
|
+
|
61
|
+
# A 'table' is emulated by using a directory to store
|
62
|
+
# one file per table row. Each file contains an object
|
63
|
+
# marshaled in YAML format. A special file called
|
64
|
+
# '_sequence' stores the sequence for this 'table'.
|
65
|
+
|
66
|
+
def create_table(klass, db)
|
67
|
+
class_dir = File.join(self.class.dir(db.config[:database]), klass::DBTABLE)
|
68
|
+
FileUtils.mkdir_p(class_dir)
|
69
|
+
|
70
|
+
seq_file = File.join(class_dir, 'seq')
|
71
|
+
File.open(seq_file, 'w') { |f| f << '1' }
|
72
|
+
rescue => ex
|
73
|
+
Logger.error "Cannot create directory to store '#{klass}' classes!"
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# The Filesys adapter connection.
|
79
|
+
|
80
|
+
class FilesysConnection < Connection
|
81
|
+
|
82
|
+
def initialize(db)
|
83
|
+
super
|
84
|
+
config = db.config
|
85
|
+
|
86
|
+
begin
|
87
|
+
raise unless File.directory?(FilesysAdapter.dir(config[:database]))
|
88
|
+
rescue => ex
|
89
|
+
if true # ex.to_s =~ /database .* does not exist/i
|
90
|
+
Logger.info "Database '#{config[:database]}' not found!"
|
91
|
+
@db.adapter.create_db(config[:database], config[:user])
|
92
|
+
retry
|
93
|
+
end
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def save(obj)
|
99
|
+
seq = nil
|
100
|
+
class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), obj.class::DBTABLE)
|
101
|
+
File.open(File.join(class_dir, 'seq'), 'r') { |f| seq = f.read.to_i }
|
102
|
+
obj.oid = seq
|
103
|
+
File.open(File.join(class_dir, "#{seq}.yml"), 'w') { |f| f << obj.to_yaml }
|
104
|
+
seq += 1
|
105
|
+
File.open(File.join(class_dir, 'seq'), 'w') { |f| f << seq }
|
106
|
+
end
|
107
|
+
|
108
|
+
def load(oid, klass)
|
109
|
+
obj = nil
|
110
|
+
class_dir = File.join(FilesysAdapter.dir(@db.config[:database]), klass::DBTABLE)
|
111
|
+
File.open(File.join(class_dir, "#{oid}.yml"), 'r') { |f| obj = YAML::load(f.read) }
|
112
|
+
return obj
|
113
|
+
end
|
114
|
+
|
115
|
+
def close
|
116
|
+
Logger.debug "Closed DB connection." if $DBG
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
data/lib/og/adapters/mysql.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: mysql.rb
|
3
|
+
# $Id: mysql.rb 266 2005-02-28 14:50:48Z gmosx $
|
4
4
|
|
5
5
|
require 'mysql'
|
6
6
|
|
@@ -8,7 +8,7 @@ require 'og/adapter'
|
|
8
8
|
require 'og/connection'
|
9
9
|
require 'glue/attribute'
|
10
10
|
|
11
|
-
|
11
|
+
module Og
|
12
12
|
|
13
13
|
# The MySQL adapter. This adapter communicates with
|
14
14
|
# an MySQL rdbms. For extra documentation see
|
@@ -18,7 +18,7 @@ class MysqlAdapter < Adapter
|
|
18
18
|
|
19
19
|
def initialize
|
20
20
|
super
|
21
|
-
@typemap.update(
|
21
|
+
@typemap.update(TrueClass => 'tinyint')
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.escape(str)
|
@@ -42,7 +42,7 @@ class MysqlAdapter < Adapter
|
|
42
42
|
elsif p.klass.ancestors.include?(Float)
|
43
43
|
return "#\{@#{p.symbol} || 'NULL'\}"
|
44
44
|
elsif p.klass.ancestors.include?(String)
|
45
|
-
return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
|
45
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
|
46
46
|
elsif p.klass.ancestors.include?(Time)
|
47
47
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
48
48
|
elsif p.klass.ancestors.include?(Date)
|
@@ -50,6 +50,7 @@ class MysqlAdapter < Adapter
|
|
50
50
|
elsif p.klass.ancestors.include?(TrueClass)
|
51
51
|
return "#\{@#{p.symbol} ? 1 : 0 \}"
|
52
52
|
else
|
53
|
+
# gmosx: keep the '' for nil symbols.
|
53
54
|
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
54
55
|
end
|
55
56
|
end
|
@@ -82,6 +83,10 @@ class MysqlAdapter < Adapter
|
|
82
83
|
super
|
83
84
|
end
|
84
85
|
|
86
|
+
def props_for_insert(klass)
|
87
|
+
klass.__props.reject { |p| :oid == p.symbol }
|
88
|
+
end
|
89
|
+
|
85
90
|
def insert_code(klass, db, pre_cb, post_cb)
|
86
91
|
props = props_for_insert(klass)
|
87
92
|
values = props.collect { |p| write_prop(p) }.join(',')
|
@@ -98,7 +103,7 @@ class MysqlAdapter < Adapter
|
|
98
103
|
end
|
99
104
|
|
100
105
|
def new_connection(db)
|
101
|
-
return
|
106
|
+
return MysqlConnection.new(db)
|
102
107
|
end
|
103
108
|
|
104
109
|
def calc_field_index(klass, db)
|
@@ -0,0 +1,374 @@
|
|
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 266 2005-02-28 14:50:48Z gmosx $
|
5
|
+
|
6
|
+
require 'oracle'
|
7
|
+
|
8
|
+
require 'og/adapter'
|
9
|
+
require 'og/connection'
|
10
|
+
require 'glue/attribute'
|
11
|
+
|
12
|
+
module Og
|
13
|
+
|
14
|
+
# The Oracle adapter. This adapter communicates with
|
15
|
+
# an Oracle rdbms. For extra documentation see
|
16
|
+
# lib/og/adapter.rb
|
17
|
+
|
18
|
+
class OracleAdapter < Adapter
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
super
|
22
|
+
@typemap.update(
|
23
|
+
Integer => 'number',
|
24
|
+
Fixnum => 'number',
|
25
|
+
String => 'varchar2(512)',
|
26
|
+
TrueClass => 'number',
|
27
|
+
Object => 'varchar2(1024)',
|
28
|
+
Array => 'varchar2(1024)',
|
29
|
+
Hash => 'varchar2(1024)'
|
30
|
+
)
|
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 "'#\{#{self.class}.escape(@#{p.symbol})\}'"
|
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\" : 'NULL' \}"
|
56
|
+
else
|
57
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#--
|
62
|
+
# mcb:
|
63
|
+
# Unlike MySQL or Postgres, Oracle database/schema creation is a big deal.
|
64
|
+
# I don't know how to do it from the command line. I use Oracle's Database
|
65
|
+
# Configuration Assistant utility (dbca). I takes 30min - 1hr to create
|
66
|
+
# a full blown schema. So, your FIXME comments are fine. I'm thinking you
|
67
|
+
# won't be able to do this via Og, but once created, Og will be able to create
|
68
|
+
# tables, indexes, and other objects.
|
69
|
+
#++
|
70
|
+
|
71
|
+
def create_db(database, user = nil, password = nil)
|
72
|
+
# FIXME: what is appropriate for oracle?
|
73
|
+
# `createdb #{database} -U #{user}`
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def drop_db(database, user = nil, password = nil)
|
78
|
+
# FIXME: what is appropriate for oracle?
|
79
|
+
# `dropdb #{database} -U #{user}`
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
def insert_code(klass, db, pre_cb, post_cb)
|
84
|
+
props = props_for_insert(klass)
|
85
|
+
values = props.collect { |p| write_prop(p) }.join(',')
|
86
|
+
|
87
|
+
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
88
|
+
|
89
|
+
%{
|
90
|
+
#{pre_cb}
|
91
|
+
res = conn.store.exec("SELECT #{klass::DBSEQ}.nextval FROM DUAL")
|
92
|
+
@oid = res.fetch[0].to_i
|
93
|
+
res.close
|
94
|
+
conn.exec "#{sql}"
|
95
|
+
#{post_cb}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
def new_connection(db)
|
100
|
+
return OracleConnection.new(db)
|
101
|
+
end
|
102
|
+
|
103
|
+
def calc_field_index(klass, db)
|
104
|
+
# gmosx: This is incredible!!! argh!
|
105
|
+
# res = db.query "SELECT * FROM #{klass::DBTABLE} # LIMIT 1"
|
106
|
+
res = db.query "SELECT * FROM (SELECT * FROM #{klass::DBTABLE}) WHERE ROWNUM <= 1"
|
107
|
+
meta = db.managed_classes[klass]
|
108
|
+
|
109
|
+
columns = res.getColNames
|
110
|
+
|
111
|
+
for idx in (0...columns.size)
|
112
|
+
# mcb: Oracle will return column names in uppercase.
|
113
|
+
meta.field_index[columns[idx].downcase] = idx
|
114
|
+
end
|
115
|
+
|
116
|
+
ensure
|
117
|
+
res.close if res
|
118
|
+
end
|
119
|
+
|
120
|
+
def eval_og_oid(klass)
|
121
|
+
klass.class_eval %{
|
122
|
+
prop_accessor :oid, Fixnum, :sql => "number PRIMARY KEY"
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_table(klass, db)
|
127
|
+
conn = db.get_connection
|
128
|
+
|
129
|
+
fields = create_fields(klass)
|
130
|
+
|
131
|
+
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
132
|
+
|
133
|
+
# Create table constrains.
|
134
|
+
|
135
|
+
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
136
|
+
sql << ", #{constrains.join(', ')}"
|
137
|
+
end
|
138
|
+
|
139
|
+
# mcb: Oracle driver chokes on semicolon.
|
140
|
+
sql << ")"
|
141
|
+
|
142
|
+
# mcb:
|
143
|
+
# Oracle driver appears to have problems executing multiple SQL
|
144
|
+
# statements in single exec() call. Chokes with or without semicolon
|
145
|
+
# delimiter. Solution: make separate calls for each statement.
|
146
|
+
|
147
|
+
begin
|
148
|
+
conn.store.exec(sql).close
|
149
|
+
Logger.info "Created table '#{klass::DBTABLE}'."
|
150
|
+
|
151
|
+
# Create indices.
|
152
|
+
|
153
|
+
if klass.__meta and indices = klass.__meta[:sql_index]
|
154
|
+
for data in indices
|
155
|
+
idx, options = *data
|
156
|
+
idx = idx.to_s
|
157
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
158
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
159
|
+
sql = " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})"
|
160
|
+
conn.store.exec(sql).close
|
161
|
+
Logger.info "Created index '#{klass::DBTABLE}_#{idxname}_idx'."
|
162
|
+
end
|
163
|
+
end
|
164
|
+
rescue Exception => ex
|
165
|
+
# gmosx: any idea how to better test this?
|
166
|
+
if ex.to_s =~ /ORA-00955/i
|
167
|
+
Logger.debug 'Table or index already exists' if $DBG
|
168
|
+
return
|
169
|
+
else
|
170
|
+
raise
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Create the sequence for this table.
|
175
|
+
begin
|
176
|
+
conn.store.exec("CREATE SEQUENCE #{klass::DBSEQ}").close
|
177
|
+
Logger.info "Created sequence '#{klass::DBSEQ}'."
|
178
|
+
rescue Exception => ex
|
179
|
+
# gmosx: any idea how to better test this?
|
180
|
+
if ex.to_s =~ /ORA-00955/i
|
181
|
+
Logger.debug "Sequence already exists" if $DBG
|
182
|
+
else
|
183
|
+
raise
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Create join tables if needed. Join tables are used in
|
188
|
+
# 'many_to_many' relations.
|
189
|
+
|
190
|
+
if klass.__meta and joins = klass.__meta[:sql_join]
|
191
|
+
for data in joins
|
192
|
+
# the class to join to and some options.
|
193
|
+
join_class, options = *data
|
194
|
+
|
195
|
+
# gmosx: dont use DBTABLE here, perhaps the join class
|
196
|
+
# is not managed yet.
|
197
|
+
join_table = "#{self.class.join_table(klass, join_class)}"
|
198
|
+
join_src = "#{self.class.encode(klass)}_oid"
|
199
|
+
join_dst = "#{self.class.encode(join_class)}_oid"
|
200
|
+
begin
|
201
|
+
conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").close
|
202
|
+
conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
|
203
|
+
conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
|
204
|
+
rescue Exception => ex
|
205
|
+
# gmosx: any idea how to better test this?
|
206
|
+
if ex.to_s =~ /ORA-00955/i
|
207
|
+
Logger.debug "Join table already exists" if $DBG
|
208
|
+
else
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
ensure
|
216
|
+
db.put_connection
|
217
|
+
end
|
218
|
+
|
219
|
+
def drop_table(klass)
|
220
|
+
super
|
221
|
+
exec "DROP SEQUENCE #{klass::DBSEQ}"
|
222
|
+
end
|
223
|
+
|
224
|
+
# Generate the property for oid.
|
225
|
+
|
226
|
+
#--
|
227
|
+
# mcb:
|
228
|
+
# Oracle doesn't have a "serial" datatype. Replace with
|
229
|
+
# integer (which is probably just a synonym for NUMBER(38,0))
|
230
|
+
# A sequence is created automatically by Og.
|
231
|
+
#++
|
232
|
+
|
233
|
+
def eval_og_oid(klass)
|
234
|
+
klass.class_eval %{
|
235
|
+
prop_accessor :oid, Fixnum, :sql => 'integer PRIMARY KEY'
|
236
|
+
}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# The Oracle connection.
|
241
|
+
|
242
|
+
class OracleConnection < Connection
|
243
|
+
|
244
|
+
# mcb:
|
245
|
+
# The database connection details are tucked away in a
|
246
|
+
# TNS entry (Transparent Network Substrate) which specifies host,
|
247
|
+
# port, protocol, and database instance. Here is a sample TNS
|
248
|
+
# entry:
|
249
|
+
#
|
250
|
+
# File: tns_names.ora
|
251
|
+
#
|
252
|
+
# KBSID =
|
253
|
+
# (DESCRIPTION =
|
254
|
+
# (ADDRESS_LIST =
|
255
|
+
# (ADDRESS = (PROTOCOL = TCP)(HOST = keebler.farweststeel.com)(PORT = 1521))
|
256
|
+
# )
|
257
|
+
# (CONNECT_DATA =
|
258
|
+
# (SID = KBSID)
|
259
|
+
# )
|
260
|
+
# )
|
261
|
+
|
262
|
+
def initialize(db)
|
263
|
+
super
|
264
|
+
config = db.config
|
265
|
+
|
266
|
+
begin
|
267
|
+
# FIXME: how to pass address etc?
|
268
|
+
@store = Oracle.new(config[:user], config[:password], config[:database])
|
269
|
+
# gmosx: better use this???
|
270
|
+
# @store = Oracle.new(config[:tns])
|
271
|
+
|
272
|
+
# gmosx: does this work?
|
273
|
+
@store.autocommit = true
|
274
|
+
rescue Exception => ex
|
275
|
+
# mcb:
|
276
|
+
# Oracle will raise a ORA-01017 if username, password, or
|
277
|
+
# SID aren't valid. I verified this for all three.
|
278
|
+
# irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
|
279
|
+
# /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017: invalid username/password; logon denied (OCIError)
|
280
|
+
# gmosx:
|
281
|
+
# any idea how to better test this? an integer error id?
|
282
|
+
if ex.to_s =~ /ORA-01017/i
|
283
|
+
Logger.info "Database '#{config[:database]}' not found!"
|
284
|
+
@db.adapter.create_db(config[:database], config[:user])
|
285
|
+
retry
|
286
|
+
end
|
287
|
+
raise
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def close
|
292
|
+
@store.logoff
|
293
|
+
super
|
294
|
+
end
|
295
|
+
|
296
|
+
def query(sql)
|
297
|
+
Logger.debug sql if $DBG
|
298
|
+
begin
|
299
|
+
return @store.exec(sql)
|
300
|
+
rescue Exception => ex
|
301
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
302
|
+
Logger.error ex.backtrace.join("\n")
|
303
|
+
raise
|
304
|
+
# return nil
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def exec(sql)
|
309
|
+
Logger.debug sql if $DBG
|
310
|
+
begin
|
311
|
+
@store.exec(sql)
|
312
|
+
rescue Exception => ex
|
313
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
314
|
+
Logger.error ex.backtrace.join("\n")
|
315
|
+
raise
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def start
|
320
|
+
@store.autocommit = false
|
321
|
+
end
|
322
|
+
|
323
|
+
def commit
|
324
|
+
@store.commit
|
325
|
+
ensure
|
326
|
+
@store.autocommit = true
|
327
|
+
end
|
328
|
+
|
329
|
+
def rollback
|
330
|
+
@store.rollback
|
331
|
+
ensure
|
332
|
+
@store.autocommit = true
|
333
|
+
end
|
334
|
+
|
335
|
+
def valid_res?(res)
|
336
|
+
return !(res.nil?)
|
337
|
+
end
|
338
|
+
|
339
|
+
def read_one(res, klass)
|
340
|
+
return nil unless valid_res?(res)
|
341
|
+
|
342
|
+
row = res.fetch
|
343
|
+
return nil unless row
|
344
|
+
|
345
|
+
obj = klass.new
|
346
|
+
obj.og_read(row)
|
347
|
+
|
348
|
+
res.close
|
349
|
+
return obj
|
350
|
+
end
|
351
|
+
|
352
|
+
def read_all(res, klass)
|
353
|
+
return [] unless valid_res?(res)
|
354
|
+
objects = []
|
355
|
+
|
356
|
+
while row = res.fetch
|
357
|
+
obj = klass.new
|
358
|
+
obj.og_read(row)
|
359
|
+
objects << obj
|
360
|
+
end
|
361
|
+
|
362
|
+
res.close
|
363
|
+
return objects
|
364
|
+
end
|
365
|
+
|
366
|
+
def read_int(res, idx = 0)
|
367
|
+
val = res.fetch[idx].to_i
|
368
|
+
res.close
|
369
|
+
return val
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|