Class: RDBI::Database

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

Overview

RDBI::Database is the base class for database handles. This is the primary method in which most users will access their database system.

To execute statements, look at prepare and execute.

To retrieve schema information, look at schema and table_schema.

To deal with transactions, 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.



90
91
92
93
94
95
96
# File 'lib/rdbi/database.rb', line 90

def initialize(*args)
  @connect_args   = RDBI::Util.key_hash_as_symbols(args[0])
  @connected      = true
  @mutex          = Mutex.new
  @in_transaction = 0
  self.open_statements = []
end

Instance Attribute Details

#connect_argsObject (readonly)

the arguments used to create the connection.



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

def connect_args
  @connect_args
end

#database_nameObject

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



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

def database_name
  @database_name
end

#driverObject

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



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

def driver
  @driver
end

#mutexObject (readonly)

the mutex for this database handle.



48
49
50
# File 'lib/rdbi/database.rb', line 48

def mutex
  @mutex
end

Instance Method Details

#commitObject

:method: commit ends the outstanding transaction and commits the result.



82
# File 'lib/rdbi/database.rb', line 82

inline(:commit)   { @in_transaction -= 1 unless @in_transaction == 0 }

#connectedObject

:attr_accessor: connected? are we connected to the database?



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

inline(:connected, :connected?) { @connected }

#disconnectObject

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



108
109
110
111
112
113
114
115
# File 'lib/rdbi/database.rb', line 108

def disconnect
  unless self.open_statements.empty?
    warn "[RDBI] Open statements during disconnection -- automatically finishing. You should fix this."
    self.open_statements.each(&:finish)
  end
  self.open_statements = []
  @connected = false
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

You can also use a block form which will finish the statement and yield the result handle:

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

Which will be considerably more performant under some database drivers.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/rdbi/database.rb', line 203

def execute(query, *binds)
  res = nil

  mutex.synchronize do
    self.last_query = query
    self.last_statement = sth = new_statement(query)
    res = sth.execute(*binds)

    if block_given?
      yield res
    else
      res.coerce_to_array
    end

    sth.finish
  end

  return res
end

#in_transactionObject

:attr: in_transaction? are we currently in a transaction?



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

inline(:in_transaction, :in_transaction?) { @in_transaction > 0 }

#last_queryObject

:attr: last_query the last query sent, as a string.



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

attr_threaded_accessor :last_query

#last_statementObject

:attr_reader: last_statement

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



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

attr_threaded_accessor :last_statement

#open_statementsObject

:attr: open_statements all the open statement handles.



36
# File 'lib/rdbi/database.rb', line 36

attr_threaded_accessor :open_statements

#pingObject

:method: ping ping the database. yield an integer result on success.



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

inline(:ping) { raise NoMethodError, "this method is not implemented in this driver" }

#prepare(query) ⇒ Object

Prepares a statement for execution. Takes a query as its only argument, returns a 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


173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rdbi/database.rb', line 173

def prepare(query)
  sth = nil
  mutex.synchronize do
    self.last_query = query
    sth = new_statement(query)
    yield sth if block_given?
    sth.finish if block_given?
  end

  return self.last_statement = sth
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.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/rdbi/database.rb', line 243

def preprocess_query(query, *binds)
  mutex.synchronize do
    self.last_query = query
  end
 
  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

#reconnectObject

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



99
100
101
102
# File 'lib/rdbi/database.rb', line 99

def reconnect
  disconnect rescue nil
  @connected = true
end

#rollbackObject

:method: rollback ends the outstanding transaction and rolls the affected rows back.



77
# File 'lib/rdbi/database.rb', line 77

inline(:rollback) { @in_transaction -= 1 unless @in_transaction == 0 }

#schemaObject

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



72
# File 'lib/rdbi/database.rb', line 72

inline(:schema) { |*args| raise NoMethodError, "this method is not implemented in this driver" }

#table_schemaObject

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



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

inline(:table_schema) { |*args| raise NoMethodError, "this method is not implemented in this driver" }

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



145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rdbi/database.rb', line 145

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