Skip to content

Commit b965ec6

Browse files
committed
#43: Option to return results as a list, instead of a map
1 parent 97bdf07 commit b965ec6

5 files changed

Lines changed: 109 additions & 33 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ ws4sqlite --db mydatabase.db
1919

2020
It's possible to make a POST call to `http://localhost:12321/mydatabase`, e.g. with the following body:
2121

22-
```json
22+
```json5
2323
{
24+
"resultFormat": "map", // "map" or "list"; if omitted, "map"
2425
"transaction": [
2526
{
2627
"statement": "INSERT INTO TEST_TABLE (ID, VAL, VAL2) VALUES (:id, :val, :val2)",
@@ -63,6 +64,8 @@ Obtaining an answer of
6364
- [**In-memory DBs**](https://germ.gitbook.io/ws4sqlite/documentation/configuration-file#path) are supported;
6465
- Serving of [**multiple databases**](https://germ.gitbook.io/ws4sqlite/documentation/configuration-file) in the same server instance;
6566
- [**Batching**](https://germ.gitbook.io/ws4sqlite/documentation/requests#batch-parameter-values-for-a-statement) of multiple value sets for a single statement;
67+
- **Parameters** may be passed to statements positionally (lists) or by name (maps);
68+
- **Results** of queries may be returned as key-value maps, or as values lists;
6669
- All queries of a call are executed in a [**transaction**](https://germ.gitbook.io/ws4sqlite/documentation/requests);
6770
- For each query/statement, specify if a failure should rollback the whole transaction, or the failure is [**limited**](https://germ.gitbook.io/ws4sqlite/documentation/errors#managed-errors) to that query;
6871
- "[**Stored Statements**](https://germ.gitbook.io/ws4sqlite/documentation/stored-statements)": define SQL in the server, and call it from the client;

src/sched_tasks_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ package main
1818

1919
import (
2020
"fmt"
21-
"github.com/robfig/cron/v3"
2221
"os"
2322
"path/filepath"
2423
"testing"
2524
"time"
25+
26+
"github.com/robfig/cron/v3"
2627
)
2728

2829
// The post-0.14 "scheduledTasks" structure is only actually tested in TestAtStartupMultiple, but the contents of the
@@ -236,7 +237,7 @@ func TestSchedTasksWithStatement(t *testing.T) {
236237
t.Error("did not succeed, but should have")
237238
}
238239

239-
if fmt.Sprint(getDefault[float64](res.Results[0].ResultSet[0], "num")) != "17" {
240+
if fmt.Sprint(res.Results[0].ResultSet[0].(map[string]interface{})["num"]) != "17" {
240241
t.Error("scheduled statement probably didn't execute")
241242
}
242243
}

src/structs.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import (
2121
"encoding/json"
2222
"fmt"
2323
"sync"
24-
25-
"github.com/iancoleman/orderedmap"
2624
)
2725

2826
// This is the ws4sqlite error type
@@ -116,8 +114,9 @@ type requestItem struct {
116114
}
117115

118116
type request struct {
119-
Credentials *credentials `json:"credentials"`
120-
Transaction []requestItem `json:"transaction"`
117+
ResultFormat *string `json:"resultFormat"`
118+
Credentials *credentials `json:"credentials"`
119+
Transaction []requestItem `json:"transaction"`
121120
}
122121

123122
type requestParams struct {
@@ -128,11 +127,12 @@ type requestParams struct {
128127
// These are for generating the response
129128

130129
type responseItem struct {
131-
Success bool `json:"success"`
132-
RowsUpdated *int64 `json:"rowsUpdated,omitempty"`
133-
RowsUpdatedBatch []int64 `json:"rowsUpdatedBatch,omitempty"`
134-
ResultSet []orderedmap.OrderedMap `json:"resultSet,omitnil"` // omitnil is used by jettison
135-
Error string `json:"error,omitempty"`
130+
Success bool `json:"success"`
131+
RowsUpdated *int64 `json:"rowsUpdated,omitempty"`
132+
RowsUpdatedBatch []int64 `json:"rowsUpdatedBatch,omitempty"`
133+
ResultHeaders []string `json:"resultHeaders,omitempty"`
134+
ResultSet []interface{} `json:"resultSet,omitnil"` // omitnil is used by jettison
135+
Error string `json:"error,omitempty"`
136136
}
137137

138138
type response struct {

src/web_service.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ func reportError(err error, code int, reqIdx int, noFail bool, results []respons
5757
if !noFail {
5858
panic(newWSError(reqIdx, code, err.Error()))
5959
}
60-
results[reqIdx] = responseItem{false, nil, nil, nil, capitalize(err.Error())}
60+
results[reqIdx] = responseItem{false, nil, nil, nil, nil, capitalize(err.Error())}
6161
}
6262

6363
// Processes a query, and returns a suitable responseItem
6464
//
6565
// This method is needed to execute properly the defers.
66-
func processWithResultSet(tx *sql.Tx, query string, params requestParams) (*responseItem, error) {
67-
resultSet := make([]orderedmap.OrderedMap, 0)
66+
func processWithResultSet(tx *sql.Tx, query string, isListResultSet bool, params requestParams) (*responseItem, error) {
67+
resultSet := make([]interface{}, 0)
6868

6969
rows := (*sql.Rows)(nil)
7070
err := (error)(nil)
@@ -91,19 +91,29 @@ func processWithResultSet(tx *sql.Tx, query string, params requestParams) (*resp
9191
return nil, err
9292
}
9393

94-
toAdd := orderedmap.New()
95-
for i := range values {
96-
toAdd.Set(fields[i], values[i])
97-
}
94+
if isListResultSet {
95+
// List-style result set
96+
toAdd := make([]interface{}, 0)
97+
for i := range values {
98+
toAdd = append(toAdd, values[i])
99+
}
100+
resultSet = append(resultSet, toAdd)
101+
} else {
102+
// Map-style result set
103+
toAdd := orderedmap.New()
104+
for i := range values {
105+
toAdd.Set(fields[i], values[i])
106+
}
98107

99-
resultSet = append(resultSet, *toAdd)
108+
resultSet = append(resultSet, *toAdd)
109+
}
100110
}
101111

102112
if err = rows.Err(); err != nil {
103113
return nil, err
104114
}
105115

106-
return &responseItem{true, nil, nil, resultSet, ""}, nil
116+
return &responseItem{true, nil, nil, fields, resultSet, ""}, nil
107117
}
108118

109119
// Process a single statement, and returns a suitable responseItem
@@ -126,7 +136,7 @@ func processForExec(tx *sql.Tx, statement string, params requestParams) (*respon
126136
return nil, err
127137
}
128138

129-
return &responseItem{true, &rowsUpdated, nil, nil, ""}, nil
139+
return &responseItem{true, &rowsUpdated, nil, nil, nil, ""}, nil
130140
}
131141

132142
// Process a batch statement, and returns a suitable responseItem.
@@ -161,7 +171,7 @@ func processForExecBatch(tx *sql.Tx, q string, paramsBatch []requestParams) (*re
161171
rowsUpdatedBatch = append(rowsUpdatedBatch, rowsUpdated)
162172
}
163173

164-
return &responseItem{true, nil, rowsUpdatedBatch, nil, ""}, nil
174+
return &responseItem{true, nil, rowsUpdatedBatch, nil, nil, ""}, nil
165175
}
166176

167177
func ckSQL(sql string) string {
@@ -187,6 +197,8 @@ func handler(databaseId string) func(c *fiber.Ctx) error {
187197
return newWSError(-1, fiber.StatusBadRequest, "in parsing body: %s", err.Error())
188198
}
189199

200+
isListResultSet := body.ResultFormat != nil && strings.EqualFold(*body.ResultFormat, "list")
201+
190202
db, found := dbs[databaseId]
191203
if !found {
192204
return newWSError(-1, fiber.StatusNotFound, "database with ID '%s' not found", databaseId)
@@ -309,7 +321,7 @@ func handler(databaseId string) func(c *fiber.Ctx) error {
309321
if hasResultSet {
310322
// Query
311323
// Externalized in a func so that defer rows.Close() actually runs
312-
retWR, err := processWithResultSet(tx, sqll, *params)
324+
retWR, err := processWithResultSet(tx, sqll, isListResultSet, *params)
313325
if err != nil {
314326
reportError(err, fiber.StatusInternalServerError, i, txItem.NoFail, ret.Results)
315327
continue

src/ws4sqlite_test.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ package main
1818

1919
import (
2020
"encoding/json"
21-
"github.com/gofiber/fiber/v2"
2221
"os"
2322
"slices"
2423
"sync"
2524
"testing"
2625
"time"
2726

27+
"github.com/gofiber/fiber/v2"
28+
2829
mllog "github.com/proofrock/go-mylittlelogger"
2930
)
3031

@@ -221,7 +222,7 @@ func TestTx(t *testing.T) {
221222
t.Error("req 1 inconsistent")
222223
}
223224

224-
if !res.Results[2].Success || getDefault[string](res.Results[2].ResultSet[0], "VAL") != "ONE" {
225+
if !res.Results[2].Success || res.Results[2].ResultSet[0].(map[string]interface{})["VAL"] != "ONE" {
225226
t.Error("req 2 inconsistent")
226227
}
227228

@@ -364,7 +365,7 @@ func TestConcurrent(t *testing.T) {
364365
t.Error("req 1 inconsistent")
365366
}
366367

367-
if !res.Results[2].Success || getDefault[string](res.Results[2].ResultSet[0], "VAL") != "ONE" {
368+
if !res.Results[2].Success || res.Results[2].ResultSet[0].(map[string]interface{})["VAL"] != "ONE" {
368369
t.Error("req 2 inconsistent")
369370
}
370371

@@ -417,10 +418,11 @@ func TestResultSetOrder(t *testing.T) {
417418
return
418419
}
419420

420-
queryResult := res.Results[2].ResultSet[0]
421+
queryResult := res.Results[2].ResultSet[0].(map[string]interface{})
422+
queryResultHeaders := res.Results[2].ResultHeaders
421423
expectedKeys := []string{"d", "c", "b", "a"}
422424
if !slices.Equal(
423-
queryResult.Keys(),
425+
queryResultHeaders,
424426
expectedKeys,
425427
) {
426428
t.Error("should have the right order")
@@ -429,7 +431,7 @@ func TestResultSetOrder(t *testing.T) {
429431

430432
expectedValues := []float64{4, 3, 2, 1}
431433
for i, key := range expectedKeys {
432-
value, ok := queryResult.Get(key)
434+
value, ok := queryResult[key]
433435
if !ok {
434436
t.Error("unreachable code")
435437
return
@@ -442,6 +444,64 @@ func TestResultSetOrder(t *testing.T) {
442444
}
443445
}
444446

447+
var listResults string = "list"
448+
449+
func TestListResultSet(t *testing.T) {
450+
// See this issue for more context: https://github.com/proofrock/sqliterg/issues/5
451+
req := request{
452+
ResultFormat: &listResults,
453+
Transaction: []requestItem{
454+
{
455+
Statement: "CREATE TABLE table_with_many_columns (d INT, c INT, b INT, a INT)",
456+
},
457+
{
458+
Statement: "INSERT INTO table_with_many_columns VALUES (4, 3, 2, 1)",
459+
},
460+
{
461+
Query: "SELECT * FROM table_with_many_columns",
462+
},
463+
{
464+
Statement: "DROP TABLE table_with_many_columns",
465+
},
466+
},
467+
}
468+
code, _, res := call("test", req, t)
469+
470+
if code != 200 {
471+
t.Error("did not succeed")
472+
return
473+
}
474+
475+
if !res.Results[0].Success ||
476+
!res.Results[1].Success ||
477+
!res.Results[2].Success ||
478+
!res.Results[3].Success {
479+
t.Error("did not succeed")
480+
return
481+
}
482+
483+
queryResult := res.Results[2].ResultSet[0].([]interface{})
484+
queryResultHeaders := res.Results[2].ResultHeaders
485+
expectedKeys := []string{"d", "c", "b", "a"}
486+
if !slices.Equal(
487+
queryResultHeaders,
488+
expectedKeys,
489+
) {
490+
t.Error("should have the right order")
491+
return
492+
}
493+
494+
expectedValues := []float64{4, 3, 2, 1}
495+
for i, key := range expectedKeys {
496+
value := queryResult[slices.Index(queryResultHeaders, key)]
497+
expectedValue := expectedValues[i]
498+
if value != expectedValue {
499+
t.Error("wrong value")
500+
return
501+
}
502+
}
503+
}
504+
445505
func TestArrayParams(t *testing.T) {
446506
req := request{
447507
Transaction: []requestItem{
@@ -550,7 +610,7 @@ func TestOkRO(t *testing.T) {
550610
return
551611
}
552612

553-
if !res.Results[0].Success || getDefault[string](res.Results[0].ResultSet[3], "VAL") != "FOUR" {
613+
if !res.Results[0].Success || res.Results[0].ResultSet[3].(map[string]interface{})["VAL"] != "FOUR" {
554614
t.Error("req is inconsistent")
555615
}
556616
}
@@ -577,7 +637,7 @@ func TestConcurrentRO(t *testing.T) {
577637
return
578638
}
579639

580-
if !res.Results[0].Success || getDefault[string](res.Results[0].ResultSet[3], "VAL") != "FOUR" {
640+
if !res.Results[0].Success || res.Results[0].ResultSet[3].(map[string]interface{})["VAL"] != "FOUR" {
581641
t.Error("req is inconsistent")
582642
}
583643
}(t)
@@ -1312,7 +1372,7 @@ func TestUnicode(t *testing.T) {
13121372
if code != 200 {
13131373
t.Error("SELECT failed", body)
13141374
}
1315-
if getDefault[string](res.Results[0].ResultSet[0], "TXT") != "世界" {
1375+
if res.Results[0].ResultSet[0].(map[string]interface{})["TXT"] != "世界" {
13161376
t.Error("Unicode extraction failed", body)
13171377
}
13181378

0 commit comments

Comments
 (0)