Geode provides comprehensive client libraries for Python, Go, Rust, and Zig, all implementing consistent APIs for connecting to Geode servers, executing GQL queries, and managing transactions. This reference covers all client library APIs and integration patterns.
Client Library Overview
Supported Languages
- Python: Async client with aioquic (Python 3.9+)
- Go: database/sql driver implementation (Go 1.24+)
- Rust: Tokio-based async client (Rust 1.70+)
- Zig: Native client with vendored QUIC (Zig 0.1.0+)
All clients communicate via QUIC+TLS on port 3141 using Protobuf wire protocol.
Common Capabilities
- Connection pooling and management
- Parameterized query execution
- Transaction support (BEGIN, COMMIT, ROLLBACK, SAVEPOINT)
- Prepared statements
- Row-level security (RLS) policy management
- Error handling with GQL status codes
- Async/await patterns (where applicable)
Python Client API
Installation
pip install geode-client
# or
pip install -r requirements.txt
Connection Management
from geode_client import Client
import asyncio
# Context manager (recommended)
async def example():
client = Client(host="localhost", port=3141)
async with client.connection() as conn:
result, _ = await conn.query("MATCH (n) RETURN COUNT(n) AS count")
for row in result.rows:
print(row['count'])
# Manual connection
async def manual_example():
client = Client("localhost:3141")
await client.connect()
try:
result, _ = await conn.query("MATCH (n) RETURN n LIMIT 10")
for row in result.rows:
print(row)
finally:
await client.close()
# Connection pool configuration
client = Client(
"localhost:3141",
max_connections=100,
connection_timeout=30,
verify_tls=True,
ca_file="/path/to/ca.pem"
)
Query Execution
# Simple query
result, _ = await client.query("MATCH (u:User) RETURN u.name")
for row in result.rows:
print(row['name'])
# Parameterized query (named parameters)
result, _ = await client.query("""
MATCH (u:User)
WHERE u.age >= $min_age AND u.verified = $verified
RETURN u.name, u.age
""", {
"min_age": 18,
"verified": True
})
# Parameterized query (positional parameters)
result, _ = await client.query("""
MATCH (u:User)
WHERE u.age >= $1 AND u.verified = $2
RETURN u.name, u.age
""", [18, True])
# Execute with no results (INSERT, UPDATE, DELETE)
affected, _ = await client.query("""
CREATE (u:User {
name: $name,
email: $email,
created_at: CURRENT_TIMESTAMP
})
""", {"name": "Alice", "email": "[email protected]"})
print(f"Created {len(affected.rows)} nodes")
Transaction Management
# Transaction context manager
async with client.connection() as conn:
await conn.begin()
try:
await conn.execute("CREATE (u:User {name: $name})", {"name": "Alice"})
await conn.execute("CREATE (u:User {name: $name})", {"name": "Bob"})
await conn.commit()
except Exception:
await conn.rollback()
raise
# Manual transaction control
conn = await client.connect()
try:
await conn.begin()
await conn.execute("CREATE (u:User {name: 'Alice'})")
await conn.execute("CREATE (u:User {name: 'Bob'})")
await conn.commit()
except Exception:
await conn.rollback()
raise
finally:
await conn.close()
# Savepoints
async with client.connection() as tx:
await tx.begin()
await tx.execute("CREATE (u:User {name: 'Alice'})")
sp = await tx.savepoint("before_bob")
try:
await tx.execute("CREATE (u:User {name: 'Bob', invalid: ???})")
except:
await tx.rollback_to(sp) # Rollback to savepoint
await tx.execute("CREATE (u:User {name: 'Charlie'})")
await tx.commit()
# Commits Alice and Charlie, Bob rolled back
Prepared Statements
# Prepare statement
stmt = await conn.prepare("""
MATCH (u:User {id: $user_id})
RETURN u.name, u.email
""")
# Execute multiple times with different parameters
for user_id in [1, 2, 3, 4, 5]:
result, _ = await stmt.execute({"user_id": user_id})
for row in result.rows:
print(f"{row['name']}: {row['email']}")
# Close statement
await stmt.close()
Error Handling
from geode_client import (
GeodeError,
GeodeConnectionError,
QueryError,
AuthError
)
try:
result, _ = await client.query("MATCH (u:User) WHERE u.invalid_syntax")
except QueryError as e:
print(f"Query error: {e}")
except GeodeConnectionError as e:
print(f"Connection failed: {e}")
except AuthError as e:
print(f"Auth failed: {e}")
except GeodeError as e:
print(f"Geode error: {e}")
Row-Level Security (RLS)
# Set session context for RLS
async with client.connection() as conn:
await conn.execute("SET app.user_id = $user_id", {"user_id": 123})
# Query with RLS enforced
result, _ = await conn.query("""
MATCH (u:User)-[:POSTED]->(p:Post)
RETURN p.title
""")
# Only returns posts accessible under policy
# Clear RLS policy
await conn.execute("SET app.user_id = NULL")
Go Client API
Installation
go get geodedb.com/geode
Connection Management
import (
"database/sql"
_ "geodedb.com/geode"
)
// Open connection
db, err := sql.Open("geode", "quic://localhost:3141")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Configure connection pool
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour)
// Test connection
if err := db.Ping(); err != nil {
log.Fatal("Cannot connect:", err)
}
// Connection string options
// quic://host:port?tls=true&ca_file=/path/to/ca.pem&timeout=30s
db, err := sql.Open("geode", "quic://localhost:3141?tls=true&timeout=30s")
Query Execution
// Query with rows
rows, err := db.Query(`
MATCH (u:User)
WHERE u.age >= $1 AND u.verified = $2
RETURN u.name, u.age
`, 18, true)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name string
var age int
if err := rows.Scan(&name, &age); err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %d\n", name, age)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
// Single row query
var name string
var email string
err = db.QueryRow(`
MATCH (u:User {id: $1})
RETURN u.name, u.email
`, 123).Scan(&name, &email)
if err == sql.ErrNoRows {
fmt.Println("User not found")
} else if err != nil {
log.Fatal(err)
}
// Execute (no result rows)
result, err := db.Exec(`
CREATE (u:User {
name: $1,
email: $2,
created_at: CURRENT_TIMESTAMP
})
`, "Alice", "[email protected]")
if err != nil {
log.Fatal(err)
}
affected, _ := result.RowsAffected()
fmt.Printf("Created %d nodes\n", affected)
Transaction Management
// Begin transaction
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
// Execute within transaction
_, err = tx.Exec("CREATE (u:User {name: $1})", "Alice")
if err != nil {
tx.Rollback()
log.Fatal(err)
}
_, err = tx.Exec("CREATE (u:User {name: $1})", "Bob")
if err != nil {
tx.Rollback()
log.Fatal(err)
}
// Commit transaction
if err = tx.Commit(); err != nil {
log.Fatal(err)
}
// Transaction with defer pattern
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
_, err = tx.Exec("CREATE (u:User {name: 'Alice'})")
// ... more operations
Prepared Statements
// Prepare statement
stmt, err := db.Prepare(`
MATCH (u:User {id: $1})
RETURN u.name, u.email
`)
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
// Execute multiple times
for _, id := range []int64{1, 2, 3, 4, 5} {
var name, email string
err = stmt.QueryRow(id).Scan(&name, &email)
if err != nil {
log.Println(err)
continue
}
fmt.Printf("%s: %s\n", name, email)
}
Error Handling
import "geodedb.com/geode/geoderr"
rows, err := db.Query("MATCH (u:User) WHERE invalid")
if err != nil {
if geodeErr, ok := err.(*geoderr.Error); ok {
fmt.Printf("Geode error: %s\n", geodeErr.Message)
fmt.Printf("Status code: %s\n", geodeErr.StatusCode)
} else {
log.Fatal("Other error:", err)
}
}
Rust Client API
Installation
# Cargo.toml
[dependencies]
geode-client = "0.18"
tokio = { version = "1", features = ["full"] }
Connection Management
use geode_client::{Client, ConnectionPool};
#[tokio::main]
async fn main() -> geode_client::Result<()> {
// Simple connection
let client = Client::new("localhost", 3141).skip_verify(true);
let mut conn = client.connect().await?;
// Connection pool management
let pool = ConnectionPool::new("localhost", 3141, 100).skip_verify(true);
let mut pooled = pool.acquire().await?;
let (_page, _) = pooled.query("RETURN 1").await?;
Ok(())
}
Query Execution
use geode_client::Value;
use std::collections::HashMap;
let mut conn = client.connect().await?;
// Simple query
let (page, _) = conn.query(
"MATCH (u:User) RETURN u.name, u.age"
).await?;
for row in &page.rows {
let name = row.get("name").unwrap().as_string()?;
let age = row.get("age").unwrap().as_int()?;
println!("{}: {}", name, age);
}
// Parameterized query (named)
let mut params = HashMap::new();
params.insert("min_age".to_string(), Value::int(18));
params.insert("verified".to_string(), Value::bool(true));
let (_page, _) = conn.query_with_params(
"MATCH (u:User) WHERE u.age >= $min_age AND u.verified = $verified RETURN u.name, u.age",
¶ms,
).await?;
// Execute (no results)
let mut create_params = HashMap::new();
create_params.insert("name".to_string(), Value::string("Alice"));
create_params.insert("email".to_string(), Value::string("[email protected]"));
let (created, _) = conn.query_with_params(
"CREATE (u:User {name: $name, email: $email, created_at: CURRENT_TIMESTAMP})",
&create_params,
).await?;
println!("Created {} nodes", created.rows.len());
Transaction Management
use geode_client::Value;
use std::collections::HashMap;
let mut conn = client.connect().await?;
// Transaction
conn.begin().await?;
let mut params = HashMap::new();
params.insert("name".to_string(), Value::string("Alice"));
conn.query_with_params("CREATE (u:User {name: $name})", ¶ms).await?;
params.insert("name".to_string(), Value::string("Bob"));
conn.query_with_params("CREATE (u:User {name: $name})", ¶ms).await?;
conn.commit().await?;
// Transaction with error handling
conn.begin().await?;
let result = async {
conn.query("CREATE (u:User {name: 'Alice'})").await?;
conn.query("CREATE (u:User {name: 'Bob'})").await?;
Ok::<(), geode_client::Error>(())
}.await;
match result {
Ok(_) => conn.commit().await?,
Err(e) => {
conn.rollback().await?;
return Err(e);
}
}
// Savepoint
conn.begin().await?;
conn.query("CREATE (u:User {name: 'Alice'})").await?;
let sp = conn.savepoint("before_bob")?;
match conn.query("CREATE (u:User {invalid: ???})").await {
Ok(_) => {},
Err(_) => {
conn.rollback_to(&sp).await?;
}
}
conn.commit().await?;
Prepared Statements
use geode_client::Value;
use std::collections::HashMap;
let mut conn = client.connect().await?;
let stmt = conn.prepare(
"MATCH (u:User {id: $id}) RETURN u.name, u.email"
)?;
// Execute multiple times
for id in [1, 2, 3, 4, 5] {
let mut params = HashMap::new();
params.insert("id".to_string(), Value::int(id));
let (page, _) = stmt.execute(&mut conn, ¶ms).await?;
for row in &page.rows {
let name = row.get("name").unwrap().as_string()?;
let email = row.get("email").unwrap().as_string()?;
println!("{}: {}", name, email);
}
}
Error Handling
use geode_client::{Error, ErrorKind};
match client.query("MATCH (u:User) WHERE invalid", &[]).await {
Ok(result) => { /* handle result */ },
Err(Error::Query(e)) => {
eprintln!("Query error: {}", e.message);
eprintln!("Status: {}", e.status_code);
},
Err(Error::Connection(e)) => {
eprintln!("Connection error: {}", e);
},
Err(e) => {
eprintln!("Other error: {}", e);
}
}
Zig Client API
Integration
const std = @import("std");
const geode = @import("geode_client");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Connect
var client = try geode.Client.connect(allocator, "localhost", 3141);
defer client.deinit();
// Query
const result = try client.query("MATCH (n:User) RETURN n.name LIMIT 10", .{});
defer result.deinit();
while (try result.next()) |row| {
const name = row.get("name").?;
std.debug.print("Name: {s}\n", .{name.string});
}
}
Common Integration Patterns
Connection Pooling
# Python: Built-in connection pool
client = Client("localhost:3141", max_connections=100)
# Go: Standard database/sql pooling
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(10)
# Rust: Custom pool
let pool = Pool::new("localhost:3141", 100).await?;
Retry Logic
# Python with exponential backoff
import asyncio
async def query_with_retry(client, query, params, max_retries=3):
for attempt in range(max_retries):
try:
return await client.query(query, params)
except ConnectionError:
if attempt == max_retries - 1:
raise
await asyncio.sleep(2 ** attempt) # Exponential backoff
// Go with retry
func queryWithRetry(db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
rows, err := db.Query(query, args...)
if err == nil {
return rows, nil
}
if i < maxRetries-1 {
time.Sleep(time.Duration(1<<uint(i)) * time.Second)
}
}
return nil, fmt.Errorf("query failed after %d retries", maxRetries)
}
Batch Operations
# Python batch insert
async with client.connection() as tx:
await tx.begin()
for i in range(1000):
await tx.execute("""
CREATE (u:User {
id: $id,
name: $name,
created_at: CURRENT_TIMESTAMP
})
""", {"id": i, "name": f"User{i}"})
// Go batch insert
tx, _ := db.Begin()
stmt, _ := tx.Prepare("CREATE (u:User {id: $1, name: $2})")
for i := 0; i < 1000; i++ {
_, err := stmt.Exec(i, fmt.Sprintf("User%d", i))
if err != nil {
tx.Rollback()
return err
}
}
stmt.Close()
tx.Commit()
Stream Processing
# Python async streaming
async def process_large_result():
result, _ = await client.query("MATCH (n:User) RETURN n")
for row in result.rows:
await process_user(row)
# Memory efficient: only one row in memory at a time
// Rust streaming
let result = client.query("MATCH (n:User) RETURN n", &[]).await?;
for row in result.rows() {
process_user(&row)?;
// Stream-based iteration
}
Best Practices
- Use Connection Pooling: Configure appropriate pool sizes for your workload
- Parameterize Queries: Always use parameters to prevent injection attacks
- Handle Errors: Implement proper error handling and retry logic
- Transaction Management: Use context managers or defer patterns for safety
- Close Resources: Always close connections, statements, and result sets
- Streaming: Use streaming for large result sets to manage memory
- Monitoring: Track connection pool metrics and query performance
- TLS: Enable TLS for production deployments
Performance Tips
Connection Reuse
# Good: Reuse client connection
client = Client(host="localhost", port=3141)
async with client.connection() as conn:
for i in range(100):
await conn.query("MATCH (n:User {id: $id}) RETURN n", {"id": i})
# Avoid: Creating new connection per query
for i in range(100):
client = Client(host="localhost", port=3141)
async with client.connection() as conn:
await conn.query("MATCH (n:User {id: $id}) RETURN n", {"id": i})
Prepared Statements
// Good: Prepare once, execute many
stmt, _ := db.Prepare("MATCH (u:User {id: $1}) RETURN u")
defer stmt.Close()
for _, id := range ids {
stmt.QueryRow(id).Scan(&result)
}
// Avoid: Preparing repeatedly
for _, id := range ids {
stmt, _ := db.Prepare("MATCH (u:User {id: $1}) RETURN u")
stmt.QueryRow(id).Scan(&result)
stmt.Close()
}
Batch Transactions
# Good: Single transaction for batch
async with client.connection() as tx:
await tx.begin()
for item in items:
await tx.execute("CREATE (n:Node {data: $data})", {"data": item})
# Avoid: Transaction per operation
for item in items:
async with client.connection() as tx:
await tx.begin()
await tx.execute("CREATE (n:Node {data: $data})", {"data": item})
Related Topics
- Query Language: GQL query syntax
- Transactions: Transaction management
- Security: Authentication and authorization
- Performance: Query optimization
- Deployment: Production deployment
Further Reading
- Python Client - Complete Python API documentation
- Go Client - Complete Go API documentation
- Rust Client - Complete Rust API documentation
- Node.js Client - Complete Node.js API documentation
- Zig Client - Complete Zig API documentation
- Query Language - GQL syntax reference
Geode’s client libraries provide consistent, production-ready APIs across multiple languages, enabling seamless integration with your application stack while maintaining high performance and reliability.