ent icon indicating copy to clipboard operation
ent copied to clipboard

Delete actions do not call `removeInboundEdge` on field edges

Open Swahvay opened this issue 7 months ago • 0 comments

Ents with a field edge get that edge created automatically (and removed automatically when unset) through their builder. This is done in the getEditedFields method of the builder, as defined in the builder template (specifically line 291):

https://github.com/lolopinto/ent/blob/47372cfd6c0376753c805a2249ed3428866077b7/internal/tscode/builder.tmpl#L276-L295

The generated code ends up looking something like this:

private async getEditedFields(): Promise<Map<string, any>> {
  const input = this.input;

  const result = new Map<string, any>();

  const addField = function (key: string, value: any) {
    if (value !== undefined) {
      result.set(key, value);
    }
  };
  addField('competitionEventID', input.competitionEventId);
  if (input.competitionEventId !== undefined) {
    if (input.competitionEventId) {
      this.orchestrator.addInboundEdge(
        input.competitionEventId,
        EdgeType.CompetitionEventToEntries,
        NodeType.CompetitionEvent,
      );
    }
    if (
      this.existingEnt &&
      this.existingEnt.competitionEventId &&
      this.existingEnt.competitionEventId !== input.competitionEventId
    ) {
      this.orchestrator.removeInboundEdge(
        this.existingEnt.competitionEventId,
        EdgeType.CompetitionEventToEntries,
      );
    }
  }
  return $result;
}

This method is passed into the Orchestrator in the constructor of the builder like this:

this.orchestrator = new Orchestrator({
  viewer,
  operation: this.operation,
  tableName: 'generic_competition_entries',
  key: 'id',
  loaderOptions: GenericCompetitionEntry.loaderOptions(),
  builder: this,
  action,
  schema,
  editedFields: () => this.getEditedFields.apply(this), // <--- Right here
  updateInput,
  fieldInfo: genericCompetitionEntryLoaderInfo.fieldInfo,
  ...opts,
});

This works great for insert/update operations, but fails with delete actions. This is because the delete action always defines getInput as

getInput(): GenericCompetitionEntryInput {
  return {};
}

So the checks in getEditedFields always fail. I think the simplest way to get around this is to just add a check for this.operation === WriteOperation.Delete to always call removeInboundEdge even when input is empty. Thus changing the call to look like this:

private async getEditedFields(): Promise<Map<string, any>> {
  const input = this.input;

  const result = new Map<string, any>();

  const addField = function (key: string, value: any) {
    if (value !== undefined) {
      result.set(key, value);
    }
  };
  addField('competitionEventID', input.competitionEventId);
- if (input.competitionEventId !== undefined) {
+ if (
+   input.competitionEventId !== undefined ||
+   this.operation === WriteOperation.Delete
+ ) {
    if (input.competitionEventId) {
      this.orchestrator.addInboundEdge(
        input.competitionEventId,
        EdgeType.CompetitionEventToEntries,
        NodeType.CompetitionEvent,
      );
    }
    if (
      this.existingEnt &&
      this.existingEnt.competitionEventId &&
      this.existingEnt.competitionEventId !== input.competitionEventId
    ) {
      this.orchestrator.removeInboundEdge(
        this.existingEnt.competitionEventId,
        EdgeType.CompetitionEventToEntries,
      );
    }
  }
  return $result;
}

This bug doesn't have much effect since the ents are deleted so fetching edges doesn't return deleted ents, but when you try to do something like event.queryEntries().queryRawCount(), that only fetches over the edge table, which will now return a bad count.

Swahvay avatar Jul 09 '24 20:07 Swahvay