require "http/params" require "weak_ref" module DB # Acts as an entry point for database access. # Connections are managed by a pool. # 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`|`false` (default `true`) # # It should be created from DB module. See `DB#open`. # # Refer to `QueryMethods` and `SessionMethods` for documentation about querying the database. class Database include SessionMethods(Database, PoolStatement) # :nodoc: getter driver # :nodoc: getter pool # Returns the uri with the connection settings to the database getter uri getter? prepared_statements : Bool @pool : Pool(Connection) @setup_connection : Connection -> Nil @statements_cache = StringKeyCache(PoolPreparedStatement).new # :nodoc: def initialize(@driver : Driver, @uri : URI) params = HTTP::Params.parse(uri.query || "") @prepared_statements = DB.fetch_bool(params, "prepared_statements", true) pool_options = @driver.connection_pool_options(params) @setup_connection = ->(conn : Connection) {} @pool = uninitialized Pool(Connection) # in order to use self in the factory proc @pool = Pool.new(**pool_options) { conn = @driver.build_connection(self).as(Connection) @setup_connection.call conn conn } 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 @statements_cache.each_value &.close @statements_cache.clear @pool.close end # :nodoc: def fetch_or_build_prepared_statement(query) @statements_cache.fetch(query) { build_prepared_statement(query) } end # :nodoc: def build_prepared_statement(query) PoolPreparedStatement.new(self, query) end # :nodoc: def build_unprepared_statement(query) PoolUnpreparedStatement.new(self, query) end # :nodoc: def checkout_some(candidates : Enumerable(WeakRef(Connection))) : {Connection, Bool} @pool.checkout_some candidates end # :nodoc: def return_to_pool(connection) @pool.release connection end # yields a connection from the pool # the connection is returned to the pool after # when the block ends def using_connection connection = @pool.checkout begin yield connection ensure return_to_pool connection end end # :nodoc: def retry @pool.retry do yield end end end end