From ff1180524abb1699b7683b6fecc2a2c7ede984ae Mon Sep 17 00:00:00 2001 From: Hein Date: Sat, 28 Feb 2026 17:06:49 +0200 Subject: [PATCH] feat(merge): add support for merging from a list of source files --- cmd/relspec/convert.go | 53 +++++++- cmd/relspec/convert_from_list_test.go | 183 ++++++++++++++++++++++++++ cmd/relspec/merge.go | 50 +++++-- cmd/relspec/merge_from_list_test.go | 162 +++++++++++++++++++++++ cmd/relspec/testhelpers_test.go | 180 +++++++++++++++++++++++++ 5 files changed, 608 insertions(+), 20 deletions(-) create mode 100644 cmd/relspec/convert_from_list_test.go create mode 100644 cmd/relspec/merge_from_list_test.go create mode 100644 cmd/relspec/testhelpers_test.go diff --git a/cmd/relspec/convert.go b/cmd/relspec/convert.go index 2a1a093..807b4b4 100644 --- a/cmd/relspec/convert.go +++ b/cmd/relspec/convert.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" + "git.warky.dev/wdevs/relspecgo/pkg/merge" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/readers" "git.warky.dev/wdevs/relspecgo/pkg/readers/bun" @@ -45,6 +46,7 @@ var ( convertSourceType string convertSourcePath string convertSourceConn string + convertFromList []string convertTargetType string convertTargetPath string convertPackageName string @@ -166,6 +168,7 @@ func init() { convertCmd.Flags().StringVar(&convertSourceType, "from", "", "Source format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql, sqlite)") convertCmd.Flags().StringVar(&convertSourcePath, "from-path", "", "Source file path (for file-based formats)") convertCmd.Flags().StringVar(&convertSourceConn, "from-conn", "", "Source connection string (for pgsql) or file path (for sqlite)") + convertCmd.Flags().StringSliceVar(&convertFromList, "from-list", nil, "Comma-separated list of source file paths to read and merge (mutually exclusive with --from-path)") convertCmd.Flags().StringVar(&convertTargetType, "to", "", "Target format (dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql)") convertCmd.Flags().StringVar(&convertTargetPath, "to-path", "", "Target output path (file or directory)") @@ -191,17 +194,29 @@ func runConvert(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stderr, "\n=== RelSpec Schema Converter ===\n") fmt.Fprintf(os.Stderr, "Started at: %s\n\n", getCurrentTimestamp()) + // Validate mutually exclusive flags + if convertSourcePath != "" && len(convertFromList) > 0 { + return fmt.Errorf("--from-path and --from-list are mutually exclusive") + } + // Read source database fmt.Fprintf(os.Stderr, "[1/2] Reading source schema...\n") fmt.Fprintf(os.Stderr, " Format: %s\n", convertSourceType) - if convertSourcePath != "" { - fmt.Fprintf(os.Stderr, " Path: %s\n", convertSourcePath) - } - if convertSourceConn != "" { - fmt.Fprintf(os.Stderr, " Conn: %s\n", maskPassword(convertSourceConn)) - } - db, err := readDatabaseForConvert(convertSourceType, convertSourcePath, convertSourceConn) + var db *models.Database + var err error + + if len(convertFromList) > 0 { + db, err = readDatabaseListForConvert(convertSourceType, convertFromList) + } else { + if convertSourcePath != "" { + fmt.Fprintf(os.Stderr, " Path: %s\n", convertSourcePath) + } + if convertSourceConn != "" { + fmt.Fprintf(os.Stderr, " Conn: %s\n", maskPassword(convertSourceConn)) + } + db, err = readDatabaseForConvert(convertSourceType, convertSourcePath, convertSourceConn) + } if err != nil { return fmt.Errorf("failed to read source: %w", err) } @@ -237,6 +252,30 @@ func runConvert(cmd *cobra.Command, args []string) error { return nil } +func readDatabaseListForConvert(dbType string, files []string) (*models.Database, error) { + if len(files) == 0 { + return nil, fmt.Errorf("file list is empty") + } + + fmt.Fprintf(os.Stderr, " Files: %d file(s)\n", len(files)) + + var base *models.Database + for i, filePath := range files { + fmt.Fprintf(os.Stderr, " [%d/%d] %s\n", i+1, len(files), filePath) + db, err := readDatabaseForConvert(dbType, filePath, "") + if err != nil { + return nil, fmt.Errorf("failed to read %s: %w", filePath, err) + } + if base == nil { + base = db + } else { + merge.MergeDatabases(base, db, &merge.MergeOptions{}) + } + } + + return base, nil +} + func readDatabaseForConvert(dbType, filePath, connString string) (*models.Database, error) { var reader readers.Reader diff --git a/cmd/relspec/convert_from_list_test.go b/cmd/relspec/convert_from_list_test.go new file mode 100644 index 0000000..8922948 --- /dev/null +++ b/cmd/relspec/convert_from_list_test.go @@ -0,0 +1,183 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestReadDatabaseListForConvert_SingleFile(t *testing.T) { + dir := t.TempDir() + file := filepath.Join(dir, "schema.json") + writeTestJSON(t, file, []string{"users"}) + + db, err := readDatabaseListForConvert("json", []string{file}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(db.Schemas) == 0 { + t.Fatal("expected at least one schema") + } + if len(db.Schemas[0].Tables) != 1 { + t.Errorf("expected 1 table, got %d", len(db.Schemas[0].Tables)) + } +} + +func TestReadDatabaseListForConvert_MultipleFiles(t *testing.T) { + dir := t.TempDir() + file1 := filepath.Join(dir, "schema1.json") + file2 := filepath.Join(dir, "schema2.json") + writeTestJSON(t, file1, []string{"users"}) + writeTestJSON(t, file2, []string{"comments"}) + + db, err := readDatabaseListForConvert("json", []string{file1, file2}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + total := 0 + for _, s := range db.Schemas { + total += len(s.Tables) + } + if total != 2 { + t.Errorf("expected 2 tables (users + comments), got %d", total) + } +} + +func TestReadDatabaseListForConvert_PathWithSpaces(t *testing.T) { + spacedDir := filepath.Join(t.TempDir(), "my schema files") + if err := os.MkdirAll(spacedDir, 0755); err != nil { + t.Fatal(err) + } + file := filepath.Join(spacedDir, "my users schema.json") + writeTestJSON(t, file, []string{"users"}) + + db, err := readDatabaseListForConvert("json", []string{file}) + if err != nil { + t.Fatalf("unexpected error with spaced path: %v", err) + } + if db == nil { + t.Fatal("expected non-nil database") + } +} + +func TestReadDatabaseListForConvert_MultipleFilesPathWithSpaces(t *testing.T) { + spacedDir := filepath.Join(t.TempDir(), "my schema files") + if err := os.MkdirAll(spacedDir, 0755); err != nil { + t.Fatal(err) + } + file1 := filepath.Join(spacedDir, "users schema.json") + file2 := filepath.Join(spacedDir, "posts schema.json") + writeTestJSON(t, file1, []string{"users"}) + writeTestJSON(t, file2, []string{"posts"}) + + db, err := readDatabaseListForConvert("json", []string{file1, file2}) + if err != nil { + t.Fatalf("unexpected error with spaced paths: %v", err) + } + + total := 0 + for _, s := range db.Schemas { + total += len(s.Tables) + } + if total != 2 { + t.Errorf("expected 2 tables, got %d", total) + } +} + +func TestReadDatabaseListForConvert_EmptyList(t *testing.T) { + _, err := readDatabaseListForConvert("json", []string{}) + if err == nil { + t.Error("expected error for empty file list") + } +} + +func TestReadDatabaseListForConvert_InvalidFile(t *testing.T) { + _, err := readDatabaseListForConvert("json", []string{"/nonexistent/path/file.json"}) + if err == nil { + t.Error("expected error for nonexistent file") + } +} + +func TestRunConvert_FromListMutuallyExclusiveWithFromPath(t *testing.T) { + saved := saveConvertState() + defer restoreConvertState(saved) + + dir := t.TempDir() + file := filepath.Join(dir, "schema.json") + writeTestJSON(t, file, []string{"users"}) + + convertSourceType = "json" + convertSourcePath = file + convertFromList = []string{file} + convertTargetType = "json" + convertTargetPath = filepath.Join(dir, "out.json") + + err := runConvert(nil, nil) + if err == nil { + t.Error("expected error when --from-path and --from-list are both set") + } +} + +func TestRunConvert_FromListEndToEnd(t *testing.T) { + saved := saveConvertState() + defer restoreConvertState(saved) + + dir := t.TempDir() + file1 := filepath.Join(dir, "users.json") + file2 := filepath.Join(dir, "posts.json") + outFile := filepath.Join(dir, "merged.json") + writeTestJSON(t, file1, []string{"users"}) + writeTestJSON(t, file2, []string{"posts"}) + + convertSourceType = "json" + convertSourcePath = "" + convertSourceConn = "" + convertFromList = []string{file1, file2} + convertTargetType = "json" + convertTargetPath = outFile + convertPackageName = "" + convertSchemaFilter = "" + convertFlattenSchema = false + + if err := runConvert(nil, nil); err != nil { + t.Fatalf("runConvert() error = %v", err) + } + + if _, err := os.Stat(outFile); os.IsNotExist(err) { + t.Error("expected output file to be created") + } +} + +func TestRunConvert_FromListEndToEndPathWithSpaces(t *testing.T) { + saved := saveConvertState() + defer restoreConvertState(saved) + + spacedDir := filepath.Join(t.TempDir(), "my schema dir") + if err := os.MkdirAll(spacedDir, 0755); err != nil { + t.Fatal(err) + } + file1 := filepath.Join(spacedDir, "users schema.json") + file2 := filepath.Join(spacedDir, "posts schema.json") + outFile := filepath.Join(spacedDir, "merged output.json") + writeTestJSON(t, file1, []string{"users"}) + writeTestJSON(t, file2, []string{"posts"}) + + convertSourceType = "json" + convertSourcePath = "" + convertSourceConn = "" + convertFromList = []string{file1, file2} + convertTargetType = "json" + convertTargetPath = outFile + convertPackageName = "" + convertSchemaFilter = "" + convertFlattenSchema = false + + if err := runConvert(nil, nil); err != nil { + t.Fatalf("runConvert() with spaced paths error = %v", err) + } + + if _, err := os.Stat(outFile); os.IsNotExist(err) { + t.Error("expected output file to be created") + } +} diff --git a/cmd/relspec/merge.go b/cmd/relspec/merge.go index 9c87a78..071b0a3 100644 --- a/cmd/relspec/merge.go +++ b/cmd/relspec/merge.go @@ -47,6 +47,7 @@ var ( mergeSourceType string mergeSourcePath string mergeSourceConn string + mergeFromList []string mergeOutputType string mergeOutputPath string mergeOutputConn string @@ -109,8 +110,9 @@ func init() { // Source database flags mergeCmd.Flags().StringVar(&mergeSourceType, "source", "", "Source format (required): dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql") - mergeCmd.Flags().StringVar(&mergeSourcePath, "source-path", "", "Source file path (required for file-based formats)") + mergeCmd.Flags().StringVar(&mergeSourcePath, "source-path", "", "Source file path (required for file-based formats, mutually exclusive with --from-list)") mergeCmd.Flags().StringVar(&mergeSourceConn, "source-conn", "", "Source connection string (required for pgsql)") + mergeCmd.Flags().StringSliceVar(&mergeFromList, "from-list", nil, "Comma-separated list of source file paths to merge (mutually exclusive with --source-path)") // Output flags mergeCmd.Flags().StringVar(&mergeOutputType, "output", "", "Output format (required): dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql") @@ -144,6 +146,11 @@ func runMerge(cmd *cobra.Command, args []string) error { return fmt.Errorf("--output format is required") } + // Validate mutually exclusive source flags + if mergeSourcePath != "" && len(mergeFromList) > 0 { + return fmt.Errorf("--source-path and --from-list are mutually exclusive") + } + // Validate and expand file paths if mergeTargetType != "pgsql" { if mergeTargetPath == "" { @@ -157,8 +164,8 @@ func runMerge(cmd *cobra.Command, args []string) error { } if mergeSourceType != "pgsql" { - if mergeSourcePath == "" { - return fmt.Errorf("--source-path is required for %s format", mergeSourceType) + if mergeSourcePath == "" && len(mergeFromList) == 0 { + return fmt.Errorf("--source-path or --from-list is required for %s format", mergeSourceType) } mergeSourcePath = expandPath(mergeSourcePath) } else if mergeSourceConn == "" { @@ -189,19 +196,36 @@ func runMerge(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stderr, " ✓ Successfully read target database '%s'\n", targetDB.Name) printDatabaseStats(targetDB) - // Step 2: Read source database + // Step 2: Read source database(s) fmt.Fprintf(os.Stderr, "\n[2/3] Reading source database...\n") fmt.Fprintf(os.Stderr, " Format: %s\n", mergeSourceType) - if mergeSourcePath != "" { - fmt.Fprintf(os.Stderr, " Path: %s\n", mergeSourcePath) - } - if mergeSourceConn != "" { - fmt.Fprintf(os.Stderr, " Conn: %s\n", maskPassword(mergeSourceConn)) - } - sourceDB, err := readDatabaseForMerge(mergeSourceType, mergeSourcePath, mergeSourceConn, "Source") - if err != nil { - return fmt.Errorf("failed to read source database: %w", err) + var sourceDB *models.Database + if len(mergeFromList) > 0 { + fmt.Fprintf(os.Stderr, " Files: %d file(s)\n", len(mergeFromList)) + for i, filePath := range mergeFromList { + fmt.Fprintf(os.Stderr, " [%d/%d] %s\n", i+1, len(mergeFromList), filePath) + db, readErr := readDatabaseForMerge(mergeSourceType, expandPath(filePath), "", "Source") + if readErr != nil { + return fmt.Errorf("failed to read source file %s: %w", filePath, readErr) + } + if sourceDB == nil { + sourceDB = db + } else { + merge.MergeDatabases(sourceDB, db, &merge.MergeOptions{}) + } + } + } else { + if mergeSourcePath != "" { + fmt.Fprintf(os.Stderr, " Path: %s\n", mergeSourcePath) + } + if mergeSourceConn != "" { + fmt.Fprintf(os.Stderr, " Conn: %s\n", maskPassword(mergeSourceConn)) + } + sourceDB, err = readDatabaseForMerge(mergeSourceType, mergeSourcePath, mergeSourceConn, "Source") + if err != nil { + return fmt.Errorf("failed to read source database: %w", err) + } } fmt.Fprintf(os.Stderr, " ✓ Successfully read source database '%s'\n", sourceDB.Name) printDatabaseStats(sourceDB) diff --git a/cmd/relspec/merge_from_list_test.go b/cmd/relspec/merge_from_list_test.go new file mode 100644 index 0000000..c590ba8 --- /dev/null +++ b/cmd/relspec/merge_from_list_test.go @@ -0,0 +1,162 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestRunMerge_FromListMutuallyExclusiveWithSourcePath(t *testing.T) { + saved := saveMergeState() + defer restoreMergeState(saved) + + dir := t.TempDir() + file := filepath.Join(dir, "schema.json") + writeTestJSON(t, file, []string{"users"}) + + mergeTargetType = "json" + mergeTargetPath = file + mergeTargetConn = "" + mergeSourceType = "json" + mergeSourcePath = file + mergeSourceConn = "" + mergeFromList = []string{file} + mergeOutputType = "json" + mergeOutputPath = filepath.Join(dir, "out.json") + mergeOutputConn = "" + mergeSkipTables = "" + mergeReportPath = "" + + err := runMerge(nil, nil) + if err == nil { + t.Error("expected error when --source-path and --from-list are both set") + } +} + +func TestRunMerge_FromListSingleFile(t *testing.T) { + saved := saveMergeState() + defer restoreMergeState(saved) + + dir := t.TempDir() + targetFile := filepath.Join(dir, "target.json") + sourceFile := filepath.Join(dir, "source.json") + outFile := filepath.Join(dir, "output.json") + writeTestJSON(t, targetFile, []string{"users"}) + writeTestJSON(t, sourceFile, []string{"posts"}) + + mergeTargetType = "json" + mergeTargetPath = targetFile + mergeTargetConn = "" + mergeSourceType = "json" + mergeSourcePath = "" + mergeSourceConn = "" + mergeFromList = []string{sourceFile} + mergeOutputType = "json" + mergeOutputPath = outFile + mergeOutputConn = "" + mergeSkipTables = "" + mergeReportPath = "" + + if err := runMerge(nil, nil); err != nil { + t.Fatalf("runMerge() error = %v", err) + } + if _, err := os.Stat(outFile); os.IsNotExist(err) { + t.Error("expected output file to be created") + } +} + +func TestRunMerge_FromListMultipleFiles(t *testing.T) { + saved := saveMergeState() + defer restoreMergeState(saved) + + dir := t.TempDir() + targetFile := filepath.Join(dir, "target.json") + source1 := filepath.Join(dir, "source1.json") + source2 := filepath.Join(dir, "source2.json") + outFile := filepath.Join(dir, "output.json") + writeTestJSON(t, targetFile, []string{"users"}) + writeTestJSON(t, source1, []string{"posts"}) + writeTestJSON(t, source2, []string{"comments"}) + + mergeTargetType = "json" + mergeTargetPath = targetFile + mergeTargetConn = "" + mergeSourceType = "json" + mergeSourcePath = "" + mergeSourceConn = "" + mergeFromList = []string{source1, source2} + mergeOutputType = "json" + mergeOutputPath = outFile + mergeOutputConn = "" + mergeSkipTables = "" + mergeReportPath = "" + + if err := runMerge(nil, nil); err != nil { + t.Fatalf("runMerge() error = %v", err) + } + if _, err := os.Stat(outFile); os.IsNotExist(err) { + t.Error("expected output file to be created") + } +} + +func TestRunMerge_FromListPathWithSpaces(t *testing.T) { + saved := saveMergeState() + defer restoreMergeState(saved) + + spacedDir := filepath.Join(t.TempDir(), "my schema files") + if err := os.MkdirAll(spacedDir, 0755); err != nil { + t.Fatal(err) + } + targetFile := filepath.Join(spacedDir, "target schema.json") + sourceFile := filepath.Join(spacedDir, "source schema.json") + outFile := filepath.Join(spacedDir, "merged output.json") + writeTestJSON(t, targetFile, []string{"users"}) + writeTestJSON(t, sourceFile, []string{"comments"}) + + mergeTargetType = "json" + mergeTargetPath = targetFile + mergeTargetConn = "" + mergeSourceType = "json" + mergeSourcePath = "" + mergeSourceConn = "" + mergeFromList = []string{sourceFile} + mergeOutputType = "json" + mergeOutputPath = outFile + mergeOutputConn = "" + mergeSkipTables = "" + mergeReportPath = "" + + if err := runMerge(nil, nil); err != nil { + t.Fatalf("runMerge() with spaced paths error = %v", err) + } + if _, err := os.Stat(outFile); os.IsNotExist(err) { + t.Error("expected output file to be created") + } +} + +func TestRunMerge_FromListMissingSourceType(t *testing.T) { + saved := saveMergeState() + defer restoreMergeState(saved) + + dir := t.TempDir() + file := filepath.Join(dir, "schema.json") + writeTestJSON(t, file, []string{"users"}) + + mergeTargetType = "json" + mergeTargetPath = file + mergeTargetConn = "" + mergeSourceType = "json" + mergeSourcePath = "" + mergeSourceConn = "" + mergeFromList = []string{} // empty list, no source-path either + mergeOutputType = "json" + mergeOutputPath = filepath.Join(dir, "out.json") + mergeOutputConn = "" + mergeSkipTables = "" + mergeReportPath = "" + + err := runMerge(nil, nil) + if err == nil { + t.Error("expected error when neither --source-path nor --from-list is provided") + } +} diff --git a/cmd/relspec/testhelpers_test.go b/cmd/relspec/testhelpers_test.go new file mode 100644 index 0000000..5916e3b --- /dev/null +++ b/cmd/relspec/testhelpers_test.go @@ -0,0 +1,180 @@ +package main + +import ( + "encoding/json" + "os" + "testing" +) + +// minimalColumn is used to build test JSON fixtures. +type minimalColumn struct { + Name string `json:"name"` + Table string `json:"table"` + Schema string `json:"schema"` + Type string `json:"type"` + NotNull bool `json:"not_null"` + IsPrimaryKey bool `json:"is_primary_key"` + AutoIncrement bool `json:"auto_increment"` +} + +type minimalTable struct { + Name string `json:"name"` + Schema string `json:"schema"` + Columns map[string]minimalColumn `json:"columns"` +} + +type minimalSchema struct { + Name string `json:"name"` + Tables []minimalTable `json:"tables"` +} + +type minimalDatabase struct { + Name string `json:"name"` + Schemas []minimalSchema `json:"schemas"` +} + +// writeTestJSON writes a minimal JSON database file with one schema ("public") +// containing tables with the given names. Each table has a single "id" PK column. +func writeTestJSON(t *testing.T, path string, tableNames []string) { + t.Helper() + + tables := make([]minimalTable, len(tableNames)) + for i, name := range tableNames { + tables[i] = minimalTable{ + Name: name, + Schema: "public", + Columns: map[string]minimalColumn{ + "id": { + Name: "id", + Table: name, + Schema: "public", + Type: "bigint", + NotNull: true, + IsPrimaryKey: true, + AutoIncrement: true, + }, + }, + } + } + + db := minimalDatabase{ + Name: "test_db", + Schemas: []minimalSchema{{Name: "public", Tables: tables}}, + } + + data, err := json.Marshal(db) + if err != nil { + t.Fatalf("failed to marshal test JSON: %v", err) + } + if err := os.WriteFile(path, data, 0644); err != nil { + t.Fatalf("failed to write test file %s: %v", path, err) + } +} + +// convertState captures and restores all convert global vars. +type convertState struct { + sourceType string + sourcePath string + sourceConn string + fromList []string + targetType string + targetPath string + packageName string + schemaFilter string + flattenSchema bool +} + +func saveConvertState() convertState { + return convertState{ + sourceType: convertSourceType, + sourcePath: convertSourcePath, + sourceConn: convertSourceConn, + fromList: convertFromList, + targetType: convertTargetType, + targetPath: convertTargetPath, + packageName: convertPackageName, + schemaFilter: convertSchemaFilter, + flattenSchema: convertFlattenSchema, + } +} + +func restoreConvertState(s convertState) { + convertSourceType = s.sourceType + convertSourcePath = s.sourcePath + convertSourceConn = s.sourceConn + convertFromList = s.fromList + convertTargetType = s.targetType + convertTargetPath = s.targetPath + convertPackageName = s.packageName + convertSchemaFilter = s.schemaFilter + convertFlattenSchema = s.flattenSchema +} + +// mergeState captures and restores all merge global vars. +type mergeState struct { + targetType string + targetPath string + targetConn string + sourceType string + sourcePath string + sourceConn string + fromList []string + outputType string + outputPath string + outputConn string + skipDomains bool + skipRelations bool + skipEnums bool + skipViews bool + skipSequences bool + skipTables string + verbose bool + reportPath string + flattenSchema bool +} + +func saveMergeState() mergeState { + return mergeState{ + targetType: mergeTargetType, + targetPath: mergeTargetPath, + targetConn: mergeTargetConn, + sourceType: mergeSourceType, + sourcePath: mergeSourcePath, + sourceConn: mergeSourceConn, + fromList: mergeFromList, + outputType: mergeOutputType, + outputPath: mergeOutputPath, + outputConn: mergeOutputConn, + skipDomains: mergeSkipDomains, + skipRelations: mergeSkipRelations, + skipEnums: mergeSkipEnums, + skipViews: mergeSkipViews, + skipSequences: mergeSkipSequences, + skipTables: mergeSkipTables, + verbose: mergeVerbose, + reportPath: mergeReportPath, + flattenSchema: mergeFlattenSchema, + } +} + +func restoreMergeState(s mergeState) { + mergeTargetType = s.targetType + mergeTargetPath = s.targetPath + mergeTargetConn = s.targetConn + mergeSourceType = s.sourceType + mergeSourcePath = s.sourcePath + mergeSourceConn = s.sourceConn + mergeFromList = s.fromList + mergeOutputType = s.outputType + mergeOutputPath = s.outputPath + mergeOutputConn = s.outputConn + mergeSkipDomains = s.skipDomains + mergeSkipRelations = s.skipRelations + mergeSkipEnums = s.skipEnums + mergeSkipViews = s.skipViews + mergeSkipSequences = s.skipSequences + mergeSkipTables = s.skipTables + mergeVerbose = s.verbose + mergeReportPath = s.reportPath + mergeFlattenSchema = s.flattenSchema +}