Bläddra i källkod

propagate WHERE predicate errors for every query shape

The multi-table and join paths dropped a row whenever its predicate failed to
evaluate (inlineJoin and the deferred WHERE loops used `val, _ :=`), and a
constant predicate was only evaluated for single-table SELECTs. A typo'd
regclass lookup in WHERE therefore still returned zero rows or an unfiltered
cross join instead of failing.

Every WHERE evaluation now reports the error: the comma-separated multi-table
filter loops, inlineJoin (row and predicate failures), the deferred join WHERE,
and the constant-predicate pre-evaluation, which still short-circuits only on
the single-table shape. Extends the undefined-relation regression to the join,
cross-join, and empty-table constant cases.
Danilo Fragoso 1 dag sedan
förälder
incheckning
344ba07f42
2 ändrade filer med 63 tillägg och 16 borttagningar
  1. 47 16
      pkg/executor/executor.go
  2. 16 0
      pkg/executor/pg_catalog_test.go

+ 47 - 16
pkg/executor/executor.go

@@ -638,13 +638,16 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 
 	// Optimize constant WHERE clauses
 	var constantWhereResult *bool
-	if stmt.Where != nil && !isMultiTable {
-		// Check if WHERE clause is a constant expression (doesn't reference any columns)
-		refs := collectColumnRefs(stmt.Where)
-		if len(refs) == 0 {
-			// Evaluate the constant expression
+	if stmt.Where != nil {
+		// A WHERE clause that references no column is constant: evaluate it once.
+		// A failure is a statement error, not a filter that drops every row;
+		// only the single-table shape short-circuits on the resulting value.
+		if refs := collectColumnRefs(stmt.Where); len(refs) == 0 {
 			val, err := e.evalExpr(stmt.Where, nil)
-			if err == nil {
+			if err != nil {
+				return nil, err
+			}
+			if !isMultiTable {
 				result := toBool(val)
 				constantWhereResult = &result
 			}
@@ -864,7 +867,12 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 		if effectiveWhere != nil {
 			var filtered []storage.Row
 			for _, row := range rows {
-				val, _ := e.evalExpr(effectiveWhere, row)
+				val, ferr := e.evalExpr(effectiveWhere, row)
+				if ferr != nil {
+					// A failing predicate is a statement error, not a
+					// non-matching row.
+					return nil, ferr
+				}
 				if toBool(val) {
 					filtered = append(filtered, row)
 				}
@@ -1012,7 +1020,7 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 		}
 
 		// Helper: inline cross-join two row-sets, applying a predicate.
-		inlineJoin := func(left, right []storage.Row, pred parser.Expr) []storage.Row {
+		inlineJoin := func(left, right []storage.Row, pred parser.Expr) ([]storage.Row, error) {
 			out := make([]storage.Row, 0, len(left))
 			for _, l := range left {
 				for _, r := range right {
@@ -1024,7 +1032,12 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 						m[k] = v
 					}
 					if pred != nil {
-						val, _ := e.evalExpr(pred, m)
+						val, perr := e.evalExpr(pred, m)
+						if perr != nil {
+							// A failing predicate is a statement error, not a
+							// non-matching row.
+							return nil, perr
+						}
 						if !toBool(val) {
 							continue
 						}
@@ -1032,7 +1045,7 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 					out = append(out, m)
 				}
 			}
-			return out
+			return out, nil
 		}
 
 		// Pre-join connected components of "cross-only" tables (0 single-table filters,
@@ -1181,7 +1194,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 				if len(appl) > 0 {
 					pred = combineAND(appl)
 				}
-				seedRows = inlineJoin(seedRows, nextRows, pred)
+				seedRows, err = inlineJoin(seedRows, nextRows, pred)
+				if err != nil {
+					return nil, err
+				}
 			}
 
 			virtuals = append(virtuals, virtualUnit{tableIdxs: vSeen, rows: seedRows})
@@ -1263,7 +1279,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 				pred := combineAND(tableFilters[first])
 				var filtered []storage.Row
 				for _, row := range rows {
-					val, _ := e.evalExpr(pred, row)
+					val, ferr := e.evalExpr(pred, row)
+					if ferr != nil {
+						return nil, ferr
+					}
 					if toBool(val) {
 						filtered = append(filtered, row)
 					}
@@ -1288,7 +1307,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 					pred := combineAND(tableFilters[idx])
 					var filtered []storage.Row
 					for _, row := range rightRows {
-						val, _ := e.evalExpr(pred, row)
+						val, ferr := e.evalExpr(pred, row)
+						if ferr != nil {
+							return nil, ferr
+						}
 						if toBool(val) {
 							filtered = append(filtered, row)
 						}
@@ -1302,7 +1324,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 				if len(appl) > 0 {
 					pred = combineAND(appl)
 				}
-				rows = inlineJoin(rows, rightRows, pred)
+				rows, err = inlineJoin(rows, rightRows, pred)
+				if err != nil {
+					return nil, err
+				}
 			}
 		} else {
 			// All tables were pre-joined; start with empty placeholder.
@@ -1320,7 +1345,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 			if len(appl) > 0 {
 				pred = combineAND(appl)
 			}
-			rows = inlineJoin(rows, vu.rows, pred)
+			rows, err = inlineJoin(rows, vu.rows, pred)
+			if err != nil {
+				return nil, err
+			}
 		}
 
 		// Apply any remaining conditions (shouldn't normally happen).
@@ -1328,7 +1356,10 @@ func (e *Executor) executeSelect(stmt *parser.SelectStmt) (*Result, error) {
 			pred := combineAND(remaining)
 			var filtered []storage.Row
 			for _, row := range rows {
-				val, _ := e.evalExpr(pred, row)
+				val, ferr := e.evalExpr(pred, row)
+				if ferr != nil {
+					return nil, ferr
+				}
 				if toBool(val) {
 					filtered = append(filtered, row)
 				}

+ 16 - 0
pkg/executor/pg_catalog_test.go

@@ -557,6 +557,22 @@ func TestPGCatalogUndefinedRelationError(t *testing.T) {
 		`SELECT 'missing_type'::regtype`,
 		`SELECT count(*) FROM pg_class WHERE oid = 'missing_relation'::regclass`,
 		`SELECT count(*) FROM pg_class c JOIN pg_attribute a ON a.attrelid = c.oid WHERE a.attrelid = 'missing_relation'::regclass`,
+		// A constant predicate that fails is a statement error even when the
+		// table has no rows to evaluate it against.
+		`SELECT count(*) FROM orders WHERE 'missing_relation'::regclass IS NOT NULL`,
+	} {
+		_, err := execSQL(e, sql)
+		if !errors.Is(err, pgerror.ErrUndefinedTable) {
+			t.Fatalf("%s: err = %v, want an undefined_table error", sql, err)
+		}
+	}
+
+	// A failing predicate in a join is a statement error too, not a filter that
+	// silently returns 0 rows.
+	execMust(t, e, "INSERT INTO pairs (a, b, v) VALUES ('x', 'y', 'z')")
+	for _, sql := range []string{
+		`SELECT * FROM pairs p1 JOIN pairs p2 ON true WHERE 'missing_relation'::regclass IS NOT NULL`,
+		`SELECT * FROM pairs p1, pairs p2 WHERE 'missing_relation'::regclass IS NOT NULL`,
 	} {
 		_, err := execSQL(e, sql)
 		if !errors.Is(err, pgerror.ErrUndefinedTable) {