Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# TidierDB.jl updates
## v.8.8 - 2025-08-05
##v.8.9 - 2025-08-08
- fixes window keyword parsing in `@mutate` that breaking nightly runs

## v.8.8 - 2025-08-07
- fixes issue when grouping on a join id
- fixes cte construction issue
- fixes select before join order execution issue
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TidierDB"
uuid = "86993f9b-bbba-4084-97c5-ee15961ad48b"
authors = ["Daniel Rizk <rizk.daniel.12@gmail.com> and contributors"]
version = "0.8.8"
version = "0.8.9"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ TidierDB.jl currently supports the following top-level macros:
| **TidierDates.jl Functions** | `year`, `month`, `day`, `hour`, `min`, `second`, `floor_date`, `difftime`, `mdy`, `ymd`, `dmy` |
| **Aggregate Functions** | `mean`, `minimum`, `maximum`, `std`, `sum`, `cumsum`, and nearly all aggregate sql fxns supported |
| **Window Functions** | `lead`, `lag`, `dense_rank`, `nth_value`, `ntile`, `rank_dense`, `row_number`, `first_value`, `last_value`, `cume_dist`, `@window_order`, `@window_frame`, or `_by`, `_order`, and `_frame` within `@mutate` |
|**Unnesting** | `unnest_wider` and `unnest_longer` |
|**Unnesting** | `@unnest_wider` and `@unnest_longer` |

`@summarize` supports any SQL aggregate function in addition to the list above. Simply write the function as written in SQL syntax and it will work.
`@mutate` supports all builtin SQL functions as well.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TidierDB.jl currently supports:
| **TidierDates.jl Functions** | `year`, `month`, `day`, `hour`, `min`, `second`, `floor_date`, `difftime`, `mdy`, `ymd`, `dmy` |
| **Aggregate Functions** | `mean`, `minimum`, `maximum`, `std`, `sum`, `cumsum`, and nearly all aggregate sql fxns supported |
| **Window Functions** | `lead`, `lag`, `dense_rank`, `nth_value`, `ntile`, `rank_dense`, `row_number`, `first_value`, `last_value`, `cume_dist`, `@window_order`, `@window_frame`, or `_by`, `_order`, and `_frame` within `@mutate` |
|**Unnesting** | `unnest_wider` and `unnest_longer` |
|**Unnesting** | `@unnest_wider` and `@unnest_longer` |

`@summarize` supports any SQL aggregate function in addition to the list above. Simply write the function as written in SQL syntax and it will work.
`@mutate` supports all builtin SQL functions as well.
Expand Down
1 change: 0 additions & 1 deletion src/db_parsing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,6 @@ function construct_window_clause(sq::SQLQuery ; from_cumsum::Bool = false)
# Ensure to include space only when needed to avoid syntax issues
partition_and_order_clause = partition_clause * (!isempty(order_clause) ? " " * order_clause : "")
window_clause = (!isempty(partition_clause) || !isempty(order_clause) || !isempty(frame_clause)) ? "OVER ($partition_and_order_clause $frame_clause)" : "OVER ()"

return window_clause
end

Expand Down
70 changes: 37 additions & 33 deletions src/mutate_and_summ.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,47 @@ function parse_mutate(mutations)
frame_var = nothing
new_mutations = []

for expr in mutations
if isa(expr, Expr) && expr.head == :(=) && expr.args[1] == :_by
for (i, expr) in enumerate(mutations)


if isa(expr, Expr) && (expr.head == :(=) || expr.head == :kw) && expr.args[1] == :_by
arg2 = expr.args[2]
if isa(arg2, Expr) && arg2.head == :vect
grouping_var = join([symbol_to_string(arg) for arg in arg2.args], ", ")
else
grouping_var = symbol_to_string(arg2)
end
elseif isa(expr, Expr) && expr.head == :(=) && expr.args[1] == :_order
arg2 = expr.args[2]
if isa(arg2, Expr) && arg2.head == :vect
order_var = join([symbol_to_string(arg) for arg in arg2.args], ", ")
elseif isa(arg2, Expr) && arg2.head == :call && arg2.args[1] == :desc
if isa(arg2.args[2], Expr) && arg2.args[2].head == :vect
order_var = join([symbol_to_string(arg) for arg in arg2.args[2].args], ", ")
order_var = "DESC " * order_var
else
order_var = "DESC " * string(arg2.args[2])
end
else
order_var = symbol_to_string(arg2)
end
elseif isa(expr, Expr) && expr.head == :(=) && expr.args[1] == :_frame
arg2 = expr.args[2]
if isa(arg2, Expr) && arg2.head == :vect
frame_var = join([symbol_to_string(arg) for arg in arg2.args], ", ")
elseif isa(arg2, Expr) && arg2.head == :call && arg2.args[1] == :desc
frame_var = "DESC " * string(arg2.args[2])

elseif isa(expr, Expr) && (expr.head == :(=) || expr.head == :kw) && expr.args[1] == :_order
arg2 = expr.args[2]
if isa(arg2, Expr) && arg2.head == :vect
order_var = join([symbol_to_string(arg) for arg in arg2.args], ", ")
elseif isa(arg2, Expr) && arg2.head == :call && arg2.args[1] == :desc
if isa(arg2.args[2], Expr) && arg2.args[2].head == :vect
order_var = join([symbol_to_string(arg) for arg in arg2.args[2].args], ", ")
order_var = "DESC " * order_var
else
frame_var = symbol_to_string(arg2)
order_var = "DESC " * string(arg2.args[2])
end
else
order_var = symbol_to_string(arg2)
end

