diff --git a/spec/database_spec.cr b/spec/database_spec.cr index 35df12a..88de311 100644 --- a/spec/database_spec.cr +++ b/spec/database_spec.cr @@ -59,7 +59,8 @@ describe DB::Database do it "should close pool statements when closing db" do stmt = uninitialized DB::PoolStatement with_dummy do |db| - stmt = db.build("query1") + # TODO remove cast + stmt = db.build("query1").as(DB::PoolStatement) end stmt.closed?.should be_true end @@ -128,5 +129,47 @@ describe DB::Database do connection.prepared_statements?.should be_false end end + + it "should build prepared statements if true" do + with_dummy "dummy://localhost:1027?prepared_statements=true" do |db| + db.build("the query").should be_a(DB::PoolStatement) + end + end + + it "should build unprepared statements if false" do + with_dummy "dummy://localhost:1027?prepared_statements=false" do |db| + db.build("the query").should be_a(DB::PoolUnpreparedStatement) + end + end + end + + describe "unprepared statements in pool" do + it "creating statements should not create new connections" do + with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db| + stmt1 = db.unprepared.build("query1") + stmt2 = db.unprepared.build("query2") + DummyDriver::DummyConnection.connections.size.should eq(1) + end + end + + it "simultaneous statements should go to different connections" do + with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db| + rs1 = db.unprepared.query("query1") + rs2 = db.unprepared.query("query2") + rs1.statement.connection.should_not eq(rs2.statement.connection) + DummyDriver::DummyConnection.connections.size.should eq(2) + end + end + + it "sequential statements should go to different connections" do + with_dummy "dummy://localhost:1027?initial_pool_size=1" do |db| + rs1 = db.unprepared.query("query1") + rs1.close + rs2 = db.unprepared.query("query2") + rs2.close + rs1.statement.connection.should eq(rs2.statement.connection) + DummyDriver::DummyConnection.connections.size.should eq(1) + end + end end end diff --git a/src/db.cr b/src/db.cr index bd18035..faffaa4 100644 --- a/src/db.cr +++ b/src/db.cr @@ -140,6 +140,7 @@ require "./db/driver" require "./db/connection" require "./db/statement" require "./db/pool_statement" +require "./db/pool_unprepared_statement" require "./db/result_set" require "./db/error" require "./db/mapping" diff --git a/src/db/database.cr b/src/db/database.cr index 31c011c..799f276 100644 --- a/src/db/database.cr +++ b/src/db/database.cr @@ -17,6 +17,8 @@ module DB # # Refer to `QueryMethods` for documentation about querying the database. class Database + include QueryMethods + # :nodoc: getter driver # :nodoc: @@ -63,7 +65,26 @@ module DB # :nodoc: def build(query) - @statements_cache.fetch(query) { PoolStatement.new(self, query) } + if prepared_statements? + fetch_or_build_prepared_statement(query) + else + build_unprepared_statement(query) + end + end + + # :nodoc: + def fetch_or_build_prepared_statement(query) + @statements_cache.fetch(query) { build_prepared_statement(query) } + end + + # :nodoc: + def build_prepared_statement(query) + PoolStatement.new(self, query) + end + + # :nodoc: + def build_unprepared_statement(query) + PoolUnpreparedStatement.new(self, query) end # :nodoc: @@ -95,6 +116,48 @@ module DB end end - include QueryMethods + # dsl helper to build prepared statements + # returns a value that includes `QueryMethods` + def prepared + PreparedQuery.new(self) + end + + # Returns a prepared `Statement` that has not been executed yet. + def prepared(query) + prepared.build(query) + end + + # dsl helper to build unprepared statements + # returns a value that includes `QueryMethods` + def unprepared + UnpreparedQuery.new(self) + end + + # Returns an unprepared `Statement` that has not been executed yet. + def unprepared(query) + unprepared.build(query) + end + + struct PreparedQuery + include QueryMethods + + def initialize(@db : Database) + end + + def build(query) + @db.fetch_or_build_prepared_statement(query) + end + end + + struct UnpreparedQuery + include QueryMethods + + def initialize(@db : Database) + end + + def build(query) + @db.build_unprepared_statement(query) + end + end end end diff --git a/src/db/pool_unprepared_statement.cr b/src/db/pool_unprepared_statement.cr new file mode 100644 index 0000000..de0c517 --- /dev/null +++ b/src/db/pool_unprepared_statement.cr @@ -0,0 +1,58 @@ +module DB + # Represents a statement to be executed in any of the connections + # of the pool. The statement is not be executed in a non prepared fashion. + # The execution of the statement is retried according to the pool configuration. + # + # See `PoolStatement` + class PoolUnpreparedStatement + include StatementMethods + + def initialize(@db : Database, @query : String) + end + + protected def do_close + # unprepared statements do not need to be release in each connection + end + + # See `QueryMethods#exec` + def exec : ExecResult + statement_with_retry &.exec + end + + # See `QueryMethods#exec` + def exec(*args) : ExecResult + statement_with_retry &.exec(*args) + end + + # See `QueryMethods#exec` + def exec(args : Array) : ExecResult + statement_with_retry &.exec(args) + end + + # See `QueryMethods#query` + def query : ResultSet + statement_with_retry &.query + end + + # See `QueryMethods#query` + def query(*args) : ResultSet + statement_with_retry &.query(*args) + end + + # See `QueryMethods#query` + def query(args : Array) : ResultSet + statement_with_retry &.query(args) + end + + # builds a statement over a real connection + private def build_statement + @db.pool.checkout.unprepared.build(@query) + end + + private def statement_with_retry + @db.retry do + return yield build_statement + end + end + end +end