introduce database as driver wrapper.

expose list of types to support by the drivers implementors.
deal with nilable types with `#read?(T.class) : T?` methods.
change `#has_next` to `#move_next`
This commit is contained in:
Brian J. Cardiff 2016-01-29 16:13:01 -03:00
parent c16dc42e96
commit 1572062501
7 changed files with 91 additions and 51 deletions

View file

@ -8,8 +8,9 @@ describe DB::Driver do
end end
it "should instantiate driver with options" do it "should instantiate driver with options" do
driver = DB.driver "dummy", {"host": "localhost", "port": "1027"} db = DB.open "dummy", {"host": "localhost", "port": "1027"}
driver.options["host"].should eq("localhost") db.driver_class.should eq(DummyDriver)
driver.options["port"].should eq("1027") db.options["host"].should eq("localhost")
db.options["port"].should eq("1027")
end end
end end

View file

@ -18,7 +18,7 @@ class DummyDriver < DB::Driver
super(statement) super(statement)
end end
def has_next def move_next
@iterator.next.tap do |n| @iterator.next.tap do |n|
return false if n.is_a?(Iterator::Stop) return false if n.is_a?(Iterator::Stop)
@values = n.each @values = n.each
@ -26,12 +26,27 @@ class DummyDriver < DB::Driver
end end
end end
def read_string def read?(t : String.class)
@values.not_nil!.next as String n = @values.not_nil!.next
raise "end of row" if n.is_a?(Iterator::Stop)
return nil if n == "NULL"
return n as String
end end
def read_u_int64 def read?(t : Int32.class)
read_string.to_u64 read?(String).try &.to_i32
end
def read?(t : Int64.class)
read?(String).try &.to_i64
end
def read?(t : Float32.class)
read?(String).try &.to_f23
end
def read?(t : Float64.class)
read?(String).try &.to_f64
end end
end end
end end
@ -39,5 +54,5 @@ end
DB.register_driver "dummy", DummyDriver DB.register_driver "dummy", DummyDriver
def get_dummy def get_dummy
DB.driver "dummy", {} of String => String DB.open "dummy", {} of String => String
end end

View file

@ -17,50 +17,60 @@ describe DummyDriver do
it "should enumerate records by spaces" do it "should enumerate records by spaces" do
result_set = get_dummy.prepare("").exec result_set = get_dummy.prepare("").exec
result_set.has_next.should be_false result_set.move_next.should be_false
result_set = get_dummy.prepare("a,b").exec result_set = get_dummy.prepare("a,b").exec
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_false result_set.move_next.should be_false
result_set = get_dummy.prepare("a,b 1,2").exec result_set = get_dummy.prepare("a,b 1,2").exec
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_false result_set.move_next.should be_false
result_set = get_dummy.prepare("a,b 1,2 c,d").exec result_set = get_dummy.prepare("a,b 1,2 c,d").exec
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_true result_set.move_next.should be_true
result_set.has_next.should be_false result_set.move_next.should be_false
end end
it "should enumerate string fields" do it "should enumerate string fields" do
result_set = get_dummy.prepare("a,b 1,2").exec result_set = get_dummy.prepare("a,b 1,2").exec
result_set.has_next result_set.move_next
result_set.read(String).should eq("a") result_set.read(String).should eq("a")
result_set.read(String).should eq("b") result_set.read(String).should eq("b")
result_set.has_next result_set.move_next
result_set.read(String).should eq("1") result_set.read(String).should eq("1")
result_set.read(String).should eq("2") result_set.read(String).should eq("2")
end end
it "should enumerate uint64 fields" do 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
end
it "should enumerate int64 fields" do
result_set = get_dummy.prepare("3,4 1,2").exec result_set = get_dummy.prepare("3,4 1,2").exec
result_set.has_next result_set.move_next
result_set.read(UInt64).should eq(3) result_set.read(Int64).should eq(3i64)
result_set.read(UInt64).should eq(4) result_set.read(Int64).should eq(4i64)
result_set.has_next result_set.move_next
result_set.read(UInt64).should eq(1) result_set.read(Int64).should eq(1i64)
result_set.read(UInt64).should eq(2) result_set.read(Int64).should eq(2i64)
end end
it "should enumerate records using each" do it "should enumerate records using each" do
nums = [] of UInt64 nums = [] of Int32
result_set = get_dummy.prepare("3,4 1,2").exec result_set = get_dummy.prepare("3,4 1,2").exec
result_set.each do result_set.each do
nums << result_set.read(UInt64) nums << result_set.read(Int32)
nums << result_set.read(UInt64) nums << result_set.read(Int32)
end end
nums.should eq([3, 4, 1, 2]) nums.should eq([3, 4, 1, 2])

20
src/db/database.cr Normal file
View file

@ -0,0 +1,20 @@
module DB
# Acts as an entry point for database access.
# Offers a com
class Database
getter driver_class
getter options
def initialize(@driver_class, @options)
@driver = @driver_class.new(@options)
end
def prepare(query)
@driver.prepare(query)
end
def exec(query, *args)
prepare(query).exec(*args)
end
end
end

View file

@ -1,4 +1,6 @@
module DB module DB
TYPES = [String, Int32, Int64, Float32, Float64]
def self.driver_class(name) # : Driver.class def self.driver_class(name) # : Driver.class
@@drivers.not_nil![name] @@drivers.not_nil![name]
end end
@ -8,11 +10,12 @@ module DB
@@drivers.not_nil![name] = klass @@drivers.not_nil![name] = klass
end end
def self.driver(name, options) def self.open(name, options)
driver_class(name).new(options) Database.new(driver_class(name), options)
end end
end end
require "./database"
require "./driver" require "./driver"
require "./statement" require "./statement"
require "./result_set" require "./result_set"

View file

@ -6,32 +6,21 @@ module DB
end end
def each def each
while has_next while move_next
yield yield
end end
end end
abstract def has_next : Bool abstract def move_next : Bool
# def read(t : T.class) : T
# end
# list datatypes that must be supported form the driver # list datatypes that must be supported form the driver
# implementors will override read_string # users will call read(String) or read?(String) for nillables
# users will call read(String) due to overloads read(T) will be a T {% for t in DB::TYPES %}
# TODO: unable to write unions (nillables) abstract def read?(t : {{t}}.class) : {{t}}?
{% for t in [String, UInt64] %}
def read(t : {{t}}.class) : {{t}} def read(t : {{t}}.class) : {{t}}
read_{{t.name.underscore}} read?({{t}}).not_nil!
end end
protected abstract def read_{{t.name.underscore}} : {{t}}
{% end %} {% end %}
# def read(t : String.class) : String
# read_string
# end
#
# protected abstract def read_string : String
end end
end end

View file

@ -1,5 +1,7 @@
module DB module DB
abstract class Statement abstract class Statement
getter driver
def initialize(@driver) def initialize(@driver)
end end