Skip to content

skitsanos/lua-arangodb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ArangoDB Client for OpenResty/Lua

A comprehensive, modular ArangoDB client library for OpenResty/Lua with full HTTP API support.

Features

  • Full ArangoDB HTTP API coverage
  • Modular architecture with separate modules for each API area
  • Connection pooling and keepalive support
  • Both Basic and JWT authentication
  • AQL query execution with cursor pagination
  • Stream transactions support
  • Graph operations (Gharial API)
  • ArangoSearch views and analyzers
  • Vector indexes for semantic similarity search (v3.12.4+)
  • OpenAI-compatible embeddings generation (OpenAI, Ollama, vLLM, etc.)
  • Foxx microservices management
  • User and permission management
  • Cluster operations support

Installation

Dependencies

luarocks install lua-cjson
luarocks install lbase64
luarocks install lua-resty-http

Manual Installation

Copy the src/arangodb folder to your Lua package path.

Quick Start

local arangodb = require("arangodb")

-- Create client
local client = arangodb.new({
    endpoint = "http://127.0.0.1:8529",
    username = "root",
    password = "password",
    database = "_system"
})

-- Get server version
local version = client:version()
print("ArangoDB " .. version.version)

-- Execute AQL query
local results = client.query:execute(
    "FOR doc IN users FILTER doc.age >= @minAge RETURN doc",
    { minAge = 21 }
)

-- Create a document
local doc = client.document:create("users", {
    name = "Alice",
    age = 30
})

Configuration

local client = arangodb.new({
    -- Required
    endpoint = "http://127.0.0.1:8529",  -- ArangoDB server URL

    -- Authentication (one of these is required)
    username = "root",                    -- Basic auth username
    password = "password",                -- Basic auth password
    -- OR
    token = "jwt-token",                  -- JWT bearer token

    -- Optional
    database = "_system",                 -- Default database (default: "_system")
    timeout = 30000,                      -- Request timeout in ms (default: 30000)
    keepalive = 60000,                    -- Keepalive timeout in ms (default: 60000)
    pool_size = 100,                      -- Connection pool size (default: 100)
    ssl_verify = false                    -- Verify SSL certificates (default: false)
})

API Reference

Client Methods

-- Get server version
client:version(details)              -- details: include detailed info

-- Get storage engine info
client:engine()

-- Check server availability
client:isAvailable()

-- Switch database
client:useDatabase(name)
client:getDatabase()

-- Raw HTTP methods
client:get(path, options)
client:post(path, body, options)
client:put(path, body, options)
client:patch(path, body, options)
client:delete(path, options)

Database Operations (client.db)

-- List databases
client.db:list()                     -- All databases
client.db:listUser()                 -- User-accessible databases
client.db:current()                  -- Current database info
client.db:exists(name)               -- Check if database exists

-- Create database
client.db:create(name, options, users)

-- Drop database
client.db:drop(name)

-- Shorthand query (uses client.query:execute)
client.db:query(aql, bindVars, options)

Collection Operations (client.collection)

-- List collections
client.collection:list(excludeSystem)

-- Get collection info
client.collection:get(name)
client.collection:properties(name)
client.collection:exists(name)
client.collection:count(name)
client.collection:figures(name)
client.collection:revision(name)

-- Create collections
client.collection:create(name, options)
client.collection:createDocument(name, options)  -- type = 2
client.collection:createEdge(name, options)      -- type = 3

-- Modify collections
client.collection:rename(name, newName)
client.collection:setProperties(name, properties)
client.collection:truncate(name)
client.collection:drop(name, isSystem)

-- Memory management
client.collection:load(name)
client.collection:unload(name)
client.collection:loadIndexes(name)
client.collection:compact(name)

Document Operations (client.document)

-- Read documents
client.document:get(collection, key, options)
client.document:exists(collection, key)
client.document:head(collection, key)
client.document:getMany(collection, keys, options)

-- Create documents
client.document:create(collection, document, options)
client.document:createMany(collection, documents, options)

-- Update documents
client.document:update(collection, key, document, options)
client.document:updateMany(collection, documents, options)

-- Replace documents
client.document:replace(collection, key, document, options)
client.document:replaceMany(collection, documents, options)

-- Delete documents
client.document:delete(collection, key, options)
client.document:deleteMany(collection, keys, options)

-- Import/Export
client.document:import(collection, documents, options)
client.document:export(collection, options)

Document Options

