split perform_query and perform_exec

add DB::ExecResult to have a concurrency safety exec result
remove connection string from abstract DB::Connection
This commit is contained in:
Brian J. Cardiff 2016-02-01 23:02:04 -03:00
parent 67fe5c9aae
commit 3598dddb65
7 changed files with 47 additions and 57 deletions

View file

@ -36,24 +36,10 @@ describe DB do
end end
end end
it "exec should close statement" do
with_dummy do |db|
db.exec ""
DummyDriver::DummyResultSet.last_result_set.closed?.should be_true
end
end
it "scalar should close statement" do it "scalar should close statement" do
with_dummy do |db| with_dummy do |db|
db.scalar "1" db.scalar "1"
DummyDriver::DummyResultSet.last_result_set.closed?.should be_true DummyDriver::DummyResultSet.last_result_set.closed?.should be_true
end end
end end
it "exec should perform statement" do
with_dummy do |db|
db.exec ""
DummyDriver::DummyResultSet.last_result_set.executed?.should be_true
end
end
end end

View file

@ -6,6 +6,11 @@ class DummyDriver < DB::Driver
end end
class DummyConnection < DB::Connection class DummyConnection < DB::Connection
getter connection_string
def initialize(@connection_string)
end
def prepare(query) def prepare(query)
DummyStatement.new(self, query) DummyStatement.new(self, query)
end end
@ -26,12 +31,21 @@ class DummyDriver < DB::Driver
super(driver) super(driver)
end end
protected def perform(args : Slice(DB::Any)) protected def perform_query(args : Slice(DB::Any))
set_params args
DummyResultSet.new self, @query
end
protected def perform_exec(args : Slice(DB::Any))
set_params args
DB::ExecResult.new 0, 0
end
private def set_params(args)
@params.clear @params.clear
args.each_with_index do |arg, index| args.each_with_index do |arg, index|
@params[index] = arg @params[index] = arg
end end
DummyResultSet.new self, @query
end end
end end
@ -42,7 +56,6 @@ class DummyDriver < DB::Driver
super(statement) super(statement)
@iterator = query.split.map { |r| r.split(',') }.to_a.each @iterator = query.split.map { |r| r.split(',') }.to_a.each
@executed = false
@@last_result_set = self @@last_result_set = self
end end
@ -50,12 +63,7 @@ class DummyDriver < DB::Driver
@@last_result_set.not_nil! @@last_result_set.not_nil!
end end
def executed?
@executed
end
def move_next def move_next
@executed = true
@iterator.next.tap do |n| @iterator.next.tap do |n|
return false if n.is_a?(Iterator::Stop) return false if n.is_a?(Iterator::Stop)
@values = n.each @values = n.each

View file

@ -17,12 +17,8 @@ module DB
abstract class Connection abstract class Connection
# TODO add IDLE status, for connection ppool management. # TODO add IDLE status, for connection ppool management.
getter connection_string # TODO Remove
@closed = false @closed = false
def initialize(@connection_string) # TODO Remove
end
# Closes this connection. # Closes this connection.
def close def close
raise "Connection already closed" if @closed # TODO make it no fail if closed raise "Connection already closed" if @closed # TODO make it no fail if closed
@ -45,10 +41,6 @@ module DB
include QueryMethods include QueryMethods
# Returns the last inserted id through this connection.
abstract def last_insert_id : Int64 # TODO move to ExecResult record. plano. with last_rows. eagerly askit.
protected abstract def perform_close # TODO do_close protected abstract def perform_close # TODO do_close
end end
end end

View file

@ -67,8 +67,10 @@ module DB
# See `DB::TYPES` in `DB`. `Any` is a nillable version of the union of all types in `DB::TYPES` # See `DB::TYPES` in `DB`. `Any` is a nillable version of the union of all types in `DB::TYPES`
alias Any = Nil | String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8) alias Any = Nil | String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8)
# :nodoc: # Result of a `#exec` statement.
record ExecResult, rows_affected, last_insert_id
# :nodoc:
def self.driver_class(driver_name) # : Driver.class def self.driver_class(driver_name) # : Driver.class
@@drivers.not_nil![driver_name] @@drivers.not_nil![driver_name]
end end

View file

