github.com/dshekhar95/sub_dgraph@v0.0.0-20230424164411-6be28e40bbf1/dgraph/cmd/migrate/run.go (about)

     1  /*
     2   * Copyright 2022 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package migrate
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  	"github.com/spf13/cobra"
    28  	"github.com/spf13/viper"
    29  
    30  	"github.com/dgraph-io/dgraph/x"
    31  )
    32  
    33  var (
    34  	logger = log.New(os.Stderr, "", 0)
    35  	// Migrate is the sub-command invoked when running "dgraph migrate".
    36  	Migrate x.SubCommand
    37  	quiet   bool // enabling quiet mode would suppress the warning logs
    38  )
    39  
    40  func init() {
    41  	Migrate.Cmd = &cobra.Command{
    42  		Use:   "migrate",
    43  		Short: "Run the Dgraph migration tool from a MySQL database to Dgraph",
    44  		Run: func(cmd *cobra.Command, args []string) {
    45  			if err := run(Migrate.Conf); err != nil {
    46  				logger.Fatalf("%v\n", err)
    47  			}
    48  		},
    49  		Annotations: map[string]string{"group": "tool"},
    50  	}
    51  	Migrate.EnvPrefix = "DGRAPH_MIGRATE"
    52  	Migrate.Cmd.SetHelpTemplate(x.NonRootTemplate)
    53  
    54  	flag := Migrate.Cmd.Flags()
    55  	flag.StringP("user", "", "", "The user for logging in")
    56  	flag.StringP("password", "", "", "The password used for logging in")
    57  	flag.StringP("db", "", "", "The database to import")
    58  	flag.StringP("tables", "", "", "The comma separated list of "+
    59  		"tables to import, an empty string means importing all tables in the database")
    60  	flag.StringP("output_schema", "s", "schema.txt", "The schema output file")
    61  	flag.StringP("output_data", "o", "sql.rdf", "The data output file")
    62  	flag.StringP("separator", "p", ".", "The separator for constructing predicate names")
    63  	flag.BoolP("quiet", "q", false, "Enable quiet mode to suppress the warning logs")
    64  	flag.StringP("host", "", "localhost", "The hostname or IP address of the database server.")
    65  	flag.StringP("port", "", "3306", "The port of the database server.")
    66  }
    67  
    68  func run(conf *viper.Viper) error {
    69  	user := conf.GetString("user")
    70  	db := conf.GetString("db")
    71  	password := conf.GetString("password")
    72  	tables := conf.GetString("tables")
    73  	schemaOutput := conf.GetString("output_schema")
    74  	dataOutput := conf.GetString("output_data")
    75  	host := conf.GetString("host")
    76  	port := conf.GetString("port")
    77  	quiet = conf.GetBool("quiet")
    78  	separator = conf.GetString("separator")
    79  
    80  	switch {
    81  	case len(user) == 0:
    82  		logger.Fatalf("The user property should not be empty.")
    83  	case len(db) == 0:
    84  		logger.Fatalf("The db property should not be empty.")
    85  	case len(password) == 0:
    86  		logger.Fatalf("The password property should not be empty.")
    87  	case len(schemaOutput) == 0:
    88  		logger.Fatalf("Please use the --output_schema option to " +
    89  			"provide the schema output file.")
    90  	case len(dataOutput) == 0:
    91  		logger.Fatalf("Please use the --output_data option to provide the data output file.")
    92  	}
    93  
    94  	if err := checkFile(schemaOutput); err != nil {
    95  		return err
    96  	}
    97  	if err := checkFile(dataOutput); err != nil {
    98  		return err
    99  	}
   100  
   101  	initDataTypes()
   102  
   103  	pool, err := getPool(host, port, user, password, db)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	defer pool.Close()
   108  
   109  	tablesToRead, err := showTables(pool, tables)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	tableInfos := make(map[string]*sqlTable)
   115  	for _, table := range tablesToRead {
   116  		tableInfo, err := parseTables(pool, table, db)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		tableInfos[tableInfo.tableName] = tableInfo
   121  	}
   122  	populateReferencedByColumns(tableInfos)
   123  
   124  	tableGuides := getTableGuides(tableInfos)
   125  
   126  	return generateSchemaAndData(&dumpMeta{
   127  		tableInfos:  tableInfos,
   128  		tableGuides: tableGuides,
   129  		sqlPool:     pool,
   130  	}, schemaOutput, dataOutput)
   131  }
   132  
   133  // checkFile checks if the program is trying to output to an existing file.
   134  // If so, we would need to ask the user whether we should overwrite the file or abort the program.
   135  func checkFile(file string) error {
   136  	if _, err := os.Stat(file); err == nil {
   137  		// The file already exists.
   138  		reader := bufio.NewReader(os.Stdin)
   139  		for {
   140  			fmt.Printf("overwriting the file %s (y/N)? ", file)
   141  			text, err := reader.ReadString('\n')
   142  			if err != nil {
   143  				return err
   144  			}
   145  			text = strings.TrimSpace(text)
   146  
   147  			if len(text) == 0 || strings.ToLower(text) == "n" {
   148  				return errors.Errorf("not allowed to overwrite %s", file)
   149  			}
   150  			if strings.ToLower(text) == "y" {
   151  				return nil
   152  			}
   153  			fmt.Println("Please type y or n (hit enter to choose n)")
   154  		}
   155  	}
   156  
   157  	// The file does not exist.
   158  	return nil
   159  }
   160  
   161  // generateSchemaAndData opens the two files schemaOutput and dataOutput,
   162  // then it dumps schema to the writer backed by schemaOutput, and data in RDF format
   163  // to the writer backed by dataOutput
   164  func generateSchemaAndData(dumpMeta *dumpMeta, schemaOutput string, dataOutput string) error {
   165  	schemaWriter, schemaCancelFunc, err := getFileWriter(schemaOutput)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer schemaCancelFunc()
   170  	dataWriter, dataCancelFunc, err := getFileWriter(dataOutput)
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer dataCancelFunc()
   175  
   176  	dumpMeta.dataWriter = dataWriter
   177  	dumpMeta.schemaWriter = schemaWriter
   178  
   179  	if err := dumpMeta.dumpSchema(); err != nil {
   180  		return errors.Wrapf(err, "while writing schema file")
   181  	}
   182  	if err := dumpMeta.dumpTables(); err != nil {
   183  		return errors.Wrapf(err, "while writing data file")
   184  	}
   185  	return nil
   186  }