// // Large LinkBags (aka RidBags) are stored on the server. To look up their // size requires a call to the database. The size is returned. Note that the // Size field of the linkBag is NOT updated. That is left for the caller to // decide whether to do. // func FetchSizeOfRemoteLinkBag(dbc *DBClient, linkBag *oschema.OLinkBag) (int, error) { dbc.buf.Reset() err := writeCommandAndSessionId(dbc, REQUEST_RIDBAG_GET_SIZE) if err != nil { return 0, oerror.NewTrace(err) } err = writeLinkBagCollectionPointer(dbc.buf, linkBag) if err != nil { return 0, oerror.NewTrace(err) } // changes => TODO: right now not supporting any change -> just writing empty changes err = rw.WriteBytes(dbc.buf, []byte{0, 0, 0, 0}) if err != nil { return 0, oerror.NewTrace(err) } // send to the OrientDB server _, err = dbc.conx.Write(dbc.buf.Bytes()) if err != nil { return 0, oerror.NewTrace(err) } /* ---[ Read Response ]--- */ err = readStatusCodeAndSessionId(dbc) if err != nil { return 0, oerror.NewTrace(err) } size, err := rw.ReadInt(dbc.conx) if err != nil { return 0, oerror.NewTrace(err) } return int(size), 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 }
// // 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 }
// // 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 }
// // FetchEntriesOfRemoteLinkBag fills in the links of an OLinkBag that is remote // (tree-based) rather than embedded. This function will fill in the links // of the passed in OLinkBag, rather than returning the new links. The Links // will have RIDs only, not full Records (ODocuments). If you then want the // Records filled in, call the ResolveLinks function. // func FetchEntriesOfRemoteLinkBag(dbc *DBClient, linkBag *oschema.OLinkBag, inclusive bool) error { var ( firstLink *oschema.OLink linkSerde binserde.OBinaryTypeSerializer err error ) firstLink, err = FetchFirstKeyOfRemoteLinkBag(dbc, linkBag) if err != nil { return oerror.NewTrace(err) } dbc.buf.Reset() err = writeCommandAndSessionId(dbc, REQUEST_SBTREE_BONSAI_GET_ENTRIES_MAJOR) if err != nil { return oerror.NewTrace(err) } err = writeLinkBagCollectionPointer(dbc.buf, linkBag) if err != nil { return oerror.NewTrace(err) } typeByte := byte(9) linkSerde = binserde.TypeSerializers[typeByte] // the OLinkSerializer linkBytes, err := linkSerde.Serialize(firstLink) if err != nil { return oerror.NewTrace(err) } err = rw.WriteBytes(dbc.buf, linkBytes) if err != nil { return oerror.NewTrace(err) } err = rw.WriteBool(dbc.buf, inclusive) if err != nil { return oerror.NewTrace(err) } // copied from Java client OSBTreeBonsaiRemote#fetchEntriesMajor if dbc.binaryProtocolVersion >= 21 { err = rw.WriteInt(dbc.buf, 128) } // 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) } linkEntryBytes, err := rw.ReadBytes(dbc.conx) if err != nil { return oerror.NewTrace(err) } // all the rest of the response from the server in in this byte slice so // we can reset the dbc.buf and reuse it to deserialize the serialized links dbc.buf.Reset() // ignoring error since doc says this method panics rather than return // non-nil error n, _ := dbc.buf.Write(linkEntryBytes) if n != len(linkEntryBytes) { return fmt.Errorf("Unexpected error when writing bytes to bytes.Buffer") } nrecs, err := rw.ReadInt(dbc.buf) if err != nil { return oerror.NewTrace(err) } var result interface{} nr := int(nrecs) // loop over all the serialized links for i := 0; i < nr; i++ { result, err = linkSerde.Deserialize(dbc.buf) if err != nil { return oerror.NewTrace(err) } linkBag.AddLink(result.(*oschema.OLink)) // FIXME: for some reason the server returns a serialized link // followed by an integer (so far always a 1 in my expts). // Not sure what to do with this int, so ignore for now intval, err := rw.ReadInt(dbc.buf) if err != nil { return oerror.NewTrace(err) } if intval != int32(1) { ogl.Warnf("DEBUG: Found a use case where the val pair of a link was not 1: %d\n", intval) } } return nil }