Skip to main content

Advanced Operations

This guide covers advanced features available in ekoDB client libraries, including search operations, chat functionality, and real-time data handling.

tip

For complete, runnable examples, visit the ekoDB Examples Repository. It contains 93 examples (56 client library + 37 direct API examples).

Options Structs

New in v0.8.0

Cleaner method signatures using builder pattern for operation options (Rust, TypeScript, Kotlin).

Overview

Instead of long parameter lists, use options structs for cleaner, more maintainable code:

Before (v0.7.x):

await client.insert("users", record, "1h", true, "tx_123", false);
// What do these parameters mean? 🤔

After (v0.8.0):

await client.insert("users", record, {
ttl: "1h",
bypassRipple: true,
transactionId: "tx_123",
bypassCache: false
});
// Much clearer! ✨

Insert with Options

use ekodb_client::{Client, Record, InsertOptions};

let mut record = Record::new();
record.insert("name", "Alice");
record.insert("email", "alice@example.com");

// Use builder pattern for options
let options = InsertOptions::new()
.ttl("1h")
.bypass_ripple(true)
.transaction_id("tx_123")
.bypass_cache(false);

let result = client.insert("users", record, Some(options)).await?;

Update with Options

use ekodb_client::UpdateOptions;

let options = UpdateOptions::new()
.bypass_ripple(true)
.transaction_id("tx_456");

let updated = client.update("users", "user-123", updates, Some(options)).await?;

Available Options Structs

StructAvailable FieldsLanguages
InsertOptionsttl, bypass_ripple, transaction_id, bypass_cacheAll
UpdateOptionsbypass_ripple, transaction_id, bypass_cacheAll
UpsertOptionsbypass_ripple, transaction_idAll
DeleteOptionsbypass_ripple, transaction_idAll
FindOptionsbypass_cache, transaction_idAll
Language-Specific Patterns
  • Rust/TypeScript/Kotlin: Dedicated options structs with builder pattern
  • Python: Optional keyword arguments (Pythonic)
  • Go: Variadic options with pointers (idiomatic)

All approaches provide the same functionality with language-appropriate ergonomics.

Search Operations

ekoDB provides powerful search capabilities including full-text search, fuzzy search, and vector search.

Search across all fields in your documents:

use ekodb_client::{Client, SearchQuery};

let search = SearchQuery::builder()
.query("database")
.min_score(0.1)
.limit(10)
.build();

let results = client.search("articles", search).await?;

for result in results.results {
println!("Score: {:.4} - {:?}", result.score, result.record);
}

Search with custom field weights to prioritize certain fields:

use std::collections::HashMap;

let mut weights = HashMap::new();
weights.insert("title".to_string(), 2.0);
weights.insert("description".to_string(), 1.0);

let search = SearchQuery::builder()
.query("rust database")
.fields(vec!["title".to_string(), "description".to_string()])
.weights(weights)
.limit(5)
.build();

let results = client.search("articles", search).await?;

Enable typo tolerance with fuzzy matching:

let search = SearchQuery::builder()
.query("databse") // Typo: "databse" instead of "database"
.fuzzy(true)
.fuzziness(2) // Allow up to 2 character differences
.limit(10)
.build();

let results = client.search("articles", search).await?;

Perform semantic similarity search using embeddings:

// First, create a collection with vector index
let schema = Schema::builder()
.add_field("content", FieldType::String)
.add_field("embedding", FieldType::Vector(384)) // 384-dimensional vector
.build();

client.create_collection("documents", schema).await?;

// Insert document with embedding
let mut doc = Record::new();
doc.insert("content", "ekoDB is a high-performance database");
doc.insert("embedding", vec![0.1, 0.2, 0.3, /* ... 384 dimensions */]);
client.insert("documents", doc).await?;

// Search by vector similarity
let query_vector = vec![0.1, 0.2, 0.3, /* ... */];
let search = SearchQuery::builder()
.vector(query_vector)
.limit(10)
.build();

let results = client.search("documents", search).await?;

Combine text and vector search for best results:

let search = SearchQuery::builder()
.query("database performance") // Text query
.vector(query_vector) // Vector query
.limit(10)
.build();

let results = client.search("documents", search).await?;

Chat Operations

Build AI-powered chat applications with built-in context management and session handling.

Basic Chat

use ekodb_client::chat::{ChatClient, ChatMessage};

// Create chat client
let chat = ChatClient::new(client, "products");

// Send a message
let response = chat.send_message(
"What products do you have?",
"gpt-4"
).await?;

println!("AI: {}", response.content);

Chat Sessions

Manage conversation history with sessions:

// Create a new session
let session = chat.create_session("Customer Support Chat").await?;

// Send messages in the session
let response1 = chat.send_message_in_session(
&session.id,
"What's the price of ekoDB Pro?",
"gpt-4"
).await?;

let response2 = chat.send_message_in_session(
&session.id,
"What features does it include?",
"gpt-4"
).await?;

// Get session history
let messages = chat.get_session_messages(&session.id).await?;
println!("Total messages: {}", messages.len());
Schema-Aware Chat Queries