{
    waitForSync = true,      -- Wait for sync to disk
    returnNew = true,        -- Return new document
    returnOld = true,        -- Return old document
    silent = false,          -- Don't return metadata
    overwrite = false,       -- Overwrite existing
    overwriteMode = "update", -- "ignore", "update", "replace", "conflict"
    keepNull = true,         -- Keep null values in updates
    mergeObjects = true,     -- Merge nested objects
    ifMatch = "rev",         -- Conditional by revision
}

Query Operations (client.query)

-- Execute queries
local results, cursor = client.query:execute(aql, bindVars, options)
local all_results = client.query:all(aql, bindVars, options)  -- Auto-pagination

-- Cursor operations
client.query:next(cursorId)
client.query:deleteCursor(cursorId)

-- Iterator for large result sets
for doc in client.query:iterate(aql, bindVars, options) do
    print(doc.name)
end

-- Query analysis
client.query:parse(aql, options)
client.query:explain(aql, bindVars, options)

-- Running queries
client.query:listRunning(all)
client.query:kill(queryId)

-- Slow query log
client.query:listSlow(all)
client.query:clearSlow(all)

-- Query tracking
client.query:getTracking()
client.query:setTracking(properties)

-- Query cache
client.query:getCacheProperties()
client.query:setCacheProperties(properties)
client.query:getCacheEntries()
client.query:clearCache()

-- AQL functions
client.query:functions(namespace)
client.query:createFunction(name, code, isDeterministic)
client.query:deleteFunction(name, group)

-- Optimizer rules
client.query:rules()

Query Options

{
    count = true,            -- Return total count
    batchSize = 1000,        -- Batch size for cursor
    ttl = 30,                -- Cursor TTL in seconds
    cache = true,            -- Use query cache
    memoryLimit = 0,         -- Memory limit in bytes
    fullCount = true,        -- Return full count (with LIMIT)
    stream = true,           -- Stream results
    profile = 2,             -- Profile level (0, 1, 2)
    maxRuntime = 60,         -- Max runtime in seconds
}

Index Operations (client.index)

-- List indexes
client.index:list(collection, withStats, withHidden)
client.index:get(indexId)
client.index:getByName(collection, indexName)
client.index:exists(indexId)

-- Create indexes
client.index:create(collection, definition)
client.index:createPersistent(collection, fields, options)
client.index:createGeo(collection, fields, options)
client.index:createFulltext(collection, fields, options)  -- Deprecated
client.index:createTTL(collection, fields, expireAfter, options)
client.index:createZKD(collection, fields, options)
client.index:createMDI(collection, fields, options)
client.index:createInverted(collection, fields, options)

-- Vector index for semantic similarity search (v3.12.4+)
-- NOTE: Requires --vector-index startup option and documents must exist first
client.index:createVector(collection, field, params, options)

-- Drop index
client.index:drop(indexId)

-- Ensure index exists
local idx, created = client.index:ensure(collection, definition)

Vector Search (v3.12.4+)

ArangoDB supports vector similarity search for semantic/AI applications. Vector indexes use the Faiss library for approximate nearest neighbor (ANN) search.

Requirements:

  • Server must be started with --experimental-vector-index=true
  • Documents with vector embeddings must exist before creating the index
  • Once enabled, vector-index cannot be disabled (permanent RocksDB change)

Creating a Vector Index:

-- Documents must have embeddings first
for i = 1, 1000 do
    client.document:create("documents", {
        text = "Document " .. i,
        embedding = generate_embedding(...)  -- 384-dim vector
    })
end

-- Then create the vector index
local idx = client.index:createVector("documents", "embedding", {
    metric = "cosine",           -- "cosine", "l2", or "innerProduct"
    dimension = 384,             -- vector array length
    nLists = 66,                 -- ~N/15 where N is document count
    defaultNProbe = 10,          -- neighboring centroids to search (higher = slower but better)
    trainingIterations = 25      -- optional, default: 25
})

Vector Search Functions:

Function Metric Sort Order Value Range Version
APPROX_NEAR_COSINE() cosine DESC [-1, 1] v3.12.4+
APPROX_NEAR_L2() l2 ASC [0, ∞) v3.12.4+
APPROX_NEAR_INNER_PRODUCT() innerProduct DESC (-∞, ∞) v3.12.6+
COSINE_SIMILARITY() - DESC [-1, 1] v3.9.0+ (exact, no index)

Query Examples:

-- Approximate search using vector index (fast, for large datasets)
local results = client.query:execute([[
    FOR doc IN documents
      SORT APPROX_NEAR_COSINE(doc.embedding, @query) DESC
      LIMIT 10
      RETURN doc
]], { query = query_vector })

