Files
Joshen Lim 501666e504 Explorer home chat to present a Run SQL secondary action if value is detected to be a SQL query (#50560)
## Context

We previously introduced a behaviour for the explorer home tab's chat
form to run a SQL Query if the input is detected to be a SQL query.

Adjusting it to shift that behaviour into a secondary action instead

<img width="740" height="210" alt="image"
src="https://github.com/user-attachments/assets/d4b1fec1-f38c-426f-8108-b50ead1a61ca"
/>



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* SQL statements entered in Explorer can be run directly with a
dedicated “Run SQL” action.
* Assistant forms support context-specific submit icons, labels,
tooltips, and accessibility text.

* **Bug Fixes**
  * Improved SQL detection for multi-statement queries.
* Prevented mixed SQL and conversational text from being treated as
executable SQL.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-18 14:35:35 +08:00

49 lines
2.6 KiB
TypeScript

import { removeCommentsFromSql } from '@/lib/helpers'
// [Joshen] `explain` is deliberately excluded even though it's a real SQL keyword - "Explain
// how RLS works" is a common natural-language prompt, and colliding with it would misroute
// a normal chat question into a query tab.
const UNAMBIGUOUS_SQL_STATEMENT_REGEX =
/^\s*(select|insert|update|delete|alter|drop|truncate|grant|revoke|begin|vacuum|analyze|merge|call|copy|lock|reindex|refresh|execute|prepare|deallocate|comment)\b/i
// `create`, `show`, `set`, and `with` are also common natural-language sentence openers
// ("Create a table for me", "Show me my indexes", "Set up RLS", "With my current schema...").
// Matching the bare keyword alone would misroute those into a query tab, so each requires
// syntax that only shows up in the real SQL statement.
const CREATE_STATEMENT_REGEX =
/^\s*create\s+(or\s+replace\s+)?(table|unique\s+index|index|view|materialized\s+view|function|procedure|trigger|schema|extension|role|user|policy|type|sequence|database|domain|rule|publication|foreign\s+table)\b/i
const SHOW_STATEMENT_REGEX = /^\s*show\s+(all|[a-zA-Z_][a-zA-Z0-9_.]*)\s*;?\s*$/i
const SET_STATEMENT_REGEX =
/^\s*set\s+(session\s+|local\s+)?(time\s+zone\b|[a-zA-Z_][a-zA-Z0-9_.]*\s*(=|to)\s*\S)/i
const WITH_STATEMENT_REGEX = /^\s*with\s+(recursive\s+)?[a-zA-Z_][a-zA-Z0-9_]*\s+as\s*\(/i
const isSqlLikeSegment = (segment: string): boolean =>
UNAMBIGUOUS_SQL_STATEMENT_REGEX.test(segment) ||
CREATE_STATEMENT_REGEX.test(segment) ||
SHOW_STATEMENT_REGEX.test(segment) ||
SET_STATEMENT_REGEX.test(segment) ||
WITH_STATEMENT_REGEX.test(segment)
/**
* Whether `message` looks like a SQL statement rather than a natural-language chat prompt,
* so the Explorer home tab can route it to a query tab instead of creating an AI chat.
*/
export function isSqlStatement(message: string): boolean {
const sql = removeCommentsFromSql(message).trim()
const isSimpleStatement =
UNAMBIGUOUS_SQL_STATEMENT_REGEX.test(sql) ||
SHOW_STATEMENT_REGEX.test(sql) ||
SET_STATEMENT_REGEX.test(sql)
const isComplexStatement = CREATE_STATEMENT_REGEX.test(sql) || WITH_STATEMENT_REGEX.test(sql)
if (isComplexStatement) return true
if (!isSimpleStatement) return false
// Simple statements (select/insert/update/.../show/set) are rarely legitimately
// multi-paragraph, so a trailing paragraph that doesn't itself look like SQL means
// this is a chat message that happens to include a query, not a query to run.
const [, ...trailingParagraphs] = sql.split(/\n\s*\n/)
return trailingParagraphs.every(isSqlLikeSegment)
}