Local Query Engine (SQLite) lets an OutSystems 11 Mobile or Reactive app run real SQL against its local storage (local entities): SELECT, INSERT, UPDATE, DELETE and REPLACE, plus CREATE, DROP and ALTER on native devices. Version 2.0.0 is a full rewrite of the engine; existing apps keep working without changes.
You call one client action, ExecuteQuery, with the same code everywhere. The component detects where the app runs and picks the right engine:
ExecuteQuery
Runtime
How the query runs
Writes
Native mobile app (Cordova build)
Directly on the device SQLite database
Native transaction
Browser, PWA, Service Studio preview
Referenced tables are loaded from IndexedDB into an in-memory SQLite (sql.js 1.8.0)
Changed rows written back to IndexedDB in one transaction
Use it for queries local aggregates cannot express: sub-queries, GROUP BY ... HAVING, UNION, self-joins, bulk updates, and ad-hoc inspection of local data while debugging.
GROUP BY ... HAVING
UNION
Most apps need only steps 1 and 2; the rest is optional.
Configure
No extensibility configuration or Cordova plugin is required. Removed the cordova-sqlite-porter plugin; a mobile build that still lists it after the upgrade should be rebuilt.
A SELECT returns its rows as a JSON array in JSONString; deserialize it into a list of your own structure.
JSONString
ProductRow
Name
Price
SQL_Query
"SELECT Name AS Name, Price AS Price FROM LocalProduct WHERE Price > ? ORDER BY Price DESC"
Parameters
"[100]"
ReadOnly
True
ExecuteQuery.IsError
ExecuteQuery.ErrorMessage
ExecuteQuery.JSONString
ExecuteQuery.RowCount
A write statement works the same way; check RowsAffected instead of reading rows:
RowsAffected
UPDATE LocalProduct SET IsModified = 1 WHERE CategoryId = ?
with Parameters = "[3]" returns RowsAffected = the number of products changed. Use AS aliases so the JSON property names match your structure's attribute names (see Supported SQL).
"[3]"
AS
All public elements are client-side and work in both runtimes.
Runs one SQL statement against local storage and returns the result as JSON. Errors never raise an exception; they come back in IsError and ErrorMessage.
IsError
ErrorMessage
Parameter
Direction
Type
Description
In, mandatory
Text
One SQL statement (SQLite dialect). Entity names or physical table names.
In, optional
JSON array of values for the ? placeholders, in order, e.g. ["Lisbon", 10, true]. Booleans are sent as 1/0. Default empty.
?
["Lisbon", 10, true]
Boolean
True allows only SELECT, WITH, PRAGMA, EXPLAIN and VALUES. Default False.
Out
Read: JSON array of row objects ([] when no rows). Write: {"message":"Command executed successfully","rowsAffected":n}. On error: the error text (kept for 1.x compatibility).
[]
{"message":"Command executed successfully","rowsAffected":n}
True when the statement could not run. An empty result is not an error.
Error description, empty on success.
RowCount
Integer
Rows returned by a read statement.
Rows inserted, updated or deleted by a write statement.
StatementType
READ, DML or DDL.
DurationMs
Execution time in milliseconds, including loading data.
FullJson
Object
Write statements: JSON text of every changed table, { "PhysicalTableName": [rows] }. {} for reads.
{ "PhysicalTableName": [rows] }
{}
Return every user table of the local database as { "PhysicalTableName": [rows] }; system tables (OSSYS_*) are excluded. Both names behave the same in both runtimes; two names are kept for 1.x compatibility. They load all data, so avoid them on very large databases.
TableString
Snapshot as JSON text; {} on error.
TableObject
Same snapshot as a JavaScript object, for other JavaScript nodes.
True when the database could not be read.
Optional overrides, stored in client variables so they survive restarts. An empty input keeps the current value.
MobileDatabaseName
Native SQLite database name. Normally resolved automatically as ServiceCenterUID_ApplicationKey.
WebDatabaseName
IndexedDB database name for browser/PWA. Normally discovered automatically.
SqlJsBaseUrl
URL ending with / that serves sql-wasm.js and sql-wasm.wasm. Default: cdnjs sql.js 1.8.0.
/
ResetToAutomatic
True clears stored settings first, so everything is resolved automatically again.
Sorts a JSON array of objects by the value of each object's first property (numbers numerically, text alphabetically) and returns it pretty-printed. Input that is not a JSON array is returned unchanged.
JSON array of objects, e.g. ExecuteQuery.JSONString.
SortedJSONString
Sorted, indented JSON, or the original input.
Kept so 1.x consumers keep working. It calls ExecuteQuery with SQL_Query and returns JSONString and IsError. Use ExecuteQuery in new code.
Renders nothing. Place it once on a layout to preload sql.js in browser/PWA runtimes. Not required: ExecuteQuery loads sql.js on demand.
The dialect is SQLite in both runtimes, one statement per call. A trailing ; and -- comments are allowed.
;
--
Statement
Native mobile
Browser / PWA
SELECT, WITH, VALUES, EXPLAIN, PRAGMA
Yes
INSERT, UPDATE, DELETE, REPLACE
Yes, native transaction
Yes, written back to IndexedDB in one transaction
CREATE, DROP, ALTER
No, returns an error (OutSystems manages the browser schema)
BEGIN, COMMIT, ATTACH and other statements
No
Several statements in one call
Table names. Write entity names (LocalProduct) or physical names (OSUSR_ABC_LOCALPRODUCT). Entity names are matched case-insensitively, using OutSystems metadata (OSSYS_ENTITY) first and the physical-name prefix as a fallback. Names inside string literals and comments are never changed. When two modules have a local entity with the same name, use the physical name.
LocalProduct
OSUSR_ABC_LOCALPRODUCT
Column names. SQL is case-insensitive, so First_Name and FIRST_NAME both work. Result rows use the stored column names, usually the UPPERCASE attribute names (FIRST_NAME). Use AS aliases (SELECT First_Name AS FirstName) to control the JSON property names for JSON Deserialize.
First_Name
FIRST_NAME
SELECT First_Name AS FirstName
Stored values. OutSystems stores Booleans as 1/0 and Identifiers and Long Integers as text. Compare Booleans with = 1. Identifier comparisons such as WHERE StudentsId = 4 work because SQLite converts the number to text.
= 1
WHERE StudentsId = 4
Inserts. Local entities whose Id mirrors a server entity are not auto-number, so an INSERT must give the Id explicitly. For auto-number local entities the component assigns the next Id the same way OutSystems does.
Always check IsError after ExecuteQuery and show ErrorMessage. The action never raises an exception for SQL problems, so your flow continues either way. Messages come from the component or from SQLite:
Message (start)
Cause
Fix
SQL_Query is empty.
No statement was passed
Pass a statement
Only one SQL statement per call is supported.
Two statements separated by ;
Call ExecuteQuery once per statement
ReadOnly is True: only SELECT/WITH/PRAGMA statements are allowed.
A write with ReadOnly = True
Expected when blocking user writes; otherwise set ReadOnly = False
Unsupported statement.
Unknown first keyword, often a typo such as SELEC
SELEC
Correct the statement
CREATE, DROP and ALTER are only supported on native mobile devices.
DDL in the browser
Run DDL on device only, or change the entity in Service Studio
Parameters must be a JSON array
Parameters is not valid JSON
Pass e.g. ["Lisbon", 10]
["Lisbon", 10]
no such table: X
Unknown entity or table name
Check the entity name; it must be a local entity of the running app
no such column: X
Unknown column
Check the attribute name
Cannot insert into X: a value for the primary key column ... is required.
INSERT without the Id of an entity that is not auto-number
Give the Id explicitly
The local database name could not be resolved.
Native app has never run online
Run the app online once, or call Configure with MobileDatabaseName
No OutSystems local database was found in this browser
App has no local entities, or never ran in this browser
Open the app once so OutSystems creates its local storage, or call Configure with WebDatabaseName
Could not load sql.js from ...
Browser offline on first use, or blocked by Content-Security-Policy
Serve sql.js yourself and call Configure with SqlJsBaseUrl
HAVING
SELECT S.First_Name AS FirstName ...
sql-wasm.js
sql-wasm.wasm
IsModified = 1
DatabaseToJSON*
Known limitations
BEGIN
COMMIT
Upgrading from 1.x
All public actions keep their signatures; new inputs are optional, so existing calls compile after Refresh Dependencies. Review these behaviour changes:
Area
1.x
2.0.0
SELECT with no rows
IsError = True, "No data found"
IsError = False, JSONString = []
CREATE / DROP / ALTER
Ran in memory and were lost
Native only; browser returns an error
Partly executed
Rejected with an error
Feedback message on web writes
Shown by the component
Removed; the app decides what to show
Mobile plugin
cordova-sqlite-porter required
No plugin; rebuild the mobile app
ExecuteQuery_Old
Separate implementation
Deprecated wrapper around ExecuteQuery
Does the same code run on mobile and web? Yes. Your app calls the same actions with the same inputs and gets the same outputs; only DDL differs (native only).
My query returns no rows, but the aggregate shows data. Check the filter values against stored formats: Booleans are 1/0, Identifiers are text. Also confirm the entity belongs to the running app.
JSON Deserialize leaves my structure empty. The JSON property names do not match the attribute names. Alias the columns with AS to the exact attribute names.
Changes made with UPDATE do not appear on screen. Refresh the screen aggregate after the call; it does not watch local storage for direct writes.
Do SQL writes sync to the server? Only through your own sync logic. Set the flags your sync reads (for example IsModified = 1) in the statement.
Does it work offline? On native devices yes, after the database name has been resolved once online. In the browser yes once sql.js is cached, or always when you serve sql.js yourself through Configure.
Can end users type SQL? Only with ReadOnly = True and with values bound through Parameters.
Where do I report a problem? In the Forge discussion of Local Query Engine (SQLite). Include the runtime (device or browser), the SQL and the ErrorMessage.
Getting Started with the Demo
Installation:
Demo Overview:
Features Demonstrated in the Demo
Executing SQL Queries:
Database to JSON Conversion:
Web Block Integration:
Interactive SQL Command Execution: