| Class | Amalgalite::Database |
| In: |
lib/amalgalite/database.rb
|
| Parent: | Object |
| VALID_MODES | = | { "r" => Open::READONLY, "r+" => Open::READWRITE, "w+" => Open::READWRITE | Open::CREATE, } | list of valid modes for opening an Amalgalite::Database |
| api | [R] | the low level Amalgalite::SQLite3::Database |
| profile_tap | [R] | An object that follows the ProfileTap protocol, or nil. By default this is nil |
| trace_tap | [R] | An object that follows the TraceTap protocol, or nil. By default this is nil |
| type_map | [R] | An object that follows the TypeMap protocol, or nil. By default this is an instances of TypeMaps::DefaultMap |
Create a new Amalgalite database
The first parameter is the filename of the sqlite database. The second parameter is the standard file modes of how to open a file.
The modes are:
w+ is the default as this is how most databases will want to be utilized.
opts is a hash of available options for the database:
By default, databases are created with an encoding of utf8. Setting this to true and opening an already existing database has no effect.
NOTE Currently :utf16 is not supported by Amalgalite, it is planned for a later release
# File lib/amalgalite/database.rb, line 112
112: def initialize( filename, mode = "w+", opts = {})
113: @open = false
114: @profile_tap = nil
115: @trace_tap = nil
116: @type_map = ::Amalgalite::TypeMaps::DefaultMap.new
117:
118: unless VALID_MODES.keys.include?( mode )
119: raise InvalidModeError, "#{mode} is invalid, must be one of #{VALID_MODES.keys.join(', ')}"
120: end
121:
122: if not File.exist?( filename ) and opts[:utf16] then
123: raise NotImplementedError, "Currently Amalgalite has not implemented utf16 support"
124: else
125: @api = Amalgalite::SQLite3::Database.open( filename, VALID_MODES[mode] )
126: end
127: @open = true
128: end
Is the database in autocommit mode or not
# File lib/amalgalite/database.rb, line 149
149: def autocommit?
150: @api.autocommit?
151: end
clear all the current taps
# File lib/amalgalite/database.rb, line 284
284: def clear_taps!
285: self.trace_tap = nil
286: self.profile_tap = nil
287: end
Close the database
# File lib/amalgalite/database.rb, line 140
140: def close
141: if open? then
142: @api.close
143: end
144: end
Commit a transaction
# File lib/amalgalite/database.rb, line 502
502: def commit
503: execute( "COMMIT" ) if in_transaction?
504: end
Execute a single SQL statement.
If called with a block and there are result rows, then they are iteratively yielded to the block.
If no block passed and there are results, then a ResultSet is returned. Otherwise nil is returned. On an error an exception is thrown.
This is just a wrapper around the preparation of an Amalgalite Statement and iterating over the results.
# File lib/amalgalite/database.rb, line 246
246: def execute( sql, *bind_params )
247: stmt = prepare( sql )
248: stmt.bind( *bind_params )
249: if block_given? then
250: stmt.each { |row| yield row }
251: else
252: return stmt.all_rows
253: end
254: ensure
255: stmt.close if stmt
256: end
Execute a batch of statements, this will execute all the sql in the given string until no more sql can be found in the string. It will bind the same parameters to each statement. All data that would be returned from all of the statements is thrown away.
All statements to be executed in the batch must be terminated with a ’;’ Returns the number of statements executed
# File lib/amalgalite/database.rb, line 268
268: def execute_batch( sql, *bind_params)
269: count = 0
270: while sql
271: prepare( sql ) do |stmt|
272: stmt.execute( *bind_params )
273: sql = stmt.remaining_sql
274: sql = nil unless (sql.index(";") and Amalgalite::SQLite3.complete?( sql ))
275: end
276: count += 1
277: end
278: return count
279: end
return whether or not the database is currently in a transaction or not
# File lib/amalgalite/database.rb, line 184
184: def in_transaction?
185: not @api.autocommit?
186: end
Return the rowid of the last inserted row
# File lib/amalgalite/database.rb, line 156
156: def last_insert_rowid
157: @api.last_insert_rowid
158: end
Is the database open or not
# File lib/amalgalite/database.rb, line 133
133: def open?
134: @open
135: end
Prepare a statement for execution
If called with a block, the statement is yielded to the block and the statement is closed when the block is done.
db.prepare( "SELECT * FROM table WHERE c = ?" ) do |stmt|
list_of_c_values.each do |c|
stmt.execute( c ) do |row|
puts "when c = #{c} : #{row.inspect}"
end
end
end
Or without a block:
stmt = db.prepare( "INSERT INTO t1(x, y, z) VALUES ( :
# File lib/amalgalite/database.rb, line 221
221: def prepare( sql )
222: stmt = Amalgalite::Statement.new( self, sql )
223: if block_given? then
224: begin
225: yield stmt
226: ensure
227: stmt.close
228: stmt = nil
229: end
230: end
231: return stmt
232: end
Register a profile tap.
Registering a profile tap means that the obj registered will have its profile method called with an Integer and a String parameter every time a profile event happens. The Integer is the number of nanoseconds it took for the String (SQL) to execute in wall-clock time.
That is, every time a profile event happens in SQLite the following is invoked:
obj.profile( str, int )
For instance:
db.profile_tap = Amalgalite::ProfileTap.new( logger, 'debug' )
This will register an instance of ProfileTap, which wraps an logger object. On each profile event the ProfileTap#profile method will be called which in turn will call <tt>logger.debug<tt> with a formatted string containing the String and Integer from the profile event.
db.profile_tap = nil
This will unregister the profile tap
# File lib/amalgalite/database.rb, line 377
377: def profile_tap=( tap_obj )
378:
379: # unregister any previous profile tap
380: unless @profile_tap.nil?
381: @profile_tap.profile( 'unregistered as profile tap', 0.0 )
382: @profile_tap = nil
383: end
384: return @profile_tap if tap_obj.nil?
385:
386: if tap_obj.respond_to?( 'profile' ) then
387: @profile_tap = tap_obj
388: else
389: raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'profile' method"
390: end
391: @api.register_profile_tap( @profile_tap )
392: @profile_tap.profile( 'registered as profile tap', 0.0 )
393: end
Rollback a transaction
# File lib/amalgalite/database.rb, line 509
509: def rollback
510: execute( "ROLLBACK" ) if in_transaction?
511: end
return how many rows changed in the last insert, update or delete statement.
# File lib/amalgalite/database.rb, line 191
191: def row_changes
192: @api.row_changes
193: end
return how many rows have changed since this connection to the database was opened.
# File lib/amalgalite/database.rb, line 199
199: def total_changes
200: @api.total_changes
201: end
Register a trace tap.
Registering a trace tap measn that the obj registered will have its trace method called with a string parameter at various times. If the object doesn‘t respond to the trace method then write will be called.
For instance:
db.trace_tap = Amalgalite::TraceTap.new( logger, 'debug' )
This will register an instance of TraceTap, which wraps an logger object. On each trace event the TraceTap#trace method will be called, which in turn will call the logger.debug method
db.trace_tap = $stderr
This will register the $stderr io stream as a trace tap. Every time a trace event happens then $stderr.write( msg ) will be called.
db.trace_tap = nil
This will unregistere the trace tap
# File lib/amalgalite/database.rb, line 318
318: def trace_tap=( tap_obj )
319:
320: # unregister any previous trace tap
321: #
322: unless @trace_tap.nil?
323: @trace_tap.trace( 'unregistered as trace tap' )
324: @trace_tap = nil
325: end
326: return @trace_tap if tap_obj.nil?
327:
328:
329: # wrap the tap if we need to
330: #
331: if tap_obj.respond_to?( 'trace' ) then
332: @trace_tap = tap_obj
333: elsif tap_obj.respond_to?( 'write' ) then
334: @trace_tap = Amalgalite::TraceTap.new( tap_obj, 'write' )
335: else
336: raise Amalgalite::Error, "#{tap_obj.class.name} cannot be used to tap. It has no 'write' or 'trace' method. Look at wrapping it in a Tap instances."
337: end
338:
339: # and do the low level registration
340: #
341: @api.register_trace_tap( @trace_tap )
342:
343: @trace_tap.trace( 'registered as trace tap' )
344: end
Begin a transaction. The valid transaction types are:
| DEFERRED: | no read or write locks are created until the first statement is executed that requries a read or a write |
| IMMEDIATE: | a readlock is obtained immediately so that no other process can write to the database |
| EXCLUSIVE: | a read+write lock is obtained, no other proces can read or write to the database |
As a convenience, these are constants available in the Database::TransactionBehavior class.
Amalgalite Transactions are database level transactions, just as SQLite‘s are.
If a block is passed in, then when the block exits, it is guaranteed that either ‘COMMIT’ or ‘ROLLBACK’ has been executed.
If any exception happens during the transaction that is caught by Amalgalite, then a ‘ROLLBACK’ is issued when the block closes.
If no exception happens during the transaction then a ‘COMMIT’ is issued upon leaving the block.
If no block is passed in then you are on your own.
Nested transactions are not supported by SQLite, but they are faked here. If you call transaction within a transaction, no new transaction is started, the current one is just continued.
# File lib/amalgalite/database.rb, line 475
475: def transaction( mode = TransactionBehavior::DEFERRED )
476: raise Amalgalite::Error, "Invalid transaction behavior mode #{mode}" unless TransactionBehavior.valid?( mode )
477:
478: # if already in a transaction, no need to start a new one.
479: if not in_transaction? then
480: execute( "BEGIN #{mode} TRANSACTION" )
481: end
482:
483: if block_given? then
484: begin
485: return ( yield self )
486: ensure
487: if $! then
488: rollback
489: raise $!
490: else
491: commit
492: end
493: end
494: else
495: return in_transaction?
496: end
497: end
Assign your own TypeMap instance to do type conversions. The value assigned here must respond to bind_type_of and result_value_of methods. See the TypeMap class for more details.
# File lib/amalgalite/database.rb, line 404
404: def type_map=( type_map_obj )
405: %w[ bind_type_of result_value_of ].each do |method|
406: unless type_map_obj.respond_to?( method )
407: raise Amalgalite::Error, "#{type_map_obj.class.name} cannot be used to do type mapping. It does not respond to '#{method}'"
408: end
409: end
410: @type_map = type_map_obj
411: end