mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-12 03:40:27 +01:00
Compare commits
2 Commits
nv/functio
...
platform-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45e5f93b75 | ||
|
|
d2a930a893 |
@@ -1,101 +0,0 @@
|
||||
package postgressqlschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// devenvPostgresDSN is the DSN for the postgres started by `make devenv-postgres`.
|
||||
// Override with TEST_POSTGRES_DSN to point at a different instance.
|
||||
const devenvPostgresDSN = "postgres://postgres:password@localhost:5432/signoz?sslmode=disable"
|
||||
|
||||
// TestSignozDBTagUniqueIndex inspects the real postgres database the enterprise
|
||||
// server migrates and verifies the functional unique index added by migration
|
||||
// 094 on the "tag" table.
|
||||
//
|
||||
// - "MigrationCreatedIndex" is the ground-truth check: it reads the index
|
||||
// definition straight out of pg_indexes and confirms the functional unique
|
||||
// index physically exists. This proves the migration ran and postgres
|
||||
// accepted it.
|
||||
// - "GetIndicesRoundTrip" exercises the engine's GetIndices read-back path and
|
||||
// checks it reconstructs the same index. This is the part your colleague
|
||||
// asked about.
|
||||
//
|
||||
// It mirrors the sqlite signoz.db test, but talks to the devenv postgres
|
||||
// container instead of a local file. The test skips if postgres is unreachable
|
||||
// (run `make devenv-postgres` and the enterprise server first).
|
||||
func TestSignozDBTagUniqueIndex(t *testing.T) {
|
||||
dsn := os.Getenv("TEST_POSTGRES_DSN")
|
||||
if dsn == "" {
|
||||
dsn = devenvPostgresDSN
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := sqlstore.Config{
|
||||
Provider: "postgres",
|
||||
Postgres: sqlstore.PostgresConfig{DSN: dsn},
|
||||
Connection: sqlstore.ConnectionConfig{MaxOpenConns: 10, MaxConnLifetime: time.Minute},
|
||||
}
|
||||
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
store, err := postgressqlstore.New(ctx, providerSettings, cfg)
|
||||
if err != nil {
|
||||
t.Skipf("postgres unreachable at %s (run `make devenv-postgres` and the enterprise server): %v", dsn, err)
|
||||
}
|
||||
|
||||
pingCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
if err := store.SQLDB().PingContext(pingCtx); err != nil {
|
||||
t.Skipf("postgres unreachable at %s (run `make devenv-postgres` and the enterprise server): %v", dsn, err)
|
||||
}
|
||||
t.Logf("using postgres at %s", dsn)
|
||||
|
||||
schema, err := New(ctx, providerSettings, sqlschema.Config{}, store)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &sqlschema.UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []sqlschema.ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
}
|
||||
|
||||
t.Run("MigrationCreatedIndex", func(t *testing.T) {
|
||||
var def string
|
||||
err := store.
|
||||
BunDB().
|
||||
NewRaw("SELECT indexdef FROM pg_indexes WHERE tablename = 'tag' AND indexname = ?", expected.Name()).
|
||||
Scan(ctx, &def)
|
||||
require.NoError(t, err, "expected unique index %q to exist in postgres", expected.Name())
|
||||
t.Logf("stored indexdef: %s", def)
|
||||
|
||||
require.Contains(t, def, "UNIQUE")
|
||||
// postgres normalizes function names to lowercase.
|
||||
require.Contains(t, def, "lower(key)")
|
||||
require.Contains(t, def, "lower(value)")
|
||||
})
|
||||
|
||||
t.Run("GetIndicesRoundTrip", func(t *testing.T) {
|
||||
indices, err := schema.GetIndices(ctx, "tag")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("GetIndices returned %d indices", len(indices))
|
||||
var got sqlschema.Index
|
||||
for _, idx := range indices {
|
||||
t.Logf(" name=%q type=%s columns=%v create=%s", idx.Name(), idx.Type(), idx.Columns(), string(idx.ToCreateSQL(schema.Formatter())))
|
||||
if idx.Name() == expected.Name() {
|
||||
got = idx
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, got, "GetIndices did not return the functional unique index %q", expected.Name())
|
||||
require.True(t, expected.Equals(got), "round-tripped index should equal the original definition")
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
.billingContainer {
|
||||
margin-bottom: var(--spacing-20);
|
||||
padding-top: 36px;
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
margin: 0 auto var(--spacing-20);
|
||||
|
||||
.pageHeader {
|
||||
margin-bottom: var(--spacing-8);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.license-key-callout {
|
||||
margin: var(--spacing-4) var(--spacing-6);
|
||||
width: auto;
|
||||
width: auto !important;
|
||||
|
||||
.license-key-callout__description {
|
||||
display: flex;
|
||||
|
||||
@@ -1135,17 +1135,9 @@
|
||||
|
||||
.settings-dropdown,
|
||||
.help-support-dropdown {
|
||||
.ant-dropdown-menu-item {
|
||||
min-height: 32px;
|
||||
|
||||
.ant-dropdown-menu-title-content {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.user-settings-dropdown-logout-section {
|
||||
color: var(--danger-background);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.user-settings-dropdown-logout-section {
|
||||
color: var(--danger-background);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
.settings-page {
|
||||
max-height: 100vh;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.settings-page-header {
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
backdrop-filter: blur(20px);
|
||||
@@ -24,13 +28,14 @@
|
||||
}
|
||||
|
||||
.settings-page-content-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
align-items: stretch;
|
||||
|
||||
.settings-page-sidenav {
|
||||
width: 240px;
|
||||
height: calc(100vh - 48px);
|
||||
border-right: 1px solid var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
padding-top: var(--padding-1);
|
||||
@@ -74,7 +79,6 @@
|
||||
|
||||
.settings-page-content {
|
||||
flex: 1;
|
||||
height: calc(100vh - 48px);
|
||||
background: var(--l1-background);
|
||||
padding: 10px 8px;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -213,7 +213,6 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewCloudIntegrationRemoveCascadeDeleteFactory(sqlschema),
|
||||
sqlmigration.NewAddUserDashboardPreferenceFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewRecreateUserDashboardPreferenceFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddTagUniqueIndexFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ func (migration *addTags) Up(ctx context.Context, db *bun.DB) error {
|
||||
})
|
||||
sqls = append(sqls, tagTableSQLs...)
|
||||
|
||||
// TODO (@namanverma): add a unique index for tags: (org_id, kind, (LOWER(key)), (LOWER(value)))
|
||||
|
||||
tagRelationsTableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "tag_relation",
|
||||
Columns: []*sqlschema.Column{
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addTagUniqueIndex struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddTagUniqueIndexFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_tag_unique_index"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addTagUniqueIndex{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addTagUniqueIndex) Register(migrations *migrate.Migrations) error {
|
||||
return migrations.Register(migration.Up, migration.Down)
|
||||
}
|
||||
|
||||
func (migration *addTagUniqueIndex) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sqls := migration.sqlschema.Operator().CreateIndex(
|
||||
&sqlschema.UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []sqlschema.ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
},
|
||||
)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (migration *addTagUniqueIndex) Down(_ context.Context, _ *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package sqlschema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -51,23 +49,9 @@ type Index interface {
|
||||
ToDropSQL(fmter SQLFormatter) []byte
|
||||
}
|
||||
|
||||
// UniqueIndex models a unique index on a table.
|
||||
//
|
||||
// In the common case the index keys on plain columns: set only ColumnNames and
|
||||
// the SQL is emitted with each column identifier-quoted by the formatter
|
||||
// (`CREATE UNIQUE INDEX uq_t_a_b ON t (a, b)`).
|
||||
//
|
||||
// For functional indexes (e.g. case-insensitive uniqueness on `LOWER(col)`),
|
||||
// set Expressions to the raw SQL parts and use ColumnNames as metadata for
|
||||
// "which columns does this index touch". When Expressions is non-empty, it
|
||||
// overrides ColumnNames for SQL emission — each entry is written verbatim, so
|
||||
// the caller owns well-formedness — and the auto-generated name uses a hash
|
||||
// suffix instead of a readable column join because expressions aren't valid
|
||||
// identifier fragments.
|
||||
type UniqueIndex struct {
|
||||
TableName TableName
|
||||
ColumnNames []ColumnName
|
||||
Expressions []string
|
||||
name string
|
||||
}
|
||||
|
||||
@@ -87,28 +71,16 @@ func (index *UniqueIndex) Name() string {
|
||||
}
|
||||
b.WriteString(string(column))
|
||||
}
|
||||
|
||||
if len(index.Expressions) > 0 {
|
||||
if len(index.ColumnNames) > 0 {
|
||||
b.WriteString("_")
|
||||
}
|
||||
hasher := fnv.New32a()
|
||||
_, _ = hasher.Write([]byte(strings.Join(index.Expressions, "\x00")))
|
||||
fmt.Fprintf(&b, "%08x", hasher.Sum32())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (index *UniqueIndex) Named(name string) Index {
|
||||
copyOfColumnNames := make([]ColumnName, len(index.ColumnNames))
|
||||
copy(copyOfColumnNames, index.ColumnNames)
|
||||
copyOfExpressions := make([]string, len(index.Expressions))
|
||||
copy(copyOfExpressions, index.Expressions)
|
||||
|
||||
return &UniqueIndex{
|
||||
TableName: index.TableName,
|
||||
ColumnNames: copyOfColumnNames,
|
||||
Expressions: copyOfExpressions,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
@@ -129,18 +101,7 @@ func (index *UniqueIndex) Equals(other Index) bool {
|
||||
if other.Type() != IndexTypeUnique {
|
||||
return false
|
||||
}
|
||||
otherUnique, ok := other.(*UniqueIndex)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Plain and functional indexes produce different SQL even if their column
|
||||
// sets overlap; require both shapes to match.
|
||||
if (len(index.Expressions) == 0) != (len(otherUnique.Expressions) == 0) {
|
||||
return false
|
||||
}
|
||||
if len(index.Expressions) > 0 && !slices.Equal(index.Expressions, otherUnique.Expressions) {
|
||||
return false
|
||||
}
|
||||
|
||||
return index.Name() == other.Name() && slices.Equal(index.Columns(), other.Columns())
|
||||
}
|
||||
|
||||
@@ -153,20 +114,12 @@ func (index *UniqueIndex) ToCreateSQL(fmter SQLFormatter) []byte {
|
||||
sql = fmter.AppendIdent(sql, string(index.TableName))
|
||||
sql = append(sql, " ("...)
|
||||
|
||||
if len(index.Expressions) > 0 {
|
||||
for i, expr := range index.Expressions {
|
||||
if i > 0 {
|
||||
sql = append(sql, ", "...)
|
||||
}
|
||||
sql = append(sql, expr...)
|
||||
}
|
||||
} else {
|
||||
for i, column := range index.ColumnNames {
|
||||
if i > 0 {
|
||||
sql = append(sql, ", "...)
|
||||
}
|
||||
sql = fmter.AppendIdent(sql, string(column))
|
||||
for i, column := range index.ColumnNames {
|
||||
if i > 0 {
|
||||
sql = append(sql, ", "...)
|
||||
}
|
||||
|
||||
sql = fmter.AppendIdent(sql, string(column))
|
||||
}
|
||||
|
||||
sql = append(sql, ")"...)
|
||||
|
||||
@@ -38,43 +38,6 @@ func TestIndexToCreateSQL(t *testing.T) {
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "my_index" ON "users" ("id", "name", "email")`,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_SingleExpression",
|
||||
index: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "uq_users_email_1e5a87f1" ON "users" (LOWER(email))`,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_MixedColumnsAndExpressions",
|
||||
index: &UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "uq_tag_org_id_kind_key_value_57e8f81f" ON "tag" (org_id, kind, LOWER(key), LOWER(value))`,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_ComplexExpression",
|
||||
index: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"first_name", "last_name"},
|
||||
Expressions: []string{"LOWER(TRIM(first_name) || ' ' || TRIM(last_name))"},
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "uq_users_first_name_last_name_adb1ff53" ON "users" (LOWER(TRIM(first_name) || ' ' || TRIM(last_name)))`,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_Named",
|
||||
index: &UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
name: "uq_tag_org_kind_lower_key_lower_value",
|
||||
},
|
||||
sql: `CREATE UNIQUE INDEX IF NOT EXISTS "uq_tag_org_kind_lower_key_lower_value" ON "tag" (org_id, kind, LOWER(key), LOWER(value))`,
|
||||
},
|
||||
{
|
||||
name: "PartialUnique_1Column",
|
||||
index: &PartialUniqueIndex{
|
||||
@@ -266,47 +229,6 @@ func TestIndexEquals(t *testing.T) {
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_Same",
|
||||
a: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
},
|
||||
b: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
},
|
||||
equals: true,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_DifferentExpressions",
|
||||
a: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
},
|
||||
b: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"UPPER(email)"},
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
{
|
||||
name: "Unique_Functional_NotEqualToPlainSameColumns",
|
||||
a: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
},
|
||||
b: &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
},
|
||||
equals: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
@@ -316,75 +238,6 @@ func TestIndexEquals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUniqueIndexFunctionalName(t *testing.T) {
|
||||
t.Run("autogen uses uq_<table>_<hash>", func(t *testing.T) {
|
||||
idx := &UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
}
|
||||
assert.Equal(t, "uq_tag_org_id_kind_key_value_57e8f81f", idx.Name())
|
||||
})
|
||||
|
||||
t.Run("same expressions produce the same name", func(t *testing.T) {
|
||||
a := &UniqueIndex{
|
||||
TableName: "users",
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
}
|
||||
b := &UniqueIndex{
|
||||
TableName: "users",
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
}
|
||||
assert.Equal(t, a.Name(), b.Name())
|
||||
})
|
||||
|
||||
t.Run("different expressions produce different names", func(t *testing.T) {
|
||||
a := &UniqueIndex{
|
||||
TableName: "users",
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
}
|
||||
b := &UniqueIndex{
|
||||
TableName: "users",
|
||||
Expressions: []string{"UPPER(email)"},
|
||||
}
|
||||
assert.NotEqual(t, a.Name(), b.Name())
|
||||
})
|
||||
|
||||
t.Run("expressions in different order produce different names", func(t *testing.T) {
|
||||
a := &UniqueIndex{
|
||||
TableName: "tag",
|
||||
Expressions: []string{"org_id", "LOWER(key)"},
|
||||
}
|
||||
b := &UniqueIndex{
|
||||
TableName: "tag",
|
||||
Expressions: []string{"LOWER(key)", "org_id"},
|
||||
}
|
||||
assert.NotEqual(t, a.Name(), b.Name())
|
||||
})
|
||||
|
||||
t.Run("functional autogen differs from plain autogen for same columns", func(t *testing.T) {
|
||||
plain := &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
}
|
||||
functional := &UniqueIndex{
|
||||
TableName: "users",
|
||||
ColumnNames: []ColumnName{"email"},
|
||||
Expressions: []string{"LOWER(email)"},
|
||||
}
|
||||
assert.Equal(t, "uq_users_email", plain.Name())
|
||||
assert.NotEqual(t, plain.Name(), functional.Name())
|
||||
})
|
||||
|
||||
t.Run("Named() override wins over hash", func(t *testing.T) {
|
||||
idx := (&UniqueIndex{
|
||||
TableName: "tag",
|
||||
Expressions: []string{"org_id", "LOWER(key)"},
|
||||
}).Named("my_functional_index")
|
||||
assert.Equal(t, "my_functional_index", idx.Name())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPartialUniqueIndexName(t *testing.T) {
|
||||
a := &PartialUniqueIndex{
|
||||
TableName: "users",
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package sqlitesqlschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlitesqlstore"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// findSignozDB walks up from the test's working directory looking for a
|
||||
// signoz.db file (the one the community server creates at the repo root).
|
||||
func findSignozDB(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
dir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
for {
|
||||
candidate := filepath.Join(dir, "signoz.db")
|
||||
if _, err := os.Stat(candidate); err == nil {
|
||||
return candidate
|
||||
}
|
||||
|
||||
parent := filepath.Dir(dir)
|
||||
if parent == dir {
|
||||
return ""
|
||||
}
|
||||
dir = parent
|
||||
}
|
||||
}
|
||||
|
||||
// TestSignozDBTagUniqueIndex inspects the real signoz.db produced by running the
|
||||
// community server and verifies the functional unique index added by migration
|
||||
// 094 on the "tag" table.
|
||||
//
|
||||
// - "MigrationCreatedIndex" is the ground-truth check: it reads the index DDL
|
||||
// straight out of sqlite_master and confirms the functional unique index
|
||||
// physically exists. This proves the migration ran and sqlite accepted it.
|
||||
// - "GetIndicesRoundTrip" exercises the engine's GetIndices read-back path and
|
||||
// checks it reconstructs the same index. This is the part your colleague
|
||||
// asked about.
|
||||
func TestSignozDBTagUniqueIndex(t *testing.T) {
|
||||
dbPath := findSignozDB(t)
|
||||
if dbPath == "" {
|
||||
t.Skip("signoz.db not found; start the community server first so it creates the file and runs migrations")
|
||||
}
|
||||
t.Logf("using signoz.db at %s", dbPath)
|
||||
|
||||
ctx := context.Background()
|
||||
cfg := sqlstore.Config{
|
||||
Provider: "sqlite",
|
||||
Sqlite: sqlstore.SqliteConfig{
|
||||
Path: dbPath,
|
||||
Mode: "wal",
|
||||
BusyTimeout: 10 * time.Second,
|
||||
TransactionMode: "deferred",
|
||||
},
|
||||
Connection: sqlstore.ConnectionConfig{MaxOpenConns: 10},
|
||||
}
|
||||
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
store, err := sqlitesqlstore.New(ctx, providerSettings, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
schema, err := New(ctx, providerSettings, sqlschema.Config{}, store)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := &sqlschema.UniqueIndex{
|
||||
TableName: "tag",
|
||||
ColumnNames: []sqlschema.ColumnName{"org_id", "kind", "key", "value"},
|
||||
Expressions: []string{"org_id", "kind", "LOWER(key)", "LOWER(value)"},
|
||||
}
|
||||
|
||||
t.Run("MigrationCreatedIndex", func(t *testing.T) {
|
||||
var ddl string
|
||||
err := store.
|
||||
BunDB().
|
||||
NewRaw("SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = 'tag' AND name = ?", expected.Name()).
|
||||
Scan(ctx, &ddl)
|
||||
require.NoError(t, err, "expected unique index %q to exist in signoz.db", expected.Name())
|
||||
t.Logf("stored DDL: %s", ddl)
|
||||
|
||||
require.Contains(t, ddl, "UNIQUE")
|
||||
require.Contains(t, ddl, "LOWER(key)")
|
||||
require.Contains(t, ddl, "LOWER(value)")
|
||||
})
|
||||
|
||||
t.Run("GetIndicesRoundTrip", func(t *testing.T) {
|
||||
indices, err := schema.GetIndices(ctx, "tag")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Logf("GetIndices returned %d indices", len(indices))
|
||||
var got sqlschema.Index
|
||||
for _, idx := range indices {
|
||||
t.Logf(" name=%q type=%s columns=%v create=%s", idx.Name(), idx.Type(), idx.Columns(), string(idx.ToCreateSQL(schema.Formatter())))
|
||||
if idx.Name() == expected.Name() {
|
||||
got = idx
|
||||
}
|
||||
}
|
||||
|
||||
require.NotNil(t, got, "GetIndices did not return the functional unique index %q", expected.Name())
|
||||
require.True(t, expected.Equals(got), "round-tripped index should equal the original definition")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user