// // readSingleRecord should be called to read a single record from the DBClient connection // stream (from a db query/command). In particular, this function should be called // after the resultType has been read from the stream and resultType == 'r' (byte 114). // When this is called the 'r' byte shown below should have already been read. This // function will then read everything else shown here - including the serialized record, // but *NOT* including the byte after the serialized record (which is 0 to indicate // End of Transmission). // // Writing byte (1 byte): 114 [OChannelBinaryServer] <- 'r' (type=single-record) // Writing short (2 bytes): 0 [OChannelBinaryServer] <- 0=full record (-2=null, -3=RID only) // Writing byte (1 byte): 100 [OChannelBinaryServer] <- 'd'=document ('f'=flat data, 'b'=raw bytes) // Writing short (2 bytes): 11 [OChannelBinaryServer] <- cluster-id (RID part 1) // Writing long (8 bytes): 0 [OChannelBinaryServer] <- cluster-pos (RID part 2) // Writing int (4 bytes): 1 [OChannelBinaryServer] <- version // Writing bytes (4+26=30 bytes): [0, 14, 80, 97, 116, ... , 110, 107, 1] <- serialized record // // A new single ODocument pointer is returned. // // TODO: this method needs to determine how to handle 'f' (flat data) and 'b' (raw bytes) // func readSingleRecord(dbc *DBClient) (*oschema.ODocument, error) { var doc *oschema.ODocument resultType, err := rw.ReadShort(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if resultType == RecordNull { // null record // do nothing - return the zero values of the return types return nil, nil } else if resultType == RecordRID { orid, err := readRID(dbc) if err != nil { return nil, oerror.NewTrace(err) } doc = oschema.NewDocument("") doc.RID = orid ogl.Warn(fmt.Sprintf("readSingleRecord :: Code path not seen before!!: SQLCommand resulted in RID: %s\n", orid)) // TODO: would now load that record from the DB if the user (Go SQL API) wants it return doc, nil } else if resultType != int16(0) { _, file, line, _ := runtime.Caller(0) return nil, fmt.Errorf("Unexpected resultType in SQLCommand (file: %s; line %d): %d", file, line+1, resultType) } // if get here then have a full record, which can be in one of three formats: // - "flat data" // - "raw bytes" // - "document" recType, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if recType == byte('d') { return readSingleDocument(dbc) } else if recType == byte('f') { return readFlatDataRecord(dbc) // ??? } else if recType == byte('b') { return readRawBytesRecord(dbc) // ??? } else { _, file, line, _ := runtime.Caller(0) return nil, fmt.Errorf("Unexpected record type. Expected 'd', 'f' or 'b', but was %v (file: %s; line %d)", recType, file, line+1) } }
// // TODO: in the Java version there is a "fill" method on ODocument (ORecord) // to create a record from these entries => maybe move this there? // func createDocumentFromBytes(rid oschema.ORID, recVersion int32, serializedDoc []byte, dbc *DBClient) (*oschema.ODocument, error) { var doc *oschema.ODocument doc = oschema.NewDocument("") // don't know classname yet (in serialized record) doc.RID = rid doc.Version = recVersion // TODO: here need to make a query to look up the schema of the doc if we don't have it already cached // the first byte specifies record serialization version // use it to look up serializer and strip off that byte serde := dbc.RecordSerDes[int(serializedDoc[0])] recBuf := obuf.NewReadBuffer(serializedDoc[1:]) err := serde.Deserialize(dbc, doc, recBuf) if err != nil { return nil, err } return doc, nil }
// TODO: what datatypes can the params be? => right now allowing only string func serializeSimpleSQLParams(dbc *DBClient, params []string) ([]byte, error) { // Java client uses Map<Object, Object> // Entry: {0=Honda, 1=Accord}, so positional params start with 0 // OSQLQuery#serializeQueryParameters(Map<O,O> params) // creates an ODocument // params.put("params", convertToRIDsIfPossible(params)) // the convertToRIDsIfPossible is the one that handles Set vs. Map vs. ... vs. else -> primitive which is what simple strings are // then the serialization is done via ODocument#toStream -> ORecordSerializer#toStream // serializeClass(document) => returns null // only field name in the document is "params" // when the embedded map comes in {0=Honda, 1=Accord}, it calls writeSingleValue if len(params) == 0 { return nil, nil } doc := oschema.NewDocument("") // the params must be serialized as an embedded map of form: // {params => {0=>paramVal1, 1=>paramVal2}} // which in ogonori is a Field with: // Field.Name = params // Field.Value = {0=>paramVal1, 1=>paramVal2}} (map[string]interface{}) paramsMap := oschema.NewEmbeddedMapWithCapacity(2) for i, pval := range params { paramsMap.Put(strconv.Itoa(i), pval, oschema.STRING) } doc.FieldWithType("params", paramsMap, oschema.EMBEDDEDMAP) ogl.Debugf("DOC XX: %v\n", doc) // buf := new(bytes.Buffer) // err := buf.WriteByte(dbc.serializationVersion) // if err != nil { // return nil, oerror.NewTrace(err) // } serde := dbc.RecordSerDes[int(dbc.serializationVersion)] serializedBytes, err := serde.Serialize(dbc, doc) if err != nil { return nil, oerror.NewTrace(err) } ogl.Debugf("serialized params: %v\n", serializedBytes) return serializedBytes, nil // ------------------------ // final byte type = network.readByte(); // switch (type) { // case 'n': // result = null; // break; // case 'r': // result = OChannelBinaryProtocol.readIdentifiable(network); // if (result instanceof ORecord) // database.getLocalCache().updateRecord((ORecord) result); // break; // case 'l': // final int tot = network.readInt(); // final Collection<OIdentifiable> list = new ArrayList<OIdentifiable>(tot); // for (int i = 0; i < tot; ++i) { // final OIdentifiable resultItem = OChannelBinaryProtocol.readIdentifiable(network); // if (resultItem instanceof ORecord) // database.getLocalCache().updateRecord((ORecord) resultItem); // list.add(resultItem); // } // result = list; // break; // case 'a': // 'a' means "serialized result" // final String value = new String(network.readBytes()); // result = ORecordSerializerStringAbstract.fieldTypeFromStream(null, ORecordSerializerStringAbstract.getType(value), // value); // break; // default: // OLogManager.instance().warn(this, "Received unexpected result from query: %d", type); // } // return nil, nil }
// // FetchRecordByRID takes an ORID and reads that record from the database. // NOTE: for now I'm assuming all records are Documents (they can also be "raw bytes" or "flat data") // and for some reason I don't understand, multiple records can be returned, so I'm returning // a slice of ODocument // // TODO: may also want to expose options: ignoreCache, loadTombstones bool // TODO: need to properly handle fetchPlan func FetchRecordByRID(dbc *DBClient, orid oschema.ORID, fetchPlan string) ([]*oschema.ODocument, error) { dbc.buf.Reset() err := writeCommandAndSessionId(dbc, REQUEST_RECORD_LOAD) if err != nil { return nil, oerror.NewTrace(err) } err = rw.WriteShort(dbc.buf, orid.ClusterID) if err != nil { return nil, oerror.NewTrace(err) } err = rw.WriteLong(dbc.buf, orid.ClusterPos) if err != nil { return nil, oerror.NewTrace(err) } err = rw.WriteString(dbc.buf, fetchPlan) if err != nil { return nil, oerror.NewTrace(err) } ignoreCache := true // hardcoding for now err = rw.WriteBool(dbc.buf, ignoreCache) if err != nil { return nil, oerror.NewTrace(err) } loadTombstones := false // hardcoding for now err = rw.WriteBool(dbc.buf, loadTombstones) if err != nil { return nil, oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return nil, oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return nil, oerror.NewTrace(err) } // this query can return multiple records (though I don't understand why) // so must do this in a loop docs := make([]*oschema.ODocument, 0, 1) for { payloadStatus, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if payloadStatus == byte(0) { break } rectype, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } recversion, err := rw.ReadInt(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } databytes, err := rw.ReadBytes(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } ogl.Debugf("rectype:%v, recversion:%v, len(databytes):%v\n", rectype, recversion, len(databytes)) if rectype == 'd' { // we don't know the classname so set empty value doc := oschema.NewDocument("") doc.RID = orid doc.Version = recversion // the first byte specifies record serialization version // use it to look up serializer serde := dbc.RecordSerDes[int(databytes[0])] // then strip off the version byte and send the data to the serde err = serde.Deserialize(dbc, doc, obuf.NewReadBuffer(databytes[1:])) if err != nil { return nil, fmt.Errorf("ERROR in Deserialize for rid %v: %v\n", orid, err) } docs = append(docs, doc) } else { return nil, fmt.Errorf("Only `document` records are currently supported by the client. Record returned was type: %v", rectype) } } return docs, nil }
// // RequestDBList works like the "list databases" command from the OrientDB client. // The result is put into a map, where the key of the map is the name of the // database and the value is the type concatenated with the path, like so: // // key: cars // val: plocal:/path/to/orientdb-community-2.0.1/databases/cars // func RequestDBList(dbc *DBClient) (map[string]string, error) { dbc.buf.Reset() if dbc.sessionId == NoSessionID { return nil, oerror.SessionNotInitialized{} } // cmd err := rw.WriteByte(dbc.buf, REQUEST_DB_LIST) if err != nil { return nil, oerror.NewTrace(err) } // session id err = rw.WriteInt(dbc.buf, dbc.sessionId) if err != nil { return nil, oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return nil, oerror.NewTrace(err) } status, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } err = readAndValidateSessionId(dbc.conx, dbc.sessionId) if err != nil { return nil, oerror.NewTrace(err) } if status == RESPONSE_STATUS_ERROR { serverExceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } return nil, fmt.Errorf("Server Error(s): %v", serverExceptions) } // the bytes returned as a serialized EMBEDDEDMAP, so send it to the SerDe responseBytes, err := rw.ReadBytes(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } serde := dbc.RecordSerDes[int(responseBytes[0])] buf := obuf.NewReadBuffer(responseBytes[1:]) doc := oschema.NewDocument("") err = serde.Deserialize(dbc, doc, buf) if err != nil { return nil, oerror.NewTrace(err) } m := make(map[string]string) fldMap := doc.GetField("databases").Value.(map[string]interface{}) for k, v := range fldMap { m[k] = v.(string) } return m, nil }
// // readDataValue reads the next data section from `buf` according // to the type of the property (property.Typ) and updates the OField object // to have the value. // func (serde ORecordSerializerV0) readDataValue(buf *obuf.ReadBuf, datatype oschema.ODataType) (interface{}, error) { var ( val interface{} err error ) switch datatype { case oschema.BOOLEAN: val, err = rw.ReadBool(buf) ogl.Debugf("DEBUG BOOL: +readDataVal val: %v\n", val) // DEBUG case oschema.INTEGER: var i64 int64 i64, err = varint.ReadVarIntAndDecode64(buf) if err == nil { val = int32(i64) } ogl.Debugf("DEBUG INT: +readDataVal val: %v\n", val) // DEBUG case oschema.SHORT: var i32 int32 i32, err = varint.ReadVarIntAndDecode32(buf) if err == nil { val = int16(i32) } ogl.Debugf("DEBUG SHORT: +readDataVal val: %v\n", val) // DEBUG case oschema.LONG: val, err = varint.ReadVarIntAndDecode64(buf) ogl.Debugf("DEBUG LONG: +readDataVal val: %v\n", val) // DEBUG case oschema.FLOAT: val, err = rw.ReadFloat(buf) ogl.Debugf("DEBUG FLOAT: +readDataVal val: %v\n", val) // DEBUG case oschema.DOUBLE: val, err = rw.ReadDouble(buf) ogl.Debugf("DEBUG DOUBLE: +readDataVal val: %v\n", val) // DEBUG case oschema.DATETIME: // OrientDB DATETIME is precise to the second val, err = serde.readDateTime(buf) ogl.Debugf("DEBUG DATEIME: +readDataVal val: %v\n", val) // DEBUG case oschema.DATE: // OrientDB DATE is precise to the day val, err = serde.readDate(buf) ogl.Debugf("DEBUG DATE: +readDataVal val: %v\n", val) // DEBUG case oschema.STRING: val, err = varint.ReadString(buf) ogl.Debugf("DEBUG STR: +readDataVal val: %v\n", val) // DEBUG case oschema.BINARY: val, err = varint.ReadBytes(buf) ogl.Debugf("DEBUG BINARY: +readDataVal val: %v\n", val) // DEBUG case oschema.EMBEDDED: doc := oschema.NewDocument("") err = serde.Deserialize(nil, doc, buf) val = interface{}(doc) // ogl.Debugf("DEBUG EMBEDDEDREC: +readDataVal val: %v\n", val) // DEBUG case oschema.EMBEDDEDLIST: val, err = serde.readEmbeddedCollection(buf) // ogl.Debugf("DEBUG EMBD-LIST: +readDataVal val: %v\n", val) // DEBUG case oschema.EMBEDDEDSET: val, err = serde.readEmbeddedCollection(buf) // TODO: may need to create a set type as well // ogl.Debugf("DEBUG EMBD-SET: +readDataVal val: %v\n", val) // DEBUG case oschema.EMBEDDEDMAP: val, err = serde.readEmbeddedMap(buf) // ogl.Debugf("DEBUG EMBD-MAP: +readDataVal val: %v\n", val) // DEBUG case oschema.LINK: // a link is two int64's (cluster:record) - we translate it here to a string RID val, err = serde.readLink(buf) ogl.Debugf("DEBUG LINK: +readDataVal val: %v\n", val) // DEBUG case oschema.LINKLIST, oschema.LINKSET: val, err = serde.readLinkList(buf) ogl.Debugf("DEBUG LINK LIST/SET: +readDataVal val: %v\n", val) // DEBUG case oschema.LINKMAP: val, err = serde.readLinkMap(buf) ogl.Debugf("DEBUG LINKMap: +readDataVal val: %v\n", val) // DEBUG case oschema.BYTE: val, err = rw.ReadByte(buf) ogl.Debugf("DEBUG BYTE: +readDataVal val: %v\n", val) // DEBUG case oschema.LINKBAG: val, err = serde.readLinkBag(buf) ogl.Debugf("DEBUG LINKBAG: +readDataVal val: %v\n", val) // DEBUG case oschema.CUSTOM: // TODO: impl me -> how? when is this used? panic("ORecordSerializerV0#readDataValue CUSTOM NOT YET IMPLEMENTED") case oschema.DECIMAL: // TODO: impl me -> Java client uses BigDecimal for this panic("ORecordSerializerV0#readDataValue DECIMAL NOT YET IMPLEMENTED") default: // ANY and TRANSIENT are do nothing ops } return val, err }