nitro 0.2.0 → 0.3.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 +186 -0
- data/README +40 -11
- data/RELEASES +10 -1
- data/Rakefile +5 -4
- data/bin/cluster.rb +3 -3
- data/{etc/new-project.rb → bin/new_app.rb} +1 -1
- data/examples/og/README +4 -0
- data/examples/og/run.rb +254 -0
- data/examples/simple/app.rb +3 -3
- data/examples/simple/conf/config.rb +10 -22
- data/examples/simple/conf/debug-config.rb +6 -32
- data/examples/simple/conf/live-config.rb +3 -23
- data/examples/simple/conf/requires.rb +5 -5
- data/examples/simple/env.rb +3 -4
- data/examples/simple/lib/articles/entities.rb +17 -15
- data/examples/simple/lib/articles/methods.rb +15 -15
- data/examples/simple/lib/articles/part.rb +7 -8
- data/examples/simple/root/comments.si +1 -1
- data/examples/simple/root/index.sx +1 -1
- data/examples/simple/root/view-article.sx +1 -2
- data/examples/tiny/app.rb +3 -3
- data/examples/tiny/conf/config.rb +4 -4
- data/examples/tiny/conf/requires.rb +3 -4
- data/lib/n/config.rb +50 -3
- data/lib/n/logger.rb +14 -2
- data/lib/n/og.rb +381 -0
- data/lib/n/og/backend.rb +252 -0
- data/lib/n/og/backends/mysql.rb +352 -0
- data/lib/n/og/backends/psql.rb +351 -0
- data/lib/n/og/connection.rb +253 -0
- data/lib/n/og/meta.rb +127 -0
- data/lib/n/properties.rb +6 -6
- data/lib/n/server.rb +4 -7
- data/lib/n/server/appserver.rb +58 -0
- data/lib/n/{app → server}/cluster.rb +3 -3
- data/lib/n/{app → server}/cookie.rb +3 -3
- data/lib/n/server/dispatcher.rb +55 -0
- data/lib/n/server/{filter.rb → filters.rb} +1 -1
- data/lib/n/{app → server}/filters/autologin.rb +5 -5
- data/lib/n/{app → server}/fragment.rb +3 -3
- data/lib/n/{app → server}/handlers.rb +4 -4
- data/lib/n/{app → server}/handlers/code-handler.rb +6 -6
- data/lib/n/{app → server}/handlers/page-handler.rb +9 -7
- data/lib/n/{app → server}/request.rb +8 -8
- data/lib/n/{app/request-part.rb → server/requestpart.rb} +4 -4
- data/lib/n/{app → server}/script.rb +5 -5
- data/lib/n/{app → server}/server.rb +1 -1
- data/lib/n/{app → server}/session.rb +5 -5
- data/lib/n/{app → server}/user.rb +1 -1
- data/lib/n/{app/webrick-servlet.rb → server/webrick.rb} +77 -20
- data/lib/n/shaders.rb +3 -2
- data/lib/n/std.rb +5 -32
- data/test/n/{app → server}/tc_cookie.rb +2 -2
- data/test/n/server/tc_filters.rb +38 -0
- data/test/n/{app → server}/tc_request.rb +6 -6
- data/test/n/{app → server}/tc_requestpart.rb +3 -3
- data/test/n/{app → server}/tc_session.rb +2 -2
- data/test/n/tc_og.rb +178 -0
- data/test/n/ui/tc_pager.rb +3 -3
- metadata +41 -65
- data/examples/ndb/README +0 -5
- data/examples/ndb/run.rb +0 -271
- data/lib/n/app/webrick.rb +0 -73
- data/lib/n/db.rb +0 -233
- data/lib/n/db/README +0 -232
- data/lib/n/db/connection.rb +0 -365
- data/lib/n/db/managed.rb +0 -233
- data/lib/n/db/mixins.rb +0 -279
- data/lib/n/db/mysql.rb +0 -345
- data/lib/n/db/psql.rb +0 -383
- data/lib/n/db/tools.rb +0 -106
- data/lib/n/db/utils.rb +0 -102
- data/lib/n/server/PLAYBACK.txt +0 -8
- data/lib/n/server/RESEARCH.txt +0 -13
- data/test/n/tc_db.rb +0 -223
- data/test/n/tc_db_mysql.rb +0 -241
data/lib/n/db/mysql.rb
DELETED
@@ -1,345 +0,0 @@
|
|
1
|
-
# = MySQL backend
|
2
|
-
#
|
3
|
-
# Implement the Db backend using the MySQL RDBMS.
|
4
|
-
# Include the MySQL backend to the standard DbConnection
|
5
|
-
# object to synthesize a MySQLConnection at runtime.
|
6
|
-
#
|
7
|
-
# EXPERIMENTAL: NOT WORKING YET
|
8
|
-
#
|
9
|
-
# code:
|
10
|
-
# Elias Athanasopoulos <elathan@navel.gr>
|
11
|
-
# George Moschovitis <gm@navel.gr>
|
12
|
-
#
|
13
|
-
# (c) 2004 Navel, all rights reserved.
|
14
|
-
# $Id: mysql.rb 98 2004-10-22 07:36:20Z gmosx $
|
15
|
-
|
16
|
-
require "mysql"
|
17
|
-
require "time.rb"
|
18
|
-
require "date.rb"
|
19
|
-
|
20
|
-
module N;
|
21
|
-
|
22
|
-
# = DbUtils
|
23
|
-
#
|
24
|
-
# Backend specific utils. Extend the base utils
|
25
|
-
# collection.
|
26
|
-
#
|
27
|
-
module DbUtils
|
28
|
-
|
29
|
-
# Escape an sql string
|
30
|
-
#
|
31
|
-
def self.escape(str)
|
32
|
-
return nil unless str
|
33
|
-
return Mysql.quote(str)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Convert a ruby time to an sql timestamp.
|
37
|
-
#
|
38
|
-
def self.sql_timestamp(time = Time.now)
|
39
|
-
return nil unless time
|
40
|
-
return time.strftime("%Y-%m-%d %H:%M:%S")
|
41
|
-
end
|
42
|
-
|
43
|
-
# Output YYY-mm-dd
|
44
|
-
#
|
45
|
-
def self.sql_date(date)
|
46
|
-
return nil unless date
|
47
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
48
|
-
end
|
49
|
-
|
50
|
-
# Parse sql datetime
|
51
|
-
#
|
52
|
-
# TODO: Optimize this
|
53
|
-
#
|
54
|
-
def self.parse_sql_timestamp(str)
|
55
|
-
return Time.parse(str)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Input YYYY-mm-dd
|
59
|
-
#
|
60
|
-
def self.parse_sql_date(str)
|
61
|
-
return nil unless str
|
62
|
-
return Date.strptime(str)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Return an evaluator for reading the property
|
66
|
-
# No need to optimize this, used only to precalculate code.
|
67
|
-
#
|
68
|
-
def self.read_prop(p, idx)
|
69
|
-
case p.klass.to_s
|
70
|
-
when Fixnum.name
|
71
|
-
return "rows.getvalue(tuple, #{idx}).to_i()"
|
72
|
-
when Float.name
|
73
|
-
return "rows.getvalue(tuple, #{idx}).to_f()"
|
74
|
-
when Time.name
|
75
|
-
return "N::DbUtils.parse_sql_timestamp(rows.getvalue(tuple, #{idx}))"
|
76
|
-
when Date.name
|
77
|
-
return "N::DbUtils.parse_sql_date(rows.getvalue(tuple, #{idx}))"
|
78
|
-
when TrueClass.name
|
79
|
-
return "('true' == rows.getvalue(tuple, #{idx}))"
|
80
|
-
else # String
|
81
|
-
return "rows.getvalue(tuple, #{idx})"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# = MysqlBackend
|
87
|
-
#
|
88
|
-
# Implement the Db backend using the MySQL RDBMS.
|
89
|
-
#
|
90
|
-
module MysqlBackend
|
91
|
-
|
92
|
-
# map between Ruby and SQL types
|
93
|
-
#
|
94
|
-
TYPEMAP = {
|
95
|
-
Integer => "integer",
|
96
|
-
Fixnum => "integer",
|
97
|
-
Float => "float",
|
98
|
-
String => "text",
|
99
|
-
Time => "timestamp",
|
100
|
-
Date => "date",
|
101
|
-
TrueClass => "boolean",
|
102
|
-
Array => "bytea",
|
103
|
-
Hash => "bytea"
|
104
|
-
}
|
105
|
-
|
106
|
-
# Initialize a connection to the database
|
107
|
-
#
|
108
|
-
def initialize(config)
|
109
|
-
@rdb = Mysql.connect(config[:address], config[:user], config[:password], config[:database])
|
110
|
-
end
|
111
|
-
|
112
|
-
# Close the connection to the database
|
113
|
-
#
|
114
|
-
def close()
|
115
|
-
@rdb.close
|
116
|
-
end
|
117
|
-
|
118
|
-
# Create the sequence that generates the unified space id
|
119
|
-
# oids. You *MUST* call this method on newly created
|
120
|
-
# databases.
|
121
|
-
#
|
122
|
-
def create_schema()
|
123
|
-
@rdb.query("CREATE SEQUENCE oids_seq")
|
124
|
-
end
|
125
|
-
|
126
|
-
# Drop the oid sequence
|
127
|
-
#
|
128
|
-
def drop_schema()
|
129
|
-
@rdb.query("DROP SEQUENCE oids_seq")
|
130
|
-
end
|
131
|
-
|
132
|
-
# NOT IMPLEMENTED
|
133
|
-
#
|
134
|
-
def prepare_statement()
|
135
|
-
@rdb.query("PREPARE")
|
136
|
-
end
|
137
|
-
alias_method :pstatement, :prepare_statement
|
138
|
-
|
139
|
-
# NOT IMPLEMENTED
|
140
|
-
#
|
141
|
-
def execute_statement()
|
142
|
-
self.select("EXECUTE")
|
143
|
-
end
|
144
|
-
alias_method :xstatement, :execute_statement
|
145
|
-
|
146
|
-
# Create a table for an entity.
|
147
|
-
#
|
148
|
-
def create_table(klass)
|
149
|
-
fields = []
|
150
|
-
klass.__props.each { |p|
|
151
|
-
field = "#{p.symbol}"
|
152
|
-
if p.sql_type
|
153
|
-
field << " #{p.sql_type}"
|
154
|
-
else
|
155
|
-
field << " #{TYPEMAP[p.klass]}"
|
156
|
-
end
|
157
|
-
field << " #{p.sql}" if p.sql
|
158
|
-
|
159
|
-
field << " NOT NULL AUTO_INCREMENT" if p.symbol == :oid
|
160
|
-
fields << field
|
161
|
-
}
|
162
|
-
|
163
|
-
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
164
|
-
|
165
|
-
# Create table constrains
|
166
|
-
|
167
|
-
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
168
|
-
sql << ", #{constrains.join(', ')}"
|
169
|
-
end
|
170
|
-
|
171
|
-
sql << ");"
|
172
|
-
safe_query(sql)
|
173
|
-
|
174
|
-
$log.info "Created table #{klass::DBTABLE}! :: #{sql}"
|
175
|
-
|
176
|
-
# Create indices
|
177
|
-
if klass.__meta
|
178
|
-
for data in klass.__meta[:sql_index]
|
179
|
-
sql = ""
|
180
|
-
idx = data[0]
|
181
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
182
|
-
sql << " CREATE"
|
183
|
-
sql << " UNIQUE" if data[1]
|
184
|
-
sql << " INDEX #{klass::DBTABLE}_#{idxname}_idx #{data[3]} ON #{klass::DBTABLE} (#{idx});"
|
185
|
-
safe_query(sql)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
# Drop the entity table
|
191
|
-
#
|
192
|
-
def drop_table(klass)
|
193
|
-
safe_query("DROP TABLE #{klass::DBTABLE}")
|
194
|
-
end
|
195
|
-
|
196
|
-
# Calculate the fields map for the given class
|
197
|
-
#
|
198
|
-
def calc_fields(rows, klass)
|
199
|
-
# gmosx SOS, FIXME, INVESTIGATE: no need for second safe hash ????
|
200
|
-
fields = $db.fields[klass] = {}
|
201
|
-
for field in rows.fields
|
202
|
-
fields[field] = rows.fieldnum(field)
|
203
|
-
end
|
204
|
-
N::Managed.eval_db_read_row(klass)
|
205
|
-
end
|
206
|
-
|
207
|
-
# Grab the join fields returned by an sql join query, and attach them
|
208
|
-
# to the entity.
|
209
|
-
#
|
210
|
-
def get_join_fields(rows, tuple, entity, join_fields)
|
211
|
-
entity.join_fields = {}
|
212
|
-
for f in join_fields
|
213
|
-
entity.join_fields[f] = rows.getvalue(tuple, $db.fields[entity.class][f.to_s])
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# If the connection is in deserialize mode, deserialize one row.
|
218
|
-
#
|
219
|
-
def deserialize_one(rows, klass, join_fields = nil)
|
220
|
-
return nil unless rows
|
221
|
-
|
222
|
-
calc_fields(rows, klass) unless $db.fields[klass]
|
223
|
-
|
224
|
-
if @deserialize
|
225
|
-
# gmosx: Enities should have no params constructor SOS.
|
226
|
-
#
|
227
|
-
entity = klass.new()
|
228
|
-
entity.__db_read_row(rows, 0)
|
229
|
-
|
230
|
-
get_join_fields(rows, 0, entity, join_fields) if join_fields
|
231
|
-
|
232
|
-
rows.clear()
|
233
|
-
return entity
|
234
|
-
end
|
235
|
-
|
236
|
-
return rows[0]
|
237
|
-
end
|
238
|
-
|
239
|
-
# If the connection is in deserialize mode, deserialize all rows.
|
240
|
-
#
|
241
|
-
def deserialize_all(rows, klass, join_fields = nil)
|
242
|
-
return nil unless rows
|
243
|
-
|
244
|
-
calc_fields(rows, klass) unless $db.fields[klass]
|
245
|
-
|
246
|
-
if @deserialize
|
247
|
-
entities = []
|
248
|
-
|
249
|
-
for tuple in (0...rows.num_tuples)
|
250
|
-
entity = klass.new()
|
251
|
-
entity.__db_read_row(rows, tuple)
|
252
|
-
|
253
|
-
get_join_fields(rows, tuple, entity, join_fields) if join_fields
|
254
|
-
|
255
|
-
entities << entity
|
256
|
-
end
|
257
|
-
|
258
|
-
rows.clear()
|
259
|
-
return entities
|
260
|
-
end
|
261
|
-
|
262
|
-
return rows
|
263
|
-
end
|
264
|
-
|
265
|
-
#
|
266
|
-
# Execute an sql query. If the entity table is missing, create
|
267
|
-
# it and retry.
|
268
|
-
#
|
269
|
-
def retry_query(sql, klass = nil)
|
270
|
-
$log.debug sql if $DBG
|
271
|
-
retries = 0
|
272
|
-
begin
|
273
|
-
rows = @rdb.query(sql)
|
274
|
-
if rows && (rows.num_tuples > 0)
|
275
|
-
return rows
|
276
|
-
else
|
277
|
-
return nil
|
278
|
-
end
|
279
|
-
rescue => ex
|
280
|
-
if ex.errno == 1146 # table does not exist
|
281
|
-
$log.info "retry_query: #{ex}"
|
282
|
-
# table does not exist, create it!
|
283
|
-
create_table(klass)
|
284
|
-
# gmosx: only allow ONE retry to avoid loops here!
|
285
|
-
retries += 1
|
286
|
-
retry if retries <= 1
|
287
|
-
else
|
288
|
-
$log.error "DB Error: #{ex} (#{ex.errno}), [#{sql}]"
|
289
|
-
$log.error "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
290
|
-
return nil
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
# Execute an sql query and catch the errors.
|
296
|
-
#
|
297
|
-
def safe_query(sql)
|
298
|
-
$log.debug sql if $DBG
|
299
|
-
begin
|
300
|
-
rows = @rdb.query(sql)
|
301
|
-
if rows #&& (rows.num_tuples > 0)
|
302
|
-
return rows
|
303
|
-
else
|
304
|
-
return nil
|
305
|
-
end
|
306
|
-
rescue => ex
|
307
|
-
$log.error "DB Error (safe_query): #{ex} (#{ex.errno}), [#{sql}]"
|
308
|
-
$log.error "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
309
|
-
return nil
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
# Get the next oid in the sequence for this klass
|
314
|
-
#
|
315
|
-
def next_oid(klass)
|
316
|
-
retries = 0
|
317
|
-
begin
|
318
|
-
res = @rdb.insert_id()
|
319
|
-
res ? oid = res + 1 : oid = 1
|
320
|
-
return oid
|
321
|
-
rescue => ex
|
322
|
-
if ex.errno == 1146 # table does not exist
|
323
|
-
$log.info "next_oid: #{ex}"
|
324
|
-
# table does not exist, create it!
|
325
|
-
create_table(klass)
|
326
|
-
# gmosx: only allow ONE retry to avoid loops here!
|
327
|
-
retries += 1
|
328
|
-
retry if retries <= 1
|
329
|
-
else
|
330
|
-
$log.error "DB Error (next_oid): #{ex} (#{ex.errno})"
|
331
|
-
$log.error "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
332
|
-
return nil
|
333
|
-
end
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
end
|
338
|
-
|
339
|
-
# Mix into the DbConnection class
|
340
|
-
#
|
341
|
-
N::DbConnection.module_eval %{
|
342
|
-
include N::MysqlBackend
|
343
|
-
}
|
344
|
-
|
345
|
-
end # namespace
|
data/lib/n/db/psql.rb
DELETED
@@ -1,383 +0,0 @@
|
|
1
|
-
# code:
|
2
|
-
# * George Moschovitis <gm@navel.gr>
|
3
|
-
#
|
4
|
-
# (c) 2004 Navel, all rights reserved.
|
5
|
-
# $Id$
|
6
|
-
|
7
|
-
require "base64"
|
8
|
-
require "postgres"
|
9
|
-
require "time.rb"
|
10
|
-
require "date.rb"
|
11
|
-
|
12
|
-
module N;
|
13
|
-
|
14
|
-
# = DbUtils
|
15
|
-
#
|
16
|
-
# Backend specific utils
|
17
|
-
#
|
18
|
-
module DbUtils
|
19
|
-
|
20
|
-
# Escape an sql string
|
21
|
-
#
|
22
|
-
def self.escape(str)
|
23
|
-
return nil unless str
|
24
|
-
return PGconn.escape(str)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Escape bytes
|
28
|
-
#
|
29
|
-
def self.escape_bytes(bytes)
|
30
|
-
return nil unless bytes
|
31
|
-
return PGconn.escape_bytea(bytes)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Unescape bytes
|
35
|
-
# FIXME: optimize this! Even better integrate this in the
|
36
|
-
# libpq library, or find out why the default method doesnt
|
37
|
-
# work.
|
38
|
-
#
|
39
|
-
def self.unescape_bytes(bytes)
|
40
|
-
return bytes.gsub(/(\\\d+)/) { |m| m.gsub(/\\/, "").oct.chr }
|
41
|
-
end
|
42
|
-
|
43
|
-
# Convert a ruby time to an sql timestamp.
|
44
|
-
#
|
45
|
-
def self.sql_timestamp(time = Time.now)
|
46
|
-
return nil unless time
|
47
|
-
return time.strftime("%Y-%m-%d %H:%M:%S")
|
48
|
-
end
|
49
|
-
|
50
|
-
# Output YYY-mm-dd
|
51
|
-
#
|
52
|
-
def self.sql_date(date)
|
53
|
-
return nil unless date
|
54
|
-
return "#{date.year}-#{date.month}-#{date.mday}"
|
55
|
-
end
|
56
|
-
|
57
|
-
# Parse sql datetime
|
58
|
-
#
|
59
|
-
# TODO: Optimize this
|
60
|
-
#
|
61
|
-
def self.parse_sql_timestamp(str)
|
62
|
-
return Time.parse(str)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Input YYYY-mm-dd
|
66
|
-
#
|
67
|
-
def self.parse_sql_date(str)
|
68
|
-
return nil unless str
|
69
|
-
return Date.strptime(str)
|
70
|
-
end
|
71
|
-
|
72
|
-
# Return an evaluator for reading the property
|
73
|
-
# No need to optimize this, used only to precalculate code.
|
74
|
-
#--
|
75
|
-
# gmosx: base64 encoding is used because the unencode_bytea
|
76
|
-
# method of postgres is not implemented
|
77
|
-
#++
|
78
|
-
#
|
79
|
-
def self.read_prop(p, idx)
|
80
|
-
case p.klass.to_s
|
81
|
-
when Fixnum.name
|
82
|
-
return "rows.getvalue(tuple, #{idx}).to_i()"
|
83
|
-
when Float.name
|
84
|
-
return "rows.getvalue(tuple, #{idx}).to_f()"
|
85
|
-
when Time.name
|
86
|
-
return "N::DbUtils.parse_sql_timestamp(rows.getvalue(tuple, #{idx}))"
|
87
|
-
when Date.name
|
88
|
-
return "N::DbUtils.parse_sql_date(rows.getvalue(tuple, #{idx}))"
|
89
|
-
when TrueClass.name
|
90
|
-
return "('true' == rows.getvalue(tuple, #{idx}))"
|
91
|
-
when Object.name
|
92
|
-
return "Marshal.load(Base64.decode64(rows.getvalue(tuple, #{idx}).gsub(/\\\\012/, '\n')))"
|
93
|
-
when Array.name
|
94
|
-
return "Marshal.load(Base64.decode64(rows.getvalue(tuple, #{idx}).gsub(/\\\\012/, '\n')))"
|
95
|
-
# return "Marshal.load(N::DbUtils.unescape_bytes(rows.getvalue(tuple, #{idx})))"
|
96
|
-
when Hash.name
|
97
|
-
return "Marshal.load(Base64.decode64(rows.getvalue(tuple, #{idx}).gsub(/\\\\012/, '\n')))"
|
98
|
-
else # String
|
99
|
-
return "rows.getvalue(tuple, #{idx})"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# = PsqlBackend
|
105
|
-
#
|
106
|
-
# Implement the Db backend using the PostgreSQL RDBMS.
|
107
|
-
#
|
108
|
-
# Include the Psql backend to the standard DbConnection
|
109
|
-
# object to synthesize a PsqlConnection at runtime.
|
110
|
-
#
|
111
|
-
module PsqlBackend
|
112
|
-
|
113
|
-
# map between Ruby and SQL types
|
114
|
-
#
|
115
|
-
TYPEMAP = {
|
116
|
-
Integer => "integer",
|
117
|
-
Fixnum => "integer",
|
118
|
-
Float => "float",
|
119
|
-
String => "text",
|
120
|
-
Time => "timestamp",
|
121
|
-
Date => "date",
|
122
|
-
TrueClass => "boolean",
|
123
|
-
Object => "bytea",
|
124
|
-
Array => "bytea",
|
125
|
-
Hash => "bytea"
|
126
|
-
}
|
127
|
-
|
128
|
-
# Initialize a connection to the database
|
129
|
-
#
|
130
|
-
def initialize(config)
|
131
|
-
@rdb = PGconn.connect(nil, nil, nil, nil, config[:database],
|
132
|
-
config[:user], config[:password])
|
133
|
-
end
|
134
|
-
|
135
|
-
# Close the connection to the database
|
136
|
-
#
|
137
|
-
def close()
|
138
|
-
@rdb.close
|
139
|
-
end
|
140
|
-
|
141
|
-
# Create the sequence that generates the unified space id
|
142
|
-
# oids. You *MUST* call this method on newly created
|
143
|
-
# databases.
|
144
|
-
#
|
145
|
-
def create_schema()
|
146
|
-
safe_query("CREATE SEQUENCE oids_seq")
|
147
|
-
end
|
148
|
-
|
149
|
-
# Drop the oid sequence
|
150
|
-
#
|
151
|
-
def drop_schema()
|
152
|
-
safe_query("DROP SEQUENCE oids_seq")
|
153
|
-
end
|
154
|
-
|
155
|
-
# NOT IMPLEMENTED
|
156
|
-
#
|
157
|
-
def prepare_statement()
|
158
|
-
@rdb.query("PREPARE")
|
159
|
-
end
|
160
|
-
alias_method :pstatement, :prepare_statement
|
161
|
-
|
162
|
-
# NOT IMPLEMENTED
|
163
|
-
#
|
164
|
-
def execute_statement()
|
165
|
-
self.select("EXECUTE")
|
166
|
-
end
|
167
|
-
alias_method :xstatement, :execute_statement
|
168
|
-
|
169
|
-
# Create a table for an entity.
|
170
|
-
#
|
171
|
-
def create_table(klass)
|
172
|
-
fields = []
|
173
|
-
klass.__props.each { |p|
|
174
|
-
field = "#{p.symbol}"
|
175
|
-
if p.sql
|
176
|
-
field << " #{p.sql}"
|
177
|
-
else
|
178
|
-
field << " #{TYPEMAP[p.klass]}"
|
179
|
-
end
|
180
|
-
|
181
|
-
fields << field
|
182
|
-
}
|
183
|
-
|
184
|
-
sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
|
185
|
-
|
186
|
-
# Create table constrains
|
187
|
-
|
188
|
-
if klass.__meta and constrains = klass.__meta[:sql_constrain]
|
189
|
-
sql << ", #{constrains.join(', ')}"
|
190
|
-
end
|
191
|
-
|
192
|
-
sql << ") WITHOUT OIDS;"
|
193
|
-
|
194
|
-
# Create indices
|
195
|
-
|
196
|
-
if klass.__meta
|
197
|
-
for data in klass.__meta[:sql_index]
|
198
|
-
idx, pre_sql, post_sql = *data
|
199
|
-
idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
|
200
|
-
sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
safe_query(sql)
|
205
|
-
$log.info "Created table #{klass::DBTABLE}!"
|
206
|
-
|
207
|
-
# create the sequence for this table. Even if the table
|
208
|
-
# uses the oids_seq, attempt to create it. This makes
|
209
|
-
# the system more fault tolerant.
|
210
|
-
safe_query("CREATE SEQUENCE #{klass::DBSEQ}")
|
211
|
-
$log.info "Created sequence #{klass::DBSEQ}!"
|
212
|
-
end
|
213
|
-
|
214
|
-
# Drop the entity table
|
215
|
-
#
|
216
|
-
def drop_table(klass)
|
217
|
-
safe_query("DROP TABLE #{klass::DBTABLE}")
|
218
|
-
if klass.include?(N::Sequenced)
|
219
|
-
safe_query("DROP SEQUENCE #{klass::DBSEQ};")
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Calculate the fields map for the given class
|
224
|
-
#
|
225
|
-
def calc_fields(rows, klass)
|
226
|
-
# gmosx SOS, FIXME, INVESTIGATE: no need for second safe hash ????
|
227
|
-
fields = $db.fields[klass] = {}
|
228
|
-
for field in rows.fields
|
229
|
-
fields[field] = rows.fieldnum(field)
|
230
|
-
end
|
231
|
-
N::Managed.eval_db_read_row(klass)
|
232
|
-
end
|
233
|
-
|
234
|
-
# Grab the join fields returned by an sql join query, and attach them
|
235
|
-
# to the entity.
|
236
|
-
#
|
237
|
-
def get_join_fields(rows, tuple, entity, join_fields)
|
238
|
-
entity.join_fields = {}
|
239
|
-
for f in join_fields
|
240
|
-
entity.join_fields[f] = rows.getvalue(tuple, $db.fields[entity.class][f.to_s])
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
# If the connection is in deserialize mode, deserialize one row.
|
245
|
-
#
|
246
|
-
def deserialize_one(rows, klass, join_fields = nil)
|
247
|
-
return nil unless rows
|
248
|
-
|
249
|
-
calc_fields(rows, klass) unless $db.fields[klass]
|
250
|
-
|
251
|
-
if @deserialize
|
252
|
-
# gmosx: Enities should have no params constructor SOS.
|
253
|
-
#
|
254
|
-
entity = klass.new()
|
255
|
-
entity.__db_read_row(rows, 0)
|
256
|
-
|
257
|
-
get_join_fields(rows, 0, entity, join_fields) if join_fields
|
258
|
-
|
259
|
-
rows.clear()
|
260
|
-
return entity
|
261
|
-
end
|
262
|
-
|
263
|
-
return rows[0]
|
264
|
-
end
|
265
|
-
|
266
|
-
# If the connection is in deserialize mode, deserialize all rows.
|
267
|
-
#
|
268
|
-
def deserialize_all(rows, klass, join_fields = nil)
|
269
|
-
return nil unless rows
|
270
|
-
|
271
|
-
calc_fields(rows, klass) unless $db.fields[klass]
|
272
|
-
|
273
|
-
if @deserialize
|
274
|
-
entities = []
|
275
|
-
|
276
|
-
for tuple in (0...rows.num_tuples)
|
277
|
-
entity = klass.new()
|
278
|
-
entity.__db_read_row(rows, tuple)
|
279
|
-
|
280
|
-
get_join_fields(rows, tuple, entity, join_fields) if join_fields
|
281
|
-
|
282
|
-
entities << entity
|
283
|
-
end
|
284
|
-
|
285
|
-
rows.clear()
|
286
|
-
return entities
|
287
|
-
end
|
288
|
-
|
289
|
-
return rows
|
290
|
-
end
|
291
|
-
|
292
|
-
#
|
293
|
-
# Execute an sql query. If the entity table is missing, create
|
294
|
-
# it and retry.
|
295
|
-
#
|
296
|
-
# exec() is used instead of query because it is faster and we also
|
297
|
-
# need fields()
|
298
|
-
#
|
299
|
-
# FIXME: is the result cleared?
|
300
|
-
#
|
301
|
-
def retry_query(sql, klass = nil)
|
302
|
-
$log.debug sql if $DBG
|
303
|
-
retries = 0
|
304
|
-
begin
|
305
|
-
rows = @rdb.exec(sql)
|
306
|
-
if rows && (rows.num_tuples > 0)
|
307
|
-
return rows
|
308
|
-
else
|
309
|
-
return nil
|
310
|
-
end
|
311
|
-
rescue => ex
|
312
|
-
# Any idea how to better test this?
|
313
|
-
if ex.to_s =~ /relation .* not exist/
|
314
|
-
$log.info "RETRY_QUERY"
|
315
|
-
# table does not exist, create it!
|
316
|
-
create_table(klass)
|
317
|
-
# gmosx: only allow ONE retry to avoid loops here!
|
318
|
-
retries += 1
|
319
|
-
retry if retries <= 1
|
320
|
-
else
|
321
|
-
$log.error "RETRY_QUERY: surpressing db error: #{ex}, [#{sql}]"
|
322
|
-
# $log.debug "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
323
|
-
return nil
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
# Execute an sql query and catch the errors.
|
329
|
-
#
|
330
|
-
# exec() is used instead of query because it is faster and we also
|
331
|
-
# need fields()
|
332
|
-
#
|
333
|
-
def safe_query(sql)
|
334
|
-
$log.debug sql if $DBG
|
335
|
-
begin
|
336
|
-
rows = @rdb.exec(sql)
|
337
|
-
if rows && (rows.num_tuples > 0)
|
338
|
-
return rows
|
339
|
-
else
|
340
|
-
return nil
|
341
|
-
end
|
342
|
-
rescue => ex
|
343
|
-
$log.error "SAFE_QUERY: surpressing db error #{ex}, [#{sql}]"
|
344
|
-
# $log.debug "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
345
|
-
return nil
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
# Get the next oid in the sequence for this klass
|
350
|
-
#
|
351
|
-
def next_oid(klass)
|
352
|
-
retries = 0
|
353
|
-
begin
|
354
|
-
res = @rdb.exec("SELECT nextval('#{klass::DBSEQ}')")
|
355
|
-
oid = res.getvalue(0, 0).to_i()
|
356
|
-
res.clear()
|
357
|
-
return oid
|
358
|
-
rescue => ex
|
359
|
-
# Any idea how to better test this?
|
360
|
-
if ex.to_s =~ /relation .* not exist/
|
361
|
-
$log.info "next_oid: #{ex}"
|
362
|
-
# table does not exist, create it!
|
363
|
-
create_table(klass)
|
364
|
-
# gmosx: only allow ONE retry to avoid loops here!
|
365
|
-
retries += 1
|
366
|
-
retry if retries <= 1
|
367
|
-
else
|
368
|
-
$log.error "DB Error: #{ex}, #next_oid"
|
369
|
-
$log.debug "#{caller[0]} : #{caller[1]} : #{caller[2]}"
|
370
|
-
return nil
|
371
|
-
end
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
end
|
376
|
-
|
377
|
-
# Mix into the DbConnection class
|
378
|
-
#
|
379
|
-
N::DbConnection.module_eval %{
|
380
|
-
include N::PsqlBackend
|
381
|
-
}
|
382
|
-
|
383
|
-
end # namespace
|