test: cover stringAggViewSurvivesReopenAfterCheckpoint
This commit is contained in:
1 parent
69d45e0362
commit
118d58728f
1 file changed
+173
@@ -545,4 +545,177 @@ class PersistenceTest {
|
|||||||
ALTER TABLE departments RENAME TO divisions;
|
ALTER TABLE departments RENAME TO divisions;
|
||||||
.exit
|
.exit
|
||||||
""".trimIndent()
|
""".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;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user