Error Handling & Debugging
Error Handling & Debugging
Level 10 — SDKs, Deployment & Production Systematic approaches for interpreting SurrealDB error messages, diagnosing query syntax errors, troubleshooting permission failures, and debugging issues using the SurrealDB CLI.
1. Prerequisites
- SurrealDB CLI (
surreal sql) — Interactive CLI console. PERMISSIONSClause (Table & Field Level) — Permission errors.ASSERTClause — Field constraint assertions.
2. Term Category
SurrealQL Command (database transaction error handling mechanisms): - Troubleshooting & Diagnostics
3. Explanation
(1) Design Motivation — "Why did we design this?"
During application development and database maintenance, queries fail for various reasons: syntax typos, record constraint failures (ASSERT), unauthorized permission checks (PERMISSIONS), connection drops, or write transaction conflicts.
To troubleshoot effectively, developers need a systematic understanding of SurrealDB error categories and diagnostic tools:
- Syntax & Parsing Errors: Occur when SurrealQL statements violate language grammar.
- Assertion & Constraint Failures: Occur when incoming data violates
ASSERTconditions or strictSCHEMAFULLfield types. - Authorization & Permission Denied Errors: Occur when a Record Access user attempts an operation rejected by table
PERMISSIONS. - Interactive CLI Debugging (
surreal sql): Running queries directly in the CLI with--log traceto inspect detailed execution traces and server logs.
(2) Diagnostic Flowchart
┌────────────────────────────┐
│ Query Execution Failed │
└─────────────┬──────────────┘
│
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Syntax Error │ │ Runtime Error │
│ "Found invalid token" │ │ "Permission Denied" │
└────────────┬────────────┘ └──────────┬──────────────┘
│ │
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ Fix SurrealQL typos or │ │ Check $auth context and │
│ run `surreal validate` │ │ table PERMISSIONS rules │
└─────────────────────────┘ └─────────────────────────┘
(3) Code Examples
Short Snippet
# Start SurrealDB CLI with trace-level debug logging enabled for troubleshooting
surreal start --log trace --user root --pass root surrealkv://data/db.db
Fuller Example (Handling Common Error Codes in JavaScript SDK)
import { Surreal } from 'surrealdb';
const db = new Surreal();
async function safeQueryExecution() {
try {
await db.connect('ws://localhost:8000/rpc');
await db.use({ namespace: 'app', database: 'prod' });
// Intentional constraint violation to trigger assertion error
await db.create('user', { email: 'invalid-email-string' });
} catch (err: any) {
console.error('Captured SurrealDB Error:', err.message);
if (err.message.includes('ASSERT')) {
console.warn('Field validation failed! Check ASSERT rules for string formatting.');
} else if (err.message.includes('Permissions')) {
console.warn('Access denied! Verify $auth record identity and table PERMISSIONS.');
} else if (err.message.includes('No namespace')) {
console.warn('Scope missing! Ensure db.use({ namespace, database }) was called.');
}
}
}
4. Common Mistakes & Pitfalls
Mistake 1: Confusing "Record Not Found" with "Permission Denied" in Record Auth Queries
The mistake: Assuming a empty result [] returned from SELECT * FROM post means no posts exist in the database.
Why it's wrong: Under Record Access, if table PERMISSIONS restrict reads (FOR select WHERE published = true), SurrealDB silently filters out unauthorized records. To the user, it looks like 0 records exist, even if 100 unpublished posts are stored in the table.
Diagnostic Check:
Test the exact same query using Root superuser credentials in surreal sql. If records appear for Root but return [] for Record users, the issue is a PERMISSIONS policy check.
Mistake 2: Swallowing Query Execution Errors in Client Application Code
The mistake: Wrapping database calls in empty try { ... } catch {} blocks without logging or retrying.
Why it's wrong: Swallowing errors masks permission failures, unique constraint violations, and transaction conflicts, making root-cause debugging impossible.
Incorrect:
try { await db.create('user', data); } catch (e) {} // ❌ Swallows errors silently!
Fix:
try { await db.create('user', data); } catch (err) { console.error('SurrealDB Error:', err); throw err; }
Mistake 3: Ignoring Transaction Conflict Aborts in Distributed Clusters
The mistake: Executing concurrent transactions without retry mechanisms on conflict errors.
Why it's wrong: Optimistic concurrency control in distributed storage engines throws transaction conflicts under concurrent writes. Catch conflict errors and retry transactions.
Incorrect:
// Un-handled OCC transaction conflict
Fix:
Implement exponential backoff retry loops for transactional operations
5. Practice Exercises
Exercise 1: Custom Exception Throwing in Transactions
Scenario:
Check an account balance inside a transaction script. If balance is less than withdrawal amount, throw a custom exception using THROW.
Requirements:
- Declare
LET $balance = 50.00dec;. - Check
IF $balance < $withdrawal THEN THROW "Insufficient funds!" END;.
Answer
Implementation
LET $balance = 50.00dec;
LET $withdrawal = 100.00dec;
BEGIN TRANSACTION;
IF $balance < $withdrawal THEN (
THROW "Insufficient funds! Current balance: " + <string> $balance
) END;
UPDATE account:a1 SET balance -= $withdrawal;
COMMIT TRANSACTION;
Technical Explanation
THROWaborts transaction execution immediately and returns a custom error exception payload.- Automatically rolls back all uncommitted mutations inside the active transaction block.
- Enforces domain validation rules at the database tier.
Exercise 2: Catching Errors in Field Assertions
Scenario:
Catch assertion write errors when inserting an invalid email address into table user.
Requirements:
- Define field
emailwithASSERT string::is::email($value). - Demonstrate write failure on invalid string.
Answer
Implementation
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON TABLE user TYPE string
ASSERT string::is::email($value) OR THROW "Invalid email address format!";
-- Fails with custom assertion error!
CREATE user:u1 SET email = "not-an-email";
Technical Explanation
- Combining
ASSERTwithOR THROWcustomizes field validation error messages. - Returns clear error descriptions to SDK callers.
- Prevents invalid data insertion.
Exercise 3: Handling Primary Key Collision Errors
Scenario:
Handle primary key collision errors when creating a record user:alice that already exists.
Requirements:
- Execute
CREATE user:alicetwice to demonstrate primary key conflict error.
Answer
Implementation
CREATE user:alice SET name = "Alice";
-- Second execution fails with record conflict error:
-- "Database record 'user:alice' already exists"
CREATE user:alice SET name = "Alice Duplicate";
Technical Explanation
CREATEthrows a primary key collision exception if the record ID already exists.- Use
UPSERTorINSERT ON DUPLICATE KEY UPDATEif collision updates are desired. - Guarantees primary key uniqueness.
6. Related Terms
- SurrealDB CLI (
surreal sql) — Interactive CLI console. surreal validate(Query Validation) — Pre-flight syntax validation.PERMISSIONSClause (Table & Field Level) — Table security permissions.SLEEPStatement — Related concept:SLEEPStatement.
7. Key Takeaways
- Use
surreal validateto catch static syntax typos in.surqlfiles. - Enable
--log traceon the server CLI to inspect detailed execution logs. - Test queries as Root superuser vs Record user to isolate
PERMISSIONSissues from missing data.