Beispiel #1
0
//
// Next is called to populate the next row of data into
// the provided slice. The provided slice will be the same
// size as the Columns() are wide.
//
// The dest slice may be populated only with
// a driver Value type, but excluding string.
// All string values must be converted to []byte.
//
// Next should return io.EOF when there are no more rows.
//
func (rows *ogonoriRows) Next(dest []driver.Value) error {
	ogl.Debugln("** ogonoriRows.Next")
	// 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
	if rows.pos >= len(rows.docs) {
		return io.EOF
	}
	// TODO: may need to do a type switch here -> what else can come in from a query in OrientDB
	//       besides an ODocument ??
	currdoc := rows.docs[rows.pos]
	if rows.fulldoc {
		dest[0] = currdoc

	} else {
		// was a property only query
		for i := range dest {
			// TODO: need to check field.Type and see if it is one that can map to Value
			//       what will I do for types that don't map to Value (e.g., EmbeddedRecord, EmbeddedMap) ??
			field := currdoc.GetField(rows.cols[i])
			if field != nil {
				dest[i] = field.Value
			}
		}
	}

	rows.pos++
	// TODO: this is where we need to return any errors that occur
	return nil
}
Beispiel #2
0
func doExec(dbc *obinary.DBClient, cmd string, args []driver.Value) (driver.Result, error) {
	strargs := valuesToStrings(args)

	retval, docs, err := obinary.SQLCommand(dbc, cmd, strargs...)
	ogl.Debugf("exec1: %T: %v\n", retval, retval)
	if err != nil {
		return ogonoriResult{-1, -1}, err
	}

	if docs == nil {
		ogl.Debugln("exec2")
		nrows, err := strconv.ParseInt(retval, 10, 64)
		if err != nil {
			ogl.Debugf("exec3: %T: %v\n", err, err)
			nrows = -1
		}
		return ogonoriResult{nrows, -1}, err
	}

	lastdoc := docs[len(docs)-1]
	// sepIdx := strings.Index(lastDoc.RID, ":")
	// if sepIdx < 0 {
	// 	return ogonoriResult{len64(docs), -1}, fmt.Errorf("RID of returned doc not of expected format: %v", lastDoc.RID)
	// }
	// lastId, err := strconv.ParseInt(lastDoc.RID[sepIdx+1:], 10, 64)
	// if err != nil {
	// 	return ogonoriResult{len64(docs), -1}, fmt.Errorf("Couldn't parse ID from doc RID: %v: %v", lastDoc.RID, err)
	// }
	return ogonoriResult{len64(docs), lastdoc.RID.ClusterPos}, err
}
Beispiel #3
0
func (c *ogonoriConn) Query(query string, args []driver.Value) (driver.Rows, error) {
	ogl.Debugln("** ogoConn.Query")

	if c.dbc == nil {
		return nil, oerror.ErrInvalidConn{Msg: "obinary.DBClient not initialized in ogonoriConn#Exec"}
	}
	return doQuery(c.dbc, query, args)
}
Beispiel #4
0
//
// Query executes a query that may return rows, such as a SELECT.
//
func (st *ogonoriStmt) Query(args []driver.Value) (driver.Rows, error) {
	ogl.Debugln("** ogonoriStmt.Query")
	if st.conn == nil || st.conn.dbc == nil {
		return nil, oerror.ErrInvalidConn{Msg: "obinary.DBClient not initialized in ogonoriStmt#Query"}
	}

	return doQuery(st.conn.dbc, st.query, args)
}
Beispiel #5
0
func (c *ogonoriConn) Close() error {
	ogl.Debugln("** ogoConn.Close")
	// Close() must be idempotent
	if c.dbc != nil {
		err := obinary.CloseDatabase(c.dbc)
		c.dbc = nil
		return err
	}
	return nil
}
Beispiel #6
0
//
// loadSchema loads record #0:1 for the current database, caching the
// SchemaVersion, GlobalProperties and Classes info in the current ODatabase
// object (dbc.currDB).
//
func loadSchema(dbc *DBClient, schemaRID oschema.ORID) error {
	docs, err := FetchRecordByRID(dbc, schemaRID, "*:-1 index:0") // fetchPlan used by the Java client
	if err != nil {
		return err
	}
	// TODO: this idea of returning multiple docs has to be wrong
	if len(docs) != 1 {
		return fmt.Errorf("Load Record %s should only return one record. Returned: %d", schemaRID, len(docs))
	}

	/* ---[ schemaVersion ]--- */
	dbc.currDB.SchemaVersion = docs[0].GetField("schemaVersion").Value.(int32)

	/* ---[ globalProperties ]--- */
	globalPropsFld := docs[0].GetField("globalProperties")

	var globalProperty oschema.OGlobalProperty
	for _, pfield := range globalPropsFld.Value.([]interface{}) {
		pdoc := pfield.(*oschema.ODocument)
		globalProperty = oschema.NewGlobalPropertyFromDocument(pdoc)
		dbc.currDB.GlobalProperties[int(globalProperty.Id)] = globalProperty
	}

	ogl.Debugln("=======================================")
	ogl.Debugln("=======================================")
	ogl.Debugf("dbc.currDB.SchemaVersion: %v\n", dbc.currDB.SchemaVersion)
	ogl.Debugf("len(dbc.currDB.GlobalProperties): %v\n", len(dbc.currDB.GlobalProperties))
	ogl.Debugf("dbc.currDB.GlobalProperties: %v\n", dbc.currDB.GlobalProperties)
	ogl.Debugln("=======================================")
	ogl.Debugln("=======================================")

	/* ---[ classes ]--- */
	var oclass *oschema.OClass
	classesFld := docs[0].GetField("classes")
	for _, cfield := range classesFld.Value.([]interface{}) {
		cdoc := cfield.(*oschema.ODocument)
		oclass = oschema.NewOClassFromDocument(cdoc)
		dbc.currDB.Classes[oclass.Name] = oclass
	}

	return nil
}
Beispiel #7
0
//
// Implements database/sql.Scanner interface
//
func (doc *ODocument) Scan(src interface{}) error {
	ogl.Debugln("** ODocument.Scan")

	locdoc := src.(*ODocument)
	*doc = *locdoc

	// switch src.(type) {
	// case *ODocument:
	// 	locdoc := src.(*ODocument)
	// 	*doc = *locdoc
	// default:
	// 	return errors.New("Say what???")
	// }
	return nil
}
Beispiel #8
0
//
// Open returns a new connection to the database.
// The dsn (driver-specific name) is a string in a driver-specific format.
// For ogonori, the dsn should be of the format:
//   uname@passw:ip-or-host:port/dbname
//   or
//   uname@passw:ip-or-host/dbname  (default port of 2424 is used)
//
func (d *OgonoriDriver) Open(dsn string) (driver.Conn, error) {
	ogl.Debugln("** OgonoriDriver#Open")

	uname, passw, host, port, dbname, err := parseDsn(dsn)
	clientOpts := obinary.ClientOptions{ServerHost: host, ServerPort: port}
	dbc, err := obinary.NewDBClient(clientOpts)
	if err != nil {
		return nil, err
	}

	// TODO: right now assumes DocumentDB type - pass in on the dsn??
	//       NOTE: I tried a graphDB with DocumentDB type and it worked, so why is it necesary at all?
	// TODO: this maybe shouldn't happen in this method -> might do it lazily in Query/Exec methods?
	err = obinary.OpenDatabase(dbc, dbname, constants.DocumentDB, uname, passw)
	if err != nil {
		return nil, err
	}

	return &ogonoriConn{dbc}, nil
}
Beispiel #9
0
//
// Implements database/sql/driver.Valuer interface
// TODO: haven't detected when this is called yet (probably when serializing ODocument for insertion into DB??)
//
func (doc *ODocument) Value() (driver.Value, error) {
	ogl.Debugln("** ODocument.Value")

	return []byte(`{"b": 2}`), nil // FIXME: bogus
}
Beispiel #10
0
func (c *ogonoriConn) Begin() (driver.Tx, error) {
	ogl.Debugln("** ogoConn.Begin")
	return nil, nil
}
Beispiel #11
0
func (c *ogonoriConn) Prepare(query string) (driver.Stmt, error) {
	ogl.Debugln("** ogoConn.Prepare")

	return &ogonoriStmt{conn: c, query: query}, nil
}
Beispiel #12
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
}
Beispiel #13
0
//
// Close closes the statement.
//
// As of Go 1.1, a Stmt will not be closed if it's in use by any queries.
//
func (st *ogonoriStmt) Close() error {
	ogl.Debugln("** ogonoriStmt.Close")
	// nothing to do here since there is no special statement handle in OrientDB
	// that is referenced by a client driver
	return nil
}
Beispiel #14
0
//
// NumInput returns the number of placeholder parameters.
//
func (st *ogonoriStmt) NumInput() int {
	ogl.Debugln("** ogonoriStmt.NumInput")
	return -1 // -1 means sql package will not "sanity check" arg counts
}
Beispiel #15
0
//
// Close closes the rows iterator.
//
func (rows *ogonoriRows) Close() error {
	ogl.Debugln("** ogonoriRows.Close")
	return nil
}