@@ -8,10 +8,12 @@ import (
88 "fmt"
99 "os"
1010 "path/filepath"
11+ "strings"
1112
1213 "github.com/go-errors/errors"
1314 "github.com/jackc/pgconn"
1415 "github.com/spf13/afero"
16+ "github.com/spf13/viper"
1517 "github.com/supabase/cli/internal/utils"
1618)
1719
@@ -22,13 +24,129 @@ var pgDeltaDeclarativeApplyScript string
2224//
2325// The fields are surfaced to provide concise CLI feedback after apply runs.
2426type ApplyResult struct {
25- Status string `json:"status"`
26- TotalStatements int `json:"totalStatements"`
27- TotalRounds int `json:"totalRounds"`
28- TotalApplied int `json:"totalApplied"`
29- TotalSkipped int `json:"totalSkipped"`
30- Errors []string `json:"errors"`
31- StuckStatements []string `json:"stuckStatements"`
27+ Status string `json:"status"`
28+ TotalStatements int `json:"totalStatements"`
29+ TotalRounds int `json:"totalRounds"`
30+ TotalApplied int `json:"totalApplied"`
31+ TotalSkipped int `json:"totalSkipped"`
32+ Errors []ApplyIssue `json:"errors"`
33+ StuckStatements []ApplyIssue `json:"stuckStatements"`
34+ }
35+
36+ // ApplyIssue models a pg-delta apply error or stuck statement.
37+ //
38+ // pg-delta may emit either a plain string or a structured object, so unmarshal
39+ // needs to gracefully handle both forms.
40+ type ApplyIssue struct {
41+ Statement * ApplyStatement `json:"statement,omitempty"`
42+ Code string `json:"code,omitempty"`
43+ Message string `json:"message,omitempty"`
44+ IsDependencyError bool `json:"isDependencyError,omitempty"`
45+ }
46+
47+ type ApplyStatement struct {
48+ ID string `json:"id"`
49+ SQL string `json:"sql"`
50+ StatementClass string `json:"statementClass"`
51+ }
52+
53+ func (i * ApplyIssue ) UnmarshalJSON (data []byte ) error {
54+ trimmed := bytes .TrimSpace (data )
55+ if bytes .Equal (trimmed , []byte ("null" )) {
56+ * i = ApplyIssue {}
57+ return nil
58+ }
59+ var message string
60+ if err := json .Unmarshal (trimmed , & message ); err == nil {
61+ * i = ApplyIssue {Message : message }
62+ return nil
63+ }
64+ type alias ApplyIssue
65+ var parsed alias
66+ if err := json .Unmarshal (trimmed , & parsed ); err != nil {
67+ return err
68+ }
69+ * i = ApplyIssue (parsed )
70+ return nil
71+ }
72+
73+ func formatApplyFailure (result ApplyResult ) string {
74+ totalStatements := result .TotalStatements
75+ if totalStatements == 0 {
76+ totalStatements = result .TotalApplied + result .TotalSkipped + len (result .StuckStatements )
77+ }
78+ lines := []string {
79+ fmt .Sprintf ("pg-delta apply returned status %q." , result .Status ),
80+ fmt .Sprintf ("%d/%d statements applied in %d round(s); %d skipped." , result .TotalApplied , totalStatements , result .TotalRounds , result .TotalSkipped ),
81+ }
82+ if len (result .Errors ) > 0 {
83+ lines = append (lines , "Errors:" )
84+ for _ , issue := range result .Errors {
85+ lines = append (lines , formatApplyIssue (issue ))
86+ }
87+ }
88+ if len (result .StuckStatements ) > 0 {
89+ lines = append (lines , "Stuck statements:" )
90+ for _ , issue := range result .StuckStatements {
91+ lines = append (lines , formatApplyIssue (issue ))
92+ }
93+ }
94+ return strings .Join (lines , "\n " )
95+ }
96+
97+ func formatApplyIssue (issue ApplyIssue ) string {
98+ if issue .Statement == nil {
99+ return "- " + formatApplyIssueMessage (issue )
100+ }
101+ title := "- " + issue .Statement .ID
102+ if issue .Statement .StatementClass != "" {
103+ title += " [" + issue .Statement .StatementClass + "]"
104+ }
105+ lines := []string {title }
106+ lines = append (lines , " " + formatApplyIssueMessage (issue ))
107+ if sql := formatStatementSQL (issue .Statement .SQL ); sql != "" {
108+ lines = append (lines , " SQL: " + sql )
109+ }
110+ return strings .Join (lines , "\n " )
111+ }
112+
113+ func formatApplyIssueMessage (issue ApplyIssue ) string {
114+ message := strings .TrimSpace (issue .Message )
115+ if message == "" {
116+ message = "unknown pg-delta issue"
117+ }
118+ var metadata []string
119+ if issue .Code != "" {
120+ metadata = append (metadata , "SQLSTATE " + issue .Code )
121+ }
122+ if issue .IsDependencyError {
123+ metadata = append (metadata , "dependency error" )
124+ }
125+ if len (metadata ) == 0 {
126+ return message
127+ }
128+ return fmt .Sprintf ("%s (%s)" , message , strings .Join (metadata , ", " ))
129+ }
130+
131+ func formatStatementSQL (sql string ) string {
132+ normalized := strings .Join (strings .Fields (sql ), " " )
133+ const maxLen = 120
134+ if len (normalized ) <= maxLen {
135+ return normalized
136+ }
137+ return normalized [:maxLen - 3 ] + "..."
138+ }
139+
140+ func formatDebugJSON (raw []byte ) string {
141+ trimmed := bytes .TrimSpace (raw )
142+ if len (trimmed ) == 0 {
143+ return ""
144+ }
145+ var indented bytes.Buffer
146+ if err := json .Indent (& indented , trimmed , "" , " " ); err == nil {
147+ return indented .String ()
148+ }
149+ return string (trimmed )
32150}
33151
34152// ApplyDeclarative applies files from supabase/declarative to the target
@@ -64,14 +182,19 @@ func ApplyDeclarative(ctx context.Context, config pgconn.Config, fsys afero.Fs)
64182
65183 var result ApplyResult
66184 if err := json .Unmarshal (stdout .Bytes (), & result ); err != nil {
67- return errors .Errorf ("failed to parse pg-delta apply output: %w\n stdout: %s" , err , stdout .String ())
185+ if viper .GetBool ("DEBUG" ) {
186+ return errors .Errorf ("failed to parse pg-delta apply output: %w\n stdout: %s" , err , stdout .String ())
187+ }
188+ return errors .Errorf ("failed to parse pg-delta apply output: %w" , err )
68189 }
69190 if result .Status != "success" {
70- if len (result .Errors ) > 0 {
71- fmt .Fprintf (os .Stderr , "Errors: %v\n " , result .Errors )
72- }
73- if len (result .StuckStatements ) > 0 {
74- fmt .Fprintf (os .Stderr , "Stuck statements: %v\n " , result .StuckStatements )
191+ if viper .GetBool ("DEBUG" ) {
192+ if debugJSON := formatDebugJSON (stdout .Bytes ()); len (debugJSON ) > 0 {
193+ fmt .Fprintln (os .Stderr , "pg-delta apply result:" )
194+ fmt .Fprintln (os .Stderr , debugJSON )
195+ }
196+ } else {
197+ fmt .Fprintln (os .Stderr , formatApplyFailure (result ))
75198 }
76199 return errors .Errorf ("pg-delta declarative apply failed with status: %s" , result .Status )
77200 }
0 commit comments