From 385cf70a8a5d41374cce988168e82495743ee459 Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Mon, 20 Mar 2017 15:08:30 -0300 Subject: [PATCH] Introduce DB::ConnectionContext (#44) * make Database a ConnectionContext. * introduce SingleConnectionContext for independant connections. * add `DB#connect` to create non pooled connections. --- spec/custom_drivers_types_spec.cr | 8 +++---- spec/db_spec.cr | 19 ++++++++++++++++ spec/dummy_driver.cr | 8 +++---- src/db.cr | 33 +++++++++++++++++++++++++++- src/db/connection.cr | 10 ++++----- src/db/connection_context.cr | 36 +++++++++++++++++++++++++++++++ src/db/database.cr | 16 +++++++++----- src/db/driver.cr | 8 +++---- 8 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 src/db/connection_context.cr diff --git a/spec/custom_drivers_types_spec.cr b/spec/custom_drivers_types_spec.cr index 8f4a0ca..7b868a6 100644 --- a/spec/custom_drivers_types_spec.cr +++ b/spec/custom_drivers_types_spec.cr @@ -43,8 +43,8 @@ class FooDriver < DB::Driver @@row end - def build_connection(db : DB::Database) : DB::Connection - FooConnection.new(db) + def build_connection(context : DB::ConnectionContext) : DB::Connection + FooConnection.new(context) end class FooConnection < DB::Connection @@ -106,8 +106,8 @@ class BarDriver < DB::Driver @@row end - def build_connection(db : DB::Database) : DB::Connection - BarConnection.new(db) + def build_connection(context : DB::ConnectionContext) : DB::Connection + BarConnection.new(context) end class BarConnection < DB::Connection diff --git a/spec/db_spec.cr b/spec/db_spec.cr index c619c9d..d5836c3 100644 --- a/spec/db_spec.cr +++ b/spec/db_spec.cr @@ -25,6 +25,25 @@ describe DB do connections.first.closed?.should be_true end + it "should create a connection and close it" do + DummyDriver::DummyConnection.clear_connections + DB.connect "dummy://localhost" do |cnn| + cnn.should be_a(DummyDriver::DummyConnection) + end + connections.size.should eq(1) + connections.first.closed?.should be_true + end + + it "should create a connection and wait for explicit closing" do + DummyDriver::DummyConnection.clear_connections + cnn = DB.connect "dummy://localhost" + cnn.should be_a(DummyDriver::DummyConnection) + connections.size.should eq(1) + connections.first.closed?.should be_false + cnn.close + connections.first.closed?.should be_true + end + it "query should close result_set" do with_witness do |w| with_dummy do |db| diff --git a/spec/dummy_driver.cr b/spec/dummy_driver.cr index a5980ac..cda056e 100644 --- a/spec/dummy_driver.cr +++ b/spec/dummy_driver.cr @@ -2,13 +2,13 @@ require "spec" require "../src/db" class DummyDriver < DB::Driver - def build_connection(db : DB::Database) : DB::Connection - DummyConnection.new(db) + def build_connection(context : DB::ConnectionContext) : DB::Connection + DummyConnection.new(context) end class DummyConnection < DB::Connection - def initialize(db) - super(db) + def initialize(context) + super(context) @connected = true @@connections ||= [] of DummyConnection @@connections.not_nil! << self diff --git a/src/db.cr b/src/db.cr index df0ee2a..12f105b 100644 --- a/src/db.cr +++ b/src/db.cr @@ -113,12 +113,42 @@ module DB end end + # Opens a connection using the specified *uri*. + # The scheme of the *uri* determines the driver to use. + # Returned connection must be closed by `Connection#close`. + # If a block is used the connection is yielded and closed automatically. + def self.connect(uri : URI | String) + build_connection(uri) + end + + # ditto + def self.connect(uri : URI | String, &block) + cnn = build_connection(uri) + begin + yield cnn + ensure + cnn.close + end + end + private def self.build_database(connection_string : String) build_database(URI.parse(connection_string)) end private def self.build_database(uri : URI) - Database.new(driver_class(uri.scheme).new, uri) + Database.new(build_driver(uri), uri) + end + + private def self.build_connection(connection_string : String) + build_connection(URI.parse(connection_string)) + end + + private def self.build_connection(uri : URI) + build_driver(uri).build_connection(SingleConnectionContext.new(uri)).as(Connection) + end + + private def self.build_driver(uri : URI) + driver_class(uri.scheme).new end # :nodoc: @@ -144,6 +174,7 @@ require "./db/disposable" require "./db/driver" require "./db/statement" require "./db/begin_transaction" +require "./db/connection_context" require "./db/connection" require "./db/transaction" require "./db/statement" diff --git a/src/db/connection.cr b/src/db/connection.cr index d2ca57c..9b742fb 100644 --- a/src/db/connection.cr +++ b/src/db/connection.cr @@ -24,15 +24,15 @@ module DB include BeginTransaction # :nodoc: - getter database + getter context @statements_cache = StringKeyCache(Statement).new @transaction = false getter? prepared_statements : Bool # :nodoc: property auto_release : Bool = true - def initialize(@database : Database) - @prepared_statements = @database.prepared_statements? + def initialize(@context : ConnectionContext) + @prepared_statements = @context.prepared_statements? end # :nodoc: @@ -59,7 +59,7 @@ module DB protected def do_close @statements_cache.each_value &.close @statements_cache.clear - @database.pool.delete self + @context.discard self end # :nodoc: @@ -75,7 +75,7 @@ module DB # managed by the database. Should be used # only if the connection was obtained by `Database#checkout`. def release - @database.return_to_pool(self) + @context.release(self) end # :nodoc: diff --git a/src/db/connection_context.cr b/src/db/connection_context.cr new file mode 100644 index 0000000..31e81d8 --- /dev/null +++ b/src/db/connection_context.cr @@ -0,0 +1,36 @@ +module DB + module ConnectionContext + # Returns the uri with the connection settings to the database + abstract def uri : URI + + # Return whether the statements should be prepared by default + abstract def prepared_statements? : Bool + + # Indicates that the *connection* was permanently closed + # and should not be used in the future. + abstract def discard(connection : Connection) + + # Indicates that the *connection* is no longer needed + # and can be reused in the future. + abstract def release(connection : Connection) + end + + # :nodoc: + class SingleConnectionContext + include ConnectionContext + + getter uri : URI + getter? prepared_statements : Bool + + def initialize(@uri : URI) + params = HTTP::Params.parse(uri.query || "") + @prepared_statements = DB.fetch_bool(params, "prepared_statements", true) + end + + def discard(connection : Connection) + end + + def release(connection : Connection) + end + end +end diff --git a/src/db/database.cr b/src/db/database.cr index 9c1d8ba..235edfe 100644 --- a/src/db/database.cr +++ b/src/db/database.cr @@ -23,6 +23,7 @@ module DB # Refer to `QueryMethods` and `SessionMethods` for documentation about querying the database. class Database include SessionMethods(Database, PoolStatement) + include ConnectionContext # :nodoc: getter driver @@ -68,6 +69,16 @@ module DB @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) @statements_cache.fetch(query) { build_prepared_statement(query) } @@ -88,11 +99,6 @@ module DB @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 # when the block ends diff --git a/src/db/driver.cr b/src/db/driver.cr index d2d7c94..5df46a2 100644 --- a/src/db/driver.cr +++ b/src/db/driver.cr @@ -6,9 +6,9 @@ module DB # ``` # require "db" # - # class FakeDriver < Driver - # def build_connection(db) - # FakeConnection.new db + # class FakeDriver < DB::Driver + # def build_connection(context : DB::ConnectionContext) + # FakeConnection.new context # end # end # @@ -26,7 +26,7 @@ module DB # Refer to `Connection`, `Statement` and `ResultSet` for further # driver implementation instructions. abstract class Driver - abstract def build_connection(db : Database) : Connection + abstract def build_connection(context : ConnectionContext) : Connection def connection_pool_options(params : HTTP::Params) {