Latu.Window (latu v0.1.0)

Copy Markdown View Source

A window specification: how to partition, how to order, and which rows the frame covers.

alias Latu.Window, as: W

W.partition_by([:suburb])
|> W.order_by([desc(:price)])
|> W.rows_between(:unbounded_preceding, :current_row)

Alias it, as PySpark does. order_by/2 would collide with Latu.order_by/2 on import.

There is no window relation and no window message of its own: Latu.Column.over/2 folds this into an Expression.Window that rides inside an ordinary Project or WithColumns. So this struct is client-side state, like Latu.GroupedData and Latu.CaseWhen, and reaches the wire only through Latu.Column.over/2.

Not to be confused with Latu.Functions.window/2, Spark's tumbling time window, which is an ordinary function over a timestamp column and has nothing to do with this.

Frame boundaries

:current_row, :unbounded_preceding (lower only), :unbounded_following (upper only), or an integer offset. Zero is :current_row — Spark has no other reading of it, and PySpark encodes it that way, so rows_between(0, 2) and rows_between(:current_row, 2) are the same plan.

rows_between/3 counts rows and range_between/3 counts values of the ordering column, so the latter needs exactly one order_by key to mean anything.

Summary

Types

The two unbounded atoms are positional; see rows_between/3.

t()

Functions

Order within each partition. Takes column names or sort keys.

Replace the ordering of an existing specification.

Partition the rows. Starts a specification, or replaces the partitioning of one.

Replace the partitioning of an existing specification.

A frame counted in values of the ordering column. rows_between/3 counts rows instead.

A frame counted in rows, relative to the current one.

Types

boundary()

@type boundary() ::
  :current_row | :unbounded_preceding | :unbounded_following | integer()

The two unbounded atoms are positional; see rows_between/3.

t()

@type t() :: %Latu.Window{
  frame: nil | {:rows | :range, boundary(), boundary()},
  orders: [Latu.Plan.sort_order()],
  partitions: [Latu.Plan.expression()] | nil
}

Functions

order_by(columns)

@spec order_by([term()] | term()) :: t()

Order within each partition. Takes column names or sort keys.

A bare name sorts ascending with nulls first, which is Latu.Column.asc/1's rule and SQL's.

order_by(window, columns)

@spec order_by(t(), [term()] | term()) :: t()

Replace the ordering of an existing specification.

partition_by(columns)

@spec partition_by([term()] | term()) :: t()

Partition the rows. Starts a specification, or replaces the partitioning of one.

A window nobody partitioned moves every row to a single partition; Latu.Column.over/2 says so out loud, as PySpark does. partition_by([]) is the explicit global window, and quiet.

A single column needs no list, as everywhere else.

partition_by(window, columns)

@spec partition_by(t(), [term()] | term()) :: t()

Replace the partitioning of an existing specification.

range_between(window, lower, upper)

@spec range_between(t(), boundary(), boundary()) :: t()

A frame counted in values of the ordering column. rows_between/3 counts rows instead.

rows_between(window, lower, upper)

@spec rows_between(t(), boundary(), boundary()) :: t()

A frame counted in rows, relative to the current one.

W.partition_by([:suburb]) |> W.rows_between(-1, 1)

Offsets are 32-bit here and 64-bit in range_between/3, which is Spark's asymmetry, not a choice — see docs/decisions.md.