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