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