LibSQL client library for Ring with SQLite compatibility and support for local, remote, and embedded replica databases.
- Full SQLite-compatible database operations
- Local file-based databases (
:memory:or file paths) - Remote database access with authentication
- Embedded replica support for offline-first applications with sync capabilities
- Prepared statements with parameter binding (int, float, string, blob, null)
- Encryption support for both local and remote databases
- WebPKI support for secure HTTPS connections
- Transaction support and connection management
- Cross-platform support (Windows, Linux, macOS, FreeBSD)
Note
This extension/library is built on top of the experimental c bindings of libsql.
This package can be installed using the Ring Package Manager (RingPM):
ringpm install ring-libsql from ysdragon
First, load the library in your Ring script:
load "libsql.ring"load "libsql.ring"
# Open in-memory database
db = new LibSQL
db.openExt(":memory:")
conn = db.connect()
# Create a table and insert data
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
conn.execute("INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')")
conn.execute("INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')")
# Query data
rows = conn.query("SELECT * FROM users")
while True
row = rows.fetchRow()
if isNull(row)
exit
ok
data = row.toList()
? "ID: " + data[1] + ", Name: " + data[2] + ", Email: " + data[3]
end
# Or fetch all at once as associative array
results = conn.query("SELECT * FROM users").fetchAllAssoc()
for user in results
? user # Prints [["id", 1], ["name", "Alice"], ["email", "alice@example.com"]]
next
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
# Open file-based database
db = new LibSQL
db.openFile("mydata.db")
conn = db.connect()
# Use the database
conn.execute("CREATE TABLE IF NOT EXISTS products (id INTEGER, name TEXT, price REAL)")
conn.execute("INSERT INTO products VALUES (1, 'Laptop', 999.99)")
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
# Option 1: Connect to Turso hosted database
db = new LibSQL
db.openRemote("libsql://your-database.turso.io", "your-auth-token")
conn = db.connect()
# Option 2: Connect to your self-hosted LibSQL server
# Can be localhost, VPS, cloud server, or any remote machine
# db = new LibSQL
# db.openRemote("http://your-server.com:8080", "optional-auth-token")
# conn = db.connect()
# Execute queries
conn.execute("INSERT INTO logs (message, timestamp) VALUES ('Hello from Ring!', datetime('now'))")
# Query remote data
rows = conn.query("SELECT * FROM logs ORDER BY timestamp DESC LIMIT 10")
logs = rows.fetchAllAssoc()
for log in logs
? log
next
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
# Open embedded replica with sync configuration
config = [
:db_path = "local_replica.db",
:primary_url = "libsql://your-database.turso.io",
:auth_token = "your-auth-token",
:read_your_writes = 1,
:sync_interval = 60 # Auto-sync every 60 seconds
]
db = new LibSQL
db.openSyncWithConfig(config)
conn = db.connect()
# Work with local replica (works offline)
conn.execute("INSERT INTO tasks (title, done) VALUES ('Learn Ring', 0)")
conn.execute("INSERT INTO tasks (title, done) VALUES ('Build App', 0)")
# Manually sync with remote
db.sync()
# Get sync statistics
syncInfo = db.sync2()
? "Synced frames: " + syncInfo[2]
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
db = new LibSQL
db.openExt(":memory:")
conn = db.connect()
conn.execute("CREATE TABLE users (id INTEGER, name TEXT, score REAL, active INTEGER)")
# Prepare statement
stmt = conn.prepare("INSERT INTO users (id, name, score, active) VALUES (?, ?, ?, ?)")
# Method 1: Bind individually with type-specific methods
stmt.bindInt(1, 1)
.bindString(2, "Alice")
.bindFloat(3, 95.5)
.bindInt(4, 1)
.execute()
# Method 2: Bind all parameters at once (auto-detects types)
stmt.reset()
.bindParams([2, "Bob", 87.3, 1])
.execute()
# Method 3: Use smart bind (handles nulls and auto-types)
stmt.reset()
.bind(1, 3)
.bind(2, "Charlie")
.bind(3, 92.0)
.bind(4, null) # NULL value
.execute()
# Query with prepared statements
queryStmt = conn.prepare("SELECT * FROM users WHERE score > ?")
queryStmt.bindFloat(1, 90.0)
results = queryStmt.query().fetchAllAssoc()
for user in results
? user
next
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
# Embedded replica with encryption (syncs with remote server)
db = new LibSQL
db.openSync(
"local_encrypted.db",
"libsql://your-database.turso.io", # Remote URL required
"your-auth-token",
1, # read_your_writes
"my-encryption-key-32-bytes-long"
)
conn = db.connect()
conn.execute("CREATE TABLE IF NOT EXISTS secrets (id INTEGER, data TEXT)")
conn.execute("INSERT INTO secrets VALUES (1, 'Top Secret Data')")
# Sync encrypted data with remote
db.sync()
# Cleanup
conn.disconnect()
db.close()load "libsql.ring"
db = new LibSQL
db.openExt(":memory:")
conn = db.connect()
conn.execute("CREATE TABLE mixed_types (
id INTEGER,
name TEXT,
price REAL,
data BLOB,
nullable TEXT
)")
# Insert with different types
stmt = conn.prepare("INSERT INTO mixed_types VALUES (?, ?, ?, ?, ?)")
stmt.bindInt(1, 1)
.bindString(2, "Product")
.bindFloat(3, 29.99)
.bindBlob(4, "Binary Data Here")
.bindNull(5)
.execute()
# Fetch and access by type
rows = conn.query("SELECT * FROM mixed_types")
row = rows.fetchRow()
? "ID (int): " + row.getIntValue(1)
? "Name (string): " + row.getStringValue(2)
? "Price (float): " + row.getFloatValue(3)
? "Data (blob): " + row.getBlobValue(4)
nullableVal = row.getValue(5)
if isNull(nullableVal)
? "Nullable: NULL"
else
? "Nullable: " + nullableVal
ok
# Cleanup
conn.disconnect()
db.close()The main database class for opening and managing database connections.
openFile(path)- Open local file databaseopenExt(path)- Open database (:memory:, file path, or URL)openRemote(url, auth_token)- Open remote databaseopenRemoteWithWebPKI(url, auth_token)- Open remote with WebPKIopenRemoteWithEncryption(url, auth_token, key)- Open remote with encryptionopenSync(db_path, url, token, read_your_writes, key)- Open embedded replicaopenSyncWithWebPKI(db_path, url, token, read_your_writes, key)- Open embedded replica with WebPKIopenSyncWithConfig(config_list)- Open with configuration list
connect()- Create connection, returns LibSQLConnection objectsync()- Manually sync embedded replica with remotesync2()- Sync and return frame statistics[frame_no, frames_synced]close()- Close database
Represents an active database connection for executing queries.
execute(sql)- Execute SQL without returning rowsquery(sql)- Execute query, returns LibSQLRows objectprepare(sql)- Prepare statement, returns LibSQLStatement objectchanges()- Get number of rows affected by last operationlastInsertRowID()- Get last inserted row ID
disconnect()- Close connectionreset()- Reset connection stateloadExtension(path, entry_point)- Load SQLite extensionsetReservedBytes(bytes)- Set reserved bytes for encryptiongetReservedBytes()- Get reserved bytes
Prepared statement with parameter binding.
bindInt(index, value)- Bind integer (1-based index)bindFloat(index, value)- Bind float (1-based index)bindString(index, value)- Bind string (1-based index)bindBlob(index, value)- Bind blob (1-based index)bindNull(index)- Bind NULL (1-based index)bind(index, value)- Smart bind (auto-detects type, 1-based index)bindParams(params_list)- Bind all parameters from list (1-based)
execute()- Execute statement without returning rowsquery()- Execute statement, returns LibSQLRows objectreset()- Reset statement for reuse
Represents query results.
columnCount()- Get number of columnscolumnName(index)- Get column name (1-based index)columnNames()- Get list of all column names
fetchRow()- Fetch next row, returns LibSQLRow or nullfetchAll()- Fetch all rows as list of listsfetchAllAssoc()- Fetch all rows as associative arrays
Represents a single result row.
getIntValue(index)- Get integer value (1-based index)getFloatValue(index)- Get float valuegetStringValue(index)- Get string valuegetBlobValue(index)- Get blob valuegetType(index)- Get column type constantgetValue(index)- Get value with automatic type conversion
toList()- Convert row to list of valuestoAssoc()- Convert row to associative array[["col", val], ...]
LIBSQL_INT- Integer column typeLIBSQL_FLOAT- Float column typeLIBSQL_TEXT- Text column typeLIBSQL_BLOB- Blob column typeLIBSQL_NULL- NULL column type
For advanced users, all underlying C functions are also available:
libsql_open_*, libsql_connect, libsql_execute, libsql_query, libsql_prepare,
libsql_bind_*, libsql_next_row, libsql_get_*, etc.
If you wish to contribute to the development of Ring LibSQL or build it from the source, follow these steps.
- CMake: Version 3.16 or higher
- C Compiler: A C compiler compatible with your platform (e.g., GCC, Clang, MSVC)
- Rust & Cargo: Required to build libsql from source
- Git: For cloning repositories
- Ring Source Code: Ring language source code must be available on your machine
-
Clone the Repository:
git clone https://github.com/ysdragon/ring-libsql.git cd ring-libsqlNote If you installed the library via RingPM, you can skip this step.
-
Set the
RINGEnvironment Variable: This variable must point to the root directory of the Ring language source code.- Windows (Command Prompt):
set RING=X:\path\to\ring
- Windows (PowerShell):
$env:RING = "X:\path\to\ring"
- Unix-like Systems (Linux, macOS or FreeBSD):
export RING=/path/to/ring
- Windows (Command Prompt):
-
Configure with CMake: Create a build directory and run CMake from within it. CMake will automatically:
- Clone libsql from GitHub if not found
- Build libsql using Cargo
mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -
Build the Project: Compile the source code using the build toolchain configured by CMake.
cmake --build .The compiled library will be available in the
lib/<os>/<arch>directory.
Contributions are always welcome! If you have suggestions for improvements or have identified a bug, please feel free to open an issue or submit a pull request.
This project is licensed under the MIT License. See the LICENSE file for more details.