Docs
View as markdown

Swift API

The public types and methods of the relate Swift package, the CypherValue cases, result metadata, errors, concurrency rules, and platforms.

swift

The Swift client A language package that runs the relate engine inside a program's process: the Python, Node.js, Rust, Java, Go and Swift clients. Read more in the glossary is the package Relate, from the repository github.com/relatedb/relate, for iOS 16 or later and macOS 13 or later in the Swift 6 language mode. It is a Swift layer over the relate C library, linked in as the static XCFramework RelateFFI, whose C application binary interface (C ABI) is version 2: values cross through the library's typed value walker and parameter builder, with no JSON. Every call is synchronous and throws RelateError. This page describes the package of relate 0.1.2.

Synopsis

import Relate

public final class Database: @unchecked Sendable {
    public init(path: String) throws
    public init(readOnlyPath path: String) throws
    public static func inMemory() throws -> Database
    public let path: String
    public let isReadOnly: Bool
    public func connect() throws -> Connection
    public func connect(namespace: String?) throws -> Connection
    public func checkpoint() throws
}

public final class Connection {
    public let database: Database
    @discardableResult
    public func query(_ cypher: String, _ parameters: [String: CypherValue] = [:]) throws -> QueryResult
    public func setNamespace(_ name: String) throws
    public func begin() throws
    @discardableResult public func commit() throws -> String?
    public func rollback() throws
    @discardableResult public func transaction<T>(_ body: (Connection) throws -> T) throws -> T
}

Public types

TypeKindHolds
Databasefinal class, SendableAn open store
Connectionfinal class, not SendableOne session on a store
QueryResultstruct, SendableOne statement's columns, rows, and metadata
QueryTypeenum, String raw values.read "r", .write "w", .readWrite "rw", .schema "s"
Namespaceenum, String raw values.neo4j "neo4j", .system "system", .systemOntologies "system_ontologies"
QueryStatsstructThe 11 mutation counters
CypherValueenum, Sendable, HashableOne Cypher value, at any depth
CypherValueKindenum, UInt32 raw valuesThe kind of a CypherValue
NotificationstructOne planner notification
PlanDocument, PlanNode, PlanActual, PlanStartstructsThe structured plan
RelateErrorenum, LocalizedErrorEvery error the package throws
EngineErrorstructThe engine's classified error: code, gqlStatus, stage, retryable, message

Database

MemberBehavior
init(path:)Opens :memory:, a directory store or a .rl store for writing; creates a missing store
init(readOnlyPath:)Loads an existing store once; refuses writes and checkpoints; a missing store is an error
inMemory()Database(path: ":memory:")
path, isReadOnlyThe path the database was opened with, and whether it is read-only
connect()A connection in the neo4j namespace
connect(namespace:)A connection in neo4j, system or system_ontologies, matched case-insensitively; nil selects neo4j
checkpoint()Writes a checkpoint, folding the write-ahead log into a fresh snapshot of committed state; refused on a read-only database

A Database closes its store A relate database: a directory, a single .rl file, or :memory:, opened with --database. Read more in the glossary when its last reference is released; there is no close(). Every Connection keeps its database, so the store outlives its connections. A failed open throws .engine with message internal error, code Neo.DatabaseError.General.UnknownError, GQLSTATUS 42000 and stage .internal, whether the store is locked by another writer or missing. An unknown namespace throws semantic error: DatabaseNotFound: Graph not found: <name>.

Connection

MemberBehavior
query(_:_:)Runs one statement; auto-commits outside an explicit transaction; an empty dictionary binds no parameters
setNamespace(_:)Changes the session namespace; refused inside a transaction
begin()Opens an explicit transaction; transactions do not nest
commit()Commits; returns the bookmark "relate:<n>", or nil when the transaction wrote nothing
rollback()Discards the explicit transaction
transaction(_:)Begins, runs the body, and commits when it returns; when the body or the commit throws, rolls back and rethrows that error

A Connection disconnects when it is released. Its own refusals, each .engine with code Neo.ClientError.Statement.SemanticError and GQLSTATUS 42000:

