Fecha: 2026-06-15 Estado: Aceptado Bloque: M3 (fundacional de Fase 4 — red de seguridad del optimizer) Origen: docs/TAREAS_PENDIENTES.md §3 y docs/ANALISIS_POST_P5.md §4.1 M3 — mencionado como “deuda más cara” después de la sesión P5. Refina: ADR-0071 (P5c), ADR-0072 (P5d), ADR-0077 (R6), ADR-0081 (R2).

Contexto

Tras la sesión P5 (P5c + P5d + R6), el planner de gabysql es cost-based: las stats persistidas determinan QUÉ path corre.

Los 3 cambios son plan-cambios: el motor toma decisiones distintas según las stats. La correctness invariante crítica es:

El resultado de un SELECT NUNCA debe cambiar según si ANALYZE corrió o no, ni cómo está calibrado el threshold.

Si esa invariante se rompe, el motor mintió: devolvió un set distinto porque eligió un path distinto. Un test unitario tradicional solo verifica casos específicos elegidos por el dev — fácil dejar pasar el caso adversarial. Property tests cubren la combinatoria.

Decisión

Crear tests/proptest_planner.rs (test binario aparte para no agrandar el monolito de integration_test.rs) con 3 property tests hand-rolled — zero deps externas (alinea con ADR-0001).

Infraestructura

Tests

  1. m3_select_results_invariant_with_vs_without_analyze (50 iters × 3 queries = 150 comparaciones): para cada (seed, WHERE), popula 2 DBs idénticas, corre ANALYZE en una sola, ejecuta SELECT id FROM t WHERE <generado> ORDER BY id en ambas. Los Vec<Vec<Value>> deben ser iguales.
  2. m3_count_invariant_with_vs_without_analyze (30 iters): mismo patrón pero con COUNT(*) WHERE <generado>. Cardinalidad invariante sin necesidad de ordenar.
  3. m3_inner_join_invariant_with_vs_without_analyze (20 iters × 3 queries fijas): JOIN inner sobre u × o. Ordenamos los resultados en Rust (sort_by sobre el format!("{:?}", row)) para defenderse del orden no determinístico que P5d swap puede producir sin ORDER BY robusto. La invariante real es el set, no el orden.

Notas de implementación

Consecuencias

Positivas

Negativas / deuda

Tests añadidos

Suite total: 810 integration + 3 proptest = 813 verde, 3 ignored. Sin regresiones.

Alternativas consideradas

  1. Usar proptest crate. Mejor shrinking, mejor reporting, ergonomía más pulida. Choca con ADR-0001 (zero-deps core). Rechazado por defecto; reconsiderable si el LCG-rolled hand-test demuestra ser limitante.
  2. Agregar tests al archivo integration_test.rs existente. Ya tiene ~19k líneas. Property tests son una categoría conceptual distinta (regenerativa, no-determinista por shape, con seed seed logging diferente). Aparte se navega mejor.
  3. Solo correrlos en CI, no en local. Innecesario — el costo es bajo (15s en local) y la confianza per-push vale más.

Referencias