Cada error user-facing del motor lleva un código estable de 4 dígitos en el prefijo [GBY-NNNN]. Inspirado en los ER_* de MySQL: el código es el contrato, el texto humano que viene después puede evolucionar sin romper a los clientes que reaccionan al código.

Las definiciones canónicas viven en src/errors.rs. Este documento es la vista operacional: qué dispara cada código, cómo se resuelve, ejemplo de mensaje real.

Para el estilo y filosofía de los mensajes (qué/por qué/cómo), ver ERROR_HANDLING.md. Para los patrones operacionales con cada error, ver TROUBLESHOOTING.md y RUNBOOK.md.


🧭 Cómo leer un código

Todo mensaje de gabysql con código tiene esta forma:

[GBY-2001] tabla no existe: orders
Pieza Significado
GBY- Prefijo fijo del motor (gabysql)
2001 Número estable y único de este error. Padded a 4 dígitos con 0
tabla no existe: orders Mensaje humano, en español, con el nombre concreto del objeto

Lo que es contrato: el número. Lo que puede cambiar entre versiones: la frase humana (mejor redacción, más contexto, traducciones futuras). Si tu herramienta parsea el texto, usá una regex sobre el número.


🔢 Rangos numéricos

Rango Subsistema
1000–1999 Storage / Pager / WAL / file lock
2000–2999 Catalog / schema / identificadores
3000–3999 Constraints (PK, NOT NULL, UNIQUE, FK)
4000–4999 Superficie SQL (parser, planner, limitaciones)
5000–5999 Server / HTTP / auth

Los rangos están reservados (no se asignan códigos cross-range) para que un código sea suficiente para inferir el subsistema sin abrir docs.


1000–1999 · Storage

Código Símbolo Causa Remedio
1001 REFUSE_OVERWRITE_DB gabysql init o Pager::create sobre un archivo .db ya existente. Use gabysql init --force <file.db> si la intención es resetear.
1002 DB_LOCKED_BY_PROCESS Otro proceso gabysql tiene la DB abierta (file lock cross-process, ADR-0013). Detener el otro proceso (gabysql-server, CLI, etc.) o esperar a que cierre. Ver TROUBLESHOOTING.md §database is locked.
1003 UNSUPPORTED_FORMAT_VERSION El .db declara una VERSION distinta a la del binario actual. Re-crear la base con el binario actual: gabysql init <file.db>. No hay migración automática entre versiones del formato.
1004 BAD_MAGIC_BYTES El archivo no empieza con GABYSQL1 — no es una DB gabysql. Apuntar al archivo correcto, o crear uno nuevo con gabysql init.
1005 TX_ALREADY_STARTED Pager::begin() con una transacción ya abierta (uso embebido). Llamar a commit() o rollback() antes del nuevo begin().
1006 NO_ACTIVE_TX Pager::commit() o rollback() sin un begin() previo. Asegurar que begin() corra antes de cualquier mutación.
1007 PAGE_CRC_INVALID El trailer CRC32 de una página no coincide con el contenido. Corrupción de disco o WAL parcialmente aplicado. Restaurar desde el último backup. Considerar gabysql verify <db> para localizar la página.
1008 WAL_RECORD_CRC_INVALID Un record del WAL falla CRC durante el replay. El WAL está corrupto — descartar .wal, restaurar .db desde backup. Ver RUNBOOK.md §Recovery tras caída.
1009 UNSUPPORTED_PAGE_SIZE El archivo declara un page_size distinto al fijo del build (4096). Re-crear la DB con el binario actual.

2000–2999 · Catalog / Schema

Código Símbolo Causa Remedio
2001 TABLE_NOT_FOUND Operación sobre una tabla que no existe en la DB. Crear la tabla con CREATE TABLE, o corregir el nombre. SHOW DATABASES y phpgabyadmin listan lo disponible.
2002 COLUMN_NOT_FOUND Columna referenciada (en INSERT, UPDATE, WHERE, SELECT) no existe en la tabla. Verificar el schema con GET /schema?table=... o phpgabyadmin → Structure.
2003 INDEX_NOT_FOUND DROP INDEX sobre un índice inexistente. SHOW DATABASES / phpgabyadmin → Structure muestran los índices definidos.
2004 TABLE_ALREADY_EXISTS CREATE TABLE sobre un nombre ya tomado. Usar otro nombre o DROP TABLE previo.
2005 INDEX_ALREADY_EXISTS CREATE INDEX con un nombre que ya existe en la DB, o sobre una columna que ya tiene índice. Usar otro nombre. Esta versión admite un solo índice por columna.
2006 INVALID_IDENTIFIER Nombre de tabla/columna/índice vacío, demasiado largo (>64), con caracteres prohibidos o palabra reservada del motor. Identificadores válidos: [A-Za-z_][A-Za-z0-9_]{0,63}, no reservados.
2007 DUPLICATE_COLUMN_NAME CREATE TABLE con dos columnas que comparten nombre (case-insensitive), o INSERT/UPDATE con la misma columna repetida en la lista. Eliminar la duplicada.
2008 INCOMPATIBLE_DEFAULT_TYPE El literal de DEFAULT no es compatible con el tipo declarado de la columna (ej. DEFAULT 'foo' en INT). Ajustar el literal al tipo de la columna.
2009 INDEX_ON_JSON CREATE INDEX sobre una columna JSON. No soportado. Cambiar el tipo o usar otro motor para queries vectoriales (gabysql-mcp con búsqueda semántica, ADR-0011).

