test: cover explicitGroupByCollateOverridesTheColumnsDeclaredCollation
This commit is contained in:
1 parent
1024cd60b0
commit
2a6dad7aee
1 file changed
+157
@@ -160,3 +160,160 @@ class ColumnCollationTest {
|
|||||||
assertEquals(listOf("Bob" to 1L, "bob" to 1L).map { (name, count) -> listOf(text(name), int(count)) }, result.rows)
|
assertEquals(listOf("Bob" to 1L, "bob" to 1L).map { (name, count) -> listOf(text(name), int(count)) }, result.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun explicitGroupByCollateOverridesTheColumnsDeclaredCollation() {
|
||||||
|
seedNames()
|
||||||
|
val result = select("SELECT name COLLATE BINARY, COUNT(*) FROM people GROUP BY name COLLATE BINARY ORDER BY name COLLATE BINARY")
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alice" to 1L, "Bob" to 1L, "Zoe" to 1L, "bob" to 1L).map { (name, count) -> listOf(text(name), int(count)) },
|
||||||
|
result.rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selectDistinctDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||||
|
seedNames()
|
||||||
|
val result = select("SELECT DISTINCT name FROM people ORDER BY name")
|
||||||
|
assertEquals(listOf("Alice", "Bob", "Zoe").map { listOf(text(it)) }, result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selectDistinctStarDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||||
|
// A single-column table so DISTINCT *'s whole-row key is driven entirely by name's own
|
||||||
|
// declared NOCASE - with more columns involved, any other differing column (like people's
|
||||||
|
// own id) would already make every row distinct regardless of collation.
|
||||||
|
run("CREATE TABLE tags (label TEXT COLLATE NOCASE)")
|
||||||
|
run("INSERT INTO tags VALUES ('Bob'), ('bob'), ('Alice')")
|
||||||
|
val result = select("SELECT DISTINCT * FROM tags ORDER BY label")
|
||||||
|
assertEquals(listOf("Alice", "Bob").map { listOf(text(it)) }, result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selectDistinctWithNoDeclaredCollationStaysBinary() {
|
||||||
|
run("CREATE TABLE plain (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO plain VALUES (1, 'Bob'), (2, 'bob')")
|
||||||
|
val result = select("SELECT DISTINCT name FROM plain ORDER BY name")
|
||||||
|
assertEquals(listOf("Bob", "bob").map { listOf(text(it)) }, result.rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun selectDistinctOnDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||||
|
run("CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE, hits INTEGER)")
|
||||||
|
run("INSERT INTO people VALUES (1, 'Bob', 10), (2, 'bob', 20), (3, 'Alice', 5)")
|
||||||
|
val result = select("SELECT DISTINCT ON (name) name, hits FROM people ORDER BY name, id")
|
||||||
|
assertEquals(
|
||||||
|
listOf("Alice" to 5L, "Bob" to 10L).map { (name, hits) -> listOf(text(name), int(hits)) },
|
||||||
|
result.rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun countDistinctDedupsCaseVariantsThroughTheColumnsDeclaredCollation() {
|
||||||
|
seedNames()
|
||||||
|
assertEquals(int(3), select("SELECT COUNT(DISTINCT name) FROM people").rows.single().single())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `<>` forces [kestrel.engine.SelectRunner]'s nested-loop join path (only a `BinaryExpression`
|
||||||
|
* `=` condition can hash-join at all), which reuses the same collation-aware
|
||||||
|
* [kestrel.engine.Evaluator] as `WHERE`. See the `...InAnEqualityHashJoin` tests below for the
|
||||||
|
* `=` counterpart, which takes the hash-join fast path instead.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun joiningOnColumnsWhereOnlyTheLeftSideDeclaresCollationHonorsItInANonEqualityJoinCondition() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'Bob'), (2, 'Zoe')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'BOB')")
|
||||||
|
assertEquals(
|
||||||
|
listOf(listOf(int(2))),
|
||||||
|
select("SELECT left_t.id FROM left_t JOIN right_t ON left_t.name <> right_t.name").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun joiningOnColumnsWhereOnlyTheRightSideDeclaresCollationFallsBackToTheRightWhenTheLeftHasNone() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'Bob'), (2, 'Zoe')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'BOB')")
|
||||||
|
assertEquals(
|
||||||
|
listOf(listOf(int(2))),
|
||||||
|
select("SELECT left_t.id FROM left_t JOIN right_t ON left_t.name <> right_t.name").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `=` takes [kestrel.engine.SelectRunner]'s hash-join fast path (`hashJoinKeys`/`executeJoin`),
|
||||||
|
* which must bucket [Value.TextValue] keys through the exact same [kestrel.engine.pairCollation]
|
||||||
|
* the nested-loop `<>` tests above resolve through [kestrel.engine.Evaluator] - otherwise the
|
||||||
|
* "optimization" would silently change which rows match. `right_t`'s undeclared `name` still
|
||||||
|
* only matches its own literal case ('bob'), so this also proves the fast path is not simply
|
||||||
|
* ignoring case altogether.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun joiningOnColumnsWhereOnlyTheLeftSideDeclaresCollationHonorsItInAnEqualityHashJoin() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'Bob'), (2, 'Zoe')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'BOB'), (2, 'bob')")
|
||||||
|
assertEquals(
|
||||||
|
listOf(listOf(int(1), int(1)), listOf(int(1), int(2))),
|
||||||
|
select("SELECT left_t.id, right_t.id FROM left_t JOIN right_t ON left_t.name = right_t.name ORDER BY right_t.id").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun joiningOnColumnsWhereOnlyTheRightSideDeclaresCollationHonorsItInAnEqualityHashJoin() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'BOB'), (2, 'Zoe')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'Bob')")
|
||||||
|
assertEquals(
|
||||||
|
listOf(listOf(int(1))),
|
||||||
|
select("SELECT left_t.id FROM left_t JOIN right_t ON left_t.name = right_t.name").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun explicitCollateOnAnEqualityHashJoinConditionOverridesTheColumnsDeclaredCollation() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'Bob')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'BOB')")
|
||||||
|
assertEquals(
|
||||||
|
emptyList(),
|
||||||
|
select("SELECT left_t.id FROM left_t JOIN right_t ON left_t.name COLLATE BINARY = right_t.name").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun equalityHashJoinWithNoDeclaredCollationOnEitherSideStaysBinary() {
|
||||||
|
run("CREATE TABLE left_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("CREATE TABLE right_t (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO left_t VALUES (1, 'Bob')")
|
||||||
|
run("INSERT INTO right_t VALUES (1, 'BOB')")
|
||||||
|
assertEquals(
|
||||||
|
emptyList(),
|
||||||
|
select("SELECT left_t.id FROM left_t JOIN right_t ON left_t.name = right_t.name").rows
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun declaredCollationAppliesInsideAnOnConflictDoUpdateAssignment() {
|
||||||
|
run("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE, was_bob BOOLEAN)")
|
||||||
|
run("INSERT INTO t VALUES (1, 'Bob', FALSE)")
|
||||||
|
run("INSERT INTO t (id, name) VALUES (1, 'x') ON CONFLICT (id) DO UPDATE SET was_bob = (name = 'BOB')")
|
||||||
|
assertEquals(bool(true), select("SELECT was_bob FROM t WHERE id = 1").rows.single().single())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun declaredCollationAppliesInAMergeOnCondition() {
|
||||||
|
run("CREATE TABLE accounts (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE, hits INTEGER)")
|
||||||
|
run("CREATE TABLE staging (id INTEGER PRIMARY KEY, name TEXT)")
|
||||||
|
run("INSERT INTO accounts VALUES (1, 'Bob', 0)")
|
||||||
|
run("INSERT INTO staging VALUES (2, 'BOB')")
|
||||||
|
run("MERGE INTO accounts USING staging ON accounts.name = staging.name WHEN MATCHED THEN UPDATE SET hits = hits + 1")
|
||||||
|
assertEquals(int(1), select("SELECT hits FROM accounts WHERE id = 1").rows.single().single())
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user