// // readEmbeddedMap handles the EMBEDDEDMAP type. Currently, OrientDB only uses string // types for the map keys, so that is an assumption of this method as well. // // TODO: change return type to (*oschema.OEmbeddedMap, error) { ??? func (serde ORecordSerializerV0) readEmbeddedMap(buf *obuf.ReadBuf) (map[string]interface{}, error) { numRecs, err := varint.ReadVarIntAndDecode32(buf) if err != nil { return nil, oerror.NewTrace(err) } nrecs := int(numRecs) m := make(map[string]interface{}) // final map to be returned // data structures for reading the map header section, which gives key names and // value types (and value ptrs, but I don't need those for the way I parse the data) keynames := make([]string, nrecs) valtypes := make([]oschema.ODataType, nrecs) // read map headers for i := 0; i < nrecs; i++ { keytype, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } if keytype != byte(oschema.STRING) { panic(fmt.Sprintf("ReadEmbeddedMap got a key datatype %v - but it should be 7 (string)", keytype)) } keynames[i], err = varint.ReadString(buf) if err != nil { return nil, oerror.NewTrace(err) } _, err = rw.ReadInt(buf) // pointer - throwing away if err != nil { return nil, oerror.NewTrace(err) } b, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } valtypes[i] = oschema.ODataType(b) } // read map values for i := 0; i < nrecs; i++ { val, err := serde.readDataValue(buf, valtypes[i]) if err != nil { return nil, oerror.NewTrace(err) } m[keynames[i]] = val } return m, nil }
func readStatusCodeAndSessionId(dbc *DBClient) error { status, err := rw.ReadByte(dbc.conx) if err != nil { return oerror.NewTrace(err) } sessionId, err := rw.ReadInt(dbc.conx) if err != nil { return oerror.NewTrace(err) } if sessionId != dbc.sessionId { // FIXME: use of fmt.Errorf is an anti-pattern return fmt.Errorf("sessionId from server (%v) does not match client sessionId (%v)", sessionId, dbc.sessionId) } if status == RESPONSE_STATUS_ERROR { serverException, err := rw.ReadErrorResponse(dbc.conx) if err != nil { return oerror.NewTrace(err) } return serverException } return nil }
// // Returns map of string keys to *oschema.OLink // func (serde ORecordSerializerV0) readLinkMap(buf io.Reader) (map[string]*oschema.OLink, error) { nentries, err := varint.ReadVarIntAndDecode32(buf) if err != nil { return nil, oerror.NewTrace(err) } linkMap := make(map[string]*oschema.OLink) for i := 0; i < int(nentries); i++ { /* ---[ read map key ]--- */ datatype, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } if datatype != byte(oschema.STRING) { // FIXME: even though all keys are currently strings, it would be easy to allow other types // using serde.readDataValue(dbc, buf, serde) return nil, fmt.Errorf("readLinkMap: datatype for key is NOT string but type: %v", datatype) } mapkey, err := varint.ReadString(buf) if err != nil { return nil, oerror.NewTrace(err) } /* ---[ read map value (always a RID) ]--- */ mapval, err := serde.readLink(buf) if err != nil { return nil, oerror.NewTrace(err) } linkMap[mapkey] = mapval } return linkMap, 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 }
// // readLinkBag handles both Embedded and remote Tree-based OLinkBags. // func (serde ORecordSerializerV0) readLinkBag(buf io.Reader) (*oschema.OLinkBag, error) { bagType, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } if bagType == byte(0) { return readTreeBasedLinkBag(buf) } return readEmbeddedLinkBag(buf) }
// // 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) } }
// // 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 }
// // readEmbeddedCollection handles both EMBEDDEDLIST and EMBEDDEDSET types. // Java client API: // Collection<?> readEmbeddedCollection(BytesContainer bytes, Collection<Object> found, ODocument document) { // `found`` gets added to during the recursive iterations // func (serde ORecordSerializerV0) readEmbeddedCollection(buf *obuf.ReadBuf) ([]interface{}, error) { nrecs, err := varint.ReadVarIntAndDecode32(buf) if err != nil { return nil, oerror.NewTrace(err) } datatype, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } if datatype != byte(oschema.ANY) { // OrientDB server always returns ANY // NOTE: currently the Java client doesn't handle this case either, so safe for now panic(fmt.Sprintf("ReadEmbeddedList got a datatype %v - currently that datatype is not supported", datatype)) } ary := make([]interface{}, int(nrecs)) // loop over all recs for i := range ary { // if type is ANY (unknown), then the next byte specifies the type of record to follow b, err := rw.ReadByte(buf) if err != nil { return nil, oerror.NewTrace(err) } itemtype := oschema.ODataType(b) if itemtype == oschema.ANY { ary[i] = nil // this is what the Java client does continue } val, err := serde.readDataValue(buf, itemtype) if err != nil { return nil, oerror.NewTrace(err) } ary[i] = val } return ary, nil }
// // DropCluster drops a cluster to the current database. It is a // database-level operation, so OpenDatabase must have already // been called first in order to start a session with the database. // If nil is returned, then the action succeeded. // func DropCluster(dbc *DBClient, clusterName string) error { dbc.buf.Reset() clusterID := findClusterWithName(dbc.currDB.Clusters, strings.ToLower(clusterName)) 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 DropClusterById(dbc, clusterID) return fmt.Errorf("No cluster with name %s is known in database %s\n", clusterName, dbc.currDB.Name) } err := writeCommandAndSessionId(dbc, REQUEST_DATACLUSTER_DROP) if err != nil { return oerror.NewTrace(err) } err = rw.WriteShort(dbc.buf, clusterID) 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) } delStatus, err := rw.ReadByte(dbc.conx) if err != nil { return oerror.NewTrace(err) } if delStatus != byte(1) { return fmt.Errorf("Drop cluster action failed. Return code from server was not '1', but %d", delStatus) } return nil }
// // When called the "status byte" should have already been called // Returns map where keys are RIDs (string) and values are ODocument objs // func readSupplementaryRecords(dbc *DBClient) (map[oschema.ORID]*oschema.ODocument, error) { mapRIDToDoc := make(map[oschema.ORID]*oschema.ODocument) for { doc, err := readSingleRecord(dbc) mapRIDToDoc[doc.RID] = doc status, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if status == byte(0) { break } } return mapRIDToDoc, 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 }
// // 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 }
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 }
// // 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 }
// // 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 }
// // 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 }
// // loadConfigRecord loads record #0:0 for the current database, caching // some of the information returned into OStorageConfiguration // func loadConfigRecord(dbc *DBClient) (schemaRID string, err error) { // The config record comes back as type 'b' (raw bytes), which should // just be converted to a string then tokenized by the pipe char dbc.buf.Reset() var ( clusterId int16 clusterPos int64 ) err = writeCommandAndSessionId(dbc, REQUEST_RECORD_LOAD) if err != nil { return schemaRID, err } clusterId = 0 err = rw.WriteShort(dbc.buf, clusterId) if err != nil { return schemaRID, err } clusterPos = 0 err = rw.WriteLong(dbc.buf, clusterPos) if err != nil { return schemaRID, err } fetchPlan := "*:-1 index:0" err = rw.WriteString(dbc.buf, fetchPlan) if err != nil { return schemaRID, err } ignoreCache := true err = rw.WriteBool(dbc.buf, ignoreCache) if err != nil { return schemaRID, err } loadTombstones := true // based on Java client code err = rw.WriteBool(dbc.buf, loadTombstones) if err != nil { return schemaRID, err } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return schemaRID, err } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return schemaRID, err } payloadStatus, err := rw.ReadByte(dbc.conx) if err != nil { return schemaRID, err } if payloadStatus == byte(0) { return schemaRID, errors.New("Payload status for #0:0 load was 0. No config data returned.") } rectype, err := rw.ReadByte(dbc.conx) if err != nil { return schemaRID, err } // this is the record version - don't see a reason to check or cache it right now _, err = rw.ReadInt(dbc.conx) if err != nil { return schemaRID, err } databytes, err := rw.ReadBytes(dbc.conx) if err != nil { return schemaRID, err } if rectype != 'b' { if err != nil { return schemaRID, fmt.Errorf("Expected rectype %d, but was: %d", 'b', rectype) } } payloadStatus, err = rw.ReadByte(dbc.conx) if err != nil { return schemaRID, err } if payloadStatus != byte(0) { return schemaRID, errors.New("Second Payload status for #0:0 load was not 0. More than one record returned unexpectedly") } err = parseConfigRecord(dbc.currDB, string(databytes)) if err != nil { return schemaRID, err } schemaRID = dbc.currDB.StorageCfg.schemaRID return schemaRID, err }
// // readResultSet should be called for collections (resultType = 'l') // from a SQLQuery call. // func readResultSet(dbc *DBClient) ([]*oschema.ODocument, error) { // for Collection // next val is: (collection-size:int) // and then each record is serialized according to format: // (0:short)(record-type:byte)(cluster-id:short)(cluster-position:long)(record-version:int)(record-content:bytes) resultSetSize, err := rw.ReadInt(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } rsize := int(resultSetSize) docs := make([]*oschema.ODocument, rsize) for i := 0; i < rsize; i++ { // TODO: move code below to readRecordInResultSet // this apparently should always be zero for serialized records -> not sure it's meaning zero, err := rw.ReadShort(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if zero != int16(0) { return nil, fmt.Errorf("ERROR: readResultSet: expected short value of 0 but is %d", zero) } recType, err := rw.ReadByte(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } // TODO: may need to check recType here => not sure that clusterId, clusterPos and version follow next if // type is 'b' (raw bytes) or 'f' (flat record) // see the readSingleDocument method (and probably call that one instead?) clusterId, err := rw.ReadShort(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } clusterPos, err := rw.ReadLong(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } recVersion, err := rw.ReadInt(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } if recType == byte('d') { // Document var doc *oschema.ODocument rid := oschema.ORID{ClusterID: clusterId, ClusterPos: clusterPos} recBytes, err := rw.ReadBytes(dbc.conx) if err != nil { return nil, oerror.NewTrace(err) } doc, err = createDocumentFromBytes(rid, recVersion, recBytes, dbc) if err != nil { return nil, oerror.NewTrace(err) } docs[i] = doc } else { _, file, line, _ := runtime.Caller(0) return nil, fmt.Errorf("%v: %v: Record type %v is not yet supported", file, line+1, recType) } } // end for loop // end, err := rw.ReadByte(dbc.conx) // if err != nil { // return nil, oerror.NewTrace(err) // } // if end != byte(0) { // return nil, fmt.Errorf("Final Byte read from collection result set was not 0, but was: %v", end) // } return docs, 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 }