mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
make Database return PoolStatement
create StatementMethods for common interface between Statement and PoolStatment.
This commit is contained in:
parent
6a0a450622
commit
75aa821f5f
6 changed files with 148 additions and 31 deletions
|
@ -39,4 +39,12 @@ describe DB::Database do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should allow creation of more statements than pool connections" do
|
||||||
|
DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2" do |db|
|
||||||
|
db.prepare("query1").should be_a(DB::PoolStatement)
|
||||||
|
db.prepare("query2").should be_a(DB::PoolStatement)
|
||||||
|
db.prepare("query3").should be_a(DB::PoolStatement)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -125,6 +125,7 @@ require "./db/database"
|
||||||
require "./db/driver"
|
require "./db/driver"
|
||||||
require "./db/connection"
|
require "./db/connection"
|
||||||
require "./db/statement"
|
require "./db/statement"
|
||||||
|
require "./db/pool_statement"
|
||||||
require "./db/result_set"
|
require "./db/result_set"
|
||||||
require "./db/error"
|
require "./db/error"
|
||||||
require "./db/mapping"
|
require "./db/mapping"
|
||||||
|
|
|
@ -54,18 +54,14 @@ module DB
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def prepare(query)
|
def prepare(query)
|
||||||
conn = get_from_pool
|
# TODO query based cache for pool statement
|
||||||
begin
|
# TODO clear PoolStatements when closing the DB
|
||||||
conn.prepare(query)
|
PoolStatement.new self, query
|
||||||
rescue ex
|
|
||||||
return_to_pool(conn)
|
|
||||||
raise ex
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def get_from_pool
|
def checkout_some(candidates : Enumerable(Connection)) : {Connection, Bool}
|
||||||
@pool.checkout
|
@pool.checkout_some candidates
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
|
@ -77,7 +73,7 @@ module DB
|
||||||
# the connection is returned to the pool after
|
# the connection is returned to the pool after
|
||||||
# when the block ends
|
# when the block ends
|
||||||
def using_connection
|
def using_connection
|
||||||
connection = get_from_pool
|
connection = @pool.checkout
|
||||||
begin
|
begin
|
||||||
yield connection
|
yield connection
|
||||||
ensure
|
ensure
|
||||||
|
|
|
@ -36,6 +36,25 @@ module DB
|
||||||
resource
|
resource
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ```
|
||||||
|
# selected, is_candidate = pool.checkout_some(candidates)
|
||||||
|
# ```
|
||||||
|
# `selected` be a resource from the `candidates` list and `is_candidate` == `true`
|
||||||
|
# or `selected` will be a new resource adn `is_candidate` == `false`
|
||||||
|
def checkout_some(candidates : Enumerable(T)) : {T, Bool}
|
||||||
|
# TODO honor candidates while waiting for availables
|
||||||
|
# this will allow us to remove `candidates.includes?(resource)`
|
||||||
|
candidates.each do |resource|
|
||||||
|
if is_available?(resource)
|
||||||
|
@available.delete resource
|
||||||
|
return {resource, true}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
resource = checkout
|
||||||
|
{resource, candidates.includes?(resource)}
|
||||||
|
end
|
||||||
|
|
||||||
def release(resource : T) : Nil
|
def release(resource : T) : Nil
|
||||||
if can_increase_idle_pool
|
if can_increase_idle_pool
|
||||||
@available << resource
|
@available << resource
|
||||||
|
|
73
src/db/pool_statement.cr
Normal file
73
src/db/pool_statement.cr
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
module DB
|
||||||
|
# When a statement is to be executed in a DB that has a connection pool
|
||||||
|
# a statement from the DB needs to be able to represent a statement in any
|
||||||
|
# of the connections of the pool. Otherwise the user will need to deal with
|
||||||
|
# actual connections in some point.
|
||||||
|
class PoolStatement
|
||||||
|
include StatementMethods
|
||||||
|
|
||||||
|
@statements = {} of Connection => Statement
|
||||||
|
|
||||||
|
def initialize(@db : Database, @query : String)
|
||||||
|
# Prepares a statement on some connection
|
||||||
|
# otherwise the preparation is delayed until the first execution.
|
||||||
|
# After the first initialization the connection must be released
|
||||||
|
# it will be checked out when executing it.
|
||||||
|
get_statement.release_connection
|
||||||
|
# TODO use a round-robin selection in the pool so multiple sequentially
|
||||||
|
# initialized statements are assigned to different connections.
|
||||||
|
end
|
||||||
|
|
||||||
|
protected def do_close
|
||||||
|
# TODO close all statements on all connections.
|
||||||
|
# currently statements are closed when the connection is closed.
|
||||||
|
|
||||||
|
# WHAT-IF the connection is busy? Should each statement be able to
|
||||||
|
# deallocate itself when the connection is free.
|
||||||
|
@statements.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
def exec : ExecResult
|
||||||
|
get_statement.exec
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
def exec(*args) : ExecResult
|
||||||
|
get_statement.exec(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
def exec(args : Array) : ExecResult
|
||||||
|
get_statement.exec(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
def query : ResultSet
|
||||||
|
get_statement.query
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
def query(*args) : ResultSet
|
||||||
|
get_statement.query(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
def query(args : Array) : ResultSet
|
||||||
|
get_statement.query(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# builds a statement over a real connection
|
||||||
|
# the conneciton and the stament is registered in `@statements`
|
||||||
|
private def get_statement : Statement
|
||||||
|
conn, existing = @db.checkout_some(@statements.keys)
|
||||||
|
if existing
|
||||||
|
@statements[conn]
|
||||||
|
else
|
||||||
|
stmt = conn.prepare @query
|
||||||
|
@statements[conn] = stmt
|
||||||
|
stmt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,44 @@
|
||||||
module DB
|
module DB
|
||||||
|
# Common interface for connection based statements
|
||||||
|
# and for connection pool statements.
|
||||||
|
module StatementMethods
|
||||||
|
include Disposable
|
||||||
|
|
||||||
|
protected def do_close
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#scalar`
|
||||||
|
def scalar(*args)
|
||||||
|
query(*args) do |rs|
|
||||||
|
rs.each do
|
||||||
|
return rs.read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "no results"
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
def query(*args)
|
||||||
|
rs = query(*args)
|
||||||
|
yield rs ensure rs.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
abstract def exec : ExecResult
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
abstract def exec(*args) : ExecResult
|
||||||
|
# See `QueryMethods#exec`
|
||||||
|
abstract def exec(args : Array) : ExecResult
|
||||||
|
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
abstract def query : ResultSet
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
abstract def query(*args) : ResultSet
|
||||||
|
# See `QueryMethods#query`
|
||||||
|
abstract def query(args : Array) : ResultSet
|
||||||
|
end
|
||||||
|
|
||||||
# Represents a prepared query in a `Connection`.
|
# Represents a prepared query in a `Connection`.
|
||||||
# It should be created by `QueryMethods`.
|
# It should be created by `QueryMethods`.
|
||||||
#
|
#
|
||||||
|
@ -10,7 +50,7 @@ module DB
|
||||||
# 4. `#perform_exec` executes a query that is expected to return an `ExecResult`
|
# 4. `#perform_exec` executes a query that is expected to return an `ExecResult`
|
||||||
# 6. `#do_close` is called to release the statement resources.
|
# 6. `#do_close` is called to release the statement resources.
|
||||||
abstract class Statement
|
abstract class Statement
|
||||||
include Disposable
|
include StatementMethods
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
getter connection
|
getter connection
|
||||||
|
@ -18,9 +58,6 @@ module DB
|
||||||
def initialize(@connection : Connection)
|
def initialize(@connection : Connection)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected def do_close
|
|
||||||
end
|
|
||||||
|
|
||||||
def release_connection
|
def release_connection
|
||||||
@connection.database.return_to_pool(@connection)
|
@connection.database.return_to_pool(@connection)
|
||||||
end
|
end
|
||||||
|
@ -41,17 +78,6 @@ module DB
|
||||||
perform_exec_and_release(args)
|
perform_exec_and_release(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
# See `QueryMethods#scalar`
|
|
||||||
def scalar(*args)
|
|
||||||
query(*args) do |rs|
|
|
||||||
rs.each do
|
|
||||||
return rs.read
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raise "no results"
|
|
||||||
end
|
|
||||||
|
|
||||||
# See `QueryMethods#query`
|
# See `QueryMethods#query`
|
||||||
def query
|
def query
|
||||||
perform_query Tuple.new
|
perform_query Tuple.new
|
||||||
|
@ -67,12 +93,6 @@ module DB
|
||||||
perform_query args
|
perform_query args
|
||||||
end
|
end
|
||||||
|
|
||||||
# See `QueryMethods#query`
|
|
||||||
def query(*args)
|
|
||||||
rs = query(*args)
|
|
||||||
yield rs ensure rs.close
|
|
||||||
end
|
|
||||||
|
|
||||||
private def perform_exec_and_release(args : Enumerable) : ExecResult
|
private def perform_exec_and_release(args : Enumerable) : ExecResult
|
||||||
return perform_exec(args)
|
return perform_exec(args)
|
||||||
ensure
|
ensure
|
||||||
|
|
Loading…
Reference in a new issue