refactor(db): split revision preview from meta_json
Preview BIRD-фрагменты в config_revision_preview; meta_json только счётчик префиксов. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -687,7 +687,7 @@ func (p *Postgres) GetRevision(tenantID, revisionID string) (*store.Revision, er
|
||||
if mj.PreviewFragments == nil {
|
||||
mj.PreviewFragments = map[string]string{}
|
||||
}
|
||||
r.PreviewFragments = mj.PreviewFragments
|
||||
r.PreviewFragments = loadRevisionPreview(ctx, p.pool, revisionID, mj.PreviewFragments)
|
||||
r.MaterializedPrefixCount = mj.MaterializedPrefixCount
|
||||
return &r, nil
|
||||
}
|
||||
@@ -848,26 +848,39 @@ func (p *Postgres) CreateRollbackRevision(tenantID, sourceRevisionID string) (st
|
||||
ctx := context.Background()
|
||||
newID := uuid.NewString()
|
||||
parent := sourceRevisionID
|
||||
meta, _ := json.Marshal(map[string]any{
|
||||
"preview_fragments": src.PreviewFragments,
|
||||
"materialized_prefix_count": src.MaterializedPrefixCount,
|
||||
})
|
||||
meta, err := revisionMetaWithoutPreview(src.MaterializedPrefixCount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var modArg any
|
||||
if strings.TrimSpace(src.ModuleID) != "" {
|
||||
modArg = src.ModuleID
|
||||
}
|
||||
_, err = p.pool.Exec(ctx, `
|
||||
INSERT INTO config_revision (id, tenant_id, module_id, content_hash, parent_revision_id, meta_json)
|
||||
VALUES ($1,$2,$3,$4,$5::uuid,$6::jsonb)`,
|
||||
newID, tenantID, modArg, src.ContentHash+":rollback", parent, string(meta))
|
||||
tx, err := p.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// copy materialized prefixes
|
||||
_, _ = p.pool.Exec(ctx, `
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO config_revision (id, tenant_id, module_id, content_hash, parent_revision_id, meta_json)
|
||||
VALUES ($1,$2,$3,$4,$5::uuid,$6::jsonb)`,
|
||||
newID, tenantID, modArg, src.ContentHash+":rollback", parent, meta)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := copyRevisionPreview(ctx, tx, newID, sourceRevisionID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO revision_materialized_prefix (revision_id, prefix, community_id, source, meta_json)
|
||||
SELECT $1::uuid, prefix, community_id, source, meta_json FROM revision_materialized_prefix WHERE revision_id=$2::uuid`,
|
||||
newID, sourceRevisionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return newID, nil
|
||||
}
|
||||
|
||||
@@ -1056,10 +1069,7 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
||||
if previewFragments == nil {
|
||||
previewFragments = map[string]string{}
|
||||
}
|
||||
meta, err := json.Marshal(map[string]any{
|
||||
"preview_fragments": previewFragments,
|
||||
"materialized_prefix_count": len(prefixes),
|
||||
})
|
||||
meta, err := revisionMetaWithoutPreview(len(prefixes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1073,13 +1083,17 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
||||
if parentRevisionID != nil && strings.TrimSpace(*parentRevisionID) != "" {
|
||||
parent = strings.TrimSpace(*parentRevisionID)
|
||||
}
|
||||
revID := strings.TrimSpace(revisionID)
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO config_revision (id, tenant_id, module_id, content_hash, parent_revision_id, meta_json)
|
||||
VALUES ($1::uuid, $2::uuid, $3::uuid, $4, $5::uuid, $6::jsonb)`,
|
||||
strings.TrimSpace(revisionID), tenantID, moduleID, strings.TrimSpace(contentHash), parent, string(meta))
|
||||
revID, tenantID, moduleID, strings.TrimSpace(contentHash), parent, meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := insertRevisionPreview(ctx, tx, revID, previewFragments); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(prefixes) > 0 {
|
||||
_, err = tx.CopyFrom(ctx,
|
||||
pgx.Identifier{"revision_materialized_prefix"},
|
||||
@@ -1094,7 +1108,7 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
||||
if strings.TrimSpace(src) == "" {
|
||||
src = "render"
|
||||
}
|
||||
return []any{strings.TrimSpace(revisionID), strings.TrimSpace(pr.Prefix), comm, src}, nil
|
||||
return []any{revID, strings.TrimSpace(pr.Prefix), comm, src}, nil
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
func revisionPreviewTableExists(ctx context.Context, q queryRower) bool {
|
||||
var n int
|
||||
err := q.QueryRow(ctx, `
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'config_revision_preview'
|
||||
LIMIT 1`).Scan(&n)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
type queryRower interface {
|
||||
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
|
||||
}
|
||||
|
||||
func loadRevisionPreview(ctx context.Context, q queryRower, revisionID string, metaPreview map[string]string) map[string]string {
|
||||
if revisionPreviewTableExists(ctx, q) {
|
||||
var raw []byte
|
||||
err := q.QueryRow(ctx, `
|
||||
SELECT fragments FROM config_revision_preview WHERE revision_id = $1::uuid`,
|
||||
revisionID).Scan(&raw)
|
||||
if err == nil {
|
||||
out := map[string]string{}
|
||||
_ = json.Unmarshal(raw, &out)
|
||||
if out == nil {
|
||||
out = map[string]string{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return metaPreview
|
||||
}
|
||||
}
|
||||
if metaPreview == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
return metaPreview
|
||||
}
|
||||
|
||||
type execQuerier interface {
|
||||
queryRower
|
||||
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
|
||||
}
|
||||
|
||||
func insertRevisionPreview(ctx context.Context, db execQuerier, revisionID string, preview map[string]string) error {
|
||||
if !revisionPreviewTableExists(ctx, db) {
|
||||
return nil
|
||||
}
|
||||
if preview == nil {
|
||||
preview = map[string]string{}
|
||||
}
|
||||
raw, err := json.Marshal(preview)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(ctx, `
|
||||
INSERT INTO config_revision_preview (revision_id, fragments)
|
||||
VALUES ($1::uuid, $2::jsonb)
|
||||
ON CONFLICT (revision_id) DO UPDATE SET fragments = EXCLUDED.fragments`,
|
||||
revisionID, string(raw))
|
||||
return err
|
||||
}
|
||||
|
||||
func copyRevisionPreview(ctx context.Context, db execQuerier, dstRevisionID, srcRevisionID string) error {
|
||||
if !revisionPreviewTableExists(ctx, db) {
|
||||
return nil
|
||||
}
|
||||
_, err := db.Exec(ctx, `
|
||||
INSERT INTO config_revision_preview (revision_id, fragments)
|
||||
SELECT $1::uuid, fragments FROM config_revision_preview WHERE revision_id = $2::uuid
|
||||
ON CONFLICT (revision_id) DO UPDATE SET fragments = EXCLUDED.fragments`,
|
||||
dstRevisionID, srcRevisionID)
|
||||
return err
|
||||
}
|
||||
|
||||
func revisionMetaWithoutPreview(materializedPrefixCount int) (string, error) {
|
||||
raw, err := json.Marshal(map[string]any{
|
||||
"materialized_prefix_count": materializedPrefixCount,
|
||||
})
|
||||
return string(raw), err
|
||||
}
|
||||
@@ -26,9 +26,8 @@ func (p *Postgres) seedDemo(ctx context.Context) error {
|
||||
p1 := uuid.NewString()
|
||||
p2 := uuid.NewString()
|
||||
|
||||
preview := map[string]any{
|
||||
"preview_fragments": map[string]string{
|
||||
"bird.conf": `# EvoBGP demo bundle
|
||||
previewFrags := map[string]string{
|
||||
"bird.conf": `# EvoBGP demo bundle
|
||||
router id 192.0.2.1;
|
||||
|
||||
protocol device {
|
||||
@@ -39,15 +38,12 @@ protocol direct {
|
||||
ipv6;
|
||||
}
|
||||
`,
|
||||
"bird.d/evobgp_demo.conf": "# static demo fragment\n",
|
||||
},
|
||||
"materialized_prefix_count": 128,
|
||||
"bird.d/evobgp_demo.conf": "# static demo fragment\n",
|
||||
}
|
||||
previewB, _ := json.Marshal(preview)
|
||||
parentMeta, _ := json.Marshal(map[string]any{
|
||||
"preview_fragments": map[string]string{"bird.conf": "# parent revision\n"},
|
||||
"materialized_prefix_count": 0,
|
||||
})
|
||||
previewMeta, _ := json.Marshal(map[string]any{"materialized_prefix_count": 128})
|
||||
parentMeta, _ := json.Marshal(map[string]any{"materialized_prefix_count": 0})
|
||||
parentPreview, _ := json.Marshal(map[string]string{"bird.conf": "# parent revision\n"})
|
||||
previewFragsB, _ := json.Marshal(previewFrags)
|
||||
|
||||
tx, err := p.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -76,9 +72,17 @@ protocol direct {
|
||||
VALUES ($1,$2,'sha256:parent',NULL,$3::jsonb)`, parent, tid, string(parentMeta)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO config_revision_preview (revision_id, fragments) VALUES ($1::uuid, $2::jsonb)`, parent, string(parentPreview)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO config_revision (id, tenant_id, content_hash, parent_revision_id, meta_json)
|
||||
VALUES ($1,$2,'sha256:demo-rev-1',$3::uuid,$4::jsonb)`, rid, tid, parent, string(previewB)); err != nil {
|
||||
VALUES ($1,$2,'sha256:demo-rev-1',$3::uuid,$4::jsonb)`, rid, tid, parent, string(previewMeta)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO config_revision_preview (revision_id, fragments) VALUES ($1::uuid, $2::jsonb)`, rid, string(previewFragsB)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
UPDATE config_revision cr
|
||||
SET meta_json = cr.meta_json || jsonb_build_object('preview_fragments', COALESCE(p.fragments, '{}'::jsonb))
|
||||
FROM config_revision_preview p
|
||||
WHERE p.revision_id = cr.id;
|
||||
|
||||
DROP TABLE IF EXISTS config_revision_preview;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Split BIRD preview fragments out of config_revision.meta_json (H1).
|
||||
|
||||
CREATE TABLE config_revision_preview (
|
||||
revision_id UUID PRIMARY KEY REFERENCES config_revision (id) ON DELETE CASCADE,
|
||||
fragments JSONB NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
INSERT INTO config_revision_preview (revision_id, fragments)
|
||||
SELECT id, COALESCE(meta_json->'preview_fragments', '{}'::jsonb)
|
||||
FROM config_revision
|
||||
WHERE meta_json ? 'preview_fragments';
|
||||
|
||||
UPDATE config_revision
|
||||
SET meta_json = meta_json - 'preview_fragments'
|
||||
WHERE meta_json ? 'preview_fragments';
|
||||
@@ -0,0 +1,9 @@
|
||||
UPDATE config_revision
|
||||
SET meta_json = json_set(
|
||||
meta_json,
|
||||
'$.preview_fragments',
|
||||
json(COALESCE((SELECT fragments FROM config_revision_preview p WHERE p.revision_id = config_revision.id), '{}'))
|
||||
)
|
||||
WHERE id IN (SELECT revision_id FROM config_revision_preview);
|
||||
|
||||
DROP TABLE IF EXISTS config_revision_preview;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE config_revision_preview (
|
||||
revision_id TEXT NOT NULL PRIMARY KEY REFERENCES config_revision (id) ON DELETE CASCADE,
|
||||
fragments TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
INSERT INTO config_revision_preview (revision_id, fragments)
|
||||
SELECT id, json(COALESCE(json_extract(meta_json, '$.preview_fragments'), '{}'))
|
||||
FROM config_revision
|
||||
WHERE json_extract(meta_json, '$.preview_fragments') IS NOT NULL;
|
||||
|
||||
UPDATE config_revision
|
||||
SET meta_json = json_remove(meta_json, '$.preview_fragments')
|
||||
WHERE json_extract(meta_json, '$.preview_fragments') IS NOT NULL;
|
||||
Reference in New Issue
Block a user