Skip to content

Commit 5f9ae17

Browse files
committed
Define: add 'before' and 'after' parameters
1 parent 0dc8c40 commit 5f9ae17

File tree

3 files changed

+185
-19
lines changed

3 files changed

+185
-19
lines changed

docs/src/test/nodes.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,129 @@ a simple reference.
639639
FROM "person" AS "person_1"
640640
=#
641641

642+
`Define` allows you to insert columns at the beginning or at the end of
643+
the column list.
644+
645+
q = From(person) |>
646+
Define(:age => Fun.now() .- Get.birth_datetime, Get.birth_datetime,
647+
before = true)
648+
649+
display(q)
650+
#=>
651+
let person = SQLTable(:person, …),
652+
q1 = From(person),
653+
q2 = q1 |>
654+
Define(Fun."-"(Fun.now(), Get.birth_datetime) |> As(:age),
655+
Get.birth_datetime,
656+
before = true)
657+
q2
658+
end
659+
=#
660+
661+
print(render(q))
662+
#=>
663+
SELECT
664+
(now() - "person_1"."birth_datetime") AS "age",
665+
"person_1"."birth_datetime",
666+
"person_1"."person_id",
667+
"person_1"."gender_concept_id",
668+
"person_1"."year_of_birth",
669+
"person_1"."month_of_birth",
670+
"person_1"."day_of_birth",
671+
"person_1"."location_id"
672+
FROM "person" AS "person_1"
673+
=#
674+
675+
q = From(person) |>
676+
Define(:age => Fun.now() .- Get.birth_datetime, Get.birth_datetime,
677+
after = true)
678+
679+
display(q)
680+
#=>
681+
let person = SQLTable(:person, …),
682+
q1 = From(person),
683+
q2 = q1 |>
684+
Define(Fun."-"(Fun.now(), Get.birth_datetime) |> As(:age),
685+
Get.birth_datetime,
686+
after = true)
687+
q2
688+
end
689+
=#
690+
691+
print(render(q))
692+
#=>
693+
SELECT
694+
"person_1"."person_id",
695+
"person_1"."gender_concept_id",
696+
"person_1"."year_of_birth",
697+
"person_1"."month_of_birth",
698+
"person_1"."day_of_birth",
699+
"person_1"."location_id",
700+
(now() - "person_1"."birth_datetime") AS "age",
701+
"person_1"."birth_datetime"
702+
FROM "person" AS "person_1"
703+
=#
704+
705+
It can also insert columns in front of or right after a specified column.
706+
707+
q = From(person) |>
708+
Define(:age => Fun.now() .- Get.birth_datetime, Get.birth_datetime,
709+
before = :year_of_birth)
710+
711+
print(render(q))
712+
#=>
713+
SELECT
714+
"person_1"."person_id",
715+
"person_1"."gender_concept_id",
716+
(now() - "person_1"."birth_datetime") AS "age",
717+
"person_1"."birth_datetime",
718+
"person_1"."year_of_birth",
719+
"person_1"."month_of_birth",
720+
"person_1"."day_of_birth",
721+
"person_1"."location_id"
722+
FROM "person" AS "person_1"
723+
=#
724+
725+
q = From(person) |>
726+
Define(:age => Fun.now() .- Get.birth_datetime, Get.birth_datetime,
727+
after = :birth_datetime)
728+
729+
print(render(q))
730+
#=>
731+
SELECT
732+
"person_1"."person_id",
733+
"person_1"."gender_concept_id",
734+
"person_1"."year_of_birth",
735+
"person_1"."month_of_birth",
736+
"person_1"."day_of_birth",
737+
(now() - "person_1"."birth_datetime") AS "age",
738+
"person_1"."birth_datetime",
739+
"person_1"."location_id"
740+
FROM "person" AS "person_1"
741+
=#
742+
743+
It is an error to set both `before` and `after` or to refer to a non-existent
744+
column.
745+
746+
q = From(person) |>
747+
Define(before = true, after = true)
748+
749+
print(render(q))
750+
#=>
751+
ERROR: DomainError with (before = true, after = true):
752+
only one of `before` and `after` could be set
753+
=#
754+
755+
q = Define(before = :person_id)
756+
757+
print(render(q))
758+
#=>
759+
ERROR: FunSQL.ReferenceError: cannot find `person_id` in:
760+
let q1 = Define(before = :person_id)
761+
q1
762+
end
763+
=#
764+
642765
`Define` has no effect if none of the defined fields are used in the query.
643766

644767
q = From(person) |>

src/nodes/define.jl

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,39 @@
33
mutable struct DefineNode <: TabularNode
44
over::Union{SQLNode, Nothing}
55
args::Vector{SQLNode}
6+
before::Union{Symbol, Bool}
7+
after::Union{Symbol, Bool}
68
label_map::OrderedDict{Symbol, Int}
79