@ -3,19 +3,22 @@ module DB
# All methods accepts a `query : String` and a set arguments. # All methods accepts a `query : String` and a set arguments.
# #
# Three kind of statements can be performed: # Three kind of statements can be performed:
# 1. `#exec` waits no response from the database. # 1. `#exec` waits no record response from the database. An `ExecResult` is returned.
# 2. `#scalar` reads a single value of the response. Use `#scalar?` if the response is nillable. # 2. `#scalar` reads a single value of the response. A `DB::Any` is returned.
# 3. `#query` returns a ResultSet that allows iteration over the rows in the response and column information. # 3. `#query` returns a `ResultSet` that allows iteration over the rows in the response and column information.
# #
# Arguments can be passed: # Arguments can be passed by position
# * by position: `db.query("SELECT name FROM ... WHERE age > ?", age)` #
# * by symbol: `db.query("SELECT name FROM ... WHERE age > :age", {age: age})` # ```
# * by string: `db.query("SELECT name FROM ... WHERE age > :age", {"age": age})` # db.query("SELECT name FROM ... WHERE age > ?", age)
# ```
# #
# Convention of mapping how arguments are mapped to the query depends on each driver. # Convention of mapping how arguments are mapped to the query depends on each driver.
# #
# Including `QueryMethods` requires a `prepare(query) : Statement` method. # Including `QueryMethods` requires a `prepare(query) : Statement` method.
module QueryMethods module QueryMethods
abstract def prepare(query) : Statement
# Returns a `ResultSet` for the `query`. # Returns a `ResultSet` for the `query`.
# The `ResultSet` must be closed manually. # The `ResultSet` must be closed manually.
def query(query, *args) def query(query, *args)

View file

@ -47,11 +47,6 @@ module DB
protected def do_close protected def do_close
end end
# Ensures it executes the query
def exec
move_next
end
# Move the next row in the result. # Move the next row in the result.
# Return `false` if no more rows are available. # Return `false` if no more rows are available.
# See `#each` # See `#each`

View file

@ -6,10 +6,9 @@ module DB
# #
# 1. Subclass `Statements` # 1. Subclass `Statements`
# 2. `Statements` are created from a custom driver `Connection#prepare` method. # 2. `Statements` are created from a custom driver `Connection#prepare` method.
# 3. `#begin_parameters` is called before the parameters are set. # 3. `#perform_query` executes a query that is expected to return a `ResultSet`
# 4. `#add_parameter` methods helps to support 0-based positional arguments and named arguments # 4. `#perform_exec` executes a query that is expected to return an `ExecResult`
# 5. After parameters are set `#perform` is called to return a `ResultSet` # 6. `#do_close` is called to release the statement resources.
# 6. `#on_close` is called to release the statement resources.
abstract class Statement abstract class Statement
getter connection getter connection
@ -18,10 +17,14 @@ module DB
end end
# See `QueryMethods#exec` # See `QueryMethods#exec`
def exec(*args) def exec
query(*args) do |rs| perform_exec(Slice(Any).new(0)) # no overload matches ... with types Slice(NoReturn)
rs.exec
end end
# See `QueryMethods#exec`
def exec(*args)
# TODO better way to do it
perform_exec(args.to_a.to_unsafe.to_slice(args.size))
end end
# See `QueryMethods#scalar` # See `QueryMethods#scalar`
@ -55,12 +58,12 @@ module DB
# See `QueryMethods#query` # See `QueryMethods#query`
def query(*args) def query(*args)
execute *args perform_query *args
end end
# See `QueryMethods#query` # See `QueryMethods#query`
def query(*args) def query(*args)
execute(*args).tap do |rs| perform_query(*args).tap do |rs|
begin begin
yield rs yield rs
ensure ensure
@ -69,13 +72,13 @@ module DB
end end
end end
private def execute : ResultSet private def perform_query : ResultSet
perform(Slice(Any).new(0)) perform_query(Slice(Any).new(0)) # no overload matches ... with types Slice(NoReturn)
end end
private def execute(*args) : ResultSet private def perform_query(*args) : ResultSet
# TODO better way to do it # TODO better way to do it
perform(args.to_a.to_unsafe.to_slice(args.size)) perform_query(args.to_a.to_unsafe.to_slice(args.size))
end end
# Closes this statement. # Closes this statement.
@ -95,7 +98,8 @@ module DB
close close
end end
protected abstract def perform(args : Slice(Any)) : ResultSet protected abstract def perform_query(args : Slice(Any)) : ResultSet
protected abstract def perform_exec(args : Slice(Any)) : ExecResult
protected def do_close protected def do_close
end end