nitro 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/ChangeLog +186 -0
  2. data/README +40 -11
  3. data/RELEASES +10 -1
  4. data/Rakefile +5 -4
  5. data/bin/cluster.rb +3 -3
  6. data/{etc/new-project.rb → bin/new_app.rb} +1 -1
  7. data/examples/og/README +4 -0
  8. data/examples/og/run.rb +254 -0
  9. data/examples/simple/app.rb +3 -3
  10. data/examples/simple/conf/config.rb +10 -22
  11. data/examples/simple/conf/debug-config.rb +6 -32
  12. data/examples/simple/conf/live-config.rb +3 -23
  13. data/examples/simple/conf/requires.rb +5 -5
  14. data/examples/simple/env.rb +3 -4
  15. data/examples/simple/lib/articles/entities.rb +17 -15
  16. data/examples/simple/lib/articles/methods.rb +15 -15
  17. data/examples/simple/lib/articles/part.rb +7 -8
  18. data/examples/simple/root/comments.si +1 -1
  19. data/examples/simple/root/index.sx +1 -1
  20. data/examples/simple/root/view-article.sx +1 -2
  21. data/examples/tiny/app.rb +3 -3
  22. data/examples/tiny/conf/config.rb +4 -4
  23. data/examples/tiny/conf/requires.rb +3 -4
  24. data/lib/n/config.rb +50 -3
  25. data/lib/n/logger.rb +14 -2
  26. data/lib/n/og.rb +381 -0
  27. data/lib/n/og/backend.rb +252 -0
  28. data/lib/n/og/backends/mysql.rb +352 -0
  29. data/lib/n/og/backends/psql.rb +351 -0
  30. data/lib/n/og/connection.rb +253 -0
  31. data/lib/n/og/meta.rb +127 -0
  32. data/lib/n/properties.rb +6 -6
  33. data/lib/n/server.rb +4 -7
  34. data/lib/n/server/appserver.rb +58 -0
  35. data/lib/n/{app → server}/cluster.rb +3 -3
  36. data/lib/n/{app → server}/cookie.rb +3 -3
  37. data/lib/n/server/dispatcher.rb +55 -0
  38. data/lib/n/server/{filter.rb → filters.rb} +1 -1
  39. data/lib/n/{app → server}/filters/autologin.rb +5 -5
  40. data/lib/n/{app → server}/fragment.rb +3 -3
  41. data/lib/n/{app → server}/handlers.rb +4 -4
  42. data/lib/n/{app → server}/handlers/code-handler.rb +6 -6
  43. data/lib/n/{app → server}/handlers/page-handler.rb +9 -7
  44. data/lib/n/{app → server}/request.rb +8 -8
  45. data/lib/n/{app/request-part.rb → server/requestpart.rb} +4 -4
  46. data/lib/n/{app → server}/script.rb +5 -5
  47. data/lib/n/{app → server}/server.rb +1 -1
  48. data/lib/n/{app → server}/session.rb +5 -5
  49. data/lib/n/{app → server}/user.rb +1 -1
  50. data/lib/n/{app/webrick-servlet.rb → server/webrick.rb} +77 -20
  51. data/lib/n/shaders.rb +3 -2
  52. data/lib/n/std.rb +5 -32
  53. data/test/n/{app → server}/tc_cookie.rb +2 -2
  54. data/test/n/server/tc_filters.rb +38 -0
  55. data/test/n/{app → server}/tc_request.rb +6 -6
  56. data/test/n/{app → server}/tc_requestpart.rb +3 -3
  57. data/test/n/{app → server}/tc_session.rb +2 -2
  58. data/test/n/tc_og.rb +178 -0
  59. data/test/n/ui/tc_pager.rb +3 -3
  60. metadata +41 -65
  61. data/examples/ndb/README +0 -5
  62. data/examples/ndb/run.rb +0 -271
  63. data/lib/n/app/webrick.rb +0 -73
  64. data/lib/n/db.rb +0 -233
  65. data/lib/n/db/README +0 -232
  66. data/lib/n/db/connection.rb +0 -365
  67. data/lib/n/db/managed.rb +0 -233
  68. data/lib/n/db/mixins.rb +0 -279
  69. data/lib/n/db/mysql.rb +0 -345
  70. data/lib/n/db/psql.rb +0 -383
  71. data/lib/n/db/tools.rb +0 -106
  72. data/lib/n/db/utils.rb +0 -102
  73. data/lib/n/server/PLAYBACK.txt +0 -8
  74. data/lib/n/server/RESEARCH.txt +0 -13
  75. data/test/n/tc_db.rb +0 -223
  76. data/test/n/tc_db_mysql.rb +0 -241
