Class: TalkToYourApp::Plugins::Db::Tools::Query
- Defined in:
- lib/talk_to_your_app/plugins/db/tools/query.rb
Overview
Runs a read-only SQL query on the declared :replica_readonly connection and renders the rows as JSON, plain text, or an HTML table. The query runs inside a transaction with a per-query statement timeout, so a slow query cannot poison the connection pool. Writes are rejected by the underlying read-only database role — the gem does not parse SQL.
Constant Summary collapse
- DEFAULT_TIMEOUT_MS =
30_000- CONNECTION =
:replica_readonly
Class Method Summary collapse
-
.timeout_statement(adapter_name, ms) ⇒ Object
The SQL that arms a per-query timeout for an adapter, or nil when the adapter has no per-statement timeout.
Instance Method Summary collapse
Methods inherited from Tool
argument, arguments, connection, default_arguments, description, dispatch, input_schema_hash, invoke, name, normalize_response, to_mcp_definition, to_mcp_tool
Class Method Details
.timeout_statement(adapter_name, ms) ⇒ Object
The SQL that arms a per-query timeout for an adapter, or nil when the adapter has no per-statement timeout. Pure (no connection) so it is unit-testable without each database installed.
Postgres -> transaction-local `statement_timeout` (unwound with the txn).
MySQL -> session `max_execution_time` (ms), bounding read-only SELECTs.
SQLite / MariaDB / others -> none (documented in the README).
63 64 65 66 67 68 |
# File 'lib/talk_to_your_app/plugins/db/tools/query.rb', line 63 def self.timeout_statement(adapter_name, ms) case adapter_name when /postgres/i then "SET LOCAL statement_timeout = #{ms.to_i}" when /mysql|trilogy/i then "SET SESSION max_execution_time = #{ms.to_i}" end end |
Instance Method Details
#call(args, ctx) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/talk_to_your_app/plugins/db/tools/query.rb', line 27 def call(args, ctx) format = args[:format] || "json" result = ctx.connection do |conn| conn.transaction do apply_statement_timeout(conn, timeout_ms) begin conn.exec_query(args[:sql]) ensure clear_statement_timeout(conn) end end end render(result, format) rescue ActiveRecord::QueryCanceled, ActiveRecord::StatementTimeout => e error("Query exceeded the #{timeout_ms}ms statement timeout: #{e.}") rescue ActiveRecord::ReadOnlyError => e # Rails' connected_to(role: :reading) blocks writes before they reach # the database; the read-only DB role is the backstop behind it. error("Write rejected: this connection is read-only. #{e.}") rescue ActiveRecord::StatementInvalid => e error("Query failed: #{e.}") end |