mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-05-08 04:45:12 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987a2a7faf | ||
| 157788b73b |
@@ -1529,7 +1529,7 @@ func (b *BunUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuer
|
|||||||
// Skip primary key updates
|
// Skip primary key updates
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.query = b.query.Set(column+" = ?", value)
|
b.query = b.query.Set(column+" = ?", common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||||
@@ -261,3 +262,48 @@ func GetTableNameFromModel(model interface{}) string {
|
|||||||
// This handles cases like "MasterTaskItem" -> "mastertaskitem"
|
// This handles cases like "MasterTaskItem" -> "mastertaskitem"
|
||||||
return strings.ToLower(modelType.Name())
|
return strings.ToLower(modelType.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertSliceForBun converts []interface{} values to PostgreSQL array literal strings.
|
||||||
|
// BUN's fallback appender for []interface{} is JSON encoding, which produces "[]" —
|
||||||
|
// invalid PostgreSQL array syntax. PostgreSQL expects "{}" for empty arrays and
|
||||||
|
// "{elem1,elem2}" for non-empty ones. All other value types are returned unchanged.
|
||||||
|
func ConvertSliceForBun(value interface{}) interface{} {
|
||||||
|
arr, ok := value.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if len(arr) == 0 {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
parts := make([]string, len(arr))
|
||||||
|
for i, elem := range arr {
|
||||||
|
switch e := elem.(type) {
|
||||||
|
case string:
|
||||||
|
needsQuote := e == "" || strings.ContainsAny(e, `,"\\{}`+"\t\n\r ")
|
||||||
|
if needsQuote {
|
||||||
|
e = strings.ReplaceAll(e, `\`, `\\`)
|
||||||
|
e = strings.ReplaceAll(e, `"`, `""`)
|
||||||
|
parts[i] = `"` + e + `"`
|
||||||
|
} else {
|
||||||
|
parts[i] = e
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if e == float64(int64(e)) {
|
||||||
|
parts[i] = strconv.FormatInt(int64(e), 10)
|
||||||
|
} else {
|
||||||
|
parts[i] = strconv.FormatFloat(e, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
case bool:
|
||||||
|
if e {
|
||||||
|
parts[i] = "t"
|
||||||
|
} else {
|
||||||
|
parts[i] = "f"
|
||||||
|
}
|
||||||
|
case nil:
|
||||||
|
parts[i] = "NULL"
|
||||||
|
default:
|
||||||
|
parts[i] = fmt.Sprintf("%v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "{" + strings.Join(parts, ",") + "}"
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,3 +106,66 @@ func TestExtractTagValue(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvertSliceForBun(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty slice produces empty pg array",
|
||||||
|
input: []interface{}{},
|
||||||
|
expected: "{}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string elements",
|
||||||
|
input: []interface{}{"a", "b", "c"},
|
||||||
|
expected: "{a,b,c}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string element needing quotes",
|
||||||
|
input: []interface{}{"hello world", "ok"},
|
||||||
|
expected: `{"hello world",ok}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string with comma",
|
||||||
|
input: []interface{}{"a,b"},
|
||||||
|
expected: `{"a,b"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "integer elements (JSON float64)",
|
||||||
|
input: []interface{}{float64(1), float64(2), float64(3)},
|
||||||
|
expected: "{1,2,3}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool elements",
|
||||||
|
input: []interface{}{true, false},
|
||||||
|
expected: "{t,f}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil input passthrough",
|
||||||
|
input: nil,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "string input passthrough",
|
||||||
|
input: "hello",
|
||||||
|
expected: "hello",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int input passthrough",
|
||||||
|
input: 42,
|
||||||
|
expected: 42,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := ConvertSliceForBun(tt.input)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("ConvertSliceForBun(%v) = %v; want %v", tt.input, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ func (p *NestedCUDProcessor) processInsert(
|
|||||||
query := p.db.NewInsert().Table(tableName)
|
query := p.db.NewInsert().Table(tableName)
|
||||||
|
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
query = query.Value(key, value)
|
query = query.Value(key, ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
pkName := reflection.GetPrimaryKeyName(tableName)
|
pkName := reflection.GetPrimaryKeyName(tableName)
|
||||||
// Add RETURNING clause to get the inserted ID
|
// Add RETURNING clause to get the inserted ID
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
// Standard processing without nested relations
|
// Standard processing without nested relations
|
||||||
query := h.db.NewInsert().Table(tableName)
|
query := h.db.NewInsert().Table(tableName)
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
query = query.Value(key, value)
|
query = query.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
result, err := query.Exec(ctx)
|
result, err := query.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -669,7 +669,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
for _, item := range v {
|
for _, item := range v {
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range item {
|
for key, value := range item {
|
||||||
txQuery = txQuery.Value(key, value)
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -747,7 +747,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range itemMap {
|
for key, value := range itemMap {
|
||||||
txQuery = txQuery.Value(key, value)
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
}
|
}
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
1
todo.md
1
todo.md
@@ -92,6 +92,7 @@ See [`resolvespec-python/todo.md`](./resolvespec-python/todo.md) for detailed Py
|
|||||||
|
|
||||||
- [ ] Long preload alias names may exceed PostgreSQL identifier limit
|
- [ ] Long preload alias names may exceed PostgreSQL identifier limit
|
||||||
- [ ] Some edge cases in computed column handling
|
- [ ] Some edge cases in computed column handling
|
||||||
|
- [ ] `GormResult.LastInsertId()` (`pkg/common/adapters/database/gorm.go:936`) always returns `0, nil` — GORM does not expose last insert ID via `sql.Result` for most dialects. Auto-generated IDs from GORM inserts are not propagated back through `LastInsertId`, which breaks the ID-retrieval path in `recursive_crud.go`. Fix: read the ID back from the model struct after `Create()` using reflection, or use GORM's `Statement.LastInsertId`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user