CallMessage
begin() inside a transactionsemantic error: already in a transaction
commit() or rollback() with no transactionsemantic error: no transaction
setNamespace(_:) inside a transactionsemantic error: TransactionError: Cannot change the namespace while a transaction is open.

QueryResult

A QueryResult is a plain value, copied out of the engine before query returns.

MemberTypeHolds
columns[String]The column names, in RETURN order
rows[[CypherValue]]One array per row, cells in column order
records[[String: CypherValue]]The rows keyed by column name; for a repeated name, the last column's value
scalar()CypherValueThe one value of a one-row, one-column result; otherwise throws .decoding
queryTypeQueryTypeThe plan-time classification; EXPLAIN and PROFILE report the wrapped statement's type
namespaceNamespaceThe statement's USE target, else the session namespace
bookmarkString?An auto-commit write's bookmark, relate:<n>; nil for a read, an EXPLAIN, a write whose counters are all zero (it changed nothing), and any statement inside an explicit transaction
statsQueryStatsThe mutation counters; all zero for a read or an EXPLAIN
notifications[Notification]Filled by EXPLAIN and PROFILE; empty on a plain run
planPlanDocument?The relate.plan/v1 plan document under EXPLAIN or PROFILE; nil on a plain run
elapsedMsDoubleThe engine's parse, plan and execute time, in milliseconds
parseMs, planMs, executeMsDoubleThe phases of elapsedMs; their sum never exceeds it

A QueryResult has no column types. QueryStats has 11 Int properties: nodesCreated, nodesDeleted, relationshipsCreated, relationshipsDeleted, propertiesSet, labelsAdded, labelsRemoved, indexesAdded, indexesRemoved, constraintsAdded, constraintsRemoved. A bookmark's <n> grows across every connection of the store and orders as a number: as strings, "relate:10" sorts before "relate:9".

Values

Every cell, and every element of a list or map, is a CypherValue that keeps its kind at any depth:

CaseCypher kindPayload
.nullNULL
.boolBOOLEANBool
.intINTEGERInt64
.floatFLOATDouble, NaN and infinities included
.stringSTRINGString, interior NUL characters kept
.listLIST[CypherValue]
.mapMAP[String: CypherValue]; a map shaped like a node stays .map
.nodenodeNode: id, elementId, labels, properties
.relationshiprelationshipRelationship: id, elementId, type, startId, endId, startElementId, endElementId, properties
.pathpathPath: nodes, relationships, with one more node than relationships
.pointPOINTPoint: srid, x, y, z (nil in 2D); x and y are longitude and latitude for srid 4326 and 4979
.dateDATEDate: year, month, day
.timeZONED TIMETime: hour, minute, second, nanosecond, offsetSeconds
.localTimeLOCAL TIMELocalTime: hour, minute, second, nanosecond
.dateTimeZONED DATETIMEDateTime: the date and time parts, offsetSeconds, zone (nil for an offset-only value)
.localDateTimeLOCAL DATETIMELocalDateTime: the date and time parts
.durationDURATIONDuration: months, days, seconds, nanoseconds
.vectorVECTORVector: .floats(dtype:values:) or .ints(dtype:values:)

Vector.DType has the cases .integer8, .integer16, .integer32, .integer, .float16, .bfloat16, .float32 and .float, whose raw values are the engine's names (INTEGER8 … FLOAT). UUIDs arrive as .string, byte arrays as a .list of .int. An element id The string elementId() returns for a node or relationship, such as 4:relate-neo4j:n0; it stays the same when the store is closed and opened again. Read more in the glossary is the string elementId() returns, such as 4:relate-neo4j:n0; the Int64 ids are the deprecated numeric ids id() returns. A value kind the package does not know throws .decoding.