elseif isa(expr, Expr) && (expr.head == :(=) || expr.head == :kw) && expr.args[1] == :_frame
arg2 = expr.args[2]
if isa(arg2, Expr) && arg2.head == :vect
frame_var = join([symbol_to_string(arg) for arg in arg2.args], ", ")
elseif isa(arg2, Expr) && arg2.head == :call && arg2.args[1] == :desc
frame_var = "DESC " * string(arg2.args[2])
else
frame_var = symbol_to_string(arg2)
end

else
push!(new_mutations, expr)
end
end

return grouping_var, new_mutations, order_var, frame_var
end

Expand All @@ -54,6 +60,7 @@ function symbol_to_string(s)
end
end


function process_mutate_expression(expr, sq, select_expressions, cte_name; from_transmute::Bool = false)

if isa(expr, Expr) && expr.head == :(=) && isa(expr.args[1], Symbol)
Expand Down Expand Up @@ -186,12 +193,12 @@ macro mutate(sqlquery, mutations...)
end

if $(esc(order_var)) != nothing
TidierDB.@window_order(sq, ($order_var))
TidierDB.window_order!(sq, ($order_var))
end

if $(esc(frame_var)) != nothing
TidierDB.@window_frame(sq, ($frame_var))
end
TidierDB.window_frame!(sq, ($frame_var))
end

for expr in $mutations
# Transform 'across' expressions first
Expand Down Expand Up @@ -588,15 +595,12 @@ macro transmute(sqlquery, mutations...)
group_vars_sql = expr_to_sql(group_vars, sq)
sq.groupBy = "GROUP BY " * string(group_vars_sql)
end

if $(esc(order_var)) != nothing
TidierDB.@window_order(sq, ($order_var))
TidierDB.window_order!(sq, ($order_var))
end

if $(esc(frame_var)) != nothing
TidierDB.@window_frame(sq, ($frame_var))
end

TidierDB.window_frame!(sq, ($frame_var))
end
for expr in $mutations
# Transform 'across' expressions first
if isa(expr, Expr) && expr.head == :call && expr.args[1] == :across
Expand Down
76 changes: 76 additions & 0 deletions src/windows.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,79 @@ macro window_frame(sqlquery, args...)
sq
end
end

function window_order!(sq, order_by_expr...)
@assert sq isa SQLQuery "Expected sqlquery to be an instance of SQLQuery"
order_specs = String[]
for expr in order_by_expr
if expr isa Expr && expr.head == :call && expr.args[1] == :desc
push!(order_specs, string(expr.args[2]) * " DESC")
elseif expr isa String && startswith(expr, "DESC")
push!(order_specs, string(replace(expr, "DESC" => "")) * " DESC")
elseif expr isa String || expr isa Symbol
push!(order_specs, string(expr) * " ASC")
else
throw("Unsupported column specification in window_order!: $expr")
end
end
sq.window_order = join(order_specs, ", ")

if sq.post_aggregation
sq.post_aggregation = false
cte_name = "cte_" * string(sq.cte_count + 1)
cte_sql = "SELECT * FROM " * sq.from
if !isempty(sq.where)
cte_sql *= " WHERE " * sq.where
end
new_cte = CTE(name=cte_name, select=cte_sql, from=sq.from)
up_cte_name(sq, cte_name)
push!(sq.ctes, new_cte)
sq.cte_count += 1
sq.from = cte_name
end
return sq
end

function window_frame!(sq, args...)
@assert sq isa SQLQuery "Expected sqlquery to be an instance of SQLQuery"

frame_from_value = nothing
frame_to_value = nothing

if length(args) == 1 && args[1] isa Tuple && length(args[1]) == 2
frame_from_value, frame_to_value = args[1]
elseif length(args) == 1
frame_from_value = args[1]
elseif length(args) >= 2
frame_from_value, frame_to_value = args[1], args[2]
end

frame_start_clause = ""
frame_end_clause = ""

if frame_from_value !== nothing && frame_to_value === nothing
frame_start_clause = frame_from_value == 0 ? "CURRENT ROW" :
frame_from_value < 0 ? string(abs(frame_from_value), " PRECEDING") :
string(abs(frame_from_value), " FOLLOWING")
frame_end_clause = "UNBOUNDED FOLLOWING"
elseif frame_from_value === nothing && frame_to_value !== nothing
frame_end_clause = frame_to_value == 0 ? "CURRENT ROW" :
frame_to_value < 0 ? string(abs(frame_to_value), " PRECEDING") :
string(abs(frame_to_value), " FOLLOWING")
frame_start_clause = "UNBOUNDED PRECEDING"
elseif frame_from_value !== nothing && frame_to_value !== nothing
frame_start_clause = frame_from_value == 0 ? "CURRENT ROW" :
frame_from_value < 0 ? string(abs(frame_from_value), " PRECEDING") :
string(abs(frame_from_value), " FOLLOWING")
frame_end_clause = frame_to_value == 0 ? "CURRENT ROW" :
frame_to_value < 0 ? string(abs(frame_to_value), " PRECEDING") :
string(abs(frame_to_value), " FOLLOWING")
else
frame_start_clause = "UNBOUNDED PRECEDING"
frame_end_clause = "UNBOUNDED FOLLOWING"
end

sq.windowFrame = "ROWS BETWEEN $(frame_start_clause) AND $(frame_end_clause)"
return sq
end