shard-crystal-db/src/db/database.cr

150 lines
4.2 KiB
Crystal

require "http/params"
require "weak_ref"
module DB
# Acts as an entry point for database access.
# Connections are managed by a pool.
# Use `DB#open` to create a `Database` instance.
#
# Refer to `QueryMethods` and `SessionMethods` for documentation about querying the database.
#
# ## Database URI
#
# Connection parameters are usually in a URI. The format is specified by the individual
# database drivers, yet there are some common properties names usually shared.
# See the [reference book](https://crystal-lang.org/reference/database/) for examples.
#
# The connection pool can be configured from URI parameters:
#
# - `initial_pool_size` (default 1)
# - `max_pool_size` (default 0 = unlimited)
# - `max_idle_pool_size` (default 1)
# - `checkout_timeout` (default 5.0)
# - `retry_attempts` (default 1)
# - `retry_delay` (in seconds, default 1.0)
#
# When querying a database, prepared statements are used by default.
# This can be changed from the `prepared_statements` URI parameter:
#
# - `prepared_statements` (true, or false, default true)
#
class Database
include SessionMethods(Database, PoolStatement)
include ConnectionContext
# :nodoc:
getter pool
@connection_options : Connection::Options
@pool : Pool(Connection)
@setup_connection : Connection -> Nil
# Initialize a database with the specified options and connection factory.
# This covers more advanced use cases that might not be supported by an URI connection string such as tunneling connection.
def initialize(connection_options : Connection::Options, pool_options : Pool::Options, &factory : -> Connection)
@connection_options = connection_options
@setup_connection = ->(conn : Connection) {}
@pool = uninitialized Pool(Connection) # in order to use self in the factory proc
@pool = Pool(Connection).new(pool_options) {
conn = factory.call
conn.auto_release = false
conn.context = self
@setup_connection.call conn
conn
}
end
def prepared_statements? : Bool
@connection_options.prepared_statements
end
def prepared_statements_cache? : Bool
@connection_options.prepared_statements_cache
end
# Run the specified block every time a new connection is established, yielding the new connection
# to the block.
#
# ```
# db = DB.open(DB_URL)
# db.setup_connection do |connection|
# connection.exec "SET TIME ZONE 'America/New_York'"
# end
# ```
def setup_connection(&proc : Connection -> Nil)
@setup_connection = proc
@pool.each_resource do |conn|
@setup_connection.call conn
end
end
# Closes all connection to the database.
def close
@pool.close
end
# :nodoc:
def discard(connection : Connection)
@pool.delete connection
end
# :nodoc:
def release(connection : Connection)
@pool.release connection
end
# :nodoc:
def fetch_or_build_prepared_statement(query) : PoolStatement
PoolPreparedStatement.new(self, query)
end
# :nodoc:
def build_unprepared_statement(query) : PoolStatement
PoolUnpreparedStatement.new(self, query)
end
# :nodoc:
def checkout_some(candidates : Enumerable(WeakRef(Connection))) : {Connection, Bool}
@pool.checkout_some candidates
end
# yields a connection from the pool
# the connection is returned to the pool
# when the block ends
def using_connection
connection = self.checkout
begin
yield connection
ensure
connection.release
end
end
# returns a connection from the pool
# the returned connection must be returned
# to the pool by explictly calling `Connection#release`
def checkout
connection = @pool.checkout
connection.auto_release = false
connection
end
# yields a `Transaction` from a connection of the pool
# Refer to `BeginTransaction#transaction` for documentation.
def transaction
using_connection do |cnn|
cnn.transaction do |tx|
yield tx
end
end
end
# :nodoc:
def retry
@pool.retry do
yield
end
end
end
end