-- With similarity score and custom nProbe
local results = client.query:execute([[
    FOR doc IN documents
      LET similarity = APPROX_NEAR_COSINE(doc.embedding, @query, { nProbe: 20 })
      SORT similarity DESC
      LIMIT 10
      RETURN MERGE({ similarity }, doc)
]], { query = query_vector })

-- Pre-filtering (v3.12.6+)
local results = client.query:execute([[
    FOR doc IN documents
      FILTER doc.category == @category
      SORT APPROX_NEAR_COSINE(doc.embedding, @query) DESC
      LIMIT 10
      RETURN doc
]], { query = query_vector, category = "tech" })

-- Exact cosine similarity (no index needed, for small datasets)
local results = client.query:execute([[
    FOR doc IN documents
      LET sim = COSINE_SIMILARITY(doc.embedding, @query)
      FILTER sim > 0.8
      SORT sim DESC
      LIMIT 10
      RETURN { doc, similarity: sim }
]], { query = query_vector })

-- Batch similarity with 2D array
local results = client.query:execute([[
    RETURN COSINE_SIMILARITY(@vectors, @query)
]], {
    vectors = {{0,1,0,1}, {1,0,0,1}, {1,1,1,0}},
    query = {1,1,1,1}
})
-- Returns: [0.707, 0.707, 0.866]

Metric Selection:

Metric Use Case Notes
cosine Text embeddings, normalized vectors Auto-normalizes vectors
l2 Image features, spatial data Euclidean distance
innerProduct When magnitude matters Faster than cosine (no normalization)

Graph Operations (client.graph)

-- Graph management
client.graph:list()
client.graph:get(name)
client.graph:exists(name)
client.graph:create(name, edgeDefinitions, options)
client.graph:drop(name, dropCollections)

-- Vertex collections
client.graph:listVertexCollections(graphName)
client.graph:addVertexCollection(graphName, collection, options)
client.graph:removeVertexCollection(graphName, collection, dropCollection)

-- Edge definitions
client.graph:listEdgeDefinitions(graphName)
client.graph:addEdgeDefinition(graphName, definition, options)
client.graph:replaceEdgeDefinition(graphName, edgeCollection, definition, options)
client.graph:removeEdgeDefinition(graphName, edgeCollection, options)

-- Vertex CRUD
client.graph:getVertex(graphName, collection, key, options)
client.graph:createVertex(graphName, collection, vertex, options)
client.graph:updateVertex(graphName, collection, key, vertex, options)
client.graph:replaceVertex(graphName, collection, key, vertex, options)
client.graph:deleteVertex(graphName, collection, key, options)

-- Edge CRUD
client.graph:getEdge(graphName, collection, key, options)
client.graph:createEdge(graphName, collection, edge, options)
client.graph:updateEdge(graphName, collection, key, edge, options)
client.graph:replaceEdge(graphName, collection, key, edge, options)
client.graph:deleteEdge(graphName, collection, key, options)

-- Traversal
client.graph:traverse(startVertex, options)

Transaction Operations (client.transaction)

-- JavaScript transaction (single request)
local result = client.transaction:execute({
    collections = { read = {"col1"}, write = {"col2"} },
    params = { userId = "123", amount = 50 },
    action = [[
        function(params) {
            var db = require('@arangodb').db;
            var user = db.col1.document(params.userId);
            db.col2.insert({ debit: params.amount, user: user._key });
            return "success";
        }
    ]]
})

-- Stream transactions (multi-request)
local tx = client.transaction:begin(collections, options)
local status = client.transaction:status(tx.id)
client.transaction:commit(tx.id)
client.transaction:abort(tx.id)
client.transaction:list()

-- Transaction helper (auto commit/abort)
client.transaction:run(collections, function(txId)
    -- Operations here
    return result
end, options)

-- Transaction-aware operations
client.transaction:query(txId, aql, bindVars, options)
client.transaction:createDocument(txId, collection, document, options)
client.transaction:updateDocument(txId, collection, key, document, options)
client.transaction:deleteDocument(txId, collection, key, options)

User Operations (client.user)

-- User management
client.user:list()
client.user:get(username)
client.user:exists(username)
client.user:create(username, password, options)
client.user:update(username, options)
client.user:replace(username, password, options)
client.user:delete(username)

-- Database permissions
client.user:getDatabasePermission(username, database)
client.user:setDatabasePermission(username, database, permission)
client.user:clearDatabasePermission(username, database)
client.user:listDatabasePermissions(username, full)

