Files
wehub-resource-sync 75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

113 lines
2.9 KiB
Go

package restore
import (
"regexp"
"slices"
"strconv"
"strings"
)
const ignoredErrorsMarker = "errors ignored on restore:"
var extensionNamePattern = regexp.MustCompile(`extension "([^"]+)"`)
// IsMissingExtensionOnly reports whether a non-zero pg_restore exit is explained
// solely by extensions absent from this environment — i.e. the data itself
// restored. pg_restore runs without --exit-on-error, so it tails "errors ignored
// on restore: N"; this returns true only when every visible item error is
// extension-related and their count equals N. The count guard matters because
// StderrTail is capped at 8192 bytes: a truncated tail could hide a non-extension
// error, so when N can't be fully accounted for we refuse to tolerate.
func IsMissingExtensionOnly(stderrTail string) bool {
ignoredCount, hasMarker := parseIgnoredErrorCount(stderrTail)
if !hasMarker || ignoredCount < 1 {
return false
}
itemErrors := queryErrorLines(stderrTail)
if len(itemErrors) != ignoredCount {
return false
}
for _, line := range itemErrors {
if !isExtensionErrorLine(line) {
return false
}
}
return true
}
// ExtractUnavailableExtensions returns the de-duplicated, sorted names of the
// extensions pg_restore could not create, for an operator-facing log line.
func ExtractUnavailableExtensions(stderrTail string) []string {
seen := map[string]struct{}{}
var names []string
for _, line := range queryErrorLines(stderrTail) {
if !isExtensionErrorLine(line) {
continue
}
for _, match := range extensionNamePattern.FindAllStringSubmatch(line, -1) {
name := match[1]
if _, isDup := seen[name]; isDup {
continue
}
seen[name] = struct{}{}
names = append(names, name)
}
}
slices.Sort(names)
return names
}
func parseIgnoredErrorCount(stderrTail string) (int, bool) {
idx := strings.LastIndex(strings.ToLower(stderrTail), ignoredErrorsMarker)
if idx < 0 {
return 0, false
}
fields := strings.Fields(stderrTail[idx+len(ignoredErrorsMarker):])
if len(fields) == 0 {
return 0, false
}
count, err := strconv.Atoi(fields[0])
if err != nil {
return 0, false
}
return count, true
}
func queryErrorLines(stderrTail string) []string {
var lines []string
for line := range strings.SplitSeq(stderrTail, "\n") {
if strings.Contains(strings.ToLower(line), "could not execute query: error:") {
lines = append(lines, line)
}
}
return lines
}
// isExtensionErrorLine matches the phrasings a missing extension produces — the
// failed CREATE EXTENSION and its cascading COMMENT ON EXTENSION. The "extension"
// guard keeps unrelated cascades (`type "..." does not exist`) out.
func isExtensionErrorLine(line string) bool {
lowered := strings.ToLower(line)
if !strings.Contains(lowered, "extension") {
return false
}
return strings.Contains(lowered, "is not available") ||
strings.Contains(lowered, "could not open extension control file") ||
strings.Contains(lowered, "does not exist")
}