AccessorReturns
kindThe CypherValueKind
isNulltrue for .null
boolValue, intValue, stringValue, listValue, mapValueThe payload, or nil for another case
doubleValueA .float, or an .int widened to Double
nodeValue, relationshipValue, pathValue, pointValueThe graph or spatial payload, or nil
dateValue, timeValue, localTimeValue, dateTimeValue, localDateTimeValue, durationValue, vectorValueThe temporal or vector payload, or nil
subscript(key:)A map entry, or a node or relationship property; nil otherwise
descriptionA Cypher-like rendering for logs, map keys sorted; not a format to parse

Parameters

query(_:_:) binds each dictionary entry as one value. CypherValue conforms to ExpressibleByNilLiteral, …BooleanLiteral, …IntegerLiteral, …FloatLiteral, …StringLiteral, …ArrayLiteral and …DictionaryLiteral, so ["n": 1, "tags": ["a"], "m": ["k": true]] needs no wrapping. A variable binds through CypherValue(_:) for Int, Double, String and Bool; the struct payloads of the point, temporal, duration and vector cases have public initializers. Non-finite floats and every Int64 value bind directly.

RefusalError
A .node, .relationship or .path value.invalidParameter: nodes, relationships and paths cannot be passed as parameters; pass their element id or properties
A statement, path, namespace, parameter name or map key holding a NUL byte.invalidParameter: <what> contains a NUL byte
A value the engine's builder refuses: an impossible date, an unknown zone, a srid that does not fit the dimensions.engine with stage .internal
A parameter the statement refers to and the dictionary lacks.engine: semantic error: ParameterMissing: Expected parameter(s): <names>

Errors

RelateError caseThrown whendescription
.engine(EngineError)The engine or the C boundary refused the callRelate: [<code>] <message>
.invalidParameter(String)A parameter cannot cross the C boundaryRelate invalid parameter: <message>
.decoding(String)A result broke the C contract, such as a query type or namespace outside its enum (ABI contract violated: query type '<value>'), or scalar() met other than one row and one columnRelate decoding error: <message>

error.message is the message of any case and error.engineError the EngineError of the .engine case, else nil.

EngineError propertyHolds
codeThe Neo4j status code, such as Neo.ClientError.Statement.ArithmeticError
gqlStatusThe five-character GQLSTATUS, such as 22G03
stage.compile (parse or plan), .runtime (execution) or .internal (the C boundary: a poisoned handle, a caught panic, a refused argument)
retryabletrue exactly when running the same work again can succeed: a write-write conflict (Neo.TransientError.Transaction.Outdated, message write-write conflict) or the retryable workspace cap
messageThe engine's message

A statement failure throws from query; a conflict in an explicit transaction throws from commit(). After a .internal failure the handle may be poisoned: later calls throw .internal too, and only a new handle recovers. A write on a read-only database throws code Neo.ClientError.Statement.SemanticError with GQLSTATUS 42N18. The errors reference lists the engine's messages.

Notifications and plans

Notification has the properties code, gqlStatus, severity (INFORMATION or WARNING), category (PERFORMANCE, UNRECOGNIZED, DEPRECATION, SCHEMA or HINT), title, description, remedy and operatorId, an Int64? that is nil on a statement-scoped notification.

StructProperties
PlanDocumentschema (relate.plan/v1), profiled, statement: String?, root: PlanNode
PlanNodeoperator, details, identifiers, estimatedRows: Int64?, actual: PlanActual?, children, warnings: [Notification]
PlanActualrows, timeMs, dbHits, memoryBytes, hops, neighborWalks, nodeReads, start: PlanStart?; nil on an EXPLAIN plan
PlanStartelement, backward, reason

Concurrency

RuleDetail
Database is SendableOne database may be shared across tasks and actors
Connection is not SendableThe Swift 6 compiler refuses code that shares one between isolation domains; concurrent work opens more connections
Calls blockEvery call is synchronous while the engine runs
Errors travel with the callA call from any task reports its own error; nothing depends on the calling thread
One writer per storeOnly one process holds a store writable at a time

Platforms and linking

