mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-30 00:04:25 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c1bae60c9 | ||
|
|
06b2404c0c | ||
|
|
32007480c6 | ||
|
|
ab1ce869b6 |
@@ -147,8 +147,11 @@ func (b *BunSelectQuery) Column(columns ...string) common.SelectQuery {
|
||||
}
|
||||
|
||||
func (b *BunSelectQuery) ColumnExpr(query string, args ...interface{}) common.SelectQuery {
|
||||
b.query = b.query.ColumnExpr(query, args)
|
||||
|
||||
if len(args) > 0 {
|
||||
b.query = b.query.ColumnExpr(query, args)
|
||||
} else {
|
||||
b.query = b.query.ColumnExpr(query)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,12 @@ func (g *GormSelectQuery) Column(columns ...string) common.SelectQuery {
|
||||
}
|
||||
|
||||
func (g *GormSelectQuery) ColumnExpr(query string, args ...interface{}) common.SelectQuery {
|
||||
g.db = g.db.Select(query, args...)
|
||||
if len(args) > 0 {
|
||||
g.db = g.db.Select(query, args...)
|
||||
} else {
|
||||
g.db = g.db.Select(query)
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,8 @@ func (h *Handler) SqlQueryList(sqlquery string, pNoCount, pBlankparms, pAllowFil
|
||||
return err
|
||||
}
|
||||
|
||||
dbobjlist = rows
|
||||
// Normalize PostgreSQL types for proper JSON marshaling
|
||||
dbobjlist = normalizePostgresTypesList(rows)
|
||||
|
||||
if pNoCount {
|
||||
total = int64(len(dbobjlist))
|
||||
@@ -532,7 +533,7 @@ func (h *Handler) SqlQuery(sqlquery string, pBlankparms bool) HTTPFuncType {
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
dbobj = rows[0]
|
||||
dbobj = normalizePostgresTypes(rows[0])
|
||||
}
|
||||
|
||||
// Execute AfterSQLExec hook
|
||||
@@ -946,3 +947,67 @@ func sendError(w http.ResponseWriter, status int, code, message string, err erro
|
||||
})
|
||||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
// normalizePostgresTypesList normalizes a list of result maps to handle PostgreSQL types correctly
|
||||
func normalizePostgresTypesList(rows []map[string]interface{}) []map[string]interface{} {
|
||||
if len(rows) == 0 {
|
||||
return rows
|
||||
}
|
||||
|
||||
normalized := make([]map[string]interface{}, len(rows))
|
||||
for i, row := range rows {
|
||||
normalized[i] = normalizePostgresTypes(row)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// normalizePostgresTypes normalizes a result map to handle PostgreSQL types correctly for JSON marshaling
|
||||
// This is necessary because when scanning into map[string]interface{}, PostgreSQL types like jsonb, bytea, etc.
|
||||
// are scanned as []byte which would be base64-encoded when marshaled to JSON.
|
||||
func normalizePostgresTypes(row map[string]interface{}) map[string]interface{} {
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
normalized := make(map[string]interface{}, len(row))
|
||||
for key, value := range row {
|
||||
normalized[key] = normalizePostgresValue(value)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// normalizePostgresValue normalizes a single value to the appropriate Go type for JSON marshaling
|
||||
func normalizePostgresValue(value interface{}) interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
// Check if it's valid JSON (jsonb type)
|
||||
// Try to unmarshal as JSON first
|
||||
var jsonObj interface{}
|
||||
if err := json.Unmarshal(v, &jsonObj); err == nil {
|
||||
// It's valid JSON, return as json.RawMessage so it's not double-encoded
|
||||
return json.RawMessage(v)
|
||||
}
|
||||
// Not valid JSON, could be bytea - keep as []byte for base64 encoding
|
||||
return v
|
||||
|
||||
case []interface{}:
|
||||
// Recursively normalize array elements
|
||||
normalized := make([]interface{}, len(v))
|
||||
for i, elem := range v {
|
||||
normalized[i] = normalizePostgresValue(elem)
|
||||
}
|
||||
return normalized
|
||||
|
||||
case map[string]interface{}:
|
||||
// Recursively normalize nested maps
|
||||
return normalizePostgresTypes(v)
|
||||
|
||||
default:
|
||||
// For other types (int, float, string, bool, etc.), return as-is
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,12 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
if len(options.ComputedQL) > 0 {
|
||||
for colName, colExpr := range options.ComputedQL {
|
||||
logger.Debug("Applying computed column: %s", colName)
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s) AS %s", colExpr, colName))
|
||||
if strings.Contains(colName, "cql") {
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s)::text AS %s", colExpr, colName))
|
||||
} else {
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s)AS %s", colExpr, colName))
|
||||
}
|
||||
|
||||
for colIndex := range options.Columns {
|
||||
if options.Columns[colIndex] == colName {
|
||||
// Remove the computed column from the selected columns to avoid duplication
|
||||
@@ -347,7 +352,12 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
||||
if len(options.ComputedColumns) > 0 {
|
||||
for _, cu := range options.ComputedColumns {
|
||||
logger.Debug("Applying computed column: %s", cu.Name)
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s) AS %s", cu.Expression, cu.Name))
|
||||
if strings.Contains(cu.Name, "cql") {
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s)::text AS %s", cu.Expression, cu.Name))
|
||||
} else {
|
||||
query = query.ColumnExpr(fmt.Sprintf("(%s) AS %s", cu.Expression, cu.Name))
|
||||
}
|
||||
|
||||
for colIndex := range options.Columns {
|
||||
if options.Columns[colIndex] == cu.Name {
|
||||
// Remove the computed column from the selected columns to avoid duplication
|
||||
@@ -1806,23 +1816,52 @@ func (h *Handler) generateMetadata(schema, entity string, model interface{}) *co
|
||||
if modelType.Kind() != reflect.Struct {
|
||||
logger.Error("Model type must be a struct, got %s for %s.%s", modelType.Kind(), schema, entity)
|
||||
return &common.TableMetadata{
|
||||
Schema: schema,
|
||||
Table: h.getTableName(schema, entity, model),
|
||||
Columns: []common.Column{},
|
||||
Schema: schema,
|
||||
Table: h.getTableName(schema, entity, model),
|
||||
Columns: []common.Column{},
|
||||
Relations: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
tableName := h.getTableName(schema, entity, model)
|
||||
|
||||
metadata := &common.TableMetadata{
|
||||
Schema: schema,
|
||||
Table: tableName,
|
||||
Columns: []common.Column{},
|
||||
Schema: schema,
|
||||
Table: tableName,
|
||||
Columns: []common.Column{},
|
||||
Relations: []string{},
|
||||
}
|
||||
|
||||
for i := 0; i < modelType.NumField(); i++ {
|
||||
field := modelType.Field(i)
|
||||
|
||||
// Skip unexported fields
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
gormTag := field.Tag.Get("gorm")
|
||||
jsonTag := field.Tag.Get("json")
|
||||
|
||||
// Skip fields with json:"-"
|
||||
if jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get JSON name
|
||||
jsonName := strings.Split(jsonTag, ",")[0]
|
||||
if jsonName == "" {
|
||||
jsonName = field.Name
|
||||
}
|
||||
|
||||
// Check if this is a relation field (slice or struct, but not time.Time)
|
||||
if field.Type.Kind() == reflect.Slice ||
|
||||
(field.Type.Kind() == reflect.Struct && field.Type.Name() != "Time") ||
|
||||
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct && field.Type.Elem().Name() != "Time") {
|
||||
metadata.Relations = append(metadata.Relations, jsonName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get column name from gorm tag or json tag
|
||||
columnName := field.Tag.Get("gorm")
|
||||
if strings.Contains(columnName, "column:") {
|
||||
@@ -1834,15 +1873,9 @@ func (h *Handler) generateMetadata(schema, entity string, model interface{}) *co
|
||||
}
|
||||
}
|
||||
} else {
|
||||
columnName = field.Tag.Get("json")
|
||||
if columnName == "" || columnName == "-" {
|
||||
columnName = strings.ToLower(field.Name)
|
||||
}
|
||||
columnName = jsonName
|
||||
}
|
||||
|
||||
// Check for primary key and unique constraint
|
||||
gormTag := field.Tag.Get("gorm")
|
||||
|
||||
column := common.Column{
|
||||
Name: columnName,
|
||||
Type: h.getColumnType(field.Type),
|
||||
|
||||
Reference in New Issue
Block a user