test(sql): cover DiskExecutorTest and tempFile
This commit is contained in:
1 parent
b50f24109e
commit
8858671825
1 file changed
+155
@@ -0,0 +1,155 @@
|
||||
package kestrel.store.sql
|
||||
|
||||
import kestrel.KestrelException
|
||||
import kestrel.engine.AlterTableResult
|
||||
import kestrel.engine.CreateIndexResult
|
||||
import kestrel.engine.CreateSequenceResult
|
||||
import kestrel.engine.CreateTableResult
|
||||
import kestrel.engine.CreateViewResult
|
||||
import kestrel.engine.DeallocateResult
|
||||
import kestrel.engine.DeleteResult
|
||||
import kestrel.engine.DropIndexResult
|
||||
import kestrel.engine.DropTableResult
|
||||
import kestrel.engine.DropViewResult
|
||||
import kestrel.engine.InsertResult
|
||||
import kestrel.engine.PrepareResult
|
||||
import kestrel.engine.SelectResult
|
||||
import kestrel.engine.StatementResult
|
||||
import kestrel.engine.UpdateResult
|
||||
import kestrel.sql.ast.Statement
|
||||
import kestrel.sql.lexer.Lexer
|
||||
import kestrel.sql.parser.Parser
|
||||
import kestrel.storage.Value
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* End-to-end tests that parse real SQL text through the same [Lexer]/[Parser]
|
||||
* the in-memory engine uses, then run it through [DiskExecutor] against a
|
||||
* real [DiskCatalog] backed by temp files - proving the disk storage engine
|
||||
* (page/heap/btree/wal/txn) can actually serve SQL, not just its own
|
||||
* unit tests.
|
||||
*/
|
||||
class DiskExecutorTest {
|
||||
private fun tempFile(prefix: String): File = Files.createTempFile(prefix, ".db").toFile().also { it.deleteOnExit() }
|
||||
|
||||
private fun parse(sql: String): List<Statement> = Parser(Lexer(sql).tokenize()).parseProgram()
|
||||
|
||||
private fun int(value: Long) = Value.IntegerValue(value)
|
||||
private fun text(value: String) = Value.TextValue(value)
|
||||
|
||||
private inner class Database(val dataFile: File, val walFile: File) {
|
||||
var catalog = DiskCatalog.open(dataFile, walFile)
|
||||
private set
|
||||
var executor = DiskExecutor(catalog)
|
||||
private set
|
||||
|
||||
fun run(sql: String): List<StatementResult> = parse(sql).map { executor.execute(it) }
|
||||
fun select(sql: String): SelectResult = run(sql).single() as SelectResult
|
||||
|
||||
/** Closes and reopens the catalog against the same files, simulating a fresh process. */
|
||||
fun reopen() {
|
||||
catalog.close()
|
||||
catalog = DiskCatalog.open(dataFile, walFile)
|
||||
executor = DiskExecutor(catalog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun newDatabase(): Database = Database(tempFile("kestrel-disk-exec-data"), tempFile("kestrel-disk-exec-wal"))
|
||||
|
||||
private fun Database.seedUsers() {
|
||||
run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)")
|
||||
run("INSERT INTO users VALUES (1, 'ada', 36), (2, 'linus', 54), (3, 'grace', 85)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createTableReturnsItsNameAndTheTableStartsEmpty() {
|
||||
val db = newDatabase()
|
||||
val created = db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)").single()
|
||||
assertEquals(CreateTableResult("users"), created)
|
||||
assertEquals(SelectResult(listOf("id", "name"), emptyList()), db.select("SELECT * FROM users"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun creatingTheSameTableTwiceFails() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
||||
assertFailsWith<DiskCatalogException> { db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertFillsOmittedColumnsFromDeclaredDefaultAndReportsRowCount() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)")
|
||||
|
||||
val inserted = db.run("INSERT INTO users (id, name) VALUES (1, 'ada')").single()
|
||||
assertEquals(InsertResult(1), inserted)
|
||||
|
||||
val result = db.select("SELECT * FROM users")
|
||||
assertEquals(listOf(listOf(int(1), text("ada"), int(0))), result.rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertValuesDefaultKeywordUsesTheDeclaredDefault() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER DEFAULT 0)")
|
||||
db.run("INSERT INTO users VALUES (1, 'ada', DEFAULT)")
|
||||
assertEquals(listOf(listOf(int(1), text("ada"), int(0))), db.select("SELECT * FROM users").rows)
|
||||
}
|
||||
|
||||
/** Positive coverage lives in [DiskExecutorOnConflictTest] - this table only proves it is no longer a scope-cut. */
|
||||
@Test
|
||||
fun insertOnConflictDoNothingSkipsAConflictingRowInsteadOfThrowing() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
||||
db.run("INSERT INTO users VALUES (1, 'ada')")
|
||||
val inserted = db.run("INSERT INTO users VALUES (1, 'ignored') ON CONFLICT (id) DO NOTHING").single()
|
||||
assertEquals(InsertResult(0), inserted)
|
||||
assertEquals(listOf(listOf(int(1), text("ada"))), db.select("SELECT * FROM users").rows)
|
||||
}
|
||||
|
||||
/** Positive coverage lives in [DiskExecutorTriggerTest] - this table only proves it is no longer a scope-cut. */
|
||||
@Test
|
||||
fun createTriggerSucceedsAndFiresOnInsert() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE TABLE t (id INTEGER)")
|
||||
db.run("CREATE TABLE audit (id INTEGER)")
|
||||
db.run("CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN INSERT INTO audit VALUES (NEW.id); END")
|
||||
db.run("INSERT INTO t VALUES (1)")
|
||||
assertEquals(listOf(listOf(int(1))), db.select("SELECT * FROM audit").rows)
|
||||
}
|
||||
|
||||
/** Full SEQUENCE/NEXTVAL/CURRVAL coverage lives in [DiskExecutorSequenceTest]; this just proves the wiring. */
|
||||
@Test
|
||||
fun createSequenceSucceedsAndNextvalCanBackAColumnDefault() {
|
||||
val db = newDatabase()
|
||||
assertEquals(CreateSequenceResult("seq"), db.run("CREATE SEQUENCE seq").single())
|
||||
db.run("CREATE TABLE t (id INTEGER DEFAULT NEXTVAL('seq'))")
|
||||
db.run("INSERT INTO t (id) VALUES (DEFAULT)")
|
||||
assertEquals(listOf(listOf(int(1))), db.select("SELECT id FROM t").rows)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextvalInACheckConstraintIsRejected() {
|
||||
val db = newDatabase()
|
||||
db.run("CREATE SEQUENCE seq")
|
||||
val error = assertFailsWith<DiskExecutorException> {
|
||||
db.run("CREATE TABLE t (id INTEGER CHECK (id = NEXTVAL('seq')))")
|
||||
}
|
||||
assertTrue(error.message!!.contains("not allowed"), error.message!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectStarReturnsEveryColumnInSchemaOrder() {
|
||||
val db = newDatabase()
|
||||
db.seedUsers()
|
||||
val result = db.select("SELECT * FROM users")
|
||||
assertEquals(listOf("id", "name", "age"), result.columns)
|
||||
assertEquals(3, result.rows.size)
|
||||
assertTrue(listOf(int(1), text("ada"), int(36)) in result.rows)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user