mirror of
https://github.com/pocketbase/pocketbase.git
synced 2025-03-20 06:21:06 +02:00
restored DynamicModel types cache
This commit is contained in:
parent
856cc604a7
commit
58dab5bf70
@ -13,6 +13,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -1020,9 +1021,19 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
|
|||||||
return instanceValue
|
return instanceValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cachedDynamicModelStructs = store.New[string, reflect.Type](nil)
|
||||||
|
|
||||||
// newDynamicModel creates a new dynamic struct with fields based
|
// newDynamicModel creates a new dynamic struct with fields based
|
||||||
// on the specified "shape".
|
// on the specified "shape".
|
||||||
//
|
//
|
||||||
|
// The "shape" values are used as defaults and could be of type:
|
||||||
|
// - int (ex. 0)
|
||||||
|
// - float (ex. -0)
|
||||||
|
// - string (ex. "")
|
||||||
|
// - bool (ex. false)
|
||||||
|
// - slice (ex. [])
|
||||||
|
// - map (ex. map[string]any{})
|
||||||
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// m := newDynamicModel(map[string]any{
|
// m := newDynamicModel(map[string]any{
|
||||||
@ -1030,37 +1041,21 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
|
|||||||
// "total": 0,
|
// "total": 0,
|
||||||
// })
|
// })
|
||||||
func newDynamicModel(shape map[string]any) any {
|
func newDynamicModel(shape map[string]any) any {
|
||||||
modelType := getDynamicModelStruct(shape)
|
info := make([]*shapeFieldInfo, 0, len(shape))
|
||||||
|
|
||||||
rvShapeValues := make([]reflect.Value, len(modelType.shapeValues))
|
var hash strings.Builder
|
||||||
for i, v := range modelType.shapeValues {
|
|
||||||
rvShapeValues[i] = reflect.ValueOf(v)
|
sortedKeys := make([]string, 0, len(shape))
|
||||||
|
for k := range shape {
|
||||||
|
sortedKeys = append(sortedKeys, k)
|
||||||
}
|
}
|
||||||
|
sort.Strings(sortedKeys)
|
||||||
|
|
||||||
elem := reflect.New(modelType.structType).Elem()
|
for _, k := range sortedKeys {
|
||||||
|
v := shape[k]
|
||||||
for i, v := range rvShapeValues {
|
|
||||||
elem.Field(i).Set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return elem.Addr().Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
type dynamicModelType struct {
|
|
||||||
structType reflect.Type
|
|
||||||
shapeValues []any
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDynamicModelStruct(shape map[string]any) *dynamicModelType {
|
|
||||||
result := new(dynamicModelType)
|
|
||||||
result.shapeValues = make([]any, 0, len(shape))
|
|
||||||
|
|
||||||
structFields := make([]reflect.StructField, 0, len(shape))
|
|
||||||
|
|
||||||
for k, v := range shape {
|
|
||||||
vt := reflect.TypeOf(v)
|
vt := reflect.TypeOf(v)
|
||||||
|
|
||||||
switch kind := vt.Kind(); kind {
|
switch vt.Kind() {
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
raw, _ := json.Marshal(v)
|
raw, _ := json.Marshal(v)
|
||||||
newV := types.JSONMap[any]{}
|
newV := types.JSONMap[any]{}
|
||||||
@ -1075,16 +1070,40 @@ func getDynamicModelStruct(shape map[string]any) *dynamicModelType {
|
|||||||
vt = reflect.TypeOf(newV)
|
vt = reflect.TypeOf(newV)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.shapeValues = append(result.shapeValues, v)
|
hash.WriteString(k)
|
||||||
|
hash.WriteString(":")
|
||||||
|
hash.WriteString(vt.String()) // it doesn't guarantee to be unique across all types but it should be fine with the primitive types DynamicModel is used
|
||||||
|
hash.WriteString("|")
|
||||||
|
|
||||||
structFields = append(structFields, reflect.StructField{
|
info = append(info, &shapeFieldInfo{key: k, value: v, valueType: vt})
|
||||||
Name: inflector.UcFirst(k), // ensures that the field is exportable
|
|
||||||
Type: vt,
|
|
||||||
Tag: reflect.StructTag(`db:"` + k + `" json:"` + k + `" form:"` + k + `"`),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.structType = reflect.StructOf(structFields)
|
st := cachedDynamicModelStructs.GetOrSet(hash.String(), func() reflect.Type {
|
||||||
|
structFields := make([]reflect.StructField, len(info))
|
||||||
|
|
||||||
return result
|
for i, item := range info {
|
||||||
|
structFields[i] = reflect.StructField{
|
||||||
|
Name: inflector.UcFirst(item.key), // ensures that the field is exportable
|
||||||
|
Type: item.valueType,
|
||||||
|
Tag: reflect.StructTag(`db:"` + item.key + `" json:"` + item.key + `" form:"` + item.key + `"`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.StructOf(structFields)
|
||||||
|
})
|
||||||
|
|
||||||
|
elem := reflect.New(st).Elem()
|
||||||
|
|
||||||
|
// load default values into the new model
|
||||||
|
for i, item := range info {
|
||||||
|
elem.Field(i).Set(reflect.ValueOf(item.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem.Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
type shapeFieldInfo struct {
|
||||||
|
value any
|
||||||
|
valueType reflect.Type
|
||||||
|
key string
|
||||||
}
|
}
|
||||||
|
@ -1137,7 +1137,6 @@ func TestLoadingDynamicModel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo revert the reflect caching and check other types
|
|
||||||
func TestDynamicModelMapFieldCaching(t *testing.T) {
|
func TestDynamicModelMapFieldCaching(t *testing.T) {
|
||||||
app, _ := tests.NewTestApp()
|
app, _ := tests.NewTestApp()
|
||||||
defer app.Cleanup()
|
defer app.Cleanup()
|
||||||
@ -1149,24 +1148,44 @@ func TestDynamicModelMapFieldCaching(t *testing.T) {
|
|||||||
|
|
||||||
_, err := vm.RunString(`
|
_, err := vm.RunString(`
|
||||||
let m1 = new DynamicModel({
|
let m1 = new DynamicModel({
|
||||||
|
int: 0,
|
||||||
|
float: -0,
|
||||||
|
text: "",
|
||||||
|
bool: false,
|
||||||
obj: {},
|
obj: {},
|
||||||
|
arr: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
let m2 = new DynamicModel({
|
let m2 = new DynamicModel({
|
||||||
|
int: 0,
|
||||||
|
float: -0,
|
||||||
|
text: "",
|
||||||
|
bool: false,
|
||||||
obj: {},
|
obj: {},
|
||||||
|
arr: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
m1.int = 1
|
||||||
|
m1.float = 1.5
|
||||||
|
m1.text = "a"
|
||||||
|
m1.bool = true
|
||||||
m1.obj.set("a", 1)
|
m1.obj.set("a", 1)
|
||||||
|
m1.arr.push(1)
|
||||||
|
|
||||||
|
m2.int = 2
|
||||||
|
m2.float = 2.5
|
||||||
|
m2.text = "b"
|
||||||
|
m2.bool = false
|
||||||
m2.obj.set("b", 1)
|
m2.obj.set("b", 1)
|
||||||
|
m2.arr.push(2)
|
||||||
|
|
||||||
let m1Expected = '{"obj":{"a":1}}';
|
let m1Expected = '{"arr":[1],"bool":true,"float":1.5,"int":1,"obj":{"a":1},"text":"a"}';
|
||||||
let m1Serialized = JSON.stringify(m1);
|
let m1Serialized = JSON.stringify(m1);
|
||||||
if (m1Serialized != m1Expected) {
|
if (m1Serialized != m1Expected) {
|
||||||
throw new Error("Expected m1 \n" + m1Expected + "\ngot\n" + m1Serialized);
|
throw new Error("Expected m1 \n" + m1Expected + "\ngot\n" + m1Serialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
let m2Expected = '{"obj":{"b":1}}';
|
let m2Expected = '{"arr":[2],"bool":false,"float":2.5,"int":2,"obj":{"b":1},"text":"b"}';
|
||||||
let m2Serialized = JSON.stringify(m2);
|
let m2Serialized = JSON.stringify(m2);
|
||||||
if (m2Serialized != m2Expected) {
|
if (m2Serialized != m2Expected) {
|
||||||
throw new Error("Expected m2 \n" + m2Expected + "\ngot\n" + m2Serialized);
|
throw new Error("Expected m2 \n" + m2Expected + "\ngot\n" + m2Serialized);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user