From redaxo-core
Provides rex_sql patterns for safe REDAXO database access: prepared statements, transactions, fluent inserts/updates, and rex::getTable() prefixes. Use for SQL in REDAXO projects or rex_* tables.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-coreThis skill uses the workspace's default tool permissions.
`rex_sql` is REDAXO's database wrapper. It enforces prepared statements, namespaced table prefixes (`rex_`), and ships with a fluent builder for inserts/updates.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
rex_sql is REDAXO's database wrapper. It enforces prepared statements, namespaced table prefixes (rex_), and ships with a fluent builder for inserts/updates.
Always prefer rex_sql over raw PDO so you get the configured connection, prefix handling, and the addon's logging.
rex::getTable() helperREDAXO prefixes all tables (default rex_). Never hardcode the prefix – use rex::getTable('article') which returns rex_article. This keeps code portable across installations that use a different prefix (e.g. multi-instance setups).
$table = rex::getTable('article'); // → "rex_article"
$tableMyAddon = rex::getTable('my_addon_items');
setQuery with placeholdersAlways use parameter binding. Never concatenate user input into SQL.
$sql = rex_sql::factory();
$sql->setQuery(
'SELECT id, name FROM ' . rex::getTable('article') . '
WHERE clang_id = :clang AND status = :status
ORDER BY priority',
['clang' => rex_clang::getCurrentId(), 'status' => 1]
);
foreach ($sql as $row) {
echo (int) $row->getValue('id') . ': ' . rex_escape($row->getValue('name')) . "\n";
}
Iterating the $sql object yields one row at a time. Inside the loop, $row is the same object positioned on the current row.
To get all rows as an array (small result sets only):
$rows = $sql->getArray();
$sql = rex_sql::factory();
$sql->setQuery('SELECT COUNT(*) AS cnt FROM ' . rex::getTable('article') . ' WHERE status = ?', [1]);
$count = (int) $sql->getValue('cnt');
Positional placeholders (?) work too – pass values as a positional array.
$sql = rex_sql::factory();
$sql->setTable(rex::getTable('my_addon_log'));
$sql->setValue('user_id', rex::getUser()->getId());
$sql->setValue('action', 'login');
$sql->setDateTimeValue('created_at', time()); // formats to MySQL datetime
$sql->insert();
$newId = (int) $sql->getLastId();
$sql = rex_sql::factory();
$sql->setTable(rex::getTable('my_addon_log'));
$sql->setWhere(['id' => $logId]); // becomes "WHERE id = :id"
$sql->setValue('action', 'logout');
$sql->update();
For complex WHERE clauses, pass the SQL and parameters separately:
$sql->setWhere('user_id = :uid AND created_at > :since', [
'uid' => $userId,
'since' => '2026-01-01',
]);
$sql = rex_sql::factory();
$sql->setTable(rex::getTable('my_addon_log'));
$sql->setWhere(['id' => $logId]);
$sql->delete();
$sql = rex_sql::factory();
$sql->beginTransaction();
try {
// multiple statements...
$sql->commit();
} catch (Throwable $e) {
$sql->rollBack();
throw $e;
}
rex_sql::factory() returns the same singleton connection – no need to share an instance manually. New rex_sql instances reuse the underlying PDO.
rex_sql_tableFor install.php / update.php in addons, use rex_sql_table instead of raw CREATE TABLE:
rex_sql_table::get(rex::getTable('my_addon_items'))
->ensurePrimaryIdColumn()
->ensureColumn(new rex_sql_column('title', 'varchar(255)', false, ''))
->ensureColumn(new rex_sql_column('body', 'text', true))
->ensureColumn(new rex_sql_column('created_at', 'datetime', false))
->ensureIndex(new rex_sql_index('idx_created', ['created_at']))
->ensure();
ensure() is idempotent – safe to run on every install/update. It compares the desired schema with the actual one and only applies the diff.
rex_ as the table prefix – use rex::getTable().mysqli_* or raw PDO directly – breaks with non-default DB configs.getValue() on a row object that hasn't been fetched (after exhausted iteration). Re-run the query or seek with $sql->next().getArray() consumes the result set – call it once, then operate on the array.