Bläddra i källkod

Merge pull request #4 from database-pizza/fix/array-params

fix(parser): reject array parameters and ANY/ALL predicates with 0A000
Danilo Fragoso 22 timmar sedan
förälder
incheckning
bf70f8b22b
3 ändrade filer med 99 tillägg och 0 borttagningar
  1. 33 0
      pkg/parser/arrays_test.go
  2. 9 0
      pkg/parser/parser.go
  3. 57 0
      pkg/pgserver/array_param_test.go

+ 33 - 0
pkg/parser/arrays_test.go

@@ -0,0 +1,33 @@
+package parser
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/danfragoso/pizzasql-next/pkg/lexer"
+	"github.com/danfragoso/pizzasql-next/pkg/pgerror"
+)
+
+func TestArrayParametersAndAnyPredicatesRejected(t *testing.T) {
+	for _, sql := range []string{
+		"SELECT * FROM t WHERE id = ANY('{1,2,3}')",
+		"SELECT * FROM t WHERE id <> ANY('{1,2,3}')",
+		"SELECT * FROM t WHERE id = ALL('{1,2,3}')",
+		"SELECT * FROM t WHERE id = ANY(ARRAY[1,2])",
+	} {
+		t.Run(sql, func(t *testing.T) {
+			_, err := New(lexer.New(sql)).Parse()
+			if !errors.Is(err, pgerror.ErrFeatureNotSupported) {
+				t.Fatalf("error = %v, want ErrFeatureNotSupported", err)
+			}
+		})
+	}
+}
+
+func TestAnyAsOrdinaryColumnStillParses(t *testing.T) {
+	// ANY is only a predicate keyword when followed by "(". A column literally
+	// named any must keep working.
+	if _, err := New(lexer.New("SELECT any FROM t")).Parse(); err != nil {
+		t.Fatalf("parse: %v", err)
+	}
+}

+ 9 - 0
pkg/parser/parser.go

@@ -2522,6 +2522,12 @@ func (p *Parser) parseUnaryExpr() (Expr, error) {
 
 func (p *Parser) parsePrimaryExpr() (Expr, error) {
 	switch p.curToken.Type {
+	case lexer.TokenALL:
+		if p.peekTokenIs(lexer.TokenLParen) {
+			return nil, pgerror.Unsupported("array parameters and ANY/ALL predicates are not supported (PizzaSQL has no array type); expand the list into IN (...) with individual parameters")
+		}
+		return nil, p.curError("unexpected token in expression: " + p.curToken.Type.String())
+
 	case lexer.TokenNumber:
 		expr := &LiteralExpr{Type: lexer.TokenNumber, Value: p.curToken.Literal}
 		p.nextToken()
@@ -2625,6 +2631,9 @@ func (p *Parser) parseIdentOrFunction() (Expr, error) {
 
 	// Check for function call
 	if p.curTokenIs(lexer.TokenLParen) {
+		if strings.EqualFold(name, "ANY") {
+			return nil, pgerror.Unsupported("array parameters and ANY/ALL predicates are not supported (PizzaSQL has no array type); expand the list into IN (...) with individual parameters")
+		}
 		return p.parseFunctionCall(name)
 	}
 

+ 57 - 0
pkg/pgserver/array_param_test.go

@@ -0,0 +1,57 @@
+package pgserver
+
+import (
+	"bufio"
+	"testing"
+)
+
+// A pg client that binds a text[] parameter for "id = ANY($1)" must receive a
+// clear feature-not-supported error instead of a silently empty result.
+func TestArrayParameterRejectedOverExtendedQuery(t *testing.T) {
+	c, client := newDataConnection(t)
+	reader := bufio.NewReader(client)
+
+	parse := NewMessageBuilder()
+	parse.WriteString("s1")
+	parse.WriteString("SELECT * FROM t WHERE id = ANY($1)")
+	parse.WriteInt16(1)
+	parse.WriteInt32(1009) // text[]
+	if err := c.handleParse(&Message{Type: MsgParse, Data: parse.Bytes()}); err != nil {
+		t.Fatalf("handleParse: %v", err)
+	}
+
+	bind := NewMessageBuilder()
+	bind.WriteString("p1")
+	bind.WriteString("s1")
+	bind.WriteInt16(0) // all-text parameter formats
+	bind.WriteInt16(1) // one parameter
+	bind.WriteInt32(5)
+	bind.WriteBytes([]byte("{1,2}"))
+	bind.WriteInt16(0) // all-text result formats
+	if err := c.handleBind(&Message{Type: MsgBind, Data: bind.Bytes()}); err != nil {
+		t.Fatalf("handleBind: %v", err)
+	}
+	execute := NewMessageBuilder()
+	execute.WriteString("p1")
+	execute.WriteInt32(0)
+	done := make(chan error, 1)
+	// handleBind buffered BindComplete without flushing; handleExecute's error
+	// path flushes both messages, so read them in order.
+	go func() { done <- c.handleExecute(&Message{Type: MsgExecute, Data: execute.Bytes()}) }()
+	var errMsg *Message
+	for errMsg == nil {
+		msg, err := ReadMessage(reader)
+		if err != nil {
+			t.Fatalf("read response: %v", err)
+		}
+		if msg.Type == MsgErrorResponse {
+			errMsg = msg
+		}
+	}
+	if err := <-done; err != nil {
+		t.Fatalf("handleExecute: %v", err)
+	}
+	if code := errorCode(errMsg); code != ErrCodeFeatureNotSupported {
+		t.Fatalf("error code = %q, want %q", code, ErrCodeFeatureNotSupported)
+	}
+}