Skip to content
101 changes: 96 additions & 5 deletions benchmarks/sql/LINQ.md

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions benchmarks/sql/aggregate_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
options gen2
options persistent_heap

require _common public

// User-supplied binary reducer: max(price - min) over a where-filtered slice. SQL
// can express this as `SELECT (MAX(price) - MIN(price))`. m3 traverses the array
// with the user-block invoked per element; m3f peels the block body and inlines
// alongside the where predicate — single pass with no per-element block invoke.

let THRESHOLD = 200

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let total = _sql(db |> select_from(type<Car>) |> _where(_.price > THRESHOLD)
|> _select(_.price) |> sum())
if (total == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let total = (arr |> _where(_.price > THRESHOLD)
|> aggregate(0, $(acc : int, c : Car) => acc + c.price))
if (total == 0) {
b->failNow()
}
}
}
def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let total = _fold(each(arr)._where(_.price > THRESHOLD)
.aggregate(0, $(acc : int, c : Car) => acc + c.price))
if (total == 0) {
b->failNow()
}
}
}

[benchmark]
def aggregate_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def aggregate_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def aggregate_match_m3f(b : B?) {
run_m3f(b, 100000)
}
58 changes: 58 additions & 0 deletions benchmarks/sql/element_at_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
options gen2
options persistent_heap

require _common public

let THRESHOLD = 500
let INDEX = 100

// SQL: SELECT ... WHERE price > T LIMIT 1 OFFSET N — engine skips N then takes 1.
// m3 materializes full filtered array then indexes [N].
// m3f counts matches and early-exits when counter hits N.

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let row = _sql(db |> select_from(type<Car>) |> _where(_.price > THRESHOLD)
|> skip(INDEX) |> _first())
if (row.price == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let row = arr |> _where(_.price > THRESHOLD) |> element_at(INDEX)
if (row.price == 0) {
b->failNow()
}
}
}
def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let row = _fold(each(arr)._where(_.price > THRESHOLD).element_at(INDEX))
if (row.price == 0) {
b->failNow()
}
}
}

[benchmark]
def element_at_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def element_at_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def element_at_match_m3f(b : B?) {
run_m3f(b, 100000)
}
66 changes: 66 additions & 0 deletions benchmarks/sql/groupby_having_hidden_sum.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
options gen2
options persistent_heap

require _common public

// _group_by(_.brand) |> _having(_._1 |> select(price) |> sum > 50000) |> _select((Brand=key, N=length)).
// SQL: SELECT brand, COUNT(*) FROM cars GROUP BY brand HAVING SUM(price) > 50000.
// Splice mode (PR-E) recognizes that the having predicate's inner-select-sum on price has
// NO matching select-side slot — the planner synthesizes a hidden accumulator slot on the
// internal table value type; the per-element loop updates it alongside `length`; result-build
// projects only the user-visible (Brand, N) and applies the having filter against the hidden
// slot via `kv._{hidden} > 50000`. Without PR-E this chain cascaded to tier 2 (full bucket-
// array materialization).

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let groups <- _sql(db |> select_from(type<Car>)
|> _group_by(_.brand)
|> _having(_._1 |> select($(c : Car) => c.price) |> sum > 50000)
|> _select((Brand = _._0, N = _._1 |> length)))
if (empty(groups)) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let groups <- (arr._group_by(_.brand)._having(_._1 |> select($(c : Car) => c.price) |> sum > 50000)._select((Brand = _._0, N = _._1 |> length)))
if (empty(groups)) {
b->failNow()
}
}
}
def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let groups <- _fold(each(arr)
._group_by(_.brand)
._having(_._1 |> select($(c : Car) => c.price) |> sum > 50000)
._select((Brand = _._0, N = _._1 |> length))
.to_array())
if (empty(groups)) {
b->failNow()
}
}
}

[benchmark]
def groupby_having_hidden_sum_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def groupby_having_hidden_sum_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def groupby_having_hidden_sum_m3f(b : B?) {
run_m3f(b, 100000)
}
57 changes: 57 additions & 0 deletions benchmarks/sql/last_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
options gen2
options persistent_heap

require _common public

let THRESHOLD = 500

// SQL: `SELECT ... WHERE price > THRESHOLD ORDER BY id DESC LIMIT 1` — engine reverses
// then takes one. m3 materializes the full filtered array then indexes [-1].
// m3f walks the source once, overwriting a single bind on each match.

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let row = _sql(db |> select_from(type<Car>) |> _where(_.price > THRESHOLD)
|> _order_by_descending(_.id) |> _first())
if (row.price == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let row = arr |> _where(_.price > THRESHOLD) |> last()
if (row.price == 0) {
b->failNow()
}
}
}
def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let row = _fold(each(arr)._where(_.price > THRESHOLD).last())
if (row.price == 0) {
b->failNow()
}
}
}

[benchmark]
def last_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def last_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def last_match_m3f(b : B?) {
run_m3f(b, 100000)
}
58 changes: 58 additions & 0 deletions benchmarks/sql/single_match.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
options gen2
options persistent_heap

require _common public

// Fixture car ids run 1..n so id=42 is unique. `single` walks the full source
// (semantically — exactly-one-match), but the splice still fuses upstream where.
//
// SQL: SELECT * FROM Cars WHERE id = 42 LIMIT 2 — read at most 2 to assert uniqueness.
// m3/m3f traverse the array filtering and asserting one survivor.

let TARGET_ID = 42

def run_m1(b : B?; n : int) {
with_sqlite(":memory:") $(db) {
fixture_db(db, n)
b |> run("m1_sql/{n}", n) {
let row = _sql(db |> select_from(type<Car>) |> _where(_.id == TARGET_ID) |> _first())
if (row.id == 0) {
b->failNow()
}
}
}
}

def run_m3(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3_array/{n}", n) {
let row = arr |> _where(_.id == TARGET_ID) |> single()
if (row.id == 0) {
b->failNow()
}
}
}
def run_m3f(b : B?; n : int) {
let arr <- fixture_array(n)
b |> run("m3f_array_fold/{n}", n) {
let row = _fold(each(arr)._where(_.id == TARGET_ID).single())
if (row.id == 0) {
b->failNow()
}
}
}

[benchmark]
def single_match_m1(b : B?) {
run_m1(b, 100000)
}

[benchmark]
def single_match_m3(b : B?) {
run_m3(b, 100000)
}

[benchmark]
def single_match_m3f(b : B?) {
run_m3f(b, 100000)
}
5 changes: 4 additions & 1 deletion daslib/linq.das
Original file line number Diff line number Diff line change
Expand Up @@ -1456,7 +1456,10 @@ def private aggregate_impl(var src; tt : auto(TT); seed : auto(AGG); func : bloc

[unused_argument(tt)]
def private aggregate_impl_const(src : auto(ARGT); tt : auto(TT); seed : auto(AGG); func : block<(acc : AGG -&, x : TT -&) : AGG -&>) : AGG -& -const {
static_if (typeinfo is_workhorse(type<TT>)) {
// Move-vs-return is decided by the accumulator type AGG (the return type), NOT the
// element type TT — those differ for aggregates that produce a workhorse acc from a
// non-workhorse element (e.g. summing prices off `array<Car>` with int seed).
static_if (typeinfo is_workhorse(type<AGG>)) {
return aggregate_impl(unsafe(reinterpret<ARGT -const>(src)), type<TT -const -&>, seed, func)
} else {
return <- aggregate_impl(unsafe(reinterpret<ARGT -const>(src)), type<TT -const -&>, seed, func)
Expand Down
Loading
Loading