3000–3999 · Constraints

Código Símbolo Causa Remedio
3001 DUPLICATE_PRIMARY_KEY INSERT con una PK que ya existe. Usar otro valor de PK o UPDATE la fila existente.
3002 NOT_NULL_VIOLATED INSERT/UPDATE deja NULL en una columna declarada NOT NULL. Pasar un valor explícito o agregar un DEFAULT no nulo a la columna.
3003 UNIQUE_VIOLATED INSERT/UPDATE viola un índice UNIQUE. El mensaje incluye la PK existente que ya tiene ese valor. Usar otro valor o modificar la fila conflictiva.
3004 FK_PARENT_MISSING INSERT/UPDATE con un valor de FK que no existe en la tabla padre. Crear primero la fila padre o usar un valor de FK válido.
3005 FK_RESTRICT_BLOCKS_DELETE DELETE sobre una fila que tiene hijos referenciándola con ON DELETE RESTRICT. El mensaje incluye cuántos hijos hay. Borrar primero los hijos o redefinir la FK con ON DELETE CASCADE.
3006 ROW_NOT_FOUND_FOR_PK UPDATE o DELETE sobre una PK que no existe. Verificar la PK con un SELECT previo.
3007 PRIMARY_KEY_NULL INSERT/UPDATE pasa NULL para la PK. La PK no puede ser NULL por definición — pasar un entero.
3008 CHECK_VIOLATED INSERT/UPDATE/UPSERT viola un constraint CHECK (expr). NULL pasa según 3VL ANSI; sólo FALSE rebota. El mensaje incluye el nombre del CHECK y su expresión canónica. Revisar el predicado declarado o pasar un valor que lo satisfaga.
3009 FK_SET_NULL_VIOLATES_NOT_NULL ON DELETE SET NULL intentó poner NULL en una columna FK declarada NOT NULL. La cascade aborta sin rollback parcial. Quitar NOT NULL de la columna del child o redefinir la FK con CASCADE/SET DEFAULT.
3010 FK_SET_DEFAULT_MISSING ON DELETE SET DEFAULT no encontró un DEFAULT declarado para la columna FK del child. Declarar un DEFAULT <valor> en la columna o cambiar la acción a CASCADE/SET NULL.

4000–4999 · Superficie SQL

