Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query Basic Types in Binary Format #223

Merged
merged 26 commits into from
Aug 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/asyncresults.jl
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ function _async_submit(
conn_ptr::Ptr{libpq_c.PGconn},
query::AbstractString,
parameters::Vector{Ptr{UInt8}},
binary_format::Bool=false,
)
num_params = length(parameters)

Expand All @@ -274,7 +275,7 @@ function _async_submit(
parameters,
C_NULL, # paramLengths is ignored for text format parameters
zeros(Cint, num_params), # all parameters in text format
zero(Cint), # return result in text format
Cint(binary_format), # return result in text format
)

return send_status == 1
Expand Down
24 changes: 15 additions & 9 deletions src/parsing.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"A wrapper for one value in a PostgreSQL result."
struct PQValue{OID}
struct PQValue{OID, binary_format}
iamed2 marked this conversation as resolved.
Show resolved Hide resolved
samuel-massinon marked this conversation as resolved.
Show resolved Hide resolved
"PostgreSQL result"
jl_result::Result

Expand All @@ -10,7 +10,7 @@ struct PQValue{OID}
col::Cint

function PQValue{OID}(jl_result::Result, row::Integer, col::Integer) where OID
return new{OID}(jl_result, row - 1, col - 1)
return new{OID, jl_result.binary_format}(jl_result, row - 1, col - 1)
end
end

Expand All @@ -26,7 +26,7 @@ the result.
function PQValue(jl_result::Result, row::Integer, col::Integer)
oid = libpq_c.PQftype(jl_result.result, col - 1)

return PQValue{oid}(jl_result, row, col)
return PQValue{oid, jl_result.binary_format}(jl_result, row, col)
end

"""
Expand Down Expand Up @@ -100,8 +100,10 @@ is not in UTF-8.
"""
iamed2 marked this conversation as resolved.
Show resolved Hide resolved
bytes_view(pqv::PQValue) = unsafe_wrap(Vector{UInt8}, data_pointer(pqv), num_bytes(pqv) + 1)

# bytes_view(pqv::PQValue) = bswap(unsafe_load(data_pointer(pq_value_bin)))

