func SelectByAddress(conn *sqlite.Conn, startHash *factom.Bytes32,
	adrs []factom.FAAddress, nfTkns fat1.NFTokens,
	toFrom, order string,
	page, limit uint) ([]factom.Entry, error) {
	if page == 0 {
		return nil, fmt.Errorf("invalid page")
	}
	var sql sqlbuilder.SQLBuilder
	sql.Append(SelectWhere + `"valid" = true`)
	if startHash != nil {
		sql.Append(` AND "id" >= (SELECT "id" FROM "entry" WHERE "hash" = ?)`,
			func(s *sqlite.Stmt, p int) int {
				s.BindBytes(p, startHash[:])
				return 1
			})
	}
	var to bool
	switch strings.ToLower(toFrom) {
	case "to":
		to = true
	case "from", "":
	default:
		panic(fmt.Errorf("invalid toFrom: %v", toFrom))
	}
	if len(nfTkns) > 0 {
		sql.WriteString(` AND "id" IN (
                                SELECT "entry_id" FROM "nf_token_address_transactions"
                                        WHERE "nf_tkn_id" IN (`)  
		sql.BindNParams(len(nfTkns), func(s *sqlite.Stmt, p int) int {
			i := 0
			for nfTkn := range nfTkns {
				s.BindInt64(p+i, int64(nfTkn))
				i++
			}
			return len(nfTkns)
		})
		sql.WriteString(`)`)  
		if len(adrs) > 0 {
			sql.WriteString(` AND "address_id" IN (
                                SELECT "id" FROM "address"
                                        WHERE "address" IN (`)  
			sql.BindNParams(len(adrs), func(s *sqlite.Stmt, p int) int {
				for i, adr := range adrs {
					s.BindBytes(p+i, adr[:])
				}
				return len(adrs)
			})
			sql.WriteString(`))`)  
		}
		if len(toFrom) > 0 {
			sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int {
				s.BindBool(p, to)
				return 1
			})
		}
		sql.WriteString(`)`)  
	} else if len(adrs) > 0 {
		sql.WriteString(` AND "id" IN (
                                SELECT "entry_id" FROM "address_transactions"
                                        WHERE "address_id" IN (
                                                SELECT "id" FROM "address"
                                                        WHERE "address" IN (`)  
		sql.BindNParams(len(adrs), func(s *sqlite.Stmt, p int) int {
			for i, adr := range adrs {
				s.BindBytes(p+i, adr[:])
			}
			return len(adrs)
		})
		sql.WriteString(`))`)  
		if len(toFrom) > 0 {
			sql.Append(` AND "to" = ?`, func(s *sqlite.Stmt, p int) int {
				s.BindBool(p, to)
				return 1
			})
		}
		sql.WriteString(`)`)  
	}
	sql.OrderByPaginate("id", order, page, limit)
	stmt := sql.Prep(conn)
	defer stmt.Reset()
	var entry []factom.Entry
	for {
		e, err := Select(stmt)
		if err != nil {
			return nil, err
		}
		if !e.IsPopulated() {
			break
		}
		entry = append(entry, e)
	}
	return entry, nil
}
SelectByAddress returns all the factom.Entry where adrs and nfTkns were
involved in the valid transaction, for the given pagination range.

Pages start at 1.

TODO: This should probably be moved out of the entry package and into a db
package that is more specific to FAT0 and FAT1.
package entry
import (
	"fmt"
	"strings"
	"time"
	"crawshaw.io/sqlite"
	"crawshaw.io/sqlite/sqlitex"
	"github.com/AdamSLevy/sqlbuilder"
	"github.com/Factom-Asset-Tokens/factom"
	"github.com/Factom-Asset-Tokens/factom/fat1"
	"github.com/Factom-Asset-Tokens/fatd/internal/flag"
)
const CreateTable = `CREATE TABLE "entry" (
        "id"            INTEGER PRIMARY KEY,
        "eb_seq"        INTEGER NOT NULL,
        "timestamp"     INTEGER NOT NULL,
        "valid"         BOOL NOT NULL DEFAULT FALSE,
        "hash"          BLOB NOT NULL,
        "data"          BLOB NOT NULL,
        FOREIGN KEY("eb_seq") REFERENCES "eblock"
);
CREATE INDEX "idx_entry_eb_seq" ON "entry"("eb_seq");
CREATE INDEX "idx_entry_hash" ON "entry"("hash");
`
func Insert(conn *sqlite.Conn, e factom.Entry, ebSeq uint32) (int64, error) {
	data, err := e.MarshalBinary()
	if err != nil {
		panic(fmt.Errorf("factom.Entry.MarshalBinary(): %w", err))
	}
	stmt := conn.Prep(`INSERT INTO "entry"
                ("eb_seq", "timestamp", "hash", "data")
                VALUES (?, ?, ?, ?);`)
	stmt.BindInt64(1, int64(int32(ebSeq)))  
	stmt.BindInt64(2, int64(e.Timestamp.Unix()))
	stmt.BindBytes(3, e.Hash[:])
	stmt.BindBytes(4, data)
	if _, err := stmt.Step(); err != nil {
		return -1, err
	}
	return conn.LastInsertRowID(), nil
}
func SetValid(conn *sqlite.Conn, id int64) error {
	stmt := conn.Prep(`UPDATE "entry" SET "valid" = 1 WHERE "id" = ?;`)
	stmt.BindInt64(1, id)
	_, err := stmt.Step()
	if err != nil {
		return err
	}
	if conn.Changes() == 0 {
		panic("no entry updated")
	}
	return nil
}
const SelectWhere = `SELECT "hash", "data", "timestamp" FROM "entry" WHERE `
func Select(stmt *sqlite.Stmt) (factom.Entry, error) {
	var e factom.Entry
	hasRow, err := stmt.Step()
	if err != nil {
		return e, err
	}
	if !hasRow {
		return e, nil
	}
	e.Hash = new(factom.Bytes32)
	if stmt.ColumnBytes(0, e.Hash[:]) != len(e.Hash) {
		panic("invalid hash length")
	}
	data := make([]byte, stmt.ColumnLen(1))
	stmt.ColumnBytes(1, data)
	if err := e.UnmarshalBinary(data); err != nil {
		panic(fmt.Errorf("factom.Entry.UnmarshalBinary(%v): %w",
			factom.Bytes(data), err))
	}
	e.Timestamp = time.Unix(stmt.ColumnInt64(2), 0)
	return e, nil
}
func SelectByID(conn *sqlite.Conn, id int64) (factom.Entry, error) {
	stmt := conn.Prep(SelectWhere + `"id" = ?;`)
	stmt.BindInt64(1, id)
	defer stmt.Reset()
	return Select(stmt)
}
func SelectByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) {
	stmt := conn.Prep(SelectWhere + `"hash" = ?;`)
	stmt.BindBytes(1, hash[:])
	defer stmt.Reset()
	return Select(stmt)
}
func SelectValidByHash(conn *sqlite.Conn, hash *factom.Bytes32) (factom.Entry, error) {
	stmt := conn.Prep(SelectWhere + `"hash" = ? AND "valid" = true;`)
	stmt.BindBytes(1, hash[:])
	defer stmt.Reset()
	return Select(stmt)
}
func SelectCount(conn *sqlite.Conn, validOnly bool) (int64, error) {
	stmt := conn.Prep(`SELECT count(*) FROM "entry" WHERE (? OR "valid" = true);`)
	stmt.BindBool(1, !validOnly)
	return sqlitex.ResultInt64(stmt)
}
func init() {
	sqlbuilder.LimitMaxDefault = uint(flag.APIMaxLimit)
}