// // AddCluster adds 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. // The clusterID is returned if the command is successful. // func AddCluster(dbc *DBClient, clusterName string) (clusterID int16, err error) { dbc.buf.Reset() err = writeCommandAndSessionId(dbc, REQUEST_DATACLUSTER_ADD) if err != nil { return int16(0), oerror.NewTrace(err) } cname := strings.ToLower(clusterName) err = rw.WriteString(dbc.buf, cname) if err != nil { return int16(0), oerror.NewTrace(err) } err = rw.WriteShort(dbc.buf, -1) // -1 means generate new cluster id if err != nil { return int16(0), oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return int16(0), oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return int16(0), oerror.NewTrace(err) } clusterID, err = rw.ReadShort(dbc.conx) if err != nil { return clusterID, oerror.NewTrace(err) } dbc.currDB.Clusters = append(dbc.currDB.Clusters, OCluster{cname, clusterID}) return clusterID, 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 }
// // 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 }
// // 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 }
// // 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 }
// // 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 }