Skip to content

Commit 179520f

Browse files
authored
Merge pull request #625 from tablelandnetwork/bcalza/queryparams
adds support for query parameters
2 parents 9fe8b45 + 640d45f commit 179520f

22 files changed

+268
-78
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ gen-api-v1:
9191
&& mv go/* . \
9292
&& rm -rf go main.go Dockerfile README.md api .swagger-codegen .swagger-codegen-ignore *.yaml \
9393
&& sed -i 's/\*OneOfTableAttributesValue/interface{}/' model_table_attributes.go \
94+
&& sed -i 's/\OneOfQueryParamsItems/interface{}/' model_query.go \
9495
"
9596
sudo chown -R ${USER} ${APIV1}
9697
.PHONY: gen-api-v1

cmd/api/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,12 @@ func createAPIServer(
442442
supportedChainIDs = append(supportedChainIDs, chainID)
443443
}
444444

445+
resolver := parsing.NewReadStatementResolver(sm)
446+
445447
g, err := gateway.NewGateway(
446448
parser,
447-
gatewayimpl.NewGatewayStore(db, parsing.NewReadStatementResolver(sm)),
449+
gatewayimpl.NewGatewayStore(db),
450+
resolver,
448451
gatewayConfig.ExternalURIPrefix,
449452
gatewayConfig.MetadataRendererURI,
450453
gatewayConfig.AnimationRendererURI)

cmd/healthbot/counterprobe/counterprobe.go

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func (cp *CounterProbe) getCurrentCounterValue(ctx context.Context) (int64, erro
171171
if err := cp.client.Read(
172172
ctx,
173173
fmt.Sprintf("select counter from %s", cp.tableName),
174+
[]string{},
174175
&counter,
175176
clientV1.ReadExtract(),
176177
clientV1.ReadUnwrap()); err != nil {

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/sethvargo/go-limiter v0.7.2
2121
github.com/spf13/cobra v1.7.0
2222
github.com/stretchr/testify v1.8.2
23-
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6
23+
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d
2424
github.com/textileio/cli v1.0.2
2525
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0
2626
go.opentelemetry.io/otel v1.14.0

go.sum

+4-10
Original file line numberDiff line numberDiff line change
@@ -1295,16 +1295,10 @@ github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG
12951295
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
12961296
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
12971297
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
1298-
github.com/tablelandnetwork/sqlparser v0.0.0-20230517143402-3ab9022be0df h1:SUG49BUSuO9S6U3RjAV8a0NIDRByHj3kSt8/QR75rtI=
1299-
github.com/tablelandnetwork/sqlparser v0.0.0-20230517143402-3ab9022be0df/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1300-
github.com/tablelandnetwork/sqlparser v0.0.0-20230518143735-838d223866f6 h1:f8TRklEZmT4fJd7wE+oktjf4wQndJ5BqkwXOpuHrYBU=
1301-
github.com/tablelandnetwork/sqlparser v0.0.0-20230518143735-838d223866f6/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1302-
github.com/tablelandnetwork/sqlparser v0.0.0-20230602174101-e27f9a12da58 h1:vEdQ9rJs5vwJfrwg4HIpVVrXRVJqEzyA2MM9oUehnhc=
1303-
github.com/tablelandnetwork/sqlparser v0.0.0-20230602174101-e27f9a12da58/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1304-
github.com/tablelandnetwork/sqlparser v0.0.0-20230605150512-1cb695cd5627 h1:ctGSX+KFDNvMYX25ooerc3saOcfPF6i56oAe+mo1l20=
1305-
github.com/tablelandnetwork/sqlparser v0.0.0-20230605150512-1cb695cd5627/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1306-
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6 h1:goeC/kQXlqRod2rPwqrVxvEgF3I5S3f0fa538k+Evbw=
1307-
github.com/tablelandnetwork/sqlparser v0.0.0-20230605164749-c0e6862c37f6/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1298+
github.com/tablelandnetwork/sqlparser v0.0.0-20240523182602-af3edf08e3db h1:4/eOYTPLww9YzdsteeGFhcT8dwIaCm3FIOwrxs6ppQM=
1299+
github.com/tablelandnetwork/sqlparser v0.0.0-20240523182602-af3edf08e3db/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
1300+
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d h1:Xhc6wudmyItX8Pvr+Z2SCzOmrf/zda5sX1jCgUySNRg=
1301+
github.com/tablelandnetwork/sqlparser v0.0.0-20240529190608-e3776575020d/go.mod h1:S+M/v3Q8X+236kQxMQziFcLId2Cscb1LzW06IUVhljE=
13081302
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
13091303
github.com/textileio/cli v1.0.2 h1:qSp/x4d/9SZ93TxhgZnE5okRKqzqHqrzAwKAPjuPw50=
13101304
github.com/textileio/cli v1.0.2/go.mod h1:vTlCvvVyOmXXLwddCcBg3PDavfUsCkRBZoyr6Nu1lkc=

internal/gateway/gateway.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/ethereum/go-ethereum/common"
1515
logger "github.com/rs/zerolog/log"
16+
"github.com/tablelandnetwork/sqlparser"
1617
"github.com/textileio/go-tableland/internal/tableland"
1718
"github.com/textileio/go-tableland/pkg/parsing"
1819
"github.com/textileio/go-tableland/pkg/tables"
@@ -33,14 +34,14 @@ const (
3334

3435
// Gateway defines the gateway operations.
3536
type Gateway interface {
36-
RunReadQuery(ctx context.Context, stmt string) (*TableData, error)
37+
RunReadQuery(ctx context.Context, stmt string, params []string) (*TableData, error)
3738
GetTableMetadata(context.Context, tableland.ChainID, tables.TableID) (TableMetadata, error)
3839
GetReceiptByTransactionHash(context.Context, tableland.ChainID, common.Hash) (Receipt, bool, error)
3940
}
4041

4142
// GatewayStore is the storage layer of the Gateway.
4243
type GatewayStore interface {
43-
Read(context.Context, parsing.ReadStmt) (*TableData, error)
44+
Read(context.Context, parsing.ReadStmt, sqlparser.ReadStatementResolver) (*TableData, error)
4445
GetTable(context.Context, tableland.ChainID, tables.TableID) (Table, error)
4546
GetSchemaByTableName(context.Context, string) (TableSchema, error)
4647
GetReceipt(context.Context, tableland.ChainID, string) (Receipt, bool, error)
@@ -53,6 +54,8 @@ type GatewayService struct {
5354
metadataRendererURI string
5455
animationRendererURI string
5556
store GatewayStore
57+
58+
resolver *parsing.ReadStatementResolver
5659
}
5760

5861
var _ (Gateway) = (*GatewayService)(nil)
@@ -61,6 +64,7 @@ var _ (Gateway) = (*GatewayService)(nil)
6164
func NewGateway(
6265
parser parsing.SQLValidator,
6366
store GatewayStore,
67+
resolver *parsing.ReadStatementResolver,
6468
extURLPrefix string,
6569
metadataRendererURI string,
6670
animationRendererURI string,
@@ -89,6 +93,7 @@ func NewGateway(
8993
metadataRendererURI: metadataRendererURI,
9094
animationRendererURI: animationRendererURI,
9195
store: store,
96+
resolver: resolver,
9297
}, nil
9398
}
9499

@@ -161,13 +166,17 @@ func (g *GatewayService) GetReceiptByTransactionHash(
161166
}
162167

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

170-
queryResult, err := g.store.Read(ctx, readStmt)
175+
if err := g.resolver.PrepareParams(params); err != nil {
176+
return nil, fmt.Errorf("prepare params: %s", err)
177+
}
178+
179+
queryResult, err := g.store.Read(ctx, readStmt, g.resolver)
171180
if err != nil {
172181
return nil, fmt.Errorf("running read statement: %s", err)
173182
}

internal/gateway/gateway_instrumented.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ func (g *InstrumentedGateway) GetTableMetadata(
8181
}
8282

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

8989
attributes := append([]attribute.KeyValue{

internal/gateway/impl/gateway_store.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,21 @@ import (
1818

1919
// GatewayStore is the storage layer of the gateway.
2020
type GatewayStore struct {
21-
db *database.SQLiteDB
22-
resolver sqlparser.ReadStatementResolver
21+
db *database.SQLiteDB
2322
}
2423

2524
// NewGatewayStore creates a new GatewayStore.
26-
func NewGatewayStore(db *database.SQLiteDB, resolver sqlparser.ReadStatementResolver) *GatewayStore {
25+
func NewGatewayStore(db *database.SQLiteDB) *GatewayStore {
2726
return &GatewayStore{
28-
db: db,
29-
resolver: resolver,
27+
db: db,
3028
}
3129
}
3230

3331
// Read executes a parsed read statement.
34-
func (s *GatewayStore) Read(ctx context.Context, stmt parsing.ReadStmt) (*gateway.TableData, error) {
35-
query, err := stmt.GetQuery(s.resolver)
32+
func (s *GatewayStore) Read(
33+
ctx context.Context, stmt parsing.ReadStmt, resolver sqlparser.ReadStatementResolver,
34+
) (*gateway.TableData, error) {
35+
query, err := stmt.GetQuery(resolver)
3636
if err != nil {
3737
return nil, fmt.Errorf("get query: %s", err)
3838
}

internal/gateway/impl/gateway_store_test.go

+16-12
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,25 @@ func TestGatewayInitialization(t *testing.T) {
3131
t.Run("invalid external uri", func(t *testing.T) {
3232
t.Parallel()
3333

34-
_, err := gateway.NewGateway(nil, nil, "invalid uri", "", "")
34+
_, err := gateway.NewGateway(nil, nil, nil, "invalid uri", "", "")
3535
require.Error(t, err)
3636
require.ErrorContains(t, err, "invalid external url prefix")
3737
})
3838

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

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

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

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

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

151-
svc, err := gateway.NewGateway(parser, NewGatewayStore(db, nil), "https://tableland.network", "", "")
153+
svc, err := gateway.NewGateway(parser, NewGatewayStore(db), nil, "https://tableland.network", "", "")
152154
require.NoError(t, err)
153155

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

170172
svc, err := gateway.NewGateway(
171-
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz", "",
173+
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz", "",
172174
)
173175
require.NoError(t, err)
174176

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

191193
svc, err := gateway.NewGateway(
192-
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz/", "",
194+
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz/", "",
193195
)
194196
require.NoError(t, err)
195197

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

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

224226
svc, err := gateway.NewGateway(
225-
parser, NewGatewayStore(db, nil), "https://tableland.network", "https://tables.tableland.xyz", "",
227+
parser, NewGatewayStore(db), nil, "https://tableland.network", "https://tables.tableland.xyz", "",
226228
)
227229
require.NoError(t, err)
228230

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

245247
svc, err := gateway.NewGateway(
246248
parser,
247-
NewGatewayStore(db, nil),
249+
NewGatewayStore(db),
250+
nil,
248251
"https://tableland.network",
249252
"https://tables.tableland.xyz",
250253
"https://tables.tableland.xyz",
@@ -282,15 +285,16 @@ func TestQueryConstraints(t *testing.T) {
282285

283286
gateway, err := gateway.NewGateway(
284287
parser,
285-
NewGatewayStore(db, nil),
288+
NewGatewayStore(db),
289+
nil,
286290
"https://tableland.network",
287291
"https://tables.tableland.xyz",
288292
"https://tables.tableland.xyz",
289293
)
290294
require.NoError(t, err)
291295

292296
_, err = gateway.RunReadQuery(
293-
context.Background(), "SELECT * FROM foo_1337_1 WHERE bar = 'hello2'",
297+
context.Background(), "SELECT * FROM foo_1337_1 WHERE bar = 'hello2'", []string{},
294298
) // length of 45 bytes
295299
require.Error(t, err)
296300
require.ErrorContains(t, err, "read query size is too long")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Tableland Validator - OpenAPI 3.0
3+
*
4+
* 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.
5+
*
6+
* API version: 1.1.0
7+
* Contact: [email protected]
8+
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
9+
*/
10+
package apiv1
11+
12+
type OneOfQueryParamsItems struct {
13+
}

