github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/cmd/camtool/dbinit.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     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 main
    18  
    19  import (
    20  	"database/sql"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"strings"
    26  
    27  	"camlistore.org/pkg/cmdmain"
    28  	"camlistore.org/pkg/sorted/mongo"
    29  	"camlistore.org/pkg/sorted/mysql"
    30  	"camlistore.org/pkg/sorted/postgres"
    31  	"camlistore.org/pkg/sorted/sqlite"
    32  
    33  	_ "camlistore.org/third_party/github.com/lib/pq"
    34  	_ "camlistore.org/third_party/github.com/ziutek/mymysql/godrv"
    35  	"camlistore.org/third_party/labix.org/v2/mgo"
    36  )
    37  
    38  type dbinitCmd struct {
    39  	user     string
    40  	password string
    41  	host     string
    42  	dbName   string
    43  	dbType   string
    44  	sslMode  string // Postgres SSL mode configuration
    45  
    46  	wipe bool
    47  	keep bool
    48  	wal  bool // Write-Ahead Logging for SQLite
    49  }
    50  
    51  func init() {
    52  	cmdmain.RegisterCommand("dbinit", func(flags *flag.FlagSet) cmdmain.CommandRunner {
    53  		cmd := new(dbinitCmd)
    54  		flags.StringVar(&cmd.user, "user", "root", "Admin user.")
    55  		flags.StringVar(&cmd.password, "password", "", "Admin password.")
    56  		flags.StringVar(&cmd.host, "host", "localhost", "host[:port]")
    57  		flags.StringVar(&cmd.dbName, "dbname", "", "Database to wipe or create. For sqlite, this is the db filename.")
    58  		flags.StringVar(&cmd.dbType, "dbtype", "mysql", "Which RDMS to use; possible values: mysql, postgres, sqlite, mongo.")
    59  		flags.StringVar(&cmd.sslMode, "sslmode", "require", "Configure SSL mode for postgres. Possible values: require, verify-full, disable.")
    60  
    61  		flags.BoolVar(&cmd.wipe, "wipe", false, "Wipe the database and re-create it?")
    62  		flags.BoolVar(&cmd.keep, "ignoreexists", false, "Do nothing if database already exists.")
    63  		// Defaults to true, because it fixes http://camlistore.org/issues/114
    64  		flags.BoolVar(&cmd.wal, "wal", true, "Enable Write-Ahead Logging with SQLite, for better concurrency. Requires SQLite >= 3.7.0.")
    65  
    66  		return cmd
    67  	})
    68  }
    69  
    70  func (c *dbinitCmd) Describe() string {
    71  	return "Set up the database for the indexer."
    72  }
    73  
    74  func (c *dbinitCmd) Usage() {
    75  	fmt.Fprintf(os.Stderr, "Usage: camtool [globalopts] dbinit [dbinitopts] \n")
    76  }
    77  
    78  func (c *dbinitCmd) Examples() []string {
    79  	return []string{
    80  		"-user root -password root -host localhost -dbname camliprod -wipe",
    81  	}
    82  }
    83  
    84  func (c *dbinitCmd) RunCommand(args []string) error {
    85  	if c.dbName == "" {
    86  		return cmdmain.UsageError("--dbname flag required")
    87  	}
    88  
    89  	if c.dbType != "mysql" && c.dbType != "postgres" && c.dbType != "mongo" {
    90  		if c.dbType == "sqlite" {
    91  			if !WithSQLite {
    92  				return ErrNoSQLite
    93  			}
    94  			c.wal = c.wal && sqlite.IsWALCapable()
    95  			if !c.wal {
    96  				fmt.Print("WARNING: An SQLite indexer without Write Ahead Logging will most likely fail. See http://camlistore.org/issues/114\n")
    97  			}
    98  		} else {
    99  			return cmdmain.UsageError(fmt.Sprintf("--dbtype flag: got %v, want %v", c.dbType, `"mysql" or "postgres" or "sqlite", or "mongo"`))
   100  		}
   101  	}
   102  
   103  	var rootdb *sql.DB
   104  	var err error
   105  	switch c.dbType {
   106  	case "postgres":
   107  		conninfo := fmt.Sprintf("user=%s dbname=%s host=%s password=%s sslmode=%s", c.user, "postgres", c.host, c.password, c.sslMode)
   108  		rootdb, err = sql.Open("postgres", conninfo)
   109  	case "mysql":
   110  		rootdb, err = sql.Open("mymysql", "mysql/"+c.user+"/"+c.password)
   111  	}
   112  	if err != nil {
   113  		exitf("Error connecting to the root %s database: %v", c.dbType, err)
   114  	}
   115  
   116  	dbname := c.dbName
   117  	exists := c.dbExists(rootdb)
   118  	if exists {
   119  		if c.keep {
   120  			return nil
   121  		}
   122  		if !c.wipe {
   123  			return cmdmain.UsageError(fmt.Sprintf("Database %q already exists, but --wipe not given. Stopping.", dbname))
   124  		}
   125  		if c.dbType == "mongo" {
   126  			return c.wipeMongo()
   127  		}
   128  		if c.dbType != "sqlite" {
   129  			do(rootdb, "DROP DATABASE "+dbname)
   130  		}
   131  	}
   132  	switch c.dbType {
   133  	case "sqlite":
   134  		_, err := os.Create(dbname)
   135  		if err != nil {
   136  			exitf("Error creating file %v for sqlite db: %v", dbname, err)
   137  		}
   138  	case "mongo":
   139  		return nil
   140  	case "postgres":
   141  		// because we want string comparison to work as on MySQL and SQLite.
   142  		// in particular we want: 'foo|bar' < 'foo}' (which is not the case with an utf8 collation apparently).
   143  		do(rootdb, "CREATE DATABASE "+dbname+" LC_COLLATE = 'C' TEMPLATE = template0")
   144  	default:
   145  		do(rootdb, "CREATE DATABASE "+dbname)
   146  	}
   147  
   148  	var db *sql.DB
   149  	switch c.dbType {
   150  	case "postgres":
   151  		conninfo := fmt.Sprintf("user=%s dbname=%s host=%s password=%s sslmode=%s", c.user, dbname, c.host, c.password, c.sslMode)
   152  		db, err = sql.Open("postgres", conninfo)
   153  	case "sqlite":
   154  		db, err = sql.Open("sqlite3", dbname)
   155  	default:
   156  		db, err = sql.Open("mymysql", dbname+"/"+c.user+"/"+c.password)
   157  	}
   158  	if err != nil {
   159  		return fmt.Errorf("Connecting to the %s %s database: %v", dbname, c.dbType, err)
   160  	}
   161  
   162  	switch c.dbType {
   163  	case "postgres":
   164  		for _, tableSql := range postgres.SQLCreateTables() {
   165  			do(db, tableSql)
   166  		}
   167  		for _, statement := range postgres.SQLDefineReplace() {
   168  			do(db, statement)
   169  		}
   170  		doQuery(db, fmt.Sprintf(`SELECT replaceintometa('version', '%d')`, postgres.SchemaVersion()))
   171  	case "mysql":
   172  		for _, tableSql := range mysql.SQLCreateTables() {
   173  			do(db, tableSql)
   174  		}
   175  		do(db, fmt.Sprintf(`REPLACE INTO meta VALUES ('version', '%d')`, mysql.SchemaVersion()))
   176  	case "sqlite":
   177  		for _, tableSql := range sqlite.SQLCreateTables() {
   178  			do(db, tableSql)
   179  		}
   180  		if c.wal {
   181  			do(db, sqlite.EnableWAL())
   182  		}
   183  		do(db, fmt.Sprintf(`REPLACE INTO meta VALUES ('version', '%d')`, sqlite.SchemaVersion()))
   184  	}
   185  	return nil
   186  }
   187  
   188  func do(db *sql.DB, sql string) {
   189  	_, err := db.Exec(sql)
   190  	if err != nil {
   191  		exitf("Error %v running SQL: %s", err, sql)
   192  	}
   193  }
   194  
   195  func doQuery(db *sql.DB, sql string) {
   196  	r, err := db.Query(sql)
   197  	if err == nil {
   198  		r.Close()
   199  		return
   200  	}
   201  	exitf("Error %v running SQL: %s", err, sql)
   202  }
   203  
   204  func (c *dbinitCmd) dbExists(db *sql.DB) bool {
   205  	query := "SHOW DATABASES"
   206  	switch c.dbType {
   207  	case "postgres":
   208  		query = "SELECT datname FROM pg_database"
   209  	case "mysql":
   210  		query = "SHOW DATABASES"
   211  	case "sqlite":
   212  		// There is no point in using sql.Open because it apparently does
   213  		// not return an error when the file does not exist.
   214  		fi, err := os.Stat(c.dbName)
   215  		return err == nil && fi.Size() > 0
   216  	case "mongo":
   217  		session, err := c.mongoSession()
   218  		if err != nil {
   219  			exitf("%v", err)
   220  		}
   221  		defer session.Close()
   222  		n, err := session.DB(c.dbName).C(mongo.CollectionName).Find(nil).Limit(1).Count()
   223  		if err != nil {
   224  			exitf("%v", err)
   225  		}
   226  		return n != 0
   227  	}
   228  	rows, err := db.Query(query)
   229  	check(err)
   230  	defer rows.Close()
   231  	for rows.Next() {
   232  		var db string
   233  		check(rows.Scan(&db))
   234  		if db == c.dbName {
   235  			return true
   236  		}
   237  	}
   238  	return false
   239  }
   240  
   241  func check(err error) {
   242  	if err == nil {
   243  		return
   244  	}
   245  	exitf("SQL error: %v", err)
   246  }
   247  
   248  func exitf(format string, args ...interface{}) {
   249  	if !strings.HasSuffix(format, "\n") {
   250  		format = format + "\n"
   251  	}
   252  	cmdmain.Errorf(format, args)
   253  	cmdmain.Exit(1)
   254  }
   255  
   256  var WithSQLite = false
   257  
   258  var ErrNoSQLite = errors.New("the command was not built with SQLite support. See https://code.google.com/p/camlistore/wiki/SQLite" + compileHint())
   259  
   260  func compileHint() string {
   261  	if _, err := os.Stat("/etc/apt"); err == nil {
   262  		return " (Required: apt-get install libsqlite3-dev)"
   263  	}
   264  	return ""
   265  }
   266  
   267  // mongoSession returns an *mgo.Session or nil if c.dbtype is
   268  // not "mongo" or if there was an error.
   269  func (c *dbinitCmd) mongoSession() (*mgo.Session, error) {
   270  	if c.dbType != "mongo" {
   271  		return nil, nil
   272  	}
   273  	url := ""
   274  	if c.user == "" || c.password == "" {
   275  		url = c.host
   276  	} else {
   277  		url = c.user + ":" + c.password + "@" + c.host + "/" + c.dbName
   278  	}
   279  	return mgo.Dial(url)
   280  }
   281  
   282  // wipeMongo erases all documents from the mongo collection
   283  // if c.dbType is "mongo".
   284  func (c *dbinitCmd) wipeMongo() error {
   285  	if c.dbType != "mongo" {
   286  		return nil
   287  	}
   288  	session, err := c.mongoSession()
   289  	if err != nil {
   290  		return err
   291  	}
   292  	defer session.Close()
   293  	if _, err := session.DB(c.dbName).C(mongo.CollectionName).RemoveAll(nil); err != nil {
   294  		return err
   295  	}
   296  	return nil
   297  }