nitro 0.1.2

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.
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