test: cover mergePrintsHowManyRowsWereInsertedUpdatedAndDeleted
This commit is contained in:
1 parent
3adb4b1eda
commit
e18d5cdc16
1 file changed
+196
@@ -26,6 +26,22 @@ class ReplTest {
|
|||||||
assertTrue(output.indexOf("grace") < output.indexOf("linus"), "grace should be listed before linus")
|
assertTrue(output.indexOf("grace") < output.indexOf("linus"), "grace should be listed before linus")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mergePrintsHowManyRowsWereInsertedUpdatedAndDeleted() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);
|
||||||
|
CREATE TABLE staging (id INTEGER, amount INTEGER);
|
||||||
|
INSERT INTO accounts VALUES (1, 100), (2, 200);
|
||||||
|
INSERT INTO staging VALUES (1, 10), (2, -999), (3, 5);
|
||||||
|
MERGE INTO accounts USING staging ON accounts.id = staging.id
|
||||||
|
WHEN MATCHED AND accounts.balance + staging.amount < 0 THEN DELETE
|
||||||
|
WHEN MATCHED THEN UPDATE SET balance = accounts.balance + staging.amount
|
||||||
|
WHEN NOT MATCHED THEN INSERT (id, balance) VALUES (staging.id, staging.amount);
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertContains(output, "1 row inserted, 1 row updated, 1 row deleted")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun metaTablesListsCreatedTables() {
|
fun metaTablesListsCreatedTables() {
|
||||||
val output = runScript("CREATE TABLE alpha (id INTEGER);\nCREATE TABLE beta (id INTEGER);\n.tables\n")
|
val output = runScript("CREATE TABLE alpha (id INTEGER);\nCREATE TABLE beta (id INTEGER);\n.tables\n")
|
||||||
@@ -41,6 +57,26 @@ class ReplTest {
|
|||||||
assertContains(output, "name TEXT NOT NULL")
|
assertContains(output, "name TEXT NOT NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun metaSchemaListsUniqueIndexesOnTheTable() {
|
||||||
|
val output = runScript(
|
||||||
|
"CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT NOT NULL);\n" +
|
||||||
|
"CREATE UNIQUE INDEX idx_email ON users (email);\n" +
|
||||||
|
".schema users\n"
|
||||||
|
)
|
||||||
|
assertContains(output, "CREATE UNIQUE INDEX idx_email ON users (email);")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun metaSchemaListsCompositeIndexesOnTheTable() {
|
||||||
|
val output = runScript(
|
||||||
|
"CREATE TABLE orders (id INTEGER PRIMARY KEY, region TEXT, seq INTEGER);\n" +
|
||||||
|
"CREATE INDEX idx_region_seq ON orders (region, seq);\n" +
|
||||||
|
".schema orders\n"
|
||||||
|
)
|
||||||
|
assertContains(output, "CREATE INDEX idx_region_seq ON orders (region, seq);")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseErrorIsReportedAndReplContinues() {
|
fun parseErrorIsReportedAndReplContinues() {
|
||||||
val output = runScript("SELCT bad;\nCREATE TABLE t (a INTEGER);\n.tables\n")
|
val output = runScript("SELCT bad;\nCREATE TABLE t (a INTEGER);\n.tables\n")
|
||||||
@@ -54,4 +90,164 @@ class ReplTest {
|
|||||||
assertContains(output, "Table seen created")
|
assertContains(output, "Table seen created")
|
||||||
assertTrue(!output.contains("unseen"), "statements after .exit must not run")
|
assertTrue(!output.contains("unseen"), "statements after .exit must not run")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The REPL buffers input line by line and (before this was fixed) treated ANY line ending in
|
||||||
|
* `;` as "statement complete" - a trigger body's inner `INSERT ... ;` would have tripped that
|
||||||
|
* check and been parsed as an incomplete CREATE TRIGGER, well before the closing `END;` was
|
||||||
|
* even typed. Feeding the trigger definition across several `readLine()` calls (rather than as
|
||||||
|
* one line) is the point of this test.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun multiLineCreateTriggerIsNotExecutedPrematurelyOnAnInnerSemicolon() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE orders (id INTEGER PRIMARY KEY, amount INTEGER);
|
||||||
|
CREATE TABLE audit (order_id INTEGER);
|
||||||
|
CREATE TRIGGER log_insert
|
||||||
|
AFTER INSERT ON orders
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO audit VALUES (NEW.id);
|
||||||
|
END;
|
||||||
|
INSERT INTO orders VALUES (1, 100);
|
||||||
|
SELECT order_id FROM audit;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertTrue(!output.contains("Error:"), output)
|
||||||
|
assertContains(output, "Trigger log_insert created")
|
||||||
|
assertContains(output, "1 row")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun metaTriggersListsCreatedTriggers() {
|
||||||
|
val output = runScript(
|
||||||
|
"CREATE TABLE t (id INTEGER);\n" +
|
||||||
|
"CREATE TRIGGER log AFTER INSERT ON t FOR EACH ROW BEGIN DELETE FROM t WHERE 1 = 0; END;\n" +
|
||||||
|
".triggers\n"
|
||||||
|
)
|
||||||
|
assertContains(output, "log")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun metaSequencesListsCreatedSequencesAndReportsEmpty() {
|
||||||
|
val empty = runScript(".sequences\n")
|
||||||
|
assertContains(empty, "no sequences")
|
||||||
|
val output = runScript("CREATE SEQUENCE seq;\n.sequences\n")
|
||||||
|
assertContains(output, "Sequence seq created")
|
||||||
|
assertContains(output, "seq")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createAlterAndDropSequencePrintTheirOwnConfirmations() {
|
||||||
|
val output = runScript(
|
||||||
|
"""
|
||||||
|
CREATE SEQUENCE seq;
|
||||||
|
ALTER SEQUENCE seq RESTART WITH 5;
|
||||||
|
DROP SEQUENCE seq;
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertContains(output, "Sequence seq created")
|
||||||
|
assertContains(output, "Sequence seq altered")
|
||||||
|
assertContains(output, "Sequence seq dropped")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nextvalDrivenAutoIncrementWorksEndToEndThroughTheRepl() {
|
||||||
|
val script = """
|
||||||
|
CREATE SEQUENCE order_ids;
|
||||||
|
CREATE TABLE orders (id INTEGER PRIMARY KEY DEFAULT NEXTVAL('order_ids'), item TEXT);
|
||||||
|
INSERT INTO orders (item) VALUES ('a'), ('b');
|
||||||
|
SELECT id, item FROM orders ORDER BY id;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertTrue(!output.contains("Error:"), output)
|
||||||
|
assertContains(output, "2 rows inserted")
|
||||||
|
assertContains(output, "2 rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun insertReturningPrintsTheAffectedRowsAsATableAfterTheInsertConfirmation() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||||
|
INSERT INTO users VALUES (1, 'ada') RETURNING id, name;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertContains(output, "1 row inserted")
|
||||||
|
assertTrue(
|
||||||
|
output.indexOf("1 row inserted") < output.indexOf("ada"),
|
||||||
|
"the RETURNING table should be printed after the insert confirmation"
|
||||||
|
)
|
||||||
|
assertContains(output, "ada")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun updateAndDeleteReturningAlsoPrintTheirRowsAfterTheirOwnConfirmation() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||||
|
INSERT INTO users VALUES (1, 'ada');
|
||||||
|
UPDATE users SET name = 'grace' WHERE id = 1 RETURNING name;
|
||||||
|
DELETE FROM users WHERE id = 1 RETURNING id;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertContains(output, "1 row updated")
|
||||||
|
assertContains(output, "grace")
|
||||||
|
assertContains(output, "1 row deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mergeReturningPrintsTheAffectedRowsAsATableAfterTheMergeConfirmation() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER);
|
||||||
|
CREATE TABLE staging (id INTEGER, amount INTEGER);
|
||||||
|
INSERT INTO accounts VALUES (1, 100);
|
||||||
|
INSERT INTO staging VALUES (1, 10), (2, 5);
|
||||||
|
MERGE INTO accounts USING staging ON accounts.id = staging.id
|
||||||
|
WHEN MATCHED THEN UPDATE SET balance = accounts.balance + staging.amount
|
||||||
|
WHEN NOT MATCHED THEN INSERT (id, balance) VALUES (staging.id, staging.amount)
|
||||||
|
RETURNING id, MERGE_ACTION() AS kind;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertContains(output, "1 row inserted, 1 row updated, 0 rows deleted")
|
||||||
|
assertTrue(
|
||||||
|
output.indexOf("1 row inserted, 1 row updated, 0 rows deleted") < output.indexOf("UPDATE"),
|
||||||
|
"the RETURNING table should be printed after the merge confirmation"
|
||||||
|
)
|
||||||
|
assertContains(output, "UPDATE")
|
||||||
|
assertContains(output, "INSERT")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun prepareExecuteAndDeallocatePrintTheirOwnConfirmations() {
|
||||||
|
val script = """
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||||
|
INSERT INTO users VALUES (1, 'ada');
|
||||||
|
PREPARE byId AS SELECT name FROM users WHERE id = $1;
|
||||||
|
EXECUTE byId(1);
|
||||||
|
DEALLOCATE byId;
|
||||||
|
""".trimIndent()
|
||||||
|
val output = runScript(script)
|
||||||
|
assertContains(output, "Prepared statement byId created")
|
||||||
|
assertContains(output, "ada")
|
||||||
|
assertContains(output, "Prepared statement byId deallocated")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun metaPreparedListsAndThenForgetsAPreparedStatementName() {
|
||||||
|
val before = runScript(
|
||||||
|
"""
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||||
|
PREPARE byId AS SELECT name FROM users WHERE id = $1;
|
||||||
|
.prepared
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertContains(before, "byId")
|
||||||
|
|
||||||
|
val after = runScript(
|
||||||
|
"""
|
||||||
|
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
|
||||||
|
.prepared
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertContains(after, "no prepared statements")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user