func testBinaryDatumType(t *testing.T, typ string, datumConstructor func(val string) parser.Datum) { var tests []binaryTest f, err := os.Open(filepath.Join("testdata", fmt.Sprintf("%s_test.json", typ))) if err != nil { t.Fatal(err) } if err := json.NewDecoder(f).Decode(&tests); err != nil { t.Fatal(err) } f.Close() buf := writeBuffer{bytecount: metric.NewCounter(metric.Metadata{})} for _, test := range tests { buf.wrapped.Reset() d := datumConstructor(test.In) oid, _ := sql.DatumToOid(d.ResolvedType()) func() { defer func() { if r := recover(); r != nil { fmt.Printf("%q: %s", test.In, r) panic(r) } }() buf.writeBinaryDatum(d, time.UTC) if buf.err != nil { t.Fatal(buf.err) } if got := buf.wrapped.Bytes(); !bytes.Equal(got, test.Expect) { t.Errorf("%q:\n\t%v found,\n\t%v expected", test.In, got, test.Expect) } else if datum, err := decodeOidDatum(oid, formatBinary, got[4:]); err != nil { t.Fatalf("unable to decode %v: %s", got[4:], err) } else if d.Compare(datum) != 0 { t.Errorf("expected %s, got %s", d, datum) } }() } }
func (c *v3Conn) handleParse(ctx context.Context, buf *readBuffer) error { name, err := buf.getString() if err != nil { return err } // The unnamed prepared statement can be freely overwritten. if name != "" { if c.session.PreparedStatements.Exists(name) { return c.sendInternalError(fmt.Sprintf("prepared statement %q already exists", name)) } } query, err := buf.getString() if err != nil { return err } // The client may provide type information for (some of) the // placeholders. Read this first. numQArgTypes, err := buf.getUint16() if err != nil { return err } inTypeHints := make([]oid.Oid, numQArgTypes) for i := range inTypeHints { typ, err := buf.getUint32() if err != nil { return err } inTypeHints[i] = oid.Oid(typ) } // Prepare the mapping of SQL placeholder names to // types. Pre-populate it with the type hints received from the // client, if any. sqlTypeHints := make(parser.PlaceholderTypes) for i, t := range inTypeHints { if t == 0 { continue } v, ok := sql.OidToDatum(t) if !ok { return c.sendInternalError(fmt.Sprintf("unknown oid type: %v", t)) } sqlTypeHints[fmt.Sprint(i+1)] = v } // Create the new PreparedStatement in the connection's Session. stmt, err := c.session.PreparedStatements.New(ctx, c.executor, name, query, sqlTypeHints) if err != nil { return c.sendError(err) } // Convert the inferred SQL types back to an array of pgwire Oids. inTypes := make([]oid.Oid, 0, len(stmt.SQLTypes)) for k, t := range stmt.SQLTypes { i, err := strconv.Atoi(k) if err != nil || i < 1 { return c.sendInternalError(fmt.Sprintf("invalid placeholder name: $%s", k)) } // Placeholder names are 1-indexed; the arrays in the protocol are // 0-indexed. i-- // Grow inTypes to be at least as large as i. Prepopulate all // slots with the hints provided, if any. for j := len(inTypes); j <= i; j++ { inTypes = append(inTypes, 0) if j < len(inTypeHints) { inTypes[j] = inTypeHints[j] } } // OID to Datum is not a 1-1 mapping (for example, int4 and int8 // both map to TypeInt), so we need to maintain the types sent by // the client. if inTypes[i] != 0 { continue } id, ok := sql.DatumToOid(t) if !ok { return c.sendInternalError(fmt.Sprintf("unknown datum type: %s", t)) } inTypes[i] = id } for i, t := range inTypes { if t == 0 { return c.sendInternalError( fmt.Sprintf("could not determine data type of placeholder $%d", i+1)) } } // Attach pgwire-specific metadata to the PreparedStatement. stmt.ProtocolMeta = preparedStatementMeta{inTypes: inTypes} c.writeBuf.initMsg(serverMsgParseComplete) return c.writeBuf.finishMsg(c.wr) }