Class Amalgalite::Database
In: lib/amalgalite/database.rb
Parent: Object

The encapsulation of a connection to an SQLite3 database.

Example opening and possibly creating a new daabase

  db = Amalgalite::Database.new( "mydb.db" )
  db.execute( "SELECT * FROM table" ) do |row|
    puts row
  end

  db.close

Open a database read only:

  db = Amalgalite::Database.new( "mydb.db", "r" )

Methods

Included Modules

Amalgalite::SQLite3::Constants

Classes and Modules

Class Amalgalite::Database::InvalidModeError
Class Amalgalite::Database::TransactionBehavior

Constants

VALID_MODES = { "r" => Open::READONLY, "r+" => Open::READWRITE, "w+" => Open::READWRITE | Open::CREATE, }   list of valid modes for opening an Amalgalite::Database

Attributes

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

Public Class methods

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:

  • r - Read-only
  • r+ - Read/write, an error is thrown if the database does not already exist
  • w+ - Read/write, create a new database if it doesn‘t exist

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:

  • :utf16 option to set the database to a utf16 encoding if creating a 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

[Source]

     # 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

Public Instance methods

Is the database in autocommit mode or not

[Source]

     # File lib/amalgalite/database.rb, line 149
149:     def autocommit?
150:       @api.autocommit?
151:     end

clear all the current taps

[Source]

     # 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

[Source]

     # File lib/amalgalite/database.rb, line 140
140:     def close
141:       if open? then
142:         @api.close
143:       end
144:     end

Commit a transaction

[Source]

     # File lib/amalgalite/database.rb, line 502
502:     def commit
503:       execute( "COMMIT" ) if in_transaction?
504:     end

return the encoding of the database

[Source]

     # File lib/amalgalite/database.rb, line 174
174:     def encoding
175:       unless @encoding
176:         @encoding = pragma( "encoding" ).first['encoding']
177:       end
178:       return @encoding
179:     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.

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # 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

[Source]

     # File lib/amalgalite/database.rb, line 133
133:     def open?
134:       @open
135:     end

Run a pragma command against the database

Returns the result set of the pragma

[Source]

     # File lib/amalgalite/database.rb, line 440
440:     def pragma( cmd, &block )
441:       execute("PRAGMA #{cmd}", &block)
442:     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 ( :

[Source]

     # 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

[Source]

     # 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

By default once the schema is obtained, it is cached. This is here to force the schema to be reloaded.

[Source]

     # File lib/amalgalite/database.rb, line 431
431:     def reload_schema!( dbname = "main" )
432:       @schema = nil
433:       schema( dbname )
434:     end

Rollback a transaction

[Source]

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

[Source]

     # File lib/amalgalite/database.rb, line 191
191:     def row_changes
192:       @api.row_changes
193:     end

Returns a Schema object containing the table and column structure of the database.

[Source]

     # File lib/amalgalite/database.rb, line 420
420:     def schema( dbname = "main" ) 
421:       @schema ||= ::Amalgalite::Schema.new( self, dbname )
422:     end

return how many rows have changed since this connection to the database was opened.

[Source]

     # 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

[Source]

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

[Source]

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

[Source]

     # 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

Is the database utf16 or not? A database is utf16 if the encoding is not UTF-8. Database can only be UTF-8 or UTF-16, and the default is UTF-8

[Source]

     # File lib/amalgalite/database.rb, line 164
164:     def utf16?
165:       unless @utf16.nil?
166:         @utf16 = (encoding != "UTF-8") 
167:       end
168:       return @utf16
169:     end

[Validate]