func formatValueWithType(val interface{}) string {
	if val == nil {
		return fmt.Sprintf("%d:nil", nilType)
	}
	switch t := val.(type) {
	case *pq.Error:
		return fmt.Sprintf("%d:%s", pqErrorType, formatPqError(t))
	case string:
		return fmt.Sprintf("%d:%s", stringType, strconv.Quote(t))
	case int:
		return fmt.Sprintf("%d:%d", intType, val)
	case int64:
		return fmt.Sprintf("%d:%d", int64Type, val)
	case float64:
		return fmt.Sprintf("%d:%g", float64Type, t)
	case bool:
		return fmt.Sprintf("%d:%v", boolType, t)
	case error:
		return fmt.Sprintf("%d:%s", errorType, strconv.Quote(t.Error()))
	case time.Time:
		s := t.Format(time.RFC3339Nano)
		if strings.HasSuffix(s, "Z") && t.Location() != time.UTC {
			s = s[:len(s)-1] + "+00:00"
		}
		return fmt.Sprintf("%d:%s", timeType, s)
	case []string:
		var buf bytes.Buffer
		buf.WriteByte('[')
		for i, s := range t {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(strconv.Quote(s))
		}
		buf.WriteByte(']')
		return fmt.Sprintf("%d:%s", stringSliceType, buf.String())
	case []byte:
		s := base64.RawStdEncoding.EncodeToString(t)
		return fmt.Sprintf("%d:%s", byteSliceType, s)
	case []driver.Value:
		var buf bytes.Buffer
		buf.WriteByte('[')
		for i, v := range t {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(formatValueWithType(v))
		}
		buf.WriteByte(']')
		return fmt.Sprintf("%d:%s", valueSliceType, buf.String())
	default:
		panic(fmt.Errorf("unsupported type: %T", t))
	}
}
formatValueWithType converts the given value into a formatted string suitable
for inclusion in a copyist recording file. The format is as follows:

<dataType>:<formattedValue>

where dataType is the numeric value of the `copyist.valueType` enumeration,
and stringValue is a formatted representation of the value that conforms to
these rules:

1. Contains no linefeed or newline characters. This allows simple
line-by-line parsing of record declarations.
2. Contains no tab characters. This allows a simple string split by the tab
character to break a line into fields.
3. Contains no bracket or comma characters, except as part of a valid Go
literal string format. This allows nested slice types to be parsed.

Many data types already follow these rules with nothing more to do. Those
data types that do not (e.g. string) need to perform escaping in order to
ensure their formatted representation never contains disallowed characters.
package copyist
import (
	"bytes"
	"database/sql/driver"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"strconv"
	"strings"
	"text/scanner"
	"time"
	"github.com/jackc/pgproto3"
	"github.com/lib/pq"
)
type valueType int
const (
	_               valueType = 0
	nilType         valueType = 1
	stringType      valueType = 2
	intType         valueType = 3
	int64Type       valueType = 4
	float64Type     valueType = 5
	boolType        valueType = 6
	errorType       valueType = 7
	timeType        valueType = 8
	stringSliceType valueType = 9
	byteSliceType   valueType = 10
	valueSliceType  valueType = 11
	pqErrorType valueType = 100
)