test: cover UniqueConstraintTest and run
This commit is contained in:
1 parent
464777ed2c
commit
e1a2106130
1 file changed
+157
@@ -0,0 +1,157 @@
|
||||
package kestrel
|
||||
|
||||
import kestrel.catalog.Catalog
|
||||
import kestrel.engine.Executor
|
||||
import kestrel.engine.InsertResult
|
||||
import kestrel.engine.SelectResult
|
||||
import kestrel.engine.StatementResult
|
||||
import kestrel.engine.UpdateResult
|
||||
import kestrel.sql.lexer.Lexer
|
||||
import kestrel.sql.parser.Parser
|
||||
import kestrel.storage.Value
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class UniqueConstraintTest {
|
||||
private val catalog = Catalog()
|
||||
private val executor = Executor(catalog)
|
||||
|
||||
private fun run(sql: String): List<StatementResult> =
|
||||
Parser(Lexer(sql).tokenize()).parseProgram().map { executor.execute(it) }
|
||||
|
||||
private fun exec(sql: String): StatementResult = run(sql).single()
|
||||
|
||||
private fun select(sql: String): SelectResult = exec(sql) as SelectResult
|
||||
|
||||
private fun int(value: Long) = Value.IntegerValue(value)
|
||||
private fun text(value: String) = Value.TextValue(value)
|
||||
|
||||
@Test
|
||||
fun columnLevelUniqueRejectsDuplicateValue() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("INSERT INTO users VALUES (1, 'ada@example.com')")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("INSERT INTO users VALUES (2, 'ada@example.com')")
|
||||
}
|
||||
assertTrue(error.message!!.contains("UNIQUE constraint"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun columnLevelUniqueAllowsMultipleNullValues() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("INSERT INTO users VALUES (1, NULL)")
|
||||
val result = exec("INSERT INTO users VALUES (2, NULL)")
|
||||
assertEquals(InsertResult(1), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tableLevelCompositeUniqueRejectsDuplicateCombination() {
|
||||
run(
|
||||
"CREATE TABLE enrollments (" +
|
||||
"student_id INTEGER, course_id INTEGER, term TEXT, " +
|
||||
"UNIQUE (student_id, course_id, term))"
|
||||
)
|
||||
run("INSERT INTO enrollments VALUES (1, 10, 'fall')")
|
||||
assertFailsWith<KestrelException> {
|
||||
run("INSERT INTO enrollments VALUES (1, 10, 'fall')")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tableLevelCompositeUniqueAllowsDifferingCombinations() {
|
||||
run(
|
||||
"CREATE TABLE enrollments (" +
|
||||
"student_id INTEGER, course_id INTEGER, term TEXT, " +
|
||||
"UNIQUE (student_id, course_id, term))"
|
||||
)
|
||||
run("INSERT INTO enrollments VALUES (1, 10, 'fall')")
|
||||
val bySameStudentDifferentCourse = exec("INSERT INTO enrollments VALUES (1, 11, 'fall')")
|
||||
assertEquals(InsertResult(1), bySameStudentDifferentCourse)
|
||||
val bySameStudentAndCourseDifferentTerm = exec("INSERT INTO enrollments VALUES (1, 10, 'spring')")
|
||||
assertEquals(InsertResult(1), bySameStudentAndCourseDifferentTerm)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tableLevelCompositeUniqueExemptsRowsWithAnyNullColumn() {
|
||||
run(
|
||||
"CREATE TABLE enrollments (" +
|
||||
"student_id INTEGER, course_id INTEGER, term TEXT, " +
|
||||
"UNIQUE (student_id, course_id, term))"
|
||||
)
|
||||
run("INSERT INTO enrollments VALUES (1, 10, NULL)")
|
||||
val result = exec("INSERT INTO enrollments VALUES (1, 10, NULL)")
|
||||
assertEquals(InsertResult(1), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateRejectsUniqueViolationAgainstAnotherRow() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("INSERT INTO users VALUES (1, 'ada@example.com'), (2, 'grace@example.com')")
|
||||
assertFailsWith<KestrelException> {
|
||||
run("UPDATE users SET email = 'ada@example.com' WHERE id = 2")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateToSameRowsOwnValueIsNotAViolation() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("INSERT INTO users VALUES (1, 'ada@example.com')")
|
||||
val result = exec("UPDATE users SET email = 'ada@example.com' WHERE id = 1")
|
||||
assertEquals(UpdateResult(1), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uniqueConstraintRejectsUnknownColumn() {
|
||||
assertFailsWith<KestrelException> {
|
||||
run("CREATE TABLE t (id INTEGER PRIMARY KEY, UNIQUE (missing))")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foreignKeyCanReferenceAUniqueNonPrimaryKeyColumn() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("CREATE TABLE profiles (id INTEGER PRIMARY KEY, user_email TEXT REFERENCES users(email))")
|
||||
run("INSERT INTO users VALUES (1, 'ada@example.com')")
|
||||
val result = exec("INSERT INTO profiles VALUES (1, 'ada@example.com')")
|
||||
assertEquals(InsertResult(1), result)
|
||||
assertFailsWith<KestrelException> {
|
||||
run("INSERT INTO profiles VALUES (2, 'nobody@example.com')")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foreignKeyRejectsReferencingAColumnWithoutEnforcedUniqueness() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)")
|
||||
val error = assertFailsWith<KestrelException> {
|
||||
run("CREATE TABLE profiles (id INTEGER PRIMARY KEY, user_email TEXT REFERENCES users(email))")
|
||||
}
|
||||
assertTrue(error.message!!.contains("PRIMARY KEY or UNIQUE"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cascadeUpdatePropagatesThroughAUniqueReferencedColumn() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run(
|
||||
"CREATE TABLE profiles (id INTEGER PRIMARY KEY, " +
|
||||
"user_email TEXT REFERENCES users(email) ON UPDATE CASCADE)"
|
||||
)
|
||||
run("INSERT INTO users VALUES (1, 'old@example.com')")
|
||||
run("INSERT INTO profiles VALUES (1, 'old@example.com')")
|
||||
run("UPDATE users SET email = 'new@example.com' WHERE id = 1")
|
||||
val result = select("SELECT user_email FROM profiles WHERE id = 1")
|
||||
assertEquals(listOf(listOf(text("new@example.com"))), result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rollbackAfterAFailedInsertLeavesUniqueStateReusable() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE)")
|
||||
run("BEGIN")
|
||||
run("INSERT INTO users VALUES (1, 'ada@example.com')")
|
||||
run("ROLLBACK")
|
||||
val result = exec("INSERT INTO users VALUES (1, 'ada@example.com')")
|
||||
assertEquals(InsertResult(1), result)
|
||||
assertEquals(listOf(listOf(int(1), text("ada@example.com"))), select("SELECT id, email FROM users").rows)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user