// // 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 }
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 }
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) }
// // 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) }
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 }
// // 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 }
// // 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 }
// // 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 }
// // 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 }
func (c *ogonoriConn) Begin() (driver.Tx, error) { ogl.Debugln("** ogoConn.Begin") return nil, nil }
func (c *ogonoriConn) Prepare(query string) (driver.Stmt, error) { ogl.Debugln("** ogoConn.Prepare") return &ogonoriStmt{conn: c, query: query}, 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 }
// // 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 }
// // 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 }
// // Close closes the rows iterator. // func (rows *ogonoriRows) Close() error { ogl.Debugln("** ogonoriRows.Close") return nil }