SofiaDB desde el lenguaje Sofía#

Consejo

En corto: una app de Sofía que declara permiso datos recibe una base SQL privada que ninguna otra app puede abrir. Se usa con datos.ejecutar, datos.consultar, datos.entero, datos.texto y datos.copiar; los valores van siempre entre { } y viajan aparte como parámetros, así que la inyección SQL no compila. Un bloque transaccion { } guarda todo junto o nada.

La base vive en la carpeta privada de la app (~/.sofia/datos/<id>/.sofiadb/). archivos.* no la ve. Cuando una sentencia o un transaccion { } termina, lo guardado sobrevive a un corte de luz. El detalle del motor está en SofiaDB; el SQL que acepta, en la referencia de SQL; y un recorrido con ejemplos, en el tutorial.

Un ejemplo completo#

app "Citas" id "citas" version "1.0"
permiso consola, datos

tipo Cita { id: entero, profesional: texto, inicio: entero, monto: texto }

fn reservar(prof: texto, inicio: entero) -> texto {
    var r = "RESERVADA"
    intentar {
        transaccion {
            let choca = datos.entero($"SELECT count(*) FROM cita WHERE profesional = {prof} AND inicio = {inicio}")
            si choca > 0 { fallar("HORA_OCUPADA") }
            datos.ejecutar($"INSERT INTO cita (id, profesional, inicio, monto) VALUES ({inicio}, {prof}, {inicio}, {"25.50"})")
        }
    } si falla e {
        r = e
    }
    devolver r
}

fn principal() {
    datos.ejecutar("CREATE TABLE IF NOT EXISTS cita (id INTEGER PRIMARY KEY, profesional TEXT, inicio TIMESTAMPTZ, monto NUMERIC(10,2))")
    consola.escribir_linea(reservar("ana", 1790586000000))
    para c en datos.consultar(Cita, "SELECT id, profesional, inicio, monto FROM cita ORDER BY inicio") {
        consola.escribir_linea($"{c.profesional}: {c.monto} €")
    }
}

Funciones#

FunciónDevuelveNotas
datos.ejecutar(sql)enteroCREATE TABLE, INSERT, UPDATE, DELETE… Devuelve las filas afectadas
datos.consultar(Registro, sql)lista<Registro>Una fila por registro: cada campo se rellena con la columna del mismo nombre (usa AS para renombrar). Los campos solo pueden ser entero, texto o logico
datos.entero(sql) · datos.texto(sql)entero · textoLa primera columna de la primera fila (0 o "" si no hay filas): para count(*), sum(…), un nombre suelto
datos.copiar(nombre)enteroCopia en caliente de la base al archivo nombre de la carpeta privada (que no debe existir), sin detener a las demás peticiones: exactamente lo confirmado al empezar, entera y comprobada, o nada. Devuelve sus bytes. Cuenta para el disco; no se permite dentro de transaccion { }
transaccion { … }Todo lo del bloque se confirma junto al salir; si algo dentro llama a fallar (también una función llamada desde el bloque o un error de la base), se deshace todo y el fallo sigue hacia fuera con el mismo mensaje

Consultas parametrizadas obligatorias#

Importante

La inyección SQL es imposible por construcción. El SQL tiene que ser un texto escrito en el programa; los valores van entre llaves, $"… WHERE id = {id}", y la plataforma los pasa aparte, como parámetros ($1, $2…), nunca pegados en el SQL. Un nombre como x'); DROP TABLE cita; -- se guarda tal cual. Armar el SQL con +, pasarlo en una variable o poner comillas alrededor de {valor} no compila.

Entre llaves va un entero, un texto o un logico, o cualquier expresión que dé uno de ellos. Un real se pasa como texto con texto.de_real(x).

Permiso#

permiso consola, datos

Sin permiso datos, las funciones datos.* y transaccion { } no compilan.

Tipos#

ColumnaEn el lenguajeNota
INTEGER (o ENTERO)enteroEntero de 64 bits
TEXT (o TEXTO)texto
BOOLEAN (o LOGICO)logico
BYTEA (o BYTES)textoEn base64
NUMERIC(p,s) (o DECIMAL)textoDecimal exacto, para dinero: "25.50", sin coma flotante
TIMESTAMPTZ (o INSTANTE)enteroMilisegundos desde 1970, como reloj.ahora()

Del lenguaje a la base: un entero, texto o logico entre llaves llega con su tipo; un texto se convierte al tipo de la columna como un literal SQL ({"25.50"} a NUMERIC, {"2026-09-28T09:00:00Z"} a TIMESTAMPTZ). Un tipo que no encaja es un fallo recuperable que dice qué columna es.

Reglas#

Límites#

QuéLímite
Tamaño de la baseHasta el disco de la app (256 MiB por defecto)
Una fila16 MiB
Clave primaria1 KiB
Columnas por tabla1000
Valores {…} por sentencia4096
Consulta que junta o agrupaHasta 64 MiB de memoria: agrega filtros, un índice o LIMIT
Espera a que otra petición termine su transacción10 s
Una transacción larga en una app webPasados 30 s falla

Para copias de seguridad, cifrado y acceso remoto, la línea de órdenes está en sofia datos.