{"record":{"id":"cdb1f852befdb1d1","repo":"nocobase/nocobase","slug":"only-select-query-allowed","errorCode":null,"errorMessage":"Only select query allowed","messagePattern":"Only select query allowed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-charts/src/server/query.ts","lineNumber":35,"sourceCode":"    return options.data || [];\n  },\n  sql: async (\n    options,\n    {\n      db,\n      transaction,\n      skipError,\n      validateSQL,\n    }: { db: Database; transaction?: any; skipError?: boolean; validateSQL?: boolean },\n  ) => {\n    try {\n      // 分号截取，只取第一段\n      const sql: string = options.sql.trim().split(';').shift();\n      if (!sql) {\n        throw new Error('SQL is empty');\n      }\n      if (!/^select/i.test(sql) && !/^with([\\s\\S]+)select([\\s\\S]+)/i.test(sql)) {\n        throw new Error('Only select query allowed');\n      }\n      const [data] = await db.sequelize.query(sql, { transaction });\n      return data;\n    } catch (error) {\n      if (skipError) {\n        return [];\n      }\n      throw error;\n    }\n  },\n};\n\nexport default query;\n","sourceCodeStart":17,"sourceCodeEnd":49,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-charts/src/server/query.ts#L17-L49","documentation":"The charts query runner whitelists read-only statements: SQL must start with SELECT or be a WITH ... SELECT CTE. Anything else (UPDATE, DELETE, INSERT, DROP, etc.) is rejected to prevent the charting feature from mutating data.","triggerScenarios":"A chart query configured with a non-select statement, or SQL whose first statement is not SELECT/WITH (note the regex only inspects the first semicolon-delimited statement).","commonSituations":"Developers pasting maintenance SQL into charts; building charts on stored procedures or DML; trying to run EXPLAIN-style or multi-statement scripts through the chart UI.","solutions":["Rewrite the query to return data with SELECT (or WITH ... SELECT)","Move any write/maintenance logic out of chart queries into server code or migrations","If aggregates are needed, wrap the logic in a DB view and SELECT from it"],"exampleFix":"// before\nconst sql = \"UPDATE charts SET x = 1\";\n// after\nconst sql = \"SELECT id, x FROM charts\";","handlingStrategy":"validation","validationCode":"const first = sql.trim().split(';')[0];\nif (!/^select/i.test(first) && !/^with[\\s\\S]+select[\\s\\S]+/i.test(first)) {\n  throw new Error('Chart SQL must be a SELECT or WITH...SELECT statement');\n}","typeGuard":"function isReadOnlySql(sql: string): boolean {\n  const s = sql.trim().split(';')[0];\n  return /^select/i.test(s) || /^with[\\s\\S]+select[\\s\\S]+/i.test(s);\n}","tryCatchPattern":"try {\n  const data = await query(sql);\n} catch (e) {\n  if (e.message === 'Only select query allowed') {\n    message.error('Only SELECT queries are allowed in charts');\n    return [];\n  }\n  throw e;\n}","preventionTips":["Write chart queries as SELECT or WITH...SELECT only","Never put DML/DDL in chart measures","Wrap complex logic in a DB view and SELECT from it","Remember only the first semicolon-delimited statement is checked"],"tags":["sql","security","charts","read-only"],"backgroundTag":"non-select-sql-rejected","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}