mirror of
https://gitea.invidious.io/iv-org/shard-crystal-db.git
synced 2024-08-15 00:53:32 +00:00
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:
parent
caf2676aad
commit
683e6bdfa7
12 changed files with 574 additions and 139 deletions
53
spec/std/db/db_spec.cr
Normal file
53
spec/std/db/db_spec.cr
Normal 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
|
|
@ -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
|
|
|
@ -1,6 +1,19 @@
|
||||||
|
require "spec"
|
||||||
|
|
||||||
class DummyDriver < DB::Driver
|
class DummyDriver < DB::Driver
|
||||||
def prepare(query)
|
def build_connection
|
||||||
DummyStatement.new(self, query.split.map { |r| r.split ',' })
|
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
|
end
|
||||||
|
|
||||||
class DummyStatement < DB::Statement
|
class DummyStatement < DB::Statement
|
||||||
|
@ -10,6 +23,10 @@ class DummyDriver < DB::Driver
|
||||||
super(driver)
|
super(driver)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected def begin_parameters
|
||||||
|
@params = Hash(Int32 | String, DB::Any?).new
|
||||||
|
end
|
||||||
|
|
||||||
protected def add_parameter(index : Int32, value)
|
protected def add_parameter(index : Int32, value)
|
||||||
params[index] = value
|
params[index] = value
|
||||||
end
|
end
|
||||||
|
@ -18,11 +35,7 @@ class DummyDriver < DB::Driver
|
||||||
params[":#{name}"] = value
|
params[":#{name}"] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
protected def before_execute
|
protected def perform
|
||||||
@params = Hash(Int32 | String, DB::Any).new
|
|
||||||
end
|
|
||||||
|
|
||||||
protected def execute
|
|
||||||
DummyResultSet.new self, @items.each
|
DummyResultSet.new self, @items.each
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -48,6 +61,10 @@ class DummyDriver < DB::Driver
|
||||||
"c#{index}"
|
"c#{index}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def column_type(index : Int32)
|
||||||
|
String
|
||||||
|
end
|
||||||
|
|
||||||
private def read? : DB::Any?
|
private def read? : DB::Any?
|
||||||
n = @values.not_nil!.next
|
n = @values.not_nil!.next
|
||||||
raise "end of row" if n.is_a?(Iterator::Stop)
|
raise "end of row" if n.is_a?(Iterator::Stop)
|
||||||
|
@ -91,8 +108,10 @@ class DummyDriver < DB::Driver
|
||||||
elsif value.is_a?(String)
|
elsif value.is_a?(String)
|
||||||
ary = value.bytes
|
ary = value.bytes
|
||||||
Slice.new(ary.to_unsafe, ary.size)
|
Slice.new(ary.to_unsafe, ary.size)
|
||||||
|
elsif value.is_a?(Slice(UInt8))
|
||||||
|
value
|
||||||
else
|
else
|
||||||
value as Slice(UInt8)
|
raise "#{value} is not convertible to Slice(UInt8)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -100,6 +119,25 @@ end
|
||||||
|
|
||||||
DB.register_driver "dummy", DummyDriver
|
DB.register_driver "dummy", DummyDriver
|
||||||
|
|
||||||
def get_dummy
|
class Witness
|
||||||
DB.open "dummy", {} of String => String
|
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
|
end
|
||||||
|
|
|
@ -3,118 +3,169 @@ require "db"
|
||||||
require "./dummy_driver"
|
require "./dummy_driver"
|
||||||
|
|
||||||
describe DummyDriver do
|
describe DummyDriver do
|
||||||
it "should return statements" do
|
it "with_dummy executes the block with a database" do
|
||||||
get_dummy.prepare("the query").should be_a(DB::Statement)
|
with_witness do |w|
|
||||||
|
with_dummy do |db|
|
||||||
|
w.check
|
||||||
|
db.should be_a(DB::Database)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe DummyDriver::DummyStatement do
|
describe DummyDriver::DummyStatement do
|
||||||
it "exec should return a result_set" do
|
it "should enumerate split rows by spaces" do
|
||||||
statement = get_dummy.prepare("a,b 1,2")
|
with_dummy do |db|
|
||||||
result_set = statement.exec
|
rs = db.query("")
|
||||||
result_set.should be_a(DB::ResultSet)
|
rs.move_next.should be_false
|
||||||
result_set.statement.should be(statement)
|
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
|
end
|
||||||
|
|
||||||
it "should enumerate records by spaces" do
|
it "should query with block shuold executes always" do
|
||||||
result_set = get_dummy.prepare("").exec
|
with_witness do |w|
|
||||||
result_set.move_next.should be_false
|
with_dummy do |db|
|
||||||
|
db.query "" do |rs|
|
||||||
|
w.check
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
result_set = get_dummy.prepare("a,b").exec
|
with_witness do |w|
|
||||||
result_set.move_next.should be_true
|
with_dummy do |db|
|
||||||
result_set.move_next.should be_false
|
db.query "lorem ipsum" do |rs|
|
||||||
|
w.check
|
||||||
result_set = get_dummy.prepare("a,b 1,2").exec
|
end
|
||||||
result_set.move_next.should be_true
|
end
|
||||||
result_set.move_next.should be_true
|
end
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should enumerate string fields" do
|
it "should enumerate string fields" do
|
||||||
result_set = get_dummy.prepare("a,b 1,2").exec
|
with_dummy do |db|
|
||||||
result_set.move_next
|
db.query "a,b 1,2" do |rs|
|
||||||
result_set.read(String).should eq("a")
|
rs.move_next
|
||||||
result_set.read(String).should eq("b")
|
rs.read(String).should eq("a")
|
||||||
result_set.move_next
|
rs.read(String).should eq("b")
|
||||||
result_set.read(String).should eq("1")
|
rs.move_next
|
||||||
result_set.read(String).should eq("2")
|
rs.read(String).should eq("1")
|
||||||
|
rs.read(String).should eq("2")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should enumerate nil fields" do
|
it "should enumerate nil fields" do
|
||||||
result_set = get_dummy.prepare("a,NULL 1,NULL").exec
|
with_dummy do |db|
|
||||||
result_set.move_next
|
db.query "a,NULL 1,NULL" do |rs|
|
||||||
result_set.read?(String).should eq("a")
|
rs.move_next
|
||||||
result_set.read?(String).should be_nil
|
rs.read?(String).should eq("a")
|
||||||
result_set.move_next
|
rs.read?(String).should be_nil
|
||||||
result_set.read?(Int64).should eq(1)
|
rs.move_next
|
||||||
result_set.read?(Int64).should be_nil
|
rs.read?(Int64).should eq(1)
|
||||||
|
rs.read?(Int64).should be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should enumerate int64 fields" do
|
it "should enumerate int64 fields" do
|
||||||
result_set = get_dummy.prepare("3,4 1,2").exec
|
with_dummy do |db|
|
||||||
result_set.move_next
|
db.query "3,4 1,2" do |rs|
|
||||||
result_set.read(Int64).should eq(3i64)
|
rs.move_next
|
||||||
result_set.read(Int64).should eq(4i64)
|
rs.read(Int64).should eq(3i64)
|
||||||
result_set.move_next
|
rs.read(Int64).should eq(4i64)
|
||||||
result_set.read(Int64).should eq(1i64)
|
rs.move_next
|
||||||
result_set.read(Int64).should eq(2i64)
|
rs.read(Int64).should eq(1i64)
|
||||||
|
rs.read(Int64).should eq(2i64)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should enumerate blob fields" do
|
it "should enumerate blob fields" do
|
||||||
result_set = get_dummy.prepare("az,AZ").exec
|
with_dummy do |db|
|
||||||
result_set.move_next
|
db.query("az,AZ") do |rs|
|
||||||
ary = [97u8, 122u8]
|
rs.move_next
|
||||||
result_set.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
|
ary = [97u8, 122u8]
|
||||||
ary = [65u8, 90u8]
|
rs.read(Slice(UInt8)).should eq(Slice.new(ary.to_unsafe, ary.size))
|
||||||
result_set.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
|
end
|
||||||
|
|
||||||
it "should enumerate records using each" do
|
it "should get Int32 scalars by default" do
|
||||||
nums = [] of Int32
|
with_dummy do |db|
|
||||||
result_set = get_dummy.prepare("3,4 1,2").exec
|
db.scalar("1").should be_a(Int32)
|
||||||
result_set.each do
|
db.scalar?("1").should be_a(Int32)
|
||||||
nums << result_set.read(Int32)
|
db.scalar?("NULL").should be_nil
|
||||||
nums << result_set.read(Int32)
|
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
{% for value in [1, 1_i64, "hello", 1.5, 1.5_f32] %}
|
{% for value in [1, 1_i64, "hello", 1.5, 1.5_f32] %}
|
||||||
it "should set arguments for {{value.id}}" do
|
it "numeric scalars of type of {{value.id}} should return value or nil" do
|
||||||
result_set = get_dummy.exec "?", {{value}}
|
with_dummy do |db|
|
||||||
result_set.move_next.should be_true
|
db.scalar(typeof({{value}}), "#{{{value}}}").should eq({{value}})
|
||||||
result_set.read(typeof({{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
|
end
|
||||||
|
|
||||||
it "should set arguments by symbol for {{value.id}}" do
|
it "should set arguments by symbol for {{value.id}}" do
|
||||||
result_set = get_dummy.exec ":once :twice", {once: {{value}}, twice: {{value + value}} }
|
with_dummy do |db|
|
||||||
result_set.move_next.should be_true
|
db.query ":once :twice", {once: {{value}}, twice: {{value + value}} } do |rs|
|
||||||
result_set.read(typeof({{value}})).should eq({{value}})
|
rs.move_next.should be_true
|
||||||
result_set.move_next.should be_true
|
rs.read(typeof({{value}})).should eq({{value}})
|
||||||
result_set.read(typeof({{value}})).should eq({{value + value}})
|
rs.move_next.should be_true
|
||||||
|
rs.read(typeof({{value}})).should eq({{value + value}})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should set arguments by string for {{value.id}}" do
|
it "should set arguments by string for {{value.id}}" do
|
||||||
result_set = get_dummy.exec ":once :twice", {"once": {{value}}, "twice": {{value + value}} }
|
with_dummy do |db|
|
||||||
result_set.move_next.should be_true
|
db.query ":once :twice", {"once": {{value}}, "twice": {{value + value}} } do |rs|
|
||||||
result_set.read(typeof({{value}})).should eq({{value}})
|
rs.move_next.should be_true
|
||||||
result_set.move_next.should be_true
|
rs.read(typeof({{value}})).should eq({{value}})
|
||||||
result_set.read(typeof({{value}})).should eq({{value + value}})
|
rs.move_next.should be_true
|
||||||
|
rs.read(typeof({{value}})).should eq({{value + value}})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
it "executes and selects blob" do
|
it "executes and selects blob" do
|
||||||
ary = UInt8[0x53, 0x51, 0x4C]
|
with_dummy do |db|
|
||||||
slice = Slice.new(ary.to_unsafe, ary.size)
|
ary = UInt8[0x53, 0x51, 0x4C]
|
||||||
result_set = get_dummy.exec "?", slice
|
slice = Slice.new(ary.to_unsafe, ary.size)
|
||||||
result_set.move_next
|
db.scalar(Slice(UInt8), "?", slice).to_a.should eq(ary)
|
||||||
result_set.read(Slice(UInt8)).to_a.should eq(ary)
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
20
spec/std/db/result_set_spec.cr
Normal file
20
spec/std/db/result_set_spec.cr
Normal 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
|
181
spec/std/db/statement_spec.cr
Normal file
181
spec/std/db/statement_spec.cr
Normal 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
|
25
src/db/connection.cr
Normal file
25
src/db/connection.cr
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module DB
|
||||||
|
abstract class Connection
|
||||||
|
getter options
|
||||||
|
|
||||||
|
def initialize(@options)
|
||||||
|
@closed = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Closes this connection.
|
||||||
|
def close
|
||||||
|
raise "Connection already closed" if @closed
|
||||||
|
@closed = true
|
||||||
|
perform_close
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns `true` if this statement is closed. See `#close`.
|
||||||
|
def closed?
|
||||||
|
@closed
|
||||||
|
end
|
||||||
|
|
||||||
|
abstract def prepare(query) : Statement
|
||||||
|
|
||||||
|
protected abstract def perform_close
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,43 +1,69 @@
|
||||||
module DB
|
module DB
|
||||||
# Acts as an entry point for database access.
|
# Acts as an entry point for database access.
|
||||||
# Offers a com
|
# Currently it creates a single connection to the database.
|
||||||
|
# Eventually a connection pool will be handled.
|
||||||
|
#
|
||||||
|
# It should be created from DB module. See `DB.open`.
|
||||||
class Database
|
class Database
|
||||||
getter driver_class
|
getter driver_class
|
||||||
getter options
|
getter options
|
||||||
|
|
||||||
def initialize(@driver_class, @options)
|
def initialize(@driver_class, @options)
|
||||||
@driver = @driver_class.new(@options)
|
@driver = @driver_class.new(@options)
|
||||||
|
@connection = @driver.build_connection
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# Closes all connection to the database
|
||||||
|
def close
|
||||||
|
@connection.close
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a `Connection` to the database
|
||||||
|
def connection
|
||||||
|
@connection
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prepares a `Statement`. The Statement must be closed explicitly
|
||||||
|
# after is not longer in use.
|
||||||
|
#
|
||||||
|
# Usually `#exec`, `#query` or `#scalar` should be used.
|
||||||
def prepare(query)
|
def prepare(query)
|
||||||
@driver.prepare(query)
|
connection.prepare(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(query, *args)
|
||||||
|
prepare(query).query(*args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(query, *args)
|
||||||
|
# CHECK prepare(query).query(*args, &block)
|
||||||
|
query(query, *args).tap do |rs|
|
||||||
|
begin
|
||||||
|
yield rs
|
||||||
|
ensure
|
||||||
|
rs.close
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
|
||||||
def exec(query, *args)
|
def exec(query, *args)
|
||||||
prepare(query).exec(*args)
|
prepare(query).exec(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec_non_query(query, *args)
|
def scalar(query, *args)
|
||||||
exec_query(query) do |result_set|
|
prepare(query).scalar(*args)
|
||||||
result_set.move_next
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
def scalar(t, query, *args)
|
||||||
def exec_query(query, *args)
|
prepare(query).scalar(t, *args)
|
||||||
result_set = exec(query, *args)
|
|
||||||
yield result_set
|
|
||||||
result_set.close
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec_query_each(query, *args)
|
def scalar?(query, *args)
|
||||||
exec_query(query) do |result_set|
|
prepare(query).scalar?(*args)
|
||||||
result_set.each do
|
end
|
||||||
yield result_set
|
|
||||||
end
|
def scalar?(t, query, *args)
|
||||||
end
|
prepare(query).scalar?(t, *args)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ module DB
|
||||||
TYPES = [String, Int32, Int64, Float32, Float64, Slice(UInt8)]
|
TYPES = [String, Int32, Int64, Float32, Float64, Slice(UInt8)]
|
||||||
alias Any = String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8)
|
alias Any = String | Int32 | Int64 | Float32 | Float64 | Slice(UInt8)
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
def self.driver_class(name) # : Driver.class
|
def self.driver_class(name) # : Driver.class
|
||||||
@@drivers.not_nil![name]
|
@@drivers.not_nil![name]
|
||||||
end
|
end
|
||||||
|
@ -14,9 +15,17 @@ module DB
|
||||||
def self.open(name, options)
|
def self.open(name, options)
|
||||||
Database.new(driver_class(name), options)
|
Database.new(driver_class(name), options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.open(name, options, &block)
|
||||||
|
open(name, options).tap do |db|
|
||||||
|
yield db
|
||||||
|
db.close
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "./database"
|
require "./database"
|
||||||
require "./driver"
|
require "./driver"
|
||||||
|
require "./connection"
|
||||||
require "./statement"
|
require "./statement"
|
||||||
require "./result_set"
|
require "./result_set"
|
||||||
|
|
|
@ -5,6 +5,6 @@ module DB
|
||||||
def initialize(@options)
|
def initialize(@options)
|
||||||
end
|
end
|
||||||
|
|
||||||
abstract def prepare(query) : Statement
|
abstract def build_connection : Connection
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,10 +17,11 @@ module DB
|
||||||
|
|
||||||
abstract def move_next : Bool
|
abstract def move_next : Bool
|
||||||
|
|
||||||
|
# TODO def empty? : Bool, handle internally with move_next (?)
|
||||||
|
|
||||||
abstract def column_count : Int32
|
abstract def column_count : Int32
|
||||||
abstract def column_name(index : Int32) : String
|
abstract def column_name(index : Int32) : String
|
||||||
|
abstract def column_type(index : Int32)
|
||||||
# abstract def column_type(index : Int32)
|
|
||||||
|
|
||||||
# list datatypes that must be supported form the driver
|
# list datatypes that must be supported form the driver
|
||||||
# users will call read(String) or read?(String) for nillables
|
# users will call read(String) or read?(String) for nillables
|
||||||
|
|
|
@ -1,23 +1,71 @@
|
||||||
module DB
|
module DB
|
||||||
abstract class Statement
|
abstract class Statement
|
||||||
getter driver
|
getter connection
|
||||||
|
|
||||||
def initialize(@driver)
|
def initialize(@connection)
|
||||||
@closed = false
|
@closed = false
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec(*args) : ResultSet
|
def exec(*args)
|
||||||
exec args
|
execute(*args).close
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec(arg : Slice(UInt8))
|
def scalar(*args)
|
||||||
before_execute
|
scalar(Int32, *args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# t in DB::TYPES
|
||||||
|
def scalar(t, *args)
|
||||||
|
query(*args) do |rs|
|
||||||
|
rs.each do
|
||||||
|
return rs.read(t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "unreachable"
|
||||||
|
end
|
||||||
|
|
||||||
|
def scalar?(*args)
|
||||||
|
scalar?(Int32, *args)
|
||||||
|
end
|
||||||
|
|
||||||
|
# t in DB::TYPES
|
||||||
|
def scalar?(t, *args)
|
||||||
|
query(*args) do |rs|
|
||||||
|
rs.each do
|
||||||
|
return rs.read?(t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "unreachable"
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(*args)
|
||||||
|
execute *args
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(*args)
|
||||||
|
execute(*args).tap do |rs|
|
||||||
|
begin
|
||||||
|
yield rs
|
||||||
|
ensure
|
||||||
|
rs.close
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def execute(*args) : ResultSet
|
||||||
|
execute args
|
||||||
|
end
|
||||||
|
|
||||||
|
private def execute(arg : Slice(UInt8))
|
||||||
|
begin_parameters
|
||||||
add_parameter 1, arg
|
add_parameter 1, arg
|
||||||
execute
|
perform
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec(args : Enumerable)
|
private def execute(args : Enumerable)
|
||||||
before_execute
|
begin_parameters
|
||||||
args.each_with_index(1) do |arg, index|
|
args.each_with_index(1) do |arg, index|
|
||||||
if arg.is_a?(Hash)
|
if arg.is_a?(Hash)
|
||||||
arg.each do |key, value|
|
arg.each do |key, value|
|
||||||
|
@ -27,10 +75,7 @@ module DB
|
||||||
add_parameter index, arg
|
add_parameter index, arg
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
execute
|
perform
|
||||||
end
|
|
||||||
|
|
||||||
protected def before_execute
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Closes this statement.
|
# Closes this statement.
|
||||||
|
@ -46,10 +91,12 @@ module DB
|
||||||
end
|
end
|
||||||
|
|
||||||
# 1-based positional arguments
|
# 1-based positional arguments
|
||||||
|
protected def begin_parameters
|
||||||
|
end
|
||||||
protected abstract def add_parameter(index : Int32, value)
|
protected abstract def add_parameter(index : Int32, value)
|
||||||
protected abstract def add_parameter(name : String, value)
|
protected abstract def add_parameter(name : String, value)
|
||||||
|
|
||||||
protected abstract def execute : ResultSet
|
protected abstract def perform : ResultSet
|
||||||
protected def on_close
|
protected def on_close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue