rhodes-framework 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +37 -0
  3. data/Manifest.txt +66 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +50 -0
  6. data/lib/ServeME.rb +7 -0
  7. data/lib/TestServe.rb +9 -0
  8. data/lib/bsearch.rb +120 -0
  9. data/lib/builtinME.rb +626 -0
  10. data/lib/date/format.rb +1339 -0
  11. data/lib/date.rb +1792 -0
  12. data/lib/dateME.rb +24 -0
  13. data/lib/erb.rb +896 -0
  14. data/lib/find.rb +81 -0
  15. data/lib/rational.rb +19 -0
  16. data/lib/rationalME.rb +530 -0
  17. data/lib/rho/render.rb +51 -0
  18. data/lib/rho/rho.rb +255 -0
  19. data/lib/rho/rhoapplication.rb +36 -0
  20. data/lib/rho/rhocontact.rb +110 -0
  21. data/lib/rho/rhocontroller.rb +35 -0
  22. data/lib/rho/rhofsconnector.rb +32 -0
  23. data/lib/rho/rhosupport.rb +146 -0
  24. data/lib/rho/rhoviewhelpers.rb +130 -0
  25. data/lib/rho.rb +1 -0
  26. data/lib/rhodes-framework.rb +2 -0
  27. data/lib/rhodes.rb +9 -0
  28. data/lib/rhoframework.rb +38 -0
  29. data/lib/rhofsconnector.rb +1 -0
  30. data/lib/rhom/rhom.rb +58 -0
  31. data/lib/rhom/rhom_db_adapter.rb +185 -0
  32. data/lib/rhom/rhom_db_adapterME.rb +93 -0
  33. data/lib/rhom/rhom_object.rb +69 -0
  34. data/lib/rhom/rhom_object_factory.rb +309 -0
  35. data/lib/rhom/rhom_source.rb +60 -0
  36. data/lib/rhom.rb +1 -0
  37. data/lib/singleton.rb +137 -0
  38. data/lib/time.rb +489 -0
  39. data/lib/version.rb +8 -0
  40. data/res/sqlite3/constants.rb +49 -0
  41. data/res/sqlite3/database.rb +715 -0
  42. data/res/sqlite3/driver/dl/api.rb +154 -0
  43. data/res/sqlite3/driver/dl/driver.rb +307 -0
  44. data/res/sqlite3/driver/native/driver.rb +257 -0
  45. data/res/sqlite3/errors.rb +68 -0
  46. data/res/sqlite3/pragmas.rb +271 -0
  47. data/res/sqlite3/resultset.rb +176 -0
  48. data/res/sqlite3/sqlite3_api.rb +0 -0
  49. data/res/sqlite3/statement.rb +230 -0
  50. data/res/sqlite3/translator.rb +109 -0
  51. data/res/sqlite3/value.rb +57 -0
  52. data/res/sqlite3/version.rb +14 -0
  53. data/rhodes-framework.gemspec +18 -0
  54. data/rhodes.gemspec +18 -0
  55. data/spec/app_manifest.txt +4 -0
  56. data/spec/configs/account.rb +3 -0
  57. data/spec/configs/case.rb +3 -0
  58. data/spec/configs/employee.rb +3 -0
  59. data/spec/rho_controller_spec.rb +144 -0
  60. data/spec/rho_spec.rb +75 -0
  61. data/spec/rhom_object_factory_spec.rb +372 -0
  62. data/spec/rhom_spec.rb +45 -0
  63. data/spec/spec.opts +1 -0
  64. data/spec/spec_helper.rb +49 -0
  65. data/spec/stubs.rb +39 -0
  66. data/spec/syncdbtest.sqlite +0 -0
  67. metadata +202 -0
