Tencent/WeKnora · error

compound queries (UNION/INTERSECT/EXCEPT) are not allowed

Error message

compound queries (UNION/INTERSECT/EXCEPT) are not allowed

What it means

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.

Source

Thrown at internal/utils/inject.go:1348

	// Check length limits
	if len(sql) < v.minLength {
		return fmt.Errorf("SQL query too short (min %d characters)", v.minLength)
	}
	if len(sql) > v.maxLength {
		return fmt.Errorf("SQL query too long (max %d characters)", v.maxLength)
	}

	return nil
}

// validateSelectStmt validates a SELECT statement with configured options
func (v *sqlValidator) validateSelectStmt(stmt *pg_query.SelectStmt, result *SQLValidationResult) error {
	tablesInQuery := make(map[string]string) // table name -> alias

	// Check for UNION/INTERSECT/EXCEPT (compound queries)
	if stmt.Op != pg_query.SetOperation_SETOP_NONE {
		return fmt.Errorf("compound queries (UNION/INTERSECT/EXCEPT) are not allowed")
	}

	// Check for WITH clause (CTEs)
	if v.checkCTEs && stmt.WithClause != nil {
		return fmt.Errorf("WITH clause (CTEs) is not allowed")
	}

	// Check for INTO clause (SELECT INTO)
	if stmt.IntoClause != nil {
		return fmt.Errorf("SELECT INTO is not allowed")
	}

	// Check for LOCKING clause (FOR UPDATE, etc.)
	if len(stmt.LockingClause) > 0 {
		return fmt.Errorf("locking clauses (FOR UPDATE, etc.) are not allowed")
	}

	// Validate FROM clause

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Rewrite the query as a single SELECT (move logic into WHERE/JOIN, or dedupe results in application code instead of UNION).
  2. Validate each SELECT branch separately against the library, then combine results yourself.
  3. If compound queries are legitimate in your environment, bypass this validator or extend it to allow SETOP_UNION after validating both sides.

Example fix

// before
q := "SELECT id FROM users UNION SELECT id FROM admins"

// after: validate each side separately
validate("SELECT id FROM users")
validate("SELECT id FROM admins")
ids := unionInAppCode(idsUsers, idsAdmins)
Defensive patterns

Strategy: validation

Validate before calling

upper := strings.ToUpper(sql)
for _, kw := range []string{" UNION ", " UNION ALL ", " INTERSECT ", " EXCEPT "} {
    if strings.Contains(upper, kw) {
        return fmt.Errorf("compound query with %s will be rejected; validate branches separately", strings.TrimSpace(kw))
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/126b9b3c995ec922. Report an issue: GitHub.