From f3d570a2f5fe7eb3950c3c9e2a4628792072cf54 Mon Sep 17 00:00:00 2001 From: Kyrylo Simonov Date: Sat, 14 Jun 2025 11:25:22 -0500 Subject: [PATCH 1/2] Add metadata to SQLString object --- docs/src/test/nodes.md | 9 +++++--- docs/src/test/other.md | 12 ++++++----- src/clauses/internal.jl | 10 ++++----- src/serialize.jl | 4 ++-- src/strings.jl | 46 +++++++++++++++++++++++------------------ src/translate.jl | 7 ++----- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/docs/src/test/nodes.md b/docs/src/test/nodes.md index 1d1aa14d..9426beed 100644 --- a/docs/src/test/nodes.md +++ b/docs/src/test/nodes.md @@ -4244,8 +4244,9 @@ On the next stage, the query object is converted to a SQL syntax tree. │ left = true) |> │ SELECT(ID(:person_2) |> ID(:person_id), │ ID(:visit_group_1) |> ID(:max) |> AS(:max_visit_start_date)) |> - │ WITH_CONTEXT(columns = [SQLColumn(:person_id), - │ SQLColumn(:max_visit_start_date)]) + │ WITH_CONTEXT(shape = SQLTable(:person, + │ SQLColumn(:person_id), + │ SQLColumn(:max_visit_start_date))) └ @ FunSQL … =# @@ -4281,6 +4282,8 @@ Finally, the SQL tree is serialized into SQL. │ FROM "visit_occurrence" AS "visit_occurrence_1" │ GROUP BY "visit_occurrence_1"."person_id" │ ) AS "visit_group_1" ON ("person_2"."person_id" = "visit_group_1"."person_id")""", - │ columns = [SQLColumn(:person_id), SQLColumn(:max_visit_start_date)]) + │ shape = SQLTable(:person, + │ SQLColumn(:person_id), + │ SQLColumn(:max_visit_start_date))) └ @ FunSQL … =# diff --git a/docs/src/test/other.md b/docs/src/test/other.md index bdb27c43..f0d178b1 100644 --- a/docs/src/test/other.md +++ b/docs/src/test/other.md @@ -425,14 +425,16 @@ A completely custom dialect can be specified. String(sql) #-> "SELECT * FROM person" -`SQLString` may carry a vector `columns` describing the output columns of -the query. +`SQLString` may specify the `shape` describing the output columns of the query. - sql = SQLString("SELECT person_id FROM person", columns = [SQLColumn(:person_id)]) - #-> SQLString("SELECT person_id FROM person", columns = […1 column…]) + sql = SQLString("SELECT person_id FROM person", shape = SQLTable(:person, SQLColumn(:person_id))) + #-> SQLString("SELECT person_id FROM person", shape = SQLTable(person, …1 column…)) display(sql) - #-> SQLString("SELECT person_id FROM person", columns = [SQLColumn(:person_id)]) + #=> + SQLString("SELECT person_id FROM person", + shape = SQLTable(:person, SQLColumn(:person_id))) + =# When the query has parameters, `SQLString` should include a vector of parameter names in the order they should appear in `DBInterface.execute` call. diff --git a/src/clauses/internal.jl b/src/clauses/internal.jl index 8eea0184..8ce88aee 100644 --- a/src/clauses/internal.jl +++ b/src/clauses/internal.jl @@ -4,10 +4,10 @@ struct WithContextClause <: AbstractSQLClause dialect::SQLDialect - columns::Union{Vector{SQLColumn}, Nothing} + shape::SQLTable - WithContextClause(; dialect, columns = nothing) = - new(dialect, columns) + WithContextClause(; dialect, shape = SQLTable(name = :_, columns = [])) = + new(dialect, shape) end const WITH_CONTEXT = SQLSyntaxCtor{WithContextClause}(:WITH_CONTEXT) @@ -17,8 +17,8 @@ function PrettyPrinting.quoteof(c::WithContextClause, ctx::QuoteContext) if c.dialect !== default_dialect push!(ex.args, Expr(:kw, :dialect, quoteof(c.dialect))) end - if c.columns !== nothing - push!(ex.args, Expr(:kw, :columns, Expr(:vect, Any[quoteof(col) for col in c.columns]...))) + if c.shape.name !== :_ || !isempty(c.shape.columns) || !isempty(c.shape.metadata) + push!(ex.args, Expr(:kw, :shape, quoteof(c.shape))) end ex end diff --git a/src/serialize.jl b/src/serialize.jl index 16d8a8b5..6d3df941 100644 --- a/src/serialize.jl +++ b/src/serialize.jl @@ -13,11 +13,11 @@ mutable struct SerializeContext <: IO end function serialize(s::SQLSyntax) - @dissect(s, WITH_CONTEXT(tail = (local s′), dialect = (local dialect), columns = (local columns))) || throw(IllFormedError()) + @dissect(s, WITH_CONTEXT(tail = (local s′), dialect = (local dialect), shape = (local shape))) || throw(IllFormedError()) ctx = SerializeContext(dialect, s′) serialize!(ctx) raw = String(take!(ctx.io)) - SQLString(raw, columns = columns, vars = ctx.vars) + SQLString(raw, vars = ctx.vars, shape = shape) end Base.write(ctx::SerializeContext, octet::UInt8) = diff --git a/src/strings.jl b/src/strings.jl index 122a8848..596e9126 100644 --- a/src/strings.jl +++ b/src/strings.jl @@ -1,15 +1,16 @@ # Serialized SQL query with parameter mapping. """ - SQLString(raw; columns = nothing, vars = Symbol[]) + SQLString(raw; vars = Symbol[], shape = SQLTable(:_)) Serialized SQL query. -Parameter `columns` is a vector describing the output columns. - Parameter `vars` is a vector of query parameters (created with [`Var`](@ref)) in the order they are expected by the `DBInterface.execute()` function. +Parameter `shape` describes the shape of the query output as a table +definition. + # Examples ```jldoctest @@ -23,7 +24,9 @@ SQLString(\""" "person_1"."person_id", "person_1"."year_of_birth" FROM "person" AS "person_1\\"\""", - columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)]) + shape = SQLTable(:person, + SQLColumn(:person_id), + SQLColumn(:year_of_birth))) julia> q = From(person) |> Where(Fun.and(Get.year_of_birth .>= Var.YEAR, Get.year_of_birth .< Var.YEAR .+ 10)); @@ -37,8 +40,10 @@ SQLString(\""" WHERE (`person_1`.`year_of_birth` >= ?) AND (`person_1`.`year_of_birth` < (? + 10))\""", - columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)], - vars = [:YEAR, :YEAR]) + vars = [:YEAR, :YEAR], + shape = SQLTable(:person, + SQLColumn(:person_id), + SQLColumn(:year_of_birth))) julia> render(q, dialect = :postgresql) SQLString(\""" @@ -49,17 +54,19 @@ SQLString(\""" WHERE ("person_1"."year_of_birth" >= \$1) AND ("person_1"."year_of_birth" < (\$1 + 10))\""", - columns = [SQLColumn(:person_id), SQLColumn(:year_of_birth)], - vars = [:YEAR]) + vars = [:YEAR], + shape = SQLTable(:person, + SQLColumn(:person_id), + SQLColumn(:year_of_birth))) ``` """ struct SQLString <: AbstractString raw::String - columns::Union{Vector{SQLColumn}, Nothing} vars::Vector{Symbol} + shape::SQLTable - SQLString(raw; columns = nothing, vars = Symbol[]) = - new(raw, columns, vars) + SQLString(raw; vars = Symbol[], shape = SQLTable(name = :_, columns = [])) = + new(raw, vars, shape) end Base.ncodeunits(sql::SQLString) = @@ -88,27 +95,27 @@ Base.write(io::IO, sql::SQLString) = function PrettyPrinting.quoteof(sql::SQLString) ex = Expr(:call, nameof(SQLString), sql.raw) - if sql.columns !== nothing - push!(ex.args, Expr(:kw, :columns, Expr(:vect, Any[quoteof(col) for col in sql.columns]...))) - end if !isempty(sql.vars) push!(ex.args, Expr(:kw, :vars, quoteof(sql.vars))) end + if sql.shape.name !== :_ || !isempty(sql.shape.columns) || !isempty(sql.shape.metadata) + push!(ex.args, Expr(:kw, :shape, quoteof(sql.shape))) + end ex end function Base.show(io::IO, sql::SQLString) print(io, "SQLString(") show(io, sql.raw) - if sql.columns !== nothing - print(io, ", columns = ") - l = length(sql.columns) - print(io, l == 0 ? "[]" : l == 1 ? "[…1 column…]" : "[…$l columns…]") - end if !isempty(sql.vars) print(io, ", vars = ") show(io, sql.vars) end + if sql.shape.name !== :_ || !isempty(sql.shape.columns) + print(io, ", shape = SQLTable(", sql.shape.name) + l = length(sql.shape.columns) + print(io, l == 0 ? ")" : l == 1 ? ", …1 column…)" : ", …$l columns…)") + end print(io, ')') nothing end @@ -159,4 +166,3 @@ pack(vars::Vector{Symbol}, d::AbstractDict{<:AbstractString}) = pack(vars::Vector{Symbol}, nt::NamedTuple) = Any[getproperty(nt, var) for var in vars] - diff --git a/src/translate.jl b/src/translate.jl index 84b3bd79..672a5c00 100644 --- a/src/translate.jl +++ b/src/translate.jl @@ -215,10 +215,7 @@ function translate(q::SQLQuery) ctx = TranslateContext(catalog = catalog, defs = defs) ctx′ = TranslateContext(ctx, refs = refs) base = assemble(q′, ctx′) - columns = nothing - if !isempty(refs) - columns = [SQLColumn(base.repl[ref]) for ref in refs] - end + shape = SQLTable(name = base.name, columns = [base.repl[ref] for ref in refs]) c = complete_aligned(base, ctx′) with_args = SQLSyntax[] for cte_a in ctx.ctes @@ -238,7 +235,7 @@ function translate(q::SQLQuery) if !isempty(with_args) c = WITH(tail = c, args = with_args, recursive = ctx.recursive[]) end - WITH_CONTEXT(tail = c, dialect = ctx.catalog.dialect, columns = columns) + WITH_CONTEXT(tail = c, dialect = ctx.catalog.dialect, shape = shape) end function translate(q::SQLQuery, ctx) From b34194684c60d161d4abed4d0c8906750de87bd6 Mon Sep 17 00:00:00 2001 From: Kyrylo Simonov Date: Sat, 14 Jun 2025 11:44:39 -0500 Subject: [PATCH 2/2] Propagate query shape and metadata to SQLCursor --- docs/src/test/other.md | 10 ++-- src/connections.jl | 90 ++++++++++++++++++++++++++++++----- src/strings.jl | 105 ++++++++++++++++++++++++++--------------- 3 files changed, 149 insertions(+), 56 deletions(-) diff --git a/docs/src/test/other.md b/docs/src/test/other.md index f0d178b1..87c85dc3 100644 --- a/docs/src/test/other.md +++ b/docs/src/test/other.md @@ -1,7 +1,7 @@ # Other Tests -## `SQLConnection` and `SQLStatement` +## `SQLConnection`, `SQLStatement`, and `SQLCursor` A `SQLConnection` object encapsulates a raw database connection together with the database catalog. @@ -38,7 +38,7 @@ a FunSQL-specific `SQLStatement` object. q = From(:person) stmt = DBInterface.prepare(conn, q) - #-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … )) + #-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ), shape = SQLTable( … )) DBInterface.getconnection(stmt) #-> SQLConnection( … ) @@ -47,7 +47,7 @@ The output of the statement is wrapped in a FunSQL-specific `SQLCursor` object. cr = DBInterface.execute(stmt) - #-> SQLCursor(SQLite.Query{false}( … )) + #-> SQLCursor(SQLite.Query{false}( … ), shape = SQLTable( … )) `SQLCursor` implements standard interfaces by delegating supported methods to the wrapped cursor object. @@ -100,10 +100,10 @@ by name. Where(Get.year_of_birth .>= Var.YEAR) stmt = DBInterface.prepare(conn, q) - #-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ), vars = [:YEAR]) + #-> SQLStatement(SQLConnection( … ), SQLite.Stmt( … ), vars = [:YEAR], shape = SQLTable( … )) DBInterface.execute(stmt, YEAR = 1950) - #-> SQLCursor(SQLite.Query{false}( … )) + #-> SQLCursor(SQLite.Query{false}( … ), shape = SQLTable( … )) DBInterface.close!(stmt) diff --git a/src/connections.jl b/src/connections.jl index 2b7f908b..adfdfe45 100644 --- a/src/connections.jl +++ b/src/connections.jl @@ -34,13 +34,14 @@ struct SQLStatement{RawConnType, RawStmtType} <: DBInterface.Statement conn::SQLConnection{RawConnType} raw::RawStmtType vars::Vector{Symbol} + shape::SQLTable - SQLStatement{RawConnType, RawStmtType}(conn::SQLConnection{RawConnType}, raw::RawStmtType; vars = Symbol[]) where {RawConnType, RawStmtType} = - new(conn, raw, vars) + SQLStatement{RawConnType, RawStmtType}(conn::SQLConnection{RawConnType}, raw::RawStmtType; vars = Symbol[], shape = SQLTable(name = :_, columns = [])) where {RawConnType, RawStmtType} = + new(conn, raw, vars, shape) end -SQLStatement(conn::SQLConnection{RawConnType}, raw::RawStmtType; vars = Symbol[]) where {RawConnType, RawStmtType} = - SQLStatement{RawConnType, RawStmtType}(conn, raw, vars = vars) +SQLStatement(conn::SQLConnection{RawConnType}, raw::RawStmtType; vars = Symbol[], shape = SQLTable(name = :_, columns = [])) where {RawConnType, RawStmtType} = + SQLStatement{RawConnType, RawStmtType}(conn, raw, vars = vars, shape = shape) function Base.show(io::IO, stmt::SQLStatement) print(io, "SQLStatement(") @@ -51,32 +52,70 @@ function Base.show(io::IO, stmt::SQLStatement) print(io, ", vars = ") show(io, stmt.vars) end + if stmt.shape.name !== :_ || !isempty(stmt.shape.columns) + print(io, ", shape = SQLTable(", stmt.shape.name) + l = length(stmt.shape.columns) + print(io, l == 0 ? ")" : l == 1 ? ", …1 column…)" : ", …$l columns…)") + end print(io, ')') end +DataAPI.metadatasupport(::Type{:SQLStatement}) = + DataAPI.metadatasupport(SQLTable) + +DataAPI.metadata(stmt::SQLStatement, key; style = false) = + DataAPI.metadata(stmt.shape, key; style) + +DataAPI.metadata(stmt::SQLStatement, key, default; style = false) = + DataAPI.metadata(stmt.shape, key, default; style) + +DataAPI.metadatakeys(stmt::SQLStatement) = + DataAPI.metadatakeys(stmt.shape) + +DataAPI.colmetadatasupport(::Type{:SQLStatement}) = + DataAPI.colmetadatasupport(SQLTable) + +DataAPI.colmetadata(stmt::SQLStatement, col, key; style = false) = + DataAPI.colmetadata(stmt.shape, col, key; style) + +DataAPI.colmetadata(stmt::SQLStatement, col, key, default; style = false) = + DataAPI.colmetadata(stmt.shape, col, key, default; style) + +DataAPI.colmetadatakeys(stmt::SQLStatement) = + DataAPI.colmetadatakeys(stmt.shape) + +DataAPI.colmetadatakeys(stmt::SQLStatement, col) = + DataAPI.colmetadatakeys(stmt.shape, col) + """ Shorthand for [`SQLConnection`](@ref). """ const DB = SQLConnection """ - SQLCursor(raw) + SQLCursor(raw; shape) Wraps the query result. """ struct SQLCursor{RawCrType} <: DBInterface.Cursor raw::RawCrType + shape::SQLTable - SQLCursor{RawCrType}(raw::RawCrType) where {RawCrType} = - new(raw) + SQLCursor{RawCrType}(raw::RawCrType; shape = SQLTable(name = :_, columns = [])) where {RawCrType} = + new(raw, shape) end -SQLCursor(raw::RawCrType) where {RawCrType} = - SQLCursor{RawCrType}(raw) +SQLCursor(raw::RawCrType; shape = SQLTable(name = :_, columns = [])) where {RawCrType} = + SQLCursor{RawCrType}(raw; shape) function Base.show(io::IO, cr::SQLCursor) print(io, "SQLCursor(") show(io, cr.raw) + if cr.shape.name !== :_ || !isempty(cr.shape.columns) + print(io, ", shape = SQLTable(", cr.shape.name) + l = length(cr.shape.columns) + print(io, l == 0 ? ")" : l == 1 ? ", …1 column…)" : ", …$l columns…)") + end print(io, ")") end @@ -110,6 +149,33 @@ Tables.columns(cr::SQLCursor) = Tables.schema(cr::SQLCursor) = Tables.schema(cr.raw) +DataAPI.metadatasupport(::Type{<:SQLCursor}) = + DataAPI.metadatasupport(SQLTable) + +DataAPI.metadata(cr::SQLCursor, key; style = false) = + DataAPI.metadata(cr.shape, key; style) + +DataAPI.metadata(cr::SQLCursor, key, default; style = false) = + DataAPI.metadata(cr.shape, key, default; style) + +DataAPI.metadatakeys(cr::SQLCursor) = + DataAPI.metadatakeys(cr.shape) + +DataAPI.colmetadatasupport(::Type{<:SQLCursor}) = + DataAPI.colmetadatasupport(SQLTable) + +DataAPI.colmetadata(cr::SQLCursor, col, key; style = false) = + DataAPI.colmetadata(cr.shape, col, key; style) + +DataAPI.colmetadata(cr::SQLCursor, col, key, default; style = false) = + DataAPI.colmetadata(cr.shape, col, key, default; style) + +DataAPI.colmetadatakeys(cr::SQLCursor) = + DataAPI.colmetadatakeys(cr.shape) + +DataAPI.colmetadatakeys(cr::SQLCursor, col) = + DataAPI.colmetadatakeys(cr.shape, col) + """ DBInterface.connect(DB{RawConnType}, args...; @@ -150,8 +216,8 @@ DBInterface.prepare(conn::SQLConnection, sql::Union{SQLQuery, SQLSyntax}) = Generate a prepared SQL statement. """ -DBInterface.prepare(conn::SQLConnection, str::SQLString) = - SQLStatement(conn, DBInterface.prepare(conn.raw, str.raw), vars = str.vars) +DBInterface.prepare(conn::SQLConnection, sql::SQLString) = + SQLStatement(conn, DBInterface.prepare(conn.raw, sql.raw), vars = sql.vars, shape = sql.shape) DBInterface.prepare(conn::SQLConnection, str::AbstractString) = DBInterface.prepare(conn.raw, str) @@ -183,7 +249,7 @@ DBInterface.close!(conn::SQLConnection) = Execute the prepared SQL statement. """ DBInterface.execute(stmt::SQLStatement, params) = - SQLCursor(DBInterface.execute(stmt.raw, pack(stmt.vars, params))) + SQLCursor(DBInterface.execute(stmt.raw, pack(stmt.vars, params)), shape = stmt.shape) DBInterface.getconnection(stmt::SQLStatement) = stmt.conn diff --git a/src/strings.jl b/src/strings.jl index 596e9126..fcdf5523 100644 --- a/src/strings.jl +++ b/src/strings.jl @@ -69,62 +69,89 @@ struct SQLString <: AbstractString new(raw, vars, shape) end -Base.ncodeunits(sql::SQLString) = - ncodeunits(sql.raw) +Base.ncodeunits(str::SQLString) = + ncodeunits(str.raw) -Base.codeunit(sql::SQLString) = - codeunit(sql.raw) +Base.codeunit(str::SQLString) = + codeunit(str.raw) -@Base.propagate_inbounds Base.codeunit(sql::SQLString, i::Integer) = - codeunit(sql.raw, i) +@Base.propagate_inbounds Base.codeunit(str::SQLString, i::Integer) = + codeunit(str.raw, i) -@Base.propagate_inbounds Base.isvalid(sql::SQLString, i::Integer) = - isvalid(sql.raw, i) +@Base.propagate_inbounds Base.isvalid(str::SQLString, i::Integer) = + isvalid(str.raw, i) -@Base.propagate_inbounds Base.iterate(sql::SQLString, i::Integer = 1) = - iterate(sql.raw, i) +@Base.propagate_inbounds Base.iterate(str::SQLString, i::Integer = 1) = + iterate(str.raw, i) -Base.String(sql::SQLString) = - sql.raw +Base.String(str::SQLString) = + str.raw -Base.print(io::IO, sql::SQLString) = - print(io, sql.raw) +Base.print(io::IO, str::SQLString) = + print(io, str.raw) -Base.write(io::IO, sql::SQLString) = - write(io, sql.raw) +Base.write(io::IO, str::SQLString) = + write(io, str.raw) -function PrettyPrinting.quoteof(sql::SQLString) - ex = Expr(:call, nameof(SQLString), sql.raw) - if !isempty(sql.vars) - push!(ex.args, Expr(:kw, :vars, quoteof(sql.vars))) +function PrettyPrinting.quoteof(str::SQLString) + ex = Expr(:call, nameof(SQLString), str.raw) + if !isempty(str.vars) + push!(ex.args, Expr(:kw, :vars, quoteof(str.vars))) end - if sql.shape.name !== :_ || !isempty(sql.shape.columns) || !isempty(sql.shape.metadata) - push!(ex.args, Expr(:kw, :shape, quoteof(sql.shape))) + if str.shape.name !== :_ || !isempty(str.shape.columns) || !isempty(str.shape.metadata) + push!(ex.args, Expr(:kw, :shape, quoteof(str.shape))) end ex end -function Base.show(io::IO, sql::SQLString) +function Base.show(io::IO, str::SQLString) print(io, "SQLString(") - show(io, sql.raw) - if !isempty(sql.vars) + show(io, str.raw) + if !isempty(str.vars) print(io, ", vars = ") - show(io, sql.vars) + show(io, str.vars) end - if sql.shape.name !== :_ || !isempty(sql.shape.columns) - print(io, ", shape = SQLTable(", sql.shape.name) - l = length(sql.shape.columns) + if str.shape.name !== :_ || !isempty(str.shape.columns) + print(io, ", shape = SQLTable(", str.shape.name) + l = length(str.shape.columns) print(io, l == 0 ? ")" : l == 1 ? ", …1 column…)" : ", …$l columns…)") end print(io, ')') nothing end -Base.show(io::IO, ::MIME"text/plain", sql::SQLString) = - pprint(io, sql) +Base.show(io::IO, ::MIME"text/plain", str::SQLString) = + pprint(io, str) + +DataAPI.metadatasupport(::Type{SQLString}) = + DataAPI.metadatasupport(SQLTable) + +DataAPI.metadata(str::SQLString, key; style = false) = + DataAPI.metadata(str.shape, key; style) + +DataAPI.metadata(str::SQLString, key, default; style = false) = + DataAPI.metadata(str.shape, key, default; style) + +DataAPI.metadatakeys(str::SQLString) = + DataAPI.metadatakeys(str.shape) + +DataAPI.colmetadatasupport(::Type{SQLString}) = + DataAPI.colmetadatasupport(SQLTable) + +DataAPI.colmetadata(str::SQLString, col, key; style = false) = + DataAPI.colmetadata(str.shape, col, key; style) + +DataAPI.colmetadata(str::SQLString, col, key, default; style = false) = + DataAPI.colmetadata(str.shape, col, key, default; style) + +DataAPI.colmetadatakeys(str::SQLString) = + DataAPI.colmetadatakeys(str.shape) + +DataAPI.colmetadatakeys(str::SQLString, col) = + DataAPI.colmetadatakeys(str.shape, col) """ - pack(sql::SQLString, vars::Union{Dict, NamedTuple})::Vector{Any} + pack(str::SQLString, vars::Union{Dict, NamedTuple})::Vector{Any} Convert a dictionary or a named tuple of query parameters to the positional form expected by `DBInterface.execute()`. @@ -135,16 +162,16 @@ julia> person = SQLTable(:person, columns = [:person_id, :year_of_birth]); julia> q = From(person) |> Where(Fun.and(Get.year_of_birth .>= Var.YEAR, Get.year_of_birth .< Var.YEAR .+ 10)); -julia> sql = render(q, dialect = :mysql); +julia> str = render(q, dialect = :mysql); -julia> pack(sql, (; YEAR = 1950)) +julia> pack(str, (; YEAR = 1950)) 2-element Vector{Any}: 1950 1950 -julia> sql = render(q, dialect = :postgresql); +julia> str = render(q, dialect = :postgresql); -julia> pack(sql, (; YEAR = 1950)) +julia> pack(str, (; YEAR = 1950)) 1-element Vector{Any}: 1950 ``` @@ -152,10 +179,10 @@ julia> pack(sql, (; YEAR = 1950)) function pack end -pack(sql::SQLString, params) = - pack(sql.vars, params) +pack(str::SQLString, params) = + pack(str.vars, params) -pack(sql::AbstractString, params) = +pack(str::AbstractString, params) = params pack(vars::Vector{Symbol}, d::AbstractDict{Symbol}) =