diff --git a/frontend/src/components/ExplorerCard/ExplorerCard.tsx b/frontend/src/components/ExplorerCard/ExplorerCard.tsx index a7e18e2c79..a8d2ff3424 100644 --- a/frontend/src/components/ExplorerCard/ExplorerCard.tsx +++ b/frontend/src/components/ExplorerCard/ExplorerCard.tsx @@ -199,12 +199,12 @@ function ExplorerCard({ value={viewName || undefined} > {viewsData?.data.data.map((view) => ( - + { const currentViewDetails = getViewDetailsUsingViewKey(key, viewData); if (!currentViewDetails) return; - const { - query, - name, - uuid, - panelType: currentPanelType, - } = currentViewDetails; + const { query, name, id, panelType: currentPanelType } = currentViewDetails; handleExplorerTabChange(currentPanelType, { query, name, - uuid, + id, }); }, [viewData, handleExplorerTabChange], diff --git a/frontend/src/components/ExplorerCard/__mock__/viewData.ts b/frontend/src/components/ExplorerCard/__mock__/viewData.ts index 5423cc98dc..9ba3bcdfd9 100644 --- a/frontend/src/components/ExplorerCard/__mock__/viewData.ts +++ b/frontend/src/components/ExplorerCard/__mock__/viewData.ts @@ -4,7 +4,7 @@ import { DataSource } from 'types/common/queryBuilder'; export const viewMockData: ViewProps[] = [ { - uuid: 'view1', + id: 'view1', name: 'View 1', createdBy: 'User 1', category: 'category 1', @@ -17,7 +17,7 @@ export const viewMockData: ViewProps[] = [ updatedBy: 'User 1', }, { - uuid: 'view2', + id: 'view2', name: 'View 2', createdBy: 'User 2', category: 'category 2', diff --git a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx index c869024f19..5fbd7b73f2 100644 --- a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx +++ b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx @@ -25,9 +25,9 @@ describe('MenuItemGenerator', () => { { { - const selectedView = data?.find((view) => view.uuid === viewKey); + const selectedView = data?.find((view) => view.id === viewKey); if (selectedView) { - const { compositeQuery, name, uuid, extraData } = selectedView; + const { compositeQuery, name, id, extraData } = selectedView; const query = mapQueryDataFromApi(compositeQuery); - return { query, name, uuid, panelType: compositeQuery.panelType, extraData }; + return { query, name, id, panelType: compositeQuery.panelType, extraData }; } return undefined; }; diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx index 6e0e9c57bd..3090babe1d 100644 --- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx +++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx @@ -223,7 +223,7 @@ function ExplorerOptions({ const viewName = useGetSearchQueryParam(QueryParams.viewName) || ''; const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || ''; - const extraData = viewsData?.data?.data?.find((view) => view.uuid === viewKey) + const extraData = viewsData?.data?.data?.find((view) => view.id === viewKey) ?.extraData; const extraDataColor = extraData ? JSON.parse(extraData).color : ''; @@ -357,17 +357,12 @@ function ExplorerOptions({ viewsData?.data?.data, ); if (!currentViewDetails) return; - const { - query, - name, - uuid, - panelType: currentPanelType, - } = currentViewDetails; + const { query, name, id, panelType: currentPanelType } = currentViewDetails; handleExplorerTabChange(currentPanelType, { query, name, - uuid, + id, }); }, [viewsData, handleExplorerTabChange], @@ -694,7 +689,7 @@ function ExplorerOptions({ bgColor = extraData.color; } return ( - +
{ logEvent('Homepage: Saved view clicked', { - viewId: view.uuid, + viewId: view.id, viewName: view.name, entity: selectedEntity, }); const currentViewDetails = getViewDetailsUsingViewKey( - view.uuid, + view.id, selectedEntity === 'logs' ? logsViews : tracesViews, ); if (!currentViewDetails) return; - const { query, name, uuid, panelType: currentPanelType } = currentViewDetails; + const { query, name, id, panelType: currentPanelType } = currentViewDetails; if (selectedEntity) { handleExplorerTabChange( @@ -81,7 +81,7 @@ export default function SavedViews({ { query, name, - uuid, + id, }, SOURCEPAGE_VS_ROUTES[selectedEntity], ); diff --git a/frontend/src/hooks/useHandleExplorerTabChange.ts b/frontend/src/hooks/useHandleExplorerTabChange.ts index f3b3000cfc..cf06337867 100644 --- a/frontend/src/hooks/useHandleExplorerTabChange.ts +++ b/frontend/src/hooks/useHandleExplorerTabChange.ts @@ -70,7 +70,7 @@ export const useHandleExplorerTabChange = (): { { [QueryParams.panelTypes]: newPanelType, [QueryParams.viewName]: currentQueryData?.name || viewName, - [QueryParams.viewKey]: currentQueryData?.uuid || viewKey, + [QueryParams.viewKey]: currentQueryData?.id || viewKey, }, redirectToUrl, ); @@ -78,7 +78,7 @@ export const useHandleExplorerTabChange = (): { redirectWithQueryBuilderData(query, { [QueryParams.panelTypes]: newPanelType, [QueryParams.viewName]: currentQueryData?.name || viewName, - [QueryParams.viewKey]: currentQueryData?.uuid || viewKey, + [QueryParams.viewKey]: currentQueryData?.id || viewKey, }); } }, @@ -90,6 +90,6 @@ export const useHandleExplorerTabChange = (): { interface ICurrentQueryData { name: string; - uuid: string; + id: string; query: Query; } diff --git a/frontend/src/mocks-server/__mockdata__/explorer_views.ts b/frontend/src/mocks-server/__mockdata__/explorer_views.ts index 4719e77697..a81d538405 100644 --- a/frontend/src/mocks-server/__mockdata__/explorer_views.ts +++ b/frontend/src/mocks-server/__mockdata__/explorer_views.ts @@ -2,7 +2,7 @@ export const explorerView = { status: 'success', data: [ { - uuid: 'test-uuid-1', + id: 'test-uuid-1', name: 'Table View', category: '', createdAt: '2023-08-29T18:04:10.906310033Z', @@ -78,7 +78,7 @@ export const explorerView = { extraData: '{"color":"#00ffd0"}', }, { - uuid: '8c4bf492-d54d-4ab2-a8d6-9c1563f46e1f', + id: '8c4bf492-d54d-4ab2-a8d6-9c1563f46e1f', name: 'R-test panel', category: '', createdAt: '2024-07-01T13:45:57.924686766Z', diff --git a/frontend/src/pages/SaveView/index.tsx b/frontend/src/pages/SaveView/index.tsx index 9b2bbd0eca..e470cf3013 100644 --- a/frontend/src/pages/SaveView/index.tsx +++ b/frontend/src/pages/SaveView/index.tsx @@ -81,7 +81,7 @@ function SaveView(): JSX.Element { }; const handleEditModelOpen = (view: ViewProps, color: string): void => { - setActiveViewKey(view.uuid); + setActiveViewKey(view.id); setColor(color); setActiveViewName(view.name); setNewViewName(view.name); @@ -188,11 +188,11 @@ function SaveView(): JSX.Element { const handleRedirectQuery = (view: ViewProps): void => { const currentViewDetails = getViewDetailsUsingViewKey( - view.uuid, + view.id, viewsData?.data.data, ); if (!currentViewDetails) return; - const { query, name, uuid, panelType: currentPanelType } = currentViewDetails; + const { query, name, id, panelType: currentPanelType } = currentViewDetails; if (sourcepage) { handleExplorerTabChange( @@ -200,7 +200,7 @@ function SaveView(): JSX.Element { { query, name, - uuid, + id, }, SOURCEPAGE_VS_ROUTES[sourcepage], ); @@ -258,7 +258,7 @@ function SaveView(): JSX.Element { className={isEditDeleteSupported ? '' : 'hidden'} color={Color.BG_CHERRY_500} data-testid="delete-view" - onClick={(): void => handleDeleteModelOpen(view.uuid, view.name)} + onClick={(): void => handleDeleteModelOpen(view.id, view.name)} />
diff --git a/frontend/src/types/api/saveViews/types.ts b/frontend/src/types/api/saveViews/types.ts index b0be0110a6..c5f3c9c60e 100644 --- a/frontend/src/types/api/saveViews/types.ts +++ b/frontend/src/types/api/saveViews/types.ts @@ -3,7 +3,7 @@ import { DataSource } from 'types/common/queryBuilder'; import { ICompositeMetricQuery } from '../alerts/compositeQuery'; export interface ViewProps { - uuid: string; + id: string; name: string; category: string; createdAt: string; diff --git a/pkg/query-service/app/explorer/db.go b/pkg/query-service/app/explorer/db.go index 1feb543a91..461567f042 100644 --- a/pkg/query-service/app/explorer/db.go +++ b/pkg/query-service/app/explorer/db.go @@ -14,7 +14,7 @@ import ( "github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types/authtypes" - "github.com/google/uuid" + "github.com/SigNoz/signoz/pkg/valuer" "go.uber.org/zap" ) @@ -47,7 +47,7 @@ func GetViews(ctx context.Context, orgID string) ([]*v3.SavedView, error) { return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error()) } savedViews = append(savedViews, &v3.SavedView{ - UUID: view.UUID, + ID: view.ID, Name: view.Name, Category: view.Category, CreatedAt: view.CreatedAt, @@ -83,7 +83,7 @@ func GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, na return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error()) } savedViews = append(savedViews, &v3.SavedView{ - UUID: view.UUID, + ID: view.ID, Name: view.Name, CreatedAt: view.CreatedAt, CreatedBy: view.CreatedBy, @@ -98,23 +98,19 @@ func GetViewsForFilters(ctx context.Context, orgID string, sourcePage string, na return savedViews, nil } -func CreateView(ctx context.Context, orgID string, view v3.SavedView) (string, error) { +func CreateView(ctx context.Context, orgID string, view v3.SavedView) (valuer.UUID, error) { data, err := json.Marshal(view.CompositeQuery) if err != nil { - return "", fmt.Errorf("error in marshalling explorer query data: %s", err.Error()) + return valuer.UUID{}, fmt.Errorf("error in marshalling explorer query data: %s", err.Error()) } - uuid_ := view.UUID - - if uuid_ == "" { - uuid_ = uuid.New().String() - } + uuid := valuer.GenerateUUID() createdAt := time.Now() updatedAt := time.Now() claims, ok := authtypes.ClaimsFromContext(ctx) if !ok { - return "", fmt.Errorf("error in getting email from context") + return valuer.UUID{}, fmt.Errorf("error in getting email from context") } createBy := claims.Email @@ -129,8 +125,10 @@ func CreateView(ctx context.Context, orgID string, view v3.SavedView) (string, e CreatedBy: createBy, UpdatedBy: updatedBy, }, - OrgID: orgID, - UUID: uuid_, + OrgID: orgID, + Identifiable: types.Identifiable{ + ID: uuid, + }, Name: view.Name, Category: view.Category, SourcePage: view.SourcePage, @@ -141,14 +139,14 @@ func CreateView(ctx context.Context, orgID string, view v3.SavedView) (string, e _, err = store.BunDB().NewInsert().Model(&dbView).Exec(ctx) if err != nil { - return "", fmt.Errorf("error in creating saved view: %s", err.Error()) + return valuer.UUID{}, fmt.Errorf("error in creating saved view: %s", err.Error()) } - return uuid_, nil + return uuid, nil } -func GetView(ctx context.Context, orgID string, uuid_ string) (*v3.SavedView, error) { +func GetView(ctx context.Context, orgID string, uuid valuer.UUID) (*v3.SavedView, error) { var view types.SavedView - err := store.BunDB().NewSelect().Model(&view).Where("org_id = ? AND uuid = ?", orgID, uuid_).Scan(ctx) + err := store.BunDB().NewSelect().Model(&view).Where("org_id = ? AND id = ?", orgID, uuid.StringValue()).Scan(ctx) if err != nil { return nil, fmt.Errorf("error in getting saved view: %s", err.Error()) } @@ -159,7 +157,7 @@ func GetView(ctx context.Context, orgID string, uuid_ string) (*v3.SavedView, er return nil, fmt.Errorf("error in unmarshalling explorer query data: %s", err.Error()) } return &v3.SavedView{ - UUID: view.UUID, + ID: view.ID, Name: view.Name, Category: view.Category, CreatedAt: view.CreatedAt, @@ -173,7 +171,7 @@ func GetView(ctx context.Context, orgID string, uuid_ string) (*v3.SavedView, er }, nil } -func UpdateView(ctx context.Context, orgID string, uuid_ string, view v3.SavedView) error { +func UpdateView(ctx context.Context, orgID string, uuid valuer.UUID, view v3.SavedView) error { data, err := json.Marshal(view.CompositeQuery) if err != nil { return fmt.Errorf("error in marshalling explorer query data: %s", err.Error()) @@ -191,7 +189,7 @@ func UpdateView(ctx context.Context, orgID string, uuid_ string, view v3.SavedVi Model(&types.SavedView{}). Set("updated_at = ?, updated_by = ?, name = ?, category = ?, source_page = ?, tags = ?, data = ?, extra_data = ?", updatedAt, updatedBy, view.Name, view.Category, view.SourcePage, strings.Join(view.Tags, ","), data, view.ExtraData). - Where("uuid = ?", uuid_). + Where("id = ?", uuid.StringValue()). Where("org_id = ?", orgID). Exec(ctx) if err != nil { @@ -200,10 +198,10 @@ func UpdateView(ctx context.Context, orgID string, uuid_ string, view v3.SavedVi return nil } -func DeleteView(ctx context.Context, orgID string, uuid_ string) error { +func DeleteView(ctx context.Context, orgID string, uuid valuer.UUID) error { _, err := store.BunDB().NewDelete(). Model(&types.SavedView{}). - Where("uuid = ?", uuid_). + Where("id = ?", uuid.StringValue()). Where("org_id = ?", orgID). Exec(ctx) if err != nil { diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 1d63cc0fd4..538c8e1425 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -23,6 +23,7 @@ import ( "github.com/SigNoz/signoz/pkg/http/render" "github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer" "github.com/SigNoz/signoz/pkg/signoz" + "github.com/SigNoz/signoz/pkg/valuer" "github.com/gorilla/mux" "github.com/gorilla/websocket" @@ -4625,12 +4626,18 @@ func (aH *APIHandler) createSavedViews(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) { viewID := mux.Vars(r)["viewId"] + viewUUID, err := valuer.NewUUID(viewID) + if err != nil { + render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())) + return + } + claims, ok := authtypes.ClaimsFromContext(r.Context()) if !ok { render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated")) return } - view, err := explorer.GetView(r.Context(), claims.OrgID, viewID) + view, err := explorer.GetView(r.Context(), claims.OrgID, viewUUID) if err != nil { RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) return @@ -4641,8 +4648,13 @@ func (aH *APIHandler) getSavedView(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) { viewID := mux.Vars(r)["viewId"] + viewUUID, err := valuer.NewUUID(viewID) + if err != nil { + render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())) + return + } var view v3.SavedView - err := json.NewDecoder(r.Body).Decode(&view) + err = json.NewDecoder(r.Body).Decode(&view) if err != nil { RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return @@ -4658,7 +4670,7 @@ func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) { render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated")) return } - err = explorer.UpdateView(r.Context(), claims.OrgID, viewID, view) + err = explorer.UpdateView(r.Context(), claims.OrgID, viewUUID, view) if err != nil { RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) return @@ -4670,12 +4682,17 @@ func (aH *APIHandler) updateSavedView(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) deleteSavedView(w http.ResponseWriter, r *http.Request) { viewID := mux.Vars(r)["viewId"] + viewUUID, err := valuer.NewUUID(viewID) + if err != nil { + render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())) + return + } claims, ok := authtypes.ClaimsFromContext(r.Context()) if !ok { render.Error(w, errorsV2.Newf(errorsV2.TypeUnauthenticated, errorsV2.CodeUnauthenticated, "unauthenticated")) return } - err := explorer.DeleteView(r.Context(), claims.OrgID, viewID) + err = explorer.DeleteView(r.Context(), claims.OrgID, viewUUID) if err != nil { RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil) return diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index d6a8695488..862b89709e 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/google/uuid" + "github.com/SigNoz/signoz/pkg/valuer" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -1412,7 +1412,7 @@ func (p *Point) UnmarshalJSON(data []byte) error { // The source page name is used to identify the page that initiated the query // The source page could be "traces", "logs", "metrics". type SavedView struct { - UUID string `json:"uuid,omitempty"` + ID valuer.UUID `json:"id,omitempty"` Name string `json:"name"` Category string `json:"category"` CreatedAt time.Time `json:"createdAt"` @@ -1432,9 +1432,6 @@ func (eq *SavedView) Validate() error { return fmt.Errorf("composite query is required") } - if eq.UUID == "" { - eq.UUID = uuid.New().String() - } return eq.CompositeQuery.Validate() } diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go index e5b378617d..1aca7ebd9d 100644 --- a/pkg/signoz/provider.go +++ b/pkg/signoz/provider.go @@ -62,6 +62,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM sqlmigration.NewUpdateDashboardAndSavedViewsFactory(sqlstore), sqlmigration.NewUpdatePatAndOrgDomainsFactory(sqlstore), sqlmigration.NewUpdatePipelines(sqlstore), + sqlmigration.NewDropLicensesSitesFactory(sqlstore), ) } diff --git a/pkg/sqlmigration/018_drop_licenses_sites.go b/pkg/sqlmigration/018_drop_licenses_sites.go new file mode 100644 index 0000000000..9ff7225d14 --- /dev/null +++ b/pkg/sqlmigration/018_drop_licenses_sites.go @@ -0,0 +1,62 @@ +package sqlmigration + +import ( + "context" + + "github.com/SigNoz/signoz/pkg/factory" + "github.com/SigNoz/signoz/pkg/sqlstore" + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +type dropLicensesSites struct { + store sqlstore.SQLStore +} + +func NewDropLicensesSitesFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] { + return factory.NewProviderFactory(factory.MustNewName("drop_licenses_sites"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { + return newDropLicensesSites(ctx, ps, c, sqlstore) + }) +} + +func newDropLicensesSites(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) { + return &dropLicensesSites{store: store}, nil +} + +func (migration *dropLicensesSites) Register(migrations *migrate.Migrations) error { + if err := migrations.Register(migration.Up, migration.Down); err != nil { + return err + } + + return nil +} + +func (migration *dropLicensesSites) Up(ctx context.Context, db *bun.DB) error { + tx, err := db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + if _, err := tx.NewDropTable().IfExists().Table("sites").Exec(ctx); err != nil { + return err + } + if _, err := tx.NewDropTable().IfExists().Table("licenses").Exec(ctx); err != nil { + return err + } + + _, err = migration.store.Dialect().RenameColumn(ctx, tx, "saved_views", "uuid", "id") + if err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return err + } + + return nil +} + +func (migration *dropLicensesSites) Down(context.Context, *bun.DB) error { + return nil +} diff --git a/pkg/sqlstore/postgressqlstore/dialect.go b/pkg/sqlstore/postgressqlstore/dialect.go index 0eb1310f29..49a07f160f 100644 --- a/pkg/sqlstore/postgressqlstore/dialect.go +++ b/pkg/sqlstore/postgressqlstore/dialect.go @@ -6,10 +6,10 @@ import ( "github.com/uptrace/bun" ) -type PGDialect struct { +type dialect struct { } -func (dialect *PGDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { columnType, err := dialect.GetColumnType(ctx, bun, table, column) if err != nil { return err @@ -21,16 +21,22 @@ func (dialect *PGDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB } // if the columns is integer then do this - if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil { + if _, err := bun. + ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil { return err } // add new timestamp column - if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " TIMESTAMP").Exec(ctx); err != nil { + if _, err := bun. + NewAddColumn(). + Table(table). + ColumnExpr(column + " TIMESTAMP"). + Exec(ctx); err != nil { return err } - if _, err := bun.NewUpdate(). + if _, err := bun. + NewUpdate(). Table(table). Set(column + " = to_timestamp(cast(" + column + "_old as INTEGER))"). Where("1=1"). @@ -39,14 +45,18 @@ func (dialect *PGDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB } // drop old column - if _, err := bun.NewDropColumn().Table(table).Column(column + "_old").Exec(ctx); err != nil { + if _, err := bun. + NewDropColumn(). + Table(table). + Column(column + "_old"). + Exec(ctx); err != nil { return err } return nil } -func (dialect *PGDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { columnType, err := dialect.GetColumnType(ctx, bun, table, column) if err != nil { return err @@ -56,12 +66,17 @@ func (dialect *PGDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, return nil } - if _, err := bun.ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil { + if _, err := bun. + ExecContext(ctx, `ALTER TABLE `+table+` RENAME COLUMN `+column+` TO `+column+`_old`); err != nil { return err } // add new boolean column - if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " BOOLEAN").Exec(ctx); err != nil { + if _, err := bun. + NewAddColumn(). + Table(table). + ColumnExpr(column + " BOOLEAN"). + Exec(ctx); err != nil { return err } @@ -82,7 +97,7 @@ func (dialect *PGDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, return nil } -func (dialect *PGDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { +func (dialect *dialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { var columnType string err := bun.NewSelect(). @@ -98,7 +113,7 @@ func (dialect *PGDialect) GetColumnType(ctx context.Context, bun bun.IDB, table return columnType, nil } -func (dialect *PGDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { +func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { var count int err := bun.NewSelect(). ColumnExpr("COUNT(*)"). @@ -113,3 +128,26 @@ func (dialect *PGDialect) ColumnExists(ctx context.Context, bun bun.IDB, table s return count > 0, nil } + +func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { + oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) + if err != nil { + return false, err + } + + newColumnExists, err := dialect.ColumnExists(ctx, bun, table, newColumnName) + if err != nil { + return false, err + } + + if !oldColumnExists && newColumnExists { + return true, nil + } + + _, err = bun. + ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) + if err != nil { + return false, err + } + return true, nil +} diff --git a/pkg/sqlstore/postgressqlstore/provider.go b/pkg/sqlstore/postgressqlstore/provider.go index 05ea1d0738..550e40c1e7 100644 --- a/pkg/sqlstore/postgressqlstore/provider.go +++ b/pkg/sqlstore/postgressqlstore/provider.go @@ -18,7 +18,7 @@ type provider struct { sqldb *sql.DB bundb *sqlstore.BunDB sqlxdb *sqlx.DB - dialect *PGDialect + dialect *dialect } func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { @@ -60,7 +60,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqldb: sqldb, bundb: sqlstore.NewBunDB(settings, sqldb, pgdialect.New(), hooks), sqlxdb: sqlx.NewDb(sqldb, "postgres"), - dialect: &PGDialect{}, + dialect: new(dialect), }, nil } diff --git a/pkg/sqlstore/sqlitesqlstore/dialect.go b/pkg/sqlstore/sqlitesqlstore/dialect.go index 8baefe4235..08b87d1585 100644 --- a/pkg/sqlstore/sqlitesqlstore/dialect.go +++ b/pkg/sqlstore/sqlitesqlstore/dialect.go @@ -6,10 +6,10 @@ import ( "github.com/uptrace/bun" ) -type SQLiteDialect struct { +type dialect struct { } -func (dialect *SQLiteDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { columnType, err := dialect.GetColumnType(ctx, bun, table, column) if err != nil { return err @@ -25,12 +25,17 @@ func (dialect *SQLiteDialect) MigrateIntToTimestamp(ctx context.Context, bun bun } // add new timestamp column - if _, err := bun.NewAddColumn().Table(table).ColumnExpr(column + " TIMESTAMP").Exec(ctx); err != nil { + if _, err := bun. + NewAddColumn(). + Table(table). + ColumnExpr(column + " TIMESTAMP"). + Exec(ctx); err != nil { return err } // copy data from old column to new column, converting from int (unix timestamp) to timestamp - if _, err := bun.NewUpdate(). + if _, err := bun. + NewUpdate(). Table(table). Set(column + " = datetime(" + column + "_old, 'unixepoch')"). Where("1=1"). @@ -46,7 +51,7 @@ func (dialect *SQLiteDialect) MigrateIntToTimestamp(ctx context.Context, bun bun return nil } -func (dialect *SQLiteDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { columnType, err := dialect.GetColumnType(ctx, bun, table, column) if err != nil { return err @@ -66,7 +71,8 @@ func (dialect *SQLiteDialect) MigrateIntToBoolean(ctx context.Context, bun bun.I } // copy data from old column to new column, converting from int to boolean - if _, err := bun.NewUpdate(). + if _, err := bun. + NewUpdate(). Table(table). Set(column + " = CASE WHEN " + column + "_old = 1 THEN true ELSE false END"). Where("1=1"). @@ -82,10 +88,11 @@ func (dialect *SQLiteDialect) MigrateIntToBoolean(ctx context.Context, bun bun.I return nil } -func (dialect *SQLiteDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { +func (dialect *dialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { var columnType string - err := bun.NewSelect(). + err := bun. + NewSelect(). ColumnExpr("type"). TableExpr("pragma_table_info(?)", table). Where("name = ?", column). @@ -97,7 +104,7 @@ func (dialect *SQLiteDialect) GetColumnType(ctx context.Context, bun bun.IDB, ta return columnType, nil } -func (dialect *SQLiteDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { +func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { var count int err := bun.NewSelect(). ColumnExpr("COUNT(*)"). @@ -111,3 +118,26 @@ func (dialect *SQLiteDialect) ColumnExists(ctx context.Context, bun bun.IDB, tab return count > 0, nil } + +func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { + oldColumnExists, err := dialect.ColumnExists(ctx, bun, table, oldColumnName) + if err != nil { + return false, err + } + + newColumnExists, err := dialect.ColumnExists(ctx, bun, table, newColumnName) + if err != nil { + return false, err + } + + if !oldColumnExists && newColumnExists { + return true, nil + } + + _, err = bun. + ExecContext(ctx, "ALTER TABLE "+table+" RENAME COLUMN "+oldColumnName+" TO "+newColumnName) + if err != nil { + return false, err + } + return true, nil +} diff --git a/pkg/sqlstore/sqlitesqlstore/provider.go b/pkg/sqlstore/sqlitesqlstore/provider.go index 3ce5bc9a3a..53cdd33fce 100644 --- a/pkg/sqlstore/sqlitesqlstore/provider.go +++ b/pkg/sqlstore/sqlitesqlstore/provider.go @@ -17,7 +17,7 @@ type provider struct { sqldb *sql.DB bundb *sqlstore.BunDB sqlxdb *sqlx.DB - dialect *SQLiteDialect + dialect *dialect } func NewFactory(hookFactories ...factory.ProviderFactory[sqlstore.SQLStoreHook, sqlstore.Config]) factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config] { @@ -50,7 +50,7 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config sqldb: sqldb, bundb: sqlstore.NewBunDB(settings, sqldb, sqlitedialect.New(), hooks), sqlxdb: sqlx.NewDb(sqldb, "sqlite3"), - dialect: &SQLiteDialect{}, + dialect: new(dialect), }, nil } diff --git a/pkg/sqlstore/sqlstore.go b/pkg/sqlstore/sqlstore.go index 2e57da3ec8..4e20e05240 100644 --- a/pkg/sqlstore/sqlstore.go +++ b/pkg/sqlstore/sqlstore.go @@ -37,8 +37,9 @@ type SQLStoreHook interface { } type SQLDialect interface { - MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error - MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error - GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) - ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) + MigrateIntToTimestamp(context.Context, bun.IDB, string, string) error + MigrateIntToBoolean(context.Context, bun.IDB, string, string) error + GetColumnType(context.Context, bun.IDB, string, string) (string, error) + ColumnExists(context.Context, bun.IDB, string, string) (bool, error) + RenameColumn(context.Context, bun.IDB, string, string, string) (bool, error) } diff --git a/pkg/sqlstore/sqlstoretest/dialect.go b/pkg/sqlstore/sqlstoretest/dialect.go index bc4228ef7d..bc0820ec16 100644 --- a/pkg/sqlstore/sqlstoretest/dialect.go +++ b/pkg/sqlstore/sqlstoretest/dialect.go @@ -6,21 +6,25 @@ import ( "github.com/uptrace/bun" ) -type TestDialect struct { +type dialect struct { } -func (dialect *TestDialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToTimestamp(ctx context.Context, bun bun.IDB, table string, column string) error { return nil } -func (dialect *TestDialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { +func (dialect *dialect) MigrateIntToBoolean(ctx context.Context, bun bun.IDB, table string, column string) error { return nil } -func (dialect *TestDialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { +func (dialect *dialect) GetColumnType(ctx context.Context, bun bun.IDB, table string, column string) (string, error) { return "", nil } -func (dialect *TestDialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { +func (dialect *dialect) ColumnExists(ctx context.Context, bun bun.IDB, table string, column string) (bool, error) { return false, nil } + +func (dialect *dialect) RenameColumn(ctx context.Context, bun bun.IDB, table string, oldColumnName string, newColumnName string) (bool, error) { + return true, nil +} diff --git a/pkg/sqlstore/sqlstoretest/provider.go b/pkg/sqlstore/sqlstoretest/provider.go index 4927bc9f01..55567b4df6 100644 --- a/pkg/sqlstore/sqlstoretest/provider.go +++ b/pkg/sqlstore/sqlstoretest/provider.go @@ -19,7 +19,7 @@ type Provider struct { mock sqlmock.Sqlmock bunDB *bun.DB sqlxDB *sqlx.DB - dialect *TestDialect + dialect *dialect } func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider { @@ -43,7 +43,7 @@ func New(config sqlstore.Config, matcher sqlmock.QueryMatcher) *Provider { mock: mock, bunDB: bunDB, sqlxDB: sqlxDB, - dialect: &TestDialect{}, + dialect: new(dialect), } } diff --git a/pkg/types/identity.go b/pkg/types/identity.go new file mode 100644 index 0000000000..3d94b2ee3f --- /dev/null +++ b/pkg/types/identity.go @@ -0,0 +1,9 @@ +package types + +import ( + "github.com/SigNoz/signoz/pkg/valuer" +) + +type Identifiable struct { + ID valuer.UUID `json:"id" bun:"id,pk,type:text"` +} diff --git a/pkg/types/savedview.go b/pkg/types/savedview.go index 3ebc2d9d70..821fe36fb0 100644 --- a/pkg/types/savedview.go +++ b/pkg/types/savedview.go @@ -7,10 +7,10 @@ import ( type SavedView struct { bun.BaseModel `bun:"table:saved_views"` + Identifiable TimeAuditable UserAuditable OrgID string `json:"orgId" bun:"org_id,notnull"` - UUID string `json:"uuid" bun:"uuid,pk,type:text"` Name string `json:"name" bun:"name,type:text,notnull"` Category string `json:"category" bun:"category,type:text,notnull"` SourcePage string `json:"sourcePage" bun:"source_page,type:text,notnull"` diff --git a/pkg/valuer/uuid.go b/pkg/valuer/uuid.go new file mode 100644 index 0000000000..7c9d159b34 --- /dev/null +++ b/pkg/valuer/uuid.go @@ -0,0 +1,120 @@ +package valuer + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "reflect" + + "github.com/google/uuid" +) + +var _ Valuer = (*UUID)(nil) + +type UUID struct { + val uuid.UUID +} + +func NewUUID(value string) (UUID, error) { + val, err := uuid.Parse(value) + if err != nil { + return UUID{}, err + } + + return UUID{ + val: val, + }, nil +} + +func NewUUIDFromBytes(value []byte) (UUID, error) { + val, err := uuid.ParseBytes(value) + if err != nil { + return UUID{}, err + } + + return UUID{ + val: val, + }, nil +} + +func MustNewUUID(val string) UUID { + uuid, err := NewUUID(val) + if err != nil { + panic(err) + } + + return uuid +} + +func GenerateUUID() UUID { + val, err := uuid.NewV7() + if err != nil { + panic(err) + } + + return UUID{ + val: val, + } +} + +func (enum UUID) IsZero() bool { + return enum.val == uuid.UUID{} +} + +func (enum UUID) StringValue() string { + return enum.val.String() +} + +func (enum UUID) MarshalJSON() ([]byte, error) { + return json.Marshal(enum.StringValue()) +} + +func (enum *UUID) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return err + } + + uuid, err := NewUUID(str) + if err != nil { + return err + } + + *enum = uuid + return nil +} + +func (enum UUID) Value() (driver.Value, error) { + return enum.StringValue(), nil +} + +func (enum *UUID) Scan(val interface{}) error { + if enum == nil { + return fmt.Errorf("uuid: (nil \"%s\")", reflect.TypeOf(enum).String()) + } + + if val == nil { + return fmt.Errorf("uuid: (nil \"%s\")", reflect.TypeOf(val).String()) + } + + var enumVal UUID + switch val := val.(type) { + case string: + _enumVal, err := NewUUID(val) + if err != nil { + return fmt.Errorf("uuid: (invalid-uuid \"%s\")", err.Error()) + } + enumVal = _enumVal + case []byte: + _enumVal, err := NewUUIDFromBytes(val) + if err != nil { + return fmt.Errorf("uuid: (invalid-uuid \"%s\")", err.Error()) + } + enumVal = _enumVal + default: + return fmt.Errorf("uuid: (non-uuid \"%s\")", reflect.TypeOf(val).String()) + } + + *enum = enumVal + return nil +} diff --git a/pkg/valuer/valuer.go b/pkg/valuer/valuer.go new file mode 100644 index 0000000000..703d5b20c4 --- /dev/null +++ b/pkg/valuer/valuer.go @@ -0,0 +1,22 @@ +package valuer + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" +) + +type Valuer interface { + // IsZero returns true if the value is considered empty or zero + IsZero() bool + // StringValue returns the string representation of the value + StringValue() string + // MarshalJSON returns the JSON encoding of the value. + json.Marshaler + // UnmarshalJSON returns the JSON decoding of the value. + json.Unmarshaler + // Scan into underlying struct from a database driver's value + sql.Scanner + // Convert the struct to a database driver's value + driver.Valuer +}