// // writeEmbeddedMap serializes the EMBEDDEDMAP type. Currently, OrientDB only uses string // types for the map keys, so that is an assumption of this method as well. // func (serde ORecordSerializerV0) writeEmbeddedMap(buf *obuf.WriteBuf, m oschema.OEmbeddedMap) error { // number of entries in the map err := varint.EncodeAndWriteVarInt32(buf, int32(m.Len())) if err != nil { return oerror.NewTrace(err) } ptrPos := make([]int, 0, m.Len()) // position in buf where data ptr int needs to be written // TODO: do the map entries have to be written in any particular order? I will assume no for now keys, vals, types := m.All() /* ---[ write embedded map header ]--- */ for i, k := range keys { // key type err = rw.WriteByte(buf, byte(oschema.STRING)) if err != nil { return oerror.NewTrace(err) } // write the key value err = varint.WriteString(buf, k) if err != nil { return oerror.NewTrace(err) } ptrPos = append(ptrPos, buf.Len()) buf.Skip(4) // placeholder integer for data ptr dataType := types[i] if dataType == oschema.UNKNOWN { dataType = getDataType(vals[i]) // TODO: not sure this is necessary } // write data type of the data err = rw.WriteByte(buf, byte(dataType)) if err != nil { return oerror.NewTrace(err) } } /* ---[ write embedded map data values ]--- */ for i := 0; i < len(vals); i++ { currPos := buf.Len() buf.Seek(uint(ptrPos[i])) err = rw.WriteInt(buf, int32(currPos)) if err != nil { return oerror.NewTrace(err) } buf.Seek(uint(currPos)) err = serde.writeDataValue(buf, vals[i], types[i]) if err != nil { return oerror.NewTrace(err) } } return nil }
// // Serialize takes an ODocument and serializes it to bytes in accordance // with the OrientDB binary serialization spec and writes them to the // bytes.Buffer passed in. // func (serde ORecordSerializerV0) Serialize(dbc *DBClient, doc *oschema.ODocument) ([]byte, error) { // temporarily set state for the duration of this Serialize call // dbc is allowed to be nil for reentrant (recursive) calls -- in which // case serde.dbc should already be set (not-nil) if dbc != nil { if serde.dbc != nil { return nil, errors.New("Attempted to set dbc again in Serialize when it is already set") } serde.dbc = dbc defer func() { serde.dbc = nil }() } else if serde.dbc == nil { return nil, errors.New("dbc *DBClient passed into Serialize was null and dbc had not already been set in Serializer state") } // need to create a new buffer for the serialized record for ptr value calculations, // since the incoming buffer (`buf`) already has a lot of stuff written to it (session-id, etc) // that are NOT part of the serialized record serdebuf := obuf.NewWriteBuffer(80) // holds only the serialized value // write the serialization version in so that the buffer size math works err := rw.WriteByte(serdebuf, 0) if err != nil { return nil, oerror.NewTrace(err) } err = serde.serializeDocument(serdebuf, doc) if err != nil { return nil, oerror.NewTrace(err) } return serdebuf.Bytes(), nil }
// // The link map allow to have as key the types: // STRING,SHORT,INTEGER,LONG,BYTE,DATE,DECIMAL,DATETIME,DATA,FLOAT,DOUBLE // the serialization of the linkmap is a list of entry // // +----------------------------+ // | values:link_map_entry[] | // +----------------------------+ // // link_map_entry structure // // +--------------+------------------+------------+ // | keyType:byte | keyValue:byte[] | link:LINK | // +--------------+------------------+------------+ // // keyType - is the type of the key, can be only one of the listed type. // keyValue - the value of the key serialized with the serializer of the type // link - the link value stored with the formant of a LINK // // TODO: right now only supporting string keys, but need to support the // datatypes listed above (also for EmbeddedMaps) // func (serde ORecordSerializerV0) writeLinkMap(buf *obuf.WriteBuf, m map[string]*oschema.OLink) error { // number of entries in the map err := varint.EncodeAndWriteVarInt32(buf, int32(len(m))) if err != nil { return oerror.NewTrace(err) } for k, v := range m { // keyType err = rw.WriteByte(buf, byte(oschema.STRING)) if err != nil { return oerror.NewTrace(err) } // keyValue err = varint.WriteString(buf, k) if err != nil { return oerror.NewTrace(err) } // link err = serde.writeLink(buf, v) if err != nil { return oerror.NewTrace(err) } } return nil }
// // DatabaseExists is a server-level command, so must be preceded by calling // ConnectToServer, otherwise an authorization error will be returned. // The storageType param must be either PersistentStorageType or VolatileStorageType. // func DatabaseExists(dbc *DBClient, dbname string, storageType constants.StorageType) (bool, error) { dbc.buf.Reset() if dbc.sessionId == NoSessionID { return false, oerror.SessionNotInitialized{} } // cmd err := rw.WriteByte(dbc.buf, REQUEST_DB_EXIST) if err != nil { return false, err } // session id err = rw.WriteInt(dbc.buf, dbc.sessionId) if err != nil { return false, err } // database name, storage-type err = rw.WriteStrings(dbc.buf, dbname, string(storageType)) if err != nil { return false, err } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return false, err } /* ---[ Read Response From Server ]--- */ status, err := rw.ReadByte(dbc.conx) if err != nil { return false, err } err = readAndValidateSessionId(dbc.conx, dbc.sessionId) if err != nil { return false, err } if status == RESPONSE_STATUS_ERROR { serverExceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return false, err } return false, fmt.Errorf("Server Error(s): %v", serverExceptions) } // the answer to the query dbexists, err := rw.ReadBool(dbc.conx) if err != nil { return false, err } return dbexists, nil }
// // CreateDatabase will create a `remote` database of the type and storageType specified. // dbType must be type DocumentDBType or GraphDBType. // storageType must type PersistentStorageType or VolatileStorageType. // func CreateDatabase(dbc *DBClient, dbname string, dbtype constants.DatabaseType, storageType constants.StorageType) error { dbc.buf.Reset() /* ---[ precondition checks ]--- */ // TODO: may need to change this to serverSessionid (can the "sessionId" be used for both server connections and db conx?) if dbc.sessionId == NoSessionID { return oerror.SessionNotInitialized{} } /* ---[ build request and send to server ]--- */ // cmd err := rw.WriteByte(dbc.buf, REQUEST_DB_CREATE) if err != nil { return err } // session id err = rw.WriteInt(dbc.buf, dbc.sessionId) if err != nil { return err } err = rw.WriteStrings(dbc.buf, dbname, string(dbtype), string(storageType)) if err != nil { return err } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return err } /* ---[ read response from server ]--- */ status, err := rw.ReadByte(dbc.conx) if err != nil { return err } err = readAndValidateSessionId(dbc.conx, dbc.sessionId) if err != nil { return err } if status == RESPONSE_STATUS_ERROR { serverExceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return err } return fmt.Errorf("Server Error(s): %v", serverExceptions) } return nil }
// // DropDatabase drops the specified database. The caller must provide // both the name and the type of the database. The type should either: // // obinary.DocumentDBType // obinary.GraphDBType // // This is a "server" command, so you must have already called // ConnectToServer before calling this function. // func DropDatabase(dbc *DBClient, dbname string, dbtype constants.DatabaseType) error { dbc.buf.Reset() if dbc.sessionId == NoSessionID { return oerror.SessionNotInitialized{} } // cmd err := rw.WriteByte(dbc.buf, REQUEST_DB_DROP) if err != nil { return err } // session id err = rw.WriteInt(dbc.buf, dbc.sessionId) if err != nil { return err } // database name, storage-type err = rw.WriteStrings(dbc.buf, dbname, string(dbtype)) if err != nil { return err } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return err } /* ---[ read response from server ]--- */ status, err := rw.ReadByte(dbc.conx) if err != nil { return err } err = readAndValidateSessionId(dbc.conx, dbc.sessionId) if err != nil { return err } if status == RESPONSE_STATUS_ERROR { serverExceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return err } return fmt.Errorf("Server Error(s): %v", serverExceptions) } return nil }
func writeCommandAndSessionId(dbc *DBClient, cmd byte) error { if dbc.sessionId == NoSessionID { return oerror.SessionNotInitialized{} } err := rw.WriteByte(dbc.buf, cmd) if err != nil { return oerror.NewTrace(err) } err = rw.WriteInt(dbc.buf, dbc.sessionId) if err != nil { return oerror.NewTrace(err) } return nil }
// // Serialization format for EMBEDDEDLIST and EMBEDDEDSET // +-------------+------------+-------------------+ // |size:varInt | type:Otype | items:item_data[] | // +-------------+------------+-------------------+ // // The item_data data structure is: // +------------------+--------------+ // | data_type:OType | data:byte[] | // +------------------+--------------+ // func (serde ORecordSerializerV0) serializeEmbeddedCollection(buf *obuf.WriteBuf, ls oschema.OEmbeddedList) error { err := varint.EncodeAndWriteVarInt32(buf, int32(ls.Len())) if err != nil { return oerror.NewTrace(err) } // following the lead of the Java driver, you don't specify the type of the list overall // (I tried to and it doesn't work, at least with OrientDB-2.0.1) err = rw.WriteByte(buf, byte(oschema.ANY)) if err != nil { return oerror.NewTrace(err) } for _, val := range ls.Values() { buf.WriteByte(byte(ls.Type())) err = serde.writeDataValue(buf, val, ls.Type()) if err != nil { return oerror.NewTrace(err) } } return nil }
// // UpdateRecord should be used update an existing record in the OrientDB database. // It does the REQUEST_RECORD_UPDATE OrientDB cmd (network binary protocol) // func UpdateRecord(dbc *DBClient, doc *oschema.ODocument) error { dbc.buf.Reset() err := writeCommandAndSessionId(dbc, REQUEST_RECORD_UPDATE) if err != nil { return oerror.NewTrace(err) } if doc.RID.ClusterID < 0 || doc.RID.ClusterPos < 0 { return errors.New("Document is not updateable - has negative RID values") } err = rw.WriteShort(dbc.buf, doc.RID.ClusterID) if err != nil { return oerror.NewTrace(err) } err = rw.WriteLong(dbc.buf, doc.RID.ClusterPos) if err != nil { return oerror.NewTrace(err) } // update-content flag err = rw.WriteBool(dbc.buf, true) if err != nil { return oerror.NewTrace(err) } // serialized-doc serde := dbc.RecordSerDes[int(dbc.serializationVersion)] // this writes the serialized record to dbc.buf serializedBytes, err := serde.Serialize(dbc, doc) if err != nil { return oerror.NewTrace(err) } err = rw.WriteBytes(dbc.buf, serializedBytes) if err != nil { return oerror.NewTrace(err) } // record version err = rw.WriteInt(dbc.buf, doc.Version) if err != nil { return oerror.NewTrace(err) } // record-type: document err = rw.WriteByte(dbc.buf, byte('d')) // TODO: how support 'b' (raw bytes) & 'f' (flat data)? if err != nil { return oerror.NewTrace(err) } // mode: synchronous err = rw.WriteByte(dbc.buf, 0x0) if err != nil { return oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return oerror.NewTrace(err) } doc.Version, err = rw.ReadInt(dbc.conx) if err != nil { return oerror.NewTrace(err) } nCollChanges, err := rw.ReadInt(dbc.conx) if err != nil { return oerror.NewTrace(err) } if nCollChanges != 0 { // if > 0, then have to deal with RidBag mgmt: // [(uuid-most-sig-bits:long)(uuid-least-sig-bits:long)(updated-file-id:long)(updated-page-index:long)(updated-page-offset:int)] panic("CreateRecord: Found case where number-collection-changes is not zero -> log case and impl code to handle") } return nil }
func deleteByRID(dbc *DBClient, rid string, recVersion int32, async bool) error { dbc.buf.Reset() orid := oschema.NewORIDFromString(rid) err := writeCommandAndSessionId(dbc, REQUEST_RECORD_DELETE) if err != nil { return err } err = rw.WriteShort(dbc.buf, orid.ClusterID) if err != nil { return err } err = rw.WriteLong(dbc.buf, orid.ClusterPos) if err != nil { return err } err = rw.WriteInt(dbc.buf, recVersion) if err != nil { return err } // sync mode ; 0 = synchronous; 1 = asynchronous var syncMode byte if async { syncMode = byte(1) } err = rw.WriteByte(dbc.buf, syncMode) if err != nil { return err } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return err } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return err } payloadStatus, err := rw.ReadByte(dbc.conx) if err != nil { return err } // status 1 means record was deleted; // status 0 means record was not deleted (either failed or didn't exist) if payloadStatus == byte(0) { return fmt.Errorf("Server reports record %s was not deleted. Either failed or did not exist.", rid) } return nil }
// // OpenDatabase sends the REQUEST_DB_OPEN command to the OrientDB server to // open the db in read/write mode. The database name and type are required, plus // username and password. Database type should be one of the obinary constants: // DocumentDBType or GraphDBType. // func OpenDatabase(dbc *DBClient, dbname string, dbtype constants.DatabaseType, username, passw string) error { buf := dbc.buf buf.Reset() // first byte specifies request type err := rw.WriteByte(buf, REQUEST_DB_OPEN) if err != nil { return oerror.NewTrace(err) } // session-id - send a negative number to create a new server-side conx err = rw.WriteInt(buf, RequestNewSession) if err != nil { return oerror.NewTrace(err) } err = rw.WriteStrings(buf, DriverName, DriverVersion) if err != nil { return oerror.NewTrace(err) } err = rw.WriteShort(buf, dbc.binaryProtocolVersion) if err != nil { return oerror.NewTrace(err) } // dbclient id - send as null, but cannot be null if clustered config // TODO: change to use dbc.clusteredConfig once that is added err = rw.WriteNull(buf) if err != nil { return oerror.NewTrace(err) } // serialization-impl err = rw.WriteString(buf, dbc.serializationType) if err != nil { return oerror.NewTrace(err) } // token-session // TODO: hardcoded as false for now -> change later based on ClientOptions settings err = rw.WriteBool(buf, false) if err != nil { return oerror.NewTrace(err) } // dbname, dbtype, username, password err = rw.WriteStrings(buf, dbname, string(dbtype), username, passw) if err != nil { return oerror.NewTrace(err) } // now send to the OrientDB server _, err = dbc.conx.Write(buf.Bytes()) if err != nil { return oerror.NewTrace(err) } /* ---[ read back response ]--- */ // first byte indicates success/error status, err := rw.ReadByte(dbc.conx) if err != nil { return oerror.NewTrace(err) } dbc.currDB = NewDatabase(dbname, dbtype) // the first int returned is the session id sent - which was the `RequestNewSession` sentinel sessionValSent, err := rw.ReadInt(dbc.conx) if err != nil { return oerror.NewTrace(err) } if sessionValSent != RequestNewSession { return errors.New("Unexpected Error: Server did not return expected session-request-val that was sent") } // if status returned was ERROR, then the rest of server data is the exception info if status != RESPONSE_STATUS_OK { exceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return oerror.NewTrace(err) } return fmt.Errorf("Server Error(s): %v", exceptions) } // for the REQUEST_DB_OPEN case, another int is returned which is the new sessionId sessionId, err := rw.ReadInt(dbc.conx) if err != nil { return oerror.NewTrace(err) } dbc.sessionId = sessionId // next is the token, which may be null tokenBytes, err := rw.ReadBytes(dbc.conx) if err != nil { return oerror.NewTrace(err) } dbc.token = tokenBytes // array of cluster info in this db // TODO: do we need to retain all this in memory? numClusters, err := rw.ReadShort(dbc.conx) if err != nil { return oerror.NewTrace(err) } clusters := make([]OCluster, 0, numClusters) for i := 0; i < int(numClusters); i++ { clusterName, err := rw.ReadString(dbc.conx) if err != nil { return oerror.NewTrace(err) } clusterId, err := rw.ReadShort(dbc.conx) if err != nil { return oerror.NewTrace(err) } clusters = append(clusters, OCluster{Name: clusterName, Id: clusterId}) } dbc.currDB.Clusters = clusters // cluster-config - bytes - null unless running server in clustered config // TODO: treating this as an opaque blob for now clusterCfg, err := rw.ReadBytes(dbc.conx) if err != nil { return oerror.NewTrace(err) } dbc.currDB.ClustCfg = clusterCfg // orientdb server release - throwing away for now // TODO: need this? _, err = rw.ReadString(dbc.conx) if err != nil { return oerror.NewTrace(err) } // /* ---[ load #0:0 - config record ]--- */ schemaRIDStr, err := loadConfigRecord(dbc) if err != nil { return oerror.NewTrace(err) } clusterID, clusterPos, err := parseRid(schemaRIDStr) if err != nil { return oerror.NewTrace(err) } // /* ---[ load #0:1 - schema record ]--- */ err = loadSchema(dbc, oschema.ORID{ClusterID: clusterID, ClusterPos: clusterPos}) if err != nil { return oerror.NewTrace(err) } return nil }
func getClusterCount(dbc *DBClient, countTombstones bool, clusterNames []string) (count int64, err error) { dbc.buf.Reset() clusterIDs := make([]int16, len(clusterNames)) for i, name := range clusterNames { clusterID := findClusterWithName(dbc.currDB.Clusters, strings.ToLower(name)) if clusterID < 0 { // TODO: This is problematic - someone else may add the cluster not through this // driver session and then this would fail - so options: // 1) do a lookup of all clusters on the DB // 2) provide a FetchClusterCountById(dbc, clusterID) return int64(0), fmt.Errorf("No cluster with name %s is known in database %s\n", name, dbc.currDB.Name) } clusterIDs[i] = clusterID } err = writeCommandAndSessionId(dbc, REQUEST_DATACLUSTER_COUNT) if err != nil { return int64(0), oerror.NewTrace(err) } // specify number of clusterIDs being sent and then write the clusterIDs err = rw.WriteShort(dbc.buf, int16(len(clusterIDs))) if err != nil { return int64(0), oerror.NewTrace(err) } for _, cid := range clusterIDs { err = rw.WriteShort(dbc.buf, cid) if err != nil { return int64(0), oerror.NewTrace(err) } } // count-tombstones var ct byte if countTombstones { ct = byte(1) } err = rw.WriteByte(dbc.buf, ct) // presuming that 0 means "false" if err != nil { return int64(0), oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return int64(0), oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return int64(0), oerror.NewTrace(err) } nrecs, err := rw.ReadLong(dbc.conx) if err != nil { return int64(0), oerror.NewTrace(err) } return nrecs, err }
// // 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 }
// // writeDataValue is part of the Serialize functionality // TODO: change name to writeSingleValue ? // func (serde ORecordSerializerV0) writeDataValue(buf *obuf.WriteBuf, value interface{}, datatype oschema.ODataType) (err error) { switch datatype { case oschema.STRING: err = varint.WriteString(buf, value.(string)) ogl.Debugf("DEBUG STR: -writeDataVal val: %v\n", value.(string)) // DEBUG case oschema.BOOLEAN: err = rw.WriteBool(buf, value.(bool)) ogl.Debugf("DEBUG BOOL: -writeDataVal val: %v\n", value.(bool)) // DEBUG case oschema.INTEGER: var i32val int32 i32val, err = toInt32(value) if err == nil { err = varint.EncodeAndWriteVarInt32(buf, i32val) // TODO: are serialized integers ALWAYS varint encoded? ogl.Debugf("DEBUG INT: -writeDataVal val: %v\n", i32val) // DEBUG } case oschema.SHORT: // TODO: needs toInt16 conversion fn err = varint.EncodeAndWriteVarInt32(buf, int32(value.(int16))) ogl.Debugf("DEBUG SHORT: -writeDataVal val: %v\n", value.(int16)) // DEBUG case oschema.LONG: var i64val int64 i64val, err = toInt64(value) if err == nil { err = varint.EncodeAndWriteVarInt64(buf, i64val) // TODO: are serialized longs ALWAYS varint encoded? ogl.Debugf("DEBUG LONG: -writeDataVal val: %v\n", i64val) // DEBUG } case oschema.FLOAT: var f32 float32 f32, err = toFloat32(value) if err == nil { err = rw.WriteFloat(buf, f32) } ogl.Debugf("DEBUG FLOAT: -writeDataVal val: %v\n", value) // DEBUG case oschema.DOUBLE: var f64 float64 f64, err = toFloat64(value) if err == nil { err = rw.WriteDouble(buf, f64) } ogl.Debugf("DEBUG DOUBLE: -writeDataVal val: %v\n", value.(float64)) // DEBUG case oschema.DATETIME: err = writeDateTime(buf, value) ogl.Debugf("DEBUG DATETIME: -writeDataVal val: %v\n", value) // DEBUG case oschema.DATE: err = writeDate(buf, value) ogl.Debugf("DEBUG DATE: -writeDataVal val: %v\n", value) // DEBUG case oschema.BINARY: err = varint.WriteBytes(buf, value.([]byte)) ogl.Debugf("DEBUG BINARY: -writeDataVal val: %v\n", value.([]byte)) // DEBUG case oschema.EMBEDDED: err = serde.serializeDocument(buf, value.(*oschema.ODocument)) ogl.Debugf("DEBUG EMBEDDED: -writeDataVal val: %v\n", value) // DEBUG case oschema.EMBEDDEDLIST: err = serde.serializeEmbeddedCollection(buf, value.(oschema.OEmbeddedList)) ogl.Debugf("DEBUG EMBD-LIST: -writeDataVal val: %v\n", value) // DEBUG case oschema.EMBEDDEDSET: err = serde.serializeEmbeddedCollection(buf, value.(oschema.OEmbeddedList)) ogl.Debugf("DEBUG EMBD-SET: -writeDataVal val: %v\n", value) // DEBUG case oschema.EMBEDDEDMAP: err = serde.writeEmbeddedMap(buf, value.(oschema.OEmbeddedMap)) ogl.Debugf("DEBUG EMBEDDEDMAP: val %v\n", value.(oschema.OEmbeddedMap)) case oschema.LINK: err = serde.writeLink(buf, value.(*oschema.OLink)) ogl.Debugf("DEBUG LINK: val %v\n", value) // DEBUG case oschema.LINKLIST: err = serde.writeLinkList(buf, value.([]*oschema.OLink)) ogl.Debugf("DEBUG LINKLIST: val %v\n", value) // DEBUG case oschema.LINKSET: err = serde.writeLinkList(buf, value.([]*oschema.OLink)) ogl.Debugf("DEBUG LINKSET: val %v\n", value) // DEBUG case oschema.LINKMAP: err = serde.writeLinkMap(buf, value.(map[string]*oschema.OLink)) ogl.Debugf("DEBUG LINKMAP: val %v\n", value) // DEBUG case oschema.BYTE: err = rw.WriteByte(buf, value.(byte)) ogl.Debugf("DEBUG BYTE: -writeDataVal val: %v\n", value.(byte)) // DEBUG case oschema.DECIMAL: // TODO: impl me -> Java client uses BigDecimal for this panic("ORecordSerializerV0#writeDataValue DECIMAL NOT YET IMPLEMENTED") case oschema.CUSTOM: // TODO: impl me panic("ORecordSerializerV0#writeDataValue CUSTOM NOT YET IMPLEMENTED") case oschema.LINKBAG: panic("ORecordSerializerV0#writeDataValue LINKBAG NOT YET IMPLEMENTED") default: // ANY and TRANSIENT are do nothing ops } return err }
// // In Progress attempt to rewrite writeSerializedRecord and related fns // using a seekable/skipping WriteBuf // func (serde ORecordSerializerV0) writeSerializedRecord(wbuf *obuf.WriteBuf, doc *oschema.ODocument) (err error) { nfields := len(doc.FieldNames()) ptrPos := make([]int, 0, nfields) // position in buf where data ptr int needs to be written currDB := serde.dbc.GetCurrDB() oclass, ok := currDB.Classes[doc.Classname] docFields := doc.GetFields() for _, fld := range docFields { var oprop *oschema.OProperty if ok { oprop = oclass.Properties[fld.Name] } // FROM THE JAVA CLIENT: // if (properties[i] != null) { // OVarIntSerializer.write(bytes, (properties[i].getId() + 1) * -1); // if (properties[i].getType() != OType.ANY) // pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE); // else // pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); // TODO: why does ANY required an additional byte? // } else { // writeString(bytes, entry.getKey()); // pos[i] = bytes.alloc(OIntegerSerializer.INT_SIZE + 1); if oprop != nil { // if property is known in the global properties, then // just write its encoded id varint.EncodeAndWriteVarInt32(wbuf, encodeFieldIDForHeader(oprop.ID)) ptrPos = append(ptrPos, wbuf.Len()) wbuf.Skip(4) // Note: no need to write property type when writing property ID } else { // property Name err = varint.WriteString(wbuf, fld.Name) if err != nil { return oerror.NewTrace(err) } ptrPos = append(ptrPos, wbuf.Len()) wbuf.Skip(4) // property Type err = rw.WriteByte(wbuf, byte(fld.Type)) if err != nil { return oerror.NewTrace(err) } } } wbuf.WriteByte(0) // End of Header sentinel // now write out the data values for i, fld := range docFields { currPos := wbuf.Len() wbuf.Seek(uint(ptrPos[i])) err = rw.WriteInt(wbuf, int32(currPos)) if err != nil { return oerror.NewTrace(err) } wbuf.Seek(uint(currPos)) err = serde.writeDataValue(wbuf, fld.Value, fld.Type) if err != nil { return oerror.NewTrace(err) } } return nil }
// // SQLCommand executes SQL commands that are not queries. Any SQL statement // that does not being with "SELECT" should be sent here. All SELECT // statements should go to the SQLQuery function. // // Commands can be optionally paramterized using ?, such as: // // INSERT INTO Foo VALUES(a, b, c) (?, ?, ?) // // The values for the placeholders (currently) must be provided as strings. // // Constraints (for now): // 1. cmds with only simple positional parameters allowed // 2. cmds with lists of parameters ("complex") NOT allowed // 3. parameter types allowed: string only for now // // SQL commands in OrientDB tend to return one of two types - a string return value or // one or more documents. The meaning are specific to the type of query. // // ---------------- // For example: // ---------------- // for a DELETE statement: // retval = number of rows deleted (as a string) // docs = empty list // // for an INSERT statement: // n = ? // docs = ? // // for an CREATE CLASS statement: // retval = cluster id of the class (TODO: or it might be number of classes in cluster) // docs = empty list // // for an DROP CLASS statement: // retval = "true" if successful, "" if class didn't exist (technically it returns null) // docs = empty list // func SQLCommand(dbc *DBClient, sql string, params ...string) (retval string, docs []*oschema.ODocument, err error) { dbc.buf.Reset() err = writeCommandAndSessionId(dbc, REQUEST_COMMAND) if err != nil { return "", nil, oerror.NewTrace(err) } mode := byte('s') // synchronous only supported for now err = rw.WriteByte(dbc.buf, mode) if err != nil { return "", nil, oerror.NewTrace(err) } // need a separate buffer to write the command-payload to, so // we can calculate its length before writing it to main dbc.buf commandBuf := new(bytes.Buffer) // "classname" (command-type, really) and the sql command err = rw.WriteStrings(commandBuf, "c", sql) // c for command(non-idempotent) if err != nil { return "", nil, oerror.NewTrace(err) } // SQLCommand // (text:string) // (has-simple-parameters:boolean) // (simple-paremeters:bytes[]) -> serialized Map (EMBEDDEDMAP??) // (has-complex-parameters:boolean) // (complex-parameters:bytes[]) -> serialized Map (EMBEDDEDMAP??) serializedParams, err := serializeSimpleSQLParams(dbc, params) if err != nil { return "", nil, oerror.NewTrace(err) } // has-simple-parameters err = rw.WriteBool(commandBuf, serializedParams != nil) if err != nil { return "", nil, oerror.NewTrace(err) } if serializedParams != nil { rw.WriteBytes(commandBuf, serializedParams) } // FIXME: no complex parameters yet since I don't understand what they are // has-complex-paramters => HARDCODING FALSE FOR NOW err = rw.WriteBool(commandBuf, false) if err != nil { return "", nil, oerror.NewTrace(err) } serializedCmd := commandBuf.Bytes() // command-payload-length and command-payload err = rw.WriteBytes(dbc.buf, serializedCmd) 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) } // for synchronous commands the remaining content is an array of form: // [(synch-result-type:byte)[(synch-result-content:?)]]+ // so the final value will by byte(0) to indicate the end of the array // and we must use a loop here for { resType, err := rw.ReadByte(dbc.conx) if err != nil { return "", nil, oerror.NewTrace(err) } // This implementation assumes that SQLCommand can never have "supplementary records" // from an extended fetchPlan if resType == byte(0) { break } resultType := rune(resType) ogl.Debugf("resultType for SQLCommand: %v (%s)\n", resultType, string(rune(resultType))) if resultType == 'n' { // null result // do nothing - anything need to be done here? } else if resultType == 'r' { // single record doc, err := readSingleRecord(dbc) if err != nil { return "", nil, oerror.NewTrace(err) } ogl.Debugf("r>doc = %v\n", doc) // DEBUG if doc != nil { docs = make([]*oschema.ODocument, 1) docs[0] = doc } } else if resultType == 'l' { // collection of records ogl.Debugln("... resultType l") collectionDocs, err := readResultSet(dbc) if err != nil { return "", nil, oerror.NewTrace(err) } if docs == nil { docs = collectionDocs } else { docs = append(docs, collectionDocs...) } } else if resultType == 'a' { // serialized type serializedRec, err := rw.ReadBytes(dbc.conx) if err != nil { return "", nil, oerror.NewTrace(err) } // TODO: for now I'm going to assume that this always just returns a string // need a use case that violates this assumption retval = string(serializedRec) if err != nil { return "", nil, oerror.NewTrace(err) } } else { _, file, line, _ := runtime.Caller(0) // TODO: I've not yet tested this route of code -> how do so? ogl.Warnf(">> Got back resultType %v (%v): Not yet supported: line:%d; file:%s\n", resultType, string(rune(resultType)), line, file) // TODO: returning here is NOT the correct long-term behavior return "", nil, fmt.Errorf("Got back resultType %v (%v): Not yet supported: line:%d; file:%s\n", resultType, string(rune(resultType)), line, file) } } return retval, docs, err }
// // SQLQuery // // TODO: right now I return the entire resultSet as an array, thus all loaded into memory // it would be better to have obinary.dbCommands provide an iterator based model // that only needs to read a "row" (ODocument) at a time // Perhaps SQLQuery() -> iterator/cursor // SQLQueryGetAll() -> []*ODocument ?? // func SQLQuery(dbc *DBClient, sql string, fetchPlan string, params ...string) ([]*oschema.ODocument, error) { dbc.buf.Reset() err := writeCommandAndSessionId(dbc, REQUEST_COMMAND) if err != nil { return nil, oerror.NewTrace(err) } mode := byte('s') // synchronous only supported for now err = rw.WriteByte(dbc.buf, mode) if err != nil { return nil, oerror.NewTrace(err) } // need a separate buffer to write the command-payload to, so // we can calculate its length before writing it to main dbc.buf commandBuf := new(bytes.Buffer) err = rw.WriteStrings(commandBuf, "q", sql) // q for query if err != nil { return nil, oerror.NewTrace(err) } // non-text-limit (-1 = use limit from query text) err = rw.WriteInt(commandBuf, -1) if err != nil { return nil, oerror.NewTrace(err) } // fetch plan err = rw.WriteString(commandBuf, fetchPlan) if err != nil { return nil, oerror.NewTrace(err) } serializedParams, err := serializeSimpleSQLParams(dbc, params) if err != nil { return nil, oerror.NewTrace(err) } if serializedParams != nil { rw.WriteBytes(commandBuf, serializedParams) } serializedCmd := commandBuf.Bytes() // command-payload-length and command-payload err = rw.WriteBytes(dbc.buf, serializedCmd) if err != nil { return nil, oerror.NewTrace(err) } // send to the OrientDB server finalBytes := dbc.buf.Bytes() _, err = dbc.conx.Write(finalBytes) if err != nil { return nil, oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return nil, oerror.NewTrace(err) } resType, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } resultType := int32(resType) var docs []*oschema.ODocument if resultType == 'n' { // NOTE: OStorageRemote in Java client just sets result to null and moves on ogl.Warn("Result type in SQLQuery is 'n' -> what to do? nothing ???") // DEBUG } else if resultType == 'r' { ogl.Warn("NOTE NOTE NOTE: this path has NOT YET BEEN TESTED") // DEBUG doc, err := readSingleRecord(dbc) if err != nil { return nil, oerror.NewTrace(err) } docs = append(docs, doc) } else if resultType == 'l' { docs, err = readResultSet(dbc) if err != nil { return nil, oerror.NewTrace(err) } } else { // TODO: I've not yet tested this route of code -> how do so? ogl.Warn(">> Not yet supported") ogl.Fatal(fmt.Sprintf("NOTE NOTE NOTE: testing the resultType == '%v' (else) route of code -- "+ "remove this note and test it!!", string(resultType))) } // any additional records are "supplementary" - from the fetchPlan these // need to be hydrated into ODocuments and then put into the primary Docs if dbc.binaryProtocolVersion >= int16(17) { // copied from the OrientDB 2.x Java client end, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if end != byte(0) { mapRIDToDoc, err := readSupplementaryRecords(dbc) if err != nil { return nil, oerror.NewTrace(err) } addSupplementaryRecsToPrimaryRecs(docs, mapRIDToDoc) } } return docs, nil }
// // ConnectToServer logs into the OrientDB server with the appropriate // admin privileges in order to execute server-level commands (as opposed // to database-level commands). This must be called to establish a server // session before any other server-level commands. The username and password // required are for the server (admin) not any particular database. // func ConnectToServer(dbc *DBClient, adminUser, adminPassw string) error { buf := dbc.buf buf.Reset() // first byte specifies request type err := rw.WriteByte(buf, REQUEST_CONNECT) if err != nil { return err } // session-id - send a negative number to create a new server-side conx err = rw.WriteInt(buf, RequestNewSession) if err != nil { return err } err = rw.WriteStrings(buf, DriverName, DriverVersion) if err != nil { return err } err = rw.WriteShort(buf, dbc.binaryProtocolVersion) if err != nil { return err } // dbclient id - send as null, but cannot be null if clustered config // TODO: change to use dbc.clusteredConfig once that is added err = rw.WriteNull(buf) if err != nil { return err } // serialization-impl err = rw.WriteString(buf, dbc.serializationType) if err != nil { return err } // token-session // TODO: hardcoded as false for now -> change later based on ClientOptions settings err = rw.WriteBool(buf, false) if err != nil { return err } // admin username, password err = rw.WriteStrings(buf, adminUser, adminPassw) if err != nil { return err } // send to OrientDB server _, err = dbc.conx.Write(buf.Bytes()) if err != nil { return err } /* ---[ Read Server Response ]--- */ // first byte indicates success/error status, err := rw.ReadByte(dbc.conx) if err != nil { return err } // the first int returned is the session id sent - which was the `RequestNewSession` sentinel sessionValSent, err := rw.ReadInt(dbc.conx) if err != nil { return err } if sessionValSent != RequestNewSession { return errors.New("Unexpected Error: Server did not return expected session-request-val that was sent") } // if status returned was ERROR, then the rest of server data is the exception info if status != RESPONSE_STATUS_OK { exceptions, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return err } return fmt.Errorf("Server Error(s): %v", exceptions) } // for the REQUEST_CONNECT case, another int is returned which is the new sessionId sessionId, err := rw.ReadInt(dbc.conx) if err != nil { return err } // TODO: this assumes you can only have one sessionId - but perhaps can have a server sessionid // and one or more database sessions open at the same time ????? dbc.sessionId = sessionId tokenBytes, err := rw.ReadBytes(dbc.conx) if err != nil { return err } dbc.token = tokenBytes return nil }