iamed2 marked this conversation as resolved.
Show resolved Hide resolved
Base.String(pqv::PQValue) = unsafe_string(pqv)
Base.parse(::Type{String}, pqv::PQValue) = unsafe_string(pqv)
Base.parse(::Type{String}, pqv::PQValue) where R = unsafe_string(pqv)
iamed2 marked this conversation as resolved.
Show resolved Hide resolved
Base.convert(::Type{String}, pqv::PQValue) = String(pqv)
Base.length(pqv::PQValue) = length(string_view(pqv))
Base.lastindex(pqv::PQValue) = lastindex(string_view(pqv))
Expand All @@ -119,7 +121,11 @@ By default, this uses any existing `parse` method for parsing a value of type `T
You can implement default PostgreSQL-specific parsing for a given type by overriding
`pqparse`.
"""
Base.parse(::Type{T}, pqv::PQValue) where {T} = pqparse(T, string_view(pqv))
Base.parse(::Type{T}, pqv::PQValue) where {T, R} = pqparse(T, string_view(pqv))
# Base.parse(::Type{T}, pqv::PQValue{R, false}) where {T, R} = pqparse(T, string_view(pqv))

# Base.parse(::Type{T}, pqv::PQValue{R, true}) where {T, R} = bswap(unsafe_load(Ptr{T}(data_pointer(pqv))))
Base.parse(::Type{T}, pqv::PQValue{R, true}) where {T <: Integer, R} = bswap(unsafe_load(Ptr{T}(data_pointer(pqv))))
iamed2 marked this conversation as resolved.
Show resolved Hide resolved

"""
LibPQ.pqparse(::Type{T}, str::AbstractString) -> T
Expand Down Expand Up @@ -154,7 +160,7 @@ _DEFAULT_TYPE_MAP[:numeric] = Decimal

## character
# bpchar is char(n)
function Base.parse(::Type{String}, pqv::PQValue{PQ_SYSTEM_TYPES[:bpchar]})
function Base.parse(::Type{String}, pqv::PQValue{PQ_SYSTEM_TYPES[:bpchar], false})
iamed2 marked this conversation as resolved.
Show resolved Hide resolved
return String(rstrip(string_view(pqv), ' '))
end
# char is "char"
Expand All @@ -168,7 +174,7 @@ pqparse(::Type{Char}, str::AbstractString) = Char(pqparse(PQChar, str))
_DEFAULT_TYPE_MAP[:bytea] = Vector{UInt8}

# Needs it's own `parse` method as it uses bytes_view instead of string_view
function Base.parse(::Type{Vector{UInt8}}, pqv::PQValue{PQ_SYSTEM_TYPES[:bytea]})
function Base.parse(::Type{Vector{UInt8}}, pqv::PQValue{PQ_SYSTEM_TYPES[:bytea], false})
pqparse(Vector{UInt8}, bytes_view(pqv))
end

Expand Down Expand Up @@ -291,11 +297,11 @@ function pqparse(::Type{InfExtendedTime{T}}, str::AbstractString) where {T<:Date
end

# UNIX timestamps
function Base.parse(::Type{DateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
function Base.parse(::Type{DateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8], false})
unix2datetime(parse(Int64, pqv))
end

function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8], false})
TimeZones.unix2zdt(parse(Int64, pqv))
end

Expand Down
27 changes: 18 additions & 9 deletions src/results.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ mutable struct Result
"Name of each column in the result"
column_names::Vector{String}

"Flag if the result is in binary"
binary_format::Bool

# TODO: attach encoding per https://wiki.postgresql.org/wiki/Driver_development#Result_object_and_client_encoding
function Result(
result::Ptr{libpq_c.PGresult},
Expand All @@ -29,6 +32,7 @@ mutable struct Result
type_map::AbstractDict=PQTypeMap(),
conversions::AbstractDict=PQConversions(),
not_null=false,
binary_format=false,
)
jl_result = new(result, Atomic{Bool}(result == C_NULL))

Expand Down Expand Up @@ -102,6 +106,8 @@ mutable struct Result

finalizer(close, jl_result)

jl_result.binary_format = binary_format

return jl_result
end
end
Expand Down Expand Up @@ -271,40 +277,43 @@ function execute(
jl_conn::Connection,
query::AbstractString;
throw_error::Bool=true,
binary_format::Bool=false,
kwargs...
)
result = lock(jl_conn) do
_execute(jl_conn.conn, query)
_execute(jl_conn.conn, query; binary_format)
end

return handle_result(Result(result, jl_conn; kwargs...); throw_error=throw_error)
return handle_result(Result(result, jl_conn; binary_format, kwargs...); throw_error=throw_error)
end

function execute(
jl_conn::Connection,
query::AbstractString,
parameters::Union{AbstractVector, Tuple};
throw_error::Bool=true,
binary_format::Bool=false,
kwargs...
)
string_params = string_parameters(parameters)
pointer_params = parameter_pointers(string_params)

result = lock(jl_conn) do
_execute(jl_conn.conn, query, pointer_params)
_execute(jl_conn.conn, query, pointer_params; binary_format)
end

return handle_result(Result(result, jl_conn; kwargs...); throw_error=throw_error)
return handle_result(Result(result, jl_conn; binary_format, kwargs...); throw_error=throw_error)
end

function _execute(conn_ptr::Ptr{libpq_c.PGconn}, query::AbstractString)
return libpq_c.PQexec(conn_ptr, query)
end
# function _execute(conn_ptr::Ptr{libpq_c.PGconn}, query::AbstractString)
iamed2 marked this conversation as resolved.
Show resolved Hide resolved
# return libpq_c.PQexec(conn_ptr, query)
# end

function _execute(
conn_ptr::Ptr{libpq_c.PGconn},
query::AbstractString,
parameters::Vector{Ptr{UInt8}},
parameters::Vector{Ptr{UInt8}}=Ptr{UInt8}[];
binary_format::Bool=false,
)
num_params = length(parameters)

Expand All @@ -316,7 +325,7 @@ function _execute(
parameters,
C_NULL, # paramLengths is ignored for text format parameters
zeros(Cint, num_params), # all parameters in text format
zero(Cint), # return result in text format
Cint(binary_format), # return result in text format
)
end

Expand Down
3 changes: 2 additions & 1 deletion src/statements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ function _execute_prepared(
conn_ptr::Ptr{libpq_c.PGconn},
stmt_name::AbstractString,
parameters::Vector{Ptr{UInt8}}=Ptr{UInt8}[],
binary_format::Bool=false,
)
num_params = length(parameters)

Expand All @@ -156,6 +157,6 @@ function _execute_prepared(
num_params == 0 ? C_NULL : parameters,
C_NULL, # paramLengths is ignored for text format parameters
num_params == 0 ? C_NULL : zeros(Cint, num_params), # all parameters in text format
zero(Cint), # return result in text format
Cint(binary_format), # return result in text format
)
end
98 changes: 98 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,23 @@ end
@test table_data[:yes_nulls][1] == data[:yes_nulls][1]
@test table_data[:yes_nulls][2] === missing

# The same but binary data

result = execute(
conn,
"SELECT no_nulls, yes_nulls FROM libpqjl_test ORDER BY no_nulls DESC;";
throw_error=true,
binary_format=true,
)
@test status(result) == LibPQ.libpq_c.PGRES_TUPLES_OK
@test LibPQ.num_rows(result) == 2
@test LibPQ.num_columns(result) == 2

table_data = columntable(result)
@test table_data[:no_nulls] == data[:no_nulls]
@test table_data[:yes_nulls][1] == data[:yes_nulls][1]
@test table_data[:yes_nulls][2] === missing

close(result)

result = execute(
Expand Down Expand Up @@ -275,6 +292,87 @@ end
close(conn)
end


@testset "Binary Transfer" begin
conn = LibPQ.Connection("dbname=postgres user=$DATABASE_USER")

result = execute(conn, """
CREATE TEMPORARY TABLE libpqjl_test (
no_nulls int PRIMARY KEY,
yes_nulls int
);
""")
@test status(result) == LibPQ.libpq_c.PGRES_COMMAND_OK
close(result)

# get the data from PostgreSQL and let columntable construct my NamedTuple
result = execute(conn, """
SELECT no_nulls, yes_nulls FROM (
VALUES (1, 2), (3, NULL)
) AS temp (no_nulls, yes_nulls)
ORDER BY no_nulls DESC;
""";
throw_error=true,
binary_format=true,
)
@test status(result) == LibPQ.libpq_c.PGRES_TUPLES_OK
@test LibPQ.num_rows(result) == 2
@test LibPQ.num_columns(result) == 2

data = columntable(result)

@test data[:no_nulls] == [3, 1]
@test data[:yes_nulls][1] === missing
@test data[:yes_nulls][2] == 2

stmt = LibPQ.load!(
data,
conn,
"INSERT INTO libpqjl_test (no_nulls, yes_nulls) VALUES (\$1, \$2);",
)

@test_throws ArgumentError num_affected_rows(stmt.description)
@test num_params(stmt) == 2
@test num_columns(stmt) == 0 # an insert has no results
@test LibPQ.column_number(stmt, "no_nulls") == 0
@test LibPQ.column_names(stmt) == []

result = execute(
conn,
"SELECT no_nulls, yes_nulls FROM libpqjl_test ORDER BY no_nulls DESC;";
throw_error=true,
binary_format=true,
)
@test status(result) == LibPQ.libpq_c.PGRES_TUPLES_OK
@test LibPQ.num_rows(result) == 2
@test LibPQ.num_columns(result) == 2

table_data = columntable(result)
@test table_data[:no_nulls] == data[:no_nulls]
@test table_data[:yes_nulls][1] === data[:yes_nulls][1] === missing
@test table_data[:yes_nulls][2] == data[:yes_nulls][2]

# The same but binary data

result = execute(
conn,
"SELECT no_nulls, yes_nulls FROM libpqjl_test ORDER BY no_nulls DESC;";
throw_error=true,
binary_format=true,
)
@test status(result) == LibPQ.libpq_c.PGRES_TUPLES_OK
@test LibPQ.num_rows(result) == 2
@test LibPQ.num_columns(result) == 2

table_data = columntable(result)
@test table_data[:no_nulls] == data[:no_nulls]
@test table_data[:yes_nulls][1] === data[:yes_nulls][1] === missing
@test table_data[:yes_nulls][2] == data[:yes_nulls][2]

close(conn)
close(result)
end

@testset "load!" begin
@testset "issue #204" begin
conn = LibPQ.Connection("dbname=postgres user=$DATABASE_USER")
Expand Down