Código Símbolo Causa Remedio
4001 WHERE_OPERATOR_UNSUPPORTED WHERE con un operador fuera de la gramática actual, o =/BETWEEN sobre columna no-PK sin índice secundario (fast-path indexado). Gramática soportada (E1+E2): =, <, >, <=, >=, <>/!=, BETWEEN, IS [NOT] NULL, [NOT] LIKE, [NOT] IN (lista \| SELECT), EXISTS, combinados con AND/OR/NOT + paréntesis. Ver SQL_REFERENCE.md. Para =/BETWEEN sin índice: crear CREATE INDEX o reescribir el WHERE para que caiga al post-filter (e.g. envolver en (...) AND TRUE).
4002 BETWEEN_REQUIRES_PK_OR_INT_INDEX (Histórico) BETWEEN sin índice OrderedInt. Inactivo desde el bloque F3 (2026-05-30): hoy ese BETWEEN cae a FullScan + post-filter, mismo path que =/>/<. La constante se conserva por estabilidad — nunca se reemite. Ver ADR-0066 Gap 2.
4003 UPDATE_DELETE_REQUIRES_PK_FILTER (Histórico) UPDATE o DELETE con WHERE sobre columna no-PK. Inactivo desde el bloque E3 (2026-05-25): UPDATE/DELETE ahora aceptan cualquier WHERE. La constante se conserva por estabilidad — nunca se reemite.
4004 LIMIT_NEGATIVE LIMIT n con n < 0. LIMIT admite valores >= 0.
4005 OFFSET_NEGATIVE OFFSET n con n < 0. OFFSET admite valores >= 0.
4006 STRING_LITERAL_UNTERMINATED Literal '...' sin la comilla de cierre. Cerrar el literal o escapar la comilla interna duplicándola ('').
4007 INSERT_COLS_VS_VALUES_MISMATCH INSERT INTO t (a,b,c) VALUES (1,2) — la cantidad de columnas no coincide con la cantidad de valores. Igualar las listas.
4008 UPDATE_PK_NOT_ALLOWED UPDATE t SET pk = ... — esta versión no admite cambiar la PK. Hacer INSERT con la nueva PK y DELETE de la vieja.
4009 LIMIT_DUPLICATED LIMIT n LIMIT mLIMIT aparece más de una vez. Usar uno solo.
4010 OFFSET_DUPLICATED OFFSET n OFFSET mOFFSET aparece más de una vez. Usar uno solo.
4011 SUBQUERY_MUST_RETURN_ONE_COLUMN WHERE col IN (SELECT a, b ...) — la subquery proyecta más (o menos) de una columna. Reescribir la subquery para que devuelva una sola columna.
4012 IN_PK_TYPE_MISMATCH WHERE pk_int IN (SELECT t FROM ...) donde la subquery devuelve valores no-INT. Ajustar la subquery para que devuelva INT, o filtrar por una columna no-PK.
4013 IN_REQUIRES_PK_OR_INDEX WHERE col IN (SELECT ...) (o = (SELECT ...)) cuando col no es PK ni tiene índice secundario. Crear índice (CREATE INDEX idx_t_col ON t (col);) o filtrar por la PK.
4014 SCALAR_SUBQUERY_TOO_MANY_ROWS WHERE col = (SELECT ...) cuya subquery devolvió más de 1 fila. Restringir la subquery con un WHERE/LIMIT 1, o usar IN (SELECT ...) en lugar de =.
4015 EXISTS_REQUIRES_SUBQUERY EXISTS/NOT EXISTS no seguido por (SELECT ...). Escribir EXISTS (SELECT ... FROM ... [WHERE ...]).
4016 OUTER_COLUMN_REF_INVALID col = outer_table.col usado fuera de una subquery correlacionada, o la tabla outer / columna outer no están en el alcance. Mover la referencia dentro de un EXISTS (SELECT ... WHERE inner_col = outer_table.outer_col), o usar un literal/subquery escalar.
4017 TABLE_ALIAS_DUPLICATED Dos tablas del FROM expuestas con el mismo qualifier (alias o nombre). Asignar alias distintos: FROM t AS a JOIN otra AS b.
4018 COLUMN_AMBIGUOUS Columna sin qualifier que existe en más de una tabla del FROM. Cualificar con tabla.col o alias.col.
4019 COLUMN_QUALIFIER_NOT_FOUND tabla.col donde tabla no es nombre ni alias del FROM, o col no existe en ninguna tabla. Verificar el nombre y los alias declarados.
4020 JOIN_PREDICATE_REQUIRED INNER JOIN ... sin ON l = r. Agregar ON tabla1.col = tabla2.col (o usar CROSS JOIN si querés cartesiano).
4021 CROSS_JOIN_WITH_ON CROSS JOIN ... ON ... — el cartesian product no admite predicado. Cambiar a INNER JOIN ... ON ....
4022 USING_COLUMN_INVALID JOIN ... USING (col) con col que no existe en ambas tablas, o USING con cantidad de columnas no soportada (este release: exactamente 1). Verificar que col exista en ambos lados; reescribir con ON para multi-columna.
4023 NATURAL_JOIN_NO_COMMON_COLUMN NATURAL JOIN cuyas tablas no comparten exactamente 1 columna por nombre (0 o >1). Usar JOIN ... ON o USING explícito.
4024 WHERE_COMBINATOR_CORRELATED_UNSUPPORTED DEPRECADO (Bloque H, 2026-05-26): el motor ya no lo emite — EXISTS/EqColumnRef correlacionados dentro de AND/OR/NOT están soportados. Slot reservado por estabilidad del catálogo.
4025 AGGREGATE_OUTSIDE_HAVING_OR_SELECT Función agregada (COUNT, SUM, AVG, MIN, MAX) usada fuera del SELECT list o HAVING — típicamente en WHERE. Moverla a HAVING, o aliasearla en el SELECT y referirse por alias.
4026 AGGREGATE_ARG_INVALID Argumento inválido de función agregada: SUM(*), AVG(DISTINCT x), MIN(*), o tipos no-numéricos en SUM/AVG. Solo COUNT(*) y COUNT(DISTINCT col) son combinaciones especiales aceptadas. Para SUM/AVG usar columnas INT o FLOAT.
4027 SELECT_COLUMN_NOT_IN_GROUP_BY SELECT mezcla columnas no-agregadas que no figuran en GROUP BY. Cumple la regla ANSI estricta. Agregar la columna al GROUP BY o envolverla en una función agregada (MIN/MAX).
4028 AGGREGATE_OVER_JOIN_UNSUPPORTED Restringido desde el bloque F2 (2026-05-30): COUNT/SUM/AVG/MIN/MAX + GROUP BY/HAVING sobre SELECT con JOIN ahora sí se soportan (ADR-0066 Gap 1). El código sigue activo únicamente para COUNT(DISTINCT col) sobre JOIN — el resto de los casos pasaron a producir resultados. Para COUNT(DISTINCT col) sobre JOIN: reescribir como subquery agregada sobre la tabla base, o esperar al bloque que generalice DISTINCT.
4029 TX_BEGIN_DOUBLE BEGIN SQL emitido con una transacción explícita ya abierta. SAVEPOINT no soportado todavía — la única forma de salir es COMMIT o ROLLBACK. Cerrar la transacción anterior antes de abrir una nueva.
4030 TX_END_WITHOUT_BEGIN COMMIT o ROLLBACK SQL emitido sin BEGIN previo. Las sentencias fuera de un bloque explícito son auto-commit por batch — no hace falta cerrarlas manualmente. Eliminar el COMMIT/ROLLBACK redundante, o agregar el BEGIN faltante al inicio del bloque.
4031 ON_CONFLICT_INVALID ON CONFLICT con acción no soportada o malformada (acciones aceptadas: DO NOTHING, DO UPDATE SET ...). REPLACE solo se obtiene vía REPLACE INTO .... Reescribir la cláusula con una acción soportada o usar REPLACE INTO.
4032 ON_CONFLICT_TARGET_NOT_UNIQUE ON CONFLICT (col) cuyo col no es PK ni tiene índice UNIQUE — sin un constraint indexado no se puede detectar el conflicto. Crear CREATE UNIQUE INDEX sobre la columna, usar la PK como target, u omitir (col) para que la cláusula aplique a cualquier constraint.
4033 PARSE_DEPTH_EXCEEDED Expresión SQL con anidamiento mayor al permitido por el parser (defensa contra stack exhaustion via paréntesis o NOT encadenados). Simplificar la expresión o partirla en varias consultas.
4034 SCALAR_FN_ARITY Función escalar invocada con la cantidad equivocada de argumentos (e.g. LENGTH() o SUBSTR(s)). Pasar la cantidad correcta de argumentos según la signatura de la función.
4035 SCALAR_FN_TYPE_MISMATCH Argumento de una función escalar con un tipo no aceptado (e.g. LENGTH(123) o ABS('x')). Usar el tipo correcto, o envolver con CAST(... AS TYPE).
4036 CAST_INVALID CAST(x AS TYPE) cuyo valor no se puede convertir al tipo destino (e.g. CAST('abc' AS INT)). Pre-validar el valor; usar COALESCE/CASE para descartar valores inválidos antes del CAST.
4037 SCALAR_FN_UNKNOWN Invocación a una función escalar que el motor no reconoce (e.g. FOO(1)). Ver la lista de funciones soportadas en SQL_REFERENCE.md (sección “Funciones escalares”); algunas todavía no están implementadas.
4038 CASE_BRANCH_TYPE_MISMATCH Condición de un CASE WHEN searched que no evalúa a BOOL. Reescribir la condición como una comparación (x > 10, x IS NULL, etc.).
4039 EXPR_IN_PREDICATE_NOT_SUPPORTED G2 (cerrado por G3): operador postfix (IS NULL/LIKE/IN/BETWEEN) con LHS expresional. Desde G3 la query funciona; el código queda reservado y sin emisión activa para preservar el contrato de estabilidad.
4040 WHERE_EXPR_NOT_BOOLEAN G2: expresión usada como predicado completo del WHERE/HAVING que no rinde BOOL/NULL, e.g. WHERE LENGTH(x) sin comparador. Agregar el operador de comparación faltante (= 0, > 3, etc.).
4041 UPDATE_SET_TYPE_MISMATCH G2: la RHS de UPDATE ... SET col = <expr> rinde un tipo incompatible con la columna (e.g. TEXT en INT). Envolver con CAST(... AS TIPO) explícito si la conversión es intencional.
4042 ARITH_OVERFLOW G3: operación entera con overflow (e.g. i64::MAX + 1). Promover a FLOAT con CAST(... AS FLOAT) antes de la operación si el rango es necesario.
4043 DIVISION_BY_ZERO G3: divisor cero en / o % (entero o flotante). Pre-filtrar con WHERE divisor <> 0 o usar NULLIF(divisor, 0) para devolver NULL.
4044 ARITH_TYPE_MISMATCH G3: operador aritmético sobre tipos incompatibles (e.g. 'abc' + 1). Reescribir con CAST explícito o usar \|\| si la intención era concatenar.
4045 MATH_DOMAIN G3: función matemática fuera del dominio real (e.g. SQRT(-1), POWER(0, -1)). Pre-filtrar el dominio del argumento o devolver NULL con CASE WHEN ... THEN ... ELSE NULL END.
4046 DATE_PARSE_ERROR G3: TEXT no parseable como DATE/DATETIME en DATE_ADD/DATEDIFF/EXTRACT/STRFTIME. Asegurar el formato YYYY-MM-DD o YYYY-MM-DD HH:MM:SS.
4047 EXTRACT_FIELD_INVALID G3: EXTRACT(<campo> FROM ...) con un campo desconocido. Usar uno de YEAR, MONTH, DAY, HOUR, MINUTE, SECOND.
4048 DERIVED_TABLE_REQUIRES_ALIAS H (2026-05-26): FROM (SELECT ...) sin alias — ANSI exige nombre obligatorio para poder referenciar las columnas del derived. Agregar AS sub (o un bare sub) después del ).
4049 DERIVED_DUPLICATE_COLUMN H: la subquery de un derived table proyecta dos columnas con el mismo nombre. Usar alias en la subquery (SELECT a AS x, b AS y).
4050 DERIVED_COLUMN_TYPE_AMBIGUOUS H: reservado para validación estricta futura de tipos mixtos en derived. Por ahora el motor cae a TEXT como fallback documentado.
4051 SCALAR_SUBQUERY_IN_EXPR_REQUIRES_PARENS H: reservado para subquery escalar en Expr sin paréntesis envolventes. Por ahora el parser solo acepta (SELECT ...).
4052 VALUES_IN_FROM_REQUIRES_ALIAS I (2026-05-26): FROM (VALUES (...), ...) sin alias de tabla, o sin lista de columnas (AS t(c1, c2, ...)). VALUES no provee nombres por sí mismo. Agregar AS t(c1, c2, ...) después del ).
4053 VALUES_COLUMN_ALIAS_ARITY I: la lista t(c1, c2, ...) tiene una arity distinta a las tuplas de VALUES. Igualar el número de aliases al número de expresiones por fila.
4054 SET_OP_ARITY_MISMATCH I: UNION / INTERSECT / EXCEPT entre dos queries con distinto número de columnas. Igualar la arity proyectada por ambos SELECT.
4055 SET_OP_TYPE_MISMATCH I: tipos incompatibles entre la columna N del LHS y la del RHS de un set op. INT/FLOAT promueven entre sí; cualquier otra mezcla rompe. Aplicar CAST para uniformar, o reordenar columnas.
4056 VALUES_ROW_ARITY_MISMATCH I: dos filas del mismo VALUES con distinta arity. Igualar el número de expresiones en cada fila.
4057 VALUES_EMPTY I: VALUES sin ninguna fila — sintaxis inválida. Agregar al menos una tupla (...).
4058 CTAS_REQUIRES_INT_FIRST_COLUMN K1 (2026-05-26): CREATE TABLE t AS SELECT ... cuya primera columna del result-set no es INT no-NULL. La primera columna se usa como PK INT de la nueva tabla. Antepoñer un id INT en el SELECT, o usar la forma CREATE TABLE t (id, ...) AS SELECT 1, ....
4059 CANNOT_DROP_PRIMARY_KEY K1: ALTER TABLE t DROP COLUMN <pk> — la PRIMARY KEY no se puede borrar. Usar DROP TABLE si la intención es rehacer el esquema.
4060 CANNOT_DROP_INDEXED_COLUMN K1: la columna a borrar tiene un índice asociado (CREATE INDEX o UNIQUE inline). Ejecutar DROP INDEX <name> antes del DROP COLUMN.
4061 CANNOT_DROP_REFERENCED_COLUMN K1: la columna a borrar participa en una FOREIGN KEY — saliente (la columna referencia otra tabla) o entrante (otra tabla la referencia). Recrear la tabla sin esa FK o esperar al soporte de ALTER ... DROP CONSTRAINT.
4062 RENAME_TARGET_EXISTS K1: RENAME TABLE old TO new (o RENAME COLUMN old TO new) cuyo destino ya está tomado por otra tabla/columna. Elegir un nombre libre.
4063 CTAS_COLUMN_ALIAS_ARITY K1: CREATE TABLE t (a, b) AS SELECT x, y, z FROM ... — la lista de aliases no matchea la arity del SELECT. Igualar el número de aliases al número de columnas que proyecta el SELECT.
4064 COMPOSITE_PK_REQUIRES_ALL_INT K2 (2026-05-26): PRIMARY KEY (a, b, ...) con alguna columna no-INT o nullable. El fingerprint i64 que sostiene la PK compuesta exige all-INT NOT NULL (ver ADR-0019). Declarar todas las columnas PK como INT NOT NULL, o modelar con surrogate id INT PRIMARY KEY + UNIQUE (a, b, ...).
4065 PRIMARY_KEY_DUPLICATED K2: PRIMARY KEY declarada dos veces — inline en una columna + table-level, o dos columnas con PRIMARY KEY inline. Elegir una única forma de declarar la PK.
4066 FK_TARGET_NOT_INDEXED K2 (reservado): una FOREIGN KEY apunta a columna del padre que no es ni PK ni UNIQUE. Hoy se reusa 3004 para los casos prácticos. Hacer la columna padre PK o UNIQUE.
4067 COMPOSITE_INDEX_REQUIRES_ALL_INT K2: CREATE INDEX idx ON t (a, b, ...) con alguna columna no-INT. Mismo motivo que 4064. Indexar solo columnas INT, o crear índices single-column individuales.
4068 PARTIAL_KEY_LOOKUP_UNSUPPORTED K2 (reservado): WHERE a = 1 contra PK compuesta (a, b) — el motor cae a full-scan correctamente, sin emitir error. Reservado para un futuro warning explícito. (no usado hoy).
4069 CHECK_CONTAINS_SUBQUERY L2 (2026-05-27): CHECK (expr) contiene una subquery (SELECT ...). ANSI lo prohíbe y el evaluador no lo soporta. Reescribir el predicado sin subquery, o validar desde el cliente.
4070 CHECK_EXPR_NOT_BOOLEAN L2 (reservado): predicado declarado en CHECK no evalúa a BOOL (ni NULL). Hoy el evaluador rebota con el error genérico del eval; el código queda para una validación DDL estricta futura. Comparar la expresión contra algo (CHECK (LENGTH(x) > 0) en vez de CHECK (LENGTH(x))).
4071 CONSTRAINT_NOT_FOUND Residual #2 (2026-05-27): ALTER TABLE DROP CONSTRAINT <name> no encontró ningún CHECK/UNIQUE/FK con ese nombre. El mensaje incluye un breakdown de los constraints visibles. Verificar el nombre con INTEGRITY CHECK; o usar DROP CONSTRAINT IF EXISTS para silenciar.
4072 CANNOT_DROP_PRIMARY_KEY_CONSTRAINT Residual #2: DROP CONSTRAINT <name> apuntando a la PK. La PK es inmutable durante la vida de la tabla. Usar DROP TABLE si la intención es rehacer el esquema.
4073 FK_RESTRICT_BLOCKS_UPDATE Residual #4 (2026-05-27): UPDATE que cambió un PK tiene children con ON UPDATE RESTRICT (o NO ACTION, alias). Sin estado parcial. Borrar/actualizar primero los children, o declarar la FK con ON UPDATE CASCADE/SET NULL/SET DEFAULT.
4074 FK_UPDATE_CASCADE_AFFECTS_CHILD_PK Residual #4: ON UPDATE CASCADE mutaría una columna que también participa en la PK del child. No soportado en este release. Rediseñar la FK o reescribir como DELETE + INSERT en una transacción explícita.
4075 VIEW_NOT_WRITABLE Bloque V (2026-05-27): INSERT/UPDATE/DELETE apuntando a una vista. Las vistas son read-only en este release. Modificar la tabla base directamente.
4076 VIEW_EXPANSION_DEPTH_EXCEEDED Bloque V: la cadena de vistas anidadas excedió MAX_VIEW_DEPTH (32). Típicamente un ciclo. Romper el ciclo o materializar en una tabla.
4077 VIEW_NAME_COLLIDES_WITH_OBJECT Bloque V: CREATE VIEW con un nombre ya tomado por una tabla o vista. Elegir otro nombre o usar IF NOT EXISTS (sólo aplica si la colisión es con otra vista).
4078 VIEW_SOURCE_NOT_SIMPLE_SELECT Bloque V: el source de la vista es un set op (UNION/INTERSECT/EXCEPT) o VALUES; sólo SELECT simple en este release. Refactorizar la vista como SELECT plano o esperar al soporte futuro.
4079 CTE_NAME_DUPLICATED Bloque W1 (2026-05-28): dos CTEs con el mismo nombre en la misma cláusula WITH. Renombrar uno de los CTEs.
4080 CTE_COLUMN_ARITY_MISMATCH W1: WITH t (a, b) AS (SELECT 1) — la lista de columnas no matchea la arity del SELECT. Igualar la arity.
4081 CTE_NOT_REFERENCED W1 (reservado): un CTE declarado pero no usado en el main query. (no emite hoy — reservado para warning futuro).
4082 RECURSIVE_CTE_REQUIRES_UNION Bloque W2 (2026-05-28): WITH RECURSIVE cuyo body no es anchor UNION [ALL] step. Reescribir como SELECT ... UNION ALL SELECT ....
4083 RECURSIVE_CTE_ANCHOR_REFERENCES_SELF W2: el anchor (lado izquierdo del UNION) referencia el CTE — solo el step puede. Quitar la auto-referencia del anchor.
4084 RECURSIVE_CTE_FIXPOINT_DIVERGE W2: el fixpoint no converge dentro del límite (10K iteraciones). Típicamente un step que crece sin terminar. Agregar una condición de parada en el step (e.g. WHERE n < 100).
4085 RECURSIVE_CTE_ARITY_MISMATCH W2: anchor y step tienen distinta arity. Igualar el número de columnas en ambos lados.
4086 (slot reservado)
4087 WINDOW_FUNC_UNKNOWN Bloque W3 (2026-05-28): función window no soportada. Catálogo W3: ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, FIRST_VALUE, LAST_VALUE, SUM/AVG/MIN/MAX/COUNT OVER. Usar una función del catálogo o re-armar con subqueries.
4088 WINDOW_FRAME_UNSUPPORTED W3: OVER (... ROWS BETWEEN ...) con frame explícito. Solo el frame default (UNBOUNDED PRECEDING → CURRENT ROW para agregados, sin frame para ranking). Quitar el ROWS BETWEEN.
4089 WINDOW_ARG_INVALID W3: argumento inválido de una función window (e.g. LAG() sin args, ROW_NUMBER(x) con args). Ajustar a la signatura.
4090 WINDOW_ORDER_BY_REQUIRED W3: funciones que requieren orden total (LAG, LEAD, RANK, DENSE_RANK) sin ORDER BY en el OVER. Agregar ORDER BY dentro del OVER (...).
4091 WINDOW_IN_WHERE W3: función window en WHERE / HAVING / GROUP BY (solo se permite en SELECT list y ORDER BY). Mover a una subquery y filtrar afuera.
4092 TRIGGER_NAME_DUPLICATED Bloque X1 (2026-05-28): CREATE TRIGGER con un nombre ya tomado. Usar DROP TRIGGER primero o elegir otro nombre.
4093 TRIGGER_TARGET_NOT_TABLE X1: el ON <obj> apunta a una vista u otro objeto no-tabla. Crear el trigger sobre la tabla base.
4094 TRIGGER_EVENT_UNSUPPORTED X1: evento fuera de INSERT/UPDATE/DELETE. Usar uno de los tres soportados.
4095 TRIGGER_NOT_FOUND X1: DROP TRIGGER que no existe (sin IF EXISTS). Usar DROP TRIGGER IF EXISTS o verificar el nombre.
4096 TRIGGER_DEPTH_EXCEEDED X1: cascada de triggers excedió MAX_TRIGGER_DEPTH (16). Típicamente un trigger que dispara DML sobre la misma tabla. Romper la recursión o agregar guard en el body.
4097 PROCEDURE_NAME_DUPLICATED Bloque X3 (2026-05-28): CREATE PROCEDURE con nombre ya tomado. DROP PROCEDURE primero o elegir otro nombre.
4098 PROCEDURE_NOT_FOUND X3: CALL o DROP PROCEDURE sobre uno inexistente. Verificar el nombre.
4099 PROCEDURE_ARITY_MISMATCH X3: CALL p(a, b) con cantidad de args distinta a los declarados. Igualar la arity.
4100 PROCEDURE_PARAM_TYPE_MISMATCH X3: arg con tipo incompatible para el param declarado. Ajustar el tipo o envolver con CAST.
4101 FUNCTION_NAME_DUPLICATED Bloque X3b (2026-05-28): CREATE FUNCTION con nombre ya tomado. DROP FUNCTION primero.
4102 FUNCTION_NOT_FOUND X3b: invocación o DROP FUNCTION sobre una inexistente. Verificar el nombre.
4103 FUNCTION_ARITY_MISMATCH X3b: invocación con cantidad de args distinta. Igualar la arity.
4104 FUNCTION_PARAM_TYPE_MISMATCH X3b: arg con tipo incompatible. Ajustar el tipo o CAST.
4105 IF_THEN_MALFORMED Bloque X4 (2026-05-28): bloque IF ... THEN ... [ELSIF ...]* [ELSE ...] END IF malformado — falta THEN/END IF/ELSIF. Revisar la sintaxis.
4106 IF_COND_NOT_BOOLEAN X4: condición de IF/ELSIF que no evalúa a BOOL. Reescribir como comparación.
4107 DECLARE_DUPLICATE Bloque X4b (2026-05-28): variable declarada dos veces en el mismo scope. Renombrar o quitar el duplicado.
4108 SET_VAR_NOT_DECLARED X4b: SET name = expr sobre una variable que no fue declarada. Agregar DECLARE name TYPE [DEFAULT expr] antes.
4109 SET_VAR_TYPE_MISMATCH X4b: RHS de SET con tipo incompatible. Ajustar tipo o CAST.
4110 LOOP_MAX_ITERATIONS_EXCEEDED X4b: WHILE/LOOP/FOR excedió MAX_LOOP_ITERATIONS (100K). Guard contra runaway. Agregar condición de salida (EXIT WHEN ...) o reducir el rango.
4111 RAISE_EXCEPTION Bloque X4c (2026-05-28): RAISE EXCEPTION 'msg' — aborto user-triggered. Capturar con EXCEPTION WHEN 4111 THEN ... si querés handler.
4112 RAISE_NOTICE X4c: RAISE NOTICE 'msg' — info-level, no aborta. (no es error en sentido estricto).
4113 FOR_RANGE_INVALID X4c: FOR i IN start TO end LOOP con start/end no-INT. Usar literales INT o variables INT.
4114 EXCEPTION_HANDLER_MALFORMED Bloque X4d (2026-05-28): BEGIN..EXCEPTION..END con WHEN/THEN faltante. Revisar sintaxis.
4115 LOOP_BLOCK_MALFORMED X4d: LOOP ... END LOOP standalone sin cierre. Agregar END LOOP.
4116 CASE_STATEMENT_MALFORMED Bloque X4e (2026-05-29): CASE WHEN ... THEN ... END CASE malformado. Revisar sintaxis.
4117 EXCEPTION_FILTER_INVALID X4e: EXCEPTION WHEN <filter> con filtro que no es OTHERS ni un entero. Usar OTHERS o un código numérico (WHEN 4111 THEN).
4118 RETURN_OUTSIDE_FUNCTION Bloque X4f (2026-05-29): RETURN expr fuera del body de una function. Mover el RETURN al body de un CREATE FUNCTION ... AS BEGIN ... END.
4119 VALUE_LENGTH_EXCEEDED Bloque Y2 (2026-05-29): INSERT/UPDATE de string que excede VARCHAR(n)/CHAR(n). La longitud se mide en bytes UTF-8. Truncar el string o ampliar el n con ALTER TABLE (no soportado todavía — usar DROP COLUMN + ADD COLUMN).
4120 RAISE_FORMAT_OR_FOR_STEP_INVALID Bloque X5 (2026-05-29): RAISE 'fmt %', args con arity mismatch entre % y argumentos, o FOR i IN ... STEP 0 (incremento cero → loop infinito). Igualar el número de % y args en el RAISE; o usar STEP n con n != 0.
4121 INT_RANGE_EXCEEDED Bloque Y3 (2026-05-29): INSERT/UPDATE de entero fuera del rango declarado por TINYINT ([-128, 127]), SMALLINT/INT2 ([-32768, 32767]), MEDIUMINT ([-8388608, 8388607]) o INT4 (i32). INT/BIGINT no enforcen. Truncar el valor, redeclarar la columna a un ancho mayor (no soportado en ALTER — usar DROP+ADD), o filtrar el dato en el cliente.
4122 BLOB_LITERAL_INVALID Bloque Y4 (2026-05-29): X'hex' con largo impar, char no-hex, o CAST('s' AS BLOB) con s no hex válido. Asegurarse que el contenido entre X' y ' tenga largo par y sólo caracteres [0-9A-Fa-f]. Para input desde TEXT usar CAST('0xdeadbeef' AS BLOB).
4123 DECIMAL_OUT_OF_RANGE Bloque Y6 (2026-05-29): parte entera de un valor DECIMAL(p,s) excede 10^(p-s), o overflow de i128 al parsear/multiplicar. Truncar el valor, redeclarar la columna con mayor precisión, o validar en el cliente.
41244139 (varios) Códigos de los bloques Z (seguridad / users / roles / RLS) y P1 (UNSUPPORTED_SYNTAX). Documentación detallada de este rango: deuda pendiente — ver src/errors.rs para la lista canónica con docstrings.
4140 SAVEPOINT_OUTSIDE_TX Bloque M12 (2026-06-15): SAVEPOINT name / ROLLBACK TO SAVEPOINT name / RELEASE SAVEPOINT name emitido sin un BEGIN activo. Los savepoints solo viven dentro de una transacción explícita. Envolver con BEGIN; SAVEPOINT ...; ...; COMMIT; o quitar el savepoint si no se necesita. Ver ADR-0089.
4141 SAVEPOINT_NOT_FOUND Bloque M12: ROLLBACK TO SAVEPOINT name o RELEASE SAVEPOINT name con un name que no fue declarado en la tx actual, o que fue invalidado por un ROLLBACK TO previo más arriba en la stack. Verificar que el name esté declarado con SAVEPOINT name antes en la misma tx, y que no haya un ROLLBACK TO posterior a un savepoint anterior que lo invalide.

