|
| 1 | +type Source <: Data.Source # <: IO |
| 2 | + schema::Data.Schema |
| 3 | + stmt::Stmt |
| 4 | + status::Cint |
| 5 | +end |
| 6 | + |
| 7 | +function Source(db::DB,sql::AbstractString, values=[];rows::Int=0,stricttypes::Bool=true) |
| 8 | + stmt = SQLite.Stmt(db,sql) |
| 9 | + bind!(stmt, values) |
| 10 | + status = SQLite.execute!(stmt) |
| 11 | + cols = SQLite.sqlite3_column_count(stmt.handle) |
| 12 | + header = Array(UTF8String,cols) |
| 13 | + types = Array(DataType,cols) |
| 14 | + for i = 1:cols |
| 15 | + header[i] = bytestring(SQLite.sqlite3_column_name(stmt.handle,i-1)) |
| 16 | + # do better column type inference; query what the column was created for? |
| 17 | + types[i] = stricttypes ? SQLite.juliatype(stmt.handle,i) : Any |
| 18 | + end |
| 19 | + # rows == -1 && count(*)? |
| 20 | + return SQLite.Source(Data.Schema(header,types,rows),stmt,status) |
| 21 | +end |
| 22 | + |
| 23 | +function Base.eof(s::Source) |
| 24 | + (s.status == SQLITE_DONE || s.status == SQLITE_ROW) || sqliteerror(s.stmt.db) |
| 25 | + return s.status == SQLITE_DONE |
| 26 | +end |
| 27 | + |
| 28 | +function Base.readline(s::Source,delim::Char=',',buf::IOBuffer=IOBuffer()) |
| 29 | + eof(s) && return "" |
| 30 | + cols = s.schema.cols |
| 31 | + for i = 1:cols |
| 32 | + val = sqlite3_column_text(s.stmt.handle,i-1) |
| 33 | + val != C_NULL && write(buf,bytestring(val)) |
| 34 | + write(buf,ifelse(i == cols,'\n',delim)) |
| 35 | + end |
| 36 | + s.status = sqlite3_step(s.stmt.handle) |
| 37 | + return takebuf_string(buf) |
| 38 | +end |
| 39 | + |
| 40 | +function readsplitline(s::Source) |
| 41 | + eof(s) && return UTF8String[] |
| 42 | + cols = s.schema.cols |
| 43 | + vals = Array(UTF8String, cols) |
| 44 | + for i = 1:cols |
| 45 | + val = sqlite3_column_text(s.stmt.handle,i-1) |
| 46 | + valsl[i] = val == C_NULL ? "" : bytestring(val) |
| 47 | + end |
| 48 | + s.status = sqlite3_step(s.stmt.handle) |
| 49 | + return vals |
| 50 | +end |
| 51 | + |
| 52 | +reset!(io::SQLite.Source) = (sqlite3_reset(io.stmt.handle); execute!(io.stmt)) |
| 53 | + |
| 54 | +sqlitetypecode{T<:Integer}(::Type{T}) = SQLITE_INTEGER |
| 55 | +sqlitetypecode{T<:AbstractFloat}(::Type{T}) = SQLITE_FLOAT |
| 56 | +sqlitetypecode{T<:AbstractString}(::Type{T}) = SQLITE_TEXT |
| 57 | +sqlitetypecode(::Type{BigInt}) = SQLITE_BLOB |
| 58 | +sqlitetypecode(::Type{BigFloat}) = SQLITE_BLOB |
| 59 | +sqlitetypecode(x) = SQLITE_BLOB |
| 60 | +function juliatype(handle,col) |
| 61 | + x = SQLite.sqlite3_column_type(handle,col-1) |
| 62 | + if x == SQLITE_BLOB |
| 63 | + val = sqlitevalue(Any,handle,col) |
| 64 | + return typeof(val) |
| 65 | + else |
| 66 | + return juliatype(x) |
| 67 | + end |
| 68 | +end |
| 69 | +juliatype(x) = x == SQLITE_INTEGER ? Int : x == SQLITE_FLOAT ? Float64 : x == SQLITE_TEXT ? UTF8String : Any |
| 70 | + |
| 71 | +sqlitevalue{T<:Integer}(::Type{T},handle,col) = sqlite3_column_int64(handle,col-1) |
| 72 | +sqlitevalue{T<:AbstractFloat}(::Type{T},handle,col) = sqlite3_column_double(handle,col-1) |
| 73 | +#TODO: test returning a PointerString instead of calling `bytestring` |
| 74 | +sqlitevalue{T<:AbstractString}(::Type{T},handle,col) = convert(T,bytestring(sqlite3_column_text(handle,col-1))) |
| 75 | +sqlitevalue(::Type{PointerString},handle,col) = bytestring(sqlite3_column_text(handle,col-1)) |
| 76 | +sqlitevalue(::Type{BigInt},handle,col) = sqlitevalue(Any,handle,col) |
| 77 | +sqlitevalue(::Type{BigFloat},handle,col) = sqlitevalue(Any,handle,col) |
| 78 | +function sqlitevalue{T}(::Type{T},handle,col) |
| 79 | + blob = convert(Ptr{UInt8},SQLite.sqlite3_column_blob(handle,col-1)) |
| 80 | + b = SQLite.sqlite3_column_bytes(handle,col-1) |
| 81 | + buf = zeros(UInt8,b) # global const? |
| 82 | + unsafe_copy!(pointer(buf), blob, b) |
| 83 | + r = SQLite.sqldeserialize(buf)::T |
| 84 | + return r |
| 85 | +end |
| 86 | + |
| 87 | +function getfield{T}(source::SQLite.Source, ::Type{T}, row, col) |
| 88 | + handle = source.stmt.handle |
| 89 | + t = sqlite3_column_type(handle,col-1) |
| 90 | + if t == SQLite.SQLITE_NULL |
| 91 | + val = Nullable{T}() |
| 92 | + elseif t == SQLite.sqlitetypecode(T) |
| 93 | + val = Nullable(sqlitevalue(T,handle,col)) |
| 94 | + elseif T === Any |
| 95 | + val = Nullable(sqlitevalue(juliatype(t),handle,col)) |
| 96 | + else |
| 97 | + throw(SQLiteException("strict type error trying to retrieve type `$T` on row: $(row+1), col: $col; SQLite reports a type of $(sqlitetypecode(T))")) |
| 98 | + end |
| 99 | + col == source.schema.cols && (source.status = sqlite3_step(handle)) |
| 100 | + return val |
| 101 | +end |
| 102 | + |
| 103 | +function getfield!{T}(source::SQLite.Source, dest::NullableVector{T}, ::Type{T}, row, col) |
| 104 | + @inbounds dest[row] = SQLite.getfield(source, T, row, col) |
| 105 | + return |
| 106 | +end |
| 107 | +function pushfield!{T}(source::SQLite.Source, dest::NullableVector{T}, ::Type{T}, row, col) |
| 108 | + push!(dest, SQLite.getfield(source, T, row, col)) |
| 109 | + return |
| 110 | +end |
| 111 | + |
| 112 | +function Data.stream!(source::SQLite.Source,sink::Data.Table) |
| 113 | + rows, cols = size(source) |
| 114 | + types = Data.types(source) |
| 115 | + if rows == 0 |
| 116 | + row = 0 |
| 117 | + while !eof(source) |
| 118 | + for col = 1:cols |
| 119 | + @inbounds T = types[col] |
| 120 | + SQLite.pushfield!(source, Data.unsafe_column(sink,col,T), T, row, col) # row + datarow |
| 121 | + end |
| 122 | + row += 1 |
| 123 | + end |
| 124 | + source.schema.rows = row |
| 125 | + else |
| 126 | + for row = 1:rows, col = 1:cols |
| 127 | + @inbounds T = types[col] |
| 128 | + SQLite.getfield!(source, Data.unsafe_column(sink,col,T), T, row, col) # row + datarow |
| 129 | + end |
| 130 | + end |
| 131 | + sink.schema = source.schema |
| 132 | + return sink |
| 133 | +end |
| 134 | +# creates a new DataTable according to `source` schema and streams `Source` data into it |
| 135 | +function Data.Table(source::SQLite.Source) |
| 136 | + sink = Data.Table(source.schema) |
| 137 | + return Data.stream!(source,sink) |
| 138 | +end |
| 139 | + |
| 140 | +function Data.stream!(source::SQLite.Source,sink::CSV.Sink;header::Bool=true) |
| 141 | + header && CSV.writeheaders(source,sink) |
| 142 | + rows, cols = size(source) |
| 143 | + types = Data.types(source) |
| 144 | + row = 0 |
| 145 | + while !eof(source) |
| 146 | + for col = 1:cols |
| 147 | + val = SQLite.getfield(source, types[col], row, col) |
| 148 | + CSV.writefield(sink, isnull(val) ? sink.null : get(val), col, cols) |
| 149 | + end |
| 150 | + row += 1 |
| 151 | + end |
| 152 | + source.schema.rows = row |
| 153 | + sink.schema = source.schema |
| 154 | + close(sink) |
| 155 | + return sink |
| 156 | +end |
| 157 | + |
| 158 | +function query(db::DB,sql::AbstractString, values=[];rows::Int=0,stricttypes::Bool=true) |
| 159 | + so = Source(db,sql,values;rows=rows,stricttypes=stricttypes) |
| 160 | + return Data.Table(so) |
| 161 | +end |
| 162 | + |
| 163 | +tables(db::DB) = query(db,"SELECT name FROM sqlite_master WHERE type='table';") |
| 164 | +indices(db::DB) = query(db,"SELECT name FROM sqlite_master WHERE type='index';") |
| 165 | +columns(db::DB,table::AbstractString) = query(db,"pragma table_info($table)") |
0 commit comments