-- Collection permissions
client.user:getCollectionPermission(username, database, collection)
client.user:setCollectionPermission(username, database, collection, permission)
client.user:clearCollectionPermission(username, database, collection)

-- Convenience methods
client.user:grantDatabase(username, database)           -- rw
client.user:grantDatabaseReadOnly(username, database)   -- ro
client.user:revokeDatabase(username, database)          -- none
client.user:grantCollection(username, database, collection)
client.user:grantCollectionReadOnly(username, database, collection)
client.user:revokeCollection(username, database, collection)

Admin Operations (client.admin)

-- Server info
client.admin:version(details)
client.admin:engine()
client.admin:serverId()
client.admin:serverRole()
client.admin:serverAvailability()
client.admin:serverMode()
client.admin:setServerMode(mode)

-- Statistics
client.admin:statistics()
client.admin:statisticsDescription()
client.admin:metrics(serverId)

-- Logs
client.admin:logs(options)
client.admin:logLevel(serverId)
client.admin:setLogLevel(levels, serverId)

-- Cluster operations
client.admin:clusterHealth()
client.admin:clusterEndpoints()
client.admin:clusterStatistics(dbserver)
client.admin:maintenance(serverId, mode, options)
client.admin:cleanOutServer(serverId)
client.admin:moveShard(options)
client.admin:rebalanceShards(options)

-- Async jobs
client.admin:jobs(status, count)
client.admin:jobResult(jobId)
client.admin:cancelJob(jobId)
client.admin:deleteJobs(jobType, stamp)

-- Tasks
client.admin:tasks()
client.admin:task(taskId)
client.admin:createTask(options)
client.admin:deleteTask(taskId)

-- Misc
client.admin:time()
client.admin:shutdown(soft)
client.admin:compact(options)

Analyzer Operations (client.analyzer)

-- Analyzer management
client.analyzer:list()
client.analyzer:get(name)
client.analyzer:exists(name)
client.analyzer:create(name, type, properties, features)
client.analyzer:delete(name, force)

-- Type-specific creators
client.analyzer:createIdentity(name, features)
client.analyzer:createDelimiter(name, delimiter, features)
client.analyzer:createStem(name, locale, features)
client.analyzer:createNorm(name, locale, options, features)
client.analyzer:createNgram(name, options, features)
client.analyzer:createText(name, locale, options, features)
client.analyzer:createAQL(name, queryString, options, features)
client.analyzer:createPipeline(name, pipeline, features)
client.analyzer:createStopwords(name, stopwords, options, features)
client.analyzer:createCollation(name, locale, features)
client.analyzer:createGeoJSON(name, options, features)
client.analyzer:createGeoPoint(name, options, features)

View Operations (client.view)

-- View management
client.view:list()
client.view:get(name)
client.view:properties(name)
client.view:exists(name)
client.view:drop(name)
client.view:rename(name, newName)

-- Create views
client.view:create(name, type, properties)
client.view:createArangoSearch(name, options)
client.view:createSearchAlias(name, indexes)
client.view:createSimple(name, collection, options)

-- Modify views
client.view:updateProperties(name, properties)
client.view:replaceProperties(name, properties)

-- Link management (ArangoSearch)
client.view:addLink(viewName, collection, linkOptions)
client.view:removeLink(viewName, collection)
client.view:updateLink(viewName, collection, linkOptions)

-- Index management (search-alias)
client.view:addIndex(viewName, collection, indexName)
client.view:removeIndex(viewName, collection, indexName)

Foxx Operations (client.foxx)

-- Service management
client.foxx:list(excludeSystem)
client.foxx:get(mount)
client.foxx:exists(mount)
client.foxx:installFromUrl(mount, source, options)
client.foxx:installFromPath(mount, path, options)
client.foxx:installFromZip(mount, zipData, options)
client.foxx:replace(mount, source, options)
client.foxx:upgrade(mount, source, options)
client.foxx:uninstall(mount, options)

-- Configuration
client.foxx:getConfiguration(mount)
client.foxx:updateConfiguration(mount, configuration)
client.foxx:replaceConfiguration(mount, configuration)

-- Dependencies
client.foxx:getDependencies(mount)
client.foxx:updateDependencies(mount, dependencies)
client.foxx:replaceDependencies(mount, dependencies)

-- Development mode
client.foxx:enableDevelopment(mount)
client.foxx:disableDevelopment(mount)

-- Scripts
client.foxx:listScripts(mount)
client.foxx:runScript(mount, scriptName, args)

