sqlite-postgres-compat

module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: MIT

README

sqlite-postgres-compat

Capa de compatibilidad para Go con contratos de esquema canónico, migración por snapshot, replicación incremental bidireccional y cutover en vivo de SQLite/libSQL hacia PostgreSQL.

¿Por qué?

El caso de uso: arrancar un SaaS con SQLite o libSQL (cero operación, sin servidor de base de datos que administrar) y migrar a PostgreSQL más adelante, cuando el crecimiento lo exija, sin apagar la aplicación durante la copia. Cada fase del camino —auditoría de capacidades, snapshot, replicación, cutover— produce un veredicto determinista (exit code y JSON) en vez de una promesa optimista: si una capacidad no se puede garantizar en ambos motores, el proceso se detiene y lo dice, no degrada en silencio.

Características

  • Contrato de esquema canónico auditable: declara motor, versión y capacidades requeridas; Audit devuelve un veredicto por capacidad (exact/unknown) y RequireExact corta la migración ante cualquier capacidad no garantizada.
  • Snapshot con verificación por digest: exporta el esquema y los datos a una representación canónica, importa en el destino y compara hashes canónicos de ambos lados antes de dar por buena la copia.
  • Replicación incremental por triggers: captura automática de cambios (INSERT/UPDATE/DELETE) vía triggers internos, aplicación idempotente por secuencia, detección de conflictos con Expected/Actual, y supresión de ecos transaccional (GUC local compat.suppress en Postgres, no filtra a transacciones ajenas bajo MVCC).
  • Cutover orquestado sin apagar la aplicación: compat cutover audita, instala captura, hace snapshot, drena el journal con ApplyChangesTolerant (tolera el solapamiento captura/snapshot sin ser un bypass de conflictos reales) y verifica equivalencia; incluye un modo --dry-run de sólo lectura que imprime el plan sin escribir nada.
  • Tipo vector(N) de primera clase: SQLite/libSQL (F32_BLOB(N)) ↔ PostgreSQL (pgvector), validado end-to-end contra motores reales (libSQL/sqld y pgvector), incluida inspección de dimensión, snapshot y replicación incremental hacia una columna vector nativa.
  • Lectura parametrizada sin escribir SQL (acción select en rutinas canónicas + Store.QueryRoutine): declara la relación de origen (tabla o vista, para que los JOIN y agregados vivan en la vista), las columnas de salida con su tipo, un WHERE con parámetros con nombre, el ORDER BY y LIMIT/OFFSET parametrizables. Devuelve []Row canónicas —boolean, decimal y vector incluidos— con los mismos valores en ambos motores, así el consumidor no ramifica por motor. Todo identificador va citado y todo valor del llamador va enlazado como placeholder (? / $n). Es la contraparte de lectura de CallRoutine, que no cambia.
  • Predicado de subcadena portable (CONTAINS): la capa define la semántica de coincidencia en vez de delegarla al motor. Compila a instr (SQLite) y strpos (PostgreSQL), es sensible a mayúsculas y no tiene comodines — un % es un porcentaje literal —, y por eso devuelve exactamente las mismas filas en ambos motores: 0 de 18 divergencias sobre un corpus con acentos, ñ, metacaracteres y comillas. Sirve en el WHERE de una rutina y en el camino del catálogo (CHECK, columnas generadas, índices de expresión y parciales). Su antecesor LIKE se rechaza al compilar, en los dos caminos: SQLite pliega solo ASCII y no tiene escape, PostgreSQL ILIKE pliega Unicode y usa la barra invertida como escape, así que el mismo patrón seleccionaba filas distintas según dónde corriera. En el catálogo eso caía sobre una escritura: con el mismo CHECK (t LIKE 'ñ%'), "Ñandú mayúscula" es rechazada por SQLite y aceptada por PostgreSQL — el mismo esquema admitía datos distintos según el motor. CONTAINS reemplaza al "contiene"; prefijo y sufijo no tienen reemplazo portable hoy y hay que resolverlos con otra restricción o en la aplicación. Una base ya aplicada se sigue leyendo (el rechazo es al compilar, no al validar): ver AGENTS.md §15.
  • Borrado de tablas sin CASCADE: DropTable / DropTableIfExists (y sus compiladores puros CompileDropTable / CompileDropTableIfExists) emiten una sentencia byte-idéntica en ambos motores, rechazan las tablas internas reservadas y dejan que el motor rechace el borrado si alguien la referencia, en vez de destruir dependientes en silencio. Es el paso que faltaba para reconstruir el esquema de una tabla sin ALTER TABLE.
  • Cuatro primitivas de portabilidad para el consumidor: Placeholder(engine, position) (el compositor de placeholders ?/$n, exportado para quien debe escribir SQL crudo dentro de su propia transacción), Store.TableExists (existencia física en el catálogo del motor —sqlite_master / pg_class—, sin consultar __compat_schema, que es justo lo que se está reescribiendo cuando se hace esa pregunta), Store.IsUniqueViolation (clasifica la violación de restricción única por código estructurado —2067/1555 en SQLite, SQLSTATE 23505 en PostgreSQL—, no por el texto UNIQUE constraint failed, que al migrar dejaría de detectar duplicados en silencio) y Store.IsForeignKeyViolation (su hermano para claves foráneas —787 y 1811 en SQLite, SQLSTATE 23503 en PostgreSQL—, que le evita al consumidor la comprobación previa con ventana de carrera; 1811 entra porque SQLite implementa una clave foránea RESTRICT con su maquinaria interna de disparadores y reporta el rechazo del lado del padre como SQLITE_CONSTRAINT_TRIGGER, mientras PostgreSQL usa 23503 para todo; documenta que no distingue las dos direcciones de la violación). Ver AGENTS.md §12.
  • CLIs con salidas JSON y códigos de error tipados, pensadas para ser consumidas por agentes de IA: cada fallo emite un envelope {"status":"error","code":"<CODE>","message":"..."} sobre una taxonomía cerrada de códigos.
  • Gramática canónica documentada para agentes (AGENTS.md): especifica exactamente qué SQL/esquema es traducible, qué se rechaza y con qué error.

