Move ACLItem to pgtype
This commit is contained in:
@@ -48,8 +48,8 @@ const (
|
||||
Int8ArrayOID = 1016
|
||||
Float4ArrayOID = 1021
|
||||
Float8ArrayOID = 1022
|
||||
AclItemOID = 1033
|
||||
AclItemArrayOID = 1034
|
||||
ACLItemOID = 1033
|
||||
ACLItemArrayOID = 1034
|
||||
InetArrayOID = 1041
|
||||
VarcharOID = 1043
|
||||
DateOID = 1082
|
||||
@@ -316,58 +316,6 @@ func (s NullString) Encode(w *WriteBuf, oid OID) error {
|
||||
return encodeString(w, oid, s.String)
|
||||
}
|
||||
|
||||
// AclItem is used for PostgreSQL's aclitem data type. A sample aclitem
|
||||
// might look like this:
|
||||
//
|
||||
// postgres=arwdDxt/postgres
|
||||
//
|
||||
// Note, however, that because the user/role name part of an aclitem is
|
||||
// an identifier, it follows all the usual formatting rules for SQL
|
||||
// identifiers: if it contains spaces and other special characters,
|
||||
// it should appear in double-quotes:
|
||||
//
|
||||
// postgres=arwdDxt/"role with spaces"
|
||||
//
|
||||
type AclItem string
|
||||
|
||||
// NullAclItem represents a pgx.AclItem that may be null. NullAclItem implements the
|
||||
// Scanner and Encoder interfaces so it may be used both as an argument to
|
||||
// Query[Row] and a destination for Scan for prepared and unprepared queries.
|
||||
//
|
||||
// If Valid is false then the value is NULL.
|
||||
type NullAclItem struct {
|
||||
AclItem AclItem
|
||||
Valid bool // Valid is true if AclItem is not NULL
|
||||
}
|
||||
|
||||
func (n *NullAclItem) Scan(vr *ValueReader) error {
|
||||
if vr.Type().DataType != AclItemOID {
|
||||
return SerializationError(fmt.Sprintf("NullAclItem.Scan cannot decode OID %d", vr.Type().DataType))
|
||||
}
|
||||
|
||||
if vr.Len() == -1 {
|
||||
n.AclItem, n.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
|
||||
n.Valid = true
|
||||
n.AclItem = AclItem(decodeText(vr))
|
||||
return vr.Err()
|
||||
}
|
||||
|
||||
// Particularly important to return TextFormatCode, seeing as Postgres
|
||||
// only ever sends aclitem as text, not binary.
|
||||
func (n NullAclItem) FormatCode() int16 { return TextFormatCode }
|
||||
|
||||
func (n NullAclItem) Encode(w *WriteBuf, oid OID) error {
|
||||
if !n.Valid {
|
||||
w.WriteInt32(-1)
|
||||
return nil
|
||||
}
|
||||
|
||||
return encodeString(w, oid, string(n.AclItem))
|
||||
}
|
||||
|
||||
// NullInt16 represents a smallint that may be null. NullInt16 implements the
|
||||
// Scanner and Encoder interfaces so it may be used both as an argument to
|
||||
// Query[Row] and a destination for Scan for prepared and unprepared queries.
|
||||
@@ -865,8 +813,6 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error {
|
||||
return Encode(wbuf, oid, v)
|
||||
case string:
|
||||
return encodeString(wbuf, oid, arg)
|
||||
case []AclItem:
|
||||
return encodeAclItemSlice(wbuf, oid, arg)
|
||||
case []byte:
|
||||
return encodeByteSlice(wbuf, oid, arg)
|
||||
}
|
||||
@@ -909,17 +855,10 @@ func Encode(wbuf *WriteBuf, oid OID, arg interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch arg := arg.(type) {
|
||||
case AclItem:
|
||||
// The aclitem data type goes over the wire using the same format as string,
|
||||
// so just cast to string and use encodeString
|
||||
return encodeString(wbuf, oid, string(arg))
|
||||
default:
|
||||
if strippedArg, ok := stripNamedType(&refVal); ok {
|
||||
return Encode(wbuf, oid, strippedArg)
|
||||
}
|
||||
return SerializationError(fmt.Sprintf("Cannot encode %T into oid %v - %T must implement Encoder or be converted to a string", arg, oid, arg))
|
||||
if strippedArg, ok := stripNamedType(&refVal); ok {
|
||||
return Encode(wbuf, oid, strippedArg)
|
||||
}
|
||||
return SerializationError(fmt.Sprintf("Cannot encode %T into oid %v - %T must implement Encoder or be converted to a string", arg, oid, arg))
|
||||
}
|
||||
|
||||
func stripNamedType(val *reflect.Value) (interface{}, bool) {
|
||||
@@ -981,15 +920,10 @@ func decodeByOID(vr *ValueReader) (interface{}, error) {
|
||||
// decoding to the built-in functionality.
|
||||
func Decode(vr *ValueReader, d interface{}) error {
|
||||
switch v := d.(type) {
|
||||
case *AclItem:
|
||||
// aclitem goes over the wire just like text
|
||||
*v = AclItem(decodeText(vr))
|
||||
case *Tid:
|
||||
*v = decodeTid(vr)
|
||||
case *string:
|
||||
*v = decodeText(vr)
|
||||
case *[]AclItem:
|
||||
*v = decodeAclItemArray(vr)
|
||||
case *[]interface{}:
|
||||
*v = decodeRecord(vr)
|
||||
default:
|
||||
@@ -1675,207 +1609,3 @@ func decode1dArrayHeader(vr *ValueReader) (length int32, err error) {
|
||||
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// escapeAclItem escapes an AclItem before it is added to
|
||||
// its aclitem[] string representation. The PostgreSQL aclitem
|
||||
// datatype itself can need escapes because it follows the
|
||||
// formatting rules of SQL identifiers. Think of this function
|
||||
// as escaping the escapes, so that PostgreSQL's array parser
|
||||
// will do the right thing.
|
||||
func escapeAclItem(acl string) (string, error) {
|
||||
var escapedAclItem bytes.Buffer
|
||||
reader := strings.NewReader(acl)
|
||||
for {
|
||||
rn, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Here, EOF is an expected end state, not an error.
|
||||
return escapedAclItem.String(), nil
|
||||
}
|
||||
// This error was not expected
|
||||
return "", err
|
||||
}
|
||||
if needsEscape(rn) {
|
||||
escapedAclItem.WriteRune('\\')
|
||||
}
|
||||
escapedAclItem.WriteRune(rn)
|
||||
}
|
||||
}
|
||||
|
||||
// needsEscape determines whether or not a rune needs escaping
|
||||
// before being placed in the textual representation of an
|
||||
// aclitem[] array.
|
||||
func needsEscape(rn rune) bool {
|
||||
return rn == '\\' || rn == ',' || rn == '"' || rn == '}'
|
||||
}
|
||||
|
||||
// encodeAclItemSlice encodes a slice of AclItems in
|
||||
// their textual represention for PostgreSQL.
|
||||
func encodeAclItemSlice(w *WriteBuf, oid OID, aclitems []AclItem) error {
|
||||
strs := make([]string, len(aclitems))
|
||||
var escapedAclItem string
|
||||
var err error
|
||||
for i := range strs {
|
||||
escapedAclItem, err = escapeAclItem(string(aclitems[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strs[i] = string(escapedAclItem)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteRune('{')
|
||||
buf.WriteString(strings.Join(strs, ","))
|
||||
buf.WriteRune('}')
|
||||
str := buf.String()
|
||||
w.WriteInt32(int32(len(str)))
|
||||
w.WriteBytes([]byte(str))
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseAclItemArray parses the textual representation
|
||||
// of the aclitem[] type. The textual representation is chosen because
|
||||
// Pg's src/backend/utils/adt/acl.c has only in/out (text) not send/recv (bin).
|
||||
// See https://www.postgresql.org/docs/current/static/arrays.html#ARRAYS-IO
|
||||
// for formatting notes.
|
||||
func parseAclItemArray(arr string) ([]AclItem, error) {
|
||||
reader := strings.NewReader(arr)
|
||||
// Difficult to guess a performant initial capacity for a slice of
|
||||
// aclitems, but let's go with 5.
|
||||
aclItems := make([]AclItem, 0, 5)
|
||||
// A single value
|
||||
aclItem := AclItem("")
|
||||
for {
|
||||
// Grab the first/next/last rune to see if we are dealing with a
|
||||
// quoted value, an unquoted value, or the end of the string.
|
||||
rn, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Here, EOF is an expected end state, not an error.
|
||||
return aclItems, nil
|
||||
}
|
||||
// This error was not expected
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rn == '"' {
|
||||
// Discard the opening quote of the quoted value.
|
||||
aclItem, err = parseQuotedAclItem(reader)
|
||||
} else {
|
||||
// We have just read the first rune of an unquoted (bare) value;
|
||||
// put it back so that ParseBareValue can read it.
|
||||
err := reader.UnreadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aclItem, err = parseBareAclItem(reader)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Here, EOF is an expected end state, not an error..
|
||||
aclItems = append(aclItems, aclItem)
|
||||
return aclItems, nil
|
||||
}
|
||||
// This error was not expected.
|
||||
return nil, err
|
||||
}
|
||||
aclItems = append(aclItems, aclItem)
|
||||
}
|
||||
}
|
||||
|
||||
// parseBareAclItem parses a bare (unquoted) aclitem from reader
|
||||
func parseBareAclItem(reader *strings.Reader) (AclItem, error) {
|
||||
var aclItem bytes.Buffer
|
||||
for {
|
||||
rn, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
// Return the read value in case the error is a harmless io.EOF.
|
||||
// (io.EOF marks the end of a bare aclitem at the end of a string)
|
||||
return AclItem(aclItem.String()), err
|
||||
}
|
||||
if rn == ',' {
|
||||
// A comma marks the end of a bare aclitem.
|
||||
return AclItem(aclItem.String()), nil
|
||||
} else {
|
||||
aclItem.WriteRune(rn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseQuotedAclItem parses an aclitem which is in double quotes from reader
|
||||
func parseQuotedAclItem(reader *strings.Reader) (AclItem, error) {
|
||||
var aclItem bytes.Buffer
|
||||
for {
|
||||
rn, escaped, err := readPossiblyEscapedRune(reader)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// Even when it is the last value, the final rune of
|
||||
// a quoted aclitem should be the final closing quote, not io.EOF.
|
||||
return AclItem(""), fmt.Errorf("unexpected end of quoted value")
|
||||
}
|
||||
// Return the read aclitem in case the error is a harmless io.EOF,
|
||||
// which will be determined by the caller.
|
||||
return AclItem(aclItem.String()), err
|
||||
}
|
||||
if !escaped && rn == '"' {
|
||||
// An unescaped double quote marks the end of a quoted value.
|
||||
// The next rune should either be a comma or the end of the string.
|
||||
rn, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
// Return the read value in case the error is a harmless io.EOF,
|
||||
// which will be determined by the caller.
|
||||
return AclItem(aclItem.String()), err
|
||||
}
|
||||
if rn != ',' {
|
||||
return AclItem(""), fmt.Errorf("unexpected rune after quoted value")
|
||||
}
|
||||
return AclItem(aclItem.String()), nil
|
||||
}
|
||||
aclItem.WriteRune(rn)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the next rune from r, unless it is a backslash;
|
||||
// in that case, it returns the rune after the backslash. The second
|
||||
// return value tells us whether or not the rune was
|
||||
// preceeded by a backslash (escaped).
|
||||
func readPossiblyEscapedRune(reader *strings.Reader) (rune, bool, error) {
|
||||
rn, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if rn == '\\' {
|
||||
// Discard the backslash and read the next rune.
|
||||
rn, _, err = reader.ReadRune()
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
return rn, true, nil
|
||||
}
|
||||
return rn, false, nil
|
||||
}
|
||||
|
||||
func decodeAclItemArray(vr *ValueReader) []AclItem {
|
||||
if vr.Len() == -1 {
|
||||
vr.Fatal(ProtocolError("Cannot decode null into []AclItem"))
|
||||
return nil
|
||||
}
|
||||
|
||||
str := vr.ReadString(vr.Len())
|
||||
|
||||
// Short-circuit empty array.
|
||||
if str == "{}" {
|
||||
return []AclItem{}
|
||||
}
|
||||
|
||||
// Remove the '{' at the front and the '}' at the end,
|
||||
// so that parseAclItemArray doesn't have to deal with them.
|
||||
str = str[1 : len(str)-1]
|
||||
aclItems, err := parseAclItemArray(str)
|
||||
if err != nil {
|
||||
vr.Fatal(ProtocolError(err.Error()))
|
||||
return nil
|
||||
}
|
||||
return aclItems
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user