---
name: run_mssql_query
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def run_mssql_query(host: str, database: str, user: str, password: str, sql: str, params=None, port: int = 1433, max_rows: int | None = None, login_timeout: int = 15, query_timeout: int = 30) -> dict"
description: "One-shot contra SQL Server (Navision): abre conexion, ejecuta UNA SELECT parametrizada y cierra, devolviendo {columns, rows, row_count}. Compone mssql_connect + mssql_query. Pensado para iterar queries de Navision en un solo comando (fn run run_mssql_query ...) en vez del copia-pega manual. CLI imprime JSON o CSV; la contrasena se lee de una env var (recomendado: MSSQL_PASSWORD=$(pass navision/password)), nunca hardcodeada."
tags: [mssql, sqlserver, navision, sql-connect, pipelines]
uses_functions:
  - mssql_connect_py_infra
  - mssql_query_py_infra
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: host
    desc: "Host o IP del SQL Server. Desde WSL2 debe ser la IP LAN de Windows, no localhost."
  - name: database
    desc: "Nombre de la base de datos a la que conectar (p.ej. la BD de Navision)."
  - name: user
    desc: "Usuario de login de SQL Server."
  - name: password
    desc: "Contrasena del usuario. Se pasa desde pass/env, nunca como literal en codigo."
  - name: sql
    desc: "Sentencia SELECT con placeholders pymssql %s (posicional) o %(nombre)s (nombrado) para los valores."
  - name: params
    desc: "Tuple/list (posicional), dict (nombrado) o None. Binding seguro del driver (sin inyeccion)."
  - name: port
    desc: "Puerto TCP del SQL Server. Default 1433."
  - name: max_rows
    desc: "Si es int positivo, devuelve solo las primeras max_rows filas; None devuelve todas."
  - name: login_timeout
    desc: "Segundos para la fase de conexion/login. Default 15. Evita que un host inalcanzable cuelgue."
  - name: query_timeout
    desc: "Segundos de timeout por query. Default 30."
output: "Dict {columns: [nombres], rows: [{col: val}, ...], row_count: int} con el resultado de la SELECT. La conexion se cierra siempre antes de devolver."
tested: true
tests:
  - test_run_mssql_query_composes_connect_and_query
  - test_run_mssql_query_closes_connection_on_error
  - test_to_csv_renders_header_and_rows
test_file_path: "python/functions/pipelines/run_mssql_query_test.py"
file_path: "python/functions/pipelines/run_mssql_query.py"
---

## Ejemplo

Como API programatica (compone conexion + query + cierre):

```python
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "python", "functions"))
from pipelines.run_mssql_query import run_mssql_query

res = run_mssql_query(
    host="10.0.0.5",                 # IP LAN del Windows con SQL Server (no localhost desde WSL2)
    database="navdb",
    user="sa",
    password=os.environ["MSSQL_PASSWORD"],   # nunca literal: viene de pass/env
    sql="SELECT TOP 10 [No_], [Amount] FROM [dbo].[Cartera] WHERE [Customer No_] = %s",
    params=("CLI-0001",),            # binding seguro del driver, sin inyeccion
)
print(res["row_count"], res["columns"])
for fila in res["rows"]:
    print(fila)
```

Como comando one-shot para iterar sobre Navision (imprime JSON o CSV):

```bash
# La contrasena se lee de la env var, nunca se pasa por la linea de comandos
MSSQL_PASSWORD=$(pass navision/password) \
  ./fn run run_mssql_query \
    --host 10.0.0.5 --database navdb --user sa \
    --sql "SELECT TOP 5 [No_], [Amount] FROM [dbo].[Cartera] WHERE [Customer No_] = %s" \
    --param CLI-0001 \
    --format csv
```

## Cuando usarla

Cuando quieras ejecutar una SELECT contra Navision (SQL Server) y ver las filas en un
solo paso, sin abrir y cerrar la conexion a mano. Es la via rapida para iterar sobre
una query (cartera / posted cartera, etc.): cambias el `--sql`, vuelves a lanzar, y lees
el resultado. Para muchas queries seguidas sobre la misma conexion, usa directamente
`mssql_connect` una vez + `mssql_query` N veces (este pipeline abre y cierra por llamada).

## Gotchas

- **Conectividad WSL2 → Windows**: `--host` debe ser la IP LAN del Windows que corre SQL Server, NO `localhost` (desde WSL2 localhost no alcanza al host Windows). Ver memoria `wsl2-localhost-forwarding`.
- **Credenciales**: la contrasena se lee de la env var indicada por `--password-env` (default `MSSQL_PASSWORD`). Patron: `MSSQL_PASSWORD=$(pass navision/password) ./fn run run_mssql_query ...`. `--password` literal existe pero esta DESACONSEJADO (queda visible en la lista de procesos). Nunca hardcodees credenciales.
- **Placeholders**: usa `%s` / `%(nombre)s` (pymssql), NO `?`. Pasa los valores por `--param` (posicional, repetible y en orden), jamas concatenados en el `--sql` (inyeccion).
- **Abre y cierra por llamada**: cada invocacion abre una conexion nueva y la cierra al terminar (incluso si la query falla). No es eficiente para rafagas de muchas queries — para eso compon `mssql_connect` + `mssql_query` tu mismo.
- **Read-only**: no hace commit. Pensado para SELECT. No lo uses para INSERT/UPDATE/DELETE.
- **Requiere pymssql** instalado en el venv (lo importa `mssql_connect`).
- **CSV**: `--format csv` serializa con el modulo `csv` estandar; valores no-string se convierten con `str` en JSON (`default=str`) para fechas/decimales de SQL Server.
