add schema endpoint

This commit adds a new HTTP handler to return a list of all databases
and frames in the index:

	GET /schema

This returns JSON in the following format:

	{
	  "dbs":[
	    {
	      "name":"d0",
	      "frames":[
	        {"name":"f0"},
	        {"name":"f1"}
	      ]
	    }
	  ]
	}

Fixes #61
This commit is contained in:
Ben Johnson 2016-03-22 11:22:55 -06:00
parent 81a885e557
commit 06e048def6
5 changed files with 107 additions and 0 deletions

21
db.go
View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"sync"
)
@ -130,6 +131,20 @@ func (db *DB) Frame(name string) *Frame {
func (db *DB) frame(name string) *Frame { return db.frames[name] }
// Frames returns a list of all frames in the database.
func (db *DB) Frames() []*Frame {
db.mu.Lock()
defer db.mu.Unlock()
a := make([]*Frame, 0, len(db.frames))
for _, f := range db.frames {
a = append(a, f)
}
sort.Sort(frameSlice(a))
return a
}
// CreateFrameIfNotExists returns a frame in the database by name.
func (db *DB) CreateFrameIfNotExists(name string) (*Frame, error) {
db.mu.Lock()
@ -152,3 +167,9 @@ func (db *DB) createFrameIfNotExists(name string) (*Frame, error) {
return f, nil
}
type dbSlice []*DB
func (p dbSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p dbSlice) Len() int { return len(p) }
func (p dbSlice) Less(i, j int) bool { return p[i].Name() < p[j].Name() }

View file

@ -186,3 +186,9 @@ func (f *Frame) createFragmentIfNotExists(slice uint64) (*Fragment, error) {
return frag, nil
}
type frameSlice []*Frame
func (p frameSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p frameSlice) Len() int { return len(p) }
func (p frameSlice) Less(i, j int) bool { return p[i].Name() < p[j].Name() }

View file

@ -52,6 +52,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t := time.Now()
switch r.URL.Path {
case "/schema":
switch r.Method {
case "GET":
h.handleGetSchema(w, r)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
case "/query":
switch r.Method {
case "POST":
@ -108,6 +115,39 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.logger().Printf("%s %s %.03fs", r.Method, r.URL.String(), time.Since(t).Seconds())
}
// handleGetSchema handles GET /schema requests.
func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) {
// Construct schema based on databases and frames.
var resp getSchemaResponse
for _, db := range h.Index.DBs() {
respDB := getSchemaDB{Name: db.Name()}
for _, frame := range db.Frames() {
respDB.Frames = append(respDB.Frames, getSchemaFrame{
Name: frame.Name(),
})
}
resp.DBs = append(resp.DBs, respDB)
}
// Write JSON to response.
if err := json.NewEncoder(w).Encode(resp); err != nil {
h.logger().Printf("write schema response error: %s", err)
}
}
type getSchemaResponse struct {
DBs []getSchemaDB `json:"dbs"`
}
type getSchemaDB struct {
Name string `json:"name"`
Frames []getSchemaFrame `json:"frames"`
}
type getSchemaFrame struct {
Name string `json:"name"`
}
// handlePostQuery handles /query requests.
func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) {
// Parse incoming request.

View file

@ -26,6 +26,31 @@ func TestHandler_NotFound(t *testing.T) {
}
}
// Ensure the handler can return the schema.
func TestHandler_Schema(t *testing.T) {
idx := MustOpenIndex()
defer idx.Close()
if _, err := idx.CreateFrameIfNotExists("d0", "f1"); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateFrameIfNotExists("d1", "f0"); err != nil {
t.Fatal(err)
}
if _, err := idx.CreateFrameIfNotExists("d0", "f0"); err != nil {
t.Fatal(err)
}
h := NewHandler()
h.Index = idx.Index
w := httptest.NewRecorder()
h.ServeHTTP(w, MustNewHTTPRequest("GET", "/schema", nil))
if w.Code != http.StatusOK {
t.Fatalf("unexpected status code: %d", w.Code)
} else if body := w.Body.String(); body != `{"dbs":[{"name":"d0","frames":[{"name":"f0"},{"name":"f1"}]},{"name":"d1","frames":[{"name":"f0"}]}]}`+"\n" {
t.Fatalf("unexpected body: %s", body)
}
}
// Ensure the handler can accept URL arguments.
func TestHandler_Query_Args_URL(t *testing.T) {
h := NewHandler()

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"sync"
)
@ -95,6 +96,20 @@ func (i *Index) DB(name string) *DB {
func (i *Index) db(name string) *DB { return i.dbs[name] }
// DBs returns a list of all databases in the index.
func (i *Index) DBs() []*DB {
i.mu.Lock()
defer i.mu.Unlock()
a := make([]*DB, 0, len(i.dbs))
for _, db := range i.dbs {
a = append(a, db)
}
sort.Sort(dbSlice(a))
return a
}
// CreateDBIfNotExists returns a database by name.
// The database is created if it does not already exist.
func (i *Index) CreateDBIfNotExists(name string) (*DB, error) {