---
slug: 33-sql-mongo-redis
title: SQL, Mongo y Redis
description: Una API db universal sobre SQLite/Postgres/MySQL, más mongo_* (documentos) y redis_* (key-value) — todo puro-Rust, todo gateado por la capacidad db.
example_ids: [sql]
---

# SQL, Mongo y Redis

Todas las bases abren con `db_open` y están gateadas por `require db(scope)`. Tres familias: **SQL** (SQLite/Postgres/MySQL), **documentos** (MongoDB, `mongo_*`), **key-value** (Redis, `redis_*`). Todos los drivers son puro-Rust (un solo binario estático).

```synsema
-- Doc example: SQL over SQLite in-memory (real, no server). Same API for Postgres/MySQL.
intent: "doc example: SQL"
require db(":memory:")

db_open(":memory:", "memory")
sql_exec("CREATE TABLE demo (n TEXT)")
sql_exec("INSERT INTO demo VALUES (?)", ["it works"])
print("sql demo → " + (sql("SELECT n FROM demo")[0])["n"])
db_close()

test "create, parameterized insert, query"
    db_open(":memory:", "memory")
    sql_exec("CREATE TABLE items (name TEXT, price REAL)")
    sql_exec("INSERT INTO items VALUES (?, ?)", ["Laptop", 999])
    sql_exec("INSERT INTO items VALUES (?, ?)", ["Mouse", 25])
    let rows be sql("SELECT name, price FROM items WHERE price > ? ORDER BY price DESC", [50])
    assert_eq(length(rows), 1)
    assert_eq(name of (rows[0]), "Laptop")
    db_close()
```

## SQL (SQLite / Postgres / MySQL)

```synsema
require db("./store.db")
db_open("./store.db")                 -- archivo → SQLite. ":memory:" con modo "memory". postgres:// / mysql://
sql_exec("INSERT INTO users (name) VALUES (?)", ["Ada"])   -- parametrizado (seguro)
let rows be sql("SELECT * FROM users WHERE id = ?", [1])
give paged("SELECT * FROM users ORDER BY id")              -- paginación para un handler de ruta
```

Los placeholders son siempre `?` (Postgres los reescribe a `$n` internamente; MySQL usa `?` nativo). Las columnas `bytes` round-trippean a BLOB/BYTEA byte-exacto.

## MongoDB (documentos)

```synsema
require db("mongodb://localhost/appdb")
db_open("mongodb://host:27017/appdb")
let id be mongo_insert("users", {"name": "Ana", "age": 30})
let adults be mongo_find("users", {"age": {"$gte": 18}}, {"sort": {"age": -1}})
```

Todos: `mongo_find(coll, filtro?, opts?)` (opts `{limit, skip, sort: {f: 1/-1}, fields: {f: 1}}`), `mongo_find_one(coll, filtro?)` → doc o `nothing`, `mongo_insert(coll, doc)` → `_id`, `mongo_insert_many(coll, docs)` → ids, `mongo_update(coll, filtro, update)` → `{matched, modified}` (`update` usa operadores, `{"$set": …}`), `mongo_delete(coll, filtro)` → `{deleted}`, `mongo_count(coll, filtro?)`, `mongo_aggregate(coll, pipeline)`, `mongo_collections()`. Documentos/filtros son mapas ↔ BSON; `_id` se lee como texto hex y un string de 24 hex bajo `_id` en un filtro se convierte a ObjectId.

## Redis (key-value / cache / estructuras)

```synsema
require db("redis://localhost")
db_open("redis://localhost:6379")
redis_set("session:42", token, 3600)             -- con TTL
let v be redis_get("session:42")
redis_set("cfg", json_encode({"theme": "dark"})) -- la data estructurada es explícita
```

Los valores son byte-strings (`text` si es UTF-8, si no `bytes`; enteros → `number`); los argumentos aceptan text/bytes/number (cualquier otra cosa: `json_encode`). Todos:

- strings / claves: `redis_get(key)` → valor o `nothing`, `redis_set(key, val, ttl_secs?)`, `redis_del(key…)`, `redis_exists(key…)`, `redis_mget(keys)`, `redis_mset(mapa)`, `redis_keys(patrón)` (O(N)), `redis_type(key)`.
- contadores: `redis_incr`, `redis_decr`, `redis_incrby(key, n)` (atómicos).
- TTL: `redis_expire(key, secs)`, `redis_ttl(key)` (`-1` sin TTL, `-2` ausente), `redis_persist(key)`.
- hashes: `redis_hget(key, campo)`, `redis_hset(key, mapa)` → campos nuevos, `redis_hdel(key, campo…)`, `redis_hgetall(key)` → mapa, `redis_hincrby(key, campo, n)`.
- listas: `redis_lpush`/`redis_rpush(key, val…)` → largo, `redis_lpop`/`redis_rpop(key)`, `redis_lrange(key, inicio, fin)`, `redis_llen(key)`.
- sets: `redis_sadd`/`redis_srem(key, miembro…)`, `redis_smembers(key)`, `redis_sismember(key, miembro)`.
- lock distribuido: `redis_lock(key, ttl_ms?)` → token o `nothing` si está tomado (SET NX PX, 30000 ms por defecto), `redis_unlock(key, token)` → bool (atómico, sólo libera quien tiene el token).

`db_close(path?)` cierra una conexión de cualquier familia.

> Gotcha del db-index de Redis: `redis://host:6379` → scope `redis://host` (sin `/0`); `…/0` es un scope *distinto*.
