| /* |
| Copyright 2016 The Transicator Authors |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| package pgclient |
| |
| import ( |
| "bytes" |
| "errors" |
| "regexp" |
| "strconv" |
| |
| log "github.com/Sirupsen/logrus" |
| ) |
| |
| /* |
| ParseError looks at an input message that represents an error and returns |
| a Go error. |
| */ |
| func ParseError(m *InputMessage) error { |
| if m.Type() != ErrorResponse { |
| return errors.New("Message type is not Error") |
| } |
| msg, err := ParseNotice(m) |
| if err == nil { |
| log.Debugf("Received error: %s", msg) |
| return errors.New(msg) |
| } |
| return err |
| } |
| |
| /* |
| ParseNotice looks at an input message that's an error or a "notice" message |
| (both have the same format) and returns the text. |
| */ |
| func ParseNotice(m *InputMessage) (string, error) { |
| if m.Type() != NoticeResponse && m.Type() != ErrorResponse { |
| return "", errors.New("Mismatched message type") |
| } |
| |
| msg := &bytes.Buffer{} |
| for { |
| code, err := m.ReadByte() |
| if err != nil { |
| return "", errors.New("Invalid message format") |
| } |
| |
| if code == 0 { |
| return msg.String(), nil |
| } |
| txt, err := m.ReadString() |
| if err != nil { |
| return "", errors.New("Invalid message format") |
| } |
| msg.WriteString(txt) |
| msg.WriteString(" ") |
| } |
| } |
| |
| // ParseCopyOutResponse parses the response from the CopyData message |
| func ParseCopyOutResponse(m *InputMessage) (info *CopyResponseInfo, err error) { |
| if m.Type() != CopyOutResponse { |
| err = errors.New("Message type is not CopyOutResponse") |
| return |
| } |
| |
| i8, err := m.ReadInt8() |
| if err != nil { |
| return |
| } |
| log.Debugf("copy format (raw): %d", i8) |
| copyFmt := GetCopyFormat(int(i8)) |
| |
| i16, err := m.ReadInt16() |
| if err != nil { |
| return |
| } |
| numCol := int(i16) |
| var colFmts = make([]int, numCol) |
| for i := 0; i < numCol; i++ { |
| var f int16 |
| f, err = m.ReadInt16() |
| if err != nil { |
| return |
| } |
| colFmts[i] = int(f) |
| } |
| info = &CopyResponseInfo{ |
| format: copyFmt, |
| numCol: numCol, |
| colFmts: colFmts, |
| } |
| return info, nil |
| } |
| |
| /* |
| ParseCopyData looks at a CopyData message and then parses it again as |
| another message. |
| */ |
| func ParseCopyData(m *InputMessage) (*InputMessage, error) { |
| if m.Type() != CopyData { |
| return nil, errors.New("Mismatched message type") |
| } |
| |
| buf := m.buf.Bytes() |
| typeByte := buf[0] |
| return NewInputMessage(PgInputType(typeByte), buf[1:]), nil |
| } |
| |
| // ParseRowDescription looks at a RowDescription message and parses it |
| func ParseRowDescription(m *InputMessage) ([]ColumnInfo, error) { |
| if m.Type() != RowDescription { |
| return nil, errors.New("Message type is not Row Description") |
| } |
| |
| var cols []ColumnInfo |
| numFields, err := m.ReadInt16() |
| if err != nil { |
| return nil, err |
| } |
| |
| nf := int(numFields) |
| log.Debugf("Row description has %d fields", nf) |
| for i := 0; i < nf; i++ { |
| col := ColumnInfo{} |
| col.Name, _ = m.ReadString() |
| m.ReadInt32() // Table OID |
| m.ReadInt16() // Attribute number |
| ct, _ := m.ReadInt32() |
| col.Type = PgType(ct) |
| m.ReadInt16() // Type size |
| m.ReadInt32() // Type modifier |
| fmtCode, _ := m.ReadInt16() |
| col.Binary = (fmtCode == 1) |
| cols = append(cols, col) |
| } |
| |
| return cols, nil |
| } |
| |
| // ParseParameterDescription looks at a Parameter description and returns |
| // a list of types. |
| func ParseParameterDescription(m *InputMessage) ([]PgType, error) { |
| if m.Type() != ParameterDescription { |
| return nil, errors.New("Message type is not ParameterDescription") |
| } |
| |
| var types []PgType |
| numCols, err := m.ReadInt16() |
| if err != nil { |
| return nil, err |
| } |
| |
| nf := int(numCols) |
| for i := 0; i < nf; i++ { |
| ct, err := m.ReadInt32() |
| if err != nil { |
| return nil, err |
| } |
| types = append(types, PgType(ct)) |
| } |
| |
| return types, nil |
| } |
| |
| // ParseDataRow turns a single DataRow message to a list of buffers |
| func ParseDataRow(m *InputMessage) ([][]byte, error) { |
| if m.Type() != DataRow { |
| return nil, errors.New("Message type is not DataRow") |
| } |
| |
| var fields [][]byte |
| |
| numFields, err := m.ReadInt16() |
| if err != nil { |
| return nil, err |
| } |
| |
| nf := int(numFields) |
| log.Debugf("Row has %d columns", nf) |
| for i := 0; i < nf; i++ { |
| len, _ := m.ReadInt32() |
| if len > 0 { |
| buf, _ := m.ReadBytes(int(len)) |
| fields = append(fields, buf) |
| } else { |
| fields = append(fields, nil) |
| } |
| } |
| return fields, nil |
| } |
| |
| // ParseCommandComplete parses the CommandComplete message and returns |
| // the row count that it contains |
| func ParseCommandComplete(m *InputMessage) (int64, error) { |
| if m.Type() != CommandComplete { |
| return 0, errors.New("Message type is not CommandComplete") |
| } |
| |
| s, err := m.ReadString() |
| if err != nil { |
| return 0, err |
| } |
| log.Debugf("CommandComplete %s", s) |
| |
| return parseRowCount(s), nil |
| } |
| |
| var insertCompleteRe = regexp.MustCompile("^INSERT [0-9]+ ([0-9]+)$") |
| var otherCompleteRe = regexp.MustCompile("^[A-Z]+ ([0-9]+)$") |
| |
| func parseRowCount(completeMsg string) int64 { |
| match := insertCompleteRe.FindStringSubmatch(completeMsg) |
| if match == nil { |
| match = otherCompleteRe.FindStringSubmatch(completeMsg) |
| } |
| if match == nil { |
| return 0 |
| } |
| |
| ret, err := strconv.ParseInt(match[1], 10, 64) |
| if err == nil { |
| return ret |
| } |
| return 0 |
| } |
| |
| // ParseParameterStatus parses the ParameterStatus message |
| func ParseParameterStatus(m *InputMessage) (string, error) { |
| if m.Type() != ParameterStatus { |
| return "", errors.New("Message type is not ParameterStatus") |
| } |
| |
| s, err := m.ReadString() |
| if err != nil { |
| return "", err |
| } |
| |
| s2, err := m.ReadString() |
| if err != nil { |
| return "", err |
| } |
| |
| return s + " " + s2, nil |
| } |