Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,31 +508,18 @@ func (c *Client) ExecuteSQL(query string) (json.RawMessage, error) {
if err != nil {
return nil, err
}
if code == http.StatusNotFound || code == http.StatusMethodNotAllowed {
// Backward compatibility for older Roteiro deployments.
body, code, err = c.postJSON("/api/sql/query", map[string]string{"sql": query})
if err != nil {
return nil, err
}
}
if code != http.StatusOK {
return nil, fmt.Errorf("POST SQL query endpoint returned %d: %s", code, truncate(body, 500))
}
return json.RawMessage(body), nil
}

// ListSpatialTables calls GET /api/sql/tables (or fallback /api/query/sql/datasets).
// ListSpatialTables calls GET /api/query/sql/datasets.
func (c *Client) ListSpatialTables() (json.RawMessage, error) {
body, code, err := c.get("/api/sql/tables", nil)
body, code, err := c.get("/api/query/sql/datasets", nil)
if err != nil {
return nil, err
}
if code == http.StatusNotFound || code == http.StatusMethodNotAllowed {
body, code, err = c.get("/api/query/sql/datasets", nil)
if err != nil {
return nil, err
}
}
if code != http.StatusOK {
return nil, fmt.Errorf("GET SQL tables endpoint returned %d: %s", code, truncate(body, 500))
}
Expand Down
102 changes: 102 additions & 0 deletions mcp/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package mcp

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestListSpatialTablesPrefersCurrentDuckDBEndpoint(t *testing.T) {
tablesCalled := false
datasetsCalled := false

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/query/sql/datasets":
datasetsCalled = true
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`[{"name":"roads"}]`))
case "/api/sql/tables":
tablesCalled = true
http.Error(w, `{"error":"legacy route should not be called first"}`, http.StatusGone)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()

client := NewClient(srv.URL, "")
client.HTTPClient = srv.Client()

body, err := client.ListSpatialTables()
if err != nil {
t.Fatalf("ListSpatialTables error: %v", err)
}
if !datasetsCalled {
t.Fatal("expected current /api/query/sql/datasets route to be used")
}
if tablesCalled {
t.Fatal("legacy /api/sql/tables route should not be called when the current endpoint succeeds")
}
if string(body) != `[{"name":"roads"}]` {
t.Fatalf("unexpected body: %s", body)
}
}

func TestListSpatialTablesDoesNotFallbackToLegacyEndpoint(t *testing.T) {
tablesCalled := false

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/query/sql/datasets":
http.NotFound(w, r)
case "/api/sql/tables":
tablesCalled = true
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`[{"name":"roads"}]`))
default:
http.NotFound(w, r)
}
}))
defer srv.Close()

client := NewClient(srv.URL, "")
client.HTTPClient = srv.Client()

_, err := client.ListSpatialTables()
if err == nil {
t.Fatal("expected ListSpatialTables to return an error when the current endpoint is unavailable")
}
if tablesCalled {
t.Fatal("legacy /api/sql/tables route should not be called")
}
}

func TestExecuteSQLDoesNotFallbackToLegacyEndpoint(t *testing.T) {
legacyCalled := false

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/query/sql":
http.NotFound(w, r)
case "/api/sql/query":
legacyCalled = true
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"rows":[{"value":1}]}`))
default:
http.NotFound(w, r)
}
}))
defer srv.Close()

client := NewClient(srv.URL, "")
client.HTTPClient = srv.Client()

_, err := client.ExecuteSQL("SELECT 1")
if err == nil {
t.Fatal("expected ExecuteSQL to return an error when the current endpoint is unavailable")
}
if legacyCalled {
t.Fatal("legacy /api/sql/query route should not be called")
}
}
Loading