Skip to content

ysdragon/ring-libsql

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ring LibSQL

LibSQL client library for Ring with SQLite compatibility and support for local, remote, and embedded replica databases.

✨ Features

  • 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.

πŸ“¦ Installation

This package can be installed using the Ring Package Manager (RingPM):

ringpm install ring-libsql from ysdragon

πŸ’‘ Usage

First, load the library in your Ring script:

load "libsql.ring"

Local Database (In-Memory)

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()

Local Database (File)

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()

Remote Database (Turso or Self-Hosted)

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()

Embedded Replica (Offline-First with Sync)

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()

Prepared Statements with Parameter Binding

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()

Database Encryption

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()

Working with Different Data Types

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()

πŸ“š API Reference

LibSQL Class (Database)

The main database class for opening and managing database connections.

Opening Databases

  • openFile(path) - Open local file database
  • openExt(path) - Open database (:memory:, file path, or URL)
  • openRemote(url, auth_token) - Open remote database
  • openRemoteWithWebPKI(url, auth_token) - Open remote with WebPKI
  • openRemoteWithEncryption(url, auth_token, key) - Open remote with encryption
  • openSync(db_path, url, token, read_your_writes, key) - Open embedded replica
  • openSyncWithWebPKI(db_path, url, token, read_your_writes, key) - Open embedded replica with WebPKI
  • openSyncWithConfig(config_list) - Open with configuration list

Database Operations

  • connect() - Create connection, returns LibSQLConnection object
  • sync() - Manually sync embedded replica with remote
  • sync2() - Sync and return frame statistics [frame_no, frames_synced]
  • close() - Close database

LibSQLConnection Class (Connection)

Represents an active database connection for executing queries.

Query Execution

  • execute(sql) - Execute SQL without returning rows
  • query(sql) - Execute query, returns LibSQLRows object
  • prepare(sql) - Prepare statement, returns LibSQLStatement object
  • changes() - Get number of rows affected by last operation
  • lastInsertRowID() - Get last inserted row ID

Connection Management

  • disconnect() - Close connection
  • reset() - Reset connection state
  • loadExtension(path, entry_point) - Load SQLite extension
  • setReservedBytes(bytes) - Set reserved bytes for encryption
  • getReservedBytes() - Get reserved bytes

LibSQLStatement Class (Prepared Statement)

Prepared statement with parameter binding.

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)

Execution

  • execute() - Execute statement without returning rows
  • query() - Execute statement, returns LibSQLRows object
  • reset() - Reset statement for reuse

LibSQLRows Class (Result Set)

Represents query results.

Column Information

  • columnCount() - Get number of columns
  • columnName(index) - Get column name (1-based index)
  • columnNames() - Get list of all column names

Fetching Rows

  • fetchRow() - Fetch next row, returns LibSQLRow or null
  • fetchAll() - Fetch all rows as list of lists
  • fetchAllAssoc() - Fetch all rows as associative arrays

LibSQLRow Class (Row Data)

Represents a single result row.

Type-Specific Access

  • getIntValue(index) - Get integer value (1-based index)
  • getFloatValue(index) - Get float value
  • getStringValue(index) - Get string value
  • getBlobValue(index) - Get blob value
  • getType(index) - Get column type constant
  • getValue(index) - Get value with automatic type conversion

Row Conversion

  • toList() - Convert row to list of values
  • toAssoc() - Convert row to associative array [["col", val], ...]

Constants

  • LIBSQL_INT - Integer column type
  • LIBSQL_FLOAT - Float column type
  • LIBSQL_TEXT - Text column type
  • LIBSQL_BLOB - Blob column type
  • LIBSQL_NULL - NULL column type

Low-Level C Functions

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.

πŸ› οΈ Development

If you wish to contribute to the development of Ring LibSQL or build it from the source, follow these steps.

Prerequisites

  • 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

Build Steps

  1. Clone the Repository:

    git clone https://github.com/ysdragon/ring-libsql.git
    cd ring-libsql

    Note If you installed the library via RingPM, you can skip this step.

  2. Set the RING Environment 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
  3. 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
  4. 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.

🀝 Contributing

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.

πŸ“„ License

This project is licensed under the MIT License. See the LICENSE file for more details.

About

libSQL bindings for Ring

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •