diff --git a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt index 211eb46..0483ce6 100644 --- a/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt +++ b/src/test/kotlin/kestrel/store/sql/DiskExecutorTest.kt @@ -2057,3 +2057,161 @@ class DiskExecutorTest { ) } + @Test + fun countStarOverPartitionCountsRowsRegardlessOfNulls() { + val db = newDatabase() + db.seedWindowEmployees() + db.run("INSERT INTO employees VALUES (7, 3, 'unpaid', NULL)") + val result = db.select( + "SELECT COUNT(*) OVER (PARTITION BY deptId) AS n, COUNT(salary) OVER (PARTITION BY deptId) AS paid " + + "FROM employees WHERE deptId = 3" + ) + assertEquals(listOf(listOf(int(1), int(0))), result.rows) + } + + @Test + fun windowFunctionOverAJoinPartitionsByTheJoinedColumn() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select( + "SELECT e.name, d.name AS dept, " + + "ROW_NUMBER() OVER (PARTITION BY d.name ORDER BY e.salary DESC) AS rn " + + "FROM employees e JOIN departments d ON e.deptId = d.id " + + "WHERE d.name = 'eng' ORDER BY rn ASC" + ) + assertEquals( + listOf( + listOf(text("ada"), text("eng"), int(1)), + listOf(text("linus"), text("eng"), int(2)), + listOf(text("grace"), text("eng"), int(3)) + ), + result.rows + ) + } + + @Test + fun orderByCanReferenceAWindowFunctionByItsProjectionAlias() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select("SELECT id, RANK() OVER (ORDER BY salary DESC) AS r FROM employees ORDER BY r ASC, id ASC") + assertEquals((1..6).map { it.toLong() }, result.rows.map { (it[0] as Value.IntegerValue).value }) + } + + @Test + fun selectStarWithAWindowFunctionOnlyInOrderByDoesNotLeakTheExtraColumn() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select("SELECT * FROM departments ORDER BY ROW_NUMBER() OVER (ORDER BY id) DESC") + assertEquals(listOf("id", "name"), result.columns) + assertEquals(listOf(listOf(int(2), text("sales")), listOf(int(1), text("eng"))), result.rows) + } + + @Test + fun windowFunctionIsRejectedInWhere() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT id FROM employees WHERE ROW_NUMBER() OVER (ORDER BY id) = 1") + } + assertTrue(error.message!!.contains("WHERE"), error.message!!) + } + + @Test + fun windowFunctionIsRejectedInGroupBy() { + val db = newDatabase() + db.seedWindowEmployees() + assertFailsWith { + db.run("SELECT COUNT(*) FROM employees GROUP BY ROW_NUMBER() OVER (ORDER BY id)") + } + } + + @Test + fun windowFunctionIsRejectedInHaving() { + val db = newDatabase() + db.seedWindowEmployees() + assertFailsWith { + db.run("SELECT deptId, COUNT(*) FROM employees GROUP BY deptId HAVING ROW_NUMBER() OVER (ORDER BY deptId) > 1") + } + } + + @Test + fun windowFunctionIsRejectedInAnOnCondition() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT e.id FROM employees e JOIN departments d ON ROW_NUMBER() OVER (ORDER BY e.id) = d.id") + } + assertTrue(error.message!!.contains("ON condition"), error.message!!) + } + + @Test + fun windowFunctionCannotBeCombinedWithGroupByInTheSameQuery() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT deptId, COUNT(*), ROW_NUMBER() OVER (ORDER BY deptId) FROM employees GROUP BY deptId") + } + assertTrue(error.message!!.contains("combined"), error.message!!) + } + + @Test + fun rankRequiresAnOrderByInsideOver() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT RANK() OVER (PARTITION BY deptId) FROM employees") + } + assertTrue(error.message!!.contains("ORDER BY"), error.message!!) + } + + @Test + fun denseRankRequiresAnOrderByInsideOver() { + val db = newDatabase() + db.seedWindowEmployees() + assertFailsWith { + db.run("SELECT DENSE_RANK() OVER (PARTITION BY deptId) FROM employees") + } + } + + @Test + fun nestedWindowFunctionsAreRejected() { + val db = newDatabase() + db.seedWindowEmployees() + val error = assertFailsWith { + db.run("SELECT SUM(salary) OVER (ORDER BY ROW_NUMBER() OVER (ORDER BY id)) FROM employees") + } + assertTrue(error.message!!.contains("nested"), error.message!!) + } + + @Test + fun lagReturnsThePreviousRowsValueWithinEachPartitionAndNullAtTheEdge() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select( + "SELECT id, LAG(salary) OVER (PARTITION BY deptId ORDER BY id) AS prev FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf( + listOf(int(1), Value.NullValue), listOf(int(2), int(400)), listOf(int(3), int(400)), + listOf(int(4), Value.NullValue), listOf(int(5), int(200)), listOf(int(6), int(200)) + ), + result.rows + ) + } + + @Test + fun leadReturnsTheNextRowsValueWithinEachPartitionAndNullAtTheEdge() { + val db = newDatabase() + db.seedWindowEmployees() + val result = db.select( + "SELECT id, LEAD(salary) OVER (PARTITION BY deptId ORDER BY id) AS next FROM employees ORDER BY id ASC" + ) + assertEquals( + listOf( + listOf(int(1), int(400)), listOf(int(2), int(300)), listOf(int(3), Value.NullValue), + listOf(int(4), int(200)), listOf(int(5), int(100)), listOf(int(6), Value.NullValue) + ), + result.rows + ) + } +