Class: RDBI::Database

Inherits:
Object
  • Object
show all
Defined in:
lib/rdbi/database.rb

Overview

RDBI::Database is the base class for database handles. Most users will access their database system through this class.

To execute statements, look at prepare and execute.

To retrieve schema information, look at schema and table_schema.

To deal with transactions, refer to transaction, commit, and rollback.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Database

Create a new database handle. This is typically done by a driver and likely shouldn’t be done directly.

args is the connection arguments the user initially supplied to RDBI.connect.



75
76
77
78
79
80
81
82
# File 'lib/rdbi/database.rb', line 75

def initialize(*args)
  @connect_args         = RDBI::Util.key_hash_as_symbols(args[0])
  @connected            = true
  @in_transaction       = 0
  @rewindable_result    = false
  @preprocess_quoter    = nil
  self.open_statements  = { }
end

Instance Attribute Details

#connect_argsObject (readonly)

the arguments used to create the connection.



21
22
23
# File 'lib/rdbi/database.rb', line 21

def connect_args
  @connect_args
end

#connectedObject (readonly) Also known as: connected?

are we connected to the database?



41
42
43
# File 'lib/rdbi/database.rb', line 41

def connected
  @connected
end

#database_nameObject

the name of the database we’re connected to, if any.



15
16
17
# File 'lib/rdbi/database.rb', line 15

def database_name
  @database_name
end

#driverObject

the driver class that is responsible for creating this database handle.



12
13
14
# File 'lib/rdbi/database.rb', line 12

def driver
  @driver
end

#last_queryObject

the last query sent, as a string.



27
28
29
# File 'lib/rdbi/database.rb', line 27

def last_query
  @last_query
end

#last_statementObject

the last statement handle allocated. affected by prepare and execute.



24
25
26
# File 'lib/rdbi/database.rb', line 24

def last_statement
  @last_statement
end

#open_statementsObject

all the open statement handles.



30
31
32
# File 'lib/rdbi/database.rb', line 30

def open_statements
  @open_statements
end

#rewindable_resultObject

see RDBI::Statement#rewindable_result



18
19
20
# File 'lib/rdbi/database.rb', line 18

def rewindable_result
  @rewindable_result
end

Instance Method Details

#commitObject

ends the outstanding transaction and commits the result.



65
66
67
# File 'lib/rdbi/database.rb', line 65

def commit
  @in_transaction -= 1 unless @in_transaction == 0 
end

#disconnectObject

disconnects from the database: will close (and complain, loudly) any statement handles left open.



94
95
96
97
98
99
# File 'lib/rdbi/database.rb', line 94

def disconnect
  @connected = false
  # FIXME - this is not serving a useful purpose nor public API
  self.open_statements.values.each { |x| x.finish if x }
  self.open_statements = { }
end

#execute(query, *binds) ⇒ Object

Prepares and executes a statement. Takes a string query and an optional number of variable type binds.

ex:

res = dbh.execute("select * from foo where item = ?", "an item")
ary = res.to_a

If invoked with a block, the result handle will be yielded and the handle and statement will be finished at the end of the block. Use this form when the result handle is not needed outside the block:

dbh.execute("select * from foo where item = ?", "an item") do |res|
  res.as(:Struct).fetch(:all).each do |struct|
    p struct.item
  end
end

Block invocation may be considerably more efficient under some database drivers.

For DDL and other statements which return no rows, see #execute_modification.



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/rdbi/database.rb', line 196

def execute(query, *binds)
  res = nil

  sth = prepare(query)
  begin
    res = sth.execute(*binds)
  rescue Exception => e
    sth.finish rescue nil
    raise e
  end

  unless block_given?
    RDBI::Util.upon_finalize!(res, sth, :finish)
    return res
  end

  begin
    yield res
  ensure
    res.finish rescue nil
    sth.finish rescue nil
  end
end

#execute_modification(query, *binds) ⇒ Object

Prepare, execute and finish a statement, returning the number of rows affected (number of rows INSERTed, DELETEd, etc.).

Effectively equivalent to

dbh.execute(sql_stmt) do |res|
  res.affected_count
end

but likely more efficient. See also RDBI::Statement#execute_modification



232
233
234
235
236
237
# File 'lib/rdbi/database.rb', line 232

def execute_modification(query, *binds)
  sth = prepare(query)
  sth.execute_modification(*binds)
ensure
  sth.finish rescue nil
end

#in_transactionObject Also known as: in_transaction?

are we currently in a transaction?



33
34
35
# File 'lib/rdbi/database.rb', line 33

def in_transaction
  @in_transaction > 0
