Merge branch 'feature/5-type-extensibility' into db

This commit is contained in:
Brian J. Cardiff 2016-06-23 15:14:41 -03:00
commit 186dc01f10
5 changed files with 76 additions and 14 deletions

View file

@ -1,7 +1,10 @@
name: sqlite3 name: sqlite3
version: 0.1.0 version: 0.1.0
dependencies:
db:
github: bcardiff/crystal-db
authors: authors:
- Ary Borenszweig <aborenszweig@manas.com.ar> - Ary Borenszweig <aborenszweig@manas.com.ar>
- Brian J. Cardiff <bcardiff@manas.com.ar> - Brian J. Cardiff <bcardiff@manas.com.ar>

View file

@ -25,6 +25,7 @@ def sqlite_type_for(v)
when String ; "text" when String ; "text"
when Int32, Int64 ; "int" when Int32, Int64 ; "int"
when Float32, Float64; "float" when Float32, Float64; "float"
when Time ; "text"
else else
raise "not implemented for #{typeof(v)}" raise "not implemented for #{typeof(v)}"
end end
@ -46,6 +47,9 @@ def assert_filename(uri, filename)
SQLite3::Connection.filename(URI.parse(uri)).should eq(filename) SQLite3::Connection.filename(URI.parse(uri)).should eq(filename)
end end
class NotSupportedType
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)
@ -111,7 +115,7 @@ describe Driver do
it "executes and selects blob" do it "executes and selects blob" do
with_db do |db| with_db do |db|
slice = db.scalar(%(select X'53514C697465')) as Slice(UInt8) slice = db.scalar(%(select X'53514C697465')).as(Bytes)
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
@ -119,7 +123,7 @@ 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(%(select cast(? as BLOB)), Slice.new(ary.to_unsafe, ary.size)) as Slice(UInt8) slice = db.scalar(%(select cast(? as BLOB)), Bytes.new(ary.to_unsafe, ary.size)).as(Bytes)
slice.to_a.should eq(ary) slice.to_a.should eq(ary)
end end
end end
@ -158,7 +162,7 @@ describe Driver do
rs.column_type(0).should eq(String) rs.column_type(0).should eq(String)
rs.column_type(1).should eq(Int64) rs.column_type(1).should eq(Int64)
rs.column_type(2).should eq(Float64) rs.column_type(2).should eq(Float64)
rs.column_type(3).should eq(Slice(UInt8)) rs.column_type(3).should eq(Bytes)
end end
end end
end end
@ -190,13 +194,46 @@ describe Driver do
ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65] ary = UInt8[0x53, 0x51, 0x4C, 0x69, 0x74, 0x65]
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 (?)), Bytes.new(ary.to_unsafe, ary.size)
slice = db.scalar("select cast(col1 as blob) from table1") as Slice(UInt8) slice = db.scalar("select cast(col1 as blob) from table1").as(Bytes)
slice.to_a.should eq(ary) slice.to_a.should eq(ary)
end end
end end
it "insert/get value date from table" do
with_db do |db|
value = Time.new(2016, 7, 22, 15, 0, 0, 0)
db.exec "create table table1 (col1 #{sqlite_type_for(value)})"
db.exec %(insert into table1 values (?)), value
db.query "select col1 from table1" do |rs|
rs.move_next
rs.read(Time).should eq(value)
end
db.query "select col1 from table1" do |rs|
rs.move_next
rs.read?(Time).should eq(value)
end
end
end
it "raises on unsupported param types" do
with_db do |db|
expect_raises Exception, "SQLite3::Statement does not support NotSupportedType params" do
db.query "select 1", NotSupportedType.new
end
# TODO raising exception does not close the connection and pool is exhausted
end
with_db do |db|
expect_raises Exception, "SQLite3::Statement does not support NotSupportedType params" do
db.exec "select 1", NotSupportedType.new
end
end
end
it "gets many rows from table" do it "gets many rows from table" 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)"

View file

@ -1,2 +1,6 @@
require "db" require "db"
require "./sqlite3/**" require "./sqlite3/**"
module SQLite3
DATE_FORMAT = "%F %H:%M:%S.%L"
end

View file