Estado: el núcleo canónico funciona y está probado en ambos motores, pero el proyecto todavía no ofrece compatibilidad total con cualquier SQL arbitrario de SQLite y PostgreSQL. La prueba global permanece roja hasta que esa afirmación sea verdadera.

Inicio rápido

go test ./...
go vet ./...
go run ./cmd/compat audit .\examples\contract.example.json

Para copiar un snapshot, edita examples/migration.example.json con DSN reales y ejecuta:

go run ./cmd/compat copy .\examples\migration.example.json

Para un cutover sin apagar la aplicación (edita examples/cutover.example.json con DSN reales primero):

go run ./cmd/compat cutover --dry-run .\examples\cutover.example.json

El CLI

Un único binario compat con subcomandos despacha los tres flujos (auditoría, snapshot, cutover). Invocado sin subcomando, con un subcomando desconocido o con un flag --help-ish, emite el uso en stderr y un envelope ERR_USAGE (exit 2).

Comando Qué hace Salida / exit codes
compat audit <contract.json> Audita un contrato ({source, destination, required_features}) y evalúa cada capacidad requerida. Acepta además un schema o schema_ref opcional e infiere de él las capacidades, igual que copy/cutover. Array JSON de Finding en stdout. Exit 0 si todo es exact; 1 si alguna capacidad no lo es (ERR_AUDIT_NOT_EXACT) o si el conjunto auditado queda vacío (ERR_CONFIG: un contrato que no exige nada no es un veredicto); 2 si el número de argumentos no es uno o flag inesperado (ERR_USAGE).
compat copy <migration.json> Migración por snapshot: audita, exporta el origen, importa en el destino (que debe estar vacío para esos objetos) y verifica digests. VerificationReport JSON en stdout. Exit 0 si equivalent=true; 1 ante cualquier error tipado o divergencia (ERR_VERIFY_DIVERGED); 2 si el número de argumentos no es uno o flag inesperado.
compat cutover [--dry-run] <cutover.json> Cutover sin apagar la aplicación durante la copia: audita, instala captura en el origen, hace snapshot al destino, drena el journal con ApplyChangesTolerant y verifica. --dry-run sólo audita, cuenta filas y prueba conectividad, sin escribir nada. cutoverReport JSON (status=ready/diverged) o plan JSON con --dry-run. Exit 0 si status=ready (o plan); 1 ante error tipado o status=diverged (ERR_VERIFY_DIVERGED); 2 si el número de argumentos no es uno o flag inesperado. El corte del DSN de la aplicación es manual, tras recibir status=ready.