5000–5999 · Server / HTTP

Código Símbolo Causa Remedio
5001 MISSING_DB_PARAM Endpoint multi-DB invocado sin ?db=.... Pasar ?db=<nombre>.db en la query string.
5002 MISSING_TABLE_PARAM GET /schema o GET /rows sin ?table=.... Pasar ?table=<nombre>.
5003 INVALID_DB_NAME ?db=... con /, \ u otros caracteres prohibidos. Solo se admiten nombres relativos dentro del directorio configurado con -dir.
5004 UNAUTHORIZED Falta header Authorization: Bearer <token> o el token es incorrecto. Pasar el token con el que arrancó el server.
5005 SERVER_BUSY El cap de conexiones simultáneas está al máximo (default 64). Esperar y reintentar, o subir -max-connections N.
5006 SERVER_NOT_MULTI_DB Endpoint multi-DB (?db=...) sobre un server arrancado con -db (modo single-DB). Arrancar el server con -dir <carpeta> para habilitar multi-DB.

🛠️ Cómo usar los códigos desde un cliente

CLI / shell

# Capturar el código exacto de un fallo
err=$(gabysql exec demo.db "DROP TABLE inexistente;" 2>&1)
echo "$err"
# → error: [GBY-2001] tabla no existe: inexistente

# Extraer solo el código
echo "$err" | grep -oE 'GBY-[0-9]{4}'
# → GBY-2001

