add pool unprepared statements

unprepared statements are executed in any free connection of the pool at the moment of executing them
This commit is contained in:
Brian J. Cardiff 2016-12-03 15:56:03 -03:00
parent 9ef9d19d1d
commit 0593f63dbb
4 changed files with 168 additions and 3 deletions

View File

@ -59,7 +59,8 @@ describe DB::Database do
it "should close pool statements when closing db" do
stmt = uninitialized DB::PoolStatement
with_dummy do |db|
stmt = db.build("query1")
# TODO remove cast
stmt = db.build("query1").as(DB::PoolStatement)
end
stmt.closed?.should be_true
end
@ -128,5 +129,47 @@ describe DB::Database do
connection.prepared_statements?.should be_false
end
end
it "should build prepared statements if true" do
with_dummy "dummy://localhost:1027?prepared_statements=true" do |db|
db.build("the query").should be_a(DB::PoolStatement)
end
end
it "should build unprepared statements if false" do
with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
db.build("the query").should be_a(DB::PoolUnpreparedStatement)
end
end
end
describe "unprepared statements in pool" do
it "creating statements should not create new connections" do
with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
stmt1 = db.unprepared.build("query1")
stmt2 = db.unprepared.build("query2")
DummyDriver::DummyConnection.connections.size.should eq(1)
end
end
it "simultaneous statements should go to different connections" do
with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
rs1 = db.unprepared.query("query1")
rs2 = db.unprepared.query("query2")
rs1.statement.connection.should_not eq(rs2.statement.connection)
DummyDriver::DummyConnection.connections.size.should eq(2)
end
end
it "sequential statements should go to different connections" do
with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db|
rs1 = db.unprepared.query("query1")
rs1.close
rs2 = db.unprepared.query("query2")
rs2.close
rs1.statement.connection.should eq(rs2.statement.connection)
DummyDriver::DummyConnection.connections.size.should eq(1)
end
end
end
end

View File

@ -140,6 +140,7 @@ require "./db/driver"
require "./db/connection"
require "./db/statement"
require "./db/pool_statement"
require "./db/pool_unprepared_statement"
require "./db/result_set"
require "./db/error"
require "./db/mapping"

View File

@ -17,6 +17,8 @@ module DB
#
# Refer to `QueryMethods` for documentation about querying the database.
class Database
include QueryMethods
# :nodoc:
getter driver
# :nodoc:
@ -63,7 +65,26 @@ module DB
# :nodoc:
def build(query)
@statements_cache.fetch(query) { PoolStatement.new(self, query) }
if prepared_statements?
fetch_or_build_prepared_statement(query)
else
build_unprepared_statement(query)
end
end
# :nodoc:
def fetch_or_build_prepared_statement(query)
@statements_cache.fetch(query) { build_prepared_statement(query) }
end
# :nodoc:
def build_prepared_statement(query)
PoolStatement.new(self, query)
end
# :nodoc:
def build_unprepared_statement(query)
PoolUnpreparedStatement.new(self, query)
end
# :nodoc:
@ -95,6 +116,48 @@ module DB
end
end
include QueryMethods
# dsl helper to build prepared statements
# returns a value that includes `QueryMethods`
def prepared
PreparedQuery.new(self)
end
# Returns a prepared `Statement` that has not been executed yet.
def prepared(query)
prepared.build(query)
end
# dsl helper to build unprepared statements
# returns a value that includes `QueryMethods`
def unprepared
UnpreparedQuery.new(self)
end
# Returns an unprepared `Statement` that has not been executed yet.
def unprepared(query)
unprepared.build(query)
end
struct PreparedQuery
include QueryMethods
def initialize(@db : Database)
end
def build(query)
@db.fetch_or_build_prepared_statement(query)
end
end
struct UnpreparedQuery
include QueryMethods
def initialize(@db : Database)
end
def build(query)
@db.build_unprepared_statement(query)
end
end
end
end

View File

@ -0,0 +1,58 @@
module DB
# Represents a statement to be executed in any of the connections
# of the pool. The statement is not be executed in a non prepared fashion.
# The execution of the statement is retried according to the pool configuration.
#
# See `PoolStatement`
class PoolUnpreparedStatement
include StatementMethods
def initialize(@db : Database, @query : String)
end
protected def do_close
# unprepared statements do not need to be release in each connection
end
# See `QueryMethods#exec`
def exec : ExecResult
statement_with_retry &.exec
end
# See `QueryMethods#exec`
def exec(*args) : ExecResult
statement_with_retry &.exec(*args)
end
# See `QueryMethods#exec`
def exec(args : Array) : ExecResult
statement_with_retry &.exec(args)
end
# See `QueryMethods#query`
def query : ResultSet
statement_with_retry &.query
end
# See `QueryMethods#query`
def query(*args) : ResultSet
statement_with_retry &.query(*args)
end
# See `QueryMethods#query`
def query(args : Array) : ResultSet
statement_with_retry &.query(args)
end
# builds a statement over a real connection
private def build_statement
@db.pool.checkout.unprepared.build(@query)
end
private def statement_with_retry
@db.retry do
return yield build_statement
end
end
end
end