github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/store/cmd.go (about) 1 package store 2 3 import ( 4 "database/sql" 5 "errors" 6 ) 7 8 // ErrNoMatchingCmd is the error returned when a LastCmd or FirstCmd query 9 // completes with no result. 10 var ErrNoMatchingCmd = errors.New("no matching command line") 11 12 func init() { 13 initDB["initialize command history table"] = func(db *sql.DB) error { 14 _, err := db.Exec(`create table if not exists cmd (content text, lastAmongDup bool)`) 15 if err != nil { 16 return err 17 } 18 return transaction(db, addLastAmongDup) 19 } 20 } 21 22 func addLastAmongDup(tx *sql.Tx) error { 23 // Upgrade from early version where lastAmongDup is missing. 24 rows, err := tx.Query("select * from cmd limit 1") 25 if err != nil { 26 return err 27 } 28 defer rows.Close() 29 30 hasLastAmongDup, err := hasColumn(rows, "lastAmongDup") 31 if err != nil { 32 return err 33 } 34 35 if !hasLastAmongDup { 36 _, err := tx.Exec("alter table cmd add column lastAmongDup bool") 37 if err != nil { 38 return err 39 } 40 _, err = tx.Exec("update cmd set lastAmongDup = (rowid in (select max(rowid) from cmd group by content));") 41 if err != nil { 42 return err 43 } 44 } 45 return nil 46 } 47 48 // NextCmdSeq returns the next sequence number of the command history. 49 func (s *Store) NextCmdSeq() (int, error) { 50 row := s.db.QueryRow(`select ifnull(max(rowid), 0) + 1 from cmd`) 51 var seq int 52 err := row.Scan(&seq) 53 return seq, err 54 } 55 56 // AddCmd adds a new command to the command history. 57 func (s *Store) AddCmd(cmd string) error { 58 _, err := s.db.Exec(`update cmd set lastAmongDup = 0 where content = ?`, cmd) 59 if err != nil { 60 return err 61 } 62 _, err = s.db.Exec(`insert into cmd (content, lastAmongDup) values(?, 1)`, cmd) 63 return err 64 } 65 66 // Cmd queries the command history item with the specified sequence number. 67 func (s *Store) Cmd(seq int) (string, error) { 68 row := s.db.QueryRow(`select content from cmd where rowid = ?`, seq) 69 var cmd string 70 err := row.Scan(&cmd) 71 return cmd, err 72 } 73 74 func convertCmd(row *sql.Row) (int, string, error) { 75 var ( 76 seq int 77 cmd string 78 ) 79 err := row.Scan(&seq, &cmd) 80 if err != nil { 81 if err == sql.ErrNoRows { 82 err = ErrNoMatchingCmd 83 } 84 return 0, "", err 85 } 86 return seq, cmd, nil 87 } 88 89 // LastCmd finds the last command before the given sequence number (exclusive) 90 // with the given prefix. 91 func (s *Store) LastCmd(upto int, prefix string, uniq bool) (int, string, error) { 92 var upto64 int64 = int64(upto) 93 if upto < 0 { 94 upto64 = 0x7FFFFFFFFFFFFFFF 95 } 96 row := s.db.QueryRow(`select rowid, content from cmd where rowid < ? and substr(content, 1, ?) = ? and (? or lastAmongDup) order by rowid desc limit 1`, upto64, len(prefix), prefix, !uniq) 97 return convertCmd(row) 98 } 99 100 // FirstCmd finds the first command after the given sequence number (inclusive) 101 // with the given prefix. 102 func (s *Store) FirstCmd(from int, prefix string, uniq bool) (int, string, error) { 103 row := s.db.QueryRow(`select rowid, content from cmd where rowid >= ? and substr(content, 1, ?) = ? and (? or lastAmongDup) order by rowid asc limit 1`, from, len(prefix), prefix, !uniq) 104 return convertCmd(row) 105 } 106 107 func (s *Store) Cmds(from, upto int) ([]string, error) { 108 rows, err := s.db.Query(`select content from cmd where rowid >= ? and rowid < ?`, from, upto) 109 if err != nil { 110 return nil, err 111 } 112 defer rows.Close() 113 entries := []string{} 114 for rows.Next() { 115 var cmd string 116 err = rows.Scan(&cmd) 117 if err != nil { 118 return nil, err 119 } 120 entries = append(entries, cmd) 121 } 122 return entries, nil 123 }