nitro 0.9.3 → 0.9.5
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 +64 -0
- data/RELEASES +13 -0
- data/examples/blog/README +1 -1
- data/examples/blog/conf/app.conf.rb +2 -6
- data/examples/blog/conf/lhttpd.conf +1 -1
- data/examples/blog/lib/blog/controller.rb +2 -2
- data/examples/blog/root/style.css +0 -2
- data/examples/flash/conf/app.conf.rb +2 -4
- data/examples/no_xsl_blog/README +9 -0
- data/examples/no_xsl_blog/conf/app.conf.rb +5 -8
- data/examples/no_xsl_blog/conf/lhttpd.conf +1 -1
- data/examples/no_xsl_blog/lib/blog/controller.rb +2 -3
- data/examples/no_xsl_blog/lib/blog/template.rb +3 -3
- data/examples/no_xsl_blog/root/style.css +0 -2
- data/examples/og/mock_example.rb +1 -4
- data/examples/og/mysql_to_psql.rb +2 -4
- data/examples/tiny/README +1 -1
- data/examples/tiny/conf/app.conf.rb +2 -6
- data/examples/tiny/conf/lhttpd.conf +1 -1
- data/examples/wee_style/README +10 -0
- data/examples/wee_style/wee.rb +50 -0
- data/lib/glue/cache.rb +32 -34
- data/lib/glue/number.rb +3 -9
- data/lib/glue/time.rb +14 -22
- data/lib/glue/validation.rb +2 -4
- data/lib/nitro.rb +1 -3
- data/lib/nitro/adaptors/fastcgi.rb +10 -0
- data/lib/nitro/adaptors/webrick.rb +27 -11
- data/lib/nitro/builders/rss.rb +31 -11
- data/lib/nitro/builders/xhtml.rb +2 -8
- data/lib/nitro/context.rb +3 -1
- data/lib/nitro/dispatcher.rb +21 -4
- data/lib/nitro/filters.rb +12 -12
- data/lib/nitro/version.rb +1 -1
- data/lib/og/backend.rb +36 -40
- data/lib/og/backends/psql.rb +7 -7
- data/lib/og/backends/sqlite.rb +383 -0
- data/lib/og/connection.rb +34 -34
- data/lib/og/meta.rb +8 -0
- data/lib/og/version.rb +2 -4
- data/test/nitro/builders/tc_rss.rb +22 -0
- data/test/nitro/tc_dispatcher.rb +6 -1
- metadata +7 -2
data/lib/nitro/version.rb
CHANGED
data/lib/og/backend.rb
CHANGED
@@ -1,73 +1,69 @@
|
|
1
|
-
# code:
|
2
1
|
# * George Moschovitis <gm@navel.gr>
|
3
|
-
#
|
4
|
-
# (c) 2004 Navel, all rights reserved.
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
5
3
|
# $Id: backend.rb 202 2005-01-17 10:44:13Z gmosx $
|
6
4
|
|
7
|
-
require
|
5
|
+
require 'yaml'
|
8
6
|
|
9
|
-
require
|
7
|
+
require 'og/connection'
|
10
8
|
|
11
9
|
class Og
|
12
10
|
|
13
|
-
# = Backend
|
14
|
-
#
|
15
11
|
# Abstract backend. A backend communicates with the RDBMS.
|
16
12
|
# This is the base class for the various backend implementations.
|
17
|
-
|
13
|
+
|
18
14
|
class Backend
|
19
15
|
|
20
16
|
# The actual connection to the database
|
21
17
|
attr_accessor :conn
|
22
18
|
|
23
19
|
# Intitialize the connection to the RDBMS.
|
24
|
-
|
20
|
+
|
25
21
|
def initialize(config)
|
26
22
|
raise "Not implemented"
|
27
23
|
end
|
28
24
|
|
29
25
|
# Close the connection to the RDBMS.
|
30
|
-
|
26
|
+
|
31
27
|
def close()
|
32
28
|
@conn.close()
|
33
29
|
end
|
34
30
|
|
35
|
-
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
36
|
-
# Utilities
|
31
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
32
|
+
# :section: Utilities
|
37
33
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
38
34
|
|
39
35
|
# Encode the name of the klass as an sql safe string.
|
40
36
|
# The Module separators are replaced with _ and NOT stripped
|
41
37
|
# out so that we can convert back to the original notation if
|
42
38
|
# needed. The leading module if available is removed.
|
43
|
-
|
39
|
+
|
44
40
|
def self.encode(klass)
|
45
41
|
"#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
|
46
42
|
end
|
47
43
|
|
48
44
|
# The name of the SQL table where objects of this class
|
49
45
|
# are stored.
|
50
|
-
|
46
|
+
|
51
47
|
def self.table(klass)
|
52
48
|
"_#{Og.table_prefix}#{encode(klass)}"
|
53
49
|
end
|
54
50
|
|
55
51
|
# The name of the join table for the two given classes.
|
56
|
-
|
52
|
+
|
57
53
|
def self.join_table(klass1, klass2)
|
58
54
|
"_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
|
59
55
|
end
|
60
56
|
|
61
57
|
# Returns the props that will be included in the insert query.
|
62
58
|
# For some backends the oid should be stripped.
|
63
|
-
|
59
|
+
|
64
60
|
def self.props_for_insert(klass)
|
65
61
|
klass.__props
|
66
62
|
end
|
67
63
|
|
68
64
|
# Precompile the insert code for the given class.
|
69
65
|
# The generated code sets the oid when inserting!
|
70
|
-
|
66
|
+
|
71
67
|
def self.eval_og_insert(klass)
|
72
68
|
props = props_for_insert(klass)
|
73
69
|
|
@@ -104,7 +100,7 @@ class Backend
|
|
104
100
|
|
105
101
|
# Precompile the update code for the given class.
|
106
102
|
# Ignore the oid when updating!
|
107
|
-
|
103
|
+
|
108
104
|
def self.eval_og_update(klass)
|
109
105
|
props = klass.__props.reject { |p| :oid == p.symbol }
|
110
106
|
|
@@ -146,7 +142,7 @@ class Backend
|
|
146
142
|
# Precompile the code to read objects of the given class
|
147
143
|
# from the backend. In order to allow for changing
|
148
144
|
# field/attribute orders we have to use a field mapping hash.
|
149
|
-
|
145
|
+
|
150
146
|
def self.eval_og_deserialize(klass, og)
|
151
147
|
calc_field_index(klass, og)
|
152
148
|
|
@@ -169,67 +165,67 @@ class Backend
|
|
169
165
|
end
|
170
166
|
|
171
167
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
172
|
-
# Connection methods.
|
168
|
+
# :section: Connection methods.
|
173
169
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
174
170
|
|
175
171
|
# Create the database.
|
176
|
-
|
172
|
+
|
177
173
|
def self.create_db(database, user = nil, password = nil)
|
178
174
|
Logger.info "Creating database '#{database}'."
|
179
175
|
end
|
180
176
|
|
181
177
|
# Drop the database.
|
182
|
-
|
178
|
+
|
183
179
|
def self.drop_db(database, user = nil, password = nil)
|
184
180
|
Logger.info "Dropping database '#{database}'."
|
185
181
|
end
|
186
182
|
|
187
183
|
# Execute an SQL query and return the result.
|
188
|
-
|
184
|
+
|
189
185
|
def query(sql)
|
190
186
|
raise "Not implemented"
|
191
187
|
end
|
192
188
|
|
193
189
|
# Execute an SQL query, no result returned.
|
194
|
-
|
190
|
+
|
195
191
|
def exec(sql)
|
196
192
|
raise "Not implemented"
|
197
193
|
end
|
198
194
|
|
199
195
|
# Execute an SQL query and return the result. Wrapped in a
|
200
196
|
# rescue block.
|
201
|
-
|
197
|
+
|
202
198
|
def safe_query(sql)
|
203
199
|
raise "Not implemented"
|
204
200
|
end
|
205
201
|
|
206
202
|
# Execute an SQL query, no result returned. Wrapped in a
|
207
203
|
# rescue block.
|
208
|
-
|
204
|
+
|
209
205
|
def safe_exec(sql)
|
210
206
|
raise "Not implemented"
|
211
207
|
end
|
212
208
|
|
213
209
|
# Check if it is a valid resultset.
|
214
|
-
|
210
|
+
|
215
211
|
def valid?(res)
|
216
212
|
raise "Not implemented"
|
217
213
|
end
|
218
214
|
|
219
215
|
# Start a new transaction.
|
220
|
-
|
216
|
+
|
221
217
|
def start
|
222
218
|
exec "START TRANSACTION"
|
223
219
|
end
|
224
220
|
|
225
221
|
# Commit a transaction.
|
226
|
-
|
222
|
+
|
227
223
|
def commit
|
228
224
|
exec "COMMIT"
|
229
225
|
end
|
230
226
|
|
231
227
|
# Rollback transaction.
|
232
|
-
|
228
|
+
|
233
229
|
def rollback
|
234
230
|
exec "ROLLBACK"
|
235
231
|
end
|
@@ -239,7 +235,7 @@ class Backend
|
|
239
235
|
# If the property has an :sql metadata this overrides the
|
240
236
|
# default mapping. If the property has an :extra_sql metadata
|
241
237
|
# the extra sql is appended after the default mapping.
|
242
|
-
|
238
|
+
|
243
239
|
def create_fields(klass, typemap)
|
244
240
|
fields = []
|
245
241
|
|
@@ -267,35 +263,35 @@ class Backend
|
|
267
263
|
# Create the managed object table. The properties of the
|
268
264
|
# object are mapped to the table columns. Additional sql relations
|
269
265
|
# and constrains are created (indicices, sequences, etc).
|
270
|
-
|
266
|
+
|
271
267
|
def create_table(klass)
|
272
268
|
return if query("SELECT * FROM #{klass::DBTABLE} LIMIT 1")
|
273
269
|
end
|
274
270
|
|
275
271
|
# Drop the managed object table
|
276
|
-
|
272
|
+
|
277
273
|
def drop_table(klass)
|
278
274
|
exec "DROP TABLE #{klass::DBTABLE}"
|
279
275
|
end
|
280
276
|
|
281
277
|
# Deserialize one row of the resultset.
|
282
|
-
|
278
|
+
|
283
279
|
def deserialize_one(res, klass)
|
284
|
-
raise
|
280
|
+
raise 'Not implemented'
|
285
281
|
end
|
286
282
|
|
287
283
|
# Deserialize all rows of the resultset.
|
288
|
-
|
284
|
+
|
289
285
|
def deserialize_all(res, klass)
|
290
|
-
raise
|
286
|
+
raise 'Not implemented'
|
291
287
|
end
|
292
288
|
|
293
289
|
# Return a single integer value from the resultset.
|
294
|
-
|
290
|
+
|
295
291
|
def get_int(res, idx = 0)
|
296
|
-
raise
|
292
|
+
raise 'Not implemented'
|
297
293
|
end
|
298
294
|
|
299
295
|
end
|
300
296
|
|
301
|
-
end
|
297
|
+
end
|
data/lib/og/backends/psql.rb
CHANGED
@@ -12,7 +12,7 @@ class Og
|
|
12
12
|
# This backend is compatible with Michael Neumann's postgres-pr
|
13
13
|
# pure ruby driver.
|
14
14
|
|
15
|
-
class PsqlBackend <
|
15
|
+
class PsqlBackend < Backend
|
16
16
|
|
17
17
|
# A mapping between Ruby and SQL types.
|
18
18
|
|
@@ -101,15 +101,15 @@ class PsqlBackend < Og::Backend
|
|
101
101
|
elsif p.klass.ancestors.include?(Float)
|
102
102
|
return "#\{@#{p.symbol} || 'NULL'\}"
|
103
103
|
elsif p.klass.ancestors.include?(String)
|
104
|
-
return "'#\{
|
104
|
+
return "'#\{PsqlBackend.escape(@#{p.symbol})\}'"
|
105
105
|
elsif p.klass.ancestors.include?(Time)
|
106
|
-
return %|#\{@#{p.symbol} ? "'#\{
|
106
|
+
return %|#\{@#{p.symbol} ? "'#\{PsqlBackend.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
107
107
|
elsif p.klass.ancestors.include?(Date)
|
108
|
-
return %|#\{@#{p.symbol} ? "'#\{
|
108
|
+
return %|#\{@#{p.symbol} ? "'#\{PsqlBackend.date(@#{p.symbol})\}'" : 'NULL'\}|
|
109
109
|
elsif p.klass.ancestors.include?(TrueClass)
|
110
110
|
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
111
111
|
else
|
112
|
-
return %|#\{@#{p.symbol} ? "'#\{
|
112
|
+
return %|#\{@#{p.symbol} ? "'#\{PsqlBackend.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
@@ -124,9 +124,9 @@ class PsqlBackend < Og::Backend
|
|
124
124
|
elsif p.klass.ancestors.include?(String)
|
125
125
|
return "res.getvalue(tuple, #{idx})"
|
126
126
|
elsif p.klass.ancestors.include?(Time)
|
127
|
-
return "
|
127
|
+
return "PsqlBackend.parse_timestamp(res.getvalue(tuple, #{idx}))"
|
128
128
|
elsif p.klass.ancestors.include?(Date)
|
129
|
-
return "
|
129
|
+
return "PsqlBackend.parse_date(res.getvalue(tuple, #{idx}))"
|
130
130
|
elsif p.klass.ancestors.include?(TrueClass)
|
131
131
|
return %|('t' == res.getvalue(tuple, #{idx}))|
|
132
132
|
else
|
@@ -0,0 +1,383 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id$
|
4
|
+
|
5
|
+
require 'sqlite'
|
6
|
+
|
7
|
+
require 'og/backend'
|
8
|
+
|
9
|
+
class Og
|
10
|
+
|
11
|
+
# Implements an SQLite powered backend.
|
12
|
+
|
13
|
+
class SqliteBackend < Backend
|
14
|
+
|
15
|
+
# A mapping between Ruby and SQL types.
|
16
|
+
|
17
|
+
TYPEMAP = {
|
18
|
+
Integer => 'integer',
|
19
|
+
Fixnum => 'integer',
|
20
|
+
Float => 'float',
|
21
|
+
String => 'text',
|
22
|
+
Time => 'timestamp',
|
23
|
+
Date => 'date',
|
24
|
+
TrueClass => 'boolean',
|
25
|
+
Object => 'text',
|
26
|
+
Array => 'text',
|
27
|
+
Hash => 'text'
|
28
|
+
}
|
29
|
+
|
30
|
+
# Intitialize the connection to the RDBMS.
|
31
|
+
|
32
|
+
def initialize(config)
|
33
|
+
begin
|
34
|
+
@conn = SQLite::Database.new(config[:database])
|
35
|
+
rescue => ex
|
36
|
+
# gmosx: any idea how to better test this?
|
37
|
+
if ex.to_s =~ /database .* does not exist/i
|
38
|
+
Logger.info "Database '#{config[:database]}' not found!"
|
39
|
+
SqliteBackend.create_db(config[:database], config[:user])
|
40
|
+
retry
|
41
|
+
end
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
47
|
+
# Utilities
|
48
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
49
|
+
|
50
|
+
# Escape an SQL string
|
51
|
+
|
52
|
+
def self.escape(str)
|
53
|
+
return nil unless str
|
54
|
+
return str.gsub( /'/, "''" )
|
55
|
+
end
|
56
|
+
|
57
|
+
# Convert a ruby time to an sql timestamp.
|
58
|
+
# TODO: Optimize this
|
59
|
+
|
60
|
+
def self.timestamp(time = Time.now)
|
61
|
+
return nil unless time
|
62
|
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Output YYY-mm-dd
|
66
|
+
# TODO: Optimize this
|
67
|
+
|
68
|
+
def self.date(date)
|
69
|
+
return nil unless date
|
70
|
+
return "#{date.year}-#{date.month}-#{date.mday}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Parse sql datetime
|
74
|
+
# TODO: Optimize this
|
75
|
+
|
76
|
+
def self.parse_timestamp(str)
|
77
|
+
return Time.parse(str)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Input YYYY-mm-dd
|
81
|
+
# TODO: Optimize this
|
82
|
+
|
83
|
+
def self.parse_date(str)
|
84
|
+
return nil unless str
|
85
|
+
return Date.strptime(str)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return an sql string evaluator for the property.
|
89
|
+
# No need to optimize this, used only to precalculate code.
|
90
|
+
# YAML is used to store general Ruby objects to be more
|
91
|
+
# portable.
|
92
|
+
#
|
93
|
+
# FIXME: add extra handling for float.
|
94
|
+
|
95
|
+
def self.write_prop(p)
|
96
|
+
if p.klass.ancestors.include?(Integer)
|
97
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
98
|
+
elsif p.klass.ancestors.include?(Float)
|
99
|
+
return "#\{@#{p.symbol} || 'NULL'\}"
|
100
|
+
elsif p.klass.ancestors.include?(String)
|
101
|
+
return "'#\{SqliteBackend.escape(@#{p.symbol})\}'"
|
102
|
+
elsif p.klass.ancestors.include?(Time)
|
103
|
+
return %|#\{@#{p.symbol} ? "'#\{SqliteBackend.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
|
104
|
+
elsif p.klass.ancestors.include?(Date)
|
105
|
+
return %|#\{@#{p.symbol} ? "'#\{SqliteBackend.date(@#{p.symbol})\}'" : 'NULL'\}|
|
106
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
107
|
+
return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
|
108
|
+
else
|
109
|
+
return %|#\{@#{p.symbol} ? "'#\{SqliteBackend.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return an evaluator for reading the property.
|
114
|
+
# No need to optimize this, used only to precalculate code.
|
115
|
+
|
116
|
+
def self.read_prop(p, idx)
|
117
|
+
if p.klass.ancestors.include?(Integer)
|
118
|
+
return "res.getvalue(tuple, #{idx}).to_i()"
|
119
|
+
elsif p.klass.ancestors.include?(Float)
|
120
|
+
return "res.getvalue(tuple, #{idx}).to_f()"
|
121
|
+
elsif p.klass.ancestors.include?(String)
|
122
|
+
return "res.getvalue(tuple, #{idx})"
|
123
|
+
elsif p.klass.ancestors.include?(Time)
|
124
|
+
return "PsqlBackend.parse_timestamp(res.getvalue(tuple, #{idx}))"
|
125
|
+
elsif p.klass.ancestors.include?(Date)
|
126
|
+
return "PsqlBackend.parse_date(res.getvalue(tuple, #{idx}))"
|
127
|
+
elsif p.klass.ancestors.include?(TrueClass)
|
128
|
+
return %|('t' == res.getvalue(tuple, #{idx}))|
|
129
|
+
else
|
130
|
+
return "YAML::load(res.getvalue(tuple, #{idx}))"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns the code that actually inserts the object into the
|
135
|
+
# database. Returns the code as String.
|
136
|
+
|
137
|
+
def self.insert_code(klass, sql, pre_cb, post_cb)
|
138
|
+
%{
|
139
|
+
#{pre_cb}
|
140
|
+
res = conn.db.query("SELECT nextval('#{klass::DBSEQ}')")
|
141
|
+
@oid = res.getvalue(0, 0).to_i
|
142
|
+
conn.exec "#{sql}"
|
143
|
+
#{post_cb}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
# generate the mapping of the database fields to the
|
148
|
+
# object properties.
|
149
|
+
|
150
|
+
def self.calc_field_index(klass, og)
|
151
|
+
res = og.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
|
152
|
+
meta = og.managed_classes[klass]
|
153
|
+
|
154
|
+
for field in res.fields
|
155
|
+
meta.field_index[field] = res.fieldnum(field)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Generate the property for oid
|
160
|
+
|
161
|
+
def self.eval_og_oid(klass)
|
162
|
+
klass.class_eval %{
|
163
|
+
prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
168
|
+
# Connection methods.
|
169
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
170
|
+
|
171
|
+
# Create the database.
|
172
|
+
|
173
|
+
def self.create_db(database, user = nil, password = nil)
|
174
|
+
Logger.info "Creating database '#{database}'."
|
175
|
+
`createdb #{database} -U #{user}`
|
176
|
+
end
|
177
|
+
|
178
|
+
# Drop the database.
|
179
|
+
|
180
|
+
def self.drop_db(database, user = nil, password = nil)
|
181
|
+
Logger.info "Dropping database '#{database}'."
|
182
|
+
`dropdb #{database} -U #{user}`
|
183
|
+
end
|
184
|
+
|
185
|
+
# Execute an SQL query and return the result.
|
186
|
+
|
187
|
+
def query(sql)
|
188
|
+
Logger.debug sql if $DBG
|
189
|
+
return @conn.exec(sql)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Execute an SQL query, no result returned.
|
193
|
+
|
194
|
+
def exec(sql)
|
195
|
+
Logger.debug sql if $DBG
|
196
|
+
res = @conn.exec(sql)
|
197
|
+
res.clear()
|
198
|
+
end
|
199
|
+
|
200
|
+
# Execute an SQL query and return the result. Wrapped in a rescue
|
201
|
+
# block.
|
202
|
+
|
203
|
+
def safe_query(sql)
|
204
|
+
Logger.debug sql if $DBG
|
205
|
+
begin
|
206
|
+
return @conn.exec(sql)
|
207
|
+
rescue => ex
|
208
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
209
|
+
Logger.error ex.backtrace
|
210
|
+
return nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Execute an SQL query, no result returned. Wrapped in a rescue
|
215
|
+
# block.
|
216
|
+
|
217
|
+
def safe_exec(sql)
|
218
|
+
Logger.debug sql if $DBG
|
219
|
+
begin
|
220
|
+
res = @conn.exec(sql)
|
221
|
+
res.clear()
|
222
|
+
rescue => ex
|
223
|
+
Logger.error "DB error #{ex}, [#{sql}]"
|
224
|
+
Logger.error ex.backtrace
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Check if it is a valid resultset.
|
229
|
+
|
230
|
+
def valid?(res)
|
231
|
+
return !(res.nil? or 0 == res.num_tuples)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Create the managed object table. The properties of the
|
235
|
+
# object are mapped to the table columns. Additional sql relations
|
236
|
+
# and constrains are created (indicices, sequences, etc).
|
237
|
+
|
238
|
+
def create_table(klass)
|
239
|
+
fields = create_fields(klass, TYPEMAP)
|
240
|
+
|
241
|
+
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
242
|
+
|
243
|
+
# Create table constrains
|
244
|
+
|
245
|
+
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
246
|
+
sql << ", #{constrains.join(', ')}"
|
247
|
+
end
|
248
|
+
|
249
|
+
sql << ") WITHOUT OIDS;"
|
250
|
+
|
251
|
+
# Create indices
|
252
|
+
|
253
|
+
if klass.__meta and indices = klass.__meta[:sql_index]
|
254
|
+
for data in indices
|
255
|
+
idx, options = *data
|
256
|
+
idx = idx.to_s
|
257
|
+
pre_sql, post_sql = options[:pre], options[:post]
|
258
|
+
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
259
|
+
sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
begin
|
264
|
+
exec(sql)
|
265
|
+
Logger.info "Created table '#{klass::DBTABLE}'."
|
266
|
+
rescue => ex
|
267
|
+
# gmosx: any idea how to better test this?
|
268
|
+
if ex.to_s =~ /relation .* already exists/i
|
269
|
+
Logger.debug "Table already exists" if $DBG
|
270
|
+
else
|
271
|
+
raise
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# create the sequence for this table. Even if the table
|
276
|
+
# uses the oids_seq, attempt to create it. This makes
|
277
|
+
# the system more fault tolerant.
|
278
|
+
|
279
|
+
begin
|
280
|
+
exec "CREATE SEQUENCE #{klass::DBSEQ}"
|
281
|
+
Logger.info "Created sequence '#{klass::DBSEQ}'."
|
282
|
+
rescue => ex
|
283
|
+
# gmosx: any idea how to better test this?
|
284
|
+
if ex.to_s =~ /relation .* already exists/i
|
285
|
+
Logger.debug "Sequence already exists" if $DBG
|
286
|
+
else
|
287
|
+
raise
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
# Create join tables if needed. Join tables are used in
|
292
|
+
# 'many_to_many' relations.
|
293
|
+
|
294
|
+
if klass.__meta and joins = klass.__meta[:sql_join]
|
295
|
+
for data in joins
|
296
|
+
# the class to join to and some options.
|
297
|
+
join_class, options = *data
|
298
|
+
|
299
|
+
# gmosx: dont use DBTABLE here, perhaps the join class
|
300
|
+
# is not managed yet.
|
301
|
+
join_table = "#{self.class.join_table(klass, join_class)}"
|
302
|
+
join_src = "#{self.class.encode(klass)}_oid"
|
303
|
+
join_dst = "#{self.class.encode(join_class)}_oid"
|
304
|
+
begin
|
305
|
+
exec "CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )"
|
306
|
+
exec "CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)"
|
307
|
+
exec "CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)"
|
308
|
+
rescue => ex
|
309
|
+
# gmosx: any idea how to better test this?
|
310
|
+
if ex.to_s =~ /relation .* already exists/i
|
311
|
+
Logger.debug "Join table already exists" if $DBG
|
312
|
+
else
|
313
|
+
raise
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
begin
|
320
|
+
exec(sql)
|
321
|
+
Logger.info "Created join table '#{join_table}'."
|
322
|
+
rescue => ex
|
323
|
+
# gmosx: any idea how to better test this?
|
324
|
+
if ex.to_s =~ /relation .* already exists/i
|
325
|
+
Logger.debug "Join table already exists" if $DBG
|
326
|
+
else
|
327
|
+
raise
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
# Drop the managed object table.
|
334
|
+
|
335
|
+
def drop_table(klass)
|
336
|
+
super
|
337
|
+
exec "DROP SEQUENCE #{klass::DBSEQ}"
|
338
|
+
end
|
339
|
+
|
340
|
+
# Deserialize one row of the resultset.
|
341
|
+
|
342
|
+
def deserialize_one(res, klass)
|
343
|
+
return nil unless valid?(res)
|
344
|
+
|
345
|
+
# gmosx: Managed objects should have no params constructor.
|
346
|
+
entity = klass.new()
|
347
|
+
entity.og_deserialize(res, 0)
|
348
|
+
|
349
|
+
# get_join_fields(res, 0, entity, join_fields) if join_fields
|
350
|
+
|
351
|
+
res.clear()
|
352
|
+
return entity
|
353
|
+
end
|
354
|
+
|
355
|
+
# Deserialize all rows of the resultset.
|
356
|
+
|
357
|
+
def deserialize_all(res, klass)
|
358
|
+
return [] unless valid?(res)
|
359
|
+
|
360
|
+
entities = []
|
361
|
+
|
362
|
+
for tuple in (0...res.num_tuples)
|
363
|
+
entity = klass.new()
|
364
|
+
entity.og_deserialize(res, tuple)
|
365
|
+
|
366
|
+
# get_join_fields(res, tuple, entity, join_fields) if join_fields
|
367
|
+
|
368
|
+
entities << entity
|
369
|
+
end
|
370
|
+
|
371
|
+
res.clear()
|
372
|
+
return entities
|
373
|
+
end
|
374
|
+
|
375
|
+
# Return a single integer value from the resultset.
|
376
|
+
|
377
|
+
def get_int(res, idx = 0)
|
378
|
+
return res.getvalue(0, idx).to_i
|
379
|
+
end
|
380
|
+
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|