ItemValue
PlatformsiOS 16 or later, macOS 13 or later
Language modeSwift 6 (swift-tools-version: 6.0)
Product.product(name: "Relate", package: "relate")
Binary targetRelateFFI, a static XCFramework with the slices ios-arm64, ios-arm64_x86_64-simulator and macos-arm64_x86_64, downloaded from the release the tag names
Linked system librariesiconv, CoreFoundation
Privacy manifestPrivacyInfo.xcprivacy: no data collected, no tracking; the file-timestamp API reason C617.1
RELATE_LOCAL_XCFRAMEWORK=1Builds against target/apple/RelateFFI.xcframework in a source checkout instead of the release binary

Limits

The limits come from the engine and are the same on every surface: a statement's text is at most 4194304 bytes (syntax error: query text of <n> bytes exceeds the maximum query size of 4194304 bytes), one value a statement builds is bounded by the expression memory cap, and a file-backed store has one writing open across every process. Limits and sizing lists each limit with its default and the setting that changes it.

Capabilities

FeatureSwift
In-memory storeA store that lives only in the process.yesDatabase.inMemory()
Directory storeA store in a directory on disk.yesDatabase(path:)
Single-file .rl storeA store in one .rl file.yesDatabase(path: "graph.rl")
Read-only openAn open that loads the store once and refuses every write.yesDatabase(readOnlyPath:)
Explicit transactionsSeveral statements committed or rolled back together.yesbegin(), commit(), rollback(), transaction { }
Parameters$name values passed beside the statement.yesquery(statement, [String: CypherValue])
Column types, counters and timingsMetadata returned with each result.partialstats, elapsedMs and its phase split, queryType; no column types
NotificationsThe engine's query notifications (for example a Cartesian product), each with a remedy.yesnotifications
Arrow outputResults as Apache Arrow.nono Arrow call in the client
Change data captureReading committed changes from a slot.nono change-data-capture call in the client
Full-text searchFull-text indexes and their queries.yesCREATE FULLTEXT INDEX and full-text SEARCH through the C ABI
LOAD CSV from local filesLocal LOAD CSV under a configured directory (denied unless one is set).yesRELATE_LOAD_CSV_DIR, read by the C ABI's open
Load a store from a URLOpening a .rl store fetched over https.nono open from a URL in the client
Agent modeThe CLI's agent defaults: the 100-row cap and the piped toon output.noA CLI setting

Each cell is yes, no, partial or unknown, with the call, flag or reason. Checked on 2026-11-09 against relate 0.1.2: runs of the CLI binary and relate serve (Bolt, HTTP and MCP), the WebAssembly build this site ships, and the Python, Node.js, C, Java, Go, Swift and Rust clients, and each surface's upstream reference. The C, Java, Go and Swift clients load the same C ABI library as the Node.js client.

Example

This program writes two Person nodes and a KNOWS relationship to an in-memory store and prints the CypherValue kind of each returned value:

import Relate

let conn = try Database.inMemory().connect()
let result = try conn.query("""
    CREATE (ada:Person {name: 'Ada'})-[k:KNOWS {since: 2024}]->(grace:Person {name: 'Grace'})
    RETURN ada, k, 9007199254740993 AS big, 1.5 AS ratio, date('2024-05-06') AS day,
           datetime('2024-05-06T09:30:00[Europe/Berlin]') AS meeting, duration('P1Y2M') AS term,
           point({x: 1, y: 2}) AS spot, vector([1, 2, 3], 3, INT8) AS counts
    """)
for (column, value) in zip(result.columns, result.rows[0]) {
    print("\(column): \(value.kind) \(value)")
}
ada: node (4:relate-neo4j:n0:Person {name: 'Ada'})
k: relationship (4:relate-neo4j:n0)-[5:relate-neo4j:r0:KNOWS {since: 2024}]->(4:relate-neo4j:n1)
big: int 9007199254740993
ratio: float 1.5
day: date 2024-05-06
meeting: dateTime 2024-05-06T09:30:00+02:00[Europe/Berlin]
term: duration duration(months: 14, days: 0, seconds: 0, nanoseconds: 0)
spot: point point({srid: 7203, x: 1.0, y: 2.0})
counts: vector vector<INTEGER8>[1, 2, 3]

See also