end

#pingObject

ping the database. yield an integer result on success.

Raises:

  • (NoMethodError)


45
46
47
# File 'lib/rdbi/database.rb', line 45

def ping
  raise NoMethodError, "this method is not implemented in this driver"
end

#prepare(query) ⇒ Object

Prepares a statement for execution. Takes a query as its only argument, returns an RDBI::Statement.

ex:

sth = dbh.prepare("select * from foo where item = ?")
res = sth.execute("an item")
ary = res.to_a
sth.finish

You can also use a block form which will auto-finish:

dbh.prepare("select * from foo where item = ?") do |sth|
  sth.execute("an item")
end


157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rdbi/database.rb', line 157

def prepare(query)
  sth = new_statement(query)

  self.last_query = query
  self.open_statements[sth.object_id] = self.last_statement = ::WeakRef.new(sth)

  return sth unless block_given?

  begin
    yield sth
  ensure
    sth.finish rescue nil
  end
end

#preprocess_query(query, *binds) ⇒ Object

Process the query as your driver would normally, and return the result. Depending on the driver implementation and potentially connection settings, this may include interpolated data or client binding placeholders.

Driver Authors: if the instance variable @preprocess_quoter is set to a proc that accepts an index/key, a map of named binds and an array of indexed binds, it will be called instead of the default quoter and there is no need to override this method. For example:

def initialize(...)
  @preprocess_quoter = proc do |x, named, indexed|
    @some_handle.quote((named[x] || indexed[x]).to_s)
  end
end

This will use RDBI’s code to manage the binds before quoting, but use your quoter during bind processing.



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/rdbi/database.rb', line 259

def preprocess_query(query, *binds)
  self.last_query = query

  ep = Epoxy.new(query)

  hashes = binds.select { |x| x.kind_of?(Hash) }
  binds.collect! { |x| x.kind_of?(Hash) ? nil : x }
  total_hash = hashes.inject({}) { |x, y| x.merge(y) }

  if @preprocess_quoter.respond_to?(:call)
    ep.quote(total_hash) { |x| @preprocess_quoter.call(x, total_hash, binds) }
  else
    ep.quote(total_hash) { |x| %Q{'#{(total_hash[x] || binds[x]).to_s.gsub(/'/, "''")}'} }
  end
end

#quote(item) ⇒ Object

Quote a single item using a consistent quoting method.



278
279
280
# File 'lib/rdbi/database.rb', line 278

def quote(item)
  "\'#{item.to_s}\'"
end

#reconnectObject

reconnect to the database. Any outstanding connection will be terminated.



85
86
87
88
# File 'lib/rdbi/database.rb', line 85

def reconnect
  disconnect rescue nil
  @connected = true
end

#rollbackObject

ends the outstanding transaction and rolls the affected rows back.



60
61
62
# File 'lib/rdbi/database.rb', line 60

def rollback
  @in_transaction -= 1 unless @in_transaction == 0
end

#schemaObject

query the schema for the entire database. Returns an array of RDBI::Schema objects.

Raises:

  • (NoMethodError)


55
56
57
# File 'lib/rdbi/database.rb', line 55

def schema
  raise NoMethodError, "this method is not implemented in this driver"
end

#table_schema(table_name) ⇒ Object

query the schema for a specific table. Returns an RDBI::Schema object.

Raises:

  • (NoMethodError)


50
51
52
# File 'lib/rdbi/database.rb', line 50

def table_schema(table_name)
  raise NoMethodError, "this method is not implemented in this driver"
end

#transaction(&block) ⇒ Object

Open a new transaction for processing. Accepts a block which will execute the portions during the transaction.

Example:

dbh.transaction do |dbh|
    dbh.execute("some query")
    dbh.execute("some other query")
    raise "oh crap!" # would rollback
    dbh.commit # commits
    dbh.rollback # rolls back
end

# at this point, if no raise or commit/rollback was triggered, it would
# commit.

Any exception that isn’t caught within this block will trigger a rollback. Additionally, you may use commit and rollback directly within the block to terminate the transaction early – at which point *the transaction is over with and you may be in autocommit*. The RDBI::Database accessor in_transaction exists to tell you if RDBI thinks its in a transaction or not.

If you do not commit or rollback within the block and no exception is raised, RDBI presumes you wish this transaction to succeed and commits for you.



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/rdbi/database.rb', line 129

def transaction(&block)
  @in_transaction += 1
  begin
    yield self
    self.commit if @in_transaction > 0
  rescue => e
    self.rollback
    raise e
  ensure
    @in_transaction -= 1 unless @in_transaction == 0
  end
end