Example #1
0
//
// 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
}
Example #2
0
//
// 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
}
Example #3
0
//
// 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
}
Example #4
0
//
// 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
}
Example #5
0
//
// 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
}