From 08720f94a235243d1c14b9575acaf0183a932d32 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Mon, 27 Jan 2020 19:26:45 -0700 Subject: [PATCH] Improve import-db/export-db with --target-db and --no-drop flags, fixes #1961, fixes #1652 (#2037) * Add import-db --target-db and --no-drop flags, fixes #1961 * Change to drop-by-default on import * Add targetDB to export-db * Allow import-db and export-db to take project arg * Use -f as shorthand for --src in import-db --- cmd/ddev/cmd/export-db.go | 30 +- cmd/ddev/cmd/export-db_test.go | 69 +++++ cmd/ddev/cmd/import-db.go | 39 ++- cmd/ddev/cmd/import-db_test.go | 29 +- cmd/ddev/cmd/sequelpro_test.go | 3 +- .../cmd/testdata/TestCmdExportDB/users.sql | 55 ++++ docs/users/cli-usage.md | 4 +- docs/users/faq.md | 2 +- pkg/ddevapp/ddevapp.go | 44 ++- pkg/ddevapp/ddevapp_test.go | 261 +++++++++++------- .../testdata/TestDdevImportDB/oneuser.sql | 53 ++++ .../testdata/TestDdevImportDB/stdintable.sql | 55 ++++ 12 files changed, 504 insertions(+), 140 deletions(-) create mode 100644 cmd/ddev/cmd/export-db_test.go create mode 100644 cmd/ddev/cmd/testdata/TestCmdExportDB/users.sql create mode 100644 pkg/ddevapp/testdata/TestDdevImportDB/oneuser.sql create mode 100644 pkg/ddevapp/testdata/TestDdevImportDB/stdintable.sql diff --git a/cmd/ddev/cmd/export-db.go b/cmd/ddev/cmd/export-db.go index 1f856f71e1f..47b9437cd49 100644 --- a/cmd/ddev/cmd/export-db.go +++ b/cmd/ddev/cmd/export-db.go @@ -1,8 +1,6 @@ package cmd import ( - "os" - "github.com/drud/ddev/pkg/ddevapp" "github.com/drud/ddev/pkg/dockerutil" "github.com/drud/ddev/pkg/util" @@ -11,32 +9,35 @@ import ( var outFileName string var gzipOption bool +var exportTargetDB string // ExportDBCmd is the `ddev export-db` command. var ExportDBCmd = &cobra.Command{ - Use: "export-db", - Short: "Dump a database to stdout or to a file", - Long: `Dump a database to stdout or to a file.`, - Example: "ddev export-db >/tmp/db.sql.gz\nddev export-db --gzip=false >/tmp/db.sql\nddev export-db -f /tmp/db.sql.gz", + Use: "export-db [project]", + Short: "Dump a database to stdout or to a file", + Long: `Dump a database to stdout or to a file.`, + Example: `ddev export-db > ~/tmp/db.sql.gz +ddev export-db --gzip=false > ~/tmp/db.sql +ddev export-db -f ~/tmp/db.sql.gz +ddev export-db --gzip=false myproject > ~/tmp/myproject.sql +ddev export-db someproject > ~/tmp/someproject.sql`, + Args: cobra.RangeArgs(0, 1), PreRun: func(cmd *cobra.Command, args []string) { - if len(args) > 0 { - err := cmd.Usage() - util.CheckErr(err) - os.Exit(0) - } dockerutil.EnsureDdevNetwork() }, Run: func(cmd *cobra.Command, args []string) { - app, err := ddevapp.GetActiveApp("") + projects, err := getRequestedProjects(args, false) if err != nil { - util.Failed("Failed to find project from which to export database: %v", err) + util.Failed("Unable to get project(s): %v", err) } + app := projects[0] + if app.SiteStatus() != ddevapp.SiteRunning { util.Failed("ddev can't export-db until the project is started, please start it first.") } - err = app.ExportDB(outFileName, gzipOption) + err = app.ExportDB(outFileName, gzipOption, targetDB) if err != nil { util.Failed("Failed to export database for %s: %v", app.GetName(), err) } @@ -46,5 +47,6 @@ var ExportDBCmd = &cobra.Command{ func init() { ExportDBCmd.Flags().StringVarP(&outFileName, "file", "f", "", "Provide the path to output the dump") ExportDBCmd.Flags().BoolVarP(&gzipOption, "gzip", "z", true, "If provided asset is an archive, provide the path to extract within the archive.") + ExportDBCmd.Flags().StringVarP(&exportTargetDB, "target-db", "d", "db", "If provided, target-db is alternate database to export") RootCmd.AddCommand(ExportDBCmd) } diff --git a/cmd/ddev/cmd/export-db_test.go b/cmd/ddev/cmd/export-db_test.go new file mode 100644 index 00000000000..d57fc2ddb92 --- /dev/null +++ b/cmd/ddev/cmd/export-db_test.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "github.com/drud/ddev/pkg/ddevapp" + "github.com/drud/ddev/pkg/fileutil" + "github.com/drud/ddev/pkg/testcommon" + asrt "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// TestCmdExportDB does an export-db +func TestCmdExportDB(t *testing.T) { + assert := asrt.New(t) + + testDir, _ := os.Getwd() + site := TestSites[0] + cleanup := site.Chdir() + + app, err := ddevapp.NewApp(site.Dir, false, "") + assert.NoError(err) + + defer func() { + // Make sure all databases are back to default empty + _ = app.Stop(true, false) + _ = app.Start() + cleanup() + }() + + // Read in a database + inputFileName := filepath.Join(testDir, "testdata", t.Name(), "users.sql") + _ = os.MkdirAll("tmp", 0755) + // Run the import-db command with stdin coming from users.sql + command := exec.Command(DdevBin, "import-db", "-f="+inputFileName) + importDBOutput, err := command.CombinedOutput() + require.NoError(t, err, "failed import-db from %s: %v", inputFileName, importDBOutput) + + _, _, err = app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: "mysql -e 'SHOW TABLES;'", + }) + assert.NoError(err) + + // Export the database and verify content of output + _ = os.MkdirAll(filepath.Join("tmp", t.Name()), 0755) + outputFileName := filepath.Join(site.Dir, "tmp", t.Name(), "output.sql") + _ = os.RemoveAll(outputFileName) + command = exec.Command(DdevBin, "export-db", "-f="+outputFileName, "--gzip=false") + byteout, err := command.CombinedOutput() + require.NoError(t, err, "byteout=%s", string(byteout)) + assert.FileExists(outputFileName) + assert.True(fileutil.FgrepStringInFile(outputFileName, "13751eca-19cf-41c2-90d4-9363f3a07c45")) + + // Test with named project (outside project directory) + tmpDir := testcommon.CreateTmpDir("TestCmdExportDB") + err = os.Chdir(tmpDir) + assert.NoError(err) + + err = os.RemoveAll(outputFileName) + assert.NoError(err) + command = exec.Command(DdevBin, "export-db", site.Name, "-f="+outputFileName, "--gzip=false") + byteout, err = command.CombinedOutput() + assert.NoError(err, "export-db failure output=%s", string(byteout)) + assert.FileExists(outputFileName) + assert.True(fileutil.FgrepStringInFile(outputFileName, "13751eca-19cf-41c2-90d4-9363f3a07c45")) +} diff --git a/cmd/ddev/cmd/import-db.go b/cmd/ddev/cmd/import-db.go index b6bc8d52148..1dbbbd2d2d5 100644 --- a/cmd/ddev/cmd/import-db.go +++ b/cmd/ddev/cmd/import-db.go @@ -1,8 +1,6 @@ package cmd import ( - "os" - "github.com/drud/ddev/pkg/ddevapp" "github.com/drud/ddev/pkg/dockerutil" "github.com/drud/ddev/pkg/util" @@ -11,36 +9,40 @@ import ( var dbSource string var dbExtPath string +var targetDB string +var noDrop bool var progressOption bool // ImportDBCmd represents the `ddev import-db` command. var ImportDBCmd = &cobra.Command{ - Use: "import-db", + Use: "import-db [project]", + Args: cobra.RangeArgs(0, 1), Short: "Import a sql archive into the project.", Long: `Import a sql archive into the project. The database can be provided as a SQL dump in a .sql, .sql.gz, .mysql, .mysql.gz, .zip, .tgz, or .tar.gz format. For the zip and tar formats, the path to a .sql file within the archive -can be provided if it is not located at the top level of the archive. Note the related "ddev mysql" command`, +can be provided if it is not located at the top level of the archive. An optional target database +can also be provided; the default is the default database named "db". +Also note the related "ddev mysql" command`, Example: `ddev import-db ddev import-db --src=.tarballs/junk.sql ddev import-db --src=.tarballs/junk.sql.gz +ddev import-db --target-db=newdb --src=.tarballs/junk.sql.gz ddev import-db 0 { - err := cmd.Usage() - util.CheckErr(err) - os.Exit(0) - } dockerutil.EnsureDdevNetwork() }, Run: func(cmd *cobra.Command, args []string) { - app, err := ddevapp.GetActiveApp("") + projects, err := getRequestedProjects(args, false) if err != nil { - util.Failed("Failed to import database: %v", err) + util.Failed("Unable to get project(s): %v", err) } + app := projects[0] + if app.SiteStatus() != ddevapp.SiteRunning { err = app.Start() if err != nil { @@ -48,17 +50,24 @@ gzip -dc db.sql.gz | ddev import-db`, } } - err = app.ImportDB(dbSource, dbExtPath, progressOption) + err = app.ImportDB(dbSource, dbExtPath, progressOption, noDrop, targetDB) if err != nil { - util.Failed("Failed to import database for %s: %v", app.GetName(), err) + util.Failed("Failed to import database %s for %s: %v", targetDB, app.GetName(), err) + } + util.Success("Successfully imported database '%s' for %v", targetDB, app.GetName()) + if noDrop { + util.Success("Existing database '%s' was NOT dropped before importing", targetDB) + } else { + util.Success("Existing database '%s' was dropped before importing", targetDB) } - util.Success("Successfully imported database for %v", app.GetName()) }, } func init() { - ImportDBCmd.Flags().StringVarP(&dbSource, "src", "", "", "Provide the path to a sql dump in .sql or tar/tar.gz/tgz/zip format") + ImportDBCmd.Flags().StringVarP(&dbSource, "src", "f", "", "Provide the path to a sql dump in .sql or tar/tar.gz/tgz/zip format") ImportDBCmd.Flags().StringVarP(&dbExtPath, "extract-path", "", "", "If provided asset is an archive, provide the path to extract within the archive.") + ImportDBCmd.Flags().StringVarP(&targetDB, "target-db", "d", "db", "If provided asset is an archive, provide the path to extract within the archive.") + ImportDBCmd.Flags().BoolVarP(&noDrop, "no-drop", "", false, "Set if you do NOT want to drop the db before importing") ImportDBCmd.Flags().BoolVarP(&progressOption, "progress", "p", true, "Display a progress bar during import") RootCmd.AddCommand(ImportDBCmd) } diff --git a/cmd/ddev/cmd/import-db_test.go b/cmd/ddev/cmd/import-db_test.go index 72f4048dfb6..e987a04d6b5 100644 --- a/cmd/ddev/cmd/import-db_test.go +++ b/cmd/ddev/cmd/import-db_test.go @@ -2,6 +2,7 @@ package cmd import ( "github.com/drud/ddev/pkg/ddevapp" + "github.com/drud/ddev/pkg/testcommon" asrt "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "os" @@ -18,16 +19,21 @@ func TestCmdImportDB(t *testing.T) { testDir, _ := os.Getwd() site := TestSites[0] cleanup := site.Chdir() - defer cleanup() app, err := ddevapp.NewApp(site.Dir, false, "") assert.NoError(err) + defer func() { + // Make sure all databases are back to default empty + _ = app.Stop(true, false) + _ = app.Start() + cleanup() + }() // Make sure we start with nothing in db out, _, err := app.Exec(&ddevapp.ExecOpts{ Service: "web", - Cmd: "mysql -e 'SHOW TABLES;'", + Cmd: "mysql -N -e 'SHOW TABLES;'", }) - assert.NoError(err) + assert.NoError(err, "mysql exec output=%s", out) require.Equal(t, "", out) // Set up to read from the sql import file @@ -52,4 +58,21 @@ func TestCmdImportDB(t *testing.T) { }) assert.NoError(err) assert.Equal("Tables_in_db\nusers\n", out) + + // Test with named project (outside project directory) + // Test with named project (outside project directory) + tmpDir := testcommon.CreateTmpDir("TestCmdExportDB") + err = os.Chdir(tmpDir) + assert.NoError(err) + + // Run the import-db command with stdin coming from users.sql + byteout, err := exec.Command(DdevBin, "import-db", app.Name, "--target-db=sparedb", "-f="+inputFile).CombinedOutput() + assert.NoError(err, "failed import-db: %v (%s)", err, string(byteout)) + out, _, err = app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: `echo "SELECT COUNT(*) FROM users;" | mysql -N sparedb`, + }) + assert.NoError(err) + assert.Equal("2\n", out) + } diff --git a/cmd/ddev/cmd/sequelpro_test.go b/cmd/ddev/cmd/sequelpro_test.go index 0f47ae405ea..07f63415f39 100644 --- a/cmd/ddev/cmd/sequelpro_test.go +++ b/cmd/ddev/cmd/sequelpro_test.go @@ -50,6 +50,5 @@ func TestSequelproBadApp(t *testing.T) { // Ensure it fails if we run outside of an application root. _, err := handleSequelProCommand(SequelproLoc) assert.Error(err) - assert.Contains(err.Error(), "unable to determine the project") - + assert.Contains(err.Error(), "Could not find a project in") } diff --git a/cmd/ddev/cmd/testdata/TestCmdExportDB/users.sql b/cmd/ddev/cmd/testdata/TestCmdExportDB/users.sql new file mode 100644 index 00000000000..5bda6830a88 --- /dev/null +++ b/cmd/ddev/cmd/testdata/TestCmdExportDB/users.sql @@ -0,0 +1,55 @@ +-- MySQL dump 10.13 Distrib 5.5.54, for debian-linux-gnu (x86_64) +-- +-- Host: db Database: data +-- ------------------------------------------------------ +-- Server version 5.7.17-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `users` +-- + +DROP TABLE IF EXISTS `users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users` ( + `uid` int(10) unsigned NOT NULL, + `uuid` varchar(128) CHARACTER SET ascii NOT NULL, + `langcode` varchar(12) CHARACTER SET ascii NOT NULL, + PRIMARY KEY (`uid`), + UNIQUE KEY `user_field__uuid__value` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The base table for user entities.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `users` +-- + +LOCK TABLES `users` WRITE; +/*!40000 ALTER TABLE `users` DISABLE KEYS */; +set autocommit=0; +INSERT INTO `users` VALUES (0,'13751eca-19cf-41c2-90d4-9363f3a07c45','en'),(1,'186efa0a-8aa3-4eeb-90ce-6302fb9c4e07','en'); +/*!40000 ALTER TABLE `users` ENABLE KEYS */; +UNLOCK TABLES; +commit; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2017-05-31 14:03:34 diff --git a/docs/users/cli-usage.md b/docs/users/cli-usage.md index 4b5147740b2..a4b966ff3e0 100644 --- a/docs/users/cli-usage.md +++ b/docs/users/cli-usage.md @@ -451,7 +451,7 @@ An important aspect of local web development is the ability to have a precise re ### Importing a database -The `ddev import-db` command is provided for importing the MySQL database for a project. Running this command will provide a prompt for you to specify the location of your database import. +The `ddev import-db` command is provided for importing the MySQL database for a project. Running this command will provide a prompt for you to specify the location of your database import. By default `ddev import-db` empties the default "db" database and then loads the provided dumpfile. ``` @@ -507,6 +507,8 @@ ddev import-db Database import notes * Importing from a dumpfile via stdin will not show progress because there's no way the import can know how far along through the import it has progressed. +* Use `ddev import-db --target-db ` to import to a non-default database (other than the default "db" database). This will create the database if it doesn't exist already. +* Use `ddev import-db --no-drop` to import without first emptying the database. * If a database already exists and the import does not specify dropping tables, the contents of the imported dumpfile will be *added* to the database. Most full database dumps do a table drop and create before loading, but if yours does not, you can drop all tables with `ddev stop --remove-data` before importing. ### Exporting a Database diff --git a/docs/users/faq.md b/docs/users/faq.md index 7abff3888b5..79ff6f0371b 100644 --- a/docs/users/faq.md +++ b/docs/users/faq.md @@ -11,7 +11,7 @@ * **Can I run ddev and also other Docker or non-Docker development environments at the same time?** Yes, you can, as long as they're configured with different ports. But it's easiest to shut down one before using the other. For example, if you use Lando for one project, do a `lando poweroff` before using ddev, and then do a `ddev poweroff` before using Lando again. If you run nginx or apache locally, just stop them before using ddev. More information is in the [troubleshooting](troubleshooting.md) section. * **How can I contribute to DDEV-Local?** We love contributions of knowledge, support, docs, and code, and invite you to all of them. Make an issue or PR to the [main repo](https://github.com/drud/ddev). Add your external resource to [awesome-ddev](https://github.com/drud/awesome-ddev). Add your recipe or HOWTO to [ddev-contrib](https://github.com/drud/ddev-contrib). Help others in [Stack Overflow](https://stackoverflow.com/tags/ddev) or [Slack](../index.md#support) or [gitter](https://gitter.im/drud/ddev). * **How can I show my local project to someone else?** It's often the case that we want a customer or coworker to be able to view our local environment, even if they're on a different machine, network, etc. `ddev share` (requires [ngrok](https://ngrok.com)) provides a link that anyone can view and so they can interact with your local project while you allow it. See `ddev share -h` for more information. -* **Can I use additional databases with ddev?** Yes, you can create additional databases and manually do whatever you need on them. `echo "CREATE DATABASE seconddb; GRANT ALL on seconddb.* TO 'db'@'%';" | ddev mysql -uroot -proot`. You can use `ddev mysql` for random queries, or also use the mysql client within either `ddev ssh` or `ddev ssh -s db` as well. +* **Can I use additional databases with ddev?** Yes, you can create additional databases and manually do whatever you need on them. They are automatically created if you use `ddev import-db` with `--target-db`, for example `ddev import-db --target-db=extradb --source=.tarballs/extradb.sql.gz`. You can use `ddev mysql` for random queries, or also use the mysql client within `ddev ssh` or `ddev ssh -s db` as well. * **Can different projects communicate with each other?** Yes, this is commonly required for situations like Drupal migrations. For the web container to access the db container of another project, use `ddev--db` as the hostname of the other project. For example, in project1, use `mysql ddev-project2-db` to access the db server of project2. For HTTP/S communication you can 1) access the web container of project2 directly with the hostname `ddev--web` and port 80 or 443: `curl https://ddev-project2-web` or 2) Access via the ddev router with the official hostname: `curl https://ddev-router -H Host:d7git.ddev.site`. * **How do I make ddev match my production webserver environment?** You can change the PHP major version (currently 5.6 through 7.4) and choose between nginx+fpm (default and apache+fpm and choose the MariaDB version add [extra services like solr and memcached](extend/additional-services.md). You will not be able to make every detail match your production server, but with PHP version and webserver type you'll be close. * **How do I completely destroy a project?** Use `ddev delete ` to destroy a project. (Also, `ddev stop --remove-data` will do the same thing.) By default, a `ddev snapshot` of your database is taken, but you can skip this, see `ddev delete -h` for options. diff --git a/pkg/ddevapp/ddevapp.go b/pkg/ddevapp/ddevapp.go index a4c08369ce6..1c4998f71af 100644 --- a/pkg/ddevapp/ddevapp.go +++ b/pkg/ddevapp/ddevapp.go @@ -284,8 +284,11 @@ func (app *DdevApp) GetWebserverType() string { } // ImportDB takes a source sql dump and imports it to an active site's database container. -func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool) error { +func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool, noDrop bool, targetDB string) error { app.DockerEnv() + if targetDB == "" { + targetDB = "db" + } var extPathPrompt bool dbPath, err := ioutil.TempDir(filepath.Dir(app.ConfigPath), "importdb") //nolint: errcheck @@ -367,11 +370,15 @@ func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool) error // Inside the container, the dir for imports will be at /mnt/ddev_config/ insideContainerImportPath := path.Join("/mnt/ddev_config", filepath.Base(dbPath)) - inContainerCommand := "pv " + insideContainerImportPath + "/*.*sql | mysql db" - if imPath == "" && extPath == "" { - inContainerCommand = "pv | mysql db" + preImportSQL := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s; GRANT ALL ON %s.* TO 'db'@'%%';", targetDB, targetDB) + if !noDrop { + preImportSQL = fmt.Sprintf("DROP DATABASE IF EXISTS %s; ", targetDB) + preImportSQL } + inContainerCommand := fmt.Sprintf(`mysql -uroot -proot -e "%s" && pv %s/*.*sql | mysql %s`, preImportSQL, insideContainerImportPath, targetDB) + if imPath == "" && extPath == "" { + inContainerCommand = fmt.Sprintf(`mysql -uroot -proot -e "%s" && mysql %s`, preImportSQL, targetDB) + } _, _, err = app.Exec(&ExecOpts{ Service: "db", Cmd: inContainerCommand, @@ -407,16 +414,19 @@ func (app *DdevApp) ImportDB(imPath string, extPath string, progress bool) error } // ExportDB exports the db, with optional output to a file, default gzip -func (app *DdevApp) ExportDB(outFile string, gzip bool) error { +// targetDB is the db name if not default "db" +func (app *DdevApp) ExportDB(outFile string, gzip bool, targetDB string) error { app.DockerEnv() - + if targetDB == "" { + targetDB = "db" + } opts := &ExecOpts{ Service: "db", - Cmd: "mysqldump db", + Cmd: "mysqldump " + targetDB, NoCapture: true, } if gzip { - opts.Cmd = "mysqldump db | gzip" + opts.Cmd = fmt.Sprintf("mysqldump %s | gzip", targetDB) } if outFile != "" { f, err := os.OpenFile(outFile, os.O_RDWR|os.O_CREATE, 0644) @@ -434,7 +444,21 @@ func (app *DdevApp) ExportDB(outFile string, gzip bool) error { return err } - return nil + confMsg := "Wrote database dump from " + app.Name + " database '" + targetDB + "'" + if outFile != "" { + confMsg = confMsg + " to file " + outFile + } else { + confMsg = confMsg + " to stdout" + } + if gzip { + confMsg = confMsg + " in gzip format" + } else { + confMsg = confMsg + " in plain text format" + } + + _, err = fmt.Fprintf(os.Stderr, confMsg+".\n") + + return err } // SiteStatus returns the current status of an application determined from web and db service health. @@ -535,7 +559,7 @@ func (app *DdevApp) Pull(provider Provider, opts *PullOptions) error { output.UserOut.Println("Skipping database import.") } else { output.UserOut.Println("Importing database...") - err = app.ImportDB(fileLocation, importPath, true) + err = app.ImportDB(fileLocation, importPath, true, false, "db") if err != nil { return err } diff --git a/pkg/ddevapp/ddevapp_test.go b/pkg/ddevapp/ddevapp_test.go index 9b7ee7ad11b..659e7639add 100644 --- a/pkg/ddevapp/ddevapp_test.go +++ b/pkg/ddevapp/ddevapp_test.go @@ -755,110 +755,168 @@ func TestDdevImportDB(t *testing.T) { app := &ddevapp.DdevApp{} testDir, _ := os.Getwd() - for _, site := range TestSites { - switchDir := site.Chdir() - runTime := util.TimeTrack(time.Now(), fmt.Sprintf("%s DdevImportDB", site.Name)) + site := TestSites[0] - testcommon.ClearDockerEnv() - err := app.Init(site.Dir) - assert.NoError(err) - err = app.StartAndWait(2) - assert.NoError(err) + switchDir := site.Chdir() + runTime := util.TimeTrack(time.Now(), fmt.Sprintf("%s %s", site.Name, t.Name())) - app.Hooks = map[string][]ddevapp.YAMLTask{"post-import-db": {{"exec-host": "touch hello-post-import-db-" + app.Name}}, "pre-import-db": {{"exec-host": "touch hello-pre-import-db-" + app.Name}}} + testcommon.ClearDockerEnv() + err := app.Init(site.Dir) + assert.NoError(err) + err = app.Start() + assert.NoError(err) + defer func() { + app.Hooks = nil + _ = app.WriteConfig() + _ = app.Stop(true, false) + }() - // Test simple db loads. - for _, file := range []string{"users.sql", "users.mysql", "users.sql.gz", "users.mysql.gz", "users.sql.tar", "users.mysql.tar", "users.sql.tar.gz", "users.mysql.tar.gz", "users.sql.tgz", "users.mysql.tgz", "users.sql.zip", "users.mysql.zip"} { - path := filepath.Join(testDir, "testdata", t.Name(), file) - err = app.ImportDB(path, "", false) - assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", path, err) - if err != nil { - continue - } + app.Hooks = map[string][]ddevapp.YAMLTask{"post-import-db": {{"exec-host": "touch hello-post-import-db-" + app.Name}}, "pre-import-db": {{"exec-host": "touch hello-pre-import-db-" + app.Name}}} - // Test that a settings file has correct hash_salt format - switch app.Type { - case nodeps.AppTypeDrupal7: - drupalHashSalt, err := fileutil.FgrepStringInFile(app.SiteDdevSettingsFile, "$drupal_hash_salt") - assert.NoError(err) - assert.True(drupalHashSalt) - case nodeps.AppTypeDrupal8: - settingsHashSalt, err := fileutil.FgrepStringInFile(app.SiteDdevSettingsFile, "settings['hash_salt']") - assert.NoError(err) - assert.True(settingsHashSalt) - case nodeps.AppTypeWordPress: - hasAuthSalt, err := fileutil.FgrepStringInFile(app.SiteSettingsPath, "SECURE_AUTH_SALT") - assert.NoError(err) - assert.True(hasAuthSalt) - } + // Test simple db loads. + for _, file := range []string{"users.sql", "users.mysql", "users.sql.gz", "users.mysql.gz", "users.sql.tar", "users.mysql.tar", "users.sql.tar.gz", "users.mysql.tar.gz", "users.sql.tgz", "users.mysql.tgz", "users.sql.zip", "users.mysql.zip"} { + path := filepath.Join(testDir, "testdata", t.Name(), file) + err = app.ImportDB(path, "", false, false, "db") + assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", path, err) + if err != nil { + continue } - if site.DBTarURL != "" { - _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteTarArchive", "", site.DBTarURL) - assert.NoError(err) - err = app.ImportDB(cachedArchive, "", false) + // Test that a settings file has correct hash_salt format + switch app.Type { + case nodeps.AppTypeDrupal7: + drupalHashSalt, err := fileutil.FgrepStringInFile(app.SiteDdevSettingsFile, "$drupal_hash_salt") assert.NoError(err) - assert.FileExists("hello-pre-import-db-" + app.Name) - assert.FileExists("hello-post-import-db-" + app.Name) - err = os.Remove("hello-pre-import-db-" + app.Name) + assert.True(drupalHashSalt) + case nodeps.AppTypeDrupal8: + settingsHashSalt, err := fileutil.FgrepStringInFile(app.SiteDdevSettingsFile, "settings['hash_salt']") assert.NoError(err) - err = os.Remove("hello-post-import-db-" + app.Name) + assert.True(settingsHashSalt) + case nodeps.AppTypeWordPress: + hasAuthSalt, err := fileutil.FgrepStringInFile(app.SiteSettingsPath, "SECURE_AUTH_SALT") assert.NoError(err) + assert.True(hasAuthSalt) + } + } - out, _, err := app.Exec(&ddevapp.ExecOpts{ - Service: "db", - Cmd: "mysql -e 'SHOW TABLES;'", - }) - assert.NoError(err) + if site.DBTarURL != "" { + _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteTarArchive", "", site.DBTarURL) + assert.NoError(err) + err = app.ImportDB(cachedArchive, "", false, false, "db") + assert.NoError(err) + assert.FileExists("hello-pre-import-db-" + app.Name) + assert.FileExists("hello-post-import-db-" + app.Name) + err = os.Remove("hello-pre-import-db-" + app.Name) + assert.NoError(err) + err = os.Remove("hello-post-import-db-" + app.Name) + assert.NoError(err) - assert.Contains(out, "Tables_in_db") - assert.False(strings.Contains(out, "Empty set")) + out, _, err := app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: "mysql -e 'SHOW TABLES;'", + }) + assert.NoError(err) - assert.NoError(err) - } + assert.Contains(out, "Tables_in_db") + assert.False(strings.Contains(out, "Empty set")) - if site.DBZipURL != "" { - _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteZipArchive", "", site.DBZipURL) + assert.NoError(err) + } - assert.NoError(err) - err = app.ImportDB(cachedArchive, "", false) - assert.NoError(err) + if site.DBZipURL != "" { + _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteZipArchive", "", site.DBZipURL) - assert.FileExists("hello-pre-import-db-" + app.Name) - assert.FileExists("hello-post-import-db-" + app.Name) - _ = os.RemoveAll("hello-pre-import-db-" + app.Name) - _ = os.RemoveAll("hello-post-import-db-" + app.Name) + assert.NoError(err) + err = app.ImportDB(cachedArchive, "", false, false, "db") + assert.NoError(err) - out, _, err := app.Exec(&ddevapp.ExecOpts{ - Service: "db", - Cmd: "mysql -e 'SHOW TABLES;'", - }) - assert.NoError(err) + assert.FileExists("hello-pre-import-db-" + app.Name) + assert.FileExists("hello-post-import-db-" + app.Name) + _ = os.RemoveAll("hello-pre-import-db-" + app.Name) + _ = os.RemoveAll("hello-post-import-db-" + app.Name) - assert.Contains(out, "Tables_in_db") - assert.False(strings.Contains(out, "Empty set")) - } + out, _, err := app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: "mysql -e 'SHOW TABLES;'", + }) + assert.NoError(err) - if site.FullSiteTarballURL != "" { - _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_FullSiteTarballURL", "", site.FullSiteTarballURL) - assert.NoError(err) + assert.Contains(out, "Tables_in_db") + assert.False(strings.Contains(out, "Empty set")) + } - err = app.ImportDB(cachedArchive, "data.sql", false) - assert.NoError(err, "Failed to find data.sql at root of tarball %s", cachedArchive) - assert.FileExists("hello-pre-import-db-" + app.Name) - assert.FileExists("hello-post-import-db-" + app.Name) - _ = os.RemoveAll("hello-pre-import-db-" + app.Name) - _ = os.RemoveAll("hello-post-import-db-" + app.Name) - } - // We don't want all the projects running at once. - app.Hooks = nil - _ = app.WriteConfig() - err = app.Stop(true, false) + if site.FullSiteTarballURL != "" { + _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_FullSiteTarballURL", "", site.FullSiteTarballURL) assert.NoError(err) - runTime() - switchDir() + err = app.ImportDB(cachedArchive, "data.sql", false, false, "db") + assert.NoError(err, "Failed to find data.sql at root of tarball %s", cachedArchive) + assert.FileExists("hello-pre-import-db-" + app.Name) + assert.FileExists("hello-post-import-db-" + app.Name) + _ = os.RemoveAll("hello-pre-import-db-" + app.Name) + _ = os.RemoveAll("hello-post-import-db-" + app.Name) } + + app.Hooks = nil + + for _, db := range []string{"db", "extradb"} { + + // Import from stdin, make sure that works + inputFile := filepath.Join(testDir, "testdata", t.Name(), "stdintable.sql") + f, err := os.Open(inputFile) + require.NoError(t, err) + // nolint: errcheck + defer f.Close() + savedStdin := os.Stdin + os.Stdin = f + err = app.ImportDB("", "", false, false, db) + os.Stdin = savedStdin + assert.NoError(err) + out, _, err := app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: fmt.Sprintf(`echo "SHOW DATABASES LIKE '%s'; SELECT COUNT(*) FROM stdintable;" | mysql -N %s`, db, db), + }) + assert.NoError(err) + assert.Equal(out, fmt.Sprintf("%s\n2\n", db)) + + // Import 2-user users.sql into users table + path := filepath.Join(testDir, "testdata", t.Name(), "users.sql") + err = app.ImportDB(path, "", false, false, db) + assert.NoError(err) + out, stderr, err := app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: fmt.Sprintf(`echo "SELECT COUNT(*) AS TOTALNUMBEROFTABLES FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s';" | mysql -N %s`, db, db), + }) + assert.NoError(err, "exec failed: %v", stderr) + assert.Equal("1\n", out) + + // Import 1-user sql and make sure only one row is left there + path = filepath.Join(testDir, "testdata", t.Name(), "oneuser.sql") + err = app.ImportDB(path, "", false, false, db) + assert.NoError(err) + + out, _, err = app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: fmt.Sprintf(`echo "SELECT COUNT(*) AS TOTALNUMBEROFTABLES FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s';" | mysql -N %s`, db, db), + }) + assert.NoError(err) + assert.Equal("1\n", out) + + // Import 2-user users.sql again, but with nodrop=true + // We should end up with 2 tables now + path = filepath.Join(testDir, "testdata", t.Name(), "users.sql") + err = app.ImportDB(path, "", false, true, db) + assert.NoError(err) + out, _, err = app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: fmt.Sprintf(`echo "SELECT COUNT(*) AS TOTALNUMBEROFTABLES FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '%s';" | mysql -N %s`, db, db), + }) + assert.NoError(err) + assert.Equal("2\n", out) + + } + runTime() + switchDir() } // TestDdevAllDatabases tests db import/export/start with all MariaDB versions @@ -930,7 +988,7 @@ func TestDdevAllDatabases(t *testing.T) { assert.Equal(v, strings.Trim(containerDBVersion, "\n\r ")) importPath := filepath.Join(testDir, "testdata", t.Name(), "users.sql") - err = app.ImportDB(importPath, "", false) + err = app.ImportDB(importPath, "", false, false, "db") assert.NoError(err, "failed to import %v", importPath) _ = os.Mkdir("tmp", 0777) @@ -938,7 +996,7 @@ func TestDdevAllDatabases(t *testing.T) { assert.NoError(err) // Test that we can export-db to a gzipped file - err = app.ExportDB("tmp/users1.sql.gz", true) + err = app.ExportDB("tmp/users1.sql.gz", true, "db") assert.NoError(err) // Validate contents @@ -952,7 +1010,7 @@ func TestDdevAllDatabases(t *testing.T) { assert.NoError(err) // Export to an ungzipped file and validate - err = app.ExportDB("tmp/users2.sql", false) + err = app.ExportDB("tmp/users2.sql", false, "db") assert.NoError(err) // Validate contents @@ -965,7 +1023,7 @@ func TestDdevAllDatabases(t *testing.T) { // Capture to stdout without gzip compression stdout := util.CaptureStdOut() - err = app.ExportDB("", false) + err = app.ExportDB("", false, "db") assert.NoError(err) out := stdout() assert.Contains(out, "Table structure for table `users`") @@ -1011,7 +1069,7 @@ func TestDdevExportDB(t *testing.T) { //nolint: errcheck defer app.Stop(true, false) importPath := filepath.Join(testDir, "testdata", t.Name(), "users.sql") - err = app.ImportDB(importPath, "", false) + err = app.ImportDB(importPath, "", false, false, "db") require.NoError(t, err) _ = os.Mkdir("tmp", 0777) @@ -1020,7 +1078,7 @@ func TestDdevExportDB(t *testing.T) { assert.NoError(err) // Test that we can export-db to a gzipped file - err = app.ExportDB("tmp/users1.sql.gz", true) + err = app.ExportDB("tmp/users1.sql.gz", true, "db") assert.NoError(err) // Validate contents @@ -1034,7 +1092,7 @@ func TestDdevExportDB(t *testing.T) { assert.NoError(err) // Export to an ungzipped file and validate - err = app.ExportDB("tmp/users2.sql", false) + err = app.ExportDB("tmp/users2.sql", false, "db") assert.NoError(err) // Validate contents @@ -1047,12 +1105,27 @@ func TestDdevExportDB(t *testing.T) { // Capture to stdout without gzip compression stdout := util.CaptureStdOut() - err = app.ExportDB("", false) + err = app.ExportDB("", false, "db") assert.NoError(err) output := stdout() assert.Contains(output, "Table structure for table `users`") - // Try it with capture to stdout, validate contents. + // Export an alternate database + importPath = filepath.Join(testDir, "testdata", t.Name(), "users.sql") + err = app.ImportDB(importPath, "", false, false, "anotherdb") + require.NoError(t, err) + err = app.ExportDB("tmp/anotherdb.sql.gz", true, "anotherdb") + assert.NoError(err) + importPath = "tmp/anotherdb.sql.gz" + err = app.ImportDB(importPath, "", false, false, "thirddb") + assert.NoError(err) + out, _, err := app.Exec(&ddevapp.ExecOpts{ + Service: "db", + Cmd: fmt.Sprintf(`echo "SELECT COUNT(*) FROM users;" | mysql -N thirddb`), + }) + assert.NoError(err) + assert.Equal("2\n", out) + runTime() } @@ -1105,7 +1178,7 @@ func TestDdevFullSiteSetup(t *testing.T) { if site.DBTarURL != "" { _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteTarArchive", "", site.DBTarURL) assert.NoError(err) - err = app.ImportDB(cachedArchive, "", false) + err = app.ImportDB(cachedArchive, "", false, false, "db") assert.NoError(err, "failed to import-db with dbtarball %s, app.Type=%s, mariadb_version=%s, mysql_version=%s", site.DBTarURL, app.Type, app.MariaDBVersion, app.MySQLVersion) } @@ -1189,7 +1262,7 @@ func TestDdevRestoreSnapshot(t *testing.T) { //nolint: errcheck defer app.Stop(true, false) - err = app.ImportDB(d7testerTest1Dump, "", false) + err = app.ImportDB(d7testerTest1Dump, "", false, false, "db") require.NoError(t, err, "Failed to app.ImportDB path: %s err: %v", d7testerTest1Dump, err) err = app.StartAndWait(2) @@ -1224,7 +1297,7 @@ func TestDdevRestoreSnapshot(t *testing.T) { err = os.Remove("hello-post-snapshot-" + app.Name) assert.NoError(err) - err = app.ImportDB(d7testerTest2Dump, "", false) + err = app.ImportDB(d7testerTest2Dump, "", false, false, "db") assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", d7testerTest2Dump, err) _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPSURL(), "d7 tester test 2 has 2 nodes", 45) diff --git a/pkg/ddevapp/testdata/TestDdevImportDB/oneuser.sql b/pkg/ddevapp/testdata/TestDdevImportDB/oneuser.sql new file mode 100644 index 00000000000..1bd7efdcdec --- /dev/null +++ b/pkg/ddevapp/testdata/TestDdevImportDB/oneuser.sql @@ -0,0 +1,53 @@ +-- MySQL dump 10.16 Distrib 10.2.30-MariaDB, for debian-linux-gnu (x86_64) +-- +-- Host: localhost Database: db +-- ------------------------------------------------------ +-- Server version 10.2.30-MariaDB-1:10.2.30+maria~bionic-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table users_just_one +-- + +DROP TABLE IF EXISTS `users_just_one`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `users_just_one` ( + `uid` int(10) unsigned NOT NULL, + `uuid` varchar(128) CHARACTER SET ascii NOT NULL, + `langcode` varchar(12) CHARACTER SET ascii NOT NULL, + PRIMARY KEY (`uid`), + UNIQUE KEY `user_field__uuid__value` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The base table for user entities.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `users_just_one +-- + +LOCK TABLES `users_just_one` WRITE; +/*!40000 ALTER TABLE `users_just_one` DISABLE KEYS */; +INSERT INTO `users_just_one` VALUES (0,'13751eca-19cf-41c2-90d4-9363f3a07c45','en'); +/*!40000 ALTER TABLE `users_just_one` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2020-01-21 0:50:19 diff --git a/pkg/ddevapp/testdata/TestDdevImportDB/stdintable.sql b/pkg/ddevapp/testdata/TestDdevImportDB/stdintable.sql new file mode 100644 index 00000000000..a18666d5730 --- /dev/null +++ b/pkg/ddevapp/testdata/TestDdevImportDB/stdintable.sql @@ -0,0 +1,55 @@ +-- MySQL dump 10.13 Distrib 5.5.54, for debian-linux-gnu (x86_64) +-- +-- Host: db Database: data +-- ------------------------------------------------------ +-- Server version 5.7.17-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `stdintable` +-- + +DROP TABLE IF EXISTS `stdintable`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `stdintable` ( + `uid` int(10) unsigned NOT NULL, + `uuid` varchar(128) CHARACTER SET ascii NOT NULL, + `langcode` varchar(12) CHARACTER SET ascii NOT NULL, + PRIMARY KEY (`uid`), + UNIQUE KEY `user_field__uuid__value` (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='The base table for user entities.'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `stdintable` +-- + +LOCK TABLES `stdintable` WRITE; +/*!40000 ALTER TABLE `stdintable` DISABLE KEYS */; +set autocommit=0; +INSERT INTO `stdintable` VALUES (0,'13751eca-19cf-41c2-90d4-9363f3a07c45','en'),(1,'186efa0a-8aa3-4eeb-90ce-6302fb9c4e07','en'); +/*!40000 ALTER TABLE `stdintable` ENABLE KEYS */; +UNLOCK TABLES; +commit; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2017-05-31 14:03:34