SQL Query Builder
LavaLust includes a PDO-based Database class that supports a fluent Query Builder pattern and direct raw SQL execution. All values passed through the builder are automatically bound as prepared statement parameters, protecting against SQL injection by default.
Configuration
Database connection settings live in app/config/database.php.
The main group is used by default when no group name is specified.
<?php
$database['main'] = [
'driver' => 'mysql',
'hostname' => 'localhost',
'port' => '3306',
'username' => 'root',
'password' => '',
'database' => 'my_database',
'charset' => 'utf8mb4',
'dbprefix' => '',
];
Tip
Never commit database credentials to version control. Load them from environment variables instead:
<?php
$database['main'] = [
'driver' => 'mysql',
'hostname' => getenv('DB_HOST'),
'username' => getenv('DB_USER'),
'password' => getenv('DB_PASS'),
'database' => getenv('DB_NAME'),
'charset' => 'utf8mb4',
'dbprefix' => '',
];
Configuration Values
Key |
Required |
Description |
|---|---|---|
|
Yes |
Database driver. See Supported Drivers below. Must be lowercase. |
|
Yes |
Hostname or IP of the database server. For SQLite, leave blank —
use |
|
No |
Port number. Omit to use the driver default. |
|
Yes |
Database user. Not required for SQLite. |
|
Yes |
Password for the database user. Not required for SQLite. |
|
Yes |
Name of the database. Not required for SQLite. |
|
No |
Connection character set. Recommended: |
|
No |
Optional table name prefix prepended by the Query Builder. |
|
SQLite only |
Absolute path to the SQLite |
Supported Drivers
Driver value |
Database |
Default port |
Notes |
|---|---|---|---|
|
MySQL / MariaDB |
|
Recommended for most applications. |
|
PostgreSQL |
|
Requires the PHP |
|
Microsoft SQL Server |
|
Requires the PHP |
|
SQLite 3 |
n/a |
File-based. No |
Note
LIMIT syntax is automatically adapted per driver so the same
->limit() call produces correct SQL for MySQL, PostgreSQL, SQLite,
and SQL Server without any extra configuration.
Connecting
Auto-connect (recommended)
Register 'database' in app/config/autoload.php to connect on every
request:
<?php
$autoload['libraries'] = ['database'];
This connects to the main group and assigns the instance to $this->db.
Manual connect
<?php
// Connect to the 'main' group → $this->db
$this->call->database();
// Connect to a named group → $this->analytics
$this->call->database('analytics');
Multiple simultaneous connections
<?php
$this->call->database(); // → $this->db
$this->call->database('analytics'); // → $this->analytics
$users = $this->db->table('users')->get_all();
$events = $this->analytics->table('events')->get_all();
Note
The property name on $this always matches the group name from
database.php. The default main group is always $this->db.
Query Builder
Selecting Data
<?php
// SELECT * FROM users
$users = $this->db->table('users')->get_all();
// SELECT specific columns
$users = $this->db->table('users')
->select('id, name, email')
->get_all();
// Aggregate functions with optional alias
$this->db->table('orders')->select_count('id', 'total')->get_all();
$this->db->table('products')->select_max('price', 'highest')->get_all();
$this->db->table('products')->select_min('price', 'lowest')->get_all();
$this->db->table('products')->select_sum('stock', 'total_stock')->get_all();
$this->db->table('products')->select_avg('price', 'avg_price')->get_all();
$this->db->table('users')->select_distinct('country')->get_all();
Aggregate method reference
Method |
SQL equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Joins
<?php
$this->db->table('posts')->inner_join('users', 'users.id = posts.user_id')->get_all();
$this->db->table('posts')->left_join('comments', 'comments.post_id = posts.id')->get_all();
$this->db->table('posts')->right_join('categories', 'categories.id = posts.cat_id')->get_all();
$this->db->table('orders')->full_outer_join('customers', 'customers.id = orders.user_id')->get_all();
$this->db->table('orders')->left_outer_join('products', 'products.id = orders.product_id')->get_all();
$this->db->table('orders')->right_outer_join('products', 'products.id = orders.product_id')->get_all();
Join method reference
Method |
SQL join type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WHERE Conditions
Basic WHERE
<?php
// Equality (implicit =)
$this->db->table('users')->where('status', 'active')->get_all();
// With explicit operator
$this->db->table('products')->where('price', '>', 100)->get_all();
$this->db->table('orders')->where('total', '>=', 500)->get_all();
// Multiple conditions (AND by default)
$this->db->table('users')
->where('status', 'active')
->where('role', 'admin')
->get_all();
// Array syntax
$this->db->table('users')
->where(['status' => 'active', 'role' => 'admin'])
->get_all();
// OR / NOT variants
$this->db->table('users')->or_where('role', 'moderator')->get_all();
$this->db->table('users')->not_where('status', 'banned')->get_all();
$this->db->table('users')->or_not_where('status', 'suspended')->get_all();
NULL checks
<?php
$this->db->table('users')->where_null('deleted_at')->get_all();
$this->db->table('users')->where_not_null('email_verified_at')->get_all();
LIKE
<?php
$this->db->table('users')->like('name', '%john%')->get_all();
$this->db->table('users')->not_like('name', 'admin')->get_all();
$this->db->table('users')->or_like('email', 'gmail')->get_all();
$this->db->table('users')->or_not_like('name', 'test')->get_all();
BETWEEN
<?php
$this->db->table('products')->between('price', 100, 500)->get_all();
$this->db->table('orders')->not_between('total', 0, 10)->get_all();
$this->db->table('products')->or_between('price', 50, 100)->get_all();
$this->db->table('products')->or_not_between('stock', 0, 5)->get_all();
IN
<?php
$this->db->table('users')->in('id', [1, 2, 3])->get_all();
$this->db->table('users')->not_in('status', ['banned', 'suspended'])->get_all();
$this->db->table('users')->or_in('role', ['admin', 'moderator'])->get_all();
$this->db->table('users')->or_not_in('id', [99, 100])->get_all();
Grouped conditions
Use grouped() with a closure to wrap conditions in parentheses:
<?php
// WHERE status = 'active' AND (role = 'admin' OR role = 'moderator')
$this->db->table('users')
->where('status', 'active')
->grouped(function($q) {
$q->where('role', 'admin')
->or_where('role', 'moderator');
})
->get_all();
WHERE method reference
Method |
Description |
|---|---|
|
AND WHERE |
|
OR WHERE |
|
AND NOT WHERE |
|
OR NOT WHERE |
|
AND WHERE col IS NULL |
|
AND WHERE col IS NOT NULL |
|
AND WHERE col LIKE |
|
AND WHERE col NOT LIKE |
|
OR WHERE col LIKE |
|
OR WHERE col NOT LIKE |
|
AND WHERE col BETWEEN |
|
AND WHERE col NOT BETWEEN |
|
OR WHERE col BETWEEN |
|
OR WHERE col NOT BETWEEN |
|
AND WHERE col IN (…) |
|
AND WHERE col NOT IN (…) |
|
OR WHERE col IN (…) |
|
OR WHERE col NOT IN (…) |
|
Wrap conditions in parentheses |
Ordering, Grouping & Limiting
<?php
// ORDER BY
$this->db->table('posts')->order_by('created_at', 'DESC')->get_all();
$this->db->table('products')->order_by('RAND()')->get_all();
// GROUP BY — string or array
$this->db->table('orders')->group_by('status')->get_all();
$this->db->table('orders')->group_by(['status', 'user_id'])->get_all();
// HAVING
$this->db->table('orders')
->select('user_id, SUM(total) as total_spent')
->group_by('user_id')
->having('total_spent', '>', 1000)
->get_all();
// LIMIT (driver-aware)
$this->db->table('posts')->limit(10)->get_all();
$this->db->table('posts')->limit(20, 10)->get_all(); // offset 20, take 10
// OFFSET (standalone)
$this->db->table('posts')->limit(10)->offset(20)->get_all();
// Pagination shortcut — page number based
$this->db->table('posts')->pagination(10, 2)->get_all(); // page 2, 10/page
// COUNT rows matching conditions
$total = $this->db->table('users')->where('status', 'active')->count();
Inserting Data
<?php
// Single row
$this->db->table('users')->insert([
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'created_at' => date('Y-m-d H:i:s'),
]);
$id = $this->db->last_id();
// Bulk insert — multiple rows in one query
$this->db->table('users')->bulk_insert([
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com'],
]);
Updating Data
<?php
// Standard update with WHERE
$this->db->table('users')
->where('id', 42)
->update(['name' => 'Jane Smith', 'updated_at' => date('Y-m-d H:i:s')]);
// Increment / decrement a numeric column
$this->db->table('posts')->where('id', 1)->increment('views');
$this->db->table('products')->where('id', 5)->decrement('stock', 3);
// Bulk update — different values per row, keyed by primary key
$this->db->table('users')->bulk_update([
['id' => 1, 'status' => 'active'],
['id' => 2, 'status' => 'inactive'],
], 'id');
Deleting Data
<?php
// Single row
$this->db->table('users')->where('id', 42)->delete();
// Multiple rows
$this->db->table('users')->in('id', [1, 2, 3])->delete();
// Check rows affected
$deleted = $this->db->table('sessions')
->where('last_seen', '<', date('Y-m-d', strtotime('-30 days')))
->delete();
Result Methods
<?php
// All rows as associative arrays
$rows = $this->db->table('users')->result_array();
// All rows as stdClass objects
$rows = $this->db->table('users')->result();
// All rows as numeric arrays
$rows = $this->db->table('users')->result_num();
// Single row as associative array / object
$user = $this->db->table('users')->where('id', 1)->row_array();
$user = $this->db->table('users')->where('id', 1)->row();
// Single row as numeric array
$row = $this->db->table('users')->where('id', 1)->row_num();
// Row by index (0-based)
$second = $this->db->table('users')->row_array(1);
// First and last row
$first = $this->db->table('posts')->order_by('id')->first_row();
$last = $this->db->table('posts')->order_by('id')->last_row();
// Map to a custom class
$objects = $this->db->table('users')->custom_result_object('UserEntity');
$object = $this->db->table('users')->where('id', 1)->custom_row_object(0, 'UserEntity');
// Single column from all rows (0-based column index)
$emails = $this->db->table('users')->select('email')->get_column(0);
// Key-value pairs — first column as key, second as value (great for dropdowns)
$options = $this->db->table('users')->select('id, name')->get_key_pair();
// Group results by the first column's value
$grouped = $this->db->table('orders')->select('status, id, total')->get_grouped();
// Counts
$num = $this->db->row_count(); // rows affected by last exec()
$num = $this->db->num_rows(); // rows in last get() / get_all() result
// Last insert ID and last SQL string
$id = $this->db->last_id();
$sql = $this->db->get_sql();
Result method reference
Method |
Returns |
|---|---|
|
All rows as array of associative arrays |
|
All rows as array of stdClass objects |
|
All rows as array of numeric arrays |
|
Single row as associative array (optional 0-based index) |
|
Single row as stdClass object (optional 0-based index) |
|
Single row as numeric array |
|
First row as stdClass object |
|
Last row as stdClass object |
|
All rows mapped to instances of |
|
Single row at |
|
All values from one column (0-based column index) |
|
Associative array — first column as key, second as value |
|
Results grouped by the first column’s value |
|
Row count from the last |
|
Rows affected by the last |
|
Auto-increment ID of the last inserted row |
|
Last SQL string sent to the database |
Raw Queries
<?php
// Simple raw query
$stmt = $this->db->raw('SELECT * FROM users WHERE status = "active"');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// With bound parameters (always preferred for user input)
$stmt = $this->db->raw(
'SELECT * FROM users WHERE email = ? AND status = ?',
['jane@example.com', 'active']
);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
Warning
Always pass user input as bound parameters to raw(). Never
interpolate $_GET or $_POST values directly into a SQL string.
Transactions
Group multiple write operations into a single atomic unit. Nested transactions are supported via savepoints.
<?php
$this->db->transaction();
try
{
$this->db->table('orders')->insert($order_data);
$order_id = $this->db->last_id();
foreach ($items as $item) {
$this->db->table('order_items')->insert([
'order_id' => $order_id,
'product_id' => $item['id'],
'qty' => $item['qty'],
]);
$this->db->table('products')
->where('id', $item['id'])
->decrement('stock', $item['qty']);
}
$this->db->commit();
}
catch (Exception $e)
{
$this->db->roll_back();
log_message('error', 'Order transaction failed: ' . $e->getMessage());
}
Method |
Description |
|---|---|
|
Begin a transaction (or create a savepoint for nested calls) |
|
Commit the transaction (or release the savepoint) |
|
Roll back to the last savepoint, or fully roll back the transaction |
Tips and Best Practices
Practice |
Details |
|---|---|
Keep SQL in models |
Controllers should call model methods; never build queries directly in a controller. |
Prefer the Query Builder |
Values are bound as prepared statement parameters automatically — no manual escaping needed. |
Use bound parameters for raw SQL |
Always pass user input as |
Use transactions for multi-step writes |
Wrap any operation involving more than one related INSERT/UPDATE/DELETE
in |
Avoid |
Always name the columns you need to prevent accidentally exposing sensitive fields and to reduce unnecessary data transfer. |
Use |
Returns |
Secure credentials |
Load database passwords from environment variables and never commit them to version control. |
Debug with |
Call |