2016-07-07 18:50:09 +00:00
|
|
|
require "http/params"
|
2016-09-12 17:35:21 +00:00
|
|
|
require "weak_ref"
|
2016-07-07 18:50:09 +00:00
|
|
|
|
2016-01-29 19:13:01 +00:00
|
|
|
module DB
|
|
|
|
# Acts as an entry point for database access.
|
2016-07-07 18:50:09 +00:00
|
|
|
# Connections are managed by a pool.
|
2019-07-02 13:01:17 +00:00
|
|
|
# Use `DB#open` to create a `Database` instance.
|
|
|
|
#
|
|
|
|
# Refer to `QueryMethods` and `SessionMethods` for documentation about querying the database.
|
|
|
|
#
|
|
|
|
# ## Database URI
|
|
|
|
#
|
|
|
|
# Connection parameters are configured in a URI. The format is specified by the individual
|
|
|
|
# database drivers. See the [reference book](https://crystal-lang.org/reference/database/) for examples.
|
|
|
|
#
|
2016-07-07 18:50:09 +00:00
|
|
|
# The connection pool can be configured from URI parameters:
|
|
|
|
#
|
2019-07-02 13:01:17 +00:00
|
|
|
# - `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)
|
2016-01-30 22:46:43 +00:00
|
|
|
#
|
2019-07-02 13:01:17 +00:00
|
|
|
# When querying a database, prepared statements are used by default.
|
2016-12-04 18:14:43 +00:00
|
|
|
# This can be changed from the `prepared_statements` URI parameter:
|
|
|
|
#
|
2019-07-02 13:01:17 +00:00
|
|
|
# - `prepared_statements` (true, or false, default true)
|
2016-01-31 22:40:02 +00:00
|
|
|
#
|
2016-01-29 19:13:01 +00:00
|
|
|
class Database
|
2016-12-04 18:14:43 +00:00
|
|
|
include SessionMethods(Database, PoolStatement)
|
2017-03-20 18:08:30 +00:00
|
|
|
include ConnectionContext
|
2016-12-03 18:56:03 +00:00
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# :nodoc:
|
2016-02-03 22:30:51 +00:00
|
|
|
getter driver
|
2016-08-29 16:14:47 +00:00
|
|
|
# :nodoc:
|
|
|
|
getter pool
|
2016-01-31 22:40:02 +00:00
|
|
|
|
2016-02-03 22:30:51 +00:00
|
|
|
# Returns the uri with the connection settings to the database
|
2019-08-02 14:54:52 +00:00
|
|
|
getter uri : URI
|
2016-01-29 19:13:01 +00:00
|
|
|
|
2016-12-03 01:09:27 +00:00
|
|
|
getter? prepared_statements : Bool
|
|
|
|
|
2016-07-07 18:50:09 +00:00
|
|
|
@pool : Pool(Connection)
|
2016-08-18 03:55:43 +00:00
|
|
|
@setup_connection : Connection -> Nil
|
2016-12-03 19:03:50 +00:00
|
|
|
@statements_cache = StringKeyCache(PoolPreparedStatement).new
|
2016-06-16 15:14:57 +00:00
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# :nodoc:
|
2016-06-16 15:14:57 +00:00
|
|
|
def initialize(@driver : Driver, @uri : URI)
|
2016-08-30 18:22:27 +00:00
|
|
|
params = HTTP::Params.parse(uri.query || "")
|
2016-12-03 01:09:27 +00:00
|
|
|
@prepared_statements = DB.fetch_bool(params, "prepared_statements", true)
|
2016-07-07 18:50:09 +00:00
|
|
|
pool_options = @driver.connection_pool_options(params)
|
|
|
|
|
2016-08-18 03:55:43 +00:00
|
|
|
@setup_connection = ->(conn : Connection) {}
|
2023-05-28 01:51:38 +00:00
|
|
|
factory = @driver.connection_builder(@uri)
|
2016-07-07 18:50:09 +00:00
|
|
|
@pool = uninitialized Pool(Connection) # in order to use self in the factory proc
|
2016-08-18 03:55:43 +00:00
|
|
|
@pool = Pool.new(**pool_options) {
|
2023-05-28 01:51:38 +00:00
|
|
|
conn = factory.call
|
2020-09-14 13:55:18 +00:00
|
|
|
conn.auto_release = false
|
2023-05-28 01:42:51 +00:00
|
|
|
conn.context = self
|
2016-08-18 03:55:43 +00:00
|
|
|
@setup_connection.call conn
|
|
|
|
conn
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-10-27 15:55:06 +00:00
|
|
|
# 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
|
|
|
|
# ```
|
2016-08-18 03:55:43 +00:00
|
|
|
def setup_connection(&proc : Connection -> Nil)
|
|
|
|
@setup_connection = proc
|
|
|
|
@pool.each_resource do |conn|
|
|
|
|
@setup_connection.call conn
|
|
|
|
end
|
2016-01-29 19:13:01 +00:00
|
|
|
end
|
|
|
|
|
2016-01-31 22:40:02 +00:00
|
|
|
# Closes all connection to the database.
|
2016-01-30 22:46:43 +00:00
|
|
|
def close
|
2016-08-30 19:20:18 +00:00
|
|
|
@statements_cache.each_value &.close
|
|
|
|
@statements_cache.clear
|
|
|
|
|
2016-07-07 18:50:09 +00:00
|
|
|
@pool.close
|
2016-01-30 22:46:43 +00:00
|
|
|
end
|
|
|
|
|
2017-03-20 18:08:30 +00:00
|
|
|
# :nodoc:
|
|
|
|
def discard(connection : Connection)
|
|
|
|
@pool.delete connection
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
|
|
|
def release(connection : Connection)
|
|
|
|
@pool.release connection
|
|
|
|
end
|
|
|
|
|
2016-12-03 18:56:03 +00:00
|
|
|
# :nodoc:
|
2019-08-02 14:54:52 +00:00
|
|
|
def fetch_or_build_prepared_statement(query) : PoolStatement
|
2016-12-03 18:56:03 +00:00
|
|
|
@statements_cache.fetch(query) { build_prepared_statement(query) }
|
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
2019-08-02 14:54:52 +00:00
|
|
|
def build_prepared_statement(query) : PoolStatement
|
2016-12-03 19:03:50 +00:00
|
|
|
PoolPreparedStatement.new(self, query)
|
2016-12-03 18:56:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# :nodoc:
|
2019-08-02 14:54:52 +00:00
|
|
|
def build_unprepared_statement(query) : PoolStatement
|
2016-12-03 18:56:03 +00:00
|
|
|
PoolUnpreparedStatement.new(self, query)
|
2016-01-30 22:46:43 +00:00
|
|
|
end
|
|
|
|
|
2016-02-03 19:57:54 +00:00
|
|
|
# :nodoc:
|
2016-09-12 17:35:21 +00:00
|
|
|
def checkout_some(candidates : Enumerable(WeakRef(Connection))) : {Connection, Bool}
|
2016-08-29 19:56:34 +00:00
|
|
|
@pool.checkout_some candidates
|
2016-02-03 22:30:51 +00:00
|
|
|
end
|
|
|
|
|
2016-06-23 20:27:30 +00:00
|
|
|
# yields a connection from the pool
|
2017-02-12 19:30:32 +00:00
|
|
|
# the connection is returned to the pool
|
2016-06-23 20:27:30 +00:00
|
|
|
# when the block ends
|
|
|
|
def using_connection
|
2017-02-12 19:30:32 +00:00
|
|
|
connection = self.checkout
|
2016-07-11 15:56:40 +00:00
|
|
|
begin
|
|
|
|
yield connection
|
|
|
|
ensure
|
2017-02-12 19:30:32 +00:00
|
|
|
connection.release
|
2016-07-11 15:56:40 +00:00
|
|
|
end
|
2016-06-23 20:27:30 +00:00
|
|
|
end
|
|
|
|
|
2017-02-12 19:30:32 +00:00
|
|
|
# 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
|
|
|
|
|
2016-12-14 15:13:46 +00:00
|
|
|
# yields a `Transaction` from a connection of the pool
|
|
|
|
# Refer to `BeginTransaction#transaction` for documentation.
|
2016-11-16 02:46:11 +00:00
|
|
|
def transaction
|
|
|
|
using_connection do |cnn|
|
|
|
|
cnn.transaction do |tx|
|
|
|
|
yield tx
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-31 20:32:01 +00:00
|
|
|
# :nodoc:
|
|
|
|
def retry
|
|
|
|
@pool.retry do
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
end
|
2016-01-29 19:13:01 +00:00
|
|
|
end
|
|
|
|
end
|