major db refactor for a better api.

`#query`, `#exec`, `#scalar`, `#scalar?` as main query methods from `Database`
blocks overrides that ensure statements are closed.
This commit is contained in:
Brian J. Cardiff 2016-01-30 19:46:43 -03:00
parent caf2676aad
commit 683e6bdfa7
12 changed files with 574 additions and 139 deletions

53
spec/std/db/db_spec.cr Normal file
View file

@ -0,0 +1,53 @@
require "spec"
require "db"
require "./dummy_driver"
describe DB do
it "should get driver class by name" do
DB.driver_class("dummy").should eq(DummyDriver)
end
it "should instantiate driver with options" do
db = DB.open "dummy", {"host": "localhost", "port": "1027"}
db.driver_class.should eq(DummyDriver)
db.options["host"].should eq("localhost")
db.options["port"].should eq("1027")
end
it "should create a connection and close it" do
cnn = nil
DB.open "dummy", {"host": "localhost"} do |db|
cnn = db.connection
end
cnn.should be_a(DummyDriver::DummyConnection)
cnn.not_nil!.closed?.should be_true
end
it "query should close statement" do
with_witness do |w|
with_dummy do |db|
db.query "1,2" do
break
end
w.check
db.connection.last_statement.closed?.should be_true
end
end
end
it "exec should close statement" do
with_dummy do |db|
db.exec ""
db.connection.last_statement.closed?.should be_true
end
end
it "scalar should close statement" do
with_dummy do |db|
db.scalar "1"
db.connection.last_statement.closed?.should be_true
end
end
end

View file

@ -1,16 +0,0 @@
require "spec"
require "db"
require "./dummy_driver"
describe DB::Driver do
it "should get driver class by name" do
DB.driver_class("dummy").should eq(DummyDriver)
end
it "should instantiate driver with options" do
db = DB.open "dummy", {"host": "localhost", "port": "1027"}
db.driver_class.should eq(DummyDriver)
db.options["host"].should eq("localhost")
db.options["port"].should eq("1027")
end
end

View file

@ -1,6 +1,19 @@
require "spec"
class DummyDriver < DB::Driver
def prepare(query)
DummyStatement.new(self, query.split.map { |r| r.split ',' })
def build_connection
DummyConnection.new(options)
end
class DummyConnection < DB::Connection
getter! last_statement
def prepare(query)
@last_statement = DummyStatement.new(self, query.split.map { |r| r.split ',' })
end
def perform_close
end
end
class DummyStatement < DB::Statement
@ -10,6 +23,10 @@ class DummyDriver < DB::Driver
super(driver)
end
protected def begin_parameters
@params = Hash(Int32 | String, DB::Any?).new
end
protected def add_parameter(index : Int32, value)
params[index] = value
end
@ -18,11 +35,7 @@ class DummyDriver < DB::Driver
params[":#{name}"] = value
end
protected def before_execute
@params = Hash(Int32 | String, DB::Any).new
end
protected def execute
protected def perform
DummyResultSet.new self, @items.each
end
end
@ -48,6 +61,10 @@ class DummyDriver < DB::Driver
"c#{index}"
end
def column_type(index : Int32)
String
end
private def read? : DB::Any?
n = @values.not_nil!.next
raise "end of row" if n.is_a?(Iterator::Stop)
@ -91,8 +108,10 @@ class DummyDriver < DB::Driver
elsif value.is_a?(String)
ary = value.bytes
Slice.new(ary.to_unsafe, ary.size)
elsif value.is_a?(Slice(UInt8))
value
else
value as Slice(UInt8)
raise "#{value} is not convertible to Slice(UInt8)"
end
end
end
@ -100,6 +119,25 @@ end
DB.register_driver "dummy", DummyDriver
def get_dummy
DB.open "dummy", {} of String => String
class Witness
getter count
def initialize(@count)
end
def check
@count -= 1
end
end
def with_witness(count = 1)
w = Witness.new(count)
yield w
w.count.should eq(0), "The expected coverage was unmet"
end
def with_dummy
DB.open "dummy", {} of String => String do |db|
yield db
end
end

View file

