Functions
Functions
Section titled “Functions”Functions are the foundation of scaf tests. Each function definition declares a named database query that tests will exercise.
Basic Syntax
Section titled “Basic Syntax”fn FunctionName(params) ` -- Your database query here`The query body is enclosed in backticks (`) and contains raw database syntax in your configured dialect.
Naming Functions
Section titled “Naming Functions”Function names must be valid identifiers:
- Start with a letter or underscore
- Contain letters, digits, or underscores
- Are case-sensitive
fn GetUser(id) `...` // Validfn getUserById(id) `...` // Validfn get_user_2(id) `...` // Validfn _internal(id) `...` // Valid (starts with underscore)Parameters
Section titled “Parameters”Basic Parameters
Section titled “Basic Parameters”Functions use $paramName syntax for parameters in the query body:
fn GetUser(userId) `MATCH (u:User {id: $userId})RETURN u.name, u.email`
fn SearchUsers(searchTerm, minAge) `MATCH (u:User)WHERE u.name CONTAINS $searchTerm AND u.age >= $minAgeRETURN u`Parameters are provided in test cases:
GetUser { test "finds user" { $userId: 1 $searchTerm: "ali" $minAge: 18
u.name: "Alice" }}Type Annotations
Section titled “Type Annotations”Parameters can have optional type annotations:
fn GetUser(userId: string) `MATCH (u:User {id: $userId})RETURN u.name, u.age`Supported Types
Section titled “Supported Types”| Type | Description |
|---|---|
string | Text values |
int | Integer numbers |
float64 | Floating-point numbers |
bool | Boolean values |
any | Any type (default) |
fn CreateUser( id: string, name: string, age: int, score: float64, verified: bool,) `CREATE (u:User {id: $id, name: $name, age: $age, score: $score, verified: $verified})RETURN u`Nullable Types
Section titled “Nullable Types”Use ? suffix for nullable types:
fn FindUser(name: string, age: int?) `MATCH (u:User {name: $name})WHERE u.age = $age OR $age IS NULLRETURN u`Array Types
Section titled “Array Types”Use [type] for array types:
fn GetUsersByIds(ids: [string]) `MATCH (u:User) WHERE u.id IN $idsRETURN u.name`Map Types
Section titled “Map Types”Use {keyType: valueType} for map types:
fn CreateWithMeta(data: {string: any}) `CREATE (n:Node $data)RETURN n`Multi-line Parameters
Section titled “Multi-line Parameters”Parameters can span multiple lines with trailing commas:
fn CreateUser( id: string, name: string, email: string, age: int?, // trailing comma OK) `CREATE (u:User {id: $id, name: $name, email: $email, age: $age})RETURN u`No Parameters
Section titled “No Parameters”Functions with no parameters use empty parentheses:
fn CountAllNodes() `MATCH (n) RETURN count(n) as total`Return Values
Section titled “Return Values”Functions should return named values that tests can reference:
fn GetUserStats(userId) `MATCH (u:User {id: $userId})OPTIONAL MATCH (u)-[:AUTHORED]->(p:Post)RETURN u.name as name, u.email as email, count(p) as postCount, collect(p.title) as postTitles`In tests, reference these by their alias:
GetUserStats { test "user with posts" { $userId: 1
name: "Alice" postCount: 5 postTitles: ["Post 1", "Post 2", "Post 3", "Post 4", "Post 5"] }}Property Paths
Section titled “Property Paths”For node/relationship returns, use dot notation:
fn GetUserNode(userId) `MATCH (u:User {id: $userId})RETURN u`
GetUserNode { test "returns user node" { $userId: 1
u.name: "Alice" u.email: "alice@example.com" u.age: 30 }}Multi-Statement Queries
Section titled “Multi-Statement Queries”Functions can contain multiple statements:
fn CreateAndReturn(id, name) `CREATE (u:User {id: $id, name: $name})WITH uMATCH (u)-[:FRIENDS_WITH]->(f:User)RETURN u.name, collect(f.name) as friends`Cypher Examples
Section titled “Cypher Examples”Simple MATCH
Section titled “Simple MATCH”fn GetUser(userId) `MATCH (u:User {id: $userId})RETURN u.name, u.email`With Relationships
Section titled “With Relationships”fn GetUserWithFriends(userId) `MATCH (u:User {id: $userId})-[:FRIENDS_WITH]->(f:User)RETURN u.name, collect(f.name) as friends`Aggregations
Section titled “Aggregations”fn GetUserStats(userId) `MATCH (u:User {id: $userId})OPTIONAL MATCH (u)-[:AUTHORED]->(p:Post)OPTIONAL MATCH (p)<-[:LIKES]-(liker:User)RETURN u.name, count(DISTINCT p) as postCount, count(liker) as totalLikes`Optional Matching
Section titled “Optional Matching”fn GetUserOptional(userId) `MATCH (u:User {id: $userId})OPTIONAL MATCH (u)-[:HAS_PROFILE]->(p:Profile)RETURN u.name, p.bio`CREATE Operations
Section titled “CREATE Operations”fn CreateUser(id, name, email) `CREATE (u:User {id: $id, name: $name, email: $email})RETURN u.id, u.name`
CreateUser { test "creates user" { $id: 999 $name: "New User" $email: "new@example.com"
u.id: 999 u.name: "New User" }}UPDATE Operations
Section titled “UPDATE Operations”fn UpdateUserEmail(userId, newEmail) `MATCH (u:User {id: $userId})SET u.email = $newEmailRETURN u.email`DELETE Operations
Section titled “DELETE Operations”fn DeleteUser(userId) `MATCH (u:User {id: $userId})DELETE uRETURN count(*) as deleted`
DeleteUser { test "deletes user" { $userId: 1
deleted: 1 }}SQL Examples
Section titled “SQL Examples”Simple SELECT
Section titled “Simple SELECT”fn GetUser(userId) `SELECT name, email, ageFROM usersWHERE id = $userId`With JOINs
Section titled “With JOINs”fn GetUserWithPosts(userId) `SELECT u.name, u.email, COUNT(p.id) as post_countFROM users uLEFT JOIN posts p ON p.author_id = u.idWHERE u.id = $userIdGROUP BY u.id, u.name, u.email`Best Practices
Section titled “Best Practices”-
Use descriptive names —
GetUserByIdis clearer thanQ1 -
Return named values — Makes test assertions more readable
-
Keep functions focused — One function per operation type
-
Use parameters — Never hardcode values that should vary
-
Add type annotations — Improves documentation and validation
-
Format for readability — Multi-line queries are fine
// Good: Clear, parameterized, typed, returns named valuesfn GetActiveUsersInCity(city: string, limit: int) `MATCH (u:User)WHERE u.active = true AND u.city = $cityRETURN u.name as name, u.email as email, u.joinDate as joinedAtORDER BY u.joinDate DESCLIMIT $limit`
// Avoid: Unclear name, no parameters, no typesfn Q3() `MATCH (u:User {active: true, city: "NYC"})RETURN u`