8-
function DefineNode(; over = nothing, args = [], label_map = nothing)
10+
function DefineNode(; over = nothing, args = [], before = nothing, after = nothing, label_map = nothing)
911
if label_map !== nothing
10-
new(over, args, label_map)
12+
n = new(over, args, something(before, false), something(after, false), label_map)
1113
else
12-
n = new(over, args, OrderedDict{Symbol, Int}())
14+
n = new(over, args, something(before, false), something(after, false), OrderedDict{Symbol, Int}())
1315
populate_label_map!(n)
14-
n
1516
end
17+
if (n.before isa Symbol || n.before) && (n.after isa Symbol || n.after)
18+
throw(DomainError((before = n.before, after = n.after), "only one of `before` and `after` could be set"))
19+
end
20+
n
1621
end
1722
end
1823

19-
DefineNode(args...; over = nothing) =
20-
DefineNode(over = over, args = SQLNode[args...])
24+
DefineNode(args...; over = nothing, before = nothing, after = nothing) =
25+
DefineNode(over = over, args = SQLNode[args...], before = before, after = after)
2126

2227
"""
23-
Define(; over; args = [])
24-
Define(args...; over)
28+
Define(; over; args = [], before = nothing, after = nothing)
29+
Define(args...; over, before = nothing, after = nothing)
2530
2631
The `Define` node adds or replaces output columns.
2732
33+
By default, new columns are added at the end of the column list while replaced
34+
columns retain their position. Set `after = true` (`after = <column>`) to add
35+
both new and replaced columns at the end (after a specified column).
36+
Alternatively, set `before = true` (`before = <column>`) to add both new and
37+
replaced columns at the front (before the specified column).
38+
2839
# Examples
2940
3041
*Show patients who are at least 16 years old.*
@@ -33,19 +44,19 @@ The `Define` node adds or replaces output columns.
3344
julia> person = SQLTable(:person, columns = [:person_id, :birth_datetime]);
3445
3546
julia> q = From(:person) |>
36-
Define(:age => Fun.now() .- Get.birth_datetime) |>
47+
Define(:age => Fun.now() .- Get.birth_datetime, before = :birth_datetime) |>
3748
Where(Get.age .>= "16 years");
3849
3950
julia> print(render(q, tables = [person]))
4051
SELECT
4152
"person_2"."person_id",
42-
"person_2"."birth_datetime",
43-
"person_2"."age"
53+
"person_2"."age",
54+
"person_2"."birth_datetime"
4455
FROM (
4556
SELECT
4657
"person_1"."person_id",
47-
"person_1"."birth_datetime",
48-
(now() - "person_1"."birth_datetime") AS "age"
58+
(now() - "person_1"."birth_datetime") AS "age",
59+
"person_1"."birth_datetime"
4960
FROM "person" AS "person_1"
5061
) AS "person_2"
5162
WHERE ("person_2"."age" >= '16 years')
@@ -78,6 +89,12 @@ dissect(scr::Symbol, ::typeof(Define), pats::Vector{Any}) =
7889

7990
function PrettyPrinting.quoteof(n::DefineNode, ctx::QuoteContext)
8091
ex = Expr(:call, nameof(Define), quoteof(n.args, ctx)...)
92+
if n.before !== false
93+
push!(ex.args, Expr(:kw, :before, n.before isa Symbol ? QuoteNode(n.before) : n.before))
94+
end
95+
if n.after !== false
96+
push!(ex.args, Expr(:kw, :after, n.after isa Symbol ? QuoteNode(n.after) : n.after))
97+
end
8198
if n.over !== nothing
8299
ex = Expr(:call, :|>, quoteof(n.over, ctx), ex)
83100
end

src/resolve.jl

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,44 @@ end
209209
function resolve(n::DefineNode, ctx)
210210
over′ = resolve(n.over, ctx)
211211
t = row_type(over′)
212+
anchor =
213+
n.before isa Symbol ? n.before :
214+
n.before && !isempty(t.fields) ? first(first(t.fields)) :
215+
n.after isa Symbol ? n.after :
216+
n.after && !isempty(t.fields) ? first(last(t.fields)) :
217+
nothing
218+
if anchor !== nothing && !haskey(t.fields, anchor)
219+
throw(ReferenceError(REFERENCE_ERROR_TYPE.UNDEFINED_NAME, name = anchor, path = get_path(ctx)))
220+
end
221+
before = n.before isa Symbol || n.before
222+
after = n.after isa Symbol || n.after
212223
args′ = resolve_scalar(n.args, ctx, t)
213224
fields = FieldTypeMap()
214225
for (f, ft) in t.fields
215226
i = get(n.label_map, f, nothing)
216-
if i !== nothing
217-
ft = type(args′[i])
227+
if f === anchor
228+
if after && i === nothing
229+
fields[f] = ft
230+
end
231+
for (l, j) in n.label_map
232+
fields[l] = type(args′[j])
233+
end
234+
if before && i === nothing
235+
fields[f] = ft
236+
end
237+
elseif i !== nothing
238+
if anchor === nothing
239+
fields[f] = type(args′[i])
240+
end
241+
else
242+
fields[f] = ft
218243
end
219-
fields[f] = ft
220244
end
221-
for (f, i) in n.label_map
222-
if !haskey(fields, f)
223-
fields[f] = type(args′[i])
245+
if anchor === nothing
246+
for (l, j) in n.label_map
247+
if !haskey(fields, l)
248+
fields[l] = type(args′[j])
249+
end
224250
end
225251
end
226252
n′ = Define(over = over′, args = args′, label_map = n.label_map)

0 commit comments

Comments
 (0)