github.com/cayleygraph/cayley@v0.7.7/cmd/cayley/command/database.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"runtime/pprof"
     9  	"sort"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/spf13/cobra"
    14  	"github.com/spf13/viper"
    15  
    16  	"github.com/cayleygraph/cayley/clog"
    17  	"github.com/cayleygraph/cayley/graph"
    18  	"github.com/cayleygraph/cayley/internal"
    19  	"github.com/cayleygraph/quad"
    20  )
    21  
    22  const (
    23  	KeyBackend  = "store.backend"
    24  	KeyAddress  = "store.address"
    25  	KeyPath     = "store.path"
    26  	KeyReadOnly = "store.read_only"
    27  	KeyOptions  = "store.options"
    28  
    29  	KeyLoadBatch = "load.batch"
    30  )
    31  
    32  const (
    33  	flagLoad       = "load"
    34  	flagLoadFormat = "load_format"
    35  	flagDump       = "dump"
    36  	flagDumpFormat = "dump_format"
    37  )
    38  
    39  var ErrNotPersistent = errors.New("database type is not persistent")
    40  
    41  func registerLoadFlags(cmd *cobra.Command) {
    42  	// TODO: allow to load multiple files
    43  	cmd.Flags().StringP(flagLoad, "i", "", `quad file to load after initialization (".gz" supported, "-" for stdin)`)
    44  	var names []string
    45  	for _, f := range quad.Formats() {
    46  		if f.Reader != nil {
    47  			names = append(names, f.Name)
    48  		}
    49  	}
    50  	sort.Strings(names)
    51  	cmd.Flags().String(flagLoadFormat, "", `quad file format to use for loading instead of auto-detection ("`+strings.Join(names, `", "`)+`")`)
    52  }
    53  
    54  func registerDumpFlags(cmd *cobra.Command) {
    55  	cmd.Flags().StringP(flagDump, "o", "", `quad file to dump the database to (".gz" supported, "-" for stdout)`)
    56  	var names []string
    57  	for _, f := range quad.Formats() {
    58  		if f.Writer != nil {
    59  			names = append(names, f.Name)
    60  		}
    61  	}
    62  	sort.Strings(names)
    63  	cmd.Flags().String(flagDumpFormat, "", `quad file format to use instead of auto-detection ("`+strings.Join(names, `", "`)+`")`)
    64  }
    65  
    66  func NewInitDatabaseCmd() *cobra.Command {
    67  	cmd := &cobra.Command{
    68  		Use:   "init",
    69  		Short: "Create an empty database.",
    70  		RunE: func(cmd *cobra.Command, args []string) error {
    71  			printBackendInfo()
    72  			name := viper.GetString(KeyBackend)
    73  			if graph.IsRegistered(name) && !graph.IsPersistent(name) {
    74  				return ErrNotPersistent
    75  			}
    76  			// TODO: maybe check read-only flag in config before that?
    77  			if err := initDatabase(); err != nil {
    78  				return err
    79  			}
    80  			return nil
    81  		},
    82  	}
    83  	return cmd
    84  }
    85  
    86  func NewLoadDatabaseCmd() *cobra.Command {
    87  	cmd := &cobra.Command{
    88  		Use:   "load",
    89  		Short: "Bulk-load a quad file into the database.",
    90  		RunE: func(cmd *cobra.Command, args []string) error {
    91  			printBackendInfo()
    92  			p := mustSetupProfile(cmd)
    93  			defer mustFinishProfile(p)
    94  			load, _ := cmd.Flags().GetString(flagLoad)
    95  			if load == "" && len(args) == 1 {
    96  				load = args[0]
    97  			}
    98  			if load == "" {
    99  				return errors.New("one quads file must be specified")
   100  			}
   101  			if init, err := cmd.Flags().GetBool("init"); err != nil {
   102  				return err
   103  			} else if init {
   104  				if err = initDatabase(); err != nil {
   105  					return err
   106  				}
   107  			}
   108  			h, err := openDatabase()
   109  			if err != nil {
   110  				return err
   111  			}
   112  			defer h.Close()
   113  
   114  			qw, err := h.NewQuadWriter()
   115  			if err != nil {
   116  				return err
   117  			}
   118  			defer qw.Close()
   119  
   120  			// TODO: check read-only flag in config before that?
   121  			typ, _ := cmd.Flags().GetString(flagLoadFormat)
   122  			if err = internal.Load(qw, quad.DefaultBatch, load, typ); err != nil {
   123  				return err
   124  			}
   125  
   126  			if dump, _ := cmd.Flags().GetString(flagDump); dump != "" {
   127  				typ, _ := cmd.Flags().GetString(flagDumpFormat)
   128  				if err = dumpDatabase(h, dump, typ); err != nil {
   129  					return err
   130  				}
   131  			}
   132  			return nil
   133  		},
   134  	}
   135  	cmd.Flags().Bool("init", false, "initialize the database before using it")
   136  	registerLoadFlags(cmd)
   137  	registerDumpFlags(cmd)
   138  	return cmd
   139  }
   140  
   141  func NewDumpDatabaseCmd() *cobra.Command {
   142  	cmd := &cobra.Command{
   143  		Use:   "dump",
   144  		Short: "Bulk-dump the database into a quad file.",
   145  		RunE: func(cmd *cobra.Command, args []string) error {
   146  			printBackendInfo()
   147  			dump, _ := cmd.Flags().GetString(flagDump)
   148  			if dump == "" && len(args) == 1 {
   149  				dump = args[0]
   150  			}
   151  			if dump == "" {
   152  				dump = "-"
   153  			}
   154  			h, err := openDatabase()
   155  			if err != nil {
   156  				return err
   157  			}
   158  			defer h.Close()
   159  
   160  			typ, _ := cmd.Flags().GetString(flagDumpFormat)
   161  			return dumpDatabase(h, dump, typ)
   162  		},
   163  	}
   164  	registerDumpFlags(cmd)
   165  	return cmd
   166  }
   167  
   168  func NewUpgradeCmd() *cobra.Command {
   169  	cmd := &cobra.Command{
   170  		Use:   "upgrade",
   171  		Short: "Upgrade Cayley database to current supported format.",
   172  		RunE: func(cmd *cobra.Command, args []string) error {
   173  			printBackendInfo()
   174  			name := viper.GetString(KeyBackend)
   175  			if graph.IsRegistered(name) && !graph.IsPersistent(name) {
   176  				return ErrNotPersistent
   177  			}
   178  			addr := viper.GetString(KeyAddress)
   179  			opts := graph.Options(viper.GetStringMap(KeyOptions))
   180  			clog.Infof("upgrading database...")
   181  			return graph.UpgradeQuadStore(name, addr, opts)
   182  		},
   183  	}
   184  	return cmd
   185  }
   186  
   187  func printBackendInfo() {
   188  	name := viper.GetString(KeyBackend)
   189  	path := viper.GetString(KeyAddress)
   190  	if path != "" {
   191  		path = " (" + path + ")"
   192  	}
   193  	clog.Infof("using backend %q%s", name, path)
   194  }
   195  
   196  func initDatabase() error {
   197  	name := viper.GetString(KeyBackend)
   198  	path := viper.GetString(KeyAddress)
   199  	opts := viper.GetStringMap(KeyOptions)
   200  	return graph.InitQuadStore(name, path, graph.Options(opts))
   201  }
   202  
   203  func openDatabase() (*graph.Handle, error) {
   204  	name := viper.GetString(KeyBackend)
   205  	path := viper.GetString(KeyAddress)
   206  	opts := graph.Options(viper.GetStringMap(KeyOptions))
   207  	qs, err := graph.NewQuadStore(name, path, opts)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	qw, err := graph.NewQuadWriter("single", qs, opts)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return &graph.Handle{QuadStore: qs, QuadWriter: qw}, nil
   216  }
   217  
   218  func openForQueries(cmd *cobra.Command) (*graph.Handle, error) {
   219  	if init, err := cmd.Flags().GetBool("init"); err != nil {
   220  		return nil, err
   221  	} else if init {
   222  		if err = initDatabase(); err == graph.ErrDatabaseExists {
   223  			clog.Infof("database already initialized, skipping init")
   224  		} else if err != nil {
   225  			return nil, err
   226  		}
   227  	}
   228  	var load string
   229  	h, err := openDatabase()
   230  	if err == graph.ErrQuadStoreNotPersistent {
   231  		load = viper.GetString(KeyAddress)
   232  		viper.Set(KeyAddress, "")
   233  		h, err = openDatabase()
   234  	}
   235  	if err == graph.ErrQuadStoreNotPersistent {
   236  		return nil, fmt.Errorf("%v; did you mean -i flag?", err)
   237  	} else if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	if load2, _ := cmd.Flags().GetString(flagLoad); load2 != "" {
   242  		if load != "" {
   243  			h.Close()
   244  			return nil, fmt.Errorf("both -a and -i flags cannot be specified")
   245  		}
   246  		load = load2
   247  	}
   248  	if load != "" {
   249  		qw, err := h.NewQuadWriter()
   250  		if err != nil {
   251  			h.Close()
   252  			return nil, err
   253  		}
   254  		defer qw.Close()
   255  
   256  		typ, _ := cmd.Flags().GetString(flagLoadFormat)
   257  		// TODO: check read-only flag in config before that?
   258  		start := time.Now()
   259  		if err = internal.Load(qw, quad.DefaultBatch, load, typ); err != nil {
   260  			h.Close()
   261  			return nil, err
   262  		}
   263  		clog.Infof("loaded %q in %v", load, time.Since(start))
   264  	}
   265  	return h, nil
   266  }
   267  
   268  type profileData struct {
   269  	cpuProfile *os.File
   270  	memPath    string
   271  }
   272  
   273  func mustSetupProfile(cmd *cobra.Command) profileData {
   274  	p := profileData{}
   275  	mpp := cmd.Flag("memprofile")
   276  	p.memPath = mpp.Value.String()
   277  	cpp := cmd.Flag("cpuprofile")
   278  	v := cpp.Value.String()
   279  	if v != "" {
   280  		f, err := os.Create(v)
   281  		if err != nil {
   282  			fmt.Fprintf(os.Stderr, "Could not open CPU profile file %s\n", v)
   283  			os.Exit(1)
   284  		}
   285  		p.cpuProfile = f
   286  		pprof.StartCPUProfile(f)
   287  	}
   288  	return p
   289  }
   290  
   291  func mustFinishProfile(p profileData) {
   292  	if p.cpuProfile != nil {
   293  		pprof.StopCPUProfile()
   294  		p.cpuProfile.Close()
   295  	}
   296  	if p.memPath != "" {
   297  		f, err := os.Create(p.memPath)
   298  		if err != nil {
   299  			fmt.Fprintf(os.Stderr, "Could not open memory profile file %s\n", p.memPath)
   300  			os.Exit(1)
   301  		}
   302  		runtime.GC()
   303  		if err := pprof.WriteHeapProfile(f); err != nil {
   304  			fmt.Fprintf(os.Stderr, "Could not write memory profile file %s\n", p.memPath)
   305  		}
   306  		f.Close()
   307  	}
   308  }