Skip to content
Open
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
19 changes: 19 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ type Client interface {
// Pass one or more objects that will be used as templates for the schema.
UpdateSchema(context.Context, ...any) error

// AlterSchema applies a raw Dgraph Schema Definition Language string directly,
// bypassing the object-template inference of UpdateSchema. Use it when you need
// full control over predicate types, indexes, and directives — for example,
// schema migrations that declare predicates no Go type models yet.
AlterSchema(ctx context.Context, schema string) error

// GetSchema retrieves the current schema definition from the database.
// Returns a string containing the full schema in Dgraph Schema Definition Language.
GetSchema(context.Context) (string, error)
Expand Down Expand Up @@ -585,6 +591,19 @@ func (c client) Query(ctx context.Context, model any) *dg.Query {
return txn.Get(model).All(c.options.maxEdgeTraversal)
}

// AlterSchema applies a raw DQL schema string directly via Dgraph Alter,
// without the object-template inference performed by UpdateSchema.
func (c client) AlterSchema(ctx context.Context, schema string) error {
dgClient, err := c.pool.get()
if err != nil {
c.logger.Error(err, "Failed to get client from pool")
return err
}
defer c.pool.put(dgClient)

return dgClient.Alter(ctx, &api.Operation{Schema: schema})
}

// UpdateSchema implements updating the Dgraph schema. Pass one or more
// objects that will be used to generate the schema.
// If any object contains SimString fields tagged `dgraph:"embedding"`, the
Expand Down
6 changes: 6 additions & 0 deletions embedded_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ func (c *embeddedDgraphClient) Alter(
}
return &api.Payload{}, nil
}
if in.DropAttr != "" {
if err := c.engine.dropPredicate(ctx, c.ns, in.DropAttr); err != nil {
return nil, err
}
return &api.Payload{}, nil
}
if in.Schema != "" {
if err := c.engine.alterSchema(ctx, c.ns, in.Schema); err != nil {
return nil, err
Expand Down
19 changes: 19 additions & 0 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,25 @@ func (engine *Engine) dropData(ctx context.Context, ns *Namespace) error {
return nil
}

// dropPredicate deletes a single predicate (and its data) from the embedded
// engine — the in-process equivalent of a gRPC Alter with DropAttr set.
func (engine *Engine) dropPredicate(ctx context.Context, ns *Namespace, pred string) error {
engine.mutex.Lock()
defer engine.mutex.Unlock()

if !engine.isOpen.Load() {
return ErrClosedEngine
}

startTs, err := engine.z.nextTs()
if err != nil {
return err
}

nsAttr := x.NamespaceAttr(ns.ID(), pred)
return posting.DeletePredicate(ctx, nsAttr, startTs)
}

func (engine *Engine) alterSchema(ctx context.Context, ns *Namespace, sch string) error {
engine.mutex.Lock()
defer engine.mutex.Unlock()
Expand Down
84 changes: 84 additions & 0 deletions schema_ddl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

package modusgraph_test

import (
"context"
"os"
"testing"

"github.com/dgraph-io/dgo/v250/protos/api"
"github.com/stretchr/testify/require"
)

// TestDropPredicateEmbedded exercises the schema-DDL surface end-to-end:
// Client.AlterSchema declares a raw predicate, and a gRPC Alter with DropAttr
// routes through embedded_client.go's DropAttr arm into engine.dropPredicate.
func TestDropPredicateEmbedded(t *testing.T) {
testCases := []struct {
name string
uri string
skip bool
}{
{
name: "DropPredicateWithFileURI",
uri: "file://" + GetTempDir(t),
},
{
name: "DropPredicateWithDgraphURI",
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.Skipf("Skipping %s: MODUSGRAPH_TEST_ADDR not set", tc.name)
return
}

client, cleanup := CreateTestClient(t, tc.uri)
defer cleanup()

ctx := context.Background()

// Declare an indexed string predicate and insert a node carrying it.
err := client.AlterSchema(ctx, "dropme: string @index(exact) .")
require.NoError(t, err, "AlterSchema should succeed")

dg, dgCleanup, err := client.DgraphClient()
require.NoError(t, err, "DgraphClient should succeed")
defer dgCleanup()

_, err = dg.NewTxn().Mutate(ctx, &api.Mutation{
SetJson: []byte(`[{"dropme":"hello"}]`),
CommitNow: true,
})
require.NoError(t, err, "mutate should succeed")

// Confirm the predicate is present before the drop.
raw, err := client.QueryRaw(ctx, `{ q(func: has(dropme)) { c: count(uid) } }`, nil)
require.NoError(t, err, "count query should succeed")
require.Contains(t, string(raw), `"c":1`, "predicate present before drop")

// Drop the predicate via the public path; this exercises
// embedded_client.go's DropAttr arm + engine.dropPredicate.
err = dg.Alter(ctx, &api.Operation{DropAttr: "dropme"})
require.NoError(t, err, "DropAttr should succeed")

// Confirm the data is gone (no nodes have the predicate).
raw, err = client.QueryRaw(ctx, `{ q(func: has(dropme)) { c: count(uid) } }`, nil)
require.NoError(t, err, "count query should succeed after drop")
require.Contains(t, string(raw), `"c":0`, "predicate values gone after drop")

// Confirm the schema entry is gone.
raw, err = client.QueryRaw(ctx, `schema(pred: [dropme]) { type }`, nil)
require.NoError(t, err, "schema query should succeed after drop")
require.NotContains(t, string(raw), "dropme", "predicate schema entry gone after drop")
})
}
}
Loading