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"
# Openin-memorydatabase
db =newLibSQLdb.openExt(":memory:")
conn=db.connect()
# Createatableandinsertdataconn.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')")
# Querydatarows=conn.query("SELECT * FROM users")
whileTruerow=rows.fetchRow()
ifisNull(row)
exitokdata=row.toList()
? "ID: " + data[1] + ", Name: " + data[2] + ", Email: " + data[3]
end
# Orfetchallatonceasassociativearrayresults=conn.query("SELECT * FROM users").fetchAllAssoc()
foruserinresults
? user # Prints [["id", 1], ["name", "Alice"], ["email", "alice@example.com"]]
next
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
# Openfile-baseddatabase
db =newLibSQLdb.openFile("mydata.db")
conn=db.connect()
# Usethedatabaseconn.execute("CREATE TABLE IF NOT EXISTS products (id INTEGER, name TEXT, price REAL)")
conn.execute("INSERT INTO products VALUES (1, 'Laptop', 999.99)")
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
# Option1: ConnecttoTursohosteddatabase
db =newLibSQLdb.openRemote("libsql://your-database.turso.io", "your-auth-token")
conn=db.connect()
# Option2: Connecttoyourself-hostedLibSQLserver
# Canbelocalhost, VPS, cloudserver, or anyremotemachine
# db =newLibSQL
# db.openRemote("http://your-server.com:8080", "optional-auth-token")
# conn=db.connect()
# Executequeriesconn.execute("INSERT INTO logs (message, timestamp) VALUES ('Hello from Ring!', datetime('now'))")
# Queryremotedatarows=conn.query("SELECT * FROM logs ORDER BY timestamp DESC LIMIT 10")
logs=rows.fetchAllAssoc()
forloginlogs
? lognext
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
# Openembeddedreplicawithsyncconfigurationconfig= [
: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-syncevery60seconds
]
db =newLibSQLdb.openSyncWithConfig(config)
conn=db.connect()
# Workwithlocal replica (worksoffline)
conn.execute("INSERT INTO tasks (title, done) VALUES ('Learn Ring', 0)")
conn.execute("INSERT INTO tasks (title, done) VALUES ('Build App', 0)")
# Manuallysyncwithremotedb.sync()
# GetsyncstatisticssyncInfo=db.sync2()
? "Synced frames: " + syncInfo[2]
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
db =newLibSQLdb.openExt(":memory:")
conn=db.connect()
conn.execute("CREATE TABLE users (id INTEGER, name TEXT, score REAL, active INTEGER)")
# Preparestatementstmt=conn.prepare("INSERT INTO users (id, name, score, active) VALUES (?, ?, ?, ?)")
# Method1: Bindindividuallywithtype-specificmethodsstmt.bindInt(1, 1)
.bindString(2, "Alice")
.bindFloat(3, 95.5)
.bindInt(4, 1)
.execute()
# Method2: Bindallparametersatonce (auto-detectstypes)
stmt.reset()
.bindParams([2, "Bob", 87.3, 1])
.execute()
# Method3: Usesmartbind (handlesnulls and auto-types)
stmt.reset()
.bind(1, 3)
.bind(2, "Charlie")
.bind(3, 92.0)
.bind(4, null) # NULLvalue
.execute()
# QuerywithpreparedstatementsqueryStmt=conn.prepare("SELECT * FROM users WHERE score > ?")
queryStmt.bindFloat(1, 90.0)
results=queryStmt.query().fetchAllAssoc()
foruserinresults
? usernext
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
# Embeddedreplicawith encryption (syncswithremoteserver)
db =newLibSQLdb.openSync(
"local_encrypted.db",
"libsql://your-database.turso.io", # RemoteURLrequired"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')")
# Syncencrypteddatawithremotedb.sync()
# Cleanupconn.disconnect()
db.close()load"libsql.ring"
db =newLibSQLdb.openExt(":memory:")
conn=db.connect()
conn.execute("CREATE TABLE mixed_types ( id INTEGER, name TEXT, price REAL, data BLOB, nullable TEXT)")
# Insertwithdifferenttypesstmt=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()
# Fetchandaccessbytyperows=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)
ifisNull(nullableVal)
? "Nullable: NULL"else
? "Nullable: " + nullableValok
# Cleanupconn.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):
setRING=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=ReleaseBuild 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.