FlowiseAI/Flowise · error · Error

Invalid SQL statement: statement is required and must be a s

Error message

Invalid SQL statement: statement is required and must be a string

What it means

Thrown by assertReadOnlySqlStatement (packages/components/src/validator.ts:381) when the SQL argument is falsy or not a string. The guard is applied to LLM-generated SQL in the Sql Database Chain so only read-only SELECT/WITH can run; a non-string input fails the type check before any SQL parsing.

Source

Thrown at packages/components/src/validator.ts:381

 * single read-only SELECT/WITH statement.
 *
 * The Sql Database Chain hands LLM-generated SQL directly to TypeORM's raw query
 * executor with no statement-type filtering. Without this guard, a compromised or
 * malicious LLM response can run `ATTACH DATABASE`/`VACUUM INTO`/bare `PRAGMA` to write
 * arbitrary files anywhere the process can write, bypassing validateSQLitePath (which
 * only constrains the initial connection path, not queries run afterward).
 *
 * All legitimate queries issued against sqlite by this chain (including langchain's own
 * schema introspection, which uses `pragma_table_info()` as a table-valued function
 * inside a SELECT) are single SELECT/WITH statements, so this restriction does not
 * affect normal operation.
 *
 * @param {string} sql The SQL statement to validate
 * @throws {Error} If the statement is not a single read-only SELECT/WITH statement
 */
export const assertReadOnlySqlStatement = (sql: string): void => {
    if (!sql || typeof sql !== 'string') {
        throw new Error('Invalid SQL statement: statement is required and must be a string')
    }

    let trimmed = sql.trim()
    // Strip at most one trailing semicolon (+ trailing whitespace)
    trimmed = trimmed.replace(/;\s*$/, '')

    if (trimmed.includes(';')) {
        throw new Error('Invalid SQL statement: multiple statements are not allowed')
    }

    if (!/^(SELECT|WITH)\b/i.test(trimmed)) {
        throw new Error('Invalid SQL statement: only read-only SELECT/WITH statements are allowed')
    }

    if (/load_extension\s*\(/i.test(trimmed)) {
        throw new Error('Invalid SQL statement: load_extension is not allowed')
    }
}

View on GitHub (pinned to abe4a8601a)

Solutions

  1. Extract the SQL string from the LLM/tool response before calling the validator (e.g. response.text or response.sql).
  2. Default to a safe no-op query like 'SELECT 1' when the LLM returns nothing.
  3. Add a typeof sql === 'string' && sql.trim() guard upstream.

Example fix

// before
assertReadOnlySqlStatement(llmResponse)   // llmResponse is { sql: '...' }

// after
assertReadOnlySqlStatement(llmResponse?.sql ?? 'SELECT 1')
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof sql !== 'string' || sql.trim() === '') throw new Error('SQL statement missing');
assertReadOnlySqlStatement(sql);

Type guard

const isSqlString = (s: unknown): s is string => typeof s === 'string' && s.trim() !== '';

Try / catch

try { assertReadOnlySqlStatement(sql) } catch (e) { if (e instanceof Error && /statement is required/.test(e.message)) { sql = 'SELECT 1' } else throw e }

Prevention

When it happens

Trigger: assertReadOnlySqlStatement is called with undefined, null, '', a number, or an object — e.g. the LLM returned no SQL, a tool returned a parsed object instead of text, or a caller forgot to extract the .sql field.

Common situations: LLM hallucinated an empty/structured response; chattool forwarded a JSON object instead of the SQL string; refactored code changed the value passed to the validator.

Related errors


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/341ba2f4c0ae4a2a. Report an issue: GitHub.