Skip to content

Commit

Permalink
Merge pull request #25 from crystal-lang/feature/unprepared
Browse files Browse the repository at this point in the history
Support non prepared statements
  • Loading branch information
bcardiff authored Dec 13, 2016
2 parents cd871c9 + da2831c commit 825046e
Show file tree
Hide file tree
Showing 14 changed files with 376 additions and 85 deletions.
12 changes: 10 additions & 2 deletions spec/custom_drivers_types_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ class FooDriver < DB::Driver
end

class FooConnection < DB::Connection
def build_statement(query)
def build_prepared_statement(query)
FooStatement.new(self)
end

def build_unprepared_statement(query)
raise "not implemented"
end
end

class FooStatement < DB::Statement
Expand Down Expand Up @@ -107,9 +111,13 @@ class BarDriver < DB::Driver
end

class BarConnection < DB::Connection
def build_statement(query)
def build_prepared_statement(query)
BarStatement.new(self)
end

def build_unprepared_statement(query)
raise "not implemented"
end
end

class BarStatement < DB::Statement
Expand Down
96 changes: 89 additions & 7 deletions spec/database_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,24 @@ 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.prepare("query1").should be_a(DB::PoolStatement)
db.prepare("query2").should be_a(DB::PoolStatement)
db.prepare("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

it "should return same statement in pool per query" do
with_dummy do |db|
stmt = db.prepare("query1")
db.prepare("query2").should_not eq(stmt)
db.prepare("query1").should eq(stmt)
stmt = db.build("query1")
db.build("query2").should_not eq(stmt)
db.build("query1").should eq(stmt)
end
end

it "should close pool statements when closing db" do
stmt = uninitialized DB::PoolStatement
with_dummy do |db|
stmt = db.prepare("query1")
stmt = db.build("query1")
end
stmt.closed?.should be_true
end
Expand Down Expand Up @@ -97,4 +97,86 @@ describe DB::Database do
DummyDriver::DummyConnection.connections.size.should eq(2)
end
end

describe "prepared_statements connection option" do
it "defaults to true" do
with_dummy "dummy://localhost:1027" do |db|
db.prepared_statements?.should be_true
end
end

it "can be set to false" do
with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
db.prepared_statements?.should be_false
end
end

it "is copied to connections (false)" do
with_dummy "dummy://localhost:1027?prepared_statements=false&initial_pool_size=1" do |db|
connection = DummyDriver::DummyConnection.connections.first
connection.prepared_statements?.should be_false
end
end

it "is copied to connections (true)" do
with_dummy "dummy://localhost:1027?prepared_statements=true&initial_pool_size=1" do |db|
connection = DummyDriver::DummyConnection.connections.first
connection.prepared_statements?.should be_true
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::PoolPreparedStatement)
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

it "should be overrided by dsl" do
with_dummy "dummy://localhost:1027?prepared_statements=true" do |db|
stmt = db.unprepared.query("the query").statement.as(DummyDriver::DummyStatement)
stmt.prepared?.should be_false
end

with_dummy "dummy://localhost:1027?prepared_statements=false" do |db|
stmt = db.prepared.query("the query").statement.as(DummyDriver::DummyStatement)
stmt.prepared?.should be_true
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
15 changes: 15 additions & 0 deletions spec/db_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,19 @@ describe DB do
DB.open "foobar://baz"
end
end

it "should parse boolean query string params" do
DB.fetch_bool(HTTP::Params.parse("foo=true"), "foo", false).should be_true
DB.fetch_bool(HTTP::Params.parse("foo=True"), "foo", false).should be_true

DB.fetch_bool(HTTP::Params.parse("foo=false"), "foo", true).should be_false
DB.fetch_bool(HTTP::Params.parse("foo=False"), "foo", true).should be_false

DB.fetch_bool(HTTP::Params.parse("bar=true"), "foo", false).should be_false
DB.fetch_bool(HTTP::Params.parse("bar=true"), "foo", true).should be_true

expect_raises(ArgumentError, %(invalid "other" value for option "foo")) do
DB.fetch_bool(HTTP::Params.parse("foo=other"), "foo", true)
end
end
end
18 changes: 13 additions & 5 deletions spec/dummy_driver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ class DummyDriver < DB::Driver
@@connections.try &.clear
end

def build_statement(query)
DummyStatement.new(self, query)
def build_prepared_statement(query)
DummyStatement.new(self, query, true)
end

def build_unprepared_statement(query)
DummyStatement.new(self, query, false)
end

def last_insert_id : Int64
Expand All @@ -46,7 +50,7 @@ class DummyDriver < DB::Driver
class DummyStatement < DB::Statement
property params

def initialize(connection, @query : String)
def initialize(connection, @query : String, @prepared : Bool)
@params = Hash(Int32 | String, DB::Any).new
super(connection)
end
Expand Down Expand Up @@ -79,6 +83,10 @@ class DummyDriver < DB::Driver
raise "not implemented for #{value.class}"
end

