test: cover declaredCollationAppliesInATriggerWhenClauseOnNew
This commit is contained in:
1 parent
2a6dad7aee
commit
b870a00b06
1 file changed
+165
@@ -317,3 +317,168 @@ class ColumnCollationTest {
|
||||
assertEquals(int(1), select("SELECT hits FROM accounts WHERE id = 1").rows.single().single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun declaredCollationAppliesInATriggerWhenClauseOnNew() {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||
run("CREATE TABLE hits (id INTEGER PRIMARY KEY, count INTEGER)")
|
||||
run("INSERT INTO t VALUES (1, 'Bob')")
|
||||
run("INSERT INTO hits VALUES (1, 0)")
|
||||
run(
|
||||
"CREATE TRIGGER bump AFTER UPDATE ON t FOR EACH ROW WHEN NEW.name = 'BOB' " +
|
||||
"BEGIN UPDATE hits SET count = count + 100 WHERE id = NEW.id; END"
|
||||
)
|
||||
run("UPDATE t SET name = 'bob' WHERE id = 1")
|
||||
assertEquals(int(100), select("SELECT count FROM hits WHERE id = 1").rows.single().single())
|
||||
}
|
||||
|
||||
/**
|
||||
* `UNION`/`INTERSECT`/`EXCEPT`'s own `ORDER BY` ([kestrel.engine.SelectRunner.
|
||||
* declaredSetOperationCollation]) - the last named gap left by #66 ("multiKeyComparator-owa
|
||||
* sciezka UNION/INTERSECT/EXCEPT ORDER BY dalej binarna"). A `SELECT *` branch's declared
|
||||
* collation is traced back to the real table each output column comes from.
|
||||
*/
|
||||
@Test
|
||||
fun declaredCollationAppliesToASetOperationsOrderByThroughAStarProjection() {
|
||||
seedNames()
|
||||
val result = select(
|
||||
"SELECT * FROM people WHERE id <= 2 UNION ALL SELECT * FROM people WHERE id > 2 ORDER BY name"
|
||||
)
|
||||
// Combined pre-sort order is (1,Bob),(2,bob),(3,Alice),(4,Zoe) - NOCASE ranks Bob/bob equal,
|
||||
// so the stable sort keeps Bob (scanned first) ahead of bob.
|
||||
assertEquals(
|
||||
listOf(int(3) to "Alice", int(1) to "Bob", int(2) to "bob", int(4) to "Zoe").map { (id, name) -> listOf(id, text(name)) },
|
||||
result.rows
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun declaredCollationAppliesToASetOperationsOrderByThroughAnAliasedProjection() {
|
||||
seedNames()
|
||||
val result = select(
|
||||
"SELECT name AS n FROM people WHERE id <= 2 UNION ALL SELECT name AS n FROM people WHERE id > 2 ORDER BY n"
|
||||
)
|
||||
assertEquals(listOf("Alice", "Bob", "bob", "Zoe").map { listOf(text(it)) }, result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun explicitCollateInASetOperationsOrderByOverridesTheColumnsDeclaredCollation() {
|
||||
seedNames()
|
||||
val result = select(
|
||||
"SELECT * FROM people WHERE id <= 2 UNION ALL SELECT * FROM people WHERE id > 2 ORDER BY name COLLATE BINARY"
|
||||
)
|
||||
assertEquals(listOf("Alice", "Bob", "Zoe", "bob"), result.rows.map { (it[1] as Value.TextValue).value })
|
||||
}
|
||||
|
||||
/**
|
||||
* The declared collation of an output column joined from a second `FROM` source is found past
|
||||
* the first source's own column count - proves `declaredSetOperationCollation`'s per-source
|
||||
* offset walk, not just the single-table case above.
|
||||
*/
|
||||
@Test
|
||||
fun declaredCollationAppliesToASetOperationsOrderByAcrossAJoinedSecondTable() {
|
||||
run("CREATE TABLE ids (id INTEGER PRIMARY KEY)")
|
||||
run("CREATE TABLE names (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||
run("INSERT INTO ids VALUES (1), (2), (3), (4)")
|
||||
run("INSERT INTO names VALUES (1, 'Bob'), (2, 'bob'), (3, 'Alice'), (4, 'Zoe')")
|
||||
val result = select(
|
||||
"SELECT * FROM ids JOIN names ON ids.id = names.id WHERE ids.id <= 2 " +
|
||||
"UNION ALL SELECT * FROM ids JOIN names ON ids.id = names.id WHERE ids.id > 2 " +
|
||||
"ORDER BY name"
|
||||
)
|
||||
assertEquals(listOf("Alice", "Bob", "bob", "Zoe"), result.rows.map { (it[2] as Value.TextValue).value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Still-open scope cut (see [kestrel.engine.SelectRunner.declaredSetOperationCollation]'s own
|
||||
* doc comment): a set operation's left branch reading through a derived table rather than a
|
||||
* real table directly reports no declared collation, so its ORDER BY stays binary even though
|
||||
* the underlying table declares NOCASE - symmetric with the pre-existing view/CTE scope cut for
|
||||
* a plain SELECT's own ORDER BY.
|
||||
*/
|
||||
@Test
|
||||
fun declaredCollationDoesNotReachThroughADerivedTableInASetOperationsLeftBranch() {
|
||||
seedNames()
|
||||
val result = select(
|
||||
"SELECT * FROM (SELECT * FROM people) sub WHERE id <= 2 " +
|
||||
"UNION ALL SELECT * FROM (SELECT * FROM people) sub2 WHERE id > 2 " +
|
||||
"ORDER BY name"
|
||||
)
|
||||
assertEquals(listOf("Alice", "Bob", "Zoe", "bob"), result.rows.map { (it[1] as Value.TextValue).value })
|
||||
}
|
||||
|
||||
/**
|
||||
* `UNION`/`INTERSECT`/`EXCEPT`'s own multiset dedup ([kestrel.engine.SelectRunner.
|
||||
* runSetOperation]) - the largest piece of the scope-cut #67/#68 left open ("UNION/CTE dedup ...
|
||||
* dalej uzywaja bajtowej rownosci"). Reuses [kestrel.engine.SelectRunner.declaredSetOperationCollation],
|
||||
* the same resolver the ORDER BY tests above already exercise, but now folded into the dedup key
|
||||
* itself rather than only the comparator.
|
||||
*/
|
||||
@Test
|
||||
fun plainUnionDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||
seedNames()
|
||||
val result = select("SELECT name FROM people WHERE id = 1 UNION SELECT name FROM people WHERE id = 2")
|
||||
assertEquals(listOf(listOf(text("Bob"))), result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun plainUnionWithNoDeclaredCollationKeepsCaseVariantsDistinct() {
|
||||
run("CREATE TABLE plain2 (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
run("INSERT INTO plain2 VALUES (1, 'Bob'), (2, 'bob')")
|
||||
val result = select("SELECT name FROM plain2 WHERE id = 1 UNION SELECT name FROM plain2 WHERE id = 2")
|
||||
assertEquals(listOf(listOf(text("Bob")), listOf(text("bob"))), result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intersectMatchesCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||
seedNames()
|
||||
val result = select("SELECT name FROM people WHERE id = 1 INTERSECT SELECT name FROM people WHERE id = 2")
|
||||
assertEquals(listOf(listOf(text("Bob"))), result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun exceptExcludesCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||
seedNames()
|
||||
val result = select("SELECT name FROM people WHERE id IN (1, 4) EXCEPT SELECT name FROM people WHERE id = 2")
|
||||
assertEquals(listOf(listOf(text("Zoe"))), result.rows)
|
||||
}
|
||||
|
||||
/**
|
||||
* `INTERSECT ALL`/`EXCEPT ALL` preserve duplicate counts through [kestrel.engine.SelectRunner.
|
||||
* multisetFilter], which grouped its right-hand side by raw `Value` equality before this session
|
||||
* - proving the count itself (not just presence/absence) is now grouped through the declared
|
||||
* collation: 3 case-variant 'Bob' rows on the left, 2 on the right, min(3,2)=2 kept.
|
||||
*/
|
||||
@Test
|
||||
fun intersectAllMatchesCaseVariantsThroughTheColumnsDeclaredCollationAndPreservesDuplicateCounts() {
|
||||
run("CREATE TABLE dup (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||
run("INSERT INTO dup VALUES (1, 'Bob'), (2, 'Bob'), (3, 'bob'), (4, 'Zoe')")
|
||||
val result = select(
|
||||
"SELECT name FROM dup WHERE id IN (1, 2, 3) INTERSECT ALL SELECT name FROM dup WHERE id IN (2, 3)"
|
||||
)
|
||||
assertEquals(listOf(listOf(text("Bob")), listOf(text("Bob"))), result.rows)
|
||||
}
|
||||
|
||||
/**
|
||||
* A recursive CTE's own fixed-point dedup ([kestrel.engine.SelectRunner.buildCteScope]), folded
|
||||
* through [kestrel.engine.SelectRunner.declaredRecursiveCteCollation] resolved against the
|
||||
* anchor (base) term - mirrors [declaredSetOperationCollation] but for the base/recursive-term
|
||||
* split instead of a set operation's left/right. `steps` chains 'Bob' -> 'Carol' -> 'bob'; without
|
||||
* collation-aware dedup 'bob' would be treated as a new row (it differs from 'Bob' byte-for-byte)
|
||||
* and the walk would keep going instead of recognizing it as the already-visited start node.
|
||||
*/
|
||||
@Test
|
||||
fun recursiveCteWithUnionDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||
run("CREATE TABLE origin (name TEXT COLLATE NOCASE)")
|
||||
run("INSERT INTO origin VALUES ('Bob')")
|
||||
run("CREATE TABLE steps (name TEXT COLLATE NOCASE, next_name TEXT COLLATE NOCASE)")
|
||||
run("INSERT INTO steps VALUES ('Bob', 'Carol'), ('Carol', 'bob')")
|
||||
val result = select(
|
||||
"WITH RECURSIVE walk(name) AS (" +
|
||||
"SELECT name FROM origin " +
|
||||
"UNION " +
|
||||
"SELECT steps.next_name FROM steps JOIN walk ON steps.name = walk.name" +
|
||||
") SELECT name FROM walk ORDER BY name"
|
||||
)
|
||||
assertEquals(listOf(listOf(text("Bob")), listOf(text("Carol"))), result.rows)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user