diff --git a/pkg/dbutil/dbutil.go b/pkg/dbutil/dbutil.go new file mode 100644 index 00000000..872a0911 --- /dev/null +++ b/pkg/dbutil/dbutil.go @@ -0,0 +1,175 @@ +package dbutil + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "strconv" +) + +func QuickSetStr(strVal *string, m map[string]interface{}, name string) { + v, ok := m[name] + if !ok { + return + } + ival, ok := v.(int64) + if ok { + *strVal = strconv.FormatInt(ival, 10) + return + } + str, ok := v.(string) + if !ok { + return + } + *strVal = str +} + +func QuickSetInt64(ival *int64, m map[string]interface{}, name string) { + v, ok := m[name] + if !ok { + return + } + sqlInt, ok := v.(int64) + if !ok { + return + } + *ival = sqlInt +} + +func QuickSetBool(bval *bool, m map[string]interface{}, name string) { + v, ok := m[name] + if !ok { + return + } + sqlInt, ok := v.(int64) + if ok { + if sqlInt > 0 { + *bval = true + } + return + } + sqlBool, ok := v.(bool) + if ok { + *bval = sqlBool + } +} + +func QuickSetBytes(bval *[]byte, m map[string]interface{}, name string) { + v, ok := m[name] + if !ok { + return + } + sqlBytes, ok := v.([]byte) + if ok { + *bval = sqlBytes + } +} + +func getByteArr(m map[string]any, name string, def string) ([]byte, bool) { + v, ok := m[name] + if !ok { + return nil, false + } + barr, ok := v.([]byte) + if !ok { + str, ok := v.(string) + if !ok { + return nil, false + } + barr = []byte(str) + } + if len(barr) == 0 { + barr = []byte(def) + } + return barr, true +} + +func QuickSetJson(ptr interface{}, m map[string]interface{}, name string) { + barr, ok := getByteArr(m, name, "{}") + if !ok { + return + } + json.Unmarshal(barr, ptr) +} + +func QuickSetNullableJson(ptr interface{}, m map[string]interface{}, name string) { + barr, ok := getByteArr(m, name, "null") + if !ok { + return + } + json.Unmarshal(barr, ptr) +} + +func QuickSetJsonArr(ptr interface{}, m map[string]interface{}, name string) { + barr, ok := getByteArr(m, name, "[]") + if !ok { + return + } + json.Unmarshal(barr, ptr) +} + +func QuickNullableJson(v interface{}) string { + if v == nil { + return "null" + } + barr, _ := json.Marshal(v) + return string(barr) +} + +func QuickJson(v interface{}) string { + if v == nil { + return "{}" + } + barr, _ := json.Marshal(v) + return string(barr) +} + +func QuickJsonBytes(v interface{}) []byte { + if v == nil { + return []byte("{}") + } + barr, _ := json.Marshal(v) + return barr +} + +func QuickJsonArr(v interface{}) string { + if v == nil { + return "[]" + } + barr, _ := json.Marshal(v) + return string(barr) +} + +func QuickJsonArrBytes(v interface{}) []byte { + if v == nil { + return []byte("[]") + } + barr, _ := json.Marshal(v) + return barr +} + +func QuickScanJson(ptr interface{}, val interface{}) error { + barrVal, ok := val.([]byte) + if !ok { + strVal, ok := val.(string) + if !ok { + return fmt.Errorf("cannot scan '%T' into '%T'", val, ptr) + } + barrVal = []byte(strVal) + } + if len(barrVal) == 0 { + barrVal = []byte("{}") + } + return json.Unmarshal(barrVal, ptr) +} + +func QuickValueJson(v interface{}) (driver.Value, error) { + if v == nil { + return "{}", nil + } + barr, err := json.Marshal(v) + if err != nil { + return nil, err + } + return string(barr), nil +} diff --git a/pkg/dbutil/map.go b/pkg/dbutil/map.go new file mode 100644 index 00000000..7e67516b --- /dev/null +++ b/pkg/dbutil/map.go @@ -0,0 +1,183 @@ +package dbutil + +import ( + "fmt" + "reflect" + "strings" + + "github.com/sawka/txwrap" +) + +type DBMappable interface { + UseDBMap() +} + +type MapConverter interface { + ToMap() map[string]interface{} + FromMap(map[string]interface{}) bool +} + +type HasSimpleKey interface { + GetSimpleKey() string +} + +type MapConverterPtr[T any] interface { + MapConverter + *T +} + +type DBMappablePtr[T any] interface { + DBMappable + *T +} + +func FromMap[PT MapConverterPtr[T], T any](m map[string]any) PT { + if len(m) == 0 { + return nil + } + rtn := PT(new(T)) + ok := rtn.FromMap(m) + if !ok { + return nil + } + return rtn +} + +func GetMapGen[PT MapConverterPtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) PT { + m := tx.GetMap(query, args...) + return FromMap[PT](m) +} + +func GetMappable[PT DBMappablePtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) PT { + rtn := PT(new(T)) + m := tx.GetMap(query, args...) + if len(m) == 0 { + return nil + } + FromDBMap(rtn, m) + return rtn +} + +func SelectMapsGen[PT MapConverterPtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) []PT { + var rtn []PT + marr := tx.SelectMaps(query, args...) + for _, m := range marr { + val := FromMap[PT](m) + if val != nil { + rtn = append(rtn, val) + } + } + return rtn +} + +func MakeGenMap[T HasSimpleKey](arr []T) map[string]T { + rtn := make(map[string]T) + for _, val := range arr { + rtn[val.GetSimpleKey()] = val + } + return rtn +} + +func isStructType(rt reflect.Type) bool { + if rt.Kind() == reflect.Struct { + return true + } + if rt.Kind() == reflect.Pointer && rt.Elem().Kind() == reflect.Struct { + return true + } + return false +} + +func isByteArrayType(t reflect.Type) bool { + return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 +} + +func ToDBMap(v DBMappable, useBytes bool) map[string]interface{} { + if v == nil { + return nil + } + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Pointer { + rv = rv.Elem() + } + if rv.Kind() != reflect.Struct { + panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructToDBMap", v)) + } + rt := rv.Type() + m := make(map[string]interface{}) + numFields := rt.NumField() + for i := 0; i < numFields; i++ { + field := rt.Field(i) + fieldVal := rv.FieldByIndex(field.Index) + dbName := field.Tag.Get("dbmap") + if dbName == "" { + dbName = strings.ToLower(field.Name) + } + if dbName == "-" { + continue + } + if isByteArrayType(field.Type) { + m[dbName] = fieldVal.Interface() + } else if field.Type.Kind() == reflect.Slice { + if useBytes { + m[dbName] = QuickJsonArrBytes(fieldVal.Interface()) + } else { + m[dbName] = QuickJsonArr(fieldVal.Interface()) + } + } else if isStructType(field.Type) { + if useBytes { + m[dbName] = QuickJsonBytes(fieldVal.Interface()) + } else { + m[dbName] = QuickJson(fieldVal.Interface()) + } + } else { + m[dbName] = fieldVal.Interface() + } + } + return m +} + +func FromDBMap(v DBMappable, m map[string]interface{}) { + if v == nil { + panic("StructFromDBMap, v cannot be nil") + } + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Pointer { + rv = rv.Elem() + } + if rv.Kind() != reflect.Struct { + panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructFromDBMap", v)) + } + rt := rv.Type() + numFields := rt.NumField() + for i := 0; i < numFields; i++ { + field := rt.Field(i) + fieldVal := rv.FieldByIndex(field.Index) + dbName := field.Tag.Get("dbmap") + if dbName == "" { + dbName = strings.ToLower(field.Name) + } + if dbName == "-" { + continue + } + if isByteArrayType(field.Type) { + barrVal := fieldVal.Addr().Interface() + QuickSetBytes(barrVal.(*[]byte), m, dbName) + } else if field.Type.Kind() == reflect.Slice { + QuickSetJsonArr(fieldVal.Addr().Interface(), m, dbName) + } else if isStructType(field.Type) { + QuickSetJson(fieldVal.Addr().Interface(), m, dbName) + } else if field.Type.Kind() == reflect.String { + strVal := fieldVal.Addr().Interface() + QuickSetStr(strVal.(*string), m, dbName) + } else if field.Type.Kind() == reflect.Int64 { + intVal := fieldVal.Addr().Interface() + QuickSetInt64(intVal.(*int64), m, dbName) + } else if field.Type.Kind() == reflect.Bool { + boolVal := fieldVal.Addr().Interface() + QuickSetBool(boolVal.(*bool), m, dbName) + } else { + panic(fmt.Sprintf("StructFromDBMap invalid field type %v in %T", fieldVal.Type(), v)) + } + } +} diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 852a6ef0..647dc282 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -15,6 +15,7 @@ import ( "github.com/scripthaus-dev/mshell/pkg/base" "github.com/scripthaus-dev/mshell/pkg/packet" "github.com/scripthaus-dev/mshell/pkg/shexec" + "github.com/scripthaus-dev/sh2-server/pkg/dbutil" "github.com/scripthaus-dev/sh2-server/pkg/scbase" ) @@ -88,7 +89,7 @@ func GetAllRemotes(ctx context.Context) ([]*RemoteType, error) { query := `SELECT * FROM remote ORDER BY remoteidx` marr := tx.SelectMaps(query) for _, m := range marr { - rtn = append(rtn, FromMap[*RemoteType](m)) + rtn = append(rtn, dbutil.FromMap[*RemoteType](m)) } return nil }) @@ -103,7 +104,7 @@ func GetRemoteByAlias(ctx context.Context, alias string) (*RemoteType, error) { err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote WHERE remotealias = ?` m := tx.GetMap(query, alias) - remote = FromMap[*RemoteType](m) + remote = dbutil.FromMap[*RemoteType](m) return nil }) if err != nil { @@ -117,7 +118,7 @@ func GetRemoteById(ctx context.Context, remoteId string) (*RemoteType, error) { err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote WHERE remoteid = ?` m := tx.GetMap(query, remoteId) - remote = FromMap[*RemoteType](m) + remote = dbutil.FromMap[*RemoteType](m) return nil }) if err != nil { @@ -131,7 +132,7 @@ func GetLocalRemote(ctx context.Context) (*RemoteType, error) { err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote WHERE local` m := tx.GetMap(query) - remote = FromMap[*RemoteType](m) + remote = dbutil.FromMap[*RemoteType](m) return nil }) if err != nil { @@ -144,7 +145,7 @@ func GetRemoteByCanonicalName(ctx context.Context, cname string) (*RemoteType, e var remote *RemoteType err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote WHERE remotecanonicalname = ?` - remote = GetMapGen[*RemoteType](tx, query, cname) + remote = dbutil.GetMapGen[*RemoteType](tx, query, cname) return nil }) if err != nil { @@ -157,7 +158,7 @@ func GetRemoteByPhysicalId(ctx context.Context, physicalId string) (*RemoteType, var remote *RemoteType err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote WHERE physicalid = ?` - remote = GetMapGen[*RemoteType](tx, query, physicalId) + remote = dbutil.GetMapGen[*RemoteType](tx, query, physicalId) return nil }) if err != nil { @@ -342,7 +343,7 @@ func runHistoryQuery(tx *TxWrap, opts HistoryQueryOpts, realOffset int, itemLimi marr := tx.SelectMaps(query, queryArgs...) rtn := make([]*HistoryItemType, len(marr)) for idx, m := range marr { - hitem := FromMap[*HistoryItemType](m) + hitem := dbutil.FromMap[*HistoryItemType](m) rtn[idx] = hitem } return rtn, nil @@ -367,7 +368,7 @@ func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryR func GetHistoryItemByLineNum(ctx context.Context, screenId string, lineNum int) (*HistoryItemType, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*HistoryItemType, error) { query := `SELECT * FROM history WHERE screenid = ? AND linenum = ?` - hitem := GetMapGen[*HistoryItemType](tx, query, screenId, lineNum) + hitem := dbutil.GetMapGen[*HistoryItemType](tx, query, screenId, lineNum) return hitem, nil }) } @@ -438,12 +439,12 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { session.Full = true } query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` - update.Screens = SelectMapsGen[*ScreenType](tx, query) + update.Screens = dbutil.SelectMapsGen[*ScreenType](tx, query) for _, screen := range update.Screens { screen.Full = true } query = `SELECT * FROM remote_instance` - riArr := SelectMapsGen[*RemoteInstance](tx, query) + riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query) for _, ri := range riArr { s := sessionMap[ri.SessionId] if s != nil { @@ -459,14 +460,14 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenLinesType, error) { query := `SELECT screenid FROM screen WHERE screenid = ?` - screen := GetMappable[*ScreenLinesType](tx, query, screenId) + screen := dbutil.GetMappable[*ScreenLinesType](tx, query, screenId) if screen == nil { return nil, nil } query = `SELECT * FROM line WHERE screenid = ? ORDER BY linenum` tx.Select(&screen.Lines, query, screen.ScreenId) query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?)` - screen.Cmds = SelectMapsGen[*CmdType](tx, query, screen.ScreenId) + screen.Cmds = dbutil.SelectMapsGen[*CmdType](tx, query, screen.ScreenId) return screen, nil }) } @@ -475,7 +476,7 @@ func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) { return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) { query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts` - rtn := SelectMapsGen[*ScreenType](tx, query, sessionId) + rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId) for _, screen := range rtn { screen.Full = true } @@ -716,7 +717,7 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { query := `SELECT * FROM screen WHERE screenid = ?` - screen := GetMapGen[*ScreenType](tx, query, screenId) + screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId) screen.Full = true return screen, nil }) @@ -766,7 +767,7 @@ func GetLineCmdByLineId(ctx context.Context, screenId string, lineId string) (*L var cmdRtn *CmdType if lineVal.CmdId != "" { query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` - cmdRtn = GetMapGen[*CmdType](tx, query, screenId, lineVal.CmdId) + cmdRtn = dbutil.GetMapGen[*CmdType](tx, query, screenId, lineVal.CmdId) } return &lineVal, cmdRtn, nil }) @@ -781,7 +782,7 @@ func GetLineCmdByCmdId(ctx context.Context, screenId string, cmdId string) (*Lin return nil, nil, nil } query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` - cmdRtn := GetMapGen[*CmdType](tx, query, screenId, cmdId) + cmdRtn := dbutil.GetMapGen[*CmdType](tx, query, screenId, cmdId) return &lineVal, cmdRtn, nil }) } @@ -832,7 +833,7 @@ func GetCmdByScreenId(ctx context.Context, screenId string, cmdId string) (*CmdT var cmd *CmdType err := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?` - cmd = GetMapGen[*CmdType](tx, query, screenId, cmdId) + cmd = dbutil.GetMapGen[*CmdType](tx, query, screenId, cmdId) return nil }) if err != nil { @@ -1181,7 +1182,7 @@ func GetRemoteInstance(ctx context.Context, sessionId string, screenId string, r var ri *RemoteInstance txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote_instance WHERE sessionid = ? AND screenid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?` - ri = GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name) + ri = dbutil.GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name) return nil }) if txErr != nil { @@ -1227,7 +1228,7 @@ func UpdateRemoteState(ctx context.Context, sessionId string, screenId string, r return fmt.Errorf("cannot update remote instance state: %w", err) } query := `SELECT * FROM remote_instance WHERE sessionid = ? AND screenid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?` - ri = GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name) + ri = dbutil.GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name) if ri == nil { ri = &RemoteInstance{ RIId: scbase.GenPromptUUID(), @@ -1408,7 +1409,7 @@ func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, err var rtn []*CmdType txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * from cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?) AND status = ?` - rtn = SelectMapsGen[*CmdType](tx, query, screenId, CmdStatusRunning) + rtn = dbutil.SelectMapsGen[*CmdType](tx, query, screenId, CmdStatusRunning) return nil }) if txErr != nil { @@ -1824,7 +1825,7 @@ func GetFullState(ctx context.Context, ssPtr ShellStatePtr) (*packet.ShellState, } for idx, diffHash := range ssPtr.DiffHashArr { query = `SELECT * FROM state_diff WHERE diffhash = ?` - stateDiff := GetMapGen[*StateDiff](tx, query, diffHash) + stateDiff := dbutil.GetMapGen[*StateDiff](tx, query, diffHash) if stateDiff == nil { return fmt.Errorf("ShellStateDiff %s not found", diffHash) } @@ -1953,7 +1954,7 @@ func GetRIsForScreen(ctx context.Context, sessionId string, screenId string) ([] var rtn []*RemoteInstance txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM remote_instance WHERE sessionid = ? AND (screenid = '' OR screenid = ?)` - rtn = SelectMapsGen[*RemoteInstance](tx, query, sessionId, screenId) + rtn = dbutil.SelectMapsGen[*RemoteInstance](tx, query, sessionId, screenId) return nil }) if txErr != nil { @@ -2112,12 +2113,12 @@ func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) { var query string if tag == "" { query = `SELECT * FROM bookmark` - bms = SelectMapsGen[*BookmarkType](tx, query) + bms = dbutil.SelectMapsGen[*BookmarkType](tx, query) } else { query = `SELECT * FROM bookmark WHERE EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)` - bms = SelectMapsGen[*BookmarkType](tx, query, tag) + bms = dbutil.SelectMapsGen[*BookmarkType](tx, query, tag) } - bmMap := MakeGenMap(bms) + bmMap := dbutil.MakeGenMap(bms) var orders []bookmarkOrderType query = `SELECT bookmarkid, orderidx FROM bookmark_order WHERE tag = ?` tx.Select(&orders, query, tag) @@ -2139,7 +2140,7 @@ func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*Bookm var rtn *BookmarkType txErr := WithTx(ctx, func(tx *TxWrap) error { query := `SELECT * FROM bookmark WHERE bookmarkid = ?` - rtn = GetMapGen[*BookmarkType](tx, query, bookmarkId) + rtn = dbutil.GetMapGen[*BookmarkType](tx, query, bookmarkId) if rtn == nil { return nil } @@ -2275,7 +2276,7 @@ func CreatePlaybook(ctx context.Context, name string) (*PlaybookType, error) { func selectPlaybook(tx *TxWrap, playbookId string) *PlaybookType { query := `SELECT * FROM playbook where playbookid = ?` - playbook := GetMapGen[*PlaybookType](tx, query, playbookId) + playbook := dbutil.GetMapGen[*PlaybookType](tx, query, playbookId) return playbook } @@ -2363,7 +2364,7 @@ func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryIte query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))` tx.Select(&lineArr, query, quickJsonArr(getLineIdsFromHistoryItems(historyItems))) query = `SELECT * FROM cmd WHERE cmdid IN (SELECT value FROM json_each(?))` - cmdArr := SelectMapsGen[*CmdType](tx, query, quickJsonArr(getCmdIdsFromHistoryItems(historyItems))) + cmdArr := dbutil.SelectMapsGen[*CmdType](tx, query, quickJsonArr(getCmdIdsFromHistoryItems(historyItems))) return lineArr, cmdArr, nil }) } @@ -2371,7 +2372,7 @@ func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryIte func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItemType, error) { return WithTxRtn(ctx, func(tx *TxWrap) ([]*HistoryItemType, error) { query := `SELECT * FROM history WHERE historyid IN (SELECT value FROM json_each(?))` - rtn := SelectMapsGen[*HistoryItemType](tx, query, quickJsonArr(historyIds)) + rtn := dbutil.SelectMapsGen[*HistoryItemType](tx, query, quickJsonArr(historyIds)) query = `DELETE FROM history WHERE historyid IN (SELECT value FROM json_each(?))` tx.Exec(query, quickJsonArr(historyIds)) for _, hitem := range rtn { diff --git a/pkg/sstore/map.go b/pkg/sstore/map.go index 6f97600e..c24a5345 100644 --- a/pkg/sstore/map.go +++ b/pkg/sstore/map.go @@ -2,81 +2,8 @@ package sstore import ( "context" - "fmt" - "reflect" - "strings" ) -type DBMappable interface { - UseDBMap() -} - -type MapConverter interface { - ToMap() map[string]interface{} - FromMap(map[string]interface{}) bool -} - -type HasSimpleKey interface { - GetSimpleKey() string -} - -type MapConverterPtr[T any] interface { - MapConverter - *T -} - -type DBMappablePtr[T any] interface { - DBMappable - *T -} - -func FromMap[PT MapConverterPtr[T], T any](m map[string]any) PT { - if len(m) == 0 { - return nil - } - rtn := PT(new(T)) - ok := rtn.FromMap(m) - if !ok { - return nil - } - return rtn -} - -func GetMapGen[PT MapConverterPtr[T], T any](tx *TxWrap, query string, args ...interface{}) PT { - m := tx.GetMap(query, args...) - return FromMap[PT](m) -} - -func GetMappable[PT DBMappablePtr[T], T any](tx *TxWrap, query string, args ...interface{}) PT { - rtn := PT(new(T)) - m := tx.GetMap(query, args...) - if len(m) == 0 { - return nil - } - FromDBMap(rtn, m) - return rtn -} - -func SelectMapsGen[PT MapConverterPtr[T], T any](tx *TxWrap, query string, args ...interface{}) []PT { - var rtn []PT - marr := tx.SelectMaps(query, args...) - for _, m := range marr { - val := FromMap[PT](m) - if val != nil { - rtn = append(rtn, val) - } - } - return rtn -} - -func MakeGenMap[T HasSimpleKey](arr []T) map[string]T { - rtn := make(map[string]T) - for _, val := range arr { - rtn[val.GetSimpleKey()] = val - } - return rtn -} - func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT, error) { var rtn RT txErr := WithTx(ctx, func(tx *TxWrap) error { @@ -104,99 +31,3 @@ func WithTxRtn3[RT1 any, RT2 any](ctx context.Context, fn func(tx *TxWrap) (RT1, }) return rtn1, rtn2, txErr } - -func isStructType(rt reflect.Type) bool { - if rt.Kind() == reflect.Struct { - return true - } - if rt.Kind() == reflect.Pointer && rt.Elem().Kind() == reflect.Struct { - return true - } - return false -} - -func isByteArrayType(t reflect.Type) bool { - return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 -} - -func ToDBMap(v DBMappable) map[string]interface{} { - if v == nil { - return nil - } - rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Pointer { - rv = rv.Elem() - } - if rv.Kind() != reflect.Struct { - panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructToDBMap", v)) - } - rt := rv.Type() - m := make(map[string]interface{}) - numFields := rt.NumField() - for i := 0; i < numFields; i++ { - field := rt.Field(i) - fieldVal := rv.FieldByIndex(field.Index) - dbName := field.Tag.Get("dbmap") - if dbName == "" { - dbName = strings.ToLower(field.Name) - } - if dbName == "-" { - continue - } - if isByteArrayType(field.Type) { - m[dbName] = fieldVal.Interface() - } else if field.Type.Kind() == reflect.Slice { - m[dbName] = quickJsonArr(fieldVal.Interface()) - } else if isStructType(field.Type) { - m[dbName] = quickJson(fieldVal.Interface()) - } else { - m[dbName] = fieldVal.Interface() - } - } - return m -} - -func FromDBMap(v DBMappable, m map[string]interface{}) { - if v == nil { - panic("StructFromDBMap, v cannot be nil") - } - rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Pointer { - rv = rv.Elem() - } - if rv.Kind() != reflect.Struct { - panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructFromDBMap", v)) - } - rt := rv.Type() - numFields := rt.NumField() - for i := 0; i < numFields; i++ { - field := rt.Field(i) - fieldVal := rv.FieldByIndex(field.Index) - dbName := field.Tag.Get("dbmap") - if dbName == "" { - dbName = strings.ToLower(field.Name) - } - if dbName == "-" { - continue - } - if isByteArrayType(field.Type) { - barrVal := fieldVal.Addr().Interface() - quickSetBytes(barrVal.(*[]byte), m, dbName) - } else if field.Type.Kind() == reflect.Slice { - quickSetJsonArr(fieldVal.Addr().Interface(), m, dbName) - } else if isStructType(field.Type) { - quickSetJson(fieldVal.Addr().Interface(), m, dbName) - } else if field.Type.Kind() == reflect.String { - strVal := fieldVal.Addr().Interface() - quickSetStr(strVal.(*string), m, dbName) - } else if field.Type.Kind() == reflect.Int64 { - intVal := fieldVal.Addr().Interface() - quickSetInt64(intVal.(*int64), m, dbName) - } else if field.Type.Kind() == reflect.Bool { - boolVal := fieldVal.Addr().Interface() - quickSetBool(boolVal.(*bool), m, dbName) - } else { - panic(fmt.Sprintf("StructFromDBMap invalid field type %v in %T", fieldVal.Type(), v)) - } - } -} diff --git a/pkg/sstore/quick.go b/pkg/sstore/quick.go index 116b2499..788c404d 100644 --- a/pkg/sstore/quick.go +++ b/pkg/sstore/quick.go @@ -1,161 +1,18 @@ package sstore import ( - "database/sql/driver" - "encoding/json" - "fmt" - "strconv" + "github.com/scripthaus-dev/sh2-server/pkg/dbutil" ) -func quickSetStr(strVal *string, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - ival, ok := v.(int64) - if ok { - *strVal = strconv.FormatInt(ival, 10) - return - } - str, ok := v.(string) - if !ok { - return - } - *strVal = str -} - -func quickSetInt64(ival *int64, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - sqlInt, ok := v.(int64) - if !ok { - return - } - *ival = sqlInt -} - -func quickSetBool(bval *bool, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - sqlInt, ok := v.(int64) - if ok { - if sqlInt > 0 { - *bval = true - } - return - } - sqlBool, ok := v.(bool) - if ok { - *bval = sqlBool - } -} - -func quickSetBytes(bval *[]byte, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - sqlBytes, ok := v.([]byte) - if ok { - *bval = sqlBytes - } -} - -func quickSetJson(ptr interface{}, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - str, ok := v.(string) - if !ok { - return - } - if str == "" { - str = "{}" - } - json.Unmarshal([]byte(str), ptr) -} - -func quickSetNullableJson(ptr interface{}, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - str, ok := v.(string) - if !ok { - return - } - if str == "" { - str = "null" - } - json.Unmarshal([]byte(str), ptr) -} - -func quickSetJsonArr(ptr interface{}, m map[string]interface{}, name string) { - v, ok := m[name] - if !ok { - return - } - str, ok := v.(string) - if !ok { - return - } - if str == "" { - str = "[]" - } - json.Unmarshal([]byte(str), ptr) -} - -func quickNullableJson(v interface{}) string { - if v == nil { - return "null" - } - barr, _ := json.Marshal(v) - return string(barr) -} - -func quickJson(v interface{}) string { - if v == nil { - return "{}" - } - barr, _ := json.Marshal(v) - return string(barr) -} - -func quickJsonArr(v interface{}) string { - if v == nil { - return "[]" - } - barr, _ := json.Marshal(v) - return string(barr) -} - -func quickScanJson(ptr interface{}, val interface{}) error { - barrVal, ok := val.([]byte) - if !ok { - strVal, ok := val.(string) - if !ok { - return fmt.Errorf("cannot scan '%T' into '%T'", val, ptr) - } - barrVal = []byte(strVal) - } - if len(barrVal) == 0 { - barrVal = []byte("{}") - } - return json.Unmarshal(barrVal, ptr) -} - -func quickValueJson(v interface{}) (driver.Value, error) { - if v == nil { - return "{}", nil - } - barr, err := json.Marshal(v) - if err != nil { - return nil, err - } - return string(barr), nil -} +var quickSetStr = dbutil.QuickSetStr +var quickSetInt64 = dbutil.QuickSetInt64 +var quickSetBool = dbutil.QuickSetBool +var quickSetBytes = dbutil.QuickSetBytes +var quickSetJson = dbutil.QuickSetJson +var quickSetNullableJson = dbutil.QuickSetNullableJson +var quickSetJsonArr = dbutil.QuickSetJsonArr +var quickNullableJson = dbutil.QuickNullableJson +var quickJson = dbutil.QuickJson +var quickJsonArr = dbutil.QuickJsonArr +var quickScanJson = dbutil.QuickScanJson +var quickValueJson = dbutil.QuickValueJson diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 46c90ed1..d5440984 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -22,6 +22,7 @@ import ( "github.com/sawka/txwrap" "github.com/scripthaus-dev/mshell/pkg/base" "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/sh2-server/pkg/dbutil" "github.com/scripthaus-dev/sh2-server/pkg/scbase" _ "github.com/mattn/go-sqlite3" @@ -1095,7 +1096,7 @@ func createClientData(tx *TxWrap) error { } query := `INSERT INTO client ( clientid, userid, activesessionid, userpublickeybytes, userprivatekeybytes, winsize) VALUES (:clientid,:userid,:activesessionid,:userpublickeybytes,:userprivatekeybytes,:winsize)` - tx.NamedExec(query, ToDBMap(c)) + tx.NamedExec(query, dbutil.ToDBMap(c, false)) log.Printf("create new clientid[%s] userid[%s] with public/private keypair\n", c.ClientId, c.UserId) return nil } @@ -1113,7 +1114,7 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) { return nil, createErr } } - cdata := GetMappable[*ClientData](tx, `SELECT * FROM client`) + cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`) if cdata == nil { return nil, fmt.Errorf("no client data found") } @@ -1148,7 +1149,7 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) { func GetCmdMigrationInfo(ctx context.Context) (*ClientMigrationData, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*ClientMigrationData, error) { - cdata := GetMappable[*ClientData](tx, `SELECT * FROM client`) + cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`) if cdata == nil { return nil, fmt.Errorf("no client data found") }