Files
dolthub--doltgresql/server/connection_data.go
T
2026-07-13 12:32:25 +08:00

237 lines
9.3 KiB
Go

// 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
}