# Audit Filesystem Changes Source: https://docs.turso.tech/agentfs/guides/auditing Inspect and analyze what an agent did during a session AgentFS records every file operation and tool call, giving you complete visibility into agent behavior. This is essential for debugging, compliance, and understanding how agents work. ## Viewing the Timeline See a chronological list of tool calls: ```bash theme={null} agentfs timeline my-session ``` Output: ``` ID TOOL STATUS DURATION STARTED 4 execute_code pending -- 2024-01-05 09:44:20 3 api_call error 300ms 2024-01-05 09:44:15 2 read_file success 50ms 2024-01-05 09:44:10 1 web_search success 1200ms 2024-01-05 09:43:45 ``` ### Filtering Results Show only specific tool types: ```bash theme={null} agentfs timeline my-session --filter web_search ``` Show only errors: ```bash theme={null} agentfs timeline my-session --status error ``` Limit number of entries: ```bash theme={null} agentfs timeline my-session --limit 20 ``` ### JSON Output For programmatic analysis: ```bash theme={null} agentfs timeline my-session --format json ``` ```json theme={null} [ { "id": 1, "name": "web_search", "status": "success", "started_at": 1704447825, "completed_at": 1704447826, "duration_ms": 1200, "parameters": {"query": "AI agents"}, "result": {"results": ["result1", "result2"]} } ] ``` ## Inspecting Files List all files in a session: ```bash theme={null} agentfs fs ls my-session ``` Output: ``` d artifacts f config.json f output.txt ``` List a subdirectory: ```bash theme={null} agentfs fs ls my-session /artifacts ``` Read file contents: ```bash theme={null} agentfs fs cat my-session /output.txt ``` ## Viewing Changes (Diff) See what changed compared to the original filesystem: ```bash theme={null} agentfs diff my-session ``` This shows: * New files created * Modified files * Deleted files ## Querying with SQL Since AgentFS uses SQLite, you can run arbitrary queries: **Turso** ```bash theme={null} tursodb .agentfs/my-session.db ``` **SQLite** ```bash theme={null} sqlite3 .agentfs/my-session.db ``` Example queries: ```sql theme={null} -- Find all tool calls that took longer than 1 second SELECT name, duration_ms FROM toolcalls WHERE duration_ms > 1000; -- Count tool usage by type SELECT name, COUNT(*) as count FROM toolcalls GROUP BY name ORDER BY count DESC; -- Find files modified in the last hour SELECT path, mtime FROM fs_inode WHERE mtime > strftime('%s', 'now', '-1 hour'); -- Get total bytes written SELECT SUM(size) as total_bytes FROM fs_inode WHERE mode & 0170000 = 0100000; -- regular files only ``` ## Use Cases ### Debugging Agent Failures When an agent fails, use the timeline to understand what happened: ```bash theme={null} # See recent activity agentfs timeline my-session --limit 20 # Focus on errors agentfs timeline my-session --status error # Check what files were created/modified agentfs fs ls my-session ``` ### Performance Analysis Find slow operations: ```sql theme={null} SELECT name, AVG(duration_ms) as avg_ms, COUNT(*) as count FROM toolcalls GROUP BY name ORDER BY avg_ms DESC; ``` ### Compliance Auditing Export a complete record of agent activity: ```bash theme={null} agentfs timeline my-session --format json > audit-log.json ``` ## Next Steps > **[Overlay Filesystem](/agentfs/guides/overlay)** > > Understanding copy-on-write > **[Syncing](/agentfs/guides/sync)** > > Sync sessions to Turso Cloud # MCP Server Integration Source: https://docs.turso.tech/agentfs/guides/mcp Use AgentFS as an MCP server for AI assistants AgentFS can run as an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server, exposing filesystem and key-value operations to AI assistants like Claude Desktop. ## Starting the MCP Server ```bash theme={null} agentfs serve mcp my-agent ``` This starts an MCP server that exposes AgentFS tools over stdio. ## Available Tools ### Filesystem Tools | Tool | Description | | - | - | | `read_file` | Read file contents | | `write_file` | Write file contents | | `readdir` | List directory contents | | `mkdir` | Create directory | | `remove` | Remove file or directory | | `rename` | Rename file or directory | | `stat` | Get file metadata | | `access` | Check file access permissions | ### Key-Value Tools | Tool | Description | | - | - | | `kv_get` | Get value by key | | `kv_set` | Set key-value pair | | `kv_delete` | Delete key | | `kv_list` | List all keys | ## Limiting Exposed Tools For security, you can limit which tools are available: ```bash theme={null} # Read-only mode agentfs serve mcp my-agent --tools read_file,readdir,stat,kv_get,kv_list # Filesystem only agentfs serve mcp my-agent --tools read_file,write_file,readdir,mkdir,remove # Key-value only agentfs serve mcp my-agent --tools kv_get,kv_set,kv_delete,kv_list ``` ## Security Considerations > [!WARNING] > > The MCP server gives AI assistants direct access to the AgentFS filesystem. Consider these security practices: > > * **Limit tools** - Only expose tools the assistant needs > * **Use overlays** - Create overlay filesystems instead of giving access to real directories > * **Review the agent database** - Check `.agentfs/*.db` periodically ## Debugging Check if the MCP server is working: ```bash theme={null} # Run manually to see output agentfs serve mcp my-agent # Check if the database exists ls -la .agentfs/my-agent.db ``` ## Next Steps > **[NFS Server](/agentfs/guides/nfs)** > > Remote access via NFS > **[Copy-on-Write Overlays](/agentfs/guides/overlay)** > > How isolation works # NFS Server Access Source: https://docs.turso.tech/agentfs/guides/nfs Export AgentFS over the network via NFS AgentFS can expose filesystems over NFS, enabling remote access from other machines, containers, or virtual machines. ## Starting the NFS Server ```bash theme={null} agentfs serve nfs my-agent ``` By default, this listens on `127.0.0.1:11111`. ### Binding to All Interfaces To allow remote access: ```bash theme={null} agentfs serve nfs my-agent --bind 0.0.0.0 --port 2049 ``` > [!WARNING] > > Exposing NFS on `0.0.0.0` makes the filesystem accessible to anyone who can reach your machine. Use firewall rules or a VPN for production deployments. ## Mounting the Filesystem ### From the Same Machine ```bash theme={null} mkdir /mnt/agentfs mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock 127.0.0.1:/ /mnt/agentfs ``` ### From a Remote Machine ```bash theme={null} mount -t nfs -o vers=3,tcp,port=2049,mountport=2049,nolock server-ip:/ /mnt/agentfs ``` ### Mount Options Explained | Option | Description | | - | - | | `vers=3` | Use NFSv3 protocol | | `tcp` | Use TCP transport | | `port=11111` | NFS server port | | `mountport=11111` | Mount protocol port | | `nolock` | Disable file locking (single-user) | ## Unmounting ```bash theme={null} umount /mnt/agentfs ``` If the mount is busy: ```bash theme={null} umount -f /mnt/agentfs # Force unmount # or umount -l /mnt/agentfs # Lazy unmount ``` ## Next Steps > **[MCP Server](/agentfs/guides/mcp)** > > Expose AgentFS via MCP > **[Sync with Turso Cloud](/agentfs/guides/sync)** > > Back up and share agent state # Copy-on-Write Overlays Source: https://docs.turso.tech/agentfs/guides/overlay How copy-on-write isolation works in AgentFS AgentFS uses an overlay filesystem to provide copy-on-write isolation. This lets agents freely modify files while keeping your original data safe. ## How Overlay Works An overlay filesystem combines two layers: ``` ┌────────────────────────────────────┐ │ Merged View (what you see) │ ├────────────────────────────────────┤ │ Delta Layer (AgentFS database) │ ← Writes go here ├────────────────────────────────────┤ │ Base Layer (original directory) │ ← Read-only └────────────────────────────────────┘ ``` **Reading a file:** 1. Check delta layer first 2. If not found, read from base layer **Writing a file:** 1. Copy file to delta layer (if from base) 2. Write changes to delta layer 3. Base layer is never modified **Deleting a file:** 1. Create a "whiteout" marker in delta layer 2. File appears deleted in merged view 3. Original file in base layer is untouched ## Creating an Overlay Filesystem ### With `agentfs run` The simplest way - automatically creates an overlay over your current directory: ```bash theme={null} cd /path/to/project agentfs run /bin/bash ``` ### With `agentfs init --base` Create an overlay explicitly: ```bash theme={null} agentfs init my-overlay --base /path/to/project ``` Then mount it: ```bash theme={null} agentfs mount my-overlay ./workspace ``` Or run a command in it: ```bash theme={null} agentfs run --session my-overlay /bin/bash ``` ## Viewing Changes See what's different from the base: ```bash theme={null} agentfs diff my-overlay ``` ## Practical Examples ### Safe Refactoring Let an agent refactor your code without risk: ```bash theme={null} cd my-project agentfs run --session refactor python3 refactor_agent.py # Review what changed agentfs diff refactor # Happy? Apply changes manually or via script # Not happy? Just delete the session rm .agentfs/refactor.db ``` ### Testing Destructive Operations Try something dangerous safely: ```bash theme={null} agentfs run --session test /bin/bash $ rm -rf src/ # Yikes! $ exit # Original files are fine ls src/ # Still there! # Only the overlay was affected agentfs fs ls test # Shows the deletion ``` ### Parallel Experiments Run multiple experiments on the same codebase: ```bash theme={null} # Experiment A agentfs run --session exp-a python3 approach_a.py # Experiment B (same base, different changes) agentfs run --session exp-b python3 approach_b.py # Compare results agentfs diff exp-a agentfs diff exp-b ``` ## Storage Efficiency The overlay only stores: * **Modified files** - Full copy after first write * **New files** - Stored entirely in delta * **Deleted files** - Small whiteout marker Original files are never duplicated. A 10GB project with 1MB of changes only uses \~1MB in the delta layer. ## Technical Details ### File Operations | Operation | Behavior | | - | - | | Read existing file | Pass through to base | | Write existing file | Copy to delta, then write | | Create new file | Write directly to delta | | Delete file | Create whiteout in delta | | Rename file | Delete old + create new | ### Whiteouts When you delete a base file, AgentFS creates a "whiteout" - a marker that hides the file: ```sql theme={null} -- Whiteouts in the database SELECT * FROM fs_inode WHERE whiteout = 1; ``` ### Database Schema The delta layer is stored in SQLite: ```sql theme={null} -- Files and directories CREATE TABLE fs_inode ( ino INTEGER PRIMARY KEY, parent_ino INTEGER, name TEXT, mode INTEGER, size INTEGER, ... whiteout INTEGER DEFAULT 0 ); -- File contents CREATE TABLE fs_block ( ino INTEGER, block_num INTEGER, data BLOB ); ``` See the [AgentFS Specification](https://github.com/tursodatabase/agentfs/blob/main/SPEC.md) for the complete schema. ## Next Steps > **[Sessions](/agentfs/guides/sessions)** > > Share state between agents > **[Syncing](/agentfs/guides/sync)** > > Sync overlays to Turso Cloud # Agentic Coding Source: https://docs.turso.tech/agentfs/guides/sandbox Run AI coding agents safely with AgentFS sandboxing AgentFS lets you run AI coding agents like Claude Code, Codex, and OpenCode in a sandboxed environment where file changes are isolated from your original source tree. ## Supported Agents | Agent | Command | | - | - | | [Claude Code](https://code.claude.com/docs/en/overview) | `agentfs run claude` | | [Codex](https://openai.com/codex/) | `agentfs run codex` | | [OpenCode](https://opencode.ai/) | `agentfs run opencode` | | Any CLI tool | `agentfs run ` | ## Workflow Here's a typical agentic coding workflow. This example uses Claude Code, but the workflow is the same with OpenCode, Codex, and others. ### Step 1: Clone the Repository Clone the git repository to a host directory. Keep this source tree clean from build artifacts. When there's a merge to main, pull to this repository to keep it up-to-date. ```bash theme={null} git clone https://github.com/tursodatabase/agentfs.git cd agentfs ``` ### Step 2: Start an AgentFS Session Use `agentfs run` with the `--session` flag to start a session. Pick a name that can eventually become your git branch name. **Claude Code** ```bash theme={null} agentfs run --session fix-bug-123 claude ``` **Codex** ```bash theme={null} agentfs run --session fix-bug-123 codex ``` **OpenCode** ```bash theme={null} agentfs run --session fix-bug-123 opencode ``` The agent now runs in a copy-on-write environment. Your original source tree remains untouched while the agent works. ### Step 3 (Optional): Manual Investigation If the agent does something unusual, open another terminal and join the same session to investigate: ```bash theme={null} agentfs run --session fix-bug-123 bash ``` The filesystem is shared between the shell and the agent. You can browse files, run tests, or check what's happening while the agent works. ### Step 4: Push to Branch Once the agent has completed the work, ask it to create a branch, commit the code, and push: > "Create a branch called fix-bug-123, commit these changes, and push to origin" You can then create a PR manually from the pushed branch. ## Why This Workflow? * **Clean source tree** - Your original clone stays pristine and easy to update from upstream * **Isolated changes** - Each task gets its own session, preventing cross-contamination * **Easy investigation** - Join any session to see what's happening in real-time * **Standard git flow** - Push branches and create PRs as usual ## Next Steps > **[Multi-Agent Sessions](/agentfs/guides/sessions)** > > Share state between agents and terminals > **[Auditing](/agentfs/guides/auditing)** > > Inspect what an agent did with diff and timeline # Shared Agent Sessions Source: https://docs.turso.tech/agentfs/guides/sessions Share state between multiple agents and terminals with named sessions Named sessions let you share a filesystem between multiple agents, terminals, or runs. All participants see the same copy-on-write view of your project. ## Creating a Session Use `--session` to create a named session: ```bash theme={null} agentfs run --session my-project /bin/bash ``` This creates a session called `my-project`. All changes are stored in `.agentfs/my-project.db`. ## Resuming a Session Run the same command to resume where you left off: ```bash theme={null} # First run - make some changes agentfs run --session my-project /bin/bash $ echo "hello" > test.txt $ exit # Second run - changes are still there agentfs run --session my-project /bin/bash $ cat test.txt hello ``` ## Multi-Terminal Collaboration Multiple terminals can share the same session simultaneously: ```bash theme={null} # Terminal 1 agentfs run --session shared-work /bin/bash # Terminal 2 - joins the same filesystem agentfs run --session shared-work python3 agent.py # Terminal 3 - also sees the same files agentfs run --session shared-work vim ``` All terminals share the same copy-on-write filesystem. Changes made in one terminal are immediately visible in others. ## Use Cases ### Iterative Development Work on a task across multiple sessions: ```bash theme={null} # Morning session agentfs run --session feature-x /bin/bash # ... work on the feature, then exit for lunch # Afternoon session - continue where you left off agentfs run --session feature-x /bin/bash ``` ### Safe Experimentation Create a session, try something risky, then decide whether to keep it: ```bash theme={null} agentfs run --session experiment /bin/bash # ... try some changes # Option A: Happy with changes agentfs diff experiment # Review # Apply changes to real filesystem (manually or via script) # Option B: Discard everything rm .agentfs/experiment.db ``` ### Agent Supervision Run an agent and monitor its work from another terminal: ```bash theme={null} # Terminal 1: Run the agent agentfs run --session agent-task python3 agent.py # Terminal 2: Watch what it's doing agentfs run --session agent-task /bin/bash $ watch ls -la # See files being created $ tail -f agent.log # Monitor logs ``` ## Managing Sessions List files in a session: ```bash theme={null} agentfs fs ls my-session ``` View session changes: ```bash theme={null} agentfs diff my-session ``` Delete a session: ```bash theme={null} rm .agentfs/my-session.db ``` ## Session Storage Sessions are stored as SQLite databases in `.agentfs/`: ``` .agentfs/ ├── my-project.db ├── experiment.db └── shared-work.db ``` Each database contains: * All modified files * Deleted file markers (whiteouts) * Tool call audit log * Key-value store data ## Next Steps > **[Auditing](/agentfs/guides/auditing)** > > Inspect what happened in a session > **[Overlay Filesystem](/agentfs/guides/overlay)** > > How copy-on-write works # Sync with Turso Cloud Source: https://docs.turso.tech/agentfs/guides/sync Synchronize agent filesystems with remote Turso databases AgentFS can sync with [Turso Cloud](https://turso.tech), enabling backup, collaboration, and distributed agent deployments. ## Setting Up Sync ### 1. Create a Turso Database If you don't have a Turso account, [sign up](https://turso.tech) first. Create a database for your agent: ```bash theme={null} turso db create my-agent-db ``` Get the database URL: ```bash theme={null} turso db show my-agent-db --url # Output: libsql://my-agent-db-username.turso.io ``` ### 2. Initialize with Sync Create a new agent filesystem with sync enabled: ```bash theme={null} agentfs init my-agent --sync-remote-url libsql://my-agent-db-username.turso.io ``` Or add sync to an existing agent: ```bash theme={null} # Edit the database to add sync configuration # (Feature coming soon) ``` ## Sync Commands ### Pull Remote Changes Download changes from Turso Cloud: ```bash theme={null} agentfs sync my-agent pull ``` ### Push Local Changes Upload local changes to Turso Cloud: ```bash theme={null} agentfs sync my-agent push ``` ### View Sync Status Check sync statistics: ```bash theme={null} agentfs sync my-agent stats ``` ### Create Checkpoint Force a sync checkpoint: ```bash theme={null} agentfs sync my-agent checkpoint ``` ## Use Cases ### Backup and Recovery Automatically back up agent state: ```bash theme={null} # After important work agentfs sync my-agent push # On a new machine agentfs init my-agent --sync-remote-url libsql://... agentfs sync my-agent pull ``` ### Multi-Machine Agents Run agents on multiple machines with shared state: ```bash theme={null} # Machine A agentfs run --session shared-agent python3 agent.py agentfs sync shared-agent push # Machine B agentfs sync shared-agent pull agentfs run --session shared-agent python3 agent.py ``` ## Partial Sync For large filesystems, use partial sync to only fetch what's needed: ```bash theme={null} agentfs init my-agent \ --sync-remote-url libsql://... \ --sync-partial-prefetch \ --sync-partial-segment-size 1000 ``` ### Partial Sync Options | Option | Description | | - | - | | `--sync-partial-prefetch` | Enable prefetching | | `--sync-partial-segment-size` | Segment size for partial sync | | `--sync-partial-bootstrap-query` | Custom bootstrap query | | `--sync-partial-bootstrap-length` | Bootstrap prefix length | ## Authentication Sync requires a Turso auth token. Set it via environment variable: ```bash theme={null} export TURSO_AUTH_TOKEN=$(turso db tokens create my-agent-db) ``` Or create a long-lived token: ```bash theme={null} turso db tokens create my-agent-db --expiration none ``` ## Conflict Resolution AgentFS uses last-write-wins for conflicts. If the same file is modified on multiple machines: 1. Push from Machine A succeeds 2. Push from Machine B fails (conflict) 3. Machine B must pull first, then push ## Next Steps > **[Turso Cloud](https://turso.tech)** > > Learn more about Turso Cloud > **[NFS Server](/agentfs/guides/nfs)** > > Remote access via NFS # Installation Source: https://docs.turso.tech/agentfs/installation Install the AgentFS CLI on Linux, macOS, and Windows ## Quick Install The fastest way to install AgentFS: ```bash theme={null} curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh ``` This installs the `agentfs` binary to `~/.cargo/bin`. ## Platform-Specific Instructions **macOS** ```bash theme={null} curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh ``` After installation, restart your terminal or run: ```bash theme={null} source ~/.zshrc # or ~/.bashrc ``` **Linux** **Using the installer:** ```bash theme={null} curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh ``` After installation, restart your terminal or run: ```bash theme={null} source ~/.bashrc # or ~/.zshrc ``` **Requirements:** * FUSE must be available for filesystem mounting * On Ubuntu/Debian: `sudo apt install fuse3` * On Fedora: `sudo dnf install fuse3` **Windows** **Using PowerShell:** ```powershell theme={null} irm https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.ps1 | iex ``` Or download the binary directly from the [releases page](https://github.com/tursodatabase/agentfs/releases). ## Verify Installation ```bash theme={null} agentfs --version ``` ## Shell Completions Enable tab completion for your shell: ```bash theme={null} # Auto-detect shell agentfs completions install # Or specify shell explicitly agentfs completions install bash agentfs completions install zsh agentfs completions install fish ``` Restart your shell after installing completions. ## Building from Source Requires Rust 1.70+: ```bash theme={null} git clone https://github.com/tursodatabase/agentfs.git cd agentfs/cli cargo install --path . ``` ## Next Steps > **[Agentic Coding](/agentfs/guides/sandbox)** > > Run AI coding agents safely > **[CLI Reference](/agentfs/reference/cli)** > > Full command reference # AgentFS Source: https://docs.turso.tech/agentfs/introduction A filesystem for AI agents, built on Turso AgentFS is a filesystem for AI agents. It provides copy-on-write isolation, letting agents safely modify files while keeping your original data untouched. It is available both as a CLI, that wraps an existing program in a sandboxed session, and an SDK that allows you to build custom agents with a filesystem abstraction. ## Key Features * **Copy-on-Write Isolation** — Run agents in sandboxed environments where changes are isolated from your source tree * **Single File Storage** — Everything stored in one portable SQLite database for easy sharing and snapshotting * **Built-in Auditing** — Every file operation is recorded and queryable * **Cloud Sync** — Optionally sync agent state to Turso Cloud ## Quick Start ```bash theme={null} # Install curl -fsSL https://agentfs.ai/install | bash # Run a shell in an isolated environment cd /path/to/project agentfs run bash # Any changes are isolated - your original files are safe ``` ## Next Steps > **[Installation](/agentfs/installation)** > > Install the AgentFS CLI > **[Agentic Coding](/agentfs/guides/sandbox)** > > Run AI coding agents safely > **[AgentFS SDKs](/agentfs/sdk)** > > Build custom agents with the AgentFS SDKs > [!WARNING] > > This software is in BETA. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups. # CLI Reference Source: https://docs.turso.tech/agentfs/reference/cli Complete reference for the AgentFS command-line interface Complete reference for the `agentfs` command-line tool. > [!NOTE] > > For the latest reference, see [MANUAL.md](https://github.com/tursodatabase/agentfs/blob/main/MANUAL.md) in the repository. ## Installation ```bash theme={null} curl -fsSL https://github.com/tursodatabase/agentfs/releases/latest/download/agentfs-installer.sh | sh ``` ## Commands ### agentfs init Initialize a new agent filesystem. ```bash theme={null} agentfs init [OPTIONS] [ID] ``` **Arguments:** * `ID` - Agent identifier (default: `agent-{timestamp}`) **Options:** | Option | Description | | - | - | | `--force` | Overwrite existing agent filesystem | | `--base ` | Base directory for overlay filesystem | | `--sync-remote-url ` | Remote Turso database URL | **Example:** ```bash theme={null} agentfs init my-agent agentfs init my-overlay --base /path/to/project ``` *** ### agentfs run Execute a program in a sandboxed environment. ```bash theme={null} agentfs run [OPTIONS] [ARGS]... ``` **Options:** | Option | Description | | - | - | | `--session ` | Named session for persistence | | `--allow ` | Allow write access to directory | | `--no-default-allows` | Disable default allowed directories | | `--experimental-sandbox` | Use ptrace sandbox (Linux) | | `--strace` | Show intercepted syscalls | **Examples:** ```bash theme={null} agentfs run /bin/bash agentfs run --session my-project python3 agent.py agentfs run --allow /tmp --allow ~/.cache /bin/bash ``` *** ### agentfs mount Mount an agent filesystem or list mounts. ```bash theme={null} agentfs mount [OPTIONS] [ID_OR_PATH] [MOUNT_POINT] ``` Without arguments, lists all mounted filesystems. **Options:** | Option | Description | | - | - | | `-a, --auto-unmount` | Automatically unmount on exit | | `--allow-root` | Allow root user access | | `-f, --foreground` | Run in foreground | | `--uid ` | User ID for files | | `--gid ` | Group ID for files | **Examples:** ```bash theme={null} agentfs mount # List mounts agentfs mount my-agent ./mnt # Mount agent agentfs mount my-agent ./mnt -a -f # Foreground with auto-unmount ``` **Unmounting:** * Linux: `fusermount -u ` * macOS: `umount ` *** ### agentfs serve mcp Start an MCP (Model Context Protocol) server. ```bash theme={null} agentfs serve mcp [OPTIONS] ``` **Options:** | Option | Description | | - | - | | `--tools ` | Comma-separated list of tools | **Available Tools:** | Category | Tools | | - | - | | Filesystem | `read_file`, `write_file`, `readdir`, `mkdir`, `remove`, `rename`, `stat`, `access` | | Key-Value | `kv_get`, `kv_set`, `kv_delete`, `kv_list` | **Examples:** ```bash theme={null} agentfs serve mcp my-agent agentfs serve mcp my-agent --tools read_file,readdir,stat ``` *** ### agentfs serve nfs Start an NFS server. ```bash theme={null} agentfs serve nfs [OPTIONS] ``` **Options:** | Option | Description | | - | - | | `--bind ` | IP to bind (default: `127.0.0.1`) | | `--port ` | Port (default: `11111`) | **Example:** ```bash theme={null} agentfs serve nfs my-agent --bind 0.0.0.0 --port 2049 ``` **Mounting:** ```bash theme={null} mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock localhost:/ /mnt ``` *** ### agentfs sync Synchronize with a remote Turso database. ```bash theme={null} agentfs sync ``` **Subcommands:** | Command | Description | | - | - | | `pull` | Pull remote changes | | `push` | Push local changes | | `stats` | View sync statistics | | `checkpoint` | Create checkpoint | **Example:** ```bash theme={null} agentfs sync my-agent pull agentfs sync my-agent push ``` *** ### agentfs fs Filesystem operations on agent databases. #### agentfs fs ls ```bash theme={null} agentfs fs ls [FS_PATH] ``` List files. Output: `f ` for files, `d ` for directories. #### agentfs fs cat ```bash theme={null} agentfs fs cat ``` Display file contents. #### agentfs fs write ```bash theme={null} agentfs fs write ``` Write content to a file. **Examples:** ```bash theme={null} agentfs fs ls my-agent agentfs fs cat my-agent /config.json agentfs fs write my-agent /hello.txt "Hello, world!" ``` *** ### agentfs diff Show filesystem changes in overlay mode. ```bash theme={null} agentfs diff ``` *** ### agentfs timeline Display agent action timeline. ```bash theme={null} agentfs timeline [OPTIONS] ``` **Options:** | Option | Description | | - | - | | `--limit ` | Limit entries (default: 100) | | `--filter ` | Filter by tool name | | `--status ` | Filter: `pending`, `success`, `error` | | `--format ` | Output: `table`, `json` | **Examples:** ```bash theme={null} agentfs timeline my-agent agentfs timeline my-agent --filter web_search --status error agentfs timeline my-agent --format json ``` *** ### agentfs completions Manage shell completions. ```bash theme={null} agentfs completions install [SHELL] agentfs completions uninstall [SHELL] agentfs completions show ``` Supported: `bash`, `zsh`, `fish`, `powershell` ## Environment Variables Variables set inside the sandbox: | Variable | Description | | - | - | | `AGENTFS` | Set to `1` inside sandbox | | `AGENTFS_SANDBOX` | Sandbox type | | `AGENTFS_SESSION` | Session ID | ## Files | Path | Description | | - | - | | `.agentfs/.db` | Agent filesystem database | | `~/.config/agentfs/` | Configuration directory | ## See Also > **[Installation](/agentfs/installation)** > > Installation instructions > **[Sandbox Guide](/agentfs/guides/sandbox)** > > Running agents in sandboxes # Python SDK Source: https://docs.turso.tech/agentfs/sdk/python AgentFS Python SDK for building AI agents The AgentFS Python SDK provides an async interface for building AI agents with persistent storage. ## Installation ```bash theme={null} pip install agentfs-sdk ``` ## Quick Start ```python theme={null} import asyncio from agentfs_sdk import AgentFS, AgentFSOptions async def main(): # Open an agent filesystem agent = await AgentFS.open(AgentFSOptions(id='my-agent')) # Use key-value store await agent.kv.set('config', {'debug': True}) config = await agent.kv.get('config') # Use filesystem await agent.fs.write_file('/notes.txt', 'Hello, AgentFS!') content = await agent.fs.read_file('/notes.txt') # Track tool calls call_id = await agent.tools.start('search', {'query': 'Python'}) await agent.tools.success(call_id, {'results': ['result1']}) await agent.close() asyncio.run(main()) ``` ## Opening a Filesystem ### By Agent ID Creates database at `.agentfs/{id}.db`: ```python theme={null} agent = await AgentFS.open(AgentFSOptions(id='my-agent')) ``` ### By Path Specify a custom database path: ```python theme={null} agent = await AgentFS.open(AgentFSOptions(path='./data/mydb.db')) ``` ### Context Manager Automatically closes the database: ```python theme={null} async with await AgentFS.open(AgentFSOptions(id='my-agent')) as agent: await agent.kv.set('key', 'value') # Database is closed when exiting ``` ## Key-Value Store Simple key-value storage with JSON serialization. ### Set a Value ```python theme={null} await agent.kv.set('user:123', {'name': 'Alice', 'age': 30}) await agent.kv.set('counter', 42) await agent.kv.set('active', True) ``` ### Get a Value ```python theme={null} user = await agent.kv.get('user:123') # Returns None if key doesn't exist ``` ### List Keys ```python theme={null} # List all keys with prefix keys = await agent.kv.list('user:') # Returns: ['user:123', 'user:456', ...] ``` ### Delete a Key ```python theme={null} await agent.kv.delete('user:123') ``` ## Filesystem POSIX-like filesystem operations. ### Write a File Creates parent directories automatically: ```python theme={null} await agent.fs.write_file('/data/config.json', '{"key": "value"}') # Write bytes await agent.fs.write_file('/data/image.png', image_bytes) ``` ### Read a File ```python theme={null} # Read as string (default) content = await agent.fs.read_file('/data/config.json') # Read as bytes data = await agent.fs.read_file('/data/image.png', encoding=None) ``` ### List Directory ```python theme={null} entries = await agent.fs.readdir('/data') # Returns: ['config.json', 'image.png', ...] ``` ### Get File Stats ```python theme={null} stats = await agent.fs.stat('/data/config.json') print(f"Size: {stats.size} bytes") print(f"Modified: {stats.mtime}") print(f"Is file: {stats.is_file()}") print(f"Is directory: {stats.is_dir()}") ``` ### Delete a File ```python theme={null} await agent.fs.delete_file('/data/config.json') ``` ### Create Directory ```python theme={null} await agent.fs.mkdir('/data/subdir') ``` ## Tool Calls Track and analyze tool/function calls for debugging and auditing. ### Start and Complete ```python theme={null} # Start a tool call call_id = await agent.tools.start('search', {'query': 'Python'}) # ... perform the operation ... # Mark as successful await agent.tools.success(call_id, {'results': ['result1', 'result2']}) # Or mark as failed await agent.tools.error(call_id, 'Connection timeout') ``` ### Record Completed Call If you have start and end times: ```python theme={null} await agent.tools.record( name='search', started_at=1234567890.0, completed_at=1234567892.0, parameters={'query': 'Python'}, result={'results': ['result1']} ) ``` ### Query Tool Calls ```python theme={null} # Get by name calls = await agent.tools.get_by_name('search', limit=10) # Get recent calls recent = await agent.tools.get_recent(since=1234567890.0) # Get by ID call = await agent.tools.get(42) ``` ### Get Statistics ```python theme={null} stats = await agent.tools.get_stats() for stat in stats: print(f"{stat.name}:") print(f" Total calls: {stat.total_calls}") print(f" Successful: {stat.successful}") print(f" Failed: {stat.failed}") print(f" Avg duration: {stat.avg_duration_ms:.2f}ms") ``` ## Complete Example ```python theme={null} import asyncio import time from agentfs_sdk import AgentFS, AgentFSOptions async def main(): async with await AgentFS.open(AgentFSOptions(id='demo-agent')) as agent: # Store configuration await agent.kv.set('agent:config', { 'model': 'gpt-4', 'temperature': 0.7 }) # Create a research document research = """ # Research Notes ## Topic: AgentFS AgentFS provides persistent storage for AI agents. """ await agent.fs.write_file('/research/agentfs.md', research) # Track a simulated tool call call_id = await agent.tools.start('web_search', { 'query': 'AgentFS documentation' }) # Simulate some work await asyncio.sleep(0.1) await agent.tools.success(call_id, { 'results_count': 5, 'top_result': 'https://docs.turso.tech/agentfs' }) # List what we created files = await agent.fs.readdir('/research') print(f"Files: {files}") # Get tool stats stats = await agent.tools.get_stats() for stat in stats: print(f"{stat.name}: {stat.total_calls} calls") asyncio.run(main()) ``` ## API Reference ### AgentFS | Method | Description | | - | - | | `AgentFS.open(options)` | Open or create an agent filesystem | | `agent.close()` | Close the database connection | | `agent.kv` | Key-value store interface | | `agent.fs` | Filesystem interface | | `agent.tools` | Tool calls interface | ### AgentFSOptions | Property | Type | Description | | - | - | - | | `id` | `str` | Agent identifier (creates `.agentfs/{id}.db`) | | `path` | `str` | Custom database path | ### KvStore | Method | Description | | - | - | | `set(key, value)` | Store a value | | `get(key)` | Retrieve a value | | `delete(key)` | Delete a key | | `list(prefix)` | List keys with prefix | ### Filesystem | Method | Description | | - | - | | `write_file(path, content)` | Write file contents | | `read_file(path, encoding)` | Read file contents | | `readdir(path)` | List directory entries | | `stat(path)` | Get file metadata | | `delete_file(path)` | Delete a file | | `mkdir(path)` | Create directory | ### ToolCalls | Method | Description | | - | - | | `start(name, params)` | Start tracking a call | | `success(id, result)` | Mark call as successful | | `error(id, message)` | Mark call as failed | | `record(...)` | Record a completed call | | `get(id)` | Get call by ID | | `get_by_name(name, limit)` | Query by tool name | | `get_recent(since)` | Get recent calls | | `get_stats()` | Get usage statistics | ## Next Steps > **[OpenAI Agents](/agentfs/integrations/openai-agents)** > > Using AgentFS with OpenAI Agents SDK > **[LlamaIndex](/agentfs/integrations/llamaindex)** > > Using AgentFS with LlamaIndex # Rust SDK Source: https://docs.turso.tech/agentfs/sdk/rust Complete reference for the AgentFS Rust SDK Build high-performance stateful AI agents with the AgentFS Rust SDK. ## Installation Add AgentFS to your `Cargo.toml`: ```toml theme={null} [dependencies] agentfs = "0.1" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" ``` ## Quick Start ```rust theme={null} use agentfs::{AgentFS, AgentFSOptions}; use anyhow::Result; #[tokio::main] async fn main() -> Result<()> { // Persistent storage with identifier let agent = AgentFS::open(AgentFSOptions::with_id("my-agent")).await?; // Creates: .agentfs/my-agent.db // Or use ephemeral in-memory database let ephemeral = AgentFS::open(AgentFSOptions::ephemeral()).await?; // Use the three main APIs agent.kv.set("key", "value").await?; // Key-value store agent.fs.write_file("/file.txt", b"data").await?; // Filesystem agent.tools.record(...).await?; // Tool tracking Ok(()) } ``` ## Core APIs ### AgentFS Struct The main entry point for all AgentFS operations. #### `AgentFS::open(options: AgentFSOptions)` Creates or opens an AgentFS database. ```rust theme={null} use agentfs::{AgentFS, AgentFSOptions}; // Persistent storage with identifier let agent = AgentFS::open(AgentFSOptions::with_id("my-agent")).await?; // Creates: .agentfs/my-agent.db // Ephemeral in-memory database let ephemeral = AgentFS::open(AgentFSOptions::ephemeral()).await?; ``` **AgentFSOptions Configuration:** ```rust theme={null} pub struct AgentFSOptions { /// Optional unique identifier for the agent. /// - If Some(id): Creates persistent storage at `.agentfs/{id}.db` /// - If None: Uses ephemeral in-memory database pub id: Option, } impl AgentFSOptions { /// Create options for a persistent agent with the given ID pub fn with_id(id: impl Into) -> Self; /// Create options for an ephemeral in-memory agent pub fn ephemeral() -> Self; } ``` #### Fields * `kv`: Key-value store interface * `fs`: Filesystem interface * `tools`: Tool call tracking interface * `db`: Direct access to the underlying Turso database ### Key-Value Store API Fast, typed storage with Serde serialization support. #### `kv.set(key: &str, value: T)` Store any serializable value. ```rust theme={null} use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct UserPreferences { theme: String, language: String, } // Store structured data agent.kv.set("user:preferences", UserPreferences { theme: "dark".to_string(), language: "en".to_string(), }).await?; // Store primitives agent.kv.set("counter", 42).await?; agent.kv.set("active", true).await?; ``` #### `kv.get(key: &str) -> Option` Retrieve and deserialize values. ```rust theme={null} // Get with type inference let prefs: Option = agent.kv.get("user:preferences").await?; // Handle missing values if let Some(count) = agent.kv.get::("counter").await? { println!("Counter: {}", count); } // With default let theme = agent.kv.get::("theme").await?.unwrap_or("light".to_string()); ``` #### `kv.delete(key: &str)` Remove a key-value pair. ```rust theme={null} agent.kv.delete("session:expired").await?; ``` #### `kv.list(options: ListOptions)` List keys with filtering and pagination. ```rust theme={null} use agentfs::ListOptions; // List all keys let all_keys = agent.kv.list(ListOptions::default()).await?; // Filter by prefix let user_keys = agent.kv.list(ListOptions { prefix: Some("user:".to_string()), limit: Some(100), cursor: None, }).await?; // Paginate let page1 = agent.kv.list(ListOptions { limit: Some(10), ..Default::default() }).await?; let page2 = agent.kv.list(ListOptions { limit: Some(10), cursor: page1.cursor, ..Default::default() }).await?; ``` #### `kv.clear()` Remove all key-value pairs. ```rust theme={null} agent.kv.clear().await?; ``` ### Filesystem API POSIX-like filesystem operations for managing agent data. #### `fs.write_file(path: &str, data: &[u8])` Write bytes to a file, creating parent directories as needed. ```rust theme={null} // Write text agent.fs.write_file("/report.md", b"# Report\nContent...").await?; // Write string let content = "Hello, World!"; agent.fs.write_file("/greeting.txt", content.as_bytes()).await?; // Write binary let image_data = std::fs::read("./chart.png")?; agent.fs.write_file("/images/chart.png", &image_data).await?; ``` #### `fs.read_file(path: &str) -> Vec` Read file contents as bytes. ```rust theme={null} // Read as bytes let data = agent.fs.read_file("/report.md").await?; // Convert to string let text = String::from_utf8(data)?; // Parse JSON let json_data = agent.fs.read_file("/config.json").await?; let config: serde_json::Value = serde_json::from_slice(&json_data)?; ``` #### `fs.mkdir(path: &str)` Create a directory, optionally creating parent directories. ```rust theme={null} // Create single directory agent.fs.mkdir("/reports").await?; // Create nested directories agent.fs.mkdir_p("/reports/2024/Q4").await?; ``` #### `fs.readdir(path: &str) -> Vec` List directory contents with metadata. ```rust theme={null} let entries = agent.fs.readdir("/reports").await?; for entry in entries { println!("Name: {}", entry.name); println!("Type: {:?}", entry.entry_type); println!("Size: {} bytes", entry.size); println!("Modified: {:?}", entry.modified); } // Filter by type let files: Vec<_> = entries .iter() .filter(|e| e.entry_type == EntryType::File) .collect(); ``` #### `fs.stat(path: &str) -> FileStat` Get file or directory metadata. ```rust theme={null} let stat = agent.fs.stat("/report.md").await?; println!("Size: {} bytes", stat.size); println!("Type: {:?}", stat.file_type); println!("Created: {:?}", stat.created); println!("Modified: {:?}", stat.modified); println!("Permissions: {:o}", stat.mode); if stat.is_file() { println!("It's a file"); } else if stat.is_directory() { println!("It's a directory"); } ``` #### `fs.exists(path: &str) -> bool` Check if a file or directory exists. ```rust theme={null} if agent.fs.exists("/config.json").await? { let config = agent.fs.read_file("/config.json").await?; } ``` #### `fs.rm(path: &str)` Remove a file or empty directory. ```rust theme={null} // Remove file agent.fs.rm("/old-report.md").await?; // Remove empty directory agent.fs.rm("/empty-dir").await?; // Remove directory tree agent.fs.rm_rf("/old-data").await?; ``` #### `fs.rename(old_path: &str, new_path: &str)` Move or rename files and directories. ```rust theme={null} // Rename file agent.fs.rename("/draft.md", "/final.md").await?; // Move to different directory agent.fs.rename("/temp/data.json", "/archive/data.json").await?; ``` #### `fs.copy_file(src: &str, dest: &str)` Copy a file to a new location. ```rust theme={null} agent.fs.copy_file("/template.md", "/new-report.md").await?; ``` ### Tool Call Tracking API Record and query agent tool invocations. #### `tools.record()` Record a tool invocation with timing and I/O data. ```rust theme={null} use agentfs::ToolCall; use chrono::Utc; use serde_json::json; let start = Utc::now(); // ... perform operation ... let end = Utc::now(); agent.tools.record(ToolCall { name: "web_search".to_string(), started_at: start, ended_at: end, input: json!({ "query": "rust async programming", "max_results": 10 }), output: json!({ "results_count": 8, "success": true }), }).await?; ``` #### `tools.list(options: ToolListOptions)` Query recorded tool calls. ```rust theme={null} use agentfs::ToolListOptions; use chrono::{Duration, Utc}; // Get recent calls let recent = agent.tools.list(ToolListOptions { limit: Some(10), ..Default::default() }).await?; // Filter by name let searches = agent.tools.list(ToolListOptions { name: Some("web_search".to_string()), limit: Some(100), ..Default::default() }).await?; // Time range query let last_hour = agent.tools.list(ToolListOptions { since: Some(Utc::now() - Duration::hours(1)), until: Some(Utc::now()), ..Default::default() }).await?; ``` #### `tools.get(id: &str) -> Option` Get details of a specific tool call. ```rust theme={null} if let Some(call) = agent.tools.get("call_123abc").await? { let duration = call.ended_at - call.started_at; println!("Tool: {}", call.name); println!("Duration: {}ms", duration.num_milliseconds()); } ``` #### `tools.delete(id: &str)` Remove a tool call record. ```rust theme={null} agent.tools.delete("call_123abc").await?; ``` #### `tools.clear()` Remove all tool call records. ```rust theme={null} agent.tools.clear().await?; ``` ## Support * [GitHub Issues](https://github.com/tursodatabase/agentfs/issues) - Bug reports and feature requests * [Discord](https://tur.so/discord) - Community support * [Examples](https://github.com/tursodatabase/agentfs/tree/main/examples/rust) - Sample applications # TypeScript/JavaScript SDK Source: https://docs.turso.tech/agentfs/sdk/typescript Complete reference for the AgentFS TypeScript and JavaScript SDK Build stateful AI agents with the AgentFS TypeScript SDK, supporting both Node.js and browser environments. ## Installation **npm** ```bash theme={null} npm install agentfs-sdk ``` **bun** ```bash theme={null} bun add agentfs-sdk ``` **deno** ```typescript theme={null} import { AgentFS } from "npm:agentfs-sdk"; ``` ## Quick Start ```typescript theme={null} import { AgentFS } from 'agentfs-sdk'; // Persistent storage with identifier const agent = await AgentFS.open({ id: 'my-agent' }); // Creates: .agentfs/my-agent.db // Or use ephemeral in-memory database const ephemeralAgent = await AgentFS.open(); // Use the three main APIs // Key-value store await agent.kv.set('key', 'value'); // Filesystem await agent.fs.writeFile('/file.txt', 'data'); // Tool tracking await agent.tools.record(...); ``` ## Core APIs ### AgentFS Class The main entry point for all AgentFS operations. #### `AgentFS.open(options?: AgentFSOptions)` Creates or opens an AgentFS database. ```typescript theme={null} interface AgentFSOptions { /** * Optional unique identifier for the agent. * - If provided: Creates persistent storage at `.agentfs/{id}.db` * - If omitted: Uses ephemeral in-memory database */ id?: string; } // Persistent storage const agent = await AgentFS.open({ id: 'my-agent' }); // Creates: .agentfs/my-agent.db // Ephemeral in-memory database const ephemeralAgent = await AgentFS.open(); ``` #### Properties * `kv`: Key-value store interface * `fs`: Filesystem interface * `tools`: Tool call tracking interface * `db`: Direct access to the underlying Turso database ### Key-Value Store API Fast, typed storage for agent state and configuration. #### `kv.set(key: string, value: any)` Store a value with automatic JSON serialization. ```typescript theme={null} await agent.kv.set('user:123', { name: 'Alice', preferences: { theme: 'dark' } }); await agent.kv.set('session:current', 'abc-123'); await agent.kv.set('counter', 42); ``` #### `kv.get(key: string): Promise` Retrieve a value with automatic deserialization. ```typescript theme={null} interface UserData { name: string; preferences: { theme: string }; } const user = await agent.kv.get('user:123'); if (user) { console.log(user.name); // Type-safe access } ``` #### `kv.delete(key: string)` Remove a key-value pair. ```typescript theme={null} await agent.kv.delete('session:expired'); ``` #### `kv.list(prefix: string): Promise<{ key: string, value: any }[]>` List keys matching a prefix. ```typescript theme={null} // List all user keys const userEntries = await agent.kv.list('user:'); for (const { key, value } of userEntries) { console.log(key, value); } // List all keys (empty prefix) const allEntries = await agent.kv.list(''); ``` ### Filesystem API POSIX-like filesystem operations for managing agent data. #### `fs.writeFile(path: string, data: string | Buffer)` Write data to a file, creating parent directories as needed. ```typescript theme={null} // Write text await agent.fs.writeFile('/reports/summary.md', '# Report\nContent...'); // Write binary data const imageBuffer = await fetch(url).then(r => r.arrayBuffer()); await agent.fs.writeFile('/images/chart.png', Buffer.from(imageBuffer)); ``` #### `fs.readFile(path: string, options?: BufferEncoding): Promise` Read file contents. Returns a Buffer by default, or a string if encoding is specified. ```typescript theme={null} // Read as Buffer (default) const data = await agent.fs.readFile('/reports/summary.md'); const text = data.toString('utf-8'); // Read as string with encoding const content = await agent.fs.readFile('/reports/summary.md', 'utf-8'); // For JSON files const jsonData = await agent.fs.readFile('/config.json'); const config = JSON.parse(jsonData.toString()); ``` #### `fs.readdir(path: string): Promise` List directory contents. ```typescript theme={null} const files = await agent.fs.readdir('/reports'); // Returns: ['summary.md', '2024/', 'archive/'] // Check each entry type for (const entry of files) { const stats = await agent.fs.stat(`/reports/${entry}`); if (stats.isDirectory()) { console.log(`Directory: ${entry}`); } else { console.log(`File: ${entry} (${stats.size} bytes)`); } } ``` #### `fs.stat(path: string): Promise` Get file or directory metadata. ```typescript theme={null} interface Stats { size: number; // File size in bytes mode: number; // File mode/permissions mtime: number; // Modified time (Unix timestamp) ctime: number; // Created time (Unix timestamp) isFile(): boolean; isDirectory(): boolean; } const stats = await agent.fs.stat('/reports/summary.md'); console.log(`Size: ${stats.size} bytes`); console.log(`Modified: ${new Date(stats.mtime * 1000)}`); ``` #### `fs.exists(path: string): Promise` Check if a file or directory exists. ```typescript theme={null} if (await agent.fs.exists('/reports/draft.md')) { console.log('Draft exists'); } ``` #### `fs.deleteFile(path: string)` Delete a file. ```typescript theme={null} await agent.fs.deleteFile('/reports/old.md'); ``` ### Tool Call Tracking API Record and query agent tool invocations for debugging and compliance. #### `tools.record(name, started_at, completed_at, parameters?, result?, error?): Promise` Record a tool invocation. ```typescript theme={null} await agent.tools.record( name: string, // Tool identifier started_at: number, // Unix timestamp (seconds) completed_at: number, // Unix timestamp (seconds) parameters?: any, // Tool parameters (JSON-serializable) result?: any, // Tool result (JSON-serializable) error?: string // Error message if failed ); // Example: Track an API call const start = Date.now() / 1000; const response = await callOpenAI(prompt); const id = await agent.tools.record( 'openai_completion', start, Date.now() / 1000, { prompt, model: 'gpt-4', temperature: 0.7 }, { response, tokensUsed: 150 } ); ``` #### `tools.get(id: number): Promise` Get details of a specific tool call. ```typescript theme={null} const toolCall = await agent.tools.get(42); if (toolCall) { console.log('Duration:', toolCall.duration_ms, 'ms'); } ``` ## Browser Support AgentFS works in browser environments using WebAssembly: ```html theme={null} ``` ## Support * [GitHub Issues](https://github.com/tursodatabase/agentfs/issues) - Bug reports and feature requests * [Discord](https://tur.so/discord) - Community support and discussions * [Examples](https://github.com/tursodatabase/agentfs/tree/main/examples) - Sample applications # List Audit Logs Source: https://docs.turso.tech/api-reference/audit-logs/list GET /v1/organizations/{organizationSlug}/audit-logs Return the audit logs for the given organization, ordered by the `created_at` field in descending order. > [!WARNING] > > Audit Logs are limited to scaler plan and above. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/audit-logs \ -H 'Authorization: Bearer TOKEN' ``` # Authentication Source: https://docs.turso.tech/api-reference/authentication The Turso API uses API tokens to authenticate requests. You can create and revoke API tokens using the [Turso CLI](/cli) and [Authentication API](/api-reference/tokens/create). Tokens can be minted at three levels of restriction: | Restriction | What it can do | How to mint | | - | - | - | | Organization-scoped | Restricted to a single organization. Optionally limited to a set of [scopes](#organization-scoped-tokens-with-scopes). | `turso auth api-tokens mint my-token --org my-org` | | Group-scoped | Pinned to one group inside an organization, with a chosen set of permissions. | `turso auth api-tokens mint my-token --org my-org --group default --read-only` | | Unrestricted *(deprecated)* | Acts on every organization the caller belongs to. | `turso auth api-tokens mint my-token` | > [!WARNING] > > **Unrestricted (cross-org) tokens are deprecated and will be removed in a future release.** New tokens should always be scoped to an organization with `--org`. Existing unrestricted tokens continue to work for now, but you should rotate them to scoped tokens at your earliest convenience. ```bash theme={null} # Token scoped to a single organization turso auth api-tokens mint my-token --org my-org # Group-scoped token, read-only (tighter scope, for automations that only need one group) turso auth api-tokens mint my-token --org my-org --group default --read-only # Token with access to all organizations — deprecated turso auth api-tokens mint my-token ``` * Scope tokens to a single organization with `--org`, especially in CI/CD and production environments. * Use environment variables when working with API tokens. * Never share your API token in public, including repositories, and CI/CD Actions. Turso uses Bearer authentication, and requires your API token to be passed with all protected requests in the `Authorization` header: ```bash theme={null} Authorization: Bearer TOKEN ``` ## Organization-scoped tokens with scopes An organization-scoped token without scopes can do everything that the user who minted it can do in the organization. To limit it, mint it with scopes. The token can then do only the listed operations, in every group of the organization. ```bash theme={null} # Read the audit logs, members, and billing data of the organization turso auth api-tokens mint audit-reader --org my-org --scope org:read # Create databases and mint SQL tokens in every group turso auth api-tokens mint deploy-bot --org my-org --scope db:create --scope db:mint-token ``` An organization-scoped token can have all the [group scopes](#scope-vocabulary). It can also have these organization scopes, which group-scoped tokens cannot have: | Scope | Allows | | - | - | | `group:create` | Create groups in the organization. | | `group:delete` | Delete groups in the organization. | | `org:read` | Read organization data: members, invites, billing, invoices, audit logs, SSO, JWKS, and API tokens. | Reading invites and audit logs also requires that the user who minted the token is an admin or owner of the organization. A token with scopes cannot do operations that have no scope. These include managing members and invites, changing organization settings, transferring groups or databases, and minting API tokens. Use a token without scopes for these operations. The `full-access` preset does not include the organization scopes. To grant them, list each scope with `--scope`. ## Group-scoped tokens A group-scoped token is a Platform API credential pinned to one group inside an organization, with an explicit list of allowed operations. It's the right shape for automations that should be able to provision and manage databases inside a single group without being able to reach the rest of the organization. The caller minting a group-scoped token must be an admin or owner of the organization. > [!NOTE] > > Group-scoped tokens are control-plane credentials — they authorize requests to the Turso Platform API. They are independent from the [SQL-engine tokens](/sdk/authorization/tokens) that your application uses to query a database. The two systems do not share key material or vocabulary. ### Scope vocabulary The platform expands the presets `read-only` and `full-access` to the corresponding individual scopes server-side, so the values you see on a token after creation are always individual scopes. | Scope | Allows | | - | - | | `read` | All GET-style routes: list/retrieve databases and groups, configuration, usage, instances, locations. | | `db:create` | Create databases inside the group (`POST /databases`), seed from a dump, restore. | | `db:delete` | Delete a database inside the group. | | `db:configure` | Patch database configuration, wake, add or remove instances. | | `db:mint-token` | Issue a SQL-engine token or TLS client certificate for a database inside the group. | | `db:rotate-creds` | Rotate the database signing key, invalidating every SQL token previously issued for it. | | `group:configure` | Configure, rename, update, unarchive the group, or add and remove locations. | | `group:mint-token` | Issue a group-level SQL-engine token. | | `group:rotate-creds` | Rotate the group signing key, invalidating every SQL token issued against any database in the group. | > [!WARNING] > > `db:mint-token` and `db:rotate-creds` are deliberately separate scopes. Minting a new SQL credential is additive; rotating invalidates every credential currently in use, which can take down running applications. Grant rotation only to automations that need it. ### Presets | Preset | Expands to | | - | - | | `read-only` | `read` | | `full-access` | Every individual scope listed above. | Group create, group delete, group transfer, and AWS migration are intentionally not reachable from a group-scoped token at any scope — those operations are gated to organization-level credentials. ### Lifecycle * Group-scoped tokens are pinned by the group's UUID, not its name. If a group is deleted and a new one is created with the same name, old tokens do not transfer — they are revoked along with the group. * Deleting a group cascades a revoke to every token scoped to it. * Transferring a group to a different organization cascades a revoke to every token scoped to it. ### Managing organization tokens Two endpoints let admins manage every token scoped to an organization in one place — they are what backs the dashboard's organization-level token table: * [`GET /v1/organizations/{organizationSlug}/api-tokens`](/api-reference/tokens/list-organization) — list every org-scoped and group-scoped token, with the minting user attached. * [`DELETE /v1/organizations/{organizationSlug}/api-tokens/{tokenId}`](/api-reference/tokens/revoke-organization) — revoke by ID. Members and viewers can call the same endpoints, but only see and revoke tokens they minted themselves. ## Base URL The Turso API is located at the following URL: ```bash theme={null} https://api.turso.tech ``` # Retrieve Database Configuration Source: https://docs.turso.tech/api-reference/databases/configuration GET /v1/organizations/{organizationSlug}/databases/{databaseName}/configuration Retrieve an individual database configuration belonging to the organization or user. ```bash cURL theme={null} curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \ -H 'Authorization: Bearer TOKEN' ``` # Create Database Source: https://docs.turso.tech/api-reference/databases/create POST /v1/organizations/{organizationSlug}/databases Creates a new database in a group for the organization or user. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default" }' ``` ```bash cURL (for upload) theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "seed": { "type": "database_upload" } }' ``` ```bash cURL (for encrypted upload) theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "seed": { "type": "database_upload" }, "remote_encryption": { "encryption_key": "BASE64_ENCRYPTION_KEY", "encryption_cipher": "aes256gcm" } }' ``` ```bash cURL (new encrypted database) theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "remote_encryption": { "encryption_key": "BASE64_ENCRYPTION_KEY", "encryption_cipher": "aes256gcm" } }' ``` ```bash cURL (fork encrypted database) theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "seed": { "type": "database", "name": "source-encrypted-db" }, "remote_encryption": { "encryption_key": "BASE64_ENCRYPTION_KEY", "encryption_cipher": "aes256gcm" } }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const database = await turso.databases.create("new-database", { group: "default", }); ``` # Generate Database Auth Token Source: https://docs.turso.tech/api-reference/databases/create-token POST /v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens Generates an authorization token for the specified database. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens?expiration=2w&authorization=full-access' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const token = await turso.databases.createToken("my-db", { expiration: "2w", authorization: "full-access", }); ``` ```json theme={null} { "jwt": "TOKEN" } ``` # Delete Database Source: https://docs.turso.tech/api-reference/databases/delete DELETE /v1/organizations/{organizationSlug}/databases/{databaseName} Delete a database belonging to the organization or user. > [!NOTE] > > On paid plans, a deleted database can be restored for up to five days — see [Recover Deleted Databases](/features/recover-deleted-databases). ```bash cURL theme={null} curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const database = await turso.databases.delete("my-db"); ``` # Invalidate All Database Auth Tokens Source: https://docs.turso.tech/api-reference/databases/invalidate-tokens POST /v1/organizations/{organizationSlug}/databases/{databaseName}/auth/rotate Invalidates all authorization tokens for the specified database. > [!WARNING] > > A short downtime is required to complete the changes. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/rotate' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const response = await turso.databases.rotateTokens("my-db"); ``` # List Databases Source: https://docs.turso.tech/api-reference/databases/list GET /v1/organizations/{organizationSlug}/databases Returns a list of databases belonging to the organization or user. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const databases = await turso.databases.list(); ``` # List Deleted Databases Source: https://docs.turso.tech/api-reference/databases/list-deleted GET /v3/organizations/{organizationId}/databases Returns databases deleted within the last five days, which can still be restored. Requires a paid plan. Available even while recovery is disabled for the organization. > [!NOTE] > > Unlike the v1 endpoints, this endpoint identifies the organization by its > **UUID** — the `id` field from > [Retrieve Organization](/api-reference/organizations/retrieve). ```bash cURL theme={null} curl -L 'https://api.turso.tech/v3/organizations/{organizationId}/databases?deleted=true' \ -H 'Authorization: Bearer TOKEN' ``` # List Database Instances Source: https://docs.turso.tech/api-reference/databases/list-instances GET /v1/organizations/{organizationSlug}/databases/{databaseName}/instances Returns a list of instances of a database. Instances are the individual primary or replica databases in each region defined by the group. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/instances \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const instances = await turso.databases.listInstances("my-db"); ``` # Restore Database Source: https://docs.turso.tech/api-reference/databases/restore POST /v3/organizations/{organizationId}/databases/{databaseId}/restore Restore a database deleted within the last five days, exactly as it was at the moment it was deleted. Requires a paid plan, an admin or owner of the organization, and recovery enabled for the organization. > [!NOTE] > > Unlike the v1 endpoints, this endpoint identifies the organization and database > by **UUID** — use the `id` field from > [Retrieve Organization](/api-reference/organizations/retrieve) and the `DbId` > field from [List Deleted Databases](/api-reference/databases/list-deleted). ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v3/organizations/{organizationId}/databases/{databaseId}/restore' \ -H 'Authorization: Bearer TOKEN' ``` ```bash cURL (new name) theme={null} curl -L -X POST 'https://api.turso.tech/v3/organizations/{organizationId}/databases/{databaseId}/restore' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "my-restored-database" }' ``` # Retrieve Database Source: https://docs.turso.tech/api-reference/databases/retrieve GET /v1/organizations/{organizationSlug}/databases/{databaseName} Returns a database belonging to the organization or user. ```bash cURL theme={null} curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const database = await turso.databases.retrieve("my-db"); ``` # Retrieve Database Instance Source: https://docs.turso.tech/api-reference/databases/retrieve-instance GET /v1/organizations/{organizationSlug}/databases/{databaseName}/instances/{instanceName} Return the individual database instance by name. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/instances/{instanceName} \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const instance = await turso.databases.retrieveInstance( "my-db", "instanceName", ); ``` # Retrieve Database Stats Source: https://docs.turso.tech/api-reference/databases/stats GET /v1/organizations/{organizationSlug}/databases/{databaseName}/stats Fetch the top queries of a database, including the count of rows read and written. ```bash cURL theme={null} curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/stats' \ -H 'Authorization: Bearer TOKEN' ``` # Update Database Configuration Source: https://docs.turso.tech/api-reference/databases/update-configuration PATCH /v1/organizations/{organizationSlug}/databases/{databaseName}/configuration Update a database configuration belonging to the organization or user. ```bash cURL theme={null} curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "size_limit": "500mb", "delete_protection": true, "block_reads": false, "block_writes": false, "allowed_ips": ["203.0.113.7", "10.0.0.0/8"], "allowed_aws_vpc_ids": ["vpce-0fe6c8807461bba49"] }' ``` # Upload Database Source: https://docs.turso.tech/api-reference/databases/upload POST https://{database_hostname}/v1/upload Upload a SQLite database file to a database created with `seed.type = "database_upload"`. > [!NOTE] > > This endpoint is on the **database hostname** (e.g., `my-db-my-org.turso.io`), not `api.turso.tech`. > [!NOTE] > > You must first [create a database](/api-reference/databases/create) with `seed.type = "database_upload"`. > [!NOTE] > > This endpoint requires a **database token**, not a platform API token. You can [create a database token](/api-reference/databases/create-token) after creating the database. ## Headers | Header | Required | Description | | - | - | - | | `Authorization` | Yes | Bearer token for the database | | `Content-Length` | Yes | Size of the database file in bytes | | `x-turso-encryption-key` | No | Base64-encoded encryption key (32 bytes for 256-bit ciphers, 16 bytes for 128-bit ciphers) | | `x-turso-encryption-cipher` | No | Encryption cipher. See table below for options. | ## Request Body The raw binary SQLite database file. ## Database File Requirements The SQLite database file must meet these requirements: * **Journal mode**: WAL (`PRAGMA journal_mode = WAL`) * **Page size**: 4096 bytes (`PRAGMA page_size = 4096`) * **Auto vacuum**: Disabled (`PRAGMA auto_vacuum = 0`) * **Encoding**: UTF-8 (`PRAGMA encoding = 'UTF-8'`) For encrypted databases, you must also set the reserved bytes to match the cipher: | Cipher | Reserved Bytes | | - | - | | `aes256gcm` | 28 | | `aes128gcm` | 28 | | `chacha20poly1305` | 28 | | `aegis128l` | 32 | | `aegis128x2` | 32 | | `aegis128x4` | 32 | | `aegis256` | 48 | | `aegis256x2` | 48 | | `aegis256x4` | 48 | ## Responses ### Success Returns an empty response with status `200 OK` on success. ### Errors | Status | Description | | - | - | | `400` | Invalid database file (wrong format, settings, or corruption) | | `401` | Invalid or missing authorization token | ```bash cURL theme={null} curl -X POST 'https://{database_hostname}/v1/upload' \ -H 'Authorization: Bearer DATABASE_TOKEN' \ -H 'Content-Length: FILE_SIZE' \ --data-binary '@/path/to/database.db' ``` ```bash cURL (with encryption) theme={null} curl -X POST 'https://{database_hostname}/v1/upload' \ -H 'Authorization: Bearer DATABASE_TOKEN' \ -H 'Content-Length: FILE_SIZE' \ -H 'x-turso-encryption-key: BASE64_ENCRYPTION_KEY' \ -H 'x-turso-encryption-cipher: aes256gcm' \ --data-binary '@/path/to/database.db' ``` # Retrieve Database Usage Source: https://docs.turso.tech/api-reference/databases/usage GET /v1/organizations/{organizationSlug}/databases/{databaseName}/usage Fetch activity usage for a database in a given time period. ```bash cURL theme={null} curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/usage?from=2023-01-01T00:00:00Z&to=2023-02-01T00:00:00Z' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const usageStatsWithDate = await turso.databases.usage("my-db"); const usageStatsWithDate = await turso.databases.usage("my-db", { from: new Date("2023-01-01"), to: new Date("2023-02-01"), }); const usageStatsWithString = await turso.databases.usage("my-db", { from: "2023-01-01T00:00:00Z", to: "2023-02-01T00:00:00Z", }); ``` # Retrieve Group Configuration Source: https://docs.turso.tech/api-reference/groups/configuration GET /v1/organizations/{organizationSlug}/groups/{groupName}/configuration Retrieve an individual group configuration belonging to the organization or user. ```bash cURL theme={null} curl -L -X GET 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/configuration' \ -H 'Authorization: Bearer TOKEN' ``` # Create Group Source: https://docs.turso.tech/api-reference/groups/create POST /v1/organizations/{organizationSlug}/groups Creates a new group for the organization or user. > [!WARNING] > > Creating more than one group is limited to Scaler, Pro and Enterprise plans. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-group", "location": "lhr" }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const group = await turso.groups.create("new-group", { location: "lhr", extensions: ["vector", "uuid"], // 'all' }); ``` # Create Group Auth Token Source: https://docs.turso.tech/api-reference/groups/create-token POST /v1/organizations/{organizationSlug}/groups/{groupName}/auth/tokens Generates an authorization token for the specified group. > [!NOTE] > > Tokens cannot be retrieved once created, and cannot be revoked individually. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/auth/tokens?expiration=2w&authorization=full-access' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const token = await turso.groups.createToken("default", { expiration: "2w", authorization: "full-access", }); ``` ```json theme={null} { "jwt": "TOKEN" } ``` # Delete Group Source: https://docs.turso.tech/api-reference/groups/delete DELETE /v1/organizations/{organizationSlug}/groups/{groupName} Delete a group belonging to the organization or user. > [!NOTE] > > On paid plans, a deleted group and its databases can be restored for up to five > days — see > [Recover Deleted Databases](/features/recover-deleted-databases). ```bash cURL theme={null} curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const group = await turso.groups.delete("new-group"); ``` # Invalidate All Group Auth Tokens Source: https://docs.turso.tech/api-reference/groups/invalidate-tokens POST /v1/organizations/{organizationSlug}/groups/{groupName}/auth/rotate Invalidates all authorization tokens for the specified group. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/auth/rotate' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const response = await turso.groups.invalidateTokens("default"); ``` # List Groups Source: https://docs.turso.tech/api-reference/groups/list GET /v1/organizations/{organizationSlug}/groups Returns a list of groups belonging to the organization or user. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/groups \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const groups = await turso.groups.list(); ``` # List Deleted Groups Source: https://docs.turso.tech/api-reference/groups/list-deleted GET /v3/organizations/{organizationId}/deleted-groups Returns groups deleted within the last five days, including empty groups, which can still be restored. Requires a paid plan and an organization-level token. Available even while recovery is disabled for the organization. > [!NOTE] > > Unlike the v1 endpoints, this endpoint identifies the organization by its > **UUID** — the `id` field from > [Retrieve Organization](/api-reference/organizations/retrieve). ```bash cURL theme={null} curl -L 'https://api.turso.tech/v3/organizations/{organizationId}/deleted-groups' \ -H 'Authorization: Bearer TOKEN' ``` # Restore Group Source: https://docs.turso.tech/api-reference/groups/restore POST /v3/organizations/{organizationId}/groups/{groupId}/restore Restore a group deleted within the last five days, along with the member databases that were deleted with it. Requires a paid plan, an admin or owner of the organization, and recovery enabled for the organization. If some databases fail to restore, the group still becomes active: retry the failed database UUIDs individually with Restore Database. > [!NOTE] > > Unlike the v1 endpoints, this endpoint identifies the organization and group by > **UUID** — use the `id` field from > [Retrieve Organization](/api-reference/organizations/retrieve) and the `uuid` > field from [List Deleted Groups](/api-reference/groups/list-deleted). ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v3/organizations/{organizationId}/groups/{groupId}/restore' \ -H 'Authorization: Bearer TOKEN' ``` # Retrieve Group Source: https://docs.turso.tech/api-reference/groups/retrieve GET /v1/organizations/{organizationSlug}/groups/{groupName} Returns a group belonging to the organization or user. ```bash cURL theme={null} curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const group = await turso.groups.retrieve("default"); ``` # Transfer Group Source: https://docs.turso.tech/api-reference/groups/transfer POST /v1/organizations/{organizationSlug}/groups/{groupName}/transfer Transfer a group to another organization that you own or a member of. > [!NOTE] > > You can only transfer groups to organizations you own or are an admin. > [!WARNING] > > Existing database URL and tokens will continue to work, but should update your application to use the new URL and token as soon as possible. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/transfer' \ -H 'Authorization: Bearer TOKEN' \ -d '{ "organization": "new-organization-slug" }' ``` # Unarchive Group Source: https://docs.turso.tech/api-reference/groups/unarchive POST /v1/organizations/{organizationSlug}/groups/{groupName}/unarchive Unarchive a group that has been archived due to inactivity. > [!NOTE] > > Databases get archived after 10 days of inactivity for users on a free plan. You can unarchive inactive groups using the API. ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/unarchive' \ -H 'Authorization: Bearer TOKEN' \ ``` # Update Group Configuration Source: https://docs.turso.tech/api-reference/groups/update-configuration PATCH /v1/organizations/{organizationSlug}/groups/{groupName}/configuration Update a group configuration belonging to the organization or user. ```bash cURL theme={null} curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/configuration' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "delete_protection": true }' ``` # Turso Platform API Source: https://docs.turso.tech/api-reference/introduction Programmatically manage databases, groups, organizations, and tokens with the Turso Platform API. ## Quickstart with the TypeScript SDK The fastest way to use the Platform API from JavaScript or TypeScript is the official [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client: ```bash theme={null} npm install @tursodatabase/api ``` ```ts theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: process.env.TURSO_ORG!, token: process.env.TURSO_PLATFORM_TOKEN!, }); // Create a database const db = await turso.databases.create("user-abc123", { group: "default" }); // Generate a scoped auth token for that database const { jwt } = await turso.databases.createToken("user-abc123"); // Delete it when no longer needed await turso.databases.delete("user-abc123"); ``` For the raw HTTP reference, see the resource endpoints below. ## Common use cases ### Database per user / database per agent (multi-tenant) Provision a dedicated database per user or per AI agent. Each entity gets its own isolated SQLite database that you create, token-scope, and tear down via the API. ```ts theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: process.env.TURSO_ORG!, token: process.env.TURSO_PLATFORM_TOKEN!, }); // On user signup or agent creation — provision a database const db = await turso.databases.create(`agent-${agentId}`, { group: "default" }); // Generate a scoped token for that database const { jwt } = await turso.databases.createToken(`agent-${agentId}`); // Store db.hostname and jwt for the session ``` Once the database is created, connect to it from your application: * **Over the wire** — query the database remotely using [`@tursodatabase/serverless`](/sdk/ts/quickstart) * **Local-first with sync** — sync the database to and from a local file using [`@tursodatabase/sync`](/sync/usage) ### Platform / reseller Create and manage databases on behalf of your end users. Use the Platform API to build your own database-as-a-service on top of Turso. ```ts theme={null} // List all databases in your organization const databases = await turso.databases.list(); // Get details for a specific database const db = await turso.databases.get("customer-db"); ``` > **[API Quickstart](/api-reference/quickstart)** > > Step-by-step guide to create your first database with the API. ## API Resources Start integrating the Turso API with your platform in a few simple steps. # Closest Region Source: https://docs.turso.tech/api-reference/locations/closest-region GET https://region.turso.io Returns the closest region to the user's location. #### Response **server** `string` The location code for the server responding. **client** `string` The location code for the client request. ```bash cURL theme={null} curl https://region.turso.io ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const { server, client } = await turso.locations.closest(); ``` ```json 200 theme={null} { "server": "lhr", "client": "lhr" } ``` # List Locations Source: https://docs.turso.tech/api-reference/locations/list GET /v1/locations Returns a list of locations where you can create or replicate databases. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/locations \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const locations = await turso.locations.list(); ``` # Create Invite Source: https://docs.turso.tech/api-reference/organizations/invites/create-v2 POST /v2/organizations/{organizationSlug}/invites Invite a user to an organization. If the user isn't already registered with Turso, they will receive a signup link. If an existing pending invite exists for the same email, it will be replaced. > [!WARNING] > > Invites are limited to scaler plan and above. > [!NOTE] > > You can invite by **email** or by **username** — provide exactly one. When inviting by username, the invite is sent to that user's registered email. > [!NOTE] > > You must be an `owner` or `admin` to invite other users. **You can only invite users to a team and not your personal account.** > [!NOTE] > > If a pending invite already exists for the same email, it will be replaced with a new one. ```bash Invite by email theme={null} curl -L -X POST https://api.turso.tech/v2/organizations/{organizationSlug}/invites \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{"email": "user@example.com", "role": "member"}' ``` ```bash Invite by username theme={null} curl -L -X POST https://api.turso.tech/v2/organizations/{organizationSlug}/invites \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{"username": "turso-user", "role": "member"}' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "mycompany", token: "", }); const invite = await turso.organizations.inviteUser( "user@example.com", "member" ); ``` # Delete Invite Source: https://docs.turso.tech/api-reference/organizations/invites/delete-v2 DELETE /v2/organizations/{organizationSlug}/invites/{email} Delete a pending invite for the organization by email. > [!WARNING] > > Invites are limited to scaler plan and above. ```bash cURL theme={null} curl -L -X DELETE https://api.turso.tech/v2/organizations/{organizationSlug}/invites/{email} \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "mycompany", token: "", }); await turso.organizations.deleteInvite("user@example.com"); ``` # List Invites Source: https://docs.turso.tech/api-reference/organizations/invites/list-v2 GET /v2/organizations/{organizationSlug}/invites Returns a list of pending invites for the organization. ```bash cURL theme={null} curl -L https://api.turso.tech/v2/organizations/{organizationSlug}/invites \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "mycompany", token: "", }); const invites = await turso.organizations.listInvites(); ``` # List Invoices Source: https://docs.turso.tech/api-reference/organizations/invoices GET /v1/organizations/{organizationSlug}/invoices Returns a list of invoices for the organization. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/invoices \ -H 'Authorization: Bearer TOKEN' ``` # List Organizations Source: https://docs.turso.tech/api-reference/organizations/list GET /v1/organizations Returns a list of organizations the authenticated user owns or is a member of. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const organizations = await turso.organizations.list(); ``` # Add Member Source: https://docs.turso.tech/api-reference/organizations/members/add POST /v1/organizations/{organizationSlug}/members Add an existing Turso user to an organization. > [!NOTE] > > If you want to add someone who is not a registered Turso user, you can [create an invite](/api-reference/organizations/invites/create) instead. > [!NOTE] > > You must be an `owner` or `admin` to add other members. **You can only add users to a team and not your personal account.** # List Members Source: https://docs.turso.tech/api-reference/organizations/members/list GET /v1/organizations/{organizationSlug}/members Returns a list of members part of the organization. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/members \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const members = await turso.organizations.members("mycompany"); ``` # Remove Member Source: https://docs.turso.tech/api-reference/organizations/members/remove DELETE /v1/organizations/{organizationSlug}/members/{username} Remove a user from the organization by username. ```bash cURL theme={null} curl -L -X DELETE https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const member = await turso.organizations.removeMember("mycompany", "iku"); ``` # Retrieve Member Source: https://docs.turso.tech/api-reference/organizations/members/retrieve GET /v1/organizations/{organizationSlug}/members/{username} Retrieve details of a specific member in the organization. ```bash theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \ -H 'Authorization: Bearer TOKEN' ``` # Update Member Role Source: https://docs.turso.tech/api-reference/organizations/members/update PATCH /v1/organizations/{organizationSlug}/members/{username} Update the role of an organization member. Only organization admins or owners can perform this action. ```bash theme={null} curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug}/members/{username} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "role": "member" }' ``` # List Plans Source: https://docs.turso.tech/api-reference/organizations/plans GET /v1/organizations/{organizationSlug}/plans Returns a list of available plans and their quotas. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/plans \ -H 'Authorization: Bearer TOKEN' ``` # Retrieve Organization Source: https://docs.turso.tech/api-reference/organizations/retrieve GET /v1/organizations/{organizationSlug} Retrieve details of a specific organization. ```bash theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug} \ -H 'Authorization: Bearer TOKEN' ``` # Current Subscription Source: https://docs.turso.tech/api-reference/organizations/subscription GET /v1/organizations/{organizationSlug}/subscription Returns the current subscription details for the organization. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/subscription \ -H 'Authorization: Bearer TOKEN' ``` # Update Organization Source: https://docs.turso.tech/api-reference/organizations/update PATCH /v1/organizations/{organizationSlug} Update an organization you own or are a member of. ```bash cURL (overages) theme={null} curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "overages": true }' ``` ```bash cURL (require MFA) theme={null} curl -L -X PATCH https://api.turso.tech/v1/organizations/{organizationSlug} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "require_mfa": true }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const organization = await turso.organizations.update("mycompany", { overages: true, }); ``` # Organization Usage Source: https://docs.turso.tech/api-reference/organizations/usage GET /v1/organizations/{organizationSlug}/usage Fetch current billing cycle usage for an organization. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/organizations/{organizationSlug}/usage \ -H 'Authorization: Bearer TOKEN' ``` # Quickstart Source: https://docs.turso.tech/api-reference/quickstart Get started with Turso API in a few easy steps. > [!NOTE] > > **Using TypeScript?** You can skip the raw HTTP examples and use the [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client instead. See the [SDK quickstart](/api-reference/introduction#quickstart-with-the-typescript-sdk). **1. Signup or Login using the Turso CLI** Make sure to [install the Turso CLI](/cli/installation) if you haven't already. ```bash Signup theme={null} turso auth signup ``` ```bash Login theme={null} turso auth login ``` **2. Retrieve your account or organization slug** The Platform API can be used with your personal account or with an organization. You'll need the obtain the `slug` of your account or organization using using the Turso CLI: ```bash theme={null} turso org list ``` **3. Create a new Platform API Token** Now create a new API Token using the Turso CLI: ```bash theme={null} turso auth api-tokens mint quickstart ``` You can scope the token to a specific organization with `--org`: ```bash theme={null} turso auth api-tokens mint quickstart --org my-org ``` > [!NOTE] > > Make sure to save the token somewhere safe. We'll need it next. **4. Fetch available Locations** Before we can create a group or database in a specific region, we'll need to fetch the list of available regions: ```bash cURL theme={null} curl -L 'https://api.turso.tech/v1/locations' \ -H 'Authorization: Bearer TOKEN' \ ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const locations = await turso.locations.list(); ``` > [!NOTE] > > The `organizationSlug` is the name of your organization or personal account. **5. Create a Group** All databases belong to a group that can exist in one or more locations. Begin by creating a group, giving it a `name` and primary `location`: ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "default", "location": "lhr" }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const group = await turso.groups.addLocation("default", "lhr"); ``` **6. Create a Database** Now create your first database in the group you created above: ```bash cURL theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "my-db", "group": "default" }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const database = await turso.databases.create("my-db", "ams"); ``` **7. Connect to your database** You now have a database, distributed across multiple regions that you can connect to using one of the official or experimental SDKs: # Errors Source: https://docs.turso.tech/api-reference/response-codes The Turso API will respond with an HTTP status code, and in the event of an error, return an error message in the response body. | Code | Description | | - | - | | `200` | `OK` — Successful request | | `401` | `Unauthorized` — Invalid or expired auth token | | `402` | `Payment required` — Check you have an active subscription | | `403` | `Forbidden` — You do not have permission to access this resource | | `409` | `Conflict` — Resource already exists | # Create API Token Source: https://docs.turso.tech/api-reference/tokens/create POST /v1/auth/api-tokens/{tokenName} Returns a new API token belonging to a user. The token can be minted at three levels of restriction, in increasing order of narrowness: - **Organization-scoped** — pass `organization`, and optionally `scopes`. The token can only act on resources inside that organization. With `scopes`, it can do only the listed operations, in every group of the organization. - **Group-scoped** — pass `organization`, `group`, and `scopes`. The token is pinned to a single group inside the organization and only the operations listed in `scopes` are allowed. The caller must be an admin or owner of the organization. - **Unrestricted** *(deprecated)* — no request body. The token can act on every organization the caller belongs to. **Unrestricted tokens are deprecated and will be removed in a future release.** Always pass `organization` for new tokens and rotate existing unrestricted tokens to scoped tokens. Group-scoped tokens are designed for automations that should be able to provision and manage databases inside a single group without being able to touch the rest of the organization. > [!WARNING] > > The `token` in the response is never revealed again. Store this somewhere safe, and never share or commit it to source control. > [!WARNING] > > **Unrestricted (cross-org) tokens are deprecated and will be removed in a future release.** Always include at least `organization` in the request body. ```bash cURL (org-scoped) theme={null} curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{"organization": "my-org"}' ``` ```bash cURL (org-scoped, with scopes) theme={null} curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "organization": "my-org", "scopes": ["org:read"] }' ``` ```bash cURL (deprecated, unrestricted) theme={null} curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \ -H 'Authorization: Bearer TOKEN' ``` ```bash cURL (group-scoped, presets) theme={null} curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "organization": "my-org", "group": "default", "scopes": ["read-only"] }' ``` ```bash cURL (group-scoped, fine-grained) theme={null} curl -L -X POST https://api.turso.tech/v1/auth/api-tokens/{tokenName} \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "organization": "my-org", "group": "default", "scopes": ["db:create", "db:configure", "db:mint-token"] }' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const apiToken = await turso.apiTokens.create("my-token"); ``` # List API Tokens Source: https://docs.turso.tech/api-reference/tokens/list GET /v1/auth/api-tokens Returns a list of API tokens belonging to a user. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/auth/api-tokens \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const apiTokens = await turso.apiTokens.list(); ``` # List Organization API Tokens Source: https://docs.turso.tech/api-reference/tokens/list-organization GET /v1/organizations/{organizationSlug}/api-tokens Returns the API tokens scoped to this organization (both organization-scoped and group-scoped). Unrestricted tokens are not returned here — manage those via [`GET /v1/auth/api-tokens`](/api-reference/tokens/list). Authorization is symmetric with the revoke endpoint: - **Admins and owners** see every token scoped to the organization, with the minting user attached in the `owner` field. - **Members and viewers** see only tokens they minted themselves. This mirrors the personal-access-token model used in GitHub organization settings: admins get the full attribution list; everyone else sees their own access. > [!NOTE] > > Use this endpoint to enumerate the tokens that act on a single organization (both org-scoped and group-scoped). Admins and owners see every token in the organization with the minting user attached; members and viewers see only their own. To list tokens across every organization the caller belongs to, use [`GET /v1/auth/api-tokens`](/api-reference/tokens/list) instead. ```bash cURL theme={null} curl -L 'https://api.turso.tech/v1/organizations/{organizationSlug}/api-tokens' \ -H 'Authorization: Bearer TOKEN' ``` # Revoke API Token Source: https://docs.turso.tech/api-reference/tokens/revoke DELETE /v1/auth/api-tokens/{tokenName} Revokes the provided API token belonging to a user. ```bash cURL theme={null} curl -L -X DELETE 'https://api.turso.tech/v1/auth/api-tokens/{tokenName}' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const response = await turso.apiTokens.revoke("my-token"); ``` # Revoke Organization API Token Source: https://docs.turso.tech/api-reference/tokens/revoke-organization DELETE /v1/organizations/{organizationSlug}/api-tokens/{tokenId} Revokes a token scoped to this organization. The path takes a token **ID**, not a name, because names are unique per user but not across users — an admin revoking a member's token can't disambiguate by name alone. Authorization is symmetric with the list endpoint: - **Admins and owners** can revoke any token scoped to the organization (org-scoped or group-scoped, regardless of who minted it). - **Members and viewers** can revoke only tokens they minted themselves. A token scoped to a different organization returns `404`, not `403`, so the endpoint does not leak the existence of cross-org token IDs. Unrestricted tokens are also unreachable here and must be revoked via [`DELETE /v1/auth/api-tokens/{tokenName}`](/api-reference/tokens/revoke). > [!NOTE] > > This endpoint takes a token **ID** (not a name) because token names are not unique across users in an organization. Admins and owners may revoke any token scoped to the organization; members and viewers may only revoke tokens they minted themselves. Use [`DELETE /v1/auth/api-tokens/{tokenName}`](/api-reference/tokens/revoke) to revoke unrestricted tokens. ```bash cURL theme={null} curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/api-tokens/{tokenId}' \ -H 'Authorization: Bearer TOKEN' ``` # Validate API Token Source: https://docs.turso.tech/api-reference/tokens/validate GET /v1/auth/validate Validates an API token belonging to a user. ```bash cURL theme={null} curl -L 'https://api.turso.tech/v1/auth/validate' \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const response = await turso.apiTokens.validate("..."); ``` # Get Current User Source: https://docs.turso.tech/api-reference/user/get-current GET /v1/user Returns information about the currently authenticated user. ```bash cURL theme={null} curl -L https://api.turso.tech/v1/user \ -H 'Authorization: Bearer TOKEN' ``` ```ts Node.js theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ org: "...", token: "", }); const user = await turso.users.getCurrent(); ``` # auth api-tokens list Source: https://docs.turso.tech/cli/auth/api-tokens/list To list every API token you have minted across every organization you belong to, run the following command: ```bash theme={null} turso auth api-tokens list ``` The output is a table: ``` NAME SCOPE PERMISSIONS CREATED AT ci-bot all — 2026-04-12 deploy-bot my-org — 2026-05-02 provisioner my-org/default read-only 2026-05-21 schema-bot my-org/default db:create, db:configure, db:mint-token 2026-05-23 admin-bot my-org/default full-access 2026-05-24 ``` * **Scope** shows how the token is bound: * `all` — unrestricted, can act on every organization the caller belongs to. * `` — organization-scoped. * `/` — group-scoped. * **Permissions** summarizes the scope grant for group-scoped tokens: * `—` for tokens without an explicit scope set (unrestricted and org-scoped tokens). * `read-only` when the only scope is `read`. * `full-access` when every scope is granted. * Otherwise, the comma-separated scope list. The list always reflects what the platform stores. Presets passed at creation time (`--read-only`, `--full-access`) are expanded into individual scopes before storage and are surfaced here in expanded form. # auth api-tokens mint Source: https://docs.turso.tech/cli/auth/api-tokens/mint To create a new API token, scoped to a single organization, run the following command: ```bash theme={null} turso auth api-tokens mint --org ``` > [!WARNING] > > **Unrestricted (cross-org) tokens — minting without `--org` — are deprecated and will be removed in a future release.** Always pass `--org` for new tokens. Existing unrestricted tokens continue to work for now, but you should rotate them to scoped tokens at your earliest convenience. ## Flags | Flag | Description | | - | - | | `--org` | Restrict the token to a specific organization. | | `--group` | Restrict the token to a specific group inside `--org`. Requires `--org` and at least one scope. | | `--scope` | Permission to grant the token. Repeatable. | | `--read-only` | Shorthand for `--scope read`. | | `--full-access` | Shorthand for granting every group scope. Does not include the organization scopes. | ## Organization-scoped tokens Pass `--org` to restrict a token to a single organization: ```bash theme={null} turso auth api-tokens mint --org ``` An org-scoped token can only manage resources (groups, databases, members) within that organization. Requests to any other organization will be rejected. Without scopes, the token can do everything that you can do in the organization. Add `--scope` to limit it. The scopes then apply to every group in the organization: ```bash theme={null} # Read the audit logs, members, and billing data of the organization turso auth api-tokens mint audit-reader --org my-org --scope org:read # Create databases and mint SQL tokens in every group turso auth api-tokens mint deploy-bot --org my-org --scope db:create --scope db:mint-token ``` An org-scoped token accepts all the [group scopes](#available-scopes), and also the organization scopes that follow. A token with scopes cannot manage members or invites, change organization settings, transfer groups or databases, or mint API tokens. ### Organization scopes These scopes are valid only on org-scoped tokens, that is, without `--group`: | Scope | Allows | | - | - | | `group:create` | Create groups in the organization. | | `group:delete` | Delete groups in the organization. | | `org:read` | Read organization data: members, invites, billing, invoices, audit logs, SSO, JWKS, and API tokens. | Reading invites and audit logs also requires that you are an admin or owner of the organization. `--full-access` does not include these scopes. To grant them, list each scope with `--scope`. ## Group-scoped tokens A group-scoped token is pinned to a single group inside an organization and is restricted to an explicit set of operations. It's the right shape for automations that should be able to provision and manage databases inside one group without being able to touch the rest of the organization. You must be an admin or owner of the organization to mint a group-scoped token. ```bash theme={null} # Read-only access to one group turso auth api-tokens mint deploy-bot --org my-org --group default --read-only # Full access inside one group, but nothing outside it turso auth api-tokens mint deploy-bot --org my-org --group default --full-access # Fine-grained: provision and configure databases, mint SQL tokens turso auth api-tokens mint deploy-bot --org my-org --group default \ --scope db:create --scope db:configure --scope db:mint-token ``` ### Available scopes `--scope` can be repeated and accepts any of the following: | Scope | Allows | | - | - | | `read` | All GET-style routes: list/retrieve databases and groups, configuration, usage, instances, locations. | | `db:create` | Create databases inside the group, seed from a dump, restore. | | `db:delete` | Delete a database inside the group. | | `db:configure` | Patch database configuration, wake, add or remove instances. | | `db:mint-token` | Issue a SQL-engine token or TLS client certificate for a database inside the group. | | `db:rotate-creds` | Rotate the database signing key, invalidating every SQL token previously issued for it. | | `group:configure` | Configure, rename, update, unarchive the group, or add and remove locations. | | `group:mint-token` | Issue a group-level SQL-engine token. | | `group:rotate-creds` | Rotate the group signing key, invalidating every SQL token issued against any database in the group. | > [!WARNING] > > `db:mint-token` and `db:rotate-creds` are deliberately separate scopes. Minting a new SQL credential is additive; rotating invalidates every credential currently in use, which can take down running applications. Grant rotation only to automations that need it. `--scope`, `--read-only`, and `--full-access` are mutually exclusive — pass one flavor at a time. Unknown scope labels are rejected before the request is sent. Group create, group delete, group transfer, and AWS migration are never reachable from a group-scoped token at any scope. ### Lifecycle Group-scoped tokens are pinned by the group's UUID, not its name. Deleting a group, or transferring it to another organization, cascades a revoke to every token scoped to it. A new group created with the same name does not inherit tokens from the previous one. > [!WARNING] > > The token value is only shown once at creation time. Store it somewhere safe. # auth api-tokens revoke Source: https://docs.turso.tech/cli/auth/api-tokens/revoke To revoke an API token for the current organization, run the following command: ```bash theme={null} turso auth api-tokens revoke ``` # auth login Source: https://docs.turso.tech/cli/auth/login To authenticate the Turso CLI with your account, run the following command: ```bash theme={null} turso auth login ``` > [!NOTE] > > If you're using Windows with WSL or a remote CI/CD environment, pass the --headless flag. ## Flags | Flag | Description | | - | - | | `--headless` | Useful when the CLI can't interact with the system's web browser. | # auth logout Source: https://docs.turso.tech/cli/auth/logout To logout of the Turso CLI, run the following command: ```bash theme={null} turso auth logout ``` ## Flags | Flag | Description | | - | - | | `-a, --all` | Invalidate all sessions for the current user. | # auth signup Source: https://docs.turso.tech/cli/auth/signup To sign up for a new Turso account using the CLI, run the following command: ```bash theme={null} turso auth signup ``` > [!NOTE] > > If you're using Windows with WSL or a remote CI/CD environment, pass the --headless flag. ## Flags | Flag | Description | | - | - | | `--headless` | Useful when the CLI can't interact with the system's web browser. | # auth token Source: https://docs.turso.tech/cli/auth/token You can obtain your current API token that is used to authenticate with the Turso Platform API by running the following command: ```bash theme={null} turso auth token ``` # auth whoami Source: https://docs.turso.tech/cli/auth/whoami You can obtain the username for the currently logged in user by running the following command: ```bash theme={null} turso auth whoami ``` # Authentication Source: https://docs.turso.tech/cli/authentication ## `login` The Turso CLI uses an access token to authenticate with Turso. You can login using the command: ```bash theme={null} turso auth login ``` This will open a browser window, asking you to log into your account with GitHub. > [!NOTE] > > If you're using Windows with WSL or a remote CI/CD environment, pass the `--headless` flag. Once you're logged in, you can manage your account and databases. You can explore all of the commands via the docs, or by using the `--help` flag for each subcommand: ```bash theme={null} turso ``` > [!NOTE] > > The Turso CLI requires you to reauthenticate after one week. Don't share your token. ## `signup` If you're new to Turso, you can create an account using the Turso CLI: ```bash theme={null} turso auth signup ``` ## `logout` You can logout of the Turso CLI using the `logout` command: ```bash theme={null} turso auth logout ``` # contact bookmeeting Source: https://docs.turso.tech/cli/contact/bookmeeting You can book a meeting with the Turso team to discuss your project and requirements using the following command: ```bash theme={null} turso contact bookmeeting ``` > [!NOTE] > > You will be redirected to a webpage where you can select a date and time for the meeting. # contact feedback Source: https://docs.turso.tech/cli/contact/feedback You can send feedback to the Turso team by using the following command: ```bash theme={null} turso contact feedback ``` # db config allow-rules clear Source: https://docs.turso.tech/cli/db/config/allow-rules/clear Clear the access allow rules for a database, allowing connections from any source. ```bash theme={null} turso db config allow-rules clear [flags] ``` Without any flags, both the IP list and the AWS VPC endpoint list are cleared. Use `--ips` or `--aws-vpcs` to clear only one list. ## Flags | Flag | Description | | - | - | | `--ips` | Clear only the list of allowed IPs. | | `--aws-vpcs` | Clear only the list of allowed AWS VPC endpoint IDs. | ## Examples ### Clear all rules ```bash theme={null} turso db config allow-rules clear my-db ``` ### Clear only the IP list ```bash theme={null} turso db config allow-rules clear my-db --ips ``` ### Clear only the VPC list ```bash theme={null} turso db config allow-rules clear my-db --aws-vpcs ``` # db config allow-rules set Source: https://docs.turso.tech/cli/db/config/allow-rules/set Set the access allow rules for a database. Each flag replaces the corresponding list; a list whose flag is not provided is left unchanged. ```bash theme={null} turso db config allow-rules set [--ip ]... [--aws-vpc ]... ``` At least one of `--ip` or `--aws-vpc` must be provided. To remove restrictions use [`turso db config allow-rules clear`](/cli/db/config/allow-rules/clear). ## Flags | Flag | Description | | - | - | | `--ip` | IP address or CIDR block to allow. Repeatable. Replaces the current IP list. | | `--aws-vpc` | AWS VPC endpoint ID (`vpce-...`) to allow. Repeatable. Replaces the current VPC list. | ## Examples ### Allow a single IP ```bash theme={null} turso db config allow-rules set my-db --ip 203.0.113.7 ``` ### Allow a CIDR range ```bash theme={null} turso db config allow-rules set my-db --ip 10.0.0.0/8 ``` ### Allow multiple IPs ```bash theme={null} turso db config allow-rules set my-db --ip 203.0.113.7 --ip 10.0.0.0/8 ``` ### Restrict to an AWS VPC endpoint ```bash theme={null} turso db config allow-rules set my-db --aws-vpc vpce-0fe6c8807461bba49 ``` ### Combine IP and VPC rules When both lists are set, connections must satisfy both constraints. ```bash theme={null} turso db config allow-rules set my-db \ --ip 10.0.0.0/8 \ --aws-vpc vpce-0fe6c8807461bba49 ``` # db config allow-rules show Source: https://docs.turso.tech/cli/db/config/allow-rules/show Print the access allow rules for a database. ```bash theme={null} turso db config allow-rules show ``` When no rules are configured, all connections are accepted and the command reports: ``` Access allow rules are empty: connections from any source are accepted ``` When rules are set, each list is printed: ``` Allowed IPs: 203.0.113.7 10.0.0.0/8 Allowed AWS VPC endpoint IDs: vpce-0fe6c8807461bba49 ``` # db create Source: https://docs.turso.tech/cli/db/create You can create a new database in a specific group using the following command: ```bash theme={null} turso db create [database-name] ``` > [!NOTE] > > The `[database-name]` argument is optional. If you don't provide a name, the Turso team will use the name of your project. > [!NOTE] > > If you have more than one group, you will need to pass the `--group` flag. ## Flags | Flag | Description | | - | - | | `--enable-extensions` | Enables experimental support for SQLite extensions. | | `--from-csv` | Create the database from a csv file. | | `--csv-table-name` | Name of the table in the csv file. | | `--from-db` | Select another database to copy data from. | | `--timestamp` | Set a point in time in the past to copy data from the selected database. Must be used with the 'from-db' flag. Must be in [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) format like '2023-09-29T10:16:13-03:00' | | `--from-dump` | Create the database from a local SQLite dump. | | `--from-dump-url` | Create the database from a remote SQLite dump. | | `--from-file` | Create the database from a local SQLite3-compatible file. | | `--group` | Create the database in the specified group. | | `--size-limit` | The maximum size of the database in bytes. Values with units are also accepted, e.g. 1mb, 256mb, 1gb. | | `--canary` | Use database canary build. | | `-w`, `--wait` | Wait for the database to be ready to receive requests. | | `-h`, `--help` | Get help for create. | ## Examples The examples below outline the most common use cases for the `db create` command. ### Create database in a specific group You can create a new database in a specific group using the `--group` flag: ```bash theme={null} turso db create [database-name] --group ``` ### Create database from SQLite file You can create a new Turso database from an existing SQLite file using the `--from-file` flag: ```bash theme={null} turso db create [database-name] --from-file ./path/to/file.db ``` > [!NOTE] > > The file size is limited to 2GB. ### Create database from SQLite dump You can create a new database using the output of `.dump` using the `--from-dump` flag: ```bash theme={null} turso db create [database-name] --from-dump ./path/to/dump.sql ``` ### Create database from another existing database You can create a new Turso database from an existing database using the `--from-db` flag: ```bash theme={null} turso db create [database-name] --from-db ``` ### Create database from a point in time You can create a new Turso database from an existing database at a specific point in time using the `--from-db` and `--timestamp` flags: ```bash theme={null} turso db create [database-name] --from-db --timestamp 2024-01-01T10:10:10-10:00 ``` ### Create database from a CSV file You can create a new Turso database from a CSV file using the `--from-csv` flag: ```bash theme={null} turso db create [database-name] --from-csv ./path/to/file.csv --csv-table-name ``` ### Create a database with extensions enabled You can create a database with experimental support for SQLite [extensions](/libsql#extensions) using the `--enable-extensions` flag: ```bash theme={null} turso db create [database-name] --enable-extensions ``` > [!NOTE] > > Using `--enable-extensions` will enable extensions on all databases in the group. # db destroy Source: https://docs.turso.tech/cli/db/destroy You can destroy a database by using the following command: ```bash theme={null} turso db destroy [flags] ``` > [!NOTE] > > On paid plans, a destroyed database can be restored for up to five days — see [Recover Deleted Databases](/features/recover-deleted-databases). ## Flags | Flag | Description | | - | - | | `-y`, `--yes` | Confirms the destruction the database. | # db export Source: https://docs.turso.tech/cli/db/export Export a database snapshot from Turso to a SQLite file. This command exports a snapshot of the current generation of a Turso database to a local SQLite file. Note that the exported file may not contain the latest changes. Use SDK to sync the database after exporting to ensure you have the most recent version. ```bash theme={null} turso db export [flags] ``` ## Flags | Flag | Description | | - | - | | `--output-file` | Specify the output file name (default: `.db`) | | `--overwrite` | Overwrite output file if it exists | | `--with-metadata` | Include metadata in the export | | `-h`, `--help` | Help for export | # db import Source: https://docs.turso.tech/cli/db/import You can import an existing SQLite file to Turso Cloud using the following command: ```bash theme={null} turso db import ~/path/to/database.db ``` > [!NOTE] > > Make sure the SQLite file has WAL journal mode enabled. > [!NOTE] > > If you have more than one group, you will need to pass the `--group` flag. ## Flags | Flag | Description | | - | - | | `--group` | Import the database in the specified group. | | `-h`, `--help` | Get help for import. | # db inspect Source: https://docs.turso.tech/cli/db/inspect You can inspect the usage of a database, including the total space used, rows read and written with the following command: ```bash theme={null} turso db inspect ``` ## Flags | Flag | Description | | - | - | | `--queries` | Show database query statistics. | | `--verbose` | Show detailed information, including the location and instance type. | ## Examples ### Top Queries You can fetch the most executed, resource-intensive SQL statements that have been run against your database using the Turso CLI: ```bash Command theme={null} turso db inspect --queries ``` ```bash Response theme={null} SELECT * FROM Orders WHERE Status = 'Pending'; SELECT COUNT(*), CustomerID FROM Orders GROUP BY CustomerID HAVING COUNT(*) > 5; SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID; UPDATE Products SET Price = Price * 1.10 WHERE Category = 'Electronics'; ``` # db list Source: https://docs.turso.tech/cli/db/list You can obtain a list of all databases for the current user or organization by running the following command: ```bash theme={null} turso db list ``` ## Flags | Flag | Description | | - | - | | `--group ` | Filter databases by group. | # db locations Source: https://docs.turso.tech/cli/db/locations You can fetch a list of supported locations where databases can be located by running the following command: ```bash theme={null} turso db locations ``` ## Flags | Flag | Description | | - | - | | `-l`, `--show-latencies` | Display latencies from your current location to each of Turso's locations | # db shell Source: https://docs.turso.tech/cli/db/shell You can connect directly to a Turso database by using the following command: ```bash theme={null} turso db shell [sql] [flags] ``` ## Flags | Flag | Description | | - | - | | `--instance ` | Connect to the specified database instance. | | `--location ` | Connect to the database at the specified location. | | `--proxy ` | The proxy to use when connecting to the database. | ## Examples ### Execute SQL You can execute SQL directly against a database using the shell: ```bash theme={null} turso db shell "SELECT * FROM users" ``` ### Database dump You can dump the contents of a Turso database using the following command: ```bash theme={null} turso db shell .dump > dump.sql ``` > [!NOTE] > > The `.dump` can be used to rebuild a database and doesn't contain any libSQL or SQLite internal tables. ### Load from dump You can load a dump file into a new database using the following command: ```bash theme={null} turso db shell < dump.sql ``` ### Shell with libSQL server If you're using `turso dev` locally, you can use the shell by providing the URL to your database: ```bash theme={null} turso db shell http://127.0.0.1:8080 ``` # db show Source: https://docs.turso.tech/cli/db/show You can obtain details about the database, including the name, ID, libSQL server version, group, size and location. ```bash theme={null} turso db show [flags] ``` You can also obtain the different URLs for the database using the following: ## Flags | Flag | Description | | - | - | | `--url` | Show URL for the database HTTP API. | | `--http-url` | Show HTTP URL for the database HTTP API. | # db tokens create Source: https://docs.turso.tech/cli/db/tokens/create You can create a new token that can be used to connect to one database using the command: ```bash theme={null} turso db tokens create [flags] ``` ## Flags | Flag | Description | | - | - | | `-e`, `--expiration` | The expiration time for a token, can be `never` or a value in days, e.g. `7d` . | | `-r`, `--read-only` | Restrict the token to read only access. | ## Examples The examples below outline the most common use cases for the `db tokens create` command. ### Create a token with read only access You can create a token with read only access to a database using the following command: ```bash theme={null} turso db tokens create --read-only ``` ### Create a token with a specific expiration time You can create a token with a specific expiration time using the following command: ```bash theme={null} turso db tokens create --expiration 7d3h2m1s ``` # db tokens invalidate Source: https://docs.turso.tech/cli/db/tokens/invalidate You can invalidate all tokens for a database by running the following command: ```bash theme={null} turso db tokens invalidate [flags] ``` ## Flags | Flag | Description | | - | - | | `-y`, `--yes` | Confirms the invalidation of all existing db tokens. | > [!WARNING] > > All tokens in the group that provided database belongs will also be invalidated. This means that all existing tokens will no longer be valid and will need to be regenerated. # dev Source: https://docs.turso.tech/cli/dev # group aws-migration abort Source: https://docs.turso.tech/cli/group/aws-migration/abort You can abort the AWS migration process by using the following command: ```bash theme={null} turso group aws-migration abort ``` # group aws-migration info Source: https://docs.turso.tech/cli/group/aws-migration/info You can check the AWS migration details by using the following command: ```bash theme={null} turso group aws-migration info ``` # group aws-migration start Source: https://docs.turso.tech/cli/group/aws-migration/start You can start the AWS migration process by using the following command: ```bash theme={null} turso group aws-migration start ``` # group create Source: https://docs.turso.tech/cli/group/create You can create a new group of databases. Groups belong to a primary region. ```bash theme={null} turso group create [flags] ``` > [!WARNING] > > Creating more than one group is limited to Scaler, Pro and Enterprise plans. > [!NOTE] > > Turso will automatically detect the closest region and use that as the primary region. You can override this by specifying the `--location` flag. ## Flags | Flag | Description | | - | - | | `--canary` | Use database canary build. | | `--location` | Create the group in the specified primary location using a three-digit code. | | `-w`, `--wait` | Wait for group to be ready before exiting. | # group destroy Source: https://docs.turso.tech/cli/group/destroy You can destroy a group and all of its databases using the following command: ```bash theme={null} turso group destroy [flags] ``` > [!NOTE] > > On paid plans, a destroyed group and its databases can be restored for up to five > days — see > [Recover Deleted Databases](/features/recover-deleted-databases). ## Flags | Flag | Description | | - | - | | `-y`, `--yes` | Confirms destroying the group, with all locations and databases. | # group list Source: https://docs.turso.tech/cli/group/list You can obtain a list of all groups by using the following command: ```bash theme={null} turso group list ``` # group tokens create Source: https://docs.turso.tech/cli/group/tokens/create You can create a new token that can be used to connect to any database in the group using the command: ```bash theme={null} turso group tokens create [flags] ``` ## Flags | Flag | Description | | - | - | | `-e`, `--expiration` | The expiration time for a token, can be `never` or a value in days, e.g. `7d` . | | `-r`, `--read-only` | Restrict the token to read only access. | ## Examples The examples below outline the most common use cases for the `group tokens create` command. ### Create a token with read only access You can create a token with read only access using the command: ```bash theme={null} turso group tokens create --read-only ``` ### Create a token with a specific expiration time You can create a token with a specific expiration time using the command: ```bash theme={null} turso group tokens create --expiration 7d ``` # group tokens invalidate Source: https://docs.turso.tech/cli/group/tokens/invalidate You can invalidate all tokens for a group by running the command: ```bash theme={null} turso group tokens invalidate [flags] ``` ## Flags | Flag | Description | | - | - | | `-y`, `--yes` | Confirms the invalidation of all existing group tokens. | # group transfer Source: https://docs.turso.tech/cli/group/transfer You can transfer a group (including its databases) to an organization you're an admin or owner of using the following command: ```bash theme={null} turso group transfer [flags] ``` > [!WARNING] > > Existing database URL and tokens will continue to work, but should update your application to use the new URL and token as soon as possible. ## Flags | Flag | Description | | - | - | | `-y`, `--yes` | Confirms the transfer to a new organization. | # group unarchive Source: https://docs.turso.tech/cli/group/unarchive You can unarchive inactive databases by running the following command: ```bash theme={null} turso group unarchive ``` > [!NOTE] > > Databases get archived after 10 days of inactivity for users on a free plan. # group update Source: https://docs.turso.tech/cli/group/update You can update the group, including all databases the following command: ```bash theme={null} turso group update [flags] ``` ## Flags | Flag | Description | | - | - | | `--extensions string` | Enable extensions by passing `all` or `none`. | | `--version string` | Specify the version of the group to update to. Values include `latest` or `canary`. | | `-y`, `--yes` | Skip confirmation prompt and confirm. | ## Examples ### Update a group to enable all extensions You can update a group and all its databases to enable `all` extensions: ```bash theme={null} turso group update --extensions all ``` # Headless Mode Source: https://docs.turso.tech/cli/headless-mode The Turso CLI will automatically attempt to open a browser, or wait for further instructions when interacting with various commands. You can opt out of this behaviour by passing the `--headless` flag with operations: ```bash theme={null} turso auth login --headless ``` > [!NOTE] > > If you're using Windows with WSL or a remote CI/CD environment, pass the `--headless` flag. # Help Source: https://docs.turso.tech/cli/help You can always find a summary of all commands by running the following command: ```bash theme={null} turso help ``` You can also find out more about each command, as well as any required or optional arguments by passing the `--help` flag to the command: ```bash theme={null} turso auth --help turso db create --help ``` If you require support integrating Turso into your project, [we can help](/support). # Installation Source: https://docs.turso.tech/cli/installation Learn how to install the Turso CLI on Mac, Linux and Windows. ## macOS To install the Turso CLI on macOS, we recommend that you use Homebrew: ```bash theme={null} brew install tursodatabase/tap/turso ``` If you don't use Homebrew, you can use the following command to execute a shell script that installs the CLI: ```bash theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` ## Linux Run the following command to execute a shell script that installs the CLI: ```bash theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` ## Windows Installing the Turso CLI on Windows requires that you have [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) installed. Once WSL is installed, run the following in PowerShell: ```bash theme={null} wsl ``` You can then execute the script to install the Turso CLI: ```bash theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` ## Verify installation You must open a new shell to verify the installation with the following command: ```bash theme={null} turso ``` # Introduction Source: https://docs.turso.tech/cli/introduction The Turso CLI gives you everything you need from the command line to manage your database, API tokens, inviting users, and launching the database shell. If you're waiting for a migration to run, there's also a relax command. You can also programmatically manage your Turso account, including groups, databases, organizations and invites using the [Platform API](/api-reference). **1. Install** Begin by installing the Turso CLI: ```bash macOS theme={null} brew install tursodatabase/tap/turso ``` ```bash Linux theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` ```bash Windows (WSL) theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` **2. Authenticate** Now signup or login: ```bash Signup theme={null} turso auth signup ``` ```bash Login theme={null} turso auth login ``` ```bash Signup (WSL) theme={null} turso auth signup --headless ``` ```bash Login (WSL) theme={null} turso auth login --headless ``` **3. Operate** The Turso CLI provides the following commands: | Command | Description | | - | - | | [`auth`](/cli/auth) | Authenticate and manage API tokens. | | [`contact`](/cli/contact) | Submit your feedback, ideas and create a meeting with the team. | | [`db`](/cli/db) | Create and manage databases, access tokens and connect to the shell. | | `dev` | Run Turso [locally](/local-development) for development. | | [`group`](/cli/group) | Create groups for databases with a shared location. | | [`org`](/cli/org) | Manage billing and invite members. | | [`plan`](/cli/plan) | `overages`, `select`, `show`, `upgrade` | | [`quickstart`](/quickstart) | Get started with Turso in 5 minutes. | | `relax` | Take some time out and relax with Turso. | | `update` | Update to the Turso CLI to the latest version with one command. | # org billing Source: https://docs.turso.tech/cli/org/billing To open the billing portal for your organization: ```bash theme={null} turso org billing ``` # org create Source: https://docs.turso.tech/cli/org/create To create a new organization that you own, run the following command: ```bash theme={null} turso org create ``` > [!NOTE] > > You will be asked to add a payment method and subscribe to the Scaler plan to successfully create a new organization. # destroy Source: https://docs.turso.tech/cli/org/destroy To delete an existing organization that you own, run the following command: ```bash theme={null} turso org destroy ``` > [!NOTE] > > You will need to transfer or delete any existing projects before you can delete an organization. Users cannot also destroy the a personal account or active organization. # org list Source: https://docs.turso.tech/cli/org/list To list organizations of which you are the owner or a member, run the following command: ```bash theme={null} turso org list ``` # org members add Source: https://docs.turso.tech/cli/org/members/add To add an existing Turso user as a member to the current active organization, run the following command: ```bash theme={null} turso org members add [flags] ``` ## Flags | Flag | Description | | - | - | | `-a`, `--admin` | Add the user as an admin. | # org members invite Source: https://docs.turso.tech/cli/org/members/invite To invite someone who isn't already a Turso user to the current active organization, run the following command: ```bash theme={null} turso org members invite [flags] ``` ## Flags | Flag | Description | | - | - | | `-a`, `--admin` | Add the user as an admin. | # org members list Source: https://docs.turso.tech/cli/org/members/list To list all members of the current active organization, run the following command: ```bash theme={null} turso org members list ``` > [!NOTE] > > This command is only available to active organizations, use [`org switch`](/cli/org/switch) if you're currently authenticated with your personal account. # org members rm Source: https://docs.turso.tech/cli/org/members/rm To remove a member from the current organization, use the following command: ```bash theme={null} turso org members rm ``` # org switch Source: https://docs.turso.tech/cli/org/switch To switch the current active organization for the CLI, use the following command: ```bash theme={null} turso org switch ``` # plan overages disable Source: https://docs.turso.tech/cli/plan/overages/disable You can disable overages for the current repository by running the following command: ```bash theme={null} turso plan overages disable ``` > [!NOTE] > > Any overages that have already been incurred will still be charged. # plan overages enable Source: https://docs.turso.tech/cli/plan/overages/enable You can enable overages for the current repository by running the following command: ```bash theme={null} turso plan overages enable ``` # plan select Source: https://docs.turso.tech/cli/plan/select You can switch your current plan at any time using the command: ```bash theme={null} turso plan select ``` > [!NOTE] > > You will need to add your credit card details to your account before you can upgrade to a paid plan. [Learn more](https://turso.tech/pricing) about pricing. # plan show Source: https://docs.turso.tech/cli/plan/show You can show the current plan usage and limits with the command: ```bash theme={null} turso plan show ``` # plan upgrade Source: https://docs.turso.tech/cli/plan/upgrade You can upgrade your account to a paid plan at any time using the command: ```bash theme={null} turso plan upgrade ``` > [!NOTE] > > You will need to add your credit card details to your account before you can upgrade to a paid plan. # Upgrading Source: https://docs.turso.tech/cli/upgrading Update the Turso CLI to the latest version to benefit from all the latest features, and fixes. ## macOS ```bash theme={null} brew update brew upgrade ``` ## Linux ```bash theme={null} turso update ``` ## Windows ```bash theme={null} turso update ``` # Database Access Allow Rules Source: https://docs.turso.tech/cloud/allow-rules Restrict database access to specific IP addresses, CIDR ranges, or AWS VPC endpoints. Allow rules let you lock down a database so that only connections from specific sources are accepted. You can restrict by IP address or CIDR range, by AWS VPC endpoint ID, or both at the same time. > [!NOTE] > > **AND semantics.** When both lists are configured, a connection must satisfy **both** rules: the client IP must be on the allowed-IP list **and** the connection must arrive through one of the allowed VPC endpoints. ## Show Current Rules ```bash theme={null} turso db config allow-rules show ``` If no rules are configured, all connections are accepted: ``` Access allow rules are empty: connections from any source are accepted ``` When rules are set, the command prints each list: ``` Allowed IPs: 203.0.113.7 10.0.0.0/8 Allowed AWS VPC endpoint IDs: vpce-0fe6c8807461bba49 ``` ## Restrict by IP Address or CIDR Use `--ip` (repeatable) to set the list of allowed IP addresses and CIDR blocks. The flag **replaces** the current list each time it is used. ```bash theme={null} # Allow a single IP turso db config allow-rules set my-db --ip 203.0.113.7 # Allow a CIDR range turso db config allow-rules set my-db --ip 10.0.0.0/8 # Allow multiple entries at once turso db config allow-rules set my-db --ip 203.0.113.7 --ip 10.0.0.0/8 ``` Both IPv4 and IPv6 addresses are accepted. CIDR notation (e.g. `10.0.0.0/8`) is supported for ranges. ## Restrict by AWS VPC Endpoint Use `--aws-vpc` (repeatable) to set the list of allowed [AWS VPC endpoint IDs](/cloud/private-endpoints). IDs must start with `vpce-`. ```bash theme={null} turso db config allow-rules set my-db --aws-vpc vpce-0fe6c8807461bba49 ``` ## Combine IP and VPC Rules You can set both lists in a single command. Connections must satisfy both constraints. ```bash theme={null} turso db config allow-rules set my-db \ --ip 10.0.0.0/8 \ --aws-vpc vpce-0fe6c8807461bba49 ``` A later call that only specifies `--ip` leaves the VPC list unchanged, and vice versa: ```bash theme={null} # Add a new IP without touching the VPC list turso db config allow-rules set my-db --ip 198.51.100.5 ``` ## Clear Rules Remove all allow rules to allow connections from any source: ```bash theme={null} turso db config allow-rules clear my-db ``` Clear only one list while leaving the other intact: ```bash theme={null} # Clear only the IP list turso db config allow-rules clear my-db --ips # Clear only the VPC list turso db config allow-rules clear my-db --aws-vpcs ``` ## API Allow rules are managed through the database [configuration endpoint](/api-reference/databases/update-configuration). Set `allowed_ips` and/or `allowed_aws_vpc_ids` in the request body: ```bash theme={null} curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "allowed_ips": ["203.0.113.7", "10.0.0.0/8"], "allowed_aws_vpc_ids": ["vpce-0fe6c8807461bba49"] }' ``` Pass an explicit empty array to clear a list: ```bash theme={null} curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/configuration' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "allowed_ips": [], "allowed_aws_vpc_ids": [] }' ``` Omitting a field from the request body leaves that list unchanged. # Durability Guarantees Source: https://docs.turso.tech/cloud/durability The following are the durability guarantees provided by the Turso Cloud platform for users who registered or upgraded **after March 17, 2025**: | | Durability | Added commit latency | | - | - | - | | **Free** | 99.999999999% | up to 100ms | | **Developer** | 99.999999999% | up to 50ms | | **Scaler** | 99.999999999% | up to 25ms | | **Pro and above** | 99.999999999% | up to 10ms | For all Turso AWS regions, we employ a diskless architecture where data is backed by a combination of S3-Express One Zone and S3. Commits are only acknowledged once data is safely stored in either S3 or S3-express. Compute nodes can come and go at any time, and local disks act as a local cache. Therefore, we can provide the same durability guarantees as AWS’s best-in-class S3 storage. > [!NOTE] > > Pro and Enterprise customers can use their own S3 / S3-express buckets, > guaranteeing that data lives inside their infrastructure. In our future > roadmap, compute will also be runnable inside your account, for a full BYOC > solution ## Short term and long term guarantees Any SQLite-compatible database has mainly two storage components: The Write-Ahead Log, or WAL, which contains the most recent commits, and the database file. Periodically, the data in the WAL is folded back into the main database file. New commits to the database that are sent to the Turso Cloud are stored in S3-express One Zone before they are acknowledged to the application. Periodically, the data is folded back into the main database file and stored on S3. As per AWS service agreement, both technologies provide a 99.999999999% durability guarantee. ## Commit latency One of the reasons why an S3-based architecture is not as popular for transactional workloads as it is for analytical workloads is the high cost of writing to object store. Due to Turso Cloud’s multi-tenant architecture, commits are batched across many databases to amortize this cost. Furthermore, commits are also batched in time, that is the added commit latency as described in Table 1. The commit latency is not the expected latency in every commit, but the ceiling of added latency in each commit. To understand how this works, let's consider the following scenario: ```mermaid theme={null} sequenceDiagram participant Timeline participant DB1 as Database 1 participant DB2 as Database 2 Note over Timeline: 0ms - Commit Window Starts Timeline->>DB1: Commit c1 arrives Note over DB1: Waits 10ms for batch Note over Timeline: 4ms Timeline->>DB2: Commit c1 arrives Note over DB2: Waits 6ms for batch Note over Timeline: 10ms - Commit Window Ends Timeline->>DB1: Commit c2 arrives Note over DB1: No wait (batch is ready) Timeline-->>DB1: Acknowledge c1 and c2 Timeline-->>DB2: Acknowledge c1 ``` Let's break it down: * The first commit (c1) to database d1 that happens at the beginning of the 10ms commit window must wait the full 10ms * The commit (c1) to database d2 that happens 4ms into the window only waits the remaining 6ms * The second commit (c2) to database d1 that arrives exactly at the end of the commit window has no added latency ## Testing methodology Turso Cloud server is written from the ground up to use Deterministic Simulation Testing. DST, for short, is a technique that automatically tests the code across a variety of possible, randomly generated inputs, and upon any failure generates a seed that deterministically reproduces the issue with all its preconditions. In addition to our simulator, we rely on [Antithesis](https://antithesis.com) full-system Deterministic Simulation Hypervisor to test the interaction of the system with external components, like AWS S3 and S3-Express One Zone. The combination of our ground-up DST server and Antithesis allows us to test years of possible combinations of unlikely scenarios over the course of mere hours, making sure the system works as specified. # BYOK Encryption Source: https://docs.turso.tech/cloud/encryption All databases are encrypted-at-rest at the volume level in the Turso Cloud, as part of our SOC2 standards. See [our trust center](https://trust.turso.tech) for details. For Pro, and Enterprise customers, there is the option to encrypt the databases with a key that you provide (Bring-Your-Own-Key Encryption, or BYOK), as described below. BYOK Encryption happens at the server level, making sure that nobody has access to your data at any time. In fact, because it is possible to encrypt different databases with different keys, it is possible for you to use different keys for different customers' databases. If the keys themselves are provided by your customers, BYOK Encryption offers your customers the guarantee that not even you can have access to their data. Turso Cloud BYOK encryption for data at rest uses industry-standard AEAD algorithms. Each database page is encrypted individually, ensuring data integrity and security across all environments. BYOK encryption is available on Pro, and [Enterprise](https://tur.so/turso-enterprise-chat) plans. > [!NOTE] > > **You hold the keys.** Each database can be encrypted with its own unique key that only you control. Turso never sees or stores your encryption keys. Encryption works seamlessly with both remote queries and sync. * **Page-level encryption**: Each 4 KiB page is encrypted individually with a unique nonce * **AEAD algorithms**: Support for AEGIS-256, AES-GCM, and ChaCha20Poly1305 * **Data integrity**: Built-in authentication tags prevent tampering and corruption * **In-memory keys**: Encryption keys are never stored on disk Turso Cloud with BYOK encryption is perfect for: > **AI Agents** > > Manage a fleet of trusted agents that deal with personal private data > **Fintech Applications** > > Meet regulatory requirements for sensitive financial data > **Privacy-Focused Products** > > Build user trust with strong data protection > **Healthcare Apps** > > Protect patient data and meet HIPAA compliance > **AI/ML Applications** > > Secure training data and model outputs ## What's Encrypted > **Encrypted** > > * The database file and Write-Ahead Log (WAL) file on disk and on S3. ## Generate an Encryption Key Generate a secure encryption key in base64 format. The key size depends on the cipher you choose: **256-bit (32 bytes)** For AEGIS-256 variants and AES-256-GCM: ```bash theme={null} openssl rand -base64 32 ``` Output example: ``` YUfkdsD2SJe4KzTB8n6CjwCgs1YIW6k1xxMEguRuI5s= ``` **128-bit (16 bytes)** For AEGIS-128 variants and AES-128-GCM: ```bash theme={null} openssl rand -base64 16 ``` Output example: ``` XG1iWMFBFYEYrmYxjWDf5w== ``` > [!WARNING] > > **Store your key securely!** If you lose the encryption key, you will not be > able to access your encrypted database. Keys are never stored anywhere in the Turso Cloud. ## Create an Encrypted Database **1. Launch with encryption** Use the `--remote-encryption-key`, `--remote-encryption-cipher` flags and specify your cipher and key in database creation command: ```bash theme={null} turso db create my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" --remote-encryption-cipher aegis128l ``` Replace `YOUR_SECRET_KEY` with the key you generated above. You can also set the `TURSO_DB_REMOTE_ENCRYPTION_KEY` environment variable instead of passing the flag each time. **2. Create and insert data** Use `--remote-encryption-key` flag to start the shell, create a table and insert some data: ```bash theme={null} turso db shell my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" ``` ```sql theme={null} CREATE TABLE secrets (id INT, data TEXT); INSERT INTO secrets VALUES (1, 'sensitive information'); INSERT INTO secrets VALUES (2, 'confidential data'); ``` **3. Verify encryption** Exit the shell (type `.quit`) and try to open the database without the key: ```bash theme={null} turso db shell my-secret-db ``` You won't be able to access the data. The database is encrypted in the cloud. ## Upload Existing Database To upload an existing encrypted database, provide the cipher and key flags during creation: ```bash theme={null} turso db create my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" --remote-encryption-cipher aegis128l --from-file my-db.db ``` ## Branching and Point-in-Time Recovery (PITR) Encrypted databases support [branching](/features/branching), and [point-in-time recovery (PITR)](/features/point-in-time-recovery). The new branch will use the same cipher algorithm and the same encryption key. During creation you need to specify the same encryption key that was used for the parent database: ```bash theme={null} turso db create my-secret-db-fork --remote-encryption-key "YOUR_SECRET_KEY" --from-db my-secret-db ``` ## Rekeying Rekeying is not supported yet. However, you can export the database and create a new one with a different cipher and key: ```bash theme={null} turso db export my-secret-db --remote-encryption-key "YOUR_SECRET_KEY" turso db create my-new-secret-db --remote-encryption-key "YOUR_NEW_SECRET_KEY" --remote-encryption-cipher aegis256 --from-file my-secret-db.db ``` ## Supported Ciphers Turso Cloud supports multiple variants of three AEAD encryption algorithms, offering different trade-offs between performance and compatibility. ### AES-GCM NIST-approved standard for compliance requirements. Widely supported across industries. | Cipher | Key Size | Cipher Code | Use Case | | - | - | - | - | | **AES-128-GCM** | 128-bit (16 bytes) | `aes128gcm` | Compliance with 128-bit requirements | | **AES-256-GCM** | 256-bit (32 bytes) | `aes256gcm` | Maximum security for compliance | ### ChaCha20-Poly1305 | Cipher | Key Size | Cipher Code | Use Case | | - | - | - | - | | **ChaCha20-Poly1305** | 256-bit (32 bytes) | `chacha20poly1305` | Alternative for AES | ### AEGIS Modern, high-performance cipher family optimized for speed. **Recommended for most use cases.** | Cipher | Key Size | Cipher Code | Use Case | | - | - | - | - | | **AEGIS-128L** | 128-bit (16 bytes) | `aegis128l` | Balanced performance, 128-bit security | | **AEGIS-128X2** | 128-bit (16 bytes) | `aegis128x2` | 2x parallel processing | | **AEGIS-128X4** | 128-bit (16 bytes) | `aegis128x4` | 4x parallel processing, maximum speed | | **AEGIS-256** | 256-bit (32 bytes) | `aegis256` | Balanced 256-bit security | | **AEGIS-256X2** | 256-bit (32 bytes) | `aegis256x2` | 2x parallel processing, 256-bit | | **AEGIS-256X4** | 256-bit (32 bytes) | `aegis256x4` | 4x parallel, maximum speed & security | > [!NOTE] > > **Choosing a cipher:** > > * Use `aegis128l` for 128 bit encryption, `aegis256` for 256 bit encryption (default recommendation) > * For AES, use `aes128gcm` for 128 bit encryption, `aes256gcm` or `chacha20poly1305` for 256 bit encryption ## Future Work Future encryption features in development: * **Encrypt existing databases**: Migrate unencrypted databases to encrypted format * **Key rotation**: Update encryption keys without data loss # Limitations Source: https://docs.turso.tech/cloud/limitations Known limitations and differences when using SQLite on Turso Cloud. # Limitations Turso Cloud is compatible with most SQLite features, but there are some known limitations to be aware of — especially if you're migrating from vanilla SQLite. ## Pragma statements Some SQLite pragma statements behave differently on Turso Cloud or are not supported. | Pragma | Notes | | - | - | | `user_version` | Read-only on Turso Cloud. Use a `_schema_version` table instead to track schema migrations. | | `application_id` | Read-only on Turso Cloud. Use a `_metadata` table instead to store application identifiers. | | `busy_timeout` | Not supported. Turso Cloud manages concurrency internally, so setting a busy timeout has no effect. | | `journal_mode` | Not supported. Turso Cloud manages journaling internally and does not allow changing the journal mode. | > [!WARNING] > > If you're using `PRAGMA user_version` to track schema migrations (common in vanilla SQLite), you'll need to switch to a table-based approach on Turso Cloud: > > ```sql theme={null} > CREATE TABLE IF NOT EXISTS _schema_version (version INTEGER NOT NULL); > INSERT INTO _schema_version (version) VALUES (0); > ``` # Migrate to Turso Source: https://docs.turso.tech/cloud/migrate-to-turso Learn how to import your existing SQLite database to Turso. This guide will walk you through the process of migrating your existing SQLite database to Turso Cloud. You can choose between using the Turso CLI, or the Platform API. ![Migrate to Turso](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/cloud/import.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=b9672b095d28bc1d3efdc25d337a9a73) ## Preparing to Migrate Before importing your SQLite database to Turso, you need to ensure it's compatible with Turso's requirements. Specifically, your SQLite database should be using WAL (Write-Ahead Logging) mode. **1. Open your SQLite database** Use the SQLite command-line tool or any SQLite client to open your database: ```bash theme={null} sqlite3 path/to/your/database.db ``` **2. Set WAL journal mode** Run the following command to switch your database to WAL mode: ```sql theme={null} PRAGMA journal_mode='wal'; ``` This should return `wal` to confirm the change was successful. **3. Checkpoint and truncate the WAL file** Execute a checkpoint to ensure all changes are written to the main database file and truncate the WAL file: ```sql theme={null} PRAGMA wal_checkpoint(truncate); ``` **4. Verify the journal mode** Confirm that your database is now in WAL mode: ```sql theme={null} PRAGMA journal_mode; ``` This should return `wal`. **5. Close the database** Exit the SQLite shell: ```sql theme={null} .exit ``` Your database is now ready for migration to Turso. ## Using the CLI You can create a new database from a local SQLite file using the Turso CLI: **1. Install the Turso CLI** Make sure you have the [Turso CLI installed](/cli/introduction), and you're authenticated. **2. Import your SQLite Database** Import your existing SQLite database file using the `db import` command: ```bash theme={null} turso db import ~/path/to/my-database.db ``` > [!NOTE] > > Your database will be named after the file (without the .db extension), and > will import all your tables, data, and schema. **3. Connect to your database** You can now connect to your database using the shell: ```bash theme={null} turso db shell ``` ## Using the Platform API You can also use the Platform API to migrate your existing SQLite database: **1. Signup or Login using the Turso CLI** Make sure to [install the Turso CLI](/cli/installation) if you haven't already. ```bash Signup theme={null} turso auth signup ``` ```bash Login theme={null} turso auth login ``` **2. Create a new Platform API Token** Now create a new API Token using the Turso CLI: ```bash theme={null} turso auth api-tokens mint quickstart ``` > [!NOTE] > > Make sure to save the token somewhere safe. You'll need it to create a database and database token. **3. Retrieve your account or organization slug** The Platform API can be used with your personal account or with an organization. You'll need the obtain the `slug` of your account or organization using using the Turso CLI: ```bash theme={null} turso org list ``` **4. Create a Database for Import** First, create a database that's ready to receive an import: ```bash theme={null} curl -X POST "https://api.turso.tech/v1/organizations/{organizationSlug}/databases" \ -L \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "new-database", "group": "default", "seed": { "type": "database_upload" } }' ``` > [!NOTE] > > The `seed` parameter with `"type": "database_upload"` indicates that you plan to upload a database file. If you don't proceed to upload a database, this database will count towards your quota, but will not be usable. **5. Create a Database Token** Generate an authentication token for your database: ```bash theme={null} curl -X POST "https://api.turso.tech/v1/organizations/{organizationSlug}/databases/{databaseName}/auth/tokens" \ -L \ -H "Authorization: Bearer TOKEN" ``` This token will be used to authenticate your upload request, and future requests to the database. **6. Upload Your SQLite Database** Finally, upload your SQLite database file: ```bash theme={null} curl -X POST "https://{databaseName}-{organizationSlug}.turso.io/v1/upload" \ -H "Authorization: Bearer DATABASE_TOKEN" \ --data-binary @/path/to/your/database.db ``` > [!NOTE] > > The `Authorization` header uses the database token you generated in the > previous step, not your Platform API token. You're now ready to connect to your new Turso database using any of the Turso client libraries. # Private Endpoints Source: https://docs.turso.tech/cloud/private-endpoints Learn how to configure private endpoints for your Turso Database on AWS. Set up an AWS VPC endpoint to securely access Turso databases through AWS PrivateLink, keeping all traffic within AWS's private network. ## Prerequisites * Existing VPC in your target AWS region * Subnets in supported availability zones (see region-specific requirements below) * IAM permissions to create VPC endpoints and modify security groups * Active Turso account with configured databases ## Quickstart **us-east-1** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.us-east-1.vpce-svc-0d9cc6f6a688ce8a5` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`use1-az4` and/or `use1-az6`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-us-east-1.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-us-east-1.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-us-east-1.turso.io ``` **us-east-2** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.us-east-2.vpce-svc-08cc04ea0962d5036` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`use2-az1` and/or `use2-az2`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-us-east-2.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-us-east-2.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-us-east-2.turso.io ``` **us-west-2** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.us-west-2.vpce-svc-0372e98cdacb325b7` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`usw2-az1` and/or `usw2-az3`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-us-west-2.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-us-west-2.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-us-west-2.turso.io ``` **eu-west-1** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.eu-west-1.vpce-svc-09559a4fb467b07fd` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`euw1-az1` and/or `euw1-az3`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-eu-west-1.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-eu-west-1.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-eu-west-1.turso.io ``` **ap-south-1** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.ap-south-1.vpce-svc-0c1607d1026a49817` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`aps1-az1` and/or `aps1-az3`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-ap-south-1.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-ap-south-1.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-ap-south-1.turso.io ``` **ap-northeast-1** **1. Create VPC Endpoint** Navigate to **VPC Dashboard** → **Endpoints** → **Create endpoint** and configure: > **Basic Settings** > > * **Name**: Enter a descriptive name (e.g., `turso-database-endpoint`) > * **Service category**: Select "Other endpoint services" > * **Service name**: Enter `com.amazonaws.vpce.ap-northeast-1.vpce-svc-070f604683ada07db` > * Click **Verify service** > **Network Configuration** > > * Select your **VPC** > * Enable **DNS name** > * Choose **IPv4** for DNS record type > * Select subnets in supported AZs (`apne1-az4` and/or `apne1-az1`) > * Configure security groups and click **Create endpoint** **2. Configure Security Groups** Update security groups to allow proper communication: **VPC Endpoint Security Group:** * Inbound: Allow HTTPS (port 443) from your application security groups **Application Security Groups:** * Outbound: Allow HTTPS (port 443) to the VPC endpoint security group **3. Update Application Connections** Make sure you are using the regional Turso URL format: ```bash theme={null} curl https://-.aws-ap-northeast-1.turso.io ``` **4. Verify Setup** Test your VPC endpoint configuration from within your VPC: ```bash theme={null} # Test connectivity curl -v https://-.aws-ap-northeast-1.turso.io # Verify private routing (should show traffic staying within AWS network) traceroute -T -.aws-ap-northeast-1.turso.io ``` ## Important Notes * Traffic remains within AWS's private network * Standard AWS VPC endpoint pricing applies * Applications can access the endpoint from any AZ in your VPC * Each region has specific service names and supported availability zones # Connect to Turso using Dart Source: https://docs.turso.tech/connect/dart **1. Install** Add the turso\_dart package to your `pubspec.yaml`: ```yaml theme={null} dependencies: turso_dart: ``` Then run: ```bash theme={null} dart pub get ``` **2. Connect** Create a client connection. You can connect to an in-memory database or a local file: ```dart theme={null} import 'package:turso_dart/turso_dart.dart'; // In memory final client = TursoClient.memory(); // Or local file final client = TursoClient.local('/path/to/local.db'); await client.connect(); ``` **3. Create table** Create a table for customers: ```dart theme={null} await client.execute( "CREATE TABLE IF NOT EXISTS customers (id INTEGER PRIMARY KEY, name TEXT)" ); ``` **4. Insert data** Insert some data into the customers table: ```dart theme={null} await client.query("INSERT INTO customers(name) VALUES ('John Doe')"); await client.query("INSERT INTO customers(name) VALUES ('Jane Smith')"); ``` **5. Query data** Query all customers from the table: ```dart theme={null} final result = await client.query("SELECT * FROM customers"); print(result); ``` **6. Prepared statements** Use prepared statements for better performance and security: ```dart theme={null} final statement = await client.prepare("SELECT * FROM customers WHERE id = ?"); final result = await statement.query(positional: [1]); print(result); ``` # Connect to Turso using Go Source: https://docs.turso.tech/connect/go **1. Install** Add the Turso package to your Go project: ```bash theme={null} go get turso.tech/database/tursogo ``` **2. Connect** Here's how you can connect to a local SQLite database: ```go theme={null} package main import ( "database/sql" "fmt" _ "turso.tech/database/tursogo" ) func main() { db, err := sql.Open("turso", "sqlite.db") if err != nil { panic(err) } defer db.Close() } ``` **3. Create table** Create a table for users: ```go theme={null} _, err = db.Exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL ) `) if err != nil { panic(err) } ``` **4. Insert data** Insert some data into the users table: ```go theme={null} _, err = db.Exec("INSERT INTO users (username) VALUES (?)", "alice") if err != nil { panic(err) } _, err = db.Exec("INSERT INTO users (username) VALUES (?)", "bob") if err != nil { panic(err) } ``` **5. Query data** Query all users from the table: ```go theme={null} rows, err := db.Query("SELECT * FROM users") if err != nil { panic(err) } defer rows.Close() for rows.Next() { var id int var username string if err := rows.Scan(&id, &username); err != nil { panic(err) } fmt.Printf("User: ID: %d, Username: %s\n", id, username) } ``` # Connect to Turso using Java Source: https://docs.turso.tech/connect/java **1. Install** Currently, we have not published to the maven central. Instead, you can locally build the jar and deploy it to maven local to use it. Build jar and publish to maven local: ```bash theme={null} cd bindings/java # Please select the appropriate target platform, currently supports `macos_x86`, `macos_arm64`, `windows` and `linux_x86` make macos_x86 # deploy to maven local make publish_local ``` Now you can use the dependency as follows: ```gradle theme={null} dependencies { implementation("tech.turso:turso:0.0.1-SNAPSHOT") } ``` # Connect to Turso using JavaScript Source: https://docs.turso.tech/connect/javascript ## Quickstart **1. Install** Add the Turso database package to your JavaScript project: ```bash theme={null} npm i @tursodatabase/database ``` **2. Connect** Here's how you can connect to a local SQLite database: ```javascript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("sqlite.db"); ``` **3. Create table** Create a table for users: ```javascript theme={null} const createTable = db.prepare(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL ) `); createTable.run(); ``` **4. Insert data** Insert some data into the users table: ```javascript theme={null} const insertUser = db.prepare("INSERT INTO users (username) VALUES (?)"); insertUser.run("alice"); insertUser.run("bob"); ``` **5. Query data** Query all users from the table: ```javascript theme={null} const stmt = db.prepare("SELECT * FROM users"); const users = stmt.all(); console.log(users); ``` ## Examples Explore these JavaScript examples to learn more about using Turso Database: * [Node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-node) — Node.js, local file database (no sync) * [Wasm + Vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-wasm-vite) — Browser (WASM), local database in the browser # Connect to Turso using Python Source: https://docs.turso.tech/connect/python **1. Install** Add the Turso package to your Python project: ```bash theme={null} uv pip install pyturso ``` **2. Connect** Here's how you can connect to a local SQLite database: ```python theme={null} import turso con = turso.connect("sqlite.db") cur = con.cursor() ``` **3. Create table** Create a table for users: ```python theme={null} cur.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL ) """) con.commit() ``` **4. Insert data** Insert some data into the users table: ```python theme={null} cur.execute("INSERT INTO users (username) VALUES (?)", ("alice",)) cur.execute("INSERT INTO users (username) VALUES (?)", ("bob",)) con.commit() ``` **5. Query data** Query all users from the table: ```python theme={null} res = cur.execute("SELECT * FROM users") users = res.fetchall() print(users) ``` # Connect to Turso using Rust Source: https://docs.turso.tech/connect/rust **1. Install** Add the Turso crate to your Rust project: ```bash theme={null} cargo add turso ``` **2. Connect** Here's how you can connect to a local SQLite database: ```rust theme={null} use turso::Builder; #[tokio::main] async fn main() -> Result<(), Box> { let db = Builder::new_local("sqlite.db").build().await?; let conn = db.connect()?; Ok(()) } ``` **3. Create table** Create a table for users: ```rust theme={null} conn.execute( "CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL )", () ).await?; ``` **4. Insert data** Insert some data into the users table: ```rust theme={null} conn.execute("INSERT INTO users (username) VALUES (?)", ("alice",)).await?; conn.execute("INSERT INTO users (username) VALUES (?)", ("bob",)).await?; ``` **5. Query data** Query all users from the table: ```rust theme={null} let res = conn.query("SELECT * FROM users", ()).await?; println!("{:?}", res); ``` # AI & Embeddings Source: https://docs.turso.tech/features/ai-and-embeddings Vector Similarity Search is built into Turso and libSQL Server as a native feature. Turso and libSQL enable vector search capability without an extension. ## How it works * Create a table with one or more vector columns (e.g. `FLOAT32`) * Provide vector values in binary format or convert text representation to binary using the appropriate conversion function (e.g. `vector32(...)`) * Calculate vector similarity between vectors in the table or from the query itself using dedicated vector functions (e.g. `vector_distance_cos`) * Create a special vector index to speed up nearest neighbors queries (use the `libsql_vector_idx(column)` expression in the `CREATE INDEX` statement to create vector index) * Query the index with the special `vector_top_k(idx_name, q_vector, k)` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions) # Vectors ### Types LibSQL uses the native SQLite BLOB [storage class](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes) for vector columns. To align with SQLite [affinity rules](https://www.sqlite.org/datatype3.html#determination_of_column_affinity), all type names have two alternatives: one that is easy to type and another with a `_BLOB` suffix that is consistent with affinity rules. > [!NOTE] > > We suggest library authors use type names with the `_BLOB` suffix to make > results more generic and universal. For regular applications, developers can > choose either alternative, as the type name only serves as a **hint** for > SQLite and external extensions. > [!NOTE] > > As LibSQL does not introduce a new storage class, all metadata about vectors > is also encoded in the `BLOB` itself. This comes at the cost of a few bytes > per row but greatly simplifies the design of the feature. The table below lists six vector types currently supported by LibSQL. Types are listed from more precise and storage-heavy to more compact but less precise alternatives (the number of dimensions in vector $D$ is used to estimate storage requirements for a single vector). | Type name | Storage (bytes) | Description | | - | - | - | | `FLOAT64` \| `F64_BLOB` | $8D + 1$ | Implementation of [IEEE 754 double precision format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) for 64-bit floating point numbers | | `FLOAT32` \| `F32_BLOB` | $4D$ | Implementation of [IEEE 754 single precision format](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) for 32-bit floating point numbers | | `FLOAT16` \| `F16_BLOB` | $2D + 1$ | Implementation of [IEEE 754-2008 half precision format](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) for 16-bit floating point numbers | | `FLOATB16` \| `FB16_BLOB` | $2D + 1$ | Implementation of [bfloat16 format](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format) for 16-bit floating point numbers | | `FLOAT8` \| `F8_BLOB` | $D + 14$ | LibSQL specific implementation which compresses each vector component to single `u8` byte `b` and reconstruct value from it using simple transformation: $\texttt{shift} + \texttt{alpha} \cdot b$ | | `FLOAT1BIT` \| `F1BIT_BLOB` | $\lceil \frac{D}{8} \rceil + 3$ | LibSQL-specific implementation which compresses each vector component down to 1-bit and packs multiple components into a single machine word, achieving a very compact representation | > [!NOTE] > > For most applications, the `FLOAT32` type should be a good starting point, but > you may want to explore more compact options if your table has a large number > of rows with vectors. > [!NOTE] > > While `FLOAT16` and `FLOATB16` use the same amount of storage, they provide > different trade-offs between speed and accuracy. Generally, operations over > `bfloat16` are faster but come at the expense of lower precision. ### Functions To work with vectors, LibSQL provides several functions that operate in the vector domain. Each function understands vectors in binary format aligned with the six types described above or in text format as a single JSON array of numbers. Currently, LibSQL supports the following functions: | Function name | Description | | - | - | | `vector64` \| `vector32` \| `vector16` \| `vectorb16` \| `vector8` \| `vector1bit` | Conversion function which accepts a valid vector and converts it to the corresponding target type | | `vector` | Alias for `vector32` conversion function | | `vector_extract` | Extraction function which accepts valid vector and return its text representation | | `vector_distance_cos` | Cosine distance (1 - [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity)) function which operates over vector of **same type** with **same dimensionality** | | `vector_distance_l2` | Euclidean distance function which operates over vector of **same type** with **same dimensionality** | ### Vectors usage **1. Create a table** Begin by declaring a column used for storing vectors with the `F32_BLOB` datatype: ```sql theme={null} CREATE TABLE movies ( title TEXT, year INT, embedding F32_BLOB(4) -- 4-dimensional f32 vector ); ``` The number in parentheses `(4)` specifies the dimensionality of the vector. This means each vector in this column will have exactly 4 components. **2. Generate and insert embeddings** Once you generate embeddings for your data (via an LLM), you can insert them into your table: ```sql theme={null} INSERT INTO movies (title, year, embedding) VALUES ('Napoleon', 2023, vector32('[0.800, 0.579, 0.481, 0.229]')), ('Black Hawk Down', 2001, vector32('[0.406, 0.027, 0.378, 0.056]')), ('Gladiator', 2000, vector32('[0.698, 0.140, 0.073, 0.125]')), ('Blade Runner', 1982, vector32('[0.379, 0.637, 0.011, 0.647]')); ``` Popular tools like [LangChain](https://www.langchain.com), [Hugging Face](https://huggingface.co) or [OpenAI](https://turso.tech/blog/how-to-generate-and-store-openai-vector-embeddings-with-turso) can be used to generate embeddings. **3. Perform a vector similarity search** You can now write queries combining vectors and standard SQLite data: ```sql theme={null} SELECT title, vector_extract(embedding), vector_distance_cos(embedding, vector32('[0.064, 0.777, 0.661, 0.687]')) AS distance FROM movies ORDER BY distance ASC; ``` ### Understanding Distance Results The `vector_distance_cos` function calculates the cosine distance, which is defined as: * Cosine Distance = 1 — [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) The cosine distance ranges from 0 to 2, where: * A distance close to 0 indicates that the vectors are nearly identical or exactly matching. * A distance close to 1 indicates that the vectors are orthogonal (perpendicular). * A distance close to 2 indicates that the vectors are pointing in opposite directions. > [!NOTE] > > Very small negative numbers close to zero (for example, `-10^-14`) may > occasionally appear due to floating-point arithmetic precision. These should > be interpreted as effectively zero, indicating an exact or near-exact match > between vectors. > > ```sql theme={null} > SELECT vector_distance_cos('[1000]', '[1000]'); > -- Output: -2.0479999918166e-09 > ``` ### Vector Limitations * Euclidean distance is **not supported** for 1-bit `FLOAT1BIT` vectors * LibSQL can only operate on vectors with no more than 65536 dimensions ## Indexing Nearest neighbors (NN) queries are popular for various AI-powered applications ([RAG](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) uses NN queries to extract relevant information, and recommendation engines can suggest items based on embedding similarity). LibSQL implements [DiskANN](https://turso.tech/blog/approximate-nearest-neighbor-search-with-diskann-in-libsql) algorithm in order to speed up approximate nearest neighbors queries for tables with vector columns. > [!NOTE] > > The DiskANN algorithm trades search accuracy for speed, so LibSQL queries may > return slightly suboptimal neighbors for tables with a large number of rows. ### Vector Index LibSQL introduces a custom index type that helps speed up nearest neighbors queries against a fixed distance function (cosine similarity by default). From a syntax perspective, the vector index differs from ordinary application-defined B-Tree indices in that it must wrap the vector column into a `libsql_vector_idx` marker function like this ```sql theme={null} CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding)); ``` > [!NOTE] > > Vector index works only for column with one of the vector types described > above The vector index is fully integrated into the LibSQL core, so it inherits all operations and most features from ordinary indices: * An index created for a table with existing data will be automatically populated with this data * All updates to the base table will be **automatically** reflected in the index * You can rebuild index from scratch using `REINDEX movies_idx` command * You can drop index with `DROP INDEX movies_idx` command * You can create [partial](https://www.sqlite.org/partialindex.html) vector index with a custom filtering rule: ```sql theme={null} CREATE INDEX movies_idx ON movies (libsql_vector_idx(embedding)) WHERE year >= 2000; ``` ### Query At the moment vector index must be queried **explicitly** with special `vector_top_k(idx_name, q_vector, k)` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions). The function accepts index name, query vector and amount of neighbors to return. This function searches for `k` approximate nearest neighbors and returns `ROWID` of these rows or `PRIMARY KEY` if base index [does not have ROWID](https://www.sqlite.org/withoutrowid.html). In order for table-valued function to work query vector **must** have the same vector type and dimensionality. ### Settings LibSQL vector index optionally can accept settings which must be specified as variadic parameters of the `libsql_vector_idx` function as strings in the format `key=value`: ```sql theme={null} CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding, 'metric=l2', 'compress_neighbors=float8')); ``` At the moment LibSQL supports the following settings: | Setting key | Value type | Description | | - | - | - | | `metric` | `cosine` \| `l2` | Which distance function to use for building the index. Default: `cosine` | | `max_neighbors` | positive integer | How many neighbors to store for every node in the DiskANN graph. The lower the setting -- the less storage index will use in exchange to search precision. Default: $3 \sqrt{D}$ where $D$ -- dimensionality of vector column | | `compress_neighbors` | `float1bit`\|`float8`\| `float16`\|`floatb16`\| `float32` | Which vector type must be used to store neighbors for every node in the DiskANN graph. The more compact vector type is used for neighbors -- the less storage index will use in exchange to search precision. Default: **no compression** (neighbors has same type as base table) | | `alpha` | positive float $\geq 1$ | "Density" parameter of general sparse neighborhood graph build during DiskANN algorithm. The lower parameter -- the more sparse is DiskANN graph which can speed up query speed in exchange to lower search precision. Default: `1.2` | | `search_l` | positive integer | Setting which limits the amount of neighbors visited during vector search. The lower the setting -- the faster will be search query in exchange to search precision. Default: `200` | | `insert_l` | positive integer | Setting which limits the amount of neighbors visited during vector insert. The lower the setting -- the faster will be insert query in exchange to DiskANN graph navigability properties. Default: `70` | > [!NOTE] > > Vector index for column of type `T1` with `max_neighbors=M` and > `compress_neighbors=T2` will approximately use $\texttt{N} (Storage(\texttt {T1}) + \texttt{M} \cdot Storage(\texttt{T2}))$ storage bytes for `N` rows. ### Index usage **1. Create a table** Begin by declaring a column used for storing vectors with the `F32_BLOB` datatype: ```sql theme={null} CREATE TABLE movies ( title TEXT, year INT, embedding F32_BLOB(4) -- 4-dimensional f32 vector ); ``` The number in parentheses `(4)` specifies the dimensionality of the vector. This means each vector in this column will have exactly 4 components. **2. Generate and insert embeddings** Once you generate embeddings for your data (via an LLM), you can insert them into your table: ```sql theme={null} INSERT INTO movies (title, year, embedding) VALUES ('Napoleon', 2023, vector32('[0.800, 0.579, 0.481, 0.229]')), ('Black Hawk Down', 2001, vector32('[0.406, 0.027, 0.378, 0.056]')), ('Gladiator', 2000, vector32('[0.698, 0.140, 0.073, 0.125]')), ('Blade Runner', 1982, vector32('[0.379, 0.637, 0.011, 0.647]')); ``` Popular tools like [LangChain](https://www.langchain.com), [Hugging Face](https://huggingface.co) or [OpenAI](https://turso.tech/blog/how-to-generate-and-store-openai-vector-embeddings-with-turso) can be used to generate embeddings. **3. Create an Index** Create an index using the `libsql_vector_idx` function: ```sql theme={null} CREATE INDEX movies_idx ON movies(libsql_vector_idx(embedding)); ``` This creates an index optimized for vector similarity searches on the `embedding` column. > [!NOTE] > > The `libsql_vector_idx` marker function is **required** and used by libSQL to > distinguish `ANN`-indices from ordinary B-Tree indices. **4. Query the indexed table** ```sql theme={null} SELECT title, year FROM vector_top_k('movies_idx', vector32('[0.064, 0.777, 0.661, 0.687]'), 3) JOIN movies ON movies.rowid = id WHERE year >= 2020; ``` This query uses the `vector_top_k` [table-valued function](https://www.sqlite.org/vtab.html#table_valued_functions) to efficiently find the top 3 most similar vectors to `[0.064, 0.777, 0.661, 0.687]` using the index. ### Index limitations * Vector index works only for tables **with** `ROWID` or with singular `PRIMARY KEY`. Composite `PRIMARY KEY` without `ROWID` is not supported # Attach Database (Deprecated) Source: https://docs.turso.tech/features/attach-database Attach and read data across multiple databases. > [!WARNING] > > This feature is now deprecated for all new users. Existing paid users can > continue to use `ATTACH` — [read the > announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap) The `ATTACH` statement enables you to link multiple databases within a single transaction, which is ideal for: * Organizing data in a modular way * Streamlining data access and enhancing scalability * Aggregating data ## How it works 1. You enable the `ATTACH` feature on the databases you want to connect to. 2. You retrieve the **Database ID** for the database you want to `ATTACH`. 3. You connect to the database * **CLI**: `--attach` flag to automatically create a token with the correct permissions. * **SDK**: Create a token with the `attach` permission for the database you want to attach. 4. You invoke `ATTACH` to connect to the other databases within the database shell or SDK. ## Usage You can use the `ATTACH` statement to connect to other databases within a transaction using the CLI, or libSQL SDK. Once attached, you can query the attached databases as if they were part of the current database using the assigned alias. ### Turso CLI Make sure you have the [Turso CLI](/cli/installation) installed, and [logged in](/cli/auth/login). **1. Enable attach on required databases** You will first need to enable the `ATTACH` feature on the database(s) you want to attach: ```bash theme={null} turso db config attach allow ``` **2. Retrieve Database ID** You now need to retrieve the **Database ID** for the database you want to `ATTACH`: ```bash theme={null} turso db show ``` **3. Connect to any database with attach** Now pass the names of the databases via the `--attach` flag when connecting to your database(s): ```bash theme={null} turso db shell --attach <...database-name(s)> ``` **4. ATTACH** Now once connected to the database you can invoke an `ATTACH` statement to connect the other database(s): ```sql theme={null} ATTACH "" AS my_db; ``` **5. Query** Execute a query using the alias for any attached database(s): ```sql theme={null} SELECT * FROM my_db.my_table; ``` ### libSQL SDKs You can use one of the libSQL client SDKs with [TypeScript](/sdk/ts), [Rust](/sdk/rust), [Go](/sdk/go), [Python](/sdk/python), or over [HTTP](/sdk/http). **1. Enable attach on required databases** You will first need to enable the `ATTACH` feature on the database(s) you want to attach: ```bash theme={null} turso db config attach allow ``` **2. Retrieve Database ID** You now need to retrieve the **Database ID** for the database you want to `ATTACH`: ```bash theme={null} turso db show ``` **3. Create token with ATTACH permissions** Now create a token for the libSQL client with the `attach` permission for the database you want to attach: ```bash theme={null} turso db tokens create --attach ``` **4. Connect to any database with ATTACH** Use a [Client SDK](/sdk) to attach the desired database within a read transaction: ```ts @libsql/client theme={null} import { createClient } from "@libsql/client"; const client = createClient({ syncUrl: "libsql://...", authToken: "...", }); const txn = await db.transaction("read"); await txn.execute('ATTACH "" AS my_db'); const rs = await txn.execute("SELECT * FROM my_db.my_table"); ``` ## Things to know * You can only attach databases that have the `attach` feature enabled. * You can only attach databases belonging to a group, and in the same group. * There is a maximum of 10 databases that can be attached to a single transaction. * The attached databases are read only. * `ATTACH` statement can be used only within transactions. * `ATTACH` doesn't support [Embedded Replicas](/features/embedded-replicas) # Branching Source: https://docs.turso.tech/features/branching A branch is a separate database instance that is created from an existing database. You can also create a branch from a [point-in-time](/features/point-in-time-recovery) snapshot of a database. Branches are useful for development and testing, because they allow you to make changes to the database without affecting the original database. ## How it works 1. You create a new database from an existing database using the CLI or API. 2. You connect to the new database using the group API token. 3. Make changes to the new schema using a migration tool (optional). 4. Apply the changes to the original database using a migration tool when merging using a GitHub Action (optional). 5. Delete the database when you no longer need it. ## Usage You can create a new database from an existing database using the CLI or API: ```bash CLI theme={null} turso db create my-new-database-branch --from-db my-existing-database ``` ```bash Platform API theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "seed": { "type:": "database", "name": "my-existing-database" } }' ``` Refer to the following references for more details about all arguments: > **[CLI Reference](/cli/db/create)** > **[API Reference](/api-reference/databases/create)** ## Things to know * Database branches are completely separate from the original database. This means that you need to handle merging any schema changes or data manually using a migration tool. * You will need to [create a new token](/cli/db/tokens/create) (or use a group token) to connect to the new database. * You will need to manually delete the database branch when you no longer need it. * Branches count towards your plan's database quota. ## CI/CD Automating branching is useful for creating a new database for each pull request. This allows you to test changes without affecting the original database. Here's an example of what that might look like using the [Platform API](/api-reference/databases/create): ```yml .github/workflows/create-database-branch.yml theme={null} name: Create Database on: create jobs: triggerAPI: runs-on: ubuntu-latest steps: - name: Generate Branch Name id: branch_name run: | BRANCH_NAME=$(echo "${{ github.ref_name }}" | tr -cd '[:alnum:]' | sed 's/./\L&/g' | cut -c 1-32) echo "::set-output name=branch_name::$BRANCH_NAME" - name: Create Database run: | curl -X POST \ -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{"name": "${{ steps.branch_name.outputs.branch_name }}", "group": "default", "seed": {"type": "database", "name": "${{ secrets.DATABASE_NAME }}"} }' \ "https://api.turso.tech/v1/organizations/${{ secrets.ORGANIZATION_NAME }}/databases" ``` # Data Edge (Deprecated) Source: https://docs.turso.tech/features/data-edge Allow your users to reach local replicas of your database, wherever they are. > [!WARNING] > > This feature is now deprecated for all new users. Existing users can continue > to use Edge Replicas on Fly — [read the > announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap) For those seeking the ultimate in speed, Turso enables the [embedding of databases](/features/embedded-replicas) directly within your application on the same node. This configuration eliminates inter-regional request hopping, effectively bringing latency down to zero. [YouTube video player](https://www.youtube.com/embed/DyO4OP5v0IM) ## How it works 1. You create a database in a primary location 2. You add additional locations where data should be replicated 3. You query a single URL that automatically routes to the nearest edge ## Add replica location You can add locations to your database group using the [Turso CLI](/cli/group/locations/add) or [Platform API](/api-reference/groups/add-location): ```bash CLI theme={null} turso group locations add ``` ```bash Platform API theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/locations/{location}' \ -H 'Authorization: Bearer TOKEN' ``` ## Remove replica location You can remove locations from your database group using the [Turso CLI](/cli/group/locations/remove) or [Platform API](/api-reference/groups/remove-location): ```bash CLI theme={null} turso group locations remove <...location-codes> ``` ```bash Platform API theme={null} curl -L -X DELETE 'https://api.turso.tech/v1/organizations/{organizationSlug}/groups/{groupName}/locations/{location}' \ -H 'Authorization: Bearer TOKEN' ``` # Embedded Replicas Source: https://docs.turso.tech/features/embedded-replicas/introduction > [!NOTE] > > Embedded Replicas keep a local read replica of a Turso Cloud database: reads run locally from the file in microseconds, and writes are sent to the cloud primary and then reflected back to the replica. Embedded Replicas are fully supported in production. > > For new projects that need sync, we recommend [Turso Sync](/sync/usage). Sync is built on the Turso Database engine and uses logical change-data-capture, providing local-first writes, explicit `push()` / `pull()`, and [significantly less bandwidth and lower latency](https://turso.tech/blog/sync-benchmark) than the page-level replication Embedded Replicas use. Turso Cloud's embedded replicas let you replicate a database right within your application. This is especially useful for VMs or VPS deployments, and for mobile applications where stable connectivity is a challenge, since they allow uninterrupted access to the local database. Embedded replicas provide a smooth switch between local and remote database operations, allowing the same database to adapt to various scenarios. They also ensure fast data access by syncing local copies with the remote database, enabling microsecond-level read operations. ## How it works 1. You configure a local file to be your main database. * The `url` parameter in the client configuration. 2. You configure a remote database to sync with. * The `syncUrl` parameter in the client configuration. 3. You read from a database: * Reads are always served from the local replica configured at `url`. 4. You write to a database: * **Writes are sent to the remote primary database** configured at `syncUrl` by default. They are NOT written to the local file first. * You can write locally if you set the `offline` config option to `true`. * Any write transactions with reads are also sent to the remote primary database. * Once the write is successful, the local database is updated with the changes automatically (read your own writes, can be disabled). ### Periodic sync You can automatically sync data to your embedded replica using the periodic sync interval property. Simply pass the `syncInterval` parameter when instantiating the client: ```ts theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:path/to/db-file.db", authToken: "...", syncUrl: "...", syncInterval: 60, offline: true, // Optional: Enable offline mode (default: false) }); ``` ### Read your writes Embedded Replicas also will guarantee read-your-writes semantics. What that means in practice is that after a write returns successfully, the replica that initiated the write will always be able to see the new data right away, even if it never calls `sync()`. Other replicas will see the new data when they call `sync()`, or at the next sync period, if [Periodic Sync](#periodic-sync) is used. ![Read your writes](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/features/embedded-replicas/read-your-writes.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=44d76ec117694d77b50d0b6f134065fe) ### Encryption at rest Embedded Replicas support encryption at rest with one of the libSQL client SDKs. Simply pass the `encryptionKey` parameter when instantiating the client: > [!NOTE] > > The encryption key used should be generated and managed by you. ## Usage To use embedded replicas, you need to create a client with a `syncUrl` parameter. This parameter specifies the URL of the remote Turso Cloud database that the client will sync with: ```ts TypeScript theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:replica.db", syncUrl: "libsql://...", authToken: "...", }); ``` ```go Go theme={null} package main import ( "database/sql" "fmt" "os" "path/filepath" "github.com/tursodatabase/go-libsql" ) func main() { dbName := "local.db" primaryUrl := "libsql://[DATABASE].turso.io" authToken := "..." dir, err := os.MkdirTemp("", "libsql-*") if err != nil { fmt.Println("Error creating temporary directory:", err) os.Exit(1) } defer os.RemoveAll(dir) dbPath := filepath.Join(dir, dbName) connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl, libsql.WithAuthToken(authToken), ) if err != nil { fmt.Println("Error creating connector:", err) os.Exit(1) } defer connector.Close() db := sql.OpenDB(connector) defer db.Close() } ``` ```rust Rust theme={null} use libsql::{Builder}; let build = Builder::new_remote_replica("file:replica.db", "libsql://...", "...") .build() .await?; let client = build.connect()?; ``` ```php PHP theme={null} use Libsql\Database; $db = new Database( path: 'replica.db', url: getenv('TURSO_URL'), authToken: getenv('TURSO_AUTH_TOKEN'), syncInterval: 300 // Sync every 5 minutes ); $conn = $db->connect(); ``` ```php Laravel theme={null} // config/database.php return [ "default" => env("DB_CONNECTION", "libsql"), "connections" => [ "libsql" => [ "driver" => "libsql", "database" => database_path("database.db"), "url" => env("TURSO_DATABASE_URL"), "password" => env("TURSO_AUTH_TOKEN"), "sync_interval" => env("TURSO_SYNC_INTERVAL", 300), ], // ... ], ]; // .env DB_CONNECTION=libsql TURSO_DATABASE_URL=libsql://... TURSO_AUTH_TOKEN=... TURSO_SYNC_INTERVAL=300 ``` You can sync changes from the remote database to the local replica manually: ```ts TypeScript theme={null} await client.sync(); ``` ```go Go theme={null} if err := connector.Sync(); err != nil { fmt.Println("Error syncing database:", err) } ``` ```rust Rust theme={null} client.sync().await?; ``` ```php PHP theme={null} $db->sync(); ``` > [!NOTE] > > You should call `.sync()` in the background whenever your application wants to sync the remote and local embedded replica. For example, you can call it every 5 minutes or every time the application starts. ## Things to know * Do not open the local database while the embedded replica is syncing. This can lead to data corruption. * In certain contexts, such as serverless environments without a filesystem, you can't use embedded replicas. * There are a couple scenarios where you may sync more frames than you might expect. * A write that causes the internal btree to split at any node would cause many new frames to be written to the replication log. * A server restart that left the on-disk wal in dirty state would regenerate the replication log and sync additional frames. * Removing/invalidating the local files on disk could cause the embedded replica to re-sync from scratch. * One frame equals 4kB of data (one on disk page frame), so if you write a 1 byte row, it will always show up as a 4kB write since that is the unit in which libsql writes with. ## Deployment Guides > **[Turso + Fly](/features/embedded-replicas/with-fly)** > > Deploy a JavaScript project with Embedded Replicas to Fly.io > **[Turso + Koyeb](/features/embedded-replicas/with-koyeb)** > > Deploy a JavaScript/Rust project with Embedded Replicas to Koyeb > **[Turso + Railway](/features/embedded-replicas/with-railway)** > > Deploy a JavaScript/Rust project with Embedded Replicas to Railway > **[Turso + Render](/features/embedded-replicas/with-render)** > > Deploy a JavaScript project with Embedded Replicas to Render > **[Turso + Linode by Akamai](/features/embedded-replicas/with-akamai)** > > Deploy a JavaScript/Rust project with Embedded Replicas to Akamai # Turso + Linode by Akamai Source: https://docs.turso.tech/features/embedded-replicas/with-akamai Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Akamai](https://www.linode.com/). ![Akamai banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/akamai-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=66d07bd3d96ab8ddcec273d80a00fdca) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso Cloud](/cli/authentication#signup) * Have an Akamai account - [create one](https://login.linode.com/signup) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **Step 2** Fork one of the following embedded replica projects from GitHub > **[My Expenses Tracker - (Elysia + Bun)](https://github.com/tursodatabase/embedded-replicas-with-js)** > > See the full source code > **[Web Traffic API - (Rust)](https://github.com/tursodatabase/embedded-replicas-with-rust)** > > See the full source code **3. Set up a Linode server** Configure and create a new linode. Then, [set up SSH authentication](https://www.linode.com/docs/guides/use-public-key-authentication-with-ssh/) to securely access the Linode server from your terminal. Prepare the newly created linode server environment by accessing and set it up for Rust/JavaScript development depending on the project you forked earlier. Install and set up [Git](https://git-scm.com/) too. **4. Transfer project to Linode server** SSH into your server, clone the project from GitHub, and follow its README instructions to set it up. **5. Deploy** Build, run the project, and set up load balancing for it. [pm2](https://www.npmjs.com/package/pm2) is one of the good candidates out there with built-in load balancing, log monitoring, and bug/exception alerts. > [!NOTE] > > You can go with your favorite options for where to buy domains, reverse proxy setup, and SSL certificates. [Caddy](https://caddyserver.com/) is another good option here. # Embedded Replicas on Fly Source: https://docs.turso.tech/features/embedded-replicas/with-fly Deploy a JavaScript app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Fly.io](https://www.fly.io/). ![Koyeb banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/fly-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=f3d7f266e79052e9d176d519a4a277fc) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso Cloud](/cli/authentication#signup) * [Install the Fly.io CLI](https://fly.io/docs/hands-on/install-flyctl/) **1. Locate your application** You should have an application ready using your Turso Cloud database that you want to deploy to Fly. **2. Launch with Fly** Using the Fly CLI, launch it: ```bash theme={null} fly launch ``` Your application will automatically deploy to Fly, but we're not ready yet. **3. Create a shared volume** Now create a volume that will be used to store the embedded replica(s): ```bash theme={null} fly volumes create libsql_data ``` **4. Mount and configure volumes** The files `fly.toml` and `Dockerfile` created created when you launched previously. Update `fly.toml` this file to mount the new volume: ```toml theme={null} [[mounts]] source = "libsql_data" destination = "/app/data" ``` Then inside `Dockerfile`, make sure you install and update `ca-certificates`: ```dockerfile theme={null} RUN apt-get update -qq && \ apt-get install -y ca-certificates && \ update-ca-certificates ``` Make sure to also add the following line after any `COPY` commands to copy the certificates: ```dockerfile theme={null} COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ``` **5. Configure the libSQL client** You will want to change the `url` to point to a local file, and set the `syncUrl` to be your Turso Cloud database URL: ```ts theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:/app/data/local.db", syncUrl: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, syncInterval: 60, }); ``` **6. Deploy your updated app** ```bash theme={null} fly deploy ``` # Turso + Koyeb Source: https://docs.turso.tech/features/embedded-replicas/with-koyeb Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Koyeb](https://www.koyeb.com/). ![Koyeb banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/koyeb-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=6f7f0ee22ae417bb3743cd771b4c8a4a) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso Cloud](/cli/authentication#signup) * Have a Koyeb account - [create one](https://app.koyeb.com/) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **Step 2** Fork one of the following embedded replica project from GitHub > **[My Expenses Tracker - (Elysia + Bun)](https://github.com/tursodatabase/embedded-replicas-with-js)** > > See the full source code > **[Web Traffic API - (Rust)](https://github.com/tursodatabase/embedded-replicas-with-rust)** > > See the full source code > [!NOTE] > > Or, you can: > > > **[Deploy to Koyeb with a single-click](https://app.koyeb.com/deploy?name=er-with-js\&type=git\&repository=tursodatabase/embedded-replicas-with-js\&branch=main\&env\[PORT]=8000\&env\[TURSO_DATABASE_URL]=REPLACE_ME\&env\[TURSO_AUTH_TOKEN]=REPLACE_ME\&env\[LOCAL_DB]=file:expenses.db)** **3. Add a new Koyeb app** 1. Create a new app in the Koyeb control panel. 2. Select GitHub as the deployment option. 3. Import the GitHub project to Koyeb. **4. Fill in the environment variables on Koyeb's deploy page** ![Koyeb deploy page - environment variables](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/koyeb-env-variables.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=fbe69407ce481b6f89954813101965b4) **5. Deploy** Click the **Deploy** button at the bottom to deploy your web service. # Turso + Railway Source: https://docs.turso.tech/features/embedded-replicas/with-railway Deploy a JavaScript/Rust app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Railway](https://railway.app/). ![Koyeb banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/railway-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=c04df842dadd8d42cd24364580fbf7bf) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso Cloud](/cli/authentication#signup) * [Install the Railway CLI](https://docs.railway.app/guides/cli#installing-the-cli) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Get application code** Fork and clone the following embedded replica project from GitHub locally: > **[My Expenses Tracker - (Elysia + Bun)](https://github.com/tursodatabase/embedded-replicas-with-js)** > > See the full source code > **[Web Traffic API - (Rust)](https://github.com/tursodatabase/embedded-replicas-with-rust)** > > See the full source code **3. Create a new Railway project** Run the following command to create a new Railway project. Provide the project's name when prompted. ```sh theme={null} railway init ``` **4. Add a service to the Railway project** [Create a new empty service](https://docs.railway.app/guides/services#creating-a-service) on your Railway project to act as your app's deployment target. **5. Link application to service** Run the following command to list and select the service to link to your application: ```sh theme={null} railway service ``` **6. Add database credentials** Open the service on your Railway dashboard and add your Turso Cloud database credentials. ```sh theme={null} TURSO_DATABASE_URL=libsql://[db-name]-[github-username].turso.io TURSO_AUTH_TOKEN=... LOCAL_DB=file:local-db-name.db ``` **7. Deploy** Run the following command to deploy your application: ```sh theme={null} railway up ``` > [!NOTE] > > Make sure you [expose your application to the internet](https://docs.railway.app/guides/public-networking) to make it accessible from the public network. > [!WARNING] > > If you are on a free plan, you'll need to connect your Railway account to GitHub to have access to code deployments. # Turso + Render Source: https://docs.turso.tech/features/embedded-replicas/with-render Deploy a JavaScript app using [Turso Cloud embedded replicas](/features/embedded-replicas) to [Render](https://render.com/). ![Render banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/platforms/render-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=6e416c1d54d38e041de310997834574e) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso Cloud](/cli/authentication#signup) * Have a Render account - [create one](https://dashboard.render.com/) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Get application code** Fork the following embedded replica project from GitHub locally: > **[My Expenses Tracker - (Elysia + Bun)](https://github.com/tursodatabase/embedded-replicas-with-js)** > > See the full source code > [!NOTE] > > Or, you can: > > > **[Deploy to Render with a single-click](https://render.com/deploy?repo=https://github.com/tursodatabase/embedded-replicas-with-js)** **3. Create a web service** Create a new Render **Web Service** by clicking on the "New Web Service" button on the Web Services card inside you Render dashboard. **4. Connect to Git repository** 1. Select "build and deploy from a Git repository" and proceed to the next page. 2. Click on "Connect" for your target project repository **5. Set project's environment variables** On the web service configuration page, under "Advanced" add **a secret file** and fill it in with your database secret credentials: ![Render secret credentials](https://mintlify.s3.us-west-1.amazonaws.com/turso/features/embedded-replicas/images/platforms/render-env-vars.png) **6. Deploy project** Scroll to the bottom of the web service configuration page and click on "Create Web Service". # Multi-DB Schemas (Deprecated) Source: https://docs.turso.tech/features/multi-db-schemas Create and share a single schema across multiple databases. > [!WARNING] > > This feature is now deprecated for all new users. Existing paid users can > continue to use Multi-DB Schemas — [read the > announcement](https://turso.tech/blog/upcoming-changes-to-the-turso-platform-and-roadmap) Turso allows you to create a single schema and share it across multiple databases. This is useful for creating a multi-tenant application where each tenant has their own database. > [!NOTE] > > **Using TypeScript?** To provision databases programmatically, use the [`@tursodatabase/api`](https://www.npmjs.com/package/@tursodatabase/api) client rather than calling the REST API directly. See the [SDK quickstart](/api-reference/introduction#quickstart-with-the-typescript-sdk). > [!NOTE] > > Try the [Turso Per User Starter](https://github.com/notrab/turso-per-user-starter) to get started with a multi-tenant application using Next.js, Drizzle, Clerk, libSQL, and Turso Multi-DB Schemas. ## How it works 1. You create a database that is used as the parent schema database. 2. You create one or more databases that are used as the child databases. 3. You apply schema changes to the parent database, child databases are automatically updated with the new schema. ## Usage You can create and manage parent or child databases using the [Turso CLI](/cli/db/create), and [Platform API](/api-reference/databases/create). [Multi database schema changes](https://www.youtube.com/embed/Slacu1aGm8A) ### Turso CLI Make sure you have the [Turso CLI](/cli/installation) installed, and [logged in](/cli/auth/login). **1. Create Parent Database** Create a single database using the `--type` flag set to `schema`: ```bash theme={null} turso db create parent-db --type schema ``` **2. Create Schema** Connect to your database using the shell to `CREATE TABLE`, `CREATE TRIGGER`, `CREATE VIEW`, and anything else to setup your schema: ```bash theme={null} turso db shell parent-db ``` **3. Create Child Database(s)** Create one or more child databases using the `--schema` flag with the name of the parent database: ```bash theme={null} turso db create child-db --schema parent-db ``` **4. Apply Additional Schema Changes** You can now apply additional schema changes to the parent database, and the child databases will be automatically updated: ```bash theme={null} turso db shell parent-db "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);" ``` ### Platform API Make sure you have an API Token, and know your Organization name: **1. Create Parent Database** Create a database and set the `is_schema` field to `true`: ```bash theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "parent-db", "group": "default", "is_schema": true }' ``` **2. Create Schema** Connect to your database using the shell, [SDK](/sdk), or migration script to `CREATE TABLE`, `CREATE TRIGGER`, `CREATE VIEW`, and anything else to setup your schema: ```bash Turso CLI theme={null} turso db shell parent-db ``` **3. Create Child Database(s)** Create one or more child databases and pass the `schema` field the name of your parent database: ```bash theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "child-db", "group": "default", "schema": "parent-db" }' ``` **4. Apply Additional Schema Changes** You can now apply additional schema changes to the parent database, and the child databases will be automatically updated: ```bash theme={null} turso db shell parent-db "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);" ``` ## Things to know * Schema databases cannot be shared across groups or globally via an organization/account. * You can (but not recommended) `INSERT` rows into the parent database that can be queried by the child database(s). * Be aware of any constraints that may conflict with the child database(s). * You can't delete a parent database if it has one or more child databases. * When a migration is applied to the schema database: * It's first run as a dry-run on the schema and all other associated databases. * If successful, a migration job is created. * Tasks are created for each database that references this schema. * The migration is then applied to each referencing database. * Finally, if all tasks succeed, the migration is applied to the schema database itself. * You can't create or delete a database if there are any migrations running. * **During a migration, all databases are locked to write operations.** * Make sure any application querying the child databases handle any databases not yet updated with the schema. * You cannot apply schema changes to a child database directly. You must use the parent (schema) database. * You can check the status of a migration using the `/jobs` endpoint — [learn more](/sdk/http/reference#schema-migration-jobs). # Point-in-Time Recovery Source: https://docs.turso.tech/features/point-in-time-recovery Turso supports point-in-time recovery (PITR) for databases. PITR allows you to restore a database to a specific point in time. This is useful for recovering from user errors, such as dropping a table by mistake. > [!NOTE] > > Backups are created automatically at `COMMIT`. Free plan users can restore to any point in time within the last 24 hours. Developer, Scaler, and Pro users can restore to any point in time within the last 10, 30, or 90 days, respectively. > [!NOTE] > > PITR restores an **existing** database — it cannot bring back a database that was > deleted. On paid plans, deleted databases can be restored for up to five days: see > [Recover Deleted Databases](/features/recover-deleted-databases). ## How it works 1. You create a new database from the existing database using the CLI or API. 2. You update your application to use the new database connection string. 3. You delete the old database when you no longer need it. ## Usage ```bash CLI theme={null} turso db create my-new-database --from-db my-existing-database --timestamp 2024-01-01T00:00:00Z ``` ```bash Platform API theme={null} curl -L -X POST 'https://api.turso.tech/v1/organizations/{organizationSlug}/databases' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "name": "new-database", "group": "default", "seed": { "type:": "database", "name": "my-existing-database", "timestamp": "2024-01-01T00:00:00Z" } }' ``` Refer to the following references for more details about all arguments: > **[CLI Reference](/cli/db/create)** > **[API Reference](/api-reference/databases/create)** ## Things to know * Restoring from a PITR creates a new database. You will need to update your application to use the new database connection string. * You cannot restore from a PITR to a database that already exists. * You will need to [create a new token](/cli/db/tokens/create) (or use a group token) to connect to the new database. * You will need to manually delete the old database when you no longer need it. * Restores count towards your plan's database quota. # Recover Deleted Databases Source: https://docs.turso.tech/features/recover-deleted-databases Restore databases and groups for up to five days after deletion. Turso Cloud keeps deleted databases and groups recoverable for up to **five days** after deletion. Restoring brings the database back exactly as it was at the moment it was deleted — same name, same data. This protects you against accidents, whether the delete came from a teammate, a script, or an AI agent with too much enthusiasm. Recovery is available on all paid plans at no additional cost. > [!NOTE] > > This is different from [Point-in-Time Recovery](/features/point-in-time-recovery) > (PITR). PITR restores an **existing** database to an earlier point in time, but it > cannot bring back a database that was deleted. Recovery brings back a **deleted** > database at the moment it was deleted. ## Requirements * A **paid plan**. Databases deleted while the organization was on a paid plan can be restored. * **Admin or owner** access to the organization. * Recovery enabled for the organization (the **Allow restore** toggle — see [below](#disabling-recovery)). ## Restore from the dashboard 1. Open the **Restore** page in the [Turso dashboard](https://app.turso.tech). 2. Find the database under **Recently deleted** — you can search by name, ID, hostname, or group. 3. Click **Restore**. Deleted groups appear in their own section on the same page. Restoring a group also attempts to restore the databases that were deleted along with it. ## Restore with the Platform API List the databases deleted within the last five days, then restore by the database's UUID: ```bash List deleted databases theme={null} curl -L 'https://api.turso.tech/v3/organizations/{organizationId}/databases?deleted=true' \ -H 'Authorization: Bearer TOKEN' ``` ```bash Restore a database theme={null} curl -L -X POST 'https://api.turso.tech/v3/organizations/{organizationId}/databases/{databaseId}/restore' \ -H 'Authorization: Bearer TOKEN' ``` ```bash List deleted groups theme={null} curl -L 'https://api.turso.tech/v3/organizations/{organizationId}/deleted-groups' \ -H 'Authorization: Bearer TOKEN' ``` ```bash Restore a group theme={null} curl -L -X POST 'https://api.turso.tech/v3/organizations/{organizationId}/groups/{groupId}/restore' \ -H 'Authorization: Bearer TOKEN' ``` These endpoints identify everything by **UUID** rather than name: `{organizationId}` is the `id` field from [Retrieve Organization](/api-reference/organizations/retrieve), and the database and group UUIDs come from the deleted listings. To restore under a different name (for example, when a new database already uses the old one), pass a JSON body: `{ "name": "my-restored-database" }`. Refer to the API reference for all parameters and response fields: > **[List Deleted Databases](/api-reference/databases/list-deleted)** > **[Restore Database](/api-reference/databases/restore)** > **[List Deleted Groups](/api-reference/groups/list-deleted)** > **[Restore Group](/api-reference/groups/restore)** ## Restore with an AI agent The [Turso MCP server](/integrations/mcp) exposes recovery to AI agents, so an agent that deleted a database by mistake can bring it back in the same session: | Tool | Description | | - | - | | `list_deleted_databases` | List databases deleted within the last five days. Returns the UUID needed to restore. | | `restore_database` | Restore a deleted database by UUID, optionally under a new name. | | `list_deleted_groups` | List groups deleted within the last five days, including empty groups. | | `restore_group` | Restore a deleted group and the databases that were deleted with it. | ## Restoring groups Destroying a group deletes all of its databases. Restoring the group brings the group back and attempts to restore those databases as well. If some databases fail to restore, the group still becomes active — you can retry the failed databases individually with `restore_database`. ## Disabling recovery Some organizations need deleted to mean deleted. Admins and owners can turn recovery off for the whole organization with the **Allow restore** toggle on the dashboard's Restore page, through the API, or through the MCP server (`set_organization_restore_enabled`): ```bash theme={null} curl -L -X PATCH 'https://api.turso.tech/v1/organizations/{organizationSlug}' \ -H 'Authorization: Bearer TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "restore_enabled": false }' ``` The current state is returned as `restore_enabled` by [Retrieve Organization](/api-reference/organizations/retrieve). While recovery is disabled, deleted databases and groups still appear in the deleted listings, but restore attempts fail until an admin or owner re-enables recovery. ## Things to know * The recovery window is **five days** from deletion. After that, the database is gone for good. * Restores are identified by the database's **UUID**, not its name — names can be reused. If a new database already exists with the old name, restore the deleted one under a new name. * The restored database contains everything committed up to the moment of deletion. To rewind further back in time, follow up with [Point-in-Time Recovery](/features/point-in-time-recovery). * Recovery requires regional support: appearing in the deleted listing does not guarantee the database can be recovered. * To prevent deletion in the first place, enable **delete protection** on the [database](/api-reference/databases/update-configuration) or [group](/api-reference/groups/update-configuration) — a protected database cannot be deleted until protection is turned off. > **[Point-in-Time Recovery](/features/point-in-time-recovery)** > **[MCP (AI agents)](/integrations/mcp)** # SQLite Extensions Source: https://docs.turso.tech/features/sqlite-extensions Learn how to use SQLite extensions with Turso. Turso comes loaded with extensions that provide additional functionality to SQLite. Some plans will have more extensions preloaded. > [!NOTE] > > If you're looking to enable vector extensions, you should instead consider > using the native [libSQL vector datatype](/features/ai-and-embeddings). ## Preloaded Extensions The following extensions are always-on and available: | Extension | Version | Description | | - | - | - | | [JSON](https://www.sqlite.org/json1.html) | Built-in | Work with JSON data in SQLite. | | [FTS5](https://www.sqlite.org/fts5.html) | Built-in | Full-text search and indexing. | | [R\*Tree](https://www.sqlite.org/rtree.html) | Built-in | Indexing and querying spatial data. | | [SQLean Crypto](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/crypto.md) | 0.24.1 | Hashing, message digest, encoding, and decoding. | | [SQLean Fuzzy](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/fuzzy.md) | 0.24.1 | Fuzzy string matching and phonetics. A fork of Spellfix1 with improvements. | | [SQLean Math](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/math.md) | 0.24.1 | Advanced mathematical calculations. | | [SQLean Stats](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/stats.md) | 0.24.1 | Common statistical functions with SQLite. | | [SQLean Text](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/text.md) | 0.24.1 | String manipulation (reverse, split) with SQLite. | | [SQLean UUID](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/uuid.md) | 0.24.1 | Limited support for RFC 4122 compliant UUIDs. | ## Optional Extensions The following extensions are also enabled in addition to the preloaded extensions on databases using the Fly Cloud Provider (Pro & Enterprise). | Extension | Version | Description | | - | - | - | | [SQLean Regexp](https://github.com/nalgeon/sqlean/blob/0.24.1/docs/regexp.md) | 0.24.1 | Regular expressions with SQLite. | | [sqlite-vec](https://github.com/asg017/sqlite-vec/tree/v0.1.3) | 0.1.3 | The Vector Search successor to `sqlite-vss`. | # Agent Databases Source: https://docs.turso.tech/guides/agent-databases > [!NOTE] > > Looking for a complete agent state management solution? Check out [AgentFS](/agentfs/introduction) - our specialized SDK designed specifically for AI agents, offering filesystem operations, key-value storage, and automatic tool call tracking in a single database file. AI agents need databases to store context, track state, and maintain memory across executions. Turso Database offers two powerful approaches: * **Embedded Databases** for local-first agent processing, and * **Turso Sync** for distributed agent coordination with cloud persistence. ## Embedded Databases (Local-First) Use embedded databases for agents that process data locally within a single workflow, need offline-capable access, or work with temporary data without sharing state with other agents. Local data pipelines, on-device processing of sensitive data, development/testing workflows, and single-agent analysis tasks. ```javascript theme={null} import { connect } from "@tursodatabase/database"; // Create a local embedded database for this agent const db = await connect("agent-workflow.db"); // Agent can now store and query data locally const stmt = db.prepare(` CREATE TABLE IF NOT EXISTS steps ( id INTEGER PRIMARY KEY AUTOINCREMENT, action TEXT NOT NULL, result TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) `); stmt.run(); // Track agent workflow steps db.prepare("INSERT INTO steps (action, result) VALUES (?, ?)").run( "fetch_data", "success", ); ``` Benefit from zero network latency, full offline capability, complete data privacy, and simple single-file deployment. > [!NOTE] > > Data is isolated to this agent instance with no built-in multi-agent collaboration or cross-session persistence (unless you manage the file yourself). ## Turso Sync (Cloud-Connected) Use Turso Sync for agents that need persistent memory across sessions, coordinate with other agents, or require cloud-backed durability for long-running workflows. 💡 Conversational agents with session memory, multi-agent coordination systems, long-running workflows with recovery needs, and agents reporting to central systems. ```javascript theme={null} import { connect } from "@tursodatabase/database"; // Connect to a synced database (embedded locally, synced to Turso Cloud) const db = await connect({ path: "agent-memory.db", url: "https://your-database.turso.io", authToken: "your-auth-token", sync: "full", // Bidirectional sync }); // Agent can access data from previous sessions or other agents const previousContext = db .prepare("SELECT * FROM conversation_history WHERE session_id = ?") .all("session-123"); // New data is automatically synced to cloud and other agent instances db.prepare("INSERT INTO agent_actions (agent_id, action) VALUES (?, ?)").run( "agent-1", "processed_document", ); // Manually trigger sync if needed await db.sync(); ``` 💡 Persistent memory across restarts, multi-agent coordination via shared state, cloud backup and durability, with fast local reads and background sync. > [!NOTE] > > **Sync modes:** `sync` (bidirectional), `pull` (cloud → agent), `push` (agent → cloud). ## Multi-Agent Architectures ### Pattern 1: Isolated Agent Databases Each agent gets its own embedded database. Best for independent agents with no shared state. ```javascript theme={null} // Agent 1 const agent1DB = await connect("agent-1.db"); // Agent 2 const agent2DB = await connect("agent-2.db"); ``` > [!NOTE] > > Independent agents performing separate tasks with no coordination needs and maximum isolation. ### Pattern 2: Shared Database with Sync Multiple agents connect to the same synced database. Best for coordinated agent systems. ```javascript theme={null} // Agent 1 connects and writes tasks const agent1DB = await connect({ path: "agent-1-local.db", url: "https://shared-tasks.turso.io", authToken: process.env.TURSO_TOKEN, sync: "full", }); agent1DB.prepare("INSERT INTO tasks (status) VALUES (?)").run("pending"); // Agent 2 connects to same database and reads tasks const agent2DB = await connect({ path: "agent-2-local.db", url: "https://shared-tasks.turso.io", // Same database authToken: process.env.TURSO_TOKEN, sync: "full", }); const pendingTasks = agent2DB .prepare("SELECT * FROM tasks WHERE status = ?") .all("pending"); ``` 💡 Agents coordinating work through shared task queues or a common knowledge base. ### Pattern 3: Hub-and-Spoke A central synced database with agents pushing results and pulling new tasks. ```javascript theme={null} // Worker agents push results only const workerDB = await connect({ path: "worker-local.db", url: "https://hub.turso.io", authToken: process.env.WORKER_TOKEN, sync: "push", // Only push results to cloud }); // Coordinator agent pulls from all workers const coordinatorDB = await connect({ path: "coordinator-local.db", url: "https://hub.turso.io", authToken: process.env.COORDINATOR_TOKEN, sync: "pull", // Only pull data from cloud }); ``` 💡 Many worker agents feeding results to a central coordinator for monitoring or aggregation with unidirectional data flow. ## Database-Per-Agent Pattern For systems managing many agents, you can create individual databases for each agent using the [Platform API](https://docs.turso.tech/api-reference): ```javascript theme={null} import { createClient } from "@tursodatabase/api"; const turso = createClient({ token: process.env.TURSO_PLATFORM_API_TOKEN, org: process.env.TURSO_ORG_NAME, }); // Create a dedicated database for each agent async function provisionAgentDatabase(agentId) { const database = await turso.databases.create(`agent-${agentId}`, { group: "default", }); console.log(`Database created for agent ${agentId}: ${database.Name}`); return database; } ``` > [!NOTE] > > Complete isolation between agents, independent scaling per agent, improved security (compromised agent only affects one database), and easy cleanup when agents are retired. ## Examples Explore these examples to see agent database patterns in action: * [sync-node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/sync-node) — Node.js with bidirectional sync to Turso Cloud * [sync-wasm-vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/sync-wasm-vite) — Browser (WASM) with bidirectional sync to Turso Cloud * [database-node](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-node) — Node.js, local file database (no sync) * [database-wasm-vite](https://github.com/tursodatabase/turso/blob/main/examples/javascript/database-wasm-vite) — Browser (WASM), local database in the browser # AI Memory Source: https://docs.turso.tech/guides/ai-memory AI agents start every session from scratch. A persistent memory system lets agents store lessons and retrieve relevant context via vector search. Turso's embedded database with built-in vector functions makes this possible in a single local file. This guide covers the schema and queries for building a memory system with vector similarity search. ## Schema At its simplest, a memory system needs just two tables: one for memories and one for tracking which memories were used in which tasks. ```sql theme={null} CREATE TABLE IF NOT EXISTS memories ( id TEXT PRIMARY KEY, content TEXT NOT NULL, embedding F8_BLOB(384), category TEXT NOT NULL, created_at INTEGER NOT NULL, last_retrieved INTEGER, retrieval_count INTEGER DEFAULT 0, source_task TEXT ); CREATE TABLE IF NOT EXISTS tasks ( id TEXT PRIMARY KEY, description TEXT, embedding F8_BLOB(384), started_at INTEGER, finished_at INTEGER ); ``` * **`memories`** stores each lesson with its vector embedding and a category (e.g. `correction`, `insight`, `user`, `discovery`). The embedding is a 384-dimensional int8-quantized vector (`F8_BLOB(384)`). * **`tasks`** records each task the agent worked on, linking task descriptions to their embeddings for retrieval. ## Connecting ```javascript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect(".memory/agent.db"); await db.exec(SCHEMA); ``` ## Storing a memory When the agent learns something — a correction, a user preference, an insight — store it with an embedding. Use `vector8()` to convert a JSON float array into an int8-quantized vector: ```sql theme={null} INSERT INTO memories (id, content, embedding, category, created_at, source_task) VALUES (?, ?, vector8(?), ?, ?, ?); ``` The parameter for `vector8()` is a JSON-stringified float array (e.g. 384 dimensions from a model like `all-MiniLM-L6-v2`). Turso handles the quantization internally: ```javascript theme={null} db.prepare(` INSERT INTO memories (id, content, embedding, category, created_at) VALUES (?, ?, vector8(?), ?, ?) `).run(id, content, JSON.stringify(Array.from(embedding)), "correction", Date.now()); ``` ## Retrieving relevant memories When a new task starts, find the most relevant memories using Turso's `vector_distance_cos()` function: ```sql theme={null} SELECT id, content, category, created_at, retrieval_count, vector_distance_cos(embedding, vector8(?)) AS distance FROM memories WHERE embedding IS NOT NULL ORDER BY distance ASC LIMIT ?; ``` > [!NOTE] > > `vector_distance_cos()` returns cosine **distance** (0 = identical, 2 = opposite). Lower is better. To convert to similarity: `1.0 - distance`. After retrieval, update the memory's metadata: ```sql theme={null} UPDATE memories SET last_retrieved = ?, retrieval_count = retrieval_count + 1 WHERE id = ?; ``` ## Correcting wrong memories When a memory turns out to be wrong, delete it and store a corrected version: ```sql theme={null} DELETE FROM memories WHERE id = ?; INSERT INTO memories (id, content, embedding, category, created_at) VALUES (?, ?, vector8(?), 'correction', ?); ``` ## Task tracking Record tasks to understand what the agent has been working on: ```sql theme={null} -- Start a task INSERT INTO tasks (id, description, embedding, started_at) VALUES (?, ?, vector8(?), ?); -- Complete a task UPDATE tasks SET finished_at = ? WHERE id = ?; ``` ## Purging old memories Remove memories that have been around a long time but are never retrieved: ```sql theme={null} DELETE FROM memories WHERE retrieval_count = 0 AND created_at < ?; ``` ## Key design points * **Vector search** uses Turso's `vector8()` and `vector_distance_cos()` for cosine similarity directly in SQL. Int8 quantization reduces storage by 75% compared to float32 with minimal impact on search quality. * For small per-project datasets, a full table scan with `ORDER BY distance LIMIT k` is fast enough without a vector index. * Everything runs in a single embedded database file with no external services. ## Going further The schema above is deliberately minimal. In practice you will want to experiment with strategies for keeping the memory store useful as it grows. Some ideas: * **Weighted memories** — add a `weight REAL` column and boost or penalize memories based on whether the agent found them useful after retrieval. Use an exponential moving average to update weights over time. * **Time decay** — multiply a recency factor into the retrieval ranking so that stale memories rank lower. For example: `(1.0 - distance) * POWER(0.95, days_since_last_retrieved)`. * **Garbage collection** — periodically remove memories with low weight and high retrieval count (retrieved often, never useful), or memories that have never been retrieved after a threshold period. * **Task scoring** — track outcome metrics (tokens used, errors, corrections) per task and use them to compute a credit signal for the memories that were retrieved during that task. * **Retrieval attribution** — add a join table `(memory_id, task_id, similarity, credit)` to record exactly which memories were used in each task and how they were rated. > [!WARNING] > > There is no single best approach — the right strategy depends on your agent's workload, how often memories are created, and how aggressively you want to prune. Start simple and iterate. ## Example [memelord](https://github.com/glommer/memelord) is an MCP server and hooks system that implements this pattern with reinforcement learning and weight decay as a persistent memory layer for AI coding agents. # Code Indexing Source: https://docs.turso.tech/guides/code-indexing AI coding agents work better when they can search a codebase semantically ("find the authentication logic") rather than just by filename or grep. A code index backed by Turso can combine full-text search over identifiers with vector similarity search over embeddings, all in a single embedded database. This guide covers the schema and queries for building a code indexer with both FTS and vector search. ## Schema ```sql theme={null} CREATE TABLE IF NOT EXISTS codebases ( id INTEGER PRIMARY KEY AUTOINCREMENT, root_path TEXT NOT NULL UNIQUE, name TEXT NOT NULL DEFAULT '', indexed_at INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, codebase_id INTEGER NOT NULL REFERENCES codebases(id), file_path TEXT NOT NULL, chunk_key TEXT NOT NULL UNIQUE, language TEXT NOT NULL, kind TEXT NOT NULL, name TEXT NOT NULL DEFAULT '', signature TEXT NOT NULL DEFAULT '', snippet TEXT NOT NULL, start_line INTEGER NOT NULL, end_line INTEGER NOT NULL, file_hash TEXT NOT NULL, indexed_at INTEGER NOT NULL, embedding F8_BLOB(384), embedding_model TEXT DEFAULT '' ); CREATE TABLE IF NOT EXISTS indexed_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, codebase_id INTEGER NOT NULL REFERENCES codebases(id), file_path TEXT NOT NULL, file_hash TEXT NOT NULL, chunk_count INTEGER NOT NULL DEFAULT 0, indexed_at INTEGER NOT NULL, UNIQUE(codebase_id, file_path) ); ``` ### How it fits together * **`codebases`** registers each project root. A single database can index multiple codebases. * **`chunks`** stores individual semantic units extracted from source files — functions, structs, classes, impl blocks, etc. Each chunk has a `name`, `signature`, code `snippet`, line range, and optionally a vector `embedding`. The `chunk_key` is a unique identifier (e.g. `file_path::kind::name`) for upsert operations. * **`indexed_files`** tracks which files have been indexed and their content hashes, enabling incremental re-indexing — only changed files are re-processed. ## Connecting The FTS index features require an experimental flag at connection time: ```javascript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect(".index/code.db", { experimental: ["index_method"], }); await db.exec(SCHEMA); ``` > [!NOTE] > > The `experimental: ["index_method"]` flag enables Turso's `USING fts` index syntax and the `fts_match()` / `fts_score()` functions. ## Indexing files ### Upserting chunks When a file is parsed, upsert its chunks. Setting `embedding = NULL` on conflict forces re-embedding when content changes: ```sql theme={null} INSERT INTO chunks (codebase_id, file_path, chunk_key, language, kind, name, signature, snippet, start_line, end_line, file_hash, indexed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(chunk_key) DO UPDATE SET language = excluded.language, kind = excluded.kind, name = excluded.name, signature = excluded.signature, snippet = excluded.snippet, start_line = excluded.start_line, end_line = excluded.end_line, file_hash = excluded.file_hash, indexed_at = excluded.indexed_at, embedding = NULL, embedding_model = ''; ``` ### Tracking indexed files ```sql theme={null} INSERT INTO indexed_files (codebase_id, file_path, file_hash, chunk_count, indexed_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(codebase_id, file_path) DO UPDATE SET file_hash = excluded.file_hash, chunk_count = excluded.chunk_count, indexed_at = excluded.indexed_at; ``` ### Skipping unchanged files Before parsing a file, check if it has changed: ```sql theme={null} SELECT file_hash FROM indexed_files WHERE codebase_id = ? AND file_path = ?; ``` If the hash matches, skip it entirely. ## Storing embeddings Embeddings can be stored as int8-quantized vectors using Turso's `vector8()` function, which reduces storage from 1,536 bytes (float32, 384 dims) to 395 bytes: ```sql theme={null} UPDATE chunks SET embedding = vector8(?), embedding_model = ? WHERE chunk_key = ?; ``` The parameter for `vector8()` is a JSON-stringified float array. Turso handles the quantization internally. Find chunks that need embedding: ```sql theme={null} SELECT chunk_key, name, signature, file_path, kind, snippet FROM chunks WHERE codebase_id = ? AND (embedding IS NULL OR embedding_model != ?) LIMIT ?; ``` ## Full-text search Turso provides an FTS index with weighted BM25 scoring that is separate from SQLite's `fts5` virtual tables. ### Creating the FTS index Create an FTS table per codebase and populate it from the chunks: ```sql theme={null} -- Create the FTS table CREATE TABLE IF NOT EXISTS fts_1 ( chunk_id INTEGER NOT NULL REFERENCES chunks(id) ON DELETE CASCADE, name TEXT NOT NULL DEFAULT '', signature TEXT NOT NULL DEFAULT '' ); -- Populate from chunks INSERT INTO fts_1 (chunk_id, name, signature) SELECT id, name, signature FROM chunks WHERE codebase_id = 1; -- Create the FTS index with weighted columns CREATE INDEX IF NOT EXISTS idx_fts_1 ON fts_1 USING fts (name, signature) WITH ( tokenizer = 'default', weights = 'name=5.0,signature=3.0' ); ``` The `weights` parameter controls BM25 scoring — here, matches in the function/type `name` are weighted 5x and `signature` matches 3x. ### Searching with FTS ```sql theme={null} SELECT chunk_id, fts_score(name, signature, ?1) AS score FROM fts_1 WHERE fts_match(name, signature, ?1) ORDER BY score DESC LIMIT ?; ``` Then fetch the full chunk data: ```sql theme={null} SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line FROM chunks WHERE id = ?; ``` ## Vector search For semantic/natural language queries, use vector cosine distance: ```sql theme={null} SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line, vector_distance_cos(embedding, vector8(?)) AS distance FROM chunks WHERE embedding IS NOT NULL ORDER BY distance ASC LIMIT ?; ``` The score is `1 - distance` (cosine similarity from cosine distance). ## Hybrid search For the best results, combine both approaches using Reciprocal Rank Fusion (RRF). Run the FTS and vector queries separately, then merge results in application code: ```javascript theme={null} // Run both searches const ftsResults = await ftsSearch(query, limit); const vecResults = await vectorSearch(queryEmbedding, limit); // Reciprocal Rank Fusion const k = 60; // RRF constant const scores = new Map(); ftsResults.forEach((r, i) => { const key = r.chunk_key; scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1)); }); vecResults.forEach((r, i) => { const key = r.chunk_key; scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1)); }); // Sort by combined score const merged = [...scores.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, limit); ``` ## Removing stale files When files are deleted from the codebase, clean up their chunks and records: ```sql theme={null} DELETE FROM chunks WHERE codebase_id = ? AND file_path = ?; DELETE FROM indexed_files WHERE codebase_id = ? AND file_path = ?; ``` After removing stale data, rebuild the FTS table to keep the index consistent: ```sql theme={null} DROP TABLE IF EXISTS fts_1; -- Then recreate and repopulate as shown above ``` ## Key design points * **Incremental indexing** via file hashes means only changed files are re-parsed and re-embedded, making updates fast even on large codebases. * **Two search modes** — FTS for identifier/keyword lookup and vector search for semantic/natural language queries — cover different use cases. Hybrid search combines both. * **Weighted FTS** (`name=5.0, signature=3.0`) biases results toward function and type names, which is what developers usually search for. * **Int8 quantized vectors** via `vector8()` reduce storage by 75% compared to float32 with minimal impact on search quality. * Everything runs in a single embedded database file with no external services. ## Example [codemogger](https://github.com/glommer/codemogger) is an MCP server that implements this pattern as a code indexer for AI coding agents. # Content Caching Source: https://docs.turso.tech/guides/content-caching Tools and agents frequently re-read files they have already seen, wasting tokens and time. A content-addressed cache backed by an embedded Turso database can detect unchanged files and return diffs instead of full content, dramatically reducing redundant I/O. This guide walks through the schema and queries needed to build a file content cache with per-session tracking. ## Schema The cache uses four tables: one for file versions, one for per-session read pointers, and two for statistics. ```sql theme={null} CREATE TABLE IF NOT EXISTS file_versions ( path TEXT NOT NULL, hash TEXT NOT NULL, content TEXT NOT NULL, lines INTEGER NOT NULL, created_at INTEGER NOT NULL, PRIMARY KEY (path, hash) ); CREATE TABLE IF NOT EXISTS session_reads ( session_id TEXT NOT NULL, path TEXT NOT NULL, hash TEXT NOT NULL, read_at INTEGER NOT NULL, PRIMARY KEY (session_id, path) ); CREATE TABLE IF NOT EXISTS stats ( key TEXT PRIMARY KEY, value INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS session_stats ( session_id TEXT NOT NULL, key TEXT NOT NULL, value INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (session_id, key) ); INSERT OR IGNORE INTO stats (key, value) VALUES ('tokens_saved', 0); ``` ### How it fits together * **`file_versions`** is a content-addressed store. The composite primary key `(path, hash)` means the same file can have multiple versions stored simultaneously. When a file changes and then changes back (e.g. a branch switch), the old version is already cached. * **`session_reads`** tracks which version of each file a given session last saw. This is keyed by `(session_id, path)` so each session independently tracks its own read state. * **`stats`** and **`session_stats`** track cumulative token savings globally and per session. ## Connecting ```javascript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect(".cache/content.db"); await db.exec(SCHEMA); // The CREATE TABLE statements above ``` ## Storing a file version When a file is read for the first time (or has changed), store its content keyed by a hash. `INSERT OR IGNORE` makes this a no-op if the same version already exists: ```sql theme={null} INSERT OR IGNORE INTO file_versions (path, hash, content, lines, created_at) VALUES (?, ?, ?, ?, ?); ``` The hash should be computed from the file content (e.g. a truncated SHA-256). This makes storage content-addressed — identical content is never stored twice for the same path. ## Tracking reads per session Each session needs to know what it last saw for a given file. On first read, record the session's read pointer: ```sql theme={null} INSERT OR REPLACE INTO session_reads (session_id, path, hash, read_at) VALUES (?, ?, ?, ?); ``` On subsequent reads, check what the session last saw: ```sql theme={null} SELECT hash FROM session_reads WHERE session_id = ? AND path = ?; ``` If the stored hash matches the current file hash, the file is unchanged — return a short confirmation instead of the full content. If the hash differs, fetch the old content to compute a diff: ```sql theme={null} SELECT content FROM file_versions WHERE path = ? AND hash = ?; ``` Then update the session's read pointer to the new hash: ```sql theme={null} UPDATE session_reads SET hash = ?, read_at = ? WHERE session_id = ? AND path = ?; ``` ## Tracking token savings Use an atomic counter for global statistics and an upsert for per-session tracking: ```sql theme={null} -- Global counter UPDATE stats SET value = value + ? WHERE key = 'tokens_saved'; -- Per-session counter (upsert) INSERT INTO session_stats (session_id, key, value) VALUES (?, 'tokens_saved', ?) ON CONFLICT(session_id, key) DO UPDATE SET value = value + ?; ``` ## Querying statistics ```sql theme={null} -- Number of distinct files cached SELECT COUNT(DISTINCT path) as file_count FROM file_versions; -- Global tokens saved SELECT value FROM stats WHERE key = 'tokens_saved'; -- Tokens saved in the current session SELECT value FROM session_stats WHERE session_id = ? AND key = 'tokens_saved'; ``` ## Cleanup When a file is deleted from disk, remove all its versions and read pointers: ```sql theme={null} DELETE FROM file_versions WHERE path = ?; DELETE FROM session_reads WHERE path = ?; ``` To clear the entire cache: ```sql theme={null} DELETE FROM file_versions; DELETE FROM session_reads; DELETE FROM session_stats; UPDATE stats SET value = 0; ``` ## Key design points * **Content-addressed storage** means branch switches are handled naturally. If you switch from `main` to a feature branch and back, the original file versions are still in the cache. * **Session isolation** ensures that each consumer independently tracks what it has seen. Session B gets full file content even if Session A already cached it. * **Hash-based change detection** at read time means there is no polling or file watching required for correctness. * An embedded Turso database keeps everything in a single file with zero network overhead. ## Example [cachebro](https://github.com/glommer/cachebro) is an MCP server that implements this pattern as a drop-in file reading cache for AI coding agents. # Secrets Vault Source: https://docs.turso.tech/guides/secrets-vault AI coding agents regularly need real credentials — an API token to call GitHub as you, a key to hit your billing provider, a token to deploy. The path of least resistance is to paste the secret into the chat, but then the value lives in the model's context, the transcript, and your provider's logs forever. A secrets vault backed by an encrypted Turso database lets an agent *use* a secret without ever seeing its value: the secret is injected into a child process, the command runs, and the output is scrubbed before the agent sees it. This guide covers the schema, the encrypted connection, and the queries for a local secrets vault that an agent can use but never read. ## Schema The vault uses two tables: one for the secrets themselves, one for an append-only audit log. ```sql theme={null} CREATE TABLE IF NOT EXISTS secrets ( name TEXT PRIMARY KEY, value TEXT NOT NULL, provider TEXT, account TEXT, environment TEXT, access TEXT, tags TEXT NOT NULL DEFAULT '', description TEXT, created_at TEXT NOT NULL, last_used_at TEXT ); CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, secrets TEXT NOT NULL, command TEXT NOT NULL, cwd TEXT NOT NULL, exit_code INTEGER, outcome TEXT NOT NULL ); ``` ### How it fits together * **`secrets`** stores each credential keyed by `name`. The `value` column holds the secret itself and is only ever read to inject into a child process, never returned to the agent. The metadata columns (`provider`, `account`, `environment`, `access`, `tags`) describe a secret without revealing it, so an agent can reason about "the production database token" by its attributes. * **`audit_log`** is an append-only record of every use: which secrets were injected, the command, the working directory, the exit code, and the outcome (`ran` or `denied`). ## Connecting The vault is a single encrypted database file. Turso encrypts every page at rest when you pass a key at connection time, and `multiprocess_wal` lets several processes (a couple of agent sessions plus a CLI) open the same file safely: ```javascript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("vault.db", { encryption: { cipher: "aes256gcm", hexkey }, experimental: ["multiprocess_wal"], }); await db.exec(SCHEMA); // The CREATE TABLE statements above ``` > [!NOTE] > > `hexkey` is a 64-character hex string. Where it comes from is up to you. You can derive it from a passphrase with scrypt (and never store it), unwrap it from the OS keychain, or fetch it from a cloud KMS. Turso only needs the key at `connect()` time; the storage layer is identical regardless of the source. ## Storing a secret Upsert by `name` so rotating a value keeps the existing metadata: ```sql theme={null} INSERT INTO secrets (name, value, provider, account, environment, access, tags, description, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(name) DO UPDATE SET value = excluded.value, provider = COALESCE(excluded.provider, secrets.provider), account = COALESCE(excluded.account, secrets.account), environment = COALESCE(excluded.environment, secrets.environment), access = COALESCE(excluded.access, secrets.access), description = COALESCE(excluded.description, secrets.description); ``` ## Listing without exposing values The query an agent is allowed to run simply never selects the `value` column: ```sql theme={null} SELECT name, provider, account, environment, access, tags, description, created_at, last_used_at FROM secrets ORDER BY name; ``` This returns secret names and attributes — enough for an agent to pick the right credential — while the value never leaves the database. ## Using a secret To actually use a secret, read its value, inject it into a child process as an environment variable, run the command, and scrub the value out of the output before returning it: ```sql theme={null} SELECT value FROM secrets WHERE name = ?; ``` The injection and scrubbing happen in application code, not the database: ```javascript theme={null} const { value } = await db.prepare( "SELECT value FROM secrets WHERE name = ?" ).get([name]); const result = await runCommand(command, { env: { ...process.env, [name]: value }, }); // Replace every occurrence of the value before the agent sees the output const safe = result.stdout.replaceAll(value, "***"); ``` > [!WARNING] > > Scrubbing captured output is not a sandbox. A command that can read a secret can always leak it on purpose (write it to a file, send it over the network). What the vault guarantees is narrower and still useful: the *model* never receives the value, and a careless `echo $TOKEN` gets redacted. The secret only ever lives in the encrypted file and the short-lived process that used it. ## Auditing every use Record each access as a row, and update the secret's `last_used_at`: ```sql theme={null} INSERT INTO audit_log (ts, secrets, command, cwd, exit_code, outcome) VALUES (?, ?, ?, ?, ?, ?); UPDATE secrets SET last_used_at = ? WHERE name = ?; ``` "Who used the production key, when, and for what" is then an ordinary query: ```sql theme={null} SELECT ts, secrets, command, cwd, exit_code, outcome FROM audit_log ORDER BY id DESC LIMIT ?; ``` ## Key design points * **Encrypted at rest, for free.** Turso handles whole-database encryption (AES-256-GCM, or one of the faster AEGIS ciphers). The vault never touches a cipher or manages page encryption — it derives a key and hands it to `connect()`. * **No coordinating daemon.** `multiprocess_wal` lets each agent session and CLI invocation open the same encrypted file directly, so the database is the shared state instead of a long-running server you have to keep alive. * **The value never appears in a list.** Listing is a `SELECT` that omits the `value` column; the agent sees names and attributes only. * **Fail closed.** An unknown secret name aborts before the command runs, rather than executing with a missing credential. * **Auditing is just a table.** Every use is an `INSERT`; reporting on it is a `SELECT` you can export or alert on. ## Example [keymaxxer](https://github.com/glommer/keymaxxer) is an MCP server that implements this pattern as an encrypted secrets vault for AI coding agents. # Vector Search Source: https://docs.turso.tech/guides/vector-search Build semantic search, recommendation systems, and RAG workflows with native vector search in Turso. Turso supports vector search as a native feature — no extensions required. Store vector embeddings alongside your relational data and query them using built-in distance functions for similarity search. ## Vector Types Turso supports **dense**, **sparse**, **quantized**, and **binary** vector representations, each suited to different workloads. ### Dense Vectors Dense vectors store a value for every dimension. Turso provides two precision levels: | Function | Precision | Storage per dimension | Best for | | - | - | - | - | | `vector32` | 32-bit float | 4 bytes | Most ML embeddings (OpenAI, sentence transformers) | | `vector64` | 64-bit float | 8 bytes | Applications requiring higher precision | ### Sparse Vectors Sparse vectors only store non-zero values and their indices, making them memory-efficient for high-dimensional data with many zero values. | Function | Storage | Best for | | - | - | - | | `vector32_sparse` | Non-zero values + indices | TF-IDF, bag-of-words, high-dimensional sparse data | ### Quantized Vectors | Function | Storage per dimension | Best for | | - | - | - | | `vector8` | 1 byte (+8 bytes overhead) | Large-scale search where \~4x compression vs Float32 is worth minimal precision loss | Values are linearly quantized to the 0-255 range using min/max scaling. Dequantization: `f_i = alpha * q_i + shift`. ### Binary Vectors | Function | Storage per dimension | Best for | | - | - | - | | `vector1bit` | 1 bit | Binary hashing, approximate nearest neighbor search (\~32x compression vs Float32) | Positive values become 1, non-positive values become 0. Extracted values are displayed as +1/-1. > [!NOTE] > > For most applications, `vector32` is a good starting point. Explore more compact types if your table has a large number of rows. ## Storing Vectors Create a table with a BLOB column to store embeddings alongside your relational data: ```sql theme={null} CREATE TABLE documents ( id INTEGER PRIMARY KEY, title TEXT, content TEXT, embedding BLOB ); ``` Insert rows with vector embeddings using the appropriate conversion function: ```sql theme={null} INSERT INTO documents (title, content, embedding) VALUES ('Machine learning basics', 'An introduction to ML concepts...', vector32('[0.2, 0.5, 0.1, 0.8]')), ('Database fundamentals', 'How relational databases work...', vector32('[0.1, 0.3, 0.9, 0.2]')), ('Neural networks guide', 'Deep learning architectures...', vector32('[0.3, 0.6, 0.2, 0.7]')); ``` For sparse vectors, zero values are automatically compressed: ```sql theme={null} INSERT INTO documents (title, content, embedding) VALUES ('Sparse example', 'A document with sparse features...', vector32_sparse('[0.0, 1.5, 0.0, 2.3, 0.0]')); ``` ## Similarity Search Use distance functions to find the most similar vectors. All distance functions require both vectors to have the **same type and dimensionality**. Lower values indicate greater similarity. ### Cosine Distance Measures the angle between vectors, ignoring magnitude. Returns a value between 0 (identical direction) and 2 (opposite direction). ```sql theme={null} SELECT title, vector_distance_cos(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance FROM documents ORDER BY distance LIMIT 5; ``` Best for text embeddings and document similarity where direction matters more than magnitude. ### Euclidean (L2) Distance Measures straight-line distance in n-dimensional space. Not supported for `vector1bit` vectors. ```sql theme={null} SELECT title, vector_distance_l2(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance FROM documents ORDER BY distance LIMIT 5; ``` Best for image embeddings, spatial data, and unnormalized embeddings where absolute differences matter. ### Dot Product Distance Computes the negative dot product: `-sum(v1[i] * v2[i])`. Lower (more negative) values indicate higher similarity. ```sql theme={null} SELECT title, vector_distance_dot(embedding, vector32('[0.25, 0.55, 0.15, 0.75]')) AS distance FROM documents ORDER BY distance LIMIT 5; ``` Best for normalized embeddings (equivalent to cosine distance when vectors are unit-length) and maximum inner product search (MIPS). ### Jaccard Distance Computes weighted Jaccard distance based on the ratio of minimum to maximum values across dimensions. For `vector1bit` vectors, computes binary Jaccard distance. ```sql theme={null} SELECT title, vector_distance_jaccard(embedding, vector32_sparse('[0.0, 1.0, 0.0, 2.0]')) AS distance FROM documents ORDER BY distance LIMIT 5; ``` Best for sparse vectors, set-like comparisons, TF-IDF representations, and binary similarity with `vector1bit`. ## Utility Functions ### vector\_extract Convert a vector BLOB back to a readable JSON representation: ```sql theme={null} SELECT vector_extract(embedding) FROM documents WHERE id = 1; -- [0.200000,0.500000,0.100000,0.800000] ``` ### vector\_concat Concatenate two vectors into one: ```sql theme={null} SELECT vector_extract( vector_concat(vector32('[1.0, 2.0]'), vector32('[3.0, 4.0]')) ); -- [1.000000,2.000000,3.000000,4.000000] ``` ### vector\_slice Extract a contiguous portion of a vector (zero-based, end exclusive): ```sql theme={null} SELECT vector_extract( vector_slice(vector32('[1.0, 2.0, 3.0, 4.0, 5.0]'), 1, 4) ); -- [2.000000,3.000000,4.000000] ``` ## Limitations * Euclidean distance is **not supported** for `vector1bit` vectors * Maximum vector dimensionality is 65,536 * `vector1bit` cosine distance returns Hamming distance (number of differing bits) instead of standard cosine distance ## Example: Semantic Search A complete end-to-end example combining vector storage and similarity search: ```sql theme={null} -- Create a table with vector embeddings CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, content TEXT, embedding BLOB ); -- Insert articles with precomputed embeddings INSERT INTO articles (title, content, embedding) VALUES ('Introduction to Machine Learning', 'ML is a subset of AI...', vector32('[0.12, -0.34, 0.56, 0.78]')), ('Database Design Patterns', 'Relational databases organize...', vector32('[0.82, 0.15, -0.44, 0.23]')), ('Neural Networks Explained', 'Neural networks are computing...', vector32('[0.14, -0.28, 0.62, 0.71]')), ('SQL Query Optimization', 'Efficient queries start with...', vector32('[0.75, 0.22, -0.38, 0.19]')); -- Find the 3 most similar articles to a query vector SELECT title, content, vector_distance_cos(embedding, vector32('[0.10, -0.30, 0.50, 0.80]')) AS distance FROM articles ORDER BY distance LIMIT 3; ``` > [!NOTE] > > Similarity searches use a linear scan over the table. For large datasets, consider limiting the search to a subset of rows with a `WHERE` clause. ## See Also * [Vector Functions](/sql-reference/functions/vector) — full SQL reference for all vector functions * [Data Types](/sql-reference/data-types) — how BLOBs are handled in Turso # Usage & Billing Source: https://docs.turso.tech/help/usage-and-billing For the Starter and Scaler plans, Turso usage is limited monthly based on the following usage observations during each calendar month: * Number of table [rows read](#rows-read) * Number of table [rows written](#rows-written) * Amount of [total storage](#total-storage) > [!NOTE] > > In billing plans that include monthly quotas for row reads, row writes, and total storage, any query that exceeds these limits will result in a failure, indicated by the `BLOCKED` error code. ## Rows Read In SQLite, the term "row read" actually refers to a "row scan" during statement execution. Key points to remember with the Turso CLI metrics: * **SQL Queries:** May scan more rows than returned. * **SQL Updates:** Each updated row results in at least one row scan. ### Aggregate Function Impact Using functions like `count`, `avg`, `min`, `max`, and `sum` results in a row scan for every row considered in the aggregation. > [!NOTE] > > Improve query efficiency by storing aggregate values in a separate table and > updating it transactionally with the base table changes. ### Full Table Scans Queries lacking index support perform a full table scan, incurring a row scan for each table row. > [!NOTE] > > Seek strategies to minimize costly table scans. ### Complex Query Costs Table joins, subqueries, and compound queries incur a row scan for each considered row from all involved tables. ### Update Mechanics SQL updates read (and write) each row they modify. Absent an index for row filtering, a full table scan is performed, adding a read for each table row, plus a write for each updated row. ### `ALTER TABLE` and Row Reads `ALTER TABLE` operations, especially those rewriting row contents, necessitate a full table scan, incurring a read for each table row. However, not all `ALTER TABLE` actions, like `DROP COLUMN`, lead to full scans. Be mindful of potential row writes as well. ### Indexing Costs Adding an index to an existing table triggers a full table scan, with one read per existing row. ### SQLite System Tables Internal SQLite tables, like `dbstat` and those with `sqlite_` prefix, don't incur row reads in queries. ### Zero-Read Commands Commands not involving row reads/writes, like `select 1`, default to one row read. ## Rows Written In SQLite, a "row written" encompasses both the insertion of a new row and the update of an existing row. ### `ALTER TABLE` and Row Writes `ALTER TABLE` operations can result in a row write for each existing row, especially if the row data is altered during the process. It's important to understand how different types of `ALTER TABLE` statements impact row writes. ### Implications of Aborted Transactions Even if a transaction is not committed, any row that was inserted or updated during the transaction incurs a row write. This underscores the importance of transaction management in controlling database writes. > [!NOTE] > > `ALTER TABLE` actions may also lead to row reads, adding another layer to consider when modifying table structures. ## Total Storage SQLite utilizes the virtual table [`dbstat`](https://www.sqlite.org/dbstat.html) to calculate the total space used by all tables and indexes. The base unit for this measurement is a database file page, which is 4KB. > [!NOTE] > > In SQLite, the `VACUUM` command is a common tool for optimizing storage by compacting the database. However, it's important to note that this command is currently disabled in Turso. Future updates may introduce options for developers to efficiently manage and reduce the total storage footprint of their databases. ## Reducing Usage ### Query Execution Familiarizing yourself with the [SQLite query planner](https://www.sqlite.org/queryplanner.html) can significantly enhance your understanding of how your queries are executed. This knowledge is pivotal in optimizing query efficiency. ### Query Planning Utilize the [`EXPLAIN QUERY PLAN`](https://www.sqlite.org/eqp.html) statement to gain insights into your query's execution plan. This tool is invaluable for identifying whether your query is performing a full table scan and if it's leveraging the most efficient index to reduce unnecessary reads. ### Indexing Ensure that your queries are designed to take advantage of indexes for row filtering. The absence of suitable indexes forces SQLite to resort to full table scans, incrementally increasing the read count by one for each row in the table. Efficient indexing is key to minimizing this overhead. Incorporating necessary indexes at the table creation stage is a best practice. Adding indexes to tables that already contain rows triggers a full table scan, with each existing row necessitating one read. Proactive index management is crucial for maintaining optimal database performance. # MCP (AI agents) Source: https://docs.turso.tech/integrations/mcp Connect AI agents to Turso Cloud over the Model Context Protocol — manage databases and run SQL with OAuth, no API token to copy. Turso runs a hosted [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server at **`https://mcp.turso.ai/mcp`**. Connect any MCP-capable AI agent and it can manage your Turso Cloud account — organizations, databases, groups — and run SQL, on your behalf. Authentication is **OAuth 2.1** (the same model you use to log in to the dashboard): you approve access in the browser and the agent receives a scoped token. **There's no API token to copy or paste.** ## What the agent can do Once connected, the agent has tools to: * **Databases** — list, inspect, create, delete, and branch databases (including point-in-time branches), and update configuration (delete-protection, IP/VPC allow rules, size limits). * **SQL** — run read-only queries, writes, deletes, and schema changes, each as a separate tool so the agent uses the least-powerful one for the task. * **Insight** — per-database analytics (top queries by frequency and latency). * **Recovery** — list and restore databases and groups deleted within the last five days, so an agent can undo its own mistakes (see [Recover Deleted Databases](/features/recover-deleted-databases)). Read-only and destructive operations are labeled, so the agent can tell them apart and confirm before doing anything that changes data. ## Connect **Claude Code** Install the plugin, then authenticate: ```bash theme={null} /plugin marketplace add tursodatabase/turso-mcp /plugin install turso@turso ``` Run `/mcp`, select **turso**, and choose **Authenticate**. Your browser opens to approve access (see [Authorize](#authorize-access) below). **Codex** Install the plugin (it bundles the Turso skill too), then authenticate: ```bash theme={null} codex plugin marketplace add tursodatabase/turso-mcp codex plugin add turso@turso codex mcp login turso ``` A browser opens to approve access (see [Authorize](#authorize-access) below). MCP-only alternative: add `[mcp_servers.turso]` with `url = "https://mcp.turso.ai/mcp"` to `~/.codex/config.toml`, then `codex mcp login turso`. **Cursor** Install the plugin (it bundles the Turso skill too): open **Customize → Plugins → Add marketplace**, enter `https://github.com/tursodatabase/turso-mcp`, and install **Turso** from it. Then open **Settings → MCP**, select **turso**, and follow the login prompt to approve access (see [Authorize](#authorize-access) below). One-click alternative (MCP server only, no skill): [**Add to Cursor**](cursor://anysphere.cursor-deeplink/mcp/install?name=turso\&config=eyJ1cmwiOiJodHRwczovL21jcC50dXJzby5haS9tY3AifQ==), or add the server manually to `~/.cursor/mcp.json`: ```json theme={null} { "mcpServers": { "turso": { "url": "https://mcp.turso.ai/mcp" } } } ``` **Cursor web & cloud agents** don't read plugin or `mcp.json` config — add the server once for your account instead: at [cursor.com/agents](https://cursor.com/agents), open the **“+” menu** next to the model picker → hover **MCP Servers** → **Add MCP**, and enter `https://mcp.turso.ai/mcp`. **Claude (web & desktop)** Add a custom connector pointing at the Turso MCP server: 1. Open **Settings → Connectors → Add custom connector**. 2. Set the URL to `https://mcp.turso.ai/mcp` and connect. 3. Approve access in the browser (see [Authorize](#authorize-access) below). **Other MCP clients** Point any MCP client that supports remote (Streamable HTTP) servers with OAuth at: ``` https://mcp.turso.ai/mcp ``` The client discovers the authorization server automatically (RFC 9728 / RFC 8414\) and walks you through the same browser approval. ## Authorize access When the browser opens, you'll log in to Turso (if you aren't already) and land on a consent screen where you choose **what the agent can access**: * the **organization**; * optionally a single **group** to limit the token to — or the whole organization for full access; * for a group, the **permissions** (read-only, full-access, or a custom set). Approve, and the agent is connected. To re-scope or switch organizations, disconnect and authenticate again (in Claude Code: `/mcp` → **turso** → **Clear authentication**). > [!NOTE] > > Picking a **group** scopes the token to that group's resources — the > least-privilege choice. "Entire organization" grants full access. Either way the > token is enforced at Turso's API: every tool call is checked for org-binding, > role, and scope, and recorded in your audit log. ## How it works * The MCP server holds no privilege of its own. Each tool forwards your request to the regular Turso platform API carrying your token, so the same authorization and audit rules apply as any other API access. * The token is **organization- or group-scoped**; unscoped tokens are rejected. * Consent happens on the Turso dashboard — the only place that can see your login session — which mints the scoped token the agent then uses. ## Resources * Plugin source and per-agent configs: [github.com/tursodatabase/turso-mcp](https://github.com/tursodatabase/turso-mcp) # Vercel Source: https://docs.turso.tech/integrations/vercel Use Turso with Vercel serverless and edge functions. Turso offers two approaches for database access from Vercel serverless and edge functions: * **SQL over HTTP** — Every query is sent over the network to the database. Simple to set up and works in any runtime, including edge functions, without filesystem access. This is the model you already know from every other database. * **In-Function SQLite** — Database pages are replicated into the serverless function. Reads execute locally with no network round-trips; writes go directly to the remote database for durability. If you've used Turso's embedded replicas or Cloudflare's Durable Objects with SQLite, this model will feel familiar. See [Bringing SQLite to Vercel Functions](https://turso.tech/blog/serverless) for a deep dive. | | SQL over HTTP | In-Function SQLite | | - | - | - | | **Packages** | `@tursodatabase/serverless`, `@libsql/client` | `@tursodatabase/vercel-experimental` | | **Best for** | Write-heavy workloads, edge runtimes without filesystem | Read-heavy workloads, per-tenant databases | | **Read latency** | Network round-trip per query | In-process SQLite read (zero network hops) | | **Write latency** | Network round-trip | Network round-trip (same) | | **Multi-tenant** | Shared database | Database-per-tenant with automatic on-demand provisioning | | **Runtime requirements** | `fetch` API only | Ephemeral filesystem (`/tmp`) | | **ORM support** | Drizzle, Prisma, and others (`@libsql/client`) | Not yet supported | ## SQL over HTTP Connect to Turso databases from Vercel serverless and edge environments. Configure your Vercel environment with the following variables: * `TURSO_DATABASE_URL` — Your Turso database URL * `TURSO_AUTH_TOKEN` — Your Turso auth token | | `@tursodatabase/serverless` | `@libsql/client` | | - | - | - | | **Use with** | [Turso](/tursodb/quickstart) databases | [libSQL](/libsql) databases | | **Dependencies** | Uses only `fetch` — zero native dependencies | Requires Node.js or `/web` subpath | | **Concurrent writes** | Yes — built into the Turso engine | Not supported | | **ORM support** | Not yet supported | Drizzle, Prisma, and others | Match the driver to your database engine: **`@tursodatabase/serverless`** for [Turso](/tursodb/quickstart) databases, **`@libsql/client`** for [libSQL](/libsql) databases and ORM integration (Drizzle, Prisma). ### Quickstart **@tursodatabase/serverless** The `@tursodatabase/serverless` package connects to Turso using only the `fetch` API — no native dependencies, making it the smallest option for edge and serverless runtimes. ```bash theme={null} npm install @tursodatabase/serverless ``` ```typescript theme={null} import { connect } from "@tursodatabase/serverless"; const conn = connect({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?"); const row = await stmt.get([123]); ``` For compatibility with the libSQL client API, use the compat module: ```typescript theme={null} import { createClient } from "@tursodatabase/serverless/compat"; const client = createClient({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]); ``` **@libsql/client** The `@libsql/client` package is the production-ready libSQL driver used across the Turso ecosystem. Use the `/web` import for Vercel serverless and edge runtimes: ```bash theme={null} npm install @libsql/client ``` ```typescript theme={null} import { createClient } from "@libsql/client/web"; const client = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN!, }); const result = await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [123], }); ``` > [!NOTE] > > Use `@libsql/client/web` instead of `@libsql/client` for Vercel deployments. The `/web` import uses the HTTP protocol, which is compatible with edge and serverless runtimes. ## In-Function SQLite > [!WARNING] > > The `@tursodatabase/vercel-experimental` package is in beta. It may still contain bugs and unexpected behavior. Use caution with production data and ensure you have backups. The `@tursodatabase/vercel-experimental` package replicates SQLite database pages into the serverless function itself. Reads execute against the local copy with no network round-trips. Writes go directly to the remote database for durability and consistency. This approach is ideal for read-heavy workloads where per-query network latency is the dominant cost. * **Per-tenant isolation and sharding** — Each agent, workspace, or session can have its own database with independent storage limits * **Colocation of compute and data** — Business logic runs next to the database, enabling zero-hop reads and many queries per request * **Automatic on-demand provisioning** — Databases are created implicitly when `createDb()` is called, no redeploy or binding management needed If you have used Turso's embedded replicas or Cloudflare’s Durable Objects with SQLite, this model will feel familiar. ### Quickstart Install the package: ```bash theme={null} npm install @tursodatabase/vercel-experimental ``` Get your Turso API token and organization slug: ```bash theme={null} turso auth api-tokens mint my-vercel-token turso org list ``` Add environment variables to your Vercel project: ``` TURSO_API_TOKEN=your-api-token TURSO_ORG=your-org-slug TURSO_GROUP=your-group-name TURSO_DATABASE=your-database-name ``` > [!NOTE] > > The `TURSO_GROUP` environment variable scopes all database access to a single group. Even though the API token grants access to the entire organization, an attacker who controls the database name can only access databases within the configured group. The database name is still recommended to be stored as a secret environment variable when possible. Use `createDb()` to open a database and start querying: ```ts theme={null} import { createDb } from "@tursodatabase/vercel-experimental"; const db = await createDb(process.env.TURSO_DATABASE!); try { // Create tables await db.execute(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE ) `); // Insert data await db.execute( "INSERT INTO users (name, email) VALUES (?, ?)", ["Alice", "alice@example.com"] ); // Query data const result = await db.query("SELECT * FROM users"); console.log(result.rows); } finally { await db.close(); // Syncs and closes the connection } ``` ### Per-tenant databases The auto-provisioning makes it straightforward to implement a database-per-tenant pattern. Instead of routing all tenants through a single shared database, each user or session gets its own isolated SQLite database. Simply derive the database name from a tenant identifier: ```ts theme={null} // Per-user database const db = await createDb(`user-${userId}`); // Per-agent-session database const db = await createDb(`session-${sessionId}`); ``` Because the driver creates databases on demand, there is no provisioning step, no migration to run, and no connection routing to configure. Each tenant's data is fully isolated at the SQLite level, and every database independently benefits from partial-sync reads. ### API Reference **`createDb(name, options?)`** Creates or retrieves a database instance. If the database does not exist, it is created automatically within the configured group (`TURSO_GROUP`). Calling `createDb()` with the same name within a single function invocation returns the existing instance. ```ts theme={null} const db = await createDb(process.env.TURSO_DATABASE!, { remoteWrites: false, // Batch writes locally and push on close() (optional, default: true) }); ``` When `remoteWrites` is `true` (the default), write statements (`INSERT`, `UPDATE`, `DELETE`) are sent directly to the remote Turso database for immediate durability. When set to `false`, writes are applied to the local SQLite copy and pushed in a single batch when `db.push()` or `db.close()` is called. This can be useful for bulk ingestion or background tasks where eventual visibility is acceptable. **`db.query(sql, params?)`** Execute a SELECT query and return results. ```ts theme={null} const result = await db.query("SELECT * FROM users WHERE id = ?", [1]); console.log(result.columns); // ["id", "name", "email"] console.log(result.rows); // [[1, "Alice", "alice@example.com"]] ``` **`db.execute(sql, params?)`** Execute an INSERT, UPDATE, DELETE, or DDL statement. By default, writes are sent directly to the remote Turso database. If `remoteWrites: false` is set, writes are applied locally and pushed on `close()`. ```ts theme={null} await db.execute("UPDATE users SET name = ? WHERE id = ?", ["Bob", 1]); ``` **`db.close()`** Close the connection. If `remoteWrites: false` is set, this also pushes any pending local writes to the remote Turso database. Always call this in a `finally` block. As a safety net, the package registers a callback with Vercel's [`waitUntil()`](https://vercel.com/docs/functions/functions-api-reference#waituntil) API to push pending changes if `close()` is not called within 5 seconds of function completion. ```ts theme={null} const db = await createDb(process.env.TURSO_DATABASE!); try { await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]); } finally { await db.close(); } ``` **`db.push()`** Manually push local changes to the remote Turso database without closing the connection. Only applicable when using `remoteWrites: false`. ```ts theme={null} await db.execute("INSERT INTO users (name) VALUES (?)", ["Charlie"]); await db.push(); // Sync without closing ``` **`db.pull()`** Pull latest changes from the remote Turso database. ```ts theme={null} await db.pull(); ``` # Welcome to Turso Source: https://docs.turso.tech/introduction The small database to power your big dreams in the age of AI. ![Turso Quickstart](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/turso-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=e06c9ad0e66501d523ed87aaecefa6ba) Turso is the small database to power your big dreams in the age of AI. The most efficient way to build for traditional applications, smart devices, embodied AI, agents, and anything in between. ## Get Started Choose your path to get started with Turso: > **[Turso Database (Embedded)](/tursodb/quickstart)** > > An embedded database engine that goes anywhere. Offline, in the browser, or > on-device. > **[AgentFS](/agentfs/introduction)** > > Filesystem and state SDK for AI agents with built-in auditability. > **[Turso Cloud](/turso-cloud)** > > Fully managed platform for Turso and libSQL databases, with branching, > analytics, backups, and more. > **[How Well You Know Turso?](https://www.howwellyouknow.com/play/turso)** > > Test your knowledge of Turso and share your results. > [!NOTE] > > **Need sync?** If you want local reads and writes with push/pull sync to the cloud, start with [Turso Database](/tursodb/quickstart) and [Turso Sync](/sync/usage). ## Turso Database Features The next evolution of SQLite, built for modern applications: > **Vector Search** > > Native similarity search for AI apps and RAG workflows, no extensions > required > **Async Design** > > Modern async primitives such as Linux io\_uring to keep your apps responsive > **SQLite Compatible** > > Fully backwards compatible with SQLite, but built for the next generation > **Concurrent Writes** > > Multiple writers, zero conflicts, without locking > **Richer Type System** > > Richer and stricter type system, including User-Defined Types (experimental) > **Open Contribution** > > Take your seat at the table and contribute to the next evolution of SQLite ## Join Our Communities Connect with developers and the Turso team: > **[Turso Database Core Developers](https://discord.gg/jgjmyYgHwB)** > > Join the core development community for Turso Database discussions > **[Turso Community](https://tur.so/discord)** > > Connect with users and get support for Turso Cloud ### Follow Us > **[GitHub](https://github.com/tursodatabase)** > **[X (Twitter)](https://twitter.com/tursodatabase)** > **[LinkedIn](https://www.linkedin.com/company/turso)** # libSQL Source: https://docs.turso.tech/libsql libSQL is a production-ready fork of SQLite, maintained by Turso. libSQL is a fork of SQLite, created because SQLite is [open-source but not open-contribution](https://www.sqlite.org/copyright.html). libSQL is production-ready, fully backwards compatible with SQLite, and adds features like native vector search. > [!NOTE] > > libSQL represents where we started. Today, our focus is [Turso Database](/tursodb/quickstart), a full rewrite of SQLite built from scratch, designed for the highest density of databases with no need for servers or connectivity. If you're starting a new project, we recommend [Turso Database](/tursodb/quickstart). For mission-critical workloads that need a battle-tested foundation today, libSQL is the right choice. > **[tursodatabase/libsql](https://github.com/tursodatabase/libsql/)** > > Browse the libSQL source code on GitHub, report issues, feature requests and contribute using pull requests. > **[Discord](https://tur.so/discord)** > > Join the community on Discord to talk about the development of libSQL. ## libSQL vs. Turso Database libSQL is a fork of SQLite. It maintains the same file format, the same API, and full backwards compatibility. It extends SQLite with features the ecosystem has long needed but couldn't contribute upstream. [Turso Database](/tursodb/quickstart) is a different approach: a ground-up rewrite of SQLite. It reimplements SQLite's semantics with a modern architecture designed for concurrent writes, async I/O, and the highest database density in the industry. | | libSQL | Turso Database | | - | - | - | | Approach | Fork of SQLite | Full rewrite of SQLite | | Maturity | Production-ready | Production-ready | | SQLite compatibility | Full (same file format and API) | Backwards compatible | | Best for | Mission-critical workloads today | New projects, agents, smart devices, high-density use cases | Both are open-contribution and maintained by Turso, and both can be deployed on [Turso Cloud](/turso-cloud): libSQL has powered it for years, and Turso databases are now available on the platform as well. ## Extensions > [!NOTE] > > If you're looking to enable vector extensions, you should instead consider > using the native [libSQL vector datatype](/features/ai-and-embeddings). A full list of supported extensions can be found [here](/features/sqlite-extensions). # Local Development Source: https://docs.turso.tech/local-development Build locally with a local Turso database, or a local libSQL server. Developers can build locally with Turso using either of the following methods: * [Local Turso database](#local-turso-database) — local database file, no server needed (recommended) * [Turso CLI](#turso-cli) — managed libSQL server ## Using a dump locally You can always dump your production database and use it locally for development: **1. Create a dump using the Turso CLI** ```bash theme={null} turso db shell your-database .dump > dump.sql ``` **2. Create SQLite file from dump** ```bash theme={null} cat dump.sql | sqlite3 local.db ``` **3. Connect to the file** You can use either of the methods below with the `local.db` file, or you can use a new file name if you prefer to create a database from scratch. ## Local Turso database Whenever your application runs against a local database, we recommend the [Turso packages](/sdk). They are fully SQLite-compatible — they open existing SQLite files — and run entirely in your process, no server needed: ```ts JavaScript theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("local.db"); ``` ```rust Rust theme={null} use turso::Builder; let db = Builder::new_local("local.db").build().await?; let conn = db.connect()?; ``` ```go Go theme={null} package main import ( "database/sql" _ "turso.tech/database/tursogo" ) func main() { db, _ := sql.Open("turso", "local.db") defer db.Close() } ``` ```python Python theme={null} import turso db = turso.connect("local.db") ``` > [!NOTE] > > You don't need to provide an `authToken` in development. > [!NOTE] > > It's recommended to use environment variables for both `url` and `authToken` for a seamless developer experience. ## Turso CLI If you're developing against a [libSQL](/libsql) database and use libSQL-specific features like [extensions](/libsql#extensions), you should use the Turso CLI: ```bash theme={null} turso dev ``` This will start a local libSQL server and create a database for you. You can then connect to it with your libSQL client using the `url` option: ```ts JavaScript theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "http://127.0.0.1:8080", }); ``` The same URL works with the libSQL clients for [other languages](/sdk). > [!WARNING] > > Changes will be lost when you stop the server. If you want to persist changes, or use a production dump, you can pass the `--db-file` flag with the name of the SQLite file: ```bash theme={null} turso dev --db-file local.db ``` ## Turso Cloud database If you already have a database created on Turso Cloud, you can use that same one in development by passing the `url` option to your SDK. > [!WARNING] > > Keep in mind that using the Turso Cloud hosted database will incur platform costs and count towards your quota. Consider one of the local methods above to avoid platform costs. ## Connecting a GUI During development you can easily connect to a SQLite, libSQL, or Turso database using one of the tools below: * [Beekeeper Studio](https://www.beekeeperstudio.io/db/libsql-client/) — macOS, Linux, and Windows * [Outerbase](https://www.outerbase.com) — Runs in the browser * [TablePlus](https://tableplus.com) — macOS, Windows, and Linux * [Dataflare](https://dataflare.app) — Paid (with limited free version) macOS, Windows, and Linux * [Outerbase Studio](https://libsqlstudio.com) - Runs in the browser * [DBeaver](https://dbeaver.io) - macOS, Windows, and Linux # Quickstart Source: https://docs.turso.tech/quickstart Welcome to Turso Cloud! Get started in minutes. **In this quickstart you will learn how to:** 1. Install the Turso CLI 2. Signup to Turso Cloud 3. Create your first Turso Cloud database 4. Connect to Database Shell **1. Install the Turso CLI** We'll be using the Turso CLI throughout this quickstart to manage databases and connect to the database shell. ```bash macOS theme={null} brew install tursodatabase/tap/turso ``` ```bash Linux theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` ```bash Windows (WSL) theme={null} curl -sSfL https://get.tur.so/install.sh | bash ``` **2. Signup to Turso Cloud** The next command will open your browser to sign up: ```bash Signup theme={null} turso auth signup ``` ```bash Login theme={null} turso auth login ``` **3. Create a Database** Turso Cloud hosts two SQLite-compatible engines: [Turso](/tursodb/quickstart), with concurrent writes and multi-user access built in, and [libSQL](/libsql). Create your first Turso database with the name `my-db`: ```bash theme={null} turso db create my-db --tursodb ``` To create a libSQL database instead, omit the `--tursodb` flag: ```bash theme={null} turso db create my-db ``` You can inspect your new database using the following command: ```bash theme={null} turso db show my-db ``` **4. Connect to Database Shell** Congratulations, you created a database! Now connect to it with the `shell` command: ```bash theme={null} turso db shell my-db ``` > [!NOTE] > > Replace `my-db` with the name of your database if you named it differently. > **Create table** > > Now create a table for `users` using SQL: > > ```sql theme={null} > CREATE TABLE users ( > ID INTEGER PRIMARY KEY AUTOINCREMENT, > name TEXT > ); > ``` > **Insert into new table** > > Then insert a row into the `users` table: > > ```sql theme={null} > INSERT INTO users (name) VALUES ('Iku'); > ``` > **Select all rows from table** > > Finally, query for all `users`: > > ```sql theme={null} > SELECT * FROM users; > ``` When you're ready to move onto the next step, you'll want to to quit the shell: ```sql theme={null} .quit ``` **5. Connect your application to your database** You're now ready to connect your application to your database. Pick from one of the SDKs below to continue: # Ruby on Rails + Turso Source: https://docs.turso.tech/sdk/activerecord/guides/rails Set up Turso in your Ruby on Rails project in minutes. ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Rails app — [learn more](https://guides.rubyonrails.org/getting_started.html) **1. Install the libsql\_activerecord Rubygem** Add the following to your Gemfile: ```ruby theme={null} gem 'libsql_activerecord' ``` Then run: ```bash theme={null} bundle install ``` **2. Configure database credentials** Add your Turso credentials to your Rails credentials file or environment variables. **3. Use the libsql adapter** ```yml theme={null} default: &default adapter: libsql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default url: <%= ENV['TURSO_DATABASE_URL'] %> auth_token: <%= ENV['TURSO_AUTH_TOKEN'] %> path: storage/development.sqlite3 # For embedded replica ``` **4. Create and run migrations** Generate a migration: ```bash theme={null} rails generate migration CreateProducts name:string description:text ``` Run the migration: ```bash theme={null} rails db:migrate ``` **5. Define models** Create a model in `app/models/product.rb`: ```ruby theme={null} class Product < ApplicationRecord validates :name, presence: true end ``` **6. Use in controllers** In your controllers, you can now use ActiveRecord methods to interact with your Turso database: ```ruby theme={null} class ProductsController < ApplicationController def index @products = Product.all end def create @product = Product.new(product_params) if @product.save redirect_to @product, notice: 'Product was successfully created.' else render :new end end private def product_params params.require(:product).permit(:name, :description) end end ``` **7. Execute raw SQL (if needed)** You can also execute raw SQL queries: ```ruby theme={null} results = ActiveRecord::Base.connection.execute("SELECT * FROM products") ``` ## Examples > **[Ruby on Rails App](https://github.com/tursodatabase/libsql-activerecord/tree/main/examples/railsapp)** > > See the full source code # Turso Quickstart (ActiveRecord) Source: https://docs.turso.tech/sdk/activerecord/quickstart Get started with Turso and ActiveRecord in a few simple steps. In this Ruby quickstart we will learn how to: * Retrieve database credentials * Install the libSQL ActiveRecord gem * Connect to a local or remote Turso database * Define and create models * Perform basic CRUD operations * Execute raw SQL queries * Work with migrations **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). **2. Install** In your Ruby project, add the following gems to your Gemfile: ```ruby theme={null} gem 'libsql_activerecord' gem 'activerecord' ``` Then run: ```bash theme={null} bundle install ``` **3. Connect** Create a Ruby file (e.g., `database.rb`) to set up the database connection: > **Embedded Replicas** > > ```rb theme={null} > require 'libsql_activerecord' > require 'active_record' > > ActiveRecord::Base.establish_connection( > adapter: 'libsql', > url: ENV['TURSO_DATABASE_URL'], > auth_token: ENV['TURSO_AUTH_TOKEN'], > path: 'path/to/local/replica.db' > ) > ``` > **Local only** > > ```rb theme={null} > require 'libsql_activerecord' > require 'active_record' > > ActiveRecord::Base.establish_connection( > adapter: 'libsql', > path: 'path/to/local.db' > ) > ``` > **Remote only** > > ```rb theme={null} > require 'libsql_activerecord' > require 'active_record' > > ActiveRecord::Base.establish_connection( > adapter: 'libsql', > url: ENV['TURSO_DATABASE_URL'], > auth_token: ENV['TURSO_AUTH_TOKEN'] > ) > ``` > **In-memory** > > ```rb theme={null} > require 'libsql_activerecord' > require 'active_record' > > ActiveRecord::Base.establish_connection(adapter: 'libsql') > ``` **4. Define models** Create model files for your database tables. For example, `product.rb`: ```rb theme={null} class Product < ActiveRecord::Base validates :name, presence: true end ``` **5. Create and execute a migration** Create a migration file (e.g., `001_create_products.rb`): ```rb theme={null} class CreateProducts < ActiveRecord::Migration[8.0] def change create_table :products do |t| t.string :name t.text :description t.timestamps end end end ``` Execute the migration: ```rb theme={null} require_relative 'database' require_relative '001_create_products' CreateProducts.migrate(:up) ``` **6. Execute** Perform some basic CRUD operations: ```rb Create theme={null} product = Product.create(name: 'Book', description: 'A book about books') ``` ```rb Read theme={null} all_products = Product.all first_product = Product.first found_product = Product.find_by(name: 'Book') ``` ```rb Update theme={null} product.update(description: 'An updated description') ``` ```rb Delete theme={null} product.destroy ``` ```rb Raw SQL theme={null} results = ActiveRecord::Base.connection.execute("SELECT * FROM products WHERE name LIKE '%Book%'") results.each do |row| puts row.inspect end ``` **7. Work with associations** Define associations in your models: ```ruby theme={null} class Author < ActiveRecord::Base has_many :books end class Book < ActiveRecord::Base belongs_to :author end ``` Use associations in your code: ```ruby theme={null} author = Author.create(name: 'Jane Doe') book = author.books.create(title: 'My First Book') puts author.books.count # => 1 puts book.author.name # => "Jane Doe" ``` # Authentication Source: https://docs.turso.tech/sdk/authentication SDKs connect using the `turso://` protocol for [Turso](/tursodb/quickstart) databases and the `libsql://` protocol for [libSQL](/libsql) databases, unless using the [HTTP API](/sdk/http). ## Database URL You can find your database URL using the [Turso CLI](/cli/db/show) or [Platform API](/api-reference/databases/retrieve), it looks something like this: ```bash Turso theme={null} turso://[DB-NAME]-[ORG-NAME].turso.io ``` ```bash libSQL theme={null} libsql://[DB-NAME]-[ORG-NAME].turso.io ``` ```bash HTTPS theme={null} https://[DB-NAME]-[ORG-NAME].turso.io ``` > [!NOTE] > > WebSockets generally offer better performance when maintaining an open socket for multiple queries, whereas HTTP is typically more efficient for single queries due to reduced client-server round trips — it's advisable to benchmark both to ascertain the best option for your specific needs. ## Auth Tokens SDKs require an auth token, unless [working locally](/local-development). You can create tokens using the [Turso CLI](/cli) or [Platform API](/api-reference), or let your authentication provider issue tokens via [JWKS](/sdk/authorization#jwks-authentication-providers). See [Authorization](/sdk/authorization) for full details on token creation, fine-grained permissions, and JWKS setup. # Authorization Source: https://docs.turso.tech/sdk/authorization Turso uses scoped, JWT-based tokens to control access to your databases. Every token can be restricted by database, permission level, and expiration — giving you full control over what each client can and cannot do. ## Scoping Levels Tokens are scoped at multiple levels, from broad to narrow: | Level | Scope | How to create | | - | - | - | | **Group** | Access all databases in a group | `turso group tokens create ` | | **Database** | Access a single database | `turso db tokens create ` | | **Read-only** | Queries only, no writes | Add `--read-only` flag | | **Table + Action** | Specific tables and operations | Add `-p :` flag | | **Time-limited** | Auto-expires after a duration | Add `--expiration 7d` flag | These can be combined. For example, a read-only token scoped to a single database that expires in 7 days: ```bash theme={null} turso db tokens create mydb --read-only --expiration 7d ``` Or a token that only allows reading from all tables and inserting into `comments`: ```bash theme={null} turso db tokens create mydb \ -p all:data_read \ -p comments:data_add ``` ## Issuing Tokens There are two ways to issue tokens: * **[Platform Tokens](/sdk/authorization/tokens)** — Create tokens directly via the Turso CLI or Platform API. * **[External Auth Providers](/sdk/authorization/jwks)** — Let your authentication provider (e.g., Clerk, Auth0) issue tokens using JWKS. Both approaches support [fine-grained permissions](/sdk/authorization/fine-grained-permissions) to control access at the table and action level. ## Using Tokens All tokens are passed as the `authToken` when creating a database client: ```javascript theme={null} import { connect } from "@tursodatabase/serverless"; const db = connect({ url: "", authToken: "", }); ``` > [!NOTE] > > You can get your database URL with `turso db show --url`. # Fine-Grained Permissions Source: https://docs.turso.tech/sdk/authorization/fine-grained-permissions Fine-grained permissions let you control access at the table and action level. They work with both [CLI-generated tokens](/sdk/authorization/tokens) and [JWKS-issued tokens](/sdk/authorization/jwks). ## Permission Format Permissions follow the format `:,`: ```bash theme={null} # Read-only access to all tables turso db tokens create mydb -p all:data_read # Specific actions on a specific table turso db tokens create mydb -p users:data_read,data_update # Multiple permission rules turso db tokens create mydb \ -p all:data_read \ -p comments:data_add,data_update \ -p posts:data_add,data_update,data_delete ``` Permission rules only grant access. There are no deny rules. Use `all` as the table name to apply permissions to every table. ## Available Actions | Action | Description | | - | - | | `data_read` | Read data from tables | | `data_add` | Insert new rows | | `data_update` | Update existing rows | | `data_delete` | Delete rows | | `schema_add` | Create tables, views, indexes, and triggers | | `schema_update` | Change tables with `ALTER TABLE` | | `schema_delete` | Drop tables, views, indexes, and triggers | > [!NOTE] > > `data_read` is allowed on SQLite system tables (e.g., `sqlite_master`, > `sqlite_schema`) by default, allowing users to query database metadata. > [!WARNING] > > `data_add` also allows replacing existing rows through `INSERT OR REPLACE` or `REPLACE INTO`. ## Schema Actions Each schema statement needs a schema action on a specific table name: | Statement | Action | Table name in the rule | | - | - | - | | `CREATE TABLE`, `CREATE VIEW` | `schema_add` | The new table or view | | `CREATE INDEX`, `CREATE TRIGGER` | `schema_add` | The table of the index or trigger | | `ALTER TABLE` (`ADD COLUMN`, `RENAME TO`, `RENAME COLUMN`, `DROP COLUMN`) | `schema_update` | The altered table | | `DROP INDEX`, `DROP TRIGGER` | `schema_delete` | The table of the index or trigger | | `DROP TABLE`, `DROP VIEW` | `schema_delete` | The dropped table. `DROP TABLE` also requires `data_delete` on the table. | ### Example This token can read all tables. It can change `posts` with `ALTER TABLE`, and create or drop indexes and triggers on `posts`. It cannot drop the `posts` table, because it does not have `data_delete` on `posts`: ```bash theme={null} turso db tokens create mydb \ -p all:data_read \ -p posts:schema_add,schema_update,schema_delete ``` ## Role-Based Access Example You can configure your JWT template to grant different permissions based on user roles: | Role | Permissions | | - | - | | Admin | `all:data_read,data_add,data_update,data_delete,schema_add,schema_update,schema_delete` | | Moderator | `all:data_read,data_update` | | User | `all:data_read` | With JWKS, these permissions are embedded in the JWT token based on user metadata (e.g., role, group membership) from your auth provider. # External Auth Providers Source: https://docs.turso.tech/sdk/authorization/jwks Instead of managing tokens manually, you can let your authentication provider issue JWT tokens using JWKS. This allows you to leverage your existing auth infrastructure (e.g., Clerk, Auth0). > [!NOTE] > > During the Turso Beta, we only support Clerk & Auth0 as OIDC providers. **1. Generate JWT Template** Use the Turso CLI to generate a JWT claims template for your auth provider: ```bash theme={null} # Full access to a database turso org jwks template --database --scope full-access # Read-only access to a group turso org jwks template --group --scope read-only # Fine-grained permissions turso org jwks template \ --database \ --permissions all:data_read \ --permissions comments:data_add ``` Copy the generated template into your auth provider's JWT configuration. **2. Add JWKS Endpoint to Turso** Register your auth provider's JWKS endpoint with your Turso organization: ```bash theme={null} turso org jwks save ``` For example, with Clerk: ```bash theme={null} turso org jwks save clerk https://your-app.clerk.accounts.dev/.well-known/jwks.json ``` You can also add JWKS endpoints in the [Turso Dashboard](https://turso.tech/app) under organization settings. **3. Use Tokens in Your Application** Get the JWT from your auth provider and pass it as the `authToken`: ```javascript theme={null} import { connect } from "@tursodatabase/serverless"; const authToken = await getAuthToken(); // e.g., from Clerk, Auth0 const db = connect({ url: "", authToken, }); ``` ## Managing JWKS Endpoints ```bash theme={null} # List all JWKS endpoints turso org jwks list # Remove a JWKS endpoint turso org jwks remove ``` > [!WARNING] > > If you don't setup a JWT template with specific permissions, the generated > tokens will have access to **all databases in all groups** by default. # Platform Tokens Source: https://docs.turso.tech/sdk/authorization/tokens Create tokens for a single database or all databases in a group using the CLI: ```bash theme={null} # Token for a single database turso db tokens create # Token for all databases in a group turso group tokens create ``` You can control token scope, permissions, and expiration: | Flag | Description | | - | - | | `-r`, `--read-only` | Create a read-only token (queries only, no writes) | | `-p`, `--permissions` | Set [fine-grained permissions](/sdk/authorization/fine-grained-permissions) per table and action | | `-e`, `--expiration` | Set token expiration (`never`, `7d`, `30d`, etc.) | ```bash Read-only token theme={null} turso db tokens create mydb --read-only ``` ```bash Token with 7-day expiration theme={null} turso db tokens create mydb --expiration 7d ``` ```bash Fine-grained permissions theme={null} turso db tokens create mydb \ -p all:data_read \ -p comments:data_add,data_update ``` ```bash Combined scoping theme={null} turso db tokens create mydb \ --read-only \ --expiration 7d ``` You can also create tokens using the [Platform API](/api-reference/databases/create-token). ## Invalidating Tokens You can invalidate all existing tokens for a database or group, which rotates the signing keys: ```bash theme={null} # Invalidate all tokens for a database turso db tokens invalidate # Invalidate all tokens for a group (and all its databases) turso group tokens invalidate ``` # Turso Examples (C) Source: https://docs.turso.tech/sdk/c/examples | Example | Description | | - | - | | [local](https://github.com/tursodatabase/libsql-c/tree/master/examples/local) | Uses libsql with a local SQLite file. Creates database, inserts data, and queries. | | [remote](https://github.com/tursodatabase/libsql-c/tree/master/examples/remote) | Connects to a remote database. Requires environment variables for URL and auth token. | | [sync](https://github.com/tursodatabase/libsql-c/tree/master/examples/sync) | Demonstrates synchronization between local and remote databases. | | [batch](https://github.com/tursodatabase/libsql-c/tree/master/examples/batch) | Executes multiple SQL statements in a single batch operation. | | [transactions](https://github.com/tursodatabase/libsql-c/tree/master/examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back. | | [memory](https://github.com/tursodatabase/libsql-c/tree/master/examples/memory) | Uses an in-memory SQLite database for temporary storage or fast access. | | [vector](https://github.com/tursodatabase/libsql-c/tree/master/examples/vector) | Works with vector embeddings, storing and querying for similarity search. | | [encryption](https://github.com/tursodatabase/libsql-c/tree/master/examples/encryption) | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. | # Turso Quickstart (C) Source: https://docs.turso.tech/sdk/c/quickstart Get started with Turso and C using the libSQL client in a few simple steps. In this C quickstart we will learn how to: * Retrieve database credentials * Install the libSQL package * Connect to a local or remote Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). **2. Install** To use libSQL with C, you need to build the library from source: > **[libSQL C](https://github.com/tursodatabase/libsql)** > > Build from source code After building, make sure to link against the library when compiling your C program: ```bash theme={null} gcc -o your_program your_program.c -L/path/to/libsql/lib -llibsql ``` **3. Connect** You must first initialize libSQL, create a `libsql_database_t` object, and then open a `libsql_connection_t` to it: > **Embedded Replicas** > > ```c theme={null} > #include "libsql.h" > > libsql_setup((libsql_config_t){0}); > > libsql_database_t db = libsql_database_init((libsql_database_desc_t){ > .path = "local.db", > .url = "TURSO_DATABASE_URL", > .auth_token = "TURSO_AUTH_TOKEN", > .sync_interval = 300 > }); > > libsql_connection_t conn = libsql_database_connect(db); > ``` > **Local only** > > ```c theme={null} > #include "libsql.h" > > libsql_setup((libsql_config_t){0}); > > libsql_database_t db = libsql_database_init((libsql_database_desc_t){ > .path = "local.db" > }); > > libsql_connection_t conn = libsql_database_connect(db); > ``` > **Remote only** > > ```c theme={null} > #include "libsql.h" > > libsql_setup((libsql_config_t){0}); > > libsql_database_t db = libsql_database_init((libsql_database_desc_t){ > .url = "TURSO_DATABASE_URL", > .auth_token = "TURSO_AUTH_TOKEN" > }); > > libsql_connection_t conn = libsql_database_connect(db); > ``` **4. Execute** You can execute a SQL query against your existing database by preparing a statement and then executing it: ```c theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users"); libsql_rows_t rows = libsql_statement_query(stmt); ``` If you need to use placeholders for values, you can do that: ```c Positional Query theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = ?"); libsql_statement_bind_value(stmt, libsql_integer(1)); libsql_rows_t rows = libsql_statement_query(stmt); ``` ```c Positional Insert theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "INSERT INTO users (id) VALUES (?)"); libsql_statement_bind_value(stmt, libsql_integer(1)); libsql_execute_t result = libsql_statement_execute(stmt); ``` **5. Sync (Embedded Replicas only)** When using embedded replicas, you should call `libsql_database_sync()` on the database to sync your local database with the primary database, unless you are using `sync_interval` (though there is no issue with calling `sync` with `sync_interval` enabled): ```c theme={null} libsql_sync_t sync = libsql_database_sync(db); if (sync.err) { fprintf(stderr, "Error syncing database: %s\n", libsql_error_message(sync.err)); } else { printf("Sync completed. Frame number: %llu, Frames synced: %llu\n", (unsigned long long)sync.frame_no, (unsigned long long)sync.frames_synced); } ``` # C Reference Source: https://docs.turso.tech/sdk/c/reference libSQL C Reference ## Installing To use the libSQL C bindings, you need to include the `libsql.h` header file in your project and link against the `liblibsql.so` shared library. > **[libSQL C](https://github.com/tursodatabase/libsql)** > > Build from source code Before using libSQL, you need to call the setup function: ```c theme={null} libsql_setup((libsql_config_t){0}); ``` ## In-Memory Databases libSQL supports connecting to \[in-memory databases]\([https://www.sqlite.org/inmemorydb.htm](https://www.sqlite.org/inmemorydb.htm) databases for cases where you don't require persistence: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){.path = ":memory:"}); libsql_connection_t conn = libsql_database_connect(db); ``` ## Local Development You can work locally using an SQLite file: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){.path = "local.db"}); libsql_connection_t conn = libsql_database_connect(db); ``` ## Remote Databases You can connect to remote databases using a URL and auth token: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){ .url = "TURSO_DATABASE_URL", .auth_token = "TURSO_AUTH_TOKEN" }); libsql_connection_t conn = libsql_database_connect(db); ``` ## Embedded Replicas You can work with embedded replicas that can sync from the remote URL and delegate writes to the remote primary database: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){ .path = "local.db", .url = "TURSO_DATABASE_URL", .auth_token = "TURSO_AUTH_TOKEN" }); libsql_connection_t conn = libsql_database_connect(db); ``` ### Manual Sync The `libsql_database_sync` function allows you to sync manually the local database with the remote counterpart: ```c theme={null} libsql_sync_t sync = libsql_database_sync(db); ``` ### Sync Interval The `sync_interval` parameter in the database description allows you to set an interval for automatic synchronization of the database in the background: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){ .path = "local.db", .url = "TURSO_DATABASE_URL", .auth_token = "TURSO_AUTH_TOKEN", .sync_interval = 60000 }); ``` ### Read Your Own Writes The `not_read_your_writes` parameter in the database description configures the database connection to ensure that writes are immediately visible to subsequent read operations initiated by the same connection. This is **enabled by default**, and you can disable it by setting `not_read_your_writes` to `true`: ```c theme={null} libsql_database_t db = libsql_database_init((libsql_database_desc_t){ .path = "local.db", .url = "TURSO_DATABASE_URL", .auth_token = "TURSO_AUTH_TOKEN", .not_read_your_writes = true }); ``` ## Simple query You can use `libsql_connection_batch` for simple queries without parameters: ```c theme={null} libsql_batch_t batch = libsql_connection_batch(conn, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"); ``` For queries with results, use `libsql_connection_prepare` and `libsql_statement_query`: ```c theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users"); libsql_rows_t rows = libsql_statement_query(stmt); ``` ## Prepared Statements You can prepare a statement using `libsql_connection_prepare` and then execute it with `libsql_statement_execute` or `libsql_statement_query`: ```c theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "INSERT INTO users (name) VALUES (?)"); libsql_statement_bind_value(stmt, libsql_text("John Doe", 8)); libsql_execute_t result = libsql_statement_execute(stmt); ``` ## Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```c Positional theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = ?"); libsql_statement_bind_value(stmt, libsql_integer(1)); ``` ```c Named theme={null} libsql_statement_t stmt = libsql_connection_prepare(conn, "SELECT * FROM users WHERE id = :id"); libsql_statement_bind_named(stmt, ":id", libsql_integer(1)); ``` ## Transactions libSQL supports transactions: ```c theme={null} libsql_transaction_t tx = libsql_connection_transaction(conn); // Perform operations within the transaction libsql_transaction_commit(tx); // or libsql_transaction_rollback(tx) ``` ## Cleanup Remember to clean up resources when you're done: ```c theme={null} libsql_statement_deinit(stmt); libsql_connection_deinit(conn); libsql_database_deinit(db); ``` # Examples Source: https://docs.turso.tech/sdk/flutter/examples # Drift + Turso / LibSQL Source: https://docs.turso.tech/sdk/flutter/integrations/drift Configure `drift` to work with `libsql_dart` ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) **1. Add packages to your project** ```bash theme={null} flutter pub add drift_libsql drift drift_flutter dev:build_runner dev:drift_dev ``` **2. Retrieve database credentials** **3. Create a table schema** ```dart theme={null} class TaskTable extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text()(); TextColumn get description => text()(); BoolColumn get completed => boolean()(); } ``` **4. Create db class** ```dart theme={null} @DriftDatabase(tables: [TaskTable]) class AppDatabase extends _$AppDatabase { AppDatabase(super.e); @override int get schemaVersion => 1; } ``` **5. Run build runner** ```bash theme={null} dart run build_runner build ``` **6. Create the db** ```dart theme={null} final db = AppDatabase(DriftLibsqlDatabase( "${dir.path}/replica.db", syncUrl: url, authToken: token, readYourWrites: true, syncIntervalSeconds: 3, )); ``` **7. Perform SQL operations** ```dart theme={null} await db.into(db.taskTable).insert(TaskTableCompanion.insert( title: task.title, description: task.description, completed: task.completed)); ``` # Turso Quickstart (Flutter / Dart) Source: https://docs.turso.tech/sdk/flutter/quickstart Get started with Flutter and Dart using the libSQL client in a few simple steps > [!NOTE] > > This SDK is community maintained and may not be officially supported by Turso, or up to date with the latest features. Join the `#libsql-dart` channel [on Discord](https://tur.so/discord) for help and feedback. In this Flutter/Dart quickstart we will learn how to: * Retrieve database credentials * Install the Flutter/Dart libSQL client * Connect to a local or remote Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install libsql\_dart** ```bash theme={null} flutter pub add libsql_dart ``` **3. Initialize** Now connect to your local or remote database using the `LibsqlClient` constructor: > **Embedded Replicas** > > ```dart theme={null} > final dir = await getApplicationCacheDirectory(); > final path = '${dir.path}/local.db'; > > final client = LibsqlClient(path) > ..authToken = '' > ..syncUrl = '' > ..syncIntervalSeconds = 5 > ..readYourWrites = true; > ``` > **Local only** > > ```dart File theme={null} > final dir = await getApplicationCacheDirectory(); > final path = '${dir.path}/local.db'; > > final client = LibsqlClient(path); > ``` > > ```dart In-Memory theme={null} > final client = LibsqlClient(":memory:"); > ``` > **Remote only** > > ```dart theme={null} > final client = LibsqlClient('') > ..authToken = ''; > ``` **4. Connect** ```dart theme={null} await client.connect(); ``` **5. Execute** ```dart Execute theme={null} await client.execute("create table if not exists customers (id integer primary key, name text);"); ``` ```dart Insert theme={null} await client.query("insert into customers(name) values ('John Doe')"); ``` ```dart Select theme={null} print(await client.query("select * from customers")); ``` ```dart Batch theme={null} await client.batch("""insert into customers (name) values ('Jane Doe'); insert into customers (name) values ('Jake Doe');"""); ``` ```dart Prepared statement theme={null} final statement = await client .prepare("select * from customers where id = ?"); await statement.query(positional: [1]) ``` ```dart Interactive transaction theme={null} final tx = await client.transaction(); await tx .execute("update customers set name = 'John Noe' where id = 1"); await tx .execute("update customers set name = 'Jane Noe' where id = 2"); print(await tx .query("select * from customers where id = ?", positional: [1])); await tx.commit(); ``` If you need to use placeholders for values, you can do that: ```dart Positional theme={null} final statement = await client .prepare("select * from customers where id = ?"); await statement.query(positional: [1]) ``` ```dart Named theme={null} final statement = await client .prepare("select * from customers where id = :id"); await statement.query(named: {"id": 1}) ``` **6. Sync (Embedded Replicas only)** When using embedded replicas you should call `sync()` on the connector to sync your local database with the primary database. ```dart theme={null} await client.sync(); ``` # Reference Source: https://docs.turso.tech/sdk/flutter/reference libSQL Flutter / Dart Reference The libSQL package for Flutter / Dart contains everything you need to work with Turso / libSQL and works flawlessly with all features, because it’s build on top libSQL Rust crate and `flutter_rust_bridge` package, allowing for seamless communication between Rust and Dart. ## Add the package to your project ```bash theme={null} flutter pub add libsql_dart ``` Alternatively, manually add it to your project's `pubspec.yaml` ```yaml theme={null} libsql_dart: ``` ## Initializing Call `LibsqlClient` constructor to create the database client. Different configurations are supported, allowing connection to in-memory database, local sqlite file, remote Turso / libSQL database, or embedded replica. ## In-Memory Databases libSQL supports connecting to [in-memory databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't require persistence: ```dart theme={null} final client = LibsqlClient(":memory:"); ``` ## Local Development You can work locally using an SQLite file and passing the path to `LibsqlClient`: ```dart theme={null} final dir = await getApplicationCacheDirectory(); final path = '${dir.path}/local.db'; final client = LibsqlClient(path); ``` ## Remote You can work with remote database by passing your Turso Database URL: ```dart theme={null} final client = LibsqlClient('') ..authToken = ''; ``` ## Embedded Replicas You can work with embedded replicas by passing your Turso Database URL to `syncUrl`: ```dart theme={null} final dir = await getApplicationCacheDirectory(); final path = '${dir.path}/local.db'; final client = LibsqlClient(path) ..authToken = '' ..syncUrl = '' ..readYourWrites = true; ``` ### Connect ```dart theme={null} await client.connect(); ``` ### Manual Sync The `sync()` function allows you to sync manually the local database with the remote counterpart: ```ts theme={null} await client.sync(); ``` ### Periodic Sync You can automatically sync at intervals by configuring the `syncIntervalSeconds` property when instantiating the client: ```dart theme={null} final dir = await getApplicationCacheDirectory(); final path = '${dir.path}/local.db'; final client = LibsqlClient(path) ..authToken = '' ..syncUrl = '' ..syncIntervalSeconds = 5 ..readYourWrites = true; ``` ### Encryption at rest To enable encryption on a SQLite file, pass the `encryptionKey`: ```dart theme={null} final dir = await getApplicationCacheDirectory(); final path = '${dir.path}/local.db'; final client = LibsqlClient(path)..encryptionKey = ''; ``` Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest). ## Execute Returns number of rows affected: ```dart theme={null} await client.execute("create table if not exists customers (id integer primary key, name text);"); ``` ## Query Returns rows as `List>`. Will returns empty list when is not performing select query: ```dart theme={null} await client.query("insert into customers(name) values ('John Doe')"); print(await client.query("select * from customers")); ``` ## Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```dart Positional theme={null} final statement = await client .prepare("select * from customers where id = ?"); await statement.query(positional: [1]) ``` ```dart Named theme={null} final statement = await client .prepare("select * from customers where id = :id"); await statement.query(named: {"id": 1}) ``` > [!NOTE] > > libSQL supports the same named placeholder characters as SQLite — `:`, `@` and `$`. ## Batch Transactions A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications. ```dart theme={null} await client.batch("""insert into customers (name) values ('Jane Doe'); insert into customers (name) values ('Jake Doe');"""); ``` ## Transaction Modes | Mode | SQLite command | Description | | - | - | - | | `LibsqlTransactionBehavior.immediate` | `BEGIN IMMEDIATE` | The transaction may execute statements that read and write data. Write transactions executed on a replica are forwarded to the primary instance, and can't operate in parallel. | | `LibsqlTransactionBehavior.readOnly` | `BEGIN TRANSACTION READONLY` | The transaction may only execute statements that read data (select). Read transactions can occur on replicas, and can operate in parallel with other read transactions. | | `LibsqlTransactionBehavior.deferred_` | `BEGIN DEFERRED` | The transaction starts in read mode, then changes to write as soon as a write statement is executed. This mode change may fail if there is a write transaction currently executing on the primary. | ## Interactive Transactions Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity. | Method | Description | | - | - | | `execute()` | Similar to `execute()` except within the context of the transaction | | `query()` | Similar to `query()` except within the context of the transaction | | `commit()` | Commits all write statements in the transaction | | `rollback()` | Rolls back the entire transaction | ```dart theme={null} final tx = await client.transaction(); await tx .execute("update customers set name = 'John Noe' where id = 1"); await tx .execute("update customers set name = 'Jane Noe' where id = 2"); print(await tx .query("select * from customers where id = ?", positional: [1])); await tx.commit(); ``` > [!WARNING] > > Interactive transactions in libSQL lock the database for writing until > committed or rolled back, with a 5-second timeout. They can impact performance > on high-latency or busy databases. ## ATTACH You can attach multiple databases to the current connection using the `ATTACH` attachment: ```dart theme={null} final tx = await client.transaction(behavior: LibsqlTransactionBehavior.readOnly); await tx.execute("ATTACH "" AS attached"); print(await tx.execute("SELECT * FROM attached.customers")); await tx.commit(); ``` > [!NOTE] > > Make sure to [allow `ATTACH`](/cli/db/config/attach/allow) and create a token > with the permission to attach a database — [learn > more](/features/attach-database) # Examples Source: https://docs.turso.tech/sdk/go/examples # Turso Quickstart (Go) Source: https://docs.turso.tech/sdk/go/quickstart Get started with Turso and Go in a few simple steps. In this Go quickstart we will learn how to: * Install the Turso package * Connect to a local or remote database * Execute a query using SQL * Sync changes to the cloud ## Recommended: tursogo (Local + Cloud Sync) `tursogo` is the recommended package for running a local database, including synchronizing it to and from Turso Cloud. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O. It implements the standard `database/sql` driver interface, so it works as a drop-in replacement for any Go SQLite driver. It ships prebuilt native libraries and uses [purego](https://github.com/ebitengine/purego) for FFI — no CGO required. **1. Install** ```bash theme={null} go get turso.tech/database/tursogo ``` **2. Connect** ```go theme={null} package main import ( "database/sql" "fmt" _ "turso.tech/database/tursogo" ) func main() { db, _ := sql.Open("turso", "app.db") defer db.Close() db.Exec(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL )`) db.Exec("INSERT INTO users (name) VALUES (?)", "Alice") rows, _ := db.Query("SELECT * FROM users") defer rows.Close() for rows.Next() { var id int var name string rows.Scan(&id, &name) fmt.Printf("User: %d %s\n", id, name) } } ``` **3. Sync (push and pull)** If you need to sync your local database with Turso Cloud, use the sync driver: ```go theme={null} package main import ( "context" "os" turso "turso.tech/database/tursogo" ) func main() { ctx := context.Background() syncDb, _ := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "app.db", RemoteUrl: os.Getenv("TURSO_DATABASE_URL"), AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), }) db, _ := syncDb.Connect(ctx) defer db.Close() db.ExecContext(ctx, "INSERT INTO users (name) VALUES (?)", "Bob") // Push local writes to Turso Cloud syncDb.Push(ctx) // Pull remote changes to local database syncDb.Pull(ctx) } ``` All reads and writes happen against the local database file — fast, offline-capable. `Push()` sends your changes to the cloud. `Pull()` brings remote changes down. See the [reference](/sdk/go/reference) for checkpoint, stats, and encryption. See [Turso Sync](/sync/usage) for details on conflict resolution and more. > [!NOTE] > > You can test sync locally without a Turso Cloud account by starting a local sync server: > > ```bash theme={null} > tursodb :memory: --sync-server 127.0.0.1:8080 > ``` > > Then set `RemoteUrl` to `http://127.0.0.1:8080` (no `AuthToken` needed). See [Turso Database quickstart](/tursodb/quickstart) for how to install `tursodb`. ## Remote Access (Over-the-Wire) If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the driver that matches your database engine: `tursogo-serverless` for [Turso databases](/tursodb/quickstart), or `libsql-client-go` for [libSQL](/libsql) databases. > [!NOTE] > > For most applications, we recommend running a local database with sync (`NewTursoSyncDb`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments). ### Remote Turso database: tursogo-serverless `tursogo-serverless` connects to a remote Turso database over HTTP — pure Go, no CGO, no native libraries. It implements the standard `database/sql` interface, compatible with the embedded `tursogo` driver. **1. Retrieve database credentials** You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} go get turso.tech/database/tursogo-serverless ``` **3. Connect and query** ```go theme={null} package main import ( "database/sql" "fmt" "os" turso "turso.tech/database/tursogo-serverless" ) func main() { db := sql.OpenDB(turso.NewConnector( os.Getenv("TURSO_DATABASE_URL"), os.Getenv("TURSO_AUTH_TOKEN"), )) defer db.Close() rows, _ := db.Query("SELECT * FROM users") defer rows.Close() for rows.Next() { var id int var name string rows.Scan(&id, &name) fmt.Printf("User: %d %s\n", id, name) } } ``` ### Remote libSQL database: libsql-client-go `libsql-client-go` connects to a remote libSQL database via the libSQL wire protocol — no local file needed, pure Go. **1. Retrieve database credentials** You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} go get github.com/tursodatabase/libsql-client-go/libsql ``` **3. Connect and query** ```go theme={null} package main import ( "database/sql" "fmt" "os" _ "github.com/tursodatabase/libsql-client-go/libsql" ) func main() { url := os.Getenv("TURSO_DATABASE_URL") + "?authToken=" + os.Getenv("TURSO_AUTH_TOKEN") db, _ := sql.Open("libsql", url) defer db.Close() rows, _ := db.Query("SELECT * FROM users") defer rows.Close() for rows.Next() { var id int var name string rows.Scan(&id, &name) fmt.Printf("User: %d %s\n", id, name) } } ``` ## Embedded Replicas (go-libsql) > [!NOTE] > > Embedded Replicas give your Go app a local read copy of a Turso Cloud database. Reads are served locally; writes go to the cloud primary and are reflected back to the replica. Embedded Replicas are fully supported in production. > > For new projects that need sync, we recommend `tursogo` with `NewTursoSyncDb`: both reads and writes are local, you sync explicitly with `Push()` / `Pull()`, and the wire format is logical change-data-capture rather than page frames ([benchmark](https://turso.tech/blog/sync-benchmark)). See the [reference](/sdk/go/reference) for full documentation on Embedded Replicas with `go-libsql`. # Reference Source: https://docs.turso.tech/sdk/go/reference Go Reference for Turso Turso offers four Go packages: | | `tursogo` | `tursogo-serverless` | `libsql-client-go` | `go-libsql` | | - | - | - | - | - | | **Use case** | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database (over-the-wire) | Existing libSQL codebases | | **Engine** | Turso (rewrite) | Turso (rewrite) | libSQL wire protocol | libSQL (SQLite fork) | | **Concurrent writes** | Yes (MVCC) | Yes (MVCC) | N/A (remote) | Not supported | | **Sync** | push/pull (local-first) | — | — | Embedded Replicas (writes go to cloud primary) | | **CGO** | Not required | Not required | Not required | Required | | **API** | `database/sql` | `database/sql` | `database/sql` | `database/sql` | **Starting a new project?** Use `tursogo` for local/embedded use or sync. For remote access, match the driver to the database engine: [`tursogo-serverless`](/sdk/go/quickstart) for Turso databases, `libsql-client-go` for libSQL databases. None of these require CGO. ## tursogo For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O. ### Installing ```bash theme={null} go get turso.tech/database/tursogo ``` ### Connecting ```go theme={null} import ( "database/sql" _ "turso.tech/database/tursogo" ) db, err := sql.Open("turso", "app.db") ``` In-memory databases are also supported: ```go theme={null} db, err := sql.Open("turso", ":memory:") ``` ### Querying ```go theme={null} _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL )`) _, err = db.Exec("INSERT INTO users (name) VALUES (?)", "Alice") rows, err := db.Query("SELECT * FROM users") defer rows.Close() for rows.Next() { var id int var name string rows.Scan(&id, &name) fmt.Printf("User: %d %s\n", id, name) } ``` ### Encryption Encrypt local databases at rest using DSN options: ```go theme={null} import ( "database/sql" "fmt" _ "turso.tech/database/tursogo" ) hexkey := "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327" dsn := fmt.Sprintf("encrypted.db?experimental=encryption&encryption_cipher=aegis256&encryption_hexkey=%s", hexkey) db, err := sql.Open("turso", dsn) ``` Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`. Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them. > [!NOTE] > > Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption). ### Sync (Push and Pull) For local database with cloud sync. All reads and writes happen locally; use `Push()` to send changes to the cloud and `Pull()` to fetch remote changes. ```go theme={null} import ( "context" "os" turso "turso.tech/database/tursogo" ) ctx := context.Background() syncDb, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "app.db", RemoteUrl: os.Getenv("TURSO_DATABASE_URL"), AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), }) db, err := syncDb.Connect(ctx) ``` On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details. #### Push and Pull ```go theme={null} // Push local writes to Turso Cloud err := syncDb.Push(ctx) // Pull remote changes to local database changed, err := syncDb.Pull(ctx) ``` #### Checkpoint Compact the local WAL to bound disk usage while preserving sync state: ```go theme={null} err := syncDb.Checkpoint(ctx) ``` #### Stats ```go theme={null} stats, err := syncDb.Stats(ctx) fmt.Printf("CDC operations: %d\n", stats.CdcOperations) fmt.Printf("Main WAL size: %d\n", stats.MainWalSize) fmt.Printf("Network sent: %d bytes\n", stats.NetworkSentBytes) fmt.Printf("Network received: %d bytes\n", stats.NetworkReceivedBytes) fmt.Printf("Last pull: %d\n", stats.LastPullUnixTime) fmt.Printf("Last push: %d\n", stats.LastPushUnixTime) fmt.Printf("Revision: %s\n", stats.Revision) ``` ## libsql-client-go (Remote) The recommended package for **any application that connects to a remote Turso Cloud database** over the network — web servers, Docker containers, serverless functions. Pure Go, no native dependencies. ### Installing ```bash theme={null} go get github.com/tursodatabase/libsql-client-go/libsql ``` ### Connecting ```go theme={null} import ( "database/sql" "os" _ "github.com/tursodatabase/libsql-client-go/libsql" ) url := os.Getenv("TURSO_DATABASE_URL") + "?authToken=" + os.Getenv("TURSO_AUTH_TOKEN") db, err := sql.Open("libsql", url) ``` ### Querying Uses the standard `database/sql` interface — same as `tursogo`: ```go theme={null} rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1) ``` ## go-libsql (libSQL) The `go-libsql` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `go-libsql`-based codebase. > [!NOTE] > > With `go-libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `tursogo` with `NewTursoSyncDb` — see the [quickstart](/sdk/go/quickstart). ### Embedded Replicas > [!NOTE] > > For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend `tursogo` with `NewTursoSyncDb` — both reads and writes are local, and you sync explicitly with `Push()` / `Pull()`. You can work with [embedded replicas](/features/embedded-replicas) that can sync from the remote database to a local SQLite file, and delegate writes to the remote primary database: ```go theme={null} package main import ( "database/sql" "fmt" "os" "path/filepath" "github.com/tursodatabase/go-libsql" ) func main() { dbName := "local.db" primaryUrl := "libsql://[DATABASE].turso.io" authToken := "..." dir, err := os.MkdirTemp("", "libsql-*") if err != nil { fmt.Println("Error creating temporary directory:", err) os.Exit(1) } defer os.RemoveAll(dir) dbPath := filepath.Join(dir, dbName) connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl, libsql.WithAuthToken(authToken) ) if err != nil { fmt.Println("Error creating connector:", err) os.Exit(1) } defer connector.Close() db := sql.OpenDB(connector) defer db.Close() } ``` #### Manual Sync ```go theme={null} if err := connector.Sync(); err != nil { fmt.Println("Error syncing database:", err) } ``` #### Periodic Sync ```go theme={null} syncInterval := time.Minute connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl, libsql.WithAuthToken(authToken), libsql.WithSyncInterval(syncInterval), ) ``` #### Read Your Writes By default, after a sync the server must fully catch up with your changes before returning — guaranteeing you always read your own writes. This is safer but **much slower**, since the server must process all pending changes. If you can tolerate eventually-consistent reads, disable this for significantly faster syncs: ```go theme={null} connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl, libsql.WithAuthToken(authToken), libsql.WithReadYourWrites(false), ) ``` ### Encryption > [!WARNING] > > For new projects, we recommend [`tursogo`](#tursogo) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support. To enable encryption on a SQLite file, pass the encryption key value as an argument to the constructor: ```go theme={null} encryptionKey := "SuperSecretKey" connector, err := libsql.NewEmbeddedReplicaConnector(dbPath, primaryUrl, libsql.WithAuthToken(authToken), libsql.WithEncryption(encryptionKey), ) ``` > [!NOTE] > > Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest). # Turso Quickstart (SQL over HTTP) Source: https://docs.turso.tech/sdk/http/quickstart Get started with SQL over HTTP for Turso in a few simple steps. In this quickstart we will learn how to: * Connect to a remote Turso database * Execute SQL queries using [SQL over HTTP](https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md#hrana-over-http) | | `@tursodatabase/serverless` | `@libsql/client` | Raw HTTP | | - | - | - | - | | **Use with** | [Turso](/tursodb/quickstart) databases | [libSQL](/libsql) databases | Any database | | **Dependencies** | Uses only `fetch` — zero native dependencies | Requires Node.js or `/web` subpath | None (any HTTP client) | | **ORM support** | Not yet supported | Drizzle, Prisma, and others | N/A | Match the driver to your database engine: **`@tursodatabase/serverless`** for [Turso](/tursodb/quickstart) databases, **`@libsql/client`** for [libSQL](/libsql) databases. **@tursodatabase/serverless** The `@tursodatabase/serverless` package connects to Turso using only the `fetch` API — no native dependencies, making it the smallest option for edge and serverless runtimes. **1. Install** ```bash theme={null} npm install @tursodatabase/serverless ``` **2. Connect and query** ```typescript theme={null} import { connect } from "@tursodatabase/serverless"; const conn = connect({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?"); const row = await stmt.get([123]); ``` For compatibility with the libSQL client API, use the compat module: ```typescript theme={null} import { createClient } from "@tursodatabase/serverless/compat"; const client = createClient({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); const result = await client.execute("SELECT * FROM users WHERE id = ?", [123]); ``` **@libsql/client** The `@libsql/client` package is the production-ready libSQL driver used across the Turso ecosystem. Use the `/web` import for serverless and edge runtimes. **1. Install** ```bash theme={null} npm install @libsql/client ``` **2. Connect and query** ```typescript theme={null} import { createClient } from "@libsql/client/web"; const client = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN!, }); const result = await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [123], }); ``` > [!NOTE] > > Use `@libsql/client/web` instead of `@libsql/client` for edge and serverless runtimes. The `/web` import uses the HTTP protocol. **Raw HTTP** You can also send queries directly over HTTP using any HTTP client. **1. Create HTTP Database URL** Using the [Turso CLI](/cli) or [Platform API](/api-reference), fetch your database URL: ```bash theme={null} turso db show --http-url ``` > [!NOTE] > > Append `/v2/pipeline` to the URL and continue. **2. Create Database Auth Token** Using the Turso CLI or Platform API, create a new auth token for your database: ```bash theme={null} turso db tokens create ``` **3. Create JSON Request Payload** We'll be sending the query using JSON, so let's create a JSON payload that executes a SQL statement and closes the connection immediately: ```json theme={null} { "requests": [ { "type": "execute", "stmt": { "sql": "SELECT * FROM users" } }, { "type": "close" } ] } ``` > [!NOTE] > > Make sure to update the `stmt.sql` to select from a table you already have. > > Bound parameter examples included on the [Reference Page](/sdk/http/reference#parameter-binding), **4. Execute HTTP Request** Depending on your language, you can use a HTTP client library to send the request to the URL you created as well as the `Authorization` header set to the token you created, and the request body as JSON with your SQL statement. You must append to the Base URL the actual pipeline URL that accepts requests — `/v2/pipeline`. ```ts JavaScript (Fetch) theme={null} const url = "https://[databaseName]-[organizationSlug].turso.io/v2/pipeline"; const authToken = "..."; fetch(url, { method: "POST", headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ requests: [ { type: "execute", stmt: { sql: "SELECT * FROM users" } }, { type: "close" }, ], }), }) .then((res) => res.json()) .then((data) => console.log(data)) .catch((err) => console.log(err)); ``` ```ruby Ruby theme={null} require 'net/http' require 'json' require 'uri' url = "https://[databaseName]-[organizationSlug].turso.io/v2/pipeline" auth_token = "..." uri = URI(url) request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer #{auth_token}" request["Content-Type"] = "application/json" request.body = JSON.generate({ "requests": [ { "type": "execute", "stmt": { "sql": "SELECT * FROM users" } }, { "type": "close" } ] }) begin response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http| http.request(request) end puts JSON.parse(response.body) rescue => e puts e.message end ``` > **Response** > > The response will be a JSON object with a `results` array containing the results of your query that looks something like this: > > ```json theme={null} > { > "baton": null, > "base_url": null, > "results": [ > { > "type": "ok", > "response": { > "type": "execute", > "result": { > "cols": [], > "rows": [], > "affected_row_count": 0, > "last_insert_rowid": null, > "replication_index": "1" > } > } > }, > { > "type": "ok", > "response": { > "type": "close" > } > } > ] > } > ``` If you need to use placeholders for values, you can do that: ```json Positional theme={null} { "type": "execute", "stmt": { "sql": "SELECT * FROM users WHERE id = ?", "args": [ { "type": "integer", "value": "1" } ] } } ``` ```json Named theme={null} { "type": "execute", "stmt": { "sql": "SELECT * FROM users WHERE name = :name OR name = $second_name OR name = @third_name", "named_args": [ { "name": "name", "value": { "type": "text", "value": "Turso" } }, { "name": "second_name", "value": { "type": "text", "value": "Not Turso" } }, { "name": "third_name", "value": { "type": "text", "value": "Maybe Turso" } } ] } } ``` > [!NOTE] > > The `type` field within each arg corresponds to the column datatype and can be one of the following: `null`, `integer`, `float`, `text`, or `blob`. > [!NOTE] > > In JSON, the `value` is a `String` to avoid losing precision, because some > JSON implementations treat all numbers as 64-bit floats. # SQL over HTTP Reference Source: https://docs.turso.tech/sdk/http/reference Turso Databases can be accessed via HTTP. The API enables developers to perform SQL operations using [SQL over HTTP](https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md), retrieve server version information, and monitor the health status. > [!NOTE] > > It's recommended you use a [native SDK](/sdk). ## Base URL Simply replace your database URL protocol (`turso://` for Turso databases, `libsql://` for libSQL databases) with `https://`: ```bash theme={null} https://[databaseName]-[organizationSlug].turso.io ``` You can obtain your database base URL from the Turso CLI: > **Obtain HTTP URL using Turso CLI** > > ```bash theme={null} > turso db show --http-url > ``` ## Authentication Turso uses Bearer authentication, and requires your API token to be passed with all protected requests in the `Authorization` header: ```bash theme={null} Authorization: Bearer TOKEN ``` > **Create token using Turso CLI** > > ```bash theme={null} > turso db tokens create > ``` ## Endpoints Your database has the endpoints available below: ### `POST /v2/pipeline` You can use the `/v2/pipeline` endpoint to query a database. The endpoint accepts a series of operations to perform against a database connection. The supported operation types are: * `execute`: execute a statement on the connection. * `close`: close the connection. #### Simple query ```json Request theme={null} { "requests": [ { "type": "execute", "stmt": { "sql": "CREATE TABLE users (name)" } }, { "type": "close" } ] } ``` ```json Response theme={null} { "baton": null, "base_url": null, "results": [ { "type": "ok", "response": { "type": "execute", "result": { "cols": [], "rows": [], "affected_row_count": 0, "last_insert_rowid": null, "replication_index": "1" } } }, { "type": "ok", "response": { "type": "close" } } ] } ``` > [!NOTE] > > Connections are left open until they timeout, unless you close them explicitly in the request (as shown above). Every request made on a connection bumps the timeout. You should close the connection when it's no longer needed. #### Parameter binding Queries with bound parameters come in two types: 1. Positional query parameters, bound by their position in the parameter list, and prefixed `?`. If the query uses positional parameters, the values should be provided as an array to the `args` field. ```json Request theme={null} { "requests": [ { "type": "execute", "stmt": { "sql": "SELECT * FROM users WHERE name = ?", "args": [ { "type": "text", "value": "Turso" } ] } }, { "type": "close" } ] } ``` ```json Response theme={null} { "baton": null, "base_url": null, "results": [ { "type": "ok", "response": { "type": "execute", "result": { "cols": [ { "name": "name", "decltype": "TEXT" } ], "rows": [ [ { "type": "text", "value": "Turso" } ] ], "affected_row_count": 0, "last_insert_rowid": null } } }, { "type": "ok", "response": { "type": "close" } } ] } ``` 2. Named bound parameters, where the parameter is referred to by a name and is prefixed with a `:`, a `@` or a `$`. If the query uses named parameters, then the `named_args` field of the query should be an array of objects mapping parameters to their values. ```json Request theme={null} { "requests": [ { "type": "execute", "stmt": { "sql": "SELECT * FROM users WHERE name = :name OR name = $second_name OR name = @third_name", "named_args": [ { "name": "name", "value": { "type": "text", "value": "Turso" } }, { "name": "second_name", "value": { "type": "text", "value": "Not Turso" } }, { "name": "third_name", "value": { "type": "text", "value": "Maybe Turso" } } ] } }, { "type": "close" } ] } ``` ```json Response theme={null} { "baton": null, "base_url": null, "results": [ { "type": "ok", "response": { "type": "execute", "result": { "cols": [ { "name": "name", "decltype": "TEXT" } ], "rows": [ [ { "type": "text", "value": "Turso" } ] ], "affected_row_count": 0, "last_insert_rowid": null } } }, { "type": "ok", "response": { "type": "close" } } ] } ``` > [!NOTE] > > The `name` property of each `named_args` can match the prefix or omit the prefix. They were omitted in the example above, but both versions are valid. > > The `type` field within each arg corresponds to the column datatype and can be one of the following: `null`, `integer`, `float`, `text`, or `blob`. > [!NOTE] > > If using the `blob` type, replace the `value` property with `base64` and encode the argument into `base64` before sending the request. > [!NOTE] > > In JSON, the `value` is a `String` to avoid losing precision, because some > JSON implementations treat all numbers as 64-bit floats. #### Interactive query Sometimes, it may be desirable to perform multiple operation on the same connection, in multiple roundtrips. We can do this by not closing the connection right away: ```json Body theme={null} { "requests": [{ "type": "execute", "stmt": { "sql": "BEGIN" } }] } ``` ```json Response theme={null} { "baton": "m7lVVgEvknpf1P1irxHsHqrAqH7BLiwO4DQIAwr93PdZWGvdBNugLSokSsCZNkry", "base_url": null, "results": [ { "type": "ok", "response": { "type": "execute", "result": { "cols": [], "rows": [], "affected_row_count": 0, "last_insert_rowid": null, "replication_index": "1" } } } ] } ``` We can see that we have received a `baton` back. This is because we haven't closed the connection. We can now use this baton to perform more queries on the same connection: ```json Body theme={null} { "baton": "m7lVVgEvknpf1P1irxHsHqrAqH7BLiwO4DQIAwr93PdZWGvdBNugLSokSsCZNkry", "requests": [ { "type": "execute", "stmt": { "sql": "CREATE TABLE users (name)" } }, { "type": "execute", "stmt": { "sql": "INSERT INTO users VALUES (\"iku\")" } }, { "type": "execute", "stmt": { "sql": "COMMIT" } }, { "type": "close" } ] } ``` > [!NOTE] > > Note that both transactions and connections have timeouts. Transaction have a 5 seconds window to complete, while connections get closed after 10 seconds of idle time. #### Response types The response contains the following fields: | Field | Type | Description | | - | - | - | | `baton` | string | The baton is used to identify a connection with the server so that it can be reused. | | `base_url` | string | The base URL of the server that handled the request. This URL can be reused for subsequent requests to force routing to that server. | | `results` | array | The results for each of the requests made in the pipeline. | The `results` array contains the results for each of the requests made in the pipeline. Each result has the following fields: | Field | Type | Description | | - | - | - | | `cols` | array | The list of columns for the returned rows. | | `rows` | array | The rows returned for the query. | | `affected_row_count` | integer | The number of rows affected by the query. | | `last_insert_rowid` | integer | The ID of the last inserted row. | | `replication_index` | string | The replication timestamp at which this query was executed. | | `rows_read` | integer | The number of rows read by the query. | | `rows_written` | integer | The number of rows written by the query. | | `query_duration_ms` | float | The duration of the query in milliseconds. | ### `GET /version` To obtain the current version of the server running your database you can use the `/version` endpoint: ```bash Request theme={null} curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/version' \ -H 'Authorization: Bearer TOKEN' ``` ```text 200 theme={null} sqld 0.21.9 (67f3ea5d 2023-10-26) ``` ### `GET /health` To check the health of your database, you can use the `/health` endpoint which returns an empty body with an HTTP status: ```bash Request theme={null} curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/health' \ -H 'Authorization: Bearer TOKEN' ``` ```text 200 theme={null} This response has no body data. ``` ### `GET /dump` You can dump the database using the `/dump` endpoint: ```bash Request theme={null} curl -L -X GET 'https://[databaseName]-[organizationSlug].turso.io/dump' \ -H 'Authorization: Bearer TOKEN' ``` ```text 200 theme={null} PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS mytable ( content TEXT, embedding FLOAT32(1536) ); CREATE TABLE IF NOT EXISTS libsql_vector_index (type TEXT, name TEXT, vector_type TEXT, block_size INTEGER, dims INTEGER, distance_ops TEXT); INSERT INTO libsql_vector_index VALUES('diskann','mytable_idx','float32',128,1536,'cosine'); CREATE INDEX mytable_idx USING diskann_cosine_ops ON mytable (embedding); COMMIT; ``` If you're using [multi-db schemas](/features/multi-db-schemas), or vector databases, you will see statements that represent internally managed tables. ## Schema Migration Status Turso provides a managed multi-tenant schema system — [Multi-DB Schemas](/features/multi-db-schemas). This feature allows you to create a single database that shares its schema automatically with any related child databases. Changes to the parent database automatically propogate to the related databases. The `/v1/jobs` endpoint lets you monitor the status of schema migrations. > [!WARNING] > > Currently not available on AWS for Free, Developer and Scaler plans. ### `GET /v1/jobs` Returns a summary of all migration jobs for a schema: ```bash Request theme={null} curl -X GET 'https://[databaseName]-[organizationSlug].turso.io/v1/jobs' \ -H 'Authorization: Bearer TOKEN' ``` ```json Example Response theme={null} { "schema_version": 4, "migrations": [ { "job_id": 43, "status": "RunSuccess" }, { "job_id": 42, "status": "RunSuccess" }, { "job_id": 39, "status": "RunSuccess" } ] } ``` #### Response Fields **schema\_version** `number` Current version of the schema. **migrations** `array` List of migration jobs **Migration Object** **job\_id** `number` Unique ID for the migration job. **status** `string` Current status of the job. Possible values: `WaitingDryRun`, `DryRunSuccess`, `DryRunFailure`, `WaitingRun`, `RunSuccess`, `RunFailure` ### `GET /v1/jobs/:id` Returns detailed information about a specific migration job. ```bash Request theme={null} curl -X GET 'https://[databaseName]-[organizationSlug].turso.io/v1/jobs/:id' \ -H 'Authorization: Bearer TOKEN' ``` ```json Example Response theme={null} { "job_id": 1, "status": "RunSuccess", "error": null, "progress": [ { "namespace": "db1", "status": "Success", "error": null }, { "namespace": "db2", "status": "Failure", "error": "Connection lost" } ] } ``` #### Path Parameters **id** `number` *required* The ID of the migration job. #### Response Fields **job\_id** `number` Unique ID of the migration job. **status** `string` Overall status of the job. Possible values are the same as in the `/v1/jobs` endpoint. **error** `string` Error message if the job failed, null otherwise. **progress** `array` List of migration statuses for the individual databases. **Progress Object** **namespace** `string` Name of the database. **status** `string` Status of migration for this database. Possible values: `Enqueued`, `DryRunSuccess`, `DryRunFailure`, `Run`, `Success`, `Failure` **error** `string` Error message if migration failed for this database, null otherwise. ## Listen to changes You can listen to changes committed to your database using the `/beta/listen` endpoint: > [!WARNING] > > Currently not available on AWS for Free, Developer and Scaler plans. > [!NOTE] > > Your database group must be using the version `v0.24.18` or greater — `turso group update --version latest` ### `GET /beta/listen` ```bash Request theme={null} curl -L 'https://[primary-instance-id]-[databaseName]-[organizationSlug].turso.io/beta/listen?table=TABLE_NAME' \ -H 'Authorization: Bearer TOKEN' ``` #### Query Parameters **table** `string` *required* The name of the table to listen to. **action** `string` *required* The name of the action to listen to — `insert`, `update`, or `delete`. # Turso SDKs Source: https://docs.turso.tech/sdk/introduction ## Which package should I use? Two rules cover almost every case: 1. **If a local database is involved, use the Turso packages.** That means fully local databases (embedded, on-device, offline) and local databases that sync with the cloud — even when the database on the other side is libSQL. 2. **If your application only talks to a cloud database over the network, match the driver to the database engine.** [Turso databases](/tursodb/quickstart) use the serverless drivers; [libSQL](/libsql) databases use the libSQL clients. | Use case | TypeScript | Python | Go | Rust | | - | - | - | - | - | | **Local database** (embedded, on-device, offline) | `@tursodatabase/database` | `pyturso` | `tursogo` | `turso` | | **Local database + cloud sync** (push/pull) | `@tursodatabase/sync` | `pyturso` (with sync) | `tursogo` (with sync) | `turso` (with `sync` feature) | | **Remote Turso database** (over-the-wire) | `@tursodatabase/serverless` | `turso_serverless` | `tursogo-serverless` | `turso_serverless` | | **Remote libSQL database** (over-the-wire) | `@libsql/client` | `libsql` | `libsql-client-go` | `libsql` (with `remote` feature) | **Starting a new project?** Use `@tursodatabase/database` (TypeScript), `pyturso` (Python), `tursogo` (Go), or `turso` (Rust). These are built on the Turso engine — the ground-up rewrite of SQLite with concurrent writes, async I/O, and local-first sync. **Need sync?** Use [Turso Sync](/sync/usage) for local reads and writes with explicit `push()` / `pull()` to Turso Cloud. **Using an ORM?** * **TypeScript** — Drizzle and Prisma run on `@libsql/client` with production-ready support from all major ORMs in the ecosystem. * **Rust** — [Toasty](/sdk/rust/orm/toasty), the async ORM from the Tokio project, has a native Turso driver built on the `turso` crate. * **Python** — see the [SQLAlchemy guide](/sdk/python/orm/sqlalchemy). **Migrating from SQLite?** Turso is a drop-in replacement. Your existing SQL, schema, and queries work unchanged. **Already using `@libsql/client`, `libsql`, or `go-libsql`?** These packages are built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that has powered Turso Cloud for years. They are production-ready and battle-tested, and remain the recommended way to access libSQL databases remotely. If your workload needs concurrent writes, push/pull sync, or local-first writes (offline / multi-writer / bidirectional), consider a Turso database and its packages instead. ### The serverless drivers The serverless drivers speak SQL over HTTP — no persistent connections, no native libraries. They work anywhere your language runs: servers, Docker containers, serverless functions, and edge runtimes. Each mirrors the API of its embedded counterpart, so code moves between local and remote with minimal changes. > [!NOTE] > > **Need sync?** For new projects, use [Turso Sync](/sync/usage) — it gives you local reads and writes with explicit `push()` / `pull()` to the cloud. The libSQL-based SDKs below also support Embedded Replicas, where reads are local and writes are sent to the cloud primary. ## Official SDKs ## Community SDKs These SDKs are community-driven and don't come with official Turso support. > **[Flutter / Dart](/sdk/flutter/quickstart)** > **[Capacitor](https://capawesome.io/plugins/libsql)** > **[React Native / OP-SQLite](https://op-engineering.github.io/op-sqlite/)** > **[Laravel](https://github.com/richan-fongdasen/turso-laravel)** > **[.NET](https://github.com/tvandinther/libsql-client-dotnet)** > **[Java](https://github.com/dbeaver/dbeaver-jdbc-libsql)** > **[Stateless libSQL](https://github.com/DaBigBlob/libsql-stateless-easy)** ## SQL over HTTP # Turso Quickstart (Android) Source: https://docs.turso.tech/sdk/kotlin/quickstart Get started with Turso and Android using the libSQL client in a few simple steps. > [!NOTE] > > This will only work with the Android Gradle Plugin for now. Fully Kotlin > support is coming. In this Kotlin quickstart we will learn how to: * Retrieve database credentials * Add libSQL as a dependency in your Android Gradle project * Connect to a local or remote Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). **2. Install** Add libsql as a implementation dependency in Gradle: > [!NOTE] > > This will only work with the Android Gradle Plugin for now. ```kotlin theme={null} dependencies { implementation("tech.turso.libsql:libsql:0.1.0") } ``` **3. Connect** You must first create a `Database` object and then open a `Connection` to it: > **Embedded Replicas** > > ```kotlin theme={null} > import tech.turso.libsql.Libsql > > val db = Libsql.open( > path = "./local.db", > url = "TURSO_DATABASE_URL", > authToken = "TURSO_AUTH_TOKEN", > ) > > val conn = db.connect() > ``` > **Local only** > > ```kotlin theme={null} > import tech.turso.libsql.Libsql > > val db = Libsql.open(path = "./local.db") > val conn = db.connect() > ``` > **Remote only** > > ```kotlin theme={null} > import tech.turso.libsql.Libsql > > val db = Libsql.open( > url = "TURSO_DATABASE_URL", > authToken = "TURSO_AUTH_TOKEN", > ) > > val conn = db.connect() > ``` **4. Execute** You can execute a SQL query against your existing database by calling `execute()`: ```kotlin theme={null} db.connect().use { it.execute("INSERT INTO users (id) VALUES (1)") } ``` If you need to use placeholders for values, you can do that: ```kotlin Positional theme={null} db.connect().use { it.query("SELECT * FROM users WHERE id = ?", 1) } ``` ```kotlin Named theme={null} db.connect().use { it.query("INSERT INTO users (name) VALUES (:name)", mapOf(":name" to "Iku")) } ``` **5. Sync (Embedded Replicas only)** When using embedded replicas you should call `sync()` on the database type to sync your local database with the primary database, unless you are using `syncInterval` (though there is no issue with calling `sync` with `syncInterval` enabled): ```kotlin theme={null} db.sync() ``` # Android Reference Source: https://docs.turso.tech/sdk/kotlin/reference libSQL Android Reference > [!NOTE] > > This will only work with the Android Gradle Plugin for now. Fully Kotlin > support is coming. ## Installing Add libsql as a implementation dependency in Gradle: ```kotlin theme={null} dependencies { implementation("tech.turso.libsql:libsql:0.1.0") } ``` ## In-Memory Databases libSQL supports connecting to [in-memory databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't require persistence: ```kotlin theme={null} import tech.turso.libsql.Libsql val db = Libsql.open(":memory:") val conn = db.connect() ``` ## Local Development You can work locally using an SQLite file: ```kotlin theme={null} import tech.turso.libsql.Libsql val db = Libsql.open(path = "./local.db") val conn = db.connect() ``` ## Embedded Replicas You can work with embedded replicas that can sync from the remote URL and delegate writes to the remote primary database: ```kotlin theme={null} import tech.turso.libsql.Libsql val db = Libsql.open( path = "./local.db", url = "TURSO_DATABASE_URL", authToken = "TURSO_AUTH_TOKEN", ) val conn = db.connect() ``` ### Manual Sync The `sync` function allows you to sync manually the local database with the remote counterpart: ```kotlin theme={null} db.sync() // Call sync manually to update local database (only for EmbeddedReplicaDatabase) ``` ## Simple query You can pass a string to `query()` to invoke a SQL statement, as well as optional arguments: ```kotlin Execute theme={null} db.connect().use { it.execute("INSERT INTO users VALUES (?)", 1) } ``` ```kotlin Query theme={null} db.connect().use { it.query("INSERT INTO users VALUES (?)", 1) } ``` ```kotlin Arguments theme={null} db.connect().use { it.query("SELECT * FROM users WHERE id = ?", 1) } ``` ## Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```kotlin Positional theme={null} db.connect().use { it.query("SELECT * FROM users WHERE id = ?", 1) } ``` ```kotlin Named theme={null} db.connect().use { it.query("SELECT * FROM users WHERE id = :id", mapOf(":id" to 1)) } ``` ## Batch Transactions A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications. ```kotlin theme={null} db.connect().use { it.execute_batch(" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); INSERT INTO users (name) VALUES ('Alice'); INSERT INTO users (name) VALUES ('Bob'); ") } ``` ## Transactions Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity. ```kotlin theme={null} db.connect().use { val tx = it.transaction(); tx.execute("INSERT INTO users (name) VALUES (?)", "Iku"); tx.execute("INSERT INTO users (name) VALUES (?)", "Iku 2"); tx.commit() // or tx.rollback() } ``` # Examples Source: https://docs.turso.tech/sdk/php/examples [View examples on GitHub](https://github.com/tursodatabase/libsql-client-ts/tree/main/packages/libsql-client/examples) # Turso Quickstart (Laravel) Source: https://docs.turso.tech/sdk/php/guides/laravel In this Laravel quickstart we will learn how to: * Install the Turso Laravel adapter * Configure Laravel to use Turso * Create a model and migration * Perform CRUD operations using Eloquent **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install** Install the package to your Laravel project using Composer: ```console theme={null} composer require turso/libsql-laravel ``` > [!NOTE] > > Make sure to enable FFI in your PHP configuration to use the Laravel adapter. **3. Configure** Choose your preferred setup and configure accordingly: > **Local only** > > Update your `config/database.php`: > > ```php theme={null} > return [ > "default" => env("DB_CONNECTION", "libsql"), > > "connections" => [ > "libsql" => [ > "driver" => "libsql", > "database" => database_path("database.db"), > ], > // ... > ], > ]; > ``` > > This will use a local SQLite file with the libSQL adapter. > **Remote only** > > Update your `config/database.php`: > > ```php theme={null} > return [ > "default" => env("DB_CONNECTION", "libsql"), > > "connections" => [ > "libsql" => [ > "driver" => "libsql", > "url" => env("TURSO_DATABASE_URL"), > "password" => env("TURSO_AUTH_TOKEN"), > ], > // ... > ], > ]; > ``` > > Then add to your `.env` file: > > ``` > DB_CONNECTION=libsql > TURSO_DATABASE_URL=libsql://... > TURSO_AUTH_TOKEN=... > ``` > **Embedded Replicas** > > Update your `config/database.php`: > > ```php theme={null} > return [ > "default" => env("DB_CONNECTION", "libsql"), > > "connections" => [ > "libsql" => [ > "driver" => "libsql", > "database" => database_path("database.db"), > "url" => env("TURSO_DATABASE_URL"), > "password" => env("TURSO_AUTH_TOKEN"), > "sync_interval" => env("TURSO_SYNC_INTERVAL", 300), > ], > // ... > ], > ]; > ``` > > Then add to your `.env` file: > > ``` > DB_CONNECTION=libsql > TURSO_DATABASE_URL=libsql://... > TURSO_AUTH_TOKEN=... > TURSO_SYNC_INTERVAL=300 > ``` > > > [!NOTE] > > > > The `sync_interval` is optional and defaults to 300 seconds (5 minutes). **4. Create a model and migration** Create a User model and migration: ```console theme={null} php artisan make:model User -m ``` Update the migration file: ```php theme={null} public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->timestamps(); }); } ``` **5. Run migrations** Apply the migration to create the users table: ```console theme={null} php artisan migrate ``` **6. Execute CRUD operations** Now you can perform CRUD operations using Eloquent: ```php Create theme={null} use App\Models\User; $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); ``` ```php Read theme={null} use App\Models\User; // Fetch all users $allUsers = User::all(); // Find a specific user by ID $user = User::find(1); // Filter users $filteredUsers = User::where('name', 'John Doe')->get(); ``` ```php Update theme={null} use App\Models\User; // Update a single record $user = User::find(1); $user->name = 'Jane Doe'; $user->save(); // Update multiple records User::where('name', 'John Doe')->update(['name' => 'Jane Doe']); ``` ```php Delete theme={null} use App\Models\User; // Delete a single record $user = User::find(1); $user->delete(); // Delete multiple records User::where('name', 'Jane Doe')->delete(); ``` # Turso + Doctrine DBAL Source: https://docs.turso.tech/sdk/php/orm/doctrine-dbal Set up Turso in your PHP + Doctrine DBAL project in minutes ![Doctrine DBAL](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/doctrine-dbal-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=42ebe47228681e458a0f7570d61f74e5) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have an PHP Application with Doctrine DBAL **1. Configure database credentials** Get the database URL: ```bash theme={null} turso db show --url ``` Get the database authentication token: ```bash theme={null} turso db tokens create ``` Assign credentials to the environment variables inside `.env`. ```bash theme={null} TURSO_DATABASE_URL= TURSO_AUTH_TOKEN= ``` > [!NOTE] > > Save this later when you need to connect to **Remote** and **Embedded Replica** **2. Create PHP Project** Create new directory: ```bash theme={null} mkdir your-awesome-php-project cd your-awesome-php-project ``` **3. Install Turso Doctrine DBAL** ```bash theme={null} composer require tursodatabase/turso-doctrine-dbal ``` **4. Configure database connection** Setup the environment variable in your Laravel application, choose what the type connection you need. ```php In-Memory Only theme={null} $params = [ "url" => ":memory:", 'driverClass' => \Turso\Doctrine\DBAL\Driver::class, ]; ``` ```php Local Only theme={null} $params = [ "url" => "database.db", 'driverClass' => \Turso\Doctrine\DBAL\Driver::class, ]; ``` ```php Remote Only theme={null} $params = [ "auth_token" => getenv('TURSO_AUTH_TOKEN'), "sync_url" => getenv('TURSO_DATABASE_URL'), 'driverClass' => \Turso\Doctrine\DBAL\Driver::class, ]; ``` ```php Embedded Replica theme={null} $params = [ "url" => "database.db", "auth_token" => getenv('TURSO_AUTH_TOKEN'), "sync_url" => getenv('TURSO_DATABASE_URL'), 'driverClass' => \Turso\Doctrine\DBAL\Driver::class, ]; ``` **Other environment variables (Embedded Replica Only)** **Other environment variables (Embedded Replica Only)** **sync\_interval** `integer` Integer value representing synchronization interval in seconds (optional, default: `5`). **read\_your\_writes** `boolean` Boolean value indicating whether to read your writes (optional, default: `true`). **encryption\_key** `string` String value for encryption purposes (optional, default: empty). **5. Project Structure Example** ```bash theme={null} src/ ├── helpers.php └── Todo.php vendor/ composer.json composer.lock todo # todo executable ``` Here you simple php todo cli + Doctrine BDAL ```php todo theme={null} #!/usr/bin/env php listTodos(); break; case 'a': $app->addTodo(); clearScreen(); break; case 'e': $app->editTodo(); clearScreen(); break; case 'd': $app->deleteTodo(); clearScreen(); break; case 'x': exit("Goodbye!" . PHP_EOL); default: echo "Invalid choice." . PHP_EOL; clearScreen(); } } echo PHP_EOL; } main(); ``` ```php src/helpers.php theme={null} getenv('TURSO_AUTH_TOKEN'), "sync_url" => getenv('TURSO_DATABASE_URL'), 'driverClass' => \Turso\Doctrine\DBAL\Driver::class, ]; $db = DriverManager::getConnection($params); return $db; } ``` ```php src/Todo.php theme={null} db = createConnection(); } public function loadTodos(): array { $sql = "SELECT * FROM todos"; return $this->db->fetchAllAssociative($sql); } public function saveTodo(string $todo): void { $sql = "INSERT INTO todos (name) VALUES (?)"; $this->db->executeStatement($sql, [$todo]); } public function updateTodo(int $id, string $todo): void { $sql = "UPDATE todos SET name = ? WHERE id = ?"; $this->db->executeStatement($sql, [$todo, $id]); } public function removeTodo(int $id): void { $sql = "DELETE FROM todos WHERE id = ?"; $this->db->executeStatement($sql, [$id]); } public function listTodos() { $todos = $this->loadTodos(); echo "\n" . "TODO-List " . str_repeat('-', WIDTH - 12) . "\n\n"; if (!empty($todos)) { foreach ($todos as $todo) { echo sprintf("[%d] %s\n", $todo['id'], $todo['name']); } } else { echo "Nothing...\n"; } echo "\n" . str_repeat('-', WIDTH) . "\n"; } public function addTodo() { echo "Enter the new To-Do: "; $todo = trim(fgets(STDIN)); $this->saveTodo($todo); echo "To-Do added.\n"; } public function editTodo() { $this->listTodos(); echo "Enter the number of the To-Do to edit: "; $index = intval(trim(fgets(STDIN))); $findTodo = $this->db->executeQuery("SELECT * FROM todos WHERE id = ?", [$index])->fetchAssociative(); if (!empty($findTodo)) { echo "Enter the new value: "; $todo = trim(fgets(STDIN)); $this->updateTodo($findTodo['id'], $todo); echo "To-Do updated.\n"; } else { echo "Invalid index.\n"; } } public function deleteTodo() { $this->listTodos(); echo "Enter the number of the To-Do to delete: "; $index = intval(trim(fgets(STDIN))); $findTodo = $this->db->executeQuery("SELECT * FROM todos WHERE id = ?", [$index])->fetchAssociative(); if (!empty($findTodo)) { $this->removeTodo($findTodo['id']); echo "To-Do deleted.\n"; } else { echo "Invalid index.\n"; } } } ``` **6. Your PHP Application Ready!** ![PHP TODO CLI - TURSO](https://i.imgur.com/dmrPFr2.png) ## Examples > **[PHP Todo CLI + Doctrine DBAL](https://github.com/tursodatabase/example/php-todo-cli-doctrine-dbal/tree/master)** > > See the full source code > **[Symfony Rest API CRUD](https://github.com/tursodatabase/example/symfony7-rest-api-crud/tree/master)** > > See the full source code # Turso Quickstart (PHP) Source: https://docs.turso.tech/sdk/php/quickstart Get started with Turso and PHP using the libSQL client in a few simple steps. In this PHP quickstart we will learn how to: * Install libSQL with Composer * Retrieve database credentials * Connect to a Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install** Install the package to your project using composer: ```console theme={null} composer require turso/libsql ``` **3. Connect** Now connect to your local or remote database using the libSQL connector: > **Embedded Replica** > > ```php theme={null} > use Libsql\Database; > > $db = new Database( > path: 'test.db', > url: getenv('TURSO_URL'), > authToken: getenv('TURSO_AUTH_TOKEN') > syncInterval: 100 // Sync every second > ); > $conn = $db->connect(); > ``` > **Local only** > > ```php theme={null} > use Libsql\Database; > > $db = new Database(path: "database.db"); > ``` > > Or just: > > ```php theme={null} > use Libsql\Database; > > $db = new Database("database.db"); > ``` > **Local only** > > ```php theme={null} > use Libsql\Database; > > $db = new Database(path: "database.db"); > ``` > > Or just: > > ```php theme={null} > use Libsql\Database; > > $db = new Database("database.db"); > ``` > **Remote only** > > ```php theme={null} > use Libsql\Database; > > $db = new Database( > url: getenv('TURSO_URL'), > authToken: getenv('TURSO_AUTH_TOKEN'), > ) > ``` **4. Execute** You can execute SQL queries against your existing database as follows: ```php theme={null} $createUsers = " CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER ); INSERT INTO users (name, age) VALUES ('Prof. Ir. Onno Widodo Purbo, M.Eng., Ph.D', 61); INSERT INTO users (name, age) VALUES ('Jim Geovedi', 41); INSERT INTO users (name, age) VALUES ('Nasirun', 59); "; $db->executeBatch($createUsers); $db->query("SELECT * FROM users")->fetchArray(); ``` If you need to use placeholders for values, you can do that: ```php Positional theme={null} $db->query("SELECT * FROM users WHERE id = ?", [1])->fetchArray(); ``` ```php Named theme={null} $db->query("SELECT * FROM users WHERE id = :id", [":id" => 1])->fetchArray(); ``` **5. Sync (Embedded Replicas only)** When using embedded replicas you should call `sync()` on the database type to sync your local database with the primary database: ```php theme={null} sync(); ``` # Reference Source: https://docs.turso.tech/sdk/php/reference libSQL PHP Reference ## Installing Install the package to your project using composer: ```console theme={null} composer require turso/libsql ``` ## Initializing Make sure to add `use Libsql\Database` to access the `Database` object. ```php theme={null} $db = new Database("local.db") ``` ## In-Memory Databases libSQL supports connecting to [in-memory databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't require persistence: ```php theme={null} $db = new Database(":memory:"); ``` Or the simpler: ```php theme={null} $db = new Database(); ``` ## Local Development You can work locally by passing a path as the first parameter. ```php theme={null} $db = new Database("local.db") ``` Or more explicitly: ```php theme={null} $db = new Database(path: "local.db") ``` ## Remote Only You can use a remote only database by passing `url` and `authToken`. ```php theme={null} $db = new Database( url: getenv('TURSO_URL'), authToken: getenv('TURSO_AUTH_TOKEN'), ); ``` ## Embedded Replicas You can work with embedded replicas by passing a `path`, `url` and `authToken`. Embedded replicas can sync from the remote URL and delegate writes to the remote primary database: ```php theme={null} $db = new Database( path: 'test.db', url: getenv('TURSO_URL'), authToken: getenv('TURSO_AUTH_TOKEN'), ); ``` ### Sync Interval The `sync_interval` function allows you to set an interval for automatic synchronization of the database in the background: ```php theme={null} $db = new Database( path: 'test.db', url: getenv('TURSO_URL'), authToken: getenv('TURSO_AUTH_TOKEN'), syncInterval: 300, // Sync every 3 seconds ); ``` ### Manual Sync The `sync` function allows you to sync manually the local database with the remote counterpart: ```php theme={null} $db->sync() ``` ### Read Your Own Writes The `readYourWrites` parameter configures the database connection to ensure that writes are immediately visible to subsequent read operations initiated by the same connection. This is **enabled by default**, and is particularly important in distributed systems to ensure consistency from the perspective of the writing process. You can disable this behavior by passing `false`: ```php theme={null} $db = new Database( path: 'test.db', url: getenv('TURSO_URL'), authToken: getenv('TURSO_AUTH_TOKEN'), readYourWrites: false, ); ``` ## Simple Query You can acquire a connection from a database and call `query()` to invoke a SQL statement, as well as optional arguments: ```php Query theme={null} $rows = $conn->query("SELECT * FROM users"); ``` ```php Arguments theme={null} $rows = $conn->query("SELECT * FROM users WHERE id = ?1", [1]); ``` ```php Named Arguments theme={null} $rows = $conn->query("SELECT * FROM users WHERE id = :id", [":id" => 1]); ``` ## Prepared Statements You can prepare a cached statement using `prepare()`, bind parameters, and then query it: ```php theme={null} $stmt = $conn->prepare("SELECT * FROM users where id = ?"); $rows = $stmt->bind([1])->query(); ``` ## Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```rust Positional theme={null} conn->query("SELECT * FROM users WHERE id = ?", [1]); ``` ```rust Named theme={null} conn->execute("INSERT INTO users (name) VALUES (:name)", [ ":name" => "Iku" ]); ``` ## Batch Transactions A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications. ```php theme={null} $conn->execute_batch(" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); INSERT INTO users (name) VALUES ('Alice'); INSERT INTO users (name) VALUES ('Bob'); "); ``` ## Interactive Transactions Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity. ```php theme={null} $tx = conn->transaction(); $tx->execute("INSERT INTO users (name) VALUES (?1)", ["Iku"]); $tx->execute("INSERT INTO users (name) VALUES (?1)", ["Iku 2"]); tx->commit(); // or, $tx->rollback() ``` # Examples Source: https://docs.turso.tech/sdk/python/examples # Flask + Turso Source: https://docs.turso.tech/sdk/python/guides/flask Set up Turso in your Flask project in minutes ![Flask banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/flask-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=29d0d7405a0b24c0f3ccf312ac32ad4b) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Flask app — [learn more](https://flask.palletsprojects.com/en/3.0.x/installation/#create-an-environment) **1. Install the libSQL dialect** ```bash theme={null} pip install sqlalchemy-libsql python-dotenv ``` **2. Retrieve database credentials** **3. Create database models** ```python models.py theme={null} from sqlalchemy import String from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class Item(Base): __tablename__ = "items" id: Mapped[str] = mapped_column(primary_key=True) foo: Mapped[str] = mapped_column(String(255)) bar: Mapped[str] = mapped_column(String(100)) def __repr__(self) -> str: return f"Item(id={self.id!r}, foo={self.foo!r}, bar={self.bar!r})" ``` **4. Query** ```python theme={null} from dotenv import load_dotenv from sqlalchemy import create_engine from sqlalchemy.orm import Session from sqlalchemy import select from models import Item load_dotenv() # Get environment variables TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL") TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN") # construct special SQLAlchemy URL dbUrl = f"sqlite+{TURSO_DATABASE_URL}/?authToken={TURSO_AUTH_TOKEN}&secure=true" engine = create_engine(dbUrl, connect_args={'check_same_thread': False}, echo=True) @app.route("/", methods=(["GET"])) def home(): session = Session(engine) # get & print items stmt = select(Item) for item in session.scalars(stmt): print(item) ``` ## Examples > **[Social App](https://github.com/tursodatabase/examples/tree/master/app-find-me-on-python-htmx)** > > See the full source code # SQLAlchemy + Turso Source: https://docs.turso.tech/sdk/python/orm/sqlalchemy Configure SQLAlchemy to work with your Turso database ![SQLAlchemy Quickstart](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/sqlalchemy-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=c43a5833c30b4a5d3403fd4d68fd84e2) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) **1. Install the libSQL dialect for SQLAlchemy** ```bash theme={null} pip install sqlalchemy-libsql ``` **2. Retrieve database credentials** **3. Create database models** ```python models.py theme={null} from sqlalchemy import String from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class Foo(Base): __tablename__ = "foo" id: Mapped[str] = mapped_column(primary_key=True) bar: Mapped[str] = mapped_column(String(100)) def __repr__(self) -> str: return f"Item(id={self.id!r}, bar={self.bar!r})" ``` **4. Create engine** > **Embedded Replicas** > > ```python theme={null} > from dotenv import load_dotenv > from sqlalchemy import create_engine > > TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL") > TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN") > > engine = create_engine( > "sqlite+libsql:///embedded.db", > connect_args={ > "auth_token": TURSO_AUTH_TOKEN, > "sync_url": TURSO_DATABASE_URL, > }, > ) > ``` > **Remote only** > > ```python theme={null} > from dotenv import load_dotenv > from sqlalchemy import create_engine > > TURSO_DATABASE_URL = os.environ.get("TURSO_DATABASE_URL") > TURSO_AUTH_TOKEN = os.environ.get("TURSO_AUTH_TOKEN") > > engine = create_engine( > f"sqlite+{TURSO_DATABASE_URL}?secure=true", > connect_args={ > "auth_token": TURSO_AUTH_TOKEN, > }, > ) > ``` > **Memory Only** > > ```python theme={null} > from sqlalchemy import create_engine > > engine = create_engine("sqlite+libsql://") > ``` > **Local only** > > ```python theme={null} > from sqlalchemy import create_engine > > engine = create_engine("sqlite+libsql:///local.db") > ``` **5. Query** ```py app.py theme={null} from sqlalchemy.orm import Session from sqlalchemy import select from models import Foo @app.route("/", methods=(["GET"])) def home(): session = Session(engine) # get & print foos stmt = select(Foo) for item in session.scalars(stmt): print(item) ``` # Turso Quickstart (Python) Source: https://docs.turso.tech/sdk/python/quickstart Get started with Turso and Python in a few simple steps. In this Python quickstart we will learn how to: * Install the Turso package * Connect to a local or remote database * Execute a query using SQL * Sync changes to local database ## Recommended: pyturso (Local / Embedded) `pyturso` is the recommended package for local and embedded use cases. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O. The API follows the standard Python `sqlite3` interface, so it works as a drop-in replacement. **1. Install** ```bash theme={null} uv add pyturso ``` Or with pip: ```bash theme={null} pip install pyturso ``` **2. Connect** ```py theme={null} import turso db = turso.connect("app.db") ``` In-memory databases are also supported: ```py theme={null} db = turso.connect(":memory:") ``` **3. Execute** ```py theme={null} db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)") db.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)) db.commit() for row in db.execute("SELECT * FROM users"): print(row) ``` **4. Sync (push and pull)** If you need to sync your local database with Turso Cloud, use `turso.sync`: ```py theme={null} import os import turso.sync db = turso.sync.connect( "app.db", remote_url=os.environ["TURSO_DATABASE_URL"], auth_token=os.environ["TURSO_AUTH_TOKEN"], ) db.execute("INSERT INTO users (name) VALUES (?)", ("Bob",)) db.commit() # Push local writes to Turso Cloud db.push() # Pull remote changes to local database db.pull() ``` All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See [Turso Sync](/sync/usage) for details on conflict resolution, checkpointing, and more. ## Remote Access (Over-the-Wire) If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the driver that matches your database engine: `turso_serverless` for [Turso databases](/tursodb/quickstart), or `libsql` for [libSQL](/libsql) databases. > [!NOTE] > > For most applications, we recommend running a local database with [Turso Sync](/sync/usage) (`turso.sync`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments). ### Remote Turso database: turso\_serverless `turso_serverless` connects to a remote Turso database over HTTP — no persistent connections, no native extensions, just the standard library. Like `pyturso`, it implements the standard DB-API interface. **1. Retrieve database credentials** You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} uv add turso_serverless ``` Or with pip: ```bash theme={null} pip install turso_serverless ``` **3. Connect and query** ```py theme={null} import os import turso_serverless conn = turso_serverless.connect( os.environ["TURSO_DATABASE_URL"], auth_token=os.environ["TURSO_AUTH_TOKEN"], ) conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)") conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)) conn.commit() for row in conn.execute("SELECT * FROM users"): print(row) conn.close() ``` ### Remote libSQL database: libsql `libsql` connects to a remote libSQL database via HTTP — no local file needed. **1. Retrieve database credentials** You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} uv add libsql ``` Or with pip: ```bash theme={null} pip install libsql ``` **3. Connect and query** ```py theme={null} import os import libsql conn = libsql.connect( database=os.environ["TURSO_DATABASE_URL"], auth_token=os.environ["TURSO_AUTH_TOKEN"], ) conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)") conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)) conn.commit() rows = conn.execute("SELECT * FROM users").fetchall() print(rows) ``` ## Embedded Replicas (libsql) The `libsql` package also supports Embedded Replicas — local reads from a file, with writes sent to the cloud primary and reflected back to the replica. Embedded Replicas are fully supported in production. For new projects that need sync, we recommend `turso.sync` instead: both reads and writes are local, and you sync explicitly with `push()` / `pull()`. See the [reference](/sdk/python/reference) for full documentation on Embedded Replicas, encryption, and periodic sync. # Reference Source: https://docs.turso.tech/sdk/python/reference Python Reference for Turso Turso offers three Python packages: | | `pyturso` | `turso_serverless` | `libsql` | | - | - | - | - | | **Use case** | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database, existing libSQL codebases | | **Engine** | Turso (rewrite) | Turso (rewrite) | libSQL (SQLite fork) | | **Concurrent writes** | Yes (MVCC) | Yes (MVCC) | Not supported | | **Sync** | push/pull (local-first) | — | Embedded Replicas (writes go to cloud primary) | | **API** | Python `sqlite3`-compatible | Python `sqlite3`-compatible | Python `sqlite3`-compatible | **Starting a new project?** Use `pyturso` — it is built on the Turso engine with concurrent writes and local-first sync. For remote access over the network, match the driver to the database engine: [`turso_serverless`](/sdk/python/quickstart) for Turso databases, `libsql` for libSQL databases. ## pyturso For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O. ### Installing ```bash theme={null} pip install pyturso ``` ### Connecting ```py theme={null} import turso conn = turso.connect("app.db") ``` In-memory databases are also supported: ```py theme={null} conn = turso.connect(":memory:") ``` ### Querying ```py theme={null} conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)") conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)) conn.commit() for row in conn.execute("SELECT * FROM users"): print(row) ``` ### Encryption Encrypt local databases at rest using the `encryption` option: ```py theme={null} from turso import connect, EncryptionOpts conn = connect("encrypted.db", experimental_features="encryption", encryption=EncryptionOpts(cipher="aegis256", hexkey="b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327")) ``` Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`. Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them. > [!NOTE] > > Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption). ## libsql (libSQL) The `libsql` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `libsql`-based codebase. > [!NOTE] > > With `libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `turso.sync` — see the [quickstart](/sdk/python/quickstart). ## Embedded Replicas > [!NOTE] > > For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend `turso.sync` — both reads and writes are local, and you sync explicitly with `push()` / `pull()`. See the [quickstart](/sdk/python/quickstart). You can work with [embedded replicas](/features/embedded-replicas) that can sync from the remote database to a local SQLite file, and delegate writes to the remote primary database: ```py theme={null} import os import libsql conn = libsql.connect("local.db", sync_url=os.getenv("LIBSQL_URL"), auth_token=os.getenv("LIBSQL_AUTH_TOKEN")) conn.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER);") conn.execute("INSERT INTO users(id) VALUES (1);") conn.commit() print(conn.execute("select * from users").fetchall()) ``` ### Periodic Sync You can automatically sync at intervals by passing time in seconds to the `sync_interval` option. For example, to sync every minute, you can use the following code: ```py theme={null} conn = libsql.connect("local.db", sync_interval=60, sync_url=os.getenv("LIBSQL_URL"), auth_token=os.getenv("LIBSQL_AUTH_TOKEN")) ``` ### Manual Sync The `Sync` function allows you to sync manually the local database with the remote counterpart: ```py theme={null} conn.execute("INSERT INTO users(id) VALUES (2);") conn.commit() conn.sync() ``` ## Encryption > [!WARNING] > > For new projects, we recommend [`pyturso`](#pyturso) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support. To enable encryption on a SQLite file, pass the encryption secret to the `encryption_key` option: ```py theme={null} conn = libsql.connect("encrypted.db", sync_url=os.getenv("LIBSQL_URL"), auth_token=os.getenv("LIBSQL_AUTH_TOKEN"), encryption_key=os.getenv("ENCRYPTION_KEY")) ``` > [!NOTE] > > Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest). # Turso Examples (Ruby) Source: https://docs.turso.tech/sdk/ruby/examples | Example | Description | | - | - | | [local](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/local) | Uses libsql with a local SQLite file. Creates database, inserts data, and queries. | | [remote](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/remote) | Connects to a remote database. Requires environment variables for URL and auth token. | | [sync](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/sync) | Demonstrates synchronization between local and remote databases. | | [transactions](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/transactions) | Shows transaction usage: starting, performing operations, and committing/rolling back. | | [memory](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/memory) | Uses an in-memory SQLite database for temporary storage or fast access. | | [vector](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/vector) | Works with vector embeddings, storing and querying for similarity search. | | [encryption](https://github.com/tursodatabase/libsql-ruby/tree/master/examples/encryption) | Creates and uses an encrypted SQLite database, demonstrating setup and data operations. | # Turso Quickstart (Ruby) Source: https://docs.turso.tech/sdk/ruby/quickstart Get started with Turso and Ruby using the libSQL client in a few simple steps. In this Ruby quickstart we will learn how to: * Retrieve database credentials * Install the libSQL package * Connect to a local or remote Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). **2. Install** Inside your Ruby project, install the following Rubygem: ```bash theme={null} bundle add turso_libsql ``` **3. Connect** > **Embedded Replicas** > > ```rb theme={null} > require 'turso_libsql' > > db =Libsql::Database.new( > path: 'local.db', > url: ENV['TURSO_DATABASE_URL'], > auth_token: ENV['TURSO_AUTH_TOKEN'], > sync_interval: 100 > ) > ``` > **Local only** > > ```rb theme={null} > require 'turso_libsql' > > db = Libsql::Database.new(path: 'local.db') > ``` > **Remote only** > > ```rb theme={null} > require 'turso_libsql' > > db = Libsql::Database.new( > url: ENV['TURSO_DATABASE_URL'], > auth_token: ENV['TURSO_AUTH_TOKEN'] > ) > ``` **4. Execute** You can execute a SQL query against your existing database by preparing a statement and then executing it: ```c theme={null} db.connect do |conn| rows = conn.query 'SELECT * FROM users' rows.close end ``` If you need to use placeholders for values, you can do that: ```c Positional theme={null} name = 'Iku' rows = conn.execute 'INSERT INTO users (id) VALUES (?)', [name] ``` **5. Sync (Embedded Replicas only)** When using embedded replicas, you should call `sync` on the database to sync your local database with the primary database, unless you are using `sync_interval` (though there is no issue with calling `sync` with `sync_interval` enabled): ```rb theme={null} db.sync ``` # Examples Source: https://docs.turso.tech/sdk/rust/examples # Turso + Actix Source: https://docs.turso.tech/sdk/rust/guides/actix Set up Turso in your Actix project in minutes ![Actix banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/actix-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=4d06a48cc1186caa607491545546e600) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have an Actix app — [learn more](https://actix.rs/docs/getting-started) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Add the libsql crate to the project** ```sh theme={null} cargo add libsql ``` > [!NOTE] > > Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files: > > ```sh theme={null} > cargo add dotenvy > ``` **3. Execute SQL** ```rust theme={null} #[tokio::main] #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| App::new().route("/", web::get().to(index)).route("/items", web::get().to(get_items))) .bind(("127.0.0.1", 8080))? .run() .await } async fn get_items() -> Result { dotenv().expect(".env file not found"); let db_url = env::var("TURSO_DATABASE_URL").unwrap(); let auth_token = env::var("TURSO_AUTH_TOKEN").unwrap(); let db_file = env::var("LOCAL_DB").unwrap(); let db = Builder::new_remote_replica(db_file, url, auth_token) .read_your_writes(true) .build() .await .unwrap(); let conn = db.connect().unwrap(); let mut results = conn.query("SELECT * FROM items", ()).await.unwrap(); let mut items: Vec = Vec::new(); while let Some(row) = results.next().await.unwrap() { let item: Item = Item { task: row.get(0).unwrap(), }; items.push(item); } Ok(HttpResponse::Ok().json(items)) } ``` ## Examples > **[Turso + Actix Web Traffic Tracker](https://github.com/tursodatabase/examples/tree/master/app-web-traffic-tracker-actix)** > > See the full source code # Turso + Axum Source: https://docs.turso.tech/sdk/rust/guides/axum Set up Turso in your Axum project in minutes ![Axum banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/axum-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=e92d18a8f72196e94541553f254a77b3) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have an Axum app — [learn more](https://github.com/tokio-rs/axum) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Add the libsql crate to the project** ```sh theme={null} cargo add libsql ``` > [!NOTE] > > Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files: > > ```sh theme={null} > cargo add dotenvy > ``` **3. Execute SQL** ```rust theme={null} use libsql::{Builder, Connection, Database, Result}; #[tokio::main] async fn main() -> Result<()> { dotenv().ok(); let db_url = std::env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL must be set"); let auth_token = std::env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN must be set"); let db = Builder::new_remote(db_url, auth_token) .build() .await?; let conn = db.connect()?; // Execute a query let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let name: String = row.get(1)?; println!("User: {} - {}", id, name); } Ok(()) } ``` **4. Use in an Axum handler** ```rust theme={null} use axum::{ extract::State, routing::get, Json, Router, }; use libsql::{Builder, Connection, Database}; use serde::Serialize; #[derive(Clone)] struct AppState { db: Database, } #[derive(Serialize)] struct User { id: i64, name: String, } async fn get_users(State(state): State) -> Json> { let conn = state.db.connect().unwrap(); let mut rows = conn.query("SELECT id, name FROM users", ()).await.unwrap(); let mut users = Vec::new(); while let Some(row) = rows.next().await.unwrap() { users.push(User { id: row.get(0).unwrap(), name: row.get(1).unwrap(), }); } Json(users) } #[tokio::main] async fn main() { let db_url = std::env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL must be set"); let auth_token = std::env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN must be set"); let db = Builder::new_remote(db_url, auth_token) .build() .await .unwrap(); let app_state = AppState { db }; let app = Router::new() .route("/users", get(get_users)) .with_state(app_state); axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); } ``` This example creates a shared `Database` instance in the application state, which is then used in the handler to execute queries. # Turso + Rocket Source: https://docs.turso.tech/sdk/rust/guides/rocket Set up Turso in your Rocket project in minutes ![Rocket banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/rocket-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=74dabf48dfb2a2ec82d5750672eab0e9) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Rocket app — [learn more](https://rocket.rs/v0.5/guide/getting-started/) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Add the libsql crate to the project** ```sh theme={null} cargo add libsql ``` > [!NOTE] > > Optionally, you can add a package such as [`dotenvy`](https://docs.rs/dotenvy/latest/dotenvy) to help you work with `.env` files: > > ```sh theme={null} > cargo add dotenvy > ``` **3. Execute SQL** ```rust theme={null} #[get("/todos")] async fn get_todos() -> Json> { dotenv().expect(".env file not found"); let url = env::var("TURSO_DATABASE_URL").expect("TURSO_DATABASE_URL not found!"); let token = env::var("TURSO_AUTH_TOKEN").expect("TURSO_AUTH_TOKEN not found!"); let db = Database::open_remote(url, token).unwrap(); let conn = db.connect().unwrap(); let mut response = conn.query("select * from todos", ()).await.unwrap(); let mut todos: Vec = Vec::new(); while let Some(row) = response.next().unwrap() { let todo: Todo = Todo { task: row.get(0).unwrap(), }; todos.push(todo); } Json(todos) } #[launch] fn rocket() -> _ { dotenv().ok(); rocket::build().mount("/", routes![get_todos]) } ``` ## Examples > **[Turso + Rocket Todo List](https://github.com/tursodatabase/examples/tree/master/app-todo-rocket)** > > See the full source code # Tauri + Turso Source: https://docs.turso.tech/sdk/rust/guides/tauri Set up Turso in your Tauri project in minutes ![Tauri banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/tauri-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=92bdd8d06ba15875c3c553650796b77d) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Tauri app — [learn more](https://tauri.app/v1/guides/getting-started/setup) **1. Add the following crates to the Tauri project's dependencies** ```toml src-tauri/Cargo.toml theme={null} [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } reqwest = { version = "0.11.22", features = ["json", "blocking"] } dotenvy = "0.15.7" tokio = { version = "1", features = ["full"] } libsql = { git = "https://github.com/tursodatabase/libsql" } tracing-subscriber = "0.3" tracing = "0.1.40" [patch.crates-io] sqlite3-parser = { git = "https://github.com/LucioFranco/lemon-rs" } ``` **2. Fetch Rust dependencies inside the `src-tauri` directory.** ```bash theme={null} cargo fetch ``` **3. Configure database credentials** Get the database URL. ```bash theme={null} turso db show --url ``` Get the database authentication token. ```bash theme={null} turso db tokens create ``` Assign acquired credentials to the environment variables inside `src-tauri/.env`. ```bash src-tauri/.env theme={null} TURSO_SYNC_URL="..." TURSO_AUTH_TOKEN="..." DB_PATH=./my-data.db # embedded replica ``` **4. Execute SQL** ```rust src-tauri/src/main.rs theme={null} use dotenvy::dotenv; use libsql::{params, Database}; use serde::{Deserialize, Serialize}; use std::env; use tracing::info; #[derive(Serialize, Debug)] struct Error { msg: String, } type Result = std::result::Result; impl From for Error where T: std::error::Error, { fn from(value: T) -> Self { Self { msg: value.to_string(), } } } #[derive(Deserialize, Serialize, Debug)] pub struct Item { id: String, foo: String, bar: String, } #[tauri::command] async fn get_all_items() -> Result> { dotenv().expect(".env file not found"); let db_path = env::var("DB_PATH").unwrap(); let sync_url = env::var("TURSO_SYNC_URL").unwrap(); let auth_token = env::var("TURSO_AUTH_TOKEN").unwrap(); let db = Database::open_with_remote_sync(db_path, sync_url, auth_token).await?; let conn = db.connect()?; let mut results = conn .query("SELECT * FROM table_name", ()) .await?; let mut items: Vec = Vec::new(); while let Some(row) = results.next()? { let note: Item = Item { id: row.get(0)?, foo: row.get(1)?, bar: row.get(2)?, }; items.push(item); } Ok(items) } fn main() { tracing_subscriber::fmt::init(); tauri::Builder::default() .invoke_handler(tauri::generate_handler![ get_all_items, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` ## Examples > **[Personal Notes App](https://github.com/tursodatabase/examples/tree/master/app-turso-notes)** > > See the full source code # Toasty + Turso Source: https://docs.turso.tech/sdk/rust/orm/toasty Use Toasty, the async ORM from the Tokio project, with Turso Database. [Toasty](https://github.com/tokio-rs/toasty) is an async ORM for Rust maintained by the [Tokio](https://tokio.rs/) project. It ships a native Turso driver (`toasty-driver-turso`) that connects to a local Turso Database file or an in-memory database — the same engine you get from the [`turso` crate](/sdk/rust/quickstart). > [!NOTE] > > The Toasty Turso driver currently targets local and in-memory databases. For sync against Turso Cloud, drop down to the [`turso` crate directly](/sdk/rust/quickstart#sync-push-and-pull). ## Prerequisites * Rust 1.95 or newer (Toasty's minimum supported version) * A Rust project (`cargo init`) * Tokio as your async runtime **1. Install Toasty with the Turso driver** ```bash theme={null} cargo add toasty --features turso cargo add tokio --features full cargo add uuid ``` **2. Define your models** Toasty derives schema, queries, and relations from plain Rust structs: ```rust models.rs theme={null} #[derive(Debug, toasty::Model)] struct User { #[key] #[auto] id: uuid::Uuid, name: String, #[unique] email: String, #[has_many] todos: toasty::Deferred>, } #[derive(Debug, toasty::Model)] struct Todo { #[key] #[auto] id: uuid::Uuid, #[index] user_id: uuid::Uuid, #[belongs_to(key = user_id, references = id)] user: toasty::Deferred, title: String, } ``` **3. Connect to Turso** Pass a `turso:` URL to `Db::builder` — either an in-memory database or a file path: > **In-memory** > > ```rust theme={null} > let db = toasty::Db::builder() > .models(toasty::models!(crate::*)) > .connect("turso::memory:") > .await?; > ``` > **File-backed** > > ```rust theme={null} > let db = toasty::Db::builder() > .models(toasty::models!(crate::*)) > .connect("turso:./app.db") > .await?; > ``` > **With concurrent writes (MVCC)** > > To enable Turso's MVCC journal so multiple writers can run concurrently, build the driver explicitly and pass it to `build()`: > > ```rust theme={null} > use toasty_driver_turso::Turso; > > let driver = Turso::file("app.db").concurrent_writes(); > > let db = toasty::Db::builder() > .models(toasty::models!(crate::*)) > .build(driver) > .await?; > ``` > > When `concurrent_writes()` is enabled, write-write conflicts surface as a serialization failure — check `err.is_serialization_failure()` and retry the transaction. **4. Create the schema and query** ```rust theme={null} #[tokio::main] async fn main() -> toasty::Result<()> { let mut db = toasty::Db::builder() .models(toasty::models!(crate::*)) .connect("turso:./app.db") .await?; db.push_schema().await?; let user = toasty::create!(User { name: "Ada Lovelace", email: "ada@example.com", }) .exec(&mut db) .await?; toasty::create!(in user.todos() { title: "ship Toasty + Turso" }) .exec(&mut db) .await?; let reloaded = User::get_by_email(&mut db, &user.email).await?; println!("{reloaded:#?}"); Ok(()) } ``` ## Where to go next * The [Toasty guide](https://tokio-rs.github.io/toasty/) covers models, relations, transactions, and migrations. * The [Turso chapter](https://tokio-rs.github.io/toasty/nightly/guide/turso.html) of the Toasty guide documents the connection URL forms, the `concurrent_writes()` flag, and the `experimental_*` toggles for Turso's engine features. * The [`turso` crate quickstart](/sdk/rust/quickstart) shows how to use Turso Database directly without an ORM, including sync against Turso Cloud. # Turso Quickstart (Rust) Source: https://docs.turso.tech/sdk/rust/quickstart Get started with Turso and Rust in a few simple steps. In this Rust quickstart we will learn how to: * Install the Turso crate * Connect to a local or remote database * Execute a query using SQL * Sync changes to the cloud ## Recommended: turso (Local + Cloud Sync) `turso` is the recommended crate for running a local database, including synchronizing it to and from Turso Cloud. It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC), async I/O, and native Rust async/await support. **1. Install** ```bash theme={null} cargo add turso tokio --features tokio/full ``` **2. Connect** ```rust theme={null} use turso::Builder; #[tokio::main] async fn main() -> Result<(), Box> { let db = Builder::new_local("app.db").build().await?; let conn = db.connect()?; conn.execute( "CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL )", (), ).await?; conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?; let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let name: String = row.get(1)?; println!("User: {} {}", id, name); } Ok(()) } ``` **3. Sync (push and pull)** If you need to sync your local database with Turso Cloud, enable the `sync` feature: ```bash theme={null} cargo add turso --features sync ``` ```rust theme={null} use turso::sync::Builder; #[tokio::main] async fn main() -> Result<(), Box> { let db = Builder::new_remote("app.db") .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?) .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?) .build() .await?; let conn = db.connect().await?; conn.execute("INSERT INTO users (name) VALUES (?)", ("Bob",)).await?; // Push local writes to Turso Cloud db.push().await?; // Pull remote changes to local database db.pull().await?; Ok(()) } ``` All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See the [reference](/sdk/rust/reference) for checkpoint, stats, and encryption. See [Turso Sync](/sync/usage) for details on conflict resolution and more. > [!NOTE] > > You can test sync locally without a Turso Cloud account by starting a local sync server: > > ```bash theme={null} > tursodb :memory: --sync-server 127.0.0.1:8080 > ``` > > Then use `http://127.0.0.1:8080` as the remote URL (no auth token needed). See [Turso Database quickstart](/tursodb/quickstart) for how to install `tursodb`. ## Remote Access (Over-the-Wire) If your application needs to query a Turso Cloud database directly over the network (e.g., from a web server or serverless function), use the crate that matches your database engine: `turso_serverless` for [Turso databases](/tursodb/quickstart), or `libsql` (with the `remote` feature) for [libSQL](/libsql) databases. > [!NOTE] > > For most applications, we recommend running a local database with sync (`turso::sync`) instead — it gives you faster reads, offline support, and lower latency. Remote access is useful when you cannot store a local database file (e.g., stateless serverless environments). ### Remote Turso database: turso\_serverless `turso_serverless` connects to a remote Turso database over HTTP — no persistent connections, no C compiler required. Its API mirrors the embedded `turso` crate, so code moves between local and remote with minimal changes. **1. Retrieve database credentials** You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} cargo add turso_serverless tokio --features tokio/full ``` **3. Connect and query** ```rust theme={null} use turso_serverless::Builder; #[tokio::main] async fn main() -> Result<(), Box> { let db = Builder::new_remote(std::env::var("TURSO_DATABASE_URL")?) .with_auth_token(std::env::var("TURSO_AUTH_TOKEN")?) .build() .await?; let conn = db.connect()?; let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let name: String = row.get(1)?; println!("User: {} {}", id, name); } Ok(()) } ``` ### Remote libSQL database: libsql The `libsql` crate with the `remote` feature connects to a remote libSQL database over HTTP — no local file needed, no C compiler required. **1. Retrieve database credentials** You will need an existing libSQL database to continue. If you don't have one, [create one](/quickstart). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} cargo add libsql --features remote ``` **3. Connect and query** ```rust theme={null} use libsql::Builder; #[tokio::main] async fn main() -> Result<(), Box> { let url = std::env::var("TURSO_DATABASE_URL")?; let token = std::env::var("TURSO_AUTH_TOKEN")?; let db = Builder::new_remote(url, token).build().await?; let conn = db.connect()?; let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let name: String = row.get(1)?; println!("User: {} {}", id, name); } Ok(()) } ``` ## Using an ORM [Toasty](/sdk/rust/orm/toasty), the async ORM from the Tokio project, has a native Turso driver. It speaks to the `turso` crate directly, so you get a typed model layer (`#[derive(toasty::Model)]`, derived queries, relations) over the same Turso Database engine used in the quickstart above. ```bash theme={null} cargo add toasty --features turso ``` See the [Toasty + Turso guide](/sdk/rust/orm/toasty) for the full walkthrough. ## Embedded Replicas (libsql) > [!NOTE] > > Embedded Replicas give your Rust app a local read copy of a Turso Cloud database. Reads are served locally; writes go to the cloud primary and are reflected back to the replica. Embedded Replicas are fully supported in production. > > For new projects that need sync, we recommend the `turso` crate with `turso::sync`: both reads and writes are local, you sync explicitly with `push()` / `pull()`, and the wire format is logical change-data-capture rather than page frames ([benchmark](https://turso.tech/blog/sync-benchmark)). See the [reference](/sdk/rust/reference) for full documentation on Embedded Replicas with `libsql`. # Reference Source: https://docs.turso.tech/sdk/rust/reference Rust Reference for Turso Turso offers three Rust crates: | | `turso` | `turso_serverless` | `libsql` | | - | - | - | - | | **Use case** | Local / embedded database, sync | Remote Turso database (over-the-wire) | Remote libSQL database, existing libSQL codebases | | **Engine** | Turso (rewrite) | Turso (rewrite) | libSQL (SQLite fork) | | **Concurrent writes** | Yes (MVCC) | Yes (MVCC) | Not supported | | **Sync** | push/pull (local-first) | — | Embedded Replicas (writes go to cloud primary) | | **C compiler** | Not required | Not required | Required for `core`, `replication`, `encryption` features | **Starting a new project?** Use `turso` for local/embedded use or sync. For remote access, match the crate to the database engine: [`turso_serverless`](/sdk/rust/quickstart) for Turso databases, `libsql` with the `remote` feature for libSQL databases (no C compiler needed). ## turso For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O. ### Installing ```bash theme={null} cargo add turso tokio --features tokio/full ``` ### Connecting ```rust theme={null} use turso::Builder; let db = Builder::new_local("app.db").build().await?; let conn = db.connect()?; ``` In-memory databases are also supported: ```rust theme={null} let db = Builder::new_local(":memory:").build().await?; ``` ### Querying ```rust theme={null} conn.execute( "CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL )", (), ).await?; conn.execute("INSERT INTO users (name) VALUES (?)", ("Alice",)).await?; let mut rows = conn.query("SELECT * FROM users", ()).await?; while let Some(row) = rows.next().await? { let id: i64 = row.get(0)?; let name: String = row.get(1)?; println!("User: {} {}", id, name); } ``` ### Prepared Statements ```rust theme={null} let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?; let mut rows = stmt.query([42]).await?; ``` ### Transactions ```rust theme={null} let tx = conn.transaction().await?; tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?; tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?; tx.commit().await?; ``` ### Encryption Encrypt local databases at rest: ```rust theme={null} use turso::{Builder, EncryptionOpts}; let db = Builder::new_local("encrypted.db") .experimental_encryption(true) .with_encryption(EncryptionOpts { cipher: "aegis256".to_string(), hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327".to_string(), }) .build() .await?; ``` Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`. Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them. > [!NOTE] > > Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption). ### Sync (Push and Pull) For local database with cloud sync. All reads and writes happen locally; use `push()` to send changes to the cloud and `pull()` to fetch remote changes. Enable the `sync` feature: ```bash theme={null} cargo add turso --features sync ``` ```rust theme={null} use turso::sync::Builder; let db = Builder::new_remote("app.db") .with_remote_url(&std::env::var("TURSO_DATABASE_URL")?) .with_auth_token(&std::env::var("TURSO_AUTH_TOKEN")?) .bootstrap_if_empty(true) // Download schema on first sync (default) .build() .await?; let conn = db.connect().await?; ``` On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details. #### Push and Pull ```rust theme={null} // Push local writes to Turso Cloud db.push().await?; // Pull remote changes (returns true if changes were applied) let changed = db.pull().await?; ``` #### Checkpoint Compact the local WAL to bound disk usage while preserving sync state: ```rust theme={null} db.checkpoint().await?; ``` #### Stats ```rust theme={null} let stats = db.stats().await?; println!("Network received: {} bytes", stats.network_received_bytes); println!("Network sent: {} bytes", stats.network_sent_bytes); println!("WAL size: {} bytes", stats.main_wal_size); ``` ## libsql (Remote) Use the `libsql` crate with the `remote` feature for over-the-wire access to Turso Cloud. This uses pure Rust HTTP — no C compiler needed. ### Installing ```bash theme={null} cargo add libsql --features remote ``` ### Connecting ```rust theme={null} use libsql::Builder; let url = std::env::var("TURSO_DATABASE_URL")?; let token = std::env::var("TURSO_AUTH_TOKEN")?; let db = Builder::new_remote(url, token).build().await?; let conn = db.connect()?; ``` ### Querying ```rust theme={null} let mut rows = conn.query("SELECT * FROM users WHERE id = ?1", [1]).await?; ``` ## libsql (libSQL) The `libsql` crate is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that powers Turso Cloud today. It is production-ready and battle-tested, and is the right choice when you are working with an existing `libsql`-based codebase. > [!NOTE] > > With `libsql` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend the `turso` crate with `turso::sync` — see the [quickstart](/sdk/rust/quickstart). ### Embedded Replicas You can work with [Embedded Replicas](/features/embedded-replicas) that sync from a Turso Cloud database to a local SQLite file. Reads run locally; writes are sent to the cloud primary and then reflected back to the replica: ```rust theme={null} use libsql::Builder; let url = std::env::var("TURSO_DATABASE_URL")?; let token = std::env::var("TURSO_AUTH_TOKEN")?; let db = Builder::new_remote_replica("local.db", url, token) .build() .await?; let conn = db.connect()?; ``` #### Manual Sync ```rust theme={null} db.sync().await?; ``` #### Sync Interval ```rust theme={null} use std::time::Duration; let db = Builder::new_remote_replica("local.db", url, token) .sync_interval(Duration::from_secs(300)) .build() .await?; ``` #### Read Your Own Writes By default, after a `push()`, the next `pull()` waits for the server to fully catch up with your changes before returning — guaranteeing you always read your own writes. This is safer but **much slower**, since the server must process all pending changes. If you can tolerate eventually-consistent reads, disable this for significantly faster pulls: ```rust theme={null} let db = Builder::new_remote_replica("local.db", url, token) .read_your_writes(false) .build() .await?; ``` ### Encryption > [!WARNING] > > For new projects, we recommend the [`turso`](#turso) crate for local encryption — it is built on the Turso Database engine with better performance and concurrent write support. To enable encryption on a SQLite file, pass the encryption key value as an argument to the constructor: > [!NOTE] > > Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest). ### Conditional compilation The `libsql` crate supports [conditionally compiling](https://doc.rust-lang.org/cargo/reference/features.html#dependency-features) features: | Feature | Description | | - | - | | `remote` | HTTP-only client, pure Rust. No C compiler needed. | | `core` | Local database only. Requires a C compiler. | | `replication` | Combines `core` with embedded replica support. Requires a C compiler. | | `encryption` | Encryption at rest. Requires `cmake`. Not enabled by default. | ### Simple query ```rust theme={null} conn.execute("SELECT * FROM users", ()).await?; conn.execute("SELECT * FROM users WHERE id = ?1", [1]).await?; ``` ### Placeholders ```rust Positional theme={null} conn.execute("SELECT * FROM users WHERE id = ?1", libsql::params![1]).await?; ``` ```rust Named theme={null} conn.execute("INSERT INTO users (name) VALUES (:name)", libsql::named_params! { ":name": "Iku" }).await?; ``` ### Deserialization ```rust theme={null} use libsql::{de, Builder}; #[derive(Debug, serde::Deserialize)] struct User { name: String, age: i64, } let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?; let row = stmt.query([1]).await?.next().await?.unwrap(); let user = de::from_row::(&row)?; ``` ### Batch Transactions ```rust theme={null} conn.execute_batch(r#" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); INSERT INTO users (name) VALUES ('Alice'); INSERT INTO users (name) VALUES ('Bob'); "#).await?; ``` ### Interactive Transactions ```rust Default theme={null} let tx = conn.transaction().await?; tx.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?; tx.execute("INSERT INTO users (name) VALUES (?1)", ["Bob"]).await?; tx.commit().await?; ``` ```rust Advanced control theme={null} use libsql::TransactionBehavior; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate).await?; tx.execute("UPDATE users SET age = age + 1 WHERE id = ?1", [1]).await?; tx.commit().await?; ``` # Turso Quickstart (Swift) Source: https://docs.turso.tech/sdk/swift/quickstart Get started with Turso and Swift using the libSQL client in a few simple steps. In this Swift quickstart we will learn how to: * Retrieve database credentials * Install the libSQL package * Connect to a local or remote Turso database * Execute a query using SQL * Sync changes to local database (optional) **1. Retrieve database credentials** You will need an existing database to continue. If you don't have one, [create one](/quickstart). **2. Install** First begin by adding `libsql` as a package dependency in XCode using this repo: > **[libSQL Swift](https://github.com/tursodatabase/libsql-swift)** > > Build from source code Or add it to your SwiftPM dependencies: ```swift theme={null} import PackageDescription let package = Package( // ... dependencies: [ .package(url: "https://github.com/tursodatabase/libsql-swift", from: "0.1.1"), ], // ... ) ``` **3. Connect** You must first create a `Database` object and then open a `Connection` to it: > **Embedded Replicas** > > ```swift theme={null} > import Libsql > > let db = try Database( > path: "./local.db", > url: "TURSO_DATABASE_URL", > authToken: "TURSO_AUTH_TOKEN", > syncInterval: 300 > ) > > let conn = try db.connect() > ``` > **Local only** > > ```swift theme={null} > import Libsql > > let db = try Database( > path: "./local.db", > ) > > let conn = try db.connect() > ``` > **Remote only** > > ```swift theme={null} > import Libsql > > let db = try Database( > url: "TURSO_DATABASE_URL", > authToken: "TURSO_AUTH_TOKEN" > ) > > let conn = try db.connect() > ``` **4. Execute** You can execute a SQL query against your existing database by calling `query()`, or `execute()` when you expect the query to not yield any rows: ```swift theme={null} let rows = try conn.query("SELECT * FROM users") ``` If you need to use placeholders for values, you can do that: ```swift Positional Query theme={null} try conn.query("SELECT * FROM users WHERE id = ?", 1) ``` ```swift Positional Insert theme={null} try conn.execute("INSERT INTO users (id) VALUES (?)", 1) ``` > [!NOTE] > > Named arguments are not supported yet. **5. Sync (Embedded Replicas only)** When using embedded replicas you should call `sync()` on the database type to sync your local database with the primary database, unless you are using `sync_interval` (though there is no issue with calling `sync` with `sync_interval` enabled): ```swift theme={null} try db.sync() ``` # Swift Reference Source: https://docs.turso.tech/sdk/swift/reference libSQL Swift Reference ## Installing First begin by adding `libsql` as a package dependency in XCode using this repo: > **[libSQL Swift](https://github.com/tursodatabase/libsql-swift)** > > Build from source code Or add it to your SwiftPM dependencies: ```swift theme={null} import PackageDescription let package = Package( // ... dependencies: [ .package(url: "https://github.com/tursodatabase/libsql-swift", from: "0.1.1"), ], // ... ) ``` ## In-Memory Databases libSQL supports connecting to [in-memory databases](https://www.sqlite.org/inmemorydb.html) for cases where you don't require persistence: ```swift theme={null} import Libsql let db = Database(":memory:") let conn = try db.connect() ``` ## Local Development You can work locally using an SQLite file: ```swift theme={null} import Libsql let db = Database("local.db") let conn = try db.connect() ``` ## Embedded Replicas You can work with embedded replicas that can sync from the remote URL and delegate writes to the remote primary database: ```swift theme={null} import Libsql let db = try Database( path: "./local.db", url: "TURSO_DATABASE_URL", authToken: "TURSO_AUTH_TOKEN" ) let conn = try db.connect() ``` ### Manual Sync The `sync` function allows you to sync manually the local database with the remote counterpart: ```swift theme={null} try db.sync() // Call sync manually to update local database ``` ### Sync Interval The `syncInterval` parameter allows you to set an interval for automatic synchronization of the database in the background: ```swift theme={null} import Libsql let db = try Database( path: "./local.db", url: "TURSO_DATABASE_URL", authToken: "TURSO_AUTH_TOKEN" syncInterval: 300 // Sync every 3 seconds ) let conn = try db.connect() ``` ### Read Your Own Writes The `readYourWrites` parameter configures the database connection to ensure that writes are immediately visible to subsequent read operations initiated by the same connection. This is **enabled by default**, and is particularly important in distributed systems to ensure consistency from the perspective of the writing process. You can disable this behavior by passing `false` to the function: ```swift theme={null} import Libsql let db = try Database( path: "./local.db", url: , authToken: , readYourWrites: false ) let conn = try db.connect() ``` ## Simple query You can pass a string to `query()` to invoke a SQL statement, as well as optional arguments: ```swift Execute theme={null} try conn.execute("INSERT INTO users VALUES (?)", [1]) ``` ```swift Query theme={null} try conn.query("SELECT * FROM users") ``` ```swift Arguments theme={null} try conn.query("SELECT * FROM users WHERE id = ?", [1]) ``` ## Prepared Statements You can prepare a cached statement using `prepare()` and then execute it with `query()`: ```swift theme={null} let stmt = try conn.prepare("SELECT * FROM users WHERE id = ?") stmt.bind([1]) stmt.query() ``` ## Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```swift Positional theme={null} try conn.query("SELECT * FROM users WHERE id = ?", [1]) ``` ```swift Named theme={null} try conn.query("SELECT * FROM users WHERE id = :id", [ ":id": 1 ]) ``` # Examples Source: https://docs.turso.tech/sdk/ts/examples # Astro + Turso Source: https://docs.turso.tech/sdk/ts/guides/astro Set up Turso in your Astro project in minutes. ![Astro banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/astro-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=5b0999657788a4cdccf007d44d34ca89) ## Prerequisites To get the most out of this guide, you'll need to: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have an Astro project — [learn more](https://docs.astro.build/en/install/auto/#1-run-the-setup-wizard) **1. Install the libSQL SDK** **2. Configure database credentials** **3. Configure libSQL client** ```ts src/turso.ts theme={null} import { createClient } from "@libsql/client/web"; export const turso = createClient({ url: import.meta.env.TURSO_DATABASE_URL!, authToken: import.meta.env.TURSO_AUTH_TOKEN, }); ``` > [!NOTE] > > Astro will soon introduce a new ENV API. [Take a > look](https://docs.astro.build/en/reference/configuration-reference/#experimentalenv). **4. Execute SQL** ```ts theme={null} --- import { turso } from './turso' const { rows } = await turso.execute('SELECT * FROM table_name') --- ``` ## Examples > **[Blog](https://github.com/tursodatabase/examples/tree/master/app-tustro-blog)** > > See the full source code # Elysia + Turso Source: https://docs.turso.tech/sdk/ts/guides/elysia Set up Turso in your Elysia project in minutes. ![Elysia banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/elysia-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=feb0570b5243f4be9c133fe35f0f8f42) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have an Elysia app — [learn more](https://elysiajs.com/quick-start.html) **1. Install the libSQL SDK** **2. Retrieve database credentials** **3. Configure libSQL client** **4. Execute SQL** ```ts theme={null} import { Elysia } from "elysia"; import { turso } from "./lib/turso"; const app = new Elysia().get("/items", async () => { const { rows } = await turso.execute("SELECT * FROM items"); return rows; }); ``` ## Examples > **[Expenses tracker app with Elysia & Turso](https://github.com/tursodatabase/examples/tree/master/app-expenses-tracker-elysia)** > > See the full source code # Hono + Turso Source: https://docs.turso.tech/sdk/ts/guides/hono Set up Turso in your Hono project in minutes. ![Hono banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/hono-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=1c88c1bf0425b350d511be756e47f83f) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Hono app — [learn more](https://hono.dev/top#quick-start) **1. Install the libSQL SDK** **2. Retrieve database credentials** **3. Configure libSQL client** **4. Execute SQL** ```ts theme={null} import { Hono } from "hono"; import { turso } from "./lib/turso"; const app = new Hono(); app.get("/items", async (c) => { const { rows } = await turso.execute("SELECT * FROM items"); return c.json({ rows }); }); ``` ## Examples > **[Expenses tracker app with Hono & Turso](https://github.com/tursodatabase/examples/tree/master/app-expenses-tracker-hono)** > > See the full source code # Next.js + Turso Source: https://docs.turso.tech/sdk/ts/guides/nextjs Set up Turso in your Next.js project in minutes. ![Next.js banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/nextjs-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=70fd7aa0941f6871ae5e4a2eb3292cce) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Next.js app — [learn more](https://nextjs.org/docs/getting-started/installation) **1. Install the libSQL SDK** **2. Configure database credentials** **3. Configure libSQL client** **4. Execute SQL** ```tsx App Router theme={null} import { turso } from "@/lib/turso"; export default async function Page() { const { rows } = await turso.execute("SELECT * FROM table_name"); return (
    {rows.map((row) => (
  • {row.id}
  • ))}
); } ``` ```ts Pages Directory theme={null} import type { InferGetServerSidePropsType, GetServerSideProps } from "next"; import { turso } from "@/lib/turso"; export const getServerSideProps = (async () => { const { rows } = await turso.execute("SELECT * FROM table_name"); return { props: { rows, }, }; }) satisfies GetServerSideProps<{ rows: any[] }>; export default function Page({ rows, }: InferGetServerSidePropsType) { return (
    {rows.map((row) => (
  • {row.id}
  • ))}
); } ``` ## Examples > **[Full Stack App](https://github.com/tursodatabase/nextjs-turso-starter)** > > Build with Next.js, Turso, and Drizzle ORM. # Nuxt + Turso Source: https://docs.turso.tech/sdk/ts/guides/nuxt Set up Turso in your Nuxt project in minutes ![Nuxt banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/nuxt-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=9c0353730a9944cd5e99e71cb2666025) Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Nuxt app — [learn more](https://nuxt.com/docs/getting-started/installation#new-project) **1. Install the libSQL SDK** **2. Configure database credentials** Get the database URL: ```bash theme={null} turso db show --url ``` Get the database authentication token: ```bash theme={null} turso db tokens create ``` Assign credentials to the environment variables inside `.env`. ```bash theme={null} NUXT_TURSO_DATABASE_URL= NUXT_TURSO_AUTH_TOKEN= ``` **3. Configure variables inside Nuxt's runtime config.** ```ts nuxt.config.ts theme={null} export default defineNuxtConfig({ runtimeConfig: { turso: { databaseUrl: "", authToken: "", }, }, }); ``` > [!NOTE] > > Make sure that names of the keys in the `runtimeConfig` object match the names > of your environment variables. Read more about this > [here](https://nuxt.com/docs/guide/going-further/runtime-config). **4. Configure libSQL Client.** ```ts server/utils/turso.ts theme={null} import { createClient } from "@libsql/client"; // You can optionally pass in the event to useRuntimeConfig // import { H3Event } from "h3"; export function useTurso(/* event: H3Event */) { const { turso } = useRuntimeConfig(/* event */); return createClient({ url: turso.databaseUrl, authToken: turso.authToken, }); } ``` **5. Execute SQL** ```ts server/api/items.get.ts theme={null} export default defineEventHandler(async (event) => { const client = useTurso(/* event */); const { rows } = await client.execute("select * from table_name"); return { data: { items: rows, }, }; }); ``` ## Examples > **[Website + App](https://github.com/tursodatabase/examples/tree/master/app-top-web-frameworks)** > > See the full source code # Quasar + Turso Source: https://docs.turso.tech/sdk/ts/guides/quasar Set up Turso in your Quasar project in minutes ![Quasar banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/quasar-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=182fddd15a287e68b1780415464cec13) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Quasar app — [learn more](https://quasar.dev/start/quick-start#step-1-create-a-project) **1. Install the libSQL SDK** **2. Configure database credentials** Get the database URL: ```bash theme={null} turso db show --url ``` Get the database authentication token: ```bash theme={null} turso db tokens create ``` Assign credentials to the environment variables inside `.env.local`. ```bash theme={null} VITE_TURSO_DATABASE_URL="..." VITE_TURSO_AUTH_TOKEN="..." ``` **3. Configure libSQL Client** ```js theme={null} import { createClient } from "@libsql/client/web"; const turso = createClient({ url: import.meta.env.VITE_TURSO_DATABASE_URL, authToken: import.meta.env.VITE_TURSO_AUTH_TOKEN, }); ``` > [!NOTE] > > Avoid a [gotcha > moment](https://github.com/quasarframework/quasar/discussions/16071) by > modifying the app configuration using the settings below. ```javascript quasar.config.js theme={null} { build: { target: { browser: [ 'es2020', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ], node: 'node16' }, extendViteConf(config) { config.optimizeDeps = { esbuildOptions: { target: 'es2020', } } } } } ``` **4. Fetch data from Turso.** ```js IndexPage.vue theme={null} import { ref } from "vue"; const items = ref(); const { rows } = await turso.execute("select * from my-table"); items.value = rows; ``` ## Examples > **[Todo App](https://github.com/tursodatabase/examples/tree/master/quasar-todo-list)** > > See the full source code # Qwik + Turso Source: https://docs.turso.tech/sdk/ts/guides/qwik Set up Turso in your Qwik project in minutes ![Qwik banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/qwik-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=bd50c1555e2915546f901981ac6efba5) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Qwik app — [learn more](https://qwik.builder.io/docs/getting-started/#create-an-app-using-the-cli) **1. Add Turso Integration** ```bash npm theme={null} npm run qwik add turso ``` ```bash pnpm theme={null} pnpm qwik add turso ``` ```bash yarn theme={null} yarn qwik add turso ``` **2. Configure database credentials** Get the database URL: ```bash theme={null} turso db show --url ``` Get the database authentication token: ```bash theme={null} turso db tokens create ``` Assign credentials to the environment variables inside `.env.local`. ```bash theme={null} PRIVATE_TURSO_DATABASE_URL="..." PRIVATE_TURSO_AUTH_TOKEN="..." ``` **3. Execute SQL** ```ts theme={null} import { tursoClient } from "~/utils/turso"; export const useFrameworks = routeLoader$( async (requestEvent: RequestEventBase) => { const db = tursoClient(requestEvent["env"]); const { rows } = await db.execute("select * from table_name"); return { items: rows, }; }, ); ``` ## Examples > **[Social Website](https://github.com/tursodatabase/examples/tree/master/app-find-me-on)** > > See the full source code > **[Shopping Cart](https://github.com/tursodatabase/examples/tree/master/app-turqw-store)** > > See the full source code # Remix + Turso Source: https://docs.turso.tech/sdk/ts/guides/remix Set up Turso in your Remix project in minutes ![Remix banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/remix-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=84822de5a6eb5531447a66d6afe6662a) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a Remix app — [learn more](https://remix.run/docs/en/main/start/quickstart#quick-start) **1. Install the libSQL SDK** **2. Configure database credentials** **3. Configure libSQL Client.** **4. Execute SQL** ```ts app/routes/_index.ts theme={null} import type { LoaderFunction } from "@remix-run/node"; import { turso } from "~/lib/turso"; export const loader: LoaderFunction = async () => { const { rows } = await turso.execute("SELECT * from TABLE_NAME"); return { items: rows, }; }; ``` ## Examples > **[E-commerce Store](https://github.com/tursodatabase/examples/tree/master/app-the-mug-store)** > > See the full source code > **[CRM App](https://github.com/tursodatabase/examples/tree/master/app-turso-crm)** > > See the full source code # SvelteKit + Turso Source: https://docs.turso.tech/sdk/ts/guides/sveltekit Set up Turso in your SvelteKit project in minutes ![SvelteKit banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/svelte-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=c5f43db5d48bf2eb2657f687d1f60fa2) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Have a SvelteKit app — [learn more](https://kit.svelte.dev/docs/creating-a-project) **1. Install the libSQL SDK** **2. Configure database credentials** **3. Configure libSQL Client.** ```ts Node.js / Serverless theme={null} import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from "$env/static/private"; import { createClient } from "@libsql/client"; export const turso = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN, }); ``` ```ts Edge Runtimes theme={null} import { TURSO_DATABASE_URL, TURSO_AUTH_TOKEN } from "$env/static/private"; import { createClient } from "@libsql/client/web"; export const turso = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN, }); ``` **4. Execute SQL** ```ts src/routes/+page.server.ts theme={null} import { turso } from "$lib/turso.server"; export async function load() { const { rows } = await turso.execute("SELECT * FROM table_name"); return { rows }; } ``` ```svelte src/routes/+page.svelte theme={null}
    {#each data.rows as row}
  • {row.id}
  • {/each}
``` ## Examples > **[Blog](https://github.com/tursodatabase/examples/tree/master/app-at-the-polls)** > > See the full source code # Sentry Source: https://docs.turso.tech/sdk/ts/integrations/sentry Trace slow queries and capture SQL errors with Sentry. The [Sentry](https://sentry.io/integrations/turso) integration for `@libsql/client` provides tracing, breadcrumbs, and error handling for SQL queries. ## Install ```bash theme={null} npm install sentry-integration-libsql-client ``` ## Quickstart Once you have a `@libsql/client` instance, you can integrate it with Sentry like this: ```ts theme={null} import * as Sentry from "@sentry/node"; import { createClient } from "@libsql/client"; import { libsqlIntegration } from "sentry-integration-libsql-client"; const libsqlClient = createClient({ url: "libsql://...", authToken: "...", }); Sentry.init({ dsn: "...", integrations: [libsqlIntegration(libsqlClient, Sentry)], }); await libsqlClient.execute("SELECT * FROM users"); ``` ## Options By default, everything is turned on. You can configure what you want by passing a third argument to the `libsqlIntegration` function. | Option | Default | Description | | - | - | - | | `tracing` | `true` | Enable tracing for SQL queries. | | `breadcrumbs` | `true` | Enable breadcrumbs for SQL queries. | | `errors` | `true` | Enable error handling for SQL queries. | ```ts theme={null} Sentry.init({ dsn: "...", integrations: [ libsqlIntegration(libsqlClient, Sentry, { tracing: false, breadcrumbs: false, errors: false, }), ], }); ``` # Drizzle + Turso Source: https://docs.turso.tech/sdk/ts/orm/drizzle Configure Drizzle to work with Turso ![Drizzle banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/drizzle-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=fa7f430abb2a3b3df72c6ca45649b099) > [!NOTE] > > Drizzle uses `@libsql/client` for Turso integration. There is also [beta support for `@tursodatabase/database`](https://orm.drizzle.team/docs/connect-turso-database) for local/embedded use cases. ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) **1. Install Drizzle and the libSQL SDK** ```bash npm theme={null} npm i drizzle-orm @libsql/client dotenv npm i -D drizzle-kit ``` ```bash pnpm theme={null} pnpm add drizzle-orm @libsql/client dotenv pnpm add -D drizzle-kit ``` ```bash yarn theme={null} yarn add drizzle-orm @libsql/client dotenv yarn add -D drizzle-kit ``` Finish by updating `package.json` to include three new `scripts`: ```json theme={null} { "scripts": { "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:studio": "drizzle-kit studio" } } ``` **2. Retrieve database credentials** **3. Create a Drizzle schema** ```ts db/schema.ts theme={null} import { sql } from "drizzle-orm"; import { text, sqliteTable } from "drizzle-orm/sqlite-core"; export const fooTable = sqliteTable("foo", { bar: text("bar").notNull().default("Hey!"), }); ``` **4. Configure Drizzle Kit** Create the file `drizzle.config.ts` in the root of your project with the following: ```ts drizzle.config.ts theme={null} require("dotenv").config(); import type { Config } from "drizzle-kit"; export default { schema: "./db/schema.ts", out: "./migrations", dialect: "turso", dbCredentials: { url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN, }, } satisfies Config; ``` > [!NOTE] > > We're using `dotenv` above, but if you're using something like Next.js, Remix, Astro, or Vite, you can use their built-in environment variables manager to source these values. **5. Connect Drizzle with libSQL** ```ts Node.js / Serverless theme={null} import { drizzle } from "drizzle-orm/libsql"; import { createClient } from "@libsql/client"; const turso = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN, }); export const db = drizzle(turso); ``` ```ts Edge Runtimes theme={null} import { drizzle } from "drizzle-orm/libsql"; import { createClient } from "@libsql/client/web"; const turso = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN, }); export const db = drizzle(turso); ``` **6. Database migrations** Drizzle can generate and apply database migrations with `drizzle-kit`. Whenever you make changes to the schema, run `db:generate`: ```bash theme={null} npm run db:generate ``` Now apply these changes to the database with `db:migrate`: ```bash theme={null} npm run db:migrate ``` **7. Query** ```ts theme={null} import { db } from "./db"; import { fooTable } from "./schema"; const result = await db.select().from(fooTable).all(); ``` **8. Connect Drizzle Studio** ```bash npm theme={null} npm run db:studio ``` ```bash pnpm theme={null} pnpm run db:studio ``` ## Vector Embeddings You can extend Drizzle to support Turso's native vector — [learn more](/features/ai-and-embeddings). **1. Define custom vector type** Inside `db/schema.ts`, add the following: ```typescript theme={null} import { sql } from "drizzle-orm"; import { customType } from "drizzle-orm/sqlite-core"; const float32Array = customType<{ data: number[]; config: { dimensions: number }; configRequired: true; driverData: Buffer; }>({ dataType(config) { return `F32_BLOB(${config.dimensions})`; }, fromDriver(value: Buffer) { return Array.from(new Float32Array(value.buffer)); }, toDriver(value: number[]) { return sql`vector32(${JSON.stringify(value)})`; }, }); ``` **2. Create a table with a vector column** Now where you define the schema, invoke `float32Array` to create a column that stores vectors: ```typescript theme={null} import { sqliteTable, integer } from "drizzle-orm/sqlite-core"; export const vectorTable = sqliteTable("vector_table", { id: integer("id").primaryKey(), vector: float32Array("vector", { dimensions: 3 }), }); ``` **3. Create a vector index** You will need to use raw SQL to create the index: ```typescript theme={null} import { drizzle } from "drizzle-orm/libsql"; import { createClient } from "@libsql/client"; const client = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN, }); const db = drizzle(client); await db.run(sql` CREATE INDEX IF NOT EXISTS vector_index ON vector_table(vector) USING vector_cosine(3) `); ``` **4. Insert vector data** ```typescript theme={null} await db .insert(vectorTable) .values([{ vector: sql`vector32(${JSON.stringify([1.1, 2.2, 3.3])})` }]); ``` **5. Query vector data** Calculate vector distance: ```typescript theme={null} const res = await db .select({ distance: sql`vector_distance_cos(${vectorTable.vector}, vector32(${JSON.stringify([2.2, 3.3, 4.4])}))`, }) .from(vectorTable); console.log(res); ``` Perform efficient nearest neighbor search: ```typescript theme={null} const topK = await db .select({ id: sql`id`, distance: sql`distance`, }) .from( sql`vector_top_k('vector_index', vector32(${JSON.stringify([2.2, 3.3, 4.4])}), 5)`, ) .leftJoin(vectorTable, sql`${vectorTable}.id = id`); console.log(topK); ``` Remember to create appropriate indexes for efficient vector operations and adjust vector dimensions as needed for your use case. # Prisma + Turso Source: https://docs.turso.tech/sdk/ts/orm/prisma Configure Prisma to work with your Turso database ![Prisma banner](https://mintcdn.com/turso/7mjM7fXIfeZ8ZwNC/images/guides/prisma-banner.png?fit=max\&auto=format\&n=7mjM7fXIfeZ8ZwNC\&q=85\&s=ce5d303037fe9f45a7918d238def83f4) ## Prerequisites Before you start, make sure you: * [Install the Turso CLI](/cli/installation) * [Sign up or login to Turso](/cli/authentication#signup) * Prisma versions 5.4.2 and later **1. Install the libSQL SDK and its Prisma driver** ```bash npm theme={null} npm install @libsql/client @prisma/adapter-libsql ``` ```bash pnpm theme={null} pnpm add @libsql/client @prisma/adapter-libsql ``` ```bash yarn theme={null} yarn add @libsql/client @prisma/adapter-libsql ``` **2. Retrieve database credentials** **3. Enable the `driverAdapters` preview feature flag:** ```js prisma/schema.prisma theme={null} generator client { provider = "prisma-client-js" previewFeatures = ["driverAdapters"] } datasource db { provider = "sqlite" url = "file:./dev.db" } ``` **4. Generate Prisma client** ```sh npm theme={null} npx prisma generate ``` ```sh pnpm theme={null} pnpm dlx prisma generate ``` **5. Update your Prisma Client Instance** ```ts Node.js / Serverless theme={null} import { PrismaClient } from "@prisma/client"; import { PrismaLibSQL } from "@prisma/adapter-libsql"; const adapter = new PrismaLibSQL({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }) const prisma = new PrismaClient({ adapter }) ``` ```ts Edge Runtimes theme={null} import { PrismaClient } from "@prisma/client"; import { PrismaLibSQL } from "@prisma/adapter-libsql"; const adapter = new PrismaLibSQL({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }) const prisma = new PrismaClient({ adapter }) ``` **6. Database Migrations** Prisma Migrate and Introspection workflows are currently not supported when working with Turso — [learn more](https://www.prisma.io/docs/orm/overview/databases/turso#how-to-manage-schema-changes). First, generate a migration file using prisma migrate dev against a local SQLite database ```sh npm theme={null} npx prisma migrate dev --name init ``` ```sh pnpm theme={null} pnpm dlx prisma migrate dev --name init ``` Then, apply the migration to your Turso database using the Turso's CLI ```bash theme={null} turso db shell turso-prisma-db < ./prisma/migrations/20230922132717_init/migration.sql ``` > [!NOTE] > > Replace `20230922132717_init` with the name of your migration created from the `npx prisma migrate dev` command. **7. Query** ```ts theme={null} const response = await prisma.table_name.findMany(); ``` # Turso Quickstart (TypeScript) Source: https://docs.turso.tech/sdk/ts/quickstart Get started with Turso and TypeScript in a few simple steps. In this TypeScript quickstart we will learn how to: * Install the Turso package * Connect to a local or remote database * Execute a query using SQL * Sync changes to the cloud ## Recommended: @tursodatabase/database (Local / Embedded) `@tursodatabase/database` is the recommended package for local and embedded use cases (Node.js, Electron, mobile, IoT). It is built on the Turso Database engine — a ground-up rewrite of SQLite with concurrent writes (MVCC) and async I/O. **1. Install** ```bash theme={null} npm install @tursodatabase/database ``` **2. Connect** ```ts theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("app.db"); ``` **3. Execute** ```ts theme={null} const createTable = db.prepare(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL ) `); await createTable.run(); const insertUser = db.prepare("INSERT INTO users (username) VALUES (?)"); await insertUser.run("alice"); const stmt = db.prepare("SELECT * FROM users"); const users = await stmt.all(); console.log(users); ``` **4. Sync (push and pull)** If you need to sync your local database with Turso Cloud, use [`@tursodatabase/sync`](/sync/usage): ```bash theme={null} npm install @tursodatabase/sync ``` ```ts theme={null} import { connect } from "@tursodatabase/sync"; const db = await connect({ path: "./app.db", url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); await db.exec("INSERT INTO users (username) VALUES ('bob')"); // Push local writes to Turso Cloud await db.push(); // Pull remote changes to local database const changed = await db.pull(); ``` All reads and writes happen against the local database file — fast, offline-capable. `push()` sends your changes to the cloud. `pull()` brings remote changes down. See [Turso Sync](/sync/usage) for details on conflict resolution, checkpointing, and more. ## Remote Turso database: @tursodatabase/serverless `@tursodatabase/serverless` is the package for applications that connect to a remote [Turso database](/tursodb/quickstart) on Turso Cloud over the network — including Node.js servers, Docker containers, serverless functions, and edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy). It uses only `fetch` — zero native dependencies, works everywhere. **1. Retrieve database credentials** You will need an existing Turso database to continue. If you don't have one, create one with `turso db create --tursodb` (see the [quickstart](/quickstart)). > [!NOTE] > > You will want to store these as environment variables. **2. Install** ```bash theme={null} npm install @tursodatabase/serverless ``` **3. Connect to your database** ```ts theme={null} import { connect } from "@tursodatabase/serverless"; const conn = connect({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); ``` For compatibility with the `@libsql/client` API, use the compat module: ```ts theme={null} import { createClient } from "@tursodatabase/serverless/compat"; const client = createClient({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); ``` **4. Execute a query using SQL** ```ts theme={null} const stmt = await conn.prepare("SELECT * FROM users"); const rows = await stmt.all(); ``` If you need to use placeholders for values, you can do that: ```ts theme={null} const stmt = await conn.prepare("SELECT * FROM users WHERE id = ?"); const row = await stmt.get([1]); ``` ## Remote libSQL database: @libsql/client `@libsql/client` is the package for applications that connect to a remote [libSQL](/libsql) database on Turso Cloud. It is also the package to use for ORM integration (Drizzle, Prisma). See the [reference](/sdk/ts/reference#libsqlclient) for full documentation. **1. Install** **2. Connect** **3. Execute** ```ts theme={null} await turso.execute("SELECT * FROM users"); ``` **4. Sync** If you need to sync your local database with a remote Turso Cloud database (local reads and writes with push/pull to the cloud), use [Turso Sync](/sync/usage). Turso Sync is built on the Turso Database engine and provides local-first sync with explicit `push()` and `pull()` operations. # Reference Source: https://docs.turso.tech/sdk/ts/reference TypeScript Reference for Turso Turso offers four TypeScript packages: | | `@tursodatabase/database` | `@tursodatabase/sync` | `@tursodatabase/serverless` | `@libsql/client` | | - | - | - | - | - | | **Use case** | Local / embedded database | Local database + cloud sync | Remote Turso database (servers, containers, serverless, edge) | Remote libSQL database, ORM support (Drizzle, Prisma) | | **Engine** | Turso (rewrite) | Turso (rewrite) | Turso | libSQL (SQLite fork) | | **Dependencies** | Native (Node.js, WASM) | Native (Node.js) | `fetch` only — zero native deps | Requires Node.js or `/web` subpath | | **Concurrent writes** | Yes (MVCC) | Yes (MVCC) | Yes (MVCC) | Not supported | | **Sync** | — | push/pull (local-first) | — | Embedded Replicas (writes go to cloud primary) | | **ORM support** | Drizzle (beta) | — | — | Drizzle, Prisma, and others | **Starting a new project?** Use `@tursodatabase/database` for local/embedded use, `@tursodatabase/sync` for local + cloud sync, and `@tursodatabase/serverless` to reach a remote [Turso database](/tursodb/quickstart) over the network (Node.js servers, Docker containers, serverless functions, edge runtimes). **Connecting to a remote [libSQL](/libsql) database, or using an ORM?** Use `@libsql/client` — it's production-ready and supported by Drizzle, Prisma, and others. Drizzle has [beta support](https://orm.drizzle.team/docs/connect-turso-database) for `@tursodatabase/database` for local/embedded use. The following runtime environments are known to be compatible: * Node.js version 12 or later * Deno * CloudFlare Workers * Netlify & Vercel Edge Functions ## @tursodatabase/database For local and embedded use. Built on the Turso Database engine with concurrent writes (MVCC) and async I/O. ### Installing ```bash theme={null} npm install @tursodatabase/database ``` ### Initializing ```ts theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("app.db"); ``` In-memory databases are also supported: ```ts theme={null} const db = await connect(":memory:"); ``` ### Querying ```ts theme={null} const stmt = db.prepare("SELECT * FROM users"); const users = await stmt.all(); const insert = db.prepare("INSERT INTO users (username) VALUES (?)"); await insert.run("alice"); ``` ### Encryption Encrypt local databases at rest using the `encryption` option: ```ts theme={null} import { connect } from "@tursodatabase/database"; const db = await connect("encrypted.db", { encryption: { cipher: "aegis256", hexkey: "b1bbfda4f589dc9daaf004fe21111e00dc00c98237102f5c7002a5669fc76327", }, }); ``` Supported ciphers: `aegis256`, `aegis256x2`, `aegis128l`, `aegis128x2`, `aegis128x4`, `aes256gcm`, `aes128gcm`. Encrypted databases cannot be read as standard SQLite databases — you must use the Turso Database engine to open them. > [!NOTE] > > Turso Cloud databases can also be encrypted with bring-your-own-key — [learn more](/cloud/encryption). ## @tursodatabase/sync For local database with cloud sync. All reads and writes happen locally; use `push()` to send changes to the cloud and `pull()` to fetch remote changes. ### Installing ```bash theme={null} npm install @tursodatabase/sync ``` ### Initializing ```ts theme={null} import { connect } from "@tursodatabase/sync"; const db = await connect({ path: "./app.db", url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); ``` On the first run, the local database is automatically bootstrapped from the remote. See [Turso Sync](/sync/usage) for full details. ### Push and Pull ```ts theme={null} // Push local writes to Turso Cloud await db.push(); // Pull remote changes to local database const changed = await db.pull(); ``` ### Checkpoint Compact the local WAL to bound disk usage while preserving sync state: ```ts theme={null} await db.checkpoint(); ``` ### Stats ```ts theme={null} const s = await db.stats(); console.info({ cdcOperations: s.cdcOperations, mainWalSize: s.mainWalSize, networkReceivedBytes: s.networkReceivedBytes, networkSentBytes: s.networkSentBytes, lastPullUnixTime: s.lastPullUnixTime, lastPushUnixTime: s.lastPushUnixTime, revision: s.revision, }); ``` ## @tursodatabase/serverless The recommended package for **any application that connects to a remote Turso Cloud database** — Node.js servers, Docker containers, serverless functions (AWS Lambda, Vercel Functions), and edge runtimes (Cloudflare Workers, Deno Deploy). Uses only `fetch` — zero native dependencies, works everywhere `fetch` is available. ### Installing ```bash theme={null} npm install @tursodatabase/serverless ``` ### Initializing ```ts theme={null} import { connect } from "@tursodatabase/serverless"; const conn = connect({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); ``` For compatibility with the `@libsql/client` API, use the compat module: ```ts theme={null} import { createClient } from "@tursodatabase/serverless/compat"; const client = createClient({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); ``` ### Querying ```ts theme={null} const stmt = await conn.prepare("SELECT * FROM users"); const rows = await stmt.all(); const stmt2 = await conn.prepare("SELECT * FROM users WHERE id = ?"); const row = await stmt2.get([1]); ``` ## @libsql/client The `@libsql/client` package is built on [libSQL](https://github.com/tursodatabase/libsql), the open-source fork of SQLite that has powered Turso Cloud for years. It is production-ready, battle-tested, and the right choice for remote access to libSQL databases, when you need ORM integration beyond Drizzle (e.g., Prisma), or when working with an existing `@libsql/client`-based codebase. > [!NOTE] > > With `@libsql/client` Embedded Replicas, reads are local and writes are sent to the cloud primary, then reflected back to the replica. Embedded Replicas are fully supported. For new projects that need sync, we recommend `@tursodatabase/sync` with [Turso Sync](/sync/usage). ### Installing ### Initializing > [!NOTE] > > If you're using libsql locally or an sqlite file, you can ignore passing `authToken`. ### In-Memory Databases ```ts {4} theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: ":memory:", }); ``` ### Local Development You can work locally using an SQLite file and passing the path to `createClient`: ```ts {4} theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:path/to/db-file.db", authToken: "...", }); ``` > [!WARNING] > > The `@libsql/client/web` does not support local file URLs. ### Embedded Replicas > [!NOTE] > > For workloads that need **offline writes**, **bidirectional sync**, or **multi-writer convergence**, we recommend [`@tursodatabase/sync`](#tursodatabasesync) — both reads and writes are local, and you sync explicitly with `push()` / `pull()`. You can work with embedded replicas by passing your Turso Database URL to `syncUrl`: ```ts {5} theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:path/to/db-file.db", syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io", authToken: "...", }); ``` #### Manual Sync ```ts theme={null} await client.sync(); ``` #### Periodic Sync ```ts {6} theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: "file:path/to/db-file.db", syncUrl: "libsql://[databaseName]-[organizationSlug].turso.io", syncInterval: 60, authToken: "...", }); ``` ### Encryption > [!WARNING] > > For new projects, we recommend [`@tursodatabase/database`](#tursodatabasedatabase) for local encryption — it is built on the Turso Database engine with better performance and concurrent write support. Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — [learn more](/libsql#encryption-at-rest). ### Concurrency By default, the client performs up to `20` concurrent requests: ```ts {4} theme={null} import { createClient } from "@libsql/client"; const client = createClient({ concurrency: 10, }); ``` ### Response Each method listed below returns a `Promise`: | Property | Type | Description | | - | - | - | | `rows` | `Array` | An array of Row objects containing the row values, empty for write operations | | `columns` | `Array` | An array of strings with the names of the columns in the order they appear in each Row, empty for write operations | | `rowsAffected` | `number` | The number of rows affected by a write statement, `0` otherwise | | `lastInsertRowid` | `bigint \| undefined` | The ID of a newly inserted row, or `undefined` if there is none for the statement | ### Simple query You can pass a string or object to `execute()` to invoke a SQL statement: ```ts String theme={null} const result = await client.execute("SELECT * FROM users"); ``` ```ts Object theme={null} const result = await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [1], }); ``` ### Placeholders libSQL supports the use of positional and named placeholders within SQL statements: ```ts Positional theme={null} const result = await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [1], }); const result = await client.batch( [ { sql: "INSERT INTO users VALUES (?)", args: ["Iku"], }, ], "write", ); ``` ```ts Named theme={null} const result = await client.execute({ sql: "INSERT INTO users VALUES (:name)", args: { name: "Iku" }, }); const result = await client.batch( [ { sql: "INSERT INTO users VALUES (:name)", args: { name: "Iku" }, }, ], "write", ); ``` > [!NOTE] > > libSQL supports the same named placeholder characters as SQLite — `:`, `@` and `$`. ### Transaction Modes | Mode | SQLite command | Description | | - | - | - | | `write` | `BEGIN IMMEDIATE` | The transaction may execute statements that read and write data. Write transactions executed on a replica are forwarded to the primary instance, and can't operate in parallel. | | `read` | `BEGIN TRANSACTION READONLY` | The transaction may only execute statements that read data (select). Read transactions can occur on replicas, and can operate in parallel with other read transactions. | | `deferred` | `BEGIN DEFERRED` | The transaction starts in read mode, then changes to write as soon as a write statement is executed. This mode change may fail if there is a write transaction currently executing on the primary. | ### Batch Transactions A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications. ```ts theme={null} const result = await client.batch( [ { sql: "INSERT INTO users VALUES (?)", args: ["Iku"], }, { sql: "INSERT INTO users VALUES (?)", args: ["Iku 2"], }, ], "write", ); ``` ### Interactive Transactions Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction's scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity. | Method | Description | | - | - | | `execute()` | Similar to `execute()` except within the context of the transaction | | `commit()` | Commits all write statements in the transaction | | `rollback()` | Rolls back the entire transaction | | `close()` | Immediately stops the transaction | ```ts Rollback Usage theme={null} try { const userId = "user123"; const withdrawalAmount = 500; const transaction = await client.transaction("write"); const balanceResult = await transaction.execute({ sql: "SELECT balance FROM accounts WHERE userId = ?", args: [userId], }); const currentBalance = balanceResult.rows[0]["balance"] as number; if (currentBalance >= withdrawalAmount) { await transaction.execute({ sql: "UPDATE accounts SET balance = balance - ? WHERE userId = ?", args: [withdrawalAmount, userId], }); } else { console.log("Insufficient funds"); await transaction.rollback(); return; } await transaction.commit(); } catch (e) { console.error(e); } ``` ```ts Batch Usage theme={null} let transaction: Transaction | null = null; try { const records = [ { name: "Alice", age: 30 }, { name: "Bob", age: 25 }, { name: "Charlie", age: 35 }, ]; transaction = await client.transaction("write"); for (const record of records) { await transaction.execute({ sql: "INSERT INTO people (name, age) VALUES (?, ?)", args: [record.name, record.age], }); } await transaction.commit(); } catch (e) { console.error(e); if (transaction) await transaction.rollback(); } ``` ```ts Conditional Usage theme={null} try { const productId = "prod456"; const newPrice = 150; const transaction = await client.transaction("write"); const productResult = await transaction.execute({ sql: "SELECT price FROM products WHERE productId = ?", args: [productId], }); const currentPrice = productResult.rows[0]["price"] as number; if (currentPrice > newPrice) { await transaction.execute({ sql: "UPDATE products SET price = ? WHERE productId = ?", args: [newPrice, productId], }); } else { console.log("New price is not lower than current price"); await transaction.rollback(); return; } await transaction.commit(); } catch (e) { console.error(e); } ``` > [!WARNING] > > Interactive transactions in libSQL lock the database for writing until > committed or rolled back, with a 5-second timeout. They can impact performance > on high-latency or busy databases. ### ATTACH You can attach multiple databases to the current connection using the `ATTACH` attachment: ```ts theme={null} import { createClient } from "@libsql/client"; const client = createClient({ url: process.env.TURSO_DATABASE_URL, authToken: process.env.TURSO_AUTH_TOKEN, }); const txn = await db.transaction("read"); await txn.execute('ATTACH "" AS attached'); const rs = await txn.execute("SELECT * FROM attached.users"); ``` > [!NOTE] > > Make sure to [allow `ATTACH`](/cli/db/config/attach/allow) and create a token > with the permission to attach a database — [learn > more](/features/attach-database) # Command-Line Options Source: https://docs.turso.tech/sql-reference/cli/command-line-options CLI flags, arguments, and server modes for the tursodb command # Command-Line Options ```bash theme={null} tursodb [OPTIONS] [DATABASE] [SQL] ``` ## Arguments | Argument | Default | Description | | - | - | - | | `DATABASE` | `:memory:` | Path to a SQLite database file. If omitted, an in-memory database is created | | `SQL` | — | SQL statement to execute. If provided, the shell runs the query and exits | ### Examples ```bash theme={null} # In-memory database tursodb # Open an existing file tursodb customers.db # Run a query and exit tursodb customers.db "SELECT count(*) FROM orders;" # Pipe SQL from stdin echo "SELECT 1 + 1;" | tursodb -q ``` ## Options ### `-m, --output-mode ` Set the output display format. | Mode | Description | | - | - | | `pretty` | Table with borders (default) | | `list` | Pipe-delimited values | | `line` | One column per line with column names | ```bash theme={null} tursodb -m list mydata.db "SELECT * FROM users;" ``` ### `-o, --output ` Redirect query output to a file instead of stdout. ```bash theme={null} tursodb -o results.txt mydata.db "SELECT * FROM users;" ``` ### `-q, --quiet` Suppress the startup banner and version information. ```bash theme={null} tursodb -q mydata.db ``` ### `-e, --echo` Print each SQL statement before executing it. Useful for debugging scripts. ```bash theme={null} echo "SELECT 42;" | tursodb -q -e ``` ``` SELECT 42; 42 ``` ### `-v, --vfs ` Select the Virtual File System backend. | VFS | Description | | - | - | | `syscall` | Standard OS file I/O (default) | | `memory` | In-memory storage | | `io_uring` | Linux io\_uring (requires feature flag) | ```bash theme={null} tursodb -v memory ``` ### `-t, --tracing-output ` Write internal log traces to a file. Useful for debugging and performance analysis. ```bash theme={null} tursodb -t trace.log mydata.db ``` ### `--readonly` Open the database in read-only mode. Write operations will return an error. ```bash theme={null} tursodb --readonly production.db "SELECT count(*) FROM users;" ``` Attempting to write in read-only mode: ```bash theme={null} tursodb --readonly production.db "INSERT INTO users VALUES (1, 'test');" # Error: Resource is read-only ``` ### `--mcp` Start a Model Context Protocol server instead of the interactive shell. This allows AI assistants and other MCP clients to interact with the database via JSON-RPC. ```bash theme={null} tursodb --mcp mydata.db ``` ### `--sync-server
` Start a sync server for database replication, listening at the given address. ```bash theme={null} tursodb --sync-server 0.0.0.0:8080 mydata.db ``` ## Experimental Feature Flags These flags enable features that are still under development. They may change or be removed in future releases. > [!WARNING] > > Experimental features may have incomplete implementations or known limitations. Use them for testing and development only. | Flag | Description | | - | - | | `--experimental-views` | Enable views (`CREATE VIEW` / `CREATE MATERIALIZED VIEW`) | | `--experimental-custom-types` | Enable custom types (`CREATE TYPE` / `DROP TYPE` / `CREATE DOMAIN` / `DROP DOMAIN`) | | `--experimental-encryption` | Enable at-rest database encryption | | `--experimental-index-method` | Enable custom index methods. Necessary for FTS and sparse vector indexes | | `--experimental-autovacuum` | Enable automatic database vacuuming | | `--experimental-vacuum` | Enable in-place `VACUUM` | | `--experimental-attach` | Enable `ATTACH DATABASE` / `DETACH DATABASE` | | `--experimental-generated-columns` | Enable virtual `GENERATED ALWAYS AS` columns | | `--experimental-without-rowid` | Enable `WITHOUT ROWID` tables | | `--experimental-multiprocess-wal` | Enable sharing a database between processes via a shared WAL coordinator | | `--experimental-mvcc-passive-checkpoint` | Enable passive checkpointing under MVCC (`journal_mode=mvcc`) | > [!NOTE] > > Triggers (`CREATE TRIGGER` / `DROP TRIGGER`) are stable as of Turso 0.7 and no longer require a flag. ```bash theme={null} # Enable views, custom types, and in-place VACUUM tursodb --experimental-views --experimental-custom-types --experimental-vacuum mydata.db ``` ## Other Flags | Flag | Description | | - | - | | `--mvcc` | Start the MVCC concurrent-transaction harness instead of the normal shell | | `--unsafe-testing` | Enable unsafe testing features (e.g., `sqlite_dbpage` writes) | | `-h, --help` | Print usage help | | `-V, --version` | Print version information | # Getting Started Source: https://docs.turso.tech/sql-reference/cli/getting-started Install and use the Turso interactive SQL shell # Getting Started The Turso CLI (`tursodb`) is an interactive SQL shell for working with Turso databases. It supports in-memory and file-based databases, multiple output modes, CSV import, database cloning, and built-in documentation. ## Quick Start ### In-Memory Database Launch the shell with a transient in-memory database: ```bash theme={null} tursodb ``` ### Open a Database File ```bash theme={null} tursodb mydata.db ``` ### Run a Query Directly Pass SQL as a command-line argument to run it and exit: ```bash theme={null} tursodb mydata.db "SELECT * FROM users;" ``` ### Pipe SQL from stdin ```bash theme={null} echo "SELECT sqlite_version();" | tursodb -q ``` ## Interactive Shell When launched without a SQL argument, the shell provides an interactive REPL with command history and syntax highlighting. ### Multi-line Input Unfinished statements (missing semicolons, unbalanced parentheses) automatically continue on the next line. The prompt indicates nesting depth: ``` tursodb> SELECT * ...> FROM employees ...> WHERE department = 'Engineering'; ``` ### Command History Command history is saved automatically to `~/.limbo_history` and accessible with up/down arrow keys. ### Exiting Use `.quit` or `.exit` to leave the shell. Pressing Ctrl+C twice also exits. ## Output Modes Turso supports three output modes, selectable with the `-m` flag or the `.mode` dot command. ### Pretty (Default) Human-readable table with borders: ```bash theme={null} tursodb -q -m pretty ``` ``` ┌────┬─────────┬─────────────┬─────────┐ │ id │ name │ department │ salary │ ├────┼─────────┼─────────────┼─────────┤ │ 1 │ Alice │ Engineering │ 95000.0 │ ├────┼─────────┼─────────────┼─────────┤ │ 2 │ Bob │ Marketing │ 72000.0 │ └────┴─────────┴─────────────┴─────────┘ ``` ### List Pipe-delimited values suitable for scripting: ```bash theme={null} tursodb -q -m list ``` ``` 1|Alice|Engineering|95000.0 2|Bob|Marketing|72000.0 ``` ### Line One column per line, with column names: ```bash theme={null} tursodb -q -m line ``` ``` id = 1 name = Alice department = Engineering salary = 95000.0 ``` ## Non-Interactive Mode When input is piped (not a terminal), the shell runs in non-interactive mode: * Output defaults to `list` mode (override with `-m`) * No startup banner, history, or syntax highlighting * Exits with code 1 on query errors ```bash theme={null} echo "SELECT 1 + 2;" | tursodb -q ``` ## Server Modes The CLI can also run as a server instead of an interactive shell. ### MCP Server Start a [Model Context Protocol](https://modelcontextprotocol.io/) server, allowing AI assistants to interact with the database: ```bash theme={null} tursodb --mcp mydata.db ``` ### Sync Server Start a sync server for database replication: ```bash theme={null} tursodb --sync-server 0.0.0.0:8080 mydata.db ``` # Shell Commands Source: https://docs.turso.tech/sql-reference/cli/shell-commands Dot commands available in the Turso interactive SQL shell # Shell Commands Dot commands are special commands available in the interactive shell. They start with a period (`.`) and do not require a trailing semicolon. ``` tursodb> .help ``` ## Database & Navigation ### .open Open a database file, optionally specifying a VFS backend. ``` .open [VFS] ``` ``` tursodb> .open mydata.db tursodb> .open mydata.db memory ``` ### .quit Exit the shell. Aliases: `.q`, `.qu`, `.qui`. ``` tursodb> .quit ``` ### .exit Exit the shell with an optional return code. Aliases: `.ex`, `.exi`. ``` .exit [CODE] ``` ``` tursodb> .exit tursodb> .exit 1 ``` ### .cd Change the current working directory. ``` .cd ``` ``` tursodb> .cd /tmp ``` ## Schema Inspection ### .tables List all tables in the database, optionally filtered by a pattern. ``` .tables [PATTERN] ``` ```sql theme={null} CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT); ``` ``` tursodb> .tables employees departments tursodb> .tables emp% employees ``` ### .schema Display the CREATE statement for a table, or all tables if no argument is given. ``` .schema [TABLE] ``` ``` tursodb> .schema employees CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT); tursodb> .schema CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT); ``` ### .indexes Show index names, optionally filtered by table. ``` .indexes [TABLE] ``` ```sql theme={null} CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, department TEXT); CREATE INDEX idx_dept ON employees(department); CREATE INDEX idx_name ON employees(name); ``` ``` tursodb> .indexes idx_dept idx_name tursodb> .indexes employees idx_dept idx_name ``` ### .databases List all attached databases. ``` tursodb> .databases main: /path/to/mydata.db r/w ``` ## Output Control ### .mode Set the output display mode. ``` .mode ``` | Mode | Description | | - | - | | `pretty` | Table with borders (default) | | `list` | Pipe-delimited values | | `line` | One column per line with column names | ``` tursodb> .mode list tursodb> SELECT 1 AS a, 2 AS b; 1|2 tursodb> .mode line tursodb> SELECT 1 AS a, 2 AS b; a = 1 b = 2 tursodb> .mode pretty tursodb> SELECT 1 AS a, 2 AS b; ┌───┬───┐ │ a │ b │ ├───┼───┤ │ 1 │ 2 │ └───┴───┘ ``` ### .headers Toggle column headers on or off in `list` mode. ``` .headers ``` ``` tursodb> .mode list tursodb> .headers on tursodb> SELECT 1 AS x, 2 AS y; x|y 1|2 tursodb> .headers off tursodb> SELECT 1 AS x, 2 AS y; 1|2 ``` ### .nullvalue Set the string displayed for NULL values in `list` mode. ``` .nullvalue ``` ``` tursodb> .mode list tursodb> .nullvalue [NULL] tursodb> SELECT 1 AS a, NULL AS b; 1|[NULL] ``` ### .output Redirect query output to a file. Call with no argument or `stdout` to restore output to the terminal. ``` .output [PATH] ``` ``` tursodb> .output results.txt tursodb> SELECT * FROM employees; tursodb> .output tursodb> -- Output is back to the terminal ``` ### .echo Toggle echo mode. When on, each SQL statement is printed before execution. ``` .echo ``` ``` tursodb> .echo on tursodb> SELECT 42; SELECT 42; ┌────┐ │ 42 │ ├────┤ │ 42 │ └────┘ ``` ### .show Display current shell settings. ``` tursodb> .show Settings: Output mode: pretty DB: mydata.db Output: STDOUT Null value: CWD: /home/user Echo: off Headers: off ``` ## Performance & Diagnostics ### .timer Toggle query timing. When on, shows execution time and I/O statistics after each query. ``` .timer ``` ``` tursodb> .timer on tursodb> SELECT 42; 42 Command stats: ---------------------------- total: 442 us (this includes parsing/coloring of cli app) query execution stats: ---------------------------- Execution: avg=4 us, total=8 us I/O: No samples available ``` ### .stats Display database statistics. Use `on`/`off` to toggle automatic display after every query, or `--reset` to clear counters. ``` .stats [on|off] [--reset] ``` ``` tursodb> .stats Connection Metrics: Total statements: 3 High-water marks: Max VM steps: 19 Max rows read: 1 Aggregate Statistics: Statement Metrics: Row Operations: Rows read: 1 Rows written: 2 Execution: VM steps: 48 Instructions: 47 ... ``` ### .opcodes Show VDBE (Virtual Database Engine) opcodes with descriptions. Optionally filter by opcode name. ``` .opcodes [OPCODE] ``` ``` tursodb> .opcodes Integer Integer ------- The 64-bit integer value P1 is written into register P2. This is different from SQLite, where this opcode is used for 32-bit integers ``` ### .vfslist List available Virtual File System modules. ``` tursodb> .vfslist Available VFS modules: memory syscall ``` ## Data Import & Export ### .dump Output the entire database as SQL statements that can recreate it. ``` tursodb> .dump PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT); INSERT INTO "employees" VALUES(1,'Alice'); INSERT INTO "employees" VALUES(2,'Bob'); COMMIT; ``` > [!NOTE] > > The output of `.dump` can be piped into another Turso instance to recreate the database: > > ```bash theme={null} > tursodb original.db ".dump" | tursodb -q clone.db > ``` ### .import Import data from a file into a table. ``` .import [--csv] [--skip N] [--verbose]
``` | Option | Default | Description | | - | - | - | | `--csv` | on | Use comma as field separator and newline as record separator | | `--skip N` | 0 | Skip the first N rows (useful for skipping a header row) | | `--verbose` | off | Print progress information during import | ``` tursodb> CREATE TABLE people (name TEXT, age INTEGER); tursodb> .import --csv --skip 1 data.csv people tursodb> SELECT * FROM people; ┌─────────┬─────┐ │ name │ age │ ├─────────┼─────┤ │ Alice │ 30 │ ├─────────┼─────┤ │ Bob │ 25 │ ├─────────┼─────┤ │ Charlie │ 35 │ └─────────┴─────┘ ``` Given a CSV file `data.csv`: ```csv theme={null} name,age Alice,30 Bob,25 Charlie,35 ``` ### .clone Clone the current database to a new file. ``` .clone ``` ``` tursodb> .clone backup.db employees... done ``` ### .read Execute SQL statements from a file. ``` .read ``` ``` tursodb> .read setup.sql ``` ### .load Load an extension library. ``` .load ``` ``` tursodb> .load ./liblimbo_regexp ``` > [!NOTE] > > Only Turso-native extensions can be loaded. SQLite `.so`/`.dll` loadable extensions are not supported. See the [Extensions](/sql-reference/extensions) documentation for available extensions. ## Debugging ### .dbtotxt Display raw database page contents in hex format. Useful for debugging storage-level issues. ``` .dbtotxt [--page PAGE_NO] ``` ``` tursodb> .dbtotxt --page 1 | size 8192 pagesize 4096 filename :memory: | page 1 offset 0 | 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3. | 16: 10 00 02 02 00 40 20 20 00 00 00 01 00 00 00 02 .....@ ........ ... ``` ### .dbconfig Print or set database configuration flags. Currently a no-op in Turso. ``` .dbconfig [CONFIG] [on|off] ``` ## Documentation ### .manual Display built-in manual pages for Turso features. Call with no argument to list all available manuals. ``` .manual [PAGE] ``` Aliases: `.man`. ``` tursodb> .manual # Turso Manual Pages Available manuals: custom-types Custom Types for STRICT Tables cdc Change Data Capture encryption At-Rest Database Encryption vector Vector Search materialized-views Live Materialized Views mcp Model Context Protocol server tursodb> .manual vector ``` # SQLite Compatibility Source: https://docs.turso.tech/sql-reference/compatibility Turso compatibility with SQLite - supported features, known differences, and limitations # SQLite Compatibility The authoritative compatibility reference is maintained at [`COMPAT.md`](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) in the repository root. It tracks the support status for every SQL statement, expression, function, PRAGMA, C API function, VDBE opcode, and extension. Refer to that document for the current state of compatibility. It is updated alongside code changes to stay accurate. ## Turso-Specific Extensions These features extend Turso beyond SQLite compatibility: | Feature | Description | | - | - | | [CREATE TYPE](/sql-reference/statements/create-type) | User-defined types for STRICT tables | | [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view) | Live materialized views with incremental maintenance | | [BEGIN CONCURRENT](/sql-reference/statements/transactions#begin-concurrent) | Optimistic concurrent write transactions (MVCC) | | [Vector functions](/sql-reference/functions/vector) | Vector storage, distance calculations, similarity search | | [Full-text search](/sql-reference/functions/fts) | Tantivy-powered FTS with BM25 scoring | | [CDC](/sql-reference/pragmas#change-data-capture) | Change Data Capture via PRAGMA | | [Encryption](/sql-reference/pragmas#encryption) | At-rest database encryption | | [Custom index methods](/sql-reference/statements/create-index#using-method) | CREATE INDEX ... USING for FTS and custom access methods | | stddev() | Standard deviation aggregate function | | [DEFAULT in VALUES](/sql-reference/statements/insert#default-keyword-in-values) | SQL-standard DEFAULT keyword in INSERT VALUES lists | ## See Also * [Experimental Features](/sql-reference/experimental-features) for how to enable experimental features * [COMPAT.md](https://github.com/tursodatabase/turso/blob/main/COMPAT.md) for the full compatibility matrix # Data Types Source: https://docs.turso.tech/sql-reference/data-types Storage classes, type affinity, STRICT tables, and custom types in Turso # Data Types Turso uses the same dynamic type system as SQLite, where values have types but columns do not enforce a single type (unless using STRICT tables). Every value stored in Turso belongs to one of five storage classes. Turso extends this system with [custom types](#custom-types), [composite types](#composite-types-struct-and-union), and [native array types](#array-types) for STRICT tables. ## Storage Classes | Storage Class | Description | | - | - | | NULL | The NULL value | | INTEGER | A signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on magnitude | | REAL | An 8-byte IEEE 754 floating-point number | | TEXT | A UTF-8 encoded string | | BLOB | Raw binary data, stored exactly as input | ```sql theme={null} SELECT typeof(NULL); -- 'null' SELECT typeof(42); -- 'integer' SELECT typeof(3.14); -- 'real' SELECT typeof('hello'); -- 'text' SELECT typeof(x'CAFE'); -- 'blob' ``` ## Type Affinity When a column is declared with a type name, Turso assigns a **type affinity** to that column. Type affinity is a recommendation for how to store values, not a strict constraint (unless using STRICT tables). Turso uses the same affinity rules as SQLite. ### Affinity Determination Rules The type affinity of a column is determined by the declared type name, using these rules applied in order: | Rule | Condition | Affinity | Examples | | - | - | - | - | | 1 | Type name contains "INT" | INTEGER | `INT`, `INTEGER`, `BIGINT`, `SMALLINT`, `TINYINT` | | 2 | Type name contains "CHAR", "CLOB", or "TEXT" | TEXT | `TEXT`, `VARCHAR(255)`, `CLOB`, `CHARACTER(20)` | | 3 | Type name contains "BLOB" or no type specified | BLOB | `BLOB`, (no type) | | 4 | Type name contains "REAL", "FLOA", or "DOUB" | REAL | `REAL`, `FLOAT`, `DOUBLE`, `DOUBLE PRECISION` | | 5 | Otherwise | NUMERIC | `NUMERIC`, `DECIMAL`, `BOOLEAN`, `DATE` | ```sql theme={null} -- Type affinity is a suggestion, not a constraint CREATE TABLE flexible ( id INTEGER, name TEXT, data BLOB ); -- This works - TEXT value in an INTEGER column INSERT INTO flexible VALUES ('not a number', 42, 'text in blob'); SELECT typeof(id), typeof(name), typeof(data) FROM flexible; -- 'text', 'integer', 'text' ``` ### Type Conversions When a value is inserted into a column, Turso attempts to convert the value to the column's affinity: * **INTEGER affinity**: If the value is TEXT or REAL that looks like an integer, Turso converts the value to INTEGER * **REAL affinity**: If the value is TEXT that looks like a number, Turso converts to REAL. If the value is an integer, Turso converts to REAL * **NUMERIC affinity**: Turso tries INTEGER first, then REAL, then keeps as TEXT * **TEXT affinity**: Integer and REAL values are converted to their text representation * **BLOB affinity**: No conversion is attempted ## STRICT Tables STRICT tables enforce type checking at the storage layer. Every value inserted into a STRICT table must match the declared column type or be convertible to the column type. ```sql theme={null} CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER, score REAL ) STRICT; -- This works - values match declared types INSERT INTO users VALUES (1, 'Alice', 30, 95.5); -- This fails - 'thirty' cannot be converted to INTEGER INSERT INTO users VALUES (2, 'Bob', 'thirty', 80.0); -- Error: cannot store TEXT value in INTEGER column ``` ### Allowed Types in STRICT Tables STRICT tables only allow these base type names: | Type | Description | | - | - | | INTEGER | Signed integer | | REAL | Floating-point number | | TEXT | UTF-8 string | | BLOB | Raw binary data | | ANY | Any storage class (disables type checking for this column) | > [!NOTE] > > **Turso Extension**: STRICT tables also accept [custom type](/sql-reference/statements/create-type) names, [composite types](#composite-types-struct-and-union) (STRUCT and UNION), and [array types](#array-types). Any base type can be turned into an array column by appending `[]` to the type name (e.g., `INTEGER[]`, `TEXT[]`). Arrays, custom types, and composite types are Turso-specific features. ## Custom Types > [!NOTE] > > **Turso Extension**: Custom types are a Turso-specific feature not available in SQLite. Custom types work only with STRICT tables. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). Custom types let you define how values are encoded before storage and decoded when read, enforce domain constraints at the storage layer, attach operators, and provide defaults. Custom types are declared with the `CREATE TYPE` statement. ```sql theme={null} -- A type that stores monetary values as cents CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100; CREATE TABLE prices ( id INTEGER PRIMARY KEY, amount cents ) STRICT; INSERT INTO prices VALUES (1, 42); SELECT amount FROM prices; -- 42 (stored on disk as 4200) ``` ### Built-in Custom Types Turso provides several built-in custom types available in STRICT tables: | Type | Base | Description | | - | - | - | | `date` | TEXT | ISO 8601 date (YYYY-MM-DD) | | `time` | TEXT | ISO 8601 time (HH:MM:SS) | | `timestamp` | TEXT | ISO 8601 datetime | | `varchar(N)` | TEXT | Text with maximum length constraint | | `numeric(P,S)` | BLOB | Fixed-point decimal with precision and scale | | `smallint` | INTEGER | Integer constrained to -32768..32767 | | `boolean` | INTEGER | Integer constrained to 0 or 1 | | `uuid` | BLOB | UUID stored as 16-byte blob, displayed as string | | `bytea` | BLOB | Binary data (PostgreSQL-compatible alias) | | `inet` | TEXT | IP address | | `json` | TEXT | Validated JSON text | | `jsonb` | BLOB | JSON in binary format | ```sql theme={null} CREATE TABLE events ( id uuid PRIMARY KEY, name varchar(100), event_date date, is_active boolean DEFAULT 1, metadata json ) STRICT; INSERT INTO events VALUES ( uuid4(), 'Product Launch', '2025-03-15', 1, '{"venue": "online"}' ); ``` For the full custom types reference including ENCODE/DECODE, operators, parametric types, and validation, see [CREATE TYPE](/sql-reference/statements/create-type). ### Composite Types: STRUCT and UNION Turso also supports composite types: **STRUCT** (named product type) and **UNION** (discriminated union). These let you store structured data in a single column and access it with dot notation. ```sql theme={null} -- STRUCT: group related fields, access with col.field CREATE TYPE point AS STRUCT(x INT, y INT); CREATE TABLE locations(id INT PRIMARY KEY, pos point) STRICT; INSERT INTO locations VALUES (1, struct_pack(10, 20)); SELECT pos.x, pos.y FROM locations WHERE pos.x > 5; -- 10|20 -- UNION: tagged variants, access with col.variant CREATE TYPE platform_id AS UNION(telegram INT, slack TEXT); CREATE TABLE contacts(id INT PRIMARY KEY, platform platform_id) STRICT; INSERT INTO contacts VALUES (1, union_value('telegram', 12345)); INSERT INTO contacts VALUES (2, union_value('slack', 'U0ABC')); SELECT id, platform.telegram, platform.slack FROM contacts ORDER BY id; -- 1|12345| -- 2||U0ABC ``` For the full composite types reference, see [CREATE TYPE — Composite Types](/sql-reference/statements/create-type#composite-types-struct-and-union). ## Array Types > [!NOTE] > > **Turso Extension**: Array types are a Turso-specific feature not available in SQLite. Array columns work only with STRICT tables. Array columns store ordered collections of values. Declare them by appending `[]` to any base type name: ```sql theme={null} CREATE TABLE sensors ( id INTEGER PRIMARY KEY, readings REAL[], labels TEXT[], flags INTEGER[] ) STRICT; ``` Multi-dimensional arrays are supported with multiple bracket pairs: ```sql theme={null} CREATE TABLE matrices ( id INTEGER PRIMARY KEY, data INTEGER[][] ) STRICT; ``` Arrays can be inserted using the `ARRAY[...]` constructor or as JSON text: ```sql theme={null} INSERT INTO sensors VALUES (1, ARRAY[1.5, 2.5, 3.5], '["a","b"]', '[0, 1, 1]'); ``` Arrays are displayed as JSON arrays on output. Element types are validated against the declared base type — for example, an `INTEGER[]` column rejects non-numeric text elements. For the full set of array functions, operators, and subscript syntax, see [Array Functions](/sql-reference/functions/array). ### Inspecting Types List all available types (built-in and custom): ```sql theme={null} PRAGMA list_types; ``` All types are also available through the `sqlite_turso_types` virtual table: ```sql theme={null} SELECT name, sql FROM sqlite_turso_types; ``` ## Comparison and Sorting Turso uses the same comparison rules as SQLite. Values of different storage classes are ordered as: ``` NULL < INTEGER/REAL < TEXT < BLOB ``` * NULL values are considered less than any other value * INTEGER and REAL values are compared numerically * TEXT values are compared using the column's collation sequence (default: BINARY) * BLOB values are compared using `memcmp()` ```sql theme={null} SELECT 1 < 2; -- 1 (true) SELECT 'abc' < 'abd'; -- 1 (true) SELECT 1 < '2'; -- 1 (true, numeric < text) SELECT NULL < 1; -- NULL (any comparison with NULL yields NULL) SELECT NULL IS NULL; -- 1 (use IS to test for NULL) ``` ## See Also * [CREATE TABLE](/sql-reference/statements/create-table) for column definitions and constraints * [CREATE TYPE](/sql-reference/statements/create-type) for custom type definitions * [Array Functions](/sql-reference/functions/array) for array construction, manipulation, and operators * [Expressions](/sql-reference/expressions) for CAST expressions and type conversions * [Compatibility](/sql-reference/compatibility) for differences from SQLite # Experimental Features Source: https://docs.turso.tech/sql-reference/experimental-features How to enable experimental features in the CLI and each SDK # Experimental Features Some Turso features are still experimental and must be explicitly enabled before use. Experimental features are stable enough for testing but their API may change in future releases. ## Experimental Features List | Feature | Flag | Description | | - | - | - | | Views | `views` | [CREATE VIEW](/sql-reference/statements/create-view) and [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view) | | Custom Types | `custom_types` | [CREATE TYPE](/sql-reference/statements/create-type), [DROP TYPE](/sql-reference/statements/drop-type), [CREATE DOMAIN](/sql-reference/statements/create-domain), and [DROP DOMAIN](/sql-reference/statements/drop-domain) for STRICT tables | | Encryption | `encryption` | At-rest encryption via [PRAGMA cipher / hexkey](/sql-reference/pragmas#encryption) | | Index Methods | `index_method` | [CREATE INDEX ... USING](/sql-reference/statements/create-index#using-clause) for FTS and custom index types | | Autovacuum | `autovacuum` | Automatic database file compaction | | Vacuum | `vacuum` | In-place [VACUUM](/sql-reference/statements/vacuum) compaction | | Attach | `attach` | [ATTACH DATABASE](/sql-reference/statements/attach-database) and [DETACH DATABASE](/sql-reference/statements/detach-database) | | Generated Columns | `generated_columns` | Virtual [`GENERATED ALWAYS AS`](/sql-reference/statements/create-table) columns | | Without Rowid | `without_rowid` | `WITHOUT ROWID` tables | | Multi-Process WAL | `multiprocess_wal` | [Multi-Process Access](/sql-reference/multiprocess-access) — share a database file between OS processes via a shared WAL coordinator | | MVCC Passive Checkpoint | `mvcc_passive_checkpoint` | Passive checkpointing under MVCC (`journal_mode=mvcc`) | > [!NOTE] > > Triggers are no longer experimental as of Turso 0.7 — [CREATE TRIGGER](/sql-reference/statements/create-trigger) and [DROP TRIGGER](/sql-reference/statements/drop-trigger) work without any flag. ## CLI Pass `--experimental-` flags when starting `tursodb`: ```bash theme={null} tursodb --experimental-views --experimental-custom-types database.db ``` Multiple flags can be combined: ```bash theme={null} tursodb \ --experimental-views \ --experimental-custom-types \ --experimental-encryption \ --experimental-index-method \ --experimental-vacuum \ database.db ``` ## Rust SDK The Rust binding uses a fluent `Builder` API: ```rust theme={null} use turso::Builder; let db = Builder::new_local("database.db") .experimental_encryption(true) .experimental_materialized_views(true) .experimental_custom_types(true) .experimental_index_method(true) .experimental_vacuum(true) .experimental_attach(true) .experimental_multiprocess_wal(true) .build()?; ``` ## Python SDK Pass a comma-separated string to the `experimental_features` parameter: ```python theme={null} import turso conn = turso.connect( "database.db", experimental_features="views,custom_types,vacuum", ) ``` ## JavaScript / Node.js SDK Pass an array of feature strings to the `experimental` option: ```typescript theme={null} import { Database } from "@tursodatabase/libsql"; const db = new Database("file:database.db", { experimental: ["views", "custom_types", "encryption", "vacuum"], }); ``` ## Go SDK Set the `ExperimentalFeatures` field as a comma-separated string: ```go theme={null} db, err := turso.NewDatabase(turso.TursoDatabaseConfig{ Path: "database.db", ExperimentalFeatures: "views,custom_types,vacuum", }) ``` The Go driver also supports DSN query parameters: ```go theme={null} db, err := sql.Open("turso", "database.db?experimental=views,vacuum") ``` ## Java SDK The Java binding currently supports encryption through a dedicated method: ```java theme={null} TursoDB db = TursoDB.openWithEncryption( "database.db", OpenFlags.CREATE, "aegis256", "your-hex-key-here" ); ``` ## .NET SDK The .NET binding currently supports encryption through the C FFI: ```csharp theme={null} var db = Database.OpenWithEncryption( "database.db", "aegis256", "your-hex-key-here" ); ``` ## See Also * [Compatibility](/sql-reference/compatibility) for the full feature support matrix # Expressions Source: https://docs.turso.tech/sql-reference/expressions SQL expression syntax including literals, operators, CAST, CASE, and subqueries # Expressions Expressions are combinations of values, operators, and functions that Turso evaluates to produce a result. Expressions appear in many SQL clauses including SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values. ## Literals ### Numeric Literals ```sql theme={null} SELECT 42; -- integer literal SELECT -17; -- negative integer SELECT 3.14; -- real (floating-point) literal SELECT 2.5e10; -- scientific notation SELECT 0xFF; -- hexadecimal integer (255) ``` ### String Literals String literals are enclosed in single quotes. To include a single quote within a string, use two consecutive single quotes: ```sql theme={null} SELECT 'hello world'; -- text literal SELECT 'it''s a test'; -- embedded single quote SELECT ''; -- empty string ``` ### Blob Literals Blob literals are hexadecimal strings preceded by `x` or `X`: ```sql theme={null} SELECT x'CAFEBABE'; -- blob literal SELECT X'48454C4C4F'; -- blob literal (case insensitive prefix) ``` ### NULL Literal ```sql theme={null} SELECT NULL; -- null value ``` ### Boolean Literals Turso does not have a separate boolean type. Use integers `0` (false) and `1` (true): ```sql theme={null} SELECT 1; -- true SELECT 0; -- false ``` ## Operators ### Arithmetic Operators | Operator | Description | Example | Result | | - | - | - | - | | `+` | Addition | `3 + 4` | `7` | | `-` | Subtraction | `10 - 3` | `7` | | `*` | Multiplication | `3 * 4` | `12` | | `/` | Division | `10 / 3` | `3` (integer division) | | `-` (unary) | Negation | `-5` | `-5` | | `+` (unary) | No-op | `+5` | `5` | Integer division truncates toward zero. Use `CAST` or multiply by `1.0` for floating-point division: ```sql theme={null} SELECT 10 / 3; -- 3 (integer division) SELECT 10 / 3.0; -- 3.333... (real division) SELECT CAST(10 AS REAL) / 3; -- 3.333... ``` ### Comparison Operators | Operator | Description | Example | | - | - | - | | `=` or `==` | Equal | `x = 5` | | `!=` or `<>` | Not equal | `x != 5` | | `<` | Less than | `x < 5` | | `>` | Greater than | `x > 5` | | `<=` | Less than or equal | `x <= 5` | | `>=` | Greater than or equal | `x >= 5` | All comparison operators return `1` (true), `0` (false), or `NULL` (if either operand is NULL). ### Logical Operators | Operator | Description | Example | | - | - | - | | `AND` | Logical AND | `x > 0 AND x < 10` | | `OR` | Logical OR | `x = 1 OR x = 2` | | `NOT` | Logical NOT | `NOT x = 5` | ### Bitwise Operators | Operator | Description | Example | Result | | - | - | - | - | | `&` | Bitwise AND | `5 & 3` | `1` | | `\|` | Bitwise OR | `5 \| 3` | `7` | | `~` | Bitwise NOT | `~5` | `-6` | | `<<` | Left shift | `1 << 4` | `16` | | `>>` | Right shift | `16 >> 2` | `4` | ### String Concatenation | Operator | Description | Example | Result | | - | - | - | - | | `\|\|` | Concatenation | `'hello' \|\| ' ' \|\| 'world'` | `'hello world'` | When either operand is an array, `||` performs array concatenation instead. See [Array Operators](#array-operators). ### Array Operators > [!NOTE] > > **Turso Extension**: Array operators are a Turso-specific feature not available in SQLite. | Operator | Description | Example | Result | | - | - | - | - | | `@>` | Contains all | `ARRAY[1,2,3] @> ARRAY[1,2]` | `1` | | `&&` | Overlaps | `ARRAY[1,2,3] && ARRAY[3,4,5]` | `1` | | `\|\|` | Array concat | `ARRAY[1,2] \|\| ARRAY[3,4]` | `[1,2,3,4]` | | `=`, `!=`, `<`, `>`, `<=`, `>=` | Comparison | `ARRAY[1,2] < ARRAY[1,3]` | `1` | The `@>` operator tests if the left array contains all elements of the right array. The `&&` operator tests if two arrays share any common elements. Both return `1` (true) or `0` (false). Array comparison is element-wise from left to right. If all compared elements are equal, the shorter array is considered less than the longer one. ```sql theme={null} SELECT ARRAY[1,2,3] @> ARRAY[1,3]; -- 1 (contains both 1 and 3) SELECT ARRAY[1,2] && ARRAY[3,4]; -- 0 (no common elements) SELECT ARRAY[1,2] < ARRAY[1,2,3]; -- 1 (shorter is less) ``` For the full reference, see [Array Functions](/sql-reference/functions/array). ## CAST Expression The CAST expression converts a value to a specified type. ```sql theme={null} CAST(expression AS type-name) ``` | Parameter | Type | Description | | - | - | - | | expression | any | The value to convert | | type-name | type | The target type name | ```sql theme={null} SELECT CAST(3.7 AS INTEGER); -- 3 (truncates toward zero) SELECT CAST(42 AS TEXT); -- '42' SELECT CAST('123' AS INTEGER); -- 123 SELECT CAST('abc' AS INTEGER); -- 0 SELECT CAST(NULL AS INTEGER); -- NULL ``` > [!NOTE] > > **Turso Extension**: In STRICT tables with custom types, `CAST(value AS custom_type)` applies the custom type's ENCODE function. See [CREATE TYPE](/sql-reference/statements/create-type) for details. ## COLLATE Expression The COLLATE expression specifies a collation sequence for string comparison. ```sql theme={null} expression COLLATE collation-name ``` Built-in collation sequences: | Collation | Description | | - | - | | `BINARY` | Byte-by-byte comparison (default) | | `NOCASE` | Case-insensitive comparison for ASCII characters | | `RTRIM` | Like BINARY but ignores trailing spaces | ```sql theme={null} SELECT 'ABC' = 'abc'; -- 0 (BINARY comparison) SELECT 'ABC' = 'abc' COLLATE NOCASE; -- 1 (case-insensitive) SELECT 'abc ' = 'abc' COLLATE RTRIM; -- 1 (trailing space ignored) SELECT name FROM users ORDER BY name COLLATE NOCASE; ``` ## Pattern Matching ### LIKE Operator The LIKE operator performs case-insensitive pattern matching (for ASCII characters). The `%` wildcard matches any sequence of characters, and `_` matches any single character. ```sql theme={null} expression [NOT] LIKE pattern [ESCAPE escape-char] ``` ```sql theme={null} SELECT 'Hello World' LIKE 'hello%'; -- 1 (case-insensitive) SELECT 'Hello World' LIKE 'H_llo%'; -- 1 (_ matches 'e') SELECT 'Hello World' LIKE '%World'; -- 1 SELECT '10%' LIKE '10\%' ESCAPE '\'; -- 1 (escaped % literal) ``` ### GLOB Operator The GLOB operator performs case-sensitive pattern matching using Unix-style wildcards. `*` matches any sequence of characters, and `?` matches any single character. ```sql theme={null} expression [NOT] GLOB pattern ``` ```sql theme={null} SELECT 'Hello' GLOB 'H*'; -- 1 SELECT 'Hello' GLOB 'h*'; -- 0 (case-sensitive) SELECT 'Hello' GLOB 'H?llo'; -- 1 SELECT 'Hello' GLOB 'H[a-z]*'; -- 1 (character class) ``` ### REGEXP Operator The REGEXP operator performs regular expression matching. Requires the regexp extension (loaded by default). ```sql theme={null} expression [NOT] REGEXP pattern ``` ```sql theme={null} SELECT 'Hello123' REGEXP '[A-Za-z]+[0-9]+'; -- 1 SELECT 'test@email.com' REGEXP '^[^@]+@[^@]+\.[^@]+$'; -- 1 ``` ## BETWEEN Expression The BETWEEN expression tests whether a value falls within an inclusive range. ```sql theme={null} expression [NOT] BETWEEN low AND high ``` The BETWEEN expression is equivalent to `expression >= low AND expression <= high`: ```sql theme={null} SELECT 5 BETWEEN 1 AND 10; -- 1 SELECT 5 NOT BETWEEN 1 AND 3; -- 1 SELECT 'b' BETWEEN 'a' AND 'c'; -- 1 ``` ## IN Expression The IN expression tests whether a value matches any value in a list or subquery result. ```sql theme={null} expression [NOT] IN (value1, value2, ...) expression [NOT] IN (select-statement) ``` ```sql theme={null} SELECT 3 IN (1, 2, 3, 4, 5); -- 1 SELECT 'red' NOT IN ('blue', 'green'); -- 1 SELECT name FROM users WHERE id IN (SELECT user_id FROM orders WHERE total > 100); ``` ## EXISTS Expression The EXISTS expression returns `1` if the subquery returns at least one row, and `0` otherwise. ```sql theme={null} [NOT] EXISTS (select-statement) ``` ```sql theme={null} SELECT EXISTS (SELECT 1 FROM users WHERE name = 'Alice'); -- 1 if Alice exists SELECT name FROM departments d WHERE EXISTS ( SELECT 1 FROM employees e WHERE e.department_id = d.id ); ``` ## IS NULL / IS NOT NULL The IS NULL expression tests whether a value is NULL. Unlike `= NULL`, which always returns NULL, `IS NULL` returns `1` or `0`. ```sql theme={null} expression IS NULL expression IS NOT NULL ``` ```sql theme={null} SELECT NULL IS NULL; -- 1 SELECT NULL = NULL; -- NULL (not 1!) SELECT 42 IS NOT NULL; -- 1 SELECT NULL IS NOT NULL; -- 0 ``` ## IS DISTINCT FROM The IS DISTINCT FROM expression compares two values, treating NULL as a comparable value. ```sql theme={null} expression IS [NOT] DISTINCT FROM expression ``` ```sql theme={null} SELECT 1 IS DISTINCT FROM 2; -- 1 (different values) SELECT 1 IS DISTINCT FROM 1; -- 0 (same value) SELECT NULL IS DISTINCT FROM NULL; -- 0 (both NULL) SELECT NULL IS DISTINCT FROM 1; -- 1 (NULL vs non-NULL) SELECT 1 IS NOT DISTINCT FROM 1; -- 1 ``` ## CASE Expression The CASE expression provides conditional logic within SQL expressions. ### Simple CASE ```sql theme={null} CASE expression WHEN value1 THEN result1 WHEN value2 THEN result2 ... [ELSE default_result] END ``` ### Searched CASE ```sql theme={null} CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 ... [ELSE default_result] END ``` If no WHEN clause matches and there is no ELSE clause, the CASE expression returns NULL. ```sql theme={null} -- Simple CASE SELECT name, CASE status WHEN 'A' THEN 'Active' WHEN 'I' THEN 'Inactive' ELSE 'Unknown' END AS status_text FROM users; -- Searched CASE SELECT name, score, CASE WHEN score >= 90 THEN 'A' WHEN score >= 80 THEN 'B' WHEN score >= 70 THEN 'C' ELSE 'F' END AS grade FROM students; ``` ## Scalar Subqueries A subquery enclosed in parentheses that returns a single value can be used as an expression. ```sql theme={null} (select-statement) ``` The subquery must return exactly one column and at most one row. If the subquery returns no rows, the expression evaluates to NULL. ```sql theme={null} SELECT name, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) AS order_count FROM users; SELECT * FROM products WHERE price > (SELECT AVG(price) FROM products); ``` ## RAISE Function The RAISE function raises an error condition. In standard SQLite, RAISE can only be used inside triggers. In Turso, `RAISE(ABORT, msg)` can also be used outside triggers — in CHECK constraints, custom type ENCODE expressions, and standalone queries. The other forms (`RAISE(IGNORE)`, `RAISE(ROLLBACK, msg)`, `RAISE(FAIL, msg)`) are only valid inside triggers. ```sql theme={null} RAISE(IGNORE) RAISE(ROLLBACK, error-message) RAISE(ABORT, error-message) RAISE(FAIL, error-message) ``` | Form | Description | | - | - | | `RAISE(IGNORE)` | Abandon the current trigger action but continue the statement | | `RAISE(ROLLBACK, msg)` | Abort the statement and roll back the current transaction | | `RAISE(ABORT, msg)` | Abort the current statement; prior changes in the transaction are preserved | | `RAISE(FAIL, msg)` | Abort the current statement at the current point; prior row changes are preserved | ```sql theme={null} -- In a trigger CREATE TRIGGER validate_age BEFORE INSERT ON users BEGIN SELECT CASE WHEN NEW.age < 0 THEN RAISE(ABORT, 'age must be non-negative') END; END; -- In a custom type ENCODE expression CREATE TYPE positive_int BASE integer ENCODE CASE WHEN value > 0 THEN value ELSE RAISE(ABORT, 'value must be positive') END DECODE value; ``` ## Operator Precedence Operators are evaluated in the following order (highest precedence first): | Precedence | Operators | | - | - | | 1 (highest) | `~` (unary NOT), `+` (unary), `-` (unary) | | 2 | `\|\|` (concatenation) | | 3 | `*`, `/` | | 4 | `+`, `-` | | 5 | `<<`, `>>`, `&`, `\|` | | 6 | `<`, `<=`, `>`, `>=`, `@>`, `&&` | | 7 | `=`, `==`, `!=`, `<>`, `IS`, `IS NOT`, `IS DISTINCT FROM`, `IN`, `LIKE`, `GLOB`, `REGEXP`, `BETWEEN` | | 8 | `NOT` | | 9 | `AND` | | 10 (lowest) | `OR` | Use parentheses to override precedence when needed: ```sql theme={null} SELECT 2 + 3 * 4; -- 14 (multiplication first) SELECT (2 + 3) * 4; -- 20 (parentheses override) ``` ## See Also * [Data Types](/sql-reference/data-types) for storage classes and type affinity * [Scalar Functions](/sql-reference/functions/scalar) for built-in functions usable in expressions * [SELECT](/sql-reference/statements/select) for using expressions in queries # Extensions Source: https://docs.turso.tech/sql-reference/extensions Built-in and loadable extensions for additional SQL functions and virtual tables # Extensions Turso provides several built-in extensions that add specialized SQL functions and virtual tables. Extensions can be loaded at runtime using the `load_extension()` function. ## Loading Extensions ```sql theme={null} SELECT load_extension('extension_name'); ``` Turso supports loading Turso-native extensions. SQLite `.so`/`.dll` loadable extensions are not supported. ## UUID Extension The UUID extension provides functions for generating and working with UUIDs. UUIDs are stored as 16-byte BLOBs by default. ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `uuid4()` | none | BLOB | Generate a random UUID v4 | | `uuid4_str()` | none | TEXT | Generate a random UUID v4 as string. Alias: `gen_random_uuid()` | | `uuid7()` | none | BLOB | Generate a time-ordered UUID v7 | | `uuid7(seconds)` | INTEGER | BLOB | Generate a UUID v7 with specified seconds since epoch | | `uuid7_timestamp_ms(uuid)` | BLOB | INTEGER | Extract milliseconds since epoch from a UUID v7 | | `uuid_str(uuid)` | BLOB | TEXT | Convert a UUID blob to string representation | | `uuid_blob(uuid)` | TEXT | BLOB | Convert a UUID string to blob representation | ### Examples ```sql theme={null} -- Generate UUIDs SELECT uuid4_str(); -- '550e8400-e29b-41d4-a716-446655440000' SELECT uuid_str(uuid4()); -- 'f47ac10b-58cc-4372-a567-0e02b2c3d479' -- UUID v7 is time-ordered (good for primary keys) SELECT uuid_str(uuid7()); -- '0190a5c0-1234-7abc-8def-0123456789ab' -- Extract timestamp from UUID v7 SELECT uuid7_timestamp_ms(uuid7()); -- 1720000000000 -- Use in a table CREATE TABLE documents ( id BLOB PRIMARY KEY DEFAULT (uuid7()), title TEXT ); INSERT INTO documents (title) VALUES ('My Document'); SELECT uuid_str(id), title FROM documents; ``` ## Regexp Extension The regexp extension provides regular expression functions compatible with [sqlean-regexp](https://github.com/nalgeon/sqlean/blob/main/docs/regexp.md). ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `regexp(pattern, source)` | TEXT, TEXT | INTEGER | Returns 1 if source matches pattern | | `regexp_like(source, pattern)` | TEXT, TEXT | INTEGER | Returns 1 if source matches pattern (argument order reversed) | | `regexp_substr(source, pattern)` | TEXT, TEXT | TEXT | Returns the first substring matching pattern, or NULL | | `regexp_capture(source, pattern)` | TEXT, TEXT | TEXT | Returns the first capture group match | | `regexp_capture(source, pattern, n)` | TEXT, TEXT, INTEGER | TEXT | Returns the n-th capture group match | | `regexp_replace(source, pattern, replacement)` | TEXT, TEXT, TEXT | TEXT | Replace matches with replacement string | ### Examples ```sql theme={null} -- Pattern matching SELECT regexp('[0-9]+', 'abc123def'); -- 1 -- The REGEXP operator uses this extension SELECT 'hello123' REGEXP '[a-z]+[0-9]+'; -- 1 -- Extract matching substring SELECT regexp_substr('Price: $42.50', '[0-9]+\.[0-9]+'); -- '42.50' -- Capture groups SELECT regexp_capture('2025-03-15', '(\d{4})-(\d{2})-(\d{2})', 1); -- '2025' -- Replace SELECT regexp_replace('Hello World', '[aeiou]', '*'); -- 'H*ll* W*rld' ``` ## Vector Extension > [!NOTE] > > **Turso Extension**: Native vector search for similarity search and semantic search applications. The vector extension provides functions for creating, storing, and searching vector embeddings. Vectors are stored as BLOBs. For the complete function reference, see [Vector Functions](/sql-reference/functions/vector). ### Quick Reference | Function | Description | | - | - | | `vector32(json)` | Create a 32-bit float vector | | `vector64(json)` | Create a 64-bit float vector | | `vector_distance_cos(v1, v2)` | Cosine distance | | `vector_distance_l2(v1, v2)` | Euclidean distance | | `vector_extract(v)` | Convert vector to JSON | | `vector_concat(v1, v2)` | Concatenate vectors | | `vector_slice(v, start, end)` | Extract vector slice | ### Example ```sql theme={null} CREATE TABLE docs ( id INTEGER PRIMARY KEY, content TEXT, embedding BLOB ); INSERT INTO docs VALUES (1, 'database systems', vector32('[0.1, 0.2, 0.3]')); INSERT INTO docs VALUES (2, 'machine learning', vector32('[0.4, 0.5, 0.6]')); -- Find similar documents SELECT content, vector_distance_cos(embedding, vector32('[0.1, 0.25, 0.35]')) AS dist FROM docs ORDER BY dist LIMIT 5; ``` ## Time Extension The time extension is compatible with [sqlean-time](https://github.com/nalgeon/sqlean/blob/main/docs/time.md) and provides advanced time manipulation functions. > [!NOTE] > > A **time value** (`t` in the tables below) is an opaque 13-byte **BLOB**, not an integer — this matches sqlean-time byte-for-byte (`version(1) + seconds(8) + nanoseconds(4)`, with seconds counted from year 0001). Any function that produces a time (`time_now`, `time_date`, `time_add`, …) returns a BLOB, and `typeof(time_now())` is `blob`. Use `time_to_unix` / `time_to_nano` to get an INTEGER, and the `time_fmt_*` functions to get TEXT. > > To store a time value in a `STRICT` table, use a `BLOB` column, or convert it first: > > ```sql theme={null} > CREATE TABLE X (t BLOB) STRICT; > INSERT INTO X (t) VALUES (time_now()); > > -- Or store a Unix integer: > CREATE TABLE Y (t INTEGER) STRICT; > INSERT INTO Y (t) VALUES (time_to_nano(time_now())); > ``` ### Key Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `time_now()` | none | BLOB | Current time | | `time_date(y,m,d,...)` | INTEGER... | BLOB | Create time from components | | `make_date(y,m,d)` | INTEGER... | BLOB | Create time from a date | | `make_timestamp(...)` | INTEGER... | BLOB | Create time from a timestamp | | `time_unix(sec)` | INTEGER | BLOB | Create time from Unix seconds | | `to_timestamp(sec)` | INTEGER | BLOB | Create time from Unix seconds | | `time_milli(ms)` | INTEGER | BLOB | Create time from milliseconds | | `time_micro(us)` | INTEGER | BLOB | Create time from microseconds | | `time_nano(ns)` | INTEGER | BLOB | Create time from nanoseconds | | `time_to_unix(t)` | BLOB | INTEGER | Convert to Unix seconds | | `time_to_milli(t)` | BLOB | INTEGER | Convert to milliseconds | | `time_to_micro(t)` | BLOB | INTEGER | Convert to microseconds | | `time_to_nano(t)` | BLOB | INTEGER | Convert to nanoseconds | | `time_get_year(t)` | BLOB | INTEGER | Extract year | | `time_get_month(t)` | BLOB | INTEGER | Extract month (1-12) | | `time_get_day(t)` | BLOB | INTEGER | Extract day (1-31) | | `time_get_hour(t)` | BLOB | INTEGER | Extract hour (0-23) | | `time_get_minute(t)` | BLOB | INTEGER | Extract minute (0-59) | | `time_get_second(t)` | BLOB | INTEGER | Extract second (0-59) | | `time_get_weekday(t)` | BLOB | INTEGER | Day of week (0=Sunday) | | `time_get(t, field)` | BLOB, TEXT | INTEGER | Extract named field | | `time_add(t, d)` | BLOB, INTEGER | BLOB | Add duration | | `time_add_date(t, y, m, d)` | BLOB, INTEGER... | BLOB | Add years/months/days | | `time_sub(t, u)` | BLOB, BLOB | INTEGER | Difference between times (duration) | | `time_since(t)` | BLOB | INTEGER | Duration since time | | `time_until(t)` | BLOB | INTEGER | Duration until time | | `time_trunc(t, field)` | BLOB, TEXT | BLOB | Truncate to field | | `time_round(t, d)` | BLOB, INTEGER | BLOB | Round to nearest duration | | `time_after(t, u)` | BLOB, BLOB | INTEGER | 1 if t is after u | | `time_before(t, u)` | BLOB, BLOB | INTEGER | 1 if t is before u | | `time_compare(t, u)` | BLOB, BLOB | INTEGER | -1, 0, or 1 | | `time_equal(t, u)` | BLOB, BLOB | INTEGER | 1 if equal | | `time_fmt_iso(t)` | BLOB | TEXT | Format as ISO 8601 | | `time_fmt_datetime(t)` | BLOB | TEXT | Format as datetime | | `time_fmt_date(t)` | BLOB | TEXT | Format as date | | `time_fmt_time(t)` | BLOB | TEXT | Format as time | | `time_parse(s)` | TEXT | BLOB | Parse time string | #### Duration Constants | Function | Value | | - | - | | `dur_ns()` | 1 nanosecond | | `dur_us()` | 1 microsecond | | `dur_ms()` | 1 millisecond | | `dur_s()` | 1 second | | `dur_m()` | 1 minute | | `dur_h()` | 1 hour | ### Example ```sql theme={null} -- Current time formatted SELECT time_fmt_iso(time_now()); -- '2025-03-15T10:30:00Z' -- Date arithmetic SELECT time_fmt_date(time_add_date(time_now(), 0, 1, 0)); -- add 1 month to today -- Duration since a timestamp SELECT time_since(time_date(2025, 1, 1, 0, 0, 0)) / dur_h(); -- hours since Jan 1, 2025 ``` ## Full-Text Search (FTS) > [!NOTE] > > **Turso Extension**: Full-text search powered by Tantivy, replacing SQLite's FTS3/FTS4/FTS5. Turso provides full-text search through the FTS index method. For the complete reference including query syntax, tokenizers, and scoring, see [FTS Functions](/sql-reference/functions/fts). ### Quick Reference ```sql theme={null} -- Create an FTS index CREATE INDEX idx ON articles USING fts (title, body); -- Search SELECT title, fts_score('idx') AS score FROM articles WHERE fts_match('idx', 'database search') ORDER BY score; -- Highlighted results SELECT fts_highlight('idx', 0, '', '') AS title FROM articles WHERE fts_match('idx', 'database'); ``` ## CSV Extension The CSV extension provides a virtual table for reading CSV files. ### Usage ```sql theme={null} CREATE VIRTUAL TABLE temp.csv_data USING csv( filename='/path/to/data.csv', header=yes ); SELECT * FROM csv_data; ``` | Parameter | Description | | - | - | | `filename` | Path to the CSV file | | `header` | `yes` if the first row contains column names | ## Percentile Extension Statistical aggregate functions for computing percentiles. ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `median(X)` | column | REAL | Median value (50th percentile) | | `percentile(Y, P)` | column, REAL | REAL | P-th percentile of Y (P between 0 and 100) | | `percentile_cont(Y, P)` | column, REAL | REAL | Continuous percentile (interpolated) | | `percentile_disc(Y, P)` | column, REAL | REAL | Discrete percentile (nearest value) | ### Examples ```sql theme={null} CREATE TABLE scores (value REAL); INSERT INTO scores VALUES (10), (20), (30), (40), (50); SELECT median(value) FROM scores; -- 30.0 SELECT percentile(value, 75) FROM scores; -- 40.0 SELECT percentile_cont(value, 0.25) FROM scores; -- 20.0 ``` ## Crypto Extension The crypto extension provides hashing and encoding functions, compatible with [sqlean-crypto](https://github.com/nalgeon/sqlean/blob/main/docs/crypto.md). ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `crypto_md5(data)` | TEXT/BLOB | BLOB | MD5 hash | | `crypto_sha1(data)` | TEXT/BLOB | BLOB | SHA-1 hash | | `crypto_sha256(data)` | TEXT/BLOB | BLOB | SHA-256 hash | | `crypto_sha384(data)` | TEXT/BLOB | BLOB | SHA-384 hash | | `crypto_sha512(data)` | TEXT/BLOB | BLOB | SHA-512 hash | | `crypto_blake3(data)` | TEXT/BLOB | BLOB | BLAKE3 hash | | `crypto_encode(data, format)` | BLOB, TEXT | TEXT | Encode a blob as text using `format` (e.g. `hex`, `base32`, `base64`, `url`) | | `crypto_decode(text, format)` | TEXT, TEXT | BLOB | Decode text in the given `format` back into a blob | ```sql theme={null} -- Hash a value and encode it as hex SELECT crypto_encode(crypto_sha256('hello'), 'hex'); -- Base64 round-trip SELECT crypto_decode(crypto_encode(x'CAFE', 'base64'), 'base64'); ``` ## Fuzzy Extension The fuzzy extension provides string-similarity, distance, and phonetic functions, compatible with [sqlean-fuzzy](https://github.com/nalgeon/sqlean/blob/main/docs/fuzzy.md). ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `fuzzy_editdist(a, b)` | TEXT, TEXT | INTEGER | Spellcheck-style edit distance | | `fuzzy_leven(a, b)` | TEXT, TEXT | INTEGER | Levenshtein distance | | `fuzzy_damlev(a, b)` | TEXT, TEXT | INTEGER | Damerau-Levenshtein distance | | `fuzzy_osadist(a, b)` | TEXT, TEXT | INTEGER | Optimal string alignment distance | | `fuzzy_hamming(a, b)` | TEXT, TEXT | INTEGER | Hamming distance | | `fuzzy_jarowin(a, b)` | TEXT, TEXT | REAL | Jaro-Winkler similarity | | `fuzzy_soundex(s)` | TEXT | TEXT | Soundex phonetic code | | `fuzzy_rsoundex(s)` | TEXT | TEXT | Refined Soundex code | | `fuzzy_caver(s)` | TEXT | TEXT | Caverphone phonetic code | | `fuzzy_phonetic(s)` | TEXT | TEXT | US Census phonetic code | | `fuzzy_translit(s)` | TEXT | TEXT | Transliterate Unicode text to ASCII | | `fuzzy_script(s)` | TEXT | INTEGER | Identify the writing script of the text | | `phonetics(a, b)` | TEXT, TEXT | INTEGER | 1 if two strings are phonetically equal | ```sql theme={null} -- Distance between two strings SELECT fuzzy_leven('awesome', 'aewsime'); -- 3 -- Phonetic encoding SELECT fuzzy_soundex('Robert'); -- 'R163' ``` ## IP Address Extension The ipaddr extension provides functions for working with IPv4 and IPv6 addresses and networks. ### Functions | Function | Parameters | Return Type | Description | | - | - | - | - | | `ipfamily(ip)` | TEXT | INTEGER | Returns 4 for IPv4 or 6 for IPv6 | | `iphost(ip)` | TEXT | TEXT | The host portion of an address (strips any netmask) | | `ipmasklen(cidr)` | TEXT | INTEGER | The prefix (mask) length of a CIDR network | | `ipnetwork(cidr)` | TEXT | TEXT | The network address of a CIDR value | | `ipcontains(network, ip)` | TEXT, TEXT | INTEGER | 1 if the CIDR network contains the given address | ```sql theme={null} SELECT ipfamily('192.168.1.1'); -- 4 SELECT ipnetwork('192.168.1.42/24'); -- '192.168.1.0/24' SELECT ipcontains('10.0.0.0/8', '10.1.2.3'); -- 1 ``` ## generate\_series The `generate_series` table-valued function generates a sequence of integers. ```sql theme={null} SELECT value FROM generate_series(start, stop); SELECT value FROM generate_series(start, stop, step); ``` | Parameter | Type | Description | | - | - | - | | start | INTEGER | Starting value (inclusive) | | stop | INTEGER | Ending value (inclusive) | | step | INTEGER | Step increment (default: 1) | ```sql theme={null} SELECT value FROM generate_series(1, 5); -- 1, 2, 3, 4, 5 SELECT value FROM generate_series(0, 10, 2); -- 0, 2, 4, 6, 8, 10 SELECT value FROM generate_series(10, 1, -3); -- 10, 7, 4, 1 ``` ## See Also * [Vector Functions](/sql-reference/functions/vector) for detailed vector function reference * [FTS Functions](/sql-reference/functions/fts) for full-text search function reference * [Aggregate Functions](/sql-reference/functions/aggregate) for percentile functions used with GROUP BY * [Compatibility](/sql-reference/compatibility) for extension support status # Aggregate Functions Source: https://docs.turso.tech/sql-reference/functions/aggregate Built-in aggregate functions for computing summary values over groups of rows # Aggregate Functions Aggregate functions compute a single result from a set of input rows. They are typically used with the `GROUP BY` clause in SELECT statements, but can also be used without `GROUP BY` to aggregate over all rows. When used in a SELECT with non-aggregate columns and no `GROUP BY`, the result is a single row. All standard aggregate functions ignore NULL values (except `count(*)`). If every input value is NULL, the aggregate returns NULL, with the exception of `count()` (which returns 0) and `total()` (which returns 0.0). ## Function Reference | Function | Return Type | Description | | - | - | - | | `array_agg(X)` | BLOB (array) | Collects all values of X into an array (Turso extension) | | `avg(X)` | REAL | Average of all non-NULL values of X | | `count(X)` | INTEGER | Count of rows where X is not NULL | | `count(*)` | INTEGER | Count of all rows in the group | | `group_concat(X)` | TEXT | Concatenation of all non-NULL values of X, separated by commas | | `group_concat(X, Y)` | TEXT | Concatenation of all non-NULL values of X, separated by Y | | `string_agg(X, Y)` | TEXT | Alias for `group_concat(X, Y)` | | `max(X)` | same as X | Maximum non-NULL value of X | | `min(X)` | same as X | Minimum non-NULL value of X | | `sum(X)` | INTEGER or REAL | Sum of all non-NULL values of X. Returns NULL if all values are NULL | | `total(X)` | REAL | Sum of all non-NULL values of X. Always returns REAL, 0.0 if all values are NULL | ## Detailed Descriptions and Examples The examples below use the following table: ```sql theme={null} CREATE TABLE sales ( id INTEGER PRIMARY KEY, region TEXT, product TEXT, amount REAL, quantity INTEGER ); INSERT INTO sales VALUES (1, 'North', 'Widget', 100.00, 5), (2, 'North', 'Gadget', 250.00, 2), (3, 'South', 'Widget', 150.00, 8), (4, 'South', 'Gadget', NULL, 3), (5, 'North', 'Widget', 200.00, NULL), (6, 'South', 'Widget', 175.00, 6); ``` ### avg(X) ```sql theme={null} avg(X) ``` Returns the average of all non-NULL values of X as a REAL (floating-point) number. Returns NULL if all values are NULL. | Parameter | Type | Description | | - | - | - | | X | any numeric | The expression to average | **Return type:** REAL ```sql theme={null} SELECT avg(amount) FROM sales; -- 175.0 (sum of non-NULL amounts / count of non-NULL amounts = 875.0 / 5) SELECT region, avg(amount) AS avg_amount FROM sales GROUP BY region; ``` | region | avg\_amount | | - | - | | North | 183.333333333333 | | South | 162.5 | > [!NOTE] > > `avg(X)` ignores NULL values in both the sum and the count. In the example above, the South region has three non-NULL amounts (150 + 175 = 325, but also the NULL row is excluded), so the average is computed over the non-NULL values only. ### count(X) and count(\*) ```sql theme={null} count(X) count(*) ``` `count(X)` returns the number of rows where X is not NULL. `count(*)` returns the total number of rows in the group, including rows with NULL values. | Parameter | Type | Description | | - | - | - | | X | any | The expression to count non-NULL values for | | `*` | special | Counts all rows regardless of NULL | **Return type:** INTEGER ```sql theme={null} SELECT count(*) FROM sales; -- 6 (all rows) SELECT count(amount) FROM sales; -- 5 (excludes the row where amount is NULL) SELECT region, count(*) AS total_rows, count(amount) AS rows_with_amount FROM sales GROUP BY region; ``` | region | total\_rows | rows\_with\_amount | | - | - | - | | North | 3 | 3 | | South | 3 | 2 | ### group\_concat(X) and group\_concat(X, Y) ```sql theme={null} group_concat(X) group_concat(X, Y) ``` Concatenates all non-NULL values of X into a single string. The default separator is a comma (`,`). When Y is provided, it is used as the separator instead. | Parameter | Type | Description | | - | - | - | | X | any | The expression whose values to concatenate | | Y | TEXT | Separator string (default: `","`) | **Return type:** TEXT ```sql theme={null} SELECT group_concat(product) FROM sales; -- 'Widget,Gadget,Widget,Gadget,Widget,Widget' SELECT group_concat(DISTINCT product) FROM sales; -- 'Widget,Gadget' SELECT region, group_concat(product, ' | ') AS products FROM sales GROUP BY region; ``` | region | products | | - | - | | North | Widget \| Gadget \| Widget | | South | Widget \| Gadget \| Widget | ### string\_agg(X, Y) ```sql theme={null} string_agg(X, Y) ``` Alias for `group_concat(X, Y)`. Provided for compatibility with PostgreSQL. | Parameter | Type | Description | | - | - | - | | X | any | The expression whose values to concatenate | | Y | TEXT | Separator string | **Return type:** TEXT ```sql theme={null} SELECT region, string_agg(product, ', ') AS products FROM sales GROUP BY region; ``` | region | products | | - | - | | North | Widget, Gadget, Widget | | South | Widget, Gadget, Widget | ### max(X) and min(X) ```sql theme={null} max(X) min(X) ``` `max(X)` returns the maximum non-NULL value of X. `min(X)` returns the minimum non-NULL value of X. Values are compared using the standard SQLite comparison rules. Returns NULL if all values are NULL. | Parameter | Type | Description | | - | - | - | | X | any | The expression to find the maximum or minimum value of | **Return type:** Same as the input type ```sql theme={null} SELECT max(amount), min(amount) FROM sales; -- max: 250.0, min: 100.0 SELECT region, max(amount) AS highest, min(amount) AS lowest FROM sales GROUP BY region; ``` | region | highest | lowest | | - | - | - | | North | 250.0 | 100.0 | | South | 175.0 | 150.0 | > [!NOTE] > > When `max(X)` or `min(X)` is called with a single argument in an aggregate context, it acts as an aggregate function. When called with two or more arguments (e.g., `max(a, b, c)`), it acts as a [scalar function](/sql-reference/functions/scalar) and returns the largest argument. ### sum(X) and total(X) ```sql theme={null} sum(X) total(X) ``` Both functions return the sum of all non-NULL values of X. They differ in return type and behavior when all values are NULL. | Parameter | Type | Description | | - | - | - | | X | any numeric | The expression to sum | **Return type:** * `sum(X)`: INTEGER if all non-NULL inputs are integers and no overflow occurs, otherwise REAL. Returns NULL if all values are NULL. * `total(X)`: Always REAL. Returns 0.0 if all values are NULL. ```sql theme={null} SELECT sum(amount), total(amount) FROM sales; -- sum: 875.0, total: 875.0 SELECT sum(quantity), total(quantity) FROM sales; -- sum: 24, total: 24.0 ``` #### Difference between sum() and total() The key difference appears when all values in the group are NULL: ```sql theme={null} CREATE TABLE empty_amounts (val REAL); INSERT INTO empty_amounts VALUES (NULL), (NULL); SELECT sum(val) FROM empty_amounts; -- NULL SELECT total(val) FROM empty_amounts; -- 0.0 ``` This makes `total()` convenient when you need a numeric result even for empty or all-NULL groups: ```sql theme={null} SELECT region, sum(amount) AS sum_amount, total(amount) AS total_amount FROM sales GROUP BY region; ``` | region | sum\_amount | total\_amount | | - | - | - | | North | 550.0 | 550.0 | | South | 325.0 | 325.0 | ```sql theme={null} -- total() is useful in arithmetic to avoid NULL propagation SELECT total(amount) * 1.1 AS with_tax FROM sales; -- 962.5 -- sum() with all NULLs would produce NULL, making the multiplication NULL too ``` > [!NOTE] > > `sum(X)` returns an integer result when all inputs are integers and the result fits within a 64-bit signed integer. If the sum overflows, it automatically switches to REAL. Use `total(X)` when you always want a floating-point result. ## Using Aggregates with GROUP BY The `GROUP BY` clause partitions rows into groups. Each aggregate function is computed independently for each group. ```sql theme={null} SELECT region, product, count(*) AS order_count, sum(amount) AS total_sales, avg(amount) AS avg_sale, min(amount) AS min_sale, max(amount) AS max_sale FROM sales GROUP BY region, product; ``` | region | product | order\_count | total\_sales | avg\_sale | min\_sale | max\_sale | | - | - | - | - | - | - | - | | North | Gadget | 1 | 250.0 | 250.0 | 250.0 | 250.0 | | North | Widget | 2 | 300.0 | 150.0 | 100.0 | 200.0 | | South | Gadget | 1 | NULL | NULL | NULL | NULL | | South | Widget | 2 | 325.0 | 162.5 | 150.0 | 175.0 | ## Filtering Groups with HAVING The `HAVING` clause filters groups after aggregation. Use `WHERE` to filter rows before aggregation and `HAVING` to filter groups after. ```sql theme={null} SELECT region, sum(amount) AS total_sales FROM sales WHERE amount IS NOT NULL GROUP BY region HAVING sum(amount) > 400; ``` | region | total\_sales | | - | - | | North | 550.0 | ## Aggregates with DISTINCT The `DISTINCT` keyword causes the aggregate to consider only unique non-NULL values. ```sql theme={null} SELECT count(product) FROM sales; -- 6 (all non-NULL product values) SELECT count(DISTINCT product) FROM sales; -- 2 (only 'Widget' and 'Gadget') SELECT group_concat(DISTINCT product) FROM sales; -- 'Widget,Gadget' ``` ## Aggregates as Window Functions All standard aggregate functions can be used as window functions. When used with an `OVER` clause, the function computes a running or partitioned result without collapsing rows. ```sql theme={null} SELECT id, region, amount, sum(amount) OVER (PARTITION BY region ORDER BY id) AS running_total, count(*) OVER (PARTITION BY region) AS region_count FROM sales ORDER BY region, id; ``` | id | region | amount | running\_total | region\_count | | - | - | - | - | - | | 1 | North | 100.0 | 100.0 | 3 | | 2 | North | 250.0 | 350.0 | 3 | | 5 | North | 200.0 | 550.0 | 3 | | 3 | South | 150.0 | 150.0 | 3 | | 4 | South | NULL | 150.0 | 3 | | 6 | South | 175.0 | 325.0 | 3 | ## Ordered-Set Aggregates (WITHIN GROUP) > [!NOTE] > > `mode`, `percentile_cont` and `percentile_disc` with `WITHIN GROUP (ORDER BY ...)` are built in and available by default — no extension required. The syntax and results match PostgreSQL. (Standard SQLite does not support `WITHIN GROUP`.) Ordered-set aggregates compute a result over the values of an `ORDER BY` expression, sorted within each group: ```sql theme={null} mode() WITHIN GROUP (ORDER BY sort_expr) percentile_cont(fraction) WITHIN GROUP (ORDER BY sort_expr) percentile_disc(fraction) WITHIN GROUP (ORDER BY sort_expr) ``` The `ORDER BY` expression is the value being aggregated. For the percentile functions, the argument before `WITHIN GROUP` is the percentile *fraction*. NULL values of the `ORDER BY` expression are ignored; if there are no non-NULL values, the result is NULL. ### mode() ```sql theme={null} mode() WITHIN GROUP (ORDER BY X) ``` Returns the most frequent value of X. If several values are equally frequent, the smallest is returned. Works with any type and returns the value in its original type. | Parameter | Type | Description | | - | - | - | | X | any | The expression whose most frequent value to return | **Return type:** same as X ```sql theme={null} SELECT mode() WITHIN GROUP (ORDER BY product) FROM sales; -- 'Widget' (the most frequent product) SELECT region, mode() WITHIN GROUP (ORDER BY product) AS top_product FROM sales GROUP BY region; ``` | region | top\_product | | - | - | | North | Widget | | South | Widget | > [!NOTE] > > `mode()` is only valid with `WITHIN GROUP`; calling `mode(X)` without it is an error. ### percentile\_cont(fraction) and percentile\_disc(fraction) ```sql theme={null} percentile_cont(fraction) WITHIN GROUP (ORDER BY X) percentile_disc(fraction) WITHIN GROUP (ORDER BY X) ``` Compute the `fraction`-th percentile of X, where `fraction` is between 0.0 and 1.0: * `percentile_cont` returns a continuous, interpolated value (always REAL). * `percentile_disc` returns the actual element at the discrete percentile position, in its original type. | Parameter | Type | Description | | - | - | - | | fraction | REAL | Percentile fraction, 0.0 to 1.0 | | X | numeric (`percentile_cont`) / any (`percentile_disc`) | The values to compute the percentile over | **Return type:** REAL for `percentile_cont`; same as X for `percentile_disc` ```sql theme={null} -- Median, interpolated vs. an actual data point SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) FROM sales; -- 175.0 SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY amount) FROM sales; -- 175.0 -- Quartiles per region SELECT region, percentile_cont(0.25) WITHIN GROUP (ORDER BY amount) AS q1, percentile_cont(0.75) WITHIN GROUP (ORDER BY amount) AS q3 FROM sales WHERE amount IS NOT NULL GROUP BY region; ``` The fraction must be a constant with respect to the rows being aggregated — a literal, a constant expression, or a parameter. It cannot reference the columns being aggregated, and an out-of-range constant fraction is reported regardless of how many rows match: ```sql theme={null} SELECT percentile_cont(2.0) WITHIN GROUP (ORDER BY amount) FROM sales; -- Error: percentile value 2 is not between 0 and 1 ``` ### Collation Text values are ordered using the applicable collation — an explicit `COLLATE` on the `ORDER BY` expression, the column's declared collation, or `BINARY` by default — consistent with `ORDER BY`. ### Notes and limitations * A single `ORDER BY` expression is required. Multiple expressions, `DESC`, and `NULLS FIRST/LAST` inside `WITHIN GROUP` are not yet supported. * A subquery as the fraction argument is not currently supported. * These aggregates work with `GROUP BY`, `FILTER (WHERE ...)`, `HAVING`, subqueries, CTEs, joins, and attached databases. * The **percentile** extension also provides non-standard two-argument forms, `percentile_cont(Y, P)` and `percentile_disc(Y, P)` (see [Extension Aggregate Functions](#extension-aggregate-functions)). The `WITHIN GROUP` forms above are the SQL-standard versions and require no extension. ## Turso Extension: array\_agg(X) > [!NOTE] > > `array_agg(X)` is a Turso extension and is not part of standard SQLite. It is available by default in Turso without loading any additional extensions. ```sql theme={null} array_agg(X) ``` Collects all values of X (including NULLs) into an array. Returns NULL if the group is empty. | Parameter | Type | Description | | - | - | - | | X | any | The expression whose values to collect into an array | **Return type:** BLOB (array) ```sql theme={null} SELECT array_agg(name) FROM users; -- ["Alice","Bob","Charlie"] SELECT department, array_agg(name) FROM employees GROUP BY department; ``` | department | array\_agg(name) | | - | - | | Engineering | \["Alice","Bob"] | | Sales | \["Charlie","Diana","Eve"] | ```sql theme={null} -- Control ordering with a subquery SELECT array_agg(name) FROM (SELECT name FROM users ORDER BY name); -- Combine with array_length SELECT array_length(array_agg(name)) FROM users; -- 3 ``` For more array functions, see [Array Functions](/sql-reference/functions/array). ## Turso Extension: stddev(X) > [!NOTE] > > `stddev(X)` is a Turso extension and is not part of standard SQLite. It is available by default in Turso without loading any additional extensions. ```sql theme={null} stddev(X) ``` Returns the population standard deviation of all non-NULL values of X. Returns NULL if there are no non-NULL values. | Parameter | Type | Description | | - | - | - | | X | any numeric | The expression to compute the standard deviation of | **Return type:** REAL ```sql theme={null} SELECT stddev(amount) FROM sales; SELECT region, avg(amount) AS mean, stddev(amount) AS std_dev FROM sales GROUP BY region; ``` ## Extension Aggregate Functions The following aggregate functions are available through the **percentile** extension. Load it before use. > [!NOTE] > > These functions require the `percentile` extension. Load it with `SELECT load_extension('./percentile');` or by configuring your connection to auto-load it. ### median(X) ```sql theme={null} median(X) ``` Returns the median (middle value) of all non-NULL values of X. | Parameter | Type | Description | | - | - | - | | X | any numeric | The expression to find the median of | **Return type:** REAL ```sql theme={null} SELECT median(amount) FROM sales; -- 175.0 SELECT region, median(amount) AS median_amount FROM sales GROUP BY region; ``` ### percentile(Y, P) ```sql theme={null} percentile(Y, P) ``` Returns the P-th percentile of all non-NULL values of Y. Uses linear interpolation between adjacent values. | Parameter | Type | Description | | - | - | - | | Y | any numeric | The values to compute the percentile over | | P | REAL | The percentile to compute (0.0 to 100.0) | **Return type:** REAL ```sql theme={null} SELECT percentile(amount, 50) FROM sales; -- 50th percentile (median) SELECT percentile(amount, 90) FROM sales; -- 90th percentile SELECT percentile(amount, 25) FROM sales; -- 25th percentile (Q1) ``` ### percentile\_cont(Y, P) and percentile\_disc(Y, P) ```sql theme={null} percentile_cont(Y, P) percentile_disc(Y, P) ``` These two-argument forms are a convenience provided by the **percentile** extension. The SQL-standard spelling — `percentile_cont(P) WITHIN GROUP (ORDER BY Y)` — is built in and needs no extension; see [Ordered-Set Aggregates](#ordered-set-aggregates-within-group) above. `percentile_cont` uses a continuous (interpolated) distribution, while `percentile_disc` returns a discrete input value. | Parameter | Type | Description | | - | - | - | | Y | any numeric | The values to compute the percentile over | | P | REAL | The percentile fraction (0.0 to 1.0) | **Return type:** REAL ```sql theme={null} -- Continuous percentile (interpolates between values) SELECT percentile_cont(amount, 0.5) FROM sales; -- Discrete percentile (returns an actual input value) SELECT percentile_disc(amount, 0.5) FROM sales; ``` > [!NOTE] > > Note the difference in P range: `percentile(Y, P)` takes P from 0 to 100, while `percentile_cont` and `percentile_disc` take P from 0.0 to 1.0. ## See Also * [Scalar Functions](/sql-reference/functions/scalar) for per-row functions * [Expressions](/sql-reference/expressions) for operator syntax, CAST, CASE, and subqueries * [SELECT](/sql-reference/statements/select) for GROUP BY, HAVING, and window function syntax # Array Functions Source: https://docs.turso.tech/sql-reference/functions/array Create, manipulate, and query array values # Array Functions > [!NOTE] > > **Turso Extension**: Array types and functions are a Turso-specific feature for working with ordered collections directly in SQL. These functions are not part of the SQLite standard. Array columns require [STRICT tables](/sql-reference/statements/create-table#strict-tables). Turso provides built-in support for array columns and a set of functions for constructing, querying, and transforming arrays. Arrays are stored internally as compact record-format BLOBs and displayed as JSON arrays on output. *** ## Array Construction ### ARRAY\[] Literal Constructs an array from a list of expressions. ```sql theme={null} ARRAY[expr1, expr2, ...] ``` ```sql theme={null} SELECT ARRAY[1, 2, 3]; -- [1,2,3] SELECT ARRAY['hello', 'world']; -- ["hello","world"] SELECT ARRAY[]; -- [] ``` Arrays can also be inserted as JSON text: ```sql theme={null} CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT; INSERT INTO t VALUES (1, ARRAY['a', 'b', 'c']); INSERT INTO t VALUES (2, '["x", "y", "z"]'); SELECT tags FROM t ORDER BY id; -- ["a","b","c"] -- ["x","y","z"] ``` ### string\_to\_array Splits a string into an array using a delimiter. ```sql theme={null} string_to_array(text, delimiter) string_to_array(text, delimiter, null_string) ``` | Parameter | Type | Description | | - | - | - | | `text` | TEXT | The string to split | | `delimiter` | TEXT | The delimiter to split on. If NULL, splits into individual characters | | `null_string` | TEXT | Optional. Elements matching this string are replaced with NULL | **Returns:** BLOB -- an array value. ```sql theme={null} SELECT string_to_array('one,two,three', ','); -- ["one","two","three"] SELECT string_to_array('hello', NULL); -- ["h","e","l","l","o"] SELECT string_to_array('a,NULL,b', ',', 'NULL'); -- ["a",null,"b"] ``` *** ## Element Access ### Subscript Operator \[] Access individual elements using zero-based indexing. Returns NULL for out-of-bounds or negative indices. ```sql theme={null} array_expr[index] ``` ```sql theme={null} CREATE TABLE t (id INTEGER PRIMARY KEY, items TEXT[]) STRICT; INSERT INTO t VALUES (1, '["first", "second", "third"]'); SELECT items[0], items[1], items[2] FROM t; -- first|second|third SELECT items[100] FROM t; -- NULL SELECT items[-1] FROM t; -- NULL ``` Subscripts can be chained for multi-dimensional arrays: ```sql theme={null} CREATE TABLE m (id INTEGER PRIMARY KEY, matrix INTEGER[][]) STRICT; INSERT INTO m VALUES (1, ARRAY[ARRAY[1,2], ARRAY[3,4]]); SELECT matrix[0][1] FROM m; -- 2 SELECT matrix[1][0] FROM m; -- 3 ``` ### Slice Operator \[start:end] Extract a sub-array using half-open range `[start, end)`. ```sql theme={null} array_expr[start:end] ``` ```sql theme={null} CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT; INSERT INTO t VALUES (1, ARRAY['a','b','c','d']); SELECT tags[1:3] FROM t; -- ["b","c"] ``` *** ## Scalar Functions ### array\_length Returns the number of elements along a given dimension of an array. ```sql theme={null} array_length(array) array_length(array, dimension) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to measure | | `dimension` | INTEGER | Optional. 1-based dimension index. Defaults to `1` (the outermost dimension) | **Returns:** INTEGER -- the number of elements along `dimension`. NULL if the input is NULL, if `dimension < 1`, or if `dimension` exceeds the array's nesting depth. For multi-dimensional arrays the walker peeks into element zero at each level, so values are reported as if every inner array at a given depth has the same length (the layout Turso materialises). ```sql theme={null} SELECT array_length(ARRAY[10, 20, 30]); -- 3 SELECT array_length('[]'); -- 0 SELECT array_length(NULL); -- NULL SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 1); -- 2 (outer dimension) SELECT array_length(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2); -- 3 (inner dimension) SELECT array_length(ARRAY[1, 2, 3], 2); -- NULL (dimension beyond the array's depth) SELECT array_length(ARRAY[1, 2, 3], 0); -- NULL (dimension < 1) ``` ### array\_upper Returns the upper bound (inclusive) of a given dimension of an array. ```sql theme={null} array_upper(array, dimension) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to measure | | `dimension` | INTEGER | 1-based dimension index | **Returns:** INTEGER -- the upper bound of `dimension`. NULL with the same conditions as `array_length`. Turso arrays are always 1-indexed, so `array_upper(arr, dim)` is equivalent to `array_length(arr, dim)` for every valid input. The function is provided for PostgreSQL compatibility. ```sql theme={null} SELECT array_upper(ARRAY[10, 20, 30], 1); -- 3 SELECT array_upper(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]], 2); -- 3 SELECT array_upper(ARRAY[1, 2, 3], 2); -- NULL ``` ### array\_append Appends an element to the end of an array. ```sql theme={null} array_append(array, element) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to append to. If NULL, a new single-element array is created | | `element` | any | The element to append | **Returns:** BLOB -- a new array with the element appended. ```sql theme={null} SELECT array_append('[1, 2]', 3); -- [1,2,3] SELECT array_append('[]', 'x'); -- ["x"] SELECT array_append(NULL, 'x'); -- ["x"] ``` ### array\_prepend Prepends an element to the beginning of an array. ```sql theme={null} array_prepend(element, array) ``` | Parameter | Type | Description | | - | - | - | | `element` | any | The element to prepend | | `array` | BLOB/TEXT | The array to prepend to | **Returns:** BLOB -- a new array with the element prepended. ```sql theme={null} SELECT array_prepend('z', '["a", "b"]'); -- ["z","a","b"] ``` ### array\_cat Concatenates two arrays. ```sql theme={null} array_cat(array1, array2) ``` | Parameter | Type | Description | | - | - | - | | `array1` | BLOB/TEXT | The first array | | `array2` | BLOB/TEXT | The second array | **Returns:** BLOB -- a new array containing all elements from both arrays. ```sql theme={null} SELECT array_cat('[1, 2]', '[3, 4]'); -- [1,2,3,4] SELECT array_cat('[]', '[1, 2]'); -- [1,2] ``` The `||` operator also works for array concatenation: ```sql theme={null} CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT; INSERT INTO t VALUES (1, ARRAY['a', 'b']); -- Append element SELECT tags || 'c' FROM t; -- ["a","b","c"] -- Prepend element SELECT 'z' || tags FROM t; -- ["z","a","b"] -- Concatenate arrays SELECT tags || ARRAY['x', 'y'] FROM t; -- ["a","b","x","y"] ``` ### array\_remove Removes all occurrences of an element from an array. ```sql theme={null} array_remove(array, element) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to remove from | | `element` | any | The element to remove | **Returns:** BLOB -- a new array with all occurrences of the element removed. ```sql theme={null} SELECT array_remove('[1, 2, 3, 2, 1]', 2); -- [1,3,1] SELECT array_remove('[1, 2, 3]', 99); -- [1,2,3] ``` ### array\_contains Tests whether an array contains a specific element. ```sql theme={null} array_contains(array, element) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to search | | `element` | any | The element to search for | **Returns:** INTEGER -- 1 if found, 0 if not found. ```sql theme={null} SELECT array_contains('[1, 2, 3]', 2); -- 1 SELECT array_contains('[1, 2, 3]', 5); -- 0 ``` ### array\_position Returns the zero-based index of the first occurrence of an element. ```sql theme={null} array_position(array, element) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to search | | `element` | any | The element to find | **Returns:** INTEGER -- the zero-based index, or NULL if not found. ```sql theme={null} SELECT array_position('[10, 20, 30]', 20); -- 1 SELECT array_position('[10, 20, 30]', 10); -- 0 SELECT array_position('[10, 20, 30]', 99); -- NULL ``` ### array\_slice Extracts a sub-array by index range. ```sql theme={null} array_slice(array, start, end) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The source array | | `start` | INTEGER | Start index (zero-based, inclusive). NULL means 0 | | `end` | INTEGER | End index (exclusive) | **Returns:** BLOB -- a new array containing elements from `start` to `end - 1`. ```sql theme={null} SELECT array_slice('[1, 2, 3, 4]', 1, 3); -- [2,3] SELECT array_slice('[1, 2, 3, 4]', NULL, 2); -- [1,2] ``` ### array\_to\_string Joins array elements into a string with a delimiter. ```sql theme={null} array_to_string(array, delimiter) array_to_string(array, delimiter, null_string) ``` | Parameter | Type | Description | | - | - | - | | `array` | BLOB/TEXT | The array to join | | `delimiter` | TEXT | The separator between elements | | `null_string` | TEXT | Optional. Replacement text for NULL elements. If omitted, NULLs are skipped | **Returns:** TEXT -- the joined string. ```sql theme={null} SELECT array_to_string('[1, 2, 3]', ','); -- 1,2,3 SELECT array_to_string('["hello", "world"]', ' '); -- hello world SELECT array_to_string('[1, null, 3]', ','); -- 1,3 SELECT array_to_string('[1, null, 3]', ',', 'N/A'); -- 1,N/A,3 ``` ### array\_contains\_all Tests whether one array contains all elements of another. ```sql theme={null} array_contains_all(haystack, needles) ``` | Parameter | Type | Description | | - | - | - | | `haystack` | BLOB/TEXT | The array to search in | | `needles` | BLOB/TEXT | The array of elements to search for | **Returns:** INTEGER -- 1 if all elements of `needles` are found in `haystack`, 0 otherwise. ```sql theme={null} SELECT array_contains_all('[1, 2, 3, 4, 5]', '[2, 4]'); -- 1 SELECT array_contains_all('[1, 2, 3]', '[2, 4]'); -- 0 SELECT array_contains_all('[1, 2, 3]', '[]'); -- 1 ``` The `@>` operator is an alias for this function. See [Array Operators](/sql-reference/expressions#array-operators). ### array\_overlap Tests whether two arrays share any common elements. ```sql theme={null} array_overlap(array1, array2) ``` | Parameter | Type | Description | | - | - | - | | `array1` | BLOB/TEXT | The first array | | `array2` | BLOB/TEXT | The second array | **Returns:** INTEGER -- 1 if any element appears in both arrays, 0 otherwise. ```sql theme={null} SELECT array_overlap('[1, 2, 3]', '[3, 4, 5]'); -- 1 SELECT array_overlap('[1, 2, 3]', '[4, 5, 6]'); -- 0 SELECT array_overlap('[]', '[1, 2]'); -- 0 ``` `array_overlaps` is accepted as an alias. The `&&` operator is also an alias. See [Array Operators](/sql-reference/expressions#array-operators). *** ## Aggregate Function ### array\_agg Collects values from a group of rows into an array. ```sql theme={null} array_agg(expression) ``` | Parameter | Type | Description | | - | - | - | | `expression` | any | The value to collect. NULL values are included | **Returns:** BLOB -- an array containing one element per row in the group. Returns NULL for empty groups. ```sql theme={null} CREATE TABLE t (grp TEXT, val TEXT); INSERT INTO t VALUES ('x', 'a'), ('x', 'b'), ('y', 'c'), ('y', 'd'), ('y', 'e'); SELECT grp, array_agg(val) FROM t GROUP BY grp ORDER BY grp; ``` | grp | array\_agg(val) | | - | - | | x | \["a","b"] | | y | \["c","d","e"] | ```sql theme={null} -- Control order with a subquery SELECT array_agg(val) FROM (SELECT val FROM t ORDER BY val); -- ["a","b","c","d","e"] -- Combine with array_length SELECT array_length(array_agg(val)) FROM t; -- 5 ``` *** ## Array Operators ### Containment: @> Tests whether the left array contains all elements of the right array. Equivalent to `array_contains_all(left, right)`. ```sql theme={null} array1 @> array2 ``` ```sql theme={null} SELECT ARRAY[1,2,3] @> ARRAY[1,2]; -- 1 SELECT ARRAY[1,2,3] @> ARRAY[1,4]; -- 0 ``` ### Overlap: && Tests whether two arrays share any common elements. Equivalent to `array_overlap(left, right)`. ```sql theme={null} array1 && array2 ``` ```sql theme={null} SELECT ARRAY[1,2,3] && ARRAY[3,4,5]; -- 1 SELECT ARRAY[1,2] && ARRAY[3,4]; -- 0 ``` ### Comparison: =, !=, \<, >, \<=, >= Arrays support element-wise comparison. Elements are compared pairwise from left to right; if all compared elements are equal, the shorter array is considered less than the longer one. ```sql theme={null} SELECT ARRAY[1,2,3] = ARRAY[1,2,3]; -- 1 SELECT ARRAY[1,2] < ARRAY[1,3]; -- 1 SELECT ARRAY[1,2] < ARRAY[1,2,3]; -- 1 (shorter is less) ``` ### Concatenation: || When either operand is an array, `||` performs array concatenation or element append/prepend instead of string concatenation. ```sql theme={null} SELECT ARRAY[1,2] || ARRAY[3,4]; -- [1,2,3,4] (array + array) SELECT ARRAY[1,2] || 3; -- [1,2,3] (append element) SELECT 0 || ARRAY[1,2]; -- [0,1,2] (prepend element) ``` ### Using operators in WHERE clauses ```sql theme={null} CREATE TABLE docs (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT; INSERT INTO docs VALUES (1, ARRAY['sql','database','tutorial']); INSERT INTO docs VALUES (2, ARRAY['rust','systems']); INSERT INTO docs VALUES (3, ARRAY['sql','rust']); -- Find docs tagged with both 'sql' and 'rust' SELECT id FROM docs WHERE tags @> ARRAY['sql','rust']; -- 3 -- Find docs with any overlap with a set of tags SELECT id FROM docs WHERE tags && ARRAY['database','systems'] ORDER BY id; -- 1 -- 2 ``` *** ## Subscript Assignment Array elements can be updated individually using subscript syntax in UPDATE statements. ```sql theme={null} UPDATE table SET column[index] = value WHERE ...; ``` ```sql theme={null} CREATE TABLE t (id INTEGER PRIMARY KEY, tags TEXT[]) STRICT; INSERT INTO t VALUES (1, ARRAY['a','b','c']); UPDATE t SET tags[0] = 'X' WHERE id = 1; SELECT tags FROM t; -- ["X","b","c"] -- Multiple subscripts in one UPDATE UPDATE t SET tags[0] = 'A', tags[2] = 'C' WHERE id = 1; SELECT tags FROM t; -- ["A","b","C"] ``` Out-of-bounds assignments are silently ignored (the array is unchanged). *** ## Examples ### Tagging system ```sql theme={null} CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, tags TEXT[] ) STRICT; INSERT INTO articles VALUES (1, 'Getting Started with SQL', ARRAY['sql','beginner','tutorial']); INSERT INTO articles VALUES (2, 'Advanced Indexing', ARRAY['sql','performance','advanced']); INSERT INTO articles VALUES (3, 'Rust for Systems', ARRAY['rust','systems']); -- Find articles tagged 'sql' SELECT title FROM articles WHERE array_contains(tags, 'sql'); -- Find articles with both 'sql' and 'advanced' SELECT title FROM articles WHERE tags @> ARRAY['sql','advanced']; -- Advanced Indexing -- Count tags per article SELECT title, array_length(tags) AS tag_count FROM articles; -- Collect all unique tags SELECT array_agg(DISTINCT tag) FROM ( SELECT array_to_string(tags, ',') AS tag FROM articles ); ``` ### Multi-dimensional arrays ```sql theme={null} CREATE TABLE matrices ( id INTEGER PRIMARY KEY, data INTEGER[][] ) STRICT; INSERT INTO matrices VALUES (1, ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]]); -- Access a row SELECT data[0] FROM matrices WHERE id = 1; -- [1,2,3] -- Access a single element SELECT data[1][2] FROM matrices WHERE id = 1; -- 6 -- Get number of rows SELECT array_length(data) FROM matrices WHERE id = 1; -- 2 ``` ### Type checking Array columns in STRICT tables validate element types on insert and update: ```sql theme={null} CREATE TABLE scores (id INTEGER PRIMARY KEY, vals INTEGER[]) STRICT; -- OK: exact integers INSERT INTO scores VALUES (1, '[1, 2, 3]'); -- OK: floats that are exact integers are coerced INSERT INTO scores VALUES (2, '[1.0, 2.0]'); -- OK: numeric strings are coerced INSERT INTO scores VALUES (3, '["42", "100"]'); -- Error: non-numeric string in INTEGER array INSERT INTO scores VALUES (4, '["hello"]'); -- Error: cannot store TEXT value in INTEGER -- NULL elements are always allowed INSERT INTO scores VALUES (5, '[1, null, 3]'); ``` ## See Also * [Data Types](/sql-reference/data-types#array-types) for array column type declarations * [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table requirements * [Expressions](/sql-reference/expressions#array-operators) for array operator syntax * [Aggregate Functions](/sql-reference/functions/aggregate) for `array_agg` # Date and Time Functions Source: https://docs.turso.tech/sql-reference/functions/date-time Functions for creating, formatting, and manipulating dates, times, and timestamps # Date and Time Functions Turso provides a complete set of date and time functions compatible with SQLite. These functions accept time values in various formats, apply optional modifiers, and return results as TEXT, REAL, or INTEGER depending on the function. All date and time functions use the [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) and assume UTC unless the `localtime` modifier is applied. ## Functions ### date() Returns the date as TEXT in `YYYY-MM-DD` format. ```sql theme={null} date(time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** TEXT in `YYYY-MM-DD` format, or NULL if any argument is invalid. ```sql theme={null} SELECT date('now'); -- '2025-06-15' SELECT date('2025-06-15 14:30:00'); -- '2025-06-15' SELECT date('2025-06-15', '+1 month'); -- '2025-07-15' SELECT date('2025-06-15', 'start of year'); -- '2025-01-01' ``` *** ### time() Returns the time as TEXT in `HH:MM:SS` format. ```sql theme={null} time(time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** TEXT in `HH:MM:SS` format, or NULL if any argument is invalid. ```sql theme={null} SELECT time('now'); -- '14:30:00' SELECT time('2025-06-15 14:30:00'); -- '14:30:00' SELECT time('14:30:00', '+2 hours'); -- '16:30:00' SELECT time('14:30:00', '+90 minutes'); -- '16:00:00' ``` *** ### datetime() Returns the date and time as TEXT in `YYYY-MM-DD HH:MM:SS` format. ```sql theme={null} datetime(time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** TEXT in `YYYY-MM-DD HH:MM:SS` format, or NULL if any argument is invalid. ```sql theme={null} SELECT datetime('now'); -- '2025-06-15 14:30:00' SELECT datetime('2025-06-15', '+1 day', '+6 hours'); -- '2025-06-16 06:00:00' SELECT datetime(1718458200, 'unixepoch'); -- '2024-06-15 14:30:00' ``` *** ### julianday() Returns the [Julian day number](https://en.wikipedia.org/wiki/Julian_day) as a REAL. The Julian day number is the number of days since noon on November 24, 4714 B.C. (proleptic Gregorian calendar). ```sql theme={null} julianday(time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** REAL representing the Julian day number, or NULL if any argument is invalid. ```sql theme={null} SELECT julianday('2025-06-15'); -- 2460811.5 SELECT julianday('now'); -- 2460811.104166... -- Compute the number of days between two dates SELECT julianday('2025-12-25') - julianday('2025-06-15'); -- 193.0 ``` *** ### unixepoch() Returns the Unix timestamp as an INTEGER. The Unix timestamp is the number of seconds since `1970-01-01 00:00:00 UTC`. ```sql theme={null} unixepoch(time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** INTEGER representing seconds since the Unix epoch, or NULL if any argument is invalid. If the `subsec` modifier is used, returns REAL with fractional seconds. ```sql theme={null} SELECT unixepoch('now'); -- 1718458200 SELECT unixepoch('2025-06-15 14:30:00'); -- 1750001400 SELECT unixepoch('2025-06-15 14:30:00.123', 'subsec'); -- 1750001400.123 ``` *** ### strftime() Returns a formatted date/time string according to the specified format string. ```sql theme={null} strftime(format, time-value, modifier, modifier, ...) ``` | Parameter | Type | Description | | - | - | - | | `format` | TEXT | A format string containing [substitution codes](#strftime-format-codes) | | `time-value` | TEXT, REAL, or INTEGER | A time value in any [supported format](#time-value-formats) | | `modifier` | TEXT | Zero or more [modifiers](#modifiers) applied left to right | **Returns:** TEXT with format codes replaced by date/time components, or NULL if any argument is invalid. The other date/time functions can be expressed as `strftime` calls: | Function | Equivalent strftime | | - | - | | `date(...)` | `strftime('%Y-%m-%d', ...)` | | `time(...)` | `strftime('%H:%M:%S', ...)` | | `datetime(...)` | `strftime('%Y-%m-%d %H:%M:%S', ...)` | | `julianday(...)` | `strftime('%J', ...)` (as TEXT) | | `unixepoch(...)` | `strftime('%s', ...)` (as TEXT) | ```sql theme={null} SELECT strftime('%Y', 'now'); -- '2025' SELECT strftime('%m/%d/%Y', '2025-06-15'); -- '06/15/2025' SELECT strftime('%H:%M', '2025-06-15 14:30:00'); -- '14:30' SELECT strftime('%Y-%m-%d %H:%M:%f', '2025-06-15 14:30:00.123'); -- '2025-06-15 14:30:00.123' SELECT strftime('%s', '2025-06-15 14:30:00'); -- '1750001400' SELECT strftime('%W', '2025-06-15'); -- '23' (week of year) ``` #### strftime Format Codes | Code | Description | Example | | - | - | - | | `%d` | Day of month (01-31) | `15` | | `%e` | Day of month with leading space ( 1-31) | `15` | | `%f` | Fractional seconds (SS.SSS) | `00.123` | | `%F` | ISO 8601 date (YYYY-MM-DD) | `2025-06-15` | | `%H` | Hour (00-23) | `14` | | `%I` | Hour (01-12) | `02` | | `%j` | Day of year (001-366) | `166` | | `%J` | Julian day number | `2460811.5` | | `%k` | Hour with leading space ( 0-23) | `14` | | `%l` | Hour with leading space ( 1-12) | ` 2` | | `%m` | Month (01-12) | `06` | | `%M` | Minute (00-59) | `30` | | `%p` | AM or PM | `PM` | | `%P` | am or pm | `pm` | | `%R` | Time as HH:MM | `14:30` | | `%s` | Seconds since Unix epoch | `1750001400` | | `%S` | Seconds (00-59) | `00` | | `%T` | Time as HH:MM:SS | `14:30:00` | | `%u` | Day of week (1=Monday, 7=Sunday) | `7` | | `%w` | Day of week (0=Sunday, 6=Saturday) | `0` | | `%W` | Week of year (00-53) | `23` | | `%Y` | Year (0000-9999) | `2025` | | `%%` | Literal `%` character | `%` | *** ### timediff() Returns the difference between two time values as a TEXT string in the format `(+|-)YYYY-MM-DD HH:MM:SS.SSS`. The result represents the time that must be added to `time2` to produce `time1`. ```sql theme={null} timediff(time1, time2) ``` | Parameter | Type | Description | | - | - | - | | `time1` | TEXT, REAL, or INTEGER | The later time value | | `time2` | TEXT, REAL, or INTEGER | The earlier time value | **Returns:** TEXT in `(+|-)YYYY-MM-DD HH:MM:SS.SSS` format, or NULL if either argument is invalid. ```sql theme={null} SELECT timediff('2025-06-15', '2025-06-10'); -- '+0000-00-05 00:00:00.000' SELECT timediff('2025-01-01', '2024-01-01'); -- '+0001-00-00 00:00:00.000' SELECT timediff('2025-06-15 14:30:00', '2025-06-15 10:00:00'); -- '+0000-00-00 04:30:00.000' SELECT timediff('2024-01-01', '2025-06-15'); -- '-0001-05-14 00:00:00.000' ``` ## Time Value Formats All date and time functions accept time values in the following formats. | Format | Example | Description | | - | - | - | | `YYYY-MM-DD` | `'2025-06-15'` | Date only (time defaults to 00:00:00) | | `YYYY-MM-DD HH:MM` | `'2025-06-15 14:30'` | Date and time (seconds default to 00) | | `YYYY-MM-DD HH:MM:SS` | `'2025-06-15 14:30:00'` | Date and time with seconds | | `YYYY-MM-DD HH:MM:SS.SSS` | `'2025-06-15 14:30:00.123'` | Date and time with fractional seconds | | `YYYY-MM-DDTHH:MM` | `'2025-06-15T14:30'` | ISO 8601 with `T` separator | | `YYYY-MM-DDTHH:MM:SS` | `'2025-06-15T14:30:00'` | ISO 8601 with `T` separator | | `YYYY-MM-DDTHH:MM:SS.SSS` | `'2025-06-15T14:30:00.123'` | ISO 8601 with `T` separator | | `HH:MM` | `'14:30'` | Time only (date defaults to 2000-01-01) | | `HH:MM:SS` | `'14:30:00'` | Time only with seconds | | `HH:MM:SS.SSS` | `'14:30:00.123'` | Time only with fractional seconds | | `now` | `'now'` | Current date and time in UTC | | Julian day number | `2460811.5` | REAL number representing a Julian day | | Unix timestamp | `1718458200` | INTEGER, requires `'unixepoch'` modifier | > [!NOTE] > > When passing a Unix timestamp as the time value, you must include the `'unixepoch'` modifier so the function knows to interpret the number as seconds since 1970-01-01, not as a Julian day number. ```sql theme={null} -- Unix timestamp requires the 'unixepoch' modifier SELECT datetime(1718458200, 'unixepoch'); -- '2024-06-15 14:30:00' -- Without 'unixepoch', the number is treated as a Julian day SELECT datetime(1718458200); -- NULL (or a very distant date) -- 'now' returns the current UTC time SELECT datetime('now'); -- '2025-06-15 14:30:00' ``` ## Modifiers Modifiers transform the time value. Multiple modifiers are applied left to right. If any modifier is invalid, the function returns NULL. ### Offset Modifiers Offset modifiers add or subtract a specified amount from the time value. | Modifier | Description | Example | | - | - | - | | `NNN days` | Add NNN days | `'+7 days'`, `'-1 days'` | | `NNN hours` | Add NNN hours | `'+6 hours'` | | `NNN minutes` | Add NNN minutes | `'+30 minutes'` | | `NNN seconds` | Add NNN seconds | `'+90 seconds'` | | `NNN.NNNN seconds` | Add fractional seconds | `'+0.5 seconds'` | | `NNN months` | Add NNN months | `'+1 months'` | | `NNN years` | Add NNN years | `'+1 years'` | NNN can be a positive or negative integer (or real number for seconds). The `+` sign is optional for positive values. ```sql theme={null} SELECT date('2025-06-15', '+7 days'); -- '2025-06-22' SELECT datetime('2025-06-15 14:30:00', '-6 hours'); -- '2025-06-15 08:30:00' SELECT date('2025-01-31', '+1 months'); -- '2025-03-03' (Jan 31 + 1 month = Mar 3) ``` ### Time Offset Modifier A time offset in the format `+HH:MM` or `-HH:MM` adds or subtracts the specified hours and minutes. ```sql theme={null} SELECT time('14:30:00', '+05:30'); -- '20:00:00' SELECT datetime('2025-06-15 14:30:00', '-08:00'); -- '2025-06-15 06:30:00' ``` ### Start-of Modifiers These modifiers reset the time value to the start of a period. | Modifier | Description | | - | - | | `start of day` | Sets time to 00:00:00, keeps date | | `start of month` | Sets to first day of the month, time to 00:00:00 | | `start of year` | Sets to January 1 of the year, time to 00:00:00 | ```sql theme={null} SELECT datetime('2025-06-15 14:30:00', 'start of day'); -- '2025-06-15 00:00:00' SELECT date('2025-06-15', 'start of month'); -- '2025-06-01' SELECT date('2025-06-15', 'start of year'); -- '2025-01-01' ``` ### Weekday Modifier The `weekday N` modifier advances the date to the next occurrence of the specified weekday, where 0 = Sunday, 1 = Monday, ..., 6 = Saturday. If the current date already falls on that weekday, it is unchanged. ```sql theme={null} -- Next Sunday (weekday 0) SELECT date('2025-06-15', 'weekday 0'); -- '2025-06-15' (June 15, 2025 is a Sunday) -- Next Monday SELECT date('2025-06-15', 'weekday 1'); -- '2025-06-16' -- Next Friday SELECT date('2025-06-15', 'weekday 5'); -- '2025-06-20' ``` ### Interpretation Modifiers | Modifier | Description | | - | - | | `unixepoch` | Interpret the time value as a Unix timestamp (seconds since 1970-01-01) | | `julianday` | Interpret the time value as a Julian day number | | `auto` | Automatically detect whether the value is a Unix timestamp or Julian day | ```sql theme={null} SELECT datetime(1718458200, 'unixepoch'); -- '2024-06-15 14:30:00' SELECT datetime(2460811.5, 'julianday'); -- '2025-06-15 00:00:00' SELECT datetime(0, 'unixepoch'); -- '1970-01-01 00:00:00' ``` ### Timezone Modifiers | Modifier | Description | | - | - | | `localtime` | Convert from UTC to local time | | `utc` | Convert from local time to UTC | ```sql theme={null} SELECT datetime('2025-06-15 14:30:00', 'localtime'); -- '2025-06-15 07:30:00' (example: UTC-7) SELECT time('now', 'localtime'); -- Local current time ``` ### Rounding Modifiers | Modifier | Description | | - | - | | `ceiling` | When adding months, if the day overflows, advance to the first day of the next month | | `floor` | When adding months, if the day overflows, use the last day of the target month | These modifiers affect how month arithmetic handles months with different numbers of days. ```sql theme={null} -- Without floor/ceiling, month overflow rolls forward SELECT date('2025-01-31', '+1 months'); -- '2025-03-03' -- With floor, clamp to last day of target month SELECT date('2025-01-31', 'floor', '+1 months'); -- '2025-02-28' -- With ceiling, advance to first of next month SELECT date('2025-01-31', 'ceiling', '+1 months'); -- '2025-03-01' ``` ### Subsec Modifier The `subsec` modifier causes `unixepoch()` to return a REAL with fractional seconds instead of truncating to INTEGER. ```sql theme={null} SELECT unixepoch('2025-06-15 14:30:00.456', 'subsec'); -- 1750001400.456 ``` ## Practical Examples ### Get the Current Date and Time ```sql theme={null} SELECT date('now'); -- Current UTC date SELECT time('now'); -- Current UTC time SELECT datetime('now'); -- Current UTC date and time SELECT datetime('now', 'localtime'); -- Current local date and time ``` ### Date Arithmetic ```sql theme={null} -- Tomorrow SELECT date('now', '+1 day'); -- 90 days from now SELECT date('now', '+90 days'); -- Last day of the current month SELECT date('now', 'start of month', '+1 month', '-1 day'); -- First Monday of the current month SELECT date('now', 'start of month', 'weekday 1'); ``` ### Age Calculation ```sql theme={null} SELECT name, birth_date, timediff('now', birth_date) AS age_diff FROM users; ``` ### Convert Between Formats ```sql theme={null} -- Unix timestamp to human-readable SELECT datetime(1718458200, 'unixepoch'); -- Human-readable to Unix timestamp SELECT unixepoch('2025-06-15 14:30:00'); -- Date to Julian day SELECT julianday('2025-06-15'); -- Julian day to date SELECT date(2460811.5); ``` ### Group Records by Time Period ```sql theme={null} -- Count orders per month SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS order_count FROM orders GROUP BY strftime('%Y-%m', order_date) ORDER BY month; -- Count events per day of week SELECT CASE strftime('%w', event_date) WHEN '0' THEN 'Sunday' WHEN '1' THEN 'Monday' WHEN '2' THEN 'Tuesday' WHEN '3' THEN 'Wednesday' WHEN '4' THEN 'Thursday' WHEN '5' THEN 'Friday' WHEN '6' THEN 'Saturday' END AS day_name, COUNT(*) AS event_count FROM events GROUP BY strftime('%w', event_date) ORDER BY strftime('%w', event_date); ``` ### Filter by Date Range ```sql theme={null} -- Records from the last 30 days SELECT * FROM logs WHERE timestamp >= datetime('now', '-30 days'); -- Records from a specific month SELECT * FROM orders WHERE order_date >= '2025-06-01' AND order_date < '2025-07-01'; -- Records from this year SELECT * FROM sales WHERE date(sale_date) >= date('now', 'start of year'); ``` ### Compute Elapsed Time ```sql theme={null} -- Days between two dates SELECT julianday('2025-12-25') - julianday('now') AS days_until_christmas; -- Seconds between two timestamps SELECT unixepoch('2025-06-15 18:00:00') - unixepoch('2025-06-15 14:30:00') AS seconds_elapsed; -- 12600 ``` ## See Also * [Expressions](/sql-reference/expressions) for using functions in expressions * [Data Types](/sql-reference/data-types) for TEXT, REAL, and INTEGER storage classes * [Scalar Functions](/sql-reference/functions/scalar) for other built-in functions # Full-Text Search Source: https://docs.turso.tech/sql-reference/functions/fts Full-text search with Tantivy-powered FTS indexes, scoring, and highlighting # Full-Text Search > [!NOTE] > > **Turso Extension**: Full-text search (FTS) in Turso is powered by [Tantivy](https://github.com/quickwit-oss/tantivy), not SQLite's FTS3/FTS4/FTS5 modules. The syntax and functions differ from SQLite FTS. Turso provides full-text search through custom FTS indexes and three SQL functions: `fts_match` for filtering, `fts_score` for relevance ranking, and `fts_highlight` for displaying results with matched terms highlighted. *** ## Creating FTS Indexes FTS indexes are created with the `USING fts` clause on `CREATE INDEX`. Each indexed column participates in the full-text search. ```sql theme={null} CREATE INDEX index_name ON table_name USING fts (column1, column2, ...); ``` ### Tokenizer Configuration Each column can use a different tokenizer via the `WITH` clause. ```sql theme={null} -- All columns use the default tokenizer CREATE INDEX idx_articles ON articles USING fts (title, body); -- Per-column tokenizer configuration CREATE INDEX idx_articles ON articles USING fts ( title WITH tokenizer=simple, body WITH tokenizer=ngram ); -- Global tokenizer for all columns CREATE INDEX idx_tags ON tags USING fts (tag) WITH (tokenizer = 'raw'); ``` ### Available Tokenizers | Tokenizer | Description | Use case | | - | - | - | | `default` | Unicode-aware tokenizer with lowercasing and punctuation splitting (40-char limit) | General-purpose text search | | `raw` | No tokenization -- matches the entire field value as a single token | IDs, UUIDs, tags, exact-match fields | | `simple` | Splits on whitespace and punctuation without lowercasing | Simple text without case-insensitive needs | | `whitespace` | Splits on whitespace only | Whitespace-delimited tokens | | `ngram` | Generates 2-3 character n-grams from the text | Autocomplete, substring matching | **Tokenizer examples:** | Input | default | raw | ngram | | - | - | - | - | | `Hello World` | `hello`, `world` | `Hello World` | `He`, `Hel`, `el`, `ell`, `ll`, `llo`, ... | | `user-123` | `user`, `123` | `user-123` | `us`, `use`, `se`, `ser`, ... | ### Field Weights You can assign relative weights to indexed columns to influence the BM25 relevance score. ```sql theme={null} CREATE INDEX idx_articles ON articles USING fts (title, body) WITH (weights = 'title=2.0,body=1.0'); ``` | Parameter | Default | Description | | - | - | - | | `weights` | `1.0` for all fields | Comma-separated `column=weight` pairs. Higher weights increase score contribution. | *** ## Functions ### fts\_match Returns 1 if the row matches the full-text query, or 0 otherwise. Used in the `WHERE` clause to filter rows. ```sql theme={null} fts_match(column1, column2, ..., query) ``` | Parameter | Type | Description | | - | - | - | | `column1, column2, ...` | TEXT | One or more columns covered by an FTS index | | `query` | TEXT | The search query string (see [Query Syntax](#query-syntax) below) | **Returns:** INTEGER -- 1 if the row matches, 0 otherwise. The columns passed to `fts_match` must correspond to columns in an existing FTS index. When Turso's query planner detects `fts_match` in a WHERE clause, it routes the query through the FTS index for efficient lookup. ```sql theme={null} SELECT id, title FROM articles WHERE fts_match(title, body, 'database'); -- Single column SELECT id, title FROM articles WHERE fts_match(body, 'machine learning'); ``` ### fts\_score Computes the BM25 relevance score for each matching row. Lower scores indicate higher relevance. ```sql theme={null} fts_score(column1, column2, ..., query) ``` | Parameter | Type | Description | | - | - | - | | `column1, column2, ...` | TEXT | One or more columns covered by an FTS index | | `query` | TEXT | The search query string | **Returns:** REAL -- the BM25 relevance score. Lower values mean higher relevance. Use `ORDER BY score ASC` or `ORDER BY score DESC` depending on your preference for result ordering. When used in SELECT, the FTS index automatically provides scored results. ```sql theme={null} SELECT id, title, fts_score(title, body, 'database') AS score FROM articles ORDER BY score DESC LIMIT 10; ``` ### fts\_highlight Returns text with matching query terms wrapped in custom tags. Useful for displaying search results with visual emphasis on matched terms. ```sql theme={null} fts_highlight(column1, column2, ..., open_tag, close_tag, query) ``` | Parameter | Type | Description | | - | - | - | | `column1, column2, ...` | TEXT | One or more text columns to highlight | | `open_tag` | TEXT | The tag to insert before each matching term (e.g. `''`) | | `close_tag` | TEXT | The tag to insert after each matching term (e.g. `''`) | | `query` | TEXT | The search query string | **Returns:** TEXT -- the input text with matching terms wrapped in the specified tags. Returns the original text if no matches are found. Returns NULL if the query, open\_tag, or close\_tag is NULL. When multiple columns are provided, their text is concatenated with spaces. ```sql theme={null} SELECT fts_highlight(title, '', '', 'database') AS highlighted FROM articles WHERE fts_match(title, body, 'database'); -- Database Design Patterns -- Multiple columns are concatenated SELECT fts_highlight(title, body, '', '', 'database') AS highlighted FROM articles WHERE fts_match(title, body, 'database'); -- Standalone usage (without an FTS index) SELECT fts_highlight('The quick brown fox', '', '', 'quick fox'); -- The quick brown fox ``` *** ## Query Syntax The query string passed to `fts_match` and `fts_score` supports Tantivy's query parser syntax. ### Basic queries | Syntax | Example | Description | | - | - | - | | Single term | `'database'` | Match rows containing "database" | | Multiple terms (OR) | `'database search'` | Match rows containing "database" OR "search" | | Boolean AND | `'database AND search'` | Match rows containing both terms | | Boolean NOT | `'database NOT nosql'` | Match "database" but exclude "nosql" | ### Advanced queries | Syntax | Example | Description | | - | - | - | | Phrase search | `'"exact phrase"'` | Match the exact phrase | | Prefix search | `'data*'` | Match terms starting with "data" | | Column-specific | `'title:database'` | Match "database" only in the `title` field | | Boosting | `'title:database^2'` | Boost matches in `title` by a factor of 2 | ### Examples ```sql theme={null} -- Simple term search SELECT * FROM articles WHERE fts_match(title, body, 'database'); -- Phrase search: match "full text search" as an exact phrase SELECT * FROM articles WHERE fts_match(title, body, '"full text search"'); -- Boolean AND: both terms must be present SELECT * FROM articles WHERE fts_match(title, body, 'database AND performance'); -- Prefix search: match "optim", "optimize", "optimization", etc. SELECT * FROM articles WHERE fts_match(title, body, 'optim*'); -- Column-specific: only match "rust" in the title field SELECT * FROM articles WHERE fts_match(title, body, 'title:rust'); -- Boosted search: title matches count 2x more toward the score SELECT id, title, fts_score(title, body, 'title:database^2 body:database') AS score FROM articles ORDER BY score DESC; -- Exclusion: match "database" but not "nosql" SELECT * FROM articles WHERE fts_match(title, body, 'database NOT nosql'); ``` *** ## Complete Example This example walks through creating a table, adding an FTS index, inserting data, and running queries with scoring and highlighting. ```sql theme={null} -- Create a table CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, body TEXT, author TEXT ); -- Create an FTS index on title and body with field weights CREATE INDEX idx_articles_fts ON articles USING fts (title, body) WITH (weights = 'title=2.0,body=1.0'); -- Insert sample data INSERT INTO articles VALUES (1, 'Introduction to Databases', 'Databases store and organize data for efficient retrieval.', 'Alice'); INSERT INTO articles VALUES (2, 'Full-Text Search in Practice', 'Full-text search allows finding documents by content.', 'Bob'); INSERT INTO articles VALUES (3, 'Database Performance Tuning', 'Optimizing database queries requires understanding indexes.', 'Alice'); INSERT INTO articles VALUES (4, 'Getting Started with Rust', 'Rust is a systems programming language focused on safety.', 'Carol'); -- Simple search: find articles mentioning "database" SELECT id, title FROM articles WHERE fts_match(title, body, 'database'); -- 1 | Introduction to Databases -- 3 | Database Performance Tuning -- Ranked search: order by relevance SELECT id, title, fts_score(title, body, 'database') AS score FROM articles WHERE fts_match(title, body, 'database') ORDER BY score DESC LIMIT 10; -- Highlighted results SELECT id, fts_highlight(title, '', '', 'database') AS title, fts_highlight(body, '', '', 'database') AS body FROM articles WHERE fts_match(title, body, 'database'); -- Combine FTS with regular SQL filters SELECT id, title, author, fts_score(title, body, 'database') AS score FROM articles WHERE fts_match(title, body, 'database') AND author = 'Alice' ORDER BY score DESC; ``` *** ## Index Maintenance ### OPTIMIZE INDEX Merges all Tantivy segments into a single optimized segment. This improves query performance and reduces storage overhead, particularly after bulk inserts. ```sql theme={null} -- Optimize a specific FTS index OPTIMIZE INDEX idx_articles_fts; -- Optimize all FTS indexes in the database OPTIMIZE INDEX; ``` **When to use:** * After bulk data imports with many INSERT statements * When query performance degrades over time * During scheduled maintenance windows **What it does:** * Flushes any pending documents to disk * Merges all segments into a single segment * Removes deleted document tombstones * Invalidates internal caches for fresh reads ### DML and Index Updates FTS indexes are updated automatically when you modify the underlying table. | Operation | FTS behavior | | - | - | | `INSERT` | New rows are indexed immediately (batched commits every 1000 documents) | | `UPDATE` | Implemented as DELETE + INSERT internally | | `DELETE` | Marks documents as deleted via tombstones, cleaned up on OPTIMIZE | ```sql theme={null} -- All of these automatically update the FTS index INSERT INTO articles VALUES (5, 'New Article', 'Content here.', 'Dave'); UPDATE articles SET body = 'Updated content.' WHERE id = 5; DELETE FROM articles WHERE id = 5; ``` *** ## Comparison with SQLite FTS5 | Feature | SQLite FTS5 | Turso FTS | | - | - | - | | Filtering | `WHERE t MATCH 'query'` | `WHERE fts_match(cols, 'query')` | | Ranking | `bm25(t)`, `rank` column | `fts_score(cols, 'query')` | | Highlighting | `highlight(t, col_idx, open, close)` | `fts_highlight(cols, open, close, query)` | | Snippets | `snippet(t, ...)` | Not supported | | Boolean operators | AND, OR, NOT | AND, OR, NOT | | Phrase search | `"exact phrase"` | `"exact phrase"` | | Prefix search | `word*` | `word*` | | Column filter | `col:term` | `col:term` | | Tokenizers | unicode61, ascii, porter | default, raw, simple, whitespace, ngram | | Segment management | Automatic | Manual via `OPTIMIZE INDEX` | | Transaction visibility | Immediate | After COMMIT | *** ## Current Limitations * **No snippet function**: Use `fts_highlight` for term emphasis; context snippets are not yet available. * **No automatic segment merging**: Use `OPTIMIZE INDEX` periodically after bulk writes. * **No read-your-writes in a transaction**: FTS changes within a transaction are not visible to queries until the transaction is committed. ROLLBACK correctly discards both table and FTS changes. * **No MATCH operator syntax**: Use `fts_match()` function calls instead of `WHERE table MATCH 'query'`. ## See Also * [CREATE INDEX](/sql-reference/statements/create-index) for the full `CREATE INDEX ... USING fts` syntax * [Vector Functions](/sql-reference/functions/vector) for similarity search with embeddings # JSON Functions Source: https://docs.turso.tech/sql-reference/functions/json Create, extract, modify, and aggregate JSON data # JSON Functions Turso provides a full set of JSON functions compatible with SQLite's JSON1 extension. These functions operate on JSON stored as TEXT or in Turso's internal binary JSON (JSONB) format. Most functions come in pairs: a `json_*` variant that returns TEXT and a `jsonb_*` variant that returns BLOB in the internal binary format. The JSONB variants are more efficient when the result will be stored or passed to another JSON function rather than returned to the application. ## JSON Path Syntax Many JSON functions accept a **path** argument that identifies a specific element within a JSON document. | Syntax | Meaning | | - | - | | `$` | The root element | | `$.key` | Object member named `key` | | `$[N]` | Array element at index `N` (zero-based) | | `$.key1.key2` | Nested object member | | `$.key[0]` | First element of an array inside an object member | | `$[0].key` | Object member inside the first array element | Path arguments must begin with `$`. If a path does not match any element, functions generally return `NULL`. ```sql theme={null} SELECT json_extract('{"a": {"b": [10, 20, 30]}}', '$.a.b[1]'); -- 20 ``` *** ## JSON Creation and Validation ### json Validates a JSON string and returns it in minified form. If the input is not valid JSON, an error is raised. ```sql theme={null} json(json_text) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT | A JSON string to validate and minify | **Returns:** TEXT -- the minified JSON string. ```sql theme={null} SELECT json(' { "name": "Alice" , "age": 30 } '); -- {"name":"Alice","age":30} ``` ### jsonb Converts a JSON string to the internal binary JSON format. ```sql theme={null} jsonb(json_text) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT | A JSON string to convert | **Returns:** BLOB -- the value in binary JSON format. ```sql theme={null} SELECT typeof(jsonb('{"a":1}')); -- blob ``` ### json\_array / jsonb\_array Creates a JSON array from the arguments. ```sql theme={null} json_array(value1, value2, ...) jsonb_array(value1, value2, ...) ``` | Parameter | Type | Description | | - | - | - | | `value1, value2, ...` | any | Values to include in the array. SQL NULL becomes JSON `null`. | **Returns:** TEXT (json\_array) or BLOB (jsonb\_array) -- a JSON array. ```sql theme={null} SELECT json_array(1, 'hello', NULL, 3.14); -- [1,"hello",null,3.14] SELECT json_array(); -- [] ``` ### json\_object / jsonb\_object Creates a JSON object from alternating label/value pairs. When called with `*`, expands all columns of the row into label/value pairs using column names as keys. ```sql theme={null} json_object(label1, value1, label2, value2, ...) jsonb_object(label1, value1, label2, value2, ...) json_object(*) ``` | Parameter | Type | Description | | - | - | - | | `label1, label2, ...` | TEXT | Keys for the JSON object. Must be strings. | | `value1, value2, ...` | any | Corresponding values. SQL NULL becomes JSON `null`. | **Returns:** TEXT (json\_object) or BLOB (jsonb\_object) -- a JSON object. ```sql theme={null} SELECT json_object('name', 'Alice', 'age', 30); -- {"name":"Alice","age":30} SELECT json_object('items', json_array(1, 2, 3)); -- {"items":[1,2,3]} ``` ### json\_quote Converts a SQL value to its JSON representation. ```sql theme={null} json_quote(value) ``` | Parameter | Type | Description | | - | - | - | | `value` | any | A SQL value to quote as JSON | **Returns:** TEXT -- the JSON representation of the value. ```sql theme={null} SELECT json_quote('hello'); -- "hello" SELECT json_quote(42); -- 42 SELECT json_quote(NULL); -- null ``` ### json\_valid Returns 1 if the argument is well-formed JSON, or 0 otherwise. ```sql theme={null} json_valid(json_text) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT | A string to check for valid JSON | **Returns:** INTEGER -- 1 if valid JSON, 0 otherwise. ```sql theme={null} SELECT json_valid('{"name":"Alice"}'); -- 1 SELECT json_valid('not json'); -- 0 SELECT json_valid(NULL); -- 0 ``` ### json\_error\_position Returns the character position of the first syntax error in a JSON string, or 0 if the string is valid JSON. ```sql theme={null} json_error_position(json_text) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT | A string to check for JSON errors | **Returns:** INTEGER -- character position of the first error (1-based), or 0 if valid. ```sql theme={null} SELECT json_error_position('{"a":1}'); -- 0 SELECT json_error_position('{"a":}'); -- 6 ``` *** ## JSON Extraction ### json\_extract / jsonb\_extract Extracts one or more values from a JSON document using path arguments. ```sql theme={null} json_extract(json_text, path) json_extract(json_text, path1, path2, ...) jsonb_extract(json_text, path) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | One or more JSON path expressions | **Returns:** With a single path, returns the extracted value using its natural SQL type (INTEGER, REAL, TEXT, or NULL). JSON objects and arrays are returned as TEXT. With multiple paths, returns a JSON array of the extracted values. `jsonb_extract` returns BLOB. ```sql theme={null} SELECT json_extract('{"name":"Alice","age":30}', '$.name'); -- Alice SELECT json_extract('{"name":"Alice","age":30}', '$.age'); -- 30 -- Multiple paths return a JSON array SELECT json_extract('{"a":1,"b":2,"c":3}', '$.a', '$.c'); -- [1,3] ``` ### -> operator Extracts a value from JSON and returns it as JSON. This is a shorthand for `json_extract` that always returns JSON text (objects and arrays remain as JSON, strings are JSON-quoted). ```sql theme={null} json_text -> path ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | A JSON path expression | **Returns:** TEXT -- the extracted value as JSON. ```sql theme={null} SELECT '{"name":"Alice"}' -> '$.name'; -- "Alice" SELECT '{"items":[1,2,3]}' -> '$.items'; -- [1,2,3] ``` ### ->> operator Extracts a value from JSON and returns it as a SQL value. Strings are unquoted, numbers are returned as INTEGER or REAL, and booleans are returned as integers (0 or 1). ```sql theme={null} json_text ->> path ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | A JSON path expression | **Returns:** The extracted value as its natural SQL type (TEXT, INTEGER, REAL, or NULL). ```sql theme={null} SELECT '{"name":"Alice"}' ->> '$.name'; -- Alice SELECT '{"count":42}' ->> '$.count'; -- 42 ``` ### json\_type Returns the type of a JSON value as a string: `"null"`, `"true"`, `"false"`, `"integer"`, `"real"`, `"text"`, `"array"`, or `"object"`. ```sql theme={null} json_type(json_text) json_type(json_text, path) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | Optional. A JSON path to inspect. If omitted, inspects the root. | **Returns:** TEXT -- the JSON type name. ```sql theme={null} SELECT json_type('{"a":1}'); -- object SELECT json_type('[1, 2, 3]'); -- array SELECT json_type('{"a": 1}', '$.a'); -- integer SELECT json_type('{"a": "hello"}', '$.a'); -- text ``` *** ## JSON Modification ### json\_insert / jsonb\_insert Inserts new values into a JSON document. Existing values are **not** overwritten. If the path already exists, the value is left unchanged. ```sql theme={null} json_insert(json_text, path1, value1, path2, value2, ...) jsonb_insert(json_text, path1, value1, path2, value2, ...) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | A JSON path where the value should be inserted | | `value` | any | The value to insert | **Returns:** TEXT (json\_insert) or BLOB (jsonb\_insert) -- the modified JSON. ```sql theme={null} SELECT json_insert('{"a":1}', '$.b', 2); -- {"a":1,"b":2} -- Existing values are NOT overwritten SELECT json_insert('{"a":1}', '$.a', 99); -- {"a":1} ``` ### json\_replace / jsonb\_replace Replaces existing values in a JSON document. If the path does not exist, no insertion is made. ```sql theme={null} json_replace(json_text, path1, value1, path2, value2, ...) jsonb_replace(json_text, path1, value1, path2, value2, ...) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | A JSON path identifying the value to replace | | `value` | any | The replacement value | **Returns:** TEXT (json\_replace) or BLOB (jsonb\_replace) -- the modified JSON. ```sql theme={null} SELECT json_replace('{"a":1,"b":2}', '$.a', 99); -- {"a":99,"b":2} -- Non-existent paths are ignored SELECT json_replace('{"a":1}', '$.b', 2); -- {"a":1} ``` ### json\_set / jsonb\_set Inserts or replaces values in a JSON document. Combines the behavior of `json_insert` and `json_replace`: if the path exists, the value is replaced; if it does not exist, the value is inserted. ```sql theme={null} json_set(json_text, path1, value1, path2, value2, ...) jsonb_set(json_text, path1, value1, path2, value2, ...) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | A JSON path for the value | | `value` | any | The value to set | **Returns:** TEXT (json\_set) or BLOB (jsonb\_set) -- the modified JSON. ```sql theme={null} -- Replace existing SELECT json_set('{"a":1}', '$.a', 99); -- {"a":99} -- Insert new SELECT json_set('{"a":1}', '$.b', 2); -- {"a":1,"b":2} ``` ### json\_remove / jsonb\_remove Removes one or more elements from a JSON document. ```sql theme={null} json_remove(json_text, path1, path2, ...) jsonb_remove(json_text, path1, path2, ...) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | One or more JSON paths to remove | **Returns:** TEXT (json\_remove) or BLOB (jsonb\_remove) -- the modified JSON. ```sql theme={null} SELECT json_remove('{"a":1,"b":2,"c":3}', '$.b'); -- {"a":1,"c":3} SELECT json_remove('[1,2,3,4]', '$[1]'); -- [1,3,4] ``` ### json\_patch / jsonb\_patch Applies an RFC 7396 merge patch to a JSON document. Object members in the patch overwrite members in the target. A `null` value in the patch removes the corresponding member. ```sql theme={null} json_patch(json_text, patch) jsonb_patch(json_text, patch) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | The target JSON document | | `patch` | TEXT or BLOB | The merge patch to apply | **Returns:** TEXT (json\_patch) or BLOB (jsonb\_patch) -- the patched JSON. ```sql theme={null} SELECT json_patch('{"a":1,"b":2}', '{"b":3,"c":4}'); -- {"a":1,"b":3,"c":4} -- null in the patch removes a key SELECT json_patch('{"a":1,"b":2}', '{"b":null}'); -- {"a":1} ``` ### json\_pretty Returns a pretty-printed (indented) representation of a JSON document. ```sql theme={null} json_pretty(json_text) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | **Returns:** TEXT -- the formatted JSON string with indentation. ```sql theme={null} SELECT json_pretty('{"name":"Alice","scores":[90,85,92]}'); /* { "name": "Alice", "scores": [ 90, 85, 92 ] } */ ``` *** ## JSON Array Functions ### json\_array\_length Returns the number of elements in a JSON array. Returns 0 for an empty array and NULL for non-array JSON values. ```sql theme={null} json_array_length(json_text) json_array_length(json_text, path) ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | Optional. A JSON path to an array within the document. | **Returns:** INTEGER -- the number of elements, or NULL if the value at the path is not an array. ```sql theme={null} SELECT json_array_length('[1, 2, 3, 4]'); -- 4 SELECT json_array_length('{"items": [10, 20]}', '$.items'); -- 2 SELECT json_array_length('{"a": 1}'); -- NULL ``` *** ## JSON Aggregate Functions ### json\_group\_array / jsonb\_group\_array Aggregate function that collects values from a group into a JSON array. ```sql theme={null} json_group_array(value) jsonb_group_array(value) ``` | Parameter | Type | Description | | - | - | - | | `value` | any | The value to aggregate from each row | **Returns:** TEXT (json\_group\_array) or BLOB (jsonb\_group\_array) -- a JSON array of all values in the group. ```sql theme={null} CREATE TABLE items (category TEXT, name TEXT); INSERT INTO items VALUES ('fruit', 'apple'), ('fruit', 'banana'), ('veggie', 'carrot'); SELECT category, json_group_array(name) FROM items GROUP BY category; -- fruit | ["apple","banana"] -- veggie | ["carrot"] ``` ### json\_group\_object / jsonb\_group\_object Aggregate function that collects label/value pairs from a group into a JSON object. ```sql theme={null} json_group_object(label, value) jsonb_group_object(label, value) ``` | Parameter | Type | Description | | - | - | - | | `label` | TEXT | The key for each entry | | `value` | any | The value for each entry | **Returns:** TEXT (json\_group\_object) or BLOB (jsonb\_group\_object) -- a JSON object. ```sql theme={null} CREATE TABLE settings (key TEXT, value TEXT); INSERT INTO settings VALUES ('theme', 'dark'), ('lang', 'en'); SELECT json_group_object(key, value) FROM settings; -- {"theme":"dark","lang":"en"} ``` *** ## JSON Table-Valued Functions ### json\_each A table-valued function that walks the top-level elements of a JSON array or object, returning one row per element. ```sql theme={null} SELECT * FROM json_each(json_text); SELECT * FROM json_each(json_text, path); ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | Optional. A JSON path to the array or object to iterate. Defaults to `$`. | **Output columns:** | Column | Type | Description | | - | - | - | | `key` | TEXT or INTEGER | Object key (TEXT) or array index (INTEGER) | | `value` | any | The value of the element, as JSON text for objects/arrays or as a SQL value for primitives | | `type` | TEXT | JSON type: `null`, `true`, `false`, `integer`, `real`, `text`, `array`, or `object` | | `atom` | any | The SQL value for primitives (NULL for arrays and objects) | | `id` | INTEGER | A sequential identifier for the element | | `parent` | INTEGER | The id of the parent element (NULL for top-level) | | `fullkey` | TEXT | The full JSON path to this element | | `path` | TEXT | The JSON path to the parent of this element | ```sql theme={null} SELECT key, value, type FROM json_each('[10, "hello", null]'); -- 0 | 10 | integer -- 1 | hello | text -- 2 | null | null SELECT key, value FROM json_each('{"a":1, "b":2}'); -- a | 1 -- b | 2 -- With a path SELECT key, value FROM json_each('{"data": [1, 2, 3]}', '$.data'); -- 0 | 1 -- 1 | 2 -- 2 | 3 ``` ### json\_tree > [!WARNING] > > `json_tree` has partial support. Some advanced traversal features may not work as expected. A table-valued function that recursively walks a JSON document, returning one row for every element at every level of nesting. ```sql theme={null} SELECT * FROM json_tree(json_text); SELECT * FROM json_tree(json_text, path); ``` | Parameter | Type | Description | | - | - | - | | `json_text` | TEXT or BLOB | A JSON document | | `path` | TEXT | Optional. A JSON path to the subtree to walk. Defaults to `$`. | **Output columns:** Same as `json_each`. ```sql theme={null} SELECT key, value, type, path FROM json_tree('{"a": [1, 2]}'); -- NULL | {"a":[1,2]} | object | $ -- a | [1,2] | array | $ -- 0 | 1 | integer | $.a -- 1 | 2 | integer | $.a ``` *** ## Practical Examples ### Storing and querying JSON data ```sql theme={null} CREATE TABLE events (id INTEGER PRIMARY KEY, data TEXT); INSERT INTO events VALUES (1, '{"type":"click","x":100,"y":200}'); INSERT INTO events VALUES (2, '{"type":"scroll","offset":500}'); -- Extract specific fields SELECT id, data ->> '$.type' AS event_type FROM events; -- 1 | click -- 2 | scroll -- Filter by JSON value SELECT * FROM events WHERE data ->> '$.type' = 'click'; ``` ### Modifying JSON in place ```sql theme={null} UPDATE events SET data = json_set(data, '$.timestamp', '2025-01-15T10:30:00Z') WHERE id = 1; SELECT data FROM events WHERE id = 1; -- {"type":"click","x":100,"y":200,"timestamp":"2025-01-15T10:30:00Z"} ``` ### Building JSON from relational data ```sql theme={null} CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT); INSERT INTO users VALUES (1, 'Alice', 'alice@example.com'); INSERT INTO users VALUES (2, 'Bob', 'bob@example.com'); SELECT json_object('users', json_group_array( json_object('id', id, 'name', name, 'email', email) )) FROM users; -- {"users":[{"id":1,"name":"Alice","email":"alice@example.com"},{"id":2,"name":"Bob","email":"bob@example.com"}]} ``` ### Flattening JSON arrays with json\_each ```sql theme={null} CREATE TABLE orders (id INTEGER PRIMARY KEY, items TEXT); INSERT INTO orders VALUES (1, '["widget","gadget","gizmo"]'); INSERT INTO orders VALUES (2, '["sprocket"]'); -- Expand each order's items into individual rows SELECT orders.id, each.value AS item FROM orders, json_each(orders.items) AS each; -- 1 | widget -- 1 | gadget -- 1 | gizmo -- 2 | sprocket ``` ## See Also * [Data Types](/sql-reference/data-types) for how JSON values map to SQL types * [Expressions](/sql-reference/expressions) for using JSON operators in expressions # Math Functions Source: https://docs.turso.tech/sql-reference/functions/math Mathematical functions for trigonometry, logarithms, rounding, and arithmetic # Math Functions Turso provides all SQLite math functions. These functions operate on numeric values and return REAL unless otherwise noted. If any argument is NULL, the function returns NULL. If any argument is non-numeric and cannot be converted, the function returns NULL or 0.0 depending on the function. ## Function Reference ### Trigonometric Functions | Function | Description | Return Type | | - | - | - | | `acos(X)` | Inverse cosine of X in radians. X must be between -1.0 and 1.0 | REAL | | `asin(X)` | Inverse sine of X in radians. X must be between -1.0 and 1.0 | REAL | | `atan(X)` | Inverse tangent of X in radians | REAL | | `atan2(Y, X)` | Inverse tangent of Y/X in radians, using the signs of both arguments to determine the quadrant | REAL | | `cos(X)` | Cosine of X, where X is in radians | REAL | | `sin(X)` | Sine of X, where X is in radians | REAL | | `tan(X)` | Tangent of X, where X is in radians | REAL | ### Hyperbolic Functions | Function | Description | Return Type | | - | - | - | | `acosh(X)` | Inverse hyperbolic cosine of X. X must be >= 1.0 | REAL | | `asinh(X)` | Inverse hyperbolic sine of X | REAL | | `atanh(X)` | Inverse hyperbolic tangent of X. X must be between -1.0 and 1.0 | REAL | | `cosh(X)` | Hyperbolic cosine of X | REAL | | `sinh(X)` | Hyperbolic sine of X | REAL | | `tanh(X)` | Hyperbolic tangent of X | REAL | ### Angle Conversion | Function | Description | Return Type | | - | - | - | | `degrees(X)` | Convert X from radians to degrees | REAL | | `radians(X)` | Convert X from degrees to radians | REAL | ### Rounding and Truncation | Function | Description | Return Type | | - | - | - | | `ceil(X)` | Smallest integer not less than X | INTEGER | | `ceiling(X)` | Alias for `ceil(X)` | INTEGER | | `floor(X)` | Largest integer not greater than X | INTEGER | | `trunc(X)` | Integer part of X (truncates toward zero) | INTEGER | ### Logarithmic and Exponential Functions | Function | Description | Return Type | | - | - | - | | `exp(X)` | e raised to the power X | REAL | | `ln(X)` | Natural logarithm of X. X must be > 0 | REAL | | `log(X)` | Natural logarithm of X (same as `ln(X)`) | REAL | | `log(B, X)` | Logarithm of X with base B. Both must be > 0, B != 1 | REAL | | `log10(X)` | Base-10 logarithm of X. X must be > 0 | REAL | | `log2(X)` | Base-2 logarithm of X. X must be > 0 | REAL | > [!NOTE] > > The single-argument `log(X)` returns the natural logarithm (base e), matching SQLite behavior. This differs from some other databases where `log(X)` returns the base-10 logarithm. ### Arithmetic Functions | Function | Description | Return Type | | - | - | - | | `mod(X, Y)` | Remainder of X divided by Y | REAL | | `pow(X, Y)` | X raised to the power Y | REAL | | `power(X, Y)` | Alias for `pow(X, Y)` | REAL | | `sqrt(X)` | Square root of X. X must be >= 0 | REAL | | `sign(X)` | Returns -1, 0, or +1 for negative, zero, or positive X | INTEGER | | `gcd(A, B)` | Greatest common divisor of two integers | INTEGER | | `lcm(A, B)` | Least common multiple of two integers | INTEGER | ### Constants | Function | Description | Return Type | | - | - | - | | `pi()` | Returns the value of pi (3.141592653589793) | REAL | ## Signatures ### acos() ```sql theme={null} acos(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value between -1.0 and 1.0 | **Returns:** REAL. The angle in radians whose cosine is X. Returns NULL if X is outside \[-1.0, 1.0]. *** ### acosh() ```sql theme={null} acosh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value >= 1.0 | **Returns:** REAL. The inverse hyperbolic cosine of X. Returns NULL if X \< 1.0. *** ### asin() ```sql theme={null} asin(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value between -1.0 and 1.0 | **Returns:** REAL. The angle in radians whose sine is X. Returns NULL if X is outside \[-1.0, 1.0]. *** ### asinh() ```sql theme={null} asinh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** REAL. The inverse hyperbolic sine of X. *** ### atan() ```sql theme={null} atan(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** REAL. The angle in radians whose tangent is X. Result is in the range (-pi/2, pi/2). *** ### atan2() ```sql theme={null} atan2(Y, X) ``` | Parameter | Type | Description | | - | - | - | | `Y` | REAL or INTEGER | The y-coordinate | | `X` | REAL or INTEGER | The x-coordinate | **Returns:** REAL. The angle in radians between the positive x-axis and the point (X, Y). Result is in the range (-pi, pi]. Unlike `atan(Y/X)`, `atan2` correctly handles all quadrants and the case where X is zero. *** ### atanh() ```sql theme={null} atanh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value between -1.0 and 1.0 (exclusive) | **Returns:** REAL. The inverse hyperbolic tangent of X. Returns NULL if X is outside (-1.0, 1.0). *** ### ceil() / ceiling() ```sql theme={null} ceil(X) ceiling(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** INTEGER. The smallest integer not less than X. ```sql theme={null} SELECT ceil(3.2); -- 4 SELECT ceil(-3.2); -- -3 SELECT ceiling(5.0); -- 5 ``` *** ### cos() ```sql theme={null} cos(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Angle in radians | **Returns:** REAL. The cosine of X. *** ### cosh() ```sql theme={null} cosh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** REAL. The hyperbolic cosine of X. *** ### degrees() ```sql theme={null} degrees(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Angle in radians | **Returns:** REAL. X converted from radians to degrees. ```sql theme={null} SELECT degrees(pi()); -- 180.0 SELECT degrees(pi()/2); -- 90.0 ``` *** ### exp() ```sql theme={null} exp(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Exponent | **Returns:** REAL. The value of e raised to the power X. ```sql theme={null} SELECT exp(0); -- 1.0 SELECT exp(1); -- 2.718281828459045 ``` *** ### gcd() ```sql theme={null} gcd(A, B) ``` | Parameter | Type | Description | | - | - | - | | `A` | INTEGER | First operand | | `B` | INTEGER | Second operand | **Returns:** INTEGER. The greatest common divisor of A and B. The result is always non-negative regardless of the signs of the inputs. `gcd(0, 0)` returns `0`. Returns NULL if either argument is NULL. Raises an integer-overflow error when the result is not representable as a signed 64-bit integer (the only such case involves `-9223372036854775808`). ```sql theme={null} SELECT gcd(12, 8); -- 4 SELECT gcd(-12, 8); -- 4 SELECT gcd(0, 7); -- 7 SELECT gcd(0, 0); -- 0 SELECT gcd(NULL, 7); -- NULL ``` > [!NOTE] > > `gcd` and `lcm` are PostgreSQL/MySQL/Oracle-compatible additions that SQLite does not ship. They operate on signed 64-bit integers; non-integer text inputs that cannot be parsed return NULL. *** ### lcm() ```sql theme={null} lcm(A, B) ``` | Parameter | Type | Description | | - | - | - | | `A` | INTEGER | First operand | | `B` | INTEGER | Second operand | **Returns:** INTEGER. The least common multiple of A and B. The result is always non-negative regardless of the signs of the inputs. `lcm(_, 0)` and `lcm(0, _)` return `0`. Returns NULL if either argument is NULL. Raises an integer-overflow error when the multiplication does not fit in a signed 64-bit integer. ```sql theme={null} SELECT lcm(4, 6); -- 12 SELECT lcm(-4, 6); -- 12 SELECT lcm(5, 0); -- 0 SELECT lcm(NULL, 7); -- NULL ``` *** ### floor() ```sql theme={null} floor(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** INTEGER. The largest integer not greater than X. ```sql theme={null} SELECT floor(3.7); -- 3 SELECT floor(-3.2); -- -4 SELECT floor(5.0); -- 5 ``` *** ### ln() ```sql theme={null} ln(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value > 0 | **Returns:** REAL. The natural logarithm (base e) of X. Returns NULL if X \<= 0. ```sql theme={null} SELECT ln(1); -- 0.0 SELECT ln(exp(1)); -- 1.0 SELECT ln(2); -- 0.6931471805599453 ``` *** ### log() ```sql theme={null} log(X) log(B, X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value > 0 | | `B` | REAL or INTEGER | Base > 0 and != 1 (optional) | **Returns:** REAL. With one argument, returns the natural logarithm of X (same as `ln(X)`). With two arguments, returns the logarithm of X in base B. ```sql theme={null} SELECT log(10); -- 2.302585... (natural log) SELECT log(10, 100); -- 2.0 (log base 10 of 100) SELECT log(2, 256); -- 8.0 (log base 2 of 256) ``` *** ### log10() ```sql theme={null} log10(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value > 0 | **Returns:** REAL. The base-10 logarithm of X. Returns NULL if X \<= 0. ```sql theme={null} SELECT log10(100); -- 2.0 SELECT log10(1000); -- 3.0 ``` *** ### log2() ```sql theme={null} log2(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value > 0 | **Returns:** REAL. The base-2 logarithm of X. Returns NULL if X \<= 0. ```sql theme={null} SELECT log2(256); -- 8.0 SELECT log2(1024); -- 10.0 ``` *** ### mod() ```sql theme={null} mod(X, Y) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Dividend | | `Y` | REAL or INTEGER | Divisor | **Returns:** REAL. The remainder when X is divided by Y. Returns NULL if Y is 0. ```sql theme={null} SELECT mod(10, 3); -- 1.0 SELECT mod(10.5, 3); -- 1.5 SELECT mod(-10, 3); -- -1.0 ``` *** ### pi() ```sql theme={null} pi() ``` Takes no parameters. **Returns:** REAL. The mathematical constant pi (3.141592653589793). ```sql theme={null} SELECT pi(); -- 3.141592653589793 SELECT 2 * pi() * 5; -- 31.41592653589793 (circumference of circle with radius 5) ``` *** ### pow() / power() ```sql theme={null} pow(X, Y) power(X, Y) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Base | | `Y` | REAL or INTEGER | Exponent | **Returns:** REAL. X raised to the power Y. ```sql theme={null} SELECT pow(2, 10); -- 1024.0 SELECT power(3, 3); -- 27.0 SELECT pow(100, 0.5); -- 10.0 (square root) ``` *** ### radians() ```sql theme={null} radians(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Angle in degrees | **Returns:** REAL. X converted from degrees to radians. ```sql theme={null} SELECT radians(180); -- 3.141592653589793 SELECT radians(90); -- 1.5707963267948966 ``` *** ### sign() ```sql theme={null} sign(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** INTEGER. Returns -1 if X is negative, 0 if X is zero, and +1 if X is positive. Returns NULL if X is NULL. ```sql theme={null} SELECT sign(-42); -- -1 SELECT sign(0); -- 0 SELECT sign(3.14); -- 1 ``` *** ### sin() ```sql theme={null} sin(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Angle in radians | **Returns:** REAL. The sine of X. *** ### sinh() ```sql theme={null} sinh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** REAL. The hyperbolic sine of X. *** ### sqrt() ```sql theme={null} sqrt(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Value >= 0 | **Returns:** REAL. The square root of X. Returns NULL if X is negative. ```sql theme={null} SELECT sqrt(144); -- 12.0 SELECT sqrt(2); -- 1.4142135623730951 ``` *** ### tan() ```sql theme={null} tan(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Angle in radians | **Returns:** REAL. The tangent of X. *** ### tanh() ```sql theme={null} tanh(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** REAL. The hyperbolic tangent of X. Result is always in the range (-1.0, 1.0). *** ### trunc() ```sql theme={null} trunc(X) ``` | Parameter | Type | Description | | - | - | - | | `X` | REAL or INTEGER | Any numeric value | **Returns:** INTEGER. The integer part of X, removing any fractional digits. Truncates toward zero. ```sql theme={null} SELECT trunc(3.7); -- 3 SELECT trunc(-3.7); -- -3 SELECT trunc(5.0); -- 5 ``` ## Examples ### Trigonometry ```sql theme={null} -- Sine and cosine of 45 degrees SELECT sin(radians(45)); -- 0.7071067811865475 SELECT cos(radians(45)); -- 0.7071067811865476 -- Angle from coordinates SELECT degrees(atan2(1, 1)); -- 45.0 -- Verify the Pythagorean identity: sin^2 + cos^2 = 1 SELECT pow(sin(1.0), 2) + pow(cos(1.0), 2); -- 1.0 ``` ### Rounding ```sql theme={null} -- Different rounding behaviors SELECT ceil(2.3), floor(2.3), trunc(2.3); -- 3, 2, 2 SELECT ceil(-2.3), floor(-2.3), trunc(-2.3); -- -2, -3, -2 ``` ### Logarithms ```sql theme={null} -- Compute the number of bits needed to represent N SELECT ceil(log2(1000)); -- 10 -- Decibel conversion SELECT 10 * log10(100); -- 20.0 -- Verify log identities SELECT ln(exp(5)); -- 5.0 ``` ### Distance Calculation ```sql theme={null} -- Euclidean distance between two points SELECT sqrt(pow(3 - 0, 2) + pow(4 - 0, 2)); -- 5.0 (3-4-5 triangle) ``` ## See Also * [Expressions](/sql-reference/expressions) for arithmetic operators * [Aggregate Functions](/sql-reference/functions/aggregate) for SUM, AVG, MIN, MAX * [Data Types](/sql-reference/data-types) for REAL and INTEGER storage classes # Scalar Functions Source: https://docs.turso.tech/sql-reference/functions/scalar Built-in scalar functions for string manipulation, math, type inspection, and more # Scalar Functions Scalar functions accept one or more arguments and return a single value. They can be used anywhere an expression is valid: SELECT columns, WHERE conditions, ORDER BY, GROUP BY, HAVING, CHECK constraints, and DEFAULT values. ## Function Reference ### Math Functions | Function | Description | | - | - | | `abs(X)` | Absolute value of X. Returns INTEGER if X is INTEGER, REAL if X is REAL, NULL if X is NULL | | `max(X, Y, ...)` | Returns the argument with the maximum value | | `min(X, Y, ...)` | Returns the argument with the minimum value | | `random()` | Returns a random 64-bit signed integer | | `round(X)` | Rounds X to the nearest integer | | `round(X, Y)` | Rounds X to Y decimal places | | `sign(X)` | Returns -1, 0, or 1 for negative, zero, or positive X | ### String Functions | Function | Description | | - | - | | `char(X1, X2, ..., XN)` | Returns a string composed of characters with Unicode code points X1 through XN | | `chr(X)` | Alias for `char(X)`. PostgreSQL/SQL-standard spelling | | `concat(X, ...)` | Concatenates all arguments as strings. NULL arguments are skipped | | `concat_ws(SEP, X, ...)` | Concatenates arguments with separator SEP. NULL arguments are skipped | | `format(FORMAT, ...)` | Returns a formatted string using printf-style format specifiers | | `hex(X)` | Returns the uppercase hexadecimal representation of X | | `instr(X, Y)` | Returns the 1-based position of the first occurrence of Y in X, or 0 if not found | | `strpos(X, Y)` | Alias for `instr(X, Y)`. PostgreSQL spelling, identical argument order | | `length(X)` | Returns the string length in characters, or blob length in bytes | | `char_length(X)` | Alias for `length(X)`. SQL-standard short form | | `character_length(X)` | Alias for `length(X)`. SQL-standard long form | | `lower(X)` | Returns a lowercase copy of string X | | `lpad(X, N)` / `lpad(X, N, F)` | Pads X on the left to length N using F (defaults to a single space) | | `ltrim(X)` | Removes leading whitespace from X | | `ltrim(X, Y)` | Removes leading characters found in Y from X | | `octet_length(X)` | Returns the length of X in bytes | | `printf(FORMAT, ...)` | Alias for `format()`. Returns a formatted string | | `quote(X)` | Returns the SQL literal representation of X | | `repeat(X, N)` | Returns X concatenated N times. `N <= 0` produces an empty string | | `replace(X, Y, Z)` | Returns X with every occurrence of Y replaced by Z | | `reverse(X)` | Returns the characters of string X in reverse order. Alias for `string_reverse(X)` | | `rpad(X, N)` / `rpad(X, N, F)` | Pads X on the right to length N using F (defaults to a single space) | | `rtrim(X)` | Removes trailing whitespace from X | | `rtrim(X, Y)` | Removes trailing characters found in Y from X | | `soundex(X)` | Returns the Soundex encoding of string X | | `string_reverse(X)` | Returns the characters of string X in reverse order | | `substr(X, Y)` | Returns the substring of X starting at position Y (1-based) | | `substr(X, Y, Z)` | Returns Z characters from X starting at position Y | | `substring(X, Y)` | Alias for `substr(X, Y)` | | `substring(X, Y, Z)` | Alias for `substr(X, Y, Z)` | | `trim(X)` | Removes leading and trailing whitespace from X | | `trim(X, Y)` | Removes leading and trailing characters found in Y from X | | `btrim(X)` / `btrim(X, Y)` | Alias for `trim(X)` / `trim(X, Y)`. PostgreSQL/Oracle spelling | | `unhex(X)` | Converts hexadecimal string X to a blob. Returns NULL if X contains non-hex characters | | `unhex(X, Y)` | Like `unhex(X)`, but characters in Y are silently ignored in X | | `unicode(X)` | Returns the Unicode code point of the first character of string X | | `unistr(X)` | Evaluates PostgreSQL-style Unicode escapes in X (`\XXXX`, `\+XXXXXX`, `\uXXXX`, `\UXXXXXXXX`, `\\`) | | `unistr_quote(X)` | Returns X as a quoted string literal with non-ASCII characters written as `unistr` escapes | | `upper(X)` | Returns an uppercase copy of string X | | `zeroblob(N)` | Returns a blob consisting of N zero bytes | ### Conditional Functions | Function | Description | | - | - | | `coalesce(X, Y, ...)` | Returns the first non-NULL argument. Returns NULL if all arguments are NULL | | `ifnull(X, Y)` | Returns X if X is not NULL, otherwise returns Y. Equivalent to `coalesce(X, Y)` | | `iif(X, Y, Z)` | Returns Y if X is true (non-zero), otherwise returns Z | | `if(X, Y, Z)` | Alias for `iif(X, Y, Z)` | | `nullif(X, Y)` | Returns NULL if X equals Y, otherwise returns X | ### Type Functions | Function | Description | | - | - | | `typeof(X)` | Returns the type of X as a string: `"null"`, `"integer"`, `"real"`, `"text"`, or `"blob"` | ### Pattern Matching Functions | Function | Description | | - | - | | `glob(X, Y)` | Returns 1 if string Y matches glob pattern X, 0 otherwise. Case-sensitive | | `like(X, Y)` | Returns 1 if string Y matches LIKE pattern X, 0 otherwise. Case-insensitive for ASCII | | `like(X, Y, Z)` | Like `like(X, Y)` but uses Z as the escape character | ### Optimizer Hints | Function | Description | | - | - | | `likelihood(X, Y)` | Returns X unchanged. Hints to the query planner that X is true with probability Y (0.0 to 1.0) | | `likely(X)` | Returns X unchanged. Equivalent to `likelihood(X, 0.9375)` | | `unlikely(X)` | Returns X unchanged. Equivalent to `likelihood(X, 0.0625)` | ### Blob Functions | Function | Description | | - | - | | `hex(X)` | Returns the uppercase hexadecimal representation of blob or integer X | | `randomblob(N)` | Returns a blob of N bytes filled with pseudo-random data | | `unhex(X)` | Converts hexadecimal string X to a blob | | `zeroblob(N)` | Returns a blob of N zero bytes | ### System Functions | Function | Description | | - | - | | `last_insert_rowid()` | Returns the rowid of the most recent successful INSERT on the same database connection | | `changes()` | Returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE | | `total_changes()` | Returns the total number of rows modified since the database connection was opened | | `sqlite_version()` | Returns the version string `"3.42.0"` | | `sqlite_source_id()` | Returns the source identifier string for the SQLite-compatible engine | | `turso_version()` | Returns the Turso engine version string (e.g. `"0.7.0"`) | | `load_extension(X)` | Loads a Turso-native extension from the shared library at path X | ## Detailed Descriptions and Examples ### abs(X) Returns the absolute value of X. The return type matches the input: INTEGER for integer inputs, REAL for floating-point inputs. Returns NULL if X is NULL. Returns INTEGER for a text value that looks like an integer. ```sql theme={null} SELECT abs(-42); -- 42 SELECT abs(3.14); -- 3.14 SELECT abs(0); -- 0 SELECT abs(NULL); -- NULL ``` ### char(X1, X2, ..., XN) and chr(X) `char` returns a string composed of characters having the Unicode code points X1 through XN. Arguments that are not valid code points are replaced with the Unicode replacement character (U+FFFD). `chr` is a single-argument alias provided for PostgreSQL and SQL-standard compatibility. It is identical to `char(X)`. ```sql theme={null} SELECT char(72, 101, 108, 108, 111); -- 'Hello' SELECT char(9731); -- snowman character SELECT hex(char(0)); -- '00' SELECT chr(65); -- 'A' ``` ### coalesce(X, Y, ...) Returns the first argument that is not NULL. If all arguments are NULL, returns NULL. Requires at least two arguments. ```sql theme={null} SELECT coalesce(NULL, NULL, 'fallback'); -- 'fallback' SELECT coalesce(1, 2, 3); -- 1 SELECT coalesce(NULL, 42); -- 42 ``` ### concat(X, ...) and concat\_ws(SEP, X, ...) `concat` joins all arguments as strings, skipping NULLs. `concat_ws` inserts the separator between non-NULL arguments. ```sql theme={null} SELECT concat('Hello', ' ', 'World'); -- 'Hello World' SELECT concat('a', NULL, 'b'); -- 'ab' SELECT concat_ws(', ', 'Alice', 'Bob', NULL, 'Carol'); -- 'Alice, Bob, Carol' SELECT concat_ws('-', 2024, 1, 15); -- '2024-1-15' ``` ### format(FORMAT, ...) and printf(FORMAT, ...) Returns a formatted string using printf-style format specifiers. `printf` is an alias for `format`. | Specifier | Description | | - | - | | `%d` | Signed integer | | `%f` | Floating-point | | `%s` | String | | `%x` | Lowercase hexadecimal integer | | `%X` | Uppercase hexadecimal integer | | `%o` | Octal integer | | `%e` | Scientific notation | | `%g` | General floating-point (shortest representation) | | `%%` | Literal percent sign | ```sql theme={null} SELECT format('Hello, %s! You are #%d.', 'Alice', 1); -- 'Hello, Alice! You are #1.' SELECT printf('%.2f%%', 99.5); -- '99.50%' SELECT format('0x%08X', 255); -- '0x000000FF' ``` ### glob(X, Y) Returns 1 if string Y matches the glob pattern X, and 0 otherwise. Glob matching is case-sensitive and uses `*` for any sequence of characters, `?` for any single character, and `[...]` for character classes. ```sql theme={null} SELECT glob('*.txt', 'readme.txt'); -- 1 SELECT glob('*.TXT', 'readme.txt'); -- 0 (case-sensitive) SELECT glob('H?llo', 'Hello'); -- 1 ``` > [!NOTE] > > The `glob(X, Y)` function is the functional form of the `Y GLOB X` operator. Note the reversed argument order compared to the operator syntax. ### hex(X) and unhex(X) `hex` returns the uppercase hexadecimal representation of its argument. For text, it returns the hex encoding of the UTF-8 bytes. For blobs, it encodes each byte. For integers, it returns the hex of the value. `unhex` converts a hexadecimal string back to a blob. Returns NULL if the input contains non-hex characters, unless a second argument specifies characters to ignore. ```sql theme={null} SELECT hex('ABC'); -- '414243' SELECT hex(255); -- 'FF' SELECT hex(x'CAFE'); -- 'CAFE' SELECT unhex('48454C4C4F'); -- x'48454C4C4F' (blob for 'HELLO') SELECT unhex('48-45-4C', '-'); -- x'48454C' (ignoring dashes) SELECT unhex('ZZZZ'); -- NULL (invalid hex) ``` ### iif(X, Y, Z) and if(X, Y, Z) Returns Y if X is true (non-zero and non-NULL), otherwise returns Z. `if` is an alias for `iif`. ```sql theme={null} SELECT iif(1 > 0, 'yes', 'no'); -- 'yes' SELECT iif(NULL, 'yes', 'no'); -- 'no' SELECT if(10 > 5, 'big', 'small'); -- 'big' ``` ### instr(X, Y) and strpos(X, Y) Returns the 1-based position of the first occurrence of string Y in string X. Returns 0 if Y is not found in X. If either argument is NULL, returns NULL. `strpos` is a PostgreSQL-compatible alias with the same argument order. ```sql theme={null} SELECT instr('Hello World', 'World'); -- 7 SELECT instr('Hello World', 'xyz'); -- 0 SELECT instr('abcabc', 'bc'); -- 2 SELECT strpos('Hello World', 'World'); -- 7 ``` ### length(X), char\_length(X), character\_length(X), and octet\_length(X) `length` returns the number of characters in a text value, or the number of bytes in a blob value. For NULL, returns NULL. For numeric values, returns the length of the text representation. `char_length` and `character_length` are SQL-standard aliases for `length`, accepted for compatibility with PostgreSQL and other engines. `octet_length` always returns the length in bytes, regardless of type. ```sql theme={null} SELECT length('Hello'); -- 5 SELECT length(x'AABBCC'); -- 3 (bytes for blob) SELECT length(12345); -- 5 (text representation) SELECT char_length('café'); -- 4 (counts characters, not bytes) SELECT character_length('café'); -- 4 SELECT octet_length('Hello'); -- 5 (ASCII, 1 byte per char) SELECT octet_length('café'); -- 5 (é is 2 bytes in UTF-8) ``` ### like(X, Y) and like(X, Y, Z) Returns 1 if string Y matches LIKE pattern X, and 0 otherwise. `%` matches any sequence of characters, `_` matches any single character. Matching is case-insensitive for ASCII characters. The optional third argument Z specifies an escape character. ```sql theme={null} SELECT like('%ello%', 'Hello World'); -- 1 SELECT like('H_llo', 'Hello'); -- 1 SELECT like('10\%%', '10% discount', '\'); -- 1 (escaped %) ``` > [!NOTE] > > The `like(X, Y)` function is the functional form of the `Y LIKE X` operator. Note the reversed argument order compared to the operator syntax. ### lower(X), upper(X) `lower` returns a copy of string X with all ASCII characters converted to lowercase. `upper` converts to uppercase. ```sql theme={null} SELECT lower('Hello World'); -- 'hello world' SELECT upper('Hello World'); -- 'HELLO WORLD' ``` ### ltrim(X), rtrim(X), trim(X), btrim(X) These functions remove characters from the ends of a string. Without a second argument, they remove whitespace. With a second argument Y, they remove any characters present in the string Y. `btrim` is a PostgreSQL/Oracle-compatible alias for `trim` (both the one- and two-argument forms). ```sql theme={null} -- Whitespace trimming SELECT ltrim(' Hello'); -- 'Hello' SELECT rtrim('Hello '); -- 'Hello' SELECT trim(' Hello '); -- 'Hello' SELECT btrim(' Hello '); -- 'Hello' -- Character trimming SELECT ltrim('xxxHello', 'x'); -- 'Hello' SELECT rtrim('Helloyyy', 'y'); -- 'Hello' SELECT trim('***Hello***', '*'); -- 'Hello' SELECT trim('abcHelloabc', 'abc'); -- 'Hello' (removes any of a, b, or c) SELECT btrim('xyzHelloxyz', 'xyz'); -- 'Hello' ``` ### max(X, Y, ...) and min(X, Y, ...) The multi-argument forms of `max` and `min` return the largest or smallest argument, respectively. Arguments are compared using the standard SQLite comparison rules. If any argument is NULL, the result is NULL. ```sql theme={null} SELECT max(1, 5, 3); -- 5 SELECT min(1, 5, 3); -- 1 SELECT max('apple', 'banana'); -- 'banana' SELECT min(10, NULL, 3); -- NULL ``` > [!NOTE] > > The multi-argument `max()` and `min()` are scalar functions. When called with a single argument inside an aggregate query (e.g., `SELECT max(salary) FROM employees`), they act as [aggregate functions](/sql-reference/functions/aggregate). ### nullif(X, Y) Returns NULL if X equals Y, otherwise returns X. This is useful for converting sentinel values to NULL. ```sql theme={null} SELECT nullif(0, 0); -- NULL SELECT nullif(5, 0); -- 5 SELECT nullif('N/A', 'N/A'); -- NULL SELECT nullif('hello', ''); -- 'hello' ``` ### quote(X) Returns the text of an SQL literal that represents the value X. Strings are enclosed in single quotes with escaping. BLOBs are encoded as hex literals. NULL returns the string `'NULL'`. Numbers are returned as-is. ```sql theme={null} SELECT quote('it''s'); -- '''it''s''' SELECT quote(42); -- '42' SELECT quote(NULL); -- 'NULL' SELECT quote(x'CAFE'); -- 'X''CAFE''' ``` ### random() and randomblob(N) `random` returns a pseudo-random 64-bit signed integer. `randomblob` returns a blob of N pseudo-random bytes. ```sql theme={null} SELECT random(); -- e.g., -4520312828827489743 SELECT hex(randomblob(4)); -- e.g., 'A1B2C3D4' (4 random bytes) SELECT abs(random()) % 100; -- random number between 0 and 99 ``` ### lpad(X, N) and lpad(X, N, F) Left-pads the string X with the fill string F until the result has exactly N characters. F defaults to a single space. If X is already at least N characters long, it is truncated from the right to length N. F is cycled when it is more than one character long. Returns NULL if any argument is NULL. If F is an empty string, the input is returned unchanged. Lengths are counted in Unicode characters, not bytes, so multi-byte input is padded correctly. ```sql theme={null} SELECT lpad('abc', 6); -- ' abc' SELECT lpad('abc', 6, 'xy'); -- 'xyxabc' SELECT lpad('abcdef', 3); -- 'abc' (truncated from the right) SELECT lpad('aéc', 4); -- ' aéc' (character count, not bytes) SELECT lpad('abc', 10, ''); -- 'abc' (empty fill) SELECT lpad(NULL, 5); -- NULL ``` ### rpad(X, N) and rpad(X, N, F) Right-pads the string X with the fill string F until the result has exactly N characters. F defaults to a single space. Truncation, fill cycling, NULL propagation, and empty-fill handling are identical to `lpad` (see the section above). ```sql theme={null} SELECT rpad('abc', 6); -- 'abc ' SELECT rpad('abc', 6, 'xy'); -- 'abcxyx' SELECT rpad('abcdef', 3); -- 'abc' (truncated from the right) SELECT rpad('aéc', 4); -- 'aéc ' ``` ### repeat(X, N) Returns the string X concatenated with itself N times. Returns an empty string when `N <= 0`. Returns NULL if either argument is NULL. Non-text inputs are converted to their text representation first. ```sql theme={null} SELECT repeat('ab', 3); -- 'ababab' SELECT repeat('x', 0); -- '' SELECT repeat('x', -1); -- '' SELECT repeat('', 5); -- '' SELECT repeat(NULL, 3); -- NULL ``` ### replace(X, Y, Z) Returns a copy of string X with every occurrence of string Y replaced by string Z. If Y is empty, X is returned unchanged. ```sql theme={null} SELECT replace('Hello World', 'World', 'Turso'); -- 'Hello Turso' SELECT replace('aabbcc', 'bb', 'XX'); -- 'aaXXcc' SELECT replace('2024-01-15', '-', '/'); -- '2024/01/15' ``` ### reverse(X) and string\_reverse(X) Returns string X with its characters in reverse order. Reversal is done on Unicode characters, so multi-byte characters are preserved intact. Returns NULL if X is NULL. Non-text inputs are converted to their text representation first. `reverse` is the PostgreSQL (since version 17), MySQL, and Oracle spelling; `string_reverse` is the canonical Turso name. The two are interchangeable. ```sql theme={null} SELECT reverse('hello'); -- 'olleh' SELECT reverse(''); -- '' SELECT reverse('café'); -- 'éfac' SELECT string_reverse('abc'); -- 'cba' SELECT reverse(NULL); -- NULL ``` ### round(X) and round(X, Y) Rounds X to Y decimal places. If Y is omitted, it defaults to 0. The return type is always REAL. ```sql theme={null} SELECT round(3.7); -- 4.0 SELECT round(3.14159, 2); -- 3.14 SELECT round(2.5); -- 3.0 SELECT round(-2.5); -- -3.0 ``` ### sign(X) Returns -1 for negative values, 0 for zero, and 1 for positive values. Returns NULL if X is NULL. ```sql theme={null} SELECT sign(-42); -- -1 SELECT sign(0); -- 0 SELECT sign(3.14); -- 1 SELECT sign(NULL); -- NULL ``` ### substr(X, Y) and substr(X, Y, Z) Returns a substring of X starting at the Y-th character (1-based). If Z is provided, the substring is at most Z characters long. Negative Y counts from the end of the string. `substring` is an alias. ```sql theme={null} SELECT substr('Hello World', 7); -- 'World' SELECT substr('Hello World', 1, 5); -- 'Hello' SELECT substr('Hello World', -5); -- 'World' SELECT substring('Hello', 2, 3); -- 'ell' ``` ### typeof(X) Returns the storage class of X as a lowercase string: `"null"`, `"integer"`, `"real"`, `"text"`, or `"blob"`. ```sql theme={null} SELECT typeof(42); -- 'integer' SELECT typeof(3.14); -- 'real' SELECT typeof('hello'); -- 'text' SELECT typeof(NULL); -- 'null' SELECT typeof(x'CAFE'); -- 'blob' SELECT typeof(1 + 1.0); -- 'real' ``` ### unicode(X) Returns the Unicode code point of the first character of string X. Returns NULL if X is NULL or an empty string. ```sql theme={null} SELECT unicode('A'); -- 65 SELECT unicode('Hello'); -- 72 (code point of 'H') ``` ### soundex(X) Returns the Soundex encoding of string X as a four-character code. Soundex encodes a string based on how it sounds in English, which is useful for fuzzy name matching. ```sql theme={null} SELECT soundex('Robert'); -- 'R163' SELECT soundex('Rupert'); -- 'R163' SELECT soundex('Smith'); -- 'S530' SELECT soundex('Smythe'); -- 'S530' ``` ### zeroblob(N) Returns a blob consisting of N zero bytes (0x00). Useful for pre-allocating blob storage. ```sql theme={null} SELECT length(zeroblob(10)); -- 10 SELECT hex(zeroblob(4)); -- '00000000' ``` ### last\_insert\_rowid() Returns the rowid of the most recent successful INSERT on the current database connection. Returns 0 if no INSERT has been performed. ```sql theme={null} CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT); INSERT INTO users (name) VALUES ('Alice'); SELECT last_insert_rowid(); -- 1 INSERT INTO users (name) VALUES ('Bob'); SELECT last_insert_rowid(); -- 2 ``` ### changes() and total\_changes() `changes` returns the number of rows modified by the most recent INSERT, UPDATE, or DELETE statement. `total_changes` returns the total number of rows modified since the database connection was opened. ```sql theme={null} CREATE TABLE t (x INTEGER); INSERT INTO t VALUES (1), (2), (3); SELECT changes(); -- 3 DELETE FROM t WHERE x > 1; SELECT changes(); -- 2 SELECT total_changes(); -- 5 (3 inserted + 2 deleted) ``` ### sqlite\_version() Returns the SQLite-compatible version string. ```sql theme={null} SELECT sqlite_version(); -- '3.42.0' ``` ### turso\_version() Returns the version string of the Turso engine. ```sql theme={null} SELECT turso_version(); -- e.g. '0.7.0' ``` ### load\_extension(X) Loads a Turso-native extension from the shared library at path X. ```sql theme={null} SELECT load_extension('./my_extension'); ``` > [!NOTE] > > Extension loading must be enabled on the database connection. See the [Extensions documentation](/docs/extensions) for details on building and loading extensions. ## See Also * [Aggregate Functions](/sql-reference/functions/aggregate) for functions that operate across groups of rows * [Expressions](/sql-reference/expressions) for operator syntax, CAST, CASE, and subqueries * [Data Types](/sql-reference/data-types) for storage classes and type affinity * [SELECT](/sql-reference/statements/select) for using functions in queries # Vector Functions Source: https://docs.turso.tech/sql-reference/functions/vector Create, compare, and manipulate vector embeddings # Vector Functions > [!NOTE] > > **Turso Extension**: Vector functions are a Turso-specific feature for working with vector embeddings directly in SQL. These functions are not part of the SQLite standard. Turso provides built-in functions for storing and querying vector embeddings. Vectors are stored as BLOBs in a compact binary format and can be compared using distance functions for similarity search. *** ## Vector Creation ### vector32 Creates a 32-bit floating-point vector from a JSON array of numbers. ```sql theme={null} vector32(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of numbers, e.g. `'[1.0, 2.0, 3.0]'` | **Returns:** BLOB -- the vector in 32-bit float binary format. This is the most common vector format and offers a good balance of precision and storage efficiency. Each dimension uses 4 bytes. ```sql theme={null} SELECT vector32('[1.0, 2.0, 3.0]'); -- (BLOB: 12 bytes) -- Store embeddings in a table CREATE TABLE documents ( id INTEGER PRIMARY KEY, content TEXT, embedding BLOB ); INSERT INTO documents VALUES (1, 'Introduction to databases', vector32('[0.1, 0.3, 0.5, 0.7]')); ``` ### vector64 Creates a 64-bit floating-point vector from a JSON array of numbers. ```sql theme={null} vector64(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of numbers, e.g. `'[1.0, 2.0, 3.0]'` | **Returns:** BLOB -- the vector in 64-bit float binary format. Use this format when you need higher precision. Each dimension uses 8 bytes. ```sql theme={null} SELECT vector64('[1.0, 2.0, 3.0]'); -- (BLOB: 24 bytes) ``` ### vector Creates a vector using the default format (32-bit float). ```sql theme={null} vector(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of numbers | **Returns:** BLOB -- the vector in default (32-bit float) binary format. ```sql theme={null} SELECT vector('[1.0, 2.0, 3.0]'); ``` ### vector8 Creates an 8-bit quantized vector from a JSON array of numbers. ```sql theme={null} vector8(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of integer values (0-255) | **Returns:** BLOB -- the vector in 8-bit integer binary format. Use this format for storage-efficient approximate representations. Each dimension uses 1 byte. ```sql theme={null} SELECT vector8('[128, 64, 255, 0]'); ``` ### vector1bit Creates a 1-bit binary vector from a JSON array. ```sql theme={null} vector1bit(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of 0s and 1s | **Returns:** BLOB -- the vector in 1-bit binary format. Use this format for extremely compact representations. Eight dimensions are packed into a single byte. ```sql theme={null} SELECT vector1bit('[1, 0, 1, 1, 0, 0, 1, 0]'); ``` ### vector32\_sparse Creates a 32-bit floating-point **sparse** vector from a JSON array. Only non-zero components are stored, which is efficient for high-dimensional vectors that are mostly zero. ```sql theme={null} vector32_sparse(json_array) ``` | Parameter | Type | Description | | - | - | - | | `json_array` | TEXT | A JSON array of numbers, typically with many zeros | **Returns:** BLOB -- the vector in 32-bit float sparse binary format. ```sql theme={null} SELECT vector32_sparse('[0.0, 0.0, 1.5, 0.0, 0.0, 2.5]'); ``` > [!NOTE] > > Sparse vectors can be indexed with the experimental sparse vector index method (enable `--experimental-index-method`). See [CREATE INDEX](/sql-reference/statements/create-index). *** ## Distance Functions Distance functions compute the distance between two vectors. Smaller values indicate more similar vectors. ### vector\_distance\_cos Computes the cosine distance between two vectors, defined as `1 - cosine_similarity`. A result of 0 means the vectors are identical in direction; a result of 1 means they are orthogonal. ```sql theme={null} vector_distance_cos(v1, v2) ``` | Parameter | Type | Description | | - | - | - | | `v1` | BLOB | First vector | | `v2` | BLOB | Second vector (must have the same number of dimensions as `v1`) | **Returns:** REAL -- the cosine distance between the two vectors. ```sql theme={null} SELECT vector_distance_cos( vector32('[1.0, 0.0, 0.0]'), vector32('[0.0, 1.0, 0.0]') ); -- 1.0 (orthogonal vectors) SELECT vector_distance_cos( vector32('[1.0, 0.0]'), vector32('[1.0, 0.0]') ); -- 0.0 (identical vectors) ``` ### vector\_distance\_l2 Computes the Euclidean (L2) distance between two vectors. A result of 0 means the vectors are identical. ```sql theme={null} vector_distance_l2(v1, v2) ``` | Parameter | Type | Description | | - | - | - | | `v1` | BLOB | First vector | | `v2` | BLOB | Second vector (must have the same number of dimensions as `v1`) | **Returns:** REAL -- the Euclidean distance between the two vectors. ```sql theme={null} SELECT vector_distance_l2( vector32('[1.0, 0.0, 0.0]'), vector32('[0.0, 1.0, 0.0]') ); -- 1.4142135623730951 SELECT vector_distance_l2( vector32('[3.0, 4.0]'), vector32('[0.0, 0.0]') ); -- 5.0 ``` ### vector\_distance\_dot Computes the negative dot product distance between two vectors. The result is the negation of the dot product, so smaller (more negative) values indicate higher similarity. ```sql theme={null} vector_distance_dot(v1, v2) ``` | Parameter | Type | Description | | - | - | - | | `v1` | BLOB | First vector | | `v2` | BLOB | Second vector (must have the same number of dimensions as `v1`) | **Returns:** REAL -- the negative dot product of the two vectors. ```sql theme={null} SELECT vector_distance_dot( vector32('[1.0, 2.0, 3.0]'), vector32('[4.0, 5.0, 6.0]') ); -- -32.0 (dot product is 32) ``` ### vector\_distance\_jaccard Computes the Jaccard distance between two binary vectors. Best suited for use with `vector1bit` vectors. ```sql theme={null} vector_distance_jaccard(v1, v2) ``` | Parameter | Type | Description | | - | - | - | | `v1` | BLOB | First vector | | `v2` | BLOB | Second vector (must have the same number of dimensions as `v1`) | **Returns:** REAL -- the Jaccard distance between the two vectors. *** ## Utility Functions ### vector\_extract Converts a vector BLOB back to a JSON text representation. ```sql theme={null} vector_extract(vector_blob) ``` | Parameter | Type | Description | | - | - | - | | `vector_blob` | BLOB | A vector created by `vector32`, `vector64`, or another vector function | **Returns:** TEXT -- a JSON array string of the vector's components. ```sql theme={null} SELECT vector_extract(vector32('[1.5, 2.5, 3.5]')); -- [1.500000,2.500000,3.500000] ``` ### vector\_concat Concatenates two or more vectors into a single vector. ```sql theme={null} vector_concat(v1, v2, ...) ``` | Parameter | Type | Description | | - | - | - | | `v1, v2, ...` | BLOB | Two or more vectors to concatenate. All vectors must be the same type. | **Returns:** BLOB -- a new vector containing the dimensions of all input vectors in order. ```sql theme={null} SELECT vector_extract( vector_concat( vector32('[1.0, 2.0]'), vector32('[3.0, 4.0]') ) ); -- [1.000000,2.000000,3.000000,4.000000] ``` ### vector\_slice Extracts a contiguous portion of a vector. ```sql theme={null} vector_slice(vector_blob, start, end) ``` | Parameter | Type | Description | | - | - | - | | `vector_blob` | BLOB | The source vector | | `start` | INTEGER | Start index (zero-based, inclusive) | | `end` | INTEGER | End index (exclusive) | **Returns:** BLOB -- a new vector containing dimensions from `start` to `end - 1`. ```sql theme={null} SELECT vector_extract( vector_slice(vector32('[10.0, 20.0, 30.0, 40.0, 50.0]'), 1, 4) ); -- [20.000000,30.000000,40.000000] ``` *** ## Examples ### Creating a table with vector embeddings ```sql theme={null} CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, content TEXT, embedding BLOB ); INSERT INTO articles VALUES ( 1, 'Introduction to Machine Learning', 'Machine learning is a subset of artificial intelligence...', vector32('[0.12, -0.34, 0.56, 0.78, -0.11, 0.45, -0.23, 0.67]') ); INSERT INTO articles VALUES ( 2, 'Database Design Patterns', 'Relational databases organize data into tables...', vector32('[0.82, 0.15, -0.44, 0.23, 0.61, -0.33, 0.09, 0.77]') ); INSERT INTO articles VALUES ( 3, 'Neural Networks Explained', 'Neural networks are computing systems inspired by biological neural networks...', vector32('[0.14, -0.28, 0.62, 0.71, -0.09, 0.51, -0.18, 0.59]') ); ``` ### Similarity search with ORDER BY distance ```sql theme={null} -- Find articles most similar to a query embedding using cosine distance SELECT id, title, vector_distance_cos(embedding, vector32('[0.1, -0.3, 0.5, 0.8, -0.1, 0.4, -0.2, 0.6]')) AS distance FROM articles ORDER BY distance ASC LIMIT 5; ``` ### Cosine distance vs. L2 distance Cosine distance measures the angle between two vectors, ignoring magnitude. L2 distance measures the absolute distance in space. ```sql theme={null} -- Cosine distance: direction matters, not magnitude SELECT vector_distance_cos( vector32('[1.0, 0.0]'), vector32('[100.0, 0.0]') ); -- 0.0 (same direction, cosine distance is 0) -- L2 distance: magnitude matters SELECT vector_distance_l2( vector32('[1.0, 0.0]'), vector32('[100.0, 0.0]') ); -- 99.0 (very far apart in absolute terms) ``` Use cosine distance when you care about the direction of the vector (e.g., semantic similarity of text embeddings). Use L2 distance when absolute position matters (e.g., geospatial coordinates or image feature vectors). ### Working with vector data ```sql theme={null} -- Inspect the contents of a stored vector SELECT vector_extract(embedding) FROM articles WHERE id = 1; -- Concatenate vectors from two different models SELECT vector_extract( vector_concat( (SELECT embedding FROM articles WHERE id = 1), (SELECT embedding FROM articles WHERE id = 2) ) ); -- Extract the first 4 dimensions of an embedding SELECT vector_extract( vector_slice(embedding, 0, 4) ) FROM articles WHERE id = 1; -- [0.120000,-0.340000,0.560000,0.780000] ``` > [!NOTE] > > By default, similarity searches use a linear scan over the table. An experimental sparse vector index method is available behind the `--experimental-index-method` flag; without it, consider limiting search to a subset of rows with a WHERE clause for large datasets. ## See Also * [CREATE INDEX](/sql-reference/statements/create-index) for index types including FTS * [Data Types](/sql-reference/data-types) for how BLOBs are handled in Turso # Window Functions Source: https://docs.turso.tech/sql-reference/functions/window Perform calculations across sets of rows related to the current row without collapsing them # Window Functions Window functions perform calculations across a set of rows that are related to the current row. Unlike aggregate functions with GROUP BY, window functions do not collapse rows into a single output row. Every input row produces a corresponding output row, with the window function result appended. > [!NOTE] > > Turso supports aggregate functions used as window functions with the default frame definition, the `row_number()` ranking function, and the `FILTER (WHERE ...)` clause. The remaining dedicated window functions (`rank`, `dense_rank`, `ntile`, `lag`, `lead`, `first_value`, `last_value`, `nth_value`) and custom frame specifications (ROWS, RANGE, or GROUPS with explicit bounds) are not yet supported. ## Syntax ```sql theme={null} aggregate_function(expression) OVER ( [PARTITION BY expression [, ...]] [ORDER BY expression [ASC | DESC] [, ...]] ) ``` | Clause | Description | | - | - | | `aggregate_function` | Any supported aggregate function: `count`, `sum`, `avg`, `min`, `max`, `total`, `group_concat` | | `OVER (...)` | Defines the window over which the function operates | | `PARTITION BY` | Divides the result set into partitions. The function is applied independently within each partition. If omitted, the entire result set is one partition | | `ORDER BY` | Defines the order of rows within each partition. This determines which rows are included in the frame for each calculation | ### Default Frame When ORDER BY is specified, the default frame is: ``` RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ``` This means the function considers all rows from the start of the partition up to and including the current row (and any rows with equal ORDER BY values, since the frame mode is RANGE). When ORDER BY is omitted, the default frame covers the entire partition. ## Supported Aggregate Functions as Window Functions Any aggregate function can be used as a window function by adding an OVER clause. | Function | Description | | - | - | | `count(*)` | Number of rows in the frame | | `count(expression)` | Number of non-NULL values in the frame | | `sum(expression)` | Sum of non-NULL values in the frame | | `avg(expression)` | Average of non-NULL values in the frame | | `min(expression)` | Minimum value in the frame | | `max(expression)` | Maximum value in the frame | | `total(expression)` | Sum as REAL (returns 0.0 for empty frames instead of NULL) | | `group_concat(expression, separator)` | Concatenation of values in the frame | ## Ranking Functions ### row\_number() Assigns a sequential integer (starting at 1) to each row within its partition, in the order defined by the window's `ORDER BY`. This is the one dedicated ranking function supported in Turso. ```sql theme={null} SELECT department, name, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rank_in_dept FROM employees; ``` | department | name | salary | rank\_in\_dept | | - | - | - | - | | Engineering | Alice | 90000 | 1 | | Engineering | Bob | 85000 | 2 | | Engineering | Carol | 75000 | 3 | | Marketing | Dave | 70000 | 1 | | Marketing | Eve | 60000 | 2 | ## FILTER (WHERE ...) A `FILTER (WHERE ...)` clause restricts which rows an aggregate window function includes, without affecting the rows returned by the query. ```sql theme={null} SELECT department, name, salary, SUM(salary) FILTER (WHERE salary > 70000) OVER (PARTITION BY department) AS high_earner_total FROM employees; ``` ## PARTITION BY PARTITION BY divides the rows into groups. The window function resets and recalculates independently for each partition. ```sql theme={null} SELECT department, name, salary, SUM(salary) OVER (PARTITION BY department) AS dept_total FROM employees; ``` | department | name | salary | dept\_total | | - | - | - | - | | Engineering | Alice | 90000 | 250000 | | Engineering | Bob | 85000 | 250000 | | Engineering | Carol | 75000 | 250000 | | Marketing | Dave | 70000 | 130000 | | Marketing | Eve | 60000 | 130000 | Without PARTITION BY, the function treats the entire result set as one partition: ```sql theme={null} SELECT name, salary, SUM(salary) OVER () AS company_total FROM employees; ``` ## ORDER BY ORDER BY within the OVER clause determines row ordering within each partition. Combined with the default frame (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), this produces running calculations. ```sql theme={null} SELECT name, salary, SUM(salary) OVER (ORDER BY salary) AS running_total FROM employees; ``` | name | salary | running\_total | | - | - | - | | Eve | 60000 | 60000 | | Dave | 70000 | 130000 | | Carol | 75000 | 205000 | | Bob | 85000 | 290000 | | Alice | 90000 | 380000 | ### PARTITION BY with ORDER BY Use both clauses together for running calculations within groups: ```sql theme={null} SELECT department, name, salary, SUM(salary) OVER ( PARTITION BY department ORDER BY salary ) AS dept_running_total FROM employees; ``` | department | name | salary | dept\_running\_total | | - | - | - | - | | Engineering | Carol | 75000 | 75000 | | Engineering | Bob | 85000 | 160000 | | Engineering | Alice | 90000 | 250000 | | Marketing | Eve | 60000 | 60000 | | Marketing | Dave | 70000 | 130000 | ## Named Windows The WINDOW clause defines a reusable window specification that can be referenced by multiple window functions in the same query. This avoids repeating the same OVER definition. ```sql theme={null} SELECT department, name, salary, SUM(salary) OVER w AS running_total, AVG(salary) OVER w AS running_avg, COUNT(*) OVER w AS running_count FROM employees WINDOW w AS (PARTITION BY department ORDER BY salary) ORDER BY department, salary; ``` Multiple named windows can be defined: ```sql theme={null} SELECT department, name, salary, SUM(salary) OVER dept AS dept_total, SUM(salary) OVER company AS company_total FROM employees WINDOW dept AS (PARTITION BY department), company AS () ORDER BY department, name; ``` ## Examples ### Running Total ```sql theme={null} SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date) AS running_total FROM orders; ``` ### Count per Group ```sql theme={null} -- Show each employee alongside their department headcount SELECT name, department, COUNT(*) OVER (PARTITION BY department) AS dept_size FROM employees ORDER BY department, name; ``` ### Running Average ```sql theme={null} SELECT date, temperature, AVG(temperature) OVER (ORDER BY date) AS running_avg_temp FROM weather_readings ORDER BY date; ``` ### Percentage of Total ```sql theme={null} SELECT product_name, revenue, ROUND(100.0 * revenue / SUM(revenue) OVER (), 2) AS pct_of_total FROM products ORDER BY revenue DESC; ``` ### Multiple Window Functions in One Query ```sql theme={null} SELECT department, name, salary, MIN(salary) OVER (PARTITION BY department) AS dept_min, MAX(salary) OVER (PARTITION BY department) AS dept_max, AVG(salary) OVER (PARTITION BY department) AS dept_avg, salary - AVG(salary) OVER (PARTITION BY department) AS diff_from_avg FROM employees ORDER BY department, salary DESC; ``` ### Group Concatenation over a Window ```sql theme={null} SELECT department, name, GROUP_CONCAT(name, ', ') OVER (PARTITION BY department ORDER BY name) AS names_so_far FROM employees; ``` ## Limitations The following window function features are not yet supported in Turso: | Feature | Status | | - | - | | `rank()` | Not supported | | `dense_rank()` | Not supported | | `ntile(N)` | Not supported | | `lag(expr, offset, default)` | Not supported | | `lead(expr, offset, default)` | Not supported | | `first_value(expr)` | Not supported | | `last_value(expr)` | Not supported | | `nth_value(expr, N)` | Not supported | | `cume_dist()` | Not supported | | `percent_rank()` | Not supported | | Custom frame: `ROWS BETWEEN ...` | Not supported | | Custom frame: `RANGE BETWEEN ... AND ...` | Not supported | | Custom frame: `GROUPS BETWEEN ...` | Not supported | | `EXCLUDE` clause | Not supported | `row_number()` and `FILTER (WHERE ...)` are supported — see the sections above. ## See Also * [SELECT](/sql-reference/statements/select) for the full SELECT syntax including the WINDOW clause * [Aggregate Functions](/sql-reference/functions/aggregate) for aggregate function reference * [Expressions](/sql-reference/expressions) for using window function results in expressions # Multi-Process Access Source: https://docs.turso.tech/sql-reference/multiprocess-access Share a Turso database file between multiple OS processes using the experimental multiprocess WAL coordinator # Multi-Process Access > [!WARNING] > > Multi-process access is **experimental**. The on-disk coordination format and the public API may change between releases. Do not rely on the format for long-term storage across Turso versions. By default, a Turso database file is opened by a single OS process. Multiple threads and connections within that process can safely share the database, but opening the same file from a second process is rejected with a locking error. Multi-process access removes that restriction. When the `multiprocess_wal` feature is enabled, several processes can open the same `.db` file concurrently and coordinate WAL reads, writes, and checkpoints through a shared memory file. ## When to Use It Enable multi-process access when you need: * Multiple independent OS processes (workers, sidecars, CLI + embedded app) to read and write the same database file without a server tier. * Zero-downtime deploys where an old process and a new process briefly overlap on the same database. * Process-per-connection deployments that need stronger isolation than threads. For single-process applications — including multi-threaded servers using a connection pool — the default mode is faster and requires no configuration. ## How It Works When multi-process access is enabled, Turso creates an additional sibling file next to the database: | File | Purpose | | - | - | | `mydb.db` | The database file (unchanged). | | `mydb.db-wal` | The write-ahead log (unchanged). | | `mydb.db-tshm` | Turso shared memory. Memory-mapped coordinator that tracks WAL state, the active writer, the active checkpointer, reader slots, and a shared page-to-frame index. | All participating processes mmap the `.tshm` file and coordinate through: * A **single-writer** slot. At most one process across the cluster holds the writer lock at any time. * A **single-checkpointer** slot. Checkpointing is serialized across processes. * A bounded set of **reader slots**. Each active read transaction pins a WAL frame so it is not overwritten by concurrent writers or reclaimed by the checkpointer. * A shared **frame index** so readers in any process can resolve a page number to the latest WAL frame without scanning the WAL from the beginning. Cross-process byte-range locks on the `.tshm` file (OFD locks on Linux, `fcntl` on macOS) gate ownership transitions; the mmap regions provide the fast path for metadata lookups. ## Requirements Multi-process WAL is only available when all of the following hold: * **Platform**: 64-bit Unix-like OS (Linux, macOS, Android, other supported Unix systems). On Windows, WASM, and 32-bit targets the flag is accepted but has no effect, and the database opens in single-process mode. * **IO backend**: The active IO backend must implement shared WAL coordination. The default file-backed backends do; memory-only and some custom VFSes do not. * **Filesystem**: The database must live on a local filesystem that implements POSIX byte-range locks and mmap correctly. The following filesystems are **explicitly rejected** because shared mmap coordination is not safe on them: | Filesystem | Reason | | - | - | | NFS | Advisory lock semantics diverge across clients | | CIFS / SMB2 | Same as above; mmap coherence is not guaranteed | | CephFS | Distributed cache semantics | | GFS2 | Cluster filesystem; not validated | | Lustre | Distributed; not validated | | OCFS2 | Cluster filesystem; not validated | | AFS, CODA, NCP, 9P (v9fs) | Network/legacy filesystems with unreliable mmap+lock semantics | Opening a database on any of these filesystems with `multiprocess_wal` enabled returns `InvalidArgument`. * **Not in-memory**: `:memory:` and `file::memory:` paths are rejected — shared coordination requires a durable file. ## Enabling Multi-Process Access ### CLI Pass `--experimental-multiprocess-wal`: ```bash theme={null} tursodb --experimental-multiprocess-wal mydb.db ``` Every `tursodb` (or SDK) process that opens `mydb.db` at the same time must pass the flag. Mixing modes is rejected — see [Mixing Modes](#mixing-modes). ### Rust SDK ```rust theme={null} use turso::Builder; let db = Builder::new_local("mydb.db") .experimental_multiprocess_wal(true) .build()?; ``` ### JavaScript / Node.js SDK ```typescript theme={null} import { Database } from "@tursodatabase/libsql"; const db = new Database("file:mydb.db", { experimental: ["multiprocess_wal"], }); ``` ### Python SDK ```python theme={null} import turso conn = turso.connect( "mydb.db", experimental_features="multiprocess_wal", ) ``` ### Go SDK ```go theme={null} db, err := turso.NewDatabase(turso.TursoDatabaseConfig{ Path: "mydb.db", ExperimentalFeatures: "multiprocess_wal", }) ``` Or via DSN: ```go theme={null} db, err := sql.Open("turso", "mydb.db?experimental=multiprocess_wal") ``` ## Concurrency Model Multi-process access preserves Turso's WAL concurrency guarantees across processes: * **Writers are serialized across processes.** At any instant, at most one process (and one connection within that process) holds the writer slot. Writers in other processes block until it is released. * **Readers never block writers.** Each active read transaction registers a reader slot that pins its snapshot's max-frame. A reader in one process cannot be invalidated by a writer in another. * **Checkpointing is serialized across processes.** A single checkpointer truncates the WAL; readers pinning older frames prevent premature reclamation regardless of which process owns them. * **Snapshots are stable.** Each read transaction observes a consistent snapshot, even if a concurrent process commits new frames. Schema changes (DDL) made in one process are picked up by other processes on their next statement via the existing schema refresh path — prepared statements in sibling processes may return `SchemaUpdated` and re-prepare, just as they do across connections within a single process. ## Mixing Modes The database cannot be open simultaneously in single-process mode from one process and multi-process mode from another. The open path probes for an incompatible live opener and fails fast: * Opening without `multiprocess_wal` while another process holds a live `.tshm` authority fails with: > Database is already open with experimental multiprocess WAL in another process * Opening with `multiprocess_wal` while another process holds the legacy exclusive DB-file lock fails with: > Database is already open without experimental multiprocess WAL in another process To switch modes, close all existing connections to the database, then reopen with the desired configuration. The `.tshm` file may be left in place — Turso reuses it on the next multi-process open and rebuilds its state from the WAL if necessary. Read-only opens are advisory: if `multiprocess_wal` is requested on a read-only open and no `.tshm` file exists, Turso falls back to the legacy read-only WAL path rather than failing. This lets read-only tooling work against a database that was closed cleanly by a single-process writer. ## Limitations * The `.tshm` file format is versioned and may change in future releases. A format bump invalidates existing `.tshm` files — Turso recreates them on open; it does not attempt cross-version migration. * Attached databases (via [ATTACH DATABASE](/sql-reference/statements/attach-database)) inherit the multi-process setting of the main connection. * The feature is not yet supported under MVCC (`BEGIN CONCURRENT`). Use either MVCC within a single process or multi-process WAL with the default isolation model, not both. * The concurrent-simulator and stress tests for multi-process mode run only on 64-bit Unix targets. Windows, WASM, and 32-bit builds silently ignore the flag. ## See Also * [Experimental Features](/sql-reference/experimental-features) — how to enable experimental features across all surfaces * [Transactions](/sql-reference/statements/transactions) — WAL concurrency model and `BEGIN CONCURRENT` * [Compatibility](/sql-reference/compatibility) — SQLite compatibility matrix # PRAGMA Statements Source: https://docs.turso.tech/sql-reference/pragmas Database configuration and metadata commands in Turso # PRAGMA Statements PRAGMA statements are special commands used to query or modify database configuration, retrieve metadata, and control database behavior. Unlike standard SQL, PRAGMAs are specific to SQLite and Turso. ## Syntax ```sql theme={null} PRAGMA pragma-name; PRAGMA pragma-name = value; PRAGMA pragma-name(value); ``` ## Database Metadata ### database\_list Returns one row for each attached database. ```sql theme={null} PRAGMA database_list; -- seq | name | file -- 0 | main | /path/to/database.db ``` ### page\_count Returns the total number of pages in the database file. ```sql theme={null} PRAGMA page_count; -- 42 ``` ### page\_size Returns or sets the page size of the database. The page size can only be set before any tables are created. ```sql theme={null} PRAGMA page_size; -- 4096 PRAGMA page_size = 8192; ``` ### max\_page\_count Returns or sets the maximum number of pages allowed in the database file. ```sql theme={null} PRAGMA max_page_count; PRAGMA max_page_count = 1000000; ``` ### freelist\_count Returns the number of unused pages in the database file. ```sql theme={null} PRAGMA freelist_count; ``` ### encoding Returns the text encoding used by the database. ```sql theme={null} PRAGMA encoding; -- UTF-8 ``` ### schema\_version Returns the schema version number. This value is incremented each time the schema changes. ```sql theme={null} PRAGMA schema_version; -- 5 ``` ### application\_id Returns or sets the application ID stored in the database header. Applications can use this 32-bit integer to identify the database file format. ```sql theme={null} PRAGMA application_id; PRAGMA application_id = 12345; ``` ### user\_version Returns or sets the user version number. This is a 32-bit integer available for application use. ```sql theme={null} PRAGMA user_version; PRAGMA user_version = 3; ``` ## Schema Introspection ### table\_info Returns one row for each column in the named table. ```sql theme={null} PRAGMA table_info(table-name); ``` | Column | Type | Description | | - | - | - | | cid | INTEGER | Column index (0-based) | | name | TEXT | Column name | | type | TEXT | Declared type name | | notnull | INTEGER | 1 if NOT NULL constraint exists | | dflt\_value | TEXT | Default value expression, or NULL | | pk | INTEGER | 1 if column is part of the PRIMARY KEY | ```sql theme={null} CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT); PRAGMA table_info(users); -- cid | name | type | notnull | dflt_value | pk -- 0 | id | INTEGER | 0 | | 1 -- 1 | name | TEXT | 1 | | 0 -- 2 | email | TEXT | 0 | | 0 ``` ### table\_xinfo Similar to `table_info` but also includes hidden columns and additional metadata. ```sql theme={null} PRAGMA table_xinfo(table-name); ``` Returns the same columns as `table_info` plus a `hidden` column (0 for normal columns, non-zero for hidden columns in virtual tables). ### table\_list Returns one row for each table and view in the database. ```sql theme={null} PRAGMA table_list; -- schema | name | type | ncol | wr | strict -- main | users | table | 3 | 0 | 0 -- main | orders | table | 4 | 0 | 0 ``` ### index\_list Returns one row for each index on the named table. ```sql theme={null} PRAGMA index_list(table-name); -- seq | name | unique | origin | partial -- 0 | idx_email | 1 | c | 0 ``` | Column | Type | Description | | - | - | - | | seq | INTEGER | Index sequence number | | name | TEXT | Index name | | unique | INTEGER | 1 if the index is UNIQUE | | origin | TEXT | `c` for CREATE INDEX, `u` for UNIQUE constraint, `pk` for PRIMARY KEY | | partial | INTEGER | 1 if the index is a partial index | ### index\_info Returns one row for each column in the named index. ```sql theme={null} PRAGMA index_info(index-name); -- seqno | cid | name -- 0 | 2 | email ``` ### index\_xinfo Similar to `index_info` but includes additional columns. ```sql theme={null} PRAGMA index_xinfo(index-name); ``` ### function\_list Returns one row for each SQL function available. ```sql theme={null} PRAGMA function_list; -- name | builtin | type | enc | narg | flags ``` ### pragma\_list Returns the list of all supported PRAGMA commands. ```sql theme={null} PRAGMA pragma_list; ``` ## Database Configuration ### journal\_mode Returns or sets the journal mode. ```sql theme={null} PRAGMA journal_mode; -- wal PRAGMA journal_mode = wal; ``` Turso supports WAL (Write-Ahead Logging) mode. Rollback journal modes (DELETE, TRUNCATE, PERSIST, MEMORY) are not supported. > [!NOTE] > > **Turso Extension**: Turso supports an experimental MVCC journal mode for concurrent writes: > > ```sql theme={null} > PRAGMA journal_mode = mvcc; > ``` > > When MVCC mode is active, you can use `BEGIN CONCURRENT` for optimistic concurrent write transactions. See [Transactions](/sql-reference/statements/transactions#begin-concurrent) for details. ### cache\_size Returns or sets the suggested maximum number of database pages held in memory. ```sql theme={null} PRAGMA cache_size; PRAGMA cache_size = 2000; -- positive: number of pages PRAGMA cache_size = -2000; -- negative: kilobytes of memory ``` ### cache\_spill Enables or disables cache spilling (writing dirty pages to the WAL before the cache is full). ```sql theme={null} PRAGMA cache_spill; PRAGMA cache_spill = 0; -- disable PRAGMA cache_spill = 1; -- enable ``` ### synchronous Controls the fsync behavior for durability guarantees. ```sql theme={null} PRAGMA synchronous; PRAGMA synchronous = OFF; -- no fsync (fastest, risk of corruption on crash) PRAGMA synchronous = FULL; -- fsync after every transaction (safest) ``` Only `OFF` and `FULL` are supported in Turso. ### temp\_store Controls where temporary tables and indexes are stored. ```sql theme={null} PRAGMA temp_store; PRAGMA temp_store = 0; -- DEFAULT PRAGMA temp_store = 1; -- FILE PRAGMA temp_store = 2; -- MEMORY ``` ### busy\_timeout Sets the busy timeout in milliseconds. When a table is locked, Turso waits up to this many milliseconds before returning SQLITE\_BUSY. ```sql theme={null} PRAGMA busy_timeout; PRAGMA busy_timeout = 5000; -- 5 seconds ``` ### query\_only When enabled, prevents any changes to the database. ```sql theme={null} PRAGMA query_only = 1; -- enable read-only mode PRAGMA query_only = 0; -- disable read-only mode ``` ### foreign\_keys Enables or disables foreign key constraint enforcement. ```sql theme={null} PRAGMA foreign_keys; PRAGMA foreign_keys = ON; PRAGMA foreign_keys = OFF; ``` Foreign key enforcement is off by default for SQLite compatibility. ### legacy\_file\_format Returns the legacy file format flag. ```sql theme={null} PRAGMA legacy_file_format; ``` ### ignore\_check\_constraints When enabled, CHECK constraints are not enforced. ```sql theme={null} PRAGMA ignore_check_constraints = 1; -- disable CHECK constraints PRAGMA ignore_check_constraints = 0; -- enable CHECK constraints ``` ### data\_sync\_retry Controls whether Turso retries a disk sync (fsync) after a failure instead of treating it as fatal. ```sql theme={null} PRAGMA data_sync_retry = 1; -- retry on sync failure PRAGMA data_sync_retry = 0; -- do not retry (default) ``` ### require\_where > [!NOTE] > > **Turso Extension**: a safety pragma with no SQLite equivalent. When enabled, Turso rejects any `UPDATE` or `DELETE` that does not include a `WHERE` clause, guarding against accidental full-table modifications. `i_am_a_dummy` is an alias that enables the same behavior. ```sql theme={null} PRAGMA require_where = 1; DELETE FROM users; -- rejected: no WHERE clause DELETE FROM users WHERE id = 1; -- allowed PRAGMA i_am_a_dummy = 1; -- equivalent to require_where = 1 ``` ## Integrity Checks ### integrity\_check Performs a thorough integrity check of the entire database. ```sql theme={null} PRAGMA integrity_check; -- ok PRAGMA integrity_check(N); -- check only the first N errors ``` Returns `ok` if no problems are found, otherwise returns one row per error. ### quick\_check Performs a faster but less thorough integrity check than `integrity_check`. ```sql theme={null} PRAGMA quick_check; ``` ## WAL Operations ### wal\_checkpoint Forces a WAL checkpoint. ```sql theme={null} PRAGMA wal_checkpoint; ``` A checkpoint writes pages from the WAL file back to the database file. ## MVCC Tuning > [!NOTE] > > **Turso Extension**: these pragmas apply only when MVCC mode is enabled (`PRAGMA journal_mode = mvcc`). ### mvcc\_checkpoint\_threshold Sets the amount of committed work that accumulates before Turso triggers an MVCC checkpoint. ```sql theme={null} PRAGMA mvcc_checkpoint_threshold = 1000; ``` ### mvcc\_gc\_threshold Sets the threshold that controls how aggressively Turso garbage-collects obsolete MVCC row versions. ```sql theme={null} PRAGMA mvcc_gc_threshold = 1000; ``` ## Change Data Capture > [!NOTE] > > **Turso Extension**: Change Data Capture (CDC) is a Turso-specific feature that tracks all data changes for replication, auditing, and reactive applications. ### capture\_data\_changes\_conn Enables CDC for the current connection. Changes are captured to a designated table. ```sql theme={null} PRAGMA capture_data_changes_conn('mode'); PRAGMA capture_data_changes_conn('mode,table_name'); ``` The legacy name `unstable_capture_data_changes_conn` is still accepted for backwards compatibility. | Parameter | Description | | - | - | | mode | Capture mode: `off`, `id`, `before`, `after`, `full` | | table\_name | Custom table name for storing changes (default: `turso_cdc`) | #### Capture Modes | Mode | Description | | - | - | | `off` | Disable CDC for this connection | | `id` | Capture only the primary key/rowid of changed rows | | `before` | Capture row state before changes (for updates/deletes) | | `after` | Capture row state after changes (for inserts/updates) | | `full` | Capture both before and after states, plus update details | #### CDC Table Structure The CDC table contains the following columns: | Column | Type | Description | | - | - | - | | change\_id | INTEGER | Auto-incrementing unique identifier | | change\_time | INTEGER | Unix epoch timestamp | | change\_type | INTEGER | 1 (INSERT), 0 (UPDATE), -1 (DELETE) | | table\_name | TEXT | Name of the changed table | | id | varies | Primary key/rowid of the changed row | | before | BLOB | Row data before the change (modes: before, full) | | after | BLOB | Row data after the change (modes: after, full) | | updates | BLOB | Updated column details (mode: full) | #### CDC Examples ```sql theme={null} -- Enable full CDC PRAGMA capture_data_changes_conn('full'); -- Make changes CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT); INSERT INTO users VALUES (1, 'Alice'); UPDATE users SET name = 'Alicia' WHERE id = 1; DELETE FROM users WHERE id = 1; -- Query the changes SELECT change_type, table_name, id FROM turso_cdc; -- 1 | users | 1 (INSERT) -- 0 | users | 1 (UPDATE) -- -1 | users | 1 (DELETE) -- Use a custom table name PRAGMA capture_data_changes_conn('full,audit_log'); -- Disable CDC PRAGMA capture_data_changes_conn('off'); ``` CDC respects transaction boundaries. Changes are only recorded when a transaction commits. If a transaction rolls back, no CDC entries are created. ## Encryption > [!NOTE] > > **Turso Extension**: At-rest encryption is a Turso-specific feature. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). ### cipher Sets the encryption cipher for the database. ```sql theme={null} PRAGMA cipher = 'aegis256'; ``` Supported ciphers: | Cipher | Key Size | Description | | - | - | - | | `aes128gcm` | 16 bytes | AES-128 in Galois/Counter Mode | | `aes256gcm` | 32 bytes | AES-256 in Galois/Counter Mode | | `aegis128l` | 16 bytes | AEGIS-128L | | `aegis256` | 32 bytes | AEGIS-256 (recommended) | | `aegis128x2` | 16 bytes | AEGIS-128 with 2x parallelization | | `aegis128x4` | 16 bytes | AEGIS-128 with 4x parallelization | | `aegis256x2` | 32 bytes | AEGIS-256 with 2x parallelization | | `aegis256x4` | 32 bytes | AEGIS-256 with 4x parallelization | ### hexkey Sets the encryption key as a hexadecimal string. ```sql theme={null} PRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d'; ``` #### Encryption Example ```sql theme={null} -- Set cipher and key before creating tables PRAGMA cipher = 'aegis256'; PRAGMA hexkey = '2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d'; CREATE TABLE secrets (id INTEGER PRIMARY KEY, data TEXT); INSERT INTO secrets VALUES (1, 'sensitive data'); ``` Alternatively, specify encryption parameters in the database URI: ``` file:database.db?cipher=aegis256&hexkey=2d7a30... ``` To open an existing encrypted database, the cipher and key must be provided as URI parameters. ## Custom Types > [!NOTE] > > **Turso Extension**: Custom types are a Turso-specific feature. ### list\_types Lists all available types (built-in and custom) with their metadata. ```sql theme={null} PRAGMA list_types; -- type | parent | encode | decode | default | operators -- INTEGER | | | | | -- REAL | | | | | -- TEXT | | | | | -- BLOB | | | | | -- ANY | | | | | ``` See [CREATE TYPE](/sql-reference/statements/create-type) for creating custom types. ## See Also * [Transactions](/sql-reference/statements/transactions) for transaction control * [CREATE TYPE](/sql-reference/statements/create-type) for custom type definitions * [Compatibility](/sql-reference/compatibility) for the full list of supported PRAGMAs # ALTER TABLE Source: https://docs.turso.tech/sql-reference/statements/alter-table Modify the structure of an existing table by renaming, adding, or dropping columns # ALTER TABLE Modify the structure of an existing table. ## Syntax ```sql theme={null} ALTER TABLE [schema-name.]table-name RENAME TO new-table-name; ALTER TABLE [schema-name.]table-name RENAME COLUMN old-column-name TO new-column-name; ALTER TABLE [schema-name.]table-name ADD COLUMN column-def; ALTER TABLE [schema-name.]table-name DROP COLUMN column-name; ``` ## Description `ALTER TABLE` modifies the structure of an existing table without requiring you to recreate the table and copy its data. Turso supports four forms of ALTER TABLE: renaming the table, renaming a column, adding a new column, and dropping an existing column. ### Parameters | Parameter | Description | | - | - | | `schema-name` | The name of an attached database containing the table. Defaults to the main database if omitted. | | `table-name` | The name of the table to alter. | ## RENAME TO Rename an existing table. ```sql theme={null} ALTER TABLE old_name RENAME TO new_name; ``` Renaming a table updates all references to the table in: * Triggers that reference the table * Indexes on the table * Foreign key constraints (both as parent and child table) * CHECK constraints * Views that reference the table > [!NOTE] > > The new table name must not collide with an existing table, view, or index name in the same database. ### Example ```sql theme={null} CREATE TABLE customers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); ALTER TABLE customers RENAME TO clients; -- The table is now accessible as 'clients' SELECT * FROM clients; ``` ## RENAME COLUMN Rename an existing column in a table. ```sql theme={null} ALTER TABLE table-name RENAME COLUMN old-column-name TO new-column-name; ``` Renaming a column updates all references to the column in: * Indexes that reference the column (including expression indexes) * Triggers that reference the column * CHECK constraints that reference the column * Foreign key constraint definitions ### Restrictions A column rename fails if: * The column does not exist in the table. * The new column name conflicts with an existing column in the same table. * A trigger on the table uses a qualified reference (e.g., `table_name.column_name`) to the column in its body. * A trigger's WHEN clause references the column. ### Example ```sql theme={null} CREATE TABLE products ( id INTEGER PRIMARY KEY, product_name TEXT NOT NULL, price REAL ); CREATE INDEX idx_name ON products(product_name); ALTER TABLE products RENAME COLUMN product_name TO name; -- The index is automatically updated -- Queries now use the new column name SELECT name, price FROM products; ``` ## ADD COLUMN Add a new column to an existing table. The new column is always appended as the last column. ```sql theme={null} ALTER TABLE table-name ADD COLUMN column-name type [constraints]; ``` ### Restrictions A new column added with ADD COLUMN must satisfy these requirements: | Requirement | Reason | | - | - | | No `PRIMARY KEY` constraint | The primary key of a table cannot be changed after creation | | No `UNIQUE` constraint | Adding a UNIQUE column would require scanning all existing rows | | `NOT NULL` requires a non-NULL default | Existing rows must have a valid value for the new column | | Default must be a constant expression | Only literal values, signed literals, and parenthesized constant expressions are allowed. Functions like `random()` are not permitted | | `CURRENT_TIME`, `CURRENT_DATE`, `CURRENT_TIMESTAMP` defaults require an empty table | These non-deterministic values cannot be retroactively applied to existing rows | | CHECK constraints require an empty table | The constraint cannot be validated against existing rows | | No duplicate column name | The column name must not already exist in the table | | No `GENERATED ALWAYS AS` | Generated columns cannot be added via ALTER TABLE | | STRICT tables require an explicit type | The column type must be a valid STRICT type | ### Supported Column Constraints The following constraints are supported on added columns: * `NOT NULL` (with a non-NULL default) * `DEFAULT expression` * `CHECK (expression)` * `REFERENCES` (foreign key) * `COLLATE` ### Examples #### Add a simple column ```sql theme={null} ALTER TABLE users ADD COLUMN phone TEXT; ``` #### Add a column with a default value ```sql theme={null} ALTER TABLE orders ADD COLUMN priority INTEGER DEFAULT 0; ``` #### Add a NOT NULL column with a default ```sql theme={null} ALTER TABLE products ADD COLUMN in_stock INTEGER NOT NULL DEFAULT 1; ``` #### Add a column with a foreign key ```sql theme={null} ALTER TABLE orders ADD COLUMN warehouse_id INTEGER REFERENCES warehouses(id); ``` #### Add a column on a STRICT table ```sql theme={null} CREATE TABLE metrics ( id INTEGER PRIMARY KEY, value REAL ) STRICT; -- Must specify a valid STRICT type ALTER TABLE metrics ADD COLUMN label TEXT DEFAULT 'unknown'; ``` ## DROP COLUMN Remove an existing column from a table. ```sql theme={null} ALTER TABLE table-name DROP COLUMN column-name; ``` ### Restrictions A column cannot be dropped if any of the following conditions are true: | Condition | Error | | - | - | | The column is a PRIMARY KEY or part of one | Cannot drop PRIMARY KEY column | | The column has a UNIQUE constraint | Cannot drop UNIQUE column | | The column is referenced by an index | Cannot drop indexed column | | The column is referenced in an expression index | Cannot drop column used in expression index | | The column is named in a partial index WHERE clause | Cannot drop column used in partial index | | The column is referenced in a CHECK constraint belonging to another column or the table | Cannot drop column used in CHECK constraint | | The column is referenced by a foreign key in another table | Cannot drop column used in foreign key | | The table has only one column | Cannot drop the last remaining column | | The column appears in a trigger or view | Cannot drop column referenced by trigger or view | ### Examples #### Drop a column ```sql theme={null} ALTER TABLE users DROP COLUMN phone; ``` #### Verify columns after dropping ```sql theme={null} CREATE TABLE example ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, old_field TEXT, active INTEGER DEFAULT 1 ); ALTER TABLE example DROP COLUMN old_field; PRAGMA table_info(example); -- cid | name | type | notnull | dflt_value | pk -- 0 | id | INTEGER | 0 | NULL | 1 -- 1 | name | TEXT | 1 | NULL | 0 -- 2 | active | INTEGER | 0 | 1 | 0 ``` ## Examples ### Complete Schema Evolution ```sql theme={null} -- Create initial table CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, body TEXT ); -- Add an author column with a foreign key ALTER TABLE articles ADD COLUMN author_id INTEGER REFERENCES users(id); -- Add a publication status ALTER TABLE articles ADD COLUMN status TEXT NOT NULL DEFAULT 'draft'; -- Rename a column for clarity ALTER TABLE articles RENAME COLUMN body TO content; -- Add a nullable timestamp column ALTER TABLE articles ADD COLUMN updated_at TEXT; -- Rename the table ALTER TABLE articles RENAME TO posts; -- Final schema PRAGMA table_info(posts); -- cid | name | type | notnull | dflt_value | pk -- 0 | id | INTEGER | 0 | NULL | 1 -- 1 | title | TEXT | 1 | NULL | 0 -- 2 | content | TEXT | 0 | NULL | 0 -- 3 | author_id | INTEGER | 0 | NULL | 0 -- 4 | status | TEXT | 1 | 'draft' | 0 -- 5 | updated_at | TEXT | 0 | NULL | 0 ``` ## See Also * [CREATE TABLE](/sql-reference/statements/create-table) for creating tables * [DROP TABLE](/sql-reference/statements/drop-table) for removing tables * [CREATE INDEX](/sql-reference/statements/create-index) for indexing columns * [Data Types](/sql-reference/data-types) for column types and STRICT table types # ANALYZE Source: https://docs.turso.tech/sql-reference/statements/analyze Collect statistics about indexes to help the query optimizer # ANALYZE The ANALYZE statement collects statistics about the contents of tables and indexes. The query optimizer uses these statistics to choose better query plans, particularly when deciding which index to use and how to order joins. ## Syntax ```sql theme={null} ANALYZE; ANALYZE schema-name; ANALYZE [schema-name.]table-or-index-name; ``` ## Description The ANALYZE statement gathers statistics about the distribution of values in indexes and stores the results in the `sqlite_stat1` table (and optionally `sqlite_stat4`). The query optimizer reads these statistics to make better decisions about: * Which index to use for a query * The order in which to process tables in a join * Whether to use an index or a full table scan ### Forms | Form | Description | | - | - | | `ANALYZE` | Analyze all tables and indexes in all attached databases | | `ANALYZE schema-name` | Analyze all tables and indexes in the named database | | `ANALYZE table-name` | Analyze all indexes on the named table | | `ANALYZE index-name` | Analyze the named index | ## Examples ### Analyze All Tables ```sql theme={null} -- Collect statistics for the entire database ANALYZE; ``` ### Analyze a Specific Table ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER, product_id INTEGER, amount REAL ); CREATE INDEX idx_customer ON orders(customer_id); CREATE INDEX idx_product ON orders(product_id); -- Collect statistics for the orders table and its indexes ANALYZE orders; ``` ### Viewing Statistics The statistics are stored in the `sqlite_stat1` table: ```sql theme={null} SELECT * FROM sqlite_stat1; -- tbl | idx | stat -- orders | idx_customer | 1000 50 -- orders | idx_product | 1000 10 ``` The `stat` column contains space-separated integers. The first integer is the total number of rows in the table. Subsequent integers estimate the average number of rows that share the same value for the leftmost N columns of the index. ## See Also * [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes * [EXPLAIN](/sql-reference/statements/explain) for examining query plans # ATTACH DATABASE Source: https://docs.turso.tech/sql-reference/statements/attach-database Attach an additional database file to the current connection # ATTACH DATABASE The ATTACH DATABASE statement adds another database file to the current connection under a schema name. Once attached, tables in the external database can be queried alongside tables in the main database. ## Syntax ```sql theme={null} ATTACH [DATABASE] filename AS schema-name; ``` ## Description ATTACH opens the database file at `filename` and makes its tables accessible through the given `schema-name`. Tables in the attached database are referenced as `schema-name.table-name`. ### Clauses | Clause | Description | | - | - | | `DATABASE` | Optional keyword. Has no effect on behavior. | | `filename` | A string expression that evaluates to the path of the database file to attach. Use `':memory:'` to attach an in-memory database. | | `schema-name` | The name used to qualify table references from the attached database. This name must be unique among all attached databases on the connection. | Every connection has a built-in schema named `main` for its primary database. The names `main` and `temp` are reserved and cannot be used as schema names for attached databases. > [!NOTE] > > This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). Attached databases support both read and write operations (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). The attached database's journal mode must match the main database's journal mode (e.g., both WAL or both MVCC). ## Examples ### Attach and Query an External Database ```sql theme={null} -- Attach another database file ATTACH DATABASE 'archive.db' AS archive; -- Query a table in the attached database SELECT * FROM archive.events WHERE event_date > '2025-01-01'; ``` ### Cross-Database Queries ```sql theme={null} ATTACH 'customers.db' AS customers_db; -- Join tables across the main and attached databases SELECT o.id, o.total, c.name FROM main.orders o JOIN customers_db.customers c ON o.customer_id = c.id; ``` ### Attach an In-Memory Database ```sql theme={null} ATTACH ':memory:' AS scratch; ``` ## See Also * [DETACH DATABASE](/sql-reference/statements/detach-database) for removing an attached database * [SELECT](/sql-reference/statements/select) for querying tables across databases # CREATE DOMAIN Source: https://docs.turso.tech/sql-reference/statements/create-domain Define a named constraint wrapper over a base type for use in STRICT tables # CREATE DOMAIN > [!NOTE] > > **Turso Extension**: CREATE DOMAIN is a Turso-specific statement not available in standard SQLite. Domains require STRICT tables and the custom types experimental feature. This feature must be [enabled before use](/sql-reference/experimental-features). The CREATE DOMAIN statement defines a named type alias with optional constraints. Unlike [CREATE TYPE](/sql-reference/statements/create-type), a domain does not specify custom ENCODE/DECODE logic or OPERATORs. Instead, a domain adds NOT NULL and CHECK constraints on top of a base type, and values are stored using the base type's native storage format. ## Syntax ```sql theme={null} CREATE DOMAIN [IF NOT EXISTS] domain-name AS base-type [DEFAULT default-expr] [NOT NULL] [[CONSTRAINT constraint-name] CHECK (expr)] ...; ``` ## Description A domain wraps a base type with validation constraints. When a value is written to a column of a domain type, the CHECK constraints and NOT NULL restriction (if any) are verified. If validation passes, the value is stored using the base type's native format. When a value is read, it is returned unchanged. Domains are transparent to the query engine for operations like ORDER BY, indexing, arithmetic, and aggregation. A domain column behaves exactly like a column of its base type, with the addition of input validation. Domains work only with STRICT tables. Using a domain name in a non-STRICT table has no effect. ## Clauses ### IF NOT EXISTS Suppresses the error that would occur if a domain with the same name already exists. The existing domain is left unchanged. ```sql theme={null} CREATE DOMAIN IF NOT EXISTS positive_int AS integer CHECK (value > 0); ``` ### AS base-type Specifies the underlying type. The base type can be a primitive type (`integer`, `real`, `text`, `blob`) or another domain, allowing domains to be layered. ```sql theme={null} CREATE DOMAIN percentage AS integer CHECK (value >= 0) CHECK (value <= 100); ``` ### DEFAULT Sets a default value for columns of this domain type. A column-level DEFAULT overrides the domain-level DEFAULT. ```sql theme={null} CREATE DOMAIN status AS text DEFAULT 'active'; CREATE TABLE accounts ( id INTEGER PRIMARY KEY, state status ) STRICT; INSERT INTO accounts(id) VALUES (1); SELECT state FROM accounts; -- active ``` ### NOT NULL Adds a NOT NULL constraint to the domain. Any attempt to insert or update a NULL value into a column of this domain type will fail, even if the column definition does not specify NOT NULL. ```sql theme={null} CREATE DOMAIN required_text AS text NOT NULL; CREATE TABLE contacts ( id INTEGER PRIMARY KEY, name required_text ) STRICT; INSERT INTO contacts VALUES (1, 'Alice'); -- OK INSERT INTO contacts VALUES (2, NULL); -- Error: domain required_text does not allow null values ``` A column-level NULL declaration does not override the domain's NOT NULL constraint. ### CHECK Adds a validation constraint. The expression can reference `value` to refer to the input value being checked. Multiple CHECK constraints can be specified and all must pass. ```sql theme={null} CREATE DOMAIN positive_int AS integer CHECK (value > 0); CREATE TABLE measurements ( id INTEGER PRIMARY KEY, reading positive_int ) STRICT; INSERT INTO measurements VALUES (1, 42); -- OK INSERT INTO measurements VALUES (2, -5); -- Error: domain positive_int constraint violation ``` ### CONSTRAINT name CHECK CHECK constraints can optionally be given a name for documentation purposes. ```sql theme={null} CREATE DOMAIN valid_score AS integer CONSTRAINT non_negative CHECK (value >= 0) CONSTRAINT max_hundred CHECK (value <= 100); ``` ## Domain Chaining A domain can use another domain as its base type. When domains are chained, all constraints in the chain are enforced from child to ancestor. ```sql theme={null} CREATE DOMAIN base_amount AS integer CHECK (value > 0); CREATE DOMAIN small_amount AS base_amount CHECK (value < 1000); CREATE TABLE orders ( id INTEGER PRIMARY KEY, quantity small_amount ) STRICT; INSERT INTO orders VALUES (1, 50); -- OK: passes both value > 0 and value < 1000 INSERT INTO orders VALUES (2, -1); -- Error: fails base_amount's CHECK (value > 0) INSERT INTO orders VALUES (3, 5000); -- Error: fails small_amount's CHECK (value < 1000) ``` Three or more levels of chaining are supported: ```sql theme={null} CREATE DOMAIN text_val AS text; CREATE DOMAIN nonempty AS text_val CHECK (length(value) > 0); CREATE DOMAIN short_text AS nonempty CHECK (length(value) < 50); CREATE TABLE labels ( id INTEGER PRIMARY KEY, name short_text ) STRICT; INSERT INTO labels VALUES (1, 'OK'); -- OK INSERT INTO labels VALUES (2, ''); -- Error: fails nonempty's CHECK (length(value) > 0) ``` ## UPDATE Enforcement Domain constraints are enforced on UPDATE as well as INSERT. ```sql theme={null} CREATE DOMAIN positive_int AS integer CHECK (value > 0); CREATE TABLE items ( id INTEGER PRIMARY KEY, stock positive_int ) STRICT; INSERT INTO items VALUES (1, 10); UPDATE items SET stock = 20 WHERE id = 1; SELECT stock FROM items; -- 20 UPDATE items SET stock = -1 WHERE id = 1; -- Error: domain positive_int constraint violation ``` ## CAST Support CAST applies the domain's validation constraints: ```sql theme={null} CREATE DOMAIN positive_int AS integer CHECK (value > 0); SELECT CAST(42 AS positive_int); -- 42 SELECT CAST(-1 AS positive_int); -- Error: domain positive_int constraint violation ``` Casting NULL to a NOT NULL domain is rejected: ```sql theme={null} CREATE DOMAIN notnull_int AS integer NOT NULL; SELECT CAST(NULL AS notnull_int); -- Error: domain notnull_int does not allow null values ``` ## CHECK Constraints with Table-Level Checks Domain CHECK constraints and table-level CHECK constraints both apply. The domain constraint is checked during encoding and the table CHECK is checked separately. ```sql theme={null} CREATE DOMAIN positive_int AS integer CHECK (value > 0); CREATE TABLE bounded ( id INTEGER PRIMARY KEY, val positive_int CHECK (val < 100) ) STRICT; INSERT INTO bounded VALUES (1, 50); -- OK: passes domain CHECK (> 0) and table CHECK (< 100) INSERT INTO bounded VALUES (2, -1); -- Error: fails domain CHECK INSERT INTO bounded VALUES (3, 200); -- Error: fails table CHECK ``` ## Operations on Domain Columns Domain columns support the same operations as their base type: arithmetic, comparisons, ordering, and aggregation. ```sql theme={null} CREATE DOMAIN myint AS integer; CREATE TABLE data ( id INTEGER PRIMARY KEY, a myint, b myint ) STRICT; INSERT INTO data VALUES (1, 10, 3); SELECT a + b, a - b, a * b FROM data; -- 13|7|30 ``` ```sql theme={null} CREATE DOMAIN myint AS integer; CREATE TABLE scores ( id INTEGER PRIMARY KEY, val myint ) STRICT; INSERT INTO scores VALUES (1, 30); INSERT INTO scores VALUES (2, 10); INSERT INTO scores VALUES (3, 20); SELECT val FROM scores ORDER BY val; -- 10 -- 20 -- 30 ``` ## Dropping Domains Domains are dropped with [DROP DOMAIN](/sql-reference/statements/drop-domain), not DROP TYPE. Attempting to use DROP TYPE on a domain (or DROP DOMAIN on a type) results in an error. A domain cannot be dropped while any table column or another domain references it. ```sql theme={null} CREATE DOMAIN my_domain AS integer; CREATE TABLE t(x my_domain) STRICT; DROP DOMAIN my_domain; -- Error: type 'my_domain' is in use DROP TABLE t; DROP DOMAIN my_domain; -- OK ``` ## Restrictions * **STRICT tables only**: Using a domain type on a non-STRICT table is rejected at CREATE TABLE and ALTER TABLE ADD COLUMN time. Non-STRICT tables allow arbitrary type names as affinity hints, but domain constraints (CHECK, NOT NULL, DEFAULT) are only enforced on STRICT tables, so permitting them would be misleading. * **Cannot drop while in use**: A domain cannot be dropped while any table column or another domain references it. > [!NOTE] > > If a non-STRICT table was created with a type name that did not exist at the time (e.g., `CREATE TABLE t(x mydom)`), and a domain with that name is created afterwards (`CREATE DOMAIN mydom AS ...`), the domain constraints will **not** be retroactively enforced on the existing table. This is not a bug: non-STRICT tables treat all type names as plain affinity hints regardless of whether a matching domain exists. STRICT tables prevent this scenario because they reject unknown type names at CREATE TABLE time. * **No UNIQUE, PRIMARY KEY, or REFERENCES**: These constraints are not supported in domain definitions. Use table-level constraints instead. * **No duplicate clauses**: Multiple DEFAULT or NOT NULL clauses in the same definition are rejected. Conflicting NULL and NOT NULL clauses are also rejected. ## See Also * [DROP DOMAIN](/sql-reference/statements/drop-domain) for removing domains * [CREATE TYPE](/sql-reference/statements/create-type) for defining custom types with ENCODE/DECODE logic * [Data Types](/sql-reference/data-types) for an overview of the type system * [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table definitions # CREATE INDEX Source: https://docs.turso.tech/sql-reference/statements/create-index Create an index on one or more columns or expressions to accelerate queries # CREATE INDEX Create an index on a table to improve query performance for lookups, joins, and ordering. ## Syntax ```sql theme={null} CREATE [UNIQUE] INDEX [IF NOT EXISTS] [schema-name.]index-name ON table-name (column-or-expr [COLLATE collation-name] [ASC|DESC], ...) [WHERE filter-expression]; ``` ## Description `CREATE INDEX` builds an index on one or more columns or expressions of a table. Turso uses B-tree indexes in the same format as SQLite. The query planner automatically uses indexes when they can speed up a query -- you do not need to reference an index explicitly in your SQL statements. ### Parameters | Parameter | Description | | - | - | | `UNIQUE` | Enforces a uniqueness constraint. Turso rejects any INSERT or UPDATE that would create duplicate values in the indexed columns. | | `IF NOT EXISTS` | Prevents an error if an index with the same name already exists. The statement is a no-op when the index is present. | | `schema-name` | The name of the attached database containing the table. Defaults to the main database if omitted. | | `index-name` | A unique name for the index within the database. | | `table-name` | The table to index. | | `column-or-expr` | A column name or an expression to include in the index. Multiple entries are separated by commas. | | `COLLATE collation-name` | An optional collation sequence applied to the indexed column (e.g. `NOCASE`). | | `ASC` / `DESC` | Sort direction for the indexed column. Default is `ASC`. | | `WHERE filter-expression` | An optional filter that creates a partial index. Only rows matching the expression are included in the index. | ## Column Indexes The most common form indexes one or more columns by name. ```sql theme={null} -- Single-column index CREATE INDEX idx_users_email ON users (email); -- Multi-column (composite) index CREATE INDEX idx_orders_customer_date ON orders (customer_id, order_date DESC); ``` A composite index is useful when queries filter or sort by multiple columns. The order of columns matters -- an index on `(a, b)` can accelerate queries filtering on `a` alone, but not queries filtering only on `b`. ## UNIQUE Indexes A `UNIQUE` index enforces that no two rows contain the same combination of values in the indexed columns. NULL values are considered distinct from each other, so a `UNIQUE` index permits multiple rows with NULL in the indexed columns. ```sql theme={null} CREATE UNIQUE INDEX idx_users_email_unique ON users (email); -- This succeeds: INSERT INTO users (email) VALUES ('alice@example.com'); -- This fails with a UNIQUE constraint violation: INSERT INTO users (email) VALUES ('alice@example.com'); ``` ## Partial Indexes A partial index includes only the rows that satisfy the `WHERE` clause. Partial indexes are smaller than full indexes and are more efficient for queries that always include the same filter condition. ```sql theme={null} -- Index only active orders CREATE INDEX idx_active_orders ON orders (customer_id) WHERE status = 'active'; -- The query planner uses this index when the WHERE clause matches SELECT * FROM orders WHERE status = 'active' AND customer_id = 42; ``` The `WHERE` clause of a partial index can reference any column of the table and may use operators, literal values, and built-in functions. Subqueries are not allowed. ## Expression Indexes An expression index stores the result of an expression rather than a raw column value. Use expression indexes when queries frequently filter or sort by a computed value. ```sql theme={null} -- Index on lowercase email for case-insensitive lookups CREATE INDEX idx_users_email_lower ON users (lower(email)); -- The query planner can use this index for: SELECT * FROM users WHERE lower(email) = 'alice@example.com'; ``` ```sql theme={null} -- Index on an arithmetic expression CREATE INDEX idx_items_total ON items (quantity * unit_price); ``` Each expression in the index must be a deterministic expression that references only columns of the indexed table. Aggregate functions and subqueries are not allowed. ## Custom Index Methods > [!NOTE] > > **Turso Extension**: Custom index methods extend indexing beyond B-trees. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). Turso supports a `USING` clause to specify an alternative index method. ```sql theme={null} CREATE INDEX index-name ON table-name USING method-name (columns...); ``` ### Full-Text Search with `USING fts` The `fts` index method creates a full-text search index powered by Tantivy. FTS indexes support tokenizer configuration through the `WITH` clause. ```sql theme={null} -- Basic FTS index CREATE INDEX idx_articles_search ON articles USING fts (title, body); -- FTS index with custom tokenizers CREATE INDEX idx_articles_search ON articles USING fts ( title WITH tokenizer=simple, body WITH tokenizer=ngram ); ``` Once an FTS index exists, use the `search()` function to query it: ```sql theme={null} SELECT * FROM articles WHERE search(articles, 'database performance'); ``` ## Examples ### Index for a Common Lookup Pattern ```sql theme={null} CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, category TEXT, price REAL, in_stock INTEGER DEFAULT 1 ); -- Speed up lookups by category CREATE INDEX idx_products_category ON products (category); -- Speed up price range queries within a category CREATE INDEX idx_products_cat_price ON products (category, price); ``` ### Unique Index to Enforce a Business Rule ```sql theme={null} CREATE TABLE employees ( id INTEGER PRIMARY KEY, email TEXT NOT NULL, department TEXT ); CREATE UNIQUE INDEX idx_employees_email ON employees (email); ``` ### Partial Index for a Status Filter ```sql theme={null} CREATE TABLE tasks ( id INTEGER PRIMARY KEY, assignee TEXT, status TEXT DEFAULT 'pending', due_date TEXT ); -- Only index pending tasks -- completed tasks are rarely queried CREATE INDEX idx_pending_tasks ON tasks (assignee, due_date) WHERE status = 'pending'; ``` ## See Also * [DROP INDEX](/sql-reference/statements/drop-index) for removing indexes * [REINDEX](/sql-reference/statements/reindex) for rebuilding indexes * [CREATE TABLE](/sql-reference/statements/create-table) for inline UNIQUE and PRIMARY KEY constraints * [EXPLAIN](/sql-reference/statements/explain) for verifying index usage in query plans # CREATE MATERIALIZED VIEW Source: https://docs.turso.tech/sql-reference/statements/create-materialized-view Create a materialized view that stores precomputed results and updates automatically # CREATE MATERIALIZED VIEW > [!NOTE] > > **Turso Extension**: Materialized views are a Turso-specific feature not available in SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). A materialized view stores precomputed query results on disk and automatically keeps them up to date using Incremental View Maintenance (IVM). Unlike standard views, reading from a materialized view does not re-execute the underlying SELECT. ## Syntax ```sql theme={null} CREATE MATERIALIZED VIEW [IF NOT EXISTS] view-name AS select-statement; ``` ### Parameters | Parameter | Description | | - | - | | `IF NOT EXISTS` | Prevents an error if a materialized view with the same name already exists. The statement is a no-op when the view is present. | | `view-name` | A unique name for the materialized view within the database. | | `select-statement` | The SELECT query that defines the materialized view's contents. | ## How Incremental View Maintenance Works When you modify data in a table that underlies a materialized view, Turso incrementally updates the materialized view within the same transaction. There is no manual `REFRESH` step. The materialized view is always consistent with the base tables. * Changes to the materialized view are committed or rolled back together with the changes to the base tables. * Aggregations like `COUNT`, `SUM`, and `AVG` are maintained efficiently — Turso does not recompute the entire result set on each change. ## Current Limitations * Not all SQL functions are supported in materialized view definitions. * A materialized view cannot reference another view or materialized view. * The `TEMPORARY` modifier is not supported for materialized views. ## Examples ### Precomputed Aggregations ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER, amount REAL, created_at TEXT ); -- Precompute per-customer totals CREATE MATERIALIZED VIEW customer_totals AS SELECT customer_id, COUNT(*) AS order_count, SUM(amount) AS total_spent FROM orders GROUP BY customer_id; -- Fast read — no aggregation at query time SELECT * FROM customer_totals WHERE total_spent > 1000; ``` ### Automatic Updates ```sql theme={null} -- When new orders are inserted, customer_totals updates automatically INSERT INTO orders VALUES (1, 100, 59.99, '2025-01-15'); INSERT INTO orders VALUES (2, 100, 24.50, '2025-01-16'); -- Reflects both inserts without a manual refresh SELECT * FROM customer_totals WHERE customer_id = 100; -- customer_id: 100, order_count: 2, total_spent: 84.49 ``` ### Filtered Summary ```sql theme={null} CREATE MATERIALIZED VIEW recent_activity AS SELECT customer_id, COUNT(*) AS recent_orders FROM orders WHERE created_at >= '2025-01-01' GROUP BY customer_id; ``` ## See Also * [CREATE VIEW](/sql-reference/statements/create-view) for standard (non-materialized) views * [DROP VIEW](/sql-reference/statements/drop-view) for removing materialized views * [SELECT](/sql-reference/statements/select) for the query syntax used in view definitions # CREATE SEQUENCE Source: https://docs.turso.tech/sql-reference/statements/create-sequence Define a named sequence generator for producing auto-incrementing numeric values # CREATE SEQUENCE > [!NOTE] > > **Turso Extension**: CREATE SEQUENCE is a Turso-specific statement not available in standard SQLite. The CREATE SEQUENCE statement defines a named sequence object that generates a series of numeric values. Sequences are independent of any table and can be used across multiple tables and queries via the `nextval()`, `currval()`, and `setval()` functions. ## Syntax ```sql theme={null} CREATE SEQUENCE [IF NOT EXISTS] [schema-name.]sequence-name [START [WITH] value] [INCREMENT [BY] value] [MINVALUE value] [MAXVALUE value] [CYCLE | NO CYCLE]; ``` ## Description A sequence generates a series of integer values according to its configuration. Each call to `nextval()` produces the next value in the series. Sequences are persisted in the database schema and survive restarts. Unlike `AUTOINCREMENT` on an `INTEGER PRIMARY KEY`, sequences are standalone objects. A single sequence can supply values to multiple tables, or multiple sequences can feed into different columns of the same table. ### Parameters | Parameter | Description | Default | | - | - | - | | `IF NOT EXISTS` | Prevents an error if a sequence with the same name already exists. The statement is a no-op when the sequence is present. | | | `schema-name` | The name of an attached database. Defaults to the main database if omitted. | | | `sequence-name` | The name of the sequence to create. | | | `START [WITH]` | The first value returned by `nextval()`. | `MINVALUE` for ascending sequences, `MAXVALUE` for descending sequences. | | `INCREMENT [BY]` | The step between consecutive values. Use a negative value for descending sequences. | `1` | | `MINVALUE` | The lower bound of the sequence. | `1` for ascending sequences, minimum 64-bit integer for descending sequences. | | `MAXVALUE` | The upper bound of the sequence. | Maximum 64-bit integer for ascending sequences, `-1` for descending sequences. | | `CYCLE` | When the sequence reaches its boundary, wrap around to the opposite end instead of raising an error. | `NO CYCLE` | ### Validation The following rules are enforced at creation time: * `INCREMENT BY` must not be zero. * `MINVALUE` must be less than `MAXVALUE`. * For ascending sequences (`INCREMENT > 0`): `START` must be greater than or equal to `MINVALUE`. * For descending sequences (`INCREMENT < 0`): `START` must be less than or equal to `MAXVALUE`. ## Sequence Functions ### nextval() Returns the next value from the sequence and advances it. ```sql theme={null} SELECT nextval('counter'); -- 1 SELECT nextval('counter'); -- 2 ``` On the first call, `nextval()` returns the `START` value. Each subsequent call increments by `INCREMENT BY`. Sequence persistence under WAL mode is transactional: the sequence backing-table update is committed or rolled back with the transaction that called `nextval()`. Under MVCC mode (`journal_mode=experimental_mvcc`) inside `BEGIN CONCURRENT`, the backing-table write is autonomous and survives an outer `ROLLBACK` (postgres-like). For BEGIN / BEGIN IMMEDIATE / autocommit, the inner write is bundled with the outer and rolls back with it (SQLite-like). Turso does not guarantee gap-free sequences, and it also does not guarantee PostgreSQL-style permanent burning of values from rolled-back transactions in WAL mode or under an exclusive (BEGIN IMMEDIATE / BEGIN DEFERRED) MVCC outer. If a transaction calls `nextval()` and rolls back, that value may be generated again later if no committed transaction used that value or a later value. If another concurrent transaction commits after allocating a later value, the rolled-back value can remain a gap. The guarantee is that persisted sequence state remains consistent with committed database state. ### currval() Returns the last value returned by `nextval()` in the current connection. Raises an error if `nextval()` has not been called on this sequence in the current session. ```sql theme={null} CREATE SEQUENCE my_seq; SELECT nextval('my_seq'); -- 1 SELECT nextval('my_seq'); -- 2 SELECT currval('my_seq'); -- 2 ``` ### setval() Sets the sequence to an explicit value. ```sql theme={null} setval(sequence_name, value) setval(sequence_name, value, is_called) ``` | Parameter | Description | | - | - | | `sequence_name` | The name of the sequence. | | `value` | The value to set. Must be within the sequence's `MINVALUE`..`MAXVALUE` range. | | `is_called` | When `true` (the default), the next `nextval()` will return `value + increment`. When `false`, the next `nextval()` will return `value` itself. | ```sql theme={null} CREATE SEQUENCE my_seq; SELECT setval('my_seq', 100); -- 100 SELECT nextval('my_seq'); -- 101 SELECT setval('my_seq', 200, false); -- 200 SELECT nextval('my_seq'); -- 200 (returns the set value since is_called was false) SELECT nextval('my_seq'); -- 201 ``` ## Examples ### Basic Sequence ```sql theme={null} CREATE SEQUENCE counter; SELECT nextval('counter'); -- 1 SELECT nextval('counter'); -- 2 SELECT nextval('counter'); -- 3 ``` ### Custom Start and Increment ```sql theme={null} CREATE SEQUENCE order_seq START WITH 1000 INCREMENT BY 10; SELECT nextval('order_seq'); -- 1000 SELECT nextval('order_seq'); -- 1010 SELECT nextval('order_seq'); -- 1020 ``` ### Descending Sequence ```sql theme={null} CREATE SEQUENCE countdown START WITH 10 INCREMENT BY -1 MINVALUE 1 MAXVALUE 10; SELECT nextval('countdown'); -- 10 SELECT nextval('countdown'); -- 9 SELECT nextval('countdown'); -- 8 ``` ### Cycling Sequence When the sequence reaches its boundary, it wraps around: ```sql theme={null} CREATE SEQUENCE color_cycle MINVALUE 1 MAXVALUE 3 CYCLE; SELECT nextval('color_cycle'); -- 1 SELECT nextval('color_cycle'); -- 2 SELECT nextval('color_cycle'); -- 3 SELECT nextval('color_cycle'); -- 1 (wraps around) SELECT nextval('color_cycle'); -- 2 ``` Without `CYCLE`, exceeding the boundary raises an error: ```sql theme={null} CREATE SEQUENCE bounded MINVALUE 1 MAXVALUE 3; SELECT nextval('bounded'); -- 1 SELECT nextval('bounded'); -- 2 SELECT nextval('bounded'); -- 3 SELECT nextval('bounded'); -- Error: nextval: reached maximum value of sequence "bounded" ``` ### Sequence as Column Default A sequence can supply default values for a table column: ```sql theme={null} CREATE SEQUENCE invoice_seq START WITH 1000; CREATE TABLE invoices ( id INTEGER PRIMARY KEY, invoice_number INTEGER DEFAULT (nextval('invoice_seq')), description TEXT ); INSERT INTO invoices (description) VALUES ('First order'); INSERT INTO invoices (description) VALUES ('Second order'); INSERT INTO invoices (description) VALUES ('Third order'); SELECT * FROM invoices; -- 1|1000|First order -- 2|1001|Second order -- 3|1002|Third order ``` ### IF NOT EXISTS ```sql theme={null} -- Safe to run multiple times CREATE SEQUENCE IF NOT EXISTS my_seq; CREATE SEQUENCE IF NOT EXISTS my_seq; SELECT nextval('my_seq'); -- 1 ``` ## Concurrency `nextval()` and `setval()` persist sequence state to a backing table. In **WAL mode** (the default), the backing-table write rides on the caller's write transaction. Only one connection can hold the write lock at a time; if another connection is already writing, `SELECT nextval(...)` will return `SQLITE_BUSY` ("Database is busy"). This is different from `AUTOINCREMENT`, which uses a process-local allocator and writes the committed watermark with the table insert. In **MVCC mode** (`journal_mode=experimental_mvcc`), `nextval()` inside a `BEGIN CONCURRENT` outer wraps the backing-table write in an autonomous inner transaction that commits immediately and independently of the outer's eventual commit or rollback. This lets multiple concurrent `BEGIN CONCURRENT` callers each get a fresh value (serialized by the standard MVCC PK conflict + bounded retry on the backing table), and matches PostgreSQL's "burnt value" semantics for this case. For autocommit, `BEGIN`, `BEGIN IMMEDIATE`, and `BEGIN DEFERRED`, the outer holds the exclusive lock and the inner write runs inline — under those modes a `ROLLBACK` un-burns the value (SQLite-like). In rollback cases, generated values are not guaranteed to be reused and are not guaranteed to remain gaps. Reuse depends on the outer transaction mode and on whether another committed transaction advanced the durable watermark past that value. If your workload calls `nextval()` from many concurrent connections in WAL mode, consider using `AUTOINCREMENT` instead, or serializing sequence access at the application level. ## Recovering the current value The "current value" reachable from a fresh connection depends on mode: * **WAL mode**: read `seq` from `sqlite_sequence` (for AUTOINCREMENT tables) or query the sequence backing table directly: ```sql theme={null} SELECT MAX(value) FROM "__turso_internal_seq_"; ``` Both are kept in sync at commit time. * **MVCC mode**: `sqlite_sequence` is updated **only at checkpoint time** — between checkpoints it may lag the actual high-water mark or be empty for a freshly-created AUTOINCREMENT table. Do **not** rely on `sqlite_sequence` to recover the live value in MVCC mode. Instead, query the internal sequence backing table directly: ```sql theme={null} SELECT MAX(value) FROM "__turso_internal_seq_"; ``` For AUTOINCREMENT, the sequence name is `__turso_internal_autoincrement_
`, so the backing-table query becomes: ```sql theme={null} SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_
"; ``` After `PRAGMA wal_checkpoint(TRUNCATE)`, the backing table is coalesced to a single row and `sqlite_sequence` is also brought up to date with exactly one row per AUTOINCREMENT table — at that point both are safe to read with normal SQLite-compatible queries. ## Errors | Error | Cause | | - | - | | `sequence "name" already exists` | A sequence with that name already exists and `IF NOT EXISTS` was not specified. | | `INCREMENT must not be zero` | `INCREMENT BY 0` was specified. | | `MINVALUE (x) must be less than MAXVALUE (y)` | `MINVALUE` is greater than or equal to `MAXVALUE`. | | `START value (x) cannot be less than MINVALUE (y)` | For ascending sequences, `START` is below `MINVALUE`. | | `START value (x) cannot be greater than MAXVALUE (y)` | For descending sequences, `START` is above `MAXVALUE`. | ## See Also * [DROP SEQUENCE](/sql-reference/statements/drop-sequence) for removing sequences * [CREATE TABLE](/sql-reference/statements/create-table) for `AUTOINCREMENT` on `INTEGER PRIMARY KEY` columns # CREATE TABLE Source: https://docs.turso.tech/sql-reference/statements/create-table Create a new table in the database with column definitions, constraints, and table options # CREATE TABLE Create a new table in the database with column definitions, data types, and constraints. ## Syntax ```sql theme={null} CREATE TABLE [IF NOT EXISTS] [schema-name.]table-name ( column-def [, column-def ...] [, table-constraint [, table-constraint ...]] ) [STRICT]; ``` ### Column Definition ```sql theme={null} column-name [type-name] [column-constraint ...] ``` ### Column Constraints ```sql theme={null} PRIMARY KEY [ASC | DESC] [AUTOINCREMENT] NOT NULL UNIQUE DEFAULT expression CHECK (expression) REFERENCES foreign-table [(column-name)] [foreign-key-actions] COLLATE collation-name ``` ### Table Constraints ```sql theme={null} PRIMARY KEY (column-name [, column-name ...]) UNIQUE (column-name [, column-name ...]) CHECK (expression) FOREIGN KEY (column-name [, column-name ...]) REFERENCES foreign-table [(column-name [, column-name ...])] [foreign-key-actions] ``` ## Description `CREATE TABLE` defines a new table in the database. The table name must be unique within the database schema. Each table consists of one or more column definitions and optional table-level constraints. ### Parameters | Parameter | Description | | - | - | | `IF NOT EXISTS` | Prevents an error if a table with the same name already exists. The statement is a no-op when the table is present. | | `schema-name` | The name of an attached database. Defaults to the main database if omitted. | | `table-name` | The name of the table to create. | | `STRICT` | Enables strict type checking on the table. See [STRICT Tables](#strict-tables). | ## Column Definitions Each column definition specifies a column name, an optional type, and zero or more column constraints. When no type is specified, the column accepts any storage class. When a type name is provided, it determines the column's [type affinity](/sql-reference/data-types#type-affinity), which influences how inserted values are stored. ```sql theme={null} CREATE TABLE measurements ( id INTEGER PRIMARY KEY, sensor_name TEXT NOT NULL, reading REAL, raw_data BLOB, notes -- no type: accepts any storage class ); ``` ## Column Constraints ### PRIMARY KEY Designates a column as the table's primary key. A table can have at most one primary key. ```sql theme={null} CREATE TABLE users ( id INTEGER PRIMARY KEY ); ``` When a column is declared as `INTEGER PRIMARY KEY`, it becomes an alias for the internal `rowid`, and inserted integer values are used as the row identifier directly. This is the most efficient primary key form. #### AUTOINCREMENT The `AUTOINCREMENT` keyword can only be used with `INTEGER PRIMARY KEY`. It prevents the reuse of rowid values from previously deleted rows by maintaining a counter in the `sqlite_sequence` system table. ```sql theme={null} CREATE TABLE events ( id INTEGER PRIMARY KEY AUTOINCREMENT, description TEXT ); ``` Without `AUTOINCREMENT`, Turso may reuse rowid values after rows are deleted. With `AUTOINCREMENT`, automatically generated rowids are greater than any rowid that has ever existed in a committed row for the table, even if rows have been deleted. > [!NOTE] > > `AUTOINCREMENT` adds minor overhead because it maintains a committed high-water mark. Rolled-back inserts do not have to permanently burn their generated rowids, but gaps are still possible when concurrent transactions commit later generated values. > [!WARNING] > > **AUTOINCREMENT under MVCC (`journal_mode=experimental_mvcc`)**: `sqlite_sequence` is updated lazily — only when the WAL is checkpointed. Between checkpoints it may lag the actual high-water mark or be empty for a freshly-created AUTOINCREMENT table. Do **not** rely on `sqlite_sequence` to recover the current value in MVCC mode. Instead, query the internal sequence backing table directly: > > ```sql theme={null} > SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_"; > ``` > > This row is updated immediately by every committed (and durably-burnt) `nextval`. After `PRAGMA wal_checkpoint(TRUNCATE)`, `sqlite_sequence` is coalesced to a single row per AUTOINCREMENT table and is then safe to read with normal SQLite-compatible queries. > > Note also that MVCC autoincrement does **not** un-burn rolled-back rowids — a `ROLLBACK` after `INSERT` leaves a gap rather than recycling the rowid. SQLite WAL mode and Turso's WAL mode both un-burn on rollback; only MVCC mode keeps them burnt. ### NOT NULL Prevents the column from containing NULL values. Any INSERT or UPDATE that would set the column to NULL raises a constraint error. ```sql theme={null} CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL ); ``` ### UNIQUE Ensures that all values in the column are distinct. NULL values are considered distinct from each other (multiple NULL values are allowed in a UNIQUE column). ```sql theme={null} CREATE TABLE accounts ( id INTEGER PRIMARY KEY, email TEXT UNIQUE ); ``` ### DEFAULT Specifies a default value for the column when an INSERT statement does not provide one. ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, status TEXT DEFAULT 'pending', created_at TEXT DEFAULT CURRENT_TIMESTAMP, quantity INTEGER DEFAULT 1 ); ``` The default expression can be: | Expression | Example | | - | - | | A literal value | `DEFAULT 42`, `DEFAULT 'active'` | | NULL | `DEFAULT NULL` | | `CURRENT_TIME` | Current time as `HH:MM:SS` | | `CURRENT_DATE` | Current date as `YYYY-MM-DD` | | `CURRENT_TIMESTAMP` | Current datetime as `YYYY-MM-DD HH:MM:SS` | | A parenthesized expression | `DEFAULT (1 + 1)` | ### CHECK Defines a boolean expression that must evaluate to true (or NULL) for every row in the table. The expression can reference any column in the same row. ```sql theme={null} CREATE TABLE employees ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER CHECK (age >= 18), salary REAL CHECK (salary > 0) ); ``` ### REFERENCES (Foreign Key) Establishes a foreign key relationship between a column and a column in another table. See [Foreign Key Constraints](#foreign-key-constraints) for the full syntax. ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER REFERENCES customers(id) ); ``` ### COLLATE Specifies the collation sequence used for text comparisons and sorting on the column. | Collation | Description | | - | - | | `BINARY` | Compares text using the raw byte values (default) | | `NOCASE` | Case-insensitive comparison for ASCII characters | | `RTRIM` | Like `BINARY`, but ignores trailing spaces | Turso also resolves locale-aware collations and custom collations registered on the connection. An unknown collation name now raises a `no such collation sequence` error rather than being silently ignored. ```sql theme={null} CREATE TABLE contacts ( id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE, code TEXT COLLATE RTRIM ); ``` ## Table Constraints Table constraints apply to one or more columns and are specified after all column definitions. ### Composite PRIMARY KEY Defines a primary key spanning multiple columns. Each combination of values across the key columns must be unique. ```sql theme={null} CREATE TABLE enrollment ( student_id INTEGER, course_id INTEGER, semester TEXT, grade REAL, PRIMARY KEY (student_id, course_id, semester) ); ``` ### Composite UNIQUE Ensures that each combination of values across the specified columns is unique. ```sql theme={null} CREATE TABLE assignments ( employee_id INTEGER, project_id INTEGER, role TEXT, UNIQUE (employee_id, project_id) ); ``` ### Table-Level CHECK A CHECK constraint at the table level can reference multiple columns. ```sql theme={null} CREATE TABLE reservations ( id INTEGER PRIMARY KEY, start_date TEXT NOT NULL, end_date TEXT NOT NULL, CHECK (end_date > start_date) ); ``` ### Foreign Key Constraints Foreign key constraints enforce referential integrity between tables. They ensure that values in the child table correspond to existing values in the parent table. > [!NOTE] > > Foreign key enforcement is off by default. Enable it with `PRAGMA foreign_keys = ON;` before performing operations that should be checked. #### Syntax ```sql theme={null} -- Column constraint form (single column) column-name type REFERENCES parent-table(parent-column) [ON DELETE action] [ON UPDATE action] [DEFERRABLE INITIALLY DEFERRED] -- Table constraint form (single or multiple columns) FOREIGN KEY (column-name [, column-name ...]) REFERENCES parent-table(parent-column [, parent-column ...]) [ON DELETE action] [ON UPDATE action] [DEFERRABLE INITIALLY DEFERRED] ``` #### Foreign Key Actions The `ON DELETE` and `ON UPDATE` clauses specify what happens to child rows when a referenced parent row is deleted or updated. | Action | Description | | - | - | | `NO ACTION` | Raises an error if the foreign key constraint is violated (default) | | `RESTRICT` | Same as `NO ACTION`, but checked immediately rather than at statement end | | `CASCADE` | Deletes or updates all child rows that reference the parent row | | `SET NULL` | Sets the foreign key columns in child rows to NULL | | `SET DEFAULT` | Sets the foreign key columns in child rows to their default values | ```sql theme={null} CREATE TABLE departments ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE employees ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, dept_id INTEGER, manager_id INTEGER, FOREIGN KEY (dept_id) REFERENCES departments(id) ON DELETE CASCADE, FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL ); ``` ## STRICT Tables STRICT tables enforce type checking at the storage layer. Every value inserted into a STRICT column must match the declared column type or be losslessly convertible to that type. ```sql theme={null} CREATE TABLE sensor_data ( id INTEGER PRIMARY KEY, reading REAL NOT NULL, label TEXT, payload BLOB ) STRICT; ``` ### Allowed Column Types STRICT tables only allow these base column types: | Type | Description | | - | - | | `INTEGER` | Signed integer | | `REAL` | Floating-point number | | `TEXT` | UTF-8 string | | `BLOB` | Raw binary data | | `ANY` | Any storage class (disables type checking for this column) | Every column in a STRICT table must have an explicit type declaration. ```sql theme={null} -- This fails: STRICT tables require explicit types on every column CREATE TABLE bad ( id INTEGER PRIMARY KEY, data -- error: missing type ) STRICT; ``` > [!NOTE] > > **Turso Extension**: STRICT tables also support custom types defined with [CREATE TYPE](/sql-reference/statements/create-type) and [array types](/sql-reference/data-types#array-types). Custom types extend the type system with user-defined encoding, decoding, validation, and operator overloading. Array columns are declared by appending `[]` to a base type. ```sql theme={null} CREATE TABLE events ( id uuid PRIMARY KEY, name varchar(100) NOT NULL, event_date date, is_active boolean DEFAULT 1, tags TEXT[] ) STRICT; ``` ## Unsupported Features The following CREATE TABLE features are not yet supported: | Feature | Notes | | - | - | | `CREATE TEMPORARY TABLE` | Temporary tables are not supported | | `CREATE TABLE ... AS SELECT` | Creating a table from a query result is not supported | | `ON CONFLICT` clause on column constraints | The column-level ON CONFLICT clause (e.g., `NOT NULL ON CONFLICT REPLACE`) is not supported. Use the [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) syntax instead | ### Experimental Features The following are available behind [experimental feature flags](/sql-reference/experimental-features): | Feature | Flag | Notes | | - | - | - | | `WITHOUT ROWID` | `--experimental-without-rowid` | WITHOUT ROWID tables have limitations; see the experimental features page | | `GENERATED ALWAYS AS` | `--experimental-generated-columns` | Virtual generated (computed) columns only; stored columns are not yet supported | ## Examples ### Basic Table with Constraints ```sql theme={null} CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE, display_name TEXT, bio TEXT DEFAULT '', created_at TEXT DEFAULT CURRENT_TIMESTAMP ); ``` ### Table with Foreign Keys and CHECK Constraints ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL, total_amount REAL NOT NULL CHECK (total_amount >= 0), status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'shipped', 'delivered', 'cancelled')), order_date TEXT NOT NULL DEFAULT CURRENT_DATE, FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE RESTRICT ); ``` ### STRICT Table ```sql theme={null} CREATE TABLE inventory ( product_id INTEGER PRIMARY KEY, sku TEXT NOT NULL UNIQUE, quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0), price REAL NOT NULL CHECK (price > 0), category TEXT NOT NULL ) STRICT; -- This succeeds: '42' is losslessly convertible to INTEGER INSERT INTO inventory VALUES (1, 'WIDGET-001', '42', 9.99, 'widgets'); -- This fails: 'abc' cannot be converted to INTEGER INSERT INTO inventory VALUES (2, 'GADGET-001', 'abc', 19.99, 'gadgets'); -- Error: cannot store TEXT value in INTEGER column ``` ### Composite Primary Key with Foreign Keys ```sql theme={null} CREATE TABLE course_enrollment ( student_id INTEGER NOT NULL, course_id INTEGER NOT NULL, enrolled_date TEXT DEFAULT CURRENT_DATE, grade REAL CHECK (grade >= 0.0 AND grade <= 4.0), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE ); ``` ### IF NOT EXISTS ```sql theme={null} -- Safe to run multiple times CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, token TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); ``` ## See Also * [ALTER TABLE](/sql-reference/statements/alter-table) for modifying existing tables * [DROP TABLE](/sql-reference/statements/drop-table) for removing tables * [Data Types](/sql-reference/data-types) for type affinity and STRICT table types * [CREATE INDEX](/sql-reference/statements/create-index) for indexing table columns * [CREATE TYPE](/sql-reference/statements/create-type) for custom types in STRICT tables * [INSERT](/sql-reference/statements/insert) for adding rows to a table # CREATE TRIGGER Source: https://docs.turso.tech/sql-reference/statements/create-trigger Create a trigger that executes statements automatically in response to table changes # CREATE TRIGGER Create a trigger that automatically executes one or more SQL statements when a row is inserted into, updated in, or deleted from a table. ## Syntax ```sql theme={null} CREATE [TEMPORARY] TRIGGER [IF NOT EXISTS] [schema-name.]trigger-name [BEFORE | AFTER | INSTEAD OF] {INSERT | UPDATE [OF column-name, ...] | DELETE} ON table-name [FOR EACH ROW] [WHEN expression] BEGIN statement; [statement; ...] END; ``` ## Description A trigger defines a set of SQL statements that run automatically when a specified data modification event occurs on a table or view. Triggers execute within the same transaction as the statement that fired them -- if the transaction is rolled back, the trigger's effects are also rolled back. ### Parameters | Parameter | Description | | - | - | | `TEMPORARY` | Creates the trigger in a temporary database. The trigger is visible only to the current connection and is dropped when the connection closes. `TEMP` is accepted as a synonym. | | `IF NOT EXISTS` | Prevents an error if a trigger with the same name already exists. | | `schema-name` | The name of the attached database containing the trigger. Defaults to the main database if omitted. Cannot be used with `TEMPORARY`. | | `trigger-name` | A unique name for the trigger within the database. | | `BEFORE` / `AFTER` / `INSTEAD OF` | When the trigger fires relative to the triggering event. Default is `BEFORE`. `INSTEAD OF` triggers are not yet supported. | | `INSERT` / `UPDATE` / `DELETE` | The data modification event that fires the trigger. | | `OF column-name, ...` | For UPDATE triggers only. Restricts the trigger to fire only when the specified columns are modified. | | `table-name` | The table (or view, for INSTEAD OF triggers) that the trigger monitors. | | `FOR EACH ROW` | The trigger fires once per modified row. This is the only supported mode and is the default even if omitted. | | `WHEN expression` | An optional condition. The trigger body executes only for rows where the expression evaluates to true. | ## Trigger Timing ### BEFORE Triggers A `BEFORE` trigger fires before the triggering statement modifies the row. Use BEFORE triggers to validate or transform data before it is written. * The `NEW` row reference contains the values that are about to be written. For INSERT triggers, `NEW` is the row being inserted. For UPDATE triggers, `NEW` contains the updated values. * The `OLD` row reference is available in UPDATE and DELETE triggers and contains the current values before modification. * If a BEFORE trigger raises an error, the triggering operation is aborted for that row. ### AFTER Triggers An `AFTER` trigger fires after the triggering statement has modified the row. Use AFTER triggers for logging, auditing, or cascading changes to other tables. * The `NEW` and `OLD` row references are available with the same semantics as BEFORE triggers. * The row has already been written when the trigger body executes. ### INSTEAD OF Triggers > [!WARNING] > > `INSTEAD OF` triggers are not yet supported in Turso. `CREATE TRIGGER ... INSTEAD OF ...` returns an error. In SQLite, an `INSTEAD OF` trigger can only be created on a view and fires in place of the triggering INSERT, UPDATE, or DELETE, allowing you to make views writable. Turso parses this syntax but does not yet execute it. ## Row References Inside a trigger body, `NEW` and `OLD` are special row references that provide access to column values. | Reference | INSERT | UPDATE | DELETE | | - | - | - | - | | `NEW.column` | Value being inserted | Updated value | Not available | | `OLD.column` | Not available | Value before update | Value being deleted | ```sql theme={null} -- Access individual columns NEW.email OLD.status ``` ## WHEN Clause The optional `WHEN` clause filters which rows cause the trigger body to execute. The expression can reference `NEW` and `OLD` columns. ```sql theme={null} -- Only fire when the status column actually changes CREATE TRIGGER log_status_change AFTER UPDATE OF status ON orders WHEN OLD.status != NEW.status BEGIN INSERT INTO order_log (order_id, old_status, new_status, changed_at) VALUES (NEW.id, OLD.status, NEW.status, datetime('now')); END; ``` ## UPDATE OF Columns For UPDATE triggers, you can restrict the trigger to fire only when specific columns are modified. Without the `OF` clause, the trigger fires on any UPDATE to the table. ```sql theme={null} -- Only fires when price or quantity changes, not when name changes CREATE TRIGGER recalc_total BEFORE UPDATE OF price, quantity ON line_items BEGIN UPDATE line_items SET total = NEW.price * NEW.quantity WHERE id = NEW.id; END; ``` ## RAISE Function The `RAISE` function is used inside trigger bodies (and other contexts) to interrupt execution and signal an error. It takes one of four forms: | Form | Behavior | | - | - | | `RAISE(IGNORE)` | Skips the remainder of the trigger body and the triggering statement for the current row. Processing continues with the next row. | | `RAISE(ABORT, message)` | Aborts the current statement and rolls back any changes made by that statement, but preserves prior changes in the transaction. This is the default error handling behavior. | | `RAISE(ROLLBACK, message)` | Aborts the current statement and rolls back the entire transaction. | | `RAISE(FAIL, message)` | Aborts the current statement. Changes already made by the statement (previous rows) are preserved, but the current row and subsequent rows are not processed. | ```sql theme={null} CREATE TRIGGER validate_age BEFORE INSERT ON users BEGIN SELECT RAISE(ABORT, 'age must be positive') WHERE NEW.age <= 0; END; ``` ## Multiple Statements A trigger body can contain multiple SQL statements separated by semicolons. The statements execute in order within the same transaction. ```sql theme={null} CREATE TRIGGER on_user_delete AFTER DELETE ON users BEGIN DELETE FROM user_preferences WHERE user_id = OLD.id; DELETE FROM user_sessions WHERE user_id = OLD.id; INSERT INTO audit_log (action, entity, entity_id, performed_at) VALUES ('delete', 'user', OLD.id, datetime('now')); END; ``` ## Examples ### Audit Logging Trigger Track all changes to a table with an audit log. ```sql theme={null} CREATE TABLE accounts ( id INTEGER PRIMARY KEY, owner TEXT NOT NULL, balance REAL NOT NULL DEFAULT 0 ); CREATE TABLE account_audit ( id INTEGER PRIMARY KEY, account_id INTEGER NOT NULL, action TEXT NOT NULL, old_balance REAL, new_balance REAL, changed_at TEXT NOT NULL ); CREATE TRIGGER audit_balance_change AFTER UPDATE OF balance ON accounts BEGIN INSERT INTO account_audit (account_id, action, old_balance, new_balance, changed_at) VALUES (NEW.id, 'update', OLD.balance, NEW.balance, datetime('now')); END; -- This UPDATE automatically creates an audit row UPDATE accounts SET balance = balance + 100 WHERE id = 1; ``` ### Validation Trigger Enforce business rules before data is written. ```sql theme={null} CREATE TABLE reservations ( id INTEGER PRIMARY KEY, room_id INTEGER NOT NULL, check_in TEXT NOT NULL, check_out TEXT NOT NULL ); CREATE TRIGGER validate_reservation BEFORE INSERT ON reservations BEGIN SELECT RAISE(ABORT, 'check_out must be after check_in') WHERE NEW.check_out <= NEW.check_in; END; ``` ### Cascading Update Trigger Propagate changes to related tables. ```sql theme={null} CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, category_id INTEGER ); CREATE TABLE categories ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); -- When a category is deleted, unset the category on all associated products CREATE TRIGGER clear_product_category BEFORE DELETE ON categories BEGIN UPDATE products SET category_id = NULL WHERE category_id = OLD.id; END; ``` ## See Also * [DROP TRIGGER](/sql-reference/statements/drop-trigger) for removing triggers * [CREATE VIEW](/sql-reference/statements/create-view) for views that can use INSTEAD OF triggers * [INSERT](/sql-reference/statements/insert), [UPDATE](/sql-reference/statements/update), [DELETE](/sql-reference/statements/delete) for the statements that fire triggers # CREATE TYPE Source: https://docs.turso.tech/sql-reference/statements/create-type Define a custom type with encoding, decoding, validation, operators, and defaults for use in STRICT tables # CREATE TYPE > [!NOTE] > > **Turso Extension**: CREATE TYPE is a Turso-specific statement not available in standard SQLite. Custom types require STRICT tables. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). The CREATE TYPE statement defines a user-defined type that controls how values are encoded before storage, decoded when read, validated on input, compared for ordering, and what default value to use. Custom types extend the STRICT table type system beyond the five built-in SQLite storage classes. ## Syntax ```sql theme={null} CREATE TYPE [IF NOT EXISTS] type-name [(parameters)] BASE base-type ENCODE encode-expr DECODE decode-expr [OPERATOR 'op' [function-name] ...] [DEFAULT default-expr]; ``` ## Description A custom type wraps one of the four base storage types with user-defined logic. When a value is written to a column of a custom type, the ENCODE expression transforms it before storage. When a value is read, the DECODE expression transforms it back. This lets you store data in an efficient on-disk representation while presenting a different form to queries. Custom types work only with STRICT tables. Using a custom type name in a non-STRICT table has no effect. ## Clauses ### IF NOT EXISTS Suppresses the error that would occur if a type with the same name already exists. The existing type is left unchanged. ```sql theme={null} CREATE TYPE IF NOT EXISTS cents BASE integer ENCODE value * 100 DECODE value / 100; ``` ### BASE Specifies the underlying SQLite storage class used to store values on disk. Every custom type must have a BASE clause. | Base Type | Description | | - | - | | `integer` | Signed integer, stored in 1-8 bytes depending on magnitude | | `real` | 8-byte IEEE 754 floating-point number | | `text` | UTF-8 encoded string | | `blob` | Raw binary data | ```sql theme={null} CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100; ``` ### ENCODE / DECODE The ENCODE expression is evaluated whenever a value is written to a column of this type. The DECODE expression is evaluated whenever a value is read. Both expressions use the identifier `value` to refer to the input. * **ENCODE**: Transforms the input value into the base storage form. Runs on INSERT, UPDATE, and CAST. * **DECODE**: Transforms the stored value back into the presentation form. Runs on SELECT. * **NULL handling**: NULL values bypass both ENCODE and DECODE. A NULL input produces a NULL output without evaluating the expression. ```sql theme={null} -- Store text in reversed form CREATE TYPE reversed_text BASE text ENCODE turso_reverse(value) DECODE turso_reverse(value); ``` The ENCODE expression is the place to put validation logic. If the ENCODE expression raises an error, the INSERT or UPDATE is aborted. ### Parameters Custom types can accept parameters that are available in the ENCODE and DECODE expressions. Parameters are declared as a parenthesized list after the type name. Each parameter has a name and an optional type annotation. ```sql theme={null} CREATE TYPE varchar(value text, maxlen integer) BASE text ENCODE CASE WHEN length(value) <= maxlen THEN value ELSE RAISE(ABORT, 'value too long for varchar') END DECODE value; ``` When a column uses a parametric type, the arguments are supplied in parentheses after the type name: ```sql theme={null} CREATE TABLE users ( id INTEGER PRIMARY KEY, name varchar(100), bio varchar(500) ) STRICT; ``` The first parameter is always the input `value`. Additional parameters are the arguments provided in the column type declaration, in order. ### OPERATOR The OPERATOR clause maps SQL operators to functions. This allows columns of a custom type to participate in comparisons, arithmetic, and ordering. ```sql theme={null} OPERATOR 'op' function-name ``` | Component | Description | | - | - | | `'op'` | The operator symbol as a string literal: `'+'`, `'-'`, `'*'`, `'/'`, `'<'`, `'='`, etc. | | `function-name` | The name of a SQL function to call when this operator is used. Omit to use the base type's native comparison. | When an operator is defined, expressions like `column + 1` are rewritten into `function_name(column, 1)`. #### Ordering with OPERATOR '\<' The `<` operator is special: it controls how values of this type are sorted in ORDER BY, MIN, MAX, and CREATE INDEX. * **`OPERATOR '<'`** (no function name): Uses the base type's native comparison for ordering. This is sufficient for types where the encoded form sorts correctly (e.g., text dates in ISO 8601 format). * **`OPERATOR '<' my_compare`**: Uses `my_compare(a, b)` as a custom comparator. The function must return a negative integer if `a < b`, zero if `a = b`, and a positive integer if `a > b`. * **No `<` operator**: The type cannot be used in ORDER BY or CREATE INDEX. ```sql theme={null} -- Use base type comparison for ordering CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100 OPERATOR '<'; -- Full operator set for a numeric type CREATE TYPE uint BASE integer ENCODE CASE WHEN value >= 0 THEN value ELSE RAISE(ABORT, 'uint must be non-negative') END DECODE value OPERATOR '+' uint_add OPERATOR '-' uint_sub OPERATOR '*' uint_mul OPERATOR '<' OPERATOR '='; ``` The `=` operator, when defined, is also used to derive `!=`. The `<` operator is used to derive `>`, `>=`, and `<=` automatically by swapping arguments or negating the result. ### DEFAULT The DEFAULT clause sets a type-level default value. When a column of this type does not specify its own DEFAULT, this value is used. ```sql theme={null} CREATE TYPE boolean BASE integer ENCODE boolean_to_int(value) DECODE CASE WHEN value THEN 1 ELSE 0 END OPERATOR '<' DEFAULT 0; ``` A column-level DEFAULT overrides the type-level DEFAULT: ```sql theme={null} CREATE TABLE flags ( id INTEGER PRIMARY KEY, is_active boolean, -- uses type default (0) is_admin boolean DEFAULT 1 -- overrides type default ) STRICT; ``` ## Validation with RAISE Use CASE/WHEN with RAISE in the ENCODE expression to validate input at write time. If the condition fails, the statement is aborted with the specified error message. ```sql theme={null} CREATE TYPE positive_int BASE integer ENCODE CASE WHEN value > 0 THEN value ELSE RAISE(ABORT, 'value must be positive') END DECODE value; ``` ```sql theme={null} CREATE TYPE email BASE text ENCODE CASE WHEN value LIKE '%_@_%.__%' THEN lower(value) ELSE RAISE(ABORT, 'invalid email address') END DECODE value; ``` ## CAST Support The CAST expression applies a custom type's ENCODE logic: ```sql theme={null} CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100; SELECT CAST(42.5 AS cents); -- 4250 ``` This is useful for converting values to the encoded form outside of INSERT/UPDATE, such as in WHERE clauses and CHECK constraints. ## CHECK Constraints with Custom Types In STRICT tables with custom types, CHECK constraints operate on the **decoded** (presentation) values. When comparing a column of a custom type against a literal, use CAST to encode the literal: ```sql theme={null} CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100; CREATE TABLE products ( id INTEGER PRIMARY KEY, price cents CHECK(price >= CAST(0 AS cents)) ) STRICT; ``` ## Inspecting Types ### PRAGMA list\_types Lists all available types, including built-in and user-defined: ```sql theme={null} PRAGMA list_types; -- name -- boolean -- varchar -- date -- time -- timestamp -- smallint -- numeric -- ... ``` ### sqlite\_turso\_types Virtual Table The `sqlite_turso_types` virtual table provides the name and SQL definition of each type: ```sql theme={null} SELECT name, sql FROM sqlite_turso_types; -- name | sql -- boolean | CREATE TYPE boolean(value any) BASE integer ENCODE ... -- ... ``` ## Using with ALTER TABLE Custom types can be used when adding columns with ALTER TABLE ADD COLUMN: ```sql theme={null} ALTER TABLE users ADD COLUMN email varchar(255); ``` The added column follows the same STRICT type-checking rules as columns defined in the original CREATE TABLE. ## Built-in Types Turso provides the following built-in custom types in STRICT tables: | Type | Base | Parameters | Description | | - | - | - | - | | `boolean` | integer | -- | Constrained to 0 or 1. Aliases: `bool`. | | `smallint` | integer | -- | Integer constrained to -32768 through 32767. Aliases: `int2`. | | `bigint` | integer | -- | Integer (no range constraint). Aliases: `int8`. | | `varchar(N)` | text | `maxlen` | Text with a maximum length of N characters. | | `date` | text | -- | ISO 8601 date (YYYY-MM-DD), validated on insert. | | `time` | text | -- | ISO 8601 time (HH:MM:SS), validated on insert. | | `timestamp` | text | -- | ISO 8601 datetime, validated on insert. | | `numeric(P,S)` | blob | `precision`, `scale` | Fixed-point decimal with P total digits and S decimal places. Supports arithmetic operators. | | `uuid` | blob | -- | UUID stored as 16-byte blob, displayed as string. Default: `uuid4_str()`. | | `inet` | text | -- | Validated IP address (IPv4 or IPv6). | | `bytea` | blob | -- | Binary data (PostgreSQL-compatible alias). | | `json` | text | -- | Validated JSON text. | | `jsonb` | blob | -- | JSON stored in binary format, returned as JSON text on read. | > [!NOTE] > > The `uuid`, `json`, and `jsonb` types require their respective extensions to be compiled in. They are available by default in standard Turso builds. ## Composite Types: STRUCT and UNION In addition to encode/decode custom types, CREATE TYPE supports two composite type forms: **STRUCT** (named product type) and **UNION** (discriminated union / tagged variant). Both store data as blobs on disk and require STRICT tables. ### STRUCT A STRUCT groups multiple named fields into a single column. Use dot notation to read and filter on individual fields: ```sql theme={null} CREATE TYPE point AS STRUCT(x INT, y INT); CREATE TABLE locations ( id INTEGER PRIMARY KEY, pos point ) STRICT; INSERT INTO locations VALUES (1, struct_pack(10, 20)); INSERT INTO locations VALUES (2, struct_pack(30, 40)); -- Read fields with dot notation SELECT pos.x, pos.y FROM locations WHERE id = 1; -- 10|20 -- Filter and order by fields SELECT id, pos.x FROM locations WHERE pos.y > 25 ORDER BY pos.x; -- 2|30 ``` Dot notation works everywhere a column expression does — SELECT, WHERE, ORDER BY, GROUP BY, HAVING, and aggregate functions: ```sql theme={null} CREATE TYPE address AS STRUCT(street TEXT, city TEXT, zip TEXT); CREATE TABLE people(name TEXT, home address) STRICT; INSERT INTO people VALUES ('Alice', struct_pack('123 Main St', 'Springfield', '62704')); INSERT INTO people VALUES ('Bob', struct_pack('456 Oak Ave', 'Springfield', '62701')); SELECT home.city, COUNT(*) FROM people GROUP BY home.city; -- Springfield|2 ``` When a table name and column name collide, table references always win. Use an alias to reach the struct field: ```sql theme={null} -- t.x here is table.column, not column.field SELECT t.x FROM t; -- Use an alias to access the struct field SELECT s.pos.x FROM locations AS s; ``` #### struct\_pack() Creates a struct value for INSERT and UPDATE. Arguments are positional, matching the field order in the type definition. ```sql theme={null} INSERT INTO locations VALUES (1, struct_pack(10, 20)); UPDATE locations SET pos = struct_pack(99, 88) WHERE id = 1; ``` #### struct\_extract() Function form of dot notation — `struct_extract(col, 'field')` is equivalent to `col.field`. Primarily useful in expression indexes, where dot notation cannot be used: ```sql theme={null} CREATE INDEX idx_x ON locations(struct_extract(pos, 'x')); ``` ### UNION A UNION is a discriminated union (tagged variant). Each value carries exactly one of the declared variants, identified by a tag. Use dot notation to extract a variant's value — it returns NULL when the active variant doesn't match: ```sql theme={null} CREATE TYPE platform_id AS UNION(telegram INT, slack TEXT, signal TEXT); CREATE TABLE contacts ( id INTEGER PRIMARY KEY, platform platform_id ) STRICT; INSERT INTO contacts VALUES (1, union_value('telegram', 12345)); INSERT INTO contacts VALUES (2, union_value('slack', 'U0ABC')); INSERT INTO contacts VALUES (3, union_value('signal', '+1555')); -- Dot notation extracts the variant value (NULL if tag doesn't match) SELECT id, platform.telegram, platform.slack FROM contacts ORDER BY id; -- 1|12345| -- 2||U0ABC -- 3|| -- Combine with union_tag() to filter by variant SELECT id, platform.slack FROM contacts WHERE union_tag(platform) = 'slack'; -- 2|U0ABC ``` For unions whose variants are struct types, chain dot access to reach nested fields: ```sql theme={null} CREATE TYPE telegram_msg AS STRUCT(chat_id INT, text TEXT); CREATE TYPE msg AS UNION(telegram telegram_msg, slack TEXT); CREATE TABLE messages(id INT, data msg) STRICT; INSERT INTO messages VALUES (1, union_value('telegram', struct_pack(100, 'hello'))); -- col.variant.field SELECT data.telegram.chat_id, data.telegram.text FROM messages; -- 100|hello ``` #### union\_value() Creates a union value for INSERT and UPDATE. The first argument is the variant tag (a string literal), the second is the value. The tag is resolved against the target column's union type. ```sql theme={null} INSERT INTO contacts VALUES (1, union_value('telegram', 12345)); UPDATE contacts SET platform = union_value('slack', 'U0XYZ') WHERE id = 1; ``` #### union\_tag() Returns the tag name of the active variant as text. ```sql theme={null} SELECT id, union_tag(platform) FROM contacts ORDER BY id; -- 1|telegram -- 2|slack -- 3|signal ``` #### union\_extract() Function form of dot notation — `union_extract(col, 'variant')` is equivalent to `col.variant`. Primarily useful in expression indexes: ```sql theme={null} CREATE INDEX idx_tg ON contacts(union_extract(platform, 'telegram')); ``` ### NULL handling NULL values propagate through all struct and union operations: ```sql theme={null} CREATE TYPE number AS UNION(i INT, f REAL); CREATE TABLE values_t(id INT, val number) STRICT; INSERT INTO values_t VALUES (1, union_value('i', 42)); INSERT INTO values_t VALUES (2, union_value('f', 3.14)); INSERT INTO values_t VALUES (3, NULL); SELECT id, val.i, val.f FROM values_t ORDER BY id; -- 1|42| -- 2||3.14 -- 3|| ``` ### Dot notation precedence When a dot expression `a.b` is ambiguous (e.g., `a` could be a table name or a column name), table references always take priority over struct field access. This follows DuckDB's resolution rules. ```sql theme={null} CREATE TYPE point AS STRUCT(x INT, y INT); CREATE TABLE t(x INT, t point) STRICT; INSERT INTO t VALUES (99, struct_pack(10, 20)); -- t.x resolves as table.column (99), not column.field (10) SELECT t.x FROM t; -- 99 -- Use an alias to reach the struct field SELECT s.t.x FROM t AS s; -- 10 ``` For `a.b.c`, the resolution order is: database.table.column, then table.column.field. ### Inline types not supported Inline STRUCT/UNION declarations in column definitions are not supported. Always use CREATE TYPE first: ```sql theme={null} -- This will error: CREATE TABLE t(data STRUCT(x INT, y INT)) STRICT; -- Do this instead: CREATE TYPE point AS STRUCT(x INT, y INT); CREATE TABLE t(data point) STRICT; ``` ## Array Types Array columns store ordered collections of values. Unlike STRUCT and UNION, arrays don't require an explicit CREATE TYPE — declare them by appending `[]` to any base type name in a STRICT table column definition: ```sql theme={null} CREATE TABLE sensors ( id INTEGER PRIMARY KEY, readings REAL[], labels TEXT[], flags INTEGER[] ) STRICT; INSERT INTO sensors VALUES (1, ARRAY[1.5, 2.5, 3.5], ARRAY['temp','humidity'], '[0, 1, 1]'); SELECT readings[0], labels[1], array_length(flags) FROM sensors; -- 1.5|humidity|3 ``` Multi-dimensional arrays use multiple bracket pairs: ```sql theme={null} CREATE TABLE matrices ( id INTEGER PRIMARY KEY, data INTEGER[][] ) STRICT; INSERT INTO matrices VALUES (1, ARRAY[ARRAY[1,2], ARRAY[3,4]]); SELECT data[1][0] FROM matrices; -- 3 ``` Element types are validated on insert — an `INTEGER[]` column rejects values that cannot be stored as integers. Arrays are stored internally as compact record-format BLOBs and displayed as JSON arrays on output. For the full set of array functions (`array_agg`, `array_append`, `array_contains`, `array_slice`, etc.), operators (`@>`, `&&`, `||`), and subscript syntax, see [Array Functions](/sql-reference/functions/array). ## Restrictions * **STRICT tables only**: Custom type names are ignored in non-STRICT tables. The column uses standard type affinity rules instead. * **Cannot drop while in use**: A type cannot be dropped with DROP TYPE while any table has a column of that type. > [!NOTE] > > If a non-STRICT table was created with a type name that did not exist at the time (e.g., `CREATE TABLE t(x mytype)`), and a custom type with that name is created afterwards (`CREATE TYPE mytype ...`), the type's ENCODE/DECODE logic will **not** be retroactively applied to the existing table. This is not a bug: non-STRICT tables treat all type names as plain affinity hints regardless of whether a matching custom type exists. STRICT tables prevent this scenario because they reject unknown type names at CREATE TABLE time. * **No subqueries in expressions**: The ENCODE, DECODE, and DEFAULT expressions cannot contain subqueries, aggregate functions, or window functions. * **No indexes on STRUCT/UNION columns**: CREATE INDEX on a STRUCT or UNION column is not supported. ## Examples ### Identity Type (Passthrough) A minimal type that stores and retrieves values without transformation: ```sql theme={null} CREATE TYPE my_text BASE text ENCODE value DECODE value; CREATE TABLE notes ( id INTEGER PRIMARY KEY, body my_text ) STRICT; ``` ### Monetary Values as Cents Store dollar amounts as integer cents for exact arithmetic, present them as the original value: ```sql theme={null} CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100 OPERATOR '<'; CREATE TABLE invoices ( id INTEGER PRIMARY KEY, amount cents ) STRICT; INSERT INTO invoices VALUES (1, 42); -- Stored as 4200 on disk, queried as 42 SELECT amount FROM invoices; -- 42 ``` ### JSON Validation Validate that inserted values are valid JSON: ```sql theme={null} CREATE TYPE json BASE text ENCODE json(value) DECODE value; CREATE TABLE configs ( id INTEGER PRIMARY KEY, data json ) STRICT; INSERT INTO configs VALUES (1, '{"key": "value"}'); -- succeeds INSERT INTO configs VALUES (2, 'not json'); -- Error: malformed JSON ``` ### Unsigned Integer with Operators A non-negative integer type with arithmetic and comparison operators: ```sql theme={null} CREATE TYPE uint BASE integer ENCODE CASE WHEN value >= 0 THEN value ELSE RAISE(ABORT, 'uint must be non-negative') END DECODE value OPERATOR '<' OPERATOR '='; CREATE TABLE counters ( id INTEGER PRIMARY KEY, count uint DEFAULT 0 ) STRICT; INSERT INTO counters VALUES (1, 5); SELECT * FROM counters WHERE count > 0 ORDER BY count; ``` ### Fixed-Point Decimal Use the built-in `numeric` type for precise decimal arithmetic: ```sql theme={null} CREATE TABLE transactions ( id INTEGER PRIMARY KEY, amount numeric(10, 2), tax numeric(10, 2) ) STRICT; INSERT INTO transactions VALUES (1, 99.99, 8.25); SELECT amount + tax FROM transactions; -- 108.24 ``` ## See Also * [DROP TYPE](/sql-reference/statements/drop-type) for removing custom types * [CREATE DOMAIN](/sql-reference/statements/create-domain) for defining constrained type aliases without ENCODE/DECODE logic * [Data Types](/sql-reference/data-types) for an overview of the type system and type affinity * [Array Types](/sql-reference/data-types#array-types) for native array columns (`INTEGER[]`, `TEXT[]`, etc.) * [Array Functions](/sql-reference/functions/array) for array construction, manipulation, and operators * [CREATE TABLE](/sql-reference/statements/create-table) for STRICT table definitions # CREATE VIEW Source: https://docs.turso.tech/sql-reference/statements/create-view Create a named view based on a SELECT statement # CREATE VIEW Create a named view that encapsulates a SELECT statement. ## Syntax ```sql theme={null} CREATE [TEMPORARY] VIEW [IF NOT EXISTS] [schema-name.]view-name AS select-statement; ``` ## Description A view is a virtual table defined by a SELECT statement. It does not store data on disk -- every time you query the view, Turso executes the underlying SELECT. Views simplify complex queries, provide a stable interface over evolving table schemas, and restrict which columns or rows are visible. ### Parameters | Parameter | Description | | - | - | | `TEMPORARY` | Creates the view in a temporary database. The view is visible only to the current connection and is dropped when the connection closes. `TEMP` is accepted as a synonym. | | `IF NOT EXISTS` | Prevents an error if a view with the same name already exists. The statement is a no-op when the view is present. | | `schema-name` | The name of the attached database in which to create the view. Defaults to the main database if omitted. Cannot be used with `TEMPORARY`. | | `view-name` | A unique name for the view within the database. | | `select-statement` | The SELECT query that defines the view's contents. | ### Behavior * Views are read-only. INSERT, UPDATE, and DELETE on a view are not supported unless an INSTEAD OF trigger is defined on the view. * The SELECT statement is stored in the `sqlite_schema` table and parsed each time the view is referenced. * If a table referenced by the view is dropped or altered in an incompatible way, queries against the view produce an error. ## Examples ### Simplify a Common Query ```sql theme={null} CREATE VIEW active_users AS SELECT id, name, email FROM users WHERE status = 'active'; -- Use the view like a table SELECT * FROM active_users WHERE name LIKE 'A%'; ``` ### Aggregate View ```sql theme={null} CREATE VIEW department_stats AS SELECT department, COUNT(*) AS employee_count, AVG(salary) AS avg_salary FROM employees GROUP BY department; SELECT * FROM department_stats ORDER BY avg_salary DESC; ``` ### Temporary View for a Session ```sql theme={null} CREATE TEMP VIEW my_tasks AS SELECT id, title, due_date FROM tasks WHERE assignee = 'alice'; ``` ## See Also * [CREATE MATERIALIZED VIEW](/sql-reference/statements/create-materialized-view) for views that store precomputed results on disk * [DROP VIEW](/sql-reference/statements/drop-view) for removing views * [SELECT](/sql-reference/statements/select) for the query syntax used in view definitions * [CREATE TRIGGER](/sql-reference/statements/create-trigger) for INSTEAD OF triggers that make views writable # CREATE VIRTUAL TABLE Source: https://docs.turso.tech/sql-reference/statements/create-virtual-table Create a virtual table backed by a module that provides custom data access # CREATE VIRTUAL TABLE The CREATE VIRTUAL TABLE statement creates a table whose contents are computed on-the-fly by a module rather than stored in the database file. Virtual tables provide a SQL interface over external data sources, custom generators, and table-valued functions. ## Syntax ```sql theme={null} CREATE VIRTUAL TABLE [IF NOT EXISTS] [schema-name.]table-name USING module-name [(module-arguments)]; ``` ## Description A virtual table delegates row storage and retrieval to a module. The module determines what columns the table has, what data it returns, and how queries against it are optimized. Virtual tables can be queried with SELECT just like regular tables, but they cannot be targets of INSERT, UPDATE, or DELETE unless the module implements write support. ### Clauses | Clause | Description | | - | - | | `IF NOT EXISTS` | Suppresses the error that would occur if a table with the same name already exists. | | `schema-name` | The name of the attached database in which to create the virtual table. Defaults to `main`. | | `table-name` | The name for the new virtual table. | | `module-name` | The name of the module that provides the virtual table implementation. | | `module-arguments` | Comma-separated arguments passed to the module. The format and meaning of these arguments are module-specific. | ## Available Modules ### csv The `csv` module reads data from a CSV file and exposes its contents as a read-only table. Load the csv extension first with `load_extension('csv')`. | Argument | Description | | - | - | | `filename` | Path to the CSV file to read. | | `header` | Set to `yes` if the first row contains column names. Defaults to `no`. | | `columns` | Number of columns to expect when `header` is `no`. | | `schema` | Explicit CREATE TABLE schema for column names and types. | ### generate\_series The `generate_series` module is a built-in table-valued function that produces a sequence of integer values. It does not require an extension to be loaded. | Argument | Description | | - | - | | `start` | The first value in the sequence (inclusive). | | `stop` | The last value in the sequence (inclusive). | | `step` | The increment between values. Defaults to `1`. | The `generate_series` module can be used either as a virtual table or as a table-valued function in the FROM clause. ## Examples ### Read a CSV File ```sql theme={null} -- Load the csv extension SELECT load_extension('csv'); -- Create a virtual table from a CSV file with a header row CREATE VIRTUAL TABLE employees USING csv( filename='employees.csv', header=yes ); -- Query the CSV data with SQL SELECT * FROM employees WHERE department = 'Engineering'; ``` ### Generate a Sequence of Integers ```sql theme={null} -- Use generate_series as a table-valued function SELECT value FROM generate_series(1, 10); -- value -- 1 -- 2 -- ... -- 10 -- Use with a step argument SELECT value FROM generate_series(0, 100, 10); -- value -- 0 -- 10 -- 20 -- ... -- 100 ``` ### Generate a Date Series with generate\_series ```sql theme={null} -- Generate dates for the next 7 days SELECT date('now', '+' || value || ' days') AS day FROM generate_series(0, 6); ``` ## See Also * [SELECT](/sql-reference/statements/select) for querying virtual tables * [Compatibility](/sql-reference/compatibility) for the full list of supported modules # DELETE Source: https://docs.turso.tech/sql-reference/statements/delete Remove rows from a table # DELETE The `DELETE` statement removes rows from a table. ## Syntax ```sql theme={null} DELETE FROM table-name [WHERE expression] [RETURNING result-column [, result-column ...]] [ORDER BY sort-expression [, sort-expression ...]] [LIMIT expression [OFFSET expression]] ``` ## Description `DELETE` removes rows that match the `WHERE` condition. If no `WHERE` clause is provided, all rows in the table are deleted. The table itself is not removed; use [DROP TABLE](/sql-reference/statements/drop-table) to remove a table entirely. ### WHERE Clause The `WHERE` clause restricts which rows are deleted. Only rows where the expression evaluates to true are removed. Without a `WHERE` clause, every row in the table is deleted. ```sql theme={null} DELETE FROM logs WHERE created_at < date('now', '-90 days'); ``` ### RETURNING Clause The `RETURNING` clause causes the `DELETE` statement to return the values of each deleted row. The result columns can be any expression referencing the deleted row's columns. ```sql theme={null} DELETE FROM sessions WHERE expires_at < datetime('now') RETURNING session_id, user_id; ``` ### ORDER BY and LIMIT The `ORDER BY` and `LIMIT` clauses restrict which rows are deleted. `ORDER BY` determines the order in which rows are considered, and `LIMIT` caps the number of rows that are actually removed. `ORDER BY` requires `LIMIT` to be present. ```sql theme={null} DELETE FROM logs ORDER BY created_at ASC LIMIT 1000; ``` `LIMIT` accepts an optional `OFFSET` to skip a number of rows before applying the limit: ```sql theme={null} DELETE FROM logs ORDER BY created_at ASC LIMIT 500 OFFSET 100; ``` > [!NOTE] > > `ORDER BY` without `LIMIT` is not allowed on `DELETE` statements. If you need to delete all rows matching a condition in a specific order, provide a `LIMIT` value larger than the expected number of matching rows. ## Examples ### Delete rows matching a condition ```sql theme={null} CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, completed INTEGER); INSERT INTO tasks VALUES (1, 'Write docs', 1); INSERT INTO tasks VALUES (2, 'Review PR', 0); INSERT INTO tasks VALUES (3, 'Fix bug', 1); DELETE FROM tasks WHERE completed = 1; SELECT * FROM tasks; -- 2|Review PR|0 ``` ### Delete with RETURNING ```sql theme={null} CREATE TABLE queue (id INTEGER PRIMARY KEY, payload TEXT, priority INTEGER); INSERT INTO queue VALUES (1, 'job_a', 10); INSERT INTO queue VALUES (2, 'job_b', 5); INSERT INTO queue VALUES (3, 'job_c', 10); DELETE FROM queue WHERE id = (SELECT id FROM queue ORDER BY priority DESC, id ASC LIMIT 1) RETURNING id, payload; -- 1|job_a ``` ### Delete the oldest rows with ORDER BY and LIMIT ```sql theme={null} CREATE TABLE events (id INTEGER PRIMARY KEY, message TEXT, created_at TEXT); INSERT INTO events VALUES (1, 'startup', '2025-01-01'); INSERT INTO events VALUES (2, 'request', '2025-01-02'); INSERT INTO events VALUES (3, 'shutdown', '2025-01-03'); INSERT INTO events VALUES (4, 'startup', '2025-01-04'); DELETE FROM events ORDER BY created_at ASC LIMIT 2; SELECT * FROM events; -- 3|shutdown|2025-01-03 -- 4|startup|2025-01-04 ``` ## See Also * [UPDATE](/sql-reference/statements/update) for modifying existing rows * [INSERT](/sql-reference/statements/insert) for adding new rows * [DROP TABLE](/sql-reference/statements/drop-table) for removing an entire table * [SELECT](/sql-reference/statements/select) for querying data # DETACH DATABASE Source: https://docs.turso.tech/sql-reference/statements/detach-database Remove an attached database from the current connection # DETACH DATABASE The DETACH DATABASE statement removes a previously attached database from the current connection. After detaching, tables in that database are no longer accessible through the schema name. ## Syntax ```sql theme={null} DETACH [DATABASE] schema-name; ``` ## Description DETACH closes the connection to the attached database identified by `schema-name` and frees its resources. Any subsequent references to `schema-name.table-name` will fail. ### Clauses | Clause | Description | | - | - | | `DATABASE` | Optional keyword. Has no effect on behavior. | | `schema-name` | The schema name assigned when the database was attached. | The `main` and `temp` schemas cannot be detached. > [!NOTE] > > DETACH cannot be used while a transaction is active. Commit or roll back any open transaction before detaching a database. ## Examples ### Detach a Database ```sql theme={null} ATTACH 'archive.db' AS archive; -- Use the attached database SELECT count(*) FROM archive.events; -- Detach when done DETACH DATABASE archive; ``` ### Detach Without the DATABASE Keyword ```sql theme={null} DETACH archive; ``` ## See Also * [ATTACH DATABASE](/sql-reference/statements/attach-database) for attaching a database # DROP DOMAIN Source: https://docs.turso.tech/sql-reference/statements/drop-domain Remove a user-defined domain from the database # DROP DOMAIN > [!NOTE] > > **Turso Extension**: DROP DOMAIN is a Turso-specific statement not available in standard SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). The DROP DOMAIN statement removes a user-defined domain from the database. Once dropped, the domain name can no longer be used in new column definitions. ## Syntax ```sql theme={null} DROP DOMAIN [IF EXISTS] domain-name; ``` ## Description DROP DOMAIN removes the named domain from the database. The domain must exist unless the `IF EXISTS` clause is specified. ### Clauses | Clause | Description | | - | - | | `IF EXISTS` | Suppresses the error that would occur if the domain does not exist. The statement is a no-op when the domain is not found. | | `domain-name` | The name of the domain to remove. | ### Restrictions A domain cannot be dropped while any table column uses it or while another domain references it as a base type. Remove all dependents before dropping. ```sql theme={null} CREATE DOMAIN base_d AS integer; CREATE DOMAIN child_d AS base_d CHECK (value > 0); -- Fails: child_d depends on base_d DROP DOMAIN base_d; -- Drop child first, then base DROP DOMAIN child_d; DROP DOMAIN base_d; ``` ### DROP DOMAIN vs DROP TYPE DROP DOMAIN can only remove domains created with CREATE DOMAIN. Attempting to use DROP DOMAIN on a type created with CREATE TYPE results in an error, and vice versa. ```sql theme={null} CREATE TYPE my_type BASE integer ENCODE value DECODE value; DROP DOMAIN my_type; -- Error: 'my_type' is a type, not a domain. Use DROP TYPE instead CREATE DOMAIN my_domain AS integer; DROP TYPE my_domain; -- Error: 'my_domain' is a domain, not a type. Use DROP DOMAIN instead ``` ## Examples ### Drop a Domain ```sql theme={null} CREATE DOMAIN positive_int AS integer CHECK (value > 0); DROP DOMAIN positive_int; ``` ### Drop with IF EXISTS ```sql theme={null} -- Safe to run even if the domain does not exist DROP DOMAIN IF EXISTS nonexistent_domain; ``` ### Drop After Removing Table Dependency ```sql theme={null} CREATE DOMAIN score AS integer CHECK (value >= 0); CREATE TABLE results (id INTEGER PRIMARY KEY, val score) STRICT; -- Fails: table 'results' uses domain 'score' -- DROP DOMAIN score; DROP TABLE results; DROP DOMAIN score; -- OK ``` ## See Also * [CREATE DOMAIN](/sql-reference/statements/create-domain) for defining domains * [DROP TYPE](/sql-reference/statements/drop-type) for removing custom types # DROP INDEX Source: https://docs.turso.tech/sql-reference/statements/drop-index Remove an index from the database # DROP INDEX Remove an index from the database. The underlying table and its data are not affected. ## Syntax ```sql theme={null} DROP INDEX [IF EXISTS] [schema-name.]index-name; ``` ## Description `DROP INDEX` removes a previously created index. After the index is dropped, the query planner can no longer use it to optimize queries. The table data is unchanged. ### Parameters | Parameter | Description | | - | - | | `IF EXISTS` | Prevents an error if the index does not exist. The statement is a no-op when the index is absent. | | `schema-name` | The name of the attached database containing the index. Defaults to the main database if omitted. | | `index-name` | The name of the index to drop. | ### Behavior * Only explicitly created indexes can be dropped. Indexes automatically created for PRIMARY KEY and UNIQUE constraints cannot be dropped directly -- drop the table or use ALTER TABLE instead. * Dropping an index may degrade query performance for queries that relied on it, but does not affect correctness. ## Examples ### Drop an Index ```sql theme={null} DROP INDEX idx_users_email; ``` ### Drop an Index Only if It Exists ```sql theme={null} DROP INDEX IF EXISTS idx_old_search; ``` ### Drop an Index in an Attached Database ```sql theme={null} DROP INDEX aux.idx_sessions_token; ``` ## See Also * [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes * [EXPLAIN](/sql-reference/statements/explain) for verifying query plans after dropping an index # DROP SEQUENCE Source: https://docs.turso.tech/sql-reference/statements/drop-sequence Remove a named sequence generator from the database # DROP SEQUENCE > [!NOTE] > > **Turso Extension**: DROP SEQUENCE is a Turso-specific statement not available in standard SQLite. The DROP SEQUENCE statement removes a sequence object from the database. Once dropped, the sequence name can no longer be used with `nextval()`, `currval()`, or `setval()`. ## Syntax ```sql theme={null} DROP SEQUENCE [IF EXISTS] [schema-name.]sequence-name; ``` ## Description DROP SEQUENCE removes the named sequence from the database schema. The sequence must exist unless the `IF EXISTS` clause is specified. ### Clauses | Clause | Description | | - | - | | `IF EXISTS` | Suppresses the error that would occur if the sequence does not exist. The statement is a no-op when the sequence is not found. | | `schema-name` | The name of an attached database. Defaults to the main database if omitted. | | `sequence-name` | The name of the sequence to remove. | ## Examples ### Drop a Sequence ```sql theme={null} CREATE SEQUENCE my_seq; SELECT nextval('my_seq'); -- 1 DROP SEQUENCE my_seq; SELECT nextval('my_seq'); -- Error: sequence "my_seq" does not exist ``` ### Drop with IF EXISTS ```sql theme={null} -- Safe to run even if the sequence does not exist DROP SEQUENCE IF EXISTS nonexistent; ``` ### Recreate After Drop Dropping and recreating a sequence resets it to its initial state: ```sql theme={null} CREATE SEQUENCE counter; SELECT nextval('counter'); -- 1 SELECT nextval('counter'); -- 2 DROP SEQUENCE counter; CREATE SEQUENCE counter; SELECT nextval('counter'); -- 1 ``` ## Errors | Error | Cause | | - | - | | `sequence "name" does not exist` | No sequence with that name exists and `IF EXISTS` was not specified. | ## See Also * [CREATE SEQUENCE](/sql-reference/statements/create-sequence) for defining sequences # DROP TABLE Source: https://docs.turso.tech/sql-reference/statements/drop-table Remove a table and all its data from the database # DROP TABLE Remove a table and all its data, indexes, triggers, and constraints from the database. ## Syntax ```sql theme={null} DROP TABLE [IF EXISTS] [schema-name.]table-name; ``` ## Description `DROP TABLE` permanently removes a table definition and all data stored in the table. All indexes, triggers, and constraints associated with the table are also removed. ### Parameters | Parameter | Description | | - | - | | `IF EXISTS` | Prevents an error if the table does not exist. The statement is a no-op when the table is absent. | | `schema-name` | The name of the attached database containing the table. Defaults to the main database if omitted. | | `table-name` | The name of the table to drop. | ### Behavior * All rows in the table are deleted. * All indexes built on the table are removed. * All triggers associated with the table are removed. * The table entry is removed from the `sqlite_schema` system table. * If foreign key constraints reference the dropped table, those references become invalid. Turso does not prevent dropping a table that is referenced by foreign keys in other tables. * `DROP TABLE` is not allowed while the table is being read or written by another statement in the same connection. ## Examples ### Drop a Table ```sql theme={null} DROP TABLE users; ``` ### Drop a Table Only if It Exists ```sql theme={null} DROP TABLE IF EXISTS temporary_results; ``` ### Drop a Table in an Attached Database ```sql theme={null} DROP TABLE aux.sessions; ``` ## See Also * [CREATE TABLE](/sql-reference/statements/create-table) for creating tables * [ALTER TABLE](/sql-reference/statements/alter-table) for modifying tables * [DELETE](/sql-reference/statements/delete) for removing rows without dropping the table # DROP TRIGGER Source: https://docs.turso.tech/sql-reference/statements/drop-trigger Remove a trigger from the database # DROP TRIGGER The DROP TRIGGER statement removes a trigger from the database schema. Once dropped, the trigger no longer fires in response to its associated table events. ## Syntax ```sql theme={null} DROP TRIGGER [IF EXISTS] [schema-name.]trigger-name; ``` ## Description DROP TRIGGER removes the named trigger from the database. The trigger must exist unless the `IF EXISTS` clause is specified. ### Clauses | Clause | Description | | - | - | | `IF EXISTS` | Suppresses the error that would occur if the trigger does not exist. The statement is a no-op when the trigger is not found. | | `schema-name` | The name of the attached database containing the trigger. When omitted, Turso searches for the trigger in the main database. | | `trigger-name` | The name of the trigger to remove. | Dropping a trigger does not affect any data in the table the trigger was associated with. It only removes the trigger definition from the schema. ## Examples ### Drop a Trigger ```sql theme={null} CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN INSERT INTO audit_log (action, user_id) VALUES ('insert', NEW.id); END; -- Remove the trigger DROP TRIGGER log_insert; ``` ### Drop a Trigger with IF EXISTS ```sql theme={null} -- Safe to run even if the trigger does not exist DROP TRIGGER IF EXISTS log_insert; ``` ### Drop a Trigger in an Attached Database ```sql theme={null} DROP TRIGGER IF EXISTS aux.log_insert; ``` ## See Also * [CREATE TRIGGER](/sql-reference/statements/create-trigger) for creating triggers * [CREATE TABLE](/sql-reference/statements/create-table) for table definitions # DROP TYPE Source: https://docs.turso.tech/sql-reference/statements/drop-type Remove a user-defined custom type from the database # DROP TYPE > [!NOTE] > > **Turso Extension**: DROP TYPE is a Turso-specific statement not available in standard SQLite. This feature is experimental and must be [enabled before use](/sql-reference/experimental-features). The DROP TYPE statement removes a user-defined custom type from the database. Once dropped, the type name can no longer be used in new column definitions. ## Syntax ```sql theme={null} DROP TYPE [IF EXISTS] type-name; ``` ## Description DROP TYPE removes the named custom type from the database. The type must exist unless the `IF EXISTS` clause is specified. ### Clauses | Clause | Description | | - | - | | `IF EXISTS` | Suppresses the error that would occur if the type does not exist. The statement is a no-op when the type is not found. | | `type-name` | The name of the custom type to remove. | ### Restrictions A type cannot be dropped while any table column uses it. All columns of that type must be removed or the table must be dropped before the type can be dropped. ```sql theme={null} CREATE TYPE cents BASE integer ENCODE value * 100 DECODE value / 100; CREATE TABLE prices ( id INTEGER PRIMARY KEY, amount cents ) STRICT; -- This fails because the 'prices' table has a column of type 'cents' DROP TYPE cents; -- Error: type 'cents' is in use -- Drop the table first, then the type DROP TABLE prices; DROP TYPE cents; ``` ## Examples ### Drop a Custom Type ```sql theme={null} CREATE TYPE reversed_text BASE text ENCODE turso_reverse(value) DECODE turso_reverse(value); DROP TYPE reversed_text; ``` ### Drop a Type with IF EXISTS ```sql theme={null} -- Safe to run even if the type does not exist DROP TYPE IF EXISTS reversed_text; ``` ## See Also * [CREATE TYPE](/sql-reference/statements/create-type) for defining custom types * [DROP DOMAIN](/sql-reference/statements/drop-domain) for removing domains * [Data Types](/sql-reference/data-types) for an overview of the type system # DROP VIEW Source: https://docs.turso.tech/sql-reference/statements/drop-view Remove a view from the database # DROP VIEW Remove a view definition from the database. The underlying tables and their data are not affected. ## Syntax ```sql theme={null} DROP VIEW [IF EXISTS] [schema-name.]view-name; ``` ## Description `DROP VIEW` removes a previously created view. After the view is dropped, queries referencing the view name produce an error. The tables referenced by the view are unaffected. ### Parameters | Parameter | Description | | - | - | | `IF EXISTS` | Prevents an error if the view does not exist. The statement is a no-op when the view is absent. | | `schema-name` | The name of the attached database containing the view. Defaults to the main database if omitted. | | `view-name` | The name of the view to drop. | ### Behavior * Any triggers defined as INSTEAD OF on the dropped view are also removed. * If the view is a materialized view, the stored data is also removed. ## Examples ### Drop a View ```sql theme={null} DROP VIEW active_users; ``` ### Drop a View Only if It Exists ```sql theme={null} DROP VIEW IF EXISTS old_report; ``` ### Drop a View in an Attached Database ```sql theme={null} DROP VIEW aux.cached_stats; ``` ## See Also * [CREATE VIEW](/sql-reference/statements/create-view) for creating views and materialized views # EXPLAIN Source: https://docs.turso.tech/sql-reference/statements/explain Display the execution plan or bytecode for a SQL statement # EXPLAIN The EXPLAIN statement displays information about how Turso executes a SQL statement. There are two forms: `EXPLAIN` shows the virtual machine bytecode, and `EXPLAIN QUERY PLAN` shows the high-level query execution strategy. ## Syntax ```sql theme={null} EXPLAIN statement; EXPLAIN QUERY PLAN statement; ``` | Parameter | Type | Description | | - | - | - | | statement | SQL | Any SQL statement (SELECT, INSERT, UPDATE, DELETE, etc.) | ## EXPLAIN The `EXPLAIN` prefix causes Turso to return the sequence of virtual machine (VDBE) opcodes that would be used to execute the statement, rather than executing the statement itself. ```sql theme={null} EXPLAIN SELECT * FROM users WHERE id = 1; -- addr | opcode | p1 | p2 | p3 | p4 | p5 -- 0 | Init | 0 | 9 | 0 | | 0 -- 1 | OpenRead | 0 | 2 | 0 | 3 | 0 -- 2 | SeekRowid | 0 | 8 | 1 | | 0 -- ... ``` ### Output Columns | Column | Description | | - | - | | addr | Instruction address (sequential integer) | | opcode | The operation name (e.g., OpenRead, SeekRowid, Column, ResultRow) | | p1 | First operand | | p2 | Second operand | | p3 | Third operand | | p4 | Fourth operand (often a string value like table name or collation) | | p5 | Fifth operand (flags) | ## EXPLAIN QUERY PLAN The `EXPLAIN QUERY PLAN` prefix provides a high-level description of the strategy the query optimizer chose for executing a statement. This output is more useful than raw EXPLAIN for understanding query performance. ```sql theme={null} EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = 'Alice'; -- id | parent | notused | detail -- 2 | 0 | 0 | SCAN users ``` ```sql theme={null} CREATE INDEX idx_name ON users(name); EXPLAIN QUERY PLAN SELECT * FROM users WHERE name = 'Alice'; -- id | parent | notused | detail -- 3 | 0 | 0 | SEARCH users USING INDEX idx_name (name=?) ``` ### Output Columns | Column | Description | | - | - | | id | A unique identifier for this step | | parent | The id of the parent step (0 for top-level) | | notused | Reserved for future use (always 0) | | detail | Human-readable description of the execution step | ### Common Detail Messages | Detail Pattern | Meaning | | - | - | | `SCAN table` | Full table scan (no index used) | | `SEARCH table USING INDEX idx (col=?)` | Index lookup on the specified column | | `SEARCH table USING INTEGER PRIMARY KEY (rowid=?)` | Direct rowid lookup | | `USE TEMP B-TREE FOR ORDER BY` | A temporary B-tree is used for sorting | | `USE TEMP B-TREE FOR DISTINCT` | A temporary B-tree is used for deduplication | | `COMPOUND SUBQUERY` | Indicates a UNION, INTERSECT, or EXCEPT | | `CORRELATED SCALAR SUBQUERY` | A correlated subquery that returns a single value | ## Examples ### Comparing Query Plans ```sql theme={null} CREATE TABLE orders ( id INTEGER PRIMARY KEY, customer_id INTEGER, total REAL ); -- Without index: full table scan EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 42; -- SCAN orders -- After creating an index CREATE INDEX idx_customer ON orders(customer_id); EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id = 42; -- SEARCH orders USING INDEX idx_customer (customer_id=?) ``` ### Join Order ```sql theme={null} EXPLAIN QUERY PLAN SELECT o.id, c.name FROM orders o INNER JOIN customers c ON c.id = o.customer_id WHERE c.name = 'Alice'; -- SCAN orders AS o -- SEARCH customers AS c USING INTEGER PRIMARY KEY (rowid=?) ``` ### Subqueries ```sql theme={null} EXPLAIN QUERY PLAN SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers WHERE active = 1); -- SCAN orders -- LIST SUBQUERY -- SCAN customers ``` ## See Also * [ANALYZE](/sql-reference/statements/analyze) for collecting statistics that improve query plans * [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes to speed up queries # INSERT Source: https://docs.turso.tech/sql-reference/statements/insert Add new rows to a table using literal values, query results, or default values # INSERT Adds one or more rows to a table. Rows can be specified as literal values, copied from another query, or generated from column defaults. ## Syntax ```sql theme={null} INSERT [OR conflict_action] INTO table_name [(column_name [, ...])] VALUES (expression [, ...]) [, (expression [, ...]) ...] [RETURNING result_column [, ...]]; INSERT [OR conflict_action] INTO table_name [(column_name [, ...])] select_statement [RETURNING result_column [, ...]]; INSERT [OR conflict_action] INTO table_name [(column_name [, ...])] DEFAULT VALUES [RETURNING result_column [, ...]]; ``` ### Parameters | Parameter | Type | Description | | - | - | - | | `conflict_action` | keyword | One of REPLACE, IGNORE, ABORT, ROLLBACK, or FAIL. Controls behavior on constraint violations | | `table_name` | identifier | The table to insert rows into | | `column_name` | identifier | Column to assign a value to. Unlisted columns receive their default value or NULL | | `expression` | expression | A value to insert, or the keyword `DEFAULT` to use the column's default value. Must match the position or name of the target column | | `select_statement` | SELECT query | A query whose result rows are inserted into the table | | `result_column` | expression | Column or expression to return for each inserted row | ## Description INSERT adds new rows to the specified table. There are three forms: * **VALUES**: Insert one or more rows with explicit values. * **SELECT**: Insert the result set of a query. * **DEFAULT VALUES**: Insert a single row where every column uses its default value (or NULL if no default is defined). When a column list is provided, only those columns receive explicit values. All other columns use their default value or NULL. When no column list is provided, values must be supplied for every column in the table, in the order the columns were defined. ## Inserting Rows ### Single Row ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Alice', 'Engineering', 95000); ``` ### Multiple Rows Supply multiple parenthesized groups separated by commas. ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Bob', 'Design', 82000), ('Carol', 'Engineering', 97000), ('Dave', 'Marketing', 74000); ``` ### All Columns When the column list is omitted, provide a value for every column in table-definition order. ```sql theme={null} -- Assuming employees has columns: id, name, department, salary INSERT INTO employees VALUES (1, 'Alice', 'Engineering', 95000); ``` ## INSERT INTO ... SELECT Inserts the result of a SELECT statement. The number of columns returned by the SELECT must match the number of target columns. ```sql theme={null} INSERT INTO archive_employees (name, department, salary) SELECT name, department, salary FROM employees WHERE termination_date IS NOT NULL; ``` The SELECT can include any valid clause: WHERE, JOIN, GROUP BY, ORDER BY, LIMIT, UNION, or subqueries. ```sql theme={null} INSERT INTO monthly_summary (department, headcount, total_salary) SELECT department, COUNT(*), SUM(salary) FROM employees GROUP BY department; ``` ## DEFAULT VALUES Inserts a single row where every column receives its default value. Columns without an explicit DEFAULT definition receive NULL. INTEGER PRIMARY KEY columns receive an auto-generated rowid. ```sql theme={null} CREATE TABLE audit_log ( id INTEGER PRIMARY KEY, created_at TEXT DEFAULT (datetime('now')), action TEXT DEFAULT 'unknown' ); INSERT INTO audit_log DEFAULT VALUES; -- Inserts: (auto_id, current_timestamp, 'unknown') ``` ## Column Default Values When a column list is provided, omitted columns use their default values. ```sql theme={null} CREATE TABLE tasks ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, status TEXT DEFAULT 'pending', priority INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')) ); -- status, priority, and created_at use their defaults INSERT INTO tasks (title) VALUES ('Review pull request'); ``` ## DEFAULT Keyword in VALUES > [!NOTE] > > **Turso extension** -- This feature follows the SQL standard (SQL:2016) but is not supported by SQLite. The `DEFAULT` keyword can be used in place of any value expression in a VALUES list. It resolves to the column's default value as defined in the CREATE TABLE statement, or NULL if no default is defined. This is particularly useful when inserting multiple rows where different rows need defaults for different columns -- something that cannot be achieved by simply omitting columns from the column list. ```sql theme={null} CREATE TABLE tasks ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, status TEXT DEFAULT 'pending', priority INTEGER DEFAULT 0 ); -- Use DEFAULT for specific columns INSERT INTO tasks (id, title, status, priority) VALUES (1, 'Fix bug', DEFAULT, 5); -- Inserts: (1, 'Fix bug', 'pending', 5) -- Different columns use DEFAULT in different rows INSERT INTO tasks (id, title, status, priority) VALUES (2, 'Write docs', DEFAULT, 3), (3, 'Code review', 'in_progress', DEFAULT); -- Inserts: (2, 'Write docs', 'pending', 3) -- (3, 'Code review', 'in_progress', 0) ``` The `DEFAULT` keyword is only valid inside INSERT VALUES lists. Using it in other contexts (SELECT, WHERE, UPDATE) produces an error. ## Conflict Handling The `OR` clause specifies what happens when an INSERT violates a uniqueness or NOT NULL constraint. | Action | Behavior | | - | - | | `ABORT` | Roll back the current statement and return an error. Previously inserted rows within the same INSERT are undone. This is the default | | `ROLLBACK` | Roll back the entire transaction and return an error | | `FAIL` | Stop execution and return an error. Rows already inserted by this statement before the failure are kept | | `IGNORE` | Skip the row that caused the violation and continue with the next row | | `REPLACE` | Delete the conflicting row and insert the new row in its place | ```sql theme={null} -- Skip rows that violate a UNIQUE constraint INSERT OR IGNORE INTO employees (id, name, department, salary) VALUES (1, 'Alice', 'Engineering', 95000); -- Replace the existing row on conflict INSERT OR REPLACE INTO employees (id, name, department, salary) VALUES (1, 'Alice', 'Engineering', 105000); -- Roll back only this statement on conflict INSERT OR ABORT INTO employees (id, name, department, salary) VALUES (1, 'Alice', 'Engineering', 95000); ``` > [!NOTE] > > For more granular conflict handling based on specific constraints, use the [ON CONFLICT](/sql-reference/statements/upsert) clause (UPSERT), which allows different actions depending on which constraint was violated. ## RETURNING Clause Returns data from the rows that were actually inserted. This is useful for retrieving generated values such as auto-incremented IDs or evaluated defaults. ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Eve', 'Engineering', 91000) RETURNING id, name; -- Returns: 5|Eve ``` RETURNING accepts any expression that can reference the inserted row's columns. ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Frank', 'Design', 78000) RETURNING id, name, salary * 12 AS annual_salary; -- Returns: 6|Frank|936000 ``` ### RETURNING with Multiple Rows ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Grace', 'Engineering', 102000), ('Hank', 'Marketing', 68000) RETURNING id, name, department; -- Returns one row per inserted row ``` ### RETURNING \* Return all columns of the inserted rows. ```sql theme={null} INSERT INTO employees (name, department, salary) VALUES ('Ivy', 'Product', 88000) RETURNING *; ``` ## Examples ### Insert with Subquery Values ```sql theme={null} INSERT INTO department_stats (department, avg_salary) SELECT department, AVG(salary) FROM employees GROUP BY department HAVING COUNT(*) >= 3; ``` ### Insert with Conflict Handling and RETURNING ```sql theme={null} INSERT OR REPLACE INTO settings (key, value) VALUES ('theme', 'dark'), ('locale', 'en-US') RETURNING key, value; ``` ### Archival Pattern ```sql theme={null} -- Copy rows to archive, then delete originals INSERT INTO orders_archive SELECT * FROM orders WHERE order_date < '2023-01-01'; DELETE FROM orders WHERE order_date < '2023-01-01'; ``` ## See Also * [INSERT ... ON CONFLICT (UPSERT)](/sql-reference/statements/upsert) for conditional insert-or-update behavior * [REPLACE](/sql-reference/statements/replace) for shorthand INSERT OR REPLACE * [SELECT](/sql-reference/statements/select) for the query syntax used with INSERT INTO ... SELECT * [CREATE TABLE](/sql-reference/statements/create-table) for column definitions and default values # REINDEX Source: https://docs.turso.tech/sql-reference/statements/reindex Rebuild indexes from scratch # REINDEX The REINDEX statement deletes and recreates indexes from scratch. It is useful when a collation sequence definition has changed, or to rebuild an index that may have become out of date. ## Syntax ```sql theme={null} REINDEX; REINDEX collation-name; REINDEX [schema-name.]table-name; REINDEX [schema-name.]index-name; ``` ## Description | Form | Description | | - | - | | `REINDEX` | Rebuild all indexes in all attached databases | | `REINDEX collation-name` | Rebuild every index that uses the named collation sequence | | `REINDEX table-name` | Rebuild all indexes associated with the named table | | `REINDEX index-name` | Rebuild the named index | When a single unqualified name is given, Turso resolves it in the order **collation → table → index**: if the name matches a collation sequence, all indexes using that collation are rebuilt; otherwise it is treated as a table or index name. ## Examples ```sql theme={null} -- Rebuild every index in the database REINDEX; -- Rebuild all indexes on a table REINDEX orders; -- Rebuild a single index REINDEX idx_customer; -- Rebuild all indexes that use a collation REINDEX NOCASE; ``` ## Limitations REINDEX returns an error in the following cases: * When the connection is in MVCC mode (`journal_mode=mvcc`). * On `WITHOUT ROWID` tables. * For custom index methods that have no backing B-tree, such as FTS and vector indexes. * When `PRAGMA query_only` is enabled. ## See Also * [CREATE INDEX](/sql-reference/statements/create-index) for creating indexes * [DROP INDEX](/sql-reference/statements/drop-index) for removing indexes * [ANALYZE](/sql-reference/statements/analyze) for collecting index statistics # REPLACE Source: https://docs.turso.tech/sql-reference/statements/replace Insert a row, replacing any existing row that conflicts on a unique constraint # REPLACE The `REPLACE` statement inserts a row into a table. If the row violates a `UNIQUE` or `PRIMARY KEY` constraint, the existing conflicting row is deleted before the new row is inserted. ## Syntax ```sql theme={null} REPLACE INTO table-name [(column-name [, column-name ...])] VALUES (expression [, expression ...]) [, (expression [, expression ...]) ...] REPLACE INTO table-name [(column-name [, column-name ...])] select-statement ``` ## Description `REPLACE` is equivalent to `INSERT OR REPLACE`. When a `REPLACE` inserts a row that would violate a `UNIQUE` or `PRIMARY KEY` constraint, it deletes the existing row that causes the conflict, then inserts the new row. If no constraint violation occurs, `REPLACE` behaves identically to `INSERT`. ### How REPLACE Differs from INSERT With a plain `INSERT`, a constraint violation causes the statement to fail with an error. With `REPLACE`, the conflicting row is silently deleted and the new row takes its place. Because the conflicting row is deleted first, `REPLACE` may fire `DELETE` triggers on the old row and `INSERT` triggers on the new row. > [!NOTE] > > `REPLACE` deletes the entire conflicting row and inserts a new one. This means any columns not specified in the `REPLACE` statement receive their default values, not the values from the deleted row. If you want to keep existing column values while updating specific columns, use [INSERT ... ON CONFLICT DO UPDATE](/sql-reference/statements/upsert) instead. ### Constraint Behavior When multiple `UNIQUE` constraints exist on a table, `REPLACE` deletes every row that conflicts with the new row before inserting it. This can result in more than one row being deleted by a single `REPLACE` operation. If a `NOT NULL` constraint is violated on a column that has no `DEFAULT` value, the `REPLACE` fails with an error. ## Examples ### Basic REPLACE ```sql theme={null} CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT); INSERT INTO settings VALUES ('theme', 'light'); SELECT * FROM settings; -- theme|light -- The existing row is deleted and a new row is inserted REPLACE INTO settings VALUES ('theme', 'dark'); SELECT * FROM settings; -- theme|dark ``` ### REPLACE deletes and re-inserts Unlike `UPDATE`, `REPLACE` removes the old row entirely. Columns not specified in the statement receive their default values: ```sql theme={null} CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE, created_at TEXT DEFAULT (datetime('now')) ); INSERT INTO users VALUES (1, 'Alice', 'alice@example.com', '2025-01-01'); -- This deletes row with id=1 and inserts a completely new row. -- The created_at column gets its DEFAULT value, not '2025-01-01'. REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com'); SELECT id, name, email FROM users; -- 1|Alice|alice@new.com ``` ### Multi-row REPLACE ```sql theme={null} CREATE TABLE inventory (sku TEXT PRIMARY KEY, quantity INTEGER); INSERT INTO inventory VALUES ('A001', 10); INSERT INTO inventory VALUES ('B002', 20); REPLACE INTO inventory VALUES ('A001', 15), ('B002', 25), ('C003', 30); SELECT * FROM inventory; -- A001|15 -- B002|25 -- C003|30 ``` ## See Also * [INSERT](/sql-reference/statements/insert) for adding rows without conflict resolution * [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) for conditional insert-or-update with control over which columns are updated * [UPDATE](/sql-reference/statements/update) for modifying existing rows in place # SELECT Source: https://docs.turso.tech/sql-reference/statements/select Query rows from one or more tables using filtering, joining, grouping, sorting, and set operations # SELECT Retrieves rows from one or more tables. SELECT is the primary way to read data in Turso and supports filtering, joining, aggregation, sorting, subqueries, and set operations. ## Syntax ```sql theme={null} [WITH cte_name AS (select_statement) [, ...]] SELECT [DISTINCT] result_column [, ...] [FROM table_or_subquery [join_clause ...]] [WHERE expression] [GROUP BY expression [, ...]] [HAVING expression] [WINDOW window_name AS (window_definition) [, ...]] [compound_operator select_statement] [ORDER BY ordering_term [, ...]] [LIMIT expression [OFFSET expression]] ``` ### Parameters | Parameter | Type | Description | | - | - | - | | `result_column` | expression, `*`, or `table.*` | Columns or expressions to return. Use `*` for all columns | | `table_or_subquery` | identifier or subquery | Table name, aliased table, or parenthesized SELECT | | `expression` | expression | Any valid SQL expression | | `ordering_term` | expression + direction | Expression followed by optional ASC/DESC and NULLS FIRST/LAST | | `compound_operator` | keyword | UNION, UNION ALL, INTERSECT, or EXCEPT | | `cte_name` | identifier | Name for a Common Table Expression | | `window_name` | identifier | Name for a reusable window definition | ## Basic Queries ### Selecting Columns ```sql theme={null} -- All columns SELECT * FROM employees; -- Specific columns SELECT name, department FROM employees; -- Expressions and aliases SELECT name, salary * 12 AS annual_salary FROM employees; ``` ### Column and Table Aliases Use `AS` to assign aliases to columns or tables. The `AS` keyword is optional for column aliases. ```sql theme={null} SELECT e.name, e.salary * 12 annual_pay FROM employees AS e; ``` ## FROM Clause The FROM clause specifies the source tables for the query. It accepts table names, aliased tables, subqueries, and join expressions. ```sql theme={null} -- Single table SELECT * FROM employees; -- Subquery as table source SELECT * FROM (SELECT name, salary FROM employees WHERE salary > 50000) AS high_earners; ``` ## WHERE Clause Filters rows based on a condition. Only rows where the expression evaluates to true are included in the result. ```sql theme={null} SELECT * FROM employees WHERE department = 'Engineering'; SELECT * FROM orders WHERE total > 100 AND status != 'cancelled'; ``` ### Comparison Operators | Operator | Description | | - | - | | `=` | Equal | | `!=` or `<>` | Not equal | | `<` | Less than | | `>` | Greater than | | `<=` | Less than or equal | | `>=` | Greater than or equal | | `IS` | Equal (NULL-safe) | | `IS NOT` | Not equal (NULL-safe) | | `IS DISTINCT FROM` | Not identical (NULL-safe, SQL standard) | | `IS NOT DISTINCT FROM` | Identical (NULL-safe, SQL standard) | ### Logical Operators Combine conditions with `AND`, `OR`, and `NOT`. ```sql theme={null} SELECT * FROM products WHERE (category = 'Electronics' OR category = 'Books') AND price < 50 AND NOT discontinued; ``` ### Pattern Matching ```sql theme={null} -- LIKE: case-insensitive pattern matching (% = any chars, _ = one char) SELECT * FROM employees WHERE name LIKE 'J%'; -- GLOB: case-sensitive pattern matching (* = any chars, ? = one char) SELECT * FROM files WHERE path GLOB '*.txt'; -- REGEXP: regular expression matching SELECT * FROM logs WHERE message REGEXP '^ERROR:'; ``` ### Range and Membership Tests ```sql theme={null} -- BETWEEN (inclusive on both ends) SELECT * FROM orders WHERE total BETWEEN 100 AND 500; -- IN with a list SELECT * FROM employees WHERE department IN ('Engineering', 'Design', 'Product'); -- IN with a subquery SELECT * FROM employees WHERE department_id IN (SELECT id FROM departments WHERE active = 1); ``` ### NULL Tests ```sql theme={null} SELECT * FROM employees WHERE manager_id IS NULL; SELECT * FROM employees WHERE phone IS NOT NULL; ``` ### CASE Expressions ```sql theme={null} SELECT name, CASE WHEN salary >= 100000 THEN 'Senior' WHEN salary >= 60000 THEN 'Mid' ELSE 'Junior' END AS level FROM employees; -- Simple CASE form SELECT name, CASE department WHEN 'Engineering' THEN 'Eng' WHEN 'Marketing' THEN 'Mkt' ELSE 'Other' END AS dept_code FROM employees; ``` ## JOIN Clause Combines rows from two or more tables based on a related column. ### Supported Join Types | Join Type | Description | | - | - | | `INNER JOIN` or `JOIN` | Returns rows that have matching values in both tables | | `LEFT JOIN` or `LEFT OUTER JOIN` | Returns all rows from the left table, with NULLs for unmatched right-side columns | | `FULL OUTER JOIN` | Returns all rows from both tables, with NULLs where there is no match on either side | | `NATURAL JOIN` | Joins on all columns with the same name in both tables | | `JOIN ... USING (column)` | Joins on the specified column that exists in both tables | ### INNER JOIN Returns only rows where the join condition is satisfied in both tables. ```sql theme={null} SELECT e.name, d.department_name FROM employees e INNER JOIN departments d ON e.department_id = d.id; ``` ### LEFT OUTER JOIN Returns all rows from the left table. When no matching row exists in the right table, the right-side columns contain NULL. ```sql theme={null} SELECT e.name, m.name AS manager_name FROM employees e LEFT JOIN employees m ON e.manager_id = m.id; ``` ### FULL OUTER JOIN Returns all rows from both tables. When a row in either table has no match in the other table, the missing side's columns contain NULL. ```sql theme={null} SELECT e.name, d.department_name FROM employees e FULL OUTER JOIN departments d ON e.department_id = d.id; -- Returns all employees (even without a department) -- and all departments (even without employees) ``` ### NATURAL JOIN Automatically joins on all columns with identical names in both tables. Equivalent to JOIN ... USING with every shared column name. ```sql theme={null} SELECT * FROM orders NATURAL JOIN customers; -- Joins on every column name shared between orders and customers ``` ### JOIN ... USING Joins on the specified column that must exist in both tables. The shared column appears only once in the result. ```sql theme={null} SELECT * FROM orders JOIN customers USING (customer_id); ``` ### Multi-Table Joins ```sql theme={null} SELECT o.id, c.name, p.product_name, oi.quantity FROM orders o JOIN customers c ON o.customer_id = c.id JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.product_id = p.id WHERE o.order_date > '2024-01-01'; ``` ## GROUP BY and HAVING ### GROUP BY Groups rows that share the same values in the specified columns. Typically used with aggregate functions. ```sql theme={null} SELECT department, COUNT(*) AS employee_count, AVG(salary) AS avg_salary FROM employees GROUP BY department; ``` Aggregate functions that can be used with GROUP BY: | Function | Description | | - | - | | `COUNT(*)` | Number of rows in the group | | `COUNT(expression)` | Number of non-NULL values | | `SUM(expression)` | Sum of non-NULL values | | `AVG(expression)` | Average of non-NULL values | | `MIN(expression)` | Minimum value | | `MAX(expression)` | Maximum value | | `TOTAL(expression)` | Sum as a REAL (returns 0.0 instead of NULL for empty sets) | | `GROUP_CONCAT(expression, separator)` | Concatenation of values | | `STRING_AGG(expression, separator)` | Alias for GROUP\_CONCAT | ### HAVING Filters groups after aggregation. WHERE filters individual rows before grouping; HAVING filters groups after. ```sql theme={null} SELECT department, COUNT(*) AS headcount FROM employees GROUP BY department HAVING COUNT(*) >= 5; ``` ### GROUP BY with Expressions ```sql theme={null} SELECT CASE WHEN salary >= 80000 THEN 'High' ELSE 'Standard' END AS band, COUNT(*) AS count FROM employees GROUP BY CASE WHEN salary >= 80000 THEN 'High' ELSE 'Standard' END; ``` ## DISTINCT Removes duplicate rows from the result set. ```sql theme={null} SELECT DISTINCT department FROM employees; SELECT DISTINCT department, title FROM employees; ``` > [!NOTE] > > DISTINCT applies to the entire result row, not to a single column. Two rows are considered duplicates only if every column value is identical. ## ORDER BY Sorts the result set. Without ORDER BY, the row order is unspecified. ```sql theme={null} SELECT * FROM employees ORDER BY salary DESC; -- Multiple sort keys SELECT * FROM employees ORDER BY department ASC, salary DESC; -- Ordering by column position SELECT name, salary FROM employees ORDER BY 2 DESC; -- Ordering by alias SELECT name, salary * 12 AS annual FROM employees ORDER BY annual DESC; ``` ### NULLS FIRST / NULLS LAST Controls where NULL values appear in the sorted result. ```sql theme={null} -- NULLs at the end (default for ASC is NULLS LAST, for DESC is NULLS FIRST) SELECT * FROM employees ORDER BY manager_id ASC NULLS LAST; -- NULLs at the beginning SELECT * FROM employees ORDER BY manager_id DESC NULLS FIRST; ``` | Direction | Default NULL Placement | | - | - | | ASC | NULLS LAST | | DESC | NULLS FIRST | ## LIMIT and OFFSET Restricts the number of rows returned and optionally skips a number of rows. ```sql theme={null} -- Return at most 10 rows SELECT * FROM employees ORDER BY salary DESC LIMIT 10; -- Skip 20 rows, then return 10 SELECT * FROM employees ORDER BY salary DESC LIMIT 10 OFFSET 20; ``` > [!NOTE] > > Always use ORDER BY with LIMIT and OFFSET. Without ORDER BY, the set of rows skipped or returned is arbitrary. ## Subqueries A subquery is a SELECT statement nested inside another query. ### Scalar Subqueries Returns a single value. Can be used anywhere an expression is expected. ```sql theme={null} SELECT name, salary, salary - (SELECT AVG(salary) FROM employees) AS diff_from_avg FROM employees; ``` ### Subqueries with IN Tests whether a value matches any row returned by the subquery. ```sql theme={null} SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE active = 1); SELECT * FROM products WHERE category_id NOT IN (SELECT id FROM categories WHERE discontinued = 1); ``` ### Subqueries with EXISTS Tests whether the subquery returns at least one row. The actual values are ignored. ```sql theme={null} SELECT * FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.total > 1000 ); ``` ### Subqueries in FROM A subquery in the FROM clause acts as a derived table and must have an alias. ```sql theme={null} SELECT dept, avg_salary FROM ( SELECT department AS dept, AVG(salary) AS avg_salary FROM employees GROUP BY department ) AS dept_stats WHERE avg_salary > 70000; ``` ## Common Table Expressions (CTE) A WITH clause defines one or more temporary named result sets that exist for the duration of the query. ```sql theme={null} WITH regional_sales AS ( SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region ) SELECT region, total_sales FROM regional_sales WHERE total_sales > 100000 ORDER BY total_sales DESC; ``` ### Multiple CTEs ```sql theme={null} WITH active_customers AS ( SELECT * FROM customers WHERE active = 1 ), recent_orders AS ( SELECT * FROM orders WHERE order_date > '2024-01-01' ) SELECT ac.name, COUNT(ro.id) AS order_count FROM active_customers ac JOIN recent_orders ro ON ac.id = ro.customer_id GROUP BY ac.name; ``` > [!NOTE] > > CTEs in Turso are SELECT-only. RECURSIVE CTEs and the MATERIALIZED/NOT MATERIALIZED hints are not supported. ## Window Functions A window function performs a calculation across a set of rows related to the current row, without collapsing them into a single output row. ```sql theme={null} SELECT name, department, salary, SUM(salary) OVER (PARTITION BY department ORDER BY salary) AS running_total FROM employees; ``` ### Named Windows Use the WINDOW clause to define a reusable window definition. ```sql theme={null} SELECT name, department, salary, SUM(salary) OVER w AS dept_running_total, COUNT(*) OVER w AS dept_running_count FROM employees WINDOW w AS (PARTITION BY department ORDER BY salary) ORDER BY department, salary; ``` ### Window Clause Syntax ```sql theme={null} function_name() OVER ( [PARTITION BY expression [, ...]] [ORDER BY expression [ASC | DESC] [, ...]] ) ``` | Component | Description | | - | - | | `PARTITION BY` | Divides rows into groups (partitions). The function resets for each partition | | `ORDER BY` | Defines the order of rows within each partition | Standard aggregate functions (`SUM`, `AVG`, `COUNT`, `MIN`, `MAX`, `TOTAL`, `GROUP_CONCAT`) can all be used as window functions. > [!NOTE] > > Window functions in Turso use the default frame definition (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW). Custom frame specifications (ROWS, RANGE, GROUPS with explicit bounds) are not supported. The `row_number()` ranking function and the `FILTER (WHERE ...)` clause are supported; other ranking and navigation functions (`rank`, `dense_rank`, `lag`, `lead`, and so on) are not yet available. ## Set Operations Combine the results of two or more SELECT statements. | Operator | Description | | - | - | | `UNION` | Combines results and removes duplicates | | `UNION ALL` | Combines results and keeps duplicates | | `INTERSECT` | Returns only rows present in both result sets | | `EXCEPT` | Returns rows from the first result set that are not in the second | All set operations require the same number of columns in each SELECT, with compatible types. ```sql theme={null} -- Customers who are also employees SELECT name FROM customers INTERSECT SELECT name FROM employees; -- All people from both tables, no duplicates SELECT name, email FROM customers UNION SELECT name, email FROM employees; -- Customers who are not employees SELECT name FROM customers EXCEPT SELECT name FROM employees; ``` ### UNION ALL When duplicates are acceptable, UNION ALL is faster because it skips the deduplication step. ```sql theme={null} SELECT id, 'order' AS source FROM orders UNION ALL SELECT id, 'return' AS source FROM returns; ``` ### Set Operations with ORDER BY ORDER BY applies to the entire combined result set and must appear after the last SELECT. ```sql theme={null} SELECT name FROM customers UNION SELECT name FROM employees ORDER BY name; ``` ## Examples ### Paginated Report with Aggregation ```sql theme={null} SELECT d.department_name, COUNT(e.id) AS headcount, ROUND(AVG(e.salary), 2) AS avg_salary, MIN(e.hire_date) AS earliest_hire FROM departments d LEFT JOIN employees e ON d.id = e.department_id GROUP BY d.department_name HAVING COUNT(e.id) > 0 ORDER BY avg_salary DESC LIMIT 10 OFFSET 0; ``` ### CTE with Filtered Join ```sql theme={null} WITH high_value_orders AS ( SELECT customer_id, COUNT(*) AS order_count, SUM(total) AS total_spent FROM orders WHERE order_date >= '2024-01-01' GROUP BY customer_id HAVING SUM(total) > 5000 ) SELECT c.name, h.order_count, h.total_spent FROM customers c JOIN high_value_orders h ON c.id = h.customer_id ORDER BY h.total_spent DESC; ``` ### Subquery with EXISTS and LEFT JOIN ```sql theme={null} SELECT p.product_name, p.price, c.category_name FROM products p LEFT JOIN categories c ON p.category_id = c.id WHERE EXISTS ( SELECT 1 FROM order_items oi WHERE oi.product_id = p.id ) ORDER BY p.price DESC LIMIT 20; ``` ## See Also * [INSERT](/sql-reference/statements/insert) for adding rows to a table * [UPDATE](/sql-reference/statements/update) for modifying existing rows * [DELETE](/sql-reference/statements/delete) for removing rows * [CREATE VIEW](/sql-reference/statements/create-view) for saving a query as a view * [Data Types](/sql-reference/data-types) for storage classes and type affinity # Transactions Source: https://docs.turso.tech/sql-reference/statements/transactions Start, commit, and roll back transactions to group operations atomically # Transactions Transactions group multiple SQL statements into a single atomic unit of work. Either all statements in a transaction succeed and are committed, or the entire transaction is rolled back and no changes are applied. Turso supports explicit transactions with BEGIN, COMMIT, and ROLLBACK, and also runs each standalone statement in an implicit transaction (autocommit mode). ## Syntax ```sql theme={null} BEGIN [DEFERRED | IMMEDIATE | EXCLUSIVE] [TRANSACTION]; COMMIT [TRANSACTION]; END [TRANSACTION]; ROLLBACK [TRANSACTION] [TO [SAVEPOINT] savepoint-name]; SAVEPOINT savepoint-name; RELEASE [SAVEPOINT] savepoint-name; ``` ## BEGIN The BEGIN statement starts a new explicit transaction. While a transaction is open, all subsequent statements are part of that transaction until a COMMIT or ROLLBACK is issued. ### Transaction Types | Type | Description | | - | - | | `DEFERRED` | Default. No locks are acquired until the first read or write operation. A read operation acquires a shared lock; a write operation acquires a write lock. | | `IMMEDIATE` | Acquires a write lock immediately when BEGIN is executed. Other connections can still read but cannot write until the transaction completes. | | `EXCLUSIVE` | Same behavior as IMMEDIATE in Turso. Turso always uses WAL mode, where EXCLUSIVE and IMMEDIATE are equivalent. | If no type is specified, the transaction defaults to `DEFERRED`. ```sql theme={null} -- Start a deferred transaction (default) BEGIN; -- Start an immediate transaction BEGIN IMMEDIATE; -- The TRANSACTION keyword is optional BEGIN IMMEDIATE TRANSACTION; ``` ## COMMIT / END The COMMIT statement (or its alias END) finalizes the transaction and writes all changes to the database. Once committed, changes are durable and visible to other connections. ```sql theme={null} BEGIN; INSERT INTO accounts (name, balance) VALUES ('Alice', 1000); INSERT INTO accounts (name, balance) VALUES ('Bob', 500); COMMIT; ``` COMMIT and END are interchangeable. ```sql theme={null} BEGIN; UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice'; END; ``` ## ROLLBACK The ROLLBACK statement aborts the current transaction and reverts all changes made since BEGIN. The database is left in the state it was in before the transaction started. ```sql theme={null} BEGIN; DELETE FROM accounts WHERE balance < 0; -- Changed our mind, undo everything ROLLBACK; ``` ## Implicit Transactions When no explicit transaction is active, Turso wraps each individual statement in an implicit transaction. This is called autocommit mode. Each statement is automatically committed after it executes successfully, or automatically rolled back if it fails. ```sql theme={null} -- Each of these runs in its own implicit transaction INSERT INTO logs (msg) VALUES ('first'); INSERT INTO logs (msg) VALUES ('second'); -- If the second INSERT fails, the first INSERT is still committed ``` ## Transaction Lifecycle A typical transaction lifecycle: ```sql theme={null} -- 1. Begin the transaction BEGIN; -- 2. Perform operations UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 42; INSERT INTO orders (product_id, customer_id) VALUES (42, 7); -- 3a. Commit if everything succeeded COMMIT; -- OR 3b. Rollback if something went wrong ROLLBACK; ``` Each connection can have at most one active top-level transaction at a time. For finer-grained rollback within a transaction, use savepoints. ## Savepoints Savepoints are named markers within a transaction that let you roll back part of the work without aborting the whole transaction. They can be nested. | Statement | Description | | - | - | | `SAVEPOINT name` | Establishes a new savepoint with the given name | | `RELEASE [SAVEPOINT] name` | Removes the savepoint (and any savepoints created after it), keeping its changes as part of the enclosing transaction | | `ROLLBACK [TRANSACTION] TO [SAVEPOINT] name` | Undoes all changes made since the savepoint, but keeps the savepoint and the transaction open | ```sql theme={null} BEGIN; INSERT INTO accounts (name, balance) VALUES ('Alice', 1000); SAVEPOINT before_bonus; UPDATE accounts SET balance = balance + 500 WHERE name = 'Alice'; -- Undo just the bonus, keep the insert ROLLBACK TO before_bonus; -- Discard the savepoint and keep the rest RELEASE before_bonus; COMMIT; ``` > [!NOTE] > > While a write statement on the same connection is still in progress, `SAVEPOINT`, `RELEASE`, and `ROLLBACK TO` may return `SQLITE_BUSY`. ## BEGIN CONCURRENT > [!NOTE] > > **Turso Extension**: BEGIN CONCURRENT is a Turso-specific extension not available in standard SQLite. It requires MVCC mode. The BEGIN CONCURRENT statement starts an optimistic concurrent write transaction that allows multiple connections to write simultaneously. ### Syntax ```sql theme={null} BEGIN CONCURRENT [TRANSACTION]; ``` ### Requirements BEGIN CONCURRENT requires MVCC mode. Enable it by setting the journal mode before opening any transactions: ```sql theme={null} PRAGMA journal_mode = mvcc; ``` ### How It Works A concurrent transaction operates on a snapshot of the database taken at the time of BEGIN CONCURRENT. Each connection reads from its own snapshot and writes independently. When COMMIT is issued, Turso checks whether any other transaction has modified the same rows since the snapshot was taken: * If there is no conflict, the transaction commits successfully. * If a write-write conflict is detected, the COMMIT fails and the transaction must be rolled back and retried. This provides **snapshot isolation**: each transaction sees a consistent view of the database as of its start time, without blocking other writers. ### Example ```sql theme={null} -- Connection 1 PRAGMA journal_mode = mvcc; BEGIN CONCURRENT; UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT; -- Connection 2 (running simultaneously) PRAGMA journal_mode = mvcc; BEGIN CONCURRENT; UPDATE accounts SET balance = balance + 50 WHERE id = 2; COMMIT; -- Both succeed because they modified different rows ``` If both connections modify the same row, the second COMMIT fails: ```sql theme={null} -- Connection 1 BEGIN CONCURRENT; UPDATE accounts SET balance = 900 WHERE id = 1; COMMIT; -- succeeds -- Connection 2 (started before Connection 1 committed) BEGIN CONCURRENT; UPDATE accounts SET balance = 800 WHERE id = 1; COMMIT; -- fails: write-write conflict detected ROLLBACK; -- Retry the transaction from BEGIN CONCURRENT ``` ## See Also * [INSERT](/sql-reference/statements/insert) for inserting rows * [UPDATE](/sql-reference/statements/update) for updating rows * [DELETE](/sql-reference/statements/delete) for deleting rows * [Compatibility](/sql-reference/compatibility) for differences from SQLite # UPDATE Source: https://docs.turso.tech/sql-reference/statements/update Modify existing rows in a table # UPDATE The `UPDATE` statement modifies the values of columns in existing rows of a table. ## Syntax ```sql theme={null} UPDATE [OR conflict-algorithm] table-name SET column-name = expression [, column-name = expression ...] [FROM from-clause] [WHERE expression] [RETURNING result-column [, result-column ...]] [ORDER BY sort-expression [, sort-expression ...]] [LIMIT expression [OFFSET expression]] ``` ## Description `UPDATE` changes column values in rows that match the `WHERE` condition. If no `WHERE` clause is provided, all rows in the table are updated. ### Conflict Handling The optional `OR` clause specifies how to handle constraint violations during the update. When omitted, the default behavior is `ABORT`. | Algorithm | Behavior | | - | - | | `ABORT` | Roll back the current statement and return an error. The transaction remains active. This is the default. | | `ROLLBACK` | Roll back the entire transaction and return an error. | | `FAIL` | Stop at the first constraint violation and return an error, but keep changes made by earlier rows in this statement. | | `IGNORE` | Skip the row that caused the constraint violation and continue processing remaining rows. | | `REPLACE` | Delete the conflicting row before updating the current row. If a NOT NULL constraint fails and there is no DEFAULT value, the statement behaves as `ABORT`. | ```sql theme={null} UPDATE OR IGNORE employees SET department = 'Engineering' WHERE department = 'IT'; ``` ### SET Clause The `SET` clause assigns new values to one or more columns. Each assignment consists of a column name, an equals sign, and an expression. The expression can reference other columns of the same row, and it sees the original (pre-update) values of all columns. ```sql theme={null} UPDATE products SET price = price * 1.10, updated_at = datetime('now'); ``` Multiple columns can also be set using a parenthesized column list: ```sql theme={null} UPDATE products SET (price, updated_at) = (price * 1.10, datetime('now')); ``` ### FROM Clause The `FROM` clause allows the `UPDATE` to reference other tables. Columns from the joined tables can be used in `SET` expressions and the `WHERE` clause. This is useful for updating a table based on matching rows from another table. ```sql theme={null} UPDATE inventory SET quantity = inventory.quantity - order_items.qty FROM order_items WHERE inventory.product_id = order_items.product_id AND order_items.order_id = 1001; ``` When the target table also appears in the `FROM` clause, it refers to a second instance of that table that can be used for self-joins. ### WHERE Clause The `WHERE` clause restricts which rows are updated. Only rows where the expression evaluates to true are modified. If the `WHERE` clause is omitted, every row in the table is updated. ### RETURNING Clause The `RETURNING` clause causes the `UPDATE` statement to return the new values of each modified row. The result columns can be expressions referencing the updated column values. ```sql theme={null} UPDATE accounts SET balance = balance - 100 WHERE account_id = 42 RETURNING account_id, balance; -- Returns: 42|900 ``` ### ORDER BY and LIMIT The `ORDER BY` and `LIMIT` clauses restrict which rows are updated. `ORDER BY` determines the order in which rows are considered, and `LIMIT` caps the number of rows that are actually modified. `ORDER BY` requires `LIMIT` to be present. ```sql theme={null} UPDATE logs SET archived = 1 ORDER BY created_at ASC LIMIT 1000; ``` `LIMIT` accepts an optional `OFFSET` to skip a number of rows before applying the limit: ```sql theme={null} UPDATE logs SET archived = 1 ORDER BY created_at ASC LIMIT 1000 OFFSET 500; ``` ## Examples ### Update rows matching a condition ```sql theme={null} CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary REAL, department TEXT); INSERT INTO employees VALUES (1, 'Alice', 90000, 'Engineering'); INSERT INTO employees VALUES (2, 'Bob', 75000, 'Marketing'); INSERT INTO employees VALUES (3, 'Carol', 85000, 'Engineering'); UPDATE employees SET salary = salary * 1.05 WHERE department = 'Engineering'; SELECT name, salary FROM employees WHERE department = 'Engineering'; -- Alice|94500.0 -- Carol|89250.0 ``` ### Update using a join with FROM ```sql theme={null} CREATE TABLE departments (name TEXT PRIMARY KEY, budget REAL); INSERT INTO departments VALUES ('Engineering', 500000); INSERT INTO departments VALUES ('Marketing', 200000); CREATE TABLE employees2 (id INTEGER PRIMARY KEY, name TEXT, bonus REAL, dept TEXT); INSERT INTO employees2 VALUES (1, 'Alice', 0, 'Engineering'); INSERT INTO employees2 VALUES (2, 'Bob', 0, 'Marketing'); UPDATE employees2 SET bonus = departments.budget * 0.01 FROM departments WHERE employees2.dept = departments.name; SELECT name, bonus FROM employees2; -- Alice|5000.0 -- Bob|2000.0 ``` ### Update with RETURNING ```sql theme={null} CREATE TABLE counters (name TEXT PRIMARY KEY, value INTEGER); INSERT INTO counters VALUES ('page_views', 41); UPDATE counters SET value = value + 1 WHERE name = 'page_views' RETURNING name, value; -- page_views|42 ``` ## See Also * [INSERT](/sql-reference/statements/insert) for adding new rows * [DELETE](/sql-reference/statements/delete) for removing rows * [INSERT ... ON CONFLICT](/sql-reference/statements/upsert) for conditional insert-or-update operations * [SELECT](/sql-reference/statements/select) for querying data # INSERT ... ON CONFLICT (Upsert) Source: https://docs.turso.tech/sql-reference/statements/upsert Insert rows with automatic conflict resolution using DO NOTHING or DO UPDATE # INSERT ... ON CONFLICT (Upsert) The upsert clause is an extension to `INSERT` that specifies what to do when the insert would violate a `UNIQUE` or `PRIMARY KEY` constraint. Instead of failing with an error, the statement can either skip the conflicting row or update it. ## Syntax ```sql theme={null} INSERT INTO table-name [(column-name [, column-name ...])] VALUES (expression [, expression ...]) [, ...] ON CONFLICT [(conflict-target)] DO NOTHING [ON CONFLICT [(conflict-target)] DO NOTHING | DO UPDATE ...] INSERT INTO table-name [(column-name [, column-name ...])] VALUES (expression [, expression ...]) [, ...] ON CONFLICT [(conflict-target)] DO UPDATE SET column-name = expression [, column-name = expression ...] [WHERE expression] [ON CONFLICT [(conflict-target)] DO NOTHING | DO UPDATE ...] ``` Where `conflict-target` is: ```sql theme={null} (column-name [, column-name ...]) [WHERE expression] ``` ## Description An upsert clause is added to the end of an `INSERT` statement to handle constraint violations gracefully. There are two actions: `DO NOTHING` silently skips the conflicting row, and `DO UPDATE` modifies the existing row in place. ### DO NOTHING `DO NOTHING` causes the `INSERT` to silently skip any row that would violate the specified constraint. No error is raised and the existing row is left unchanged. When a conflict target is specified, only violations on that particular constraint trigger the skip. When no conflict target is specified, any constraint violation triggers the skip. ```sql theme={null} INSERT INTO users (id, name) VALUES (1, 'Alice') ON CONFLICT DO NOTHING; ``` ### DO UPDATE `DO UPDATE` causes the `INSERT` to update the existing row when a constraint violation occurs. The `SET` clause specifies which columns to update and what values to assign. A conflict target is required for `DO UPDATE`. ```sql theme={null} INSERT INTO counters (name, value) VALUES ('hits', 1) ON CONFLICT (name) DO UPDATE SET value = counters.value + 1; ``` ### Conflict Target The conflict target identifies which `UNIQUE` or `PRIMARY KEY` constraint the clause applies to. It is a parenthesized list of column names that must exactly match the columns of an existing unique constraint on the table. For partial unique indexes (indexes with a `WHERE` clause), the conflict target must also include a `WHERE` clause that matches the index's condition. ```sql theme={null} -- Given: CREATE UNIQUE INDEX idx ON t(a, b) WHERE c > 0 INSERT INTO t VALUES (1, 2, 3) ON CONFLICT (a, b) WHERE c > 0 DO NOTHING; ``` ### The `excluded` Table Inside the `SET` and `WHERE` clauses of `DO UPDATE`, the special `excluded` table contains the values that were proposed for insertion. This allows the update expression to reference both the existing row (using the table name) and the proposed new row (using `excluded`). | Reference | Meaning | | - | - | | `table-name.column` | The current value of the column in the existing row | | `excluded.column` | The value that was proposed for insertion but caused the conflict | ```sql theme={null} INSERT INTO products (sku, name, price) VALUES ('A001', 'Widget', 12.99) ON CONFLICT (sku) DO UPDATE SET name = excluded.name, price = excluded.price; ``` ### WHERE Clause on DO UPDATE The `DO UPDATE` clause can include a `WHERE` expression that further restricts when the update occurs. If the `WHERE` condition evaluates to false for a conflicting row, that row is left unchanged (the insert is silently skipped, as with `DO NOTHING`). ```sql theme={null} INSERT INTO products (sku, name, price) VALUES ('A001', 'Widget', 8.99) ON CONFLICT (sku) DO UPDATE SET price = excluded.price WHERE excluded.price < products.price; ``` In this example, the price is only updated if the new price is lower than the existing price. ### Multiple ON CONFLICT Clauses An `INSERT` statement can have multiple `ON CONFLICT` clauses, each targeting a different constraint. The clauses are evaluated in order. When a constraint violation occurs, the first matching clause is used. ```sql theme={null} INSERT INTO users (id, email, name) VALUES (1, 'alice@example.com', 'Alice') ON CONFLICT (id) DO UPDATE SET name = excluded.name, email = excluded.email ON CONFLICT (email) DO NOTHING; ``` > [!NOTE] > > At most one `ON CONFLICT` clause can omit the conflict target. A clause without a conflict target acts as a catch-all and must appear last. Only `DO NOTHING` is allowed without a conflict target. ## Examples ### Upsert a counter ```sql theme={null} CREATE TABLE counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL DEFAULT 0); -- First insert creates the row INSERT INTO counters (name, value) VALUES ('logins', 1) ON CONFLICT (name) DO UPDATE SET value = counters.value + excluded.value; SELECT * FROM counters; -- logins|1 -- Subsequent inserts increment the counter INSERT INTO counters (name, value) VALUES ('logins', 1) ON CONFLICT (name) DO UPDATE SET value = counters.value + excluded.value; SELECT * FROM counters; -- logins|2 ``` ### Bulk insert, skipping duplicates ```sql theme={null} CREATE TABLE tags (id INTEGER PRIMARY KEY, label TEXT UNIQUE); INSERT INTO tags VALUES (1, 'rust'); INSERT INTO tags VALUES (2, 'sql'); -- Insert several tags, skipping any that already exist INSERT INTO tags VALUES (2, 'sql'), (3, 'wasm'), (4, 'database') ON CONFLICT DO NOTHING; SELECT * FROM tags; -- 1|rust -- 2|sql -- 3|wasm -- 4|database ``` ### Conditional update with excluded ```sql theme={null} CREATE TABLE prices ( product_id INTEGER PRIMARY KEY, price REAL NOT NULL, updated_at TEXT NOT NULL ); INSERT INTO prices VALUES (1, 29.99, '2025-01-01'); -- Only update the price if the new timestamp is more recent INSERT INTO prices (product_id, price, updated_at) VALUES (1, 24.99, '2025-06-15') ON CONFLICT (product_id) DO UPDATE SET price = excluded.price, updated_at = excluded.updated_at WHERE excluded.updated_at > prices.updated_at; SELECT * FROM prices; -- 1|24.99|2025-06-15 ``` ## See Also * [INSERT](/sql-reference/statements/insert) for standard insert operations * [REPLACE](/sql-reference/statements/replace) for delete-and-reinsert conflict resolution * [UPDATE](/sql-reference/statements/update) for modifying existing rows directly * [CREATE TABLE](/sql-reference/statements/create-table) for defining `UNIQUE` and `PRIMARY KEY` constraints # VACUUM Source: https://docs.turso.tech/sql-reference/statements/vacuum Rebuild the database file to reclaim unused space and defragment storage # VACUUM The VACUUM statement rebuilds the database file to reclaim unused space, defragment tables and indexes, and reduce the file size. Two forms are supported: `VACUUM` rebuilds the current database in place, while `VACUUM INTO` writes a compacted copy to a new file without modifying the source. ## Syntax ```sql theme={null} VACUUM [schema-name]; VACUUM [schema-name] INTO filename; ``` | Parameter | Description | | - | - | | `schema-name` | The database to vacuum. For in-place `VACUUM` only `main` is supported. For `VACUUM INTO` any attached schema is accepted; `temp` is a no-op and produces no file. Defaults to `main`. | | `filename` | A string literal giving the destination file path for `VACUUM INTO`. Bind parameters are not accepted. | ## Common Requirements These rules apply to both `VACUUM` and `VACUUM INTO`: * The connection must be in autocommit mode — neither form can run inside an explicit `BEGIN` transaction. * No other statement may be active on the same connection. * The connection must not be in `query_only` mode. * The source database must not have `auto_vacuum = incremental`. Incremental autovacuum is not supported. ## VACUUM > [!NOTE] > > In-place `VACUUM` is experimental and must be [enabled before use](/sql-reference/experimental-features). `VACUUM INTO` does not require the flag and is always available. In-place `VACUUM` rebuilds the main database by writing a compacted image into an internal temp database and then copying those pages back over the original file. When it completes, unused pages have been released and all storage-backed objects have been recreated. ### Effect * Unused pages from deleted rows and dropped objects are removed, shrinking the file. * All storage-backed tables are recreated and their rows reinserted, which rebuilds the associated indexes. * `sqlite_sequence` counters used by `AUTOINCREMENT` columns are preserved. * The schema cookie is bumped so that other connections reload their cached schema on their next access. * The page size, reserved space, text encoding, user version, and application ID are preserved exactly. ### Requirements In addition to the [common requirements](#common-requirements): * The database must be opened in WAL journal mode. * The database must not be in-memory. * The database must not be read-only. * Only the `main` database is supported. Vacuuming an attached schema in place is not supported yet. * No other process may currently hold the multi-process WAL. ### MVCC Databases When the database uses MVCC (`PRAGMA journal_mode = mvcc`), additional rules apply to in-place `VACUUM`: * All MVCC changes must be checkpointed first. If the MVCC log contains uncheckpointed changes, `VACUUM` returns an error — run `PRAGMA wal_checkpoint(TRUNCATE)` first. * No other MVCC transaction may be active on any connection. `VACUUM` returns a busy error if one is found. * During the operation the MVCC subsystem is paused so that the rebuilt schema and log can be reconciled atomically. ## VACUUM INTO `VACUUM INTO` builds a compacted copy of the database at the given path and leaves the source database untouched. The destination is a fully self-contained database file that can be opened independently. ### Effect * A new database file is created at `filename` containing all user tables, indexes, triggers, views, and virtual table content from the source. * Indexes, triggers, and views are recreated after the data is copied so that triggers do not fire during the copy. * Custom index methods (for example FTS and vector) rebuild their backing structures from the copied data. * `sqlite_sequence` counters used by `AUTOINCREMENT` columns are preserved. * Page size, reserved space, text encoding, user version, and application ID are copied from the source. * If the source uses MVCC, the destination is created with MVCC enabled but with fresh state. The source's MVCC metadata table is not copied across. * The file is fully synced and a TRUNCATE checkpoint is performed before the statement returns, so the destination is durable without further action. > [!WARNING] > > `VACUUM INTO` does not carry encryption through to the destination. Even when the source is encrypted, the compacted copy is written as an ordinary unencrypted database file and can be opened without a key. Reserved header bytes are still copied across, so the destination's page layout matches the source. ### Requirements In addition to the [common requirements](#common-requirements): * The destination file must not already exist. Delete or move it first if you want to overwrite. * The path must be provided as a string literal in the SQL statement. * `VACUUM temp INTO filename` is a no-op and does not create a file, matching SQLite's behavior. `VACUUM INTO` takes a consistent view of the source by starting an implicit read transaction for the duration of the copy, so the destination always reflects a single snapshot of the source even if other connections write to it during the copy. ## Examples ### Rebuild the Main Database ```sql theme={null} -- Reclaim space and defragment the current database VACUUM; ``` ### Write a Compacted Copy to a New File ```sql theme={null} VACUUM INTO 'backup.db'; ``` The source database is unchanged. `backup.db` can be opened as a standalone database: ```bash theme={null} tursodb backup.db ``` ### Vacuum an Attached Database Into a New File ```sql theme={null} ATTACH DATABASE 'archive.db' AS archive; -- Write a compacted copy of the archive schema to a new file VACUUM archive INTO 'archive-compact.db'; ``` ### Use VACUUM INTO for a Backup Snapshot Because `VACUUM INTO` copies from a consistent snapshot, it is a simple way to capture a point-in-time backup of an active database: ```sql theme={null} VACUUM INTO '/var/backups/app-2026-04-23.db'; ``` ### Restore Performance After Heavy Churn After bulk deletes or long-running workloads that leave indexes fragmented, `VACUUM` rebuilds the indexes and often improves scan speed: ```sql theme={null} DELETE FROM events WHERE created_at < '2025-01-01'; VACUUM; ``` ## Errors | Error | Cause | | - | - | | `VACUUM is an experimental feature. Enable with --experimental-vacuum flag` | In-place `VACUUM` used in a release build without the experimental flag. | | `VACUUM is only supported for the main database; schema '' is not supported yet` | In-place `VACUUM schema-name` with a schema other than `main`. | | `Cannot execute VACUUM in query_only mode` | `VACUUM` or `VACUUM INTO` run on a connection where `PRAGMA query_only` is set. | | `cannot VACUUM from within a transaction` / `cannot VACUUM INTO from within a transaction` | Run inside an explicit `BEGIN`. | | `cannot VACUUM - SQL statements in progress` | Another statement is still active on the same connection. | | `Incremental auto-vacuum is not supported` | The source database has `auto_vacuum = incremental`. Applies to both `VACUUM` and `VACUUM INTO`. | | `VACUUM requires a WAL-mode database` | In-place `VACUUM` on a non-WAL database. | | `cannot VACUUM an in-memory database` | In-place `VACUUM` on an in-memory database. | | `ReadOnly` | In-place `VACUUM` on a read-only database. | | `cannot VACUUM while experimental multiprocess WAL is active in another process` | In-place `VACUUM` while another process holds the multi-process WAL. | | `cannot VACUUM an MVCC database with uncheckpointed changes; run PRAGMA wal_checkpoint(TRUNCATE) first` | In-place `VACUUM` on an MVCC database with pending log entries. | | `output file already exists: ` | The destination for `VACUUM INTO` already exists. | | `VACUUM INTO path cannot be empty` | `VACUUM INTO ''` was used. | | `VACUUM INTO requires a string literal path` | A non-literal expression (for example a bind parameter) was used for the destination. | | `no such database: ` | `VACUUM INTO` referenced an unknown schema. | ## See Also * [Experimental Features](/sql-reference/experimental-features) for enabling in-place `VACUUM` * [PRAGMAs](/sql-reference/pragmas) for `auto_vacuum`, `journal_mode`, `query_only`, and `wal_checkpoint` * [ATTACH DATABASE](/sql-reference/statements/attach-database) for attaching a schema that can be targeted by `VACUUM INTO` * [ANALYZE](/sql-reference/statements/analyze) for refreshing query planner statistics after a vacuum # Checkpoint Source: https://docs.turso.tech/sync/checkpoint How to compact the local WAL to bound disk usage while preserving sync state. > [!NOTE] > > This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account. ## Overview The sync engine uses a WAL (Write-Ahead Log) to track local writes. Over time, the WAL grows as you make changes. Checkpoint compacts it by transferring committed frames into the main database file and then truncating the WAL. Auto-checkpoint is **disabled** for sync databases — you must call `checkpoint()` explicitly. ## Why Checkpoint Matters Without checkpointing, the WAL grows unbounded. After many writes, the WAL can become significantly larger than the database itself. Checkpointing reclaims that disk space. You can observe this with `stats()`: ```ts TypeScript theme={null} const before = await db.stats(); console.log('WAL size before:', before.mainWalSize); await db.checkpoint(); const after = await db.stats(); console.log('WAL size after:', after.mainWalSize); ``` ```py Python theme={null} before = conn.stats() print("WAL size before:", before.main_wal_size) conn.checkpoint() after = conn.stats() print("WAL size after:", after.main_wal_size) ``` ```go Go theme={null} before, _ := db.Stats(ctx) log.Printf("WAL size before: %d", before.MainWalSize) db.Checkpoint(ctx) after, _ := db.Stats(ctx) log.Printf("WAL size after: %d", after.MainWalSize) ``` ## When to Checkpoint Call `checkpoint()` periodically based on your write patterns: * **After bulk inserts** — if you insert many rows at once, checkpoint afterward to reclaim WAL space * **On a schedule** — for steady write workloads, checkpoint at regular intervals (e.g. every few minutes) * **When WAL size is large** — use `stats().mainWalSize` to monitor and checkpoint when it exceeds a threshold ```ts TypeScript theme={null} await db.checkpoint(); ``` ```py Python theme={null} conn.checkpoint() ``` ```go Go theme={null} if err := db.Checkpoint(ctx); err != nil { return err } ``` # Conflict Resolution Source: https://docs.turso.tech/sync/conflict-resolution How Turso sync handles concurrent changes from multiple clients. > [!NOTE] > > This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account. ## Last Push Wins Turso sync uses a **last push wins** strategy. When two clients modify the same data and push, the last push determines the final state on the remote. | Time | Client A | Client B | | - | - | - | | T1 | `UPDATE users SET name='Alice' WHERE id=1` | | | T2 | | `UPDATE users SET name='Bob' WHERE id=1` | | T3 | `push()` | | | T4 | | `push()` | **Result**: The name is `'Bob'` because Client B pushed last. ## What Happens During Pull When you pull and have unpushed local changes: 1. Your local database is rolled back to the last synced state 2. Remote changes are applied 3. Your unpushed local changes are **replayed** on top This rollback-and-replay happens atomically — if anything fails, your database remains in its previous state. # Local Sync Server Source: https://docs.turso.tech/sync/local-sync-server Use the Turso CLI as a local sync server for development and testing without Turso Cloud. The `tursodb` CLI includes a built-in sync server that you can run locally. This lets you develop and test sync workflows entirely on your machine — no Turso Cloud account required. > [!NOTE] > > The local sync server implements the same sync protocol as Turso Cloud, so your application code works the same way in both environments. ## Starting the server Pass `--sync-server` with an address to start the sync server. The database argument specifies where the server stores its data: ```bash theme={null} tursodb ./server.db --sync-server 0.0.0.0:8080 ``` This starts a sync server listening on port `8080`, backed by `./server.db`. ## Connecting clients Point your sync client at `http://localhost:8080`. No auth token is needed for the local server. ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const db = await connect({ path: './local-replica.db', url: 'http://localhost:8080', }); ``` ```py Python theme={null} import turso.sync conn = turso.sync.connect( path="./local-replica.db", remote_url="http://localhost:8080", ) ``` ```go Go theme={null} package main import ( turso "turso.tech/database/tursogo" ) db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "./local-replica.db", RemoteUrl: "http://localhost:8080", }) ``` Once connected, all sync operations (`push`, `pull`, `checkpoint`, `stats`) work exactly as described in the [Usage](/sync/usage) guide. ## Example: full local sync workflow This example walks through a complete workflow — starting the server, writing data from one client, and syncing it to another. **1. 1. Start the sync server** Open a terminal and start the server: ```bash theme={null} tursodb ./server.db --sync-server 0.0.0.0:8080 ``` **2. 2. Write and push from Client A** ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const clientA = await connect({ path: './client-a.db', url: 'http://localhost:8080', }); await clientA.exec("CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)"); await clientA.exec("INSERT INTO notes VALUES ('n1', 'hello from client A')"); await clientA.push(); ``` ```py Python theme={null} import turso.sync client_a = turso.sync.connect( path="./client-a.db", remote_url="http://localhost:8080", ) client_a.execute("CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)") client_a.commit() client_a.execute("INSERT INTO notes VALUES ('n1', 'hello from client A')") client_a.commit() client_a.push() ``` ```go Go theme={null} clientA, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "./client-a.db", RemoteUrl: "http://localhost:8080", }) if err != nil { return err } connA, _ := clientA.Connect(ctx) connA.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS notes (id TEXT PRIMARY KEY, body TEXT)") connA.ExecContext(ctx, "INSERT INTO notes VALUES ('n1', 'hello from client A')") clientA.Push(ctx) ``` **3. 3. Pull from Client B** ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const clientB = await connect({ path: './client-b.db', url: 'http://localhost:8080', }); const changed = await clientB.pull(); console.log('changes pulled:', changed); const rows = await clientB.query("SELECT * FROM notes"); console.log(rows); // [{ id: 'n1', body: 'hello from client A' }] ``` ```py Python theme={null} import turso.sync client_b = turso.sync.connect( path="./client-b.db", remote_url="http://localhost:8080", ) changed = client_b.pull() print("changes pulled:", changed) rows = client_b.execute("SELECT * FROM notes").fetchall() print(rows) # [('n1', 'hello from client A')] ``` ```go Go theme={null} clientB, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "./client-b.db", RemoteUrl: "http://localhost:8080", }) if err != nil { return err } changed, _ := clientB.Pull(ctx) log.Println("changes pulled:", changed) connB, _ := clientB.Connect(ctx) rows, _ := connB.QueryContext(ctx, "SELECT * FROM notes") ``` # Partial sync Source: https://docs.turso.tech/sync/partial Sync only what you need. Faster cold starts and lower bandwidth by lazily fetching database pages on demand. > [!NOTE] > > This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account. Partial sync lets your app open and use a database without downloading the entire file. The client lazily fetches pages of the database file from the Turso Cloud when a query touches data that is not present locally. This reduces startup time and network usage for large databases, while remaining fully compatible with the push/pull methods used by Turso's standard `sync` solution. > [!NOTE] > > * Reads on not-yet-downloaded data transparently trigger on-demand page fetches. > * Writes still apply locally first and are pushed as logical statements. ## Modes Two bootstrap strategies define what is present locally at connect time: * **Prefix bootstrap**: download the first N bytes of the database file. * Good default when you want a minimal, predictable starting footprint. * **Query bootstrap**: download pages touched by running a server-side SQL query. * Ideal to hydrate only a narrow working set (e.g., a single user's rows, small tables with metadata, references, etc). Both modes continue to lazily fetch missing pages on demand. ### Prefix bootstrap ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const db = await connect({ path: './app.db', url: 'turso://...', authToken: process.env.TURSO_AUTH_TOKEN, partialSyncExperimental: { bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB }, }); ``` ```py Python theme={null} import os import turso.sync conn = turso.sync.connect( path="./app.db", remote_url="turso://...", auth_token=os.environ["TURSO_AUTH_TOKEN"], partial_sync_experimental=turso.sync.PartialSyncOpts( bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024), ), ) ``` ```go Go theme={null} import ( turso "turso.tech/database/tursogo" ) db, err := turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{ Path: "./app.db", RemoteUrl: "turso://...", AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), PartialSyncExperimental: turso.TursoPartialSyncConfig{ BootstrapStrategyPrefix: 128 * 1024, // 128 KiB }, }) ``` ### Query bootstrap ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const db = await connect({ path: './app.db', url: 'turso://...', authToken: process.env.TURSO_AUTH_TOKEN, partialSyncExperimental: { bootstrapStrategy: { kind: 'query', query: `SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100`, }, }, }); ``` ```py Python theme={null} import turso.sync conn = turso.sync.connect( path=":memory:", remote_url="turso://...", partial_sync_experimental=turso.sync.PartialSyncOpts( bootstrap_strategy=turso.sync.PartialSyncQueryBootstrap( query="SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100" ), ), ) ``` ```go Go theme={null} import ( turso "turso.tech/database/tursogo" ) db, err := turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{ Path: "./app.db", RemoteUrl: "turso://...", AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), PartialSyncExperimental: turso.TursoPartialSyncConfig{ BootstrapStrategyQuery: "SELECT * FROM messages WHERE user_id = 'u_123' LIMIT 100", }, }) ``` ## Optimizations ### Segment size (batched lazy reads) Read page 7 → fetch segment 5-8 (3 new pages) When the client needs a page that isn’t present locally, partial sync performs an on-demand fetch from the remote database. To reduce round-trips and speed up these fetches, you can configure the **segment size**: instead of requesting a single page, the client downloads a whole *segment* of pages in one request. This lets the client amortize network overhead and hydrate nearby pages that are likely to be accessed soon. **How it works** Suppose your database has: * `page_size = 4 KiB` * `segment_size = 16 KiB` If a local query touches page **6**, the client computes the segment that page belongs to: * 16 KiB segment = 4 pages * Segment covering page 6 = pages **5-8** The client then fetches all four pages in a single request and stores them locally. Future reads to those pages incur no additional network cost. **Benefits** * Fewer HTTP requests (one segment fetch vs. many single-page fetches) * Faster hydration of hot ranges * Better performance for workloads with spatial locality (e.g., range scans, index lookups) **Default** The default `segment_size` is **128 KiB** (typically 32 pages on a 4 KiB page size), which provides a good balance between request overhead and total bytes transferred. > [!TIP] > > If your workload touches data in tight clusters (e.g., reading several adjacent rows), larger segment sizes can significantly improve performance. > > Conversely, very sparse/random-access workloads may benefit from smaller segment sizes. ```ts TypeScript theme={null} await connect({ ... partialSyncExperimental: { bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB segmentSize: 16 * 1024, }, }); ``` ```py Python theme={null} turso.sync.connect( ... partial_sync_experimental=turso.sync.PartialSyncOpts( bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024), segment_size=16 * 1024, ), ) ``` ```go Go theme={null} turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{ ... PartialSyncExperimental: turso.TursoPartialSyncConfig{ BootstrapStrategyPrefix: 128 * 1024, // 128 KiB SegmentSize: 16 * 1024, }, }) ``` ### Prefetch Read page 4 → prefetch 2 child pages (11, 12) Prefetch is an optional optimization that builds on top of lazy page fetches. When enabled, the client not only retrieves the pages required by the current query, but also **inspects the structure of the newly downloaded pages and recent access patterns** to predict which pages are likely to be needed next. If the client detects a natural continuation of the access pattern — such as child pages referenced by an internal B-tree node — it proactively downloads those pages in advance. This reduces the number of future on-demand fetches and helps avoid stalls during operations like range scans, index walks, or sequential lookups. ```ts TypeScript theme={null} await connect({ ... partialSyncExperimental: { bootstrapStrategy: { kind: 'prefix', length: 128 * 1024 }, // 128 KiB prefetch: true, }, }); ``` ```py Python theme={null} turso.sync.connect( ... partial_sync_experimental=turso.sync.PartialSyncOpts( bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024), prefetch=True, ), ) ``` ```go Go theme={null} turso.NewTursoSyncDb(context.Background(), turso.TursoSyncDbConfig{ ... PartialSyncExperimental: turso.TursoPartialSyncConfig{ BootstrapStrategyPrefix: 128 * 1024, // 128 KiB Prefetch: true, }, }) ``` > [!NOTE] > > `segment_size` and `prefetch` are complementary. > > Segment size batches nearby pages into a single on-demand fetch, while prefetch looks at the query's access pattern and proactively fetches additional pages likely to be needed next. > > Using both together can provide the best performance for real-world workloads. # Usage Source: https://docs.turso.tech/sync/usage How to enable and use sync with Turso across TypeScript, Python, and Go. This guide shows how to set up a Turso database and use the sync features from your application. > [!NOTE] > > This particular usage uses the Turso Cloud to sync the local Turso databases and assumes that you have an account. **1. 1. Setup Turso Cloud database** * Follow our [Quickstart](/quickstart) to install the CLI, create a database * Get the database URL (`turso://...`): ``` turso db show ``` * Create a token for your app: ``` turso db tokens create ``` **2. 2. Setup a basic connection with sync** You need three essentials to enable sync: * Local path: where the local, synced tursodb file is stored * Remote URL: your Turso Cloud URL (`turso://...`) * Auth token: Turso Cloud token to authenticate requests ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; const db = await connect({ path: './app.db', // local path url: 'turso://...', // remote URL (generated with turso db show --url) authToken: process.env.TURSO_AUTH_TOKEN, // authentication token (generated with turso db tokens create ) // longPollTimeoutMs: 10_000, // optional: server waits before replying to pull // bootstrapIfEmpty: false, // set to false to avoid bootstrapping on first run }); ``` ```py Python theme={null} import os import turso.sync conn = turso.sync.connect( path="./app.db", # local path remote_url="turso://...", # remote URL (generated with turso db show --url) auth_token=os.environ["TURSO_AUTH_TOKEN"], # authentication token (generated with turso db tokens create ) # long_poll_timeout_ms=10_000, # optional: server waits before replying to pull # bootstrap_if_empty=False, # set to false to avoid bootstrapping on first run ) ``` ```go Go theme={null} package main import ( turso "turso.tech/database/tursogo" ) db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "./app.db", // local path RemoteUrl: "turso://...", // remote URL (generated with turso db show --url) AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), // authentication token (generated with turso db tokens create ) // LongPollTimeoutMs: 10_000, // optional: server waits before replying to pull // BootstrapIfEmpty: new(bool), // *bool; point at false to avoid bootstrapping on first run }) ``` > [!NOTE] > > On the first run, the local database is automatically bootstrapped from the remote — so the remote must be reachable during the initial connect. > > If you set `bootstrap_if_empty` to `false`, the local database will start empty instead. > You can bootstrap or update it later at any time by explicitly calling `pull()`. **3. 3. Push changes** Push sends your local changes to the Turso Cloud server. Under the hood, logical statements are sent, and on conflicts the strategy is "last push wins". COMMITTED WRITE transactions which overlap with WRITE transactions that are still open MAY be delayed. To push every committed transaction, make sure no write transaction is open when you push. ```ts TypeScript theme={null} await db.exec("CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)"); await db.exec("INSERT INTO notes VALUES ('n1', 'hello')"); await db.push(); ``` ```py Python theme={null} conn.execute("CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)") conn.commit() conn.execute("INSERT INTO notes VALUES ('n1', 'hello')") conn.commit() conn.push() ``` ```go Go theme={null} // create *sql.DB instance conn, err := db.Connect(ctx) if err != nil { return err } _, err = conn.ExecContext(ctx, "CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT)") if err != nil { return err } _, err = conn.ExecContext(ctx, "INSERT INTO notes VALUES ('n1', 'hello')") if err != nil { return err } if err := db.Push(ctx); err != nil { return err } ``` **4. 4. Pull changes** Pull fetches remote changes and applies them locally. It returns a boolean indicating whether anything changed. * Configure `long_poll_timeout_ms`/`LongPollTimeoutMs` if you want the server to wait for changes and avoid empty replies. * If you pushed earlier, a subsequent pull can still return that something changed due to server-side conflict resolution frames. ```ts TypeScript theme={null} // Returns true if anything changed locally const changed = await db.pull(); console.info('pulled changes:', changed); ``` ```py Python theme={null} changed = conn.pull() print("pulled changes:", changed) ``` ```go Go theme={null} changed, err := db.Pull(ctx) if err != nil { return err } log.Println("pulled changes:", changed) ``` **5. 5. Checkpoint** Checkpoint compacts the local WAL to bound local disk usage while preserving sync state. ```ts TypeScript theme={null} await db.checkpoint(); ``` ```py Python theme={null} conn.checkpoint() ``` ```go Go theme={null} if err := db.Checkpoint(ctx); err != nil { return err } ``` **6. 6. Stats** Stats help you observe sync behavior and usage (WAL sizes, last push/pull times, network usage, revision, etc.). ```ts TypeScript theme={null} const s = await db.stats(); console.info({ cdcOperations: s.cdcOperations, mainWalSize: s.mainWalSize, revertWalSize: s.revertWalSize, networkReceivedBytes: s.networkReceivedBytes, networkSentBytes: s.networkSentBytes, lastPullUnixTime: s.lastPullUnixTime, lastPushUnixTime: s.lastPushUnixTime, revision: s.revision, }); ``` ```py Python theme={null} s = conn.stats() print({ "cdc_operations": s.cdc_operations, "main_wal_size": s.main_wal_size, "revert_wal_size": s.revert_wal_size, "network_received_bytes": s.network_received_bytes, "network_sent_bytes": s.network_sent_bytes, "last_pull_unix_time": s.last_pull_unix_time, "last_push_unix_time": s.last_push_unix_time, "revision": s.revision, }) ``` ```go Go theme={null} s, err := db.Stats(ctx) if err != nil { return err } log.Printf("stats: cdc=%v, main=%d revert=%d rx=%d tx=%d pull=%d push=%d revision=%s", s.CdcOperations, s.MainWalSize, s.RevertWalSize, s.NetworkReceivedBytes, s.NetworkSentBytes, s.LastPullUnixTime, s.LastPushUnixTime, s.Revision, ) ``` ## Offline-first writes If your app needs to accept writes without internet connectivity, write locally and call `push()` when the connection is available. All changes are safely stored in the local database file until they can be synced. ```ts TypeScript theme={null} import { connect } from '@tursodatabase/sync'; // bootstrapIfEmpty: false lets the app start offline without // needing to reach the remote on first launch const db = await connect({ path: './local.db', url: process.env.TURSO_URL!, authToken: process.env.TURSO_AUTH_TOKEN!, bootstrapIfEmpty: false, }); async function syncWhenOnline(db) { try { await db.push(); } catch (e) { // No connectivity — changes are safe in the local file // and will sync on the next push() call } } // On app launch, pull the latest state if online try { await db.pull(); } catch (e) { // Offline — local data is still available for reads and writes } // On a timer or connectivity event await syncWhenOnline(db); ``` ```py Python theme={null} import os import turso.sync # bootstrap_if_empty=False lets the app start offline without # needing to reach the remote on first launch conn = turso.sync.connect( path="./local.db", remote_url=os.environ["TURSO_URL"], auth_token=os.environ["TURSO_AUTH_TOKEN"], bootstrap_if_empty=False, ) def sync_when_online(conn): try: conn.push() except Exception: # No connectivity — changes are safe in the local file # and will sync on the next push() call pass # On app launch, pull the latest state if online try: conn.pull() except Exception: # Offline — local data is still available for reads and writes pass # On a timer or connectivity event sync_when_online(conn) ``` ```go Go theme={null} // BootstrapIfEmpty pointing at false lets the app start offline // without needing to reach the remote on first launch noBootstrap := false db, err := turso.NewTursoSyncDb(ctx, turso.TursoSyncDbConfig{ Path: "./local.db", RemoteUrl: os.Getenv("TURSO_URL"), AuthToken: os.Getenv("TURSO_AUTH_TOKEN"), BootstrapIfEmpty: &noBootstrap, }) func syncWhenOnline(ctx context.Context, db *turso.TursoSyncDb) { if err := db.Push(ctx); err != nil { // No connectivity — changes are safe in the local file // and will sync on the next Push() call log.Println("push deferred:", err) } } // On app launch, pull the latest state if online if _, err := db.Pull(ctx); err != nil { // Offline — local data is still available for reads and writes log.Println("pull deferred:", err) } // On a timer or connectivity event syncWhenOnline(ctx, db) ``` This is the modern equivalent of the `offline: true` flag from [`@libsql/client` Embedded Replicas](/features/embedded-replicas/introduction). With Turso Sync, all reads and writes happen locally by default — you control when to sync with explicit `push()` and `pull()` calls. # Turso Cloud Documentation Source: https://docs.turso.tech/turso-cloud Fully managed platform for SQLite-compatible databases. Deploy Turso, a rewrite of SQLite with concurrent writes and multi-user access built in, or libSQL, the battle-tested SQLite fork. No need to switch to Postgres for concurrency. Turso Cloud is a fully managed database platform, designed to scale from prototype to production. It hosts two database engines, both fully compatible with SQLite: * **[Turso](/tursodb/quickstart)** — a ground-up rewrite of SQLite. Turso supports **concurrent writes** and **multi-user access by default**: many clients can read and write the same database at the same time, over the network. If SQLite's single-writer limitation is what would push you toward a client-server database like Postgres, Turso removes it — you keep SQLite's simplicity and compatibility and gain the concurrency. Concurrent writes are the headline, but not the whole story: see the [Turso documentation](/tursodb/quickstart) for change data capture, vector search, encryption, sync, and more. * **[libSQL](/libsql)** — a fork of SQLite, battle-tested in production on Turso Cloud for years. > [!NOTE] > > Turso databases on Turso Cloud are in early preview — a reflection of how > recently the offering landed on the platform, not of the engine itself. > Create one with `turso db create --tursodb`. > **[Vector Search](/features/ai-and-embeddings)** > > Native similarity search for AI apps and RAG workflows, no extensions > required. > **[Replication & Sync](/features/embedded-replicas/introduction)** > > Keep devices in sync with each other on demand with Turso Cloud. > **[Branching](/features/branching)** > > Create isolated Copy-on-Write branches super fast. > **Analytics** > > Monitor performance and usage across databases. > **[Bring-Your-Own-Key Encryption](/cloud/encryption)** > > Keep sensitive data private with keys only you control. Keys are per-database for full isolation. > **[Scoped Access Tokens](/sdk/authorization)** > > Fine-grained tokens scoped by database, table, action, and expiration. Read-only, write, or custom permissions. > **Team Access** > > Collaborate on databases with members, and manage access permissions. > **Fully Managed** > > We handle infrastructure, scaling, and security. > **[Backups and Recovery](/features/point-in-time-recovery)** > > Restore your database to any point in time, and recover deleted databases > for up to five days. ## Start building Learn how to manage, distribute and integrate your databases with the CLI, API and SDKs. > **[Turso CLI](/cli)** > > Manage groups, databases, and API tokens with the Turso CLI. > **[Turso Platform API](/api-reference)** > > Programmatically manage groups and databases using the Platform API. > **[Client SDKs](/sdk)** > > Connect and integrate your application with one of our client SDKs, for > Turso and libSQL databases alike. > **[Tutorials](https://turso.tech/blog)** > > Learn how to work with Turso and your favorite language or framework. ## Community Join the Turso community to ask questions, share what you're working on, discuss best practices, and share tips on [Discord](https://tur.so/discord), [Twitter](https://twitter.com/tursodatabase), and [LinkedIn](https://www.linkedin.com/company/turso). > **[Discord](https://tur.so/discord)** > **[GitHub](https://github.com/tursodatabase)** > **[X (Twitter)](https://twitter.com/tursodatabase)** # Change Data Capture Source: https://docs.turso.tech/tursodb/cdc Change Data Capture (CDC) tracks all data changes (inserts, updates, deletes) made to your database tables. Changes are recorded in a local table that you can query like any other table. This is useful for building reactive applications, syncing data between systems, auditing, and more. ## Enable CDC Enable CDC per connection using a PRAGMA: ```sql theme={null} PRAGMA capture_data_changes_conn('full'); ``` To disable: ```sql theme={null} PRAGMA capture_data_changes_conn('off'); ``` ## Capture Modes | Mode | Description | | - | - | | `id` | Capture only the primary key/rowid of changed rows | | `before` | Capture row state before changes (for updates/deletes) | | `after` | Capture row state after changes (for inserts/updates) | | `full` | Capture both before and after states, plus per-column update details | ## CDC Table Changes are stored in the `turso_cdc` table by default. You can specify a custom table name: ```sql theme={null} PRAGMA capture_data_changes_conn('full,my_changes_table'); ``` The CDC table has the following schema: | Column | Type | Description | | - | - | - | | `change_id` | INTEGER | Auto-incrementing unique identifier | | `change_time` | INTEGER | Timestamp (Unix epoch) | | `change_txn_id` | INTEGER | Transaction ID (groups rows into transactions) | | `change_type` | INTEGER | `1` = INSERT, `0` = UPDATE, `-1` = DELETE, `2` = COMMIT | | `table_name` | TEXT | Name of the table that was changed | | `id` | varies | Primary key/rowid of the changed row | | `before` | BLOB | Row data before the change (modes: `before`, `full`) | | `after` | BLOB | Row data after the change (modes: `after`, `full`) | | `updates` | BLOB | Per-column change details (mode: `full`) | ## Querying Changes ```sql theme={null} -- All changes SELECT * FROM turso_cdc; -- Only inserts SELECT * FROM turso_cdc WHERE change_type = 1; -- Only updates SELECT * FROM turso_cdc WHERE change_type = 0; -- Only deletes SELECT * FROM turso_cdc WHERE change_type = -1; -- Changes for a specific table SELECT * FROM turso_cdc WHERE table_name = 'users'; -- Changes in the last hour SELECT * FROM turso_cdc WHERE change_time > unixepoch() - 3600; ``` ## Decoding Binary Records The `before`, `after`, and `updates` columns store data in a binary format. Use the built-in helper functions to decode them: ```sql theme={null} SELECT change_type, table_name, id, bin_record_json_object(table_columns_json_array('users'), after) AS after_state, bin_record_json_object(table_columns_json_array('users'), before) AS before_state FROM turso_cdc WHERE table_name = 'users' AND change_type != 2; ``` | Function | Description | | - | - | | `table_columns_json_array(table_name)` | Returns a JSON array of column names for a table (e.g. `["id","name","email"]`) | | `bin_record_json_object(columns_json, blob)` | Decodes a binary record into a JSON object using the given column names | ## Transactions CDC respects transaction boundaries. Changes are only recorded when a transaction commits. If a transaction rolls back, no CDC entries are created. All rows from the same transaction share the same `change_txn_id`, with a single COMMIT record (`change_type = 2`) at the end: ```sql theme={null} BEGIN; INSERT INTO users VALUES (1, 'Alice', 'alice@example.com'); INSERT INTO users VALUES (2, 'Bob', 'bob@example.com'); UPDATE users SET name = 'Charles' WHERE id = 1; COMMIT; -- All 3 changes + 1 COMMIT record share the same change_txn_id ``` ## Schema Changes CDC also tracks DDL operations (CREATE TABLE, DROP TABLE, CREATE INDEX, etc.) as changes to the `sqlite_schema` table: ```sql theme={null} PRAGMA capture_data_changes_conn('full'); CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT); -- Recorded in turso_cdc as a change to sqlite_schema ``` ## Multiple Connections Each connection has its own independent CDC configuration. Different connections can capture to different tables: ```sql theme={null} -- Connection 1: Capture to 'audit_log' PRAGMA capture_data_changes_conn('full,audit_log'); -- Connection 2: Capture to 'sync_queue' PRAGMA capture_data_changes_conn('id,sync_queue'); ``` ## Example ```sql theme={null} -- Create a table CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, email TEXT ); -- Enable full CDC PRAGMA capture_data_changes_conn('full'); -- Make some changes INSERT INTO users VALUES (1, 'Alice', 'alice@example.com'); INSERT INTO users VALUES (2, 'Bob', 'bob@example.com'); UPDATE users SET email = 'alice@newdomain.com' WHERE id = 1; DELETE FROM users WHERE id = 2; -- View the captured changes with decoded data SELECT change_id, CASE change_type WHEN 1 THEN 'INSERT' WHEN 0 THEN 'UPDATE' WHEN -1 THEN 'DELETE' WHEN 2 THEN 'COMMIT' END AS operation, table_name, id, bin_record_json_object(table_columns_json_array('users'), after) AS after_state FROM turso_cdc WHERE change_type != 2 ORDER BY change_id; ``` # Changelog Source: https://docs.turso.tech/tursodb/changelog # Concurrent Writes Source: https://docs.turso.tech/tursodb/concurrent-writes Turso with default configuration only allows one connection to write at a time. With MVCC (Multi-Version Concurrency Control), multiple connections can write simultaneously. If two transactions modify the same data, one will receive a conflict error and must roll back and retry. ## Enable MVCC Set the journal mode on your database connection: ```sql theme={null} PRAGMA journal_mode = 'mvcc'; ``` ## BEGIN CONCURRENT Use `BEGIN CONCURRENT` instead of `BEGIN` to start a transaction that allows other writers to proceed in parallel: ```sql theme={null} BEGIN CONCURRENT; -- your writes here COMMIT; ``` If two transactions touch the same rows, one will receive a conflict error and must roll back and retry. Transactions that write to non-overlapping data proceed without conflict. ## Handling Conflicts Your application must detect conflict errors and retry the transaction. ## Example **Rust** ```rust theme={null} fn is_retryable(e: &Error) -> bool { matches!(e, Error::Busy(_) | Error::BusySnapshot(_)) || matches!(e, Error::Error(msg) if msg.contains("conflict")) } #[tokio::main] async fn main() -> Result<(), Error> { let tmp = NamedTempFile::new().expect("failed to create temp file"); let db = Builder::new_local(tmp.path().to_str().unwrap()) .build() .await?; let conn = db.connect()?; conn.pragma_update("journal_mode", "'mvcc'").await?; conn.execute("CREATE TABLE hits (val INTEGER)", ()).await?; let mut handles = Vec::new(); for _ in 0..16 { let db = db.clone(); handles.push(tokio::spawn(async move { let val = rand::rng().random_range(1..=100); let conn = db.connect()?; loop { conn.execute("BEGIN CONCURRENT", ()).await?; let result = conn .execute(&format!("INSERT INTO hits VALUES ({val})"), ()) .await .and(conn.execute("COMMIT", ()).await); match result { Ok(_) => return Ok::<_, Error>(val), Err(ref e) if is_retryable(e) => { let _ = conn.execute("ROLLBACK", ()).await; tokio::task::yield_now().await; } Err(e) => { let _ = conn.execute("ROLLBACK", ()).await; return Err(e); } } } })); } for handle in handles { handle.await.expect("task panicked")?; } Ok(()) } ``` **JavaScript** ```js theme={null} const DB_PATH = join(mkdtempSync(join(tmpdir(), "turso-mvcc-")), "hits.db"); function isRetryable(err) { const msg = (err?.message ?? "").toLowerCase(); return msg.includes("conflict") || msg.includes("busy"); } async function writeWorker() { const val = Math.floor(Math.random() * 100) + 1; const db = await connect(DB_PATH); try { await db.exec("PRAGMA journal_mode = 'mvcc'"); while (true) { await db.exec("BEGIN CONCURRENT"); try { await db.exec(`INSERT INTO hits VALUES (${val})`); await db.exec("COMMIT"); return val; } catch (err) { try { await db.exec("ROLLBACK"); } catch (_) {} if (!isRetryable(err)) throw err; await new Promise((r) => setImmediate(r)); } } } finally { await db.close(); } } const setup = await connect(DB_PATH); await setup.exec("PRAGMA journal_mode = 'mvcc'"); await setup.exec("CREATE TABLE hits (val INTEGER)"); await setup.close(); await Promise.all(Array.from({ length: 16 }, () => writeWorker())); ``` **Python** ```python theme={null} def is_retryable(e: Exception) -> bool: msg = str(e).lower() return "conflict" in msg or "busy" in msg def write_worker(db_path: str) -> int: val = random.randint(1, 100) conn = turso.connect(db_path) try: conn.execute("PRAGMA journal_mode = 'mvcc'").fetchone() while True: conn.execute("BEGIN CONCURRENT") try: conn.execute("INSERT INTO hits VALUES (?)", (val,)) conn.execute("COMMIT") return val except Exception as e: try: conn.execute("ROLLBACK") except Exception: pass if not is_retryable(e): raise finally: conn.close() async def main() -> None: with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp: db_path = tmp.name setup = turso.connect(db_path) setup.execute("PRAGMA journal_mode = 'mvcc'").fetchone() setup.execute("CREATE TABLE hits (val INTEGER)") setup.commit() setup.close() tasks = [asyncio.to_thread(write_worker, db_path) for _ in range(16)] await asyncio.gather(*tasks) asyncio.run(main()) ``` Try the full example: [Rust](https://github.com/tursodatabase/turso/blob/main/bindings/rust/examples/concurrent_writes.rs) · [JavaScript](https://github.com/tursodatabase/turso/blob/main/examples/javascript/concurrent-writes/index.mjs) · [Python](https://github.com/tursodatabase/turso/blob/main/examples/python/concurrent_writes.py) # Encryption Source: https://docs.turso.tech/tursodb/encryption Turso Database provides native encryption for data at rest using industry-standard AEAD algorithms. Each database page is encrypted individually, ensuring data integrity and security across all environments. * **Page-level encryption**: Each 4 KiB page is encrypted individually with a unique nonce * **AEAD algorithms**: Support for AEGIS-256 (fast, modern) and AES-GCM (NIST-approved) * **Data integrity**: Built-in authentication tags prevent tampering and corruption * **High performance**: As little as 6% read overhead and 14% write overhead * **In-memory keys**: Encryption keys are never stored on disk Turso Database with encryption is perfect for: > **Fintech Applications** > > Meet regulatory requirements for sensitive financial data > **Privacy-Focused Products** > > Build user trust with strong data protection > **Healthcare Apps** > > Protect patient data and meet HIPAA compliance > **AI/ML Applications** > > Secure training data and model outputs ## What's Encrypted > **Encrypted** > > * All database pages with your data - The database file - Write-Ahead Log > (WAL) file > **Not Encrypted** > > * The database header (first 100 bytes) ## Generate an Encryption Key Generate a secure encryption key in hexadecimal format. The key size depends on the cipher you choose: **256-bit (32 bytes)** For AEGIS-256 variants and AES-256-GCM: ```bash theme={null} openssl rand -hex 32 ``` Output example: ``` 2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d ``` **128-bit (16 bytes)** For AEGIS-128 variants and AES-128-GCM: ```bash theme={null} openssl rand -hex 16 ``` Output example: ``` 5f3e2a8c9b1d4f6e7a2c8d4b9e1f3a6c ``` > [!WARNING] > > **Store your key securely!** If you lose the encryption key, you will not be > able to access your encrypted database. Keys are never stored on disk. ## Create an Encrypted Database **1. Launch with encryption** Use the `--experimental-encryption` flag and specify your cipher and key in the connection URI: ```bash theme={null} tursodb --experimental-encryption "file:encrypted.db?cipher=aegis256&hexkey=YOUR_HEX_KEY" ``` Replace `YOUR_HEX_KEY` with the key you generated above. **2. Create and insert data** Once in the interactive shell, create a table and insert some data: ```sql theme={null} CREATE TABLE secrets (id INT, data TEXT); INSERT INTO secrets VALUES (1, 'sensitive information'); INSERT INTO secrets VALUES (2, 'confidential data'); ``` **3. Verify encryption** Exit the shell (type `.quit`) and try to open the database without the key: ```bash theme={null} tursodb encrypted.db ``` You won't be able to access the data. The database is encrypted on disk. ## Open an Encrypted Database To access an existing encrypted database, provide the same cipher and key used during creation: ```bash theme={null} tursodb --experimental-encryption "file:encrypted.db?cipher=aegis256&hexkey=YOUR_HEX_KEY" ``` Then query your data: ```sql theme={null} SELECT * FROM secrets; ``` ## Supported Ciphers Turso Database supports multiple variants of two AEAD encryption algorithms, offering different trade-offs between performance and compatibility. ### AES-GCM NIST-approved standard for compliance requirements. Widely supported across industries. | Cipher | Key Size | URI Value | Use Case | | - | - | - | - | | **AES-128-GCM** | 128-bit (16 bytes) | `aes128gcm` | Compliance with 128-bit requirements | | **AES-256-GCM** | 256-bit (32 bytes) | `aes256gcm` | Maximum security for compliance | ### AEGIS Modern, high-performance cipher family optimized for speed. **Recommended for most use cases.** | Cipher | Key Size | URI Value | Use Case | | - | - | - | - | | **AEGIS-128L** | 128-bit (16 bytes) | `aegis128l` | Balanced performance, 128-bit security | | **AEGIS-128X2** | 128-bit (16 bytes) | `aegis128x2` | 2x parallel processing | | **AEGIS-128X4** | 128-bit (16 bytes) | `aegis128x4` | 4x parallel processing, maximum speed | | **AEGIS-256** | 256-bit (32 bytes) | `aegis256` | Balanced 256-bit security | | **AEGIS-256X2** | 256-bit (32 bytes) | `aegis256x2` | 2x parallel processing, 256-bit | | **AEGIS-256X4** | 256-bit (32 bytes) | `aegis256x4` | 4x parallel, maximum speed & security | > [!NOTE] > > **Choosing a cipher:** > > * Use `aegis128L` for 128 bit encryption, `aegis256` for 256 bit encryption (default recommendation) > * Use the other AEGIS variants if your hardware supports it `aegis128X2` `aegis128X4` `aegis256x2` `aegis256x4` > * For AES, use `aes128gcm` for 128 bit encryption, `aes256gcm` for 256 bit encryption ## URI Parameters When creating or opening an encrypted database, use the following URI format: ``` file:database.db?cipher=CIPHER&hexkey=HEX_KEY ``` | Parameter | Required | Description | Values | | - | - | - | - | | `cipher` | Yes | Encryption algorithm | See [Supported Ciphers](#supported-ciphers) | | `hexkey` | Yes | Encryption key in hex format | 32-character hex (128-bit) or 64-character hex (256-bit) | **Example URIs:** ```bash theme={null} # AEGIS-256 with 256-bit key file:encrypted.db?cipher=aegis256&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d # AES-128-GCM with 128-bit key file:encrypted.db?cipher=aes128gcm&hexkey=5f3e2a8c9b1d4f6e7a2c8d4b9e1f3a6c # AEGIS-256X4 (max performance) with 256-bit key file:encrypted.db?cipher=aegis256x4&hexkey=2d7a30108d3eb3e45c90a732041fe54778bdcf707c76749fab7da335d1b39c1d ``` ## Performance Encryption overhead is minimal, especially for smaller databases: * **Read operations**: \~6% overhead with AEGIS-256 * **Write operations**: \~14% overhead with AEGIS-256 * **Overall**: \~1-3% total time overhead for mixed workloads For detailed benchmarks, see the [encryption announcement blog post](https://turso.tech/blog/introducing-fast-native-encryption-in-turso-database). ## Coming Soon Future encryption features in development: * **Key derivation from passphrases**: Use memorable passphrases instead of raw keys * **Encrypt existing databases**: Migrate unencrypted databases to encrypted format * **Key rotation**: Update encryption keys without data loss # Quickstart Source: https://docs.turso.tech/tursodb/quickstart **1. Install** **macOS / Linux** ```bash theme={null} curl --proto '=https' --tlsv1.2 -LsSf https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.sh | sh ``` **Windows** Run the following command in PowerShell: ```powershell theme={null} irm https://github.com/tursodatabase/turso/releases/latest/download/turso_cli-installer.ps1 | iex ``` Alternatively, you can [download the Windows executable](https://github.com/tursodatabase/turso/releases/latest) and add it to your PATH. **2. Launch** Then launch the interactive shell: ```bash theme={null} tursodb ``` This will start the Turso interactive shell where you can execute SQL statements: ``` Turso Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database turso> ``` **3. Execute** Now you can execute SQL statements in the interactive shell: > **Create a table** > > Create a table for users: > > ```sql theme={null} > CREATE TABLE users (id INT, username TEXT); > ``` > **Insert data** > > Insert some data into the users table: > > ```sql theme={null} > INSERT INTO users VALUES (1, 'alice'); > INSERT INTO users VALUES (2, 'bob'); > ``` > **Query data** > > Query all users from the table: > > ```sql theme={null} > SELECT * FROM users; > ``` > > This will return: > > ``` > 1|alice > 2|bob > ``` ## Experimental Features You can enable experimental features when launching `tursodb` using various flags such as `--experimental-encryption`, `--experimental-mvcc`, `--experimental-strict`, and `--experimental-views`. **Note:** These features are not production ready, so do not use them for critical data right now. For a complete list of available flags and options, see the [Turso manual](https://github.com/tursodatabase/turso).