Skip to content

Commit

Permalink
feat(query): add feature flag list-in-normalize
Browse files Browse the repository at this point in the history
This feature flag allows configuring @normalize in the query
language such that if there are multiple fields with same alias,
they could be returned as a list or non-list.

There was a breaking change at some point with the PR
#7639. Some customers
do not prefer a list response in case of @normalize. We would
like to not make any breaking changes going forward, and hence,
introducing this feature flag enables users to make that choice.
  • Loading branch information
mangalaman93 committed Jun 10, 2023
1 parent ab37697 commit 1e61e46
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 5 deletions.
11 changes: 11 additions & 0 deletions dgraph/cmd/alpha/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ they form a Raft group and provide synchronous replication.
Flag("size",
"The audit log max size in MB after which it will be rolled over.").
String())

flag.String("feature-flags", worker.FeatureFlagsDefaults, z.NewSuperFlagHelp(worker.FeatureFlagsDefaults).
Head("Feature flags to enable various experimental features").
Flag("list-in-normalize", "enables returning a list when there are multiple fields with same alias, "+
"see here for more details https://github.com/dgraph-io/dgraph/pull/7639").
String())
}

func setupCustomTokenizers() {
Expand Down Expand Up @@ -737,6 +743,11 @@ func run() {
}
edgraph.Init()

// feature flags
featureFlagsConf := z.NewSuperFlag(Alpha.Conf.GetString("feature-flags")).MergeAndCheckDefault(
worker.FeatureFlagsDefaults)
x.Config.ListInNormalize = featureFlagsConf.GetBool("list-in-normalize")

x.PrintVersion()
glog.Infof("x.Config: %+v", x.Config)
glog.Infof("x.WorkerConfig: %+v", x.WorkerConfig)
Expand Down
8 changes: 7 additions & 1 deletion dgraphtest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ type ClusterConfig struct {
refillInterval time.Duration
uidLease int
// exposed port offset for grpc/http port for both alpha/zero
portOffset int
portOffset int
featureFlags string
}

func NewClusterConfig() ClusterConfig {
Expand Down Expand Up @@ -163,3 +164,8 @@ func (cc ClusterConfig) WithExposedPortOffset(offset uint64) ClusterConfig {
cc.portOffset = int(offset)
return cc
}

func (cc ClusterConfig) WithFeatureFlags(val string) ClusterConfig {
cc.featureFlags = val
return cc
}
4 changes: 4 additions & 0 deletions dgraphtest/dgraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ func (a *alpha) cmd(c *LocalCluster) []string {
}
acmd = append(acmd, zeroAddrsArg)

if c.conf.featureFlags != "" {
acmd = append(acmd, fmt.Sprintf("--feature-flags=%v", c.conf.featureFlags))
}

return acmd
}

Expand Down
122 changes: 122 additions & 0 deletions query/normalize_feature_flag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//go:build integration2

/*
* Copyright 2023 Dgraph Labs, Inc. and Contributors *
* 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 query

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"

"github.com/dgraph-io/dgraph/dgraphtest"
)

func TestNormalizeDirectiveWithNoListResponse(t *testing.T) {
conf := dgraphtest.NewClusterConfig().WithNumAlphas(3).WithNumZeros(3).
WithReplicas(3).WithFeatureFlags("list-in-normalize=false")
c, err := dgraphtest.NewLocalCluster(conf)
require.NoError(t, err)
defer c.Cleanup(t.Failed())
require.NoError(t, c.Start())

gc, cleanup, err := c.Client()
require.NoError(t, err)
defer cleanup()
require.NoError(t, c.AssignUids(gc.Dgraph, 100))

const dataSchema = `
friend : [uid] @reverse @count .
name : string @index(term, exact, trigram) @count @lang .
dob : dateTime @index(year) .`
require.NoError(t, gc.SetupSchema(dataSchema))

const triples = `
<1> <friend> <23> .
<1> <friend> <24> .
<1> <friend> <25> .
<1> <friend> <31> .
<1> <friend> <101>.
<23> <friend> <1> .
<31> <friend> <1> .
<31> <friend> <25> .
<1> <dob> "1910-01-01" .
<23> <dob> "1910-01-02" .
<24> <dob> "1909-05-05" .
<25> <dob> "1909-01-10" .
<31> <dob> "1901-01-15" .
<1> <name> "Michonne" .
<23> <name> "Rick Grimes" .
<24> <name> "Glenn Rhee" .`
_, err = gc.Mutate(triples)
require.NoError(t, err)

query := `
{
me(func: uid(0x01)) @recurse @normalize {
n: name
d: dob
friend
}
}`
js, err := gc.Query(query)
require.NoError(t, err)
fmt.Println(string(js.Json))
require.JSONEq(t, `
{
"me": [
{
"n": "Michonne",
"d": "1910-01-01T00:00:00Z",
"n": "Rick Grimes",
"d": "1910-01-02T00:00:00Z",
"n": "Michonne",
"d": "1910-01-01T00:00:00Z"
},
{
"n": "Michonne",
"d": "1910-01-01T00:00:00Z",
"n": "Glenn Rhee",
"d": "1909-05-05T00:00:00Z"
},
{
"n": "Michonne",
"d": [
"1910-01-01T00:00:00Z",
"1909-01-10T00:00:00Z"
]
},
{
"n": "Michonne",
"d": [
"1910-01-01T00:00:00Z",
"1901-01-15T00:00:00Z"
],
"n": "Michonne",
"d": "1910-01-01T00:00:00Z"
},
{
"n": "Michonne",
"d": [
"1910-01-01T00:00:00Z",
"1901-01-15T00:00:00Z",
"1909-01-10T00:00:00Z"
]
}
]
}`, string(js.Json))
}
9 changes: 6 additions & 3 deletions query/outputnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,9 +978,12 @@ func (enc *encoder) normalize(fj fastJsonNode) ([]fastJsonNode, error) {
}

for i, slice := range parentSlice {
// sort the fastJson list
// This will ensure that nodes with same attribute name comes together in response
enc.MergeSort(&parentSlice[i])
if x.Config.ListInNormalize {
// sort the fastJson list. This will ensure that nodes
// with same attribute name comes together in response
enc.MergeSort(&parentSlice[i])
}

// From every list we need to remove node with attribute "uid".
var prev, cur fastJsonNode
cur = slice
Expand Down
3 changes: 2 additions & 1 deletion worker/server_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const (
ZeroLimitsDefaults = `uid-lease=0; refill-interval=30s; disable-admin-http=false;`
GraphQLDefaults = `introspection=true; debug=false; extensions=true; poll-interval=1s; ` +
`lambda-url=;`
CacheDefaults = `size-mb=1024; percentage=0,65,35;`
CacheDefaults = `size-mb=1024; percentage=0,65,35;`
FeatureFlagsDefaults = `list-in-normalize=true`
)

// ServerState holds the state of the Dgraph server.
Expand Down
3 changes: 3 additions & 0 deletions x/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ type Options struct {
// poll-interval duration - The polling interval for graphql subscription.
GraphQL *z.SuperFlag
GraphQLDebug bool

// feature flags
ListInNormalize bool
}

// Config stores the global instance of this package's options.
Expand Down

0 comments on commit 1e61e46

Please sign in to comment.