Skip to content

Commit b778cd8

Browse files
authored
fix: pg partitions timestamp parser (#20)
1 parent e74ed48 commit b778cd8

2 files changed

Lines changed: 92 additions & 16 deletions

File tree

internal/pg/manager.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"regexp"
78
"sort"
8-
"strings"
99
"time"
1010

1111
"github.com/jackc/pgx/v5"
@@ -155,30 +155,26 @@ func (pm *PartitionManager) listPartitions(ctx context.Context) ([]partitionInfo
155155
}
156156

157157
func parsePartitionBound(bound string) (time.Time, time.Time, error) {
158-
159-
const layout = "2006-01-02"
160-
161-
// Estraggo le date
162-
var from, to string
163-
_, err := fmt.Sscanf(
164-
bound,
165-
"FOR VALUES FROM ('%s') TO ('%s')",
166-
&from,
167-
&to,
158+
re, err := regexp.Compile(
159+
`FOR VALUES FROM \('([^']+)'\) TO \('([^']+)'\)`,
168160
)
169161
if err != nil {
170-
return time.Time{}, time.Time{}, err
162+
return time.Time{}, time.Time{}, fmt.Errorf("cannot compile partiton bound regex: %w", err)
163+
}
164+
165+
matches := re.FindStringSubmatch(bound)
166+
if len(matches) != 3 {
167+
return time.Time{}, time.Time{}, fmt.Errorf("cannot parse bound: %s", bound)
171168
}
172169

173-
from = strings.TrimSuffix(from, "')")
174-
to = strings.TrimSuffix(to, "')")
170+
const layout = "2006-01-02 15:04:05-07"
175171

176-
start, err := time.Parse(layout, from)
172+
start, err := time.Parse(layout, matches[1])
177173
if err != nil {
178174
return time.Time{}, time.Time{}, err
179175
}
180176

181-
end, err := time.Parse(layout, to)
177+
end, err := time.Parse(layout, matches[2])
182178
if err != nil {
183179
return time.Time{}, time.Time{}, err
184180
}

internal/pg/manager_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package pg
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestParsePartitionBound(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input string
12+
wantStart time.Time
13+
wantEnd time.Time
14+
expectError bool
15+
}{
16+
{
17+
name: "valid postgres UTC bound",
18+
input: "FOR VALUES FROM ('2026-02-17 00:00:00+00') TO ('2026-02-18 00:00:00+00')",
19+
wantStart: time.Date(2026, 2, 17, 0, 0, 0, 0,
20+
time.FixedZone("UTC", 0)),
21+
wantEnd: time.Date(2026, 2, 18, 0, 0, 0, 0,
22+
time.FixedZone("UTC", 0)),
23+
expectError: false,
24+
},
25+
{
26+
name: "valid non-UTC offset",
27+
input: "FOR VALUES FROM ('2026-02-17 00:00:00+02') TO ('2026-02-18 00:00:00+02')",
28+
wantStart: time.Date(2026, 2, 17, 0, 0, 0, 0,
29+
time.FixedZone("", 2*3600)),
30+
wantEnd: time.Date(2026, 2, 18, 0, 0, 0, 0,
31+
time.FixedZone("", 2*3600)),
32+
expectError: false,
33+
},
34+
{
35+
name: "invalid format string",
36+
input: "INVALID STRING",
37+
expectError: true,
38+
},
39+
{
40+
name: "missing TO section",
41+
input: "FOR VALUES FROM ('2026-02-17 00:00:00+00')",
42+
expectError: true,
43+
},
44+
{
45+
name: "invalid timestamp",
46+
input: "FOR VALUES FROM ('2026-02-17') TO ('2026-02-18')",
47+
expectError: true,
48+
},
49+
{
50+
name: "postgres with timezone minutes",
51+
input: "FOR VALUES FROM ('2026-02-17 00:00:00+02:00') TO ('2026-02-18 00:00:00+02:00')",
52+
expectError: true, // attualmente non supportato dal layout
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
start, end, err := parsePartitionBound(tt.input)
59+
60+
if tt.expectError {
61+
if err == nil {
62+
t.Fatalf("expected error but got nil")
63+
}
64+
return
65+
}
66+
67+
if err != nil {
68+
t.Fatalf("unexpected error: %v", err)
69+
}
70+
71+
if !start.Equal(tt.wantStart) {
72+
t.Errorf("start mismatch\n got: %v\n want: %v", start, tt.wantStart)
73+
}
74+
75+
if !end.Equal(tt.wantEnd) {
76+
t.Errorf("end mismatch\n got: %v\n want: %v", end, tt.wantEnd)
77+
}
78+
})
79+
}
80+
}

0 commit comments

Comments
 (0)