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 }