Queries

ALOS DB supports a rich query language with comparison operators, logical operators, regex, nested field access, and an aggregation pipeline.

Basic Queries

The simplest query matches documents by exact field values:

go
users := db.Collection("users")

// Find by exact field value
user, err := users.FindOne(alosdbclient.Document{"name": "Alice"})

// Find by _id (fastest — direct hash lookup)
user, err = users.FindOne(alosdbclient.Document{"_id": "abc123"})

// FindMany multiple matching documents
admins, err := users.FindMany(alosdbclient.Document{"role": "admin"})

An empty query {} matches all documents:

go
// Return all documents in the collection
all, err := users.FindMany(alosdbclient.Document{})

Multi-field queries

Multiple fields in a query are implicitly AND-ed:

go
// Both conditions must match
results, _ := users.FindMany(alosdbclient.Document{
    "role":   "admin",
    "active": true,
})

Query Protection

ALOS DB provides literal-value helpers that prevent query injection when untrusted input is used in queries. Without protection, a malicious value such as {"$ne": "impossible"} can bypass authentication checks or leak data.

Unsafe (raw values)

go
// Vulnerable — user input can inject operators
user, _ := users.FindOne(alosdbclient.Document{
    "username": username,
    "password": password,
})

Safe (literal wrappers)

go
// Safe — values are guaranteed to be treated as literals
user, _ := users.FindOne(alosdbclient.Document{
    "username": alosdbclient.Str(username),
    "password": alosdbclient.Str(password),
})

Available literal helpers

Helper Type Example
alosdbclient.Str(v) string "username": alosdbclient.Str(name)
alosdbclient.Int(v) int "age": alosdbclient.Int(age)
alosdbclient.Int64(v) int64 "ts": alosdbclient.Int64(timestamp)
alosdbclient.Float(v) float64 "score": alosdbclient.Float(score)
alosdbclient.Bool(v) bool "active": alosdbclient.Bool(true)
alosdbclient.Nil() nil "deleted": alosdbclient.Nil()
alosdbclient.Time(v) time.Time "created": alosdbclient.Time(t)
alosdbclient.Bytes(v) []byte "data": alosdbclient.Bytes(b)
alosdbclient.Arr(items...) []interface{} "tags": alosdbclient.Arr("a", "b")
alosdbclient.Map(items...) Document "meta": alosdbclient.Map("k", "v")

Safe arrays and nested maps

go
// Safe array literal
results, _ := users.FindMany(alosdbclient.Document{
    "role": alosdbclient.Str(role),
    "tags": alosdbclient.Arr("verified", "premium"),
})

// Safe nested map literal (rejects operator keys like "$ne")
results, _ = users.FindMany(alosdbclient.Document{
    "profile": alosdbclient.Map("level", "gold"),
})

Important: alosdbclient.Map panics if any key starts with $, making injection via nested maps impossible. Use it whenever you build subdocuments from untrusted input.

Comparison Operators

Use operators inside a nested Document for richer matching:

Operator Meaning Example
$eq Equals {"age": {"$eq": 30}}
$ne Not equal {"status": {"$ne": "banned"}}
$gt Greater than {"age": {"$gt": 21}}
$gte Greater than or equal {"age": {"$gte": 18}}
$lt Less than {"price": {"$lt": 100}}
$lte Less than or equal {"score": {"$lte": 50}}
$in In array {"role": {"$in": ["admin","mod"]}}
$nin Not in array {"role": {"$nin": ["banned"]}}

Examples

go
// Age greater than or equal to 18
adults, _ := users.FindMany(alosdbclient.Document{
    "age": alosdbclient.Document{"$gte": 18},
})

// Price between 10 and 100 (combine $gte and $lte)
products, _ := db.Collection("products").FindMany(alosdbclient.Document{
    "price": alosdbclient.Document{"$gte": 10, "$lte": 100},
})

// Role in a specific set
staff, _ := users.FindMany(alosdbclient.Document{
    "role": alosdbclient.Document{"$in": []interface{}{"admin", "moderator", "editor"}},
})

// Not equal
active, _ := users.FindMany(alosdbclient.Document{
    "status": alosdbclient.Document{"$ne": "deleted"},
})

Logical Operators

Combine multiple conditions with $and and $or:

$or

go
// Match either condition
results, _ := users.FindMany(alosdbclient.Document{
    "$or": []interface{}{
        alosdbclient.Document{"role": "admin"},
        alosdbclient.Document{"age": alosdbclient.Document{"$gte": 21}},
    },
})

$and

go
// Explicit AND (useful when querying the same field twice)
results, _ := users.FindMany(alosdbclient.Document{
    "$and": []interface{}{
        alosdbclient.Document{"age": alosdbclient.Document{"$gte": 18}},
        alosdbclient.Document{"age": alosdbclient.Document{"$lt": 65}},
    },
})

Tip: You can combine multiple comparison operators on the same field without $and:
{"age": {"$gte": 18, "$lt": 65}}

Existence & Regex

$exists

go
// Documents that have an "email" field
results, _ := users.FindMany(alosdbclient.Document{
    "email": alosdbclient.Document{"$exists": true},
})

// Documents that do NOT have a "deleted_at" field
results, _ = users.FindMany(alosdbclient.Document{
    "deleted_at": alosdbclient.Document{"$exists": false},
})

$regex