ekoDB's chat system features intelligent query understanding that works with your natural data structure:

  • Field Name Matching: Queries like "What is the price?" automatically find records with a price field, even if it contains numeric data (not text-searchable)
  • Multi-Turn Context: Follow-up questions use conversation history to enhance search relevance
  • No Denormalization Required: Works with structured data as you'd naturally model it

Example:

// Your data structure
{
"product": "ekoDB",
"description": "High-performance database",
"price": 99 // Numeric field
}

When a user asks "What is the price?", ekoDB:

  1. Checks the collection schema for fields matching "price"
  2. Finds records with that field name
  3. Provides the full record to the LLM
  4. LLM responds: "The price is $99"

See complete Chat Session examples in all languages:

  • Rust: client_chat_sessions.rs
  • Python: client_chat_sessions.py
  • TypeScript: client_chat_sessions.ts
  • Go: client_chat_sessions.go
  • Kotlin: ClientChatSessions.kt

Real-Time Operations

WebSocket Queries

Subscribe to real-time data changes:

use ekodb_client::WebSocketClient;

// Connect to WebSocket
let ws_url = "wss://your-subdomain.production.google.ekodb.net/ws";
let mut ws_client = client.websocket(ws_url).await?;

// Subscribe to collection changes
let results = ws_client.find_all("users").await?;

// Process real-time updates
for record in results {
println!("New/Updated record: {:?}", record);
}

ws_client.close().await?;

Joins

ekoDB supports cross-collection joins to combine data from multiple collections in a single query.

Single Collection Join

Join users with their department data:

use ekodb_client::{Client, QueryBuilder, JoinBuilder};

// Join users with departments
let join = JoinBuilder::single(
"departments", // Target collection
"department_id", // Local field (in users)
"id", // Foreign field (in departments)
"department" // Output field name
);

let query = QueryBuilder::new()
.join(join)
.limit(10)
.build();

let users = client.find("users", query).await?;
for user in users {
println!("User: {:?}, Department: {:?}", user["name"], user["department"]);
}
Complete Join Examples

Join examples - Single and multi-collection joins with filtering:


TTL (Time-To-Live)

Set automatic expiration for documents:

// Insert with 1 hour TTL
let mut session = Record::new();
session.insert("user_id", "user-123");
session.insert("token", "abc123");

let result = client.insert_with_ttl(
"sessions",
session,
"1h" // Expires in 1 hour
).await?;
Complete TTL Examples

Document TTL examples - Insert with expiration, verify expiration works:

WebSocket TTL examples - TTL with real-time connections:

  • Rust: client_websocket_ttl.rs
  • Python: client_websocket_ttl.py
  • TypeScript: client_websocket_ttl.ts
  • JavaScript: client_websocket_ttl.js
  • Go: client_websocket_ttl.go
  • Kotlin: ClientWebsocketTtl.kt

Transactions

ekoDB supports ACID transactions with multiple isolation levels. Transactions ensure data consistency when performing multiple operations that must succeed or fail together.

Isolation Levels

LevelDescription
READ_UNCOMMITTEDCan read uncommitted changes from other transactions
READ_COMMITTEDOnly reads committed data (default)
REPEATABLE_READSame data on re-read within transaction
SERIALIZABLEHighest isolation, transactions appear sequential

Basic Transaction

// Start a transaction
let tx_id = client.begin_transaction("SERIALIZABLE").await?;

// Perform operations within transaction context

// Commit when done
client.commit_transaction(&tx_id).await?;

// Or rollback on failure
// client.rollback_transaction(&tx_id).await?;
Transaction Best Practices
  • Use the lowest isolation level that meets your consistency requirements
  • Keep transactions short to minimize lock contention
  • Always handle rollback in error cases
  • For detailed transaction patterns, see Transactions

RAG Helpers

The Go client includes convenience methods for RAG (Retrieval-Augmented Generation) workflows:

Generate Embeddings

Generate embedding vectors from text using ekoDB's native Functions:

// Generate embedding for text
embedding, err := client.Embed("Hello world", "text-embedding-3-small")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Generated %d dimensions\n", len(embedding))

Perform full-text search with stemming and fuzzy matching:

// Search for documents by text
results, err := client.TextSearch("documents", "database performance", 10)
if err != nil {
log.Fatal(err)
}
for _, doc := range results {
fmt.Printf("Found: %v\n", doc["title"])
}

Hybrid Search

Combine semantic similarity (vector) with keyword matching (text):

// Generate embedding for query
embedding, _ := client.Embed("How to optimize queries?", "text-embedding-3-small")

// Perform hybrid search
results, err := client.HybridSearch("documents", "optimize queries", embedding, 5)
if err != nil {
log.Fatal(err)
}

Find All Records

Simple method to retrieve all records from a collection:

// Get all messages (up to limit)
allMessages, err := client.FindAll("messages", 1000)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d messages\n", len(allMessages))

Functions

Server-Side Feature

Functions are ekoDB's stored procedures system that runs on the server. They can be called from any client library or via REST API to execute complex business logic, queries, CRUD operations, AI workflows, and batch processing.

