forked from skeema/tengo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
diff.go
672 lines (613 loc) · 22.2 KB
/
diff.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
package tengo
import (
"fmt"
"regexp"
"strings"
"github.com/pmezard/go-difflib/difflib"
)
// DiffType enumerates possible ways that two objects differ
type DiffType int
// Constants representing the types of diff operations.
const (
DiffTypeNone DiffType = iota
DiffTypeCreate
DiffTypeDrop
DiffTypeAlter
DiffTypeRename
)
func (dt DiffType) String() string {
switch dt {
case DiffTypeNone:
return ""
case DiffTypeCreate:
return "CREATE"
case DiffTypeAlter:
return "ALTER"
case DiffTypeDrop:
return "DROP"
default: // DiffTypeRename not supported yet
panic(fmt.Errorf("Unsupported diff type %d", dt))
}
}
// ObjectDiff is an interface allowing generic handling of differences between
// two objects.
type ObjectDiff interface {
DiffType() DiffType
ObjectKey() ObjectKey
Statement(StatementModifiers) (string, error)
}
// NextAutoIncMode enumerates various ways of handling AUTO_INCREMENT
// discrepancies between two tables.
type NextAutoIncMode int
// Constants for how to handle next-auto-inc values in table diffs. Usually
// these are ignored in diffs entirely, but in some cases they are included.
const (
NextAutoIncIgnore NextAutoIncMode = iota // omit auto-inc value changes in diff
NextAutoIncIfIncreased // only include auto-inc value if the "from" side is less than the "to" side
NextAutoIncIfAlready // only include auto-inc value if the "from" side is already greater than 1
NextAutoIncAlways // always include auto-inc value in diff
)
// StatementModifiers are options that may be applied to adjust the DDL emitted
// for a particular table, and/or generate errors if certain clauses are
// present.
type StatementModifiers struct {
NextAutoInc NextAutoIncMode // How to handle differences in next-auto-inc values
AllowUnsafe bool // Whether to allow potentially-destructive DDL (drop table, drop column, modify col type, etc)
LockClause string // Include a LOCK=[value] clause in generated ALTER TABLE
AlgorithmClause string // Include an ALGORITHM=[value] clause in generated ALTER TABLE
IgnoreTable *regexp.Regexp // Generate blank DDL if table name matches this regexp
StrictIndexOrder bool // If true, maintain index order even in cases where there is no functional difference
StrictForeignKeyNaming bool // If true, maintain foreign key names even if no functional difference in definition
CompareMetadata bool // If true, compare creation-time sql_mode and db collation for funcs, procs (and eventually events, triggers)
Flavor Flavor // Adjust generated DDL to match vendor/version. Zero value is FlavorUnknown which makes no adjustments.
}
///// SchemaDiff ///////////////////////////////////////////////////////////////
// SchemaDiff represents a set of differences between two database schemas,
// encapsulating diffs of various different object types.
type SchemaDiff struct {
FromSchema *Schema
ToSchema *Schema
TableDiffs []*TableDiff // a set of statements that, if run, would turn tables in FromSchema into ToSchema
RoutineDiffs []*RoutineDiff // " but for funcs and procs
}
// NewSchemaDiff computes the set of differences between two database schemas.
func NewSchemaDiff(from, to *Schema) *SchemaDiff {
result := &SchemaDiff{
FromSchema: from,
ToSchema: to,
}
if from == nil && to == nil {
return result
}
result.TableDiffs = compareTables(from, to)
result.RoutineDiffs = compareRoutines(from, to)
return result
}
func compareTables(from, to *Schema) []*TableDiff {
var tableDiffs, addFKAlters []*TableDiff
fromByName := from.TablesByName()
toByName := to.TablesByName()
for name, fromTable := range fromByName {
toTable, stillExists := toByName[name]
if !stillExists {
tableDiffs = append(tableDiffs, NewDropTable(fromTable))
continue
}
td := NewAlterTable(fromTable, toTable)
if td != nil {
otherAlter, addFKAlter := td.SplitAddForeignKeys()
if otherAlter != nil {
tableDiffs = append(tableDiffs, otherAlter)
}
if addFKAlter != nil {
addFKAlters = append(addFKAlters, addFKAlter)
}
}
}
for name, toTable := range toByName {
if _, alreadyExists := fromByName[name]; !alreadyExists {
tableDiffs = append(tableDiffs, NewCreateTable(toTable))
}
}
// We put ALTER TABLEs containing ADD FOREIGN KEY last, since the FKs may rely
// on tables, columns, or indexes that are being newly created earlier in the
// diff. (This is not a comprehensive solution yet though, since FKs can refer
// to other schemas, and NewSchemaDiff only operates within one schema.)
tableDiffs = append(tableDiffs, addFKAlters...)
return tableDiffs
}
func compareRoutines(from, to *Schema) (routineDiffs []*RoutineDiff) {
compare := func(fromByName map[string]*Routine, toByName map[string]*Routine) {
for name, fromRoutine := range fromByName {
toRoutine, stillExists := toByName[name]
if !stillExists {
routineDiffs = append(routineDiffs, &RoutineDiff{From: fromRoutine})
} else if !fromRoutine.Equals(toRoutine) {
// Determine if only the creation-time metadata (db collation, sql_mode)
// has changed, and flag the diffs if so. This type of change requires
// StatementModifiers to execute, since its appearance is counterintuitive
// (since otherwise it looks like a routine is being dropped and recreated
// with the exact same statement)
metadataOnly := fromRoutine.CreateStatement == toRoutine.CreateStatement
// TODO: Currently this handles all changes to existing routines via DROP-
// then-ADD, but characteristic-only changes could use ALTER FUNCTION /
// ALTER PROCEDURE instead.
routineDiffs = append(routineDiffs,
&RoutineDiff{From: fromRoutine, ForMetadata: metadataOnly},
&RoutineDiff{To: toRoutine, ForMetadata: metadataOnly},
)
}
}
for name, toRoutine := range toByName {
if _, alreadyExists := fromByName[name]; !alreadyExists {
routineDiffs = append(routineDiffs, &RoutineDiff{To: toRoutine})
}
}
}
compare(from.ProceduresByName(), to.ProceduresByName())
compare(from.FunctionsByName(), to.FunctionsByName())
return
}
// DatabaseDiff returns an object representing database-level DDL (CREATE
// DATABASE, ALTER DATABASE, DROP DATABASE), or nil if no database-level DDL
// is necessary.
func (sd *SchemaDiff) DatabaseDiff() *DatabaseDiff {
dd := &DatabaseDiff{From: sd.FromSchema, To: sd.ToSchema}
if dd.DiffType() == DiffTypeNone {
return nil
}
return dd
}
// ObjectDiffs returns a slice of all ObjectDiffs in the SchemaDiff. The results
// are returned in a sorted order, such that the diffs' Statements are legal.
// For example, if a CREATE DATABASE is present, it will occur in the slice
// prior to any table-level DDL in that schema.
func (sd *SchemaDiff) ObjectDiffs() []ObjectDiff {
result := make([]ObjectDiff, 0)
dd := sd.DatabaseDiff()
if dd != nil {
result = append(result, dd)
}
for _, td := range sd.TableDiffs {
result = append(result, td)
}
for _, rd := range sd.RoutineDiffs {
result = append(result, rd)
}
return result
}
// String returns the set of differences between two schemas as a single string.
// In building this string representation, note that no statement modifiers are
// applied, and any errors from Statement() are ignored. This means the returned
// string may contain destructive statements, and should only be used for
// display purposes, not for DDL execution.
func (sd *SchemaDiff) String() string {
allDiffs := sd.ObjectDiffs()
diffStatements := make([]string, len(allDiffs))
for n, diff := range allDiffs {
stmt, _ := diff.Statement(StatementModifiers{})
diffStatements[n] = fmt.Sprintf("%s;\n", stmt)
}
return strings.Join(diffStatements, "")
}
// FilteredTableDiffs returns any TableDiffs of the specified type(s).
func (sd *SchemaDiff) FilteredTableDiffs(onlyTypes ...DiffType) []*TableDiff {
result := make([]*TableDiff, 0, len(sd.TableDiffs))
for _, td := range sd.TableDiffs {
for _, typ := range onlyTypes {
if td.Type == typ {
result = append(result, td)
break
}
}
}
return result
}
///// DatabaseDiff /////////////////////////////////////////////////////////////
// DatabaseDiff represents differences of schema characteristics (default
// character set or default collation), or a difference in the existence of the
// the schema.
type DatabaseDiff struct {
From *Schema
To *Schema
}
// ObjectKey returns a value representing the type and name of the schema being
// diff'ed. The type is always ObjectTypeDatabase. The name will be the From
// side schema, unless it is nil (CREATE DATABASE), in which case the To side
// schema name is returned.
func (dd *DatabaseDiff) ObjectKey() ObjectKey {
key := ObjectKey{Type: ObjectTypeDatabase}
if dd == nil || (dd.From == nil && dd.To == nil) {
return key
}
if dd.From == nil {
key.Name = dd.To.Name
} else {
key.Name = dd.From.Name
}
return key
}
// DiffType returns the type of diff operation.
func (dd *DatabaseDiff) DiffType() DiffType {
if dd == nil || (dd.From == nil && dd.To == nil) {
return DiffTypeNone
} else if dd.From == nil && dd.To != nil {
return DiffTypeCreate
} else if dd.From != nil && dd.To == nil {
return DiffTypeDrop
}
if dd.From.CharSet != dd.To.CharSet || dd.From.Collation != dd.To.Collation {
return DiffTypeAlter
}
return DiffTypeNone
}
// Statement returns a DDL statement corresponding to the DatabaseDiff. A blank
// string may be returned if there is no statement to execute.
func (dd *DatabaseDiff) Statement(_ StatementModifiers) (string, error) {
if dd == nil {
return "", nil
}
switch dd.DiffType() {
case DiffTypeCreate:
return dd.To.CreateStatement(), nil
case DiffTypeDrop:
stmt := dd.From.DropStatement()
err := &ForbiddenDiffError{
Reason: "DROP DATABASE never permitted",
Statement: stmt,
}
return stmt, err
case DiffTypeAlter:
return dd.From.AlterStatement(dd.To.CharSet, dd.To.Collation), nil
}
return "", nil
}
///// TableDiff ////////////////////////////////////////////////////////////////
// TableDiff represents a difference between two tables.
type TableDiff struct {
Type DiffType
From *Table
To *Table
alterClauses []TableAlterClause
supported bool
}
// ObjectKey returns a value representing the type and name of the table being
// diff'ed. The type is always ObjectTypeTable. The name will be the From side
// table, unless the diffType is DiffTypeCreate, in which case the To side
// table name is used.
func (td *TableDiff) ObjectKey() ObjectKey {
key := ObjectKey{Type: ObjectTypeTable}
if td == nil {
return key
}
if td.Type == DiffTypeCreate {
key.Name = td.To.Name
} else {
key.Name = td.From.Name
}
return key
}
// DiffType returns the type of diff operation.
func (td *TableDiff) DiffType() DiffType {
if td == nil {
return DiffTypeNone
}
return td.Type
}
// NewCreateTable returns a *TableDiff representing a CREATE TABLE statement,
// i.e. a table that only exists in the "to" side schema in a diff.
func NewCreateTable(table *Table) *TableDiff {
return &TableDiff{
Type: DiffTypeCreate,
To: table,
supported: true,
}
}
// NewAlterTable returns a *TableDiff representing an ALTER TABLE statement,
// i.e. a table that exists in the "from" and "to" side schemas but with one
// or more differences. If the supplied tables are identical, nil will be
// returned instead of a TableDiff.
func NewAlterTable(from, to *Table) *TableDiff {
clauses, supported := from.Diff(to)
if supported && len(clauses) == 0 {
return nil
}
return &TableDiff{
Type: DiffTypeAlter,
From: from,
To: to,
alterClauses: clauses,
supported: supported,
}
}
// NewDropTable returns a *TableDiff representing a DROP TABLE statement,
// i.e. a table that only exists in the "from" side schema in a diff.
func NewDropTable(table *Table) *TableDiff {
return &TableDiff{
Type: DiffTypeDrop,
From: table,
supported: true,
}
}
// SplitAddForeignKeys looks through a TableDiff's alterClauses and pulls out
// any AddForeignKey clauses into a separate TableDiff. The first returned
// TableDiff is guaranteed to contain no AddForeignKey clauses, and the second
// returned value is guaranteed to only consist of AddForeignKey clauses. If
// the receiver contained no AddForeignKey clauses, the first return value will
// be the receiver, and the second will be nil. If the receiver contained only
// AddForeignKey clauses, the first return value will be nil, and the second
// will be the receiver.
// This method is useful for several reasons: it is desirable to only add FKs
// after other alters have been made (since FKs rely on indexes on both sides);
// it is illegal to drop and re-add an FK with the same name in the same ALTER;
// some versions of MySQL recommend against dropping and adding FKs in the same
// ALTER even if they have different names.
func (td *TableDiff) SplitAddForeignKeys() (*TableDiff, *TableDiff) {
if td.Type != DiffTypeAlter || !td.supported || len(td.alterClauses) == 0 {
return td, nil
}
addFKClauses := make([]TableAlterClause, 0)
otherClauses := make([]TableAlterClause, 0, len(td.alterClauses))
for _, clause := range td.alterClauses {
if _, ok := clause.(AddForeignKey); ok {
addFKClauses = append(addFKClauses, clause)
} else {
otherClauses = append(otherClauses, clause)
}
}
if len(addFKClauses) == 0 {
return td, nil
} else if len(otherClauses) == 0 {
return nil, td
}
result1 := &TableDiff{
Type: DiffTypeAlter,
From: td.From,
To: td.To,
alterClauses: otherClauses,
supported: true,
}
result2 := &TableDiff{
Type: DiffTypeAlter,
From: td.From,
To: td.To,
alterClauses: addFKClauses,
supported: true,
}
return result1, result2
}
// Statement returns the full DDL statement corresponding to the TableDiff. A
// blank string may be returned if the mods indicate the statement should be
// skipped. If the mods indicate the statement should be disallowed, it will
// still be returned as-is, but the error will be non-nil. Be sure not to
// ignore the error value of this method.
func (td *TableDiff) Statement(mods StatementModifiers) (string, error) {
if td == nil {
return "", nil
}
if mods.IgnoreTable != nil {
if (td.From != nil && mods.IgnoreTable.MatchString(td.From.Name)) || (td.To != nil && mods.IgnoreTable.MatchString(td.To.Name)) {
return "", nil
}
}
var err error
switch td.Type {
case DiffTypeCreate:
stmt := td.To.CreateStatement
if td.To.HasAutoIncrement() && (mods.NextAutoInc == NextAutoIncIgnore || mods.NextAutoInc == NextAutoIncIfAlready) {
stmt, _ = ParseCreateAutoInc(stmt)
}
return stmt, nil
case DiffTypeAlter:
return td.alterStatement(mods)
case DiffTypeDrop:
stmt := td.From.DropStatement()
if !mods.AllowUnsafe {
err = &ForbiddenDiffError{
Reason: "DROP TABLE not permitted",
Statement: stmt,
}
}
return stmt, err
default: // DiffTypeRename not supported yet
panic(fmt.Errorf("Unsupported diff type %d", td.Type))
}
}
// Clauses returns the body of the statement represented by the table diff.
// For DROP statements, this will be an empty string. For CREATE statements,
// it will be everything after "CREATE TABLE [name] ". For ALTER statements,
// it will be everything after "ALTER TABLE [name] ".
func (td *TableDiff) Clauses(mods StatementModifiers) (string, error) {
stmt, err := td.Statement(mods)
if stmt == "" {
return stmt, err
}
switch td.Type {
case DiffTypeCreate:
prefix := fmt.Sprintf("CREATE TABLE %s ", EscapeIdentifier(td.To.Name))
return strings.Replace(stmt, prefix, "", 1), err
case DiffTypeAlter:
prefix := fmt.Sprintf("%s ", td.From.AlterStatement())
return strings.Replace(stmt, prefix, "", 1), err
case DiffTypeDrop:
return "", err
default: // DiffTypeRename not supported yet
panic(fmt.Errorf("Unsupported diff type %d", td.Type))
}
}
func (td *TableDiff) alterStatement(mods StatementModifiers) (string, error) {
if !td.supported {
if td.To.UnsupportedDDL {
return "", &UnsupportedDiffError{
ObjectKey: td.ObjectKey(),
ExpectedCreate: td.To.GeneratedCreateStatement(mods.Flavor),
ActualCreate: td.To.CreateStatement,
}
} else if td.From.UnsupportedDDL {
return "", &UnsupportedDiffError{
ObjectKey: td.ObjectKey(),
ExpectedCreate: td.From.GeneratedCreateStatement(mods.Flavor),
ActualCreate: td.From.CreateStatement,
}
} else {
return "", &UnsupportedDiffError{
ObjectKey: td.ObjectKey(),
ExpectedCreate: td.From.CreateStatement,
ActualCreate: td.To.CreateStatement,
}
}
}
// Force StrictIndexOrder to be enabled for InnoDB tables that have no primary
// key and at least one unique index with non-nullable columns
if !mods.StrictIndexOrder && td.To.ClusteredIndexKey() != td.To.PrimaryKey {
mods.StrictIndexOrder = true
}
clauseStrings := make([]string, 0, len(td.alterClauses))
var err error
for _, clause := range td.alterClauses {
if err == nil && !mods.AllowUnsafe {
if clause, ok := clause.(Unsafer); ok && clause.Unsafe() {
err = &ForbiddenDiffError{
Reason: "Unsafe or potentially destructive ALTER TABLE not permitted",
Statement: "",
}
}
}
if clauseString := clause.Clause(mods); clauseString != "" {
clauseStrings = append(clauseStrings, clauseString)
}
}
if len(clauseStrings) == 0 {
return "", nil
}
if mods.LockClause != "" {
lockClause := fmt.Sprintf("LOCK=%s", strings.ToUpper(mods.LockClause))
clauseStrings = append([]string{lockClause}, clauseStrings...)
}
if mods.AlgorithmClause != "" {
algorithmClause := fmt.Sprintf("ALGORITHM=%s", strings.ToUpper(mods.AlgorithmClause))
clauseStrings = append([]string{algorithmClause}, clauseStrings...)
}
stmt := fmt.Sprintf("%s %s", td.From.AlterStatement(), strings.Join(clauseStrings, ", "))
if fde, isForbiddenDiff := err.(*ForbiddenDiffError); isForbiddenDiff {
fde.Statement = stmt
}
return stmt, err
}
///// RoutineDiff //////////////////////////////////////////////////////////////
// RoutineDiff represents a difference between two routines.
type RoutineDiff struct {
From *Routine
To *Routine
ForMetadata bool // if true, routine is being replaced only to update creation-time metadata
}
// ObjectKey returns a value representing the type and name of the routine being
// diff'ed. The type will be either ObjectTypeFunc or ObjectTypeProc. The name
// will be the From side routine, unless this is a Create, in which case the To
// side routine name is used.
func (rd *RoutineDiff) ObjectKey() ObjectKey {
if rd != nil && rd.From != nil {
return ObjectKey{Type: rd.From.Type, Name: rd.From.Name}
} else if rd != nil && rd.To != nil {
return ObjectKey{Type: rd.To.Type, Name: rd.To.Name}
}
return ObjectKey{}
}
// DiffType returns the type of diff operation.
func (rd *RoutineDiff) DiffType() DiffType {
if rd == nil || (rd.To == nil && rd.From == nil) {
return DiffTypeNone
} else if rd.To == nil {
return DiffTypeDrop
} else if rd.From == nil {
return DiffTypeCreate
}
return DiffTypeAlter
}
// Statement returns the full DDL statement corresponding to the RoutineDiff. A
// blank string may be returned if the mods indicate the statement should be
// skipped. If the mods indicate the statement should be disallowed, it will
// still be returned as-is, but the error will be non-nil. Be sure not to
// ignore the error value of this method.
func (rd *RoutineDiff) Statement(mods StatementModifiers) (string, error) {
// If we're replacing a routine only because its creation-time sql_mode or
// db collation has changed, only proceed if mods indicate we should. (This
// type of replacement is effectively opt-in because it is counter-intuitive
// and obscure.)
if rd != nil && rd.ForMetadata && !mods.CompareMetadata {
return "", nil
}
switch rd.DiffType() {
case DiffTypeNone:
return "", nil
case DiffTypeCreate:
return rd.To.CreateStatement, nil
case DiffTypeDrop:
var comment string
if rd.ForMetadata {
comment = fmt.Sprintf("# Dropping and re-creating %s to update metadata\n", rd.ObjectKey())
}
stmt := fmt.Sprintf("%s%s", comment, rd.From.DropStatement())
var err error
if !mods.AllowUnsafe {
err = &ForbiddenDiffError{
Reason: fmt.Sprintf("DROP %s not permitted", rd.From.Type.Caps()),
Statement: stmt,
}
}
return stmt, err
default: // DiffTypeAlter and DiffTypeRename not supported yet
return "", fmt.Errorf("Unsupported diff type %d", rd.DiffType())
}
}
///// Errors ///////////////////////////////////////////////////////////////////
// ForbiddenDiffError can be returned by ObjectDiff.Statement when the supplied
// statement modifiers do not permit the generated ObjectDiff to be used in this
// situation.
type ForbiddenDiffError struct {
Reason string
Statement string
}
// Error satisfies the builtin error interface.
func (e *ForbiddenDiffError) Error() string {
return e.Reason
}
// IsForbiddenDiff returns true if err represents an "unsafe" alteration that
// has not explicitly been permitted by the supplied StatementModifiers.
func IsForbiddenDiff(err error) bool {
_, ok := err.(*ForbiddenDiffError)
return ok
}
// UnsupportedDiffError can be returned by ObjectDiff.Statement if Tengo is
// unable to transform the object due to use of unsupported features.
type UnsupportedDiffError struct {
ObjectKey ObjectKey
ExpectedCreate string
ActualCreate string
}
// Error satisfies the builtin error interface.
func (e *UnsupportedDiffError) Error() string {
return fmt.Sprintf("%s uses unsupported features and cannot be diff'ed", e.ObjectKey)
}
// ExtendedError returns a string with more information about why the diff is
// not supported.
func (e *UnsupportedDiffError) ExtendedError() string {
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(e.ExpectedCreate),
B: difflib.SplitLines(e.ActualCreate),
FromFile: "Expected CREATE",
ToFile: "MySQL-actual SHOW CREATE",
Context: 0,
}
diffText, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
return err.Error()
}
return diffText
}
// IsUnsupportedDiff returns true if err represents an object that cannot be
// diff'ed due to use of features not supported by this package.
func IsUnsupportedDiff(err error) bool {
_, ok := err.(*UnsupportedDiffError)
return ok
}