// Execute the statement(s) in the given request and returns a response. // On error, the returned integer is an HTTP error code. func (e *Executor) Execute(args driver.Request) (driver.Response, int, error) { defer func(start time.Time) { e.latency.RecordValue(time.Now().Sub(start).Nanoseconds()) }(time.Now()) var session Session if err := proto.Unmarshal(args.Session, &session); err != nil { return driver.Response{}, http.StatusBadRequest, err } return e.ExecuteStatements(args.GetUser(), session, args.Sql, args.Params) }
// Execute the statement(s) in the given request and return a response. // On error, the returned integer is an HTTP error code. func (e *Executor) Execute(args driver.Request) (driver.Response, int, error) { planMaker := plannerPool.Get().(*planner) defer plannerPool.Put(planMaker) *planMaker = planner{ user: args.GetUser(), evalCtx: parser.EvalContext{ NodeID: e.nodeID, ReCache: e.reCache, // Copy existing GetLocation closure. See plannerPool.New() for the // initial setting. GetLocation: planMaker.evalCtx.GetLocation, }, leaseMgr: e.leaseMgr, systemConfig: e.getSystemConfig(), } // Pick up current session state. if err := proto.Unmarshal(args.Session, &planMaker.session); err != nil { return args.CreateReply(), http.StatusBadRequest, err } // Resume a pending transaction if present. if planMaker.session.Txn != nil { txn := client.NewTxn(e.db) txn.Proto = planMaker.session.Txn.Txn if planMaker.session.MutatesSystemDB { txn.SetSystemDBTrigger() } planMaker.setTxn(txn, planMaker.session.Txn.Timestamp.GoTime()) } // Send the Request for SQL execution and set the application-level error // for each result in the reply. planMaker.params = parameters(args.Params) reply := e.execStmts(args.Sql, planMaker) // Send back the session state even if there were application-level errors. // Add transaction to session state. if planMaker.txn != nil { // TODO(pmattis): Need to record the leases used by a transaction within // the transaction state and restore it when the transaction is restored. planMaker.releaseLeases(e.db) planMaker.session.Txn = &Session_Transaction{Txn: planMaker.txn.Proto, Timestamp: driver.Timestamp(planMaker.evalCtx.TxnTimestamp.Time)} planMaker.session.MutatesSystemDB = planMaker.txn.SystemDBTrigger() } else { planMaker.session.Txn = nil planMaker.session.MutatesSystemDB = false } bytes, err := proto.Marshal(&planMaker.session) if err != nil { return args.CreateReply(), http.StatusInternalServerError, err } reply.Session = bytes return reply, 0, nil }
// Execute the statement(s) in the given request and return a response. // On error, the returned integer is an HTTP error code. func (e *Executor) Execute(args driver.Request) (driver.Response, int, error) { planMaker := &planner{ user: args.GetUser(), evalCtx: parser.EvalContext{ NodeID: e.nodeID, ReCache: e.reCache, }, leaseMgr: e.leaseMgr, systemConfig: e.getSystemConfig(), } // Pick up current session state. if err := proto.Unmarshal(args.Session, &planMaker.session); err != nil { return args.CreateReply(), http.StatusBadRequest, err } // Resume a pending transaction if present. if planMaker.session.Txn != nil { txn := client.NewTxn(e.db) txn.Proto = planMaker.session.Txn.Txn if planMaker.session.MutatesSystemDB { txn.SetSystemDBTrigger() } planMaker.setTxn(txn, planMaker.session.Txn.Timestamp.GoTime()) } planMaker.evalCtx.GetLocation = planMaker.session.getLocation // Send the Request for SQL execution and set the application-level error // for each result in the reply. reply := e.execStmts(args.Sql, parameters(args.Params), planMaker) // Send back the session state even if there were application-level errors. // Add transaction to session state. if planMaker.txn != nil { planMaker.session.Txn = &Session_Transaction{Txn: planMaker.txn.Proto, Timestamp: driver.Timestamp(planMaker.evalCtx.TxnTimestamp.Time)} planMaker.session.MutatesSystemDB = planMaker.txn.SystemDBTrigger() } else { planMaker.session.Txn = nil planMaker.session.MutatesSystemDB = false } bytes, err := proto.Marshal(&planMaker.session) if err != nil { return args.CreateReply(), http.StatusInternalServerError, err } reply.Session = bytes return reply, 0, nil }
func (s server) execute(args driver.Request) (driver.Response, int, error) { // Pick up current session state. planMaker := planner{user: args.GetUser()} if err := gogoproto.Unmarshal(args.Session, &planMaker.session); err != nil { return args.CreateReply(), http.StatusBadRequest, err } // Open a pending transaction if needed. if planMaker.session.Txn != nil { txn := client.NewTxn(s.db) txn.Proto = *planMaker.session.Txn if planMaker.session.MutatesSystemDB { txn.SetSystemDBTrigger() } planMaker.txn = txn } // Send the Request for SQL execution and set the application-level error // for each result in the reply. reply := s.execStmts(args.Sql, args.GetParameters(), &planMaker) // Send back the session state even if there were application-level errors. // Add transaction to session state. if planMaker.txn != nil { planMaker.session.Txn = &planMaker.txn.Proto planMaker.session.MutatesSystemDB = planMaker.txn.SystemDBTrigger() } else { planMaker.session.Txn = nil planMaker.session.MutatesSystemDB = false } bytes, err := gogoproto.Marshal(&planMaker.session) if err != nil { return args.CreateReply(), http.StatusInternalServerError, err } reply.Session = bytes return reply, 0, nil }
// ServeHTTP serves the SQL API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according to the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() method := r.URL.Path if !strings.HasPrefix(method, driver.Endpoint) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Check TLS settings. authenticationHook, err := security.AuthenticationHook(s.context.Insecure, r.TLS) if err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } method = strings.TrimPrefix(method, driver.Endpoint) if method != driver.Execute.String() { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var args driver.Request if err := util.UnmarshalRequest(r, reqBody, &args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Check request user against client certificate user. if err := authenticationHook(&args, true /*public*/); err != nil { http.Error(w, err.Error(), http.StatusUnauthorized) return } // Pick up current session state. planMaker := planner{user: args.GetUser()} if err := gogoproto.Unmarshal(args.Session, &planMaker.session); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Open a pending transaction if needed. if planMaker.session.Txn != nil { txn := client.NewTxn(*s.db) txn.Proto = *planMaker.session.Txn planMaker.txn = txn } // Send the Request for SQL execution and set the application-level error // for each result in the reply. reply := s.exec(args, &planMaker) // Send back the session state even if there were application-level errors. // Add transaction to session state. if planMaker.txn != nil { planMaker.session.Txn = &planMaker.txn.Proto } else { planMaker.session.Txn = nil } bytes, err := gogoproto.Marshal(&planMaker.session) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } reply.Session = bytes // Marshal the response. body, contentType, err := util.MarshalResponse(r, &reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set(util.ContentTypeHeader, contentType) w.Write(body) }
// exec executes the request. Any error encountered is returned; it is // the caller's responsibility to update the response. func (s *Server) exec(req driver.Request) (driver.Response, error) { var resp driver.Response // Pick up current session state. // The request user is validated in ServeHTTP. Even in insecure mode, // it is guaranteed not to be empty. planner := planner{user: req.GetUser()} if req.Session != nil { // TODO(tschottdorf) will have to validate the Session information (for // instance, whether access to the stored database is permitted). if err := gogoproto.Unmarshal(req.Session, &planner.session); err != nil { return resp, err } } stmts, err := parser.Parse(req.Sql) if err != nil { return resp, err } for _, stmt := range stmts { // Bind all the placeholder variables in the stmt to actual values. if err := parser.FillArgs(stmt, parameters(req.Params)); err != nil { return resp, err } var plan planNode if err := s.db.Txn(func(txn *client.Txn) error { planner.txn = txn plan, err = planner.makePlan(stmt) planner.txn = nil return err }); err != nil { return resp, err } result := driver.Result{ Columns: plan.Columns(), } for plan.Next() { values := plan.Values() row := driver.Result_Row{} row.Values = make([]driver.Datum, 0, len(values)) for _, val := range values { if val == parser.DNull { row.Values = append(row.Values, driver.Datum{}) } else { switch vt := val.(type) { case parser.DBool: row.Values = append(row.Values, driver.Datum{BoolVal: (*bool)(&vt)}) case parser.DInt: row.Values = append(row.Values, driver.Datum{IntVal: (*int64)(&vt)}) case parser.DFloat: row.Values = append(row.Values, driver.Datum{FloatVal: (*float64)(&vt)}) case parser.DString: row.Values = append(row.Values, driver.Datum{StringVal: (*string)(&vt)}) default: return resp, util.Errorf("unsupported datum: %T", val) } } } result.Rows = append(result.Rows, row) } if err := plan.Err(); err != nil { return resp, err } resp.Results = append(resp.Results, result) } // Update session state. resp.Session, err = gogoproto.Marshal(&planner.session) return resp, err }