go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/pkg/dbmodel/store.go (about)

     1  /*
     2  
     3  Copyright (c) 2023 - Present. Will Charczuk. All rights reserved.
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository.
     5  
     6  */
     7  
     8  package dbmodel
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"fmt"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/wcharczuk/go-incr"
    18  	"go.charczuk.com/projects/nodes/pkg/incrutil"
    19  	"go.charczuk.com/projects/nodes/pkg/model"
    20  	"go.charczuk.com/projects/nodes/pkg/types"
    21  	"go.charczuk.com/sdk/errutil"
    22  	"go.charczuk.com/sdk/iter"
    23  	"go.charczuk.com/sdk/uuid"
    24  )
    25  
    26  // Store is a wrapper for a model manager that
    27  // implements the graph store api against the database
    28  // for a given graph by identifier.
    29  type Store struct {
    30  	GraphID uuid.UUID
    31  	UserID  uuid.UUID
    32  	Model   *Manager
    33  }
    34  
    35  func (s Store) Save(ctx context.Context) (*types.GraphFull, error) {
    36  	return s.Model.Deserialize(ctx, s.GraphID, false, false)
    37  }
    38  
    39  func (s Store) Load(ctx context.Context, graph *types.GraphFull) (id incr.Identifier, err error) {
    40  	// create a new graph essentially based on the file.
    41  	graphObj := GraphFromType(graph.Graph)
    42  	graphObj.ID = uuid.V4()
    43  	graphObj.UserID = s.UserID
    44  	graphObj.CreatedUTC = time.Now().UTC()
    45  	graphObj.UpdatedUTC = time.Now().UTC()
    46  	if err = s.Model.Invoke(ctx).Create(graphObj); err != nil {
    47  		return
    48  	}
    49  
    50  	id = incr.Identifier(graphObj.ID)
    51  
    52  	// nodes
    53  	nodeRemap := make(map[incr.Identifier]uuid.UUID)
    54  	for _, n := range graph.Nodes {
    55  		nodeObj := NodeFromTypeNode(graphObj.ID, s.UserID, &n)
    56  		nodeObj.ID = uuid.V4()
    57  		nodeRemap[n.ID] = nodeObj.ID
    58  		if err = s.Model.Invoke(ctx).Create(nodeObj); err != nil {
    59  			return
    60  		}
    61  	}
    62  
    63  	// edges
    64  	for _, e := range graph.Edges {
    65  		edgeObj := EdgeFromType(graphObj, e)
    66  		edgeObj.ParentID = nodeRemap[e.ParentID]
    67  		edgeObj.ChildID = nodeRemap[e.ChildID]
    68  		if err = s.Model.Invoke(ctx).Create(edgeObj); err != nil {
    69  			return
    70  		}
    71  	}
    72  
    73  	// node values
    74  	for _, v := range graph.Values {
    75  		nv := &NodeValue{
    76  			NodeID:    nodeRemap[v.ID],
    77  			GraphID:   graphObj.ID,
    78  			UserID:    graphObj.UserID,
    79  			ValueType: v.ValueType,
    80  			Value:     v.Value,
    81  		}
    82  		if err = s.Model.Invoke(ctx).Create(nv); err != nil {
    83  			return
    84  		}
    85  	}
    86  	return
    87  }
    88  
    89  func (s Store) Graph(ctx context.Context) (*types.Graph, error) {
    90  	var graphObj Graph
    91  	found, err := s.Model.Invoke(ctx).Get(&graphObj, s.GraphID)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	if !found {
    96  		return nil, nil
    97  	}
    98  
    99  	return &types.Graph{
   100  		ID:               incr.Identifier(graphObj.ID),
   101  		Label:            graphObj.Label,
   102  		StabilizationNum: graphObj.StabilizationNum,
   103  		Metadata: types.GraphMetadata{
   104  			UserID:       graphObj.UserID,
   105  			CreatedUTC:   graphObj.CreatedUTC,
   106  			UpdatedUTC:   graphObj.UpdatedUTC,
   107  			ViewportX:    graphObj.ViewportX,
   108  			ViewportY:    graphObj.ViewportY,
   109  			ViewportZoom: graphObj.ViewportZoom,
   110  		},
   111  	}, nil
   112  }
   113  
   114  func (s Store) Logs(ctx context.Context) (string, error) {
   115  	logsLatest, found, err := s.Model.GraphLogsLatest(ctx, s.GraphID)
   116  	if err != nil {
   117  		return "", err
   118  	}
   119  	if !found {
   120  		return "", nil
   121  	}
   122  	return logsLatest.Logs, nil
   123  }
   124  
   125  func (s Store) SetViewport(ctx context.Context, viewport types.Viewport) error {
   126  	return s.Model.SetViewport(ctx, s.GraphID, viewport)
   127  }
   128  
   129  func (s Store) Stabilize(ctx context.Context, parallel bool) (stabilizationNum uint64, err error) {
   130  	var data *types.GraphFull
   131  	data, err = s.Model.Deserialize(ctx, s.GraphID, false, false)
   132  	if err != nil {
   133  		return
   134  	}
   135  
   136  	var graph *incr.Graph
   137  	var nodes map[incr.Identifier]incr.INode
   138  	graph, nodes, err = s.buildGraph(ctx, data)
   139  	if err != nil {
   140  		return
   141  	}
   142  
   143  	var valuesMu sync.Mutex
   144  	values := make(map[uuid.UUID]any)
   145  	nodeMetadata := make(map[uuid.UUID]Node)
   146  	logs := new(bytes.Buffer)
   147  	eg := incr.ExpertGraph(graph)
   148  
   149  	defer func() {
   150  		if r := recover(); r != nil {
   151  			err = errutil.Append(err, errutil.New(fmt.Errorf("stabilization panic: %v", r)))
   152  		}
   153  	}()
   154  
   155  	updateHandler := func(n incr.INode) func(context.Context) {
   156  		en := incr.ExpertNode(n)
   157  		nodeID := uuid.UUID(n.Node().ID())
   158  		return func(_ context.Context) {
   159  			valuesMu.Lock()
   160  			defer valuesMu.Unlock()
   161  			values[nodeID] = incrutil.GetNodeValue(n)
   162  			nodeMetadata[nodeID] = Node{
   163  				SetAt:        en.SetAt(),
   164  				ChangedAt:    en.ChangedAt(),
   165  				RecomputedAt: en.RecomputedAt(),
   166  			}
   167  		}
   168  	}
   169  	for _, n := range nodes {
   170  		n.Node().OnUpdate(updateHandler(n))
   171  	}
   172  	ctx = incr.WithTracingOutputs(ctx, logs, logs)
   173  	if parallel {
   174  		err = graph.ParallelStabilize(ctx)
   175  	} else {
   176  		err = graph.Stabilize(ctx)
   177  	}
   178  
   179  	func() {
   180  		defer func() {
   181  			if r := recover(); r != nil {
   182  				err = errutil.Append(err, errutil.New(fmt.Errorf("stabilization post-actions panic: %v", r)))
   183  			}
   184  		}()
   185  
   186  		var postErrs []error
   187  		var postErr error
   188  		stabilizationNum = eg.StabilizationNum()
   189  
   190  		postErr = s.Model.UpdateGraphPostStabilization(ctx, s.GraphID, stabilizationNum)
   191  		if postErr != nil {
   192  			postErrs = append(postErrs, errutil.New(postErr))
   193  		}
   194  
   195  		postErr = s.Model.TouchGraph(ctx, s.GraphID)
   196  		if postErr != nil {
   197  			postErrs = append(postErrs, errutil.New(postErr))
   198  		}
   199  
   200  		postErr = s.Model.Invoke(ctx).Create(GraphLogs{GraphID: s.GraphID, UserID: s.UserID, StabilizationNum: stabilizationNum, Logs: logs.String()})
   201  		if postErr != nil {
   202  			postErrs = append(postErrs, errutil.New(postErr))
   203  		}
   204  
   205  		postErr = s.Model.SetNodeMetadata(ctx, s.GraphID, nodeMetadata)
   206  		if postErr != nil {
   207  			postErrs = append(postErrs, errutil.New(postErr))
   208  		}
   209  
   210  		postErr = s.Model.SetNodeValues(ctx, s.GraphID, s.UserID, values)
   211  		if postErr != nil {
   212  			postErrs = append(postErrs, errutil.New(postErr))
   213  		}
   214  
   215  		recomputeHeapIDs := iter.Apply(eg.RecomputeHeapIDs(), func(id incr.Identifier) uuid.UUID { return uuid.UUID(id) })
   216  		postErr = s.Model.SetRecomputeHeap(ctx, s.GraphID, s.UserID, recomputeHeapIDs...)
   217  		if postErr != nil {
   218  			postErrs = append(postErrs, errutil.New(postErr))
   219  		}
   220  
   221  		if len(postErrs) > 0 {
   222  			err = errutil.Append(err, postErrs...)
   223  		}
   224  	}()
   225  	return
   226  }
   227  
   228  func (s Store) AddNode(ctx context.Context, n *types.Node) (id incr.Identifier, err error) {
   229  	dn := NodeFromTypeNode(s.GraphID, s.UserID, n)
   230  	dn.ID = uuid.V4()
   231  	if err = s.Model.Invoke(ctx).Create(&dn); err != nil {
   232  		return
   233  	}
   234  	if err = s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   235  		return
   236  	}
   237  	id = incr.Identifier(dn.ID)
   238  	return
   239  }
   240  
   241  func (s Store) RemoveNode(ctx context.Context, id incr.Identifier) (found bool, err error) {
   242  	found, err = s.Model.DeleteNode(ctx, uuid.UUID(id))
   243  	if err != nil {
   244  		return
   245  	}
   246  	if err = s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   247  		return
   248  	}
   249  	return
   250  }
   251  
   252  func (s Store) Nodes(ctx context.Context) ([]types.Node, error) {
   253  	nodes, err := s.Model.Nodes(ctx, s.GraphID)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	return iter.Apply(nodes, TypeNodeFromNode), nil
   258  }
   259  
   260  func (s Store) Node(ctx context.Context, id incr.Identifier) (output types.Node, found bool, err error) {
   261  	var nodeObj Node
   262  	nodeObj, found, err = s.Model.Node(ctx, uuid.UUID(id))
   263  	if err != nil {
   264  		return
   265  	}
   266  	if !found {
   267  		return
   268  	}
   269  	output = TypeNodeFromNode(nodeObj)
   270  	return
   271  }
   272  
   273  func (s Store) PatchNodes(ctx context.Context, ps types.PatchSet) (err error) {
   274  	if err = s.Model.PatchNodes(ctx, s.GraphID, ps); err != nil {
   275  		return
   276  	}
   277  	if err = s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   278  		return
   279  	}
   280  	return
   281  }
   282  
   283  func (s Store) PatchNode(ctx context.Context, id incr.Identifier, ps types.PatchSet) (found bool, err error) {
   284  	// handle marking the node stale (and adding it to the recompute heap) if we change the expression.
   285  	// for multi-node patches we do _not_ have to do we never set the expression that way.
   286  	if _, includesExpression := ps["expression"]; includesExpression {
   287  		graph, found, err := s.Model.Graph(ctx, s.GraphID)
   288  		if err != nil {
   289  			return false, err
   290  		}
   291  		if !found {
   292  			return false, nil
   293  		}
   294  		ps["set_at"] = graph.StabilizationNum
   295  		if err := s.Model.Invoke(ctx).Upsert(&GraphRecomputeHeap{
   296  			GraphID: s.GraphID,
   297  			UserID:  s.UserID,
   298  			NodeID:  uuid.UUID(id),
   299  		}); err != nil {
   300  			return false, err
   301  		}
   302  	}
   303  	found, err = s.Model.PatchNode(ctx, uuid.UUID(id), ps)
   304  	if err != nil {
   305  		return
   306  	}
   307  	if err = s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   308  		return
   309  	}
   310  	return
   311  }
   312  
   313  func (s Store) NodeValue(ctx context.Context, id incr.Identifier) (any, bool, error) {
   314  	nv, found, err := s.Model.NodeValue(ctx, s.GraphID, uuid.UUID(id))
   315  	if err != nil {
   316  		return nil, false, err
   317  	}
   318  	if !found {
   319  		return nil, false, nil
   320  	}
   321  	parsedValue, err := nv.ParsedValue()
   322  	if err != nil {
   323  		return nil, true, err
   324  	}
   325  	return parsedValue, true, nil
   326  }
   327  
   328  func (s Store) NodeValues(ctx context.Context, nodeIDs ...incr.Identifier) ([]types.NodeValue, error) {
   329  	values, err := s.Model.NodeValues(ctx, s.GraphID, iter.Apply(nodeIDs, func(id incr.Identifier) uuid.UUID { return uuid.UUID(id) })...)
   330  	if err != nil {
   331  		return nil, err
   332  	}
   333  	return iter.Apply(values, func(v NodeValue) types.NodeValue {
   334  		parsed, _ := v.ParsedValue()
   335  		return types.NodeValue{
   336  			ID:    incr.Identifier(v.NodeID),
   337  			Value: parsed,
   338  		}
   339  	}), nil
   340  }
   341  
   342  func (s Store) SetNodeValue(ctx context.Context, nodeID incr.Identifier, value any) (bool, error) {
   343  	node, found, err := s.Model.Node(ctx, uuid.UUID(nodeID))
   344  	if err != nil {
   345  		return false, fmt.Errorf("%w; set node value; get node", err)
   346  	}
   347  	if !found {
   348  		return false, nil
   349  	}
   350  	graph, _, err := s.Model.Graph(ctx, s.GraphID)
   351  	if err != nil {
   352  		return false, fmt.Errorf("%w; set node value; get node graph", err)
   353  	}
   354  	node.SetAt = graph.StabilizationNum
   355  	if _, err = s.Model.Invoke(ctx).Update(node); err != nil {
   356  		return false, err
   357  	}
   358  	if err := s.Model.Invoke(ctx).Upsert(&GraphRecomputeHeap{
   359  		GraphID: s.GraphID,
   360  		UserID:  s.UserID,
   361  		NodeID:  node.ID,
   362  	}); err != nil {
   363  		return false, fmt.Errorf("%w; set node value; upsert graph recompute heap", err)
   364  	}
   365  	var nv = NodeValue{
   366  		NodeID:    uuid.UUID(nodeID),
   367  		GraphID:   s.GraphID,
   368  		UserID:    s.UserID,
   369  		ValueType: DetectValueType(value),
   370  		Value:     value,
   371  	}
   372  	if err := s.Model.Invoke(ctx).Upsert(&nv); err != nil {
   373  		return false, fmt.Errorf("%w; set node value; upsert node value", err)
   374  	}
   375  	if err := s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   376  		return false, fmt.Errorf("%w; set node value; touch graph", err)
   377  	}
   378  	return true, nil
   379  }
   380  
   381  func (s Store) MarkNodeStale(ctx context.Context, nodeID incr.Identifier) (found bool, err error) {
   382  	found, err = s.Model.MarkNodeStale(ctx, s.GraphID, s.UserID, uuid.UUID(nodeID))
   383  	if err != nil {
   384  		return
   385  	}
   386  	if err = s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   387  		return
   388  	}
   389  	return
   390  }
   391  
   392  func (s Store) Edges(ctx context.Context) ([]types.Edge, error) {
   393  	edges, err := s.Model.Edges(ctx, s.GraphID)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	return iter.Apply(edges, func(e Edge) types.Edge {
   398  		return types.Edge{
   399  			ParentID:       incr.Identifier(e.ParentID),
   400  			ChildID:        incr.Identifier(e.ChildID),
   401  			ChildInputName: e.ChildInputName,
   402  		}
   403  	}), nil
   404  }
   405  
   406  func (s Store) LinkInput(ctx context.Context, te types.Edge) error {
   407  	graph, nodes, err := s.fetchGraphBasic(ctx)
   408  	if err != nil {
   409  		return err
   410  	}
   411  	child, ok := nodes[te.ChildID]
   412  	if !ok {
   413  		return fmt.Errorf("child node not found with id %v", te.ChildID)
   414  	}
   415  	parent, ok := nodes[te.ParentID]
   416  	if !ok {
   417  		return fmt.Errorf("parent node not found with id %v", te.ParentID)
   418  	}
   419  	if err = s.linkInputToChildUnsafe(ctx, graph, nodes, child, parent, te.ChildInputName); err != nil {
   420  		return err
   421  	}
   422  	if err := s.Model.Invoke(ctx).Create(Edge{
   423  		GraphID:        s.GraphID,
   424  		UserID:         s.UserID,
   425  		CreatedUTC:     time.Now().UTC(),
   426  		ParentID:       uuid.UUID(te.ParentID),
   427  		ChildID:        uuid.UUID(te.ChildID),
   428  		ChildInputName: te.ChildInputName,
   429  	}); err != nil {
   430  		return err
   431  	}
   432  
   433  	if incr.ExpertNode(child).IsNecessary() || s.isObserver(child) {
   434  		if err := s.Model.Invoke(ctx).Upsert(&GraphRecomputeHeap{
   435  			GraphID: s.GraphID,
   436  			UserID:  s.UserID,
   437  			NodeID:  uuid.UUID(te.ChildID),
   438  		}); err != nil {
   439  			return err
   440  		}
   441  	}
   442  	if err := s.recomputeHeights(ctx, nodes); err != nil {
   443  		return err
   444  	}
   445  	if err := s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   446  		return err
   447  	}
   448  	return nil
   449  }
   450  
   451  func (s Store) UnlinkInput(ctx context.Context, te types.Edge) error {
   452  	if err := s.Model.DeleteEdge(ctx, uuid.UUID(te.ParentID), uuid.UUID(te.ChildID), te.ChildInputName); err != nil {
   453  		return err
   454  	}
   455  	_, nodes, err := s.fetchGraphBasic(ctx)
   456  	if err != nil {
   457  		return err
   458  	}
   459  	if err := s.recomputeHeights(ctx, nodes); err != nil {
   460  		return err
   461  	}
   462  	if err := s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   463  		return err
   464  	}
   465  	return nil
   466  }
   467  
   468  func (s Store) PatchNodeTable(ctx context.Context, nodeID incr.Identifier, ops ...types.TableOp) (bool, error) {
   469  	value, found, err := s.NodeValue(ctx, nodeID)
   470  	if err != nil {
   471  		return false, err
   472  	}
   473  	if !found {
   474  		table := new(types.Table)
   475  		table.Columns = []types.TableColumn{
   476  			{
   477  				Name:   "New Column",
   478  				Values: []any{},
   479  			},
   480  		}
   481  		if err := table.ApplyOps(ops...); err != nil {
   482  			return true, err
   483  		}
   484  		if _, err := s.SetNodeValue(ctx, nodeID, table); err != nil {
   485  			return true, err
   486  		}
   487  		return true, nil
   488  	}
   489  	typed, ok := value.(*types.Table)
   490  	if !ok {
   491  		return true, fmt.Errorf("value is not a table for table ops")
   492  	}
   493  	if err := typed.ApplyOps(ops...); err != nil {
   494  		return true, err
   495  	}
   496  	if _, err := s.SetNodeValue(ctx, nodeID, typed); err != nil {
   497  		return true, err
   498  	}
   499  	if err := s.Model.TouchGraph(ctx, s.GraphID); err != nil {
   500  		return true, err
   501  	}
   502  	return true, nil
   503  }
   504  
   505  //
   506  // internal helpers
   507  //
   508  
   509  func (s Store) isObserver(n incr.INode) bool {
   510  	if n == nil {
   511  		return false
   512  	}
   513  	metadata, ok := n.Node().Metadata().(*types.NodeMetadata)
   514  	if !ok {
   515  		return false
   516  	}
   517  	return metadata.NodeType == incrutil.NodeTypeObserver
   518  }
   519  
   520  func (s Store) fetchGraphBasic(ctx context.Context) (graph *incr.Graph, nodes map[incr.Identifier]incr.INode, err error) {
   521  	var data *types.GraphFull
   522  	data, err = s.Model.Deserialize(ctx, s.GraphID, true, true)
   523  	if err != nil {
   524  		return
   525  	}
   526  	graph, nodes, err = s.buildGraph(ctx, data)
   527  	return
   528  }
   529  
   530  func (s Store) recomputeHeights(ctx context.Context, nodes map[incr.Identifier]incr.INode) (err error) {
   531  	updatedHeights := make(map[uuid.UUID]int)
   532  	for _, n := range nodes {
   533  		fmt.Println("recompute height of", n.Node().String())
   534  		en := incr.ExpertNode(n)
   535  		newHeight := en.ComputePseudoHeight()
   536  		updatedHeights[uuid.UUID(n.Node().ID())] = newHeight
   537  	}
   538  	return s.Model.SetNodeHeights(ctx, s.GraphID, updatedHeights)
   539  }
   540  
   541  func (s Store) buildGraph(ctx context.Context, data *types.GraphFull) (graph *incr.Graph, nodes map[incr.Identifier]incr.INode, err error) {
   542  	graph = incr.New()
   543  	expertGraph := incr.ExpertGraph(graph)
   544  	nodes = make(map[incr.Identifier]incr.INode)
   545  	graph.SetLabel(data.Graph.Label)
   546  	graph.SetMetadata(&data.Graph.Metadata)
   547  	expertGraph.SetStabilizationNum(data.Graph.StabilizationNum)
   548  
   549  	for x := 0; x < len(data.Nodes); x++ {
   550  		n := data.Nodes[x]
   551  		if _, err = s.addNodeUnsafe(graph, nodes, &n); err != nil {
   552  			return
   553  		}
   554  	}
   555  	for _, e := range data.Edges {
   556  		child, ok := nodes[e.ChildID]
   557  		if !ok {
   558  			err = fmt.Errorf("edge child node not found with id %v", e.ChildID)
   559  			return
   560  		}
   561  		input, ok := nodes[e.ParentID]
   562  		if !ok {
   563  			err = fmt.Errorf("edge parent node not found with id %v", e.ParentID)
   564  			return
   565  		}
   566  		if err = s.linkInputToChildUnsafe(ctx, graph, nodes, child, input, e.ChildInputName); err != nil {
   567  			return
   568  		}
   569  	}
   570  
   571  	for x := 0; x < len(data.Values); x++ {
   572  		v := data.Values[x]
   573  		if _, err = s.setNodeValueUnsafe(graph, nodes, v.ID, v.Value); err != nil {
   574  			return
   575  		}
   576  	}
   577  
   578  	for _, nodeID := range data.RecomputeHeap {
   579  		n, ok := nodes[nodeID]
   580  		if !ok {
   581  			continue
   582  		}
   583  		expertGraph.RecomputeHeapAdd(n)
   584  	}
   585  	return
   586  }
   587  
   588  func (s Store) addNodeUnsafe(graph *incr.Graph, nodes map[incr.Identifier]incr.INode, n *types.Node) (id incr.Identifier, err error) {
   589  	if n.Metadata.NodeType == "" {
   590  		err = fmt.Errorf(`"node_type" is required`)
   591  		return
   592  	}
   593  	var in incr.INode
   594  	in, err = model.INodeFromNode(graph, n)
   595  	if err != nil {
   596  		return
   597  	}
   598  	id = in.Node().ID()
   599  	nodes[id] = in
   600  	return
   601  }
   602  
   603  func (s Store) setNodeValueUnsafe(_ *incr.Graph, nodes map[incr.Identifier]incr.INode, id incr.Identifier, value any) (ok bool, err error) {
   604  	var in incr.INode
   605  	in, ok = nodes[id]
   606  	if in == nil || !ok {
   607  		return
   608  	}
   609  	err = incrutil.SetNodeValueDuringDeserialization(in, value)
   610  	return
   611  }
   612  
   613  func (s Store) linkInputToChildUnsafe(_ context.Context, _ *incr.Graph, _ map[incr.Identifier]incr.INode, child, input incr.INode, inputName string) error {
   614  	if err := incr.DetectCycleIfLinked(child, input); err != nil {
   615  		return err
   616  	}
   617  	if typed, ok := child.(incrutil.IAddInput); ok {
   618  		if err := typed.AddInput(input); err != nil {
   619  			return err
   620  		}
   621  	} else if typed, ok := child.(incrutil.ISetInput); ok {
   622  		if err := typed.SetInput(inputName, input, true); err != nil {
   623  			return err
   624  		}
   625  	} else {
   626  		return fmt.Errorf("cannot add input node: %v", child)
   627  	}
   628  	return nil
   629  }