How-To Guide
⏱ ~30 minutes
Working with SQLite Databases
SQLite is a lightweight, file-based database that requires no server and no configuration — perfect for storing structured data in a HyperXTalk app. This guide walks you through everything from opening a database to performing full CRUD operations.
What you'll cover
- Opening or creating a SQLite database file
- Creating a table with SQL
- Inserting, querying, updating, and deleting records
- Iterating through query results into an array
- Closing the connection cleanly
Opening a Database
Use revOpenDatabase to open a SQLite file. If the file doesn't exist, SQLite creates it automatically. The function returns a database ID — a number you'll use in every subsequent call. If it returns something that isn't a number, the connection failed.
function openDatabase pFilePath
local tDatabaseID
put revOpenDatabase("sqlite", pFilePath) into tDatabaseID
if tDatabaseID is not a number then
throw "openDatabase error:" && tDatabaseID
end if
return tDatabaseID
end openDatabase
Call it with the full path to your database file:
local tDB, tPath
put specialFolderPath("documents") & "/myapp.db" into tPath
put openDatabase(tPath) into tDB
🟢
Store the database ID in a script-local or global variable so you can reuse it across handlers without reopening the connection each time.
Creating a Table
HyperXTalk has two commands for running SQL. Use revQueryDatabase for SELECT statements — it returns a cursor you iterate through. Use revExecuteSQL for everything else (CREATE, INSERT, UPDATE, DELETE) — it runs the statement and sets the result to an error message if something went wrong, or empty on success.
For a CREATE TABLE, use IF NOT EXISTS so it's safe to run every time the stack opens — it only creates the table if it isn't already there.
on createTable pDB
local tSQL, tResult
put "CREATE TABLE IF NOT EXISTS contacts (" into tSQL
put tSQL & "id INTEGER PRIMARY KEY AUTOINCREMENT," into tSQL
put tSQL & "name TEXT NOT NULL," into tSQL
put tSQL & "email TEXT," into tSQL
put tSQL & "phone TEXT)" into tSQL
revExecuteSQL pDB, tSQL
if the result is not empty then
throw "createTable error:" && the result
end if
end createTable
Inserting Records
Build your INSERT statement as a string and pass it to revQueryDatabase. Always close the cursor after a non-SELECT query:
on insertContact pDB, pName, pEmail, pPhone
local tSQL, tResult
put "INSERT INTO contacts (name, email, phone) VALUES ('" into tSQL
put tSQL & pName & "','" & pEmail & "','" & pPhone & "')" into tSQL
revExecuteSQL pDB, tSQL
if the result is not empty then
throw "insertContact error:" && the result
end if
end insertContact
⚠️
The example above builds SQL by concatenating strings directly. This is fine for internal apps, but if any value comes from user input, sanitise it first to avoid SQL injection — replace any single quotes in the value with two single quotes: replace "'" with "''" in tValue.
Querying Records
SELECT queries return a cursor ID. You iterate through the results row by row using revQueryIsAtEnd and revMoveToNextRecord, reading each column with revDatabaseColumnNamed. The results are stored in a two-dimensional array indexed by row number and column name:
function fetchAllContacts pDB
local tCursorID, tColumnNames, tResults, tRow
put revQueryDatabase(pDB, "SELECT * FROM contacts ORDER BY name ASC") into tCursorID
if tCursorID is not a number then
throw "fetchAllContacts error:" && tCursorID
end if
put revDatabaseColumnNames(tCursorID) into tColumnNames
put 0 into tRow
repeat until revQueryIsAtEnd(tCursorID)
add 1 to tRow
repeat for each item tColumn in tColumnNames
put revDatabaseColumnNamed(tCursorID, tColumn) into tResults[tRow][tColumn]
end repeat
revMoveToNextRecord tCursorID
end repeat
revCloseCursor tCursorID
return tResults
end fetchAllContacts
Access the results like this:
local tContacts, i
put fetchAllContacts(tDB) into tContacts
repeat with i = 1 to the number of elements of tContacts
put tContacts[i]["name"] & " — " & tContacts[i]["email"] & return after field "Results"
end repeat
Updating Records
on updateContact pDB, pID, pName, pEmail, pPhone
local tSQL, tResult
put "UPDATE contacts SET name='" & pName into tSQL
put tSQL & "', email='" & pEmail into tSQL
put tSQL & "', phone='" & pPhone & "' WHERE id=" & pID into tSQL
revExecuteSQL pDB, tSQL
if the result is not empty then
throw "updateContact error:" && the result
end if
end updateContact
Deleting Records
on deleteContact pDB, pID
revExecuteSQL pDB, "DELETE FROM contacts WHERE id=" & pID
if the result is not empty then
throw "deleteContact error:" && the result
end if
end deleteContact
Closing the Database
Always close the database when you're done — typically in a closeStack handler:
on closeStack
if sDatabaseID is a number then
revCloseDatabase sDatabaseID
end if
end closeStack
💡
Note the use of sDatabaseID — the s prefix is a convention for script-local variables, declared at the top of the stack script with local sDatabaseID. This keeps the database ID available across all handlers without making it global.
Putting It All Together
Here's a complete stack script tying everything together — opening the database when the stack opens, creating the table if needed, and closing cleanly on exit:
local sDatabaseID
on openStack
local tPath
put specialFolderPath("documents") & "/contacts.db" into tPath
try
put openDatabase(tPath) into sDatabaseID
createTable sDatabaseID
catch tError
answer "Database error:" && tError
end try
end openStack
on closeStack
if sDatabaseID is a number then
revCloseDatabase sDatabaseID
end if
end closeStack
-- Open the database
function openDatabase pFilePath
local tDatabaseID
put revOpenDatabase("sqlite", pFilePath) into tDatabaseID
if tDatabaseID is not a number then
throw "openDatabase error:" && tDatabaseID
end if
return tDatabaseID
end openDatabase
-- Create the table
on createTable pDB
local tSQL, tResult
put "CREATE TABLE IF NOT EXISTS contacts (" into tSQL
put tSQL & "id INTEGER PRIMARY KEY AUTOINCREMENT," into tSQL
put tSQL & "name TEXT NOT NULL, email TEXT, phone TEXT)" into tSQL
revExecuteSQL pDB, tSQL
if the result is not empty then
throw "createTable error:" && the result
end if
end createTable
-- Insert a contact
on insertContact pDB, pName, pEmail, pPhone
local tSQL, tResult
put "INSERT INTO contacts (name, email, phone) VALUES ('" into tSQL
put tSQL & pName & "','" & pEmail & "','" & pPhone & "')" into tSQL
revExecuteSQL pDB, tSQL
if the result is not empty then
throw "insertContact error:" && the result
end if
end insertContact
-- Fetch all contacts
function fetchAllContacts pDB
local tCursorID, tColumnNames, tResults, tRow
put revQueryDatabase(pDB, "SELECT * FROM contacts ORDER BY name ASC") into tCursorID
if tCursorID is not a number then
throw "fetchAllContacts error:" && tCursorID
end if
put revDatabaseColumnNames(tCursorID) into tColumnNames
put 0 into tRow
repeat until revQueryIsAtEnd(tCursorID)
add 1 to tRow
repeat for each item tColumn in tColumnNames
put revDatabaseColumnNamed(tCursorID, tColumn) into tResults[tRow][tColumn]
end repeat
revMoveToNextRecord tCursorID
end repeat
revCloseCursor tCursorID
return tResults
end fetchAllContacts
🟢
The same patterns work for MySQL and PostgreSQL — just change "sqlite" to "mysql" or "postgresql" in revOpenDatabase and provide the host, database name, username, and password instead of a file path.