@@ -0,0 +1,715 @@
1
+ require 'sqlite3/constants'
2
+ require 'sqlite3/errors'
3
+ require 'sqlite3/pragmas'
4
+ require 'sqlite3/statement'
5
+ require 'sqlite3/translator'
6
+ require 'sqlite3/value'
7
+
8
+ module SQLite3
9
+
10
+ # The Database class encapsulates a single connection to a SQLite3 database.
11
+ # Its usage is very straightforward:
12
+ #
13
+ # require 'sqlite3'
14
+ #
15
+ # db = SQLite3::Database.new( "data.db" )
16
+ #
17
+ # db.execute( "select * from table" ) do |row|
18
+ # p row
19
+ # end
20
+ #
21
+ # db.close
22
+ #
23
+ # It wraps the lower-level methods provides by the selected driver, and
24
+ # includes the Pragmas module for access to various pragma convenience
25
+ # methods.
26
+ #
27
+ # The Database class provides type translation services as well, by which
28
+ # the SQLite3 data types (which are all represented as strings) may be
29
+ # converted into their corresponding types (as defined in the schemas
30
+ # for their tables). This translation only occurs when querying data from
31
+ # the database--insertions and updates are all still typeless.
32
+ #
33
+ # Furthermore, the Database class has been designed to work well with the
34
+ # ArrayFields module from Ara Howard. If you require the ArrayFields
35
+ # module before performing a query, and if you have not enabled results as
36
+ # hashes, then the results will all be indexible by field name.
37
+ class Database
38
+ include Pragmas
39
+
40
+ class <<self
41
+
42
+ alias :open :new
43
+
44
+ # Quotes the given string, making it safe to use in an SQL statement.
45
+ # It replaces all instances of the single-quote character with two
46
+ # single-quote characters. The modified string is returned.
47
+ def quote( string )
48
+ string.gsub( /'/, "''" )
49
+ end
50
+
51
+ end
52
+
53
+ # The low-level opaque database handle that this object wraps.
54
+ attr_reader :handle
55
+
56
+ # A reference to the underlying SQLite3 driver used by this database.
57
+ attr_reader :driver
58
+
59
+ # A boolean that indicates whether rows in result sets should be returned
60
+ # as hashes or not. By default, rows are returned as arrays.
61
+ attr_accessor :results_as_hash
62
+
63
+ # A boolean indicating whether or not type translation is enabled for this
64
+ # database.
65
+ attr_accessor :type_translation
66
+
67
+ # Create a new Database object that opens the given file. If utf16
68
+ # is +true+, the filename is interpreted as a UTF-16 encoded string.
69
+ #
70
+ # By default, the new database will return result rows as arrays
71
+ # (#results_as_hash) and has type translation disabled (#type_translation=).
72
+ def initialize( file_name, options={} )
73
+ utf16 = options.fetch(:utf16, false)
74
+ load_driver( options[:driver] )
75
+
76
+ @statement_factory = options[:statement_factory] || Statement
77
+
78
+ result, @handle = @driver.open( file_name, utf16 )
79
+ Error.check( result, self, "could not open database" )
80
+
81
+ @closed = false
82
+ @results_as_hash = options.fetch(:results_as_hash,false)
83
+ @type_translation = options.fetch(:type_translation,false)
84
+ @translator = nil
85
+ @transaction_active = false
86
+ end
87
+
88
+ # Return +true+ if the string is a valid (ie, parsable) SQL statement, and
89
+ # +false+ otherwise. If +utf16+ is +true+, then the string is a UTF-16
90
+ # character string.
91
+ def complete?( string, utf16=false )
92
+ @driver.complete?( string, utf16 )
93
+ end
94
+
95
+ # Return a string describing the last error to have occurred with this
96
+ # database.
97
+ def errmsg( utf16=false )
98
+ @driver.errmsg( @handle, utf16 )
99
+ end
100
+
101
+ # Return an integer representing the last error to have occurred with this
102
+ # database.
103
+ def errcode
104
+ @driver.errcode( @handle )
105
+ end
106
+
107
+ # Return the type translator employed by this database instance. Each
108
+ # database instance has its own type translator; this allows for different
109
+ # type handlers to be installed in each instance without affecting other
110
+ # instances. Furthermore, the translators are instantiated lazily, so that
111
+ # if a database does not use type translation, it will not be burdened by
112
+ # the overhead of a useless type translator. (See the Translator class.)
113
+ def translator
114
+ @translator ||= Translator.new
115
+ end
116
+
117
+ # Closes this database.
118
+ def close
119
+ unless @closed
120
+ result = @driver.close( @handle )
121
+ Error.check( result, self )
122
+ end
123
+ @closed = true
124
+ end
125
+
126
+ # Returns +true+ if this database instance has been closed (see #close).
127
+ def closed?
128
+ @closed
129
+ end
130
+
131
+ # Installs (or removes) a block that will be invoked for every SQL
132
+ # statement executed. The block receives a two parameters: the +data+
133
+ # argument, and the SQL statement executed. If the block is +nil+,
134
+ # any existing tracer will be uninstalled.
135
+ def trace( data=nil, &block )
136
+ @driver.trace( @handle, data, &block )
137
+ end
138
+
139
+ # Installs (or removes) a block that will be invoked for every access
140
+ # to the database. If the block returns 0 (or +nil+), the statement
141
+ # is allowed to proceed. Returning 1 causes an authorization error to
142
+ # occur, and returning 2 causes the access to be silently denied.
143
+ def authorizer( data=nil, &block )
144
+ result = @driver.set_authorizer( @handle, data, &block )
145
+ Error.check( result, self )
146
+ end
147
+
148
+ # Returns a Statement object representing the given SQL. This does not
149
+ # execute the statement; it merely prepares the statement for execution.
150
+ #
151
+ # The Statement can then be executed using Statement#execute.
152
+ #
153
+ def prepare( sql )
154
+ stmt = @statement_factory.new( self, sql )
155
+ if block_given?
156
+ begin
157
+ yield stmt
158
+ ensure
159
+ stmt.close
160
+ end
161
+ else
162
+ return stmt
163
+ end
164
+ end
165
+
166
+ # Executes the given SQL statement. If additional parameters are given,
167
+ # they are treated as bind variables, and are bound to the placeholders in
168
+ # the query.
169
+ #
170
+ # Note that if any of the values passed to this are hashes, then the
171
+ # key/value pairs are each bound separately, with the key being used as
172
+ # the name of the placeholder to bind the value to.
173
+ #
174
+ # The block is optional. If given, it will be invoked for each row returned
175
+ # by the query. Otherwise, any results are accumulated into an array and
176
+ # returned wholesale.
177
+ #
178
+ # See also #execute2, #query, and #execute_batch for additional ways of
179
+ # executing statements.
180
+ def execute( sql, *bind_vars )
181
+ prepare( sql ) do |stmt|
182
+ result = stmt.execute( *bind_vars )
183
+ if block_given?
184
+ result.each { |row| yield row }
185
+ else
186
+ return result.inject( [] ) { |arr,row| arr << row; arr }
187
+ end
188
+ end
189
+ end
190
+
191
+ # Executes the given SQL statement, exactly as with #execute. However, the
192
+ # first row returned (either via the block, or in the returned array) is
193
+ # always the names of the columns. Subsequent rows correspond to the data
194
+ # from the result set.
195
+ #
196
+ # Thus, even if the query itself returns no rows, this method will always
197
+ # return at least one row--the names of the columns.
198
+ #
199
+ # See also #execute, #query, and #execute_batch for additional ways of
200
+ # executing statements.
201
+ def execute2( sql, *bind_vars )
202
+ prepare( sql ) do |stmt|
203
+ result = stmt.execute( *bind_vars )
204
+ if block_given?
205
+ yield result.columns
206
+ result.each { |row| yield row }
207
+ else
208
+ return result.inject( [ result.columns ] ) { |arr,row|
209
+ arr << row; arr }
210
+ end
211
+ end
212
+ end
213
+
214
+ # Executes all SQL statements in the given string. By contrast, the other
215
+ # means of executing queries will only execute the first statement in the
216
+ # string, ignoring all subsequent statements. This will execute each one
217
+ # in turn. The same bind parameters, if given, will be applied to each
218
+ # statement.
219
+ #
220
+ # This always returns +nil+, making it unsuitable for queries that return
221
+ # rows.
222
+ def execute_batch( sql, *bind_vars )
223
+ sql = sql.strip
224
+ until sql.empty? do
225
+ prepare( sql ) do |stmt|
226
+ stmt.execute( *bind_vars )
227
+ sql = stmt.remainder.strip
228
+ end
229
+ end
230
+ nil
231
+ end
232
+
233
+ # This is a convenience method for creating a statement, binding
234
+ # paramters to it, and calling execute:
235
+ #
236
+ # result = db.query( "select * from foo where a=?", 5 )
237
+ # # is the same as
238
+ # result = db.prepare( "select * from foo where a=?" ).execute( 5 )
239
+ #
240
+ # You must be sure to call +close+ on the ResultSet instance that is
241
+ # returned, or you could have problems with locks on the table. If called
242
+ # with a block, +close+ will be invoked implicitly when the block
243
+ # terminates.
244
+ def query( sql, *bind_vars )
245
+ result = prepare( sql ).execute( *bind_vars )
246
+ if block_given?
247
+ begin
248
+ yield result
249
+ ensure
250
+ result.close
251
+ end
252
+ else
253
+ return result
254
+ end
255
+ end
256
+
257
+ # A convenience method for obtaining the first row of a result set, and
258
+ # discarding all others. It is otherwise identical to #execute.
259
+ #
260
+ # See also #get_first_value.
261
+ def get_first_row( sql, *bind_vars )
262
+ execute( sql, *bind_vars ) { |row| return row }
263
+ nil
264
+ end
265
+
266
+ # A convenience method for obtaining the first value of the first row of a
267
+ # result set, and discarding all other values and rows. It is otherwise
268
+ # identical to #execute.
269
+ #
270
+ # See also #get_first_row.
271
+ def get_first_value( sql, *bind_vars )
272
+ execute( sql, *bind_vars ) { |row| return row[0] }
273
+ nil
274
+ end
275
+
276
+ # Obtains the unique row ID of the last row to be inserted by this Database
277
+ # instance.
278
+ def last_insert_row_id
279
+ @driver.last_insert_rowid( @handle )
280
+ end
281
+
282
+ # Returns the number of changes made to this database instance by the last
283
+ # operation performed. Note that a "delete from table" without a where
284
+ # clause will not affect this value.
285
+ def changes
286
+ @driver.changes( @handle )
287
+ end
288
+
289
+ # Returns the total number of changes made to this database instance
290
+ # since it was opened.
291
+ def total_changes
292
+ @driver.total_changes( @handle )
293
+ end
294
+
295
+ # Interrupts the currently executing operation, causing it to abort.
296
+ def interrupt
297
+ @driver.interrupt( @handle )
298
+ end
299
+
300
+ # Register a busy handler with this database instance. When a requested
301
+ # resource is busy, this handler will be invoked. If the handler returns
302
+ # +false+, the operation will be aborted; otherwise, the resource will
303
+ # be requested again.
304
+ #
305
+ # The handler will be invoked with the name of the resource that was
306
+ # busy, and the number of times it has been retried.
307
+ #
308
+ # See also the mutually exclusive #busy_timeout.
309
+ def busy_handler( data=nil, &block ) # :yields: data, retries
310
+ result = @driver.busy_handler( @handle, data, &block )
311
+ Error.check( result, self )
312
+ end
313
+
314
+ # Indicates that if a request for a resource terminates because that
315
+ # resource is busy, SQLite should sleep and retry for up to the indicated
316
+ # number of milliseconds. By default, SQLite does not retry
317
+ # busy resources. To restore the default behavior, send 0 as the
318
+ # +ms+ parameter.
319
+ #
320
+ # See also the mutually exclusive #busy_handler.
321
+ def busy_timeout( ms )
322
+ result = @driver.busy_timeout( @handle, ms )
323
+ Error.check( result, self )
324
+ end
325
+
326
+ # Creates a new function for use in SQL statements. It will be added as
327
+ # +name+, with the given +arity+. (For variable arity functions, use
328
+ # -1 for the arity.)
329
+ #
330
+ # The block should accept at least one parameter--the FunctionProxy
331
+ # instance that wraps this function invocation--and any other
332
+ # arguments it needs (up to its arity).
333
+ #
334
+ # The block does not return a value directly. Instead, it will invoke
335
+ # the FunctionProxy#set_result method on the +func+ parameter and
336
+ # indicate the return value that way.
337
+ #
338
+ # Example:
339
+ #
340
+ # db.create_function( "maim", 1 ) do |func, value|
341
+ # if value.nil?
342
+ # func.result = nil
343
+ # else
344
+ # func.result = value.split(//).sort.join
345
+ # end
346
+ # end
347
+ #
348
+ # puts db.get_first_value( "select maim(name) from table" )
349
+ def create_function( name, arity, text_rep=Constants::TextRep::ANY,
350
+ &block ) # :yields: func, *args
351
+ # begin
352
+ callback = proc do |func,*args|
353
+ begin
354
+ block.call( FunctionProxy.new( @driver, func ),
355
+ *args.map{|v| Value.new(self,v)} )
356
+ rescue StandardError, Exception => e
357
+ @driver.result_error( func,
358
+ "#{e.message} (#{e.class})", -1 )
359
+ end
360
+ end
361
+
362
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
363
+ callback, nil, nil )
364
+ Error.check( result, self )
365
+
366
+ self
367
+ end
368
+
369
+ # Creates a new aggregate function for use in SQL statements. Aggregate
370
+ # functions are functions that apply over every row in the result set,
371
+ # instead of over just a single row. (A very common aggregate function
372
+ # is the "count" function, for determining the number of rows that match
373
+ # a query.)
374
+ #
375
+ # The new function will be added as +name+, with the given +arity+. (For
376
+ # variable arity functions, use -1 for the arity.)
377
+ #
378
+ # The +step+ parameter must be a proc object that accepts as its first
379
+ # parameter a FunctionProxy instance (representing the function
380
+ # invocation), with any subsequent parameters (up to the function's arity).
381
+ # The +step+ callback will be invoked once for each row of the result set.
382
+ #
383
+ # The +finalize+ parameter must be a +proc+ object that accepts only a
384
+ # single parameter, the FunctionProxy instance representing the current
385
+ # function invocation. It should invoke FunctionProxy#set_result to
386
+ # store the result of the function.
387
+ #
388
+ # Example:
389
+ #
390
+ # db.create_aggregate( "lengths", 1 ) do
391
+ # step do |func, value|
392
+ # func[ :total ] ||= 0
393
+ # func[ :total ] += ( value ? value.length : 0 )
394
+ # end
395
+ #
396
+ # finalize do |func|
397
+ # func.set_result( func[ :total ] || 0 )
398
+ # end
399
+ # end
400
+ #
401
+ # puts db.get_first_value( "select lengths(name) from table" )
402
+ #
403
+ # See also #create_aggregate_handler for a more object-oriented approach to
404
+ # aggregate functions.
405
+ def create_aggregate( name, arity, step=nil, finalize=nil,
406
+ text_rep=Constants::TextRep::ANY, &block )
407
+ # begin
408
+ if block
409
+ proxy = AggregateDefinitionProxy.new
410
+ proxy.instance_eval(&block)
411
+ step ||= proxy.step_callback
412
+ finalize ||= proxy.finalize_callback
413
+ end
414
+
415
+ step_callback = proc do |func,*args|
416
+ ctx = @driver.aggregate_context( func )
417
+ unless ctx[:__error]
418
+ begin
419
+ step.call( FunctionProxy.new( @driver, func, ctx ),
420
+ *args.map{|v| Value.new(self,v)} )
421
+ rescue Exception => e
422
+ ctx[:__error] = e
423
+ end
424
+ end
425
+ end
426
+
427
+ finalize_callback = proc do |func|
428
+ ctx = @driver.aggregate_context( func )
429
+ unless ctx[:__error]
430
+ begin
431
+ finalize.call( FunctionProxy.new( @driver, func, ctx ) )
432
+ rescue Exception => e
433
+ @driver.result_error( func,
434
+ "#{e.message} (#{e.class})", -1 )
435
+ end
436
+ else
437
+ e = ctx[:__error]
438
+ @driver.result_error( func,
439
+ "#{e.message} (#{e.class})", -1 )
440
+ end
441
+ end
442
+
443
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
444
+ nil, step_callback, finalize_callback )
445
+ Error.check( result, self )
446
+
447
+ self
448
+ end
449
+
450
+ # This is another approach to creating an aggregate function (see
451
+ # #create_aggregate). Instead of explicitly specifying the name,
452
+ # callbacks, arity, and type, you specify a factory object
453
+ # (the "handler") that knows how to obtain all of that information. The
454
+ # handler should respond to the following messages:
455
+ #
456
+ # +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
457
+ # message is optional, and if the handler does not respond to it,
458
+ # the function will have an arity of -1.
459
+ # +name+:: this is the name of the function. The handler _must_ implement
460
+ # this message.
461
+ # +new+:: this must be implemented by the handler. It should return a new
462
+ # instance of the object that will handle a specific invocation of
463
+ # the function.
464
+ #
465
+ # The handler instance (the object returned by the +new+ message, described
466
+ # above), must respond to the following messages:
467
+ #
468
+ # +step+:: this is the method that will be called for each step of the
469
+ # aggregate function's evaluation. It should implement the same
470
+ # signature as the +step+ callback for #create_aggregate.
471
+ # +finalize+:: this is the method that will be called to finalize the
472
+ # aggregate function's evaluation. It should implement the
473
+ # same signature as the +finalize+ callback for
474
+ # #create_aggregate.
475
+ #
476
+ # Example:
477
+ #
478
+ # class LengthsAggregateHandler
479
+ # def self.arity; 1; end
480
+ #
481
+ # def initialize
482
+ # @total = 0
483
+ # end
484
+ #
485
+ # def step( ctx, name )
486
+ # @total += ( name ? name.length : 0 )
487
+ # end
488
+ #
489
+ # def finalize( ctx )
490
+ # ctx.set_result( @total )
491
+ # end
492
+ # end
493
+ #
494
+ # db.create_aggregate_handler( LengthsAggregateHandler )
495
+ # puts db.get_first_value( "select lengths(name) from A" )
496
+ def create_aggregate_handler( handler )
497
+ arity = -1
498
+ text_rep = Constants::TextRep::ANY
499
+
500
+ arity = handler.arity if handler.respond_to?(:arity)
501
+ text_rep = handler.text_rep if handler.respond_to?(:text_rep)
502
+ name = handler.name
503
+
504
+ step = proc do |func,*args|
505
+ ctx = @driver.aggregate_context( func )
506
+ unless ctx[ :__error ]
507
+ ctx[ :handler ] ||= handler.new
508
+ begin
509
+ ctx[ :handler ].step( FunctionProxy.new( @driver, func, ctx ),
510
+ *args.map{|v| Value.new(self,v)} )
511
+ rescue Exception, StandardError => e
512
+ ctx[ :__error ] = e
513
+ end
514
+ end
515
+ end
516
+
517
+ finalize = proc do |func|
518
+ ctx = @driver.aggregate_context( func )
519
+ unless ctx[ :__error ]
520
+ ctx[ :handler ] ||= handler.new
521
+ begin
522
+ ctx[ :handler ].finalize( FunctionProxy.new( @driver, func, ctx ) )
523
+ rescue Exception => e
524
+ ctx[ :__error ] = e
525
+ end
526
+ end
527
+
528
+ if ctx[ :__error ]
529
+ e = ctx[ :__error ]
530
+ @driver.sqlite3_result_error( func, "#{e.message} (#{e.class})", -1 )
531
+ end
532
+ end
533
+
534
+ result = @driver.create_function( @handle, name, arity, text_rep, nil,
535
+ nil, step, finalize )
536
+ Error.check( result, self )
537
+
538
+ self
539
+ end
540
+
541
+ # Begins a new transaction. Note that nested transactions are not allowed
542
+ # by SQLite, so attempting to nest a transaction will result in a runtime
543
+ # exception.
544
+ #
545
+ # The +mode+ parameter may be either <tt>:deferred</tt> (the default),
546
+ # <tt>:immediate</tt>, or <tt>:exclusive</tt>.
547
+ #
548
+ # If a block is given, the database instance is yielded to it, and the
549
+ # transaction is committed when the block terminates. If the block
550
+ # raises an exception, a rollback will be performed instead. Note that if
551
+ # a block is given, #commit and #rollback should never be called
552
+ # explicitly or you'll get an error when the block terminates.
553
+ #
554
+ # If a block is not given, it is the caller's responsibility to end the
555
+ # transaction explicitly, either by calling #commit, or by calling
556
+ # #rollback.
557
+ def transaction( mode = :deferred )
558
+ execute "begin #{mode.to_s} transaction"
559
+ @transaction_active = true
560
+
561
+ if block_given?
562
+ abort = false
563
+ begin
564
+ yield self
565
+ rescue ::Object
566
+ abort = true
567
+ raise
568
+ ensure
569
+ abort and rollback or commit
570
+ end
571
+ end
572
+
573
+ true
574
+ end
575
+
576
+ # Commits the current transaction. If there is no current transaction,
577
+ # this will cause an error to be raised. This returns +true+, in order
578
+ # to allow it to be used in idioms like
579
+ # <tt>abort? and rollback or commit</tt>.
580
+ def commit
581
+ execute "commit transaction"
582
+ @transaction_active = false
583
+ true
584
+ end
585
+
586
+ # Rolls the current transaction back. If there is no current transaction,
587
+ # this will cause an error to be raised. This returns +true+, in order
588
+ # to allow it to be used in idioms like
589
+ # <tt>abort? and rollback or commit</tt>.
590
+ def rollback
591
+ execute "rollback transaction"
592
+ @transaction_active = false
593
+ true
594
+ end
595
+
596
+ # Returns +true+ if there is a transaction active, and +false+ otherwise.
597
+ def transaction_active?
598
+ @transaction_active
599
+ end
600
+
601
+ # Loads the corresponding driver, or if it is nil, attempts to locate a
602
+ # suitable driver.
603
+ def load_driver( driver )
604
+ case driver
605
+ when Class
606
+ # do nothing--use what was given
607
+ when Symbol, String
608
+ require "sqlite3/driver/#{driver.to_s.downcase}/driver"
609
+ driver = SQLite3::Driver.const_get( driver )::Driver
610
+ else
611
+ [ "Native", "DL" ].each do |d|
612
+ begin
613
+ require "sqlite3/driver/#{d.downcase}/driver"
614
+ driver = SQLite3::Driver.const_get( d )::Driver
615
+ break
616
+ rescue SyntaxError
617
+ raise
618
+ rescue ScriptError, Exception, NameError
619
+ end
620
+ end
621
+ raise "no driver for sqlite3 found" unless driver
622
+ end
623
+
624
+ @driver = driver.new
625
+ end
626
+ private :load_driver
627
+
628
+ # A helper class for dealing with custom functions (see #create_function,
629
+ # #create_aggregate, and #create_aggregate_handler). It encapsulates the
630
+ # opaque function object that represents the current invocation. It also
631
+ # provides more convenient access to the API functions that operate on
632
+ # the function object.
633
+ #
634
+ # This class will almost _always_ be instantiated indirectly, by working
635
+ # with the create methods mentioned above.
636
+ class FunctionProxy
637
+
638
+ # Create a new FunctionProxy that encapsulates the given +func+ object.
639
+ # If context is non-nil, the functions context will be set to that. If
640
+ # it is non-nil, it must quack like a Hash. If it is nil, then none of
641
+ # the context functions will be available.
642
+ def initialize( driver, func, context=nil )
643
+ @driver = driver
644
+ @func = func
645
+ @context = context
646
+ end
647
+
648
+ # Calls #set_result to set the result of this function.
649
+ def result=( result )
650
+ set_result( result )
651
+ end
652
+
653
+ # Set the result of the function to the given value. The function will
654
+ # then return this value.
655
+ def set_result( result, utf16=false )
656
+ @driver.result_text( @func, result, utf16 )
657
+ end
658
+
659
+ # Set the result of the function to the given error message.
660
+ # The function will then return that error.
661
+ def set_error( error )
662
+ @driver.result_error( @func, error.to_s, -1 )
663
+ end
664
+
665
+ # (Only available to aggregate functions.) Returns the number of rows
666
+ # that the aggregate has processed so far. This will include the current
667
+ # row, and so will always return at least 1.
668
+ def count
669
+ ensure_aggregate!
670
+ @driver.aggregate_count( @func )
671
+ end
672
+
673
+ # Returns the value with the given key from the context. This is only
674
+ # available to aggregate functions.
675
+ def []( key )
676
+ ensure_aggregate!
677
+ @context[ key ]
678
+ end
679
+
680
+ # Sets the value with the given key in the context. This is only
681
+ # available to aggregate functions.
682
+ def []=( key, value )
683
+ ensure_aggregate!
684
+ @context[ key ] = value
685
+ end
686
+
687
+ # A function for performing a sanity check, to ensure that the function
688
+ # being invoked is an aggregate function. This is implied by the
689
+ # existence of the context variable.
690
+ def ensure_aggregate!
691
+ unless @context
692
+ raise MisuseException, "function is not an aggregate"
693
+ end
694
+ end
695
+ private :ensure_aggregate!
696
+
697
+ end
698
+
699
+ # A proxy used for defining the callbacks to an aggregate function.
700
+ class AggregateDefinitionProxy # :nodoc:
701
+ attr_reader :step_callback, :finalize_callback
702
+
703
+ def step( &block )
704
+ @step_callback = block
705
+ end
706
+
707
+ def finalize( &block )
708
+ @finalize_callback = block
709
+ end
710
+ end
711
+
712
+ end
713
+
714
+ end
715
+