Skip to content

Commit 21e8364

Browse files
committed
fix(db): handle missing metadata
1 parent c894841 commit 21e8364

3 files changed

Lines changed: 138 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- +goose Up
2+
3+
-- Update the trigger to also handle SQL NULL (not just JSON 'null' literal).
4+
-- The NOT NULL constraint on metadata rejects SQL NULLs, but the BEFORE trigger
5+
-- runs first and can convert them to '{}' before the constraint is checked.
6+
-- +goose StatementBegin
7+
CREATE OR REPLACE FUNCTION fix_snapshots_metadata_json_null()
8+
RETURNS trigger AS $$
9+
BEGIN
10+
IF NEW.metadata IS NULL OR NEW.metadata = 'null'::jsonb THEN
11+
NEW.metadata := '{}'::jsonb;
12+
END IF;
13+
RETURN NEW;
14+
END;
15+
$$ LANGUAGE plpgsql;
16+
-- +goose StatementEnd
17+
18+
-- +goose Down
19+
20+
-- Restore original trigger that only handles JSON null.
21+
-- +goose StatementBegin
22+
CREATE OR REPLACE FUNCTION fix_snapshots_metadata_json_null()
23+
RETURNS trigger AS $$
24+
BEGIN
25+
IF NEW.metadata = 'null'::jsonb THEN
26+
NEW.metadata := '{}'::jsonb;
27+
END IF;
28+
RETURN NEW;
29+
END;
30+
$$ LANGUAGE plpgsql;
31+
-- +goose StatementEnd

packages/db/pkg/types/types.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
11
package types
22

3+
import "encoding/json"
4+
35
type JSONBStringMap map[string]string
46

7+
// MarshalJSON ensures a nil map serializes as "{}" instead of "null",
8+
// preventing SQL NULL when pgx encodes the value for jsonb columns.
9+
func (m JSONBStringMap) MarshalJSON() ([]byte, error) {
10+
if m == nil {
11+
return []byte("{}"), nil
12+
}
13+
14+
return json.Marshal(map[string]string(m))
15+
}
16+
17+
// UnmarshalJSON ensures JSON null deserializes as an empty map instead of nil.
18+
func (m *JSONBStringMap) UnmarshalJSON(data []byte) error {
19+
if string(data) == "null" {
20+
*m = JSONBStringMap{}
21+
22+
return nil
23+
}
24+
25+
var raw map[string]string
26+
if err := json.Unmarshal(data, &raw); err != nil {
27+
return err
28+
}
29+
30+
*m = JSONBStringMap(raw)
31+
32+
return nil
33+
}
34+
535
type BuildReason struct {
636
// Message with the status reason, currently reporting only for error status
737
Message string `json:"message"`
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestJSONBStringMap_MarshalJSON_Nil(t *testing.T) {
12+
var m JSONBStringMap
13+
data, err := json.Marshal(m)
14+
require.NoError(t, err)
15+
assert.Equal(t, "{}", string(data))
16+
}
17+
18+
func TestJSONBStringMap_MarshalJSON_Empty(t *testing.T) {
19+
m := JSONBStringMap{}
20+
data, err := json.Marshal(m)
21+
require.NoError(t, err)
22+
assert.Equal(t, "{}", string(data))
23+
}
24+
25+
func TestJSONBStringMap_MarshalJSON_WithValues(t *testing.T) {
26+
m := JSONBStringMap{"key": "value"}
27+
data, err := json.Marshal(m)
28+
require.NoError(t, err)
29+
assert.Equal(t, `{"key":"value"}`, string(data))
30+
}
31+
32+
func TestJSONBStringMap_UnmarshalJSON_Null(t *testing.T) {
33+
var m JSONBStringMap
34+
err := json.Unmarshal([]byte("null"), &m)
35+
require.NoError(t, err)
36+
assert.NotNil(t, m)
37+
assert.Empty(t, m)
38+
}
39+
40+
func TestJSONBStringMap_UnmarshalJSON_EmptyObject(t *testing.T) {
41+
var m JSONBStringMap
42+
err := json.Unmarshal([]byte("{}"), &m)
43+
require.NoError(t, err)
44+
assert.NotNil(t, m)
45+
assert.Empty(t, m)
46+
}
47+
48+
func TestJSONBStringMap_UnmarshalJSON_WithValues(t *testing.T) {
49+
var m JSONBStringMap
50+
err := json.Unmarshal([]byte(`{"key":"value"}`), &m)
51+
require.NoError(t, err)
52+
assert.Equal(t, JSONBStringMap{"key": "value"}, m)
53+
}
54+
55+
func TestJSONBStringMap_RoundTrip(t *testing.T) {
56+
original := JSONBStringMap{"foo": "bar", "baz": "qux"}
57+
data, err := json.Marshal(original)
58+
require.NoError(t, err)
59+
60+
var decoded JSONBStringMap
61+
err = json.Unmarshal(data, &decoded)
62+
require.NoError(t, err)
63+
assert.Equal(t, original, decoded)
64+
}
65+
66+
func TestJSONBStringMap_NilRoundTrip(t *testing.T) {
67+
var original JSONBStringMap // nil
68+
data, err := json.Marshal(original)
69+
require.NoError(t, err)
70+
assert.Equal(t, "{}", string(data))
71+
72+
var decoded JSONBStringMap
73+
err = json.Unmarshal(data, &decoded)
74+
require.NoError(t, err)
75+
assert.NotNil(t, decoded)
76+
assert.Empty(t, decoded)
77+
}

0 commit comments

Comments
 (0)