@ -3,118 +3,169 @@ require "db"
require "./dummy_driver"
describe DummyDriver do
it "should return statements" do
get_dummy.prepare("the query").should be_a(DB::Statement)
it "with_dummy executes the block with a database" do
with_witness do |w|
with_dummy do |db|
w.check
db.should be_a(DB::Database)
end
end
end
describe DummyDriver::DummyStatement do
it "exec should return a result_set" do
statement = get_dummy.prepare("a,b 1,2")
result_set = statement.exec
result_set.should be_a(DB::ResultSet)
result_set.statement.should be(statement)
it "should enumerate split rows by spaces" do
with_dummy do |db|
rs = db.query("")
rs.move_next.should be_false
rs.close
rs = db.query("a,b")
rs.move_next.should be_true
rs.move_next.should be_false
rs.close
rs = db.query("a,b 1,2")
rs.move_next.should be_true
rs.move_next.should be_true
rs.move_next.should be_false
rs.close
rs = db.query("a,b 1,2 c,d")
rs.move_next.should be_true
rs.move_next.should be_true
rs.move_next.should be_true
rs.move_next.should be_false
rs.close
end
end
it "should enumerate records by spaces" do
result_set = get_dummy.prepare("").exec
result_set.move_next.should be_false
it "should query with block shuold executes always" do
with_witness do |w|
with_dummy do |db|
db.query "" do |rs|
w.check
end
end
end
result_set = get_dummy.prepare("a,b").exec
result_set.move_next.should be_true
result_set.move_next.should be_false
result_set = get_dummy.prepare("a,b 1,2").exec
result_set.move_next.should be_true
result_set.move_next.should be_true
result_set.move_next.should be_false
result_set = get_dummy.prepare("a,b 1,2 c,d").exec
result_set.move_next.should be_true
result_set.move_next.should be_true
result_set.move_next.should be_true
result_set.move_next.should be_false
with_witness do |w|
with_dummy do |db|
db.query "lorem ipsum" do |rs|
w.check
end
end
end
end
it "should enumerate string fields" do
result_set = get_dummy.prepare("a,b 1,2").exec
result_set.move_next
result_set.read(String).should eq("a")
result_set.read(String).should eq("b")
result_set.move_next
result_set.read(String).should eq("1")
result_set.read(String).should eq("2")
with_dummy do |db|
db.query "a,b 1,2" do |rs|
rs.move_next
rs.read(String).should eq("a")
rs.read(String).should eq("b")
rs.move_next
rs.read(String).should eq("1")
rs.read(String).should eq("2")
end
end
end
it "should enumerate nil fields" do
result_set = get_dummy.prepare("a,NULL 1,NULL").exec
result_set.move_next
result_set.read?(String).should eq("a")
result_set.read?(String).should be_nil
result_set.move_next
result_set.read?(Int64).should eq(1)
result_set.read?(Int64).should be_nil
with_dummy do |db|
db.query "a,NULL 1,NULL" do |rs|
rs.move_next
rs.read?(String).should eq("a")
rs.read?(String).should be_nil
rs.move_next
rs.read?(Int64).should eq(1)
rs.read?(Int64).should be_nil
end
end
end
it "should enumerate int64 fields" do
result_set = get_dummy.prepare("3,4 1,2").exec
result_set.move_next
result_set.read(Int64).should eq(3i64)
result_set.read(Int64).should eq(4i64)
result_set.move_next
result_set.read(Int64).should eq(1i64)
result_set.read(Int64).should eq(2i64)
with_dummy do |db|
db.query "3,4 1,2" do |rs|
rs.move_next
rs.read(Int64).should eq(3i64)
rs.read(Int64).should eq(4i64)
rs.move_next
rs.read(Int64).should eq(1i64)
rs.read(Int64).should eq(2i64)
end
end
end
it "should enumerate blob fields" do
result_set = get_dummy.prepare("az,AZ").exec
result_set.move_next
ary = [97u8, 122u8]
result_set.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
ary = [65u8, 90u8]
result_set.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
with_dummy do |db|
db.query("az,AZ") do |rs|
rs.move_next
ary = [97u8, 122u8]
rs.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
ary = [65u8, 90u8]
rs.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
end
end
end
it "should enumerate records using each" do
nums = [] of Int32
result_set = get_dummy.prepare("3,4 1,2").exec
result_set.each do
nums << result_set.read(Int32)
nums << result_set.read(Int32)
it "should get Int32 scalars by default" do
with_dummy do |db|
db.scalar("1").should be_a(Int32)
db.scalar?("1").should be_a(Int32)
db.scalar?("NULL").should be_nil
end
end
nums.should eq([3, 4, 1, 2])
it "should get String scalars" do
with_dummy do |db|
db.scalar(String, "foo").should eq("foo")
end
end
{% for value in [1, 1_i64, "hello", 1.5, 1.5_f32] %}
it "should set arguments for {{value.id}}" do
result_set = get_dummy.exec "?", {{value}}
result_set.move_next.should be_true
result_set.read(typeof({{value}})).should eq({{value}})
it "numeric scalars of type of {{value.id}} should return value or nil" do
with_dummy do |db|
db.scalar(typeof({{value}}), "#{{{value}}}").should eq({{value}})
db.scalar?(typeof({{value}}), "#{{{value}}}").should eq({{value}})
db.scalar?(typeof({{value}}), "NULL").should be_nil
end
end
it "should set positional arguments for {{value.id}}" do
with_dummy do |db|
db.scalar(typeof({{value}}), "?", {{value}}).should eq({{value}})
end
end
it "should set arguments by symbol for {{value.id}}" do
result_set = get_dummy.exec ":once :twice", {once: {{value}}, twice: {{value + value}} }
result_set.move_next.should be_true
result_set.read(typeof({{value}})).should eq({{value}})
result_set.move_next.should be_true
result_set.read(typeof({{value}})).should eq({{value + value}})
with_dummy do |db|
db.query ":once :twice", {once: {{value}}, twice: {{value + value}} } do |rs|
rs.move_next.should be_true
rs.read(typeof({{value}})).should eq({{value}})
rs.move_next.should be_true
rs.read(typeof({{value}})).should eq({{value + value}})
end
end
end
it "should set arguments by string for {{value.id}}" do
result_set = get_dummy.exec ":once :twice", {"once": {{value}}, "twice": {{value + value}} }
result_set.move_next.should be_true
result_set.read(typeof({{value}})).should eq({{value}})
result_set.move_next.should be_true
result_set.read(typeof({{value}})).should eq({{value + value}})
with_dummy do |db|
db.query ":once :twice", {"once": {{value}}, "twice": {{value + value}} } do |rs|
rs.move_next.should be_true
rs.read(typeof({{value}})).should eq({{value}})
rs.move_next.should be_true
rs.read(typeof({{value}})).should eq({{value + value}})
end
end
end
{% end %}
it "executes and selects blob" do
ary = UInt8[0x53, 0x51, 0x4C]
slice = Slice.new(ary.to_unsafe, ary.size)
result_set = get_dummy.exec "?", slice
result_set.move_next
result_set.read(Slice(UInt8)).to_a.should eq(ary)
with_dummy do |db|
ary = UInt8[0x53, 0x51, 0x4C]
slice = Slice.new(ary.to_unsafe, ary.size)
db.scalar(Slice(UInt8), "?", slice).to_a.should eq(ary)
end
end
end
end