-- Other
client.foxx:readme(mount)
client.foxx:swagger(mount)
client.foxx:download(mount)
client.foxx:runTests(mount, options)

Embeddings (arangodb.Embeddings)

The Embeddings module provides vector embedding generation via OpenAI-compatible APIs (OpenAI, Ollama, vLLM, LocalAI, etc.). This is a standalone client that can be used independently of the ArangoDB connection.

local arangodb = require("arangodb")

-- Create embeddings client (uses OPENAI_API_KEY env var by default)
local embeddings = arangodb.Embeddings.new({
    api_key = "sk-...",                    -- Optional if OPENAI_API_KEY env var is set
    base_url = "https://api.openai.com/v1", -- Default
    model = "text-embedding-3-small",       -- Default
    dimensions = 256,                       -- Optional: reduce dimensions (supported models only)
    timeout = 30000,                        -- Request timeout in ms
    ssl_verify = false                      -- SSL verification (default: false)
})

-- Generate single embedding
local vector = embeddings:create("Hello world")
-- Returns: {0.023, -0.041, 0.012, ...} (1536 dimensions for text-embedding-3-small)

-- Generate batch embeddings (more efficient for multiple texts)
local vectors = embeddings:createBatch({"Hello", "World", "Test"})
-- Returns: {{...}, {...}, {...}}

-- Get embedding with metadata (usage stats)
local result = embeddings:createWithMetadata("Hello world")
-- Returns: {
--   embeddings = {{index=0, embedding={...}}},
--   model = "text-embedding-3-small",
--   usage = {prompt_tokens=2, total_tokens=2}
-- }

-- Get embedding dimension for current model
local dim = embeddings:getDimension()
-- Returns: 1536 (for text-embedding-3-small)

-- List available embedding models (OpenAI only)
local models = embeddings:listModels()

Using with Ollama or other local providers:

local embeddings = arangodb.Embeddings.new({
    base_url = "http://localhost:11434/v1",
    model = "nomic-embed-text",
    api_key = "ollama"  -- Ollama doesn't require a real API key
})

local vector = embeddings:create("Search query")

Complete Vector Search Workflow:

local arangodb = require("arangodb")

-- 1. Create ArangoDB client
local client = arangodb.new({
    endpoint = "http://localhost:8529",
    username = "root",
    password = "password"
})

-- 2. Create embeddings client
local embeddings = arangodb.Embeddings.new()

-- 3. Store documents with embeddings
local texts = {"Document about cats", "Document about dogs", "Document about birds"}
for i, text in ipairs(texts) do
    local vector = embeddings:create(text)
    client.document:create("documents", {
        text = text,
        embedding = vector
    })
end

-- 4. Create vector index (after documents exist)
client.index:createVector("documents", "embedding", {
    metric = "cosine",
    dimension = 1536,
    nLists = 1  -- Small value for few documents
})

-- 5. Semantic search
local query_vector = embeddings:create("Tell me about felines")
local results = client.query:execute([[
    FOR doc IN documents
      SORT APPROX_NEAR_COSINE(doc.embedding, @query) DESC
      LIMIT 5
      RETURN doc.text
]], { query = query_vector })

Testing with Docker/Podman

The project includes a docker-compose setup for testing. ArangoDB is configured with --experimental-vector-index=true to enable vector search features.

# Start ArangoDB and OpenResty
podman-compose up -d
# or
docker-compose up -d

# Run tests via HTTP
curl http://localhost:8080/test

# Test specific module
curl http://localhost:8080/test/database
curl http://localhost:8080/test/collection
curl http://localhost:8080/test/document
curl http://localhost:8080/test/query
curl http://localhost:8080/test/graph

# Interactive AQL query
curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{"query": "FOR i IN 1..10 RETURN i"}'

Hurl Tests

The project includes comprehensive Hurl tests in tests/hurl/:

# Run all Hurl tests
hurl --test tests/hurl/*.hurl

# Run specific test
hurl --test tests/hurl/01-database.hurl

DNS Resolver Note

The nginx configuration uses a DNS resolver for container networking. If switching between Docker and Podman, you may need to update nginx/conf/nginx.conf:

  • Docker: resolver 127.0.0.11 ipv6=off;
  • Podman: resolver 10.89.0.1 ipv6=off; (check with podman exec <container> cat /etc/resolv.conf)

Testing with resty

# Run the test script
resty -I src src/test.lua

License

MIT License

Contributors 3

  •  
  •  
  •  

Languages