diff --git a/spec/database_spec.cr b/spec/database_spec.cr index 88de311..608c7e6 100644 --- a/spec/database_spec.cr +++ b/spec/database_spec.cr @@ -42,9 +42,9 @@ describe DB::Database do it "should allow creation of more statements than pool connections" do DB.open "dummy://localhost:1027?initial_pool_size=1&max_pool_size=2" do |db| - db.build("query1").should be_a(DB::PoolStatement) - db.build("query2").should be_a(DB::PoolStatement) - db.build("query3").should be_a(DB::PoolStatement) + db.build("query1").should be_a(DB::PoolPreparedStatement) + db.build("query2").should be_a(DB::PoolPreparedStatement) + db.build("query3").should be_a(DB::PoolPreparedStatement) end end @@ -59,8 +59,7 @@ describe DB::Database do it "should close pool statements when closing db" do stmt = uninitialized DB::PoolStatement with_dummy do |db| - # TODO remove cast - stmt = db.build("query1").as(DB::PoolStatement) + stmt = db.build("query1") end stmt.closed?.should be_true end @@ -132,7 +131,7 @@ describe DB::Database do 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) + db.build("the query").should be_a(DB::PoolPreparedStatement) end end diff --git a/src/db.cr b/src/db.cr index faffaa4..71d64c6 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_prepared_statement" require "./db/pool_unprepared_statement" require "./db/result_set" require "./db/error" diff --git a/src/db/database.cr b/src/db/database.cr index 799f276..95d2ecd 100644 --- a/src/db/database.cr +++ b/src/db/database.cr @@ -31,7 +31,7 @@ module DB @pool : Pool(Connection) @setup_connection : Connection -> Nil - @statements_cache = StringKeyCache(PoolStatement).new + @statements_cache = StringKeyCache(PoolPreparedStatement).new # :nodoc: def initialize(@driver : Driver, @uri : URI) @@ -64,7 +64,7 @@ module DB end # :nodoc: - def build(query) + def build(query) : PoolStatement if prepared_statements? fetch_or_build_prepared_statement(query) else @@ -79,7 +79,7 @@ module DB # :nodoc: def build_prepared_statement(query) - PoolStatement.new(self, query) + PoolPreparedStatement.new(self, query) end # :nodoc: diff --git a/src/db/pool_prepared_statement.cr b/src/db/pool_prepared_statement.cr new file mode 100644 index 0000000..9fe8f5d --- /dev/null +++ b/src/db/pool_prepared_statement.cr @@ -0,0 +1,50 @@ +module DB + # Represents a statement to be executed in any of the connections + # of the pool. The statement is not be executed in a prepared fashion. + # The execution of the statement is retried according to the pool configuration. + # + # See `PoolStatement` + class PoolPreparedStatement < PoolStatement + # connections where the statement was prepared + @connections = Set(WeakRef(Connection)).new + + def initialize(db : Database, query : String) + super + # Prepares a statement on some connection + # otherwise the preparation is delayed until the first execution. + # After the first initialization the connection must be released + # it will be checked out when executing it. + statement_with_retry &.release_connection + # TODO use a round-robin selection in the pool so multiple sequentially + # initialized statements are assigned to different connections. + end + + protected def do_close + # TODO close all statements on all connections. + # currently statements are closed when the connection is closed. + + # WHAT-IF the connection is busy? Should each statement be able to + # deallocate itself when the connection is free. + @connections.clear + end + + # builds a statement over a real connection + # the conneciton is registered in `@connections` + private def build_statement + clean_connections + conn, existing = @db.checkout_some(@connections) + @connections << WeakRef.new(conn) unless existing + conn.build(@query) + end + + private def clean_connections + # remove disposed or closed connections + @connections.each do |ref| + conn = ref.target + if !conn || conn.closed? + @connections.delete ref + end + end + end + end +end diff --git a/src/db/pool_statement.cr b/src/db/pool_statement.cr index 237dfd9..5a7b51f 100644 --- a/src/db/pool_statement.cr +++ b/src/db/pool_statement.cr @@ -3,29 +3,10 @@ module DB # a statement from the DB needs to be able to represent a statement in any # of the connections of the pool. Otherwise the user will need to deal with # actual connections in some point. - class PoolStatement + abstract class PoolStatement include StatementMethods - # connections where the statement was prepared - @connections = Set(WeakRef(Connection)).new - def initialize(@db : Database, @query : String) - # Prepares a statement on some connection - # otherwise the preparation is delayed until the first execution. - # After the first initialization the connection must be released - # it will be checked out when executing it. - statement_with_retry &.release_connection - # TODO use a round-robin selection in the pool so multiple sequentially - # initialized statements are assigned to different connections. - end - - protected def do_close - # TODO close all statements on all connections. - # currently statements are closed when the connection is closed. - - # WHAT-IF the connection is busy? Should each statement be able to - # deallocate itself when the connection is free. - @connections.clear end # See `QueryMethods#exec` @@ -60,22 +41,7 @@ module DB # builds a statement over a real connection # the conneciton is registered in `@connections` - private def build_statement - clean_connections - conn, existing = @db.checkout_some(@connections) - @connections << WeakRef.new(conn) unless existing - conn.build(@query) - end - - private def clean_connections - # remove disposed or closed connections - @connections.each do |ref| - conn = ref.target - if !conn || conn.closed? - @connections.delete ref - end - end - end + private abstract def build_statement : Statement private def statement_with_retry @db.retry do diff --git a/src/db/pool_unprepared_statement.cr b/src/db/pool_unprepared_statement.cr index de0c517..1a120e5 100644 --- a/src/db/pool_unprepared_statement.cr +++ b/src/db/pool_unprepared_statement.cr @@ -4,55 +4,18 @@ module DB # The execution of the statement is retried according to the pool configuration. # # See `PoolStatement` - class PoolUnpreparedStatement - include StatementMethods - - def initialize(@db : Database, @query : String) + class PoolUnpreparedStatement < PoolStatement + def initialize(db : Database, query : String) + super 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