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  }