github.com/decred/dcrlnd@v0.7.6/kvdb/postgres/readwrite_cursor.go (about)

     1  //go:build kvdb_postgres
     2  // +build kvdb_postgres
     3  
     4  package postgres
     5  
     6  import (
     7  	"database/sql"
     8  
     9  	"github.com/btcsuite/btcwallet/walletdb"
    10  )
    11  
    12  // readWriteCursor holds a reference to the cursors bucket, the value
    13  // prefix and the current key used while iterating.
    14  type readWriteCursor struct {
    15  	bucket *readWriteBucket
    16  
    17  	// currKey holds the current key of the cursor.
    18  	currKey []byte
    19  }
    20  
    21  func newReadWriteCursor(b *readWriteBucket) *readWriteCursor {
    22  	return &readWriteCursor{
    23  		bucket: b,
    24  	}
    25  }
    26  
    27  // First positions the cursor at the first key/value pair and returns
    28  // the pair.
    29  func (c *readWriteCursor) First() ([]byte, []byte) {
    30  	var (
    31  		key   []byte
    32  		value []byte
    33  	)
    34  	row, cancel := c.bucket.tx.QueryRow(
    35  		"SELECT key, value FROM " + c.bucket.table + " WHERE " +
    36  			parentSelector(c.bucket.id) +
    37  			" ORDER BY key LIMIT 1",
    38  	)
    39  	defer cancel()
    40  	err := row.Scan(&key, &value)
    41  
    42  	switch {
    43  	case err == sql.ErrNoRows:
    44  		return nil, nil
    45  
    46  	case err != nil:
    47  		panic(err)
    48  	}
    49  
    50  	// Copy current key to prevent modification by the caller.
    51  	c.currKey = make([]byte, len(key))
    52  	copy(c.currKey, key)
    53  
    54  	return key, value
    55  }
    56  
    57  // Last positions the cursor at the last key/value pair and returns the
    58  // pair.
    59  func (c *readWriteCursor) Last() ([]byte, []byte) {
    60  	var (
    61  		key   []byte
    62  		value []byte
    63  	)
    64  	row, cancel := c.bucket.tx.QueryRow(
    65  		"SELECT key, value FROM " + c.bucket.table + " WHERE " +
    66  			parentSelector(c.bucket.id) +
    67  			" ORDER BY key DESC LIMIT 1",
    68  	)
    69  	defer cancel()
    70  	err := row.Scan(&key, &value)
    71  
    72  	switch {
    73  	case err == sql.ErrNoRows:
    74  		return nil, nil
    75  
    76  	case err != nil:
    77  		panic(err)
    78  	}
    79  
    80  	// Copy current key to prevent modification by the caller.
    81  	c.currKey = make([]byte, len(key))
    82  	copy(c.currKey, key)
    83  
    84  	return key, value
    85  }
    86  
    87  // Next moves the cursor one key/value pair forward and returns the new
    88  // pair.
    89  func (c *readWriteCursor) Next() ([]byte, []byte) {
    90  	var (
    91  		key   []byte
    92  		value []byte
    93  	)
    94  	row, cancel := c.bucket.tx.QueryRow(
    95  		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
    96  			parentSelector(c.bucket.id)+
    97  			" AND key>$1 ORDER BY key LIMIT 1",
    98  		c.currKey,
    99  	)
   100  	defer cancel()
   101  	err := row.Scan(&key, &value)
   102  
   103  	switch {
   104  	case err == sql.ErrNoRows:
   105  		return nil, nil
   106  
   107  	case err != nil:
   108  		panic(err)
   109  	}
   110  
   111  	// Copy current key to prevent modification by the caller.
   112  	c.currKey = make([]byte, len(key))
   113  	copy(c.currKey, key)
   114  
   115  	return key, value
   116  }
   117  
   118  // Prev moves the cursor one key/value pair backward and returns the new
   119  // pair.
   120  func (c *readWriteCursor) Prev() ([]byte, []byte) {
   121  	var (
   122  		key   []byte
   123  		value []byte
   124  	)
   125  	row, cancel := c.bucket.tx.QueryRow(
   126  		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
   127  			parentSelector(c.bucket.id)+
   128  			" AND key<$1 ORDER BY key DESC LIMIT 1",
   129  		c.currKey,
   130  	)
   131  	defer cancel()
   132  	err := row.Scan(&key, &value)
   133  
   134  	switch {
   135  	case err == sql.ErrNoRows:
   136  		return nil, nil
   137  
   138  	case err != nil:
   139  		panic(err)
   140  	}
   141  
   142  	// Copy current key to prevent modification by the caller.
   143  	c.currKey = make([]byte, len(key))
   144  	copy(c.currKey, key)
   145  
   146  	return key, value
   147  }
   148  
   149  // Seek positions the cursor at the passed seek key.  If the key does
   150  // not exist, the cursor is moved to the next key after seek.  Returns
   151  // the new pair.
   152  func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) {
   153  	// Convert nil to empty slice, otherwise sql mapping won't be correct
   154  	// and no keys are found.
   155  	if seek == nil {
   156  		seek = []byte{}
   157  	}
   158  
   159  	var (
   160  		key   []byte
   161  		value []byte
   162  	)
   163  	row, cancel := c.bucket.tx.QueryRow(
   164  		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
   165  			parentSelector(c.bucket.id)+
   166  			" AND key>=$1 ORDER BY key LIMIT 1",
   167  		seek,
   168  	)
   169  	defer cancel()
   170  	err := row.Scan(&key, &value)
   171  
   172  	switch {
   173  	case err == sql.ErrNoRows:
   174  		return nil, nil
   175  
   176  	case err != nil:
   177  		panic(err)
   178  	}
   179  
   180  	// Copy current key to prevent modification by the caller.
   181  	c.currKey = make([]byte, len(key))
   182  	copy(c.currKey, key)
   183  
   184  	return key, value
   185  }
   186  
   187  // Delete removes the current key/value pair the cursor is at without
   188  // invalidating the cursor.  Returns ErrIncompatibleValue if attempted
   189  // when the cursor points to a nested bucket.
   190  func (c *readWriteCursor) Delete() error {
   191  	// Get first record at or after cursor.
   192  	var key []byte
   193  	row, cancel := c.bucket.tx.QueryRow(
   194  		"SELECT key FROM "+c.bucket.table+" WHERE "+
   195  			parentSelector(c.bucket.id)+
   196  			" AND key>=$1 ORDER BY key LIMIT 1",
   197  		c.currKey,
   198  	)
   199  	defer cancel()
   200  	err := row.Scan(&key)
   201  
   202  	switch {
   203  	case err == sql.ErrNoRows:
   204  		return nil
   205  
   206  	case err != nil:
   207  		panic(err)
   208  	}
   209  
   210  	// Delete record.
   211  	result, err := c.bucket.tx.Exec(
   212  		"DELETE FROM "+c.bucket.table+" WHERE "+
   213  			parentSelector(c.bucket.id)+
   214  			" AND key=$1 AND value IS NOT NULL",
   215  		key,
   216  	)
   217  	if err != nil {
   218  		panic(err)
   219  	}
   220  
   221  	rows, err := result.RowsAffected()
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	// The key exists but nothing has been deleted. This means that the key
   227  	// must have been a bucket key.
   228  	if rows != 1 {
   229  		return walletdb.ErrIncompatibleValue
   230  	}
   231  
   232  	return err
   233  }