diff --git a/go.mod b/go.mod index f1df8163d2..13371016fb 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/cockroachdb/apd/v2 v2.0.3-0.20200518165714-d020e156310a github.com/cockroachdb/errors v1.7.5 - github.com/dolthub/dolt/go v0.40.5-0.20250409001136-b28b4ee3e89d + github.com/dolthub/dolt/go v0.40.5-0.20250411012017-c74433ee514f github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20241119094239-f4e529af734d github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad - github.com/dolthub/go-mysql-server v0.19.1-0.20250408185841-05bbcc11cce4 + github.com/dolthub/go-mysql-server v0.19.1-0.20250411163327-ecbe045cc8f6 github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216 - github.com/dolthub/vitess v0.0.0-20250325024605-8131be3ca6d3 + github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 github.com/fatih/color v1.13.0 github.com/goccy/go-json v0.10.2 github.com/gogo/protobuf v1.3.2 diff --git a/go.sum b/go.sum index 737e784d1d..43476366f6 100644 --- a/go.sum +++ b/go.sum @@ -256,8 +256,8 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12 h1:IdqX7J8vi/Kn3T3Ee0VzqnLqwFmgA2hr8WZETPcQjfM= github.com/dolthub/aws-sdk-go-ini-parser v0.0.0-20250305001723-2821c37f6c12/go.mod h1:rN7X8BHwkjPcfMQQ2QTAq/xM3leUSGLfb+1Js7Y6TVo= -github.com/dolthub/dolt/go v0.40.5-0.20250409001136-b28b4ee3e89d h1:XqL6dJWMcMzUgevAR9nhMPMP8S2HwYG0y3JOp25SyhE= -github.com/dolthub/dolt/go v0.40.5-0.20250409001136-b28b4ee3e89d/go.mod h1:XHQ3SDx1MPirddSeny2sQmjPxn+owtwRb9JB3fxXnRU= +github.com/dolthub/dolt/go v0.40.5-0.20250411012017-c74433ee514f h1:I93e+mGr0HZiw6R0SmyatQqOCm+VToruBAJ2WfBLINU= +github.com/dolthub/dolt/go v0.40.5-0.20250411012017-c74433ee514f/go.mod h1:IZ1RO0V5EaP1Ygl4KqFpIZJ0MnC7AzjH3ETtrmA0nVE= github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20241119094239-f4e529af734d h1:gO9+wrmNHXukPNCO1tpfCcXIdMlW/qppbUStfLvqz/U= github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi v0.0.0-20241119094239-f4e529af734d/go.mod h1:L5RDYZbC9BBWmoU2+TjTekeqqhFXX5EqH9ln00O0stY= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= @@ -266,8 +266,8 @@ github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U= github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0= github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.1-0.20250408185841-05bbcc11cce4 h1:HWeQmWcxSBLHQri2j7+2yqsXEDlN4U4tQjnGDhZav0I= -github.com/dolthub/go-mysql-server v0.19.1-0.20250408185841-05bbcc11cce4/go.mod h1:lt1xwT52nowkmcVWaFpLShNUrScQV8EQ1aNNGGMduRE= +github.com/dolthub/go-mysql-server v0.19.1-0.20250411163327-ecbe045cc8f6 h1:s2hUUHLIN1TujkFw/T4xCINV/HWEoq4C+PLHZgK9jdQ= +github.com/dolthub/go-mysql-server v0.19.1-0.20250411163327-ecbe045cc8f6/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI= github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q= github.com/dolthub/ishell v0.0.0-20240701202509-2b217167d718 h1:lT7hE5k+0nkBdj/1UOSFwjWpNxf+LCApbRHgnCA17XE= @@ -276,8 +276,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216 h1:JWkKRE4EHUcEVQCMRBej8DYxjYjRz/9MdF/NNQh0o70= github.com/dolthub/sqllogictest/go v0.0.0-20240618184124-ca47f9354216/go.mod h1:e/FIZVvT2IR53HBCAo41NjqgtEnjMJGKca3Y/dAmZaA= -github.com/dolthub/vitess v0.0.0-20250325024605-8131be3ca6d3 h1:euU+adNAYw46Zcp1HnoaSDWhqjfaL8s/1SPU+i16gYM= -github.com/dolthub/vitess v0.0.0-20250325024605-8131be3ca6d3/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -763,8 +763,6 @@ github.com/pborman/getopt v0.0.0-20180729010549-6fdd0a2c7117/go.mod h1:85jBQOZwp github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/pganalyze/pg_query_go/v6 v6.0.0 h1:in6RkR/apfqlAtvqgDxd4Y4o87a5Pr8fkKDB4DrDo2c= -github.com/pganalyze/pg_query_go/v6 v6.0.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= diff --git a/postgres/parser/parser/sql.y b/postgres/parser/parser/sql.y index 6fda60bc07..f02de7baac 100644 --- a/postgres/parser/parser/sql.y +++ b/postgres/parser/parser/sql.y @@ -1134,7 +1134,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList { %type role_option password_clause valid_until_clause %type subquery_op %type <*tree.UnresolvedName> func_name func_name_no_crdb_extra -%type opt_compression opt_collate +%type opt_compression %type <[]tree.CompositeTypeElem> type_composite_list opt_type_composite_list %type type_range_option @@ -1149,10 +1149,11 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList { %type alter_domain_cmd %type cursor_name database_name index_name opt_index_name column_name insert_column_item statistics_name window_name -%type table_alias_name constraint_name target_name collation_name opt_from_ref_table +%type table_alias_name constraint_name target_name opt_from_ref_table +%type <*tree.UnresolvedObjectName> collation_name %type db_object_name_component %type <*tree.UnresolvedObjectName> table_name standalone_index_name sequence_name type_name routine_name aggregate_name -%type <*tree.UnresolvedObjectName> view_name db_object_name simple_db_object_name complex_db_object_name +%type <*tree.UnresolvedObjectName> view_name db_object_name simple_db_object_name complex_db_object_name opt_collate %type <*tree.UnresolvedObjectName> db_object_name_no_keywords simple_db_object_name_no_keywords complex_db_object_name_no_keywords %type <[]*tree.UnresolvedObjectName> type_name_list %type schema_name opt_schema_name opt_schema opt_version tablespace_name partition_name @@ -1435,6 +1436,7 @@ func (u *sqlSymUnion) vacuumTableAndColsList() tree.VacuumTableAndColsList { %left '+' '-' %left '*' '/' FLOORDIV '%' %left '^' +%left OPERATOR // Unary Operators %left AT // sets precedence for AT TIME ZONE %left COLLATE @@ -2372,7 +2374,7 @@ alter_opt_column_options: $$.val = &tree.AlterTableAlterColumnType{ Column: tree.Name($1), ToType: $4.typeReference(), - Collation: $5, + Collation: $5.unresolvedObjectName().UnquotedString(), Using: $6.expr(), } } @@ -2685,7 +2687,7 @@ alter_attribute_action: Action: "add", ColName: tree.Name($3), TypeName: $4.typeReference(), - Collate: $5, + Collate: $5.unresolvedObjectName().UnquotedString(), DropBehavior: $6.dropBehavior(), } } @@ -2712,7 +2714,7 @@ alter_attribute_action: Action: "alter", ColName: tree.Name($3), TypeName: $5.typeReference(), - Collate: $6, + Collate: $6.unresolvedObjectName().UnquotedString(), DropBehavior: $7.dropBehavior(), } } @@ -2722,7 +2724,7 @@ alter_attribute_action: Action: "alter", ColName: tree.Name($3), TypeName: $7.typeReference(), - Collate: $8, + Collate: $8.unresolvedObjectName().UnquotedString(), DropBehavior: $9.dropBehavior(), } } @@ -3835,7 +3837,7 @@ comment_stmt: } | COMMENT ON COLLATION collation_name IS comment_text { - $$.val = &tree.Comment{Object: &tree.CommentOnCollation{Name: $4}, Comment: $6.strPtr()} + $$.val = &tree.Comment{Object: &tree.CommentOnCollation{Name: $4.unresolvedObjectName().UnquotedString()}, Comment: $6.strPtr()} } | COMMENT ON COLUMN column_path IS comment_text { @@ -4181,7 +4183,7 @@ create_domain_stmt: $$.val = &tree.CreateDomain{ TypeName: $3.unresolvedObjectName(), DataType: $5.typeReference(), - Collate: $6, + Collate: $6.unresolvedObjectName().UnquotedString(), Default: $7.expr(), Constraints: $8.domainConstraints(), } @@ -8032,17 +8034,17 @@ partition_index_params: partition_index_elem: name opt_collate opt_opclass { - $$.val = tree.IndexElem{Column: tree.Name($1), Collation: $2, OpClass: $3.opClass()} + $$.val = tree.IndexElem{Column: tree.Name($1), Collation: $2.unresolvedObjectName().UnquotedString(), OpClass: $3.opClass()} } | '(' a_expr ')' opt_collate opt_opclass { - $$.val = tree.IndexElem{Expr: $2.expr(), Collation: $4, OpClass: $5.opClass()} + $$.val = tree.IndexElem{Expr: $2.expr(), Collation: $4.unresolvedObjectName().UnquotedString(), OpClass: $5.opClass()} } alter_column_def: column_name typename opt_collate col_constraint_list { - tableDef, err := tree.NewColumnTableDef(tree.Name($1), $2.typeReference(), "", $3, $4.colQuals()) + tableDef, err := tree.NewColumnTableDef(tree.Name($1), $2.typeReference(), "", $3.unresolvedObjectName().UnquotedString(), $4.colQuals()) if err != nil { return setErr(sqllex, err) } @@ -8055,7 +8057,7 @@ alter_column_def: create_table_column_def: column_name typename opt_compression opt_collate col_constraint_list { - tableDef, err := tree.NewColumnTableDef(tree.Name($1), $2.typeReference(), $3, $4, $5.colQuals()) + tableDef, err := tree.NewColumnTableDef(tree.Name($1), $2.typeReference(), $3, $4.unresolvedObjectName().UnquotedString(), $5.colQuals()) if err != nil { return setErr(sqllex, err) } @@ -9212,11 +9214,11 @@ opt_type_composite_list: type_composite_list: name typename opt_collate { - $$.val = []tree.CompositeTypeElem{{AttrName: $1, Type: $2.typeReference(), Collate: $3}} + $$.val = []tree.CompositeTypeElem{{AttrName: $1, Type: $2.typeReference(), Collate: $3.unresolvedObjectName().UnquotedString()}} } | type_composite_list ',' name typename opt_collate { - $$.val = append($1.compositeTypeElems(), tree.CompositeTypeElem{AttrName: $3, Type: $4.typeReference(), Collate: $5}) + $$.val = append($1.compositeTypeElems(), tree.CompositeTypeElem{AttrName: $3, Type: $4.typeReference(), Collate: $5.unresolvedObjectName().UnquotedString()}) } type_range_optional_list: @@ -9237,7 +9239,7 @@ type_range_option: SUBTYPE_OPCLASS '=' name { $$.val = tree.RangeTypeOption{Option: tree.RangeTypeSubtypeOpClass, StrVal: $3} } | COLLATION '=' collation_name - { $$.val = tree.RangeTypeOption{Option: tree.RangeTypeCollation, StrVal: $3} } + { $$.val = tree.RangeTypeOption{Option: tree.RangeTypeCollation, StrVal: $3.unresolvedObjectName().UnquotedString()} } | CANONICAL '=' name { $$.val = tree.RangeTypeOption{Option: tree.RangeTypeCanonical, StrVal: $3} } | SUBTYPE_DIFF '=' name @@ -9424,11 +9426,11 @@ index_params: index_elem: name opt_collate opt_opclass opt_asc_desc opt_nulls_order { - $$.val = tree.IndexElem{Column: tree.Name($1), Collation: $2, OpClass: $3.opClass(), Direction: $4.dir(), NullsOrder: $5.nullsOrder()} + $$.val = tree.IndexElem{Column: tree.Name($1), Collation: $2.unresolvedObjectName().UnquotedString(), OpClass: $3.opClass(), Direction: $4.dir(), NullsOrder: $5.nullsOrder()} } | '(' a_expr ')' opt_collate opt_opclass opt_asc_desc opt_nulls_order { - $$.val = tree.IndexElem{Expr: $2.expr(), Collation: $4, OpClass: $5.opClass(), Direction: $6.dir(), NullsOrder: $7.nullsOrder()} + $$.val = tree.IndexElem{Expr: $2.expr(), Collation: $4.unresolvedObjectName().UnquotedString(), OpClass: $5.opClass(), Direction: $6.dir(), NullsOrder: $7.nullsOrder()} } opt_opclass: @@ -9457,7 +9459,12 @@ opclass_option_list: opt_collate: COLLATE collation_name { $$ = $2 } -| /* EMPTY */ { $$ = "" } +| /* EMPTY */ + { + // TODO: this instantiates a zero-part object name, which then has the empty string for its String() output. It + // would probably be better to return a nil object in this case, but that requires deeper changes. + $$.val = tree.NewUnresolvedName().GetUnresolvedObjectName() + } opt_asc_desc: ASC @@ -12268,7 +12275,7 @@ a_expr: } | a_expr COLLATE collation_name { - $$.val = &tree.CollateExpr{Expr: $1.expr(), Locale: $3} + $$.val = &tree.CollateExpr{Expr: $1.expr(), Locale: $3.unresolvedObjectName().UnquotedString()} } | a_expr AT TIME ZONE a_expr %prec AT { @@ -12631,6 +12638,148 @@ a_expr: // The UNIQUE predicate is a standard SQL feature but not yet implemented // in PostgreSQL (as of 10.5). | UNIQUE '(' error { return unimplemented(sqllex, "UNIQUE predicate") } +// Below here we special-case the OPERATOR(...) syntax only for operators in the pg_catalog schema. +// This is to support particular psql commands that require it. +| a_expr OPERATOR '(' schema_name '.' '+' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Plus, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '-' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Minus, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '*' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Mult, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '/' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Div, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' FLOORDIV ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.FloorDiv, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '%' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Mod, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '^' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Pow, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '#' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Bitxor, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '&' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Bitand, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '|' ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Bitor, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '<' ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.LT, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '>' ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.GT, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '?' ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.JSONExists, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' JSON_SOME_EXISTS ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.JSONSomeExists, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' JSON_ALL_EXISTS ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.JSONAllExists, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' '=' ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.EQ, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' CONCAT ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.Concat, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' LSHIFT ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.LShift, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' RSHIFT ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.RShift, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' FETCHVAL ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.JSONFetchVal, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' FETCHTEXT ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.JSONFetchText, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' FETCHVAL_PATH ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.JSONFetchValPath, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' FETCHTEXT_PATH ')' a_expr + { + $$.val = &tree.BinaryExpr{Schema: tree.Name($4), Operator: tree.JSONFetchTextPath, Left: $1.expr(), Right: $8.expr()} + } +| a_expr OPERATOR '(' schema_name '.' REMOVE_PATH ')' a_expr + { + $$.val = &tree.FuncExpr{Func: tree.WrapFunctionSchema("json_remove_path", $4), Exprs: tree.Exprs{$1.expr(), $8.expr()}} + } + | a_expr OPERATOR '(' schema_name '.' INET_CONTAINED_BY_OR_EQUALS ')' a_expr + { + $$.val = &tree.FuncExpr{Func: tree.WrapFunctionSchema("inet_contained_by_or_equals", $4), Exprs: tree.Exprs{$1.expr(), $8.expr()}} + } + | a_expr OPERATOR '(' schema_name '.' AND_AND ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.Overlaps, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' INET_CONTAINS_OR_EQUALS ')' a_expr + { + $$.val = &tree.FuncExpr{Func: tree.WrapFunctionSchema("inet_contains_or_equals", $4), Exprs: tree.Exprs{$1.expr(), $8.expr()}} + } + | a_expr OPERATOR '(' schema_name '.' LESS_EQUALS ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.LE, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' GREATER_EQUALS ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.GE, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' NOT_EQUALS ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.NE, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' '~' ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.RegMatch, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' NOT_REGMATCH ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.NotRegMatch, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' REGIMATCH ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.RegIMatch, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' NOT_REGIMATCH ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.NotRegIMatch, Left: $1.expr(), Right: $8.expr()} + } + | a_expr OPERATOR '(' schema_name '.' TEXTSEARCHMATCH ')' a_expr + { + $$.val = &tree.ComparisonExpr{Schema: tree.Name($4), Operator: tree.TextSearchMatch, Left: $1.expr(), Right: $8.expr()} + } // Restricted expressions // @@ -14145,7 +14294,7 @@ interval_value: // Note: newlines between non-terminals matter to the doc generator. -collation_name: unrestricted_name +collation_name: db_object_name index_name: unrestricted_name @@ -14328,7 +14477,7 @@ complex_db_object_name: if err != nil { return setErr(sqllex, err) } $$.val = res } - + complex_db_object_name_no_keywords: simple_ident '.' simple_ident { diff --git a/postgres/parser/sem/tree/expr.go b/postgres/parser/sem/tree/expr.go index c24df34011..9b9712e297 100644 --- a/postgres/parser/sem/tree/expr.go +++ b/postgres/parser/sem/tree/expr.go @@ -484,6 +484,8 @@ type ComparisonExpr struct { Operator ComparisonOperator SubOperator ComparisonOperator // used for array operators (when Operator is Any, Some, or All) Left, Right Expr + // Schema is only set when using the OPERATOR() syntax + Schema Name typeAnnotation Fn *CmpOp @@ -1165,6 +1167,8 @@ func (i BinaryOperator) String() string { type BinaryExpr struct { Operator BinaryOperator Left, Right Expr + // Schema is only set for operators from a particular named schema using the OPERATOR() syntax + Schema Name typeAnnotation Fn *BinOp diff --git a/postgres/parser/sem/tree/function_name.go b/postgres/parser/sem/tree/function_name.go index 7316d093ce..c3e8be3bed 100644 --- a/postgres/parser/sem/tree/function_name.go +++ b/postgres/parser/sem/tree/function_name.go @@ -67,6 +67,13 @@ func WrapFunction(n string) ResolvableFunctionReference { return ResolvableFunctionReference{FunctionReference: un} } +// WrapFunctionSchema creates a new ResolvableFunctionReference +// holding a pre-resolved function. Helper for grammar rules. +func WrapFunctionSchema(funcName string, schemaName string) ResolvableFunctionReference { + un := &UnresolvedName{NumParts: 2, Parts: NameParts{schemaName, funcName}} + return ResolvableFunctionReference{FunctionReference: un} +} + // FunctionReference is the common interface to UnresolvedName and QualifiedFunctionName. type FunctionReference interface { fmt.Stringer diff --git a/postgres/parser/sem/tree/object_name.go b/postgres/parser/sem/tree/object_name.go index 4dd9b117df..f53e8801af 100644 --- a/postgres/parser/sem/tree/object_name.go +++ b/postgres/parser/sem/tree/object_name.go @@ -206,6 +206,11 @@ func (u *UnresolvedObjectName) Format(ctx *FmtCtx) { func (u *UnresolvedObjectName) String() string { return AsString(u) } +// UnquotedString returns the string representation of the object name without any quotes. +func (u *UnresolvedObjectName) UnquotedString() string { + return AsStringWithFlags(u, FmtBareIdentifiers) +} + // ToTableName converts the unresolved name to a table name. // // TODO(radu): the schema and catalog names might not be in the right places; we diff --git a/server/ast/expr.go b/server/ast/expr.go index 5dbd2bbc6a..d8bcf5fb57 100644 --- a/server/ast/expr.go +++ b/server/ast/expr.go @@ -24,6 +24,7 @@ import ( "github.com/dolthub/go-mysql-server/sql/expression" vitess "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/shopspring/decimal" + "github.com/sirupsen/logrus" "github.com/dolthub/doltgresql/core/id" "github.com/dolthub/doltgresql/postgres/parser/sem/tree" @@ -137,6 +138,11 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) { case *tree.ArrayFlatten: return nil, errors.Errorf("flattening arrays is not yet supported") case *tree.BinaryExpr: + // We will eventually support operators in other schemas, but for now we only can handle built-ins + if len(node.Schema) > 0 && node.Schema != "pg_catalog" { + return nil, errors.Errorf("schema %q not allowed in OPERATOR syntax", node.Schema) + } + left, err := nodeExpr(ctx, node.Left) if err != nil { return nil, err @@ -284,7 +290,8 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) { Exprs: exprs, }, nil case *tree.CollateExpr: - return nil, errors.Errorf("collations are not yet supported") + logrus.Warnf("collate is not yet supported, ignoring") + return nodeExpr(ctx, node.Expr) case *tree.ColumnAccessExpr: return nil, errors.Errorf("(E).x is not yet supported") case *tree.ColumnItem: @@ -302,6 +309,11 @@ func nodeExpr(ctx *Context, node tree.Expr) (vitess.Expr, error) { case *tree.CommentOnColumn: return nil, errors.Errorf("comment on column is not yet supported") case *tree.ComparisonExpr: + // We will eventually support operators in other schemas, but for now we only can handle built-ins + if len(node.Schema) > 0 && node.Schema != "pg_catalog" { + return nil, errors.Errorf("schema %q not allowed in OPERATOR syntax", node.Schema) + } + left, err := nodeExpr(ctx, node.Left) if err != nil { return nil, err diff --git a/server/connection_handler.go b/server/connection_handler.go index 8c6f777721..d837917804 100644 --- a/server/connection_handler.go +++ b/server/connection_handler.go @@ -423,6 +423,9 @@ func (h *ConnectionHandler) handleQuery(message *pgproto3.Query) (endOfMessages query, err := h.convertQuery(message.String) if err != nil { + if printErrorStackTraces { + fmt.Printf("Error parsing query: %+v\n", err) + } return true, err } @@ -474,6 +477,9 @@ func (h *ConnectionHandler) handleParse(message *pgproto3.Parse) error { // TODO: "Named prepared statements must be explicitly closed before they can be redefined by another Parse message, but this is not required for the unnamed statement" query, err := h.convertQuery(message.Query) if err != nil { + if printErrorStackTraces { + fmt.Printf("Error parsing query: %+v\n", err) + } return err } diff --git a/server/expression/gms_cast.go b/server/expression/gms_cast.go index 19e92c783c..373be83108 100644 --- a/server/expression/gms_cast.go +++ b/server/expression/gms_cast.go @@ -94,9 +94,18 @@ func (c *GMSCast) Eval(ctx *sql.Context, row sql.Row) (any, error) { } } fallthrough - // Although Int16 would be a closer fit for some of these types, in Postgres, Int32 is generally the smallest value - // used. To maximize overall compatibility, it's better to interpret these values as Int32 instead. - case query.Type_INT16, query.Type_INT24, query.Type_INT32, query.Type_YEAR, query.Type_ENUM: + // In Postgres, Int32 is generally the smallest value returned. But we convert int8 and int16 to this type during + // schema conversion, which means we must do so here as well to avoid runtime panics. + case query.Type_INT16: + newVal, _, err := types.Int16.Convert(ctx, val) + if err != nil { + return nil, err + } + if _, ok := newVal.(int16); !ok { + return nil, errors.Errorf("GMSCast expected type `int32`, got `%T`", val) + } + return newVal, nil + case query.Type_INT24, query.Type_INT32, query.Type_YEAR, query.Type_ENUM: newVal, _, err := types.Int32.Convert(ctx, val) if err != nil { return nil, err diff --git a/testing/go/create_table_test.go b/testing/go/create_table_test.go index 6cff5ead98..698e609e8c 100755 --- a/testing/go/create_table_test.go +++ b/testing/go/create_table_test.go @@ -337,6 +337,22 @@ func TestCreateTable(t *testing.T) { }, }, }, + { + Name: "create table with collation", + SetUpScript: []string{ + `CREATE TABLE collate_test1 ( + a int, + b text COLLATE "en-x-icu" NOT NULL + )`, + "insert into collate_test1 (a, b) values (1, 'foo');", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "select * from collate_test1;", + Expected: []sql.Row{{1, "foo"}}, + }, + }, + }, }) } @@ -365,7 +381,6 @@ func TestCreateTableInherit(t *testing.T) { {1, 2, 3, 4}, }, }, - { Query: "create table t111 () inherits (t1, t11);", Expected: []sql.Row{}, @@ -380,7 +395,6 @@ func TestCreateTableInherit(t *testing.T) { {1}, }, }, - { Query: "create table t1t1 (a int) inherits (t1);", Expected: []sql.Row{}, @@ -395,7 +409,6 @@ func TestCreateTableInherit(t *testing.T) { {1}, }, }, - { Query: "create table TT1t1 (A int) inherits (t1);", Expected: []sql.Row{}, diff --git a/testing/go/psql_test.go b/testing/go/psql_test.go new file mode 100755 index 0000000000..a2acceccdd --- /dev/null +++ b/testing/go/psql_test.go @@ -0,0 +1,121 @@ +// Copyright 2025 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package _go + +import ( + "testing" + + "github.com/dolthub/go-mysql-server/sql" +) + +func TestPsqlCommands(t *testing.T) { + RunScripts(t, []ScriptTest{ + { + // Many of the psql commands use the OPERATOR(pg_catalog.+) syntax, testing it here directly in a simpler context + Name: "operator keyword", + Assertions: []ScriptTestAssertion{ + { + Query: "select 1 OPERATOR(pg_catalog.+) 1", + Expected: []sql.Row{ + {2}, + }, + }, + { + Query: "select 1 OPERATOR(PG_CATALOG.+) 1", + Expected: []sql.Row{ + {2}, + }, + }, + { + Query: "select 1 OPERATOR(myschema.+) 1", + ExpectedErr: "schema \"myschema\" not allowed", + }, + { + Query: "select 1 OPERATOR(pg_catalog.<) 1", + Expected: []sql.Row{ + {"f"}, + }, + }, + { + Query: "select 1 OPERATOR(myschema.<) 1", + ExpectedErr: "schema \"myschema\" not allowed", + }, + { + Query: "select 1 OPERATOR(pg_catalog.<=) 1", + Expected: []sql.Row{ + {"t"}, + }, + }, + { + Query: "select 1 OPERATOR(pg_catalog.=) 1", + Expected: []sql.Row{ + {"t"}, + }, + }, + { + Query: "select 'hello' OPERATOR(pg_catalog.~) 'hello';", + Expected: []sql.Row{ + {"t"}, + }, + }, + }, + }, + { + Name: `\dt tablename`, + SetUpScript: []string{ + "CREATE TABLE test_table (id INT PRIMARY KEY, name TEXT);", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "SELECT n.nspname as \"Schema\", " + + " c.relname as \"Name\", " + + " CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\", " + + " pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\" " + + "FROM pg_catalog.pg_class c " + + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " + + " LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam " + + "WHERE c.relkind IN ('r','p','t','s','') " + + " AND c.relname OPERATOR(pg_catalog.~) '^(test_table)$' COLLATE pg_catalog.default " + + " AND pg_catalog.pg_table_is_visible(c.oid) " + + "ORDER BY 1,2;", + Expected: []sql.Row{{"public", "test_table", "table", "unknown OID()"}}, + }, + }, + }, + { + Name: `\d tablename`, + Skip: true, // needs ArrayFlatten + SetUpScript: []string{ + "CREATE TABLE test_table (id INT PRIMARY KEY, name TEXT);", + }, + Assertions: []ScriptTestAssertion{ + { + Query: `SELECT pol.polname, pol.polpermissive, + CASE WHEN pol.polroles = '{0}' THEN NULL ELSE pg_catalog.array_to_string(array(select rolname from pg_catalog.pg_roles where oid = any (pol.polroles) order by 1),',') END, + pg_catalog.pg_get_expr(pol.polqual, pol.polrelid), + pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid), + CASE pol.polcmd + WHEN 'r' THEN 'SELECT' + WHEN 'a' THEN 'INSERT' + WHEN 'w' THEN 'UPDATE' + WHEN 'd' THEN 'DELETE' + END AS cmd +FROM pg_catalog.pg_policy pol +WHERE pol.polrelid = '4131846889' ORDER BY 1;`, + }, + }, + }, + }) +}