def prepared?
@prepared
end

protected def do_close
super
end
Expand Down Expand Up @@ -204,8 +212,8 @@ def with_dummy(uri : String = "dummy://host?checkout_timeout=0.5")
end
end

def with_dummy_connection
with_dummy do |db|
def with_dummy_connection(options = "")
with_dummy("dummy://host?checkout_timeout=0.5&#{options}") do |db|
db.using_connection do |cnn|
yield cnn.as(DummyDriver::DummyConnection)
end
Expand Down
56 changes: 41 additions & 15 deletions spec/statement_spec.cr
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
require "./spec_helper"

describe DB::Statement do
it "should prepare statements" do
it "should build prepared statements" do
with_dummy_connection do |cnn|
cnn.prepare("the query").should be_a(DB::Statement)
prepared = cnn.prepared("the query")
prepared.should be_a(DB::Statement)
prepared.as(DummyDriver::DummyStatement).prepared?.should be_true
end
end

it "should build unprepared statements" do
with_dummy_connection("prepared_statements=false") do |cnn|
prepared = cnn.unprepared("the query")
prepared.should be_a(DB::Statement)
prepared.as(DummyDriver::DummyStatement).prepared?.should be_false
end
end

describe "prepared_statements flag" do
it "should build prepared statements if true" do
with_dummy_connection("prepared_statements=true") do |cnn|
stmt = cnn.query("the query").statement
stmt.as(DummyDriver::DummyStatement).prepared?.should be_true
end
end

it "should build unprepared statements if false" do
with_dummy_connection("prepared_statements=false") do |cnn|
stmt = cnn.query("the query").statement
stmt.as(DummyDriver::DummyStatement).prepared?.should be_false
end
end
end

it "should initialize positional params in query" do
with_dummy_connection do |cnn|
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
stmt = cnn.prepared("the query").as(DummyDriver::DummyStatement)
stmt.query "a", 1, nil
stmt.params[0].should eq("a")
stmt.params[1].should eq(1)
Expand All @@ -19,7 +45,7 @@ describe DB::Statement do

it "should initialize positional params in query with array" do
with_dummy_connection do |cnn|
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
stmt = cnn.prepared("the query").as(DummyDriver::DummyStatement)
stmt.query ["a", 1, nil]
stmt.params[0].should eq("a")
stmt.params[1].should eq(1)
Expand All @@ -29,7 +55,7 @@ describe DB::Statement do

it "should initialize positional params in exec" do
with_dummy_connection do |cnn|
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
stmt = cnn.prepared("the query").as(DummyDriver::DummyStatement)
stmt.exec "a", 1, nil
stmt.params[0].should eq("a")
stmt.params[1].should eq(1)
Expand All @@ -39,7 +65,7 @@ describe DB::Statement do

it "should initialize positional params in exec with array" do
with_dummy_connection do |cnn|
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
stmt = cnn.prepared("the query").as(DummyDriver::DummyStatement)
stmt.exec ["a", 1, nil]
stmt.params[0].should eq("a")
stmt.params[1].should eq(1)
Expand All @@ -49,7 +75,7 @@ describe DB::Statement do

it "should initialize positional params in scalar" do
with_dummy_connection do |cnn|
stmt = cnn.prepare("the query").as(DummyDriver::DummyStatement)
stmt = cnn.prepared("the query").as(DummyDriver::DummyStatement)
stmt.scalar "a", 1, nil
stmt.params[0].should eq("a")
stmt.params[1].should eq(1)
Expand All @@ -59,7 +85,7 @@ describe DB::Statement do

it "query with block should not close statement" do
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.query
stmt.closed?.should be_false
end
Expand All @@ -68,15 +94,15 @@ describe DB::Statement do
it "closing connection should close statement" do
stmt = uninitialized DB::Statement
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.query
end
stmt.closed?.should be_true
end

it "query with block should not close statement" do
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.query do |rs|
end
stmt.closed?.should be_false
Expand All @@ -85,7 +111,7 @@ describe DB::Statement do

it "query should not close statement" do
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.query do |rs|
end
stmt.closed?.should be_false
Expand All @@ -94,27 +120,27 @@ describe DB::Statement do

it "scalar should not close statement" do
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.scalar
stmt.closed?.should be_false
end
end

it "exec should not close statement" do
with_dummy_connection do |cnn|
stmt = cnn.prepare "3,4 1,2"
stmt = cnn.prepared "3,4 1,2"
stmt.exec
stmt.closed?.should be_false
end
end

it "connection should cache statements by query" do
with_dummy_connection do |cnn|
rs = cnn.query "1, ?", 2
rs = cnn.prepared.query "1, ?", 2
stmt = rs.statement
rs.close

rs = cnn.query "1, ?", 4
rs = cnn.prepared.query "1, ?", 4
rs.statement.should be(stmt)
end
end
Expand Down
Loading

0 comments on commit 825046e

Please sign in to comment.