2026-01-24 22:40:22 +00:00
<!DOCTYPE html>
<!-- retoor <retoor@molodetz.nl> -->
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Database Application - Wren-CLI Manual</ title >
< link rel = "stylesheet" href = "../css/style.css" >
</ head >
< body >
< button class = "mobile-menu-toggle" > Menu</ button >
< div class = "container" >
< aside class = "sidebar" >
< div class = "sidebar-header" >
< h1 >< a href = "../index.html" > Wren-CLI</ a ></ h1 >
< div class = "version" > v0.4.0</ div >
</ div >
< nav class = "sidebar-nav" >
< div class = "section" >
< span class = "section-title" > Getting Started</ span >
< ul >
< li >< a href = "../getting-started/index.html" > Overview</ a ></ li >
< li >< a href = "../getting-started/installation.html" > Installation</ a ></ li >
< li >< a href = "../getting-started/first-script.html" > First Script</ a ></ li >
< li >< a href = "../getting-started/repl.html" > Using the REPL</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > Language</ span >
< ul >
< li >< a href = "../language/index.html" > Syntax Overview</ a ></ li >
< li >< a href = "../language/classes.html" > Classes</ a ></ li >
< li >< a href = "../language/methods.html" > Methods</ a ></ li >
< li >< a href = "../language/control-flow.html" > Control Flow</ a ></ li >
< li >< a href = "../language/fibers.html" > Fibers</ a ></ li >
< li >< a href = "../language/modules.html" > Modules</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > API Reference</ span >
< ul >
< li >< a href = "../api/index.html" > Overview</ a ></ li >
2026-01-25 03:58:39 +00:00
< li >< a href = "../api/string.html" > String</ a ></ li >
< li >< a href = "../api/number.html" > Num</ a ></ li >
2026-01-24 22:40:22 +00:00
< li >< a href = "../api/http.html" > http</ a ></ li >
< li >< a href = "../api/websocket.html" > websocket</ a ></ li >
< li >< a href = "../api/tls.html" > tls</ a ></ li >
< li >< a href = "../api/net.html" > net</ a ></ li >
< li >< a href = "../api/dns.html" > dns</ a ></ li >
< li >< a href = "../api/json.html" > json</ a ></ li >
< li >< a href = "../api/base64.html" > base64</ a ></ li >
< li >< a href = "../api/regex.html" > regex</ a ></ li >
< li >< a href = "../api/jinja.html" > jinja</ a ></ li >
< li >< a href = "../api/crypto.html" > crypto</ a ></ li >
< li >< a href = "../api/os.html" > os</ a ></ li >
< li >< a href = "../api/env.html" > env</ a ></ li >
< li >< a href = "../api/signal.html" > signal</ a ></ li >
< li >< a href = "../api/subprocess.html" > subprocess</ a ></ li >
< li >< a href = "../api/sqlite.html" > sqlite</ a ></ li >
< li >< a href = "../api/datetime.html" > datetime</ a ></ li >
< li >< a href = "../api/timer.html" > timer</ a ></ li >
< li >< a href = "../api/io.html" > io</ a ></ li >
2026-01-25 03:58:39 +00:00
< li >< a href = "../api/pathlib.html" > pathlib</ a ></ li >
2026-01-24 22:40:22 +00:00
< li >< a href = "../api/scheduler.html" > scheduler</ a ></ li >
< li >< a href = "../api/math.html" > math</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > Tutorials</ span >
< ul >
< li >< a href = "index.html" > Tutorial List</ a ></ li >
< li >< a href = "http-client.html" > HTTP Client</ a ></ li >
< li >< a href = "websocket-chat.html" > WebSocket Chat</ a ></ li >
< li >< a href = "database-app.html" class = "active" > Database App</ a ></ li >
< li >< a href = "template-rendering.html" > Template Rendering</ a ></ li >
< li >< a href = "cli-tool.html" > CLI Tool</ a ></ li >
</ ul >
</ div >
< div class = "section" >
< span class = "section-title" > How-To Guides</ span >
< ul >
< li >< a href = "../howto/index.html" > How-To List</ a ></ li >
</ ul >
</ div >
</ nav >
</ aside >
< main class = "content" >
< nav class = "breadcrumb" >
< a href = "../index.html" > Home</ a >
< span class = "separator" > /</ span >
< a href = "index.html" > Tutorials</ a >
< span class = "separator" > /</ span >
< span > Database App</ span >
</ nav >
< article >
< h1 > Database Application</ h1 >
< p > In this tutorial, you will build a complete task management application using SQLite for persistent storage. You will learn to create tables, perform CRUD operations, and build a command-line interface.</ p >
< h2 > What You Will Learn</ h2 >
< ul >
< li > Creating and managing SQLite databases</ li >
< li > Designing database schemas</ li >
< li > Performing CRUD operations (Create, Read, Update, Delete)</ li >
< li > Using parameterized queries to prevent SQL injection</ li >
< li > Building a command-line interface</ li >
</ ul >
< h2 > Step 1: Setting Up the Database</ h2 >
< p > Create a file called < code > task_app.wren</ code > :</ p >
< pre >< code > import "sqlite" for Sqlite
var db = Sqlite.open("tasks.db")
db.execute("
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 1,
completed INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
due_date DATE
)
")
System.print("Database initialized!")
db.close()</ code ></ pre >
< h2 > Step 2: Creating a Task Model</ h2 >
< p > Let's create a class to manage task operations:</ p >
< pre >< code > import "sqlite" for Sqlite
import "datetime" for DateTime
class TaskManager {
construct new(dbPath) {
_db = Sqlite.open(dbPath)
initDatabase()
}
initDatabase() {
_db.execute("
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 1,
completed INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
due_date DATE
)
")
}
create(title, description, priority, dueDate) {
_db.execute(
"INSERT INTO tasks (title, description, priority, due_date) VALUES (?, ?, ?, ?)",
[title, description, priority, dueDate]
)
return _db.lastInsertId
}
findAll() {
return _db.execute("SELECT * FROM tasks ORDER BY priority DESC, due_date ASC")
}
findById(id) {
var results = _db.execute("SELECT * FROM tasks WHERE id = ?", [id])
return results.count > 0 ? results[0] : null
}
findPending() {
return _db.execute("SELECT * FROM tasks WHERE completed = 0 ORDER BY priority DESC")
}
findCompleted() {
return _db.execute("SELECT * FROM tasks WHERE completed = 1 ORDER BY created_at DESC")
}
update(id, title, description, priority, dueDate) {
_db.execute(
"UPDATE tasks SET title = ?, description = ?, priority = ?, due_date = ? WHERE id = ?",
[title, description, priority, dueDate, id]
)
}
complete(id) {
_db.execute("UPDATE tasks SET completed = 1 WHERE id = ?", [id])
}
uncomplete(id) {
_db.execute("UPDATE tasks SET completed = 0 WHERE id = ?", [id])
}
delete(id) {
_db.execute("DELETE FROM tasks WHERE id = ?", [id])
}
search(query) {
return _db.execute(
"SELECT * FROM tasks WHERE title LIKE ? OR description LIKE ?",
["\%%(query)\%", "\%%(query)\%"]
)
}
close() {
_db.close()
}
}
var tasks = TaskManager.new("tasks.db")
var id = tasks.create("Learn Wren-CLI", "Complete the database tutorial", 3, "2024-12-31")
System.print("Created task with ID: %(id)")
tasks.close()</ code ></ pre >
< h2 > Step 3: Building the CLI Interface</ h2 >
< p > Now let's create an interactive command-line interface:</ p >
< pre >< code > import "sqlite" for Sqlite
import "io" for Stdin
class TaskManager {
construct new(dbPath) {
_db = Sqlite.open(dbPath)
initDatabase()
}
initDatabase() {
_db.execute("
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
priority INTEGER DEFAULT 1,
completed INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
")
}
create(title, description, priority) {
_db.execute(
"INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)",
[title, description, priority]
)
return _db.lastInsertId
}
findAll() {
return _db.execute("SELECT * FROM tasks ORDER BY completed ASC, priority DESC")
}
complete(id) {
_db.execute("UPDATE tasks SET completed = 1 WHERE id = ?", [id])
}
delete(id) {
_db.execute("DELETE FROM tasks WHERE id = ?", [id])
}
close() {
_db.close()
}
}
class TaskApp {
construct new() {
_tasks = TaskManager.new("tasks.db")
_running = true
}
run() {
System.print("=== Task Manager ===\n")
showHelp()
while (_running) {
System.write("\n> ")
var input = Stdin.readLine()
if (input == null) break
var parts = input.split(" ")
var command = parts.count > 0 ? parts[0] : ""
if (command == "list") {
listTasks()
} else if (command == "add") {
addTask()
} else if (command == "done") {
if (parts.count > 1) {
completeTask(Num.fromString(parts[1]))
} else {
System.print("Usage: done < id > ")
}
} else if (command == "delete") {
if (parts.count > 1) {
deleteTask(Num.fromString(parts[1]))
} else {
System.print("Usage: delete < id > ")
}
} else if (command == "help") {
showHelp()
} else if (command == "quit" || command == "exit") {
_running = false
} else if (command.count > 0) {
System.print("Unknown command: %(command)")
}
}
_tasks.close()
System.print("Goodbye!")
}
showHelp() {
System.print("Commands:")
System.print(" list - Show all tasks")
System.print(" add - Add a new task")
System.print(" done < id > - Mark task as complete")
System.print(" delete < id > - Delete a task")
System.print(" help - Show this help")
System.print(" quit - Exit the application")
}
listTasks() {
var tasks = _tasks.findAll()
if (tasks.count == 0) {
System.print("No tasks found.")
return
}
System.print("\nID | Pri | Status | Title")
System.print("----|-----|--------|------")
for (task in tasks) {
var status = task["completed"] == 1 ? "[X]" : "[ ]"
var priority = ["Low", "Med", "High"][task["priority"] - 1]
System.print("%(task["id"]) | %(priority) | %(status) | %(task["title"])")
}
}
addTask() {
System.write("Title: ")
var title = Stdin.readLine()
System.write("Description (optional): ")
var description = Stdin.readLine()
System.write("Priority (1=Low, 2=Medium, 3=High): ")
var priorityStr = Stdin.readLine()
var priority = Num.fromString(priorityStr) || 1
if (priority < 1 ) priority = 1
if ( priority > 3) priority = 3
var id = _tasks.create(title, description, priority)
System.print("Task created with ID: %(id)")
}
completeTask(id) {
_tasks.complete(id)
System.print("Task %(id) marked as complete.")
}
deleteTask(id) {
_tasks.delete(id)
System.print("Task %(id) deleted.")
}
}
var app = TaskApp.new()
app.run()</ code ></ pre >
< h2 > Step 4: Adding Advanced Features</ h2 >
< p > Let's enhance our application with categories and due dates:</ p >
< pre >< code > import "sqlite" for Sqlite
import "io" for Stdin
import "datetime" for DateTime
class TaskManager {
construct new(dbPath) {
_db = Sqlite.open(dbPath)
initDatabase()
}
initDatabase() {
_db.execute("
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#808080'
)
")
_db.execute("
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
category_id INTEGER,
priority INTEGER DEFAULT 1,
completed INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
due_date DATE,
FOREIGN KEY (category_id) REFERENCES categories(id)
)
")
var categories = _db.execute("SELECT COUNT(*) as count FROM categories")
if (categories[0]["count"] == 0) {
_db.execute("INSERT INTO categories (name, color) VALUES ('Work', '#FF6B6B')")
_db.execute("INSERT INTO categories (name, color) VALUES ('Personal', '#4ECDC4')")
_db.execute("INSERT INTO categories (name, color) VALUES ('Shopping', '#45B7D1')")
}
}
createTask(title, description, categoryId, priority, dueDate) {
_db.execute(
"INSERT INTO tasks (title, description, category_id, priority, due_date) VALUES (?, ?, ?, ?, ?)",
[title, description, categoryId, priority, dueDate]
)
return _db.lastInsertId
}
findAllTasks() {
return _db.execute("
SELECT t.*, c.name as category_name
FROM tasks t
LEFT JOIN categories c ON t.category_id = c.id
ORDER BY t.completed ASC, t.priority DESC, t.due_date ASC
")
}
findTasksByCategory(categoryId) {
return _db.execute("
SELECT t.*, c.name as category_name
FROM tasks t
LEFT JOIN categories c ON t.category_id = c.id
WHERE t.category_id = ?
ORDER BY t.completed ASC, t.priority DESC
", [categoryId])
}
findOverdueTasks() {
return _db.execute("
SELECT t.*, c.name as category_name
FROM tasks t
LEFT JOIN categories c ON t.category_id = c.id
WHERE t.completed = 0 AND t.due_date < DATE (' now ')
ORDER BY t . due_date ASC
")
}
findDueToday () {
return _db . execute ("
SELECT t .*, c . name as category_name
FROM tasks t
LEFT JOIN categories c ON t . category_id = c.id
WHERE t . completed = 0 AND t . due_date = DATE('now')
ORDER BY t . priority DESC
")
}
findCategories () {
return _db . execute (" SELECT * FROM categories ORDER BY name ")
}
createCategory ( name , color ) {
_db . execute (" INSERT INTO categories ( name , color ) VALUES (?, ?)", [ name , color ])
return _db . lastInsertId
}
completeTask ( id ) {
_db . execute (" UPDATE tasks SET completed = 1 WHERE id = ?", [ id ])
}
deleteTask ( id ) {
_db . execute (" DELETE FROM tasks WHERE id = ?", [ id ])
}
getStats () {
var total = _db.execute("SELECT COUNT (*) as count FROM tasks ")[ 0 ][" count "]
var completed = _db.execute("SELECT COUNT (*) as count FROM tasks WHERE completed = 1")[0]["count"]
var pending = total - completed
var overdue = _db.execute("SELECT COUNT (*) as count FROM tasks WHERE completed = 0 AND due_date < DATE (' now ')")[ 0 ][" count "]
return {
" total " : total ,
" completed " : completed ,
" pending " : pending ,
" overdue " : overdue
}
}
close () {
_db . close ()
}
}
class TaskApp {
construct new () {
_tasks = TaskManager.new("tasks.db")
_running = true
}
run () {
System . print ("=== Task Manager v2 = ==\n")
while ( _running ) {
showMenu ()
System . write ("\ nChoice: ")
var choice = Stdin.readLine()
if ( choice = = " 1 ") {
listTasks ()
} else if ( choice = = " 2 ") {
addTask ()
} else if ( choice = = " 3 ") {
completeTask ()
} else if ( choice = = " 4 ") {
showOverdue ()
} else if ( choice = = " 5 ") {
showStats ()
} else if ( choice = = " 6 ") {
manageCategories ()
} else if ( choice = = " 0 ") {
_running = false
}
}
_tasks . close ()
System . print ("\ nGoodbye !")
}
showMenu () {
System . print ("\ n--- Main Menu --- ")
System . print (" 1 . List all tasks ")
System . print (" 2 . Add new task ")
System . print (" 3 . Complete task ")
System . print (" 4 . Show overdue ")
System . print (" 5 . Statistics ")
System . print (" 6 . Categories ")
System . print (" 0 . Exit ")
}
listTasks () {
var tasks = _tasks.findAllTasks()
if ( tasks . count = = 0 ) {
System . print ("\ nNo tasks found .")
return
}
System . print ("\ n--- All Tasks --- ")
for ( task in tasks ) {
var status = task["completed"] == 1 ? "[ X ]" : "[ ]"
var category = task["category_name"] || " None "
var due = task["due_date"] || " No due date "
System . print ("%( task [" id "]). %( status ) %( task [" title "])")
System . print (" Category: %( category ) | Due: %( due )")
}
}
addTask () {
System . print ("\ n--- Add Task --- ")
System . write (" Title: ")
var title = Stdin.readLine()
if ( title . count = = 0 ) return
System . write (" Description: ")
var description = Stdin.readLine()
var categories = _tasks.findCategories()
System . print ("\ nCategories: ")
for ( cat in categories ) {
System . print (" %( cat [" id "]). %( cat [" name "])")
}
System . write (" Category ID ( or 0 for none ) : ")
var categoryId = Num.fromString(Stdin.readLine())
if ( categoryId = = 0 ) categoryId = null
System . write (" Priority ( 1-3 ) : ")
var priority = Num.fromString(Stdin.readLine()) || 1
System . write (" Due date ( YYYY-MM-DD or empty ) : ")
var dueDate = Stdin.readLine()
if ( dueDate . count = = 0 ) dueDate = null
var id = _tasks.createTask(title, description , categoryId , priority , dueDate )
System . print ("\ nTask created with ID: %( id )")
}
completeTask () {
System . write ("\ nTask ID to complete: ")
var id = Num.fromString(Stdin.readLine())
if ( id ) {
_tasks . completeTask ( id )
System . print (" Task %( id ) marked as complete !")
}
}
showOverdue () {
var tasks = _tasks.findOverdueTasks()
if ( tasks . count = = 0 ) {
System . print ("\ nNo overdue tasks !")
return
}
System . print ("\ n--- Overdue Tasks --- ")
for ( task in tasks ) {
System . print ("%( task [" id "]). %( task [" title "]) ( Due: %( task [" due_date "]))")
}
}
showStats () {
var stats = _tasks.getStats()
System . print ("\ n--- Statistics --- ")
System . print (" Total tasks: %( stats [" total "])")
System . print (" Completed: %( stats [" completed "])")
System . print (" Pending: %( stats [" pending "])")
System . print (" Overdue: %( stats [" overdue "])")
if ( stats [" total "] > 0) {
var pct = (stats["completed"] / stats["total"] * 100).round
System.print("Completion rate: %(pct)\%")
}
}
manageCategories() {
var categories = _tasks.findCategories()
System.print("\n--- Categories ---")
for (cat in categories) {
System.print("%(cat["id"]). %(cat["name"])")
}
System.write("\nAdd new category? (y/n): ")
if (Stdin.readLine().lower == "y") {
System.write("Category name: ")
var name = Stdin.readLine()
if (name.count > 0) {
_tasks.createCategory(name, "#808080")
System.print("Category created!")
}
}
}
}
var app = TaskApp.new()
app.run()</ code ></ pre >
< h2 > Running the Application</ h2 >
< pre >< code > $ wren_cli task_app.wren
=== Task Manager v2 ===
--- Main Menu ---
1. List all tasks
2. Add new task
3. Complete task
4. Show overdue
5. Statistics
6. Categories
0. Exit
Choice: 2
--- Add Task ---
Title: Complete database tutorial
Description: Learn SQLite with Wren-CLI
Categories:
1. Work
2. Personal
3. Shopping
Category ID (or 0 for none): 1
Priority (1-3): 3
Due date (YYYY-MM-DD or empty): 2024-12-31
Task created with ID: 1</ code ></ pre >
< div class = "admonition tip" >
< div class = "admonition-title" > Tip</ div >
< p > Always use parameterized queries with < code > ?</ code > placeholders instead of string concatenation. This prevents SQL injection vulnerabilities.</ p >
</ div >
< div class = "admonition note" >
< div class = "admonition-title" > Note</ div >
< p > Use < code > ":memory:"</ code > as the database path for testing without creating a file on disk.</ p >
</ div >
< h2 > Next Steps</ h2 >
< ul >
< li > Add data export to JSON or CSV</ li >
< li > Implement task reminders with < a href = "../api/timer.html" > Timer</ a ></ li >
< li > Generate HTML reports with < a href = "template-rendering.html" > Jinja templates</ a ></ li >
< li > See the < a href = "../api/sqlite.html" > SQLite API reference</ a ></ li >
</ ul >
</ article >
< footer class = "page-footer" >
< a href = "websocket-chat.html" class = "prev" > WebSocket Chat</ a >
< a href = "template-rendering.html" class = "next" > Template Rendering</ a >
</ footer >
</ main >
</ div >
< script src = "../js/main.js" ></ script >
</ body >
</ html >