package research import ( "database/sql" "errors" "strings" ) // Matches Paper Atlas repair 4e62710's nul-soh-v1 protocol: NUL -> SOH/SOH and literal // SOH -> SOH/STX. All other bytes retain their original binary ordering. const textCodec = "nul-soh-v1" func encodedSchema(annotation sql.NullString) (bool, error) { const prefix = "paper-atlas:text-codec=" if !annotation.Valid || !strings.HasPrefix(annotation.String, prefix) { return false, nil } if strings.TrimPrefix(annotation.String, prefix) != textCodec { return false, errors.New("unsupported research text codec") } return true, nil } func encodeText(value string) string { return strings.ReplaceAll(strings.ReplaceAll(value, "\x01", "\x01\x02"), "\x00", "\x01\x01") } func decodeText(value string) (string, error) { if !strings.ContainsRune(value, '\x01') { return value, nil } var result strings.Builder for { position := strings.IndexByte(value, 1) if position < 0 { result.WriteString(value) return result.String(), nil } result.WriteString(value[:position]) if position+1 >= len(value) { return "", errors.New("invalid research text escape") } switch value[position+1] { case 1: result.WriteByte(0) case 2: result.WriteByte(1) default: return "", errors.New("invalid research text escape") } value = value[position+2:] } } type textColumn struct { encoded bool target any } func (column textColumn) Scan(source any) error { var value sql.NullString if err := value.Scan(source); err != nil { return err } if value.Valid && column.encoded { var err error value.String, err = decodeText(value.String) if err != nil { return err } } switch target := column.target.(type) { case *string: if !value.Valid { return errors.New("unexpected null research text") } *target = value.String case *sql.NullString: *target = value default: return errors.New("unsupported research text destination") } return nil } func (r *PostgresRepository) text(target any) sql.Scanner { return textColumn{encoded: r.encoded, target: target} }