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,252 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ #
4
+ # (c) 2004 Navel, all rights reserved.
5
+ # $Id: backend.rb 123 2004-11-01 11:55:11Z gmosx $
6
+
7
+ require "yaml"
8
+
9
+ require "n/og/connection"
10
+
11
+ module N
12
+
13
+ # = OgUtils
14
+ #
15
+ # A collection of useful utilities.
16
+ #
17
+ module OgUtils
18
+
19
+ # The name of the SQL table where objects of this class are stored.
20
+ # The Module separators are replaced with _ and NOT stripped out so
21
+ # that we can convert back to the original notation if needed.
22
+ # The leading module if available is removed.
23
+ #
24
+ def self.table(klass)
25
+ return "_#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
26
+ end
27
+
28
+ # Returns the props that will be included in the insert query.
29
+ # For some backends the oid should be stripped.
30
+ #
31
+ def self.props_for_insert(klass)
32
+ klass.__props
33
+ end
34
+
35
+ # Precompile the insert code for the given class.
36
+ # The generated code sets the oid when inserting!
37
+ #
38
+ def self.eval_og_insert(klass)
39
+ props = props_for_insert(klass)
40
+
41
+ values = props.collect { |p| write_prop(p) }
42
+
43
+ sql = "INSERT INTO #{table(klass)} (#{props.collect {|p| p.name}.join(',')}) VALUES (#{values.join(',')})"
44
+
45
+ if klass.instance_methods.include?("og_pre_insert")
46
+ pre_cb = "og_pre_insert(conn);"
47
+ else
48
+ pre_cb = ""
49
+ end
50
+
51
+ if klass.instance_methods.include?("og_post_insert")
52
+ post_cb = "og_post_insert(conn);"
53
+ else
54
+ post_cb = ""
55
+ end
56
+
57
+ if klass.instance_methods.include?("og_pre_insert_update")
58
+ pre_cb << "og_pre_insert_update(conn);"
59
+ end
60
+
61
+ if klass.instance_methods.include?("og_post_insert_update")
62
+ post_cb << "og_post_insert_update(conn);"
63
+ end
64
+
65
+ klass.class_eval %{
66
+ def og_insert(conn)
67
+ #{insert_code(klass, sql, pre_cb, post_cb)}
68
+ end
69
+ }
70
+ end
71
+
72
+ # Precompile the update code for the given class.
73
+ # Ignore the oid when updating!
74
+ #
75
+ def self.eval_og_update(klass)
76
+ props = klass.__props.reject { |p| :oid == p.symbol }
77
+
78
+ updates = props.collect { |p|
79
+ "#{p.name}=#{write_prop(p)}"
80
+ }
81
+
82
+ sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"
83
+
84
+ if klass.instance_methods.include?("og_pre_update")
85
+ pre_cb = "og_pre_update(conn);"
86
+ else
87
+ pre_cb = ""
88
+ end
89
+
90
+ if klass.instance_methods.include?("og_post_update")
91
+ post_cb = "og_post_update(conn);"
92
+ else
93
+ post_cb = ""
94
+ end
95
+
96
+ if klass.instance_methods.include?("og_pre_insert_update")
97
+ pre_cb << "og_pre_insert_update(conn);"
98
+ end
99
+
100
+ if klass.instance_methods.include?("og_post_insert_update")
101
+ post_cb << "og_post_insert_update(conn);"
102
+ end
103
+
104
+ klass.class_eval %{
105
+ def og_update(conn)
106
+ #{pre_cb}
107
+ conn.exec "#{sql}"
108
+ #{post_cb}
109
+ end
110
+ }
111
+ end
112
+
113
+ # Precompile the code to read objects of the given class
114
+ # from the backend. In order to allow for changing field/attribute
115
+ # orders we have to use a field mapping hash.
116
+ #
117
+ def self.eval_og_deserialize(klass, og)
118
+ calc_field_index(klass, og)
119
+
120
+ props = klass.__props
121
+ code = []
122
+
123
+ props.each do |p|
124
+ if idx = og.managed_classes[klass].field_index[p.name]
125
+ # more fault tolerant if a new field is added and it
126
+ # doesnt exist in the database.
127
+ code << "@#{p.name} = #{N::OgUtils.read_prop(p, idx)}"
128
+ end
129
+ end
130
+
131
+ klass.class_eval %{
132
+ def og_deserialize(res, tuple = nil)
133
+ #{code.join('; ')}
134
+ end
135
+ }
136
+ end
137
+
138
+ end
139
+
140
+ # = OgBackend
141
+ #
142
+ # Abstract backend. A backend communicates with the RDBMS.
143
+ # This is the base class for the various backend implementations.
144
+ #
145
+ class OgBackend
146
+
147
+ # The actual connection to the database
148
+ attr_accessor :conn
149
+
150
+ # Intitialize the connection to the RDBMS.
151
+ #
152
+ def initialize(config)
153
+ raise "Not implemented"
154
+ end
155
+
156
+ # Close the connection to the RDBMS.
157
+ #
158
+ def close()
159
+ @conn.close()
160
+ end
161
+
162
+ # Create the database.
163
+ #
164
+ def self.create_db(database, user = nil, password = nil)
165
+ $log.info "Creating database '#{database}'."
166
+ end
167
+
168
+ # Drop the database.
169
+ #
170
+ def self.drop_db(database, user = nil, password = nil)
171
+ $log.info "Dropping database '#{database}'."
172
+ end
173
+
174
+ # Execute an SQL query and return the result.
175
+ #
176
+ def query(sql)
177
+ raise "Not implemented"
178
+ end
179
+
180
+ # Execute an SQL query, no result returned.
181
+ #
182
+ def exec(sql)
183
+ raise "Not implemented"
184
+ end
185
+
186
+ # Execute an SQL query and return the result. Wrapped in a rescue
187
+ # block.
188
+ #
189
+ def safe_query(sql)
190
+ raise "Not implemented"
191
+ end
192
+
193
+ # Execute an SQL query, no result returned. Wrapped in a rescue
194
+ # block.
195
+ #
196
+ def safe_exec(sql)
197
+ raise "Not implemented"
198
+ end
199
+
200
+ # Check if it is a valid resultset.
201
+ #
202
+ def valid?(res)
203
+ raise "Not implemented"
204
+ end
205
+
206
+ # Start a new transaction.
207
+ #
208
+ def start
209
+ exec "START TRANSACTION"
210
+ end
211
+
212
+ # Commit a transaction.
213
+ #
214
+ def commit
215
+ exec "COMMIT"
216
+ end
217
+
218
+ # Rollback transaction.
219
+ #
220
+ def rollback
221
+ exec "ROLLBACK"
222
+ end
223
+
224
+ # Create the managed object table. The properties of the
225
+ # object are mapped to the table columns. Additional sql relations
226
+ # and constrains are created (indicices, sequences, etc).
227
+ #
228
+ def create_table(klass)
229
+ return if query("SELECT * FROM #{klass::DBTABLE} LIMIT 1")
230
+ end
231
+
232
+ # Drop the managed object table
233
+ #
234
+ def drop_table(klass)
235
+ exec "DROP TABLE #{klass::DBTABLE}"
236
+ end
237
+
238
+ # Deserialize one row of the resultset.
239
+ #
240
+ def deserialize_one(res, klass)
241
+ raise "Not implemented"
242
+ end
243
+
244
+ # Deserialize all rows of the resultset.
245
+ #
246
+ def deserialize_all(res, klass)
247
+ raise "Not implemented"
248
+ end
249
+
250
+ end
251
+
252
+ end # module
@@ -0,0 +1,352 @@
1
+ # code:
2
+ # * George Moschovitis <gm@navel.gr>
3
+ # * Elias Athanasopoulos <elathan@navel.gr>
4
+ #
5
+ # (c) 2004 Navel, all rights reserved.
6
+ # $Id: mysql.rb 123 2004-11-01 11:55:11Z gmosx $
7
+
8
+ require "mysql"
9
+
10
+ require "n/og/backend"
11
+
12
+ module N
13
+
14
+ # = OgUtils
15
+ #
16
+ # A collection of useful utilities.
17
+ #
18
+ module OgUtils
19
+
20
+ # Escape an SQL string
21
+ #
22
+ def self.escape(str)
23
+ return nil unless str
24
+ return Mysql.quote(str)
25
+ end
26
+
27
+ # Convert a ruby time to an sql timestamp.
28
+ # TODO: Optimize this
29
+ #
30
+ def self.timestamp(time = Time.now)
31
+ return nil unless time
32
+ return time.strftime("%Y%m%d%H%M%S")
33
+ end
34
+
35
+ # Output YYY-mm-dd
36
+ # TODO: Optimize this
37
+ #
38
+ def self.date(date)
39
+ return nil unless date
40
+ return "#{date.year}-#{date.month}-#{date.mday}"
41
+ end
42
+
43
+ # Parse sql datetime
44
+ # TODO: Optimize this
45
+ #
46
+ def self.parse_timestamp(str)
47
+ return Time.parse(str)
48
+ end
49
+
50
+ # Input YYYY-mm-dd
51
+ # TODO: Optimize this
52
+ #
53
+ def self.parse_date(str)
54
+ return nil unless str
55
+ return Date.strptime(str)
56
+ end
57
+
58
+ # Return an sql string evaluator for the property.
59
+ # No need to optimize this, used only to precalculate code.
60
+ # YAML is used to store general Ruby objects to be more
61
+ # portable.
62
+ #
63
+ # FIXME: add extra handling for float.
64
+ #
65
+ def self.write_prop(p)
66
+ if String == p.klass
67
+ return "'#\{N::OgUtils.escape(@#{p.symbol})\}'"
68
+ elsif Time == p.klass
69
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
70
+ elsif Date == p.klass
71
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.date(@#{p.symbol})\}'" : 'NULL'\}|
72
+ elsif Object == p.klass or Array == p.klass or Hash == p.klass
73
+ return %|#\{@#{p.symbol} ? "'#\{N::OgUtils.escape(@#{p.symbol}.to_yaml)\}'" : ''\}|
74
+ else
75
+ # Fixnum, TrueClass
76
+ return "#\{@#{p.symbol} || 'NULL'\}"
77
+ end
78
+ end
79
+
80
+ # Return an evaluator for reading the property.
81
+ # No need to optimize this, used only to precalculate code.
82
+ #
83
+ def self.read_prop(p, idx)
84
+ case p.klass.to_s
85
+ when Fixnum.name
86
+ return "res[#{idx}].to_i()"
87
+ when Float.name
88
+ return "res[#{idx}].to_f()"
89
+ when Time.name
90
+ return "N::OgUtils.parse_timestamp(res[#{idx}])"
91
+ when Date.name
92
+ return "N::OgUtils.parse_date(res[#{idx}])"
93
+ when TrueClass.name
94
+ return "('true' == res[#{idx}])"
95
+ when Object.name
96
+ return "YAML::load(res[#{idx}])"
97
+ when Array.name
98
+ return "YAML::load(res[#{idx}])"
99
+ when Hash.name
100
+ return "YAML::load(res[#{idx}])"
101
+ else # String
102
+ return "res[#{idx}]"
103
+ end
104
+ end
105
+
106
+ # Returns the props that will be included in the insert query.
107
+ # The oid property is rejected because it is mapped to an
108
+ # AUTO_INCREMENT column.
109
+ #
110
+ def self.props_for_insert(klass)
111
+ klass.__props.reject { |p| :oid == p.symbol }
112
+ end
113
+
114
+ # Returns the code that actually inserts the object into the
115
+ # database. Returns the code as String.
116
+ #
117
+ def self.insert_code(klass, sql, pre_cb, post_cb)
118
+ %{
119
+ #{pre_cb}
120
+ conn.exec "#{sql}"
121
+ @oid = conn.db.conn.insert_id()
122
+ #{post_cb}
123
+ }
124
+ end
125
+
126
+ # generate the mapping of the database fields to the
127
+ # object properties.
128
+ #
129
+ def self.calc_field_index(klass, og)
130
+ res = og.query "SELECT * FROM #{klass::DBTABLE} LIMIT 1"
131
+ meta = og.managed_classes[klass]
132
+
133
+ for idx in (0...res.num_fields)
134
+ meta.field_index[res.fetch_field.name] = idx
135
+ end
136
+ end
137
+
138
+ # Generate the property for oid
139
+ #
140
+ def self.eval_og_oid(klass)
141
+ klass.class_eval %{
142
+ prop_accessor Fixnum, "integer AUTO_INCREMENT PRIMARY KEY", :oid
143
+ }
144
+ end
145
+ end
146
+
147
+ # = MysqlBackend
148
+ #
149
+ # Implements a MySQL powered backend.
150
+ #
151
+ class MysqlBackend < N::OgBackend
152
+
153
+ # A mapping between Ruby and SQL types.
154
+ #
155
+ TYPEMAP = {
156
+ Integer => "integer",
157
+ Fixnum => "integer",
158
+ Float => "float",
159
+ String => "text",
160
+ Time => "timestamp",
161
+ Date => "date",
162
+ TrueClass => "boolean",
163
+ Object => "text",
164
+ Array => "text",
165
+ Hash => "text"
166
+ }
167
+
168
+ # Intitialize the connection to the RDBMS.
169
+ #
170
+ def initialize(config)
171
+ begin
172
+ @conn = Mysql.connect(config[:address], config[:user],
173
+ config[:password], config[:database])
174
+ rescue => ex
175
+ if ex.errno == 1049 # database does not exist.
176
+ $log.info "Database '#{config[:database]}' not found!"
177
+ MysqlBackend.create_db(config[:database], config[:user], config[:password])
178
+ retry
179
+ end
180
+ raise
181
+ end
182
+ end
183
+
184
+ # Create the database.
185
+ #
186
+ def self.create_db(database, user = nil, password = nil)
187
+ $log.info "Creating database '#{database}'."
188
+ `mysqladmin -f --user=#{user} --password=#{password} create #{database}`
189
+ end
190
+
191
+ # Drop the database.
192
+ #
193
+ def self.drop_db(database, user = nil, password = nil)
194
+ $log.info "Dropping database '#{database}'."
195
+ `mysqladmin -f --user=#{user} --password=#{password} drop #{database}`
196
+ end
197
+
198
+ # Execute an SQL query and return the result
199
+ #
200
+ def query(sql)
201
+ $log.debug sql if $DBG
202
+ return @conn.query(sql)
203
+ end
204
+
205
+ # Execute an SQL query, no result returned.
206
+ #
207
+ def exec(sql)
208
+ $log.debug sql if $DBG
209
+ @conn.query(sql)
210
+ end
211
+
212
+ # Execute an SQL query and return the result. Wrapped in a rescue
213
+ # block.
214
+ #
215
+ def safe_query(sql)
216
+ $log.debug sql if $DBG
217
+ begin
218
+ return @conn.query(sql)
219
+ rescue => ex
220
+ $log.error "DB error #{ex}, [#{sql}]"
221
+ $log.error ex.backtrace
222
+ return nil
223
+ end
224
+ end
225
+
226
+ # Execute an SQL query, no result returned. Wrapped in a rescue
227
+ # block.
228
+ #
229
+ def safe_exec(sql)
230
+ $log.debug sql if $DBG
231
+ begin
232
+ @conn.query(sql)
233
+ rescue => ex
234
+ $log.error "DB error #{ex}, [#{sql}]"
235
+ $log.error ex.backtrace
236
+ end
237
+ end
238
+
239
+ # Check if it is a valid resultset.
240
+ #
241
+ def valid?(res)
242
+ return !(res.nil? or 0 == res.num_rows)
243
+ end
244
+
245
+ # Start a new transaction.
246
+ #
247
+ def start
248
+ # no transaction support
249
+ end
250
+
251
+ # Commit a transaction.
252
+ #
253
+ def commit
254
+ # no transaction support
255
+ end
256
+
257
+ # Rollback transaction.
258
+ #
259
+ def rollback
260
+ # no transaction support
261
+ end
262
+
263
+ # Create the managed object table. The properties of the
264
+ # object are mapped to the table columns. Additional sql relations
265
+ # and constrains are created (indicices, sequences, etc).
266
+ #
267
+ def create_table(klass)
268
+ fields = []
269
+
270
+ klass.__props.each do |p|
271
+ field = "#{p.symbol}"
272
+
273
+ if p.sql
274
+ field << " #{p.sql}"
275
+ else
276
+ field << " #{TYPEMAP[p.klass]}"
277
+ end
278
+
279
+ fields << field
280
+ end
281
+
282
+ sql = "CREATE TABLE #{klass::DBTABLE} (#{fields.join(', ')}"
283
+
284
+ # Create table constrains
285
+
286
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
287
+ sql << ", #{constrains.join(', ')}"
288
+ end
289
+
290
+ sql << ");"
291
+
292
+ # Create indices
293
+
294
+ if klass.__meta
295
+ for data in klass.__meta[:sql_index]
296
+ idx, pre_sql, post_sql = *data
297
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
298
+ sql << " CREATE #{pre_sql} INDEX #{klass::DBTABLE}_#{idxname}_idx #{post_sql} ON #{klass::DBTABLE} (#{idx});"
299
+ end
300
+ end
301
+
302
+ begin
303
+ exec(sql)
304
+ $log.info "Created table '#{klass::DBTABLE}'."
305
+ rescue => ex
306
+ if ex.errno == 1050 # table already exists.
307
+ $log.debug "Table already exists" if $DBG
308
+ else
309
+ raise
310
+ end
311
+ end
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
+ row = res.fetch_row()
321
+ entity = klass.new()
322
+ entity.og_deserialize(row)
323
+
324
+ # get_join_fields(res, 0, entity, join_fields) if join_fields
325
+
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_rows)
337
+ row = res.fetch_row()
338
+
339
+ entity = klass.new()
340
+ entity.og_deserialize(row)
341
+
342
+ # get_join_fields(res, tuple, entity, join_fields) if join_fields
343
+
344
+ entities << entity
345
+ end
346
+
347
+ return entities
348
+ end
349
+
350
+ end
351
+
352
+ end # module