Skip to content

Commit

Permalink
By default, make query column names unique (#254)
Browse files Browse the repository at this point in the history
* By default, make query column names unique

Fixes #253. Most data formats have requirements around column name
uniqueness, including DataFrames.jl. The proposed changes here ensure
query result columns are unique by default, while still allowing column
names to be duplicated if they pass `allowduplicates=true` to
`DBInterface.execute`.

* Update test/runtests.jl

Co-authored-by: Bogumił Kamiński <[email protected]>

Co-authored-by: Bogumił Kamiński <[email protected]>
  • Loading branch information
quinnj and bkamins authored Aug 12, 2021
1 parent 466e080 commit dd14546
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 4 deletions.
4 changes: 2 additions & 2 deletions src/SQLite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ end
execute(stmt::Stmt, params::DBInterface.StatementParams) =
execute(stmt.db, _stmt(stmt), params)

execute(stmt::Stmt; kwargs...) = execute(stmt, kwargs.data)
execute(stmt::Stmt; kwargs...) = execute(stmt, values(kwargs))

function execute(db::DB, sql::AbstractString, params::DBInterface.StatementParams)
# prepare without registering _Stmt in DB
Expand All @@ -470,7 +470,7 @@ function execute(db::DB, sql::AbstractString, params::DBInterface.StatementParam
end
end

execute(db::DB, sql::AbstractString; kwargs...) = execute(db, sql, kwargs.data)
execute(db::DB, sql::AbstractString; kwargs...) = execute(db, sql, values(kwargs))

"""
SQLite.esc_id(x::Union{AbstractString,Vector{AbstractString}})
Expand Down
14 changes: 12 additions & 2 deletions src/tables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,24 @@ Calling `SQLite.reset!(result)` will re-execute the query and reset the iterator
The resultset iterator supports the [Tables.jl](https://github.com/JuliaData/Tables.jl) interface, so results can be collected in any Tables.jl-compatible sink,
like `DataFrame(results)`, `CSV.write("results.csv", results)`, etc.
"""
function DBInterface.execute(stmt::Stmt, params::DBInterface.StatementParams)
function DBInterface.execute(stmt::Stmt, params::DBInterface.StatementParams; allowduplicates::Bool=false)
status = execute(stmt, params)
_st = _stmt(stmt)
cols = sqlite3_column_count(_st.handle)
header = Vector{Symbol}(undef, cols)
types = Vector{Type}(undef, cols)
for i = 1:cols
header[i] = sym(sqlite3_column_name(_st.handle, i))
nm = sym(sqlite3_column_name(_st.handle, i))
if !allowduplicates && nm in view(header, 1:(i - 1))
j = 1
newnm = Symbol(nm, :_, j)
while newnm in view(header, 1:(i - 1))
j += 1
newnm = Symbol(nm, :_, j)
end
nm = newnm
end
header[i] = nm
types[i] = Union{juliatype(_st.handle, i), Missing}
end
return Query(stmt, Ref(status), header, types, Dict(x=>i for (i, x) in enumerate(header)))
Expand Down
5 changes: 5 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ tbl3 = (c = [7, 8, 9], a = [4, 5, 6])
# Test busy_timeout
@test SQLite.busy_timeout(db, 300) == 0

# 253, ensure query column names are unique by default
db = SQLite.DB()
res = DBInterface.execute(db, "select 1 as x2, 2 as x2, 3 as x2, 4 as x2_2") |> columntable
@test res == (x2 = [1], x2_1 = [2], x2_2 = [3], x2_2_1 = [4])

@testset "load!()/drop!() table name escaping" begin
tbl = (a = [1, 2, 3], b = ["a", "b", "c"])
SQLite.load!(tbl, db, "escape 10.0%")
Expand Down

0 comments on commit dd14546

Please sign in to comment.