func (db *PostgresDB) loadLastPaymentInfo() (uint32, int64, error) {
	const funcName = "loadLastPaymentInfo"
	var height uint32
	err := db.DB.QueryRow(selectLastPaymentHeight).Scan(&height)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			desc := fmt.Sprintf("%s: no value found for lastpaymentheight",
				funcName)
			return 0, 0, errs.DBError(errs.ValueNotFound, desc)
		}
		desc := fmt.Sprintf("%s: unable to load last payment height: %v",
			funcName, err)
		return 0, 0, errs.DBError(errs.FetchEntry, desc)
	}
	var paidOn int64
	err = db.DB.QueryRow(selectLastPaymentPaidOn).Scan(&paidOn)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			desc := fmt.Sprintf("%s: no value found for lastpaymentpaidon",
				funcName)
			return 0, 0, errs.DBError(errs.ValueNotFound, desc)
		}
		desc := fmt.Sprintf("%s: unable to load last payment paid on "+
			"time: %v", funcName, err)
		return 0, 0, errs.DBError(errs.FetchEntry, desc)
	}
	return height, paidOn, nil
}
loadLastPaymentInfo retrieves the last payment height and paidOn timestamp
from the database.
package pool
import (
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
	"math/big"
	"net/http"
	"time"
	"github.com/decred/dcrd/dcrutil/v3"
	"github.com/lib/pq"
	errs "github.com/decred/dcrpool/errors"
)
func InitPostgresDB(host string, port uint32, user, pass, dbName string, purgeDB bool) (*PostgresDB, error) {
	const funcName = "InitPostgresDB"
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, pass, dbName)
	db, err := sql.Open("postgres", psqlInfo)
	if err != nil {
		desc := fmt.Sprintf("%s: unable to open postgres: %v", funcName, err)
		return nil, errs.DBError(errs.DBOpen, desc)
	}
	pdb := &PostgresDB{db}
	if purgeDB {
		err := pdb.purge()
		if err != nil {
			return nil, err
		}
	}
	err = db.Ping()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to connect to postgres: %v", funcName, err)
		return nil, errs.DBError(errs.DBOpen, desc)
	}
	makeErr := func(table string, err error) error {
		desc := fmt.Sprintf("%s: unable to create %s table: %v",
			funcName, table, err)
		return errs.DBError(errs.CreateStorage, desc)
	}
	_, err = db.Exec(createTableMetadata)
	if err != nil {
		return nil, makeErr("metadata", err)
	}
	_, err = db.Exec(createTableAccounts)
	if err != nil {
		return nil, makeErr("accounts", err)
	}
	_, err = db.Exec(createTablePayments)
	if err != nil {
		return nil, makeErr("payments", err)
	}
	_, err = db.Exec(createTableArchivedPayments)
	if err != nil {
		return nil, makeErr("archived payments", err)
	}
	_, err = db.Exec(createTableJobs)
	if err != nil {
		return nil, makeErr("jobs", err)
	}
	_, err = db.Exec(createTableShares)
	if err != nil {
		return nil, makeErr("shares", err)
	}
	_, err = db.Exec(createTableAcceptedWork)
	if err != nil {
		return nil, makeErr("accepted work", err)
	}
	_, err = db.Exec(createTableHashData)
	if err != nil {
		return nil, makeErr("hashrate", err)
	}
	return &PostgresDB{db}, nil
}
func (db *PostgresDB) Close() error {
	funcName := "Close"
	err := db.DB.Close()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to close db: %v", funcName, err)
		return errs.DBError(errs.DBClose, desc)
	}
	return nil
}
func (db *PostgresDB) purge() error {
	funcName := "purge"
	_, err := db.DB.Exec(purgeDB)
	if err != nil {
		desc := fmt.Sprintf("%s: unable to purge db: %v", funcName, err)
		return errs.DBError(errs.DeleteEntry, desc)
	}
	return nil
}
func decodePaymentRows(rows *sql.Rows) ([]*Payment, error) {
	const funcName = "decodePaymentRows"
	var toReturn []*Payment
	for rows.Next() {
		var uuid, account, transactionID, sourceBlockHash, sourceCoinbase string
		var estimatedMaturity, height, paidOnHeight uint32
		var amount, createdon int64
		err := rows.Scan(&uuid, &account, &estimatedMaturity,
			&height, &amount, &createdon, &paidOnHeight, &transactionID,
			&sourceBlockHash, &sourceCoinbase)
		if err != nil {
			desc := fmt.Sprintf("%s: unable to scan payment entry: %v",
				funcName, err)
			return nil, errs.DBError(errs.Decode, desc)
		}
		payment := &Payment{uuid, account, estimatedMaturity,
			height, dcrutil.Amount(amount), createdon, paidOnHeight, transactionID,
			&PaymentSource{sourceBlockHash, sourceCoinbase}}
		toReturn = append(toReturn, payment)
	}
	err := rows.Err()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to decode payments: %v",
			funcName, err)
		return nil, errs.DBError(errs.Decode, desc)
	}
	return toReturn, nil
}
func decodeWorkRows(rows *sql.Rows) ([]*AcceptedWork, error) {
	const funcName = "decodeWorkRows"
	var toReturn []*AcceptedWork
	for rows.Next() {
		var uuid, blockhash, prevhash, minedby, miner string
		var confirmed bool
		var height uint32
		var createdOn int64
		err := rows.Scan(&uuid, &blockhash, &prevhash, &height,
			&minedby, &miner, &createdOn, &confirmed)
		if err != nil {
			desc := fmt.Sprintf("%s: unable to scan work entry: %v",
				funcName, err)
			return nil, errs.DBError(errs.Decode, desc)
		}
		work := &AcceptedWork{uuid, blockhash, prevhash, height,
			minedby, miner, createdOn, confirmed}
		toReturn = append(toReturn, work)
	}
	err := rows.Err()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to decode work: %v",
			funcName, err)
		return nil, errs.DBError(errs.Decode, desc)
	}
	return toReturn, nil
}
func decodeShareRows(rows *sql.Rows) ([]*Share, error) {
	const funcName = "decodeShareRows"
	var toReturn []*Share
	for rows.Next() {
		var uuid, account, weight string
		var createdon int64
		err := rows.Scan(&uuid, &account, &weight, &createdon)
		if err != nil {
			desc := fmt.Sprintf("%s: unable to scan share entry: %v",
				funcName, err)
			return nil, errs.DBError(errs.Decode, desc)
		}
		weightRat, ok := new(big.Rat).SetString(weight)
		if !ok {
			desc := fmt.Sprintf("unable to decode big.Rat string: %v", err)
			return nil, errs.DBError(errs.Parse, desc)
		}
		share := &Share{uuid, account, weightRat, createdon}
		toReturn = append(toReturn, share)
	}
	err := rows.Err()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to decode shares: %v",
			funcName, err)
		return nil, errs.DBError(errs.Decode, desc)
	}
	return toReturn, nil
}
func decodeHashDataRows(rows *sql.Rows) (map[string]*HashData, error) {
	const funcName = "decodeHashDataRows"
	toReturn := make(map[string]*HashData)
	for rows.Next() {
		var uuid, accountID, miner, ip, hashRate string
		var updatedOn int64
		err := rows.Scan(&uuid, &accountID, &miner, &ip,
			&hashRate, &updatedOn)
		if err != nil {
			desc := fmt.Sprintf("%s: unable to scan hash data entry: %v",
				funcName, err)
			return nil, errs.DBError(errs.Decode, desc)
		}
		hashRat, ok := new(big.Rat).SetString(hashRate)
		if !ok {
			desc := fmt.Sprintf("%s: unable to decode big.Rat string: %v",
				funcName, err)
			return nil, errs.DBError(errs.Parse, desc)
		}
		hashData := &HashData{uuid, accountID, miner, ip, hashRat, updatedOn}
		toReturn[hashData.UUID] = hashData
	}
	err := rows.Err()
	if err != nil {
		desc := fmt.Sprintf("%s: unable to decode hash data: %v",
			funcName, err)
		return nil, errs.DBError(errs.Decode, desc)
	}
	return toReturn, nil
}
func (db *PostgresDB) httpBackup(w http.ResponseWriter) error {
	return errs.DBError(errs.Unsupported, "httpBackup is not supported "+
		"for postgres database")
}
func (db *PostgresDB) Backup(fileName string) error {
	return errs.DBError(errs.Unsupported, "backup is not supported "+
		"for postgres database")
}
func (db *PostgresDB) fetchPoolMode() (uint32, error) {
	const funcName = "fetchPoolMode"
	var poolmode uint32
	err := db.DB.QueryRow(selectPoolMode).Scan(&poolmode)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			desc := fmt.Sprintf("%s: no value found for poolmode", funcName)
			return 0, errs.DBError(errs.ValueNotFound, desc)
		}
		desc := fmt.Sprintf("%s: unable to fetch pool mode: %v", funcName, err)
		return 0, errs.DBError(errs.FetchEntry, desc)
	}
	return poolmode, nil
}
func (db *PostgresDB) persistPoolMode(mode uint32) error {
	const funcName = "persistPoolMode"
	_, err := db.DB.Exec(insertPoolMode, mode)
	if err != nil {
		desc := fmt.Sprintf("%s: unable to persist pool mode: %v",
			funcName, err)
		return errs.DBError(errs.PersistEntry, desc)
	}
	return nil
}
func (db *PostgresDB) fetchCSRFSecret() ([]byte, error) {
	const funcName = "fetchCSRFSecret"
	var secret string
	err := db.DB.QueryRow(selectCSRFSecret).Scan(&secret)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			desc := fmt.Sprintf("%s: no value found for csrfsecret", funcName)
			return nil, errs.DBError(errs.ValueNotFound, desc)
		}
		desc := fmt.Sprintf("%s: unable to fetch CSRF secret: %v",
			funcName, err)
		return nil, errs.DBError(errs.FetchEntry, desc)
	}
	decoded, err := hex.DecodeString(secret)
	if err != nil {
		desc := fmt.Sprintf("%s: unable to decode csrf secret: %v",
			funcName, err)
		return nil, errs.DBError(errs.Parse, desc)
	}
	return decoded, nil
}
func (db *PostgresDB) persistCSRFSecret(secret []byte) error {
	const funcName = "persistCSRFSecret"
	_, err := db.DB.Exec(insertCSRFSecret, hex.EncodeToString(secret))
	if err != nil {
		desc := fmt.Sprintf("%s: unable to persist CSRF secrets: %v",
			funcName, err)
		return errs.DBError(errs.PersistEntry, desc)
	}
	return err
}
func (db *PostgresDB) persistLastPaymentInfo(height uint32, paidOn int64) error {
	const funcName = "persistLastPaymentInfo"
	tx, err := db.DB.Begin()
	if err != nil {
		return err
	}
	_, err = tx.Exec(insertLastPaymentHeight, height)
	if err != nil {
		rErr := tx.Rollback()
		if rErr != nil {
			desc := fmt.Sprintf("%s: unable to rollback persisting last "+
				"payment height tx: %v, initial error: %v", funcName,
				rErr, err)
			return errs.DBError(errs.PersistEntry, desc)
		}
		desc := fmt.Sprintf("%s: unable to persist last payment height: %v",
			funcName, err)
		return errs.DBError(errs.PersistEntry, desc)
	}
	_, err = tx.Exec(insertLastPaymentPaidOn, paidOn)
	if err != nil {
		rErr := tx.Rollback()
		if rErr != nil {
			desc := fmt.Sprintf("%s: unable to rollback persist last payment "+
				"paid on time tx: %v, initial error: %v", funcName,
				rErr, err)
			return errs.DBError(errs.PersistEntry, desc)
		}
		desc := fmt.Sprintf("%s: unable to persist last payment paid on "+
			"time: %v", funcName, err)
		return errs.DBError(errs.PersistEntry, desc)
	}
	return tx.Commit()
}