mirror of
https://gitea.invidious.io/iv-org/shard-crystal-sqlite3.git
synced 2024-08-15 00:53:26 +00:00
update to last db design
This commit is contained in:
parent
15417b7c38
commit
503868f434
7 changed files with 104 additions and 97 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ lib LibSQLite3
|
||||||
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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue