diff --git a/README.md b/README.md index c5a333a..2d522e9 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ client, err := mg.NewClient("file:///path/to/data") #### `dgraph://` - Remote Dgraph Server Connects to a Dgraph cluster. For more details on the Dgraph URI format, see the -[Dgraph Dgo documentation](https://github.com/hypermodeinc/dgo#connection-strings). +[Dgraph Dgo documentation](https://github.com/dgraph-io/dgo#connection-strings). ```go // Connect to a remote Dgraph server @@ -162,6 +162,34 @@ logger := logr.New(logr.Discard()) client, err := mg.NewClient(uri, mg.WithLogger(logger)) ``` +#### WithValidator(Validator) + +Configures custom validation for entities before mutations. The validator is called during insert, +update, and upsert operations to ensure data integrity. + +```go +import "github.com/go-playground/validator/v10" + +type User struct { + Name string `json:"name" validate:"required,min=2,max=100"` + Email string `json:"email" validate:"required,email"` + Age int `json:"age" validate:"gte=0,lte=130"` +} + +// Create a validator instance +validate := validator.New() + +// You can also register custom validations if needed +validate.RegisterValidation("custom", func(fl validator.FieldLevel) bool { + return fl.Field().String() == "custom_value" +}) + +// Create client with the validator +client, err := mg.NewClient(uri, mg.WithValidator(validate)) +``` + +See the [validator test](validate_test.go) for more examples. + You can combine multiple options: ```go @@ -241,38 +269,64 @@ type Person struct { ### Reverse Edges -Reverse edges allow efficient bidirectional traversal. When you query in the reverse direction, use -the tilde prefix in your JSON tag: +Reverse edges enable efficient bidirectional graph traversal. modusGraph supports two patterns: + +**1. Forward edges with automatic reverse indexing** - Use `dgraph:"reverse"` on a forward edge to +enable querying in both directions: + +```go +type FoafPerson struct { + UID string `json:"uid,omitempty"` + Name string `json:"person_name,omitempty" dgraph:"index=term,hash"` + Friends []*FoafPerson `json:"friends,omitempty" dgraph:"reverse"` // Forward edge with reverse index + FriendsOf []*FoafPerson `json:"~friends,omitempty" dgraph:"reverse"` // Query reverse direction + DType []string `json:"dgraph.type,omitempty"` +} +``` + +**2. Managed reverse edges** - Define relationships from the parent side using the `~predicate` JSON +tag prefix. When inserting, modusGraph automatically creates the forward edges on child entities: ```go -type Student struct { - Name string `json:"name,omitempty" dgraph:"index=exact"` - Takes_Class []*Class `json:"takes_class,omitempty" dgraph:"reverse"` +// Child: Enrollment has forward edge to Course +type Enrollment struct { + UID string `json:"uid,omitempty"` + Grade string `json:"grade,omitempty"` + InCourse []*Course `json:"in_course,omitempty" dgraph:"reverse"` + DType []string `json:"dgraph.type,omitempty"` +} - UID string `json:"uid,omitempty"` - DType []string `json:"dgraph.type,omitempty"` +// Parent: Course defines managed reverse edge to Enrollments +type Course struct { + UID string `json:"uid,omitempty"` + Name string `json:"course_name,omitempty" dgraph:"index=term"` + Enrollments []*Enrollment `json:"~in_course,omitempty" dgraph:"reverse"` // Managed reverse edge + DType []string `json:"dgraph.type,omitempty"` } +``` -type Class struct { - Name string `json:"name,omitempty" dgraph:"index=exact"` - Students []*Student `json:"~takes_class,omitempty"` // Reverse edge +With managed reverse edges, you can insert from the parent and modusGraph handles the edge direction +automatically: - UID string `json:"uid,omitempty"` - DType []string `json:"dgraph.type,omitempty"` +```go +course := &Course{ + Name: "Algorithms", + Enrollments: []*Enrollment{ + {Grade: "A"}, + {Grade: "B"}, + }, } +client.Insert(ctx, course) +// Creates: Enrollment1.in_course = [Course.UID], Enrollment2.in_course = [Course.UID] ``` -Advanced querying is required to properly bind reverse edges in query results. See the -`TestReverseEdgeQuery` test in [query_test.go](./query_test.go) for an example. +See [reverse_test.go](./reverse_test.go) for comprehensive examples including multi-level +hierarchies and friend-of-a-friend patterns. ## Basic Operations modusGraph provides a simple API for common database operations. -Note that in local-mode, unique fields are limited to the top-level object. Fields marked as unique -in embedded or lists of embedded objects that have `unique` tags will not be checked for uniqueness -when the top-level object is inserted. - ### Inserting Data To insert a new node into the database: @@ -297,20 +351,10 @@ if err != nil { fmt.Println("Created user with UID:", user.UID) ``` -Note: For local-based instances, the `InsertRaw` function is available. It applies mutations -directly to the Dgraph engine, by-passing unique checks and the transaction workflow. The UID field -must be set using the Dgraph blank node prefix concept (e.g. "\_:user1") to allow the engine to -generate a UID for the object. This function can operate at a much higher transaction rate than the -standard `Insert` function. - ### Upserting Data modusGraph provides a simple API for upserting data into the database. -Note that in local-mode, upserts are only supported on the top-level object. Fields in embedded or -lists of embedded objects that have `upsert` tags will be ignored when the top-level object is -upserted. - ```go ctx := context.Background() @@ -334,10 +378,6 @@ if err != nil { To update an existing node, first retrieve it, modify it, then save it back. -Note that in local-mode, unique update checks are only supported on the top-level object. Fields in -embedded or lists of embedded objects that have `unique` tags will not be checked for uniqueness -when the top-level object is updated. - ```go ctx := context.Background() @@ -630,10 +670,10 @@ integrating modusGraph into your workflow. We welcome external contributions. See the [CONTRIBUTING.md](./CONTRIBUTING.md) file if you would like to get involved. -Modus and its components are © Hypermode Inc., and licensed under the terms of the Apache License, -Version 2.0. See the [LICENSE](./LICENSE) file for a complete copy of the license. If you have any -questions about modus licensing, or need an alternate license or other arrangement, please contact -us at . +Modus and its components are © Istari Digital, Inc., and licensed under the terms of the Apache +License, Version 2.0. See the [LICENSE](./LICENSE) file for a complete copy of the license. If you +have any questions about modus licensing, or need an alternate license or other arrangement, please +contact us at . ## Windows Users @@ -655,8 +695,8 @@ modusGraph. ## Acknowledgements modusGraph builds heavily upon packages from the open source projects of -[Dgraph](https://github.com/hypermodeinc/dgraph) (graph query processing and transaction -management), [Badger](https://github.com/dgraph-io/badger) (data storage), and +[Dgraph](https://github.com/dgraph-io/dgraph) (graph query processing and transaction management), +[Badger](https://github.com/dgraph-io/badger) (data storage), and [Ristretto](https://github.com/dgraph-io/ristretto) (cache). modusGraph also relies on the [dgman](https://github.com/dolan-in/dgman) repository for much of its functionality. We expect the architecture and implementations of modusGraph and Dgraph to expand in differentiation over time as diff --git a/admin_test.go b/admin_test.go index 4a91486..77e1126 100644 --- a/admin_test.go +++ b/admin_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/buf_server.go b/buf_server.go deleted file mode 100644 index bc5bbf1..0000000 --- a/buf_server.go +++ /dev/null @@ -1,229 +0,0 @@ -/* - * SPDX-FileCopyrightText: Hypermode Inc. - * SPDX-License-Identifier: Apache-2.0 - */ - -package modusgraph - -import ( - "context" - "errors" - "fmt" - "log" - "net" - "strings" - - "github.com/dgraph-io/dgo/v250" - "github.com/dgraph-io/dgo/v250/protos/api" - "github.com/hypermodeinc/dgraph/v25/x" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" -) - -// bufSize is the size of the buffer for the bufconn connection -const bufSize = 1024 * 1024 * 10 - -// serverWrapper wraps the edgraph.Server to provide proper context setup -type serverWrapper struct { - api.DgraphServer - engine *Engine -} - -// Query implements the Dgraph Query method by delegating to the Engine -func (s *serverWrapper) Query(ctx context.Context, req *api.Request) (*api.Response, error) { - var ns *Namespace - - nsID, err := x.ExtractNamespace(ctx) - if err != nil || nsID == 0 { - ns = s.engine.GetDefaultNamespace() - } else { - ns, err = s.engine.GetNamespace(nsID) - if err != nil { - return nil, fmt.Errorf("error getting namespace %d: %w", nsID, err) - } - } - s.engine.logger.V(2).Info("Query using namespace", "namespaceID", ns.ID()) - - if len(req.Mutations) > 0 { - s.engine.logger.V(3).Info("Mutating", "mutations", req.Mutations) - - uids, err := ns.Mutate(ctx, req.Mutations) - if err != nil { - return nil, fmt.Errorf("engine mutation error: %w", err) - } - - uidMap := make(map[string]string) - for k, v := range uids { - if strings.HasPrefix(k, "_:") { - uidMap[k[2:]] = fmt.Sprintf("0x%x", v) - } else { - uidMap[k] = fmt.Sprintf("0x%x", v) - } - } - - return &api.Response{ - Uids: uidMap, - }, nil - } - - return ns.QueryWithVars(ctx, req.Query, req.Vars) -} - -// CommitOrAbort implements the Dgraph CommitOrAbort method -func (s *serverWrapper) CommitOrAbort(ctx context.Context, tc *api.TxnContext) (*api.TxnContext, error) { - var ns *Namespace - - nsID, err := x.ExtractNamespace(ctx) - if err != nil || nsID == 0 { - ns = s.engine.GetDefaultNamespace() - } else { - ns, err = s.engine.GetNamespace(nsID) - if err != nil { - return nil, fmt.Errorf("error getting namespace %d: %w", nsID, err) - } - } - s.engine.logger.V(2).Info("CommitOrAbort called with transaction", "transaction", tc, "namespaceID", ns.ID()) - - if tc.Aborted { - return tc, nil - } - - // For commit, we need to make a dummy mutation that has no effect but will trigger the commit - // This approach uses an empty mutation with CommitNow:true to leverage the Engine's existing - // transaction commit mechanism - emptyMutation := &api.Mutation{ - CommitNow: true, - } - - // We can't directly attach the transaction ID to the context in this way, - // but the Server implementation should handle the transaction context - // using the StartTs value in the empty mutation - - // Send the mutation through the Engine - _, err = ns.Mutate(ctx, []*api.Mutation{emptyMutation}) - if err != nil { - return nil, fmt.Errorf("error committing transaction: %w", err) - } - - s.engine.logger.V(2).Info("Transaction committed successfully") - - response := &api.TxnContext{ - StartTs: tc.StartTs, - CommitTs: tc.StartTs + 1, // We don't know the actual commit timestamp, but this works for testing - } - - return response, nil -} - -// Login implements the Dgraph Login method -func (s *serverWrapper) Login(ctx context.Context, req *api.LoginRequest) (*api.Response, error) { - // For security reasons, Authentication is not implemented in this wrapper - return nil, errors.New("authentication not implemented") -} - -// Alter implements the Dgraph Alter method by delegating to the Engine -func (s *serverWrapper) Alter(ctx context.Context, op *api.Operation) (*api.Payload, error) { - var ns *Namespace - - nsID, err := x.ExtractNamespace(ctx) - if err != nil || nsID == 0 { - ns = s.engine.GetDefaultNamespace() - } else { - ns, err = s.engine.GetNamespace(nsID) - if err != nil { - return nil, fmt.Errorf("error getting namespace %d: %w", nsID, err) - } - } - s.engine.logger.V(2).Info("Alter called with operation", "operation", op, "namespaceID", ns.ID()) - - switch { - case op.Schema != "": - err = ns.AlterSchema(ctx, op.Schema) - if err != nil { - s.engine.logger.Error(err, "Error altering schema") - return nil, fmt.Errorf("error altering schema: %w", err) - } - - case op.DropAll: - err = ns.DropAll(ctx) - if err != nil { - s.engine.logger.Error(err, "Error dropping all") - return nil, fmt.Errorf("error dropping all: %w", err) - } - case op.DropOp != 0: - switch op.DropOp { - case api.Operation_DATA: - err = ns.DropData(ctx) - if err != nil { - s.engine.logger.Error(err, "Error dropping data") - return nil, fmt.Errorf("error dropping data: %w", err) - } - default: - s.engine.logger.Error(nil, "Unsupported drop operation") - return nil, fmt.Errorf("unsupported drop operation: %d", op.DropOp) - } - case op.DropAttr != "": - s.engine.logger.Error(nil, "Drop attribute not implemented yet") - return nil, errors.New("drop attribute not implemented yet") - - default: - return nil, errors.New("unsupported alter operation") - } - - return &api.Payload{}, nil -} - -// CheckVersion implements the Dgraph CheckVersion method -func (s *serverWrapper) CheckVersion(ctx context.Context, check *api.Check) (*api.Version, error) { - // Return a version that matches what the client expects (TODO) - return &api.Version{ - Tag: "v25.0.0", // Must match major version expected by client - }, nil -} - -// setupBufconnServer creates a bufconn listener and starts a gRPC server with the Dgraph service -func setupBufconnServer(engine *Engine) (*bufconn.Listener, *grpc.Server) { - x.Config.LimitMutationsNquad = 1000000 - x.Config.LimitQueryEdge = 10000000 - - lis := bufconn.Listen(bufSize) - server := grpc.NewServer() - - // Register our server wrapper that properly handles context and routing - dgraphServer := &serverWrapper{engine: engine} - api.RegisterDgraphServer(server, dgraphServer) - - // Start the server in a goroutine - go func() { - if err := server.Serve(lis); err != nil { - log.Printf("Server exited with error: %v", err) - } - }() - - return lis, server -} - -// bufDialer is the dialer function for bufconn -func bufDialer(listener *bufconn.Listener) func(context.Context, string) (net.Conn, error) { - return func(ctx context.Context, url string) (net.Conn, error) { - return listener.Dial() - } -} - -// createDgraphClient creates a Dgraph client that connects to the bufconn server -func createDgraphClient(ctx context.Context, listener *bufconn.Listener) (*dgo.Dgraph, error) { - // Create a gRPC connection using the bufconn dialer - // nolint:staticcheck // SA1019: grpc.DialContext is deprecated - conn, err := grpc.DialContext(ctx, "bufnet", - grpc.WithContextDialer(bufDialer(listener)), - grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return nil, err - } - - // Create a Dgraph client - dgraphClient := api.NewDgraphClient(conn) - // nolint:staticcheck // SA1019: dgo.NewDgraphClient is deprecated but works with our current setup - return dgo.NewDgraphClient(dgraphClient), nil -} diff --git a/client.go b/client.go index f750fe8..ec6f6f0 100644 --- a/client.go +++ b/client.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,7 +7,6 @@ package modusgraph import ( "context" - "encoding/json" "errors" "fmt" "os" @@ -207,7 +206,7 @@ func NewValidator() *validator.Validate { // // The returned Client provides a consistent interface regardless of whether you're // connected to a remote Dgraph cluster or a local embedded database. This abstraction -// helps prevent the connection issues that can occur with raw gRPC/bufconn setups. +// helps prevent connection issues and provides seamless access to embedded Dgraph. // // For file-based URIs, the client maintains a singleton Engine instance to ensure // data consistency across multiple client connections to the same database. @@ -269,9 +268,24 @@ func NewClient(uri string, opts ...ClientOpt) (Client, error) { return nil, err } client.engine = engine - client.pool = newClientPool(options.poolSize, func() (*dgo.Dgraph, error) { - client.logger.V(2).Info("Getting Dgraph client from engine", "location", uri) - return engine.GetClient() + // Create embedded dgo client that routes to engine + ns := engine.GetDefaultNamespace() + if options.namespace != "" { + nsID, err := parseNamespaceID(options.namespace) + if err != nil { + engine.Close() + return nil, fmt.Errorf("invalid namespace ID %q: %w", options.namespace, err) + } + ns, err = engine.GetNamespace(nsID) + if err != nil { + engine.Close() + return nil, fmt.Errorf("failed to get namespace %d: %w", nsID, err) + } + } + client.pool = newClientPool(1, func() (*dgo.Dgraph, error) { + embeddedClient := newEmbeddedDgraphClient(engine, ns) + //nolint:staticcheck // dgo.NewDgraphClient is deprecated but required for embedded client + return dgo.NewDgraphClient(embeddedClient), nil }, client.logger) dg.SetLogger(client.logger) clientMap[key] = client @@ -289,13 +303,13 @@ type client struct { logger logr.Logger } -func (c client) isLocal() bool { - return strings.HasPrefix(c.uri, fileURIPrefix) -} - func (c client) key() string { - return fmt.Sprintf("%s:%t:%d:%d:%d:%s", c.uri, c.options.autoSchema, c.options.poolSize, - c.options.maxEdgeTraversal, c.options.cacheSizeMB, c.options.namespace) + validatorKey := "nil" + if c.options.validator != nil { + validatorKey = fmt.Sprintf("%p", c.options.validator) + } + return fmt.Sprintf("%s:%t:%d:%d:%d:%s:%s", c.uri, c.options.autoSchema, c.options.poolSize, + c.options.maxEdgeTraversal, c.options.cacheSizeMB, c.options.namespace, validatorKey) } func checkPointer(obj any) error { @@ -348,9 +362,6 @@ func (c client) Insert(ctx context.Context, obj any) error { return err } - if c.isLocal() { - return c.mutateWithUniqueVerification(ctx, obj, true) - } return c.process(ctx, obj, "Insert", func(tx *dg.TxnContext, obj any) ([]string, error) { return tx.MutateBasic(obj) }) @@ -358,109 +369,31 @@ func (c client) Insert(ctx context.Context, obj any) error { // InsertRaw adds a new object or slice of objects to the database. // The object must be a pointer to a struct with appropriate dgraph tags. -// This is a no-op for remote Dgraph clients. For local clients, this -// function mutates the Dgraph engine directly. No unique checks are performed. // The `UID` field for any objects must be set using the Dgraph blank node // prefix concept (e.g. "_:user1") to allow the engine to generate a UID for the object. +// +// Deprecated: InsertRaw is now identical to Insert. Use Insert instead. func (c client) InsertRaw(ctx context.Context, obj any) error { // Validate struct before insertion if err := c.validateStruct(ctx, obj); err != nil { return err } - if c.isLocal() { - // Validate object and update schema if autoSchema is enabled - schemaObj, err := checkObject(obj) - if err != nil { - return err - } - if c.options.autoSchema { - if err := c.UpdateSchema(ctx, schemaObj); err != nil { - return err - } - } - - val := reflect.ValueOf(obj) - var sliceValue reflect.Value - - mutations := []*api.Mutation{} - // Handle pointer to slice - if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Slice { - sliceValue = val.Elem() - } else if val.Kind() == reflect.Slice { - // Direct slice - sliceValue = val - } else { - // Single object - create a slice with one element - valElem := val - for valElem.Kind() == reflect.Ptr { - valElem = valElem.Elem() - } - sliceType := reflect.SliceOf(valElem.Type()) - sliceValue = reflect.MakeSlice(sliceType, 1, 1) - sliceValue.Index(0).Set(valElem) - } - - // Recursively validate and prepare all structs (including nested ones) - if err := validateAndPrepareStruct(sliceValue, "obj", make(map[uintptr]bool)); err != nil { - return err - } - - // iterate sliceValue and create mutations - for i := 0; i < sliceValue.Len(); i++ { - elem := sliceValue.Index(i) - if elem.Kind() == reflect.Ptr { - elem = elem.Elem() - } - if elem.Kind() != reflect.Struct { - continue - } - - data, err := json.Marshal(elem.Interface()) - if err != nil { - return err - } - mutations = append(mutations, &api.Mutation{SetJson: data, CommitNow: false}) - } - uidMap, err := c.engine.db0.Mutate(ctx, mutations) - if err != nil { - return err - } - - // Replace blank node UIDs with actual generated UIDs in the original obj - replaceUIDs(obj, uidMap) - - return nil - } else { - return c.process(ctx, obj, "Insert", func(tx *dg.TxnContext, obj any) ([]string, error) { - return tx.MutateBasic(obj) - }) - } + return c.process(ctx, obj, "Insert", func(tx *dg.TxnContext, obj any) ([]string, error) { + return tx.MutateBasic(obj) + }) } // Upsert implements inserting or updating an object or slice of objects in the database. // Note that the struct tag `upsert` must be used. One or more predicates can be specified // to be used for upserting. If none are specified, the first predicate with the `upsert` tag // will be used. -// Note for local file clients, only the first struct field marked with `upsert` will be used -// if none are specified in the predicates argument. func (c client) Upsert(ctx context.Context, obj any, predicates ...string) error { // Validate struct before upsert if err := c.validateStruct(ctx, obj); err != nil { return err } - if c.isLocal() { - var upsertPredicate string - if len(predicates) > 0 { - upsertPredicate = predicates[0] - if len(predicates) > 1 { - c.logger.V(1).Info("Multiple upsert predicates specified, local mode only supports one, using first of this list", - "predicates", predicates) - } - } - return c.upsert(ctx, obj, upsertPredicate) - } return c.process(ctx, obj, "Upsert", func(tx *dg.TxnContext, obj any) ([]string, error) { return tx.Upsert(obj, predicates...) }) @@ -474,10 +407,6 @@ func (c client) Update(ctx context.Context, obj any) error { return err } - if c.isLocal() { - return c.mutateWithUniqueVerification(ctx, obj, false) - } - return c.process(ctx, obj, "Update", func(tx *dg.TxnContext, obj any) ([]string, error) { return tx.MutateBasic(obj) }) @@ -503,6 +432,7 @@ func (c client) Get(ctx context.Context, obj any, uid string) error { if err != nil { return err } + client, err := c.pool.get() if err != nil { return err @@ -578,15 +508,6 @@ func (c client) DropData(ctx context.Context) 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.isLocal() { - ns := c.engine.GetDefaultNamespace() - resp, err := ns.QueryWithVars(ctx, q, vars) - if err != nil { - return nil, err - } - return resp.GetJson(), nil - } - client, err := c.pool.get() if err != nil { c.logger.Error(err, "Failed to get client from pool") @@ -632,142 +553,6 @@ func (c client) DgraphClient() (client *dgo.Dgraph, cleanup func(), err error) { return client, cleanup, err } -// validateAndPrepareStruct recursively validates and prepares structs for insertion -// by ensuring UID fields are in the correct format and DType fields are set -func validateAndPrepareStruct(val reflect.Value, path string, visited map[uintptr]bool) error { - if !val.IsValid() { - return nil - } - - // Dereference pointers - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return nil - } - // Prevent infinite recursion on circular references - ptr := val.Pointer() - if visited[ptr] { - return nil - } - visited[ptr] = true - val = val.Elem() - } - - switch val.Kind() { - case reflect.Struct: - // Check and validate UID field - uidField := val.FieldByName("UID") - if uidField.IsValid() && uidField.Kind() == reflect.String { - uidStr := uidField.String() - if uidStr == "" { - return fmt.Errorf("UID is empty at %s", path) - } - if !strings.HasPrefix(uidStr, "_:") { - return fmt.Errorf("UID at %s is not in the form of _:", path) - } - } - - // Check and set DType field if empty - dtypeField := val.FieldByName("DType") - if dtypeField.IsValid() && dtypeField.CanSet() { - if dtypeField.Kind() == reflect.Slice && dtypeField.Len() == 0 { - dtypeSlice := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1) - dtypeSlice.Index(0).SetString(val.Type().Name()) - dtypeField.Set(dtypeSlice) - } - } - - // Recursively process all struct fields - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - fieldName := val.Type().Field(i).Name - fieldPath := path + "." + fieldName - if field.CanInterface() { - if err := validateAndPrepareStruct(field, fieldPath, visited); err != nil { - return err - } - } - } - - case reflect.Slice, reflect.Array: - for i := 0; i < val.Len(); i++ { - indexPath := fmt.Sprintf("%s[%d]", path, i) - if err := validateAndPrepareStruct(val.Index(i), indexPath, visited); err != nil { - return err - } - } - - case reflect.Map: - for _, key := range val.MapKeys() { - mapVal := val.MapIndex(key) - keyPath := fmt.Sprintf("%s[%v]", path, key.Interface()) - if err := validateAndPrepareStruct(mapVal, keyPath, visited); err != nil { - return err - } - } - } - - return nil -} - -// replaceUIDs recursively walks through obj and replaces UID field values -// that match keys in the uidMap with their corresponding hex-encoded values -func replaceUIDs(obj any, uidMap map[string]uint64) { - val := reflect.ValueOf(obj) - replaceUIDsValue(val, uidMap, make(map[uintptr]bool)) -} - -func replaceUIDsValue(val reflect.Value, uidMap map[string]uint64, visited map[uintptr]bool) { - if !val.IsValid() { - return - } - - // Dereference pointers - for val.Kind() == reflect.Ptr { - if val.IsNil() { - return - } - // Prevent infinite recursion on circular references - ptr := val.Pointer() - if visited[ptr] { - return - } - visited[ptr] = true - val = val.Elem() - } - - switch val.Kind() { - case reflect.Struct: - // Check for UID field first - uidField := val.FieldByName("UID") - if uidField.IsValid() && uidField.CanSet() && uidField.Kind() == reflect.String { - currentUID := uidField.String() - if newUID, ok := uidMap[currentUID]; ok { - uidField.SetString(fmt.Sprintf("%#x", newUID)) - } - } - - // Recursively process all fields - for i := 0; i < val.NumField(); i++ { - field := val.Field(i) - if field.CanInterface() { - replaceUIDsValue(field, uidMap, visited) - } - } - - case reflect.Slice, reflect.Array: - for i := 0; i < val.Len(); i++ { - replaceUIDsValue(val.Index(i), uidMap, visited) - } - - case reflect.Map: - for _, key := range val.MapKeys() { - mapVal := val.MapIndex(key) - replaceUIDsValue(mapVal, uidMap, visited) - } - } -} - type clientPool struct { clients chan *dgo.Dgraph factory func() (*dgo.Dgraph, error) diff --git a/client_test.go b/client_test.go index 252514b..70b6031 100644 --- a/client_test.go +++ b/client_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ import ( "context" "fmt" "os" + "strings" "sync" "testing" "time" @@ -330,8 +331,8 @@ func TestClientValidator(t *testing.T) { // Create a validator instance validate := mg.NewValidator() - // Create a client with validator - client, err := mg.NewClient(tc.uri, mg.WithValidator(validate)) + // Create a client with validator and AutoSchema + client, err := mg.NewClient(tc.uri, mg.WithValidator(validate), mg.WithAutoSchema(true)) require.NoError(t, err) defer client.Close() @@ -416,3 +417,39 @@ func TestClientValidator(t *testing.T) { }) } } + +func TestClientInvalidNamespace(t *testing.T) { + // Test the parseNamespaceID function directly by testing invalid namespace IDs + // This tests the validation logic without needing to create new engines + + testCases := []struct { + namespace string + expectErr bool + errContains string + }{ + {"invalid", true, "invalid namespace ID"}, + {"abc123", true, "invalid namespace ID"}, + {"", false, ""}, // Empty string should use default namespace + {"0", false, ""}, // Valid numeric + {"1", false, ""}, // Valid numeric + {"999", false, ""}, // Valid numeric (will fail at GetNamespace level) + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("namespace_%s", tc.namespace), func(t *testing.T) { + // We can't test the full NewClient due to singleton issues, + // but we can test the parseNamespaceID logic indirectly + if tc.expectErr { + // Try to create a client with invalid namespace - should fail at parse level + _, err := mg.NewClient("file://"+t.TempDir(), mg.WithNamespace(tc.namespace)) + if err != nil { + if strings.Contains(err.Error(), "only one instance") { + t.Skip("Skipping due to singleton engine") + } + require.Error(t, err) + require.Contains(t, err.Error(), tc.errContains) + } + } + }) + } +} diff --git a/cmd/query/main.go b/cmd/query/main.go index 3ae20c4..592754b 100644 --- a/cmd/query/main.go +++ b/cmd/query/main.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/config.go b/config.go index 9f4b24c..556a627 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/delete_test.go b/delete_test.go index 070dfbb..7191e18 100644 --- a/delete_test.go +++ b/delete_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/embedded_client.go b/embedded_client.go new file mode 100644 index 0000000..329f4bf --- /dev/null +++ b/embedded_client.go @@ -0,0 +1,343 @@ +/* + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package modusgraph + +import ( + "context" + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/dgraph-io/dgo/v250/protos/api" + "github.com/dgraph-io/dgraph/v25/x" + "google.golang.org/grpc" +) + +// embeddedDgraphClient implements api.DgraphClient by routing calls to the embedded Engine. +// This allows dgman and dgo to work seamlessly with the embedded Dgraph. +type embeddedDgraphClient struct { + engine *Engine + ns *Namespace +} + +// newEmbeddedDgraphClient creates a new embedded client for the given namespace. +func newEmbeddedDgraphClient(engine *Engine, ns *Namespace) *embeddedDgraphClient { + return &embeddedDgraphClient{ + engine: engine, + ns: ns, + } +} + +func (c *embeddedDgraphClient) Login( + ctx context.Context, + in *api.LoginRequest, + opts ...grpc.CallOption, +) (*api.Response, error) { + // For embedded mode, login is a no-op (no auth required) + return &api.Response{}, nil +} + +func (c *embeddedDgraphClient) Query( + ctx context.Context, + in *api.Request, + opts ...grpc.CallOption, +) (*api.Response, error) { + // Attach namespace context + ctx = x.AttachNamespace(ctx, c.ns.ID()) + + // For requests with both query and mutations (upsert case) + if len(in.Mutations) > 0 && in.Query != "" { + return c.handleUpsert(ctx, in) + } + + // Simple mutation (no query) + if len(in.Mutations) > 0 { + uids, err := c.ns.Mutate(ctx, in.Mutations) + if err != nil { + return nil, err + } + // Convert uids map to response format + // dgman expects keys without the "_:" prefix (it strips prefix when looking up) + uidStrings := make(map[string]string) + for k, v := range uids { + key := k + if strings.HasPrefix(k, "_:") { + key = k[2:] // strip "_:" prefix + } + uidStrings[key] = fmt.Sprintf("0x%x", v) + } + return &api.Response{ + Uids: uidStrings, + Txn: &api.TxnContext{StartTs: in.StartTs}, + }, nil + } + + // Query only + return c.engine.query(ctx, c.ns, in.Query, in.Vars) +} + +// handleUpsert handles upsert requests (query + mutations) for embedded mode. +// It executes the query first to resolve variable UIDs, then substitutes +// uid(var) references in mutations before applying them. +func (c *embeddedDgraphClient) handleUpsert(ctx context.Context, in *api.Request) (*api.Response, error) { + // Step 1: Transform the upsert query to remove variable definitions + // dgman sends queries like: q_1_0(...) { u_1_0 as uid } + // We need to convert to: q_1_0(...) { uid } and map results back + transformedQuery, varMappings := transformUpsertQuery(in.Query) + + // Step 2: Execute the transformed query + queryResp, err := c.engine.query(ctx, c.ns, transformedQuery, in.Vars) + if err != nil { + return nil, fmt.Errorf("upsert query failed: %w", err) + } + + // Step 3: Parse query results and map to variable names + varUIDs, err := extractVarUIDsWithMapping(queryResp.Json, varMappings) + if err != nil { + return nil, fmt.Errorf("failed to extract var UIDs: %w", err) + } + + // Step 4: Substitute uid(var) references in mutations + for _, mu := range in.Mutations { + substituteUIDVars(mu, varUIDs) + } + + // Step 5: Apply mutations using embedded path + uids, err := c.ns.Mutate(ctx, in.Mutations) + if err != nil { + return nil, err + } + + // Convert uids map to response format + uidStrings := make(map[string]string) + for k, v := range uids { + key := k + if strings.HasPrefix(k, "_:") { + key = k[2:] + } + uidStrings[key] = fmt.Sprintf("0x%x", v) + } + + return &api.Response{ + Json: queryResp.Json, + Uids: uidStrings, + Txn: &api.TxnContext{StartTs: in.StartTs}, + }, nil +} + +func (c *embeddedDgraphClient) Alter( + ctx context.Context, + in *api.Operation, + opts ...grpc.CallOption, +) (*api.Payload, error) { + if in.DropAll { + if err := c.engine.DropAll(ctx); err != nil { + return nil, err + } + return &api.Payload{}, nil + } + if in.DropOp == api.Operation_DATA { + if err := c.engine.dropData(ctx, c.ns); 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 + } + } + return &api.Payload{}, nil +} + +func (c *embeddedDgraphClient) CommitOrAbort( + ctx context.Context, + in *api.TxnContext, + opts ...grpc.CallOption, +) (*api.TxnContext, error) { + return c.engine.commitOrAbort(ctx, c.ns, in) +} + +func (c *embeddedDgraphClient) CheckVersion( + ctx context.Context, + in *api.Check, + opts ...grpc.CallOption, +) (*api.Version, error) { + return &api.Version{Tag: "embedded"}, nil +} + +func (c *embeddedDgraphClient) RunDQL( + ctx context.Context, + in *api.RunDQLRequest, + opts ...grpc.CallOption, +) (*api.Response, error) { + return c.engine.query(ctx, c.ns, in.DqlQuery, in.Vars) +} + +func (c *embeddedDgraphClient) AllocateIDs( + ctx context.Context, + in *api.AllocateIDsRequest, + opts ...grpc.CallOption, +) (*api.AllocateIDsResponse, error) { + // Not used in embedded mode - UIDs are allocated during mutation + return &api.AllocateIDsResponse{}, nil +} + +func (c *embeddedDgraphClient) UpdateExtSnapshotStreamingState( + ctx context.Context, + in *api.UpdateExtSnapshotStreamingStateRequest, + opts ...grpc.CallOption, +) (*api.UpdateExtSnapshotStreamingStateResponse, error) { + // Not supported in embedded mode + return &api.UpdateExtSnapshotStreamingStateResponse{}, nil +} + +func (c *embeddedDgraphClient) StreamExtSnapshot( + ctx context.Context, + opts ...grpc.CallOption, +) (api.Dgraph_StreamExtSnapshotClient, error) { + // Not supported in embedded mode + return nil, nil +} + +func (c *embeddedDgraphClient) CreateNamespace( + ctx context.Context, + in *api.CreateNamespaceRequest, + opts ...grpc.CallOption, +) (*api.CreateNamespaceResponse, error) { + ns, err := c.engine.CreateNamespace() + if err != nil { + return nil, err + } + return &api.CreateNamespaceResponse{Namespace: ns.ID()}, nil +} + +func (c *embeddedDgraphClient) DropNamespace( + ctx context.Context, + in *api.DropNamespaceRequest, + opts ...grpc.CallOption, +) (*api.DropNamespaceResponse, error) { + // Not implemented yet + return &api.DropNamespaceResponse{}, nil +} + +func (c *embeddedDgraphClient) ListNamespaces( + ctx context.Context, + in *api.ListNamespacesRequest, + opts ...grpc.CallOption, +) (*api.ListNamespacesResponse, error) { + // Not implemented yet + return &api.ListNamespacesResponse{}, nil +} + +// varAsRegex matches "varname as uid" patterns in query blocks +var varAsRegex = regexp.MustCompile(`(\w+)\s+as\s+uid`) + +// transformUpsertQuery transforms a dgman upsert query to remove variable definitions. +// Input: { q_1_0(...) { u_1_0 as uid } } +// Output: { q_1_0(...) { uid } } and mapping {"q_1_0": "u_1_0"} +func transformUpsertQuery(query string) (string, map[string]string) { + varMappings := make(map[string]string) + + // Find all "varname as uid" patterns and extract the variable names + // dgman uses pattern: q_N_M for query blocks, u_N_M for uid variables + matches := varAsRegex.FindAllStringSubmatch(query, -1) + for _, match := range matches { + if len(match) >= 2 { + varName := match[1] // e.g., "u_1_0" + // Convert u_N_M to q_N_M for block name mapping + if strings.HasPrefix(varName, "u_") { + blockName := "q_" + varName[2:] + varMappings[blockName] = varName + } + } + } + + // Replace "varname as uid" with just "uid" + transformedQuery := varAsRegex.ReplaceAllString(query, "uid") + + return transformedQuery, varMappings +} + +// extractVarUIDsWithMapping parses query results and maps block names to variable names +func extractVarUIDsWithMapping(jsonData []byte, varMappings map[string]string) (map[string]string, error) { + if len(jsonData) == 0 { + return make(map[string]string), nil + } + + var result map[string][]map[string]interface{} + if err := json.Unmarshal(jsonData, &result); err != nil { + return nil, err + } + + varUIDs := make(map[string]string) + for blockName, nodes := range result { + if len(nodes) > 0 { + if uid, ok := nodes[0]["uid"].(string); ok { + // Map block name to variable name + if varName, ok := varMappings[blockName]; ok { + varUIDs[varName] = uid + } else { + varUIDs[blockName] = uid + } + } + } + } + return varUIDs, nil +} + +// uidVarRegex matches uid(varname) patterns in mutation data +var uidVarRegex = regexp.MustCompile(`uid\(([^)]+)\)`) + +// substituteUIDVars replaces uid(var) references in mutations with actual UIDs +func substituteUIDVars(mu *api.Mutation, varUIDs map[string]string) { + // Handle SetJson + if len(mu.SetJson) > 0 { + mu.SetJson = []byte(substituteInString(string(mu.SetJson), varUIDs)) + } + // Handle DeleteJson + if len(mu.DeleteJson) > 0 { + mu.DeleteJson = []byte(substituteInString(string(mu.DeleteJson), varUIDs)) + } + // Handle Set NQuads + for _, nq := range mu.Set { + substituteInNQuad(nq, varUIDs) + } + // Handle Del NQuads + for _, nq := range mu.Del { + substituteInNQuad(nq, varUIDs) + } +} + +func substituteInString(s string, varUIDs map[string]string) string { + return uidVarRegex.ReplaceAllStringFunc(s, func(match string) string { + // Extract variable name from uid(varname) + varName := match[4 : len(match)-1] // strip "uid(" and ")" + if uid, ok := varUIDs[varName]; ok { + return uid + } + // If no UID found, convert to blank node for new entity + return "_:uid(" + varName + ")" + }) +} + +func substituteInNQuad(nq *api.NQuad, varUIDs map[string]string) { + // Substitute in Subject + if strings.HasPrefix(nq.Subject, "uid(") { + varName := nq.Subject[4 : len(nq.Subject)-1] + if uid, ok := varUIDs[varName]; ok { + nq.Subject = uid + } + } + // Substitute in ObjectId + if strings.HasPrefix(nq.ObjectId, "uid(") { + varName := nq.ObjectId[4 : len(nq.ObjectId)-1] + if uid, ok := varUIDs[varName]; ok { + nq.ObjectId = uid + } + } +} diff --git a/engine.go b/engine.go index 6e8845e..d9d236d 100644 --- a/engine.go +++ b/engine.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,30 +7,30 @@ package modusgraph import ( "context" + "encoding/json" "errors" "fmt" "path" "runtime" "strconv" + "strings" "sync" "sync/atomic" "time" "github.com/dgraph-io/badger/v4" - "github.com/dgraph-io/dgo/v250" "github.com/dgraph-io/dgo/v250/protos/api" + "github.com/dgraph-io/dgraph/v25/dql" + "github.com/dgraph-io/dgraph/v25/edgraph" + "github.com/dgraph-io/dgraph/v25/hooks" + "github.com/dgraph-io/dgraph/v25/posting" + "github.com/dgraph-io/dgraph/v25/protos/pb" + "github.com/dgraph-io/dgraph/v25/query" + "github.com/dgraph-io/dgraph/v25/schema" + "github.com/dgraph-io/dgraph/v25/worker" + "github.com/dgraph-io/dgraph/v25/x" "github.com/dgraph-io/ristretto/v2/z" "github.com/go-logr/logr" - "github.com/hypermodeinc/dgraph/v25/dql" - "github.com/hypermodeinc/dgraph/v25/edgraph" - "github.com/hypermodeinc/dgraph/v25/posting" - "github.com/hypermodeinc/dgraph/v25/protos/pb" - "github.com/hypermodeinc/dgraph/v25/query" - "github.com/hypermodeinc/dgraph/v25/schema" - "github.com/hypermodeinc/dgraph/v25/worker" - "github.com/hypermodeinc/dgraph/v25/x" - "google.golang.org/grpc" - "google.golang.org/grpc/test/bufconn" ) var ( @@ -57,9 +57,7 @@ type Engine struct { // points to default / 0 / galaxy namespace db0 *Namespace - listener *bufconn.Listener - server *grpc.Server - logger logr.Logger + logger logr.Logger } // NewEngine returns a new modusGraph instance. @@ -88,6 +86,8 @@ func NewEngine(conf Config) (*Engine, error) { x.Config.MaxRetries = 10 x.Config.Limit = z.NewSuperFlag("max-pending-queries=100000") x.Config.LimitNormalizeNode = conf.limitNormalizeNode + x.Config.LimitQueryEdge = 1000000 // Allow up to 1M edges in upsert queries + x.Config.LimitMutationsNquad = 1000000 // Allow up to 1M nquads in mutations // initialize each package edgraph.Init() @@ -106,13 +106,59 @@ func NewEngine(conf Config) (*Engine, error) { engine.logger.Error(err, "Failed to reset database") return nil, fmt.Errorf("error resetting db: %w", err) } + + // Enable embedded mode with Zero hooks to bypass distributed Zero node + hooks.Enable(&hooks.Config{ + ZeroHooks: hooks.ZeroHooksFns{ + AssignUIDsFn: func(ctx context.Context, num *pb.Num) (*pb.AssignedIds, error) { + num.Type = pb.Num_UID + return engine.z.nextUIDs(num) + }, + AssignTimestampsFn: func(ctx context.Context, num *pb.Num) (*pb.AssignedIds, error) { + ts, err := engine.z.nextTs() + if err != nil { + return nil, err + } + return &pb.AssignedIds{StartId: ts, EndId: ts}, nil + }, + AssignNsIDsFn: func(ctx context.Context, num *pb.Num) (*pb.AssignedIds, error) { + num.Type = pb.Num_NS_ID + nsID, err := engine.z.nextNamespace() + if err != nil { + return nil, err + } + return &pb.AssignedIds{StartId: nsID, EndId: nsID}, nil + }, + CommitOrAbortFn: func(ctx context.Context, tc *api.TxnContext) (*api.TxnContext, error) { + if tc.Aborted { + return &api.TxnContext{StartTs: tc.StartTs, Aborted: true}, nil + } + commitTs, err := engine.z.nextTs() + if err != nil { + return nil, err + } + // Apply the commit to the oracle + delta := &pb.OracleDelta{ + Txns: []*pb.TxnStatus{{StartTs: tc.StartTs, CommitTs: commitTs}}, + } + posting.Oracle().ProcessDelta(delta) + return &api.TxnContext{StartTs: tc.StartTs, CommitTs: commitTs}, nil + }, + ApplyMutationsFn: func(ctx context.Context, m *pb.Mutations) (*api.TxnContext, error) { + // Create a proposal with the mutations + p := &pb.Proposal{Mutations: m} + err := worker.ApplyMutations(ctx, p) + return &api.TxnContext{StartTs: m.StartTs}, err + }, + }, + }) + // Store the engine as the active instance activeEngine = engine x.UpdateHealthStatus(true) engine.db0 = &Namespace{id: 0, engine: engine} - engine.listener, engine.server = setupBufconnServer(engine) return engine, nil } @@ -126,15 +172,6 @@ func Shutdown() { singleton.Store(false) } -func (engine *Engine) GetClient() (*dgo.Dgraph, error) { - engine.logger.V(2).Info("Getting Dgraph client from engine") - client, err := createDgraphClient(context.Background(), engine.listener) - if err != nil { - engine.logger.Error(err, "Failed to create Dgraph client") - } - return client, err -} - func (engine *Engine) CreateNamespace() (*Namespace, error) { engine.mutex.RLock() defer engine.mutex.RUnlock() @@ -348,6 +385,11 @@ func (engine *Engine) mutateWithDqlMutation(ctx context.Context, ns *Namespace, return nil, ErrClosedEngine } + // Check unique constraints before applying mutations + if err := engine.verifyUniqueConstraints(ctx, ns, edges, newUids); err != nil { + return nil, err + } + startTs, err := engine.z.nextTs() if err != nil { return nil, err @@ -382,6 +424,148 @@ func (engine *Engine) mutateWithDqlMutation(ctx context.Context, ns *Namespace, }) } +// verifyUniqueConstraints checks that mutations don't violate @unique constraints +func (engine *Engine) verifyUniqueConstraints( + ctx context.Context, + ns *Namespace, + edges []*pb.DirectedEdge, + newUids map[string]uint64, +) error { + namespace := ns.ID() + + // Track values seen within this mutation batch for in-batch duplicate detection + // Key: "predName:value", Value: subject UID + seenValues := make(map[string]uint64) + + for _, edge := range edges { + // Skip delete operations + if edge.Op == pb.DirectedEdge_DEL { + continue + } + + // Get predicate name - edge.Attr may or may not have namespace prefix + predName := edge.Attr + if strings.Contains(predName, x.NsSeparator) { + predName = x.ParseAttr(edge.Attr) + } + + // Check if predicate has @unique constraint + predSchema, ok := schema.State().Get(ctx, x.NamespaceAttr(namespace, predName)) + if !ok || !predSchema.Unique { + continue + } + + // Get the value being set + val := edge.Value + if len(val) == 0 { + continue + } + + valStr := string(val) + subjectUID := edge.Entity + + // Check for in-batch duplicates first + key := predName + ":" + valStr + if existingUID, seen := seenValues[key]; seen { + if existingUID != subjectUID { + return &UniqueError{ + Field: predName, + Value: valStr, + UID: fmt.Sprintf("0x%x", existingUID), + } + } + } + seenValues[key] = subjectUID + + // Build query to check for existing value in database + queryStr := fmt.Sprintf(`{ + check(func: eq(%s, %q)) { + uid + } + }`, predName, valStr) + + resp, err := engine.queryWithLock(ctx, ns, queryStr, nil) + if err != nil { + return fmt.Errorf("error checking unique constraint for %s: %w", predName, err) + } + + // Parse response to check if any existing UIDs found + existingUID, found := parseUniqueCheckResponse(resp.Json) + if !found { + continue + } + + // If found, check if it's the same UID (update case is allowed) + if existingUID != subjectUID { + return &UniqueError{ + Field: predName, + Value: valStr, + UID: fmt.Sprintf("0x%x", existingUID), + } + } + } + + return nil +} + +// parseUniqueCheckResponse parses the query response to find existing UID +func parseUniqueCheckResponse(jsonData []byte) (uint64, bool) { + if len(jsonData) == 0 { + return 0, false + } + + var result struct { + Check []struct { + UID string `json:"uid"` + } `json:"check"` + } + + if err := json.Unmarshal(jsonData, &result); err != nil { + return 0, false + } + + if len(result.Check) == 0 { + return 0, false + } + + uidStr := result.Check[0].UID + if uidStr == "" { + return 0, false + } + + // Parse UID (format: "0x123") + if len(uidStr) > 2 && uidStr[:2] == "0x" { + uid, err := strconv.ParseUint(uidStr[2:], 16, 64) + if err != nil { + return 0, false + } + return uid, true + } + + return 0, false +} + +func (engine *Engine) commitOrAbort(ctx context.Context, ns *Namespace, tc *api.TxnContext) (*api.TxnContext, error) { + if tc.Aborted { + return tc, nil + } + + // For commit, use an empty mutation to trigger the commit mechanism + emptyMutation := &api.Mutation{ + CommitNow: true, + } + + _, err := ns.Mutate(ctx, []*api.Mutation{emptyMutation}) + if err != nil { + return nil, fmt.Errorf("error committing transaction: %w", err) + } + + return &api.TxnContext{ + StartTs: tc.StartTs, + CommitTs: tc.StartTs + 1, + }, nil +} + func (engine *Engine) Load(ctx context.Context, schemaPath, dataPath string) error { return engine.db0.Load(ctx, schemaPath, dataPath) } @@ -405,6 +589,7 @@ func (engine *Engine) Close() { engine.isOpen.Store(false) x.UpdateHealthStatus(false) + hooks.Disable() posting.Cleanup() worker.State.Dispose() diff --git a/engine_test.go b/engine_test.go index 3ed9407..a7e2874 100644 --- a/engine_test.go +++ b/engine_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/errors.go b/errors.go index d882354..a4e3ef4 100644 --- a/errors.go +++ b/errors.go @@ -1,12 +1,54 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ package modusgraph -import dg "github.com/dolan-in/dgman/v2" +import ( + "regexp" + "strings" + + dg "github.com/dolan-in/dgman/v2" +) // UniqueError represents an error that occurs when attempting to insert or update // a node that would violate a unique constraint. type UniqueError = dg.UniqueError + +// parseUniqueError attempts to parse a Dgraph unique constraint violation error +// and convert it to a UniqueError. Returns nil if the error is not a unique constraint violation. +func parseUniqueError(err error) *UniqueError { + if err == nil { + return nil + } + + errStr := err.Error() + + // Match Dgraph unique constraint error format: + // "could not insert duplicate value [Value] for predicate [predicate]" + re := regexp.MustCompile(`could not insert duplicate value \[([^\]]+)\] for predicate \[([^\]]+)\]`) + matches := re.FindStringSubmatch(errStr) + if len(matches) == 3 { + return &UniqueError{ + Field: matches[2], + Value: matches[1], + } + } + + // Also check for dgman's error format: + // " with field=value already exists at uid=0x..." + if strings.Contains(errStr, "already exists at uid=") { + re2 := regexp.MustCompile(`with ([^=]+)=([^ ]+) already exists at uid=([^ ]+)`) + matches2 := re2.FindStringSubmatch(errStr) + if len(matches2) == 4 { + return &UniqueError{ + Field: matches2[1], + Value: matches2[2], + UID: matches2[3], + } + } + } + + return nil +} diff --git a/examples/basic/main.go b/examples/basic/main.go index 64c3d1d..f6c5bfe 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/examples/load/main.go b/examples/load/main.go index 6136f6b..16afbd1 100644 --- a/examples/load/main.go +++ b/examples/load/main.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -21,7 +21,7 @@ import ( ) const ( - baseURL = "https://github.com/hypermodeinc/dgraph-benchmarks/blob/main/data" + baseURL = "https://github.com/dgraph-io/benchmarks/blob/main/data" oneMillionSchema = baseURL + "/1million.schema?raw=true" oneMillionRDF = baseURL + "/1million.rdf.gz?raw=true" ) diff --git a/examples/readme/main.go b/examples/readme/main.go index 254e8b0..8ab28bc 100644 --- a/examples/readme/main.go +++ b/examples/readme/main.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/go.mod b/go.mod index 24b525e..5ec767e 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,18 @@ go 1.25.6 require ( github.com/cavaliergopher/grab/v3 v3.0.1 - github.com/dgraph-io/badger/v4 v4.8.0 + github.com/dgraph-io/badger/v4 v4.9.0 github.com/dgraph-io/dgo/v250 v250.0.0 + github.com/dgraph-io/dgraph/v25 v25.1.1-0.20260202212142-15ef722329b1 github.com/dgraph-io/ristretto/v2 v2.3.0 - github.com/dolan-in/dgman/v2 v2.2.0-preview1 + github.com/dolan-in/dgman/v2 v2.2.0-preview2 github.com/go-logr/logr v1.4.3 github.com/go-logr/stdr v1.2.2 github.com/go-playground/validator/v10 v10.30.1 - github.com/hypermodeinc/dgraph/v25 v25.0.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.19.0 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.11 ) require ( @@ -25,45 +25,44 @@ require ( github.com/go-ini/ini v1.67.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect + github.com/klauspost/crc32 v1.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/minio/crc64nvme v1.0.2 // indirect - github.com/minio/minio-go/v7 v7.0.95 // indirect + github.com/minio/crc64nvme v1.1.1 // indirect + github.com/minio/minio-go/v7 v7.0.98 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/tinylib/msgp v1.3.0 // indirect + github.com/tinylib/msgp v1.6.3 // indirect github.com/twpayne/go-geom v1.6.1 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.1 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 // indirect ) require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect - github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect - github.com/IBM/sarama v1.46.1 // indirect + github.com/HdrHistogram/hdrhistogram-go v1.2.0 // indirect + github.com/IBM/sarama v1.46.3 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/bits-and-blooms/bitset v1.24.4 // indirect // trunk-ignore(osv-scanner/GHSA-9w9f-6mg8-jp7w) - github.com/blevesearch/bleve/v2 v2.5.2 // indirect - github.com/blevesearch/bleve_index_api v1.2.8 // indirect + github.com/blevesearch/bleve/v2 v2.5.7 // indirect + github.com/blevesearch/bleve_index_api v1.3.0 // indirect github.com/blevesearch/geo v0.2.4 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/segment v0.9.1 // indirect @@ -84,17 +83,17 @@ require ( github.com/eapache/queue v1.1.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/golang/geo v0.0.0-20250813021530-247f39904721 // indirect + github.com/golang/geo v0.0.0-20251229110840-fd652594c94c // indirect github.com/golang/glog v1.2.5 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/codesearch v1.2.0 // indirect - github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect + github.com/google/flatbuffers v25.12.19+incompatible // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -105,7 +104,7 @@ require ( github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect - github.com/hashicorp/vault/api v1.21.0 // indirect + github.com/hashicorp/vault/api v1.22.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -113,28 +112,28 @@ require ( github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.11 // indirect + github.com/klauspost/compress v1.18.2 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pierrec/lz4/v4 v4.1.23 // indirect github.com/pkg/profile v1.7.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/prometheus/statsd_exporter v0.28.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -142,19 +141,19 @@ require ( github.com/viterin/vek v0.4.3 // indirect github.com/xdg/scram v1.0.5 // indirect github.com/xdg/stringprep v1.0.3 // indirect - go.etcd.io/etcd/raft/v3 v3.5.23 // indirect + go.etcd.io/etcd/raft/v3 v3.5.26 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.38.0 // indirect - golang.org/x/text v0.32.0 // indirect - golang.org/x/time v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect - google.golang.org/grpc v1.75.1 + go.uber.org/zap v1.27.1 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect + google.golang.org/grpc v1.78.0 gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f0f623e..90a9d64 100644 --- a/go.sum +++ b/go.sum @@ -38,17 +38,16 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= -github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/IBM/sarama v1.46.1 h1:AlDkvyQm4LKktoQZxv0sbTfH3xukeH7r/UFBbUmFV9M= -github.com/IBM/sarama v1.46.1/go.mod h1:ipyOREIx+o9rMSrrPGLZHGuT0mzecNzKd19Quq+Q8AA= +github.com/HdrHistogram/hdrhistogram-go v1.2.0 h1:XMJkDWuz6bM9Fzy7zORuVFKH7ZJY41G2q8KWhVGkNiY= +github.com/HdrHistogram/hdrhistogram-go v1.2.0/go.mod h1:CiIeGiHSd06zjX+FypuEJ5EQ07KKtxZ+8J6hszwVQig= +github.com/IBM/sarama v1.46.3 h1:njRsX6jNlnR+ClJ8XmkO+CM4unbrNr/2vB5KK6UA+IE= +github.com/IBM/sarama v1.46.3/go.mod h1:GTUYiF9DMOZVe3FwyGT+dtSPceGFIgA+sPc5u6CBwko= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -67,12 +66,12 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= -github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/blevesearch/bleve/v2 v2.5.2 h1:Ab0r0MODV2C5A6BEL87GqLBySqp/s9xFgceCju6BQk8= -github.com/blevesearch/bleve/v2 v2.5.2/go.mod h1:5Dj6dUQxZM6aqYT3eutTD/GpWKGFSsV8f7LDidFbwXo= -github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y= -github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= +github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE= +github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8= +github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA= +github.com/blevesearch/bleve_index_api v1.3.0 h1:DsMpWVjFNlBw9/6pyWf59XoqcAkhHj3H0UWiQsavb6E= +github.com/blevesearch/bleve_index_api v1.3.0/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko= github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk= github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8= github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= @@ -115,15 +114,16 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= -github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= +github.com/dgraph-io/badger/v4 v4.9.0 h1:tpqWb0NewSrCYqTvywbcXOhQdWcqephkVkbBmaaqHzc= +github.com/dgraph-io/badger/v4 v4.9.0/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0= github.com/dgraph-io/dgo/v250 v250.0.0 h1:zkVj8EOgNOK3s5XFEK7CJKRdftWqg5K6qGs4HEH5TcY= github.com/dgraph-io/dgo/v250 v250.0.0/go.mod h1:OVSaapUnuqaY4beLe98CajukINwbVm0JRNp0SRBCz/w= +github.com/dgraph-io/dgraph/v25 v25.1.1-0.20260202212142-15ef722329b1 h1:Z190qNq95esLskfMh4Zvdq7fuk3FCLg6Y8p4Uulvzdk= +github.com/dgraph-io/dgraph/v25 v25.1.1-0.20260202212142-15ef722329b1/go.mod h1:ia5/Xt0we+ZZcLmfZQunbFSmxRO2EX7VCsuWxbmEfF4= github.com/dgraph-io/gqlgen v0.13.2 h1:TNhndk+eHKj5qE7BenKKSYdSIdOGhLqxR1rCiMso9KM= github.com/dgraph-io/gqlgen v0.13.2/go.mod h1:iCOrOv9lngN7KAo+jMgvUPVDlYHdf7qDwsTkQby2Sis= github.com/dgraph-io/gqlparser/v2 v2.1.1/go.mod h1:MYS4jppjyx8b9tuUtjV7jU1UFZK6P9fvO8TsIsQtRKU= @@ -142,14 +142,14 @@ github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7c github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= -github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dolan-in/dgman/v2 v2.2.0-preview1 h1:WNJSXhIgsDLYm7UtV9tEQoqhI6aslRSRfncNZM7BmSc= -github.com/dolan-in/dgman/v2 v2.2.0-preview1/go.mod h1:sL8WeQ6yPsb9GwuhKLLGGKmWRjqbwjQCoJNhF/I5zxQ= +github.com/dolan-in/dgman/v2 v2.2.0-preview2 h1:BuyUS4KesesYteG8h9fQt2rUWfgk1Sst9ACjkN9b5Yo= +github.com/dolan-in/dgman/v2 v2.2.0-preview2/go.mod h1:sL8WeQ6yPsb9GwuhKLLGGKmWRjqbwjQCoJNhF/I5zxQ= github.com/dolan-in/reflectwalk v1.0.2-0.20210101124621-dc2073a29d71 h1:v3bErDrPApxsyBlz8/8nFTCb7Ai0wecA8TokfEHIQ80= github.com/dolan-in/reflectwalk v1.0.2-0.20210101124621-dc2073a29d71/go.mod h1:Y9TyDkSL5jQ18ZnDaSxOdCUhbb5SCeamqYFQ7LYxxFs= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -171,7 +171,6 @@ github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -186,8 +185,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= -github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -213,13 +212,11 @@ github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -227,9 +224,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20250813021530-247f39904721 h1:Hlto+T7Ba4CJM4SN8WiA9mw3MdMUboxWsWBaUzRuJuA= -github.com/golang/geo v0.0.0-20250813021530-247f39904721/go.mod h1:AN0OjM34c3PbjAsX+QNma1nYtJtRxl+s9MZNV7S+efw= +github.com/golang/geo v0.0.0-20251229110840-fd652594c94c h1:YwQmVsfImAI/JdPFXE5EVwZHSC+zdtcJI2z/05O30Zw= +github.com/golang/geo v0.0.0-20251229110840-fd652594c94c/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -270,8 +266,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/codesearch v1.2.0 h1:VlyAH+AntnIbGGArOUs6sEBdPVwYvf1e8Uw3/TC77cA= github.com/google/codesearch v1.2.0/go.mod h1:9wQjQDVAP7Mvt96tw1KqVeXncdBLOWUYdxRiHlsG6Xc= -github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= -github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -297,8 +293,8 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4= -github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -311,8 +307,8 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -339,12 +335,10 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/vault/api v1.21.0 h1:Xej4LJETV/spWRdjreb2vzQhEZt4+B5yxHAObfQVDOs= -github.com/hashicorp/vault/api v1.21.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/hypermodeinc/dgraph/v25 v25.0.0 h1:pbEkoDC/tXzxxNlrQ1xb+XJ9Ct62CMtE+pSqq2Ik36E= -github.com/hypermodeinc/dgraph/v25 v25.0.0/go.mod h1:oBNDzgh/7ZU9CerhYDQW/otWLToUEnQWlojTvNLMeS4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= @@ -373,17 +367,18 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.3/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= -github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= +github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -410,12 +405,12 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg= -github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= +github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.95 h1:ywOUPg+PebTMTzn9VDsoFJy32ZuARN9zhB+K3IYEvYU= -github.com/minio/minio-go/v7 v7.0.95/go.mod h1:wOOX3uxS334vImCNRVyIDdXX9OsXDm89ToynKgqUKlo= +github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0= +github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -434,7 +429,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -448,8 +442,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= +github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -480,24 +474,24 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/statsd_exporter v0.28.0 h1:S3ZLyLm/hOKHYZFOF0h4zYmd0EeKyPF9R1pFBYXUgYY= github.com/prometheus/statsd_exporter v0.28.0/go.mod h1:Lq41vNkMLfiPANmI+uHb5/rpFFUTxPXiiNpmsAYLvDI= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= @@ -505,8 +499,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -518,14 +512,12 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -541,7 +533,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -550,8 +541,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= -github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= +github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s= +github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/twpayne/go-geom v1.6.1 h1:iLE+Opv0Ihm/ABIcvQFGIiFBXd76oBIar9drAwHFhR4= github.com/twpayne/go-geom v1.6.1/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= @@ -570,10 +561,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/etcd/client/pkg/v3 v3.5.23 h1:RzwVV28JgOwGl5TUjA47s9IWxl5qQjM2VqSh8wjFFLM= -go.etcd.io/etcd/client/pkg/v3 v3.5.23/go.mod h1:IdIjxGUGNy+8HWeVlbBXDqmPqe+n8GsVGVYnccAEZ5o= -go.etcd.io/etcd/raft/v3 v3.5.23 h1:NhCdh4xz1VsrqHd2c+h6SLvhE95B1Hs7K+ESaAs6LLQ= -go.etcd.io/etcd/raft/v3 v3.5.23/go.mod h1:NJz9BGkhGvru47lIc1wL0QHsg5yvHTy6tUpEqM69ERM= +go.etcd.io/etcd/client/pkg/v3 v3.5.26 h1:ar2yomCJTa8i+3XMkny5pwScCxlmQ8dGHZDN/qssQ7E= +go.etcd.io/etcd/client/pkg/v3 v3.5.26/go.mod h1:9UifTCiLfUjX1oyEYRu0QxqrvskhZqCdq4Osw78VMnk= +go.etcd.io/etcd/raft/v3 v3.5.26 h1:jHH11ljHDDUSMZN3ONYhtyejoVJ1ZrMJp8aNtu+FPAo= +go.etcd.io/etcd/raft/v3 v3.5.26/go.mod h1:zS+k0kTQRjEYvLCj6feNVUykAM38SpPs/2TGxg5JQ/s= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -582,36 +573,36 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -622,12 +613,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -637,9 +625,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -661,8 +648,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -702,8 +689,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -773,13 +760,13 @@ golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -788,19 +775,17 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -848,12 +833,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -905,10 +886,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= -google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 h1:X9z6obt+cWRX8XjDVOn+SZWhWe5kZHm46TThU9j+jss= +google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3/go.mod h1:dd646eSK+Dk9kxVBl1nChEOhJPtMXriCcVb4x3o6J+E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -922,8 +903,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -938,13 +919,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -966,7 +946,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/insert_test.go b/insert_test.go index 64d2c12..cd8e64e 100644 --- a/insert_test.go +++ b/insert_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ package modusgraph_test import ( "context" + "errors" "os" "strings" "testing" @@ -81,8 +82,9 @@ func TestClientInsert(t *testing.T) { err = client.Insert(ctx, &entity) require.Error(t, err, "Insert should fail") if strings.HasPrefix(tc.uri, "file://") { - require.IsType(t, &modusgraph.UniqueError{}, err, "Error should be a UniqueError") - require.Equal(t, uid, err.(*modusgraph.UniqueError).UID, "UID should match") + var uniqueErr *modusgraph.UniqueError + require.True(t, errors.As(err, &uniqueErr), "Error should be a UniqueError") + require.Equal(t, uid, uniqueErr.UID, "UID should match") } var entities []TestEntity @@ -156,6 +158,83 @@ func TestEmbeddedInsert(t *testing.T) { } } +func TestEmbeddedUniqueConstraintViolation(t *testing.T) { + testCases := []struct { + name string + uri string + skip bool + }{ + { + name: "EmbeddedUniqueViolationWithFileURI", + uri: "file://" + GetTempDir(t), + }, + { + name: "EmbeddedUniqueViolationWithDgraphURI", + 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() + + // Insert first entity with embedded unique entity + firstEntity := OuterTestEntity{ + Name: "First Outer Entity", + Entity: &TestEntity{ + Name: "Unique Inner Name", + Description: "First description", + CreatedAt: time.Now(), + }, + } + + err := client.Insert(ctx, &firstEntity) + require.NoError(t, err, "First insert should succeed") + require.NotEmpty(t, firstEntity.UID, "First UID should be assigned") + require.NotEmpty(t, firstEntity.Entity.UID, "Embedded entity UID should be assigned") + + // Try to insert second entity with same embedded unique name + secondEntity := OuterTestEntity{ + Name: "Second Outer Entity", + Entity: &TestEntity{ + Name: "Unique Inner Name", // Same name - should violate unique constraint + Description: "Second description", + CreatedAt: time.Now(), + }, + } + + err = client.Insert(ctx, &secondEntity) + require.Error(t, err, "Second insert should fail due to unique constraint violation in embedded entity") + + // Verify the error is a UniqueError + var uniqueErr *modusgraph.UniqueError + require.True(t, errors.As(err, &uniqueErr), "Error should be a UniqueError") + // UID is only available for file URIs (embedded engine checks the database) + // Dgraph errors don't include the UID of the existing entity + if strings.HasPrefix(tc.uri, "file://") { + require.Equal(t, firstEntity.Entity.UID, uniqueErr.UID, "UID should match the first embedded entity") + } + + // Verify only the first entity exists + var entities []OuterTestEntity + err = client.Query(ctx, OuterTestEntity{}).Nodes(&entities) + require.NoError(t, err, "Query should succeed") + require.Len(t, entities, 1, "There should only be one outer entity") + require.Equal(t, "First Outer Entity", entities[0].Name, "Should be the first entity") + require.Equal(t, "Unique Inner Name", entities[0].Entity.Name, "Embedded entity name should match") + }) + } +} + func TestClientInsertMultipleEntities(t *testing.T) { testCases := []struct { diff --git a/internal_test.go b/internal_test.go index 81af469..75cd161 100644 --- a/internal_test.go +++ b/internal_test.go @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + package modusgraph import ( diff --git a/live.go b/live.go index f17fce5..0c89711 100644 --- a/live.go +++ b/live.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -14,9 +14,9 @@ import ( "time" "github.com/dgraph-io/dgo/v250/protos/api" - "github.com/hypermodeinc/dgraph/v25/chunker" - "github.com/hypermodeinc/dgraph/v25/filestore" - "github.com/hypermodeinc/dgraph/v25/x" + "github.com/dgraph-io/dgraph/v25/chunker" + "github.com/dgraph-io/dgraph/v25/filestore" + "github.com/dgraph-io/dgraph/v25/x" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) diff --git a/load_test/live_benchmark_test.go b/load_test/live_benchmark_test.go index fa225e4..4c0ec77 100644 --- a/load_test/live_benchmark_test.go +++ b/load_test/live_benchmark_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/load_test/live_test.go b/load_test/live_test.go index f44e826..dd397d2 100644 --- a/load_test/live_test.go +++ b/load_test/live_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -14,15 +14,15 @@ import ( "time" "github.com/cavaliergopher/grab/v3" + "github.com/dgraph-io/dgraph/v25/dgraphapi" + "github.com/dgraph-io/dgraph/v25/systest/1million/common" "github.com/go-logr/stdr" - "github.com/hypermodeinc/dgraph/v25/dgraphapi" - "github.com/hypermodeinc/dgraph/v25/systest/1million/common" "github.com/matthewmcneely/modusgraph" "github.com/stretchr/testify/require" ) const ( - baseURL = "https://github.com/hypermodeinc/dgraph-benchmarks/blob/main/data" + baseURL = "https://github.com/dgraph-io/benchmarks/blob/main/data" oneMillionSchema = baseURL + "/1million.schema?raw=true" oneMillionRDF = baseURL + "/1million.rdf.gz?raw=true" DbSchema = ` diff --git a/load_test/long_running_benchmark_test.go b/load_test/long_running_benchmark_test.go index b97cffb..872c0f0 100644 --- a/load_test/long_running_benchmark_test.go +++ b/load_test/long_running_benchmark_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/mutate.go b/mutate.go index 56483ee..e10aa8b 100644 --- a/mutate.go +++ b/mutate.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -12,11 +12,17 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" dg "github.com/dolan-in/dgman/v2" ) +// parseNamespaceID parses a namespace string to uint64 +func parseNamespaceID(ns string) (uint64, error) { + return strconv.ParseUint(ns, 10, 64) +} + // checkObject validates the passed obj. If it's a slice or a pointer // to a slice, it returns the first element of the slice. Ultimately, // the object discovered must be pointer. @@ -63,86 +69,24 @@ func (c client) process(ctx context.Context, if err != nil { return err } - } - - client, err := c.pool.get() - if err != nil { - c.logger.Error(err, "Failed to get client from pool") - return err - } - defer c.pool.put(client) - - tx := dg.NewTxnContext(ctx, client).SetCommitNow() - uids, err := txFunc(tx, obj) - if err != nil { - return err - } - c.logger.V(2).Info(operation+" successful", "uidCount", len(uids)) - return nil -} - -func (c client) mutateWithUniqueVerification(ctx context.Context, obj any, insert bool) error { - - schemaObj, err := checkObject(obj) - if err != nil { - return err - } - if c.options.autoSchema { - if err := c.UpdateSchema(ctx, schemaObj); err != nil { - return err - } - } - - // Get the value and prepare for unified slice processing - val := reflect.ValueOf(obj) - var sliceValue reflect.Value - - // Handle pointer to slice - if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Slice { - sliceValue = val.Elem() - } else if val.Kind() == reflect.Slice { - // Direct slice - sliceValue = val } else { - // Single object - create a slice with one element - valElem := val - for valElem.Kind() == reflect.Ptr { - valElem = valElem.Elem() - } - sliceType := reflect.SliceOf(valElem.Type()) - sliceValue = reflect.MakeSlice(sliceType, 1, 1) - sliceValue.Index(0).Set(valElem) - } - - seen := make(map[string]int) - for i := 0; i < sliceValue.Len(); i++ { - elem := sliceValue.Index(i).Interface() - preds := getUniquePredicates(elem) - if len(preds) == 0 { - continue - } - // In-memory duplicate check - sig := "" - for k, v := range preds { - sig += fmt.Sprintf("%s=%v;", k, v) - } - if prev, ok := seen[sig]; ok { - return fmt.Errorf("duplicate unique predicates in slice at indices %d and %d", prev, i) - } - seen[sig] = i - // Persistent uniqueness check - nodeType := getNodeType(elem) - query, vars := generateUniquePredicateQuery(preds, nodeType) - resp, err := c.QueryRaw(ctx, query, vars) + // When AutoSchema is disabled, check schema consistency + currentSchema, err := c.GetSchema(ctx) if err != nil { - return err + return fmt.Errorf("failed to get current schema: %w", err) } - uid, err := extractUIDFromDgraphQueryResult(resp) - if err != nil { - return err + + // Get the type name from the object + schemaObjVal := reflect.ValueOf(schemaObj) + if schemaObjVal.Kind() == reflect.Ptr { + schemaObjVal = schemaObjVal.Elem() } - if uid != "" && (insert || uid != getUIDValue(elem)) { - return &dg.UniqueError{NodeType: nodeType, UID: uid} + typeName := schemaObjVal.Type().Name() + + // When AutoSchema is disabled, validate that required schema exists + // Fail if user schema for the type doesn't exist, even if only system schema exists + if typeName != "" && !strings.Contains(currentSchema, "type "+typeName) { + return fmt.Errorf("schema validation failed: database schema does not contain type %s", typeName) } } @@ -154,91 +98,16 @@ func (c client) mutateWithUniqueVerification(ctx context.Context, obj any, inser defer c.pool.put(client) tx := dg.NewTxnContext(ctx, client).SetCommitNow() - uids, err := tx.MutateBasic(obj) - if err != nil { - return err - } - c.logger.V(2).Info("mutation successful", "uidCount", len(uids)) - return nil -} - -func (c client) upsert(ctx context.Context, obj any, upsertPredicate string) error { - - schemaObj, err := checkObject(obj) + uids, err := txFunc(tx, obj) if err != nil { - return err - } - - // users can pass slices, so check for that - val := reflect.ValueOf(obj) - var sliceValue reflect.Value - - // Handle pointer to slice - if val.Kind() == reflect.Ptr && val.Elem().Kind() == reflect.Slice { - sliceValue = val.Elem() - } else if val.Kind() == reflect.Slice { - // Direct slice - sliceValue = val - } - if sliceValue.IsValid() && sliceValue.Len() > 0 { - for i := 0; i < sliceValue.Len(); i++ { - elem := sliceValue.Index(i).Interface() - err := c.upsert(ctx, elem, upsertPredicate) - if err != nil { - return err - } - } - return nil - } else { - if c.options.autoSchema { - err := c.UpdateSchema(ctx, schemaObj) - if err != nil { - return err - } + // Check if this is a unique constraint violation error from Dgraph + if uniqueErr := parseUniqueError(err); uniqueErr != nil { + return uniqueErr } - } - - upsertPredicates := getUpsertPredicates(schemaObj, upsertPredicate == "") - if len(upsertPredicates) == 0 { - return errors.New("no upsert predicates found") - } - var firstPredicate string - for k := range upsertPredicates { - firstPredicate = k - break - } - if len(upsertPredicates) > 1 { - c.logger.V(1).Info("Multiple upsert predicates found, using first", "predicate", firstPredicate) - } - if upsertPredicate == "" { - upsertPredicate = firstPredicate - } else { - if _, ok := upsertPredicates[upsertPredicate]; !ok { - return fmt.Errorf("upsert predicate %q not found", upsertPredicate) - } - } - - query := fmt.Sprintf(`{ - q(func: eq(%s, "%s")) { - uid - } - }`, upsertPredicate, upsertPredicates[upsertPredicate]) - - resp, err := c.QueryRaw(ctx, query, nil) - if err != nil { - return err - } - uid, err := extractUIDFromDgraphQueryResult(resp) - if err != nil { return err } - - if uid == "" { - return c.Insert(ctx, obj) - } - objValue := reflect.ValueOf(schemaObj) - objValue.Elem().FieldByName("UID").SetString(uid) - return c.Update(ctx, objValue.Interface()) + c.logger.V(2).Info(operation+" successful", "uidCount", len(uids)) + return nil } func generateUniquePredicateQuery(predicates map[string]interface{}, nodeType string) (string, map[string]string) { diff --git a/namespace.go b/namespace.go index f94210a..ba367aa 100644 --- a/namespace.go +++ b/namespace.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/namespace_test.go b/namespace_test.go index e9975c1..022bade 100644 --- a/namespace_test.go +++ b/namespace_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -246,6 +246,77 @@ func TestTwoDBs(t *testing.T) { require.JSONEq(t, `{"me":[{"bar":"B"}]}`, string(resp.GetJson())) } +func TestCommitOrAbortNamespaceIsolation(t *testing.T) { + engine, err := modusgraph.NewEngine(modusgraph.NewDefaultConfig(t.TempDir())) + require.NoError(t, err) + defer engine.Close() + + // Create two separate namespaces + ns1, err := engine.CreateNamespace() + require.NoError(t, err) + + ns2, err := engine.CreateNamespace() + require.NoError(t, err) + + // Set up schema in both namespaces + require.NoError(t, ns1.AlterSchema(context.Background(), "name: string @index(exact) .")) + require.NoError(t, ns2.AlterSchema(context.Background(), "name: string @index(exact) .")) + + // Add data to namespace 1 + _, err = ns1.Mutate(context.Background(), []*api.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:entity1", + Predicate: "name", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: "Namespace1Entity"}}, + }, + }, + }, + }) + require.NoError(t, err) + + // Add data to namespace 2 + _, err = ns2.Mutate(context.Background(), []*api.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:entity2", + Predicate: "name", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: "Namespace2Entity"}}, + }, + }, + }, + }) + require.NoError(t, err) + + // Verify data is isolated - each namespace should only see its own data + query := `{ + me(func: has(name)) { + name + } + }` + + resp1, err := ns1.Query(context.Background(), query) + require.NoError(t, err) + require.JSONEq(t, `{"me":[{"name":"Namespace1Entity"}]}`, string(resp1.GetJson())) + + resp2, err := ns2.Query(context.Background(), query) + require.NoError(t, err) + require.JSONEq(t, `{"me":[{"name":"Namespace2Entity"}]}`, string(resp2.GetJson())) + + // Verify that dropping data from one namespace doesn't affect the other + require.NoError(t, ns1.DropData(context.Background())) + + resp1After, err := ns1.Query(context.Background(), query) + require.NoError(t, err) + require.JSONEq(t, `{"me":[]}`, string(resp1After.GetJson())) + + resp2After, err := ns2.Query(context.Background(), query) + require.NoError(t, err) + require.JSONEq(t, `{"me":[{"name":"Namespace2Entity"}]}`, string(resp2After.GetJson())) +} + func TestDBDBRestart(t *testing.T) { dataDir := t.TempDir() engine, err := modusgraph.NewEngine(modusgraph.NewDefaultConfig(dataDir)) diff --git a/query_test.go b/query_test.go index 8e046e3..6ee0286 100644 --- a/query_test.go +++ b/query_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/reverse_test.go b/reverse_test.go index 478848e..fc956b7 100644 --- a/reverse_test.go +++ b/reverse_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/update_test.go b/update_test.go index d5fe740..17d7a86 100644 --- a/update_test.go +++ b/update_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -285,3 +285,123 @@ func TestClientUpdateAllTypes(t *testing.T) { }) } } + +type UniqueEmbeddedNode struct { + Email string `json:"email,omitempty" dgraph:"unique"` + Name string `json:"name,omitempty"` + + UID string `json:"uid,omitempty"` + DType []string `json:"dgraph.type,omitempty"` +} + +type ParentWithUniqueEmbedded struct { + Name string `json:"name,omitempty"` + Node *UniqueEmbeddedNode `json:"node,omitempty"` + Nodes []*UniqueEmbeddedNode `json:"nodes,omitempty"` + + UID string `json:"uid,omitempty"` + DType []string `json:"dgraph.type,omitempty"` +} + +func TestClientUpdateWithUniqueEmbedded(t *testing.T) { + + testCases := []struct { + name string + uri string + skip bool + }{ + { + name: "UpdateWithUniqueEmbeddedWithFileURI", + uri: "file://" + GetTempDir(t), + }, + { + name: "UpdateWithUniqueEmbeddedWithDgraph", + 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() + + // 1. Test unique constraint violation in embedded node + parent1 := ParentWithUniqueEmbedded{ + Name: "Parent 1", + Node: &UniqueEmbeddedNode{ + Email: "test@example.com", + Name: "Test Node", + }, + } + err := client.Insert(ctx, &parent1) + require.NoError(t, err, "Insert should succeed for parent1") + require.NotEmpty(t, parent1.UID, "Parent1 UID should be assigned") + require.NotEmpty(t, parent1.Node.UID, "Node UID should be assigned") + + parent2 := ParentWithUniqueEmbedded{ + Name: "Parent 2", + Node: &UniqueEmbeddedNode{ + Email: "test2@example.com", + Name: "Test Node 2", + }, + } + err = client.Insert(ctx, &parent2) + require.NoError(t, err, "Insert should succeed for parent2") + require.NotEmpty(t, parent2.UID, "Parent2 UID should be assigned") + + // Try to update parent2's node to use the same email as parent1's node + parent2.Node.Email = "test@example.com" + err = client.Update(ctx, &parent2) + require.Error(t, err, "Update should fail due to unique constraint violation in embedded node") + + // 2. Test unique constraint violation in embedded node array + parent3 := ParentWithUniqueEmbedded{ + Name: "Parent 3", + Nodes: []*UniqueEmbeddedNode{ + {Email: "node1@example.com", Name: "Node 1"}, + {Email: "node2@example.com", Name: "Node 2"}, + }, + } + err = client.Insert(ctx, &parent3) + require.NoError(t, err, "Insert should succeed for parent3") + require.NotEmpty(t, parent3.Nodes[0].UID, "Nodes[0] UID should be assigned") + require.NotEmpty(t, parent3.Nodes[1].UID, "Nodes[1] UID should be assigned") + t.Logf("After insert: Nodes[0].UID=%s, Nodes[1].UID=%s", parent3.Nodes[0].UID, parent3.Nodes[1].UID) + + // Try to update parent3's nodes to have duplicate emails + parent3.Nodes[1].Email = "node1@example.com" + err = client.Update(ctx, &parent3) + require.Error(t, err, "Update should fail due to duplicate emails in embedded node array") + + // 3. Test valid update of embedded nodes + updatedNodeUID := parent3.Nodes[1].UID // Save UID before update + parent3.Nodes[1].Email = "node3@example.com" + parent3.Nodes[1].Name = "Updated Node 2" + err = client.Update(ctx, &parent3) + require.NoError(t, err, "Update should succeed with valid embedded node changes") + + // Verify the update - find node by UID since Dgraph doesn't guarantee array order + updated := ParentWithUniqueEmbedded{UID: parent3.UID} + err = client.Get(ctx, &updated, parent3.UID) + require.NoError(t, err, "Get should succeed") + var updatedNode *UniqueEmbeddedNode + for _, n := range updated.Nodes { + if n.UID == updatedNodeUID { + updatedNode = n + break + } + } + require.NotNil(t, updatedNode, "Updated node should be found by UID") + require.Equal(t, "node3@example.com", updatedNode.Email, "Email should be updated") + require.Equal(t, "Updated Node 2", updatedNode.Name, "Name should be updated") + }) + } +} diff --git a/upsert_test.go b/upsert_test.go index 1e7fa5b..a4bc384 100644 --- a/upsert_test.go +++ b/upsert_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -241,3 +241,112 @@ func TestClientUpsertSlice(t *testing.T) { }) } } + +type EmbeddedUpsertEntity struct { + Name string `json:"name,omitempty" dgraph:"index=exact upsert"` + Description string `json:"description,omitempty" dgraph:"index=term"` + CreatedAt time.Time `json:"createdAt,omitzero"` + + UID string `json:"uid,omitempty"` + DType []string `json:"dgraph.type,omitempty"` +} + +type OuterUpsertEntity struct { + Title string `json:"title,omitempty"` + Entity *EmbeddedUpsertEntity `json:"entity"` + + UID string `json:"uid,omitempty"` + DType []string `json:"dgraph.type,omitempty"` +} + +func TestEmbeddedUpsert(t *testing.T) { + testCases := []struct { + name string + uri string + skip bool + }{ + { + name: "EmbeddedUpsertWithFileURI", + uri: "file://" + GetTempDir(t), + }, + { + name: "EmbeddedUpsertWithDgraphURI", + 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() + + // First upsert - creates new entity with embedded entity + firstEntity := OuterUpsertEntity{ + Title: "First Title", + Entity: &EmbeddedUpsertEntity{ + Name: "Unique Embedded Name", + Description: "First description", + CreatedAt: time.Date(2021, 6, 9, 17, 22, 33, 0, time.UTC), + }, + } + + err := client.Upsert(ctx, &firstEntity) + require.NoError(t, err, "First upsert should succeed") + require.NotEmpty(t, firstEntity.UID, "Outer UID should be assigned") + require.NotEmpty(t, firstEntity.Entity.UID, "Embedded UID should be assigned") + + firstUID := firstEntity.UID + firstEmbeddedUID := firstEntity.Entity.UID + + // Verify first entity was created correctly + retrieved := OuterUpsertEntity{} + err = client.Get(ctx, &retrieved, firstUID) + require.NoError(t, err, "Get should succeed") + require.Equal(t, "First Title", retrieved.Title, "Title should match") + require.Equal(t, "Unique Embedded Name", retrieved.Entity.Name, "Embedded name should match") + require.Equal(t, "First description", retrieved.Entity.Description, "Embedded description should match") + + // Second upsert - embedded entity with same upsert key should be UPDATED, not created + secondEntity := OuterUpsertEntity{ + Title: "Second Title", + Entity: &EmbeddedUpsertEntity{ + Name: "Unique Embedded Name", // Same name - should trigger upsert on embedded entity + Description: "Updated description", + CreatedAt: time.Date(2022, 7, 10, 18, 30, 45, 0, time.UTC), + }, + } + + err = client.Upsert(ctx, &secondEntity) + require.NoError(t, err, "Second upsert should succeed") + require.NotEmpty(t, secondEntity.UID, "Outer UID should be assigned") + require.NotEmpty(t, secondEntity.Entity.UID, "Embedded UID should be assigned") + + // The embedded entity should have the SAME UID (upserted, not created new) + require.Equal(t, firstEmbeddedUID, secondEntity.Entity.UID, "Embedded UID should be the same (upserted)") + + // Verify only ONE embedded entity exists (it was upserted, not duplicated) + var embeddedEntities []EmbeddedUpsertEntity + err = client.Query(ctx, EmbeddedUpsertEntity{}).Nodes(&embeddedEntities) + require.NoError(t, err, "Query embedded entities should succeed") + require.Len(t, embeddedEntities, 1, "There should be only one embedded entity (upserted)") + + // Verify the embedded entity has the updated description + require.Equal(t, "Unique Embedded Name", embeddedEntities[0].Name, "Name should match") + require.Equal(t, "Updated description", embeddedEntities[0].Description, "Description should be updated") + + // Verify there are two outer entities (they have different titles, so not upserted) + var outerEntities []OuterUpsertEntity + err = client.Query(ctx, OuterUpsertEntity{}).Nodes(&outerEntities) + require.NoError(t, err, "Query outer entities should succeed") + require.Len(t, outerEntities, 2, "There should be two outer entities") + }) + } +} diff --git a/util_test.go b/util_test.go index b8eb563..d1d3347 100644 --- a/util_test.go +++ b/util_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ diff --git a/validate_test.go b/validate_test.go new file mode 100644 index 0000000..aaa1c0f --- /dev/null +++ b/validate_test.go @@ -0,0 +1,214 @@ +/* + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package modusgraph_test + +import ( + "context" + "os" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + mg "github.com/matthewmcneely/modusgraph" +) + +// ValidatableUser is a test struct with validation tags +type ValidatableUser struct { + UID string `json:"uid,omitempty"` + Name string `json:"name,omitempty" validate:"required,min=2,max=100"` + Email string `json:"email,omitempty" validate:"required,email"` + Age int `json:"age,omitempty" validate:"gte=0,lte=130"` + Status string `json:"status,omitempty" validate:"oneof=active inactive pending"` + DType []string `json:"dgraph.type,omitempty"` +} + +// CustomValidatableEntity tests custom validation rules +type CustomValidatableEntity struct { + UID string `json:"uid,omitempty"` + Code string `json:"code,omitempty" validate:"required,custom_code"` + Enabled bool `json:"enabled,omitempty"` + DType []string `json:"dgraph.type,omitempty"` +} + +func TestClientWithValidator(t *testing.T) { + + testCases := []struct { + name string + uri string + skip bool + }{ + { + name: "ValidatorWithFileURI", + uri: "file://" + GetTempDir(t), + }, + { + name: "ValidatorWithDgraphURI", + 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 + } + + // Create a validator instance + validate := validator.New() + + // Register custom validation + err := validate.RegisterValidation("custom_code", func(fl validator.FieldLevel) bool { + code := fl.Field().String() + return len(code) == 6 && code[0:3] == "ABC" + }) + require.NoError(t, err) + + // Create client with validator + client, err := mg.NewClient(tc.uri, mg.WithAutoSchema(true), mg.WithValidator(validate)) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + + t.Run("ValidEntityShouldPass", func(t *testing.T) { + user := ValidatableUser{ + Name: "John Doe", + Email: "john.doe@example.com", + Age: 30, + Status: "active", + } + + err := client.Insert(ctx, &user) + require.NoError(t, err, "Valid entity should insert successfully") + require.NotEmpty(t, user.UID, "UID should be assigned") + }) + + t.Run("InvalidEntityShouldFail", func(t *testing.T) { + user := ValidatableUser{ + Name: "", // Invalid: empty name (required) + Email: "invalid-email", // Invalid: not a valid email + Age: 150, // Invalid: age > 130 + Status: "unknown", // Invalid: not oneof allowed values + } + + err := client.Insert(ctx, &user) + require.Error(t, err, "Invalid entity should fail validation") + assert.Contains(t, err.Error(), "validation", "Error should mention validation") + assert.Empty(t, user.UID, "UID should not be assigned for failed validation") + }) + + t.Run("PartialInvalidEntityShouldFail", func(t *testing.T) { + user := ValidatableUser{ + Name: "J", // Invalid: name too short (min=2) + Email: "valid@example.com", + Age: 25, + Status: "active", + } + + err := client.Insert(ctx, &user) + require.Error(t, err, "Partially invalid entity should fail validation") + assert.Empty(t, user.UID, "UID should not be assigned for failed validation") + }) + + t.Run("CustomValidationShouldWork", func(t *testing.T) { + entity := CustomValidatableEntity{ + Code: "ABC123", // Valid: starts with ABC and 6 chars + Enabled: true, + } + + err := client.Insert(ctx, &entity) + require.NoError(t, err, "Entity with valid custom code should insert successfully") + require.NotEmpty(t, entity.UID, "UID should be assigned") + }) + + t.Run("CustomValidationShouldFail", func(t *testing.T) { + entity := CustomValidatableEntity{ + Code: "XYZ123", // Invalid: doesn't start with ABC + Enabled: true, + } + + err := client.Insert(ctx, &entity) + require.Error(t, err, "Entity with invalid custom code should fail validation") + assert.Empty(t, entity.UID, "UID should not be assigned for failed validation") + }) + + t.Run("UpdateWithValidation", func(t *testing.T) { + user := ValidatableUser{ + Name: "Jane Smith", + Email: "jane.smith@example.com", + Age: 28, + Status: "active", + } + + // Insert valid user first + err := client.Insert(ctx, &user) + require.NoError(t, err) + + // Try to update with invalid data + user.Email = "invalid-email-updated" + err = client.Update(ctx, &user) + require.Error(t, err, "Update with invalid data should fail validation") + + // Verify original data is still intact + retrieved := ValidatableUser{UID: user.UID} + err = client.Get(ctx, &retrieved, user.UID) + require.NoError(t, err) + assert.Equal(t, "jane.smith@example.com", retrieved.Email, "Original email should be preserved") + }) + + t.Run("UpsertWithValidation", func(t *testing.T) { + user := ValidatableUser{ + Name: "Bob Wilson", + Email: "bob.wilson@example.com", + Age: 35, + Status: "pending", + } + + // Upsert valid user + err := client.Upsert(ctx, &user) + require.NoError(t, err, "Valid upsert should succeed") + require.NotEmpty(t, user.UID, "UID should be assigned") + + // Try to upsert with invalid data + invalidUser := ValidatableUser{ + Name: "", // Invalid: empty name + Email: "bob.wilson@example.com", // Same email to trigger upsert path + Age: 35, + Status: "pending", + } + + err = client.Upsert(ctx, &invalidUser) + require.Error(t, err, "Upsert with invalid data should fail validation") + }) + }) + } +} + +func TestValidatorWithoutAutoSchema(t *testing.T) { + // Test validator behavior when AutoSchema is disabled + validate := validator.New() + client, err := mg.NewClient("file://"+GetTempDir(t), mg.WithValidator(validate)) + require.NoError(t, err) + defer client.Close() + + ctx := context.Background() + + user := ValidatableUser{ + Name: "Test User", + Email: "test@example.com", + Age: 25, + Status: "active", + } + + // Should still validate even without AutoSchema + err = client.Insert(ctx, &user) + require.Error(t, err, "Insert should fail without schema but validation should still run") + assert.Contains(t, err.Error(), "schema", "Error should mention schema issue") +} diff --git a/vector_test.go b/vector_test.go index 70fb285..cfaf6a0 100644 --- a/vector_test.go +++ b/vector_test.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ import ( "testing" "github.com/dgraph-io/dgo/v250/protos/api" - "github.com/hypermodeinc/dgraph/v25/dgraphapi" + "github.com/dgraph-io/dgraph/v25/dgraphapi" "github.com/matthewmcneely/modusgraph" "github.com/stretchr/testify/require" ) diff --git a/zero.go b/zero.go index d954402..551f492 100644 --- a/zero.go +++ b/zero.go @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. * SPDX-License-Identifier: Apache-2.0 */ @@ -9,10 +9,10 @@ import ( "fmt" "github.com/dgraph-io/badger/v4" - "github.com/hypermodeinc/dgraph/v25/posting" - "github.com/hypermodeinc/dgraph/v25/protos/pb" - "github.com/hypermodeinc/dgraph/v25/worker" - "github.com/hypermodeinc/dgraph/v25/x" + "github.com/dgraph-io/dgraph/v25/posting" + "github.com/dgraph-io/dgraph/v25/protos/pb" + "github.com/dgraph-io/dgraph/v25/worker" + "github.com/dgraph-io/dgraph/v25/x" "google.golang.org/protobuf/proto" )