diff --git a/CHANGELOG.md b/CHANGELOG.md index 12829408d404c..39612eb11f035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ If you do have such headers configured, then you must update the header configur - console: handle nested fragments in allowed queries (close #5137) (#5252) - console: update sidebar icons for different action and trigger types (#5445) - console: make add column UX consistent with others (#5486) +- cli: improve error messages thrown when metadata apply fails (#5513) +- cli: fix issue with creating seed migrations while using tables with capital letters (closes #5532) (#5549) - build: introduce additional log kinds for cli-migrations image (#5529) ## `v1.3.0` diff --git a/cli/commands/seed_create.go b/cli/commands/seed_create.go index 353fd778b55b9..108cfcf0597ff 100644 --- a/cli/commands/seed_create.go +++ b/cli/commands/seed_create.go @@ -70,26 +70,26 @@ func (o *SeedNewOptions) Run() error { // If we are initializing from a database table // create a hasura client and add table name opts if createSeedOpts.Data == nil { + var body []byte if len(o.FromTableNames) > 0 { migrateDriver, err := migrate.NewMigrate(ec, true) if err != nil { return errors.Wrap(err, "cannot initialize migrate driver") } // Send the query - body, err := migrateDriver.ExportDataDump(o.FromTableNames) + body, err = migrateDriver.ExportDataDump(o.FromTableNames) if err != nil { return errors.Wrap(err, "exporting seed data") } - - createSeedOpts.Data = bytes.NewReader(body) } else { const defaultText = "" - data, err := editor.CaptureInputFromEditor(editor.GetPreferredEditorFromEnvironment, defaultText, "*.sql") + var err error + body, err = editor.CaptureInputFromEditor(editor.GetPreferredEditorFromEnvironment, defaultText, "*.sql") if err != nil { return errors.Wrap(err, "cannot find default editor from env") } - createSeedOpts.Data = bytes.NewReader(data) } + createSeedOpts.Data = bytes.NewReader(body) } fs := afero.NewOsFs() diff --git a/cli/migrate/database/hasuradb/metadata.go b/cli/migrate/database/hasuradb/metadata.go index 21c6ce20d79e1..01e48632a4b76 100644 --- a/cli/migrate/database/hasuradb/metadata.go +++ b/cli/migrate/database/hasuradb/metadata.go @@ -219,7 +219,7 @@ func (h *HasuraDB) ApplyMetadata() error { if err != nil { return err } - herror.migrationQuery = "offending object: \n\r\n\r" + string(queryData) + h.logger.Debugf("offending object: \n\r\n\r" + string(queryData)) } } return herror diff --git a/cli/migrate/database/hasuradb/types.go b/cli/migrate/database/hasuradb/types.go index acbcc05215265..dab09ef922a9b 100644 --- a/cli/migrate/database/hasuradb/types.go +++ b/cli/migrate/database/hasuradb/types.go @@ -289,11 +289,35 @@ type HasuraError struct { Code string `json:"code"` } +type InconsistentMetadataError struct { + Definition interface{} `json:"definition,omitempty" mapstructure:"definition,omitempty"` + Reason string `json:"reason,omitempty" mapstructure:"reason,omitempty"` + Type string `json:"type,omitempty" mapstructure:"type,omitempty"` +} + +func (mderror *InconsistentMetadataError) String() string { + var out string + if mderror.Reason != "" { + out = fmt.Sprintf("\nreason: %v\n", mderror.Reason) + } + if mderror.Type != "" { + out = fmt.Sprintf("%stype: %v\n", out, mderror.Type) + } + if mderror.Definition != nil { + m, err := json.MarshalIndent(mderror.Definition, "", " ") + if err == nil { + out = fmt.Sprintf("%sdefinition: \n%s", out, string(m)) + } + } + return out +} + type SQLInternalError struct { - Arguments []string `json:"arguments" mapstructure:"arguments,omitempty"` - Error PostgresError `json:"error" mapstructure:"error,omitempty"` - Prepared bool `json:"prepared" mapstructure:"prepared,omitempty"` - Statement string `json:"statement" mapstructure:"statement,omitempty"` + Arguments []string `json:"arguments" mapstructure:"arguments,omitempty"` + Error *PostgresError `json:"error" mapstructure:"error,omitempty"` + Prepared bool `json:"prepared" mapstructure:"prepared,omitempty"` + Statement string `json:"statement" mapstructure:"statement,omitempty"` + InconsistentMetadataError `mapstructure:",squash"` } type PostgresError struct { StatusCode string `json:"status_code" mapstructure:"status_code,omitempty"` @@ -323,12 +347,17 @@ func (h HasuraError) Error() string { err := mapstructure.Decode(v, &internalError) if err == nil { // postgres error - errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) - if len(internalError.Error.Description) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + if internalError.Error != nil { + errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) + if len(internalError.Error.Description) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + } + if len(internalError.Error.Hint) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + } } - if len(internalError.Error.Hint) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + if e := internalError.InconsistentMetadataError.String(); e != "" { + errorStrings = append(errorStrings, e) } } } @@ -337,16 +366,25 @@ func (h HasuraError) Error() string { if err == nil { for _, internalError := range internalErrors { // postgres error - errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) - if len(internalError.Error.Description) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + if internalError.Error != nil { + errorStrings = append(errorStrings, fmt.Sprintf("[%s] %s: %s", internalError.Error.StatusCode, internalError.Error.ExecStatus, internalError.Error.Message)) + if len(internalError.Error.Description) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Description: %s", internalError.Error.Description)) + } + if len(internalError.Error.Hint) > 0 { + errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + } } - if len(internalError.Error.Hint) > 0 { - errorStrings = append(errorStrings, fmt.Sprintf("Hint: %s", internalError.Error.Hint)) + + if e := internalError.InconsistentMetadataError.String(); e != "" { + errorStrings = append(errorStrings, e) } } } } + if len(errorStrings) == 0 { + return "" + } return strings.Join(errorStrings, "\r\n") } diff --git a/cli/migrate/database/hasuradb/types_test.go b/cli/migrate/database/hasuradb/types_test.go index bfd3cf8b50658..8fab97e584294 100644 --- a/cli/migrate/database/hasuradb/types_test.go +++ b/cli/migrate/database/hasuradb/types_test.go @@ -72,3 +72,94 @@ func TestHasuraError_Error(t *testing.T) { }) } } + +func TestInconsistentMetadataError_String(t *testing.T) { + type fields struct { + Definition interface{} + Reason string + Type string + } + tests := []struct { + name string + fields fields + want string + }{ + { + "can generate error correctly when all fields are given", + fields{ + Reason: "test reason", + Type: "test", + Definition: func() interface{} { + var m interface{} + err := json.Unmarshal([]byte(`{"test": "test"}`), &m) + if err != nil { + t.Error(err) + } + return m + }(), + }, + ` +reason: test reason +type: test +definition: +{ + "test": "test" +}`, + }, + { + "will not panic when Definition is not a valid json (string)", + fields{ + Definition: func() interface{} { + return "test" + }(), + Reason: "", + Type: "", + }, + `definition: +"test"`, + }, + { + "will not panic when Definition is not a valid json (Int)", + fields{ + Definition: func() interface{} { + return 1 + }(), + Reason: "", + Type: "", + }, + `definition: +1`, + }, + { + "will not panic when Definition is (struct Array)", + fields{ + Definition: func() interface{} { + return []struct{Name string}{ { "test" } , { "test"} } + }(), + Reason: "", + Type: "", + }, + `definition: +[ + { + "Name": "test" + }, + { + "Name": "test" + } +]`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mderror := &InconsistentMetadataError{ + Definition: tt.fields.Definition, + Reason: tt.fields.Reason, + Type: tt.fields.Type, + } + if got := mderror.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} \ No newline at end of file diff --git a/cli/migrate/migrate.go b/cli/migrate/migrate.go index d497209cefc27..c7685ecfcd090 100644 --- a/cli/migrate/migrate.go +++ b/cli/migrate/migrate.go @@ -1847,7 +1847,12 @@ func (m *Migrate) ApplySeed(q interface{}) error { } func (m *Migrate) ExportDataDump(tableNames []string) ([]byte, error) { - return m.databaseDrv.ExportDataDump(tableNames) + // to support tables starting with capital letters + modifiedTableNames := make([]string, len(tableNames)) + for idx, val := range tableNames { + modifiedTableNames[idx] = fmt.Sprintf(`"%s"`, val) + } + return m.databaseDrv.ExportDataDump(modifiedTableNames) } func printDryRunStatus(migrations []*Migration) *bytes.Buffer {