Skip to content

Commit 9959261

Browse files
remi-tdclaude
authored andcommitted
Fix/skill top param pattern (Teradata#330)
* fix(skill): replace TOP :n with ROW_NUMBER() pattern in customisation skill Teradata's parser rejects TOP followed by a bind-parameter placeholder (error 3707). The example_tool.yml and the "Mixing styles" section of parameter-substitution.md both used TOP :n, which is what caused the generated dba_running_sessions tool to fail on first use. Replaced with a derived-table + ROW_NUMBER() + WHERE rn <= :n pattern, which is safe for prepared statements. Added an explicit warning callout in the reference doc covering TOP :n, TOP {n} (fragile), and SAMPLE :n. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * corrected SQL examples --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 626f7ab commit 9959261

3 files changed

Lines changed: 2672 additions & 2627 deletions

File tree

agentic/skills/teradata-mcp-customisation/examples/example_tool.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
#
77
# Demonstrates:
88
# - identifier interpolation via {database_name}
9-
# - value bind via :n
9+
# - value bind via :n (in WHERE, not TOP — see parameter-substitution.md)
10+
# - ROW_NUMBER() pattern to limit rows safely (TOP :n is a Teradata parse error)
1011
# - parameter defaults (both args optional)
1112
# - description fields that surface to the LLM
1213
# ============================================================================
@@ -28,14 +29,15 @@ dbc_top_tables_by_size:
2829
type_hint: int
2930
default: 10
3031
sql: |
31-
SELECT TOP :n
32+
SELECT
3233
TableName AS table_name,
3334
SUM(CurrentPerm) AS current_perm_bytes,
3435
SUM(PeakPerm) AS peak_perm_bytes,
3536
CAST(SUM(CurrentPerm) / 1024.0 / 1024.0
36-
AS DECIMAL(18,2)) AS current_perm_mb
37+
AS DECIMAL(18,2)) AS current_perm_mb
3738
FROM DBC.AllSpaceV
38-
WHERE DataBaseName = '{database_name}'
39+
WHERE DataBaseName = :database_name
3940
AND TableName <> 'All'
4041
GROUP BY TableName
42+
QUALIFY ROW_NUMBER() OVER (ORDER BY SUM(CurrentPerm) DESC) <= :n
4143
ORDER BY current_perm_bytes DESC

agentic/skills/teradata-mcp-customisation/reference/parameter-substitution.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,33 @@ Allowed and common: identifiers via `{…}`, values via `:…`.
6868
top_n_by_size:
6969
type: tool
7070
sql: |
71-
SELECT TOP :n DataBaseName, SUM(CurrentPerm) AS perm
72-
FROM {database_name}.AllSpaceV
73-
WHERE TableName = 'All'
74-
GROUP BY DataBaseName
75-
ORDER BY perm DESC
71+
SELECT
72+
TableName AS table_name,
73+
SUM(CurrentPerm) AS current_perm_bytes,
74+
SUM(PeakPerm) AS peak_perm_bytes,
75+
CAST(SUM(CurrentPerm) / 1024.0 / 1024.0
76+
AS DECIMAL(18,2)) AS current_perm_mb
77+
FROM DBC.AllSpaceV
78+
WHERE DataBaseName = :database_name
79+
AND TableName <> 'All'
80+
GROUP BY TableName
81+
QUALIFY ROW_NUMBER() OVER (ORDER BY SUM(CurrentPerm) DESC) <= :n
82+
ORDER BY current_perm_bytes DESC
7683
parameters:
7784
database_name: { type_hint: str, default: "DBC" }
7885
n: { type_hint: int, default: 10 }
7986
```
8087

88+
> **Never use `TOP N` with a parameter — use `ROW_NUMBER()` instead.**
89+
> `TOP :n` fails because Teradata's parser requires a literal integer immediately after
90+
> the `TOP` keyword, before bind-parameter substitution occurs (error 3707:
91+
> *"expected something like an integer … between the 'TOP' keyword and '?'"*).
92+
> `TOP {n}` avoids the parse error (Python string-substitution produces a literal), but
93+
> it bypasses the prepared-statement layer entirely, which is fragile and injection-prone.
94+
> The correct pattern is a derived-table with `ROW_NUMBER() OVER (ORDER BY …)` and
95+
> `WHERE rn <= :n` in the outer query — `:n` is then a normal value bind that works safely.
96+
> The same applies to `SAMPLE :n`.
97+
8198
## `type_hint` values
8299

83100
Resolved against `{str, int, float, bool, list, dict, Any}`. Anything else (`"datetime"`, `"Decimal"`, …) silently falls back to `str`. If you need richer typing, validate inside your SQL (`CAST(:x AS DATE)`) instead.

0 commit comments

Comments
 (0)