Los tres subcomandos comparten una taxonomía cerrada de códigos de error (ERR_USAGE, ERR_CONFIG, ERR_AUDIT_NOT_EXACT, ERR_CONNECT_SOURCE, ERR_CONNECT_DESTINATION, ERR_SCHEMA, ERR_SNAPSHOT, ERR_REPLICATION_CONFLICT, ERR_CAPTURE, ERR_VERIFY_DIVERGED, ERR_INTERNAL); ver el detalle en docs/USAGE.md.

Uso con agentes de IA

AGENTS.md es la especificación machine-facing de la gramática canónica: qué tipos, constraints, expresiones, vistas, triggers y rutinas son traducibles, y con qué error explícito se rechaza todo lo demás. contracts/migration.contract.example.md es un contrato de migración ejecutable de ejemplo (frontmatter YAML + veredictos verificables comando por comando). El binario compat emite únicamente JSON parseable, incluidos los errores tipados descritos arriba, para que un agente pueda ramificar por código sin parsear texto libre.

Estado de validación

La batería E2E (e2e/system_test.go, e2e/suppress_test.go, e2e/cutover_test.go, e2e/droptable_test.go, e2e/selectroutine_test.go) corre contra SQLite real y PostgreSQL 17 real: 59 pruebas de nivel superior, 58 superadas y 1 fallida de forma intencional (TestSystemClaimsExactCoverageForRequiredFeatureFamilies), que documenta que las familias genéricas no-canónicas (foreign_keys, check_constraints, indexes, views, triggers, stored_routines, full_text) permanecen unknown porque representan SQL arbitrario del dialecto, no cubierto todavía. Detalle completo en docs/reports/VALIDATION_REPORT.md.

La compatibilidad del tipo vector fue validada por separado contra libSQL/sqld y pgvector reales (snapshot, replicación incremental e inspección de dimensión hacia una columna vector(N) nativa). Detalle en docs/reports/VECTOR-COMPAT-REPORT.md.

Estructura del repo

compat/           # núcleo: esquema canónico, parser SQL, DDL, snapshots, journal, replicación, runtime
cmd/               # CLI unificado: compat (subcomandos audit, copy, cutover)
e2e/               # batería end-to-end contra SQLite y PostgreSQL reales
experiments/vector/# validación del tipo vector contra libSQL/sqld y pgvector reales
examples/          # contratos y configuraciones de ejemplo para los CLIs
contracts/         # contrato de migración de ejemplo para agentes/CI
docs/              # arquitectura, uso, compatibilidad, operaciones, pruebas y reportes
scripts/           # gate de calidad local (check.ps1) y batería integral E2E (test-system.ps1)
AGENTS.md          # gramática canónica machine-facing para agentes/LLMs

Documentación

Licencia

MIT

Directories

Path Synopsis
cmd
compat command
Binary compat is the single entry point for the SQLite -> PostgreSQL compatibility toolchain.
Binary compat is the single entry point for the SQLite -> PostgreSQL compatibility toolchain.
internal/cliout
Package cliout centralizes the machine-facing stdout protocol shared by the compat CLIs.
Package cliout centralizes the machine-facing stdout protocol shared by the compat CLIs.
Package compat defines the engine-neutral contract used by the compatibility implementation.
Package compat defines the engine-neutral contract used by the compatibility implementation.
experiments
contains command
Measurement probe for the `contains` operator (specs/CONTRACT-CONTAINS.md).
Measurement probe for the `contains` operator (specs/CONTRACT-CONTAINS.md).
likecatalog command
Measurement probe for the catalog-path `like` refusal (specs/CONTRACT-LIKE-CATALOGO.md).
Measurement probe for the catalog-path `like` refusal (specs/CONTRACT-LIKE-CATALOGO.md).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL