|
|
|
@@ -9,6 +9,7 @@ import (
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/schema_pb"
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/query/engine"
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/query/sqltypes"
|
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/version"
|
|
|
|
@@ -202,7 +203,7 @@ func (s *PostgreSQLServer) handleSimpleQuery(session *PostgreSQLSession, query s
|
|
|
|
|
// Send results for this statement
|
|
|
|
|
if len(result.Columns) > 0 {
|
|
|
|
|
// Send row description
|
|
|
|
|
err = s.sendRowDescription(session, result.Columns, result.Rows)
|
|
|
|
|
err = s.sendRowDescription(session, result)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -324,8 +325,12 @@ func (s *PostgreSQLServer) sendSystemQueryResult(session *PostgreSQLSession, res
|
|
|
|
|
sqlRows = append(sqlRows, sqlRow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send row description
|
|
|
|
|
err := s.sendRowDescription(session, columns, sqlRows)
|
|
|
|
|
// Send row description (create a temporary QueryResult for consistency)
|
|
|
|
|
tempResult := &engine.QueryResult{
|
|
|
|
|
Columns: columns,
|
|
|
|
|
Rows: sqlRows,
|
|
|
|
|
}
|
|
|
|
|
err := s.sendRowDescription(session, tempResult)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -418,7 +423,11 @@ func (s *PostgreSQLServer) handleDescribe(session *PostgreSQLSession, msgBody []
|
|
|
|
|
glog.V(2).Infof("PostgreSQL Describe %c (ID: %d): %s", objectType, session.processID, objectName)
|
|
|
|
|
|
|
|
|
|
// For now, send empty row description
|
|
|
|
|
return s.sendRowDescription(session, []string{}, [][]sqltypes.Value{})
|
|
|
|
|
tempResult := &engine.QueryResult{
|
|
|
|
|
Columns: []string{},
|
|
|
|
|
Rows: [][]sqltypes.Value{},
|
|
|
|
|
}
|
|
|
|
|
return s.sendRowDescription(session, tempResult)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// handleClose processes a Close message
|
|
|
|
@@ -509,13 +518,13 @@ func (s *PostgreSQLServer) sendReadyForQuery(session *PostgreSQLSession) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sendRowDescription sends row description message
|
|
|
|
|
func (s *PostgreSQLServer) sendRowDescription(session *PostgreSQLSession, columns []string, rows [][]sqltypes.Value) error {
|
|
|
|
|
func (s *PostgreSQLServer) sendRowDescription(session *PostgreSQLSession, result *engine.QueryResult) error {
|
|
|
|
|
msg := make([]byte, 0)
|
|
|
|
|
msg = append(msg, PG_RESP_ROW_DESC)
|
|
|
|
|
|
|
|
|
|
// Calculate message length
|
|
|
|
|
length := 4 + 2 // length + field count
|
|
|
|
|
for _, col := range columns {
|
|
|
|
|
for _, col := range result.Columns {
|
|
|
|
|
length += len(col) + 1 + 4 + 2 + 4 + 2 + 4 + 2 // name + null + tableOID + attrNum + typeOID + typeSize + typeMod + format
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -525,11 +534,11 @@ func (s *PostgreSQLServer) sendRowDescription(session *PostgreSQLSession, column
|
|
|
|
|
|
|
|
|
|
// Field count
|
|
|
|
|
fieldCountBytes := make([]byte, 2)
|
|
|
|
|
binary.BigEndian.PutUint16(fieldCountBytes, uint16(len(columns)))
|
|
|
|
|
binary.BigEndian.PutUint16(fieldCountBytes, uint16(len(result.Columns)))
|
|
|
|
|
msg = append(msg, fieldCountBytes...)
|
|
|
|
|
|
|
|
|
|
// Field descriptions
|
|
|
|
|
for i, col := range columns {
|
|
|
|
|
for i, col := range result.Columns {
|
|
|
|
|
// Field name
|
|
|
|
|
msg = append(msg, []byte(col)...)
|
|
|
|
|
msg = append(msg, 0) // null terminator
|
|
|
|
@@ -544,8 +553,8 @@ func (s *PostgreSQLServer) sendRowDescription(session *PostgreSQLSession, column
|
|
|
|
|
binary.BigEndian.PutUint16(attrNum, uint16(i+1))
|
|
|
|
|
msg = append(msg, attrNum...)
|
|
|
|
|
|
|
|
|
|
// Type OID (determine from data)
|
|
|
|
|
typeOID := s.getPostgreSQLType(columns, rows, i)
|
|
|
|
|
// Type OID (determine from schema if available, fallback to data inference)
|
|
|
|
|
typeOID := s.getPostgreSQLTypeFromSchema(result, col, i)
|
|
|
|
|
typeOIDBytes := make([]byte, 4)
|
|
|
|
|
binary.BigEndian.PutUint32(typeOIDBytes, typeOID)
|
|
|
|
|
msg = append(msg, typeOIDBytes...)
|
|
|
|
@@ -722,8 +731,75 @@ func (s *PostgreSQLServer) getCommandTag(query string, rowCount int) string {
|
|
|
|
|
return "SELECT 0"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getPostgreSQLType determines PostgreSQL type OID from data
|
|
|
|
|
func (s *PostgreSQLServer) getPostgreSQLType(columns []string, rows [][]sqltypes.Value, colIndex int) uint32 {
|
|
|
|
|
// getPostgreSQLTypeFromSchema determines PostgreSQL type OID from schema information first, fallback to data
|
|
|
|
|
func (s *PostgreSQLServer) getPostgreSQLTypeFromSchema(result *engine.QueryResult, columnName string, colIndex int) uint32 {
|
|
|
|
|
// Try to get type from schema if database and table are available
|
|
|
|
|
if result.Database != "" && result.Table != "" {
|
|
|
|
|
if tableInfo, err := s.sqlEngine.GetCatalog().GetTableInfo(result.Database, result.Table); err == nil {
|
|
|
|
|
if tableInfo.Schema != nil && tableInfo.Schema.RecordType != nil {
|
|
|
|
|
// Look for the field in the schema
|
|
|
|
|
for _, field := range tableInfo.Schema.RecordType.Fields {
|
|
|
|
|
if field.Name == columnName {
|
|
|
|
|
return s.mapSchemaTypeToPostgreSQL(field.Type)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle system columns
|
|
|
|
|
switch columnName {
|
|
|
|
|
case "_timestamp_ns":
|
|
|
|
|
return PG_TYPE_INT8 // PostgreSQL BIGINT for nanosecond timestamps
|
|
|
|
|
case "_key":
|
|
|
|
|
return PG_TYPE_BYTEA // PostgreSQL BYTEA for binary keys
|
|
|
|
|
case "_source":
|
|
|
|
|
return PG_TYPE_TEXT // PostgreSQL TEXT for source information
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback to data-based inference if schema is not available
|
|
|
|
|
return s.getPostgreSQLTypeFromData(result.Columns, result.Rows, colIndex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mapSchemaTypeToPostgreSQL maps SeaweedFS schema types to PostgreSQL type OIDs
|
|
|
|
|
func (s *PostgreSQLServer) mapSchemaTypeToPostgreSQL(fieldType *schema_pb.Type) uint32 {
|
|
|
|
|
if fieldType == nil {
|
|
|
|
|
return PG_TYPE_TEXT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch kind := fieldType.Kind.(type) {
|
|
|
|
|
case *schema_pb.Type_ScalarType:
|
|
|
|
|
switch kind.ScalarType {
|
|
|
|
|
case schema_pb.ScalarType_BOOL:
|
|
|
|
|
return PG_TYPE_BOOL
|
|
|
|
|
case schema_pb.ScalarType_INT32:
|
|
|
|
|
return PG_TYPE_INT4
|
|
|
|
|
case schema_pb.ScalarType_INT64:
|
|
|
|
|
return PG_TYPE_INT8
|
|
|
|
|
case schema_pb.ScalarType_FLOAT:
|
|
|
|
|
return PG_TYPE_FLOAT4
|
|
|
|
|
case schema_pb.ScalarType_DOUBLE:
|
|
|
|
|
return PG_TYPE_FLOAT8
|
|
|
|
|
case schema_pb.ScalarType_BYTES:
|
|
|
|
|
return PG_TYPE_BYTEA
|
|
|
|
|
case schema_pb.ScalarType_STRING:
|
|
|
|
|
return PG_TYPE_TEXT
|
|
|
|
|
default:
|
|
|
|
|
return PG_TYPE_TEXT
|
|
|
|
|
}
|
|
|
|
|
case *schema_pb.Type_ListType:
|
|
|
|
|
// For list types, we'll represent them as JSON text
|
|
|
|
|
return PG_TYPE_JSONB
|
|
|
|
|
case *schema_pb.Type_RecordType:
|
|
|
|
|
// For nested record types, we'll represent them as JSON text
|
|
|
|
|
return PG_TYPE_JSONB
|
|
|
|
|
default:
|
|
|
|
|
return PG_TYPE_TEXT
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getPostgreSQLTypeFromData determines PostgreSQL type OID from data (legacy fallback method)
|
|
|
|
|
func (s *PostgreSQLServer) getPostgreSQLTypeFromData(columns []string, rows [][]sqltypes.Value, colIndex int) uint32 {
|
|
|
|
|
if len(rows) == 0 || colIndex >= len(rows[0]) {
|
|
|
|
|
return PG_TYPE_TEXT // Default to text
|
|
|
|
|
}
|
|
|
|
|