Skip to content
This repository was archived by the owner on Sep 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions admin_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
/*
* Copyright 2025 Hypermode 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.
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph_test
Expand Down
5 changes: 5 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package api

type Point struct {
Expand Down
5 changes: 5 additions & 0 deletions api/types_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package api

import (
Expand Down
20 changes: 14 additions & 6 deletions client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph

import (
Expand Down Expand Up @@ -27,7 +32,10 @@ type Client interface {
GetSchema(context.Context) (string, error)
DropAll(context.Context) error
DropData(context.Context) error
QueryRaw(context.Context, string) ([]byte, error)
// QueryRaw executes a raw Dgraph query with optional query variables.
// The `query` parameter is the Dgraph query string.
// The `vars` parameter is a map of variable names to their values, used to parameterize the query.
QueryRaw(context.Context, string, map[string]string) ([]byte, error)

DgraphClient() (*dgo.Dgraph, func(), error)
}
Expand Down Expand Up @@ -288,7 +296,7 @@ func (c client) Query(ctx context.Context, model any) *dg.Query {
}
defer c.pool.put(client)

txn := dg.NewTxn(client)
txn := dg.NewReadOnlyTxnContext(ctx, client)
return txn.Get(model)
}

Expand Down Expand Up @@ -342,11 +350,11 @@ func (c client) DropData(ctx context.Context) error {
return client.Alter(ctx, &api.Operation{DropOp: api.Operation_DATA})
}

// QueryRaw implements raw querying (DQL syntax).
func (c client) QueryRaw(ctx context.Context, q string) ([]byte, error) {
// QueryRaw implements raw querying (DQL syntax) and optional variables.
func (c client) QueryRaw(ctx context.Context, q string, vars map[string]string) ([]byte, error) {
if c.engine != nil {
ns := c.engine.GetDefaultNamespace()
resp, err := ns.Query(ctx, q)
resp, err := ns.QueryWithVars(ctx, q, vars)
if err != nil {
return nil, err
}
Expand All @@ -361,7 +369,7 @@ func (c client) QueryRaw(ctx context.Context, q string) ([]byte, error) {
defer c.pool.put(client)

txn := dg.NewReadOnlyTxnContext(ctx, client)
resp, err := txn.Txn().Query(ctx, q)
resp, err := txn.Txn().QueryWithVars(ctx, q, vars)
if err != nil {
return nil, err
}
Expand Down
210 changes: 210 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph_test

import (
"context"
"fmt"
"os"
"sync"
"testing"
"time"

mg "github.com/hypermodeinc/modusgraph"
"github.com/stretchr/testify/require"
)

func TestClientPool(t *testing.T) {
testCases := []struct {
name string
uri string
skip bool
}{
{
name: "ClientPoolWithFileURI",
uri: "file://" + t.TempDir(),
},
{
name: "ClientPoolWithDgraphURI",
uri: "dgraph://" + os.Getenv("MODUSGRAPH_TEST_ADDR"),
skip: os.Getenv("MODUSGRAPH_TEST_ADDR") == "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.skip {
t.Skip("Skipping test as MODUSGRAPH_TEST_ADDR is not set")
}

// Create a client with pool size 10
client, err := mg.NewClient(tc.uri, mg.WithPoolSize(10))
require.NoError(t, err)
defer client.Close()

// Test concurrent client pool usage
const numWorkers = 20
var wg sync.WaitGroup
var mu sync.Mutex
var clientCount int

for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()

// Get a client from the pool
client, cleanup, err := client.DgraphClient()
require.NoError(t, err)
require.NotNil(t, client)
txn := client.NewReadOnlyTxn()
ctx := context.Background()
_, err = txn.Query(ctx, "query { q(func: uid(1)) { uid } }")
require.NoError(t, err)
err = txn.Discard(ctx)
require.NoError(t, err)

// Verify we got a valid Dgraph client
if client != nil {
mu.Lock()
clientCount++
mu.Unlock()
}

// Clean up the client
cleanup()
}()
}

// Wait for all workers to complete
wg.Wait()

// Verify we got clients from the pool
require.GreaterOrEqual(t, clientCount, 1)

// Get a client before close
beforeClient, cleanupBefore, err := client.DgraphClient()
require.NoError(t, err)
require.NotNil(t, beforeClient)

// Close the client pool
client.Close()
time.Sleep(100 * time.Millisecond) // Give some time for cleanup

// Verify we can still get a new client after close (pool will create a new one)
afterClient, cleanupAfter, err := client.DgraphClient()
require.NoError(t, err)
require.NotNil(t, afterClient)

// Verify the client is actually new
require.NotEqual(t, fmt.Sprintf("%p", beforeClient), fmt.Sprintf("%p", afterClient))

// Clean up the client
cleanupAfter()

// Also clean up the before client if it wasn't already closed
cleanupBefore()
})
}

// Reset singleton at the end of the test to ensure the next test can start fresh
mg.ResetSingleton()
}

func TestClientPoolStress(t *testing.T) {
testCases := []struct {
name string
uri string
skip bool
}{
{
name: "ClientPoolStressWithFileURI",
uri: "file://" + t.TempDir(),
},
{
name: "ClientPoolStressWithDgraphURI",
uri: "dgraph://" + os.Getenv("MODUSGRAPH_TEST_ADDR"),
skip: os.Getenv("MODUSGRAPH_TEST_ADDR") == "",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.skip {
t.Skip("Skipping test as MODUSGRAPH_TEST_ADDR is not set")
}

// Create a client with pool size 10
client, err := mg.NewClient(tc.uri, mg.WithPoolSize(10))
require.NoError(t, err)
defer func() {
client.Close()
}()

// Test concurrent client pool usage with high load
const numWorkers = 20
const iterations = 10
var wg sync.WaitGroup
var successCount int
var errorCount int
var mu sync.Mutex

for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < iterations; j++ {
dgraphClient, cleanup, err := client.DgraphClient()
if err != nil {
mu.Lock()
errorCount++
mu.Unlock()
continue
}

if dgraphClient != nil {
// Test the client works
txn := dgraphClient.NewReadOnlyTxn()
ctx := context.Background()
_, err = txn.Query(ctx, "query { q(func: uid(1)) { uid } }")
if err != nil {
err = txn.Discard(ctx)
if err != nil {
mu.Lock()
errorCount++
mu.Unlock()
}
cleanup()
continue
}
err = txn.Discard(ctx)
if err != nil {
mu.Lock()
errorCount++
mu.Unlock()
cleanup()
continue
}

mu.Lock()
successCount++
mu.Unlock()
}

// Clean up the client
cleanup()
}
}()
}

wg.Wait()

require.Greater(t, successCount, 0)
})

mg.ResetSingleton()
}
}
2 changes: 1 addition & 1 deletion cmd/query/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func main() {
start := time.Now()

// Execute the query
resp, err := client.QueryRaw(ctx, query)
resp, err := client.QueryRaw(ctx, query, nil)
if err != nil {
logger.Error(err, "Query execution failed")
os.Exit(1)
Expand Down
15 changes: 2 additions & 13 deletions delete_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
/*
* Copyright 2025 Hypermode 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.
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph_test
Expand Down
1 change: 1 addition & 0 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func (engine *Engine) queryWithLock(ctx context.Context,
return nil, ErrClosedEngine
}

engine.logger.V(2).Info("Querying namespace", "namespaceID", ns.ID(), "query", q)
ctx = x.AttachNamespace(ctx, ns.ID())
return (&edgraph.Server{}).QueryNoAuth(ctx, &api.Request{
ReadOnly: true,
Expand Down
5 changes: 5 additions & 0 deletions examples/basic/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
Expand Down
5 changes: 5 additions & 0 deletions examples/readme/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (
Expand Down
15 changes: 2 additions & 13 deletions insert_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
/*
* Copyright 2025 Hypermode 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.
* SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph_test
Expand Down
Loading