Deep Dive

For comprehensive architecture details, operation types, and advanced patterns, see Functions Architecture.

Functions let you create, store, and execute complete business logic as composable operations. Define your data logic once in ekoDB, then call it like puzzle pieces from any client.

What You Can Do

  • Complete business logic - Queries, CRUD, AI operations in one place
  • Parameterize everything - Dynamic values via {{param_name}}
  • Version control - Track function versions
  • Compose like puzzles - Chain operations together
  • Call from anywhere - REST API or any client library

Function Capabilities

  • Query Operations: Find, filter, search, vector search, hybrid search
  • CRUD Operations: Insert, update, delete (single and batch)
  • Transformations: Group, project, count
  • AI Operations: Chat completions, embeddings generation
  • Conditional Logic: If/then/else, foreach loops
  • External Integrations: HTTP requests to any REST API

Basic Example

Create a function to query active users:

POST /api/functions
Content-Type: application/json

{
"label": "get_active_users",
"name": "Get Active Users",
"description": "Returns active users with a limit",
"parameters": {
"limit": {
"default": 10,
"required": false
}
},
"functions": [
{
"type": "Query",
"collection": "users",
"filter": {
"type": "Condition",
"field": "status",
"operator": "Eq",
"value": "active"
},
"limit": "{{limit}}"
}
]
}

Call a Function

Execute via REST API:

POST /api/functions/get_active_users
Content-Type: application/json

{
"limit": 20
}

Managing Functions

# List all functions
GET /api/functions

# Get a specific function
GET /api/functions/get_active_users

# Update a function
PUT /api/functions/{id}

# Delete a function
DELETE /api/functions/get_active_users

Parameters

Make functions dynamic with parameters:

{
"parameters": {
"status": {
"default": "active",
"required": false,
"description": "Filter by status"
},
"min_amount": {
"required": true,
"description": "Minimum amount"
}
}
}

Reference parameters in your function definitions using {{param_name}}:

{
"type": "Query",
"collection": "orders",
"filter": {
"type": "Condition",
"field": "status",
"operator": "Eq",
"value": "{{status}}"
}
}

Available Operations

Functions support these operation types:

Query Operations

  • FindAll - Retrieve all records
  • Query - Advanced filtering, sorting, pagination
  • VectorSearch - Semantic similarity search
  • HybridSearch - Combine text + vector search
  • TextSearch - Full-text search
  • FindById - Get specific record by ID
  • FindOne - Find one by key/value

CRUD Operations

  • Insert - Insert single record
  • BatchInsert - Insert multiple records
  • Update - Update with filter
  • UpdateById - Update specific record
  • Delete - Delete with filter
  • DeleteById - Delete specific record
  • BatchDelete - Delete multiple records

Transformations

  • Group - Group and aggregate
  • Project - Select/exclude fields
  • Count - Count records

AI Operations

  • Chat - AI chat completions
  • Embed - Generate embeddings

Logic & Control

  • If - Conditional execution
  • ForEach - Loop over records
  • CallFunction - Call another function

External Integrations

  • HttpRequest - Call external APIs (Stripe, SendGrid, etc.)

Combine embeddings with search:

{
"label": "smart_search",
"name": "Smart Product Search",
"parameters": {
"query": { "required": true }
},
"functions": [
{
"type": "Embed",
"input_field": "query",
"output_field": "query_embedding"
},
{
"type": "HybridSearch",
"collection": "products",
"query_text": "{{query}}",
"query_vector": "{{query_embedding}}",
"limit": 10
}
]
}

Example: Batch Processing

Process multiple records with AI:

{
"label": "enrich_articles",
"name": "AI Content Enrichment",
"functions": [
{
"type": "Query",
"collection": "articles",
"filter": {
"type": "Condition",
"field": "embedding",
"operator": "Eq",
"value": null
},
"limit": 100
},
{
"type": "ForEach",
"functions": [
{
"type": "Embed",
"input_field": "content",
"output_field": "embedding"
},
{
"type": "UpdateById",
"collection": "articles",
"record_id": "{{id}}",
"updates": {
"embedding": "{{embedding}}"
}
}
]
}
]
}

Best Practices

  • Keep it simple - Start with single operations, build up
  • Use parameters - Make functions reusable with dynamic values
  • Filter early - Reduce data before expensive operations
  • Add descriptions - Document what each function does
  • Tag for organization - Use tags like analytics, users, ai
  • Version your functions - Track changes with version field
  • Test thoroughly - Validate with edge cases

Storage

Functions are stored in a dedicated collection: functions_{db_name} (configurable)

Complete Documentation

For complete details including:

  • All operation types and parameters
  • Advanced parameter resolution
  • Conditional logic patterns
  • External API integration examples
  • Error handling

See complete Function examples in all languages:

  • Rust: client_function_composition.rs
  • Python: client_function_composition.py
  • TypeScript: client_function_composition.ts
  • JavaScript: client_function_composition.js
  • Go: client_functions.go
  • Kotlin: ClientFunctionComposition.kt

Next Steps

Need Help?