Cliente HTTP

curl -s http://localhost:8080/exec \
  -H 'Authorization: Bearer secret' \
  -d '{"sql":"DROP TABLE inexistente;"}' | jq -r .error
# → "[GBY-2001] tabla no existe: inexistente"
import re

resp = requests.post("http://localhost:8080/exec", json={"sql": "..."}, headers={"Authorization": "Bearer ..."})
data = resp.json()
if not data["ok"]:
    match = re.match(r"\[GBY-(\d{4})\]", data["error"])
    code = int(match.group(1)) if match else None
    if code == 3001:  # DUPLICATE_PRIMARY_KEY
        # tomar acción específica
        ...

Embedido en Rust

use gabysql::errors::codes;

if let Err(err) = pager.commit() {
    let text = err.to_string();
    if text.starts_with(&format!("[GBY-{:04}]", codes::NO_ACTIVE_TX)) {
        // recover from the no-tx case
    }
}

Nota: hoy DbError solo expone el texto. Cuando exista demanda real para pattern-match programático, se introducirá enum DbErrorKind y un método code() directo. El contrato vía prefijo string es estable y suficiente para la mayoría de los casos.


📦 Por qué constantes en Rust, no JSON externo

Pregunta razonable cuando uno ve este catálogo: “¿no sería más flexible tener un errors.json cargado al arranque?”. La respuesta es no, y vale escribirla acá para no repetirla:

  1. gabysql es zero-deps embebido (ADR-0001). Un JSON externo agrega filesystem I/O al startup y una clase nueva de fallo (“no encuentro errors.json” — ¿con qué error reportás eso?).
  2. Los códigos son del motor, no de configuración. Renombrar TABLE_NOT_FOUND rompe el código que lo usa; con constantes el compilador detecta el error, con JSON solo lo detecta una run de tests dedicada.
  3. No hay ganancia real. Cambiar un mensaje hoy es editar .rs, rebuild, redeploy. Con errors.json sería editar .json, redeploy y rezar que el formato no rompió el parser. Mismo trabajo, más superficie de fallo.
  4. i18n no es el caso de uso hoy. Si en el futuro hace falta, un build feature i18n_es / i18n_en con constantes diferentes resuelve el caso sin filesystem.

📋 Estabilidad


🔗 Referencias