Skip to content

Commit

Permalink
Add helpers for accessing objects by paths. (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlekSi committed Dec 11, 2021
1 parent fd512ce commit 3aa81d5
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 155 deletions.
206 changes: 76 additions & 130 deletions internal/handlers/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package handlers

import (
"context"
"fmt"
"testing"
"time"
Expand All @@ -27,18 +28,21 @@ import (
"github.com/FerretDB/FerretDB/internal/handlers/jsonb1"
"github.com/FerretDB/FerretDB/internal/handlers/shared"
"github.com/FerretDB/FerretDB/internal/handlers/sql"
"github.com/FerretDB/FerretDB/internal/pg"
"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/testutil"
"github.com/FerretDB/FerretDB/internal/wire"
)

func TestListDatabases(t *testing.T) {
t.Parallel()
func setup(t *testing.T, poolOpts *testutil.PoolOpts) (context.Context, *Handler, *pg.Pool) {
t.Helper()

if poolOpts == nil {
poolOpts = new(testutil.PoolOpts)
}

ctx := testutil.Ctx(t)
pool := testutil.Pool(ctx, t, &testutil.PoolOpts{
ReadOnly: true,
})
pool := testutil.Pool(ctx, t, poolOpts)
l := zaptest.NewLogger(t)
shared := shared.NewHandler(pool, "127.0.0.1:12345")
sql := sql.NewStorage(pool, l.Sugar())
Expand All @@ -52,96 +56,11 @@ func TestListDatabases(t *testing.T) {
Metrics: NewMetrics(),
})

type testCase struct {
req types.Document
resp types.Document
}

testCases := map[string]testCase{
"listDatabases": {
req: types.MustMakeDocument(
"listDatabases", int32(1),
),
resp: types.MustMakeDocument(
"databases", types.Array{
types.MustMakeDocument(
"name", "monila",
"sizeOnDisk", int64(13238272),
"empty", false,
),
types.MustMakeDocument(
"name", "pagila",
"sizeOnDisk", int64(7184384),
"empty", false,
),
},
"totalSize", int64(30081827),
"totalSizeMb", int64(28),
"ok", float64(1),
),
},
}

for name, tc := range testCases { //nolint:paralleltest // false positive
name, tc := name, tc
t.Run(name, func(t *testing.T) {
t.Parallel()

reqHeader := wire.MsgHeader{
RequestID: 1,
OpCode: wire.OP_MSG,
}

var reqMsg wire.OpMsg
err := reqMsg.SetSections(wire.OpMsgSection{
Documents: []types.Document{tc.req},
})
require.NoError(t, err)

_, resBody, closeConn := handler.Handle(ctx, &reqHeader, &reqMsg)
require.False(t, closeConn, "%s", wire.DumpMsgBody(resBody))

actual, err := resBody.(*wire.OpMsg).Document()
require.NoError(t, err)

expected := tc.resp

assert.Equal(t, actual.Map()["ok"].(float64), expected.Map()["ok"].(float64))
assert.GreaterOrEqual(t, actual.Map()["totalSize"].(int64), int64(5000))
assert.GreaterOrEqual(t, actual.Map()["totalSizeMb"].(int64), int64(1))

actualDatabasesArray := actual.Map()["databases"].(types.Array)
expectedDatabasesArray := expected.Map()["databases"].(types.Array)

for i := range expectedDatabasesArray {
actualDatabase := actualDatabasesArray[i].(types.Document)
expectedDatabase := expectedDatabasesArray[i].(types.Document)
assert.GreaterOrEqual(t, actualDatabase.Map()["sizeOnDisk"].(int64), int64(1000))

actualDatabase.Remove("sizeOnDisk")
expectedDatabase.Remove("sizeOnDisk")
assert.Equal(t, actualDatabase, expectedDatabase)
}
})
}
return ctx, handler, pool
}

func TestDropDatabase(t *testing.T) { //nolint:paralleltest,tparallel // affects a global list of databases
ctx := testutil.Ctx(t)
pool := testutil.Pool(ctx, t, new(testutil.PoolOpts))
l := zaptest.NewLogger(t)
shared := shared.NewHandler(pool, "127.0.0.1:12345")
sql := sql.NewStorage(pool, l.Sugar())
jsonb1 := jsonb1.NewStorage(pool, l)

handler := New(&NewOpts{
PgPool: pool,
Logger: l,
SharedHandler: shared,
SQLStorage: sql,
JSONB1Storage: jsonb1,
Metrics: NewMetrics(),
})
ctx, handler, pool := setup(t, nil)

type testCase struct {
req types.Document
Expand Down Expand Up @@ -238,23 +157,9 @@ func TestDropDatabase(t *testing.T) { //nolint:paralleltest,tparallel // affects

func TestFind(t *testing.T) {
t.Parallel()

ctx := testutil.Ctx(t)
pool := testutil.Pool(ctx, t, &testutil.PoolOpts{
ctx, handler, _ := setup(t, &testutil.PoolOpts{
ReadOnly: true,
})
l := zaptest.NewLogger(t)
shared := shared.NewHandler(pool, "127.0.0.1:12345")
sql := sql.NewStorage(pool, l.Sugar())
jsonb1 := jsonb1.NewStorage(pool, l)
handler := New(&NewOpts{
PgPool: pool,
Logger: l,
SharedHandler: shared,
SQLStorage: sql,
JSONB1Storage: jsonb1,
Metrics: NewMetrics(),
})

lastUpdate := time.Date(2020, 2, 15, 9, 34, 33, 0, time.UTC).Local()

Expand Down Expand Up @@ -587,33 +492,23 @@ func TestFind(t *testing.T) {

func TestReadOnlyHandlers(t *testing.T) {
t.Parallel()

ctx := testutil.Ctx(t)
pool := testutil.Pool(ctx, t, &testutil.PoolOpts{
ctx, handler, _ := setup(t, &testutil.PoolOpts{
ReadOnly: true,
})
l := zaptest.NewLogger(t)
shared := shared.NewHandler(pool, "127.0.0.1:12345")
sql := sql.NewStorage(pool, l.Sugar())
jsonb1 := jsonb1.NewStorage(pool, l)
handler := New(&NewOpts{
PgPool: pool,
Logger: l,
SharedHandler: shared,
SQLStorage: sql,
JSONB1Storage: jsonb1,
Metrics: NewMetrics(),
})

type testCase struct {
req types.Document
resp types.Document
req types.Document
reqSetDB bool
resp types.Document
compareFunc func(t testing.TB, actual, expected any)
}

testCases := map[string]testCase{
"CountAllActors": {
req: types.MustMakeDocument(
"count", "actor",
),
reqSetDB: true,
resp: types.MustMakeDocument(
"n", int32(200),
"ok", float64(1),
Expand All @@ -626,6 +521,7 @@ func TestReadOnlyHandlers(t *testing.T) {
"actor_id", int32(28),
),
),
reqSetDB: true,
resp: types.MustMakeDocument(
"n", int32(1),
"ok", float64(1),
Expand All @@ -638,25 +534,69 @@ func TestReadOnlyHandlers(t *testing.T) {
"last_name", "HOFFMAN",
),
),
reqSetDB: true,
resp: types.MustMakeDocument(
"n", int32(3),
"ok", float64(1),
),
},

"ServerStatus": {
"GetParameter": {
req: types.MustMakeDocument(
"serverStatus", int32(1),
"getParameter", int32(1),
),
resp: types.MustMakeDocument(
"version", "5.0.42",
"ok", float64(1),
),
},

"GetParameter": {
"ListDatabases": {
req: types.MustMakeDocument(
"getParameter", int32(1),
"listDatabases", int32(1),
),
resp: types.MustMakeDocument(
"databases", types.Array{
types.MustMakeDocument(
"name", "monila",
"sizeOnDisk", int64(13516800),
"empty", false,
),
types.MustMakeDocument(
"name", "pagila",
"sizeOnDisk", int64(7127040),
"empty", false,
),
types.MustMakeDocument(
"name", "test",
"sizeOnDisk", int64(0),
"empty", true,
),
},
"totalSize", int64(30114595),
"totalSizeMb", int64(28),
"ok", float64(1),
),
compareFunc: func(t testing.TB, expected, actual any) {
expectedDoc, actualDoc := expected.(types.Document), actual.(types.Document)

testutil.CompareAndSetByPath(t, expectedDoc, actualDoc, 1_000_000, "totalSize")
testutil.CompareAndSetByPath(t, expectedDoc, actualDoc, 1, "totalSizeMb")

expectedDBs := testutil.GetByPath(t, expectedDoc, "databases").(types.Array)
actualDBs := testutil.GetByPath(t, actualDoc, "databases").(types.Array)
require.Equal(t, len(expectedDBs), len(actualDBs))
for i, actualDB := range actualDBs {
testutil.CompareAndSetByPath(t, expectedDBs[i], actualDB, 200_000, "sizeOnDisk")
}

assert.Equal(t, expectedDoc, actualDoc)
},
},

"ServerStatus": {
req: types.MustMakeDocument(
"serverStatus", int32(1),
),
resp: types.MustMakeDocument(
"version", "5.0.42",
Expand All @@ -672,7 +612,9 @@ func TestReadOnlyHandlers(t *testing.T) {

for _, schema := range []string{"monila", "pagila"} {
t.Run(schema, func(t *testing.T) {
tc.req.Set("$db", schema)
if tc.reqSetDB {
tc.req.Set("$db", schema)
}

reqHeader := wire.MsgHeader{
RequestID: 1,
Expand All @@ -690,7 +632,11 @@ func TestReadOnlyHandlers(t *testing.T) {

actual, err := resBody.(*wire.OpMsg).Document()
require.NoError(t, err)
assert.Equal(t, tc.resp, actual)
if tc.compareFunc == nil {
assert.Equal(t, tc.resp, actual)
} else {
tc.compareFunc(t, tc.resp, actual)
}
})
}
})
Expand Down
48 changes: 48 additions & 0 deletions internal/types/array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2021 FerretDB Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package types

import "fmt"

// Array represents BSON array.
type Array []any

// Get returns a value at the given index.
func (a Array) Get(index int) (any, error) {
if l := len(a); index < 0 || index >= l {
return nil, fmt.Errorf("types.Array.Get: index %d is out of bounds [0-%d)", index, l)
}

return a[index], nil
}

// GetByPath returns a value by path - a sequence of indexes and keys.
func (a Array) GetByPath(path ...string) (any, error) {
return getByPath(a, path...)
}

// Set sets the value at the given index.
func (a Array) Set(index int, value any) error {
if l := len(a); index < 0 || index >= l {
return fmt.Errorf("types.Array.Set: index %d is out of bounds [0-%d)", index, l)
}

if err := validateValue(value); err != nil {
return fmt.Errorf("types.Array.Set: %w", err)
}

a[index] = value
return nil
}
5 changes: 5 additions & 0 deletions internal/types/binarysubtype.go → internal/types/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ const (
BinaryEncrypted = BinarySubtype(0x06) // encrypted
BinaryUser = BinarySubtype(0x80) // user
)

type Binary struct {
Subtype BinarySubtype
B []byte
}

0 comments on commit 3aa81d5

Please sign in to comment.