---
name: tql_to_sql
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: pure
signature: "tql_to_sql::SqlEmit tql_to_sql::emit_sql(const data_table::State& state, const std::vector<data_table::TableInput>& tables, int up_to_stage = -1)"
description: "Transpiles a TQL State pipeline to DuckDB-compatible SQL (CTE chain). Also exposes transpile_expr and is_transpilable for validating Lua formula subsets. Pure: no DuckDB linked, no I/O."
tags: [tables, tql, sql, duckdb, cpp-tables]
uses_functions:
  - tql_helpers_cpp_core
uses_types:
  - data_table_types_cpp_core
returns: []
returns_optional: false
error_type: ""
imports:
  - "core/tql_to_sql.h"
  - "core/tql_helpers.h"
tested: true
tests:
  - "transpile_expr: simple arithmetic"
  - "transpile_expr: numeric literal"
  - "transpile_expr: string literal"
  - "transpile_expr: boolean literals"
  - "transpile_expr: ternary if/then/else"
  - "transpile_expr: string concat"
  - "transpile_expr: whitelisted math function"
  - "transpile_expr: forbidden keyword returns error"
  - "transpile_expr: bare identifier not allowed"
  - "is_transpilable: simple expression is transpilable"
  - "is_transpilable: loop keyword not transpilable"
  - "emit_sql: select simple single table"
  - "emit_sql: filter eq produces WHERE with placeholder"
  - "emit_sql: sort produces ORDER BY"
  - "emit_sql: group by + sum aggregation produces correct SQL"
  - "emit_sql: join two tables produces JOIN clause"
  - "emit_sql: empty state returns error"
  - "emit_sql: empty tables returns error"
  - "emit_sql: contains filter produces LIKE with wildcards"
test_file_path: "cpp/tests/test_tql_to_sql.cpp"
file_path: "cpp/functions/core/tql_to_sql.cpp"
params:
  - name: state
    desc: "TQL State with stages (filters, derived cols, breakouts, aggregations, sorts) and joins. active_stage determines the last CTE emitted."
  - name: tables
    desc: "Schema and data for each TableInput. The emitter uses headers/name to build the SELECT list and JOIN clauses. Cells are not read (pure SQL generation)."
  - name: up_to_stage
    desc: "Emit SQL only up to this stage index. -1 = use state.active_stage."
output: "SqlEmit with: sql (WITH ... CTEs + SELECT * FROM tN), params (positional ? bound values), warnings (soft issues like missing join tables or untranspilable derived cols), error (non-empty on hard failure)."
---

## Ejemplo

```cpp
#include "core/tql_to_sql.h"

data_table::State st;
st.stages.push_back(data_table::Stage{});
data_table::Filter f{0, data_table::Op::Eq, "North"};
st.stages[0].filters.push_back(f);

data_table::TableInput ti;
ti.name    = "sales";
ti.headers = {"region", "amount"};

auto e = tql_to_sql::emit_sql(st, {ti});
// e.sql  -> "WITH t0 AS (\n  SELECT \"region\", \"amount\"\n  FROM \"sales\"\n  WHERE \"region\" = ?\n)\nSELECT * FROM t0;\n"
// e.params -> {"North"}
```

## Cuando usarla

Cuando necesites exportar el estado TQL actual a SQL ejecutable en DuckDB. Usa antes de llamar a DuckDB para hidratar las tablas y ejecutar la query. El caller es responsable de registrar las tablas en DuckDB con los mismos nombres que `TableInput.name`.

## Gotchas

- Dialecto DuckDB: `date_trunc`, `quantile_cont`, `CAST(x AS DOUBLE)`. No compatible con SQLite sin adaptar.
- `transpile_expr` solo acepta el subset Lua: `[col]` refs, operadores aritmeticos/logicos/comparacion, `if/then/else/end`, funciones whitelisted (`math.*`, `string.upper/lower/len/sub`, `tostring`, `tonumber`). Closures, loops, `io.*`, `os.*` dan error.
- Filters de stages N>=1 no se emiten en v1 (pendiente `TODO v2`). Solo stage 0 tiene WHERE.
- Identifiers siempre quoted con `"double quotes"` — preserva case y permite `:` en nombres de breakout con granularity.
- Derived cols con formula fuera del subset se saltan con warning, no error.
- `resolve_main_idx` es static: si `main_source` no coincide con ninguna tabla, usa `tables[0]`.
