question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Three-level-nested upsertGraph fails on duplicate third level

See original GitHub issue

Hello there!

I’m using upsertGraph to upsert a three-level-nested object, however if the second level contains the same third level, it throws a UNIQUE constraint error on insert. Fiddling with upsertGraph options (relate/unrelate) produces the same error. I must have missed something in the config to cope with this case, but alas can’t seem to wrap my head around it, any help would be very appreciated.

The following script reproduces the issue with version 2.0.7.

let Model

try {
  Model = require('./').Model
} catch (err) {
  Model = require('objection').Model
}

const Knex = require('knex')

async function main() {
  await createSchema()

  //////////////////////////////////////////////////////////////
  // Your reproduction
  //////////////////////////////////////////////////////////////

  await Foos.query().upsertGraph(
    {
      id: 'a',
      bars: [
        {
          id: 'b1',
          bazs: [{ id: 'c' }]
        },
        {
          id: 'b2',
          bazs: [{ id: 'c' }]
        }
      ]
    },
    { insertMissing: true }
  )

  const a = await Foos.query()
    .findById('a')
    .withGraphFetched('[bars.[bazs]]')

  console.dir(a, { depth: null })

  await knex.destroy()
}

///////////////////////////////////////////////////////////////
// Database
///////////////////////////////////////////////////////////////

const knex = Knex({
  client: 'sqlite3',
  useNullAsDefault: true,
  connection: {
    filename: ':memory:'
  }
})

Model.knex(knex)

///////////////////////////////////////////////////////////////
// Models
///////////////////////////////////////////////////////////////

class Bazs extends Model {
  static get tableName() {
    return 'bazs'
  }
}

class Bars extends Model {
  static get tableName() {
    static get tableName() {
    return 'bars'
  }

  static get relationMappings() {
    return {
      bazs: {
        relation: Model.HasManyRelation,
        modelClass: Bazs,
        join: {
          from: 'bars.id',
          to: 'bazs.barId'
        }
      }
    }
  }
}

class Foos extends Model {
  static get tableName() {
    return 'foos'
  }

  static get relationMappings() {
    return {
      bars: {
        relation: Model.HasManyRelation,
        modelClass: Bars,
        join: {
          from: 'foos.id',
          to: 'bars.fooId'
        }
      }
    }
  }
}

///////////////////////////////////////////////////////////////
// Schema
///////////////////////////////////////////////////////////////

async function createSchema() {
  await knex.schema
    .dropTableIfExists('foos')
    .dropTableIfExists('bars')
    .dropTableIfExists('bazs')

  await knex.schema
    .createTable('foos', table => {
      table.string('id').primary()
    })
    .createTable('bars', table => {
      table.string('id').primary()
      table.string('fooId')
    })
    .createTable('bazs', table => {
      table.string('id').primary()
      table.string('barId')
    })
}

main()
  .then(() => console.log('success'))
  .catch(console.error)

Here’s the sqlite3 debug and error:

{
  method: 'insert',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [ 'b1', 'c' ],
  __knexQueryUid: 'cef388f2-b202-448e-9543-538d7dc14a6e',
  sql: 'insert into `bazs` (`barId`, `id`) values (?, ?)'
}
{
  method: 'insert',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [ 'b2', 'c' ],
  __knexQueryUid: 'c9ac7a18-4e30-416e-bedb-09f5b077f728',
  sql: 'insert into `bazs` (`barId`, `id`) values (?, ?)'
}
UniqueViolationError: insert into `bazs` (`barId`, `id`) values ('b2', 'c') - SQLITE_CONSTRAINT: UNIQUE constraint failed: bazs.id
    at wrapError (node_modules/db-errors/lib/dbErrors.js:19:14)
    at handleExecuteError (node_modules/objection/lib/queryBuilder/QueryBuilder.js:1494:32)
    at QueryBuilder.execute (node_modules/objection/lib/queryBuilder/QueryBuilder.js:685:13) {
  name: 'UniqueViolationError',
  nativeError: [Error: SQLITE_CONSTRAINT: UNIQUE constraint failed: bazs.id] {
    errno: 19,
    code: 'SQLITE_CONSTRAINT'
  },
  client: 'sqlite',
  table: 'bazs',
  columns: [ 'id' ],
  constraint: undefined,
  schema: undefined
}

Here’s the postgresql debug and error:

{
  method: 'insert',
  options: {},
  timeout: false,
  cancelOnTimeout: false,
  bindings: [ 'b1', 'c', 'b2', 'c' ],
  __knexQueryUid: 'f4d6f94c-7892-4503-ad73-2d8c584a9da1',
  sql: 'insert into "bazs" ("barId", "id") values (?, ?), (?, ?) returning "id"',
  returning: 'id'
}
UniqueViolationError: insert into "bazs" ("barId", "id") values ($1, $2), ($3, $4) returning "id" - duplicate key value violates unique constraint "bazs_pkey"
    at wrapError (node_modules/db-errors/lib/dbErrors.js:19:14)
    at handleExecuteError (node_modules/objection/lib/queryBuilder/QueryBuilder.js:1494:32)
    at QueryBuilder.execute (node_modules/objection/lib/queryBuilder/QueryBuilder.js:685:13) {
  name: 'UniqueViolationError',
  nativeError: error: duplicate key value violates unique constraint "bazs_pkey"
      at Connection.parseE (node_modules/pg/lib/connection.js:604:13)
      at Connection.parseMessage (node_modules/pg/lib/connection.js:403:19)
      at Socket.<anonymous> (node_modules/pg/lib/connection.js:123:22)
      at Socket.emit (events.js:200:13)
      at addChunk (_stream_readable.js:294:12)
      at readableAddChunk (_stream_readable.js:275:11)
      at Socket.Readable.push (_stream_readable.js:210:10)
      at TCP.onStreamRead (internal/stream_base_commons.js:166:17) {
    name: 'error',
    length: 177,
    severity: 'ERROR',
    code: '23505',
    detail: 'Key (id)=(c) already exists.',
    hint: undefined,
    position: undefined,
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: 'public',
    table: 'bazs',
    column: undefined,
    dataType: undefined,
    constraint: 'bazs_pkey',
    file: 'nbtinsert.c',
    line: '570',
    routine: '_bt_check_unique'
  },
  client: 'postgres',
  table: 'bazs',
  columns: [ 'id' ],
  constraint: 'bazs_pkey',
  schema: undefined
}

Cheers, Adrien.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:5

github_iconTop GitHub Comments

1reaction
koskimascommented, Dec 25, 2019

You need to explicitly tell objection the two items are the same using #ref + #id like this

PublishingCompanies.query().upsertGraph({
  id: 'publishing-company',
  books: [{
    id: 'book-1',
    authors: [{ id: 'famous-author', '#id': 'whatEverIdYouChooseToUse' }, { id: 'lesser-author' }]
  }, {
    id: 'book-2',
    authors: [{ '#ref': 'whatEverIdYouChooseToUse' }]
  }]
})

otherwise objection assumes you mistakenly used the same id twice.

1reaction
koskimascommented, Dec 25, 2019

The error is pretty clear. You cannot insert two items with the same id. If bazs was a ManyToManyRelation you could use #ref + #id to refer to the same item. You are using HasManyRelation which can only be owned by one parent at a time, making what you want theoretically impossible, even if your db accepted duplicate ids (which would also be insane).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Hidden dangers of duplicate key violations in PostgreSQL and ...
The “duplicate key violates unique constraint” error notifies the caller that a retry is needed. This seems like an intuitive approach, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found