docs/docs/en/runjs/resource/sql-resource.md
A Resource for executing queries based on saved SQL configurations or dynamic SQL, with data sourced from interfaces such as flowSql:run / flowSql:runById. It is suitable for reports, statistics, custom SQL lists, and other scenarios. Unlike MultiRecordResource, SQLResource does not depend on collections; it executes SQL queries directly and supports pagination, parameter binding, template variables ({{ctx.xxx}}), and result type control.
Inheritance: FlowResource → APIResource → BaseRecordResource → SQLResource.
Creation: ctx.makeResource('SQLResource') or ctx.initResource('SQLResource'). To execute based on a saved configuration, use setFilterByTk(uid) (the UID of the SQL template). For debugging, use setDebug(true) + setSQL(sql) to execute SQL directly. In RunJS, ctx.api is injected by the runtime environment.
| Scenario | Description |
|---|---|
| Reports / Statistics | Complex aggregations, cross-table queries, and custom statistical metrics. |
| JSBlock Custom Lists | Implementing special filtering, sorting, or associations using SQL with custom rendering. |
| Chart Blocks | Driving chart data sources with saved SQL templates, supporting pagination. |
| Choosing between SQLResource and ctx.sql | Use SQLResource when pagination, events, or reactive data are required; use ctx.sql.run() / ctx.sql.runById() for simple one-off queries. |
getData() returns different formats based on setSQLType():
selectRows (default): Array, multiple row results.selectRow: Single object.selectVar: Scalar value (e.g., COUNT, SUM).getMeta() returns metadata such as pagination: page, pageSize, count, totalPage, etc.| Method | Description |
|---|---|
setFilterByTk(uid) | Sets the UID of the SQL template to execute (corresponds to runById; must be saved in the admin interface first). |
setSQL(sql) | Sets the raw SQL (used for runBySQL only when debug mode setDebug(true) is enabled). |
setSQLType(type) | Result type: 'selectVar' / 'selectRow' / 'selectRows'. |
setDebug(enabled) | When set to true, refresh calls runBySQL(); otherwise, it calls runById(). |
run() | Calls runBySQL() or runById() based on the debug state. |
runBySQL() | Executes using the SQL defined in setSQL (requires setDebug(true)). |
runById() | Executes the saved SQL template using the current UID. |
| Method | Description |
|---|---|
setBind(bind) | Binds variables. Use an object for :name placeholders or an array for ? placeholders. |
setLiquidContext(ctx) | Template context (Liquid), used to parse {{ctx.xxx}}. |
setFilter(filter) | Additional filter conditions (passed into the request data). |
setDataSourceKey(key) | Data source identifier (used for multi-data source environments). |
| Method | Description |
|---|---|
setPage(page) / getPage() | Current page (default is 1). |
setPageSize(size) / getPageSize() | Items per page (default is 20). |
next() / previous() / goto(page) | Navigates pages and triggers refresh. |
In SQL, you can use {{ctx.limit}} and {{ctx.offset}} to reference pagination parameters. SQLResource injects limit and offset into the context automatically.
| Method | Description |
|---|---|
refresh() | Executes the SQL (runById or runBySQL), writes the result to setData(data), updates meta, and triggers the 'refresh' event. |
runAction(actionName, options) | Calls underlying actions (e.g., getBind, run, runById). |
on('refresh', fn) / on('loading', fn) | Triggered when refreshing is complete or when loading starts. |
ctx.initResource('SQLResource');
ctx.resource.setFilterByTk('active-users-report'); // UID of the saved SQL template
ctx.resource.setBind({ status: 'active' });
await ctx.resource.refresh();
const data = ctx.resource.getData();
const meta = ctx.resource.getMeta(); // page, pageSize, count, etc.
const res = ctx.makeResource('SQLResource');
res.setDebug(true);
res.setSQL('SELECT * FROM users WHERE status = :status LIMIT {{ctx.limit}}');
res.setBind({ status: 'active' });
await res.refresh();
const data = res.getData();
ctx.resource.setFilterByTk('user-list-sql');
ctx.resource.setPageSize(20);
await ctx.resource.refresh();
// Navigation
await ctx.resource.next();
await ctx.resource.previous();
await ctx.resource.goto(3);
// Multiple rows (default)
ctx.resource.setSQLType('selectRows');
const rows = ctx.resource.getData(); // [{...}, {...}]
// Single row
ctx.resource.setSQLType('selectRow');
const row = ctx.resource.getData(); // {...}
// Single value (e.g., COUNT)
ctx.resource.setSQLType('selectVar');
const total = ctx.resource.getData(); // 42
ctx.defineProperty('minId', { get: () => 10 });
const res = ctx.makeResource('SQLResource');
res.setDebug(true);
res.setSQL('SELECT * FROM users WHERE id > {{ctx.minId}} LIMIT {{ctx.limit}}');
await res.refresh();
ctx.resource?.on?.('refresh', () => {
const data = ctx.resource.getData();
ctx.render(<ul>{data?.map((r) => <li key={r.id}>{r.name}</li>)}</ul>);
});
await ctx.resource?.refresh?.();
setFilterByTk(uid) must be a SQL template ID already saved in the admin interface. You can save it via ctx.sql.save({ uid, sql }).setDebug(true) uses flowSql:run, which requires the current role to have SQL configuration permissions. runById only requires the user to be logged in.refresh() within the same event loop will only execute the last one to avoid redundant requests.setBind() with :name or ? placeholders instead of string concatenation to prevent SQL injection.ctx.sql.runById is suitable for simple one-off queries.ctx.resource.