Skip to content

run_select

run_select runs a read-only SQL query against application data, such as PS_JOB or your custom tables. It’s the only tool that reads data rather than definitions, so it’s the most tightly controlled: it can read only the tables on its allow-list, which is empty until an administrator adds tables, and every query runs with a row cap, a timeout, and masking of sensitive columns.

  • “How many training requests are still pending?”
  • “Do any ZZ_TRAIN_REQ rows point to a course that no longer exists in ZZ_COURSE_TBL?”
  • “Show me five rows from PS_ZZ_TRAIN_REQ so I can see what the data looks like.”
Argument Type Required Description
sql string Yes One SELECT statement, which can start with WITH. Up to 10,000 characters.
max_rows integer No The maximum number of rows to return, from 1 to 1,000. Default: 100.
Input schema
{
"type": "object",
"properties": {
"sql": {
"type": "string",
"minLength": 8,
"maxLength": 10000,
"description": "A single read-only SELECT statement"
},
"max_rows": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"default": 100
}
},
"required": ["sql"],
"additionalProperties": false
}
Field Description
columns The result’s columns, each with name and type
rows The rows, each an array of values in column order
rowCount The number of rows returned
truncated true when the query had more rows than max_rows
maskedColumns The columns whose values were masked
elapsedMs How long the query took to run, in milliseconds

For this query:

Example input
SELECT ZZ_TRAIN_STATUS, COUNT(*) AS REQUESTS
FROM PS_ZZ_TRAIN_REQ
GROUP BY ZZ_TRAIN_STATUS
ORDER BY ZZ_TRAIN_STATUS

the result is:

Example output
{
"columns": [
{ "name": "ZZ_TRAIN_STATUS", "type": "Character" },
{ "name": "REQUESTS", "type": "Number" }
],
"rows": [
["A", 212],
["C", 1048],
["D", 37],
["P", 64]
],
"rowCount": 4,
"truncated": false,
"maskedColumns": [],
"elapsedMs": 41
}

Before a query runs, the server parses it and checks four rules:

  1. One statement. The query is a single SELECT, which can start with WITH. A semicolon is allowed only at the very end.
  2. Read-only. Statements that change data or definitions, such as INSERT, UPDATE, DELETE, MERGE, CREATE, ALTER, DROP, TRUNCATE, and GRANT, are rejected, as are SELECT … FOR UPDATE, SELECT … INTO, locking hints such as SQL Server’s WITH (UPDLOCK), and procedure calls.
  3. Allowed tables only. Every table the query reads, including tables in subqueries and WITH clauses, must be on the allow-list. Database links and references to other databases are rejected.
  4. Sensitive columns used safely. A sensitive column can be selected, including through SELECT *, and its values come back masked as ***. Using one anywhere else, such as in a WHERE clause, an expression, or under an alias, is rejected, because masking couldn’t be guaranteed there.

The query then runs under two limits. The server reads at most max_rows rows and closes the cursor, so it never has to rewrite your SQL to cap it, and the query is cancelled if it runs longer than the timeout, 30 seconds by default.

Administrators control run_select in the server’s configuration:

Server configuration (excerpt)
run_select:
enabled: true
allowed_tables:
- PS_ZZ_TRAIN_REQ
- PS_ZZ_COURSE_TBL
- PS_JOB
sensitive_columns:
- NATIONAL_ID
- BIRTHDATE
max_rows: 1000

PeopleTools tables can’t be added to the allow-list. The definition tools already cover PeopleTools metadata, and some PeopleTools tables hold sensitive data: PSOPRDEFN, for example, stores users’ password hashes.

  • A query that breaks one of the rules returns a tool execution error that says which rule, so the LLM can fix the query. For a table that isn’t allowed, the error lists the allowed tables: “PS_PERSONAL_DATA isn’t on the run_select allow-list. Allowed tables: PS_ZZ_TRAIN_REQ, PS_ZZ_COURSE_TBL, PS_JOB.”
  • A query that runs past the timeout is cancelled, and the error reports how long it ran.
  • Long text values are cut to 2,000 characters.
  • When run_select is turned off, or its allow-list is empty, every call returns an error saying so, and the other ten tools keep working.
  • describe_record shows a record’s keys, which the LLM needs to write correct queries, especially effective-dated ones.
  • get_translate_values decodes the codes in query results, such as status values.