[], 'order' => [], 'limit' => 0, 'values' => [], ]; /** * Open a connection to the database. */ public function __construct(string $path, array $opts = []) { $opts = !empty($opts) ? $opts : [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]; try { $this->c = new PDO("sqlite:$path", null, null, $opts); $this->c->exec('PRAGMA foreign_keys = ON;'); // Enable foreign keys $this->c->exec('PRAGMA journal_mode = WAL;'); // Enable WAL } catch (PDOException $e) { $this->error = "Failed to open database: " . $e->getMessage(); throw $e; } } /** * Change the current working table. */ public function table(string $name): Database { $this->table = $name; return $this; } /** * Create a table with the name of the current working table. Will drop * the table if it already exists. */ public function create(array $columns): PDOStatement|false { $query = "CREATE TABLE IF NOT EXISTS $this->table (" . implode(', ', $columns) . ');'; $this->addToLog($query); return $this->c->query($query); } /** * Drop the current working table if it exists. */ public function drop(): PDOStatement|false { $query = "DROP TABLE IF EXISTS $this->table;"; $this->addToLog($query); return $this->c->query($query); } /** * Return the current working table's schema. */ public function schema(): array { $query = "PRAGMA table_info($this->table);"; $this->addToLog($query); return $this->c->query($query)->fetchAll(PDO::FETCH_ASSOC); } /** * Insert data into the current working table. Pass an array of key/value * pairs to insert one record, or an array of arrays to insert multiple. Returns * the number of rows inserted, or false on failure. */ public function insert(array $data): int|false { // If the first element is not an array we can assume we're doing a single insert. if (!isset($data[0])) { $query = "INSERT INTO $this->table (" . implode(', ', array_keys($data)) . ')' . ' VALUES (' . implode(', ', array_fill(0, count($data), '?')) . ');'; $this->addToLog($query); return $this->c->prepare($query)->execute(array_values($data)); } // Otherwise we will build a multi-insert query. $query = "INSERT INTO $this->table (" . implode(', ', array_keys($data[0])) . ') VALUES '; $placeholders = []; $values = []; foreach ($data as $row) { $placeholders[] = '(' . implode(', ', array_fill(0, count($row), '?')) . ')'; foreach ($row as $value) $values[] = $value; } $query .= implode(', ', $placeholders) . ';'; $this->addToLog($query); return $this->c->prepare($query)->execute($values); } /** * Add a where clause to the builder. All values are paramaterized. */ public function where(string $column, string $operator, mixed $value): Database { $this->builder['where'][] = "$column $operator ?"; $this->builder['values'][] = $value; return $this; } /** * Add an order clause to the builder. */ public function order(string $column, string $direction = 'ASC'): Database { $this->builder['order'] = "$column $direction"; return $this; } /** * Set a limit in the builder. */ public function limit(int $limit): Database { $this->builder['limit'] = $limit; return $this; } /** * Build a select query and return the result. By default selects the entire * row. Optionally use fetchAll, but there is also the selectAll() alias method * for this. */ public function select(string $what = '*', bool $fetchAll = false): array|false { $query = "SELECT $what FROM $this->table"; if (!empty($this->builder['where'])) $query .= " WHERE " . implode(' AND ', $this->builder['where']); if (!empty($this->builder['order'])) $query .= " ORDER BY " . $this->builder['order']; if (!empty($this->builder['limit'])) $query .= " LIMIT " . $this->builder['limit']; $this->addToLog($query); try { $stmt = $this->c->prepare($query); $stmt->execute($this->builder['values']); } catch (PDOException $e) { $this->error = "Failed to execute query: " . $e->getMessage(); return false; } $this->resetBuilder(); return $fetchAll ? $stmt->fetchAll() : $stmt->fetch(); } /** * Delete records from the current working table. Returns the number of rows * deleted, or false on failure. Uses where clauses if set. */ public function delete(): int|false { $query = "DELETE FROM $this->table"; if (!empty($this->builder['where'])) $query .= " WHERE " . implode(' AND ', $this->builder['where']); $this->addToLog($query); $this->resetBuilder(); return $this->c->exec($query); } /** * Update records in the current working table. Returns the number of rows * updated, or false on failure. Uses where clauses if set. */ public function update(array $data): int|false { $query = "UPDATE $this->table SET " . implode(', ', array_keys($data)) . ' = ?'; if (!empty($this->builder['where'])) $query .= " WHERE " . implode(' AND ', $this->builder['where']); $this->addToLog($query); $values = array_merge(array_values($data), $this->builder['values']); $this->resetBuilder(); return $this->c->prepare($query)->execute($values); } /** * Get the number of rows in the current working table. Can use where clauses. */ public function count(): int|false { return $this->select('COUNT(*)', true)[0]['COUNT(*)']; } /** * Alias for $db->select(true) */ public function selectAll(string $what = '*'): array|false { return $this->select($what, true); } /** * Return whether the given table exists. */ public function tableExists(string $name): bool { $query = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$name';"; $this->addToLog($query); return $this->c->query($query)->fetchColumn() > 0; } /** * Return the PDO instance; for when more complex operations are needed. */ public function c(): PDO { return $this->c; } /** * Alias for PDO::prepare */ public function prepare(string $query): PDOStatement { return $this->c->prepare($query); } /** * Alias for PDO::lastInsertId */ public function lastInsertId(): int { return $this->c->lastInsertId(); } /** * Alias for PDO::exec */ public function exec(string $query): int { $this->addToLog($query); return $this->c->exec($query); } /** * Alias for PDO::beginTransaction */ public function begin(): bool { return $this->c->beginTransaction(); } /** * Alias for PDO::commit */ public function commit(): bool { return $this->c->commit(); } /** * Alias for PDO::rollBack */ public function rollBack(): bool { return $this->c->rollBack(); } /** * Alias for PDO::query */ public function query(string $query): PDOStatement|false { $this->addToLog($query); return $this->c->query($query); } /** * Add the query to the log and increment the number of queries executed. */ private function addToLog(string $query): void { $this->log[] = $query; $this->queries++; } /** * Reset the builder to it's default structure. */ public function resetBuilder(): void { $this->builder = [ 'where' => [], 'order' => [], 'limit' => 0, 'values' => [], ]; } /** * Return the log of executed queries. */ public function log(): array { return $this->log; } /** * Return the number of queries executed. */ public function queries(): int { return $this->queries; } /** * Return the last error message. */ public function error(): string { return $this->error; } }