bitemyapp / esqueleto

New home of Esqueleto, please file issues so we can get things caught up!
BSD 3-Clause "New" or "Revised" License
370 stars 107 forks source link

`lag` and `lead` function support #390

Open rinn7e opened 2 months ago

rinn7e commented 2 months ago

I want to do a select as the following:

SELECT
    LAG(a.id, 1) OVER (
        ORDER BY a.sent_at DESC
    ) previous_a,
    lead(a.id, 1) OVER (
        ORDER BY a.sent_at DESC
    ) next_a
FROM
...

Since esqueleto doesn't have lag and lead yet, I want to create it using raw sql, but I believe using unsafeSqlFuntion doesn't work due to having over clause.

Hope someone could point me to the right direction.

rinn7e commented 2 months ago

Found a work around, inspired from unsafeSqlAggFunction:

unsafeSqlWindowFunction
    :: UnsafeSqlFunctionArgument a
    => TLB.Builder
    -> a
    -> [OrderByClause]
    -> SqlExpr (Value b)
unsafeSqlWindowFunction name args orderByClauses = ERaw noMeta $ \_ info ->
    let (orderTLB, orderVals) = makeOrderByNoNewline info orderByClauses
        (argsTLB, argsVals) =
            uncommas' $ map (\(ERaw _ f) -> f Never info) $ toArgList args

    in ( name <> parens (argsTLB) <> " OVER " <> parens(orderTLB)
       , argsVals <> orderVals
       )

lag :: SqlExpr (Value a) -> SqlExpr (Value Int64) -> [OrderByClause] -> SqlExpr (Value (Maybe a))
lag fieldArg offsetArg clause = unsafeSqlWindowFunction "lag" (fieldArg, offsetArg) clause

lead :: SqlExpr (Value a) -> SqlExpr (Value Int64) -> [OrderByClause] -> SqlExpr (Value (Maybe a))
lead fieldArg offsetArg clause = unsafeSqlWindowFunction "lead" (fieldArg, offsetArg) clause