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 {
|
if mj.PreviewFragments == nil {
|
||||||
mj.PreviewFragments = map[string]string{}
|
mj.PreviewFragments = map[string]string{}
|
||||||
}
|
}
|
||||||
r.PreviewFragments = mj.PreviewFragments
|
r.PreviewFragments = loadRevisionPreview(ctx, p.pool, revisionID, mj.PreviewFragments)
|
||||||
r.MaterializedPrefixCount = mj.MaterializedPrefixCount
|
r.MaterializedPrefixCount = mj.MaterializedPrefixCount
|
||||||
return &r, nil
|
return &r, nil
|
||||||
}
|
}
|
||||||
@@ -848,26 +848,39 @@ func (p *Postgres) CreateRollbackRevision(tenantID, sourceRevisionID string) (st
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
newID := uuid.NewString()
|
newID := uuid.NewString()
|
||||||
parent := sourceRevisionID
|
parent := sourceRevisionID
|
||||||
meta, _ := json.Marshal(map[string]any{
|
meta, err := revisionMetaWithoutPreview(src.MaterializedPrefixCount)
|
||||||
"preview_fragments": src.PreviewFragments,
|
if err != nil {
|
||||||
"materialized_prefix_count": src.MaterializedPrefixCount,
|
return "", err
|
||||||
})
|
}
|
||||||
var modArg any
|
var modArg any
|
||||||
if strings.TrimSpace(src.ModuleID) != "" {
|
if strings.TrimSpace(src.ModuleID) != "" {
|
||||||
modArg = src.ModuleID
|
modArg = src.ModuleID
|
||||||
}
|
}
|
||||||
_, err = p.pool.Exec(ctx, `
|
tx, err := p.pool.Begin(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))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// copy materialized prefixes
|
defer func() { _ = tx.Rollback(ctx) }()
|
||||||
_, _ = p.pool.Exec(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)
|
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`,
|
SELECT $1::uuid, prefix, community_id, source, meta_json FROM revision_materialized_prefix WHERE revision_id=$2::uuid`,
|
||||||
newID, sourceRevisionID)
|
newID, sourceRevisionID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return newID, nil
|
return newID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,10 +1069,7 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
|||||||
if previewFragments == nil {
|
if previewFragments == nil {
|
||||||
previewFragments = map[string]string{}
|
previewFragments = map[string]string{}
|
||||||
}
|
}
|
||||||
meta, err := json.Marshal(map[string]any{
|
meta, err := revisionMetaWithoutPreview(len(prefixes))
|
||||||
"preview_fragments": previewFragments,
|
|
||||||
"materialized_prefix_count": len(prefixes),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1073,13 +1083,17 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
|||||||
if parentRevisionID != nil && strings.TrimSpace(*parentRevisionID) != "" {
|
if parentRevisionID != nil && strings.TrimSpace(*parentRevisionID) != "" {
|
||||||
parent = strings.TrimSpace(*parentRevisionID)
|
parent = strings.TrimSpace(*parentRevisionID)
|
||||||
}
|
}
|
||||||
|
revID := strings.TrimSpace(revisionID)
|
||||||
_, err = tx.Exec(ctx, `
|
_, err = tx.Exec(ctx, `
|
||||||
INSERT INTO config_revision (id, tenant_id, module_id, content_hash, parent_revision_id, meta_json)
|
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)`,
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := insertRevisionPreview(ctx, tx, revID, previewFragments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if len(prefixes) > 0 {
|
if len(prefixes) > 0 {
|
||||||
_, err = tx.CopyFrom(ctx,
|
_, err = tx.CopyFrom(ctx,
|
||||||
pgx.Identifier{"revision_materialized_prefix"},
|
pgx.Identifier{"revision_materialized_prefix"},
|
||||||
@@ -1094,7 +1108,7 @@ func (p *Postgres) CreateRenderRevision(revisionID, tenantID, moduleID string, p
|
|||||||
if strings.TrimSpace(src) == "" {
|
if strings.TrimSpace(src) == "" {
|
||||||
src = "render"
|
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 {
|
if err != nil {
|
||||||
return err
|
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()
|
p1 := uuid.NewString()
|
||||||
p2 := uuid.NewString()
|
p2 := uuid.NewString()
|
||||||
|
|
||||||
preview := map[string]any{
|
previewFrags := map[string]string{
|
||||||
"preview_fragments": map[string]string{
|
"bird.conf": `# EvoBGP demo bundle
|
||||||
"bird.conf": `# EvoBGP demo bundle
|
|
||||||
router id 192.0.2.1;
|
router id 192.0.2.1;
|
||||||
|
|
||||||
protocol device {
|
protocol device {
|
||||||
@@ -39,15 +38,12 @@ protocol direct {
|
|||||||
ipv6;
|
ipv6;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
"bird.d/evobgp_demo.conf": "# static demo fragment\n",
|
"bird.d/evobgp_demo.conf": "# static demo fragment\n",
|
||||||
},
|
|
||||||
"materialized_prefix_count": 128,
|
|
||||||
}
|
}
|
||||||
previewB, _ := json.Marshal(preview)
|
previewMeta, _ := json.Marshal(map[string]any{"materialized_prefix_count": 128})
|
||||||
parentMeta, _ := json.Marshal(map[string]any{
|
parentMeta, _ := json.Marshal(map[string]any{"materialized_prefix_count": 0})
|
||||||
"preview_fragments": map[string]string{"bird.conf": "# parent revision\n"},
|
parentPreview, _ := json.Marshal(map[string]string{"bird.conf": "# parent revision\n"})
|
||||||
"materialized_prefix_count": 0,
|
previewFragsB, _ := json.Marshal(previewFrags)
|
||||||
})
|
|
||||||
|
|
||||||
tx, err := p.pool.Begin(ctx)
|
tx, err := p.pool.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -76,9 +72,17 @@ protocol direct {
|
|||||||
VALUES ($1,$2,'sha256:parent',NULL,$3::jsonb)`, parent, tid, string(parentMeta)); err != nil {
|
VALUES ($1,$2,'sha256:parent',NULL,$3::jsonb)`, parent, tid, string(parentMeta)); err != nil {
|
||||||
return err
|
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, `
|
if _, err := tx.Exec(ctx, `
|
||||||
INSERT INTO config_revision (id, tenant_id, content_hash, parent_revision_id, meta_json)
|
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
|
return err
|
||||||
}
|
}
|
||||||
if _, err := tx.Exec(ctx, `
|
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