diff --git a/docs/src/index.md b/docs/src/index.md index a86e333..f0f8eaf 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -35,16 +35,26 @@ The ODBC.jl package ships an experimental REPL mode for convenience in rapid que Methods: -`ODBC.query(dsn::ODBC.DSN, sql::AbstractString, sink=DataFrame, args...; weakrefstrings::Bool=true, append::Bool=false)` - -`ODBC.query{T}(dsn::DSN, sql::AbstractString, sink::T; weakrefstrings::Bool=true, append::Bool=false)` - -`ODBC.query(source::ODBC.Source, sink=DataFrame, args...; append::Bool=false)` - -`ODBC.query{T}(source::ODBC.Source, sink::T; append::Bool=false)` - - -`ODBC.query` is a high-level method for sending an SQL statement to a system and returning the results. As is shown, a valid `dsn::ODBC.DSN` and SQL statement `sql` combo can be sent, as well as an already-constructed `source::ODBC.Source`. By default, the results will be returned in a [`DataFrame`](http://juliastats.github.io/DataFrames.jl/latest/), but a variety of options exist for returning results, including `CSV.Sink`, `SQLite.Sink`, or `Feather.Sink`. `ODBC.query` actually utilizes the `DataStreams.jl` framework, so any valid [`Data.Sink`](http://juliadata.github.io/DataStreams.jl/latest/#datasink-interface) can be used to return results. The `append=false` keyword specifies whether the results should be *added to* any existing data in the `Data.Sink`, or if the resultset should fully replace any existing data. The `weakrefstrings` argument indicates whether `WeakRefString`s should be used by default for efficiency. +`ODBC.query(dsn::ODBC.DSN, sql::AbstractString, sink::Any=DataFrame, args...; weakrefstrings::Bool=true, append::Bool=false)` + +`ODBC.query(statement::Statement, sink::Any=DataFrame, args...; weakrefstrings::Bool=true, append::Bool=false)` + +`ODBC.query(source::ODBC.Source, sink::Any=DataFrame, args...; append::Bool=false)` + +`ODBC.query` is a high-level method for sending an SQL statement to a system and +returning the results. As is shown, a valid `dsn::ODBC.DSN` and SQL statement +`sql` combo can be sent, or a prepared `statement::ODBC.Statement`, as well as +an already-constructed `source::ODBC.Source`. By default, the results will be +returned in a [`DataFrame`](http://juliastats.github.io/DataFrames.jl/latest/), +but a variety of options exist for returning results, including `CSV.Sink`, +`SQLite.Sink`, or `Feather.Sink`. `ODBC.query` actually utilizes the +`DataStreams.jl` framework, so any +valid +[`Data.Sink`](http://juliadata.github.io/DataStreams.jl/latest/#datasink-interface) can +be used to return results. The `append=false` keyword specifies whether the +results should be *added to* any existing data in the `Data.Sink`, or if the +resultset should fully replace any existing data. The `weakrefstrings` argument +indicates whether `WeakRefString`s should be used by default for efficiency. Examples: @@ -54,6 +64,10 @@ dsn = ODBC.DSN(valid_dsn) # return result as a DataFrame df = ODBC.query(dsn, "select * from cool_table") +# use a prepared statement instead +statement = ODBC.prepare(dsn, "select * from cool_table") +df2 = ODBC.query(statement) + # return result as a csv file using CSV csv = ODBC.query(dsn, "select * from cool_table", CSV.Sink, "cool_table.csv") @@ -124,7 +138,14 @@ Methods: `ODBC.prepare(dsn::ODBC.DSN, querystring::String) => ODBC.Statement` -Prepare an SQL statement `querystring` against the DB and return it as an `ODBC.Statement`. This `ODBC.Statement` can then be executed once, or repeatedly in a more efficient manner than `ODBC.execute!(dsn, querystring)`. Prepared statements can also support parameter place-holders that can be filled in dynamically before executing; this is a common strategy for bulk-loading data or other statements that need to be bulk-executed with changing simple parameters before each execution. Consult your DB/vendor-specific SQL syntax for the exact specifications for parameters. +Prepare an SQL statement `querystring` against the DB and return it as an +`ODBC.Statement`. This `ODBC.Statement` can then be executed once, or repeatedly +in a more efficient manner than `ODBC.execute!(dsn, querystring)` or +`ODBC.query(dsn, querystring)`. Prepared statements can also support parameter +place-holders that can be filled in dynamically before executing; this is a +common strategy for bulk-loading data or other statements that need to be +bulk-executed with changing simple parameters before each execution. Consult +your DB/vendor-specific SQL syntax for the exact specifications for parameters. Examples: @@ -139,6 +160,17 @@ for row = 1:size(df, 1) # each time we execute the `stmt`, we pass another row to be bound to the parameters ODBC.execute!(stmt, [df[row, x] for x = 1:size(df, 2)]) end + +# prepare a statement and execute it, getting new ids back +ODBC.execute!(dsn, "CREATE TABLE sailors (id SERIAL PRIMARY KEY, name VARCHAR)") + +id_stmt = ODBC.prepare(dsn, "INSERT INTO sailors (name) VALUES (?) RETURNING id") + +sailors = ["Black Beard", "James Cook", "Ferdinand Magellan"] +sailor_ids = similar(sailors, Int) +for (i, sailor) in enumerate(sailors) + sailor_ids[i] = get(ODBC.query(id_stmt, [sailor])[1, 1]) +end ``` @@ -162,4 +194,14 @@ Constructors: `ODBC.Source(dsn::ODBC.DSN, querystring::String) => ODBC.Source` -`ODBC.Source` is an implementation of a `Data.Source` in the [DataStreams.jl](http://juliadata.github.io/DataStreams.jl/latest/#datasource-interface) framework. It takes a valid DB connection `dsn` and executes a properly formatted SQL query string `querystring` and makes preparations for returning a resultset. +`ODBC.Source(statement::Statement) => ODBC.Source` + +`ODBC.Source(statement::Statement, values) => ODBC.Source` + +`ODBC.Source` is an implementation of a `Data.Source` in +the +[DataStreams.jl](http://juliadata.github.io/DataStreams.jl/latest/#datasource-interface) framework. +It takes a valid DB connection `dsn` and executes a properly formatted SQL query +string `querystring` and makes preparations for returning a resultset. +Alternatively, it can take a prepared statement and optional values and prepares +to return a resultset. diff --git a/src/ODBC.jl b/src/ODBC.jl index 72e3372..921cbfe 100644 --- a/src/ODBC.jl +++ b/src/ODBC.jl @@ -150,6 +150,7 @@ end type Source <: Data.Source schema::Data.Schema dsn::DSN + stmt::Ptr{Void} query::String columns::Vector{Any} status::Int @@ -182,4 +183,4 @@ end end #ODBC module include("sqlreplmode.jl") -toggle_sql_repl() \ No newline at end of file +toggle_sql_repl() diff --git a/src/Source.jl b/src/Source.jl index 6ababb5..d8cfc38 100644 --- a/src/Source.jl +++ b/src/Source.jl @@ -20,7 +20,19 @@ function ODBCDriverConnect!(dbc::Ptr{Void}, conn_string, prompt::Bool) end out_conn = Block(ODBC.API.SQLWCHAR, BUFLEN) out_buff = Ref{Int16}() - @CHECK dbc ODBC.API.SQL_HANDLE_DBC ODBC.API.SQLDriverConnect(dbc, window_handle, conn_string, out_conn.ptr, BUFLEN, out_buff, driver_prompt) + @CHECK( + dbc, + ODBC.API.SQL_HANDLE_DBC, + ODBC.API.SQLDriverConnect( + dbc, + window_handle, + conn_string, + out_conn.ptr, + BUFLEN, + out_buff, + driver_prompt + ) + ) connection_string = string(out_conn, out_buff[]) return connection_string end @@ -35,7 +47,7 @@ end isnull{T}(x::Nullable{T}) = Base.isnull(x) isnull(x) = false -cast{T}(x::T) = x +cast(x::Any) = x cast(x::Date) = ODBC.API.SQLDate(x) cast(x::DateTime) = ODBC.API.SQLTimestamp(x) cast(x::String) = WeakRefString(pointer(Vector{UInt8}(x)), length(x)) @@ -60,57 +72,80 @@ clength{T}(x::Nullable{T}) = isnull(x) ? ODBC.API.SQL_NULL_DATA : clength(get(x) digits(x) = 0 digits(x::ODBC.API.SQLTimestamp) = length(string(x.fraction * 1000000)) -function execute!(statement::Statement, values) - stmt = statement.stmt +function bind_vals!(stmt::Ptr{Void}, values::Union{Array, Tuple}) values2 = Any[cast(x) for x in values] pointers = Ptr[] types = map(typeof, values2) for (i, v) in enumerate(values2) if isnull(v) - ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLBindParameter(stmt, i, ODBC.API.SQL_PARAM_INPUT, - ODBC.API.SQL_C_CHAR, ODBC.API.SQL_CHAR, 0, 0, C_NULL, 0, Ref(ODBC.API.SQL_NULL_DATA)) + ODBC.@CHECK( + stmt, + ODBC.API.SQL_HANDLE_STMT, + ODBC.API.SQLBindParameter( + stmt, + i, + ODBC.API.SQL_PARAM_INPUT, + ODBC.API.SQL_C_CHAR, + ODBC.API.SQL_CHAR, + 0, + 0, + C_NULL, + 0, + Ref(ODBC.API.SQL_NULL_DATA) + ) + ) else ctype, sqltype = ODBC.API.julia2C[types[i]], ODBC.API.julia2SQL[types[i]] csize, len, dgts = sqllength(v), clength(v), digits(v) ptr = getpointer(types[i], values2, i) # println("ctype: $ctype, sqltype: $sqltype, digits: $dgts, len: $len, csize: $csize") push!(pointers, ptr) - ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLBindParameter(stmt, i, ODBC.API.SQL_PARAM_INPUT, - ctype, sqltype, csize, dgts, ptr, len, Ref(len)) + ODBC.@CHECK( + stmt, + ODBC.API.SQL_HANDLE_STMT, + ODBC.API.SQLBindParameter( + stmt, + i, + ODBC.API.SQL_PARAM_INPUT, + ctype, + sqltype, + csize, + dgts, + ptr, + len, + Ref(len) + ) + ) end end +end + +function execute!(statement::Statement, values::Union{Array, Tuple}) + bind_vals!(statement.stmt, values) execute!(statement) - return end function execute!(statement::Statement) - stmt = statement.stmt - ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecute(stmt) - return + @CHECK statement.stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecute(statement.stmt) end "`ODBC.execute!` is a minimal method for just executing an SQL `query` string. No results are checked for or returned." -function execute!(dsn::DSN, query::AbstractString, stmt=dsn.stmt_ptr) +function execute!(dsn::DSN, sql::AbstractString, stmt::Ptr{Void}=dsn.stmt_ptr) ODBC.ODBCFreeStmt!(stmt) - ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecDirect(stmt, query) + @CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecDirect(stmt, sql) return end -""" -`ODBC.Source` constructs a valid `Data.Source` type that executes an SQL `query` string for the `dsn` ODBC DSN. -Results are checked for and an `ODBC.ResultBlock` is allocated to prepare for fetching the resultset. -""" -function Source(dsn::DSN, query::AbstractString; weakrefstrings::Bool=true, noquery::Bool=false) - stmt = dsn.stmt_ptr - noquery || ODBC.ODBCFreeStmt!(stmt) - noquery || (ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecDirect(stmt, query)) +inner_eltype{T}(::Type{NullableVector{T}}) = T + +function build_source(stmt::Ptr{Void}, dsn::DSN, sql::AbstractString; weakrefstrings::Bool=true) rows, cols = Ref{Int}(), Ref{Int16}() ODBC.API.SQLNumResultCols(stmt, cols) ODBC.API.SQLRowCount(stmt, rows) rows, cols = rows[], cols[] #Allocate arrays to hold each column's metadata cnames = Array{String}(cols) - ctypes, csizes = Array{ODBC.API.SQLSMALLINT}(cols), Array{ODBC.API.SQLULEN}(cols) + sqltypes, csizes = Array{ODBC.API.SQLSMALLINT}(cols), Array{ODBC.API.SQLULEN}(cols) cdigits, cnulls = Array{ODBC.API.SQLSMALLINT}(cols), Array{ODBC.API.SQLSMALLINT}(cols) juliatypes = Array{DataType}(cols) alloctypes = Array{DataType}(cols) @@ -124,12 +159,14 @@ function Source(dsn::DSN, query::AbstractString; weakrefstrings::Bool=true, noqu ODBC.API.SQLDescribeCol(stmt, x, cname.ptr, ODBC.BUFLEN, len, dt, csize, digits, null) cnames[x] = string(cname, len[]) t = dt[] - ctypes[x], csizes[x], cdigits[x], cnulls[x] = t, csize[], digits[], null[] + sqltypes[x], csizes[x], cdigits[x], cnulls[x] = t, csize[], digits[], null[] alloctypes[x], juliatypes[x], longtexts[x] = ODBC.API.SQL2Julia[t] longtext |= longtexts[x] end if !weakrefstrings - juliatypes = DataType[eltype(eltype(i)) <: WeakRefString ? NullableVector{String} : i for i in juliatypes] + juliatypes = DataType[ + inner_eltype(T) <: WeakRefString ? NullableVector{String} : T for T in juliatypes + ] end # Determine fetch strategy # rows might be -1 (dbms doesn't return total rows in resultset), 0 (empty resultset), or 1+ @@ -151,26 +188,86 @@ function Source(dsn::DSN, query::AbstractString; weakrefstrings::Bool=true, noqu else boundcols[x], elsize = allocate(alloctypes[x], rowset, csizes[x]) indcols[x] = Array{ODBC.API.SQLLEN}(rowset) - ODBC.API.SQLBindCols(stmt, x, ODBC.API.SQL2C[ctypes[x]], pointer(boundcols[x]), elsize, indcols[x]) + ODBC.API.SQLBindCols( + stmt, + x, + ODBC.API.SQL2C[sqltypes[x]], + pointer(boundcols[x]), + elsize, + indcols[x] + ) end end - schema = Data.Schema(cnames, juliatypes, rows, - Dict("types"=>[ODBC.API.SQL_TYPES[c] for c in ctypes], "sizes"=>csizes, "digits"=>cdigits, "nulls"=>cnulls)) + schema = Data.Schema( + cnames, + juliatypes, + rows, + Dict( + "types"=>[ODBC.API.SQL_TYPES[c] for c in sqltypes], + "sizes"=>csizes, + "digits"=>cdigits, + "nulls"=>cnulls + ) + ) rowsfetched = Ref{ODBC.API.SQLLEN}() # will be populated by call to SQLFetchScroll ODBC.API.SQLSetStmtAttr(stmt, ODBC.API.SQL_ATTR_ROWS_FETCHED_PTR, rowsfetched, ODBC.API.SQL_NTS) - types = [ODBC.API.SQL2C[ctypes[x]] for x = 1:cols] - source = ODBC.Source(schema, dsn, query, columns, 100, rowsfetched, 0, boundcols, indcols, csizes, types, [longtexts[x] ? ODBC.API.Long{eltype(eltype(T))} : eltype(eltype(T)) for (x, T) in enumerate(juliatypes)]) + ctypes = [ODBC.API.SQL2C[sqltypes[x]] for x = 1:cols] + jl_longtypes = similar(juliatypes) + for (i, T) in enumerate(juliatypes) + innertype = inner_eltype(T) + jl_longtypes[i] = longtexts[i] ? ODBC.API.Long{innertype} : innertype + end + source = ODBC.Source( + schema, + dsn, + stmt, + sql, + columns, + 100, + rowsfetched, + 0, + boundcols, + indcols, + csizes, + ctypes, + jl_longtypes + ) rows != 0 && fetch!(source) return source end +""" +`ODBC.Source` constructs a valid `Data.Source` type that executes an SQL `query` string for the `dsn` ODBC DSN. +Results are checked for and an `ODBC.ResultBlock` is allocated to prepare for fetching the resultset. +""" +function Source(dsn::DSN, sql::AbstractString; weakrefstrings::Bool=true, noquery::Bool=false) + stmt = dsn.stmt_ptr + noquery || ODBC.ODBCFreeStmt!(stmt) + noquery || (ODBC.@CHECK stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecDirect(stmt, sql)) + return build_source(stmt, dsn, sql; weakrefstrings=weakrefstrings) +end + +function Source(statement::Statement, freestmt::Bool=true; weakrefstrings::Bool=true, noquery::Bool=false) + if ! noquery + freestmt && ODBC.ODBCFreeStmt!(statement.stmt) + ODBC.@CHECK statement.stmt ODBC.API.SQL_HANDLE_STMT ODBC.API.SQLExecute(statement.stmt) + end + return build_source(statement.stmt, statement.dsn, statement.query; weakrefstrings=weakrefstrings) +end + +function Source(statement::Statement, values::Union{Array, Tuple}; weakrefstrings::Bool=true, noquery::Bool=false) + noquery || ODBC.ODBCFreeStmt!(statement.stmt) + noquery || bind_vals!(statement.stmt, values) + return Source(statement, false; weakrefstrings=weakrefstrings, noquery=noquery) +end + # primitive types allocate{T}(::Type{T}, rowset, size) = Vector{T}(rowset), sizeof(T) # string/binary types allocate{T<:Union{UInt8,UInt16,UInt32}}(::Type{T}, rowset, size) = zeros(T, rowset * (size + 1)), sizeof(T) * (size + 1) -function fetch!(source) - stmt = source.dsn.stmt_ptr +function fetch!(source::ODBC.Source) + stmt = source.stmt source.status = ODBC.API.SQLFetchScroll(stmt, ODBC.API.SQL_FETCH_NEXT, 0) source.rowsfetched[] == 0 && return types = source.jltypes @@ -188,7 +285,7 @@ function booleanize!(ind::Vector{ODBC.API.SQLLEN}, new::Vector{Bool}, len) end # primitive types -function cast!{T}(::Type{T}, source, col) +function cast!{T}(::Type{T}, source::ODBC.Source, col::Integer) len = source.rowsfetched[] if Data.isdone(source) isnull = Vector{Bool}(len) @@ -207,9 +304,9 @@ end using DecFP const DECZERO = Dec64(0) -cast(::Type{Dec64}, arr, cur, ind) = ind <= 0 ? DECZERO : parse(Dec64, String(unsafe_wrap(Array, pointer(arr, cur), ind))) +cast(::Type{Dec64}, arr::Array, cur::Integer, ind::Integer) = ind <= 0 ? DECZERO : parse(Dec64, String(unsafe_wrap(Array, pointer(arr, cur), ind))) -function cast!(::Type{Dec64}, source, col) +function cast!(::Type{Dec64}, source::Source, col::Integer) len = source.rowsfetched[] values = Vector{Dec64}(len) isnull = Vector{Bool}(len) @@ -225,9 +322,9 @@ function cast!(::Type{Dec64}, source, col) return end -cast(::Type{Vector{UInt8}}, arr, cur, ind) = arr[cur:(cur + max(ind, 0) - 1)] +cast(::Type{Vector{UInt8}}, arr::Array, cur::Integer, ind::Integer) = arr[cur:(cur + max(ind, 0) - 1)] -function cast!(::Type{Vector{UInt8}}, source, col) +function cast!(::Type{Vector{UInt8}}, source::Source, col::Integer) len = source.rowsfetched[] values = Vector{Vector{UInt8}}(len) isnull = Vector{Bool}(len) @@ -251,7 +348,7 @@ codeunits2bytes(::Type{UInt8}, bytes) = ifelse(bytes == ODBC.API.SQL_NULL_DATA, codeunits2bytes(::Type{UInt16}, bytes) = ifelse(bytes == ODBC.API.SQL_NULL_DATA, 0, Int(bytes * 2)) codeunits2bytes(::Type{UInt32}, bytes) = ifelse(bytes == ODBC.API.SQL_NULL_DATA, 0, Int(bytes * 4)) -function cast!(::Type{String}, source, col) +function cast!(::Type{String}, source::Source, col::Integer) len = source.rowsfetched[] data = source.boundcols[col] T = eltype(data) @@ -270,7 +367,7 @@ function cast!(::Type{String}, source, col) return end -function cast!{T}(::Type{WeakRefString{T}}, source, col) +function cast!{T}(::Type{WeakRefString{T}}, source::Source, col::Integer) len = source.rowsfetched[] lens = Vector{Int}(len) isnull = Vector{Bool}(len) @@ -297,9 +394,9 @@ end # long types const LONG_DATA_BUFFER_SIZE = 1024 -function cast!{T}(::Type{ODBC.API.Long{T}}, source, col) +function cast!{T}(::Type{ODBC.API.Long{T}}, source::Source, col::Integer) eT = eltype(source.boundcols[col]) - stmt = source.dsn.stmt_ptr + stmt = source.stmt data = Vector{UInt8}() buf = zeros(UInt8, ODBC.LONG_DATA_BUFFER_SIZE) ind = Ref{ODBC.API.SQLLEN}() @@ -320,7 +417,9 @@ end # DataStreams interface Data.schema(source::ODBC.Source, ::Type{Data.Column}) = source.schema "Checks if an `ODBC.Source` has finished fetching results from an executed query string" -Data.isdone(source::ODBC.Source, x=1, y=1) = source.status != ODBC.API.SQL_SUCCESS && source.status != ODBC.API.SQL_SUCCESS_WITH_INFO +function Data.isdone(source::ODBC.Source, x::Integer=1, y::Integer=1) + return source.status != ODBC.API.SQL_SUCCESS && source.status != ODBC.API.SQL_SUCCESS_WITH_INFO +end Data.streamtype{T<:ODBC.Source}(::Type{T}, ::Type{Data.Column}) = true Data.streamtype{T<:ODBC.Source}(::Type{T}, ::Type{Data.Field}) = true @@ -342,20 +441,38 @@ function Data.streamfrom{T}(source::ODBC.Source, ::Type{Data.Column}, ::Type{Nul return dest end -function query(dsn::DSN, sql::AbstractString, sink=DataFrame, args...; weakrefstrings::Bool=true, append::Bool=false, transforms::Dict=Dict{Int,Function}()) - sink = Data.stream!(Source(dsn, sql; weakrefstrings=weakrefstrings), sink, append, transforms, args...) +function query( + source::ODBC.Source, + sink::Any = DataFrame, + args...; + append::Bool=false, + transforms::Dict=Dict{Int, Function}() +) + sink = Data.stream!(source, sink, append, transforms, args...) Data.close!(sink) return sink end -function query{T}(dsn::DSN, sql::AbstractString, sink::T; weakrefstrings::Bool=true, append::Bool=false, transforms::Dict=Dict{Int,Function}()) - sink = Data.stream!(Source(dsn, sql; weakrefstrings=weakrefstrings), sink, append, transforms) - Data.close!(sink) - return sink +function query(statement::Statement, args...; weakrefstrings::Bool=true, kwargs...) + source = Source(statement; weakrefstrings=weakrefstrings) + return query(source, args...; kwargs...) end -query(source::ODBC.Source, sink=DataFrame, args...; append::Bool=false, transforms::Dict=Dict{Int,Function}()) = (sink = Data.stream!(source, sink, append, transforms, args...); Data.close!(sink); return sink) -query{T}(source::ODBC.Source, sink::T; append::Bool=false, transforms::Dict=Dict{Int,Function}()) = (sink = Data.stream!(source, sink, append, transforms); Data.close!(sink); return sink) +function query( + statement::Statement, + values::Union{Array, Tuple}, + args...; + weakrefstrings::Bool=true, + kwargs... +) + source = Source(statement, values; weakrefstrings=weakrefstrings) + return query(source, args...; kwargs...) +end + +function query(dsn::DSN, sql::AbstractString, args...; weakrefstrings::Bool=true, kwargs...) + source = Source(dsn, sql; weakrefstrings=weakrefstrings) + return query(source, args...; kwargs...) +end "Convenience string macro for executing an SQL statement against a DSN." macro sql_str(s,dsn) diff --git a/src/backend.jl b/src/backend.jl index 6b7539e..76f1962 100644 --- a/src/backend.jl +++ b/src/backend.jl @@ -153,7 +153,7 @@ function Data.getcolumn{T}(source::ODBC.Source, ::Type{T}, i) ccall(:memcpy, Void, (Ptr{T}, Ptr{T}, Csize_t), pointer(dest.values), rb.columns[i].ptr, len * sizeof(T)) booleanize!(rb.indcols[i], dest.isnull, len) if i == source.cols && !Data.isdone(source, 1, 1) - source.status = ODBC.API.SQLFetchScroll(source.dsn.stmt_ptr, ODBC.API.SQL_FETCH_NEXT, 0) + source.status = ODBC.API.SQLFetchScroll(source.stmt, ODBC.API.SQL_FETCH_NEXT, 0) end return dest end @@ -182,7 +182,7 @@ function Data.getcolumn(source::ODBC.Source, ::Type{Dec64}, i) cur += elsize end if i == source.cols && !Data.isdone(source, 1, 1) - source.status = ODBC.API.SQLFetchScroll(source.dsn.stmt_ptr, ODBC.API.SQL_FETCH_NEXT, 0) + source.status = ODBC.API.SQLFetchScroll(source.stmt, ODBC.API.SQL_FETCH_NEXT, 0) end return dest end @@ -216,7 +216,7 @@ function Data.getcolumn{T<:Union{Vector{UInt8},AbstractString}}(source::ODBC.Sou ind += elsize end if i == source.cols && !Data.isdone(source, 1, 1) - source.status = ODBC.API.SQLFetchScroll(source.dsn.stmt_ptr, ODBC.API.SQL_FETCH_NEXT, 0) + source.status = ODBC.API.SQLFetchScroll(source.stmt, ODBC.API.SQL_FETCH_NEXT, 0) end return NullableArray{T,1}(values, isnull, parent) end