objection.js: Bug: 0.9 upsertGraph HasMany no insert in one particular case

It seems to be a bug in 0.9 with upsertGraph in the following case:

Model: grandparent (update) (BelongsToOne parent)-> parent (update) (HasMany child)-> child(insert).

In this case in the dependency graph, the parent get markAsHandled set after established that it has a dependency to grandparent. After this, however, is determined that child also has dependency to parent but markAsHandled for parent is already set.

This leads to hasUnresolvedDependencies failing for child with numHandledNeeds = 0 and needs = 1. This leads to no insert.

markAsHandled should probably be called after all dependencies have been resolved. As a workaround I set manually in DependencyGraph:

    if (rel) {
      if (rel instanceof HasManyRelation) {
        node.needs.push(new HasManyDependency(parentNode, rel));
        parentNode.isNeededBy.push(new HasManyDependency(node, rel));
// new line
        node.numHandledNeeds++;

This will of course only work in my particular case.

Could maybe be useful with an error/warning message for unresolved dependencies? Many thanks.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 21

Most upvoted comments

Thank you! Now there’s a reproduction I can work with 👍

This is not actually a bug, but may be a missing feature. It depends on what you are trying to achieve with this. Are you trying to say: Relate if id is found in the database, otherwise insert a new row.?

If that’s what you want, unfortunately it’s not supported at the moment. What insertMissing means is If parent doesn't have a child by the given id, insert a new item.. It doesn’t check if the id already exists in the database. Implementing this would be all kinds of difficult too, since we would need to do a bunch of new queries to find out if the given item exist in the database. Not to mention what to do to relations that are found, but their children are not… My brains explode just thinking about implementing this.

If you are trying to simply insert the ones with ids, you can just remove the relate option:

// This one can be run in standalone

const objection = require("objection");
const Base = objection.Model;
const Knex = require("knex");

async function test() {
  try {
    var knex = require("knex")({
      client: "pg",
      connection: { host: "localhost", database: "postgres" }
    });

    // Will create a db called bug
    await knex.raw("DROP DATABASE IF EXISTS bug;");
    await knex.raw("CREATE DATABASE bug;");

    knex = require("knex")({ client: "pg", connection: { database: "bug" } });
    await knex.raw(sql);
    Base.knex(knex);

    const bsa = await BusinessDocument.query()
      .eager("UsagePoint")
      .where({ usagepoint_id: null });

    const data = {
      id: bsa[0].id,

      UsagePoint: {
        id: "0360933f-7746-4d4c-aabf-9a248e8629f4",

        Meter: [
          {
            meter_identification: "000000000010000002"
          }
        ],

        Address: {
          streetname: "Hello",
          house_number: "AA",
          postcode: "1000"
        }
      }
    };

    const output = await BusinessDocument.query()
      .upsertGraph(data, {
        insertMissing: true
      })
      .debug();

    const inserted = await BusinessDocument
      .query()
      .eager('UsagePoint.[Meter, Address]');

    console.dir(inserted, {depth: null});
  } catch (error) {
    console.log(error);
  }
}

test();

class Address extends Base {
  static get tableName() {
    return "address";
  }
  static get relationMappings() {
    return {};
  }
}

class BusinessDocument extends Base {
  static get tableName() {
    return "business_document";
  }

  static get relationMappings() {
    return {
      UsagePoint: {
        relation: Base.BelongsToOneRelation,
        modelClass: UsagePoint,
        join: {
          from: "business_document.usagepoint_id",
          to: "usagepoint.id"
        }
      }
    };
  }
}

class Meter extends Base {
  static get tableName() {
    return "meter";
  }

  static get relationMappings() {
    return {
      UsagePoint: {
        relation: Base.BelongsToOneRelation,
        modelClass: UsagePoint,
        join: {
          from: "meter.usagepoint_id",
          to: "usagepoint.id"
        }
      }
    };
  }
}

class UsagePoint extends Base {
  static get tableName() {
    return "usagepoint";
  }

  static get relationMappings() {
    return {
      Address: {
        relation: Base.BelongsToOneRelation,
        modelClass: Address,
        join: {
          from: "usagepoint.address",
          to: "address.id"
        }
      },

      BusinessDocument: {
        relation: Base.HasManyRelation,
        modelClass: BusinessDocument,
        join: {
          from: "usagepoint.id",
          to: "business_document.usagepoint_id"
        }
      },

      Meter: {
        relation: Base.HasManyRelation,
        modelClass: Meter,
        join: {
          from: "usagepoint.id",
          to: "meter.usagepoint_id"
        }
      }
    };
  }
}
var sql = `
create extension if not exists "uuid-ossp";
CREATE TABLE public.business_document
(
    id uuid NOT NULL DEFAULT public.uuid_generate_v4(),
    usagepoint_id uuid,
    CONSTRAINT business_document_pkey PRIMARY KEY (id)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.business_document
    OWNER to martin;
CREATE TABLE public.usagepoint
(
    id uuid NOT NULL DEFAULT public.uuid_generate_v4(),
    ean character varying(30) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    address uuid,
    CONSTRAINT usagepoint_pkey PRIMARY KEY (id)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.usagepoint
    OWNER to martin;
CREATE TABLE public.address
(
    id uuid NOT NULL DEFAULT public.uuid_generate_v4(),
    streetname text,
    house_number text,
    postcode text,
    CONSTRAINT address_pkey PRIMARY KEY (id)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.address
    OWNER to martin;
CREATE TABLE public.meter
(
    id uuid NOT NULL DEFAULT public.uuid_generate_v4(),
    meter_identification text COLLATE pg_catalog."default" DEFAULT NULL::character varying,
    usagepoint_id uuid,
    CONSTRAINT meter_pkey PRIMARY KEY (id)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.meter
    OWNER to martin;
INSERT INTO business_document (id) VALUES ('22e1c365-a066-4398-8155-5a87e1614760');
INSERT INTO usagepoint (id) VALUES ('22e1c365-a066-4398-8155-5a87e1614761');
`;