{"record":{"id":"126b9b3c995ec922","repo":"Tencent/WeKnora","slug":"compound-queries-union-intersect-except-are-not","errorCode":null,"errorMessage":"compound queries (UNION/INTERSECT/EXCEPT) are not allowed","messagePattern":"compound queries \\(UNION/INTERSECT/EXCEPT\\) are not allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/inject.go","lineNumber":1348,"sourceCode":"\n\t// Check length limits\n\tif len(sql) < v.minLength {\n\t\treturn fmt.Errorf(\"SQL query too short (min %d characters)\", v.minLength)\n\t}\n\tif len(sql) > v.maxLength {\n\t\treturn fmt.Errorf(\"SQL query too long (max %d characters)\", v.maxLength)\n\t}\n\n\treturn nil\n}\n\n// validateSelectStmt validates a SELECT statement with configured options\nfunc (v *sqlValidator) validateSelectStmt(stmt *pg_query.SelectStmt, result *SQLValidationResult) error {\n\ttablesInQuery := make(map[string]string) // table name -> alias\n\n\t// Check for UNION/INTERSECT/EXCEPT (compound queries)\n\tif stmt.Op != pg_query.SetOperation_SETOP_NONE {\n\t\treturn fmt.Errorf(\"compound queries (UNION/INTERSECT/EXCEPT) are not allowed\")\n\t}\n\n\t// Check for WITH clause (CTEs)\n\tif v.checkCTEs && stmt.WithClause != nil {\n\t\treturn fmt.Errorf(\"WITH clause (CTEs) is not allowed\")\n\t}\n\n\t// Check for INTO clause (SELECT INTO)\n\tif stmt.IntoClause != nil {\n\t\treturn fmt.Errorf(\"SELECT INTO is not allowed\")\n\t}\n\n\t// Check for LOCKING clause (FOR UPDATE, etc.)\n\tif len(stmt.LockingClause) > 0 {\n\t\treturn fmt.Errorf(\"locking clauses (FOR UPDATE, etc.) are not allowed\")\n\t}\n\n\t// Validate FROM clause","sourceCodeStart":1330,"sourceCodeEnd":1366,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/inject.go#L1330-L1366","documentation":"validateSelectStmt rejects SELECT statements whose parse tree has a set operation (stmt.Op != SETOP_NONE), i.e. UNION, INTERSECT, or EXCEPT compound queries. The library restricts validation to simple single SELECTs because compound queries complicate table/alias tracking and can hide injection surface in the second branch. Any query combining two SELECTs via a set operator is rejected outright.","triggerScenarios":"Submitting a query like \"SELECT a FROM t1 UNION SELECT b FROM t2\" (or INTERSECT/EXCEPT) to the SQL validation API; validateSelectStmt sees stmt.Op != pg_query.SetOperation_SETOP_NONE and returns this error.","commonSituations":"Developer builds a legitimate paginated UNION query or dedup with UNION, not realizing the validator only accepts single SELECTs; query builders that emit EXCEPT for pagination.","solutions":["Rewrite the query as a single SELECT (move logic into WHERE/JOIN, or dedupe results in application code instead of UNION).","Validate each SELECT branch separately against the library, then combine results yourself.","If compound queries are legitimate in your environment, bypass this validator or extend it to allow SETOP_UNION after validating both sides."],"exampleFix":"// before\nq := \"SELECT id FROM users UNION SELECT id FROM admins\"\n\n// after: validate each side separately\nvalidate(\"SELECT id FROM users\")\nvalidate(\"SELECT id FROM admins\")\nids := unionInAppCode(idsUsers, idsAdmins)","handlingStrategy":"validation","validationCode":"upper := strings.ToUpper(sql)\nfor _, kw := range []string{\" UNION \", \" UNION ALL \", \" INTERSECT \", \" EXCEPT \"} {\n    if strings.Contains(upper, kw) {\n        return fmt.Errorf(\"compound query with %s will be rejected; validate branches separately\", strings.TrimSpace(kw))\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat the validator as single-SELECT only; document this for your team.","Deduplicate or merge results in application code instead of UNION.","Lint queries for set-operator keywords before submission.","Validate each branch of a compound query independently."],"tags":["sql","validation","postgres","set-operations"],"backgroundTag":"unsupported-sql-construct","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}