// Copyright 2024 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 server import ( "strconv" "strings" "github.com/cockroachdb/errors" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/expression" "github.com/dolthub/go-mysql-server/sql/plan" "github.com/dolthub/go-mysql-server/sql/transform" vitess "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/jackc/pgx/v5/pgproto3" "github.com/lib/pq/oid" "github.com/dolthub/doltgresql/core/dataloader" "github.com/dolthub/doltgresql/core/id" pgexprs "github.com/dolthub/doltgresql/server/expression" "github.com/dolthub/doltgresql/server/functions/framework" "github.com/dolthub/doltgresql/server/node" pgtypes "github.com/dolthub/doltgresql/server/types" ) // ErrorResponseSeverity represents the severity of an ErrorResponse message. type ErrorResponseSeverity string const ( ErrorResponseSeverity_Error ErrorResponseSeverity = "ERROR" ErrorResponseSeverity_Fatal ErrorResponseSeverity = "FATAL" ErrorResponseSeverity_Panic ErrorResponseSeverity = "PANIC" ErrorResponseSeverity_Warning ErrorResponseSeverity = "WARNING" ErrorResponseSeverity_Notice ErrorResponseSeverity = "NOTICE" ErrorResponseSeverity_Debug ErrorResponseSeverity = "DEBUG" ErrorResponseSeverity_Info ErrorResponseSeverity = "INFO" ErrorResponseSeverity_Log ErrorResponseSeverity = "LOG" ) // ReadyForQueryTransactionIndicator indicates the state of the transaction related to the query. type ReadyForQueryTransactionIndicator byte const ( ReadyForQueryTransactionIndicator_Idle ReadyForQueryTransactionIndicator = 'I' ReadyForQueryTransactionIndicator_TransactionBlock ReadyForQueryTransactionIndicator = 'T' ReadyForQueryTransactionIndicator_FailedTransactionBlock ReadyForQueryTransactionIndicator = 'E' ) // ConvertedQuery represents a query that has been converted from the Postgres representation to the Vitess // representation. String may contain the string version of the converted query. AST will contain the tree // version of the converted query, and is the recommended form to use. If AST is nil, then use the String version, // otherwise always prefer to AST. type ConvertedQuery struct { String string AST vitess.Statement StatementTag string } // copyFromStdinState tracks the metadata for an import of data into a table using a COPY FROM STDIN statement. When // this statement is processed, the server accepts COPY DATA messages from the client with chunks of data to load // into a table. type copyFromStdinState struct { // copyFromStdinNode stores the original CopyFrom statement that initiated the CopyData message sequence. This // node is used to look at what parameters were specified, such as which table to load data into, file format, // delimiters, etc. copyFromStdinNode *node.CopyFrom // insertNode stores the analyzed insert node that will be used to load the data into the target table. This node // only needs to be built once, and can be reused with updates to its underlying data loader. insertNode sql.Node // dataLoader is the implementation of DataLoader that is used to load each individual CopyData chunk into the // target table. dataLoader dataloader.DataLoader // copyErr stores any error that was returned while processing a CopyData message and loading a chunk of data // to the target table. The server needs to keep track of any errors that were encountered while processing chunks // so that it can avoid sending a CommandComplete message if an error was encountered after the client already // sent a CopyDone message to the server. copyErr error } type PortalData struct { Query ConvertedQuery IsEmptyQuery bool Fields []pgproto3.FieldDescription BoundPlan sql.Node FormatCodes []int16 } type PreparedStatementData struct { Query ConvertedQuery ReturnFields []pgproto3.FieldDescription BindVarTypes []uint32 } // extractBindVarTypes returns types based on the given query plan. // This function is used to get bind var types for running our prepared // tests only. A valid prepared query and execution messages must have // the types defined. func extractBindVarTypes(ctx *sql.Context, queryPlan sql.Node) ([]uint32, error) { inspectNode := queryPlan types := make(map[string]uint32) var err error var extractBindVars func(ctx *sql.Context, n sql.Node, expr sql.Expression) bool extractBindVars = func(ctx *sql.Context, n sql.Node, expr sql.Expression) bool { if err != nil { return false } switch e := expr.(type) { // Subquery doesn't walk its Node child via Expressions, so we must walk it separately here case *plan.Subquery: transform.InspectExpressionsWithNode(ctx, e.Query, extractBindVars) case *expression.BindVar: var typOid uint32 if doltgresType, ok := e.Type(ctx).(*pgtypes.DoltgresType); ok { typOid = id.Cache().ToOID(doltgresType.ID.AsId()) } else if _, ok := e.Type(ctx).(sql.DeferredType); ok { // for a deferred type, we can make a guess to its type based on the containing node switch n.(type) { case *plan.Limit: typOid = uint32(oid.T_int4) case *plan.Offset: typOid = uint32(oid.T_int4) default: typOid, err = VitessTypeToObjectID(e.Type(ctx)) if err != nil { err = errors.Errorf("could not determine OID for placeholder %s: %e", e.Name, err) return false } } } else { // TODO: should remove usage non doltgres type typOid, err = VitessTypeToObjectID(e.Type(ctx)) if err != nil { err = errors.Errorf("could not determine OID for placeholder %s: %e", e.Name, err) return false } } if existingOid, ok := types[e.Name]; ok { err = checkCompatibleTypes(ctx, existingOid, typOid, e.Name) } types[e.Name] = typOid case *pgexprs.ExplicitCast: if bindVar, ok := e.Child().(*expression.BindVar); ok { var typOid uint32 if doltgresType, ok := bindVar.Type(ctx).(*pgtypes.DoltgresType); ok { typOid = id.Cache().ToOID(doltgresType.ID.AsId()) } else { typOid, err = VitessTypeToObjectID(e.Type(ctx)) if err != nil { err = errors.Errorf("could not determine OID for placeholder %s: %e", bindVar.Name, err) return false } } if existingOid, ok := types[bindVar.Name]; ok { err = checkCompatibleTypes(ctx, existingOid, typOid, bindVar.Name) } types[bindVar.Name] = typOid return false } // $1::text and similar get converted to a Convert expression wrapping the bindvar case *expression.Convert: if bindVar, ok := e.Child.(*expression.BindVar); ok { var typOid uint32 typOid, err = VitessTypeToObjectID(e.Type(ctx)) if err != nil { err = errors.Errorf("could not determine OID for placeholder %s: %e", bindVar.Name, err) return false } if existingOid, ok := types[bindVar.Name]; ok { err = checkCompatibleTypes(ctx, existingOid, typOid, bindVar.Name) } types[bindVar.Name] = typOid return false } } return true } transform.InspectExpressionsWithNode(ctx, inspectNode, extractBindVars) // Insert nodes are special, as their source expressions are not returned by the Expressions() // interface and must be walked separately. switch queryPlan := queryPlan.(type) { case *plan.InsertInto: transform.InspectExpressionsWithNode(ctx, queryPlan.Source, extractBindVars) } // above finds types of bindvars in unordered form. // the list of types needs to be ordered as v1, v2, v3, etc. typesArr := make([]uint32, len(types)) for i, t := range types { idx, err := strconv.ParseInt(strings.TrimPrefix(i, "v"), 10, 32) if err != nil { return nil, errors.Errorf("could not determine the index of placeholder %s: %e", i, err) } if int(idx-1) >= len(types) { return nil, errors.Errorf("could not determine the index of placeholder %s in slice of %d elements", i, len(types)) } typesArr[idx-1] = t } return typesArr, err } // VitessTypeToObjectID returns a type, as defined by Vitess, into a type as defined by Postgres. // OIDs can be obtained with the following query: `SELECT oid, typname FROM pg_type ORDER BY 1;` func VitessTypeToObjectID(typ sql.Type) (uint32, error) { doltgresType, err := pgtypes.FromGmsTypeToDoltgresType(typ) if err != nil { return 0, err } return id.Cache().ToOID(doltgresType.ID.AsId()), nil } // checkCompatibleTypes checks if multiple types for which a parameter are used are compatible. func checkCompatibleTypes(ctx *sql.Context, existingOid, newOid uint32, newName string) error { var err error existing := pgtypes.GetTypeByID(id.Type(id.Cache().ToInternal(existingOid))) newType := pgtypes.GetTypeByID(id.Type(id.Cache().ToInternal(newOid))) if _, _, err = framework.FindCommonType(ctx, []*pgtypes.DoltgresType{existing, newType}); err != nil { err = errors.Errorf("parameter %s is used for incompatible types: %s and %s", newName, existing.String(), newType.String()) } return err }