You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP实现:根据光标位置提取多行SQL中光标所在的单条查询语句

如何在Web SQL编辑器中执行光标所在的SQL查询

我之前也踩过类似的坑——直接按分号分割SQL肯定行不通,毕竟字符串、注释甚至存储过程里到处都可能出现分号,这些都不能算作语句结束的标志。你已经拿到了光标前后的SQL片段,接下来的核心就是精准识别前半部分中真正属于SQL语句结束的分号,而非那些“伪分号”。

核心思路:用状态机过滤无效分号

我们可以通过遍历字符,跟踪当前所处的语境(正常SQL、单引号字符串、双引号字符串、单行注释、多行注释),只有在正常SQL语境下的分号才算作有效分隔符。

具体实现步骤

1. 编写函数识别前半部分的最后一个有效分号

这个函数会遍历前半段SQL,跳过所有字符串和注释里的分号,只记录真正的语句结束分号:

function findLastValidSemiColon(sql) {
  let inSingleQuote = false;
  let inDoubleQuote = false;
  let inMultiLineComment = false;
  let inSingleLineComment = false;
  let lastValidSemi = -1;

  for (let i = 0; i < sql.length; i++) {
    const char = sql[i];
    const nextChar = sql[i+1] || '';

    // 处理单行注释(-- 开头)
    if (!inSingleQuote && !inDoubleQuote && !inMultiLineComment) {
      if (char === '-' && nextChar === '-') {
        inSingleLineComment = true;
        i++; // 跳过第二个'-'
        continue;
      }
    }

    // 处理多行注释(/* ... */)
    if (!inSingleQuote && !inDoubleQuote && !inSingleLineComment) {
      if (char === '/' && nextChar === '*') {
        inMultiLineComment = true;
        i++; // 跳过'*'
        continue;
      } else if (char === '*' && nextChar === '/') {
        inMultiLineComment = false;
        i++; // 跳过'/'
        continue;
      }
    }

    // 单行注释结束(遇到换行)
    if (inSingleLineComment && char === '\n') {
      inSingleLineComment = false;
      continue;
    }

    // 处理单引号字符串(跳过转义的单引号)
    if (!inDoubleQuote && !inMultiLineComment && !inSingleLineComment) {
      if (char === "'" && (i === 0 || sql[i-1] !== '\\')) {
        inSingleQuote = !inSingleQuote;
        continue;
      }
    }

    // 处理双引号字符串(跳过转义的双引号)
    if (!inSingleQuote && !inMultiLineComment && !inSingleLineComment) {
      if (char === '"' && (i === 0 || sql[i-1] !== '\\')) {
        inDoubleQuote = !inDoubleQuote;
        continue;
      }
    }

    // 记录正常语境下的分号
    if (!inSingleQuote && !inDoubleQuote && !inMultiLineComment && !inSingleLineComment && char === ';') {
      lastValidSemi = i;
    }
  }

  return lastValidSemi;
}

2. 找到后半部分的第一个有效分号,确定查询结束位置

同样用状态机思路,找到光标后半段的第一个有效分号,作为当前查询的结束标记:

function findFirstValidSemiColon(sql) {
  let inSingleQuote = false;
  let inDoubleQuote = false;
  let inMultiLineComment = false;
  let inSingleLineComment = false;

  for (let i = 0; i < sql.length; i++) {
    const char = sql[i];
    const nextChar = sql[i+1] || '';

    // 复用上面的语境判断逻辑,找到第一个有效分号就返回
    if (!inSingleQuote && !inDoubleQuote && !inMultiLineComment) {
      if (char === '-' && nextChar === '-') {
        inSingleLineComment = true;
        i++;
        continue;
      }
    }

    if (!inSingleQuote && !inDoubleQuote && !inSingleLineComment) {
      if (char === '/' && nextChar === '*') {
        inMultiLineComment = true;
        i++;
        continue;
      } else if (char === '*' && nextChar === '/') {
        inMultiLineComment = false;
        i++;
        continue;
      }
    }

    if (inSingleLineComment && char === '\n') {
      inSingleLineComment = false;
      continue;
    }

    if (!inDoubleQuote && !inMultiLineComment && !inSingleLineComment) {
      if (char === "'" && (i === 0 || sql[i-1] !== '\\')) {
        inSingleQuote = !inSingleQuote;
        continue;
      }
    }

    if (!inSingleQuote && !inMultiLineComment && !inSingleLineComment) {
      if (char === '"' && (i === 0 || sql[i-1] !== '\\')) {
        inDoubleQuote = !inDoubleQuote;
        continue;
      }
    }

    if (!inSingleQuote && !inDoubleQuote && !inMultiLineComment && !inSingleLineComment && char === ';') {
      return i;
    }
  }

  return sql.length; // 如果没找到分号,就取到SQL末尾
}

3. 拼接出光标所在的目标查询

结合上面两个函数,就能提取出要执行的SQL:

// 假设你已获取:fullSql(完整SQL字符串)、cursorPosition(光标在全文的索引)
const firstPart = fullSql.slice(0, cursorPosition);
const lastValidSemi = findLastValidSemiColon(firstPart);
const queryStart = lastValidSemi === -1 ? 0 : lastValidSemi + 1;

const secondPart = fullSql.slice(cursorPosition);
const firstValidSemi = findFirstValidSemiColon(secondPart);
const queryEnd = cursorPosition + firstValidSemi + 1; // +1包含分号

const targetQuery = fullSql.slice(queryStart, queryEnd).trim();
// 现在targetQuery就是光标所在的那条SQL,可以直接执行了

额外注意事项

  • 转义规则适配:上面的代码只处理了\'/\"这种转义方式,如果你的SQL方言支持其他转义(比如PostgreSQL用''表示单引号),需要调整判断逻辑。
  • 存储场景处理:如果要支持存储过程/函数(内部包含分号),还需要识别DELIMITER指令,临时更改语句分隔符,这个场景需要额外的逻辑处理。
  • 无分号查询:如果光标所在的查询没有以分号结尾,要确保函数能正确取到SQL末尾。

内容的提问来源于stack exchange,提问作者alecsvaldez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:58:16