Export the reviewed allowlisted snapshot from private source commit 05928cebd01b586cf9e9d4b8c8537a7605a6068c. This records the exact candidate, bounded capacity result, stateful migration scratch requirement, authenticated batch identity proof, and immediate live acceptance evidence. AI-Assisted: OpenAI Codex Signed-off-by: Cole Speelman <crspeelman@gmail.com>
110 lines
4.9 KiB
Go
110 lines
4.9 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gamertan.com/observatory/internal/model"
|
|
"gamertan.com/observatory/internal/query"
|
|
)
|
|
|
|
func TestBaseIndexMigrationReplacesLegacyFullIndexes(t *testing.T) {
|
|
root := filepath.Join(t.TempDir(), "data")
|
|
dir := filepath.Join(root, "organizations", "legacy")
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(dir, "projection.sqlite")
|
|
legacy, err := sql.Open("sqlite", path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statements := []string{
|
|
`CREATE TABLE observations (organization_id TEXT NOT NULL,project_id TEXT NOT NULL,environment_id TEXT NOT NULL,service_id TEXT NOT NULL,source_id TEXT NOT NULL,stream_id TEXT NOT NULL,sequence INTEGER NOT NULL,record_index INTEGER NOT NULL,signal TEXT NOT NULL,timestamp TEXT NOT NULL,name TEXT NOT NULL,severity TEXT,body TEXT,value REAL,trace_id TEXT,span_id TEXT,correlation_id TEXT,attributes_json TEXT NOT NULL,segment_digest TEXT NOT NULL,PRIMARY KEY(source_id,stream_id,sequence,record_index))`,
|
|
`CREATE TABLE storage_projection_state (id INTEGER PRIMARY KEY CHECK(id=1),metric_rollup_version INTEGER NOT NULL CHECK(metric_rollup_version BETWEEN 0 AND 1))`,
|
|
`INSERT INTO storage_projection_state(id,metric_rollup_version) VALUES(1,1)`,
|
|
`CREATE INDEX observations_value ON observations(signal,value,timestamp)`,
|
|
`CREATE INDEX observations_http_route ON observations(signal,json_extract(attributes_json,'$."http.route"'),timestamp)`,
|
|
`CREATE INDEX observations_http_status ON observations(signal,CAST(json_extract(attributes_json,'$."http.status_code"') AS INTEGER),timestamp)`,
|
|
`CREATE INDEX observations_duration ON observations(signal,CAST(json_extract(attributes_json,'$."duration_ns"') AS REAL),timestamp)`,
|
|
}
|
|
for _, statement := range statements {
|
|
if _, err = legacy.Exec(statement); err != nil {
|
|
legacy.Close()
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err = legacy.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
db, err := openProjection(t.Context(), path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
var version int
|
|
if err = db.QueryRow(`SELECT base_index_version FROM storage_projection_state WHERE id=1`).Scan(&version); err != nil || version != baseIndexVersion {
|
|
t.Fatalf("base index version=%d err=%v", version, err)
|
|
}
|
|
for _, name := range []string{"observations_value", "observations_http_route", "observations_http_status", "observations_duration"} {
|
|
var definition string
|
|
if err = db.QueryRow(`SELECT sql FROM sqlite_schema WHERE type='index' AND name=?`, name).Scan(&definition); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(strings.ToUpper(definition), " WHERE ") || !strings.Contains(strings.ToUpper(definition), " IS NOT NULL") {
|
|
t.Fatalf("index %s was not migrated: %s", name, definition)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPresenceIndexesSupportSelectiveQueries(t *testing.T) {
|
|
ctx := t.Context()
|
|
store := testStore(t)
|
|
defer store.Close()
|
|
now := time.Now().UTC()
|
|
scope := model.Scope{OrganizationID: "organization-a", ProjectID: "project-a", EnvironmentID: "production", ServiceID: "service-a"}
|
|
logToken, err := store.CreateSource(ctx, "source-logs", scope)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
logs := model.Batch{Version: model.BatchVersion, SourceID: "source-logs", StreamID: "logs", Sequence: 1, ObservedAt: now, Signal: model.SignalLogs, Records: []model.Observation{
|
|
{Timestamp: now, Name: "http.server.request", Severity: "error", Attributes: map[string]string{"http.route": "/items", "http.status_code": "503", "duration_ns": "500"}},
|
|
{Timestamp: now.Add(-time.Second), Name: "application.event", Severity: "information", Attributes: map[string]string{}},
|
|
}}
|
|
if _, err = store.Ingest(ctx, logToken, logs, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metricToken, err := store.CreateSource(ctx, "source-metrics", scope)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
value := 42.0
|
|
metrics := model.Batch{Version: model.BatchVersion, SourceID: "source-metrics", StreamID: "metrics", Sequence: 1, ObservedAt: now, Signal: model.SignalMetrics, Records: []model.Observation{{Timestamp: now, Name: "system.cpu.utilization", Value: &value}}}
|
|
if _, err = store.Ingest(ctx, metricToken, metrics, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
projectAll(t, store)
|
|
queries := []string{
|
|
`logs | where route == "/items" | summarize count() by status | limit 10`,
|
|
`logs | where status >= 500 | summarize p95(duration) by route | limit 10`,
|
|
`logs | where duration >= 100 | summarize count() by route | limit 10`,
|
|
`metrics | where value >= 1 | summarize count() by name | limit 10`,
|
|
}
|
|
for _, text := range queries {
|
|
ast, parseErr := query.Parse(text, 10)
|
|
if parseErr != nil {
|
|
t.Fatalf("parse %q: %v", text, parseErr)
|
|
}
|
|
result, queryErr := store.Query(ctx, ast, query.Scope{OrganizationID: scope.OrganizationID}, testQueryBudget(), now)
|
|
if queryErr != nil || len(result.Rows) == 0 {
|
|
t.Fatalf("query %q rows=%d err=%v", text, len(result.Rows), queryErr)
|
|
}
|
|
}
|
|
}
|