View file

@ -0,0 +1,20 @@
require "spec"
require "db"
require "./dummy_driver"
describe DB::ResultSet do
it "should enumerate records using each" do
nums = [] of Int32
with_dummy do |db|
db.query "3,4 1,2" do |rs|
rs.each do
nums << rs.read(Int32)
nums << rs.read(Int32)
end
end
end
nums.should eq([3, 4, 1, 2])
end
end

View file

@ -0,0 +1,181 @@
require "spec"
require "db"
require "./dummy_driver"
describe DB::Statement do
it "should prepare statements" do
with_dummy do |db|
db.prepare("the query").should be_a(DB::Statement)
end
end
it "should initialize positional params in query" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.query "a", 1, nil
stmt.params[1].should eq("a")
stmt.params[2].should eq(1)
stmt.params[3].should eq(nil)
end
end
it "should initialize symbol named params in query" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.query({a: "a", b: 1, c: nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize string named params in query" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.query({"a": "a", "b": 1, "c": nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize positional params in exec" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.exec "a", 1, nil
stmt.params[1].should eq("a")
stmt.params[2].should eq(1)
stmt.params[3].should eq(nil)
end
end
it "should initialize symbol named params in exec" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.exec({a: "a", b: 1, c: nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize string named params in exec" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.exec({"a": "a", "b": 1, "c": nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize positional params in scalar" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar String, "a", 1, nil
stmt.params[1].should eq("a")
stmt.params[2].should eq(1)
stmt.params[3].should eq(nil)
end
end
it "should initialize symbol named params in scalar" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar(String, {a: "a", b: 1, c: nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize string named params in scalar" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar(String, {"a": "a", "b": 1, "c": nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize positional params in scalar?" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar? String, "a", 1, nil
stmt.params[1].should eq("a")
stmt.params[2].should eq(1)
stmt.params[3].should eq(nil)
end
end
it "should initialize symbol named params in scalar?" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar?(String, {a: "a", b: 1, c: nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "should initialize string named params in scalar?" do
with_dummy do |db|
stmt = db.prepare("the query")
stmt.scalar?(String, {"a": "a", "b": 1, "c": nil})
stmt.params[":a"].should eq("a")
stmt.params[":b"].should eq(1)
stmt.params[":c"].should eq(nil)
end
end
it "query with block should not close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.query
stmt.closed?.should be_false
end
end
it "query with block should close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.query do |rs|
end
stmt.closed?.should be_true
end
end
it "query should close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.query do |rs|
end
stmt.closed?.should be_true
end
end
it "scalar should close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.scalar
stmt.closed?.should be_true
end
end
it "scalar should close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.scalar?
stmt.closed?.should be_true
end
end
it "exec should close statement" do
with_dummy do |db|
stmt = db.prepare "3,4 1,2"
stmt.exec
stmt.closed?.should be_true
end
end
end