nitro 0.1.2

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