mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-03 20:11:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9235292586 | ||
|
|
23f10387c5 |
@@ -129,9 +129,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Rust
|
- name: Set up Rust
|
||||||
run: |
|
uses: dtolnay/rust-toolchain@stable
|
||||||
rustup toolchain install stable --profile minimal
|
|
||||||
rustup default stable
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test
|
run: cargo test
|
||||||
@@ -252,6 +250,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sed -i -E "s/^version: .*/version: ${VERSION}/" pubspec.yaml
|
sed -i -E "s/^version: .*/version: ${VERSION}/" pubspec.yaml
|
||||||
sed -i -E "s#^publish_to: .*#publish_to: ${SERVER_URL}/api/packages/${OWNER}/pub#" pubspec.yaml
|
sed -i -E "s#^publish_to: .*#publish_to: ${SERVER_URL}/api/packages/${OWNER}/pub#" pubspec.yaml
|
||||||
|
if ! grep -q "^## ${VERSION}\$" CHANGELOG.md; then
|
||||||
|
{ head -n 1 CHANGELOG.md; printf '\n## %s\n\n- Release %s.\n' "$VERSION" "$VERSION"; tail -n +2 CHANGELOG.md; } > CHANGELOG.tmp
|
||||||
|
mv CHANGELOG.tmp CHANGELOG.md
|
||||||
|
fi
|
||||||
|
# pub warns about a dirty git tree; commit the stamped files locally (never pushed)
|
||||||
|
git -c user.name=ci -c user.email=ci@localhost commit -q -am "ci: stamp dart version ${VERSION}"
|
||||||
|
|
||||||
- name: Dry run
|
- name: Dry run
|
||||||
if: ${{ env.PUBLISH != 'true' }}
|
if: ${{ env.PUBLISH != 'true' }}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: resolvespec
|
name: resolvespec
|
||||||
description: Client for ResolveSpec (JSON body) and FunctionSpec endpoints.
|
description: Client for ResolveSpec (JSON body) and FunctionSpec endpoints.
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
repository: https://git.warky.dev/wdevs/ResolveSpec
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1507,6 +1507,13 @@ func (b *BunInsertQuery) OnConflict(action string) common.InsertQuery {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BunInsertQuery) ExcludeColumn(columns ...string) common.InsertQuery {
|
||||||
|
if len(columns) > 0 {
|
||||||
|
b.query = b.query.ExcludeColumn(columns...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BunInsertQuery) Returning(columns ...string) common.InsertQuery {
|
func (b *BunInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||||
if len(columns) > 0 {
|
if len(columns) > 0 {
|
||||||
b.query = b.query.Returning(strings.Join(columns, ", "))
|
b.query = b.query.Returning(strings.Join(columns, ", "))
|
||||||
@@ -1619,6 +1626,13 @@ func (b *BunUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuer
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BunUpdateQuery) ExcludeColumn(columns ...string) common.UpdateQuery {
|
||||||
|
if len(columns) > 0 {
|
||||||
|
b.query = b.query.ExcludeColumn(columns...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BunUpdateQuery) Where(query string, args ...interface{}) common.UpdateQuery {
|
func (b *BunUpdateQuery) Where(query string, args ...interface{}) common.UpdateQuery {
|
||||||
b.query = b.query.Where(query, args...)
|
b.query = b.query.Where(query, args...)
|
||||||
return b
|
return b
|
||||||
|
|||||||
@@ -751,6 +751,13 @@ func (g *GormInsertQuery) OnConflict(action string) common.InsertQuery {
|
|||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GormInsertQuery) ExcludeColumn(columns ...string) common.InsertQuery {
|
||||||
|
if len(columns) > 0 {
|
||||||
|
g.db = g.db.Omit(columns...)
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GormInsertQuery) Returning(columns ...string) common.InsertQuery {
|
func (g *GormInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||||
g.returningColumns = columns
|
g.returningColumns = columns
|
||||||
return g
|
return g
|
||||||
@@ -930,6 +937,13 @@ func (g *GormUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQue
|
|||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *GormUpdateQuery) ExcludeColumn(columns ...string) common.UpdateQuery {
|
||||||
|
if len(columns) > 0 {
|
||||||
|
g.db = g.db.Omit(columns...)
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GormUpdateQuery) Where(query string, args ...interface{}) common.UpdateQuery {
|
func (g *GormUpdateQuery) Where(query string, args ...interface{}) common.UpdateQuery {
|
||||||
g.db = g.db.Where(query, args...)
|
g.db = g.db.Where(query, args...)
|
||||||
return g
|
return g
|
||||||
|
|||||||
@@ -691,6 +691,13 @@ func (p *PgSQLInsertQuery) OnConflict(action string) common.InsertQuery {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PgSQLInsertQuery) ExcludeColumn(columns ...string) common.InsertQuery {
|
||||||
|
for _, col := range columns {
|
||||||
|
delete(p.values, col)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PgSQLInsertQuery) Returning(columns ...string) common.InsertQuery {
|
func (p *PgSQLInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||||
p.returning = columns
|
p.returning = columns
|
||||||
return p
|
return p
|
||||||
@@ -850,6 +857,13 @@ func (p *PgSQLUpdateQuery) Set(column string, value interface{}) common.UpdateQu
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PgSQLUpdateQuery) ExcludeColumn(columns ...string) common.UpdateQuery {
|
||||||
|
for _, col := range columns {
|
||||||
|
delete(p.sets, col)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PgSQLUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
func (p *PgSQLUpdateQuery) SetMap(values map[string]interface{}) common.UpdateQuery {
|
||||||
pkName := ""
|
pkName := ""
|
||||||
if p.model != nil {
|
if p.model != nil {
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ type InsertQuery interface {
|
|||||||
Table(table string) InsertQuery
|
Table(table string) InsertQuery
|
||||||
Value(column string, value interface{}) InsertQuery
|
Value(column string, value interface{}) InsertQuery
|
||||||
OnConflict(action string) InsertQuery
|
OnConflict(action string) InsertQuery
|
||||||
|
// ExcludeColumn omits columns from a Model()-based INSERT (e.g. generated columns).
|
||||||
|
ExcludeColumn(columns ...string) InsertQuery
|
||||||
Returning(columns ...string) InsertQuery
|
Returning(columns ...string) InsertQuery
|
||||||
|
|
||||||
// Execution
|
// Execution
|
||||||
@@ -94,6 +96,8 @@ type UpdateQuery interface {
|
|||||||
Table(table string) UpdateQuery
|
Table(table string) UpdateQuery
|
||||||
Set(column string, value interface{}) UpdateQuery
|
Set(column string, value interface{}) UpdateQuery
|
||||||
SetMap(values map[string]interface{}) UpdateQuery
|
SetMap(values map[string]interface{}) UpdateQuery
|
||||||
|
// ExcludeColumn omits columns from a Model()-based UPDATE (e.g. generated columns).
|
||||||
|
ExcludeColumn(columns ...string) UpdateQuery
|
||||||
Where(query string, args ...interface{}) UpdateQuery
|
Where(query string, args ...interface{}) UpdateQuery
|
||||||
Returning(columns ...string) UpdateQuery
|
Returning(columns ...string) UpdateQuery
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (p *NestedCUDProcessor) ProcessNestedCUD(
|
|||||||
case "insert", "create", "add":
|
case "insert", "create", "add":
|
||||||
// Only perform insert if we have data to insert
|
// Only perform insert if we have data to insert
|
||||||
if hasData {
|
if hasData {
|
||||||
id, err := p.processInsert(ctx, regularData, tableName)
|
id, err := p.processInsert(ctx, regularData, model, tableName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Insert failed for table=%s, data=%+v, error=%v", tableName, regularData, err)
|
logger.Error("Insert failed for table=%s, data=%+v, error=%v", tableName, regularData, err)
|
||||||
return nil, fmt.Errorf("insert failed: %w", err)
|
return nil, fmt.Errorf("insert failed: %w", err)
|
||||||
@@ -148,7 +148,7 @@ func (p *NestedCUDProcessor) ProcessNestedCUD(
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
if hasData {
|
if hasData {
|
||||||
rows, err := p.processUpdate(ctx, regularData, tableName, data[pkName])
|
rows, err := p.processUpdate(ctx, regularData, model, tableName, data[pkName])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Update failed for table=%s, id=%v, data=%+v, error=%v", tableName, data[pkName], regularData, err)
|
logger.Error("Update failed for table=%s, id=%v, data=%+v, error=%v", tableName, data[pkName], regularData, err)
|
||||||
return nil, fmt.Errorf("update failed: %w", err)
|
return nil, fmt.Errorf("update failed: %w", err)
|
||||||
@@ -295,10 +295,12 @@ func (p *NestedCUDProcessor) injectForeignKeys(data map[string]interface{}, mode
|
|||||||
func (p *NestedCUDProcessor) processInsert(
|
func (p *NestedCUDProcessor) processInsert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
data map[string]interface{},
|
data map[string]interface{},
|
||||||
|
model interface{},
|
||||||
tableName string,
|
tableName string,
|
||||||
) (interface{}, error) {
|
) (interface{}, error) {
|
||||||
logger.Debug("Inserting into %s with data: %+v", tableName, data)
|
logger.Debug("Inserting into %s with data: %+v", tableName, data)
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, data)
|
||||||
query := p.db.NewInsert().Table(tableName)
|
query := p.db.NewInsert().Table(tableName)
|
||||||
|
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
@@ -335,6 +337,7 @@ func (p *NestedCUDProcessor) processSelect(ctx context.Context, tableName string
|
|||||||
func (p *NestedCUDProcessor) processUpdate(
|
func (p *NestedCUDProcessor) processUpdate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
data map[string]interface{},
|
data map[string]interface{},
|
||||||
|
model interface{},
|
||||||
tableName string,
|
tableName string,
|
||||||
id interface{},
|
id interface{},
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
@@ -345,6 +348,7 @@ func (p *NestedCUDProcessor) processUpdate(
|
|||||||
|
|
||||||
logger.Debug("Updating %s with ID %v, data: %+v", tableName, id, data)
|
logger.Debug("Updating %s with ID %v, data: %+v", tableName, id, data)
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, data)
|
||||||
query := p.db.NewUpdate().Table(tableName).SetMap(data).Where(fmt.Sprintf("%s = ?", QuoteIdent(reflection.GetPrimaryKeyName(tableName))), id)
|
query := p.db.NewUpdate().Table(tableName).SetMap(data).Where(fmt.Sprintf("%s = ?", QuoteIdent(reflection.GetPrimaryKeyName(tableName))), id)
|
||||||
|
|
||||||
result, err := query.Exec(ctx)
|
result, err := query.Exec(ctx)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ func (m *mockInsertQuery) Value(column string, value interface{}) InsertQuery {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
func (m *mockInsertQuery) OnConflict(action string) InsertQuery { return m }
|
func (m *mockInsertQuery) OnConflict(action string) InsertQuery { return m }
|
||||||
|
func (m *mockInsertQuery) ExcludeColumn(columns ...string) InsertQuery { return m }
|
||||||
func (m *mockInsertQuery) Returning(columns ...string) InsertQuery { return m }
|
func (m *mockInsertQuery) Returning(columns ...string) InsertQuery { return m }
|
||||||
func (m *mockInsertQuery) Exec(ctx context.Context) (Result, error) {
|
func (m *mockInsertQuery) Exec(ctx context.Context) (Result, error) {
|
||||||
m.db.insertCalls = append(m.db.insertCalls, m.values)
|
m.db.insertCalls = append(m.db.insertCalls, m.values)
|
||||||
@@ -131,6 +132,7 @@ func (m *mockUpdateQuery) SetMap(values map[string]interface{}) UpdateQuery {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
func (m *mockUpdateQuery) Where(condition string, args ...interface{}) UpdateQuery { return m }
|
func (m *mockUpdateQuery) Where(condition string, args ...interface{}) UpdateQuery { return m }
|
||||||
|
func (m *mockUpdateQuery) ExcludeColumn(columns ...string) UpdateQuery { return m }
|
||||||
func (m *mockUpdateQuery) Returning(columns ...string) UpdateQuery { return m }
|
func (m *mockUpdateQuery) Returning(columns ...string) UpdateQuery { return m }
|
||||||
func (m *mockUpdateQuery) Exec(ctx context.Context) (Result, error) {
|
func (m *mockUpdateQuery) Exec(ctx context.Context) (Result, error) {
|
||||||
// Record the update call
|
// Record the update call
|
||||||
|
|||||||
@@ -895,6 +895,9 @@ func (h *Handler) create(hookCtx *HookContext) (interface{}, error) {
|
|||||||
|
|
||||||
// Insert record
|
// Insert record
|
||||||
query := hookCtx.Tx.NewInsert().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
query := hookCtx.Tx.NewInsert().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
||||||
|
if generated := reflection.NonWritableColumns(hookCtx.Model); len(generated) > 0 {
|
||||||
|
query = query.ExcludeColumn(generated...)
|
||||||
|
}
|
||||||
if _, err := query.Exec(hookCtx.Context); err != nil {
|
if _, err := query.Exec(hookCtx.Context); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create record: %w", err)
|
return nil, fmt.Errorf("failed to create record: %w", err)
|
||||||
}
|
}
|
||||||
@@ -924,6 +927,8 @@ func (h *Handler) update(hookCtx *HookContext) error {
|
|||||||
// the stored value unless disallowNulls is set, in which case null is skipped.
|
// the stored value unless disallowNulls is set, in which case null is skipped.
|
||||||
values := common.MergeUpdateValues(make(map[string]interface{}, len(updates)), updates, h.disallowNulls)
|
values := common.MergeUpdateValues(make(map[string]interface{}, len(updates)), updates, h.disallowNulls)
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(hookCtx.Model, values)
|
||||||
|
|
||||||
if len(values) > 0 {
|
if len(values) > 0 {
|
||||||
query := hookCtx.Tx.NewUpdate().Table(hookCtx.TableName).SetMap(values).
|
query := hookCtx.Tx.NewUpdate().Table(hookCtx.TableName).SetMap(values).
|
||||||
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), hookCtx.ID)
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), hookCtx.ID)
|
||||||
|
|||||||
@@ -656,7 +656,7 @@ func isColumnWritableInType(typ reflect.Type, columnName string) (found bool, wr
|
|||||||
// Check bun tag for scanonly
|
// Check bun tag for scanonly
|
||||||
bunTag := field.Tag.Get("bun")
|
bunTag := field.Tag.Get("bun")
|
||||||
if bunTag != "" {
|
if bunTag != "" {
|
||||||
if isBunFieldScanOnly(bunTag) {
|
if isBunFieldScanOnly(bunTag) || isBunFieldGenerated(bunTag) {
|
||||||
return true, false
|
return true, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -689,6 +689,70 @@ func isBunFieldScanOnly(tag string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isBunFieldGenerated checks if a bun tag marks the column as database-generated
|
||||||
|
// (GENERATED ALWAYS AS ... STORED), which can be read but never written.
|
||||||
|
// Example: "email_normalized,generated" -> true
|
||||||
|
func isBunFieldGenerated(tag string) bool {
|
||||||
|
for _, part := range strings.Split(tag, ",") {
|
||||||
|
if strings.TrimSpace(part) == "generated" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveNonWritableColumns deletes from values every key that maps to a
|
||||||
|
// non-writable model column (bun scanonly/generated, gorm read-only). Used
|
||||||
|
// before writing a read-merged record back with UPDATE ... SET.
|
||||||
|
func RemoveNonWritableColumns(model any, values map[string]interface{}) {
|
||||||
|
for key := range values {
|
||||||
|
if !IsColumnWritable(model, key) {
|
||||||
|
delete(values, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonWritableColumns returns the column names of the model that cannot be
|
||||||
|
// written (bun scanonly/generated, gorm read-only), including embedded structs.
|
||||||
|
func NonWritableColumns(model any) []string {
|
||||||
|
t := reflect.TypeOf(model)
|
||||||
|
for t != nil && (t.Kind() == reflect.Pointer || t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t == nil || t.Kind() != reflect.Struct {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var cols []string
|
||||||
|
collectNonWritable(t, &cols)
|
||||||
|
return cols
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectNonWritable(typ reflect.Type, cols *[]string) {
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
if field.Anonymous {
|
||||||
|
ft := field.Type
|
||||||
|
if ft.Kind() == reflect.Pointer {
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
if ft.Kind() == reflect.Struct {
|
||||||
|
collectNonWritable(ft, cols)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bunTag, gormTag := field.Tag.Get("bun"), field.Tag.Get("gorm")
|
||||||
|
if bunTag == "-" || gormTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (bunTag != "" && (isBunFieldScanOnly(bunTag) || isBunFieldGenerated(bunTag))) ||
|
||||||
|
(gormTag != "" && isGormFieldReadOnly(gormTag)) {
|
||||||
|
if name := getColumnNameFromField(field); name != "" {
|
||||||
|
*cols = append(*cols, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// isGormFieldReadOnly checks if a gorm tag indicates the field is read-only
|
// isGormFieldReadOnly checks if a gorm tag indicates the field is read-only
|
||||||
// Examples:
|
// Examples:
|
||||||
// - "<-:false" -> true (no writes allowed)
|
// - "<-:false" -> true (no writes allowed)
|
||||||
|
|||||||
@@ -1920,3 +1920,46 @@ func TestMapToStruct_Errors(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveNonWritableColumns_Generated(t *testing.T) {
|
||||||
|
type m struct {
|
||||||
|
ID int `bun:"id,pk"`
|
||||||
|
Email string `bun:"email"`
|
||||||
|
Norm string `bun:"email_normalized,generated"`
|
||||||
|
Scan string `bun:"scan_col,scanonly"`
|
||||||
|
}
|
||||||
|
vals := map[string]interface{}{"id": 1, "email": "A", "email_normalized": "a", "scan_col": "x", "dynamic": 1}
|
||||||
|
RemoveNonWritableColumns(&m{}, vals)
|
||||||
|
if _, ok := vals["email_normalized"]; ok {
|
||||||
|
t.Error("generated column not removed")
|
||||||
|
}
|
||||||
|
if _, ok := vals["scan_col"]; ok {
|
||||||
|
t.Error("scanonly column not removed")
|
||||||
|
}
|
||||||
|
if len(vals) != 3 {
|
||||||
|
t.Errorf("unexpected keys: %v", vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonWritableColumns(t *testing.T) {
|
||||||
|
type base struct {
|
||||||
|
Created string `bun:"created_at,scanonly"`
|
||||||
|
}
|
||||||
|
type m struct {
|
||||||
|
base
|
||||||
|
ID int `bun:"id,pk"`
|
||||||
|
Email string `bun:"email"`
|
||||||
|
Norm string `bun:"email_normalized,generated"`
|
||||||
|
Ro string `gorm:"column:ro;->"`
|
||||||
|
}
|
||||||
|
got := NonWritableColumns(&m{})
|
||||||
|
want := map[string]bool{"created_at": true, "email_normalized": true, "ro": true}
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("got %v", got)
|
||||||
|
}
|
||||||
|
for _, c := range got {
|
||||||
|
if !want[c] {
|
||||||
|
t.Errorf("unexpected %s", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ func (h *Handler) executeCreate(ctx context.Context, schema, entity string, data
|
|||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
return invalidArg("no writable fields in data")
|
return invalidArg("no writable fields in data")
|
||||||
}
|
}
|
||||||
|
reflection.RemoveNonWritableColumns(model, cols)
|
||||||
q := tx.NewInsert().Table(tableName)
|
q := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range cols {
|
for key, value := range cols {
|
||||||
q = q.Value(key, value)
|
q = q.Value(key, value)
|
||||||
@@ -726,6 +727,7 @@ func (h *Handler) executeUpdate(ctx context.Context, schema, entity, id string,
|
|||||||
existingMap[key] = v
|
existingMap[key] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, setCols)
|
||||||
q := tx.NewUpdate().Table(tableName).SetMap(setCols).
|
q := tx.NewUpdate().Table(tableName).SetMap(setCols).
|
||||||
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
res, err := q.Exec(ctx)
|
res, err := q.Exec(ctx)
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ func (h *Handler) executeWhere(ctx context.Context, req whereRequest) (_ *whereR
|
|||||||
cond := fmt.Sprintf("%s IN (%s)", common.QuoteIdent(pkName), strings.Join(inList, ", "))
|
cond := fmt.Sprintf("%s IN (%s)", common.QuoteIdent(pkName), strings.Join(inList, ", "))
|
||||||
var affected int64
|
var affected int64
|
||||||
if req.op == "update" {
|
if req.op == "update" {
|
||||||
|
reflection.RemoveNonWritableColumns(model, setCols)
|
||||||
r, err := tx.NewUpdate().Table(tableName).SetMap(setCols).Where(cond, ids...).Exec(ctx)
|
r, err := tx.NewUpdate().Table(tableName).SetMap(setCols).Where(cond, ids...).Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error updating records: %w", err)
|
return fmt.Errorf("error updating records: %w", err)
|
||||||
|
|||||||
@@ -824,6 +824,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
}
|
}
|
||||||
responseData = v
|
responseData = v
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, v)
|
||||||
query := tx.NewInsert().Table(tableName)
|
query := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
query = query.Value(key, common.ConvertSliceForBun(value))
|
query = query.Value(key, common.ConvertSliceForBun(value))
|
||||||
@@ -971,6 +972,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
item = modifiedData
|
item = modifiedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, item)
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range item {
|
for key, value := range item {
|
||||||
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
@@ -1127,6 +1129,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
itemMap = modifiedData
|
itemMap = modifiedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(model, itemMap)
|
||||||
txQuery := tx.NewInsert().Table(tableName)
|
txQuery := tx.NewInsert().Table(tableName)
|
||||||
for key, value := range itemMap {
|
for key, value := range itemMap {
|
||||||
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
txQuery = txQuery.Value(key, common.ConvertSliceForBun(value))
|
||||||
@@ -1322,6 +1325,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
|
|
||||||
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
||||||
common.MergeUpdateValues(existingMap, updates, h.disallowNulls)
|
common.MergeUpdateValues(existingMap, updates, h.disallowNulls)
|
||||||
|
reflection.RemoveNonWritableColumns(model, existingMap)
|
||||||
|
|
||||||
// Build update query with merged data
|
// Build update query with merged data
|
||||||
query := tx.NewUpdate().Table(tableName).SetMap(existingMap)
|
query := tx.NewUpdate().Table(tableName).SetMap(existingMap)
|
||||||
@@ -1507,6 +1511,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
|
|
||||||
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
||||||
common.MergeUpdateValues(existingMap, item, h.disallowNulls)
|
common.MergeUpdateValues(existingMap, item, h.disallowNulls)
|
||||||
|
reflection.RemoveNonWritableColumns(model, existingMap)
|
||||||
|
|
||||||
txQuery := tx.NewUpdate().Table(tableName).SetMap(existingMap).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
txQuery := tx.NewUpdate().Table(tableName).SetMap(existingMap).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
@@ -1662,6 +1667,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
|
|
||||||
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
// Overwrite with every key present in the request (including "" and null unless disallowed)
|
||||||
common.MergeUpdateValues(existingMap, itemMap, h.disallowNulls)
|
common.MergeUpdateValues(existingMap, itemMap, h.disallowNulls)
|
||||||
|
reflection.RemoveNonWritableColumns(model, existingMap)
|
||||||
|
|
||||||
txQuery := tx.NewUpdate().Table(tableName).SetMap(existingMap).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
txQuery := tx.NewUpdate().Table(tableName).SetMap(existingMap).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), itemID)
|
||||||
if _, err := txQuery.Exec(ctx); err != nil {
|
if _, err := txQuery.Exec(ctx); err != nil {
|
||||||
|
|||||||
@@ -1410,6 +1410,9 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
if provider, ok := modelValue.(common.TableNameProvider); !ok || provider.TableName() == "" {
|
if provider, ok := modelValue.(common.TableNameProvider); !ok || provider.TableName() == "" {
|
||||||
query = query.Table(tableName)
|
query = query.Table(tableName)
|
||||||
}
|
}
|
||||||
|
if generated := reflection.NonWritableColumns(model); len(generated) > 0 {
|
||||||
|
query = query.ExcludeColumn(generated...)
|
||||||
|
}
|
||||||
fields := reflection.GetSQLModelColumns(model)
|
fields := reflection.GetSQLModelColumns(model)
|
||||||
query = query.Returning(fields...)
|
query = query.Returning(fields...)
|
||||||
|
|
||||||
@@ -1657,6 +1660,9 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
|
|
||||||
// Create update query using Model() to preserve custom types and driver.Valuer interfaces
|
// Create update query using Model() to preserve custom types and driver.Valuer interfaces
|
||||||
query := tx.NewUpdate().Model(modelInstance)
|
query := tx.NewUpdate().Model(modelInstance)
|
||||||
|
if generated := reflection.NonWritableColumns(model); len(generated) > 0 {
|
||||||
|
query = query.ExcludeColumn(generated...)
|
||||||
|
}
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
|
||||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||||
|
|||||||
@@ -758,6 +758,9 @@ func (h *Handler) create(hookCtx *HookContext) (interface{}, error) {
|
|||||||
|
|
||||||
// Insert record
|
// Insert record
|
||||||
query := hookCtx.Tx.NewInsert().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
query := hookCtx.Tx.NewInsert().Model(hookCtx.ModelPtr).Table(hookCtx.TableName)
|
||||||
|
if generated := reflection.NonWritableColumns(hookCtx.Model); len(generated) > 0 {
|
||||||
|
query = query.ExcludeColumn(generated...)
|
||||||
|
}
|
||||||
if _, err := query.Exec(hookCtx.Context); err != nil {
|
if _, err := query.Exec(hookCtx.Context); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create record: %w", err)
|
return nil, fmt.Errorf("failed to create record: %w", err)
|
||||||
}
|
}
|
||||||
@@ -786,6 +789,8 @@ func (h *Handler) update(hookCtx *HookContext) error {
|
|||||||
// the stored value unless disallowNulls is set, in which case null is skipped.
|
// the stored value unless disallowNulls is set, in which case null is skipped.
|
||||||
values := common.MergeUpdateValues(make(map[string]interface{}, len(updates)), updates, h.disallowNulls)
|
values := common.MergeUpdateValues(make(map[string]interface{}, len(updates)), updates, h.disallowNulls)
|
||||||
|
|
||||||
|
reflection.RemoveNonWritableColumns(hookCtx.Model, values)
|
||||||
|
|
||||||
if len(values) > 0 {
|
if len(values) > 0 {
|
||||||
query := hookCtx.Tx.NewUpdate().Table(hookCtx.TableName).SetMap(values).
|
query := hookCtx.Tx.NewUpdate().Table(hookCtx.TableName).SetMap(values).
|
||||||
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), hookCtx.ID)
|
Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), hookCtx.ID)
|
||||||
|
|||||||
@@ -226,6 +226,11 @@ func (m *MockInsertQuery) OnConflict(action string) common.InsertQuery {
|
|||||||
return args.Get(0).(common.InsertQuery)
|
return args.Get(0).(common.InsertQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockInsertQuery) ExcludeColumn(columns ...string) common.InsertQuery {
|
||||||
|
args := m.Called(columns)
|
||||||
|
return args.Get(0).(common.InsertQuery)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockInsertQuery) Returning(columns ...string) common.InsertQuery {
|
func (m *MockInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||||
args := m.Called(columns)
|
args := m.Called(columns)
|
||||||
return args.Get(0).(common.InsertQuery)
|
return args.Get(0).(common.InsertQuery)
|
||||||
@@ -254,6 +259,11 @@ func (m *MockUpdateQuery) Model(model interface{}) common.UpdateQuery {
|
|||||||
return args.Get(0).(common.UpdateQuery)
|
return args.Get(0).(common.UpdateQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockUpdateQuery) ExcludeColumn(columns ...string) common.UpdateQuery {
|
||||||
|
args := m.Called(columns)
|
||||||
|
return args.Get(0).(common.UpdateQuery)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MockUpdateQuery) Table(table string) common.UpdateQuery {
|
func (m *MockUpdateQuery) Table(table string) common.UpdateQuery {
|
||||||
args := m.Called(table)
|
args := m.Called(table)
|
||||||
return args.Get(0).(common.UpdateQuery)
|
return args.Get(0).(common.UpdateQuery)
|
||||||
|
|||||||
Reference in New Issue
Block a user