Chapter 3. Programming

Table of Contents

1. Queries
2. Errors
3. Records
4. SQL Tracing

So this is how it all works. To begin with, you have to connect to the database. This is done as follows:


require 'jw/dbi'

db = JW::DBI::Database.new()

connected = db.open( host '127.0.0.1',
                     db: 'foods.db', 
                     username: 'jujyfruit',
                     password: 'yumyum',
                     driver: 'QPSQL' )

if not connected
  puts db.error()
end

That's it. There are five possible parameters, and depending on the database you use, only the db parameter is required in all cases. This miminal case works with SQLite — where the driver will always default to QSQLITE. In this case, the db refers to the database file on disk. The possible drivers correspond to the names in QSqlDatabase. They are as follows:

Table 3.1. QSql Drivers

NameDatabase
QDB2 IBM DB2
QIBASE Borland InterBase Driver
QMYSQL MySQL Driver
QOCI Oracle Call Interface Driver
QODBC ODBC Driver (includes Microsoft SQL Server)
QPSQL PostgreSQL Driver
QSQLITE SQLite version 3 or above
QSQLITE2 SQLite version 2
QTDS Sybase Adaptive Server


1. Queries

After you have your database connection, then you want to run queries. So you create a Query object using Database::query(). A query object is bound to that database and can exec SQL statements over and over. Each time you call Query::exec(), it populates itself with the result set. You can then either iterate over that set or pick out specific records by index. The follows example shows general usage:


require 'jw/dbi'

db = JW::DBI::Database.new()

db.open(db: 'foods.db', driver: 'QSQLITE')

query = db.query()

# 1. Iterate over results

# The Ruby way:
query.exec('select * from foods').each do | row |
  row.each do |key, value|
    puts "#{key}: #{value}"
  end
end

# The C programmer's way:
do
  puts query.record['name']
while query.next()

# 2. Set Navigation

query.exec('select * from foods')

# Move cursor to end of result set
query.moveLast()

# Get the row
row = query.row()

# Get the record index
puts query.at()

# Move back:
query.moveFirst()

# One step forward, two steps back
query.moveNext()
query.movePrevious()
assert query.movePrevious() == false

# Move to the 77th record:
if query.seek(77)
  puts 'It exists'
  assert query.record.isNull('name') == false
end

# Could just fetch 77th as follows:
record = query[77]
assert record.isEmpty?() == false

Notice that there is not a Database::exec() method. This would be convenient, but based on the design of QSql along with the nature of Ruby, this could be problematic. The reason being that Query objects are what assocate database resources with them (e.g. result sets, locks, etc.). A Database::exec() implementation would essentially create a Query object, execute the SQL through it, and pass it back. The problem is that this would quickly create a usage pattern of Database.exec() where the created query object would be created and used in the background over and over. In this case, resources (e.g. locks) would not get freed until Ruby's GC sweep. This is dangerous in that you don't know when it would take place and therefore you could end up with deadlocks without knowing why. And UPDATE to SQLite this way is a perfect example.

Therefore, the primary way to work with the database is by directly using a Query instance. In this case, when you execute statements over and over, you are using the same query instance and resources are automatically freed on each call to Query::exec(). Furthermore, when in doubt, you are also encouraged to explicitly free resources using Query::clear().

Thus, you really only use the database object for anything other than connecting to the database. Everything else is via a query object derived from it.