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.
@@ -0,0 +1,304 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: mysql.rb 259 2005-02-15 08:54:54Z gmosx $
4
+
5
+ require 'mysql'
6
+
7
+ require 'og/adapter'
8
+ require 'og/connection'
9
+ require 'glue/attribute'
10
+
11
+ class Og
12
+
13
+ # The MySQL adapter. This adapter communicates with
14
+ # an MySQL rdbms. For extra documentation see
15
+ # lib/og/adapter.rb
16
+
17
+ class MysqlAdapter < Adapter
18
+
19
+ def initialize
20
+ super
21
+ @typemap.update({TrueClass => 'tinyint'})
22
+ end
23
+
24
+ def self.escape(str)
25
+ return nil unless str
26
+ return Mysql.quote(str)
27
+ end
28
+
29
+ def self.timestamp(time = Time.now)
30
+ return nil unless time
31
+ return time.strftime("%Y%m%d%H%M%S")
32
+ end
33
+
34
+ def self.date(date)
35
+ return nil unless date
36
+ return "#{date.year}-#{date.month}-#{date.mday}"
37
+ end
38
+
39
+ def write_prop(p)
40
+ if p.klass.ancestors.include?(Integer)
41
+ return "#\{@#{p.symbol} || 'NULL'\}"
42
+ elsif p.klass.ancestors.include?(Float)
43
+ return "#\{@#{p.symbol} || 'NULL'\}"
44
+ elsif p.klass.ancestors.include?(String)
45
+ return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
46
+ elsif p.klass.ancestors.include?(Time)
47
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
48
+ elsif p.klass.ancestors.include?(Date)
49
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
50
+ elsif p.klass.ancestors.include?(TrueClass)
51
+ return "#\{@#{p.symbol} ? 1 : 0 \}"
52
+ else
53
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
54
+ end
55
+ end
56
+
57
+ def read_prop(p, idx)
58
+ if p.klass.ancestors.include?(Integer)
59
+ return "res[#{idx}].to_i"
60
+ elsif p.klass.ancestors.include?(Float)
61
+ return "res[#{idx}].to_f"
62
+ elsif p.klass.ancestors.include?(String)
63
+ return "res[#{idx}]"
64
+ elsif p.klass.ancestors.include?(Time)
65
+ return "#{self.class}.parse_timestamp(res[#{idx}])"
66
+ elsif p.klass.ancestors.include?(Date)
67
+ return "#{self.class}.parse_date(res[#{idx}])"
68
+ elsif p.klass.ancestors.include?(TrueClass)
69
+ return "('0' != res[#{idx}])"
70
+ else
71
+ return "YAML::load(res[#{idx}])"
72
+ end
73
+ end
74
+
75
+ def create_db(database, user = nil, password = nil)
76
+ `mysqladmin -f --user=#{user} --password=#{password} create #{database}`
77
+ super
78
+ end
79
+
80
+ def drop_db(database, user = nil, password = nil)
81
+ `mysqladmin -f --user=#{user} --password=#{password} drop #{database}`
82
+ super
83
+ end
84
+
85
+ def insert_code(klass, db, pre_cb, post_cb)
86
+ props = props_for_insert(klass)
87
+ values = props.collect { |p| write_prop(p) }.join(',')
88
+
89
+ sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
90
+
91
+ %{
92
+ #{pre_cb}
93
+ conn.store.query_with_result = false
94
+ conn.store.query "#{sql}"
95
+ @oid = conn.store.insert_id()
96
+ #{post_cb}
97
+ }
98
+ end
99
+
100
+ def new_connection(db)
101
+ return Og::MysqlConnection.new(db)
102
+ end
103
+
104
+ def calc_field_index(klass, db)
105
+ res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
106
+ meta = db.managed_classes[klass]
107
+
108
+ for idx in (0...res.num_fields)
109
+ meta.field_index[res.fetch_field.name] = idx
110
+ end
111
+
112
+ ensure
113
+ res.free if res
114
+ end
115
+
116
+ def create_table(klass, db)
117
+ conn = db.get_connection
118
+
119
+ fields = create_fields(klass)
120
+
121
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
122
+
123
+ conn.store.query_with_result = true
124
+
125
+ # Create table constrains
126
+
127
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
128
+ sql << ", #{constrains.join(', ')}"
129
+ end
130
+
131
+ sql << ');'
132
+
133
+ begin
134
+ conn.store.query(sql)
135
+ Logger.info "Created table '#{klass::DBTABLE}'."
136
+ rescue => ex
137
+ if ex.errno == 1050 # table already exists.
138
+ Logger.debug "Table already exists" if $DBG
139
+ return
140
+ else
141
+ raise
142
+ end
143
+ end
144
+
145
+ # Create indices
146
+
147
+ if klass.__meta and indices = klass.__meta[:sql_index]
148
+ for data in indices
149
+ idx, options = *data
150
+ idx = idx.to_s
151
+ pre_sql, post_sql = options[:pre], options[:post]
152
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
153
+ conn.store.query("CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx})")
154
+ end
155
+ end
156
+
157
+ # Create join tables if needed. Join tables are used in
158
+ # 'many_to_many' relations.
159
+
160
+ if klass.__meta and joins = klass.__meta[:sql_join]
161
+ for data in joins
162
+ # the class to join to and some options.
163
+ join_class, options = *data
164
+
165
+ # gmosx: dont use DBTABLE here, perhaps the join class
166
+ # is not managed yet.
167
+ join_table = "#{self.class.join_table(klass, join_class)}"
168
+ join_src = "#{self.class.encode(klass)}_oid"
169
+ join_dst = "#{self.class.encode(join_class)}_oid"
170
+ begin
171
+ conn.store.query("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )")
172
+ conn.store.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)")
173
+ conn.store.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)")
174
+ rescue => ex
175
+ if ex.errno == 1050 # table already exists.
176
+ Logger.debug "Join table already exists" if $DBG
177
+ else
178
+ raise
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ ensure
185
+ db.put_connection
186
+ end
187
+
188
+ def eval_og_oid(klass)
189
+ klass.class_eval %{
190
+ prop_accessor :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
191
+ }
192
+ end
193
+ end
194
+
195
+ # The MySQL connection.
196
+
197
+ class MysqlConnection < Connection
198
+
199
+ def initialize(db)
200
+ super
201
+
202
+ config = db.config
203
+
204
+ @store = Mysql.connect(
205
+ config[:address] || 'localhost',
206
+ config[:user],
207
+ config[:password],
208
+ config[:database]
209
+ )
210
+ rescue => ex
211
+ if ex.errno == 1049 # database does not exist.
212
+ Logger.info "Database '#{config[:database]}' not found!"
213
+ @db.adapter.create_db(config[:database], config[:user], config[:password])
214
+ retry
215
+ end
216
+ raise
217
+ end
218
+
219
+ def close
220
+ @store.close
221
+ super
222
+ end
223
+
224
+ def prepare(sql)
225
+ raise 'Not implemented!'
226
+ end
227
+
228
+ def query(sql)
229
+ Logger.debug sql if $DBG
230
+ begin
231
+ @store.query_with_result = true
232
+ return @store.query(sql)
233
+ rescue => ex
234
+ Logger.error "DB error #{ex}, [#{sql}]"
235
+ Logger.error ex.backtrace.join("\n")
236
+ return nil
237
+ end
238
+ end
239
+
240
+ def exec(sql)
241
+ Logger.debug sql if $DBG
242
+ begin
243
+ @store.query_with_result = false
244
+ @store.query(sql)
245
+ rescue => ex
246
+ Logger.error "DB error #{ex}, [#{sql}]"
247
+ Logger.error ex.backtrace.join("\n")
248
+ end
249
+ end
250
+
251
+ def start
252
+ # @store.transaction
253
+ end
254
+
255
+ def commit
256
+ # @store.commit
257
+ end
258
+
259
+ def rollback
260
+ # @store.rollback
261
+ end
262
+
263
+ def valid_res?(res)
264
+ return !(res.nil? or 0 == res.num_rows)
265
+ end
266
+
267
+ def read_one(res, klass)
268
+ return nil unless valid_res?(res)
269
+
270
+ row = res.fetch_row
271
+ obj = klass.new
272
+ obj.og_read(row)
273
+
274
+ res.free
275
+ return obj
276
+ end
277
+
278
+ def read_all(res, klass)
279
+ return [] unless valid_res?(res)
280
+
281
+ objects = []
282
+
283
+ for tuple in (0...res.num_rows)
284
+ row = res.fetch_row
285
+
286
+ obj = klass.new
287
+ obj.og_read(row)
288
+
289
+ objects << obj
290
+ end
291
+
292
+ res.free
293
+ return objects
294
+ end
295
+
296
+ def read_int(res, idx = 0)
297
+ val = res.fetch_row[idx].to_i
298
+ res.free
299
+ return val
300
+ end
301
+
302
+ end
303
+
304
+ end
@@ -0,0 +1,286 @@
1
+ # * George Moschovitis <gm@navel.gr>
2
+ # (c) 2004-2005 Navel, all rights reserved.
3
+ # $Id: psql.rb 259 2005-02-15 08:54:54Z gmosx $
4
+
5
+ require 'postgres'
6
+
7
+ require 'og/adapter'
8
+ require 'og/connection'
9
+ require 'glue/attribute'
10
+
11
+ class Og
12
+
13
+ # The PostgreSQL adapter. This adapter communicates with
14
+ # an PostgreSQL rdbms. For extra documentation see
15
+ # lib/og/adapter.rb
16
+
17
+ class PsqlAdapter < Adapter
18
+
19
+ def self.escape(str)
20
+ return nil unless str
21
+ return PGconn.escape(str)
22
+ end
23
+
24
+ def self.timestamp(time = Time.now)
25
+ return nil unless time
26
+ return time.strftime("%Y-%m-%d %H:%M:%S")
27
+ end
28
+
29
+ def self.date(date)
30
+ return nil unless date
31
+ return "#{date.year}-#{date.month}-#{date.mday}"
32
+ end
33
+
34
+ def write_prop(p)
35
+ if p.klass.ancestors.include?(Integer)
36
+ return "#\{@#{p.symbol} || 'NULL'\}"
37
+ elsif p.klass.ancestors.include?(Float)
38
+ return "#\{@#{p.symbol} || 'NULL'\}"
39
+ elsif p.klass.ancestors.include?(String)
40
+ return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
41
+ elsif p.klass.ancestors.include?(Time)
42
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
43
+ elsif p.klass.ancestors.include?(Date)
44
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
45
+ elsif p.klass.ancestors.include?(TrueClass)
46
+ return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
47
+ else
48
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
49
+ end
50
+ end
51
+
52
+ def read_prop(p, idx)
53
+ if p.klass.ancestors.include?(Integer)
54
+ return "res.getvalue(tuple, #{idx}).to_i()"
55
+ elsif p.klass.ancestors.include?(Float)
56
+ return "res.getvalue(tuple, #{idx}).to_f()"
57
+ elsif p.klass.ancestors.include?(String)
58
+ return "res.getvalue(tuple, #{idx})"
59
+ elsif p.klass.ancestors.include?(Time)
60
+ return "#{self.class}.parse_timestamp(res.getvalue(tuple, #{idx}))"
61
+ elsif p.klass.ancestors.include?(Date)
62
+ return "#{self.class}.parse_date(res.getvalue(tuple, #{idx}))"
63
+ elsif p.klass.ancestors.include?(TrueClass)
64
+ return %|('t' == res.getvalue(tuple, #{idx}))|
65
+ else
66
+ return "YAML::load(res.getvalue(tuple, #{idx}))"
67
+ end
68
+ end
69
+
70
+ def create_db(database, user = nil, password = nil)
71
+ `createdb #{database} -U #{user}`
72
+ super
73
+ end
74
+
75
+ def drop_db(database, user = nil, password = nil)
76
+ `dropdb #{database} -U #{user}`
77
+ super
78
+ end
79
+
80
+ def insert_code(klass, db, pre_cb, post_cb)
81
+ props = props_for_insert(klass)
82
+ values = props.collect { |p| write_prop(p) }.join(',')
83
+
84
+ sql = "INSERT INTO #{klass::DBTABLE} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values})"
85
+
86
+ %{
87
+ #{pre_cb}
88
+ res = conn.store.exec("SELECT nextval('#{klass::DBSEQ}')")
89
+ @oid = res.getvalue(0, 0).to_i
90
+ res.clear
91
+ conn.exec "#{sql}"
92
+ #{post_cb}
93
+ }
94
+ end
95
+
96
+ def new_connection(db)
97
+ return Og::PsqlConnection.new(db)
98
+ end
99
+
100
+ def calc_field_index(klass, db)
101
+ res = db.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
102
+ meta = db.managed_classes[klass]
103
+
104
+ for field in res.fields
105
+ meta.field_index[field] = res.fieldnum(field)
106
+ end
107
+
108
+ ensure
109
+ res.clear if res
110
+ end
111
+
112
+ def create_table(klass, db)
113
+ conn = db.get_connection
114
+
115
+ fields = create_fields(klass)
116
+
117
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
118
+
119
+ # Create table constrains
120
+
121
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
122
+ sql << ", #{constrains.join(', ')}"
123
+ end
124
+
125
+ sql << ") WITHOUT OIDS;"
126
+
127
+ # Create indices
128
+
129
+ if klass.__meta and indices = klass.__meta[:sql_index]
130
+ for data in indices
131
+ idx, options = *data
132
+ idx = idx.to_s
133
+ pre_sql, post_sql = options[:pre], options[:post]
134
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
135
+ sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
136
+ end
137
+ end
138
+
139
+ begin
140
+ conn.store.exec(sql).clear
141
+ Logger.info "Created table '#{klass::DBTABLE}'."
142
+ rescue => ex
143
+ # gmosx: any idea how to better test this?
144
+ if ex.to_s =~ /relation .* already exists/i
145
+ Logger.debug 'Table already exists' if $DBG
146
+ return
147
+ else
148
+ raise
149
+ end
150
+ end
151
+
152
+ # Create join tables if needed. Join tables are used in
153
+ # 'many_to_many' relations.
154
+
155
+ if klass.__meta and joins = klass.__meta[:sql_join]
156
+ for data in joins
157
+ # the class to join to and some options.
158
+ join_class, options = *data
159
+
160
+ # gmosx: dont use DBTABLE here, perhaps the join class
161
+ # is not managed yet.
162
+ join_table = "#{self.class.join_table(klass, join_class)}"
163
+ join_src = "#{self.class.encode(klass)}_oid"
164
+ join_dst = "#{self.class.encode(join_class)}_oid"
165
+ begin
166
+ conn.store.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").clear
167
+ conn.store.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").clear
168
+ conn.store.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").clear
169
+ rescue => ex
170
+ # gmosx: any idea how to better test this?
171
+ if ex.to_s =~ /relation .* already exists/i
172
+ Logger.debug "Join table already exists" if $DBG
173
+ else
174
+ raise
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ ensure
181
+ db.put_connection
182
+ end
183
+
184
+ def drop_table(klass)
185
+ super
186
+ exec "DROP SEQUENCE #{klass::DBSEQ}"
187
+ end
188
+
189
+ # Generate the property for oid.
190
+
191
+ def eval_og_oid(klass)
192
+ klass.class_eval %{
193
+ prop_accessor :oid, Fixnum, :sql => 'serial PRIMARY KEY'
194
+ }
195
+ end
196
+
197
+ end
198
+
199
+ # The PostgreSQL connection.
200
+
201
+ class PsqlConnection < Connection
202
+
203
+ def initialize(db)
204
+ super
205
+
206
+ config = db.config
207
+
208
+ begin
209
+ @store = PGconn.connect(
210
+ config[:address],
211
+ config[:port],
212
+ nil,
213
+ nil,
214
+ config[:database],
215
+ config[:user].to_s,
216
+ config[:password].to_s
217
+ )
218
+ rescue => ex
219
+ # gmosx: any idea how to better test this?
220
+ if ex.to_s =~ /database .* does not exist/i
221
+ Logger.info "Database '#{config[:database]}' not found!"
222
+ @db.adapter.create_db(config[:database], config[:user])
223
+ retry
224
+ end
225
+ raise
226
+ end
227
+ end
228
+
229
+ def query(sql)
230
+ Logger.debug sql if $DBG
231
+ begin
232
+ return @store.exec(sql)
233
+ rescue => ex
234
+ Logger.error "DB error #{ex}, [#{sql}]"
235
+ Logger.error ex.backtrace.join("\n")
236
+ return nil
237
+ end
238
+ end
239
+
240
+ def exec(sql)
241
+ Logger.debug sql if $DBG
242
+ begin
243
+ @store.exec(sql).clear
244
+ rescue => ex
245
+ Logger.error "DB error #{ex}, [#{sql}]"
246
+ Logger.error ex.backtrace.join("\n")
247
+ end
248
+ end
249
+
250
+ def valid_res?(res)
251
+ return !(res.nil? or 0 == res.num_tuples)
252
+ end
253
+
254
+ def read_one(res, klass)
255
+ return nil unless valid_res?(res)
256
+
257
+ obj = klass.new
258
+ obj.og_read(res, 0)
259
+
260
+ res.clear
261
+ return obj
262
+ end
263
+
264
+ def read_all(res, klass)
265
+ return [] unless valid_res?(res)
266
+ objects = []
267
+
268
+ for tuple in (0...res.num_tuples)
269
+ obj = klass.new
270
+ obj.og_read(res, tuple)
271
+ objects << obj
272
+ end
273
+
274
+ res.clear
275
+ return objects
276
+ end
277
+
278
+ def read_int(res, idx = 0)
279
+ val = res.getvalue(0, idx).to_i
280
+ res.clear
281
+ return val
282
+ end
283
+
284
+ end
285
+
286
+ end