Skip to content

Commit

Permalink
Merge pull request #625 from tablelandnetwork/bcalza/queryparams
Browse files Browse the repository at this point in the history
adds support for query parameters
  • Loading branch information
brunocalza authored May 30, 2024
2 parents 9fe8b45 + 640d45f commit 179520f
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 78 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ gen-api-v1:
&& mv go/* . \
&& rm -rf go main.go Dockerfile README.md api .swagger-codegen .swagger-codegen-ignore *.yaml \
&& sed -i 's/\*OneOfTableAttributesValue/interface{}/' model_table_attributes.go \
&& sed -i 's/\OneOfQueryParamsItems/interface{}/' model_query.go \
"
sudo chown -R ${USER} ${APIV1}
.PHONY: gen-api-v1
5 changes: 4 additions & 1 deletion cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,12 @@ func createAPIServer(
supportedChainIDs = append(supportedChainIDs, chainID)
}

resolver := parsing.NewReadStatementResolver(sm)

g, err := gateway.NewGateway(
parser,
gatewayimpl.NewGatewayStore(db, parsing.NewReadStatementResolver(sm)),
gatewayimpl.NewGatewayStore(db),
resolver,
gatewayConfig.ExternalURIPrefix,
gatewayConfig.MetadataRendererURI,
gatewayConfig.AnimationRendererURI)
Expand Down
1 change: 1 addition & 0 deletions cmd/healthbot/counterprobe/counterprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ func (cp *CounterProbe) getCurrentCounterValue(ctx context.Context) (int64, erro
if err := cp.client.Read(
ctx,
fmt.Sprintf("select counter from %s", cp.tableName),
[]string{},
&counter,
clientV1.ReadExtract(),
clientV1.ReadUnwrap()); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/sethvargo/go-limiter v0.7.2
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.2
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d
github.com/textileio/cli v1.0.2
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0
go.opentelemetry.io/otel v1.14.0
Expand Down
14 changes: 4 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1295,16 +1295,10 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tablelandnetwork/sqlparser v0.0.0-20230517143402-3ab9022be0df h1:SUG49BUSuO9S6U3RjAV8a0NIDRByHj3kSt8/QR75rtI=
github.com/tablelandnetwork/sqlparser v0.0.0-20230517143402-3ab9022be0df/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20230518143735-838d223866f6 h1:f8TRklEZmT4fJd7wE+oktjf4wQndJ5BqkwXOpuHrYBU=
github.com/tablelandnetwork/sqlparser v0.0.0-20230518143735-838d223866f6/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20230602174101-e27f9a12da58 h1:vEdQ9rJs5vwJfrwg4HIpVVrXRVJqEzyA2MM9oUehnhc=
github.com/tablelandnetwork/sqlparser v0.0.0-20230602174101-e27f9a12da58/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20230605150512-1cb695cd5627 h1:ctGSX+KFDNvMYX25ooerc3saOcfPF6i56oAe+mo1l20=
github.com/tablelandnetwork/sqlparser v0.0.0-20230605150512-1cb695cd5627/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6 h1:goeC/kQXlqRod2rPwqrVxvEgF3I5S3f0fa538k+Evbw=
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20240523182602-af3edf08e3db h1:4/eOYTPLww9YzdsteeGFhcT8dwIaCm3FIOwrxs6ppQM=
github.com/tablelandnetwork/sqlparser v0.0.0-20240523182602-af3edf08e3db/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d h1:Xhc6wudmyItX8Pvr+Z2SCzOmrf/zda5sX1jCgUySNRg=
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/textileio/cli v1.0.2 h1:qSp/x4d/9SZ93TxhgZnE5okRKqzqHqrzAwKAPjuPw50=
github.com/textileio/cli v1.0.2/go.mod h1:vTlCvvVyOmXXLwddCcBg3PDavfUsCkRBZoyr6Nu1lkc=
Expand Down
17 changes: 13 additions & 4 deletions internal/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

"github.com/ethereum/go-ethereum/common"
logger "github.com/rs/zerolog/log"
"github.com/tablelandnetwork/sqlparser"
"github.com/textileio/go-tableland/internal/tableland"
"github.com/textileio/go-tableland/pkg/parsing"
"github.com/textileio/go-tableland/pkg/tables"
Expand All @@ -33,14 +34,14 @@ const (

// Gateway defines the gateway operations.
type Gateway interface {
RunReadQuery(ctx context.Context, stmt string) (*TableData, error)
RunReadQuery(ctx context.Context, stmt string, params []string) (*TableData, error)
GetTableMetadata(context.Context, tableland.ChainID, tables.TableID) (TableMetadata, error)
GetReceiptByTransactionHash(context.Context, tableland.ChainID, common.Hash) (Receipt, bool, error)
}

// GatewayStore is the storage layer of the Gateway.
type GatewayStore interface {
Read(context.Context, parsing.ReadStmt) (*TableData, error)
Read(context.Context, parsing.ReadStmt, sqlparser.ReadStatementResolver) (*TableData, error)
GetTable(context.Context, tableland.ChainID, tables.TableID) (Table, error)
GetSchemaByTableName(context.Context, string) (TableSchema, error)
GetReceipt(context.Context, tableland.ChainID, string) (Receipt, bool, error)
Expand All @@ -53,6 +54,8 @@ type GatewayService struct {
metadataRendererURI string
animationRendererURI string
store GatewayStore

resolver *parsing.ReadStatementResolver
}

var _ (Gateway) = (*GatewayService)(nil)
Expand All @@ -61,6 +64,7 @@ var _ (Gateway) = (*GatewayService)(nil)
func NewGateway(
parser parsing.SQLValidator,
store GatewayStore,
resolver *parsing.ReadStatementResolver,
extURLPrefix string,
metadataRendererURI string,
animationRendererURI string,
Expand Down Expand Up @@ -89,6 +93,7 @@ func NewGateway(
metadataRendererURI: metadataRendererURI,
animationRendererURI: animationRendererURI,
store: store,
resolver: resolver,
}, nil
}

Expand Down Expand Up @@ -161,13 +166,17 @@ func (g *GatewayService) GetReceiptByTransactionHash(
}

// RunReadQuery allows the user to run SQL.
func (g *GatewayService) RunReadQuery(ctx context.Context, statement string) (*TableData, error) {
func (g *GatewayService) RunReadQuery(ctx context.Context, statement string, params []string) (*TableData, error) {
readStmt, err := g.parser.ValidateReadQuery(statement)
if err != nil {
return nil, fmt.Errorf("validating read query: %s", err)
}

queryResult, err := g.store.Read(ctx, readStmt)
if err := g.resolver.PrepareParams(params); err != nil {
return nil, fmt.Errorf("prepare params: %s", err)
}

queryResult, err := g.store.Read(ctx, readStmt, g.resolver)
if err != nil {
return nil, fmt.Errorf("running read statement: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/gateway/gateway_instrumented.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ func (g *InstrumentedGateway) GetTableMetadata(
}

// RunReadQuery allows the user to run SQL.
func (g *InstrumentedGateway) RunReadQuery(ctx context.Context, statement string) (*TableData, error) {
func (g *InstrumentedGateway) RunReadQuery(ctx context.Context, statement string, params []string) (*TableData, error) {
start := time.Now()
data, err := g.gateway.RunReadQuery(ctx, statement)
data, err := g.gateway.RunReadQuery(ctx, statement, params)
latency := time.Since(start).Milliseconds()

attributes := append([]attribute.KeyValue{
Expand Down
14 changes: 7 additions & 7 deletions internal/gateway/impl/gateway_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ import (

// GatewayStore is the storage layer of the gateway.
type GatewayStore struct {
db *database.SQLiteDB
resolver sqlparser.ReadStatementResolver
db *database.SQLiteDB
}

// NewGatewayStore creates a new GatewayStore.
func NewGatewayStore(db *database.SQLiteDB, resolver sqlparser.ReadStatementResolver) *GatewayStore {
func NewGatewayStore(db *database.SQLiteDB) *GatewayStore {
return &GatewayStore{
db: db,
resolver: resolver,
db: db,
}
}

// Read executes a parsed read statement.
func (s *GatewayStore) Read(ctx context.Context, stmt parsing.ReadStmt) (*gateway.TableData, error) {
query, err := stmt.GetQuery(s.resolver)
func (s *GatewayStore) Read(
ctx context.Context, stmt parsing.ReadStmt, resolver sqlparser.ReadStatementResolver,
) (*gateway.TableData, error) {
query, err := stmt.GetQuery(resolver)
if err != nil {
return nil, fmt.Errorf("get query: %s", err)
}
Expand Down
28 changes: 16 additions & 12 deletions internal/gateway/impl/gateway_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,25 @@ func TestGatewayInitialization(t *testing.T) {
t.Run("invalid external uri", func(t *testing.T) {
t.Parallel()

_, err := gateway.NewGateway(nil, nil, "invalid uri", "", "")
_, err := gateway.NewGateway(nil, nil, nil, "invalid uri", "", "")
require.Error(t, err)
require.ErrorContains(t, err, "invalid external url prefix")
})

t.Run("invalid metadata uri", func(t *testing.T) {
t.Parallel()

_, err := gateway.NewGateway(nil, nil, "https://tableland.network", "invalid uri", "")
_, err := gateway.NewGateway(nil, nil, nil, "https://tableland.network", "invalid uri", "")
require.Error(t, err)
require.ErrorContains(t, err, "metadata renderer uri could not be parsed")
})

t.Run("invalid animation uri", func(t *testing.T) {
t.Parallel()

_, err := gateway.NewGateway(nil, nil, "https://tableland.network", "https://tables.tableland.xyz", "invalid uri")
_, err := gateway.NewGateway(
nil, nil, nil, "https://tableland.network", "https://tables.tableland.xyz", "invalid uri",
)
require.Error(t, err)
require.ErrorContains(t, err, "animation renderer uri could not be parsed")
})
Expand Down Expand Up @@ -93,7 +95,7 @@ func TestGateway(t *testing.T) {
require.NoError(t, err)

svc, err := gateway.NewGateway(
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz", "",
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz", "",
)
require.NoError(t, err)
metadata, err := svc.GetTableMetadata(ctx, chainID, id)
Expand Down Expand Up @@ -148,7 +150,7 @@ func TestGetMetadata(t *testing.T) {
parser, err := parserimpl.New([]string{"system_", "registry", "sqlite_"})
require.NoError(t, err)

svc, err := gateway.NewGateway(parser, NewGatewayStore(db, nil), "https://tableland.network", "", "")
svc, err := gateway.NewGateway(parser, NewGatewayStore(db), nil, "https://tableland.network", "", "")
require.NoError(t, err)

metadata, err := svc.GetTableMetadata(context.Background(), chainID, id)
Expand All @@ -168,7 +170,7 @@ func TestGetMetadata(t *testing.T) {
require.NoError(t, err)

svc, err := gateway.NewGateway(
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz", "",
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz", "",
)
require.NoError(t, err)

Expand All @@ -189,7 +191,7 @@ func TestGetMetadata(t *testing.T) {
require.NoError(t, err)

svc, err := gateway.NewGateway(
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz/", "",
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz/", "",
)
require.NoError(t, err)

Expand All @@ -210,7 +212,7 @@ func TestGetMetadata(t *testing.T) {
parser, err := parserimpl.New([]string{"system_", "registry", "sqlite_"})
require.NoError(t, err)

_, err = gateway.NewGateway(parser, NewGatewayStore(db, nil), "https://tableland.network", "foo", "")
_, err = gateway.NewGateway(parser, NewGatewayStore(db), nil, "https://tableland.network", "foo", "")
require.Error(t, err)
require.ErrorContains(t, err, "metadata renderer uri could not be parsed")
})
Expand All @@ -222,7 +224,7 @@ func TestGetMetadata(t *testing.T) {
require.NoError(t, err)

svc, err := gateway.NewGateway(
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz", "",
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz", "",
)
require.NoError(t, err)

Expand All @@ -244,7 +246,8 @@ func TestGetMetadata(t *testing.T) {

svc, err := gateway.NewGateway(
parser,
NewGatewayStore(db, nil),
NewGatewayStore(db),
nil,
"https://tableland.network",
"https://tables.tableland.xyz",
"https://tables.tableland.xyz",
Expand Down Expand Up @@ -282,15 +285,16 @@ func TestQueryConstraints(t *testing.T) {

gateway, err := gateway.NewGateway(
parser,
NewGatewayStore(db, nil),
NewGatewayStore(db),
nil,
"https://tableland.network",
"https://tables.tableland.xyz",
"https://tables.tableland.xyz",
)
require.NoError(t, err)

_, err = gateway.RunReadQuery(
context.Background(), "SELECT * FROM foo_1337_1 WHERE bar = 'hello2'",
context.Background(), "SELECT * FROM foo_1337_1 WHERE bar = 'hello2'", []string{},
) // length of 45 bytes
require.Error(t, err)
require.ErrorContains(t, err, "read query size is too long")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Tableland Validator - OpenAPI 3.0
*
* In Tableland, Validators are the execution unit/actors of the protocol. They have the following responsibilities: - Listen to onchain events to materialize Tableland-compliant SQL queries in a database engine (currently, SQLite by default). - Serve read-queries (e.g., SELECT * FROM foo_69_1) to the external world. - Serve state queries (e.g., list tables, get receipts, etc) to the external world. In the 1.0.0 release of the Tableland Validator API, we've switched to a design first approach! You can now help us improve the API whether it's by making changes to the definition itself or to the code. That way, with time, we can improve the API in general, and expose some of the new features in OAS3. The API includes the following endpoints: - `/health`: Returns OK if the validator considers itself healthy. - `/version`: Returns version information about the validator daemon. - `/query`: Returns the results of a SQL read query against the Tableland network. - `/receipt/{chainId}/{transactionHash}`: Returns the status of a given transaction receipt by hash. - `/tables/{chainId}/{tableId}`: Returns information about a single table, including schema information.
*
* API version: 1.1.0
* Contact: [email protected]
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/
package apiv1

type OneOfQueryParamsItems struct {
}
2 changes: 2 additions & 0 deletions internal/router/controllers/apiv1/model_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ package apiv1
type Query struct {
// The SQL read query statement
Statement string `json:"statement,omitempty"`
// The values of query parameters
Params []interface{} `json:"params,omitempty"`
// The requested response format: * `objects` - Returns the query results as a JSON array of JSON objects. * `table` - Return the query results as a JSON object with columns and rows properties.
Format string `json:"format,omitempty"`
// Whether to extract the JSON object from the single property of the surrounding JSON object.
Expand Down
36 changes: 33 additions & 3 deletions internal/router/controllers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,14 @@ func (c *Controller) GetTableQuery(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")

stm := r.URL.Query().Get("statement")
params := []string{}

if r.URL.Query().Has("params") {
params = r.URL.Query()["params"]
}

start := time.Now()
res, ok := c.runReadRequest(r.Context(), stm, rw)
res, ok := c.runReadRequest(r.Context(), stm, params, rw)
if !ok {
return
}
Expand Down Expand Up @@ -276,6 +281,7 @@ func (c *Controller) PostTableQuery(rw http.ResponseWriter, r *http.Request) {

// setting a default body because these options could be missing from JSON
body := &apiv1.Query{
Params: []any{},
Format: string(formatter.Objects),
Extract: false,
Unwrap: false,
Expand All @@ -289,8 +295,31 @@ func (c *Controller) PostTableQuery(rw http.ResponseWriter, r *http.Request) {
}
_ = r.Body.Close()

params := make([]string, len(body.Params))
for i, p := range body.Params {
switch v := p.(type) {
case float64:
params[i] = fmt.Sprint(v)
case string:
params[i] = fmt.Sprintf("\"%s\"", v)
case nil:
params[i] = "null"
case bool:
params[i] = "false"
if v {
params[i] = "true"
}
default:
rw.WriteHeader(http.StatusBadRequest)
msg := fmt.Sprintf("invalid type (%T) of parameter", v)
log.Ctx(r.Context()).Error().Msg(msg)
_ = json.NewEncoder(rw).Encode(errors.ServiceError{Message: msg})
return
}
}

start := time.Now()
res, ok := c.runReadRequest(r.Context(), body.Statement, rw)
res, ok := c.runReadRequest(r.Context(), body.Statement, params, rw)
if !ok {
return
}
Expand Down Expand Up @@ -332,9 +361,10 @@ func (c *Controller) PostTableQuery(rw http.ResponseWriter, r *http.Request) {
func (c *Controller) runReadRequest(
ctx context.Context,
stm string,
params []string,
rw http.ResponseWriter,
) (*gateway.TableData, bool) {
res, err := c.gateway.RunReadQuery(ctx, stm)
res, err := c.gateway.RunReadQuery(ctx, stm, params)
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
log.Ctx(ctx).
Expand Down
Loading

0 comments on commit 179520f

Please sign in to comment.