og 0.9.5 → 0.10.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 +260 -0
- data/LICENSE +1 -0
- data/README.og +6 -5
- data/RELEASES.og +23 -0
- data/Rakefile +102 -92
- data/examples/og/mock_example.rb +0 -2
- data/examples/og/mysql_to_psql.rb +0 -2
- data/examples/og/run.rb +23 -22
- data/install.rb +44 -0
- data/lib/glue/array.rb +6 -10
- data/lib/glue/attribute.rb +0 -3
- data/lib/glue/cache.rb +1 -1
- data/lib/glue/inflector.rb +5 -5
- data/lib/glue/mixins.rb +3 -12
- data/lib/glue/number.rb +1 -1
- data/lib/glue/object.rb +7 -1
- data/lib/glue/property.rb +32 -22
- data/lib/glue/string.rb +13 -75
- data/lib/glue/time.rb +2 -2
- data/lib/glue/validation.rb +7 -11
- data/lib/og.rb +27 -261
- data/lib/og/adapter.rb +352 -0
- data/lib/og/adapters/mysql.rb +304 -0
- data/lib/og/adapters/psql.rb +286 -0
- data/lib/og/adapters/sqlite.rb +262 -0
- data/lib/og/backend.rb +1 -1
- data/lib/og/connection.rb +123 -87
- data/lib/og/database.rb +268 -0
- data/lib/og/meta.rb +23 -22
- data/lib/og/mock.rb +2 -3
- data/test/og/tc_lifecycle.rb +22 -25
- data/test/og/tc_sqlite.rb +87 -0
- data/test/tc_og.rb +61 -42
- metadata +35 -11
- data/lib/glue/macro.rb +0 -56
- data/lib/og/backends/mysql.rb +0 -370
- data/lib/og/backends/psql.rb +0 -386
- data/lib/og/backends/sqlite.rb +0 -383
- data/lib/og/version.rb +0 -9
@@ -0,0 +1,262 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: sqlite.rb 259 2005-02-15 08:54:54Z gmosx $
|
4
|
+
|
5
|
+
require 'sqlite3'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
require 'og/adapter'
|
9
|
+
require 'og/connection'
|
10
|
+
require 'glue/attribute'
|
11
|
+
|
12
|
+
class Og
|
13
|
+
|
14
|
+
# The SQLite adapter. This adapter communicates with
|
15
|
+
# an SQLite3 rdbms. For extra documentation see
|
16
|
+
# lib/og/adapter.rb
|
17
|
+
|
18
|
+
class SqliteAdapter < Adapter
|
19
|
+
|
20
|
+
def drop_db(database, user = nil, password = nil)
|
21
|
+
begin
|
22
|
+
FileUtils.rm("#{database}.db")
|
23
|
+
super
|
24
|
+
rescue
|
25
|
+
Logger.error "Cannot drop '#{database}'!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
=begin
|
29
|
+
def write_prop(p)
|
30
|
+
if p.klass.ancestors.include?(Integer)
|
31
|
+
return "@#{p.symbol}"
|
32
|
+
elsif p.klass.ancestors.include?(Float)
|
33
|
+
return "@#{p.symbol}"
|
34
|
+
elsif p.klass.ancestors.include?(String)
|
35
|
+
return "#{self.class}.escape(@#{p.symbol})"
|
36
|
+
elsif p.klass.ancestors.include?(Time)
|
37
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
38
|
+
elsif p.klass.ancestors.include?(Date)
|
39
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
|
40
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
41
|
+
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
42
|
+
else
|
43
|
+
return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
44
|
+
end
|
45
|
+
end
|
46
|
+
=end
|
47
|
+
def insert_code(klass, db, pre_cb, post_cb)
|
48
|
+
props = props_for_insert(klass)
|
49
|
+
values = props.collect { |p| write_prop(p) }.join(',')
|
50
|
+
|
51
|
+
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
|
52
|
+
|
53
|
+
%{
|
54
|
+
#{pre_cb}
|
55
|
+
conn.store.query("#{sql}").close
|
56
|
+
@oid = conn.store.last_insert_row_id
|
57
|
+
#{post_cb}
|
58
|
+
}
|
59
|
+
=begin
|
60
|
+
props = props_for_insert(klass)
|
61
|
+
|
62
|
+
placeholders = Array.new(props.size, '?').join(',')
|
63
|
+
values = props.collect { |p| write_prop(p) }.join(',')
|
64
|
+
|
65
|
+
sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{placeholders})"
|
66
|
+
klass.class_eval %{
|
67
|
+
cattr_accessor :og_insert_statement
|
68
|
+
}
|
69
|
+
|
70
|
+
klass.og_insert_statement = db.prepare(sql)
|
71
|
+
|
72
|
+
%{
|
73
|
+
#{pre_cb}
|
74
|
+
@@og_insert_statement.execute(#{values})
|
75
|
+
@oid = conn.store.last_insert_row_id
|
76
|
+
#{post_cb}
|
77
|
+
}
|
78
|
+
=end
|
79
|
+
end
|
80
|
+
|
81
|
+
def new_connection(db)
|
82
|
+
return Og::SqliteConnection.new(db)
|
83
|
+
end
|
84
|
+
|
85
|
+
def calc_field_index(klass, db)
|
86
|
+
res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
|
87
|
+
meta = db.managed_classes[klass]
|
88
|
+
|
89
|
+
columns = res.columns
|
90
|
+
|
91
|
+
for idx in (0...columns.size)
|
92
|
+
meta.field_index[columns[idx]] = idx
|
93
|
+
end
|
94
|
+
|
95
|
+
ensure
|
96
|
+
res.close
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_table(klass, db)
|
100
|
+
conn = db.get_connection
|
101
|
+
|
102
|
+
fields = create_fields(klass)
|
103
|
+
|
104
|
+
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
105
|
+
|
106
|
+
# Create table constrains
|
107
|
+
|
108
|
+
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
109
|
+
sql << ", #{constrains.join(', ')}"
|
110
|
+
end
|
111
|
+
|
112
|
+
sql << ");"
|
113
|
+
|
114
|
+
# Create indices
|
115
|
+
|
116
|
+
if klass.__meta and indices = klass.__meta[:sql_index]
|
117
|
+
for data in indices
|
118
|
+
idx, options = *data
|
119
|
+
idx = idx.to_s
|
120
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
121
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
122
|
+
sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
begin
|
127
|
+
conn.store.query(sql).close
|
128
|
+
Logger.info "Created table '#{klass::DBTABLE}'."
|
129
|
+
rescue Exception => ex
|
130
|
+
# gmosx: any idea how to better test this?
|
131
|
+
if ex.to_s =~ /table .* already exists/i
|
132
|
+
Logger.debug "Table already exists" if $DBG
|
133
|
+
return
|
134
|
+
else
|
135
|
+
raise
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Create join tables if needed. Join tables are used in
|
140
|
+
# 'many_to_many' relations.
|
141
|
+
|
142
|
+
if klass.__meta and joins = klass.__meta[:sql_join]
|
143
|
+
for data in joins
|
144
|
+
# the class to join to and some options.
|
145
|
+
join_class, options = *data
|
146
|
+
|
147
|
+
# gmosx: dont use DBTABLE here, perhaps the join class
|
148
|
+
# is not managed yet.
|
149
|
+
join_table = "#{self.class.join_table(klass, join_class)}"
|
150
|
+
join_src = "#{self.class.encode(klass)}_oid"
|
151
|
+
join_dst = "#{self.class.encode(join_class)}_oid"
|
152
|
+
begin
|
153
|
+
conn.store.query("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").close
|
154
|
+
conn.store.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
|
155
|
+
conn.store.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
|
156
|
+
rescue Exception => ex
|
157
|
+
# gmosx: any idea how to better test this?
|
158
|
+
if ex.to_s =~ /table .* already exists/i
|
159
|
+
Logger.debug "Join table already exists" if $DBG
|
160
|
+
else
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
ensure
|
168
|
+
db.put_connection
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
# The SQLite connection.
|
174
|
+
|
175
|
+
class SqliteConnection < Connection
|
176
|
+
|
177
|
+
def initialize(db)
|
178
|
+
@store = SQLite3::Database.new("#{db.config[:database]}.db")
|
179
|
+
super
|
180
|
+
end
|
181
|
+
|
182
|
+
def close
|
183
|
+
@store.close
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
187
|
+
def prepare(sql)
|
188
|
+
@store.prepare(sql)
|
189
|
+
end
|
190
|
+
|
191
|
+
def query(sql)
|
192
|
+
Logger.debug sql if $DBG
|
193
|
+
begin
|
194
|
+
return @store.query(sql)
|
195
|
+
rescue => ex
|
196
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
197
|
+
Logger.error ex.backtrace.join("\n")
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def exec(sql)
|
203
|
+
Logger.debug sql if $DBG
|
204
|
+
begin
|
205
|
+
@store.query(sql).close
|
206
|
+
rescue => ex
|
207
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
208
|
+
Logger.error ex.backtrace.join("\n")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def start
|
213
|
+
@store.transaction
|
214
|
+
end
|
215
|
+
|
216
|
+
def commit
|
217
|
+
@store.commit
|
218
|
+
end
|
219
|
+
|
220
|
+
def rollback
|
221
|
+
@store.rollback
|
222
|
+
end
|
223
|
+
|
224
|
+
def valid_res?(res)
|
225
|
+
return !(res.nil?)
|
226
|
+
end
|
227
|
+
|
228
|
+
def read_one(res, klass)
|
229
|
+
return nil unless valid_res?(res)
|
230
|
+
row = res.next
|
231
|
+
return nil unless row
|
232
|
+
|
233
|
+
obj = klass.new
|
234
|
+
obj.og_read(row)
|
235
|
+
|
236
|
+
res.close
|
237
|
+
return obj
|
238
|
+
end
|
239
|
+
|
240
|
+
def read_all(res, klass)
|
241
|
+
return [] unless valid_res?(res)
|
242
|
+
objects = []
|
243
|
+
|
244
|
+
res.each do |row|
|
245
|
+
obj = klass.new
|
246
|
+
obj.og_read(row)
|
247
|
+
objects << obj
|
248
|
+
end
|
249
|
+
|
250
|
+
res.close
|
251
|
+
return objects
|
252
|
+
end
|
253
|
+
|
254
|
+
def read_int(res, idx = 0)
|
255
|
+
val = res.next[idx].to_i
|
256
|
+
res.close
|
257
|
+
return val
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
data/lib/og/backend.rb
CHANGED
data/lib/og/connection.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# * George Moschovitis <gm@navel.gr>
|
2
2
|
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
-
# $Id: connection.rb
|
3
|
+
# $Id: connection.rb 254 2005-02-10 12:44:05Z gmosx $
|
4
4
|
|
5
5
|
class Og;
|
6
6
|
|
@@ -12,41 +12,138 @@ require 'glue/time'
|
|
12
12
|
# functionality. A backend specific implementation file (driver)
|
13
13
|
# implements all methods.
|
14
14
|
#
|
15
|
-
#
|
15
|
+
# == Future
|
16
16
|
#
|
17
17
|
# - support caching.
|
18
18
|
# - support prepared statements.
|
19
19
|
|
20
20
|
class Connection
|
21
|
-
|
22
|
-
|
23
|
-
attr_reader :og
|
24
|
-
|
25
|
-
# The backend
|
21
|
+
|
22
|
+
# The Og database object.
|
26
23
|
|
27
24
|
attr_reader :db
|
28
25
|
|
29
|
-
#
|
30
|
-
|
26
|
+
# The actual connection to the backend store.
|
27
|
+
|
28
|
+
attr_accessor :store
|
29
|
+
|
30
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
31
|
+
# :section: Backend connection methods.
|
32
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
31
33
|
|
32
|
-
attr_accessor :deserialize
|
33
|
-
|
34
34
|
# Initialize a connection to the database.
|
35
35
|
|
36
|
-
def initialize(
|
37
|
-
@
|
38
|
-
@db = @og.config[:backend].new(@og.config)
|
39
|
-
@deserialize = true
|
36
|
+
def initialize(db)
|
37
|
+
@db = db
|
40
38
|
Logger.debug "Created DB connection." if $DBG
|
41
39
|
end
|
42
40
|
|
43
41
|
# Close the connection to the database.
|
44
42
|
|
45
|
-
def close
|
46
|
-
@
|
43
|
+
def close
|
44
|
+
@store.close
|
47
45
|
Logger.debug "Closed DB connection." if $DBG
|
48
46
|
end
|
49
47
|
|
48
|
+
# Create the managed object table. The properties of the
|
49
|
+
# object are mapped to the table columns. Additional sql relations
|
50
|
+
# and constrains are created (indicices, sequences, etc).
|
51
|
+
|
52
|
+
def create_table(klass)
|
53
|
+
raise 'Not implemented!'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Drop the managed object table.
|
57
|
+
|
58
|
+
def drop_table(klass)
|
59
|
+
exec "DROP TABLE #{klass::DBTABLE}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Prepare an sql statement.
|
63
|
+
|
64
|
+
def prepare(sql)
|
65
|
+
raise 'Not implemented!'
|
66
|
+
end
|
67
|
+
|
68
|
+
# Execute an SQL query and return the result.
|
69
|
+
|
70
|
+
def query(sql)
|
71
|
+
raise 'Not implemented!'
|
72
|
+
end
|
73
|
+
|
74
|
+
# Execute an SQL query, no result returned.
|
75
|
+
|
76
|
+
def exec(sql)
|
77
|
+
raise 'Not implemented!'
|
78
|
+
end
|
79
|
+
alias_method :execute, :exec
|
80
|
+
|
81
|
+
# Start a new transaction.
|
82
|
+
|
83
|
+
def start
|
84
|
+
exec 'START TRANSACTION'
|
85
|
+
end
|
86
|
+
|
87
|
+
# Commit a transaction.
|
88
|
+
|
89
|
+
def commit
|
90
|
+
exec 'COMMIT'
|
91
|
+
end
|
92
|
+
|
93
|
+
# Rollback a transaction.
|
94
|
+
|
95
|
+
def rollback
|
96
|
+
exec 'ROLLBACK'
|
97
|
+
end
|
98
|
+
|
99
|
+
# Transaction helper. In the transaction block use
|
100
|
+
# the db pointer to the backend.
|
101
|
+
|
102
|
+
def transaction(&block)
|
103
|
+
begin
|
104
|
+
start
|
105
|
+
yield(self)
|
106
|
+
commit
|
107
|
+
rescue => ex
|
108
|
+
Logger.error "DB Error: ERROR IN TRANSACTION"
|
109
|
+
Logger.error "#{ex}"
|
110
|
+
Logger.error "#{ex.backtrace}"
|
111
|
+
rollback
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
116
|
+
# :section: Deserialization methods.
|
117
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
118
|
+
|
119
|
+
# Is the given resultset valid?
|
120
|
+
|
121
|
+
def valid_res?(res)
|
122
|
+
return !(res.nil?)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Read (deserialize) one row of the resultset.
|
126
|
+
|
127
|
+
def read_one(res, klass)
|
128
|
+
raise 'Not implemented!'
|
129
|
+
end
|
130
|
+
|
131
|
+
# Read (deserialize) all rows of the resultset.
|
132
|
+
|
133
|
+
def read_all(res, klass)
|
134
|
+
raise 'Not implemented!'
|
135
|
+
end
|
136
|
+
|
137
|
+
# Read the first column of the resultset as an Integer.
|
138
|
+
|
139
|
+
def read_int(res, idx = 0)
|
140
|
+
raise 'Not implemented!'
|
141
|
+
end
|
142
|
+
|
143
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
144
|
+
# :section: Managed object methods.
|
145
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
146
|
+
|
50
147
|
# Save an object to the database. Insert if this is a new object or
|
51
148
|
# update if this is already stored in the database.
|
52
149
|
|
@@ -110,7 +207,7 @@ class Connection
|
|
110
207
|
|
111
208
|
def load_by_oid(oid, klass)
|
112
209
|
res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}"
|
113
|
-
|
210
|
+
read_one(res, klass)
|
114
211
|
end
|
115
212
|
alias_method :get_by_oid, :load_by_oid
|
116
213
|
|
@@ -118,7 +215,7 @@ class Connection
|
|
118
215
|
|
119
216
|
def load_by_name(name, klass)
|
120
217
|
res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'"
|
121
|
-
|
218
|
+
read_one(res, klass)
|
122
219
|
end
|
123
220
|
alias_method :get_by_name, :load_by_name
|
124
221
|
|
@@ -127,7 +224,7 @@ class Connection
|
|
127
224
|
|
128
225
|
def load_all(klass, extrasql = nil)
|
129
226
|
res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}"
|
130
|
-
|
227
|
+
read_all(res, klass)
|
131
228
|
end
|
132
229
|
alias_method :get_all, :load_all
|
133
230
|
|
@@ -139,8 +236,8 @@ class Connection
|
|
139
236
|
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
140
237
|
end
|
141
238
|
|
142
|
-
res =
|
143
|
-
|
239
|
+
res = query(sql)
|
240
|
+
read_all(res, klass)
|
144
241
|
end
|
145
242
|
|
146
243
|
# Optimized for one result.
|
@@ -150,8 +247,8 @@ class Connection
|
|
150
247
|
sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
|
151
248
|
end
|
152
249
|
|
153
|
-
res =
|
154
|
-
|
250
|
+
res = query(sql)
|
251
|
+
read_one(res, klass)
|
155
252
|
end
|
156
253
|
|
157
254
|
# Perform a count query.
|
@@ -161,9 +258,8 @@ class Connection
|
|
161
258
|
sql = "SELECT COUNT(*) FROM #{klass::DBTABLE} WHERE #{sql}"
|
162
259
|
end
|
163
260
|
|
164
|
-
res =
|
165
|
-
|
166
|
-
return @db.get_int(res)
|
261
|
+
res = query(sql)
|
262
|
+
return read_int(res)
|
167
263
|
end
|
168
264
|
|
169
265
|
# Delete an object from the database. Allways perform a deep delete.
|
@@ -199,66 +295,6 @@ class Connection
|
|
199
295
|
end
|
200
296
|
alias_method :delete!, :delete
|
201
297
|
|
202
|
-
# Create the managed object table. The properties of the
|
203
|
-
# object are mapped to the table columns. Additional sql relations
|
204
|
-
# and constrains are created (indicices, sequences, etc).
|
205
|
-
|
206
|
-
def create_table(klass)
|
207
|
-
@db.create_table(klass)
|
208
|
-
end
|
209
|
-
|
210
|
-
# Drop the managed object table.
|
211
|
-
|
212
|
-
def drop_table(klass)
|
213
|
-
@db.drop_table(klass)
|
214
|
-
end
|
215
|
-
|
216
|
-
# Execute an SQL query and return the result
|
217
|
-
|
218
|
-
def query(sql)
|
219
|
-
@db.safe_query(sql)
|
220
|
-
end
|
221
|
-
|
222
|
-
# Execute an SQL query, no result returned.
|
223
|
-
|
224
|
-
def exec(sql)
|
225
|
-
@db.safe_exec(sql)
|
226
|
-
end
|
227
|
-
|
228
|
-
# Start a new transaction.
|
229
|
-
|
230
|
-
def start
|
231
|
-
@db.start
|
232
|
-
end
|
233
|
-
|
234
|
-
# Commit a transaction.
|
235
|
-
|
236
|
-
def commit
|
237
|
-
@db.commit
|
238
|
-
end
|
239
|
-
|
240
|
-
# Rollback transaction.
|
241
|
-
|
242
|
-
def rollback
|
243
|
-
@db.rollback
|
244
|
-
end
|
245
|
-
|
246
|
-
# Transaction helper. In the transaction block use
|
247
|
-
# the db pointer to the backend.
|
248
|
-
|
249
|
-
def transaction(&block)
|
250
|
-
begin
|
251
|
-
@db.start
|
252
|
-
yield(@db)
|
253
|
-
@db.commit
|
254
|
-
rescue => ex
|
255
|
-
Logger.error "DB Error: ERROR IN TRANSACTION"
|
256
|
-
Logger.error #{ex}
|
257
|
-
Logger.error #{ex.backtrace}
|
258
|
-
@db.rollback
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
298
|
end
|
263
299
|
|
264
300
|
end
|