feat(sqlmigration): cleanup the licenses and sites table (#7422)

* feat(sqlmigration): added migration for schema cleanup

* feat(sqlmigration): drop sites,licenses table and added uuid v7 for saved views

* feat(sqlmigration): commit the transaction

* feat(sqlmigration): address review comments

* feat(sqlmigration): address review comments

* feat(sqlmigration): frontend changes for saved views

* feat(sqlmigration): frontend changes for saved views

* feat(sqlmigration): frontend changes for saved views

* feat(sqlmigration): frontend changes for saved views

* feat(sqlmigration): frontend changes for saved views
This commit is contained in:
Vikrant Gupta 2025-03-25 04:05:40 +05:30 committed by GitHub
parent 9c25a33cd9
commit 64071165c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 399 additions and 110 deletions

View File

@ -199,12 +199,12 @@ function ExplorerCard({
value={viewName || undefined}
>
{viewsData?.data.data.map((view) => (
<Select.Option key={view.uuid} value={view.name}>
<Select.Option key={view.id} value={view.name}>
<MenuItemGenerator
viewName={view.name}
viewKey={viewKey}
createdBy={view.createdBy}
uuid={view.uuid}
uuid={view.id}
refetchAllView={refetchAllView}
viewData={viewsData.data.data}
sourcePage={sourcepage}

View File

@ -53,17 +53,12 @@ function MenuItemGenerator({
({ key }: { key: string }): void => {
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],

View File

@ -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',

View File

@ -25,9 +25,9 @@ describe('MenuItemGenerator', () => {
<MockQueryClientProvider>
<MenuItemGenerator
viewName={viewMockData[0].name}
viewKey={viewMockData[0].uuid}
viewKey={viewMockData[0].id}
createdBy={viewMockData[0].createdBy}
uuid={viewMockData[0].uuid}
uuid={viewMockData[0].id}
refetchAllView={jest.fn()}
viewData={viewMockData}
sourcePage={DataSource.TRACES}
@ -43,9 +43,9 @@ describe('MenuItemGenerator', () => {
<MockQueryClientProvider>
<MenuItemGenerator
viewName={viewMockData[0].name}
viewKey={viewMockData[0].uuid}
viewKey={viewMockData[0].id}
createdBy={viewMockData[0].createdBy}
uuid={viewMockData[0].uuid}
uuid={viewMockData[0].id}
refetchAllView={jest.fn()}
viewData={viewMockData}
sourcePage={DataSource.TRACES}

View File

@ -26,7 +26,7 @@ export type GetViewDetailsUsingViewKey = (
| {
query: Query;
name: string;
uuid: string;
id: string;
panelType: PANEL_TYPES;
extraData?: string;
}

View File

@ -27,11 +27,11 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
viewKey,
data,
) => {
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;
};

View File

@ -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 (
<Select.Option key={view.uuid} value={view.name}>
<Select.Option key={view.id} value={view.name}>
<div className="render-options">
<span
className="dot"

View File

@ -63,17 +63,17 @@ export default function SavedViews({
const handleRedirectQuery = (view: ViewProps): void => {
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],
);

View File

@ -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;
}

View File

@ -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',

View File

@ -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)}
/>
</div>
</div>

View File

@ -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;

View File

@ -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 {

View File

@ -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

View File

@ -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()
}

View File

@ -62,6 +62,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
sqlmigration.NewUpdateDashboardAndSavedViewsFactory(sqlstore),
sqlmigration.NewUpdatePatAndOrgDomainsFactory(sqlstore),
sqlmigration.NewUpdatePipelines(sqlstore),
sqlmigration.NewDropLicensesSitesFactory(sqlstore),
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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),
}
}

9
pkg/types/identity.go Normal file
View File

@ -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"`
}

View File

@ -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"`

120
pkg/valuer/uuid.go Normal file
View File

@ -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
}

22
pkg/valuer/valuer.go Normal file
View File

@ -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
}