From dd145465e1d677fd4d78f74c725d52b4db551367 Mon Sep 17 00:00:00 2001 From: Jacob Quinn Date: Thu, 12 Aug 2021 10:46:51 -0600 Subject: [PATCH] By default, make query column names unique (#254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Bogumił Kamiński --- src/SQLite.jl | 4 ++-- src/tables.jl | 14 ++++++++++++-- test/runtests.jl | 5 +++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/SQLite.jl b/src/SQLite.jl index d8fb19a..815168f 100644 --- a/src/SQLite.jl +++ b/src/SQLite.jl @@ -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 @@ -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}}) diff --git a/src/tables.jl b/src/tables.jl index 0592c38..d38ecc8 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -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))) diff --git a/test/runtests.jl b/test/runtests.jl index c4782f2..415cc72 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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%")