@ -22,7 +22,7 @@ class SQLite3::ResultSet < DB::ResultSet
end end
end end
{% for t in DB::TYPES %} macro nilable_read_for(t)
def read?(t : {{t}}.class) : {{t}}? def read?(t : {{t}}.class) : {{t}}?
if read_nil? if read_nil?
moving_column { nil } moving_column { nil }
@ -30,6 +30,10 @@ class SQLite3::ResultSet < DB::ResultSet
read(t) read(t)
end end
end end
end
{% for t in DB::TYPES %}
nilable_read_for({{t}})
{% end %} {% end %}
def read(t : String.class) : String def read(t : String.class) : String
@ -52,16 +56,22 @@ class SQLite3::ResultSet < DB::ResultSet
moving_column { |col| LibSQLite3.column_double(self, col) } moving_column { |col| LibSQLite3.column_double(self, col) }
end end
def read(t : Slice(UInt8).class) : Slice(UInt8) def read(t : Bytes.class) : Bytes
moving_column do |col| moving_column do |col|
blob = LibSQLite3.column_blob(self, col) blob = LibSQLite3.column_blob(self, col)
bytes = LibSQLite3.column_bytes(self, col) bytes = LibSQLite3.column_bytes(self, col)
ptr = Pointer(UInt8).malloc(bytes) ptr = Pointer(UInt8).malloc(bytes)
ptr.copy_from(blob, bytes) ptr.copy_from(blob, bytes)
Slice(UInt8).new(ptr, bytes) Bytes.new(ptr, bytes)
end end
end end
def read(t : Time.class) : Time
Time.parse read(String), SQLite3::DATE_FORMAT
end
nilable_read_for Time
def column_count def column_count
LibSQLite3.column_count(self) LibSQLite3.column_count(self)
end end
@ -74,7 +84,7 @@ class SQLite3::ResultSet < DB::ResultSet
case LibSQLite3.column_type(self, index) case LibSQLite3.column_type(self, index)
when Type::INTEGER; Int64 when Type::INTEGER; Int64
when Type::FLOAT ; Float64 when Type::FLOAT ; Float64
when Type::BLOB ; Slice(UInt8) when Type::BLOB ; Bytes
when Type::TEXT ; String when Type::TEXT ; String
when Type::NULL ; Nil when Type::NULL ; Nil
else else

View file

@ -4,7 +4,7 @@ class SQLite3::Statement < 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 perform_query(args : Slice(DB::Any)) protected def perform_query(args : Enumerable) : DB::ResultSet
LibSQLite3.reset(self) LibSQLite3.reset(self)
args.each_with_index(1) do |arg, index| args.each_with_index(1) do |arg, index|
bind_arg(index, arg) bind_arg(index, arg)
@ -12,12 +12,12 @@ class SQLite3::Statement < DB::Statement
ResultSet.new(self) ResultSet.new(self)
end end
protected def perform_exec(args : Slice(DB::Any)) protected def perform_exec(args : Enumerable) : DB::ExecResult
rs = perform_query(args) rs = perform_query(args)
rs.move_next rs.move_next
rs.close rs.close
rows_affected = LibSQLite3.changes(connection) rows_affected = LibSQLite3.changes(connection).to_i64
last_id = LibSQLite3.last_insert_rowid(connection) last_id = LibSQLite3.last_insert_rowid(connection)
DB::ExecResult.new rows_affected, last_id DB::ExecResult.new rows_affected, last_id
@ -52,10 +52,18 @@ class SQLite3::Statement < DB::Statement
check LibSQLite3.bind_text(self, index, value, value.bytesize, nil) check LibSQLite3.bind_text(self, index, value, value.bytesize, nil)
end end
private def bind_arg(index, value : Slice(UInt8)) private def bind_arg(index, value : Bytes)
check LibSQLite3.bind_blob(self, index, value, value.size, nil) check LibSQLite3.bind_blob(self, index, value, value.size, nil)
end end
private def bind_arg(index, value : Time)
bind_arg(index, value.to_s(SQLite3::DATE_FORMAT))
end
private def bind_arg(index, value)
raise "#{self.class} does not support #{value.class} params"
end
private def check(code) private def check(code)
raise Exception.new(@connection) unless code == 0 raise Exception.new(@connection) unless code == 0
end end