mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-07 00:55:55 +00:00
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:
parent
81a885e557
commit
06e048def6
5 changed files with 107 additions and 0 deletions
21
db.go
21
db.go
|
|
@ -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() }
|
||||
|
|
|
|||
6
frame.go
6
frame.go
|
|
@ -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() }
|
||||
|
|
|
|||
40
handler.go
40
handler.go
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
15
index.go
15
index.go
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue