From 0bc1684a8260be188047f5a774fc6a8ba0c5414f Mon Sep 17 00:00:00 2001 From: mibe Date: Tue, 3 Mar 2026 14:18:28 +0000 Subject: [PATCH] identifier quotes and variadic udfs --- .../exasol/skills/exasol-database/SKILL.md | 8 +-- .../exasol-database/references/exasol-sql.md | 2 +- plugins/exasol/skills/exasol-udfs/SKILL.md | 53 ++++++++++++++++++- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/plugins/exasol/skills/exasol-database/SKILL.md b/plugins/exasol/skills/exasol-database/SKILL.md index 946eec3..cc6569d 100644 --- a/plugins/exasol/skills/exasol-database/SKILL.md +++ b/plugins/exasol/skills/exasol-database/SKILL.md @@ -49,10 +49,10 @@ After the connection is established, determine the task type and load **only** t Multiple routes can apply — load all that match. 8. **Before writing any SQL** (applies to routes 2–7): - - Check all identifiers (column names, table names, aliases) against the **reserved keyword list in `references/exasol-sql.md`** (Common Traps section) - - Double-quote any identifier that appears in that list - - If a query fails with a syntax error that may be caused by a reserved keyword, fetch the live list: `exapump sql "SELECT KEYWORD FROM EXA_SQL_KEYWORDS WHERE RESERVED ORDER BY KEYWORD"` - - This is critical — Exasol reserves many common words (e.g., `YEAR`, `PROFILE`, `FILE`, `POSITION`) that are unreserved in other databases + - **Always double-quote every identifier** (column names, table names, schema names) in SELECT, FROM, WHERE, GROUP BY, ORDER BY, and JOIN clauses — without exception + - This preserves mixed-case names and prevents reserved-keyword errors in a single rule + - Do NOT quote SQL keywords, functions, or aliases — only object identifiers + - If a query fails with a syntax error, fetch the live reserved keyword list: `exapump sql "SELECT KEYWORD FROM EXA_SQL_KEYWORDS WHERE RESERVED ORDER BY KEYWORD"` ## Related Skills diff --git a/plugins/exasol/skills/exasol-database/references/exasol-sql.md b/plugins/exasol/skills/exasol-database/references/exasol-sql.md index 449df9f..35987c4 100644 --- a/plugins/exasol/skills/exasol-database/references/exasol-sql.md +++ b/plugins/exasol/skills/exasol-database/references/exasol-sql.md @@ -44,7 +44,7 @@ SELECT "myColumn" FROM "myTable"; -- works SELECT mycolumn FROM myTable; -- ERROR: "MYTABLE" not found ``` -**Rule of thumb:** Don't use quoted identifiers unless you have a specific reason. Let everything be uppercase. +**Rule of thumb:** Never use quoted identifiers in DDL statements. Always double-quote **every** identifier in SELECT (and all DML) statements — columns, tables, schemas — unconditionally, not just reserved words. --- diff --git a/plugins/exasol/skills/exasol-udfs/SKILL.md b/plugins/exasol/skills/exasol-udfs/SKILL.md index 5a051fa..935b137 100644 --- a/plugins/exasol/skills/exasol-udfs/SKILL.md +++ b/plugins/exasol/skills/exasol-udfs/SKILL.md @@ -5,7 +5,7 @@ description: "Exasol User Defined Functions (UDFs) and Script Language Container # Exasol UDFs & Script Language Containers -Trigger when the user mentions **UDF**, **user defined function**, **CREATE SCRIPT**, **ExaIterator**, **SCALAR**, **SET EMITS**, **BucketFS**, **script language container**, **SLC**, **exaslct**, **custom packages**, **GPU UDF**, **ctx.emit**, **ctx.next**, or any UDF/SLC-related topic. +Trigger when the user mentions **UDF**, **user defined function**, **CREATE SCRIPT**, **ExaIterator**, **SCALAR**, **SET EMITS**, **BucketFS**, **script language container**, **SLC**, **exaslct**, **custom packages**, **GPU UDF**, **ctx.emit**, **ctx.next**, **variadic script**, **dynamic parameters**, **EMITS(...)**, **default_output_columns**, or any UDF/SLC-related topic. ## When to Use UDFs @@ -135,6 +135,57 @@ run <- function(ctx) { / ``` +## Variadic Scripts (Dynamic Parameters) + +Use `...` to accept any number of input columns, output columns, or both. + +### Dynamic Input + +```sql +CREATE OR REPLACE PYTHON3 SCALAR SCRIPT schema.to_json(...) RETURNS VARCHAR(2000000) AS +import simplejson +def run(ctx): + obj = {} + for i in range(0, exa.meta.input_column_count, 2): + obj[ctx[i]] = ctx[i+1] # caller passes: name, value, name, value, ... + return simplejson.dumps(obj) +/ + +SELECT to_json('fruit', fruit, 'price', price) FROM products; +``` + +- Access by index: `ctx[i]` — **0-based in Python/Java, 1-based in Lua/R** +- Parameter names inside a variadic script are always `0`, `1`, `2`, ... — never the original column names +- `exa.meta.input_column_count` — total number of input columns +- `exa.meta.input_columns[i].name / .sql_type` — per-column metadata + +### Dynamic Output (`EMITS(...)`) + +Declare `EMITS(...)` in `CREATE SCRIPT`. At call time, columns must be provided one of two ways: + +| Method | Where specified | Use when | +|--------|----------------|----------| +| **EMITS in SELECT** | Caller's SQL query | Output structure depends on data values | +| **`default_output_columns()`** | Script body | Output structure derivable from input column count/types alone | + +```sql +-- EMITS in SELECT (required when output depends on data content) +SELECT split_csv(line) EMITS (a VARCHAR(100), b VARCHAR(100), c VARCHAR(100)) FROM t; +``` + +```python +# default_output_columns() — called before run(), no ctx/data access available +def default_output_columns(): + parts = [] + for i in range(exa.meta.input_column_count): + parts.append("c" + exa.meta.input_columns[i].name + " " + exa.meta.input_columns[i].sql_type) + return ",".join(parts) +``` + +If neither is provided, the query fails with: +> *The script has dynamic return arguments. Either specify the return arguments in the query via EMITS or implement the method default_output_columns in the UDF.* + + ## ExaIterator API Quick Reference ### Python