@@ -0,0 +1,351 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ #
4
+ # (c) 2004 Navel, all rights reserved.
5
+ # $Id: psql.rb 123 2004-11-01 11:55:11Z gmosx $
6
+
7
+ require "postgres"
8
+
9
+ require "n/og/backend"
10
+
11
+ module N
12
+
13
+ # = OgUtils
14
+ #
15
+ # A collection of useful utilities.
16
+ #
17
+ module OgUtils
18
+
19
+ # Escape an SQL string
20
+ #
21
+ def self.escape(str)
22
+ return nil unless str
23
+ return PGconn.escape(str)
24
+ end
25
+
26
+ # Convert a ruby time to an sql timestamp.
27
+ # TODO: Optimize this
28
+ #
29
+ def self.timestamp(time = Time.now)
30
+ return nil unless time
31
+ return time.strftime("%Y-%m-%d %H:%M:%S")
32
+ end
33
+
34
+ # Output YYY-mm-dd
35
+ # TODO: Optimize this
36
+ #
37
+ def self.date(date)
38
+ return nil unless date
39
+ return "#{date.year}-#{date.month}-#{date.mday}"
40
+ end
41
+
42
+ # Parse sql datetime
43
+ # TODO: Optimize this
44
+ #
45
+ def self.parse_timestamp(str)
46
+ return Time.parse(str)
47
+ end
48
+
49
+ # Input YYYY-mm-dd
50
+ # TODO: Optimize this
51
+ #
52
+ def self.parse_date(str)
53
+ return nil unless str
54
+ return Date.strptime(str)
55
+ end
56
+
57
+ # Return an sql string evaluator for the property.
58
+ # No need to optimize this, used only to precalculate code.
59
+ # YAML is used to store general Ruby objects to be more
60
+ # portable.
61
+ #
62
+ # FIXME: add extra handling for float.
63
+ #
64
+ def self.write_prop(p)
65
+ if String == p.klass
66
+ return "'#\{N::OgUtils.escape(@#{p.symbol})\}'"
67
+ elsif Time == p.klass
68
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
69
+ elsif Date == p.klass
70
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.date(@#{p.symbol})\}'" : 'NULL'\}|
71
+ elsif Object == p.klass or Array == p.klass or Hash == p.klass
72
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.escape(@#{p.symbol}.to_yaml)\}'" : ''\}|
73
+ else
74
+ # Fixnum, TrueClass
75
+ return "#\{@#{p.symbol} || 'NULL'\}"
76
+ end
77
+ end
78
+
79
+ # Return an evaluator for reading the property.
80
+ # No need to optimize this, used only to precalculate code.
81
+ #
82
+ def self.read_prop(p, idx)
83
+ case p.klass.to_s
84
+ when Fixnum.name
85
+ return "res.getvalue(tuple, #{idx}).to_i()"
86
+ when Float.name
87
+ return "res.getvalue(tuple, #{idx}).to_f()"
88
+ when Time.name
89
+ return "N::OgUtils.parse_timestamp(res.getvalue(tuple, #{idx}))"
90
+ when Date.name
91
+ return "N::OgUtils.parse_date(res.getvalue(tuple, #{idx}))"
92
+ when TrueClass.name
93
+ return "('true' == res.getvalue(tuple, #{idx}))"
94
+ when Object.name
95
+ return "YAML::load(res.getvalue(tuple, #{idx}))"
96
+ when Array.name
97
+ return "YAML::load(res.getvalue(tuple, #{idx}))"
98
+ when Hash.name
99
+ return "YAML::load(res.getvalue(tuple, #{idx}))"
100
+ else # String
101
+ return "res.getvalue(tuple, #{idx})"
102
+ end
103
+ end
104
+
105
+ # Returns the code that actually inserts the object into the
106
+ # database. Returns the code as String.
107
+ #
108
+ def self.insert_code(klass, sql, pre_cb, post_cb)
109
+ %{
110
+ #{pre_cb}
111
+ res = conn.db.query("SELECT nextval('#{klass::DBSEQ}')")
112
+ @oid = res.getvalue(0, 0).to_i
113
+ conn.exec "#{sql}"
114
+ #{post_cb}
115
+ }
116
+ end
117
+
118
+ # generate the mapping of the database fields to the
119
+ # object properties.
120
+ #
121
+ def self.calc_field_index(klass, og)
122
+ res = og.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
123
+ meta = og.managed_classes[klass]
124
+
125
+ for field in res.fields
126
+ meta.field_index[field] = res.fieldnum(field)
127
+ end
128
+ end
129
+
130
+ # Generate the property for oid
131
+ #
132
+ def self.eval_og_oid(klass)
133
+ klass.class_eval %{
134
+ prop_accessor Fixnum, "integer PRIMARY KEY", :oid
135
+ }
136
+ end
137
+ end
138
+
139
+ # = PsqlBackend
140
+ #
141
+ # Implements a PostgreSQL powered backend.
142
+ #
143
+ class PsqlBackend < N::OgBackend
144
+
145
+ # A mapping between Ruby and SQL types.
146
+ #
147
+ TYPEMAP = {
148
+ Integer => "integer",
149
+ Fixnum => "integer",
150
+ Float => "float",
151
+ String => "text",
152
+ Time => "timestamp",
153
+ Date => "date",
154
+ TrueClass => "boolean",
155
+ Object => "text",
156
+ Array => "text",
157
+ Hash => "text"
158
+ }
159
+
160
+ # Intitialize the connection to the RDBMS.
161
+ #
162
+ def initialize(config)
163
+ begin
164
+ @conn = PGconn.connect(nil, nil, nil, nil, config[:database],
165
+ config[:user], config[:password])
166
+ rescue => ex
167
+ # gmosx: any idea how to better test this?
168
+ if ex.to_s =~ /database .* does not exist/i
169
+ $log.info "Database '#{config[:database]}' not found!"
170
+ PsqlBackend.create_db(config[:database], config[:user])
171
+ retry
172
+ end
173
+ raise
174
+ end
175
+ end
176
+
177
+ # Create the database.
178
+ #
179
+ def self.create_db(database, user = nil, password = nil)
180
+ $log.info "Creating database '#{database}'."
181
+ `createdb #{database} -U #{user}`
182
+ end
183
+
184
+ # Drop the database.
185
+ #
186
+ def self.drop_db(database, user = nil, password = nil)
187
+ $log.info "Dropping database '#{database}'."
188
+ `dropdb #{database} -U #{user}`
189
+ end
190
+
191
+ # Execute an SQL query and return the result
192
+ #
193
+ def query(sql)
194
+ $log.debug sql if $DBG
195
+ return @conn.exec(sql)
196
+ end
197
+
198
+ # Execute an SQL query, no result returned.
199
+ #
200
+ def exec(sql)
201
+ $log.debug sql if $DBG
202
+ res = @conn.exec(sql)
203
+ res.clear()
204
+ end
205
+
206
+ # Execute an SQL query and return the result. Wrapped in a rescue
207
+ # block.
208
+ #
209
+ def safe_query(sql)
210
+ $log.debug sql if $DBG
211
+ begin
212
+ return @conn.exec(sql)
213
+ rescue => ex
214
+ $log.error "DB error #{ex}, [#{sql}]"
215
+ $log.error ex.backtrace
216
+ return nil
217
+ end
218
+ end
219
+
220
+ # Execute an SQL query, no result returned. Wrapped in a rescue
221
+ # block.
222
+ #
223
+ def safe_exec(sql)
224
+ $log.debug sql if $DBG
225
+ begin
226
+ res = @conn.exec(sql)
227
+ res.clear()
228
+ rescue => ex
229
+ $log.error "DB error #{ex}, [#{sql}]"
230
+ $log.error ex.backtrace
231
+ end
232
+ end
233
+
234
+ # Check if it is a valid resultset.
235
+ #
236
+ def valid?(res)
237
+ return !(res.nil? or 0 == res.num_tuples)
238
+ end
239
+
240
+ # Create the managed object table. The properties of the
241
+ # object are mapped to the table columns. Additional sql relations
242
+ # and constrains are created (indicices, sequences, etc).
243
+ #
244
+ def create_table(klass)
245
+ fields = []
246
+
247
+ klass.__props.each do |p|
248
+ field = "#{p.symbol}"
249
+
250
+ if p.sql
251
+ field << " #{p.sql}"
252
+ else
253
+ field << " #{TYPEMAP[p.klass]}"
254
+ end
255
+
256
+ fields << field
257
+ end
258
+
259
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
260
+
261
+ # Create table constrains
262
+
263
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
264
+ sql << ", #{constrains.join(', ')}"
265
+ end
266
+
267
+ sql << ") WITHOUT OIDS;"
268
+
269
+ # Create indices
270
+
271
+ if klass.__meta
272
+ for data in klass.__meta[:sql_index]
273
+ idx, pre_sql, post_sql = *data
274
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
275
+ sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
276
+ end
277
+ end
278
+
279
+ begin
280
+ exec(sql)
281
+ $log.info "Created table '#{klass::DBTABLE}'."
282
+ rescue => ex
283
+ # gmosx: any idea how to better test this?
284
+ if ex.to_s =~ /relation .* already exists/i
285
+ $log.debug "Table already exists" if $DBG
286
+ else
287
+ raise
288
+ end
289
+ end
290
+
291
+ # create the sequence for this table. Even if the table
292
+ # uses the oids_seq, attempt to create it. This makes
293
+ # the system more fault tolerant.
294
+ begin
295
+ exec "CREATE SEQUENCE #{klass::DBSEQ}"
296
+ $log.info "Created sequence '#{klass::DBSEQ}'."
297
+ rescue => ex
298
+ # gmosx: any idea how to better test this?
299
+ if ex.to_s =~ /relation .* already exists/i
300
+ $log.debug "Sequence already exists" if $DBG
301
+ else
302
+ raise
303
+ end
304
+ end
305
+ end
306
+
307
+ # Drop the managed object table
308
+ #
309
+ def drop_table(klass)
310
+ super
311
+ exec "DROP SEQUENCE #{klass::DBSEQ}"
312
+ end
313
+
314
+ # Deserialize one row of the resultset.
315
+ #
316
+ def deserialize_one(res, klass)
317
+ return nil unless valid?(res)
318
+
319
+ # gmosx: Managed objects should have no params constructor.
320
+ entity = klass.new()
321
+ entity.og_deserialize(res, 0)
322
+
323
+ # get_join_fields(res, 0, entity, join_fields) if join_fields
324
+
325
+ res.clear()
326
+ return entity
327
+ end
328
+
329
+ # Deserialize all rows of the resultset.
330
+ #
331
+ def deserialize_all(res, klass)
332
+ return nil unless valid?(res)
333
+
334
+ entities = []
335
+
336
+ for tuple in (0...res.num_tuples)
337
+ entity = klass.new()
338
+ entity.og_deserialize(res, tuple)
339
+
340
+ # get_join_fields(res, tuple, entity, join_fields) if join_fields
341
+
342
+ entities << entity
343
+ end
344
+
345
+ res.clear()
346
+ return entities
347
+ end
348
+
349
+ end
350
+
351
+ end # module
@@ -0,0 +1,253 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ #
4
+ # (c) 2004 Navel, all rights reserved.
5
+ # $Id: connection.rb 124 2004-11-01 12:34:17Z gmosx $
6
+
7
+ module N;
8
+
9
+ require "n/properties"
10
+ require "n/utils/array"
11
+ require "n/utils/time"
12
+
13
+ # = OgConnection
14
+ #
15
+ # A Connection to the Database. This file defines the skeleton
16
+ # functionality. A backend specific implementation file (driver)
17
+ # implements all methods.
18
+ #
19
+ # === Future
20
+ #
21
+ # - support caching.
22
+ # - support prepared statements.
23
+ #
24
+ class OgConnection
25
+ # The frontend (Og) contains useful strucutres.
26
+ attr_reader :og
27
+
28
+ # The backend
29
+ attr_reader :db
30
+
31
+ # If set to true, the select methods deserialize the
32
+ # resultset to create entities.
33
+ attr_accessor :deserialize
34
+
35
+ # Initialize a connection to the database
36
+ #
37
+ def initialize(og)
38
+ @og = og
39
+ @db = @og.config[:backend].new(@og.config)
40
+ @deserialize = true
41
+ $log.debug "Created DB connection."
42
+ end
43
+
44
+ # Close the connection to the database
45
+ #
46
+ def close()
47
+ @db.close()
48
+ $log.debug "Closed DB connection."
49
+ end
50
+
51
+ # Save an object to the database. Insert if this is a new object or
52
+ # update if this is already stored in the database.
53
+ #
54
+ def save(obj)
55
+ if obj.oid
56
+ # object allready inserted, update!
57
+ obj.og_update(self)
58
+ else
59
+ # not in the database, insert!
60
+ obj.og_insert(self)
61
+ end
62
+ end
63
+ alias_method :<<, :save
64
+ alias_method :put, :save
65
+
66
+ # Force insertion of managed object.
67
+ #
68
+ def insert(obj)
69
+ obj.og_insert(self)
70
+ end
71
+
72
+ # Force update of managed object.
73
+ #
74
+ def update(obj)
75
+ obj.og_update(self)
76
+ end
77
+
78
+ # Update only specific fields of the managed object.
79
+ #
80
+ # Input:
81
+ # sql = the sql code to updated the properties.
82
+ #
83
+ # WARNING: the object in memoryis not updated.
84
+ #--
85
+ # TODO: should update the object in memory.
86
+ #++
87
+ #
88
+ def update_properties(update_sql, obj_or_oid, klass = nil)
89
+ oid = obj_or_oid.to_i
90
+ klass = obj_or_oid.class unless klass
91
+
92
+ exec "UPDATE #{klass::DBTABLE} SET #{update_sql} WHERE oid=#{oid}"
93
+ end
94
+ alias_method :pupdate, :update_properties
95
+
96
+ # Load an object from the database.
97
+ #
98
+ # Input:
99
+ # oid = the object oid, OR the object name.
100
+ #
101
+ def load(oid, klass)
102
+ if oid.to_i > 0 # a valid Fixnum ?
103
+ load_by_oid(oid, klass)
104
+ else
105
+ load_by_name(oid, klass)
106
+ end
107
+ end
108
+ alias_method :get, :load
109
+
110
+ # Load an object by oid.
111
+ #
112
+ def load_by_oid(oid, klass)
113
+ res = query "SELECT * FROM #{klass::DBTABLE} WHERE oid=#{oid}"
114
+ @deserialize? @db.deserialize_one(res, klass) : res
115
+ end
116
+ alias_method :get_by_oid, :load_by_oid
117
+
118
+ # Load an object by name.
119
+ #
120
+ def load_by_name(name, klass)
121
+ res = query "SELECT * FROM #{klass::DBTABLE} WHERE name='#{name}'"
122
+ @deserialize? @db.deserialize_one(res, klass) : res
123
+ end
124
+ alias_method :get_by_name, :load_by_name
125
+
126
+ # Load all objects of the given klass.
127
+ # Used to be called 'collect' in an earlier version.
128
+ #
129
+ def load_all(klass, extrasql = nil)
130
+ res = query "SELECT * FROM #{klass::DBTABLE} #{extrasql}"
131
+ @deserialize? @db.deserialize_all(res, klass) : res
132
+ end
133
+ alias_method :get_all, :load_all
134
+
135
+ # Perform a standard SQL query to the database. Deserializes the
136
+ # results.
137
+ #
138
+ def select(sql, klass)
139
+ unless sql =~ /SELECT/i
140
+ sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
141
+ end
142
+
143
+ res = query(sql)
144
+ @deserialize? @db.deserialize_all(res, klass) : res
145
+ end
146
+
147
+ # Optimized for one result.
148
+ #
149
+ def select_one(sql, klass)
150
+ unless sql =~ /SELECT/i
151
+ sql = "SELECT * FROM #{klass::DBTABLE} WHERE #{sql}"
152
+ end
153
+
154
+ res = query(sql)
155
+ @deserialize? @db.deserialize_one(res, klass) : res
156
+ end
157
+
158
+ # Delete an object from the database. Allways perform a deep delete.
159
+ #
160
+ # No need to optimize here with pregenerated code. Deletes are
161
+ # not used as much as reads or writes.
162
+ #
163
+ # === Input:
164
+ #
165
+ # obj_or_oid = Object or oid to delete.
166
+ # klass = Class of object (can be nil if an object is passed)
167
+ #
168
+ def delete(obj_or_oid, klass = nil, cascade = true)
169
+ oid = obj_or_oid.to_i
170
+ klass = obj_or_oid.class unless klass
171
+
172
+ # this is a class callback!
173
+ if klass.respond_to?(:og_pre_delete)
174
+ klass.og_pre_delete(self, oid)
175
+ end
176
+
177
+ # TODO: implement this as stored procedure? naaah.
178
+ transaction do |tx|
179
+ tx.exec "DELETE FROM #{klass::DBTABLE} WHERE oid=#{oid}"
180
+
181
+ if cascade and klass.respond_to?(:og_descendants)
182
+ klass.og_descendants.each do |dclass, linkback|
183
+ tx.exec "DELETE FROM #{dclass::DBTABLE} WHERE #{linkback}=#{oid}"
184
+ end
185
+ end
186
+ end
187
+ end
188
+ alias_method :delete!, :delete
189
+
190
+ # Create the managed object table. The properties of the
191
+ # object are mapped to the table columns. Additional sql relations
192
+ # and constrains are created (indicices, sequences, etc).
193
+ #
194
+ def create_table(klass)
195
+ @db.create_table(klass)
196
+ end
197
+
198
+ # Drop the managed object table.
199
+ #
200
+ def drop_table(klass)
201
+ @db.drop_table(klass)
202
+ end
203
+
204
+ # Execute an SQL query and return the result
205
+ #
206
+ def query(sql)
207
+ @db.safe_query(sql)
208
+ end
209
+
210
+ # Execute an SQL query, no result returned.
211
+ #
212
+ def exec(sql)
213
+ @db.safe_exec(sql)
214
+ end
215
+
216
+ # Start a new transaction.
217
+ #
218
+ def start
219
+ @db.start()
220
+ end
221
+
222
+ # Commit a transaction.
223
+ #
224
+ def commit
225
+ @db.commit()
226
+ end
227
+
228
+ # Rollback transaction.
229
+ #
230
+ def rollback
231
+ @db.rollback()
232
+ end
233
+
234
+ # Transaction helper. In the transaction block use
235
+ # the db pointer to the backend.
236
+ #
237
+ def transaction(&block)
238
+ begin
239
+ @db.start()
240
+ yield(@db)
241
+ @db.commit()
242
+ rescue => ex
243
+ $log.error "DB Error: ERROR IN TRANSACTION"
244
+ $log.error #{ex}
245
+ $log.error #{ex.backtrace}
246
+ @db.rollback()
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+ end # module
253
+