moving hard to OID model

This commit is contained in:
sawka
2024-05-26 11:59:14 -07:00
parent 4ba78a1804
commit b1aaba2a37
10 changed files with 277 additions and 280 deletions
+10 -5
View File
@@ -1,24 +1,29 @@
CREATE TABLE db_client ( CREATE TABLE db_client (
clientid varchar(36) PRIMARY KEY, -- unnecessary, but useful to have a PK oid varchar(36) PRIMARY KEY,
version int NOT NULL,
data json NOT NULL data json NOT NULL
); );
CREATE TABLE db_window ( CREATE TABLE db_window (
windowid varchar(36) PRIMARY KEY, oid varchar(36) PRIMARY KEY,
version int NOT NULL,
data json NOT NULL data json NOT NULL
); );
CREATE TABLE db_workspace ( CREATE TABLE db_workspace (
workspaceid varchar(36) PRIMARY KEY, oid varchar(36) PRIMARY KEY,
version int NOT NULL,
data json NOT NULL data json NOT NULL
); );
CREATE TABLE db_tab ( CREATE TABLE db_tab (
tabid varchar(36) PRIMARY KEY, oid varchar(36) PRIMARY KEY,
version int NOT NULL,
data json NOT NULL data json NOT NULL
); );
CREATE TABLE db_block ( CREATE TABLE db_block (
blockid varchar(36) PRIMARY KEY, oid varchar(36) PRIMARY KEY,
version int NOT NULL,
data json NOT NULL data json NOT NULL
); );
+3 -3
View File
@@ -60,7 +60,7 @@ func createWindow(windowData *wstore.Window, app *application.App) {
TitleBar: application.MacTitleBarHiddenInset, TitleBar: application.MacTitleBarHiddenInset,
}, },
BackgroundColour: application.NewRGB(0, 0, 0), BackgroundColour: application.NewRGB(0, 0, 0),
URL: "/public/index.html?windowid=" + windowData.WindowId, URL: "/public/index.html?windowid=" + windowData.OID,
X: windowData.Pos.X, X: windowData.Pos.X,
Y: windowData.Pos.Y, Y: windowData.Pos.Y,
Width: windowData.WinSize.Width, Width: windowData.WinSize.Width,
@@ -146,12 +146,12 @@ func main() {
setupCtx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) setupCtx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn() defer cancelFn()
client, err := wstore.DBGetSingleton[wstore.Client](setupCtx) client, err := wstore.DBGetSingleton[*wstore.Client](setupCtx)
if err != nil { if err != nil {
log.Printf("error getting client data: %v\n", err) log.Printf("error getting client data: %v\n", err)
return return
} }
mainWindow, err := wstore.DBGet[wstore.Window](setupCtx, client.MainWindowId) mainWindow, err := wstore.DBGet[*wstore.Window](setupCtx, client.MainWindowId)
if err != nil { if err != nil {
log.Printf("error getting main window: %v\n", err) log.Printf("error getting main window: %v\n", err)
return return
+4 -4
View File
@@ -64,7 +64,7 @@ func jsonDeepCopy(val map[string]any) (map[string]any, error) {
func CreateBlock(ctx context.Context, bdef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (*wstore.Block, error) { func CreateBlock(ctx context.Context, bdef *wstore.BlockDef, rtOpts *wstore.RuntimeOpts) (*wstore.Block, error) {
blockId := uuid.New().String() blockId := uuid.New().String()
blockData := &wstore.Block{ blockData := &wstore.Block{
BlockId: blockId, OID: blockId,
BlockDef: bdef, BlockDef: bdef,
Controller: bdef.Controller, Controller: bdef.Controller,
View: bdef.View, View: bdef.View,
@@ -266,19 +266,19 @@ func ProcessStaticCommand(blockId string, cmdGen BlockCommand) error {
return nil return nil
case *SetViewCommand: case *SetViewCommand:
log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View) log.Printf("SETVIEW: %s | %q\n", blockId, cmd.View)
block, err := wstore.DBGet[wstore.Block](ctx, blockId) block, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil { if err != nil {
return fmt.Errorf("error getting block: %w", err) return fmt.Errorf("error getting block: %w", err)
} }
block.View = cmd.View block.View = cmd.View
err = wstore.DBUpdate[wstore.Block](ctx, block) err = wstore.DBUpdate(ctx, block)
if err != nil { if err != nil {
return fmt.Errorf("error updating block: %w", err) return fmt.Errorf("error updating block: %w", err)
} }
return nil return nil
case *SetMetaCommand: case *SetMetaCommand:
log.Printf("SETMETA: %s | %v\n", blockId, cmd.Meta) log.Printf("SETMETA: %s | %v\n", blockId, cmd.Meta)
block, err := wstore.DBGet[wstore.Block](ctx, blockId) block, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil { if err != nil {
return fmt.Errorf("error getting block: %w", err) return fmt.Errorf("error getting block: %w", err)
} }
+1 -1
View File
@@ -40,7 +40,7 @@ func (bs *BlockService) CloseBlock(blockId string) {
func (bs *BlockService) GetBlockData(blockId string) (*wstore.Block, error) { func (bs *BlockService) GetBlockData(blockId string) (*wstore.Block, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn() defer cancelFn()
blockData, err := wstore.DBGet[wstore.Block](ctx, blockId) blockData, err := wstore.DBGet[*wstore.Block](ctx, blockId)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting block data: %w", err) return nil, fmt.Errorf("error getting block data: %w", err)
} }
+4 -4
View File
@@ -18,7 +18,7 @@ const DefaultTimeout = 2 * time.Second
func (cs *ClientService) GetClientData() (*wstore.Client, error) { func (cs *ClientService) GetClientData() (*wstore.Client, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
clientData, err := wstore.DBGetSingleton[wstore.Client](ctx) clientData, err := wstore.DBGetSingleton[*wstore.Client](ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting client data: %w", err) return nil, fmt.Errorf("error getting client data: %w", err)
} }
@@ -28,7 +28,7 @@ func (cs *ClientService) GetClientData() (*wstore.Client, error) {
func (cs *ClientService) GetWorkspace(workspaceId string) (*wstore.Workspace, error) { func (cs *ClientService) GetWorkspace(workspaceId string) (*wstore.Workspace, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
ws, err := wstore.DBGet[wstore.Workspace](ctx, workspaceId) ws, err := wstore.DBGet[*wstore.Workspace](ctx, workspaceId)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting workspace: %w", err) return nil, fmt.Errorf("error getting workspace: %w", err)
} }
@@ -38,7 +38,7 @@ func (cs *ClientService) GetWorkspace(workspaceId string) (*wstore.Workspace, er
func (cs *ClientService) GetTab(tabId string) (*wstore.Tab, error) { func (cs *ClientService) GetTab(tabId string) (*wstore.Tab, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
tab, err := wstore.DBGet[wstore.Tab](ctx, tabId) tab, err := wstore.DBGet[*wstore.Tab](ctx, tabId)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting tab: %w", err) return nil, fmt.Errorf("error getting tab: %w", err)
} }
@@ -48,7 +48,7 @@ func (cs *ClientService) GetTab(tabId string) (*wstore.Tab, error) {
func (cs *ClientService) GetWindow(windowId string) (*wstore.Window, error) { func (cs *ClientService) GetWindow(windowId string) (*wstore.Window, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout) ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn() defer cancelFn()
window, err := wstore.DBGet[wstore.Window](ctx, windowId) window, err := wstore.DBGet[*wstore.Window](ctx, windowId)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting window: %w", err) return nil, fmt.Errorf("error getting window: %w", err)
} }
+124 -72
View File
@@ -15,78 +15,123 @@ import (
) )
const ( const (
OTypeKeyName = "otype" OTypeKeyName = "otype"
OIDKeyName = "oid" OIDKeyName = "oid"
VersionKeyName = "version"
OIDGoFieldName = "OID"
VersionGoFieldName = "Version"
) )
type waveObjDesc struct {
RType reflect.Type
OIDField reflect.StructField
}
var globalLock = &sync.Mutex{}
var waveObjMap = make(map[string]*waveObjDesc)
var waveObj WaveObj
var waveObjRType = reflect.TypeOf(&waveObj).Elem()
func RegisterType(w WaveObj) {
globalLock.Lock()
defer globalLock.Unlock()
oidType := w.GetOType()
if waveObjMap[oidType] != nil {
panic(fmt.Sprintf("duplicate WaveObj registration: %T", w))
}
rtype := reflect.TypeOf(w)
field := findOIDField(rtype)
if field == nil {
panic(fmt.Sprintf("cannot register WaveObj without OID field -- mark with tag `waveobj:\"oid\"`"))
}
waveObjMap[oidType] = &waveObjDesc{
RType: rtype,
OIDField: *field,
}
}
func findOIDField(rtype reflect.Type) *reflect.StructField {
for idx := 0; idx < rtype.NumField(); idx++ {
field := rtype.Field(idx)
if field.PkgPath != "" {
// private
continue
}
waveObjTag := field.Tag.Get("waveobj")
if waveObjTag == "oid" {
if field.Type.Kind() != reflect.String {
panic(fmt.Sprintf("in %v marked oid field is not type 'string'", rtype))
}
return &field
}
}
return nil
}
func getObjDescForOIDType(oidType string) *waveObjDesc {
globalLock.Lock()
defer globalLock.Unlock()
return waveObjMap[oidType]
}
type WaveObj interface { type WaveObj interface {
GetOType() string GetOType() string // should not depend on object state (should work with nil value)
}
type waveObjDesc struct {
RType reflect.Type
OIDField reflect.StructField
VersionField reflect.StructField
}
var waveObjMap = sync.Map{}
var waveObjRType = reflect.TypeOf((*WaveObj)(nil)).Elem()
func RegisterType[T WaveObj]() {
var waveObj T
otype := waveObj.GetOType()
if otype == "" {
panic(fmt.Sprintf("otype is empty for %T", waveObj))
}
rtype := reflect.TypeOf(waveObj)
if rtype.Kind() != reflect.Ptr {
panic(fmt.Sprintf("wave object must be a pointer for %T", waveObj))
}
oidField, found := rtype.Elem().FieldByName(OIDGoFieldName)
if !found {
panic(fmt.Sprintf("missing OID field for %T", waveObj))
}
if oidField.Type.Kind() != reflect.String {
panic(fmt.Sprintf("OID field must be string for %T", waveObj))
}
if oidField.Tag.Get("json") != OIDKeyName {
panic(fmt.Sprintf("OID field json tag must be %q for %T", OIDKeyName, waveObj))
}
versionField, found := rtype.Elem().FieldByName(VersionGoFieldName)
if !found {
panic(fmt.Sprintf("missing Version field for %T", waveObj))
}
if versionField.Type.Kind() != reflect.Int {
panic(fmt.Sprintf("Version field must be int for %T", waveObj))
}
if versionField.Tag.Get("json") != VersionKeyName {
panic(fmt.Sprintf("Version field json tag must be %q for %T", VersionKeyName, waveObj))
}
_, found = waveObjMap.Load(otype)
if found {
panic(fmt.Sprintf("otype %q already registered", otype))
}
waveObjMap.Store(otype, &waveObjDesc{
RType: rtype,
OIDField: oidField,
VersionField: versionField,
})
}
func getWaveObjDesc(otype string) *waveObjDesc {
desc, _ := waveObjMap.Load(otype)
if desc == nil {
return nil
}
return desc.(*waveObjDesc)
}
func GetOID(waveObj WaveObj) string {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return ""
}
return reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.OIDField.Index).String()
}
func SetOID(waveObj WaveObj, oid string) {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return
}
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.OIDField.Index).SetString(oid)
}
func GetVersion(waveObj WaveObj) int {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return 0
}
return int(reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).Int())
}
func SetVersion(waveObj WaveObj, version int) {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return
}
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).SetInt(int64(version))
} }
func ToJson(w WaveObj) ([]byte, error) { func ToJson(w WaveObj) ([]byte, error) {
m := make(map[string]any) m := make(map[string]any)
err := mapstructure.Decode(w, &m) dconfig := &mapstructure.DecoderConfig{
Result: &m,
TagName: "json",
}
decoder, err := mapstructure.NewDecoder(dconfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
desc := getObjDescForOIDType(w.GetOType()) err = decoder.Decode(w)
if desc == nil { if err != nil {
return nil, fmt.Errorf("otype %q (%T) not registered", w.GetOType(), w) return nil, err
} }
m[OTypeKeyName] = w.GetOType() m[OTypeKeyName] = w.GetOType()
m[OIDKeyName] = reflect.ValueOf(w).FieldByIndex(desc.OIDField.Index).String()
return json.Marshal(m) return json.Marshal(m)
} }
@@ -100,23 +145,24 @@ func FromJson(data []byte) (WaveObj, error) {
if !ok { if !ok {
return nil, fmt.Errorf("missing otype") return nil, fmt.Errorf("missing otype")
} }
oid, ok := m[OIDKeyName].(string) desc := getWaveObjDesc(otype)
if !ok {
return nil, fmt.Errorf("missing oid")
}
desc := getObjDescForOIDType(otype)
if desc == nil { if desc == nil {
return nil, fmt.Errorf("unknown oid type: %s", otype) return nil, fmt.Errorf("unknown otype: %s", otype)
} }
objVal := reflect.New(desc.RType) wobj := reflect.Zero(desc.RType).Interface().(WaveObj)
oidField := objVal.FieldByIndex(desc.OIDField.Index) dconfig := &mapstructure.DecoderConfig{
oidField.SetString(oid) Result: &wobj,
obj := objVal.Interface().(WaveObj) TagName: "json",
err = mapstructure.Decode(m, obj) }
decoder, err := mapstructure.NewDecoder(dconfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return obj, nil err = decoder.Decode(m)
if err != nil {
return nil, err
}
return wobj, nil
} }
func FromJsonGen[T WaveObj](data []byte) (T, error) { func FromJsonGen[T WaveObj](data []byte) (T, error) {
@@ -204,9 +250,12 @@ func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) {
var buf bytes.Buffer var buf bytes.Buffer
waveObjType := reflect.TypeOf((*WaveObj)(nil)).Elem() waveObjType := reflect.TypeOf((*WaveObj)(nil)).Elem()
buf.WriteString(fmt.Sprintf("type %s = {\n", rtype.Name())) buf.WriteString(fmt.Sprintf("type %s = {\n", rtype.Name()))
var isWaveObj bool
if rtype.Implements(waveObjType) || reflect.PointerTo(rtype).Implements(waveObjType) { if rtype.Implements(waveObjType) || reflect.PointerTo(rtype).Implements(waveObjType) {
isWaveObj = true
buf.WriteString(fmt.Sprintf(" %s: string;\n", OTypeKeyName)) buf.WriteString(fmt.Sprintf(" %s: string;\n", OTypeKeyName))
buf.WriteString(fmt.Sprintf(" %s: string;\n", OIDKeyName)) buf.WriteString(fmt.Sprintf(" %s: string;\n", OIDKeyName))
buf.WriteString(fmt.Sprintf(" %s: number;\n", VersionKeyName))
} }
var subTypes []reflect.Type var subTypes []reflect.Type
for i := 0; i < rtype.NumField(); i++ { for i := 0; i < rtype.NumField(); i++ {
@@ -218,6 +267,9 @@ func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) {
if fieldName == "" { if fieldName == "" {
continue continue
} }
if isWaveObj && (fieldName == OTypeKeyName || fieldName == OIDKeyName || fieldName == VersionKeyName) {
continue
}
optMarker := "" optMarker := ""
if isFieldOmitEmpty(field) { if isFieldOmitEmpty(field) {
optMarker = "?" optMarker = "?"
+10 -3
View File
@@ -7,16 +7,23 @@ import (
"log" "log"
"reflect" "reflect"
"testing" "testing"
"github.com/wavetermdev/thenextwave/pkg/wstore"
) )
type TestBlock struct {
BlockId string `json:"blockid" waveobj:"oid"`
Name string `json:"name"`
}
func (TestBlock) GetOType() string {
return "block"
}
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
log.Printf("Testing Generate\n") log.Printf("Testing Generate\n")
tsMap := make(map[reflect.Type]string) tsMap := make(map[reflect.Type]string)
var waveObj WaveObj var waveObj WaveObj
GenerateTSType(reflect.TypeOf(&waveObj).Elem(), tsMap) GenerateTSType(reflect.TypeOf(&waveObj).Elem(), tsMap)
GenerateTSType(reflect.TypeOf(wstore.Block{}), tsMap) GenerateTSType(reflect.TypeOf(TestBlock{}), tsMap)
for k, v := range tsMap { for k, v := range tsMap {
log.Printf("Type: %v, TS:\n%s\n", k, v) log.Printf("Type: %v, TS:\n%s\n", k, v)
} }
+46 -60
View File
@@ -6,31 +6,41 @@ package wstore
import ( import (
"context" "context"
"fmt" "fmt"
"sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/wavetermdev/thenextwave/pkg/shellexec" "github.com/wavetermdev/thenextwave/pkg/shellexec"
"github.com/wavetermdev/thenextwave/pkg/util/ds" "github.com/wavetermdev/thenextwave/pkg/util/ds"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
) )
var WorkspaceMap = ds.NewSyncMap[*Workspace]() var WorkspaceMap = ds.NewSyncMap[*Workspace]()
var TabMap = ds.NewSyncMap[*Tab]() var TabMap = ds.NewSyncMap[*Tab]()
var BlockMap = ds.NewSyncMap[*Block]() var BlockMap = ds.NewSyncMap[*Block]()
func init() {
waveobj.RegisterType[*Client]()
waveobj.RegisterType[*Window]()
waveobj.RegisterType[*Workspace]()
waveobj.RegisterType[*Tab]()
waveobj.RegisterType[*Block]()
}
type Client struct { type Client struct {
ClientId string `json:"clientid"` OID string `json:"oid"`
Version int `json:"version"`
MainWindowId string `json:"mainwindowid"` MainWindowId string `json:"mainwindowid"`
} }
func (c Client) GetId() string { func (*Client) GetOType() string {
return c.ClientId return "client"
} }
// stores the ui-context of the window // stores the ui-context of the window
// workspaceid, active tab, active block within each tab, window size, etc. // workspaceid, active tab, active block within each tab, window size, etc.
type Window struct { type Window struct {
WindowId string `json:"windowid"` OID string `json:"oid"`
Version int `json:"version"`
WorkspaceId string `json:"workspaceid"` WorkspaceId string `json:"workspaceid"`
ActiveTabId string `json:"activetabid"` ActiveTabId string `json:"activetabid"`
ActiveBlockMap map[string]string `json:"activeblockmap"` // map from tabid to blockid ActiveBlockMap map[string]string `json:"activeblockmap"` // map from tabid to blockid
@@ -39,42 +49,30 @@ type Window struct {
LastFocusTs int64 `json:"lastfocusts"` LastFocusTs int64 `json:"lastfocusts"`
} }
func (w Window) GetId() string { func (*Window) GetOType() string {
return w.WindowId return "window"
} }
type Workspace struct { type Workspace struct {
Lock *sync.Mutex `json:"-"` OID string `json:"oid"`
WorkspaceId string `json:"workspaceid"` Version int `json:"version"`
Name string `json:"name"` Name string `json:"name"`
TabIds []string `json:"tabids"` TabIds []string `json:"tabids"`
} }
func (ws Workspace) GetId() string { func (*Workspace) GetOType() string {
return ws.WorkspaceId return "workspace"
}
func (ws *Workspace) WithLock(f func()) {
ws.Lock.Lock()
defer ws.Lock.Unlock()
f()
} }
type Tab struct { type Tab struct {
Lock *sync.Mutex `json:"-"` OID string `json:"oid"`
TabId string `json:"tabid"` Version int `json:"version"`
Name string `json:"name"` Name string `json:"name"`
BlockIds []string `json:"blockids"` BlockIds []string `json:"blockids"`
} }
func (tab Tab) GetId() string { func (*Tab) GetOType() string {
return tab.TabId return "tab"
}
func (tab *Tab) WithLock(f func()) {
tab.Lock.Lock()
defer tab.Lock.Unlock()
f()
} }
type FileDef struct { type FileDef struct {
@@ -108,7 +106,8 @@ type WinSize struct {
} }
type Block struct { type Block struct {
BlockId string `json:"blockid"` OID string `json:"oid"`
Version int `json:"version"`
BlockDef *BlockDef `json:"blockdef"` BlockDef *BlockDef `json:"blockdef"`
Controller string `json:"controller"` Controller string `json:"controller"`
View string `json:"view"` View string `json:"view"`
@@ -116,45 +115,32 @@ type Block struct {
RuntimeOpts *RuntimeOpts `json:"runtimeopts,omitempty"` RuntimeOpts *RuntimeOpts `json:"runtimeopts,omitempty"`
} }
func (b *Block) GetOType() string { func (*Block) GetOType() string {
return "block" return "block"
} }
func (b Block) GetId() string {
return b.BlockId
}
// TODO remove
func (b *Block) WithLock(f func()) {
f()
}
func CreateTab(workspaceId string, name string) (*Tab, error) { func CreateTab(workspaceId string, name string) (*Tab, error) {
tab := &Tab{ tab := &Tab{
Lock: &sync.Mutex{}, OID: uuid.New().String(),
TabId: uuid.New().String(),
Name: name, Name: name,
BlockIds: []string{}, BlockIds: []string{},
} }
TabMap.Set(tab.TabId, tab) TabMap.Set(tab.OID, tab)
ws := WorkspaceMap.Get(workspaceId) ws := WorkspaceMap.Get(workspaceId)
if ws == nil { if ws == nil {
return nil, fmt.Errorf("workspace not found: %q", workspaceId) return nil, fmt.Errorf("workspace not found: %q", workspaceId)
} }
ws.WithLock(func() { ws.TabIds = append(ws.TabIds, tab.OID)
ws.TabIds = append(ws.TabIds, tab.TabId)
})
return tab, nil return tab, nil
} }
func CreateWorkspace() (*Workspace, error) { func CreateWorkspace() (*Workspace, error) {
ws := &Workspace{ ws := &Workspace{
Lock: &sync.Mutex{}, OID: uuid.New().String(),
WorkspaceId: uuid.New().String(), TabIds: []string{},
TabIds: []string{},
} }
WorkspaceMap.Set(ws.WorkspaceId, ws) WorkspaceMap.Set(ws.OID, ws)
_, err := CreateTab(ws.WorkspaceId, "Tab 1") _, err := CreateTab(ws.OID, "Tab 1")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -164,7 +150,7 @@ func CreateWorkspace() (*Workspace, error) {
func EnsureInitialData() error { func EnsureInitialData() error {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn() defer cancelFn()
clientCount, err := DBGetCount[Client](ctx) clientCount, err := DBGetCount[*Client](ctx)
if err != nil { if err != nil {
return fmt.Errorf("error getting client count: %w", err) return fmt.Errorf("error getting client count: %w", err)
} }
@@ -175,7 +161,7 @@ func EnsureInitialData() error {
workspaceId := uuid.New().String() workspaceId := uuid.New().String()
tabId := uuid.New().String() tabId := uuid.New().String()
client := &Client{ client := &Client{
ClientId: uuid.New().String(), OID: uuid.New().String(),
MainWindowId: windowId, MainWindowId: windowId,
} }
err = DBInsert(ctx, client) err = DBInsert(ctx, client)
@@ -183,7 +169,7 @@ func EnsureInitialData() error {
return fmt.Errorf("error inserting client: %w", err) return fmt.Errorf("error inserting client: %w", err)
} }
window := &Window{ window := &Window{
WindowId: windowId, OID: windowId,
WorkspaceId: workspaceId, WorkspaceId: workspaceId,
ActiveTabId: tabId, ActiveTabId: tabId,
ActiveBlockMap: make(map[string]string), ActiveBlockMap: make(map[string]string),
@@ -201,16 +187,16 @@ func EnsureInitialData() error {
return fmt.Errorf("error inserting window: %w", err) return fmt.Errorf("error inserting window: %w", err)
} }
ws := &Workspace{ ws := &Workspace{
WorkspaceId: workspaceId, OID: workspaceId,
Name: "default", Name: "default",
TabIds: []string{tabId}, TabIds: []string{tabId},
} }
err = DBInsert(ctx, ws) err = DBInsert(ctx, ws)
if err != nil { if err != nil {
return fmt.Errorf("error inserting workspace: %w", err) return fmt.Errorf("error inserting workspace: %w", err)
} }
tab := &Tab{ tab := &Tab{
TabId: uuid.New().String(), OID: uuid.New().String(),
Name: "Tab 1", Name: "Tab 1",
BlockIds: []string{}, BlockIds: []string{},
} }
+75 -106
View File
@@ -6,155 +6,124 @@ package wstore
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
) )
const Table_Client = "db_client" func waveObjTableName(w waveobj.WaveObj) string {
const Table_Workspace = "db_workspace" return "db_" + w.GetOType()
const Table_Tab = "db_tab"
const Table_Block = "db_block"
const Table_Window = "db_window"
// can replace with struct tags in the future
type ObjectWithId interface {
GetId() string
} }
// can replace these with struct tags in the future func tableNameGen[T waveobj.WaveObj]() string {
var idColumnName = map[string]string{ var zeroObj T
Table_Client: "clientid", return "db_" + zeroObj.GetOType()
Table_Workspace: "workspaceid",
Table_Tab: "tabid",
Table_Block: "blockid",
Table_Window: "windowid",
} }
var tableToType = map[string]reflect.Type{ func DBGetCount[T waveobj.WaveObj](ctx context.Context) (int, error) {
Table_Client: reflect.TypeOf(Client{}),
Table_Workspace: reflect.TypeOf(Workspace{}),
Table_Tab: reflect.TypeOf(Tab{}),
Table_Block: reflect.TypeOf(Block{}),
Table_Window: reflect.TypeOf(Window{}),
}
var typeToTable map[reflect.Type]string
func init() {
typeToTable = make(map[reflect.Type]string)
for k, v := range tableToType {
typeToTable[v] = k
}
}
func DBGetCount[T ObjectWithId](ctx context.Context) (int, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (int, error) { return WithTxRtn(ctx, func(tx *TxWrap) (int, error) {
var valInstance T table := tableNameGen[T]()
table := typeToTable[reflect.TypeOf(valInstance)]
if table == "" {
return 0, fmt.Errorf("unknown table type: %T", valInstance)
}
query := fmt.Sprintf("SELECT count(*) FROM %s", table) query := fmt.Sprintf("SELECT count(*) FROM %s", table)
return tx.GetInt(query), nil return tx.GetInt(query), nil
}) })
} }
func DBGetSingleton[T ObjectWithId](ctx context.Context) (*T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*T, error) {
var rtn T
query := fmt.Sprintf("SELECT data FROM %s LIMIT 1", typeToTable[reflect.TypeOf(rtn)])
jsonData := tx.GetString(query)
return TxReadJson[T](tx, jsonData), nil
})
}
func DBGet[T ObjectWithId](ctx context.Context, id string) (*T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*T, error) {
var rtn T
table := typeToTable[reflect.TypeOf(rtn)]
if table == "" {
return nil, fmt.Errorf("unknown table type: %T", rtn)
}
query := fmt.Sprintf("SELECT data FROM %s WHERE %s = ?", table, idColumnName[table])
jsonData := tx.GetString(query, id)
return TxReadJson[T](tx, jsonData), nil
})
}
type idDataType struct { type idDataType struct {
Id string OId string
Data string Version int
Data []byte
} }
func DBSelectMap[T ObjectWithId](ctx context.Context, ids []string) (map[string]*T, error) { func DBGetSingleton[T waveobj.WaveObj](ctx context.Context) (T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (map[string]*T, error) { return WithTxRtn(ctx, func(tx *TxWrap) (T, error) {
var valInstance T table := tableNameGen[T]()
table := typeToTable[reflect.TypeOf(valInstance)] query := fmt.Sprintf("SELECT oid, version, data FROM %s LIMIT 1", table)
if table == "" { var row idDataType
return nil, fmt.Errorf("unknown table type: %T", &valInstance) tx.Get(&row, query)
rtn, err := waveobj.FromJsonGen[T](row.Data)
if err != nil {
return rtn, err
} }
waveobj.SetVersion(rtn, row.Version)
return rtn, nil
})
}
func DBGet[T waveobj.WaveObj](ctx context.Context, id string) (T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (T, error) {
table := tableNameGen[T]()
query := fmt.Sprintf("SELECT oid, version, data FROM %s WHERE oid = ?", table)
var row idDataType
tx.Get(&row, query, id)
rtn, err := waveobj.FromJsonGen[T](row.Data)
if err != nil {
return rtn, err
}
waveobj.SetVersion(rtn, row.Version)
return rtn, nil
})
}
func DBSelectMap[T waveobj.WaveObj](ctx context.Context, ids []string) (map[string]T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (map[string]T, error) {
table := tableNameGen[T]()
var rows []idDataType var rows []idDataType
query := fmt.Sprintf("SELECT %s, data FROM %s WHERE %s IN (SELECT value FROM json_each(?))", idColumnName[table], table, idColumnName[table]) query := fmt.Sprintf("SELECT oid, version, data FROM %s WHERE oid IN (SELECT value FROM json_each(?))", table)
tx.Select(&rows, query, ids) tx.Select(&rows, query, ids)
rtnMap := make(map[string]*T) rtnMap := make(map[string]T)
for _, row := range rows { for _, row := range rows {
if row.Id == "" || row.Data == "" { if row.OId == "" || len(row.Data) == 0 {
continue continue
} }
r := TxReadJson[T](tx, row.Data) waveObj, err := waveobj.FromJsonGen[T](row.Data)
if r == nil { if err != nil {
continue return nil, err
} }
rtnMap[(*r).GetId()] = r waveobj.SetVersion(waveObj, row.Version)
rtnMap[row.OId] = waveObj
} }
return rtnMap, nil return rtnMap, nil
}) })
} }
func DBDelete[T ObjectWithId](ctx context.Context, id string) error { func DBDelete[T waveobj.WaveObj](ctx context.Context, id string) error {
return WithTx(ctx, func(tx *TxWrap) error { return WithTx(ctx, func(tx *TxWrap) error {
var rtn T table := tableNameGen[T]()
table := typeToTable[reflect.TypeOf(rtn)] query := fmt.Sprintf("DELETE FROM %s WHERE oid = ?", table)
if table == "" {
return fmt.Errorf("unknown table type: %T", rtn)
}
query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", table, idColumnName[table])
tx.Exec(query, id) tx.Exec(query, id)
return nil return nil
}) })
} }
func DBUpdate[T ObjectWithId](ctx context.Context, val *T) error { func DBUpdate(ctx context.Context, val waveobj.WaveObj) error {
if val == nil { oid := waveobj.GetOID(val)
return fmt.Errorf("cannot update nil value") if oid == "" {
}
if (*val).GetId() == "" {
return fmt.Errorf("cannot update %T value with empty id", val) return fmt.Errorf("cannot update %T value with empty id", val)
} }
jsonData, err := waveobj.ToJson(val)
if err != nil {
return err
}
return WithTx(ctx, func(tx *TxWrap) error { return WithTx(ctx, func(tx *TxWrap) error {
table := typeToTable[reflect.TypeOf(*val)] table := waveObjTableName(val)
if table == "" { query := fmt.Sprintf("UPDATE %s SET data = ?, version = version+1 WHERE oid = ?", table)
return fmt.Errorf("unknown table type: %T", *val) tx.Exec(query, jsonData, oid)
}
query := fmt.Sprintf("UPDATE %s SET data = ? WHERE %s = ?", table, idColumnName[table])
tx.Exec(query, TxJson(tx, val), (*val).GetId())
return nil return nil
}) })
} }
func DBInsert[T ObjectWithId](ctx context.Context, val *T) error { func DBInsert[T waveobj.WaveObj](ctx context.Context, val T) error {
if val == nil { oid := waveobj.GetOID(val)
return fmt.Errorf("cannot insert nil value") if oid == "" {
}
if (*val).GetId() == "" {
return fmt.Errorf("cannot insert %T value with empty id", val) return fmt.Errorf("cannot insert %T value with empty id", val)
} }
jsonData, err := waveobj.ToJson(val)
if err != nil {
return err
}
return WithTx(ctx, func(tx *TxWrap) error { return WithTx(ctx, func(tx *TxWrap) error {
table := typeToTable[reflect.TypeOf(*val)] table := waveObjTableName(val)
if table == "" { query := fmt.Sprintf("INSERT INTO %s (oid, version, data) VALUES (?, ?, ?)", table)
return fmt.Errorf("unknown table type: %T", *val) tx.Exec(query, oid, 1, jsonData)
}
query := fmt.Sprintf("INSERT INTO %s (%s, data) VALUES (?, ?)", table, idColumnName[table])
tx.Exec(query, (*val).GetId(), TxJson(tx, val))
return nil return nil
}) })
} }
-22
View File
@@ -5,7 +5,6 @@ package wstore
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"path" "path"
@@ -63,24 +62,3 @@ func WithTx(ctx context.Context, fn func(tx *TxWrap) error) error {
func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT, error) { func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT, error) {
return txwrap.WithTxRtn(ctx, globalDB, fn) return txwrap.WithTxRtn(ctx, globalDB, fn)
} }
func TxJson(tx *TxWrap, v any) string {
barr, err := json.Marshal(v)
if err != nil {
tx.SetErr(fmt.Errorf("json marshal (%T): %w", v, err))
return ""
}
return string(barr)
}
func TxReadJson[T any](tx *TxWrap, jsonData string) *T {
if jsonData == "" {
return nil
}
var v T
err := json.Unmarshal([]byte(jsonData), &v)
if err != nil {
tx.SetErr(fmt.Errorf("json unmarshal (%T): %w", v, err))
}
return &v
}