Database

AIScript provides native database integration with type-safe queries and comprehensive drivers for several database systems. This chapter covers how to use the database modules to interact with PostgreSQL, SQLite, and Redis.

Connection Configuration

Database connections are configured in your project's project.toml file:

[database.postgresql]
url = "postgresql://username:password@localhost:5432/mydb"

[database.sqlite]
url = "sqlite://my_database.db"

[database.redis]
url = "redis://localhost:6379"
password = "optional_password"

PostgreSQL Integration

The PostgreSQL module provides a robust interface for working with PostgreSQL databases.

Basic Queries

// Import postgres module from std library
use std.db.pg;

// Simple query with a parameter
let result = pg.query("SELECT * FROM users WHERE id = $1;", 42);
print(result);

// Insert data and return the inserted row
let new_language = pg.query(
    "INSERT INTO languages (name, created_year) VALUES($1, $2) RETURNING id, name;", 
    "AIScript", 2024
);
print(new_language); // [{id: 1, name: "AIScript"}]

Type-Safe Queries with Classes

AIScript allows mapping query results to typed objects for type-safe data access:

use std.db.pg;

// Define a class representing the database table
class Language {
    id: int,
    name: str,
    created_year: int
}

// Query results are mapped to Language instances
let languages = pg.query_as(Language, "SELECT id, name, created_year FROM languages WHERE created_year > $1;", 2000);

// Now languages is an array of Language objects
print(languages);

Transactions

AIScript supports database transactions for executing multiple operations atomically:

use std.db.pg;

// Begin a transaction
let tx = pg.begin_transaction();

// Execute multiple operations within the transaction
tx.query("INSERT INTO users (name, email) VALUES($1, $2);", "Alice", "alice@example.com") |err| {
    print(f"Transaction failed: {err}");
    tx.rollback();
    return;
};
tx.query("INSERT INTO user_roles (user_id, role) VALUES(LASTVAL(), 'admin');") |err| {
    print(f"Transaction failed: {err}");
    tx.rollback();
    return;
};

// Commit the transaction if all operations succeed
tx.commit();

print("Transaction successed");

SQLite Integration

SQLite provides a lightweight database solution that doesn't require a separate server.

Basic SQLite Operations

use std.db.sqlite;

// Create a table
sqlite.query("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT, content TEXT);");

// Insert data
sqlite.query("INSERT INTO notes (title, content) VALUES($1, $2);", "Meeting Notes", "Discuss new features");

// Query data
let notes = sqlite.query("SELECT * FROM notes;");
print(notes);

SQLite Transactions

use std.db.sqlite;

let tx = sqlite.begin_transaction();

tx.query("INSERT INTO notes (title, content) VALUES($1, $2);", "Note 1", "Content 1") |err| {
    print(f"Transaction failed: {err}");
    tx.rollback();
    return;
};
tx.query("INSERT INTO notes (title, content) VALUES($1, $2);", "Note 2", "Content 2") |err| {
    print(f"Transaction failed: {err}");
    tx.rollback();
    return;
};

tx.commit();
print("Transaction failed");

Redis Integration

The Redis module provides access to Redis data structures and operations.

Basic Redis Commands

use std.db.redis;

// Set a key
redis.cmd("SET user:1 Alice");

// Get a key
let user = redis.cmd("GET user:1");
print(user); // "Alice"

// Working with hash data
redis.cmd("HSET user:2 name Bob age 30 email bob@example.com");
let name = redis.cmd("HGET user:2 name");
let all_fields = redis.cmd("HGETALL user:2");
print(name); // "Bob"
print(all_fields); // {name: "Bob", age: "30", email: "bob@example.com"}

Redis Pipelines

Redis pipelines allow sending multiple commands in a single round-trip, improving performance:

use std.db.redis;

// Create a pipeline
let p = redis.pipeline();

// Add commands to the pipeline
p.cmd("SET counter 1");
p.cmd("INCR counter");
p.cmd("INCR counter");
p.cmd("GET counter");

// Execute all commands in one round-trip
let results = p.exec();
print(results); // ["OK", 2, 3, "3"]

Redis Lists

use std.db.redis;

// Work with lists
redis.cmd("LPUSH mylist A B C");
redis.cmd("RPUSH mylist D E F");
let list = redis.cmd("LRANGE mylist 0 -1");
print(list); // ["C", "B", "A", "D", "E", "F"]

Redis Sets

use std.db.redis;

// Work with sets
redis.cmd("SADD myset apple banana orange");
redis.cmd("SADD anotherset banana kiwi");
let intersection = redis.cmd("SINTER myset anotherset");
print(intersection); // ["banana"]