api/schema_handler.go (145 lines of code) (raw):
// Copyright (c) 2017-2018 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"encoding/json"
memCom "github.com/uber/aresdb/memstore/common"
"github.com/uber/aresdb/metastore"
"net/http"
apiCom "github.com/uber/aresdb/api/common"
metaCom "github.com/uber/aresdb/metastore/common"
"github.com/uber/aresdb/utils"
"github.com/gorilla/mux"
)
// SchemaHandler handles schema http requests.
type SchemaHandler struct {
// all write requests will go to metaStore.
metaStore metaCom.MetaStore
schemaReader memCom.TableSchemaReader
}
// NewSchemaHandler will create a new SchemaHandler with memStore and metaStore.
func NewSchemaHandler(metaStore metaCom.MetaStore, schemaReader memCom.TableSchemaReader) *SchemaHandler {
return &SchemaHandler{
metaStore: metaStore,
schemaReader: schemaReader,
}
}
// Register registers http handlers.
func (handler *SchemaHandler) Register(router *mux.Router, wrappers ...utils.HTTPHandlerWrapper) {
router.HandleFunc("/tables", utils.ApplyHTTPWrappers(handler.ListTables, wrappers...)).Methods(http.MethodGet)
router.HandleFunc("/tables", utils.ApplyHTTPWrappers(handler.AddTable, wrappers...)).Methods(http.MethodPost)
router.HandleFunc("/tables/{table}", utils.ApplyHTTPWrappers(handler.GetTable, wrappers...)).Methods(http.MethodGet)
router.HandleFunc("/tables/{table}", utils.ApplyHTTPWrappers(handler.DeleteTable, wrappers...)).Methods(http.MethodDelete)
router.HandleFunc("/tables/{table}", utils.ApplyHTTPWrappers(handler.UpdateTableConfig, wrappers...)).Methods(http.MethodPut)
router.HandleFunc("/tables/{table}/columns", utils.ApplyHTTPWrappers(handler.AddColumn, wrappers...)).Methods(http.MethodPost)
router.HandleFunc("/tables/{table}/columns/{column}", utils.ApplyHTTPWrappers(handler.UpdateColumn, wrappers...)).Methods(http.MethodPut)
router.HandleFunc("/tables/{table}/columns/{column}", utils.ApplyHTTPWrappers(handler.DeleteColumn, wrappers...)).Methods(http.MethodDelete)
}
// RegisterForDebug register handlers for debug port
func (handler *SchemaHandler) RegisterForDebug(router *mux.Router, wrappers ...utils.HTTPHandlerWrapper) {
router.HandleFunc("/tables", utils.ApplyHTTPWrappers(handler.ListTables, wrappers...)).Methods(http.MethodGet)
router.HandleFunc("/tables/{table}", utils.ApplyHTTPWrappers(handler.GetTable, wrappers...)).Methods(http.MethodGet)
}
// ListTables swagger:route GET /schema/tables listTables
// List all table schemas
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Responses:
// default: errorResponse
// 200: stringArrayResponse
func (handler *SchemaHandler) ListTables(w *utils.ResponseWriter, r *http.Request) {
response := apiCom.NewStringArrayResponse()
allTables := handler.schemaReader.GetSchemas()
for tableName := range allTables {
response.Body = append(response.Body, tableName)
}
w.WriteObject(response.Body)
}
// GetTable swagger:route GET /schema/tables/{table} getTable
// get the table schema for specific table name
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Responses:
// default: errorResponse
// 200: getTableResponse
func (handler *SchemaHandler) GetTable(w *utils.ResponseWriter, r *http.Request) {
var getTableRequest GetTableRequest
var getTableResponse GetTableResponse
err := apiCom.ReadRequest(r, &getTableRequest)
if err != nil {
w.WriteError(err)
return
}
schema, err := handler.schemaReader.GetSchema(getTableRequest.TableName)
if err != nil {
w.WriteErrorWithCode(http.StatusNotFound, ErrTableDoesNotExist)
return
}
getTableResponse.JSONBuffer, err = json.Marshal(schema.Schema)
w.WriteJSONBytes(getTableResponse.JSONBuffer, err)
}
// AddTable swagger:route POST /schema/tables addTable
// add table to table collections
//
// Consumes:
// - application/json
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) AddTable(w *utils.ResponseWriter, r *http.Request) {
var addTableRequest AddTableRequest
// add default table configs first
addTableRequest.Body.Config = metastore.DefaultTableConfig
err := apiCom.ReadRequest(r, &addTableRequest)
if err != nil {
w.WriteErrorWithCode(http.StatusBadRequest, err)
return
}
newTable := addTableRequest.Body
err = handler.metaStore.CreateTable(&newTable)
if err != nil {
w.WriteError(err)
return
}
w.WriteObject(nil)
}
// UpdateTableConfig swagger:route PUT /schema/tables/{table} updateTableConfig
// update config of the specified table
//
// Consumes:
// - application/json
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) UpdateTableConfig(w *utils.ResponseWriter, r *http.Request) {
var request UpdateTableConfigRequest
err := apiCom.ReadRequest(r, &request)
if err != nil {
w.WriteError(err)
return
}
err = handler.metaStore.UpdateTableConfig(request.TableName, request.Body)
if err != nil {
w.WriteError(err)
return
}
w.WriteObject(nil)
}
// DeleteTable swagger:route DELETE /schema/tables/{table} deleteTable
// delete table from metaStore
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) DeleteTable(w *utils.ResponseWriter, r *http.Request) {
var deleteTableRequest DeleteTableRequest
err := apiCom.ReadRequest(r, &deleteTableRequest)
if err != nil {
w.WriteError(err)
return
}
err = handler.metaStore.DeleteTable(deleteTableRequest.TableName)
if err != nil {
// TODO: need mapping from metaStore error to api error
/// for metaStore error might also be user error
w.WriteError(err)
return
}
w.WriteObject(nil)
}
// AddColumn swagger:route POST /schema/tables/{table}/columns addColumn
// add a single column to existing table
//
// Consumes:
// - application/json
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) AddColumn(w *utils.ResponseWriter, r *http.Request) {
var addColumnRequest AddColumnRequest
err := apiCom.ReadRequest(r, &addColumnRequest)
if err != nil {
w.WriteErrorWithCode(http.StatusBadRequest, err)
return
}
err = handler.metaStore.AddColumn(addColumnRequest.TableName, addColumnRequest.Body.Column, addColumnRequest.Body.AddToArchivingSortOrder)
// TODO: validate column
// might better do in metaStore and here needs to return either user error or server error
if err != nil {
// TODO: need mapping from metaStore error to api error
/// for metaStore error might also be user error
w.WriteError(err)
return
}
w.WriteObject(nil)
}
// UpdateColumn swagger:route PUT /schema/tables/{table}/columns/{column} updateColumn
// update specified column
//
// Consumes:
// - application/json
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) UpdateColumn(w *utils.ResponseWriter, r *http.Request) {
var updateColumnRequest UpdateColumnRequest
err := apiCom.ReadRequest(r, &updateColumnRequest)
if err != nil {
w.WriteError(err)
return
}
if err = handler.metaStore.UpdateColumn(updateColumnRequest.TableName,
updateColumnRequest.ColumnName, updateColumnRequest.Body); err != nil {
// TODO: need mapping from metaStore error to api error
// for metaStore error might also be user error
w.WriteError(err)
return
}
w.WriteObject(nil)
}
// DeleteColumn swagger:route DELETE /schema/tables/{table}/columns/{column} deleteColumn
// delete columns from existing table
//
// Responses:
// default: errorResponse
// 200: noContentResponse
func (handler *SchemaHandler) DeleteColumn(w *utils.ResponseWriter, r *http.Request) {
var deleteColumnRequest DeleteColumnRequest
err := apiCom.ReadRequest(r, &deleteColumnRequest)
if err != nil {
w.WriteError(err)
return
}
err = handler.metaStore.DeleteColumn(deleteColumnRequest.TableName, deleteColumnRequest.ColumnName)
// TODO: validate whether table exists and specified columns does not belong to primary key or time column
// might be better for metaStore to do this and return specified error type
if err != nil {
// TODO: need mapping from metaStore error to api error
// for metaStore error might also be user error
w.WriteError(err)
return
}
w.WriteObject(nil)
}