| name | surrealdb |
| description | Write production-ready SurrealDB queries and operations using SurrealQL. Use when users need to create schemas, write CRUD queries, model graph relationships, build authentication systems, optimize performance, or work with SurrealDB in any capacity. |
SurrealDB - Production-Ready Query Generator
Generate solid, high-quality, production-ready SurrealDB queries and operations using SurrealQL for multi-model database applications including document, graph, and relational patterns.
When to Use This Skill
Use this skill when the user wants to:
- Write SurrealQL queries (SELECT, CREATE, UPDATE, DELETE, UPSERT)
- Design database schemas (SCHEMAFULL/SCHEMALESS tables, field definitions)
- Model relationships (record links, graph edges with RELATE, nested data)
- Implement authentication (DEFINE ACCESS, SCOPE, permissions, RBAC)
- Create indexes for performance optimization
- Write custom functions using DEFINE FUNCTION
- Build real-time applications with LIVE queries
- Implement transactions for data consistency
- Migrate from SQL/NoSQL to SurrealDB
- Debug or optimize existing SurrealQL
SurrealQL Quick Reference
Core Statement Syntax
SELECT *, ->friends->person AS mutual_friends FROM person:alice;
CREATE person:john SET name = 'John', age = 30;
UPDATE person SET age += 1, tags += 'senior' WHERE age >= 65;
DELETE person WHERE active = false;
UPSERT user:email@example.com SET email = 'email@example.com', visits += 1;
RELATE person:alice->follows->person:bob SET since = time::now();
Data Types
string, int, float, bool, datetime, duration, decimal, uuid
array, object, record<table>, option<type>
geometry (point, line, polygon), bytes, null, none
Essential Functions
time::now()
time::floor(datetime, 1d)
duration::from::days(7)
string::is::email($value)
string::concat($a, ' ', $b)
string::split($s, ',')
string::lowercase($s)
array::len($arr)
array::push($arr, $item)
array::distinct($arr)
array::flatten($arr)
crypto::argon2::generate($password)
crypto::argon2::compare($hash, $password)
math::sum($arr)
math::mean($arr)
math::max($arr)
record::id($record)
record::table($record)
type::is::string($val)
type::thing($table, $id)
Instructions for Writing SurrealDB Queries
Step 1: Understand the Data Model
Before writing any SurrealQL:
- What is the data structure? (Document, graph, relational, or hybrid?)
- What relationships exist? (One-to-many, many-to-many, graph traversals?)
- What access patterns? (Read-heavy, write-heavy, real-time?)
- What consistency requirements? (Eventual, strong, transactional?)
Step 2: Choose Schema Strategy
SCHEMAFULL - Use when:
- Data structure is well-defined
- Type safety is critical
- Validation rules are needed
- Production workloads
SCHEMALESS - Use when:
- Rapid prototyping
- Evolving data structures
- Flexible document storage
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON user TYPE string ASSERT string::is::email($value);
DEFINE FIELD password ON user TYPE string;
DEFINE FIELD created_at ON user TYPE datetime DEFAULT time::now();
DEFINE FIELD status ON user TYPE string DEFAULT 'active'
ASSERT $value IN ['active', 'inactive', 'suspended'];
DEFINE TABLE event SCHEMALESS;
Step 3: Design Relationships
Choose the right relationship model:
Record Links - Simple, direct references:
CREATE user:alice SET
name = 'Alice',
friends = [user:bob, user:carol];
SELECT *, friends.* FROM user:alice;
Graph Edges (RELATE) - Complex relationships with metadata:
RELATE user:alice->follows->user:bob SET
since = time::now(),
notifications = true;
SELECT
->follows->user AS following,
<-follows<-user AS followers
FROM user:alice;
SELECT ->follows->user->follows->user AS friends_of_friends
FROM user:alice;
Embedded Documents - Denormalized data:
CREATE order SET
customer = { name: 'Alice', email: 'alice@example.com' },
items = [
{ product: 'Widget', quantity: 2, price: 29.99 },
{ product: 'Gadget', quantity: 1, price: 49.99 }
],
total = 109.97;
Step 4: Implement Authentication
Record-Level Access with DEFINE ACCESS:
DEFINE ACCESS user_auth ON DATABASE TYPE RECORD
SIGNUP (
CREATE user SET
email = $email,
password = crypto::argon2::generate($password),
created_at = time::now()
)
SIGNIN (
SELECT * FROM user
WHERE email = $email
AND crypto::argon2::compare(password, $password)
)
DURATION FOR TOKEN 24h, FOR SESSION 7d;
DEFINE TABLE post SCHEMAFULL
PERMISSIONS
FOR select WHERE published = true OR author = $auth.id
FOR create WHERE $auth.id != NONE
FOR update WHERE author = $auth.id
FOR delete WHERE author = $auth.id;
Step 5: Optimize with Indexes
DEFINE INDEX unique_email ON user FIELDS email UNIQUE;
DEFINE INDEX order_lookup ON order FIELDS customer, status;
DEFINE ANALYZER english TOKENIZERS blank FILTERS lowercase, snowball(english);
DEFINE INDEX content_search ON article FIELDS content
SEARCH ANALYZER english BM25;
EXPLAIN SELECT * FROM user WHERE email = 'test@example.com';
Step 6: Write Transactions
BEGIN TRANSACTION;
LET $amount = 100;
UPDATE account:alice SET balance -= $amount;
UPDATE account:bob SET balance += $amount;
CREATE transaction SET
from = account:alice,
to = account:bob,
amount = $amount,
timestamp = time::now();
COMMIT TRANSACTION;
Common Query Patterns
CRUD Operations
Create with validation:
CREATE user CONTENT {
email: 'user@example.com',
name: 'John Doe',
roles: ['user'],
metadata: {
source: 'signup',
ip: '192.168.1.1'
}
};
Select with filtering and pagination:
SELECT * FROM user
WHERE status = 'active'
AND created_at > time::now() - 30d
ORDER BY created_at DESC
LIMIT 20
START 0;
Update with operators:
UPDATE user:alice SET login_count += 1;
UPDATE user:alice SET tags += 'premium', tags -= 'trial';
UPDATE user SET status = 'inactive'
WHERE last_login < time::now() - 90d;
Upsert pattern:
UPSERT user:email@example.com SET
email = 'email@example.com',
last_seen = time::now(),
visits += 1;
Graph Queries
Social network - friends of friends:
SELECT
id,
name,
array::distinct(->follows->user->follows->user) AS suggested_friends
FROM user:alice
WHERE suggested_friends != user:alice;
E-commerce - product recommendations:
SELECT
<-purchased<-user->purchased->product AS related_products,
count() AS frequency
FROM product:widget123
GROUP BY related_products
ORDER BY frequency DESC
LIMIT 10;
Knowledge graph - recursive traversal:
SELECT
->parent->(1..5)->category AS ancestors
FROM category:electronics;
Analytics Queries
Aggregations:
SELECT
status,
count() AS total,
math::mean(age) AS avg_age,
math::min(created_at) AS first_created
FROM user
GROUP BY status;
Time-series analysis:
SELECT
time::floor(timestamp, 1h) AS hour,
count() AS events,
math::sum(value) AS total_value
FROM metrics
WHERE timestamp > time::now() - 24h
GROUP BY hour
ORDER BY hour;
Subqueries and Computed Fields
Subquery in SELECT:
SELECT
*,
(SELECT count() FROM post WHERE author = $parent.id) AS post_count,
(SELECT VALUE title FROM post WHERE author = $parent.id LIMIT 5) AS recent_posts
FROM user;
LET for complex queries (CTE alternative):
LET $active_users = (SELECT id FROM user WHERE status = 'active');
LET $recent_orders = (SELECT * FROM order WHERE created_at > time::now() - 7d);
SELECT * FROM $recent_orders
WHERE customer IN $active_users.id;
Schema Design Patterns
User Profile with Nested Objects
DEFINE TABLE user SCHEMAFULL;
DEFINE FIELD email ON user TYPE string ASSERT string::is::email($value);
DEFINE FIELD password ON user TYPE string;
DEFINE FIELD profile ON user TYPE object;
DEFINE FIELD profile.name ON user TYPE string;
DEFINE FIELD profile.avatar ON user TYPE option<string>;
DEFINE FIELD profile.bio ON user TYPE option<string>;
DEFINE FIELD settings ON user TYPE object DEFAULT {};
DEFINE FIELD settings.notifications ON user TYPE bool DEFAULT true;
DEFINE FIELD settings.theme ON user TYPE string DEFAULT 'light';
DEFINE FIELD created_at ON user TYPE datetime DEFAULT time::now();
DEFINE FIELD updated_at ON user TYPE datetime VALUE time::now();
DEFINE INDEX unique_email ON user FIELDS email UNIQUE;
E-commerce Schema
DEFINE TABLE product SCHEMAFULL;
DEFINE FIELD name ON product TYPE string;
DEFINE FIELD description ON product TYPE string;
DEFINE FIELD price ON product TYPE decimal;
DEFINE FIELD inventory ON product TYPE int DEFAULT 0;
DEFINE FIELD categories ON product TYPE array<record<category>>;
DEFINE FIELD active ON product TYPE bool DEFAULT true;
DEFINE INDEX product_search ON product FIELDS name, description
SEARCH ANALYZER blank BM25;
DEFINE TABLE order SCHEMAFULL;
DEFINE FIELD customer ON order TYPE record<user>;
DEFINE FIELD items ON order TYPE array;
DEFINE FIELD items.*.product ON order TYPE record<product>;
DEFINE FIELD items.*.quantity ON order TYPE int;
DEFINE FIELD items.*.price ON order TYPE decimal;
DEFINE FIELD status ON order TYPE string DEFAULT 'pending'
ASSERT $value IN ['pending', 'processing', 'shipped', 'delivered', 'cancelled'];
DEFINE FIELD total ON order TYPE decimal;
DEFINE FIELD created_at ON order TYPE datetime DEFAULT time::now();
DEFINE INDEX order_customer ON order FIELDS customer;
DEFINE INDEX order_status ON order FIELDS status, created_at;
Graph Relationship Schema
DEFINE TABLE follows SCHEMAFULL TYPE RELATION;
DEFINE FIELD in ON follows TYPE record<user>;
DEFINE FIELD out ON follows TYPE record<user>;
DEFINE FIELD since ON follows TYPE datetime DEFAULT time::now();
DEFINE FIELD notifications ON follows TYPE bool DEFAULT true;
DEFINE INDEX unique_follow ON follows FIELDS in, out UNIQUE;
Custom Functions
DEFINE FUNCTION fn::engagement_score($user_id: record<user>) {
LET $posts = (SELECT count() FROM post WHERE author = $user_id);
LET $comments = (SELECT count() FROM comment WHERE author = $user_id);
LET $likes_received = (SELECT count() FROM like WHERE post.author = $user_id);
RETURN ($posts * 5) + ($comments * 2) + $likes_received;
};
SELECT *, fn::engagement_score(id) AS score FROM user;
DEFINE FUNCTION fn::normalize_email($email: string) {
IF !string::is::email($email) {
THROW "Invalid email format";
};
RETURN string::lowercase(string::trim($email));
};
DEFINE FUNCTION fn::paginate($table: string, $page: int, $per_page: int) {
LET $offset = ($page - 1) * $per_page;
RETURN (SELECT * FROM type::table($table) LIMIT $per_page START $offset);
};
Real-Time (LIVE Queries)
LIVE SELECT * FROM post WHERE published = true;
LIVE SELECT * FROM user:alice;
LIVE SELECT *, ->comments->comment AS comments FROM post;
KILL $live_query_id;
Performance Best Practices
1. Use Specific Record IDs Instead of Scans
SELECT * FROM user:alice;
SELECT * FROM user WHERE id = 'alice';
2. Select Only Needed Fields
SELECT name, email FROM user;
SELECT * FROM user;
3. Use Indexes Effectively
DEFINE INDEX active_users ON user FIELDS status, created_at;
SELECT * FROM user
WHERE status = 'active'
ORDER BY created_at DESC;
4. Batch Operations
INSERT INTO log [
{ level: 'info', message: 'Start' },
{ level: 'info', message: 'Processing' },
{ level: 'info', message: 'Complete' }
];
5. Use Transactions for Related Operations
BEGIN TRANSACTION;
COMMIT TRANSACTION;
Common Anti-patterns to Avoid
1. Missing Field Existence Checks
SELECT * FROM user WHERE profile.name = 'John';
2. N+1 Query Problem
SELECT *, posts.* FROM user FETCH posts;
3. Not Using Appropriate Relationship Model
CREATE user SET friend_ids = ['alice', 'bob'];
CREATE user SET friends = [user:alice, user:bob];
4. Over-fetching with Graph Traversals
SELECT ->*->* FROM user:alice;
SELECT ->(1..3)->follows->user FROM user:alice;
Debugging and Testing
Explain Query Execution
EXPLAIN SELECT * FROM user WHERE email = 'test@example.com';
EXPLAIN FULL SELECT * FROM user WHERE email = 'test@example.com';
Check Table Info
INFO FOR TABLE user;
INFO FOR DB;
INFO FOR NS;
Test Queries with Parameters
LET $email = 'test@example.com';
SELECT * FROM user WHERE email = $email;
Output Format
When generating SurrealDB queries, always provide:
- Complete SurrealQL statements with proper syntax
- Schema definitions when creating tables/fields
- Index recommendations for query patterns
- Example data for testing
- Explanation of design decisions
- Performance considerations if relevant
Reference Files
Version Compatibility
- SurrealDB 2.x: Latest features, GraphQL support, improved performance
- SurrealDB 1.x: Legacy version, use
scope instead of access
Always verify target SurrealDB version before generating queries.