internal/router/controllers/apiv1/model_query.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ package apiv1
1212
type Query struct {
1313
// The SQL read query statement
1414
Statement string `json:"statement,omitempty"`
15+
// The values of query parameters
16+
Params []interface{} `json:"params,omitempty"`
1517
// 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.
1618
Format string `json:"format,omitempty"`
1719
// Whether to extract the JSON object from the single property of the surrounding JSON object.

internal/router/controllers/controller.go

+33-3
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,14 @@ func (c *Controller) GetTableQuery(rw http.ResponseWriter, r *http.Request) {
231231
rw.Header().Set("Content-Type", "application/json")
232232

233233
stm := r.URL.Query().Get("statement")
234+
params := []string{}
235+
236+
if r.URL.Query().Has("params") {
237+
params = r.URL.Query()["params"]
238+
}
234239

235240
start := time.Now()
236-
res, ok := c.runReadRequest(r.Context(), stm, rw)
241+
res, ok := c.runReadRequest(r.Context(), stm, params, rw)
237242
if !ok {
238243
return
239244
}
@@ -276,6 +281,7 @@ func (c *Controller) PostTableQuery(rw http.ResponseWriter, r *http.Request) {
276281

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

298+
params := make([]string, len(body.Params))
299+
for i, p := range body.Params {
300+
switch v := p.(type) {
301+
case float64:
302+
params[i] = fmt.Sprint(v)
303+
case string:
304+
params[i] = fmt.Sprintf("\"%s\"", v)
305+
case nil:
306+
params[i] = "null"
307+
case bool:
308+
params[i] = "false"
309+
if v {
310+
params[i] = "true"
311+
}
312+
default:
313+
rw.WriteHeader(http.StatusBadRequest)
314+
msg := fmt.Sprintf("invalid type (%T) of parameter", v)
315+
log.Ctx(r.Context()).Error().Msg(msg)
316+
_ = json.NewEncoder(rw).Encode(errors.ServiceError{Message: msg})
317+
return
318+
}
319+
}
320+
292321
start := time.Now()
293-
res, ok := c.runReadRequest(r.Context(), body.Statement, rw)
322+
res, ok := c.runReadRequest(r.Context(), body.Statement, params, rw)
294323
if !ok {
295324
return
296325
}
@@ -332,9 +361,10 @@ func (c *Controller) PostTableQuery(rw http.ResponseWriter, r *http.Request) {
332361
func (c *Controller) runReadRequest(
333362
ctx context.Context,
334363
stm string,
364+
params []string,
335365
rw http.ResponseWriter,
336366
) (*gateway.TableData, bool) {
337-
res, err := c.gateway.RunReadQuery(ctx, stm)
367+
res, err := c.gateway.RunReadQuery(ctx, stm, params)
338368
if err != nil {
339369
rw.WriteHeader(http.StatusBadRequest)
340370
log.Ctx(ctx).

0 commit comments

Comments
 (0)