Two recipes for database-connected KonsolScript apps - local SQLite for zero-config storage and MySQL for shared team databases.
Modules: sqlite plugin · JSON · String · Konsol
A persistent note-taking CLI app backed by a local notes.db file. Demonstrates the full SQLite workflow: open/create, DDL, INSERT, SELECT, and DELETE. Data survives between runs - run the script twice and your notes are still there.
Key patterns:
SQLite:Open - creates the database file if it does not existSQLite:Exec - DDL and DML (CREATE TABLE, INSERT, DELETE)SQLite:Query - returns a JSON array string; parse with JSON:Parse, iterate by indexSQLite:QueryOne - single JSON object string for by-ID lookupsSQLite:LastInsertId - get the auto-generated ID after an INSERTSQLite:RowsAffected - check whether a DELETE actually found a rowString:Replace(title, "'", "''", safe) - escape single quotes before interpolating into SQLUsage
minks sqlite_notes.ks
Sample session
=== Notes (SQLite) ===
1) List notes
2) Add note
3) View note
4) Delete note
q) Quit
Choice: 2
Title: Buy groceries
Body: Milk, eggs, bread
Saved as note #1.
Choice: 1
--- Your notes ---
[1] Buy groceries
Choice: 3
Note ID: 1
--- Buy groceries ---
Milk, eggs, bread
Choice: q
Goodbye!
Script
// sqlite_notes.ks - local note-taking app backed by SQLite
// Modules: sqlite plugin, JSON, String, Konsol
// Usage: minks sqlite_notes.ks
//
// Demonstrates the full SQLite workflow: open (or create) a database,
// create a table, insert rows, query rows, and delete - all persisted to
// a local "notes.db" file that survives between runs.
#include "sqlite"
Konsol:Print("=== Notes (SQLite) ===");
// ── Step 1: Open (or create) the database ────────────────────────────────────
// SQLite:Open creates the file if it does not exist yet.
Var:Number db;
SQLite:Open("notes.db", db);
// ── Step 2: Create the notes table on first run ───────────────────────────────
// IF NOT EXISTS makes this safe to call every time - no error if the table
// already exists from a previous run.
Var:Boolean ok;
SQLite:Exec(db, "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT)", ok);
// ── Step 3: Main menu loop ────────────────────────────────────────────────────
Var:Boolean running = true;
Var:String choice;
while (running) {
Konsol:Print("");
Konsol:Print("1) List notes");
Konsol:Print("2) Add note");
Konsol:Print("3) View note");
Konsol:Print("4) Delete note");
Konsol:Print("q) Quit");
Konsol:Input("Choice: ", choice);
if (choice == "1") {
// ── List all notes ────────────────────────────────────────────────────
// SQLite:Query returns a JSON array string, e.g. [{"id":1,"title":"..."},...].
// Parse it, then access each row by zero-based index path.
Var:String rows;
SQLite:Query(db, "SELECT id, title FROM notes ORDER BY id", rows);
JSON:Parse(rows, listDoc);
Var:Number rowCount;
JSON:Length("", listDoc, rowCount);
if (rowCount == 0) {
Konsol:Print("No notes yet.");
} else {
Konsol:Print("--- Your notes ---");
for (Number i = 0; i < rowCount; i++) {
Var:String noteId;
Var:String noteTitle;
JSON:Get("${i}.id", listDoc, noteId);
JSON:Get("${i}.title", listDoc, noteTitle);
Konsol:Print(" [${noteId}] ${noteTitle}");
}
}
} else if (choice == "2") {
// ── Add a note ────────────────────────────────────────────────────────
Var:String title;
Var:String body;
Konsol:Input("Title: ", title);
Konsol:Input("Body: ", body);
// Escape single quotes by doubling them - standard SQL injection defence
// for values coming from interactive input.
Var:String safeTitle;
Var:String safeBody;
String:Replace(title, "'", "''", safeTitle);
String:Replace(body, "'", "''", safeBody);
SQLite:Exec(db, "INSERT INTO notes (title, body) VALUES ('${safeTitle}', '${safeBody}')", ok);
Var:Number newId;
SQLite:LastInsertId(db, newId);
Konsol:Print("Saved as note #${newId}.");
} else if (choice == "3") {
// ── View a note ───────────────────────────────────────────────────────
// SQLite:QueryOne returns a single JSON object string, or "" if not found.
Var:String idStr;
Konsol:Input("Note ID: ", idStr);
Var:String row;
SQLite:QueryOne(db, "SELECT title, body FROM notes WHERE id = ${idStr}", row);
if (row == "" || row == "null") {
Konsol:Print("Note not found.");
} else {
JSON:Parse(row, noteDoc);
Var:String noteTitle;
Var:String noteBody;
JSON:Get("title", noteDoc, noteTitle);
JSON:Get("body", noteDoc, noteBody);
Konsol:Print("--- ${noteTitle} ---");
Konsol:Print("${noteBody}");
}
} else if (choice == "4") {
// ── Delete a note ─────────────────────────────────────────────────────
Var:String delId;
Konsol:Input("Note ID to delete: ", delId);
SQLite:Exec(db, "DELETE FROM notes WHERE id = ${delId}", ok);
Var:Number affected;
SQLite:RowsAffected(db, affected);
if (affected > 0) {
Konsol:Print("Note #${delId} deleted.");
} else {
Konsol:Print("Note not found.");
}
} else if (choice == "q") {
running = false;
} else {
Konsol:Print("Unknown choice - enter 1-4 or q.");
}
}
// ── Step 4: Close the connection ─────────────────────────────────────────────
// Always close when done so SQLite can flush any pending writes.
SQLite:Close(db);
Konsol:Print("Goodbye!");
Modules: mysql plugin · JSON · File · Konsol
Connects to a MySQL database, aggregates sales by category with GROUP BY, prints a top-5 leaderboard, and writes the full summary to report.csv.
Expects a sales table:
CREATE TABLE sales (
id INT PRIMARY KEY AUTO_INCREMENT,
product VARCHAR(100),
category VARCHAR(50),
amount DECIMAL(10,2),
sold_at DATE
);
Key patterns:
MySQL:Connect - takes host, user, pass, database; throws MySQLException on failureMySQL:Query - returns a JSON array string; same parse-and-index pattern as SQLiteJSON:Get("${i}.field", doc, var) with Var:Number receiver - JSON coerces to numeric typeFile:WriteUsage
minks mysql_reporter.ks <host> <user> <pass> <database>
# example
minks mysql_reporter.ks localhost root secret salesdb
Sample output
=== MySQL Sales Reporter ===
Connecting to localhost/salesdb...
Connected.
3 categories found.
Report saved to report.csv
--- Category Summary ---
Electronics: 4280.50 (12 sales)
Clothing: 1950.00 (8 sales)
Books: 340.75 (15 sales)
Grand total: 6571.25
--- Top 5 Sales ---
1. Laptop Pro - 1299.99 on 2025-11-03
2. Gaming Chair - 849.00 on 2025-10-21
...
Done.
Script
// mysql_reporter.ks - query MySQL and generate a CSV sales report
// Modules: mysql plugin, JSON, File, Konsol
// Usage: minks mysql_reporter.ks <host> <user> <pass> <database>
//
// Expects a table in the target database:
// sales (id INT, product VARCHAR, category VARCHAR, amount DECIMAL, sold_at DATE)
//
// Outputs:
// - report.csv - per-category sales totals
// - Console summary with top 5 individual sales
#include "mysql"
Konsol:Print("=== MySQL Sales Reporter ===");
// ── Step 1: Read connection args ──────────────────────────────────────────────
Var:List args;
OS:Args(args);
Var:Number argc;
List:Size(args, argc);
if (argc < 4) {
Konsol:Print("Usage: minks mysql_reporter.ks <host> <user> <pass> <database>");
Konsol:Exit(1);
}
Var:String host;
Var:String user;
Var:String pass;
Var:String dbName;
List:Get(0, args, host);
List:Get(1, args, user);
List:Get(2, args, pass);
List:Get(3, args, dbName);
// ── Step 2: Connect ───────────────────────────────────────────────────────────
// MySQL:Connect throws MySQLException on failure - wrap in try/catch for
// real apps so you can show a clean error message instead of a crash.
Konsol:Print("Connecting to ${host}/${dbName}...");
Var:Number db;
MySQL:Connect(host, user, pass, dbName, db);
Konsol:Print("Connected.");
// ── Step 3: Query per-category totals ────────────────────────────────────────
// MySQL:Query returns a JSON array string: [{"category":"...","total":"..."},...].
// Parse it and iterate by zero-based index paths.
Var:String catRows;
MySQL:Query(db, "SELECT category, SUM(amount) AS total, COUNT(*) AS cnt FROM sales GROUP BY category ORDER BY total DESC", catRows);
JSON:Parse(catRows, catDoc);
Var:Number catCount;
JSON:Length("", catDoc, catCount);
Konsol:Print("${catCount} categories found.");
// ── Step 4: Build CSV report ──────────────────────────────────────────────────
// Accumulate the report as a plain string then write it in one shot.
Var:String csvOut = "Category,Total Sales,Transactions\n";
Var:Number grandTotal = 0;
for (Number i = 0; i < catCount; i++) {
Var:String cat;
Var:Number total;
Var:Number cnt;
JSON:Get("${i}.category", catDoc, cat);
JSON:Get("${i}.total", catDoc, total);
JSON:Get("${i}.cnt", catDoc, cnt);
grandTotal = grandTotal + total;
csvOut = csvOut + "${cat},${total},${cnt}\n";
}
// Append a grand-total footer row.
csvOut = csvOut + "TOTAL,${grandTotal},\n";
// ── Step 5: Save to report.csv ────────────────────────────────────────────────
Var:Number wh;
File:Open("report.csv", "w", wh);
File:Write(csvOut, wh);
File:Close(wh);
Konsol:Print("Report saved to report.csv");
// ── Step 6: Print summary to the console ─────────────────────────────────────
Konsol:Print("");
Konsol:Print("--- Category Summary ---");
for (Number i = 0; i < catCount; i++) {
Var:String cat;
Var:Number total;
Var:Number cnt;
JSON:Get("${i}.category", catDoc, cat);
JSON:Get("${i}.total", catDoc, total);
JSON:Get("${i}.cnt", catDoc, cnt);
Konsol:Print(" ${cat}: ${total} (${cnt} sales)");
}
Konsol:Print(" Grand total: ${grandTotal}");
// ── Step 7: Top 5 individual sales ───────────────────────────────────────────
Konsol:Print("");
Konsol:Print("--- Top 5 Sales ---");
Var:String topRows;
MySQL:Query(db, "SELECT product, amount, sold_at FROM sales ORDER BY amount DESC LIMIT 5", topRows);
JSON:Parse(topRows, topDoc);
Var:Number topCount;
JSON:Length("", topDoc, topCount);
for (Number j = 0; j < topCount; j++) {
Var:String product;
Var:Number amount;
Var:String soldAt;
Var:Number rank = j + 1;
JSON:Get("${j}.product", topDoc, product);
JSON:Get("${j}.amount", topDoc, amount);
JSON:Get("${j}.sold_at", topDoc, soldAt);
Konsol:Print(" ${rank}. ${product} - ${amount} on ${soldAt}");
}
// ── Step 8: Close the connection ─────────────────────────────────────────────
MySQL:Close(db);
Konsol:Print("Done.");