test: cover stringAggViewSurvivesReopenAfterCheckpoint

This commit is contained in:
dualfroz committed 2026-09-09 18:33:50 +02:00
1 parent 69d45e0362
commit 118d58728f
1 file changed
+173
+173
View File
@@ -545,4 +545,177 @@ class PersistenceTest {
ALTER TABLE departments RENAME TO divisions;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT * FROM v;\n.exit\n")
assertContains(reopened, "eng")
}
@Test
fun stringAggViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE tags (item TEXT, label TEXT);
INSERT INTO tags VALUES ('widget', 'blue'), ('widget', 'small'), ('gadget', 'red');
CREATE VIEW item_labels AS SELECT item, STRING_AGG(label, ', ' ORDER BY label) AS labels FROM tags GROUP BY item;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT item, labels FROM item_labels ORDER BY item;\n.exit\n")
assertContains(reopened, "blue, small")
assertContains(reopened, "red")
}
/**
* `COUNT(DISTINCT ...)`'s `distinct` flag is a field on [kestrel.sql.ast.FunctionExpression] just
* like STRING_AGG's `separator`/`orderBy` above - this proves [kestrel.persistence.SqlWriter]
* actually dumps the `DISTINCT` keyword back out (not just the wrapped argument), so a view built
* on it does not silently turn into a plain, non-deduplicating `COUNT` after reopen.
*/
@Test
fun countDistinctViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE sales (dept TEXT, amount INTEGER);
INSERT INTO sales VALUES ('eng', 100), ('eng', 100), ('eng', 200), ('ops', 100), ('ops', 100);
CREATE VIEW dept_distinct_amounts AS SELECT dept, COUNT(DISTINCT amount) AS n FROM sales GROUP BY dept;
.exit
""".trimIndent()
)
val reopened = runScript(
dir,
"SELECT n FROM dept_distinct_amounts WHERE dept = 'eng';\n" +
"SELECT n FROM dept_distinct_amounts WHERE dept = 'ops';\n.exit\n"
)
assertContains(reopened, "| 2 |")
assertContains(reopened, "| 1 |")
}
/**
* `FILTER (WHERE ...)`'s predicate is a field on [kestrel.sql.ast.FunctionExpression] just like
* STRING_AGG's `separator`/`orderBy` and COUNT's `distinct` above - this proves
* [kestrel.persistence.SqlWriter] actually dumps the `FILTER (WHERE ...)` clause back out, so a
* view built on it does not silently turn into an unfiltered aggregate after reopen.
*/
@Test
fun filteredAggregateViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE sales (dept TEXT, amount INTEGER);
INSERT INTO sales VALUES ('eng', 100), ('eng', 200), ('ops', 100), ('ops', 100);
CREATE VIEW dept_high_value_counts AS SELECT dept, COUNT(*) FILTER (WHERE amount > 100) AS n FROM sales GROUP BY dept;
.exit
""".trimIndent()
)
val reopened = runScript(
dir,
"SELECT n FROM dept_high_value_counts WHERE dept = 'eng';\n" +
"SELECT n FROM dept_high_value_counts WHERE dept = 'ops';\n.exit\n"
)
assertContains(reopened, "| 1 |")
assertContains(reopened, "| 0 |")
}
/**
* [kestrel.catalog.RelationRename] rewrites a renamed table's name everywhere it could appear in a
* dependent view's stored query, including inside a nested subquery - this proves that recursion
* now also reaches a subquery sitting inside an aggregate's `FILTER (WHERE ...)` predicate
* specifically (a [kestrel.sql.ast.FunctionExpression] field distinct from `argument`, easy to miss
* when only `argument` gets rewritten), not just a subquery in WHERE/projection.
*/
@Test
fun renameTablePropagatesIntoAViewsAggregateFilterSubquery() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE items (id INTEGER PRIMARY KEY, amount INTEGER);
INSERT INTO items VALUES (1, 100), (2, 200);
CREATE TABLE config (id INTEGER PRIMARY KEY, threshold INTEGER);
INSERT INTO config VALUES (1, 150);
CREATE VIEW joined AS SELECT COUNT(*) FILTER (WHERE amount > (SELECT threshold FROM config WHERE id = 1)) AS n FROM items;
ALTER TABLE config RENAME TO settings;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT n FROM joined;\n.exit\n")
assertContains(reopened, "| 1 |")
}
/**
* [kestrel.catalog.RelationRename] rewrites a renamed table's name everywhere it could appear in a
* dependent view's stored query, including inside a nested subquery - this proves that recursion
* now also reaches a subquery sitting inside STRING_AGG's `separator` argument specifically (a
* [kestrel.sql.ast.FunctionExpression] field distinct from `argument`, easy to miss when only
* `argument` gets rewritten), not just a subquery in WHERE/projection.
*/
@Test
fun renameTablePropagatesIntoAViewsStringAggSeparatorSubquery() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE items (id INTEGER PRIMARY KEY, label TEXT);
INSERT INTO items VALUES (1, 'b'), (2, 'a');
CREATE TABLE config (id INTEGER PRIMARY KEY, sep TEXT);
INSERT INTO config VALUES (1, ',');
CREATE VIEW joined AS SELECT STRING_AGG(label, (SELECT sep FROM config WHERE id = 1) ORDER BY label) AS labels FROM items;
ALTER TABLE config RENAME TO settings;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT labels FROM joined;\n.exit\n")
assertContains(reopened, "a,b")
}
@Test
fun distinctViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE sales (dept TEXT);
INSERT INTO sales VALUES ('eng'), ('eng'), ('ops');
CREATE VIEW depts AS SELECT DISTINCT dept FROM sales;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT dept FROM depts ORDER BY dept;\n.exit\n")
assertContains(reopened, "2 rows")
assertContains(reopened, "eng")
assertContains(reopened, "ops")
}
@Test
fun limitAndOffsetViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE sales (dept TEXT, amount INTEGER);
INSERT INTO sales VALUES ('eng', 300), ('eng', 200), ('ops', 100);
CREATE VIEW second_highest AS SELECT dept, amount FROM sales ORDER BY amount DESC LIMIT 1 OFFSET 1;
.exit
""".trimIndent()
)
val reopened = runScript(dir, "SELECT dept, amount FROM second_highest;\n.exit\n")
assertContains(reopened, "1 row")
assertContains(reopened, "200")
assertTrue(!reopened.contains("300"), "amount=300 was skipped by OFFSET and must not survive")
}
@Test
fun distinctOnViewSurvivesReopenAfterCheckpoint() {
val dir = tempDir()
runScript(
dir,
"""
CREATE TABLE sales (dept TEXT, amount INTEGER);
INSERT INTO sales VALUES ('eng', 100), ('eng', 200), ('ops', 50);
CREATE VIEW top_per_dept AS SELECT DISTINCT ON (dept) dept, amount FROM sales ORDER BY dept, amount DESC;
}