update to last db design

This commit is contained in:
Brian J. Cardiff 2016-02-03 21:29:19 -03:00
parent 15417b7c38
commit 503868f434
7 changed files with 104 additions and 97 deletions

View File

@ -1,10 +1,14 @@
require "db" require "db"
require "../src/sqlite3" require "../src/sqlite3"
DB.open "sqlite3", ":memory:" do |db| DB.open "sqlite3://%3Amemory%3A" do |db|
db.exec "create table contacts (name string, age integer)" db.exec "create table contacts (name string, age integer)"
db.exec "insert into contacts values (?, ?)", "John Doe", 30 db.exec "insert into contacts values (?, ?)", "John Doe", 30
db.exec "insert into contacts values (:name, :age)", {name: "Sarah", age: 33}
args = [] of DB::Any
args << "Sarah"
args << 33
db.exec "insert into contacts values (?, ?)", args
puts "max age:" puts "max age:"
puts db.scalar "select max(age) from contacts" # => 33 puts db.scalar "select max(age) from contacts" # => 33

View File

@ -1,13 +1,13 @@
require "./spec_helper" require "./spec_helper"
def with_db(&block : DB::Database ->) def with_db(&block : DB::Database ->)
DB.open "sqlite3", DB_FILENAME, &block DB.open "sqlite3:#{DB_FILENAME}", &block
ensure ensure
File.delete(DB_FILENAME) File.delete(DB_FILENAME)
end end
def with_mem_db(&block : DB::Database ->) def with_mem_db(&block : DB::Database ->)
DB.open "sqlite3", ":memory:", &block DB.open "sqlite3://%3Amemory%3A", &block
end end
def sql(s : String) def sql(s : String)
@ -40,14 +40,29 @@ def assert_single_read?(rs, value_type, value)
rs.move_next.should be_false rs.move_next.should be_false
end end
def assert_filename(uri, filename)
SQLite3::Connection.filename(URI.parse(uri)).should eq(filename)
end
describe Driver do describe Driver do
it "should register sqlite3 name" do it "should register sqlite3 name" do
DB.driver_class("sqlite3").should eq(SQLite3::Driver) DB.driver_class("sqlite3").should eq(SQLite3::Driver)
end end
it "should get filename from uri" do
assert_filename("sqlite3:%3Amemory%3A", ":memory:")
assert_filename("sqlite3://%3Amemory%3A", ":memory:")
assert_filename("sqlite3:./file.db", "./file.db")
assert_filename("sqlite3://./file.db", "./file.db")
assert_filename("sqlite3:/path/to/file.db", "/path/to/file.db")
assert_filename("sqlite3:///path/to/file.db", "/path/to/file.db")
end
it "should use database option as file to open" do it "should use database option as file to open" do
with_db do |db| with_db do |db|
db.driver_class.should eq(SQLite3::Driver) db.driver.should be_a(SQLite3::Driver)
File.exists?(DB_FILENAME).should be_true File.exists?(DB_FILENAME).should be_true
end end
end end
@ -55,7 +70,7 @@ describe Driver do
{% 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 "executes and select {{value.id}}" do it "executes and select {{value.id}}" do
with_db do |db| with_db do |db|
db.scalar(typeof({{value}}), "select #{sql({{value}})}").should eq({{value}}) db.scalar("select #{sql({{value}})}").should eq({{value}})
db.query "select #{sql({{value}})}" do |rs| db.query "select #{sql({{value}})}" do |rs|
assert_single_read rs, typeof({{value}}), {{value}} assert_single_read rs, typeof({{value}}), {{value}}
@ -63,19 +78,9 @@ describe Driver do
end end
end end
it "executes and select {{value.id}} as nillable" do
with_db do |db|
db.scalar?(typeof({{value}}), "select #{sql({{value}})}").should eq({{value}})
db.query "select #{sql({{value}})}" do |rs|
assert_single_read? rs, typeof({{value}}), {{value}}
end
end
end
it "executes and select nil as type of {{value.id}}" do it "executes and select nil as type of {{value.id}}" do
with_db do |db| with_db do |db|
db.scalar?(typeof({{value}}), "select null").should be_nil db.scalar("select null").should be_nil
db.query "select null" do |rs| db.query "select null" do |rs|
assert_single_read? rs, typeof({{value}}), nil assert_single_read? rs, typeof({{value}}), nil
@ -85,32 +90,26 @@ describe Driver do
it "executes with bind {{value.id}}" do it "executes with bind {{value.id}}" do
with_db do |db| with_db do |db|
db.scalar(typeof({{value}}), %(select ?), {{value}}).should eq({{value}}) db.scalar(%(select ?), {{value}}).should eq({{value}})
end
end
it "executes with bind {{value.id}} read as nillable" do
with_db do |db|
db.scalar?(typeof({{value}}), %(select ?), {{value}}).should eq({{value}})
end end
end end
it "executes with bind nil as typeof {{value.id}}" do it "executes with bind nil as typeof {{value.id}}" do
with_db do |db| with_db do |db|
db.scalar?(typeof({{value}}), %(select ?), nil).should be_nil db.scalar("select ?", nil).should be_nil
end end
end end
it "executes with bind {{value.id}} as array" do it "executes with bind {{value.id}} as array" do
with_db do |db| with_db do |db|
db.scalar?(typeof({{value}}), %(select ?), [{{value}}]).should eq({{value}}) db.scalar(%(select ?), [{{value}}]).should eq({{value}})
end end
end end
{% end %} {% end %}
it "executes and selects blob" do it "executes and selects blob" do
with_db do |db| with_db do |db|
slice = db.scalar(Slice(UInt8), %(select X'53514C697465')) slice = db.scalar(%(select X'53514C697465')) as Slice(UInt8)
slice.to_a.should eq([0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]) slice.to_a.should eq([0x53, 0x51, 0x4C, 0x69, 0x74, 0x65])
end end
end end
@ -118,23 +117,11 @@ describe Driver do
it "executes with bind blob" do it "executes with bind blob" do
with_db do |db| with_db do |db|
ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65] ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]
slice = db.scalar Slice(UInt8), %(select cast(? as BLOB)), Slice.new(ary.to_unsafe, ary.size) slice = db.scalar(%(select cast(? as BLOB)), Slice.new(ary.to_unsafe, ary.size)) as Slice(UInt8)
slice.to_a.should eq(ary) slice.to_a.should eq(ary)
end end
end end
it "executes with named bind using symbol" do
with_db do |db|
db.scalar(String, %(select :value), {value: "hello"}).should eq("hello")
end
end
it "executes with named bind using string" do
with_db do |db|
db.scalar(String, %(select :value), {"value": "hello"}).should eq("hello")
end
end
it "gets column count" do it "gets column count" do
with_mem_db do |db| with_mem_db do |db|
db.exec "create table person (name string, age integer)" db.exec "create table person (name string, age integer)"
@ -176,12 +163,13 @@ describe Driver do
it "gets last insert row id" do it "gets last insert row id" do
with_mem_db do |db| with_mem_db do |db|
cnn = db.connection db.exec "create table person (name string, age integer)"
cnn.exec "create table person (name string, age integer)"
cnn.last_insert_id.should eq(0) db.exec %(insert into person values ("foo", 10))
cnn.exec %(insert into person values ("foo", 10))
cnn.last_insert_id.should eq(1) res = db.exec %(insert into person values ("foo", 10))
res.last_insert_id.should eq(2)
res.rows_affected.should eq(1)
end end
end end
@ -190,7 +178,7 @@ describe Driver do
with_db do |db| with_db do |db|
db.exec "create table table1 (col1 #{sqlite_type_for({{value}})})" db.exec "create table table1 (col1 #{sqlite_type_for({{value}})})"
db.exec %(insert into table1 values (#{sql({{value}})})) db.exec %(insert into table1 values (#{sql({{value}})}))
db.scalar(typeof({{value}}), "select col1 from table1").should eq({{value}}) db.scalar("select col1 from table1").should eq({{value}})
end end
end end
{% end %} {% end %}
@ -201,7 +189,8 @@ describe Driver do
db.exec "create table table1 (col1 blob)" db.exec "create table table1 (col1 blob)"
db.exec %(insert into table1 values (?)), Slice.new(ary.to_unsafe, ary.size) db.exec %(insert into table1 values (?)), Slice.new(ary.to_unsafe, ary.size)
slice = db.scalar(Slice(UInt8), "select col1 from table1")
slice = db.scalar("select cast(col1 as blob) from table1") as Slice(UInt8)
slice.to_a.should eq(ary) slice.to_a.should eq(ary)
end end
end end
@ -228,13 +217,13 @@ describe Driver do
it "ensures statements are closed" do it "ensures statements are closed" do
begin begin
DB.open "sqlite3", DB_FILENAME do |db| DB.open "sqlite3:#{DB_FILENAME}" do |db|
db.exec %(create table if not exists a (i int not null, str text not null);) db.exec %(create table if not exists a (i int not null, str text not null);)
db.exec %(insert into a (i, str) values (23, "bai bai");) db.exec %(insert into a (i, str) values (23, "bai bai");)
end end
2.times do |i| 2.times do |i|
DB.open "sqlite3", DB_FILENAME do |db| DB.open "sqlite3:#{DB_FILENAME}" do |db|
begin begin
db.query("SELECT i, str FROM a WHERE i = ?", 23) do |rs| db.query("SELECT i, str FROM a WHERE i = ?", 23) do |rs|
rs.move_next rs.move_next

View File

@ -1,21 +1,28 @@
class SQLite3::Connection < DB::Connection class SQLite3::Connection < DB::Connection
def initialize(connection_string) def initialize(database)
super super
check LibSQLite3.open_v2(connection_string, out @db, (LibSQLite3::Flag::READWRITE | LibSQLite3::Flag::CREATE), nil) filename = self.class.filename(database.uri)
check LibSQLite3.open_v2(filename, out @db, (LibSQLite3::Flag::READWRITE | LibSQLite3::Flag::CREATE), nil)
end end
def prepare(query) def self.filename(uri : URI)
URI.unescape (if path = uri.path
(uri.host || "") + path
else
uri.opaque.not_nil!
end)
end
def build_statement(query)
Statement2.new(self, query) Statement2.new(self, query)
end end
def perform_close def do_close
@statements_cache.values.each &.close
super
LibSQLite3.close_v2(self) LibSQLite3.close_v2(self)
end end
def last_insert_id : Int64
LibSQLite3.last_insert_rowid(self)
end
def to_unsafe def to_unsafe
@db @db
end end

View File

@ -1,6 +1,6 @@
class SQLite3::Driver < DB::Driver class SQLite3::Driver < DB::Driver
def build_connection def build_connection(db)
SQLite3::Connection.new(connection_string) SQLite3::Connection.new(db)
end end
end end

View File

@ -6,37 +6,37 @@ lib LibSQLite3
type Statement = Void* type Statement = Void*
enum Flag enum Flag
READONLY = 0x00000001 # Ok for sqlite3_open_v2() READONLY = 0x00000001 # Ok for sqlite3_open_v2()
READWRITE = 0x00000002 # Ok for sqlite3_open_v2() READWRITE = 0x00000002 # Ok for sqlite3_open_v2()
CREATE = 0x00000004 # Ok for sqlite3_open_v2() CREATE = 0x00000004 # Ok for sqlite3_open_v2()
DELETEONCLOSE = 0x00000008 # VFS only DELETEONCLOSE = 0x00000008 # VFS only
EXCLUSIVE = 0x00000010 # VFS only EXCLUSIVE = 0x00000010 # VFS only
AUTOPROXY = 0x00000020 # VFS only AUTOPROXY = 0x00000020 # VFS only
URI = 0x00000040 # Ok for sqlite3_open_v2() URI = 0x00000040 # Ok for sqlite3_open_v2()
MEMORY = 0x00000080 # Ok for sqlite3_open_v2() MEMORY = 0x00000080 # Ok for sqlite3_open_v2()
MAIN_DB = 0x00000100 # VFS only MAIN_DB = 0x00000100 # VFS only
TEMP_DB = 0x00000200 # VFS only TEMP_DB = 0x00000200 # VFS only
TRANSIENT_DB = 0x00000400 # VFS only TRANSIENT_DB = 0x00000400 # VFS only
MAIN_JOURNAL = 0x00000800 # VFS only MAIN_JOURNAL = 0x00000800 # VFS only
TEMP_JOURNAL = 0x00001000 # VFS only TEMP_JOURNAL = 0x00001000 # VFS only
SUBJOURNAL = 0x00002000 # VFS only SUBJOURNAL = 0x00002000 # VFS only
MASTER_JOURNAL = 0x00004000 # VFS only MASTER_JOURNAL = 0x00004000 # VFS only
NOMUTEX = 0x00008000 # Ok for sqlite3_open_v2() NOMUTEX = 0x00008000 # Ok for sqlite3_open_v2()
FULLMUTEX = 0x00010000 # Ok for sqlite3_open_v2() FULLMUTEX = 0x00010000 # Ok for sqlite3_open_v2()
SHAREDCACHE = 0x00020000 # Ok for sqlite3_open_v2() SHAREDCACHE = 0x00020000 # Ok for sqlite3_open_v2()
PRIVATECACHE = 0x00040000 # Ok for sqlite3_open_v2() PRIVATECACHE = 0x00040000 # Ok for sqlite3_open_v2()
WAL = 0x00080000 # VFS only WAL = 0x00080000 # VFS only
end end
enum Code enum Code
ROW = 100 ROW = 100
DONE = 101 DONE = 101
end end
alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32 alias Callback = (Void*, Int32, UInt8**, UInt8**) -> Int32
fun open = sqlite3_open_v2(filename : UInt8*, db : SQLite3*) : Int32 fun open = sqlite3_open_v2(filename : UInt8*, db : SQLite3*) : Int32
fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags: Flag, zVfs : UInt8*) : Int32 fun open_v2 = sqlite3_open_v2(filename : UInt8*, db : SQLite3*, flags : Flag, zVfs : UInt8*) : Int32
fun errcode = sqlite3_errcode(SQLite3) : Int32 fun errcode = sqlite3_errcode(SQLite3) : Int32
fun errmsg = sqlite3_errmsg(SQLite3) : UInt8* fun errmsg = sqlite3_errmsg(SQLite3) : UInt8*
@ -62,6 +62,7 @@ lib LibSQLite3
fun reset = sqlite3_reset(stmt : Statement) : Int32 fun reset = sqlite3_reset(stmt : Statement) : Int32
fun column_name = sqlite3_column_name(stmt : Statement, idx : Int32) : UInt8* fun column_name = sqlite3_column_name(stmt : Statement, idx : Int32) : UInt8*
fun last_insert_rowid = sqlite3_last_insert_rowid(db : SQLite3) : Int64 fun last_insert_rowid = sqlite3_last_insert_rowid(db : SQLite3) : Int64
fun changes = sqlite3_changes(db : SQLite3) : Int32
fun finalize = sqlite3_finalize(stmt : Statement) : Int32 fun finalize = sqlite3_finalize(stmt : Statement) : Int32
fun close_v2 = sqlite3_close_v2(SQLite3) : Int32 fun close_v2 = sqlite3_close_v2(SQLite3) : Int32

View File

@ -1,6 +1,11 @@
class SQLite3::ResultSet2 < DB::ResultSet class SQLite3::ResultSet2 < DB::ResultSet
@column_index = 0 @column_index = 0
protected def do_close
super
LibSQLite3.reset(self)
end
# Advances to the next row. Returns `true` if there's a next row, # Advances to the next row. Returns `true` if there's a next row,
# `false` otherwise. Must be called at least once to advance to the first # `false` otherwise. Must be called at least once to advance to the first
# row. # row.

View File

@ -4,29 +4,30 @@ class SQLite3::Statement2 < DB::Statement
check LibSQLite3.prepare_v2(@connection, sql, sql.bytesize + 1, out @stmt, nil) check LibSQLite3.prepare_v2(@connection, sql, sql.bytesize + 1, out @stmt, nil)
end end
protected def begin_parameters protected def perform_query(args : Slice(DB::Any))
LibSQLite3.reset(self) LibSQLite3.reset(self)
args.each_with_index(1) do |arg, index|
bind_arg(index, arg)
end
ResultSet2.new(self)
end
protected def perform_exec(args : Slice(DB::Any))
rs = perform_query(args)
rs.move_next
rs.close
rows_affected = LibSQLite3.changes(connection)
last_id = LibSQLite3.last_insert_rowid(connection)
DB::ExecResult.new rows_affected, last_id
end end
protected def on_close protected def on_close
super
check LibSQLite3.finalize(self) check LibSQLite3.finalize(self)
end end
protected def add_parameter(index : Int32, value)
bind_arg(index + 1, value)
end
protected def add_parameter(name : String, value)
converted_name = ":#{name}"
index = LibSQLite3.bind_parameter_index(self, converted_name)
raise "Unknown parameter: #{name}" if index == 0
bind_arg(index, value)
end
protected def perform
ResultSet2.new(self)
end
private def bind_arg(index, value : Nil) private def bind_arg(index, value : Nil)
check LibSQLite3.bind_null(self, index) check LibSQLite3.bind_null(self, index)
end end