diff --git a/cmd/genji/shell/command.go b/cmd/genji/shell/command.go index b4f604d25..3fa0e5c65 100644 --- a/cmd/genji/shell/command.go +++ b/cmd/genji/shell/command.go @@ -2,8 +2,11 @@ package shell import ( "context" + "encoding/csv" + "errors" "fmt" "io" + "os" "strings" "github.com/genjidb/genji" @@ -14,13 +17,19 @@ import ( "github.com/genjidb/genji/stringutil" ) -var commands = []struct { +type command struct { Name string Options string DisplayName string Description string Aliases []string -}{ +} + +func (c *command) Usage() string { + return fmt.Sprintf("%s %s", c.DisplayName, c.Options) +} + +var commands = []command{ { Name: ".exit", DisplayName: ".exit or exit", @@ -52,7 +61,7 @@ var commands = []struct { }, { Name: ".save", - Options: "[badger?] [filename]", + Options: "[badger] [filename]", DisplayName: ".save", Description: "Save database content in the specified file.", }, @@ -62,6 +71,22 @@ var commands = []struct { DisplayName: ".schema", Description: "Show the CREATE statements of all tables or of the selected ones.", }, + { + Name: ".import", + Options: "TYPE FILE table", + DisplayName: ".import", + Description: "Import data from a file. Only supported type is 'csv'", + }, +} + +func getUsage(cmdName string) string { + for _, c := range commands { + if c.Name == cmdName { + return c.Usage() + } + } + + return "" } // runHelpCmd shows all available commands. @@ -252,3 +277,49 @@ func runSaveCmd(ctx context.Context, db *genji.DB, engineName string, dbPath str return nil } + +func runImportCmd(ctx context.Context, db *genji.DB, fileType, path, table string) error { + if strings.ToLower(fileType) != "csv" { + return errors.New("TYPE should be csv") + } + + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + tx, err := db.Begin(true) + if err != nil { + return err + } + defer tx.Rollback() + + r := csv.NewReader(f) + + err = tx.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s", table)) + if err != nil { + return err + } + + headers, err := r.Read() + if err != nil { + return err + } + + for { + columns, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + return err + } + err = tx.Exec("INSERT INTO "+table+" VALUES ?", document.NewFromCSV(headers, columns)) + if err != nil { + return err + } + } + + return tx.Commit() +} diff --git a/cmd/genji/shell/shell.go b/cmd/genji/shell/shell.go index d82f4c268..61a9e7dd7 100644 --- a/cmd/genji/shell/shell.go +++ b/cmd/genji/shell/shell.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "io/ioutil" "os" "os/signal" "path/filepath" @@ -158,7 +157,7 @@ func Run(ctx context.Context, opts *Options) error { err := sh.runPrompt(ctx, promptExecCh) if err != nil && err != errExitCtrlD { - fmt.Fprintf(os.Stderr, err.Error()) + fmt.Fprintln(os.Stderr, err.Error()) } }() @@ -446,19 +445,19 @@ func (sh *Shell) runCommand(ctx context.Context, in string) error { return runHelpCmd() case ".tables": if len(cmd) > 1 { - return stringutil.Errorf("usage: .tables") + return stringutil.Errorf(getUsage(".tables")) } return runTablesCmd(sh.db, os.Stdout) case ".exit", "exit": if len(cmd) > 1 { - return stringutil.Errorf("usage: .exit") + return stringutil.Errorf(getUsage(".exit")) } return errExitCommand case ".indexes": if len(cmd) > 2 { - return stringutil.Errorf("usage: .indexes [tablename]") + return stringutil.Errorf(getUsage(".indexes")) } var tableName string @@ -484,6 +483,12 @@ func (sh *Shell) runCommand(ctx context.Context, in string) error { return runSaveCmd(ctx, sh.db, engine, path) case ".schema": return dbutil.DumpSchema(ctx, sh.db, os.Stdout, cmd[1:]...) + case ".import": + if len(cmd) != 4 { + return stringutil.Errorf(getUsage(".import")) + } + + return runImportCmd(ctx, sh.db, cmd[1], cmd[2], cmd[3]) default: return displaySuggestions(in) } @@ -498,26 +503,6 @@ func (sh *Shell) runQuery(ctx context.Context, q string) error { return err } -func (sh *Shell) runPipedInput(ctx context.Context) (ran bool, err error) { - // Check if there is any input being piped in from the terminal - stat, _ := os.Stdin.Stat() - m := stat.Mode() - - if (m&os.ModeNamedPipe) == 0 /*cat a.txt| prog*/ && !m.IsRegular() /*prog < a.txt*/ { // No input from terminal - return false, nil - } - data, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return true, stringutil.Errorf("Unable to read piped input: %w", err) - } - err = sh.runQuery(ctx, string(data)) - if err != nil { - return true, stringutil.Errorf("Unable to execute provided sql statements: %w", err) - } - - return true, nil -} - func (sh *Shell) changelivePrefix() (string, bool) { return sh.livePrefix, sh.multiLine } diff --git a/document/create.go b/document/create.go index 562328204..7ba686b19 100644 --- a/document/create.go +++ b/document/create.go @@ -285,6 +285,10 @@ func (s sliceArray) GetByIndex(i int) (Value, error) { func NewFromCSV(headers, columns []string) Document { fb := NewFieldBuffer() for i, h := range headers { + if i >= len(columns) { + break + } + fb.Add(h, NewTextValue(columns[i])) }