func KeyColumns(colNames ...string) sif.KeyingOperation {
	return func(row sif.Row) ([]byte, error) {
		irow := row.(itypes.AccessibleRow)  
		hasher := xxhash.New()
		for _, cname := range colNames {
			data, err := irow.GetHashableColData(cname)
			if err != nil {
				return nil, err
			}
			hasher.Write(data)
		}
		return hasher.Sum(nil), nil
	}
}
KeyColumns is a shortcut for defining a KeyingOperation which uses multiple source
column values to produce a compound key.
package util
import (
	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"
	xxhash "github.com/cespare/xxhash/v2"
	"github.com/go-sif/sif"
	itypes "github.com/go-sif/sif/internal/types"
)
func CreateAsyncErrorChannel() chan error {
	return make(chan error)
}
func WaitAndFetchError(wg *sync.WaitGroup, errors chan error) error {
	go func() {
		wg.Wait()
		close(errors)
	}()
	for {
		select {
		case err := <-errors:
			if err != nil {
				return err
			}
			return nil
		default:
			time.Sleep(50 * time.Millisecond)
		}
	}
}
func GetTrace() string {
	var name, file string
	var line int
	var pc [16]uintptr
	var res strings.Builder
	n := runtime.Callers(3, pc[:])
	for _, pc := range pc[:n] {
		fn := runtime.FuncForPC(pc)
		if fn == nil {
			continue
		}
		file, line = fn.FileLine(pc)
		name = fn.Name()
		if !strings.HasPrefix(name, "runtime.") {
			fmt.Fprintf(&res, "%s\n\t%s:%d\n", name, file, line)
		}
	}
	return res.String()
}
func FormatMultiError(merrs []error) string {
	var msg = ""
	for i := 0; i < len(merrs); i++ {
		msg += fmt.Sprintf("%+v\n", merrs[i])
	}
	return msg
}