diff --git a/README.md b/README.md index 1661cd2..4d044fd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ SQLite.jl [![Build Status](https://travis-ci.org/JuliaDB/SQLite.jl.svg?branch=master)](https://travis-ci.org/JuliaDB/SQLite.jl) [![Coverage Status](https://coveralls.io/repos/JuliaDB/SQLite.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/JuliaDB/SQLite.jl?branch=master) -A Julia interface to the SQLite library and support for operations on DataFrames +A Julia interface to the SQLite library and support for the `DataStreams` data processing framework. **Installation**: `julia> Pkg.add("SQLite")` diff --git a/REQUIRE b/REQUIRE index 2fdf849..b616606 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,5 +1,9 @@ -julia 0.3 +julia 0.4 BinDeps Compat +DataStreams +NullableArrays +CSV +Libz @osx Homebrew @windows WinRPM diff --git a/src/SQLite.jl b/src/SQLite.jl index 2bef75a..f13bea2 100644 --- a/src/SQLite.jl +++ b/src/SQLite.jl @@ -1,12 +1,14 @@ module SQLite -using Compat +using Compat, NullableArrays, CSV, Libz, DataStreams +import CSV.PointerString +# Deprecated exports export NULL, SQLiteDB, SQLiteStmt, ResultSet, execute, query, tables, indices, columns, droptable, dropindex, create, createindex, append, deleteduplicates -import Base: ==, show, convert, bind, close +importall Base.Operators type SQLiteException <: Exception msg::AbstractString @@ -18,39 +20,13 @@ include("api.jl") # Custom NULL type immutable NullType end const NULL = NullType() -show(io::IO,::NullType) = print(io,"NULL") +show(io::IO,::NullType) = print(io,"#NULL") -# internal wrapper type to, in-effect, mark something which has been serialized +"internal wrapper type to, in-effect, mark something which has been serialized" immutable Serialization object end -type ResultSet - colnames - values::Vector{Any} -end -==(a::ResultSet,b::ResultSet) = a.colnames == b.colnames && a.values == b.values -include("show.jl") -convert(::Type{Matrix},a::ResultSet) = [a[i,j] for i=1:size(a,1), j=1:size(a,2)] - -type SQLiteDB{T<:AbstractString} - file::T - handle::Ptr{Void} - changes::Int -end -SQLiteDB(file,handle) = SQLiteDB(file,handle,0) - -include("UDF.jl") -export @sr_str, @register, register - - -function changes(db::SQLiteDB) - new_tot = sqlite3_total_changes(db.handle) - diff = new_tot - db.changes - db.changes = new_tot - return ResultSet(["Rows Affected"],Any[Any[diff]]) -end - #TODO: Support sqlite3_open_v2 # Normal constructor from filename sqliteopen(file,handle) = sqlite3_open(file,handle) @@ -58,6 +34,25 @@ sqliteopen(file::UTF16String,handle) = sqlite3_open16(file,handle) sqliteerror() = throw(SQLiteException(bytestring(sqlite3_errmsg()))) sqliteerror(db) = throw(SQLiteException(bytestring(sqlite3_errmsg(db.handle)))) +<<<<<<< HEAD +type DB + file::UTF8String + handle::Ptr{Void} + changes::Int + + function DB(f::UTF8String) + handle = [C_NULL] + f = isempty(f) ? f : expanduser(f) + if @OK sqliteopen(f,handle) + db = new(f,handle[1],0) + register(db, regexp, nargs=2, name="regexp") + finalizer(db, _close) + return db + else # error + sqlite3_close(handle[1]) + sqliteerror() + end +======= function SQLiteDB(file::AbstractString="";UTF16::Bool=false) handle = [C_NULL] utf = UTF16 ? utf16 : utf8 @@ -70,54 +65,75 @@ function SQLiteDB(file::AbstractString="";UTF16::Bool=false) else # error sqlite3_close(handle[1]) sqliteerror() +>>>>>>> d86fcf628105a178ae6fc0128740e4ec2df8b2e7 end end +"`SQLite.DB(file::AbstractString)` opens or creates an SQLite database with `file`" +DB(f::AbstractString) = DB(utf8(f)) +"`SQLite.DB()` creates an in-memory SQLite database" +DB() = DB(":memory:") -function close{T}(db::SQLiteDB{T}) - db.handle == C_NULL && return - # ensure SQLiteStmts are finalised - gc() - @CHECK db sqlite3_close(db.handle) +function _close(db::DB) + sqlite3_close_v2(db.handle) db.handle = C_NULL return end -type SQLiteStmt{T} - db::SQLiteDB{T} - handle::Ptr{Void} - sql::T -end +Base.show(io::IO, db::SQLite.DB) = print(io, string("SQLite.DB(",db.file == ":memory:" ? "in-memory" : "\"$(db.file)\"",")")) +<<<<<<< HEAD +""" +`SQLite.Stmt(db::DB, sql::AbstractString)` creates and prepares an SQLite statement +""" +type Stmt + db::DB + handle::Ptr{Void} +======= sqliteprepare(db,sql,stmt,null) = @CHECK db sqlite3_prepare_v2(db.handle,utf8(sql),stmt,null) sqliteprepare(db::SQLiteDB{UTF16String},sql,stmt,null) = @CHECK db sqlite3_prepare16_v2(db.handle,utf16(sql),stmt,null) - -function SQLiteStmt{T}(db::SQLiteDB{T},sql::AbstractString) - handle = [C_NULL] - sqliteprepare(db,sql,handle,[C_NULL]) - stmt = SQLiteStmt(db,handle[1],convert(T,sql)) - finalizer(stmt, close) - return stmt +>>>>>>> d86fcf628105a178ae6fc0128740e4ec2df8b2e7 + + function Stmt(db::DB,sql::AbstractString) + handle = [C_NULL] + sqliteprepare(db,sql,handle,[C_NULL]) + stmt = new(db,handle[1]) + finalizer(stmt, _close) + return stmt + end end -function close(stmt::SQLiteStmt) - stmt.handle == C_NULL && return - @CHECK stmt.db sqlite3_finalize(stmt.handle) +function _close(stmt::Stmt) + sqlite3_finalize(stmt.handle) stmt.handle = C_NULL return end -# bind a row to nameless parameters -function bind(stmt::SQLiteStmt, values::Vector) +sqliteprepare(db,sql,stmt,null) = + @CHECK db sqlite3_prepare_v2(db.handle,utf8(sql),stmt,null) + +# TO DEPRECATE +type SQLiteDB{T<:AbstractString} + file::T + handle::Ptr{Void} + changes::Int +end +SQLiteDB(file,handle) = SQLiteDB(file,handle,0) +include("UDF.jl") +include("old_ui.jl") +export @sr_str, @register, register + +"bind a row (`values`) to nameless parameters by index" +function bind!(stmt::Stmt, values::Vector) nparams = sqlite3_bind_parameter_count(stmt.handle) @assert nparams == length(values) "you must provide values for all placeholders" for i in 1:nparams - @inbounds bind(stmt, i, values[i]) + @inbounds bind!(stmt, i, values[i]) end end -# bind a row to named parameters -function bind{V}(stmt::SQLiteStmt, values::Dict{Symbol, V}) +"bind a row (`Dict(:key => value)`) to named parameters" +function bind!{V}(stmt::Stmt, values::Dict{Symbol, V}) nparams = sqlite3_bind_parameter_count(stmt.handle) @assert nparams == length(values) "you must provide values for all placeholders" for i in 1:nparams @@ -125,26 +141,37 @@ function bind{V}(stmt::SQLiteStmt, values::Dict{Symbol, V}) @assert !isempty(name) "nameless parameters should be passed as a Vector" # name is returned with the ':', '@' or '$' at the start name = name[2:end] - bind(stmt, i, values[symbol(name)]) + bind!(stmt, i, values[symbol(name)]) end end # Binding parameters to SQL statements -function bind(stmt::SQLiteStmt,name::AbstractString,val) +"bind `val` to the named parameter `name`" +function bind!(stmt::Stmt,name::AbstractString,val) i = sqlite3_bind_parameter_index(stmt.handle,name) if i == 0 throw(SQLiteException("SQL parameter $name not found in $stmt")) end - return bind(stmt,i,val) + return bind!(stmt,i,val) end +<<<<<<< HEAD +bind!(stmt::Stmt,i::Int,val::AbstractFloat) = (sqlite3_bind_double(stmt.handle,i,Float64(val)); return nothing) +bind!(stmt::Stmt,i::Int,val::Int32) = (sqlite3_bind_int(stmt.handle,i,val); return nothing) +bind!(stmt::Stmt,i::Int,val::Int64) = (sqlite3_bind_int64(stmt.handle,i,val); return nothing) +bind!(stmt::Stmt,i::Int,val::NullType) = (sqlite3_bind_null(stmt.handle,i); return nothing) +bind!(stmt::Stmt,i::Int,val::AbstractString) = (sqlite3_bind_text(stmt.handle,i,val); return nothing) +bind!(stmt::Stmt,i::Int,val::PointerString) = (sqlite3_bind_text(stmt.handle,i,val.ptr,val.len); return nothing) +bind!(stmt::Stmt,i::Int,val::UTF16String) = (sqlite3_bind_text16(stmt.handle,i,val); return nothing) +======= bind(stmt::SQLiteStmt,i::Int,val::AbstractFloat) = @CHECK stmt.db sqlite3_bind_double(stmt.handle,i,@compat Float64(val)) bind(stmt::SQLiteStmt,i::Int,val::Int32) = @CHECK stmt.db sqlite3_bind_int(stmt.handle,i,val) bind(stmt::SQLiteStmt,i::Int,val::Int64) = @CHECK stmt.db sqlite3_bind_int64(stmt.handle,i,val) bind(stmt::SQLiteStmt,i::Int,val::NullType) = @CHECK stmt.db sqlite3_bind_null(stmt.handle,i) bind(stmt::SQLiteStmt,i::Int,val::AbstractString) = @CHECK stmt.db sqlite3_bind_text(stmt.handle,i,val) bind(stmt::SQLiteStmt,i::Int,val::UTF16String) = @CHECK stmt.db sqlite3_bind_text16(stmt.handle,i,val) +>>>>>>> d86fcf628105a178ae6fc0128740e4ec2df8b2e7 # We may want to track the new ByteVec type proposed at https://github.com/JuliaLang/julia/pull/8964 # as the "official" bytes type instead of Vector{UInt8} -bind(stmt::SQLiteStmt,i::Int,val::Vector{UInt8}) = @CHECK stmt.db sqlite3_bind_blob(stmt.handle,i,val) +bind!(stmt::Stmt,i::Int,val::Vector{UInt8}) = (sqlite3_bind_blob(stmt.handle,i,val); return nothing) # Fallback is BLOB and defaults to serializing the julia value function sqlserialize(x) t = IOBuffer() @@ -155,13 +182,14 @@ function sqlserialize(x) serialize(t,s) return takebuf_array(t) end -bind(stmt::SQLiteStmt,i::Int,val) = bind(stmt,i,sqlserialize(val)) +"bind `val` to the parameter at index `i`" +bind!(stmt::Stmt,i::Int,val) = bind!(stmt,i,sqlserialize(val)) #TODO: #int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); #int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); -# Execute SQL statements -function execute(stmt::SQLiteStmt) +"Execute a prepared SQLite statement" +function execute!(stmt::Stmt) r = sqlite3_step(stmt.handle) if r == SQLITE_DONE sqlite3_reset(stmt.handle) @@ -170,10 +198,10 @@ function execute(stmt::SQLiteStmt) end return r end -function execute(db::SQLiteDB,sql::AbstractString) - stmt = SQLiteStmt(db,sql) - execute(stmt) - return changes(db) +"Prepare and execute an SQLite statement" +function execute!(db::DB,sql::AbstractString) + stmt = Stmt(db,sql) + return execute!(stmt) end const SERIALIZATION = UInt8[0x11,0x01,0x02,0x0d,0x53,0x65,0x72,0x69,0x61,0x6c,0x69,0x7a,0x61,0x74,0x69,0x6f,0x6e,0x23] @@ -189,6 +217,8 @@ function sqldeserialize(r) end end +<<<<<<< HEAD +======= function query(db::SQLiteDB,sql::AbstractString, values=[]) stmt = SQLiteStmt(db,sql) bind(stmt, values) @@ -243,29 +273,28 @@ end columns(db::SQLiteDB,table::AbstractString) = query(db,"pragma table_info($table)") +>>>>>>> d86fcf628105a178ae6fc0128740e4ec2df8b2e7 # Transaction-based commands -function transaction(db, mode="DEFERRED") - #= - Begin a transaction in the spedified mode, default "DEFERRED". +""" +Begin a transaction in the spedified `mode`, default = "DEFERRED". - If mode is one of "", "DEFERRED", "IMMEDIATE" or "EXCLUSIVE" then a - transaction of that (or the default) type is started. Otherwise a savepoint - is created whose name is mode converted to AbstractString. - =# +If `mode` is one of "", "DEFERRED", "IMMEDIATE" or "EXCLUSIVE" then a +transaction of that (or the default) type is started. Otherwise a savepoint +is created whose name is `mode` converted to AbstractString. +""" +function transaction(db, mode="DEFERRED") + execute!(db,"PRAGMA temp_store=MEMORY;") if uppercase(mode) in ["", "DEFERRED", "IMMEDIATE", "EXCLUSIVE"] - execute(db, "BEGIN $(mode) TRANSACTION;") + execute!(db, "BEGIN $(mode) TRANSACTION;") else - execute(db, "SAVEPOINT $(mode);") + execute!(db, "SAVEPOINT $(mode);") end end - +"Execute the function `f` within a transaction." function transaction(f::Function, db) - #= - Execute the function f within a transaction. - =# # generate a random name for the savepoint name = string("SQLITE",randstring(10)) - execute(db,"PRAGMA synchronous = OFF") + execute!(db,"PRAGMA synchronous = OFF;") transaction(db, name) try f() @@ -275,107 +304,62 @@ function transaction(f::Function, db) finally # savepoints are not released on rollback commit(db, name) - execute(db,"PRAGMA synchronous = ON") + execute!(db,"PRAGMA synchronous = ON;") end end -# commit a transaction or savepoint (if name is given) -commit(db) = execute(db, "COMMIT TRANSACTION;") -commit(db, name) = execute(db, "RELEASE SAVEPOINT $(name);") +"commit a transaction or named savepoint" +commit(db) = execute!(db, "COMMIT TRANSACTION;") +"commit a transaction or named savepoint" +commit(db, name) = execute!(db, "RELEASE SAVEPOINT $(name);") -# rollback transaction or savepoint (if name is given) -rollback(db) = execute(db, "ROLLBACK TRANSACTION;") -rollback(db, name) = execute(db, "ROLLBACK TRANSACTION TO SAVEPOINT $(name);") +"rollback transaction or named savepoint" +rollback(db) = execute!(db, "ROLLBACK TRANSACTION;") +"rollback transaction or named savepoint" +rollback(db, name) = execute!(db, "ROLLBACK TRANSACTION TO SAVEPOINT $(name);") -function droptable(db::SQLiteDB,table::AbstractString;ifexists::Bool=false) +"drop the SQLite table `table` from the database `db`; `ifexists=true` will not return an error if `table` doesn't exist" +function drop!(db::DB,table::AbstractString;ifexists::Bool=false) exists = ifexists ? "if exists" : "" transaction(db) do - execute(db,"drop table $exists $table") + execute!(db,"drop table $exists $table") end - execute(db,"vacuum") - return changes(db) + execute!(db,"vacuum") + return end - -function dropindex(db::SQLiteDB,index::AbstractString;ifexists::Bool=false) +"drop the SQLite index `index` from the database `db`; `ifexists=true` will not return an error if `index` doesn't exist" +function dropindex!(db::DB,index::AbstractString;ifexists::Bool=false) exists = ifexists ? "if exists" : "" transaction(db) do - execute(db,"drop index $exists $index") + execute!(db,"drop index $exists $index") end - return changes(db) -end - -gettype{T<:Integer}(::Type{T}) = " INT" -gettype{T<:Real}(::Type{T}) = " REAL" -gettype{T<:AbstractString}(::Type{T}) = " TEXT" -gettype(::Type) = " BLOB" -gettype(::Type{NullType}) = " NULL" - -function create(db::SQLiteDB,name::AbstractString,table, - colnames=AbstractString[], - coltypes=DataType[] - ;temp::Bool=false,ifnotexists::Bool=false) - N, M = size(table) - colnames = isempty(colnames) ? ["x$i" for i=1:M] : colnames - coltypes = isempty(coltypes) ? [typeof(table[1,i]) for i=1:M] : coltypes - length(colnames) == length(coltypes) || throw(SQLiteException("colnames and coltypes must have same length")) - cols = [colnames[i] * gettype(coltypes[i]) for i = 1:M] - transaction(db) do - # create table statement - t = temp ? "TEMP " : "" - exists = ifnotexists ? "if not exists" : "" - execute(db,"CREATE $(t)TABLE $exists $name ($(join(cols,',')))") - # insert statements - params = chop(repeat("?,",M)) - stmt = SQLiteStmt(db,"insert into $name values ($params)") - #bind, step, reset loop for inserting values - for row = 1:N - for col = 1:M - @inbounds v = table[row,col] - bind(stmt,col,v) - end - execute(stmt) - end - end - execute(db,"analyze $name") - return changes(db) + return end - -function createindex(db::SQLiteDB,table::AbstractString,index::AbstractString,cols +""" +create the SQLite index `index` on the table `table` using `cols`, which may be a single column or comma-delimited list of columns. +`unique` specifies whether the index will be unique or not. +`ifnotexists=true` will not throw an error if the index already exists +""" +function createindex!(db::DB,table::AbstractString,index::AbstractString,cols ;unique::Bool=true,ifnotexists::Bool=false) u = unique ? "unique" : "" exists = ifnotexists ? "if not exists" : "" transaction(db) do - execute(db,"create $u index $exists $index on $table ($cols)") + execute!(db,"create $u index $exists $index on $table ($cols)") end - execute(db,"analyze $index") - return changes(db) + execute!(db,"analyze $index") + return end - -function append(db::SQLiteDB,name::AbstractString,table) - N, M = size(table) +"removes duplicate rows from `table` based on the values in `cols` which may be a single column or comma-delimited list of columns" +function removeduplicates!(db,table::AbstractString,cols::AbstractString) transaction(db) do - # insert statements - params = chop(repeat("?,",M)) - stmt = SQLiteStmt(db,"insert into $name values ($params)") - #bind, step, reset loop for inserting values - for row = 1:N - for col = 1:M - @inbounds v = table[row,col] - bind(stmt,col,v) - end - execute(stmt) - end + execute!(db,"delete from $table where rowid not in (select max(rowid) from $table group by $cols);") end - execute(db,"analyze $name") - return return changes(db) + execute!(db,"analyze $table") + return end -function deleteduplicates(db,table::AbstractString,cols::AbstractString) - transaction(db) do - execute(db,"delete from $table where rowid not in (select max(rowid) from $table group by $cols);") - end - execute(db,"analyze $table") - return changes(db) -end +include("Source.jl") +include("Sink.jl") -end #SQLite module +end # module diff --git a/src/Sink.jl b/src/Sink.jl new file mode 100644 index 0000000..4dcad46 --- /dev/null +++ b/src/Sink.jl @@ -0,0 +1,102 @@ +sqlitetype{T<:Integer}(::Type{T}) = "INT" +sqlitetype{T<:AbstractFloat}(::Type{T}) = "REAL" +sqlitetype{T<:AbstractString}(::Type{T}) = "TEXT" +sqlitetype(x) = "BLOB" +"SQLite.Sink implements the `Sink` interface in the `DataStreams` framework" +type Sink <: Data.Sink # <: IO + schema::Data.Schema + db::DB + tablename::UTF8String + stmt::Stmt +end +"constructs an SQLite.Source from an SQLite.Sink; selects all rows/columns from the underlying Sink table by default" +function Source(sink::SQLite.Sink,sql::AbstractString="select * from $(sink.tablename)") + stmt = SQLite.Stmt(sink.db,sql) + status = SQLite.execute!(stmt) + return SQLite.Source(sink.schema, stmt, status) +end + +""" +independent SQLite.Sink constructor to create a new or wrap an existing SQLite table with name `tablename`. +can optionally provide a `Data.Schema` through the `schema` argument. +`temp=true` will create a temporary SQLite table that will be destroyed automatically when the database is closed +`ifnotexists=false` will throw an error if `tablename` already exists in `db` +""" +function Sink(db::DB,tablename::AbstractString="julia_"*randstring(),schema::Data.Schema=Data.EMPTYSCHEMA;temp::Bool=false,ifnotexists::Bool=true) + rows, cols = size(schema) + temp = temp ? "TEMP" : "" + ifnotexists = ifnotexists ? "if not exists" : "" + columns = [string(schema.header[i],' ',sqlitetype(schema.types[i])) for i = 1:cols] + SQLite.execute!(db,"CREATE $temp TABLE $ifnotexists $tablename ($(join(columns,',')))") + params = chop(repeat("?,",cols)) + stmt = SQLite.Stmt(db,"insert into $tablename values ($params)") + return Sink(schema,db,utf8(tablename),stmt) +end +"constructs a new SQLite.Sink from the given `Data.Source`; uses `source` schema to create the SQLite table" +function Sink(source::Data.Source, db::DB, tablename::AbstractString="julia_"*randstring();temp::Bool=false,ifnotexists::Bool=true) + sink = Sink(db, tablename, source.schema; temp=temp, ifnotexists=ifnotexists) + return Data.stream!(source,sink) +end + +# create a new SQLite table +# Data.Table +function getbind!{T}(dt::NullableVector{T},row,col,stmt) + @inbounds val, isnull = dt.values[row]::T, dt.isnull[row] + if isnull + SQLite.bind!(stmt,col,NULL) + else + SQLite.bind!(stmt,col,val) + end + return +end +"stream the data in `dt` into the SQLite table represented by `sink`" +function Data.stream!(dt::Data.Table,sink::SQLite.Sink) + rows, cols = size(dt) + types = Data.types(dt) + handle = sink.stmt.handle + transaction(sink.db) do + if rows*cols != 0 + for row = 1:rows + for col = 1:cols + @inbounds SQLite.getbind!(Data.column(dt,col,types[col]),row,col,sink.stmt) + end + SQLite.sqlite3_step(handle) + SQLite.sqlite3_reset(handle) + end + end + end + SQLite.execute!(sink.db,"analyze $(sink.tablename)") + return sink +end +# CSV.Source +function getbind!{T}(io,::Type{T},opts,row,col,stmt) + val, isnull = CSV.getfield(io,T,opts,row,col) + if isnull + SQLite.bind!(stmt,col,NULL) + else + SQLite.bind!(stmt,col,val) + end + return +end +"stream the data in `source` CSV file to the SQLite table represented by `sink`" +function Data.stream!(source::CSV.Source,sink::SQLite.Sink) + rows, cols = size(source) + types = Data.types(source) + io = source.data + opts = source.options + stmt = sink.stmt + handle = stmt.handle + transaction(sink.db) do + if rows*cols != 0 + for row = 1:rows + for col = 1:cols + @inbounds SQLite.getbind!(io, types[col], opts, row, col, stmt) + end + SQLite.sqlite3_step(handle) + SQLite.sqlite3_reset(handle) + end + end + end + SQLite.execute!(sink.db,"analyze $(sink.tablename)") + return sink +end diff --git a/src/Source.jl b/src/Source.jl new file mode 100644 index 0000000..a7b969d --- /dev/null +++ b/src/Source.jl @@ -0,0 +1,175 @@ +""" +`SQLite.Source` implementes the `DataStreams` framework for interacting with SQLite databases +""" +type Source <: Data.Source # <: IO + schema::Data.Schema + stmt::Stmt + status::Cint +end +""" +Independently constructs an `SQLite.Source` in `db` with the SQL statement `sql`. +Will bind `values` to any parameters in `sql`. +`rows` is used to indicate how many rows to return in the query result if known beforehand. `rows=0` (the default) will return all possible rows. +`stricttypes=false` will remove strict column typing in the result set, making each column effectively `Vectot{Any}` +""" +function Source(db::DB,sql::AbstractString, values=[];rows::Int=0,stricttypes::Bool=true) + stmt = SQLite.Stmt(db,sql) + bind!(stmt, values) + status = SQLite.execute!(stmt) + cols = SQLite.sqlite3_column_count(stmt.handle) + header = Array(UTF8String,cols) + types = Array(DataType,cols) + for i = 1:cols + header[i] = bytestring(SQLite.sqlite3_column_name(stmt.handle,i-1)) + # do better column type inference; query what the column was created for? + types[i] = stricttypes ? SQLite.juliatype(stmt.handle,i) : Any + end + # rows == -1 && count(*)? + return SQLite.Source(Data.Schema(header,types,rows),stmt,status) +end + +function Base.eof(s::Source) + (s.status == SQLITE_DONE || s.status == SQLITE_ROW) || sqliteerror(s.stmt.db) + return s.status == SQLITE_DONE +end +"returns a single row as a string from an SQLite.Source" +function Base.readline(s::Source,delim::Char=',',buf::IOBuffer=IOBuffer()) + eof(s) && return "" + cols = s.schema.cols + for i = 1:cols + val = sqlite3_column_text(s.stmt.handle,i-1) + val != C_NULL && write(buf,bytestring(val)) + write(buf,ifelse(i == cols,'\n',delim)) + end + s.status = sqlite3_step(s.stmt.handle) + return takebuf_string(buf) +end +"returns a single row split by field from an SQLite.Source" +function readsplitline(s::Source) + eof(s) && return UTF8String[] + cols = s.schema.cols + vals = Array(UTF8String, cols) + for i = 1:cols + val = sqlite3_column_text(s.stmt.handle,i-1) + valsl[i] = val == C_NULL ? "" : bytestring(val) + end + s.status = sqlite3_step(s.stmt.handle) + return vals +end +"resets an SQLite.Source" +Data.reset!(io::SQLite.Source) = (sqlite3_reset(io.stmt.handle); execute!(io.stmt)) + +sqlitetypecode{T<:Integer}(::Type{T}) = SQLITE_INTEGER +sqlitetypecode{T<:AbstractFloat}(::Type{T}) = SQLITE_FLOAT +sqlitetypecode{T<:AbstractString}(::Type{T}) = SQLITE_TEXT +sqlitetypecode(::Type{BigInt}) = SQLITE_BLOB +sqlitetypecode(::Type{BigFloat}) = SQLITE_BLOB +sqlitetypecode(x) = SQLITE_BLOB +function juliatype(handle,col) + x = SQLite.sqlite3_column_type(handle,col-1) + if x == SQLITE_BLOB + val = sqlitevalue(Any,handle,col) + return typeof(val) + else + return juliatype(x) + end +end +juliatype(x) = x == SQLITE_INTEGER ? Int : x == SQLITE_FLOAT ? Float64 : x == SQLITE_TEXT ? UTF8String : Any + +sqlitevalue{T<:Integer}(::Type{T},handle,col) = sqlite3_column_int64(handle,col-1) +sqlitevalue{T<:AbstractFloat}(::Type{T},handle,col) = sqlite3_column_double(handle,col-1) +#TODO: test returning a PointerString instead of calling `bytestring` +sqlitevalue{T<:AbstractString}(::Type{T},handle,col) = convert(T,bytestring(sqlite3_column_text(handle,col-1))) +sqlitevalue(::Type{PointerString},handle,col) = bytestring(sqlite3_column_text(handle,col-1)) +sqlitevalue(::Type{BigInt},handle,col) = sqlitevalue(Any,handle,col) +sqlitevalue(::Type{BigFloat},handle,col) = sqlitevalue(Any,handle,col) +function sqlitevalue{T}(::Type{T},handle,col) + blob = convert(Ptr{UInt8},SQLite.sqlite3_column_blob(handle,col-1)) + b = SQLite.sqlite3_column_bytes(handle,col-1) + buf = zeros(UInt8,b) # global const? + unsafe_copy!(pointer(buf), blob, b) + r = SQLite.sqldeserialize(buf)::T + return r +end + +function getfield{T}(source::SQLite.Source, ::Type{T}, row, col) + handle = source.stmt.handle + t = sqlite3_column_type(handle,col-1) + if t == SQLite.SQLITE_NULL + val = Nullable{T}() + elseif t == SQLite.sqlitetypecode(T) + val = Nullable(sqlitevalue(T,handle,col)) + elseif T === Any + val = Nullable(sqlitevalue(juliatype(t),handle,col)) + else + throw(SQLiteException("strict type error trying to retrieve type `$T` on row: $(row+1), col: $col; SQLite reports a type of $(sqlitetypecode(T))")) + end + col == source.schema.cols && (source.status = sqlite3_step(handle)) + return val +end + +function getfield!{T}(source::SQLite.Source, dest::NullableVector{T}, ::Type{T}, row, col) + @inbounds dest[row] = SQLite.getfield(source, T, row, col) + return +end +function pushfield!{T}(source::SQLite.Source, dest::NullableVector{T}, ::Type{T}, row, col) + push!(dest, SQLite.getfield(source, T, row, col)) + return +end +"streams data from the SQLite.Source to a Data.Table" +function Data.stream!(source::SQLite.Source,sink::Data.Table) + rows, cols = size(source) + types = Data.types(source) + if rows == 0 + row = 0 + while !eof(source) + for col = 1:cols + @inbounds T = types[col] + SQLite.pushfield!(source, Data.unsafe_column(sink,col,T), T, row, col) # row + datarow + end + row += 1 + end + source.schema.rows = row + else + for row = 1:rows, col = 1:cols + @inbounds T = types[col] + SQLite.getfield!(source, Data.unsafe_column(sink,col,T), T, row, col) # row + datarow + end + end + sink.schema = source.schema + return sink +end +"creates a new Data.Table according to `source` schema and streams `Source` data into it" +function Data.Table(source::SQLite.Source) + sink = Data.Table(source.schema) + return Data.stream!(source,sink) +end +"streams data from an SQLite.Source to a CSV.Sink file; `header=false` will not write the column names to the file" +function Data.stream!(source::SQLite.Source,sink::CSV.Sink;header::Bool=true) + header && CSV.writeheaders(source,sink) + rows, cols = size(source) + types = Data.types(source) + row = 0 + while !eof(source) + for col = 1:cols + val = SQLite.getfield(source, types[col], row, col) + CSV.writefield(sink, isnull(val) ? sink.null : get(val), col, cols) + end + row += 1 + end + source.schema.rows = row + sink.schema = source.schema + close(sink) + return sink +end +"convenience method for executing an SQL statement and streaming the results back in a Data.Table" +function query(db::DB,sql::AbstractString, values=[];rows::Int=0,stricttypes::Bool=true) + so = Source(db,sql,values;rows=rows,stricttypes=stricttypes) + return Data.Table(so) +end +"returns a list of tables in `db`" +tables(db::DB) = query(db,"SELECT name FROM sqlite_master WHERE type='table';") +"returns a list of indices in `db`" +indices(db::DB) = query(db,"SELECT name FROM sqlite_master WHERE type='index';") +"returns a list of columns in `table`" +columns(db::DB,table::AbstractString) = query(db,"pragma table_info($table)") diff --git a/src/UDF.jl b/src/UDF.jl index d70093e..2ddb6a8 100644 --- a/src/UDF.jl +++ b/src/UDF.jl @@ -187,7 +187,7 @@ macro register(db, func) end # User-facing method for registering a Julia function to be used within SQLite -function register(db::SQLiteDB, func::Function; nargs::Int=-1, name::AbstractString=string(func), isdeterm::Bool=true) +function register(db, func::Function; nargs::Int=-1, name::AbstractString=string(func), isdeterm::Bool=true) @assert nargs <= 127 "use -1 if > 127 arguments are needed" # assume any negative number means a varargs function nargs < -1 && (nargs = -1) @@ -207,7 +207,7 @@ end # as above but for aggregate functions function register( - db::SQLiteDB, init, step::Function, final::Function=identity; + db, init, step::Function, final::Function=identity; nargs::Int=-1, name::AbstractString=string(step), isdeterm::Bool=true ) @assert nargs <= 127 "use -1 if > 127 arguments are needed" diff --git a/src/api.jl b/src/api.jl index d13e64f..f65e388 100644 --- a/src/api.jl +++ b/src/api.jl @@ -52,70 +52,65 @@ end # SQLITE_API int sqlite3_bind_paramter_count(sqlite3_stmt*) function sqlite3_bind_parameter_count(stmt::Ptr{Void}) - @NULLCHECK stmt return ccall( (:sqlite3_bind_parameter_count, sqlite3_lib), Cint, (Ptr{Void},), stmt) end #SQLITE_API const char* sqlite3_bind_parameter_name(sqlite3_stmt*, int) function sqlite3_bind_parameter_name(stmt::Ptr{Void}, col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_bind_parameter_name, sqlite3_lib), Ptr{UInt8}, (Ptr{Void}, Cint), stmt, col) end # SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); function sqlite3_bind_parameter_index(stmt::Ptr{Void},value::AbstractString) - @NULLCHECK stmt return ccall( (:sqlite3_bind_parameter_index, sqlite3_lib), Cint, (Ptr{Void},Ptr{UInt8}), stmt,utf8(value)) end # SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); function sqlite3_bind_double(stmt::Ptr{Void},col::Int,value::Float64) - @NULLCHECK stmt return ccall( (:sqlite3_bind_double, sqlite3_lib), Cint, (Ptr{Void},Cint,Float64), stmt,col,value) end # SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); function sqlite3_bind_int(stmt::Ptr{Void},col::Int,value::Int32) - @NULLCHECK stmt return ccall( (:sqlite3_bind_int, sqlite3_lib), Cint, (Ptr{Void},Cint,Int32), stmt,col,value) end # SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); function sqlite3_bind_int64(stmt::Ptr{Void},col::Int,value::Int64) - @NULLCHECK stmt return ccall( (:sqlite3_bind_int64, sqlite3_lib), Cint, (Ptr{Void},Cint,Int64), stmt,col,value) end # SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); function sqlite3_bind_null(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_bind_null, sqlite3_lib), Cint, (Ptr{Void},Cint), stmt,col) end # SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); function sqlite3_bind_text(stmt::Ptr{Void},col::Int,value::AbstractString) - @NULLCHECK stmt return ccall( (:sqlite3_bind_text, sqlite3_lib), Cint, (Ptr{Void},Cint,Ptr{UInt8},Cint,Ptr{Void}), stmt,col,value,sizeof(value),C_NULL) end +function sqlite3_bind_text(stmt::Ptr{Void},col::Int,ptr::Ptr,len::Int) + return ccall( (:sqlite3_bind_text, sqlite3_lib), + Cint, (Ptr{Void},Cint,Ptr{UInt8},Cint,Ptr{Void}), + stmt,col,ptr,len,C_NULL) +end # SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); function sqlite3_bind_text16(stmt::Ptr{Void},col::Int,value::UTF16String) - @NULLCHECK stmt return ccall( (:sqlite3_bind_text, sqlite3_lib), Cint, (Ptr{Void},Cint,Ptr{UInt16},Cint,Ptr{Void}), stmt,col,value,sizeof(value),C_NULL) end # SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); function sqlite3_bind_blob(stmt::Ptr{Void},col::Int,value) - @NULLCHECK stmt return ccall( (:sqlite3_bind_blob, sqlite3_lib), Cint, (Ptr{Void},Cint,Ptr{UInt8},Cint,Ptr{Void}), stmt,col,value,sizeof(value),SQLITE_STATIC) @@ -126,67 +121,56 @@ end # SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); function sqlite3_step(stmt::Ptr{Void}) - @NULLCHECK stmt return ccall( (:sqlite3_step, sqlite3_lib), Cint, (Ptr{Void},), stmt) end function sqlite3_column_count(stmt::Ptr{Void}) - @NULLCHECK stmt return ccall( (:sqlite3_column_count, sqlite3_lib), Cint, (Ptr{Void},), stmt) end function sqlite3_column_type(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_type, sqlite3_lib), Cint, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_blob(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_blob, sqlite3_lib), Ptr{Void}, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_bytes(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_bytes, sqlite3_lib), Cint, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_bytes16(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_bytes16, sqlite3_lib), Cint, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_double(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_double, sqlite3_lib), Cdouble, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_int(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_int, sqlite3_lib), Cint, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_int64(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_int64, sqlite3_lib), Clonglong, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_text(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_text, sqlite3_lib), Ptr{UInt8}, (Ptr{Void},Cint), stmt,col) end function sqlite3_column_text16(stmt::Ptr{Void},col::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_text16, sqlite3_lib), Ptr{Void}, (Ptr{Void},Cint), stmt,col) @@ -198,7 +182,6 @@ end # end # SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); function sqlite3_reset(stmt::Ptr{Void}) - @NULLCHECK stmt return ccall( (:sqlite3_reset, sqlite3_lib), Cint, (Ptr{Void},), stmt) @@ -206,13 +189,11 @@ end # SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); function sqlite3_column_name(stmt::Ptr{Void},n::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_name, sqlite3_lib), Ptr{UInt8}, (Ptr{Void},Cint), stmt,n) end function sqlite3_column_name16(stmt::Ptr{Void},n::Int) - @NULLCHECK stmt return ccall( (:sqlite3_column_name16, sqlite3_lib), Ptr{UInt8}, (Ptr{Void},Cint), stmt,n) diff --git a/src/consts.jl b/src/consts.jl index 236af29..bea7d8a 100644 --- a/src/consts.jl +++ b/src/consts.jl @@ -42,7 +42,7 @@ const SQLITE_CANTOPEN = 14 # /* Unable to open the database file */ const SQLITE_PROTOCOL = 15 # /* Database lock protocol error */ const SQLITE_EMPTY = 16 # /* Database is empty */ const SQLITE_SCHEMA = 17 # /* The database schema changed */ -const SQLITE_TOOBIG = 18 # /* String or BLOB exceeds size limit */ +const SQLITE_TOOBIG = 18 # /* AbstractString or BLOB exceeds size limit */ const SQLITE_CONSTRAINT = 19 # /* Abort due to constraint violation */ const SQLITE_MISMATCH = 20 # /* Data type mismatch */ const SQLITE_MISUSE = 21 # /* Library used incorrectly */ @@ -135,7 +135,7 @@ const SQLITE_CONFIG_HEAP = 8 # /* void*, int nByte, int min */ const SQLITE_CONFIG_MEMSTATUS = 9 # /* boolean */ const SQLITE_CONFIG_MUTEX = 10 # /* sqlite3_mutex_methods* */ const SQLITE_CONFIG_GETMUTEX = 11 # /* sqlite3_mutex_methods* */ -#/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ const SQLITE_CONFIG_LOOKASIDE = 13 # /* int int */ const SQLITE_CONFIG_PCACHE = 14 # /* no-op */ const SQLITE_CONFIG_GETPCACHE = 15 # /* no-op */ diff --git a/src/io.jl b/src/io.jl new file mode 100644 index 0000000..5794b37 --- /dev/null +++ b/src/io.jl @@ -0,0 +1,355 @@ +type Source <: IOSource # <: IO + schema::Schema + stmt::Stmt + status::Cint + function Source(db::DB,sql::AbstractString, values=[]) + stmt = SQLite.Stmt(db,sql) + bind!(stmt, values) + status = SQLite.execute!(stmt) + #TODO: build Schema + cols = sqlite3_column_count(stmt.handle) + header = Array(UTF8String,cols) + types = Array(DataType,cols) + for i = 1:cols + header[i] = bytestring(sqlite3_column_name(stmt.handle,i-1)) + types[i] = juliatype(sqlite3_column_type(stmt.handle,i-1)) + end + return Source(DataStreams.Schema(header,types,rows,cols),stmt,status) + end +end + +function Base.eof(s::Source) + (s.status == SQLITE_DONE || s.status == SQLITE_ROW) || sqliteerror(s.stmt.db) + return s.status == SQLITE_DONE +end + +function Base.readline(s::Source,delim::Char=',',buf::IOBuffer=IOBuffer()) + eof(s) && return "" + cols = s.schema.cols + for i = 1:cols + val = sqlite3_column_text(s.stmt.handle,i-1) + val != C_NULL && write(buf,bytestring(val)) + write(buf,ifelse(i == cols,'\n',delim)) + end + s.status = sqlite3_step(s.stmt.handle) + return takebuf_string(buf) +end + +function readsplitline(s::Source) + eof(s) && return UTF8String[] + cols = s.schema.cols + vals = Array(UTF8String, cols) + for i = 1:cols + val = sqlite3_column_text(s.stmt.handle,i-1) + valsl[i] = val == C_NULL ? "" : bytestring(val) + end + s.status = sqlite3_step(s.stmt.handle) + return vals +end + +reset!(io::SQLite.Source) = (sqlite3_reset(io.stmt.handle); execute!(io.stmt)) + +default{T}(::Type{T}) = zero(T) # default fallback for all other types +default{T<:AbstractString}(::Type{T}) = convert(T,"")::T +default(::Type{Date}) = Date() +default(::Type{DateTime}) = DateTime() + +sqlitetype{T<:Integer}(::Type{T}) = SQLITE_INTEGER +sqlitetype{T<:AbstractFloat}(::Type{T}) = SQLITE_FLOAT +sqlitetype{T<:AbstractString}(::Type{T}) = SQLITE_TEXT +sqlitetype(x) = SQLITE_BLOB +juliatype(x) = x == SQLITE_INTEGER ? Int : x == SQLITE_FLOAT ? Float64 : x == SQLITE_TEXT ? UTF8String : Any + +sqlitevalue{T<:Integer}(::Type{T},handle,col) = sqlite3_column_int64(handle,col) +sqlitevalue{T<:AbstractFloat}(::Type{T},handle,col) = sqlite3_column_double(handle,col) +sqlitevalue{T<:AbstractString}(::Type{T},handle,col) = bytestring(sqlite3_column_text(handle,col)) +function sqlitevalue{T}(::Type{T},handle,col) + blob = convert(Ptr{UInt8},sqlite3_column_blob(handle,col)) + b = sqlite3_column_bytes(handle,col) + buf = zeros(UInt8,b) # global const? + unsafe_copy!(pointer(buf), blob, b) + r = sqldeserialize(buf) +end + +function getfield{T}(source::SQLite.Source, ::Type{T}, row, col) + val::T = default(T) + eof(source) && return val, true + handle = source.stmt.handle + t = sqlite3_column_type(handle,col-1) + if t == sqlitetype(T) + val = sqlitevalue(T,handle,col-1) + null = false + elseif t == SQLITE_NULL + null = true + else + throw(SQLiteException("strict type error trying to retrieve type `$T` on row: $row, col: $col; SQLite reports a type of $(sqlitetype(T))")) + end + col == source.schema.cols && (source.status = sqlite3_step(handle)) + return val, null +end + +function getfield!{T}(source::SQLite.Source, dest::NullableVector{T}, ::Type{T}, row, col) + @inbounds val, null = SQLite.getfield(source, T, row, col) # row + datarow + @inbounds dest.values[row], dest.isnull[row] = val, null + return +end + +function DataStreams.stream!(source::SQLite.Source,sink::DataStream) + rows, cols = size(source) + types = source.schema.types + data = sink.data + for row = 1:rows, col = 1:cols + SQLite.getfield!(source, data[col], types[col], row, col) # row + datarow + end + return sink +end + +Base.start(s::Stream) = 1 +Base.done(s::Stream,col) = eof(s) +function Base.next(s::Stream,i) + t = sqlite3_column_type(s.stmt.handle,i-1) + r::Any + if t == SQLITE_INTEGER + r = sqlite3_column_int64(s.stmt.handle,i-1) + elseif t == SQLITE_FLOAT + r = sqlite3_column_double(s.stmt.handle,i-1) + elseif t == SQLITE_TEXT + #TODO: have a way to return text16? + r = bytestring( sqlite3_column_text(s.stmt.handle,i-1) ) + elseif t == SQLITE_BLOB + blob = sqlite3_column_blob(s.stmt.handle,i-1) + b = sqlite3_column_bytes(s.stmt.handle,i-1) + buf = zeros(UInt8,b) + unsafe_copy!(pointer(buf), convert(Ptr{UInt8},blob), b) + r = sqldeserialize(buf) + else + r = NULL + end + if i == s.cols + s.status = sqlite3_step(s.stmt.handle) + i = 1 + else + i += 1 + end + return r, i +end + +function scalarquery(db::DB,sql) + stream = SQLite.open(db,sql) + return next(stream,1)[1] +end + +function Base.writecsv(db,table,file;compressed::Bool=false) + out = compressed ? GZip.open(file,"w") : open(file,"w") + s = SQLite.open(SQLite.Table(db,table)) + for i = 1:s.cols + write(out,bytestring(sqlite3_column_name(s.stmt.handle,i-1))) + write(out,ifelse(i == s.cols,'\n',',')) + end + while !eof(s) + for i = 1:s.cols + val = sqlite3_column_text(s.stmt.handle,i-1) + val != C_NULL && write(out,bytestring(val)) + write(out,ifelse(i == s.cols,'\n',',')) + end + s.status = sqlite3_step(s.stmt.handle) + end + close(out) + return file +end + +function query(db::DB,sql::AbstractString, values=[]) + stream = SQLite.open(db,sql,values) + ncols = stream.cols + (eof(stream) || ncols == 0) && return changes(db) + colnames = Array(AbstractString,ncols) + results = Array(Any,ncols) + for i = 1:ncols + colnames[i] = bytestring(sqlite3_column_name(stream.stmt.handle,i-1)) + results[i] = Any[] + end + col = 1 + while !eof(stream) + c = col + r, col = next(stream,col) + push!(results[c],r) + end + return ResultSet(colnames, results) +end + +function tables(db::DB) + query(db,"SELECT name FROM sqlite_master WHERE type='table';") +end + +function indices(db::DB) + query(db,"SELECT name FROM sqlite_master WHERE type='index';") +end + +columns(db::DB,table::AbstractString) = query(db,"pragma table_info($table)") + +function drop!(db::DB,table::AbstractString;ifexists::Bool=false) + exists = ifexists ? "if exists" : "" + transaction(db) do + execute!(db,"drop table $exists $table") + end + execute!(db,"vacuum") + return changes(db) +end + +function dropindex!(db::DB,index::AbstractString;ifexists::Bool=false) + exists = ifexists ? "if exists" : "" + transaction(db) do + execute!(db,"drop index $exists $index") + end + return changes(db) +end + +function create(db::DB,name::AbstractString,table::AbstractVector, + colnames=AbstractString[],coltypes=DataType[] + ;temp::Bool=false,ifnotexists::Bool=false) + table = reshape(table,(length(table),1)) + return create(db,name,table,colnames,coltypes;temp=temp,ifnotexists=ifnotexists) +end +function create(db::DB,name::AbstractString,table, + colnames=AbstractString[], + coltypes=DataType[] + ;temp::Bool=false,ifnotexists::Bool=false) + N, M = size(table) + colnames = isempty(colnames) ? ["x$i" for i=1:M] : colnames + coltypes = isempty(coltypes) ? [typeof(table[1,i]) for i=1:M] : coltypes + length(colnames) == length(coltypes) || throw(SQLiteException("colnames and coltypes must have same length")) + cols = [colnames[i] * SQLite.gettype(coltypes[i]) for i = 1:length(colnames)] + transaction(db) do + # create table statement + t = temp ? "TEMP " : "" + exists = ifnotexists ? "if not exists" : "" + SQLite.execute!(db,"CREATE $(t)TABLE $exists $name ($(join(cols,',')))") + # insert statements + if N*M != 0 + params = chop(repeat("?,",M)) + stmt = SQLite.Stmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + for row = 1:N + for col = 1:M + @inbounds v = table[row,col] + bind!(stmt,col,v) + end + execute!(stmt) + end + end + end + execute!(db,"analyze $name") + return ResultSet(["Rows Loaded"],Any[Any[N]]) +end + +function readbind!{T<:Union{Integer,Float64}}(io,::Type{T},row,col,stmt) + val, isnull = CSV.readfield(io,T,row,col) + bind!(stmt,col,ifelse(isnull,NULL,val)) + return +end +function readbind!(io, ::Type{Date}, row, col, stmt) + bind!(stmt,col,CSV.readfield(io,Date,row,col)[1]) + return +end +function readbind!{T<:AbstractString}(io,::Type{T},row,col,stmt) + str, isnull = CSV.readfield(io,T,row,col) + if isnull + bind!(stmt,col,NULL) + else + sqlite3_bind_text(stmt.handle,col,str.ptr,str.len) + end + return +end + +function create(db::DB,file::CSV.File,name::AbstractString=splitext(basename(file.fullpath))[1] + ;temp::Bool=false,ifnotexists::Bool=false) + names = SQLite.make_unique([SQLite.identifier(i) for i in file.header]) + sqltypes = [string(names[i]) * SQLite.gettype(file.types[i]) for i = 1:file.cols] + N = transaction(db) do + # create table statement + t = temp ? "TEMP " : "" + exists = ifnotexists ? "if not exists" : "" + SQLite.execute!(db,"CREATE $(t)TABLE $exists $name ($(join(sqltypes,',')))") + # insert statements + params = chop(repeat("?,",file.cols)) + stmt = SQLite.Stmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + io = CSV.open(file) + seek(io,file.datapos+1) + N = file.datarow + while !eof(io) + for col = 1:file.cols + SQLite.readbind!(io,file.types[col],N,col,stmt) + end + SQLite.execute!(stmt) + N += 1 + b = CSV.peek(io) + empty = b == CSV.NEWLINE || b == CSV.RETURN + if empty + file.skipblankrows && CSV.skipn!(io,1,file.quotechar,file.escapechar) + end + end + return N - file.datarow + end + execute!(db,"analyze $name") + return ResultSet(["Rows Loaded"],Any[Any[N]]) +end + +function createindex(db::DB,table::AbstractString,index::AbstractString,cols + ;unique::Bool=true,ifnotexists::Bool=false) + u = unique ? "unique" : "" + exists = ifnotexists ? "if not exists" : "" + transaction(db) do + execute!(db,"create $u index $exists $index on $table ($cols)") + end + execute!(db,"analyze $index") + return changes(db) +end + +function append!(db::DB,name::AbstractString,file::CSV.File) + N = transaction(db) do + # insert statements + params = chop(repeat("?,",file.cols)) + stmt = SQLite.Stmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + io = CSV.open(file) + seek(io,file.datapos) + N = 0 + while !eof(io) + for col = 1:file.cols + SQLite.readbind!(io,file.types[col],N,col,stmt) + end + execute!(stmt) + N += 1 + end + return N + end + execute!(db,"analyze $name") + return ResultSet(["Rows Loaded"],Any[Any[N]]) +end +function append!(db::DB,name::AbstractString,table) + N, M = size(table) + transaction(db) do + # insert statements + params = chop(repeat("?,",M)) + stmt = Stmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + for row = 1:N + for col = 1:M + @inbounds v = table[row,col] + bind!(stmt,col,v) + end + execute!(stmt) + end + end + execute!(db,"analyze $name") + return ResultSet(["Rows Loaded"],Any[Any[N]]) +end + +function deleteduplicates!(db,table::AbstractString,cols::AbstractString) + transaction(db) do + execute!(db,"delete from $table where rowid not in (select max(rowid) from $table group by $cols);") + end + execute!(db,"analyze $table") + return changes(db) +end diff --git a/src/old_ui.jl b/src/old_ui.jl new file mode 100644 index 0000000..52ce242 --- /dev/null +++ b/src/old_ui.jl @@ -0,0 +1,279 @@ +export NULL, SQLiteDB, SQLiteStmt, ResultSet, + execute, query, tables, indices, columns, droptable, dropindex, + create, createindex, append, deleteduplicates + +gettype{T<:Integer}(::Type{T}) = " INT" +gettype{T<:AbstractFloat}(::Type{T}) = " REAL" +gettype{T<:AbstractString}(::Type{T}) = " TEXT" +gettype(::Type) = " BLOB" +gettype(::Type{NullType}) = " NULL" + +type ResultSet + colnames + values::Vector{Any} +end +==(a::ResultSet,b::ResultSet) = a.colnames == b.colnames && a.values == b.values +include("show.jl") +Base.convert(::Type{Matrix},a::ResultSet) = [a[i,j] for i=1:size(a,1), j=1:size(a,2)] + +function SQLiteDB(file::AbstractString="";UTF16::Bool=false) + Base.depwarn("`SQLiteDB` is deprecated; please use `SQLite.DB` instead",:SQLiteDB) + handle = [C_NULL] + utf = UTF16 ? utf16 : utf8 + file = isempty(file) ? file : expanduser(file) + if @OK sqliteopen(utf(file),handle) + db = SQLiteDB(utf(file),handle[1]) + register(db, regexp, nargs=2, name="regexp") + finalizer(db,close) + return db + else # error + sqlite3_close(handle[1]) + sqliteerror() + end +end + +function changes(db::SQLiteDB) + new_tot = sqlite3_total_changes(db.handle) + diff = new_tot - db.changes + db.changes = new_tot + return ResultSet(["Rows Affected"],Any[Any[diff]]) +end + + +function Base.close{T}(db::SQLiteDB{T}) + db.handle == C_NULL && return + # ensure SQLiteStmts are finalised + gc() + @CHECK db sqlite3_close(db.handle) + db.handle = C_NULL + return +end + +type SQLiteStmt{T} + db::SQLiteDB{T} + handle::Ptr{Void} + sql::T +end + +sqliteprepare(db,sql,stmt,null) = + @CHECK db sqlite3_prepare_v2(db.handle,utf8(sql),stmt,null) +sqliteprepare(db::SQLiteDB{UTF16String},sql,stmt,null) = + @CHECK db sqlite3_prepare16_v2(db.handle,utf16(sql),stmt,null) + +function SQLiteStmt{T}(db::SQLiteDB{T},sql::AbstractString) + Base.depwarn("`SQLiteStmt` is deprecated; please use `SQLite.Stmt` instead",:SQLiteStmt) + handle = [C_NULL] + sqliteprepare(db,sql,handle,[C_NULL]) + stmt = SQLiteStmt(db,handle[1],convert(T,sql)) + finalizer(stmt, close) + return stmt +end + +function Base.close(stmt::SQLiteStmt) + stmt.handle == C_NULL && return + @CHECK stmt.db sqlite3_finalize(stmt.handle) + stmt.handle = C_NULL + return +end + +# bind a row to nameless parameters +function bind(stmt, values::Vector) + nparams = sqlite3_bind_parameter_count(stmt.handle) + @assert nparams == length(values) "you must provide values for all placeholders" + for i in 1:nparams + @inbounds bind(stmt, i, values[i]) + end +end +# bind a row to named parameters +function bind{V}(stmt, values::Dict{Symbol, V}) + nparams = sqlite3_bind_parameter_count(stmt.handle) + @assert nparams == length(values) "you must provide values for all placeholders" + for i in 1:nparams + name = bytestring(sqlite3_bind_parameter_name(stmt.handle, i)) + @assert !isempty(name) "nameless parameters should be passed as a Vector" + # name is returned with the ':', '@' or '$' at the start + name = name[2:end] + bind(stmt, i, values[symbol(name)]) + end +end +# Binding parameters to SQL statements +function bind(stmt,name::AbstractString,val) + i = sqlite3_bind_parameter_index(stmt.handle,name) + if i == 0 + throw(SQLiteException("SQL parameter $name not found in $stmt")) + end + return bind(stmt,i,val) +end +bind(stmt,i::Int,val::AbstractFloat) = @CHECK stmt.db sqlite3_bind_double(stmt.handle,i,@compat Float64(val)) +bind(stmt,i::Int,val::Int32) = @CHECK stmt.db sqlite3_bind_int(stmt.handle,i,val) +bind(stmt,i::Int,val::Int64) = @CHECK stmt.db sqlite3_bind_int64(stmt.handle,i,val) +bind(stmt,i::Int,val::NullType) = @CHECK stmt.db sqlite3_bind_null(stmt.handle,i) +bind(stmt,i::Int,val::AbstractString) = @CHECK stmt.db sqlite3_bind_text(stmt.handle,i,val) +bind(stmt,i::Int,val::UTF16String) = @CHECK stmt.db sqlite3_bind_text16(stmt.handle,i,val) +# We may want to track the new ByteVec type proposed at https://github.com/JuliaLang/julia/pull/8964 +# as the "official" bytes type instead of Vector{UInt8} +bind(stmt,i::Int,val::Vector{UInt8}) = @CHECK stmt.db sqlite3_bind_blob(stmt.handle,i,val) +bind(stmt,i::Int,val) = bind(stmt,i,sqlserialize(val)) + +# Execute SQL statements +function execute(stmt) + r = sqlite3_step(stmt.handle) + if r == SQLITE_DONE + sqlite3_reset(stmt.handle) + elseif r != SQLITE_ROW + sqliteerror(stmt.db) + end + return r +end +function execute(db::SQLiteDB,sql::AbstractString) + stmt = SQLiteStmt(db,sql) + execute(stmt) + return changes(db) +end + + +function query(db::SQLiteDB,sql::AbstractString, values=[]) + stmt = SQLiteStmt(db,sql) + bind(stmt, values) + status = execute(stmt) + ncols = sqlite3_column_count(stmt.handle) + if status == SQLITE_DONE || ncols == 0 + return changes(db) + end + colnames = Array(AbstractString,ncols) + results = Array(Any,ncols) + for i = 1:ncols + colnames[i] = bytestring(sqlite3_column_name(stmt.handle,i-1)) + results[i] = Any[] + end + while status == SQLITE_ROW + for i = 1:ncols + t = sqlite3_column_type(stmt.handle,i-1) + if t == SQLITE_INTEGER + r = sqlite3_column_int64(stmt.handle,i-1) + elseif t == SQLITE_FLOAT + r = sqlite3_column_double(stmt.handle,i-1) + elseif t == SQLITE_TEXT + #TODO: have a way to return text16? + r = bytestring( sqlite3_column_text(stmt.handle,i-1) ) + elseif t == SQLITE_BLOB + blob = sqlite3_column_blob(stmt.handle,i-1) + b = sqlite3_column_bytes(stmt.handle,i-1) + buf = zeros(UInt8,b) + unsafe_copy!(pointer(buf), convert(Ptr{UInt8},blob), b) + r = sqldeserialize(buf) + else + r = NULL + end + push!(results[i],r) + end + status = sqlite3_step(stmt.handle) + end + if status == SQLITE_DONE + return ResultSet(colnames, results) + else + sqliteerror(stmt.db) + end +end + +function tables(db::SQLiteDB) + query(db,"SELECT name FROM sqlite_master WHERE type='table';") +end + +function indices(db::SQLiteDB) + query(db,"SELECT name FROM sqlite_master WHERE type='index';") +end + +columns(db::SQLiteDB,table::AbstractString) = query(db,"pragma table_info($table)") + +function droptable(db::SQLiteDB,table::AbstractString;ifexists::Bool=false) + exists = ifexists ? "if exists" : "" + transaction(db) do + execute(db,"drop table $exists $table") + end + execute(db,"vacuum") + return changes(db) +end + +function dropindex(db::SQLiteDB,index::AbstractString;ifexists::Bool=false) + exists = ifexists ? "if exists" : "" + transaction(db) do + execute(db,"drop index $exists $index") + end + return changes(db) +end + +function create(db::SQLiteDB,name::AbstractString,table, + colnames=AbstractString[], + coltypes=DataType[] + ;temp::Bool=false,ifnotexists::Bool=false) + N, M = size(table) + colnames = isempty(colnames) ? ["x$i" for i=1:M] : colnames + coltypes = isempty(coltypes) ? [typeof(table[1,i]) for i=1:M] : coltypes + length(colnames) == length(coltypes) || throw(SQLiteException("colnames and coltypes must have same length")) + cols = [colnames[i] * gettype(coltypes[i]) for i = 1:M] + transaction(db) do + # create table statement + t = temp ? "TEMP " : "" + exists = ifnotexists ? "if not exists" : "" + execute(db,"CREATE $(t)TABLE $exists $name ($(join(cols,',')))") + # insert statements + params = chop(repeat("?,",M)) + stmt = SQLiteStmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + for row = 1:N + for col = 1:M + @inbounds v = table[row,col] + bind(stmt,col,v) + end + execute(stmt) + end + end + execute(db,"analyze $name") + return changes(db) +end + +function createindex(db::SQLiteDB,table::AbstractString,index::AbstractString,cols + ;unique::Bool=true,ifnotexists::Bool=false) + u = unique ? "unique" : "" + exists = ifnotexists ? "if not exists" : "" + transaction(db) do + execute(db,"create $u index $exists $index on $table ($cols)") + end + execute(db,"analyze $index") + return changes(db) +end + +function append(db::SQLiteDB,name::AbstractString,table) + N, M = size(table) + transaction(db) do + # insert statements + params = chop(repeat("?,",M)) + stmt = SQLiteStmt(db,"insert into $name values ($params)") + #bind, step, reset loop for inserting values + for row = 1:N + for col = 1:M + @inbounds v = table[row,col] + bind(stmt,col,v) + end + execute(stmt) + end + end + execute(db,"analyze $name") + return return changes(db) +end + +function deleteduplicates(db,table::AbstractString,cols::AbstractString) + transaction(db) do + execute(db,"delete from $table where rowid not in (select max(rowid) from $table group by $cols);") + end + execute(db,"analyze $table") + return changes(db) +end + +@deprecate bind SQLite.bind! +@deprecate execute SQLite.execute! +@deprecate droptable SQLite.drop! +@deprecate dropindex SQLite.dropindex! +@deprecate createindex SQLite.createindex! +@deprecate deleteduplicates SQLite.deleteduplicates! diff --git a/test/old_runtests.jl b/test/old_runtests.jl new file mode 100644 index 0000000..25003dc --- /dev/null +++ b/test/old_runtests.jl @@ -0,0 +1,259 @@ +reload("SQLite") +using Base.Test, Compat + +import Base: +, == + +a = SQLite.SQLiteDB() +b = SQLite.SQLiteDB(UTF16=true) +c = SQLite.SQLiteDB(":memory:",UTF16=true) + +close(a) +close(b) +close(c) + +temp = tempname() +SQLite.SQLiteDB(temp) + +#db = SQLiteDB("C:/Users/karbarcca/.julia/v0.4/SQLite/test/Chinook_Sqlite.sqlite") +db = SQLite.SQLiteDB(joinpath(dirname(@__FILE__),"Chinook_Sqlite.sqlite")) + +results = SQLite.query(db,"SELECT name FROM sqlite_master WHERE type='table';") +@test length(results.colnames) == 1 +@test results.colnames[1] == "name" +@test size(results) == (11,1) + +results1 = SQLite.tables(db) +@test results.colnames == results1.colnames +@test results.values == results1.values + +results = SQLite.query(db,"SELECT * FROM Employee;") +@test length(results.colnames) == 15 +@test size(results) == (8,15) +@test typeof(results[1,1]) == Int64 +@test typeof(results[1,2]) <: AbstractString +@test results[1,5] == SQLite.NULL + +SQLite.query(db,"SELECT * FROM Album;") +SQLite.query(db,"SELECT a.*, b.AlbumId + FROM Artist a + LEFT OUTER JOIN Album b ON b.ArtistId = a.ArtistId + ORDER BY name;") + +EMPTY_RESULTSET = SQLite.ResultSet(["Rows Affected"],Any[Any[0]]) +SQLite.SQLite.ResultSet(x) = SQLite.ResultSet(["Rows Affected"],Any[Any[x]]) +r = SQLite.query(db,"create table temp as select * from album") +@test r == EMPTY_RESULTSET +r = SQLite.query(db,"select * from temp limit 10") +@test length(r.colnames) == 3 +@test size(r) == (10,3) +@test SQLite.query(db,"alter table temp add column colyear int") == EMPTY_RESULTSET +@test SQLite.query(db,"update temp set colyear = 2014") == SQLite.ResultSet(347) +r = SQLite.query(db,"select * from temp limit 10") +@test length(r.colnames) == 4 +@test size(r) == (10,4) +@test all(r[:,4] .== 2014) +if VERSION > v"0.4.0-" + @test SQLite.query(db,"alter table temp add column dates blob") == EMPTY_RESULTSET + stmt = SQLite.SQLiteStmt(db,"update temp set dates = ?") + SQLite.bind(stmt,1,Date(2014,1,1)) + SQLite.execute(stmt) + r = SQLite.query(db,"select * from temp limit 10") + @test length(r.colnames) == 5 + @test size(r) == (10,5) + @test typeof(r[1,5]) == Date + @test all(r[:,5] .== Date(2014,1,1)) + close(stmt) +end +@test SQLite.query(db,"drop table temp") == EMPTY_RESULTSET + +SQLite.create(db,"temp",zeros(5,5),["col1","col2","col3","col4","col5"],[Float64 for i=1:5]) +r = SQLite.query(db,"select * from temp") +@test size(r) == (5,5) +@test all(r.values[1] .== 0.0) +@test all([typeof(i) for i in r.values[1]] .== Float64) +@test r.colnames == ["col1","col2","col3","col4","col5"] +@test SQLite.droptable(db,"temp") == EMPTY_RESULTSET + +SQLite.create(db,"temp",zeros(5,5)) +r = SQLite.query(db,"select * from temp") +@test size(r) == (5,5) +@test all(r.values[1] .== 0.0) +@test all([typeof(i) for i in r.values[1]] .== Float64) +@test r.colnames == ["x1","x2","x3","x4","x5"] +@test SQLite.droptable(db,"temp") == EMPTY_RESULTSET + +SQLite.create(db,"temp",zeros(Int,5,5)) +r = SQLite.query(db,"select * from temp") +@test size(r) == (5,5) +@test all(r.values[1] .== 0) +@test all([typeof(i) for i in r.values[1]] .== Int64) +@test r.colnames == ["x1","x2","x3","x4","x5"] +SQLite.append(db,"temp",ones(Int,5,5)) +r = SQLite.query(db,"select * from temp") +@test size(r) == (10,5) +@test r.values[1] == Any[0,0,0,0,0,1,1,1,1,1] +@test typeof(r[1,1]) == Int64 +@test r.colnames == ["x1","x2","x3","x4","x5"] +@test SQLite.droptable(db,"temp") == EMPTY_RESULTSET + +if VERSION > v"0.4.0-" + rng = Date(2013):Date(2013,1,5) + SQLite.create(db,"temp",[i for i = rng, j = rng]) + r = SQLite.query(db,"select * from temp") + @test size(r) == (5,5) + @test all(r[:,1] .== rng) + @test all([typeof(i) for i in r.values[1]] .== Date) + @test r.colnames == ["x1","x2","x3","x4","x5"] + @test SQLite.droptable(db,"temp") == EMPTY_RESULTSET +end + +SQLite.query(db,"CREATE TABLE temp AS SELECT * FROM Album") +r = SQLite.query(db, "SELECT * FROM temp LIMIT ?", [3]) +@test size(r) == (3,3) +r = SQLite.query(db, "SELECT * FROM temp WHERE Title LIKE ?", ["%time%"]) +@test r.values[1] == [76, 111, 187] +SQLite.query(db, "INSERT INTO temp VALUES (?1, ?3, ?2)", [0,0,"Test Album"]) +r = SQLite.query(db, "SELECT * FROM temp WHERE AlbumId = 0") +@test r == SQLite.ResultSet(Any["AlbumId", "Title", "ArtistId"], Any[Any[0], Any["Test Album"], Any[0]]) +SQLite.droptable(db, "temp") + +binddb = SQLite.SQLiteDB() +SQLite.query(binddb, "CREATE TABLE temp (n NULL, i6 INT, f REAL, s TEXT, a BLOB)") +SQLite.query(binddb, "INSERT INTO temp VALUES (?1, ?2, ?3, ?4, ?5)", Any[SQLite.NULL, convert(Int64,6), 6.4, "some text", b"bytearray"]) +r = SQLite.query(binddb, "SELECT * FROM temp") +for (v, t) in zip(r.values, [SQLite.NullType, Int64, Float64, AbstractString, Vector{UInt8}]) + @test isa(v[1], t) +end +SQLite.query(binddb, "CREATE TABLE blobtest (a BLOB, b BLOB)") +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", b"b"]) +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", BigInt(2)]) +type Point{T} + x::T + y::T +end +==(a::Point, b::Point) = a.x == b.x && a.y == b.y +p1 = Point(1, 2) +p2 = Point(1.3, 2.4) +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p1]) +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p2]) +r = SQLite.query(binddb, "SELECT * FROM blobtest") +for v in r.values[1] + @test v == b"a" +end +for (v1, v2) in zip(r.values[2], Any[b"b", BigInt(2), p1, p2]) + @test v1 == v2 +end +close(binddb) + +# I can't be arsed to SQLite.create a new one using old dictionary syntax +if VERSION > v"0.4.0-" + SQLite.query(db,"CREATE TABLE temp AS SELECT * FROM Album") + r = SQLite.query(db, "SELECT * FROM temp LIMIT :a", Dict(:a => 3)) + @test size(r) == (3,3) + r = SQLite.query(db, "SELECT * FROM temp WHERE Title LIKE @word", Dict(:word => "%time%")) + @test r.values[1] == [76, 111, 187] + SQLite.query(db, "INSERT INTO temp VALUES (@lid, :title, \$rid)", Dict(:rid => 0, :lid => 0, :title => "Test Album")) + r = SQLite.query(db, "SELECT * FROM temp WHERE AlbumId = 0") + @test r == SQLite.ResultSet(Any["AlbumId", "Title", "ArtistId"], Any[Any[0], Any["Test Album"], Any[0]]) + SQLite.droptable(db, "temp") +end + +r = SQLite.query(db, SQLite.@sr_str("SELECT LastName FROM Employee WHERE BirthDate REGEXP '^\\d{4}-08'")) +@test r.values[1][1] == "Peacock" + +triple(x) = 3x +@test_throws AssertionError SQLite.register(db, triple, nargs=186) +SQLite.register(db, triple, nargs=1) +r = SQLite.query(db, "SELECT triple(Total) FROM Invoice ORDER BY InvoiceId LIMIT 5") +s = SQLite.query(db, "SELECT Total FROM Invoice ORDER BY InvoiceId LIMIT 5") +for (i, j) in zip(r.values[1], s.values[1]) + @test_approx_eq i 3j +end + +SQLite.@register db function add4(q) + q+4 +end +r = SQLite.query(db, "SELECT add4(AlbumId) FROM Album") +s = SQLite.query(db, "SELECT AlbumId FROM Album") +@test r[1] == s[1]+4 + +SQLite.@register db mult(args...) = *(args...) +r = SQLite.query(db, "SELECT Milliseconds, Bytes FROM Track") +s = SQLite.query(db, "SELECT mult(Milliseconds, Bytes) FROM Track") +@test r[1].*r[2] == s[1] +t = SQLite.query(db, "SELECT mult(Milliseconds, Bytes, 3, 4) FROM Track") +@test r[1].*r[2]*3*4 == t[1] + +SQLite.@register db sin +u = SQLite.query(db, "select sin(milliseconds) from track limit 5") +@test all(-1 .< u[1] .< 1) + +SQLite.register(db, hypot; nargs=2, name="hypotenuse") +v = SQLite.query(db, "select hypotenuse(Milliseconds,bytes) from track limit 5") +@test [@compat round(Int,i) for i in v[1]] == [11175621,5521062,3997652,4339106,6301714] + +SQLite.@register db str2arr(s) = convert(Array{UInt8}, s) +r = SQLite.query(db, "SELECT str2arr(LastName) FROM Employee LIMIT 2") +@test r[1] == Any[UInt8[0x41,0x64,0x61,0x6d,0x73],UInt8[0x45,0x64,0x77,0x61,0x72,0x64,0x73]] + +SQLite.@register db big +r = SQLite.query(db, "SELECT big(5)") +@test r[1][1] == big(5) + +doublesum_step(persist, current) = persist + current +doublesum_final(persist) = 2 * persist +SQLite.register(db, 0, doublesum_step, doublesum_final, name="doublesum") +r = SQLite.query(db, "SELECT doublesum(UnitPrice) FROM Track") +s = SQLite.query(db, "SELECT UnitPrice FROM Track") +@test_approx_eq r[1][1] 2*sum(s[1]) + +mycount(p, c) = p + 1 +SQLite.register(db, 0, mycount) +r = SQLite.query(db, "SELECT mycount(TrackId) FROM PlaylistTrack") +s = SQLite.query(db, "SELECT count(TrackId) FROM PlaylistTrack") +@test r[1] == s[1] + +bigsum(p, c) = p + big(c) +SQLite.register(db, big(0), bigsum) +r = SQLite.query(db, "SELECT bigsum(TrackId) FROM PlaylistTrack") +s = SQLite.query(db, "SELECT TrackId FROM PlaylistTrack") +@test r[1][1] == big(sum(s[1])) + +SQLite.query(db, "CREATE TABLE points (x INT, y INT, z INT)") +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [1, 2, 3]) +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [4, 5, 6]) +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [7, 8, 9]) +type Point3D{T<:Number} + x::T + y::T + z::T +end +==(a::Point3D, b::Point3D) = a.x == b.x && a.y == b.y && a.z == b.z ++(a::Point3D, b::Point3D) = Point3D(a.x + b.x, a.y + b.y, a.z + b.z) +sumpoint(p::Point3D, x, y, z) = p + Point3D(x, y, z) +SQLite.register(db, Point3D(0, 0, 0), sumpoint) +r = SQLite.query(db, "SELECT sumpoint(x, y, z) FROM points") +@test r[1][1] == Point3D(12, 15, 18) +SQLite.droptable(db, "points") + +db2 = SQLite.SQLiteDB() +SQLite.query(db2, "CREATE TABLE tab1 (r REAL, s INT)") + +@test_throws SQLite.SQLiteException SQLite.create(db2, "tab1", [2.1 3; 3.4 8]) +# should not throw any exceptions +SQLite.create(db2, "tab1", [2.1 3; 3.4 8], ifnotexists=true) +SQLite.create(db2, "tab2", [2.1 3; 3.4 8]) + +@test_throws SQLite.SQLiteException SQLite.droptable(db2, "nonexistant") +# should not throw anything +SQLite.droptable(db2, "nonexistant", ifexists=true) +# should drop "tab2" +SQLite.droptable(db2, "tab2", ifexists=true) +@test !in("tab2", SQLite.tables(db2)[1]) + +close(db2) + +@test size(SQLite.tables(db)) == (11,1) + +close(db) +close(db) # repeatedly trying to close db diff --git a/test/runtests.jl b/test/runtests.jl index d63b485..39f05d9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,131 +1,126 @@ -using Base.Test, SQLite, Compat +reload("SQLite") +using Base.Test, Compat, DataStreams, NullableArrays import Base: +, == -a = SQLiteDB() -b = SQLiteDB(UTF16=true) -c = SQLiteDB(":memory:",UTF16=true) - -close(a) -close(b) -close(c) +a = SQLite.DB() temp = tempname() -SQLiteDB(temp) +SQLite.DB(temp) -#db = SQLiteDB("C:/Users/karbarcca/.julia/v0.4/SQLite/test/Chinook_Sqlite.sqlite") -db = SQLiteDB(joinpath(dirname(@__FILE__),"Chinook_Sqlite.sqlite")) +# db = SQLite.DB("/Users/jacobquinn/.julia/v0.4/SQLite/test/Chinook_Sqlite.sqlite") +db = SQLite.DB(joinpath(dirname(@__FILE__),"Chinook_Sqlite.sqlite")) -results = query(db,"SELECT name FROM sqlite_master WHERE type='table';") -@test length(results.colnames) == 1 -@test results.colnames[1] == "name" -@test size(results) == (11,1) +so = SQLite.Source(db,"SELECT name FROM sqlite_master WHERE type='table';") +ds = Data.Table(so) +@test length(ds.data) == 1 +@test Data.header(ds)[1] == "name" +@test size(ds) == (11,1) -results1 = tables(db) -@test results.colnames == results1.colnames -@test results.values == results1.values +results1 = SQLite.tables(db) +@test ds.schema == results1.schema +@test ds.data[1].values == results1.data[1].values -results = query(db,"SELECT * FROM Employee;") -@test length(results.colnames) == 15 +results = SQLite.query(db,"SELECT * FROM Employee;") +@test length(results.data) == 15 @test size(results) == (8,15) -@test typeof(results[1,1]) == Int64 -@test typeof(results[1,2]) <: AbstractString -@test results[1,5] == NULL +@test typeof(results[1,1]) == Nullable{Int64} +@test typeof(results[1,2]) == Nullable{UTF8String} +@test isnull(results[1,5]) -query(db,"SELECT * FROM Album;") -query(db,"SELECT a.*, b.AlbumId +SQLite.query(db,"SELECT * FROM Album;") +SQLite.query(db,"SELECT a.*, b.AlbumId FROM Artist a LEFT OUTER JOIN Album b ON b.ArtistId = a.ArtistId ORDER BY name;") -EMPTY_RESULTSET = ResultSet(["Rows Affected"],Any[Any[0]]) -SQLite.ResultSet(x) = ResultSet(["Rows Affected"],Any[Any[x]]) -r = query(db,"create table temp as select * from album") -@test r == EMPTY_RESULTSET -r = query(db,"select * from temp limit 10") -@test length(r.colnames) == 3 +r = SQLite.query(db,"create table temp as select * from album") +@test length(r.data) == 0 +r = SQLite.query(db,"select * from temp limit 10") +@test length(r.data) == 3 @test size(r) == (10,3) -@test query(db,"alter table temp add column colyear int") == EMPTY_RESULTSET -@test query(db,"update temp set colyear = 2014") == ResultSet(347) -r = query(db,"select * from temp limit 10") -@test length(r.colnames) == 4 +@test length(SQLite.query(db,"alter table temp add column colyear int").data) == 0 +@test length(SQLite.query(db,"update temp set colyear = 2014").data) == 0 +r = SQLite.query(db,"select * from temp limit 10") +@test length(r.data) == 4 @test size(r) == (10,4) -@test all(r[:,4] .== 2014) -if VERSION > v"0.4.0-" - @test query(db,"alter table temp add column dates blob") == EMPTY_RESULTSET - stmt = SQLiteStmt(db,"update temp set dates = ?") - bind(stmt,1,Date(2014,1,1)) - execute(stmt) - r = query(db,"select * from temp limit 10") - @test length(r.colnames) == 5 - @test size(r) == (10,5) - @test typeof(r[1,5]) == Date - @test all(r[:,5] .== Date(2014,1,1)) - close(stmt) -end -@test query(db,"drop table temp") == EMPTY_RESULTSET +@test all(Bool[get(x) == 2014 for x in r[:,4]]) +@test length(SQLite.query(db,"alter table temp add column dates blob").data) == 0 +stmt = SQLite.Stmt(db,"update temp set dates = ?") +SQLite.bind!(stmt,1,Date(2014,1,1)) +SQLite.execute!(stmt) +r = SQLite.query(db,"select * from temp limit 10") +@test length(r.data) == 5 +@test size(r) == (10,5) +@test typeof(r[1,5]) == Nullable{Date} +@test all(Bool[get(x) == Date(2014,1,1) for x in r[:,5]]) +@test length(SQLite.query(db,"drop table temp").data) == 0 -create(db,"temp",zeros(5,5),["col1","col2","col3","col4","col5"],[Float64 for i=1:5]) -r = query(db,"select * from temp") +dt = Data.Table(Data.Schema([Float64,Float64,Float64,Float64,Float64],5)) +sink = SQLite.Sink(dt,db) +r = SQLite.query(db,"select * from $(sink.tablename)") @test size(r) == (5,5) -@test all(r.values[1] .== 0.0) -@test all([typeof(i) for i in r.values[1]] .== Float64) -@test r.colnames == ["col1","col2","col3","col4","col5"] -@test droptable(db,"temp") == EMPTY_RESULTSET - -create(db,"temp",zeros(5,5)) -r = query(db,"select * from temp") +@test all(map(isnull,r.data)) +@test all([typeof(i[1]) for i in r.data] .== Nullable{Any}) +@test Data.header(r) == ["Column1","Column2","Column3","Column4","Column5"] +SQLite.drop!(db,"$(sink.tablename)") + +dt = Data.Table(zeros(5,5)) +sink = SQLite.Sink(dt,db) +r = SQLite.query(db,"select * from $(sink.tablename)") @test size(r) == (5,5) -@test all(r.values[1] .== 0.0) -@test all([typeof(i) for i in r.values[1]] .== Float64) -@test r.colnames == ["x1","x2","x3","x4","x5"] -@test droptable(db,"temp") == EMPTY_RESULTSET +@test all([get(i) for i in r.data[1]] .== 0.0) +@test all([eltype(i) for i in r.data[1]] .== Float64) +SQLite.drop!(db,"$(sink.tablename)") -create(db,"temp",zeros(Int,5,5)) -r = query(db,"select * from temp") +dt = Data.Table(zeros(Int,5,5)) +sink = SQLite.Sink(dt,db) +r = SQLite.query(db,"select * from $(sink.tablename)") @test size(r) == (5,5) -@test all(r.values[1] .== 0) -@test all([typeof(i) for i in r.values[1]] .== Int64) -@test r.colnames == ["x1","x2","x3","x4","x5"] -SQLite.append(db,"temp",ones(Int,5,5)) -r = query(db,"select * from temp") +@test all([get(i) for i in r.data[1]] .== 0) +@test all([eltype(i) for i in r.data[1]] .== Int) + +dt = Data.Table(ones(Int,5,5)) +Data.stream!(dt,sink) # stream to an existing Sink +r = SQLite.query(db,"select * from $(sink.tablename)") @test size(r) == (10,5) -@test r.values[1] == Any[0,0,0,0,0,1,1,1,1,1] -@test typeof(r[1,1]) == Int64 -@test r.colnames == ["x1","x2","x3","x4","x5"] -@test droptable(db,"temp") == EMPTY_RESULTSET - -if VERSION > v"0.4.0-" - rng = Date(2013):Date(2013,1,5) - create(db,"temp",[i for i = rng, j = rng]) - r = query(db,"select * from temp") - @test size(r) == (5,5) - @test all(r[:,1] .== rng) - @test all([typeof(i) for i in r.values[1]] .== Date) - @test r.colnames == ["x1","x2","x3","x4","x5"] - @test droptable(db,"temp") == EMPTY_RESULTSET -end +@test [get(i) for i in r.data[1]] == [0,0,0,0,0,1,1,1,1,1] +@test all([eltype(i) for i in r.data[1]] .== Int) +SQLite.drop!(db,"$(sink.tablename)") + +rng = Date(2013):Date(2013,1,5) +dt = Data.Table([i for i = rng, j = rng]) +sink = SQLite.Sink(dt,db) +r = SQLite.query(db,"select * from $(sink.tablename)") +@test size(r) == (5,5) +@test all([get(i) for i in r.data[1]] .== rng) +@test all([eltype(i) for i in r.data[1]] .== Date) +SQLite.drop!(db,"$(sink.tablename)") -query(db,"CREATE TABLE temp AS SELECT * FROM Album") -r = query(db, "SELECT * FROM temp LIMIT ?", [3]) +SQLite.query(db,"CREATE TABLE temp AS SELECT * FROM Album") +r = SQLite.query(db, "SELECT * FROM temp LIMIT ?", [3]) @test size(r) == (3,3) -r = query(db, "SELECT * FROM temp WHERE Title LIKE ?", ["%time%"]) -@test r.values[1] == [76, 111, 187] -query(db, "INSERT INTO temp VALUES (?1, ?3, ?2)", [0,0,"Test Album"]) -r = query(db, "SELECT * FROM temp WHERE AlbumId = 0") -@test r == ResultSet(Any["AlbumId", "Title", "ArtistId"], Any[Any[0], Any["Test Album"], Any[0]]) -droptable(db, "temp") - -binddb = SQLiteDB() -query(binddb, "CREATE TABLE temp (n NULL, i6 INT, f REAL, s TEXT, a BLOB)") -query(binddb, "INSERT INTO temp VALUES (?1, ?2, ?3, ?4, ?5)", Any[NULL, convert(Int64,6), 6.4, "some text", b"bytearray"]) -r = query(binddb, "SELECT * FROM temp") -for (v, t) in zip(r.values, [SQLite.NullType, Int64, Float64, AbstractString, Vector{UInt8}]) - @test isa(v[1], t) -end -query(binddb, "CREATE TABLE blobtest (a BLOB, b BLOB)") -query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", b"b"]) -query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", BigInt(2)]) +r = SQLite.query(db, "SELECT * FROM temp WHERE Title LIKE ?", ["%time%"]) +@test [get(i) for i in r.data[1]] == [76, 111, 187] +SQLite.query(db, "INSERT INTO temp VALUES (?1, ?3, ?2)", [0,0,"Test Album"]) +r = SQLite.query(db, "SELECT * FROM temp WHERE AlbumId = 0") +@test r[1,1] === Nullable(0) +@test get(r[1,2]) == "Test Album" +@test r[1,3] === Nullable(0) +SQLite.drop!(db, "temp") + +binddb = SQLite.DB() +SQLite.query(binddb, "CREATE TABLE temp (n NULL, i6 INT, f REAL, s TEXT, a BLOB)") +SQLite.query(binddb, "INSERT INTO temp VALUES (?1, ?2, ?3, ?4, ?5)", Any[SQLite.NULL, convert(Int64,6), 6.4, "some text", b"bytearray"]) +r = SQLite.query(binddb, "SELECT * FROM temp") +@test isa(get(r.data[1][1],SQLite.NULL),SQLite.NullType) +@test isa(get(r.data[2][1]),Int) +@test isa(get(r.data[3][1]),Float64) +@test isa(get(r.data[4][1]),AbstractString) +@test isa(get(r.data[5][1]),Vector{UInt8}) +SQLite.query(binddb, "CREATE TABLE blobtest (a BLOB, b BLOB)") +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", b"b"]) +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", BigInt(2)]) type Point{T} x::T y::T @@ -133,95 +128,94 @@ end ==(a::Point, b::Point) = a.x == b.x && a.y == b.y p1 = Point(1, 2) p2 = Point(1.3, 2.4) -query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p1]) -query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p2]) -r = query(binddb, "SELECT * FROM blobtest") -for v in r.values[1] - @test v == b"a" -end -for (v1, v2) in zip(r.values[2], Any[b"b", BigInt(2), p1, p2]) - @test v1 == v2 +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p1]) +SQLite.query(binddb, "INSERT INTO blobtest VALUES (?1, ?2)", Any[b"a", p2]) +r = SQLite.query(binddb, "SELECT * FROM blobtest";stricttypes=false) +for v in r.data[1] + @test get(v) == b"a" end -close(binddb) - -# I can't be arsed to create a new one using old dictionary syntax -if VERSION > v"0.4.0-" - query(db,"CREATE TABLE temp AS SELECT * FROM Album") - r = query(db, "SELECT * FROM temp LIMIT :a", Dict(:a => 3)) - @test size(r) == (3,3) - r = query(db, "SELECT * FROM temp WHERE Title LIKE @word", Dict(:word => "%time%")) - @test r.values[1] == [76, 111, 187] - query(db, "INSERT INTO temp VALUES (@lid, :title, \$rid)", Dict(:rid => 0, :lid => 0, :title => "Test Album")) - r = query(db, "SELECT * FROM temp WHERE AlbumId = 0") - @test r == ResultSet(Any["AlbumId", "Title", "ArtistId"], Any[Any[0], Any["Test Album"], Any[0]]) - droptable(db, "temp") +for (v1, v2) in zip(r.data[2], Any[b"b", BigInt(2), p1, p2]) + @test get(v1) == v2 end +############################################ -r = query(db, sr"SELECT LastName FROM Employee WHERE BirthDate REGEXP '^\d{4}-08'") -@test r.values[1][1] == "Peacock" +SQLite.query(db,"CREATE TABLE temp AS SELECT * FROM Album") +r = SQLite.query(db, "SELECT * FROM temp LIMIT :a", Dict(:a => 3)) +@test size(r) == (3,3) +r = SQLite.query(db, "SELECT * FROM temp WHERE Title LIKE @word", Dict(:word => "%time%")) +@test [get(i) for i in r.data[1]] == [76, 111, 187] +SQLite.query(db, "INSERT INTO temp VALUES (@lid, :title, \$rid)", Dict(:rid => 0, :lid => 0, :title => "Test Album")) +r = SQLite.query(db, "SELECT * FROM temp WHERE AlbumId = 0") +@test r[1,1] === Nullable(0) +@test get(r[1,2]) == "Test Album" +@test r[1,3] === Nullable(0) +SQLite.drop!(db, "temp") + +r = SQLite.query(db, SQLite.@sr_str("SELECT LastName FROM Employee WHERE BirthDate REGEXP '^\\d{4}-08'")) +@test get(r.data[1][1]) == "Peacock" triple(x) = 3x @test_throws AssertionError SQLite.register(db, triple, nargs=186) SQLite.register(db, triple, nargs=1) -r = query(db, "SELECT triple(Total) FROM Invoice ORDER BY InvoiceId LIMIT 5") -s = query(db, "SELECT Total FROM Invoice ORDER BY InvoiceId LIMIT 5") -for (i, j) in zip(r.values[1], s.values[1]) - @test_approx_eq i 3j +r = SQLite.query(db, "SELECT triple(Total) FROM Invoice ORDER BY InvoiceId LIMIT 5") +s = SQLite.query(db, "SELECT Total FROM Invoice ORDER BY InvoiceId LIMIT 5") +for (i, j) in zip(r.data[1], s.data[1]) + @test_approx_eq get(i) 3*get(j) end SQLite.@register db function add4(q) q+4 end -r = query(db, "SELECT add4(AlbumId) FROM Album") -s = query(db, "SELECT AlbumId FROM Album") -@test r[1] == s[1]+4 +r = SQLite.query(db, "SELECT add4(AlbumId) FROM Album") +s = SQLite.query(db, "SELECT AlbumId FROM Album") +@test get(r[1,1]) == get(s[1,1])+4 SQLite.@register db mult(args...) = *(args...) -r = query(db, "SELECT Milliseconds, Bytes FROM Track") -s = query(db, "SELECT mult(Milliseconds, Bytes) FROM Track") -@test r[1].*r[2] == s[1] -t = query(db, "SELECT mult(Milliseconds, Bytes, 3, 4) FROM Track") -@test r[1].*r[2]*3*4 == t[1] +r = SQLite.query(db, "SELECT Milliseconds, Bytes FROM Track") +s = SQLite.query(db, "SELECT mult(Milliseconds, Bytes) FROM Track") +@test (get(r[1,1]) * get(r[1,2])) == get(s[1,1]) +t = SQLite.query(db, "SELECT mult(Milliseconds, Bytes, 3, 4) FROM Track") +@test (get(r[1,1]) * get(r[1,2]) * 3 * 4) == get(t[1,1]) SQLite.@register db sin -u = query(db, "select sin(milliseconds) from track limit 5") -@test all(-1 .< u[1] .< 1) +u = SQLite.query(db, "select sin(milliseconds) from track limit 5") +@test all(-1 .< convert(Vector{Float64},u[:,1]) .< 1) SQLite.register(db, hypot; nargs=2, name="hypotenuse") -v = query(db, "select hypotenuse(Milliseconds,bytes) from track limit 5") -@test [@compat round(Int,i) for i in v[1]] == [11175621,5521062,3997652,4339106,6301714] +v = SQLite.query(db, "select hypotenuse(Milliseconds,bytes) from track limit 5") +@test [@compat round(Int,get(i)) for i in v.data[1]] == [11175621,5521062,3997652,4339106,6301714] SQLite.@register db str2arr(s) = convert(Array{UInt8}, s) -r = query(db, "SELECT str2arr(LastName) FROM Employee LIMIT 2") -@test r[1] == Any[UInt8[0x41,0x64,0x61,0x6d,0x73],UInt8[0x45,0x64,0x77,0x61,0x72,0x64,0x73]] +r = SQLite.query(db, "SELECT str2arr(LastName) FROM Employee LIMIT 2") +@test [get(i) for i in r.data[1]] == Any[UInt8[0x41,0x64,0x61,0x6d,0x73],UInt8[0x45,0x64,0x77,0x61,0x72,0x64,0x73]] SQLite.@register db big -r = query(db, "SELECT big(5)") -@test r[1][1] == big(5) +r = SQLite.query(db, "SELECT big(5)") +@test get(r[1,1]) == big(5) doublesum_step(persist, current) = persist + current doublesum_final(persist) = 2 * persist -register(db, 0, doublesum_step, doublesum_final, name="doublesum") -r = query(db, "SELECT doublesum(UnitPrice) FROM Track") -s = query(db, "SELECT UnitPrice FROM Track") -@test_approx_eq r[1][1] 2*sum(s[1]) +SQLite.register(db, 0, doublesum_step, doublesum_final, name="doublesum") +r = SQLite.query(db, "SELECT doublesum(UnitPrice) FROM Track") +s = SQLite.query(db, "SELECT UnitPrice FROM Track") +@test_approx_eq get(r[1,1]) 2*sum(convert(Vector{Float64},s.data[1])) mycount(p, c) = p + 1 -register(db, 0, mycount) -r = query(db, "SELECT mycount(TrackId) FROM PlaylistTrack") -s = query(db, "SELECT count(TrackId) FROM PlaylistTrack") -@test r[1] == s[1] +SQLite.register(db, 0, mycount) +r = SQLite.query(db, "SELECT mycount(TrackId) FROM PlaylistTrack") +s = SQLite.query(db, "SELECT count(TrackId) FROM PlaylistTrack") +@test get(r[1,1]) == get(s[1,1]) bigsum(p, c) = p + big(c) -register(db, big(0), bigsum) -r = query(db, "SELECT bigsum(TrackId) FROM PlaylistTrack") -s = query(db, "SELECT TrackId FROM PlaylistTrack") -@test r[1][1] == big(sum(s[1])) - -query(db, "CREATE TABLE points (x INT, y INT, z INT)") -query(db, "INSERT INTO points VALUES (?, ?, ?)", [1, 2, 3]) -query(db, "INSERT INTO points VALUES (?, ?, ?)", [4, 5, 6]) -query(db, "INSERT INTO points VALUES (?, ?, ?)", [7, 8, 9]) +SQLite.register(db, big(0), bigsum) +r = SQLite.query(db, "SELECT bigsum(TrackId) FROM PlaylistTrack") +s = SQLite.query(db, "SELECT TrackId FROM PlaylistTrack") +@test get(r[1,1]) == big(sum(convert(Vector{Int},s.data[1]))) + +SQLite.query(db, "CREATE TABLE points (x INT, y INT, z INT)") +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [1, 2, 3]) +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [4, 5, 6]) +SQLite.query(db, "INSERT INTO points VALUES (?, ?, ?)", [7, 8, 9]) type Point3D{T<:Number} x::T y::T @@ -230,29 +224,48 @@ end ==(a::Point3D, b::Point3D) = a.x == b.x && a.y == b.y && a.z == b.z +(a::Point3D, b::Point3D) = Point3D(a.x + b.x, a.y + b.y, a.z + b.z) sumpoint(p::Point3D, x, y, z) = p + Point3D(x, y, z) -register(db, Point3D(0, 0, 0), sumpoint) -r = query(db, "SELECT sumpoint(x, y, z) FROM points") -@test r[1][1] == Point3D(12, 15, 18) -droptable(db, "points") +SQLite.register(db, Point3D(0, 0, 0), sumpoint) +r = SQLite.query(db, "SELECT sumpoint(x, y, z) FROM points") +@test get(r[1,1]) == Point3D(12, 15, 18) +SQLite.drop!(db, "points") -db2 = SQLiteDB() -query(db2, "CREATE TABLE tab1 (r REAL, s INT)") +db2 = SQLite.DB() +SQLite.query(db2, "CREATE TABLE tab1 (r REAL, s INT)") -@test_throws SQLite.SQLiteException create(db2, "tab1", [2.1 3; 3.4 8]) -# should not throw any exceptions -create(db2, "tab1", [2.1 3; 3.4 8], ifnotexists=true) -create(db2, "tab2", [2.1 3; 3.4 8]) - -@test_throws SQLite.SQLiteException droptable(db2, "nonexistant") +@test_throws SQLite.SQLiteException SQLite.drop!(db2, "nonexistant") # should not throw anything -droptable(db2, "nonexistant", ifexists=true) +SQLite.drop!(db2, "nonexistant", ifexists=true) # should drop "tab2" -droptable(db2, "tab2", ifexists=true) -@test !in("tab2", tables(db2)[1]) - -close(db2) +SQLite.drop!(db2, "tab2", ifexists=true) +@test !in("tab2", SQLite.tables(db2).data[1]) -@test size(tables(db)) == (11,1) +SQLite.drop!(db, "sqlite_stat1") +@test size(SQLite.tables(db)) == (11,1) -close(db) -close(db) # repeatedly trying to close db +source = SQLite.Source(db,"select * from album") +temp = tempname() +sink = CSV.Sink(temp) +Data.stream!(source,sink) +dt = Data.Table(CSV.Source(sink)) +@test get(dt[1,1]) == 1 +@test get(dt[1,2]) == "For Those About To Rock We Salute You" +@test get(dt[1,3]) == 1 + +db = nothing; gc(); gc(); + +db = SQLite.DB() +source = CSV.Source(temp) +sink = SQLite.Sink(source,db,"temp") +source2 = SQLite.Source(sink) +dt = Data.Table(source2) +@test get(dt[1,1]) == 1 +@test string(get(dt[1,2])) == "For Those About To Rock We Salute You" +@test get(dt[1,3]) == 1 + +sink = SQLite.Sink(db, "temp2", Data.schema(dt)) +Data.stream!(dt,sink) +source3 = SQLite.Source(sink) +dt = Data.Table(source3) +@test get(dt[1,1]) == 1 +@test string(get(dt[1,2])) == "For Those About To Rock We Salute You" +@test get(dt[1,3]) == 1