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