Class: RailsAiBridge::Tools::Query::Guard

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/tools/query/guard.rb

Overview

Rejects any SQL that is not a single, non-mutating SELECT statement.

v1 deliberately rejects WITH (CTE) queries: mutating CTEs (wCTEs) allow INSERT/UPDATE/DELETE inside a WITH clause, and catching every dialect's wCTE grammar is error-prone. A plain SELECT-only allowlist is the safer starting point; CTE support can be added later behind stricter parsing.

Constant Summary collapse

REJECTION_RULES =

Ordered rejection rules: the first matching pattern rejects the statement. One trailing semicolon is tolerated; any inner semicolon is not (a semicolon inside a string literal is rejected too — v1 is conservative because parsing SQL literals is error-prone).

The statement must start with SELECT (leading whitespace only). Comments, EXPLAIN, SHOW, VALUES, WITH, and other verbs are rejected — a denylist of mutating keywords is not enough.

[
  [/\A\s*\z/, 'SQL must not be empty'],
  [/;\s*\S/, 'only a single statement is allowed (no semicolons)'],
  [/\A\s*with\b/i, 'WITH (CTE) queries are not allowed; rewrite as a plain SELECT'],
  [/\A(?!\s*select\b)/i, 'only SELECT statements are allowed'],
  [/\bfor\s+(update|share|no\s+key\s+update|key\s+share)\b/i, 'only non-locking, non-mutating SELECT statements are allowed'],
  [/\block\s+in\s+share\s+mode\b/i, 'only non-locking, non-mutating SELECT statements are allowed'],
  [/\binto\b/i, 'only non-locking, non-mutating SELECT statements are allowed']
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(sql) ⇒ Guard

Returns a new instance of Guard.

Parameters:

  • sql (Object) —

    raw client-provided SQL



32
33
34
# File 'lib/rails_ai_bridge/tools/query/guard.rb', line 32

def initialize(sql)
  @sql = sql.to_s
end

Instance Method Details

#error ⇒ String?

Validates the statement against the SELECT-only allowlist.

Returns:

  • (String, nil) —

    an error message when rejected, nil when allowed



39
40
41
42
# File 'lib/rails_ai_bridge/tools/query/guard.rb', line 39

def error
  rule = REJECTION_RULES.find { |pattern, _message| @sql.match?(pattern) }
  rule&.last
end