go
// Names starting with "A"
results, _ := users.FindMany(alosdbclient.Document{
    "name": alosdbclient.Document{"$regex": "^A"},
})

// Email addresses from a specific domain
results, _ = users.FindMany(alosdbclient.Document{
    "email": alosdbclient.Document{"$regex": "@example\\.com$"},
})

$regex uses Go’s regexp package (RE2 syntax). Lookbehinds and backreferences are not supported. Regex queries perform a full collection scan — use indexed fields for high-throughput queries.

Nested Fields

Access nested document fields with dot notation:

go
// Insert a document with nested fields
users.InsertOne(alosdbclient.Document{
    "name": "Alice",
    "address": alosdbclient.Document{
        "city":  "Sydney",
        "state": "NSW",
        "zip":   "2000",
    },
})

// Query by nested field using dot notation
results, _ := users.FindMany(alosdbclient.Document{
    "address.city": "Sydney",
})

// Nested fields work with operators too
results, _ = users.FindMany(alosdbclient.Document{
    "address.zip": alosdbclient.Document{"$gte": "2000", "$lt": "3000"},
})

Dot notation works to any depth: "a.b.c.d" accesses doc["a"]["b"]["c"]["d"].

Update Operators

$set — partial update

go
// Update only the "age" field, leave everything else untouched
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$set": alosdbclient.Document{
        "age":    31,
        "active": true,
    }},
)

// Update nested fields with dot notation
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$set": alosdbclient.Document{
        "address.city": "Melbourne",
    }},
)

$add / $sub / $inc — numeric operations

go
// Add to a numeric field (creates field with 0 if missing)
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$add": alosdbclient.Document{"balance": 100.50}},
)

// Subtract from a numeric field
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$sub": alosdbclient.Document{"balance": 25.00}},
)

// $inc is an alias for $add
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$inc": alosdbclient.Document{"loginCount": 1}},
)

$add, $sub, and $inc work on any numeric type (int, int64, float64). If the field does not exist, it defaults to 0. The result preserves the original field's type when possible.

$push / $pushAll — append to arrays

go
// Append a single item to an array
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$push": alosdbclient.Document{"tags": "premium"}},
)

// Append multiple items at once
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$pushAll": alosdbclient.Document{
        "tags": []interface{}{"beta", "vip"},
    }},
)

$push appends a single value. $pushAll appends a slice of values. Both create the array if the field does not exist. Using these on a non-array field returns an error.

$pull — remove from arrays

go
// Remove a specific value from an array
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$pull": alosdbclient.Document{"tags": "beta"}},
)

// Remove a nested document from an array by exact match
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$pull": alosdbclient.Document{
        "permissions": alosdbclient.Document{"resource": "billing"},
    }},
)

$pull removes every element that equals the given value. For objects, equality is a deep match on all fields. Using $pull on a non-array field returns an error.

$unset / $rename — field manipulation

go
// Remove one or more fields entirely
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$unset": alosdbclient.Document{
        "tempToken": "",
        "legacyId":  "",
    }},
)

// Rename a field (creates new field, deletes old one)
users.UpdateOne(
    alosdbclient.Document{"name": "Alice"},
    alosdbclient.Document{"$rename": alosdbclient.Document{
        "oldName": "newName",
    }},
)

$unset accepts a document where each key is a field to delete (values are ignored). $rename moves a value from the old key to the new key. Both support dot-notation for nested fields. The value in $rename is the new field name.

Full replacement

go
// Without $set, the entire document is replaced
users.UpdateOne(
    alosdbclient.Document{"_id": id},
    alosdbclient.Document{
        "name":   "Alice",
        "age":    31,
        "role":   "admin",
        "active": true,
    },
)

$set merges fields into the existing document. Without $set, the update document completely replaces the original (except _id is preserved).

Aggregation Pipeline

Process documents through a pipeline of stages. Each stage transforms the data passed to the next:

Stage Description
$match Filter documents (same syntax as FindMany queries)
$group Group by field with accumulators ($sum, $avg, $min, $max)
$sort Sort results (1 = ascending, -1 = descending)
$project Include/exclude fields (1 = include, 0 = exclude)
$limit Limit number of results

Group and Count

go
// Count users per department
results, _ := users.Aggregate([]alosdbclient.Document{
    {"$match": alosdbclient.Document{"active": true}},
    {"$group": alosdbclient.Document{
        "_id":   "$department",
        "count": alosdbclient.Document{"$sum": 1},
    }},
    {"$sort": alosdbclient.Document{"count": -1}},
})

for _, r := range results {
    fmt.Printf("Dept %v: %v users\n", r["_id"], r["count"])
}

Average, Min, Max

go
// Average age by role
results, _ := users.Aggregate([]alosdbclient.Document{
    {"$group": alosdbclient.Document{
        "_id":     "$role",
        "avg_age": alosdbclient.Document{"$avg": "$age"},
        "min_age": alosdbclient.Document{"$min": "$age"},
        "max_age": alosdbclient.Document{"$max": "$age"},
    }},
})

Project and Limit

go
// Top 5 highest-scoring users, only name and score
results, _ := users.Aggregate([]alosdbclient.Document{
    {"$sort": alosdbclient.Document{"score": -1}},
    {"$limit": 5},
    {"$project": alosdbclient.Document{
        "name":  1,
        "score": 1,
    }},
})
ESC