pg-mem icon indicating copy to clipboard operation
pg-mem copied to clipboard

Default values are evaluated at column creation time rather than insert time

Open zookatron opened this issue 1 year ago • 3 comments

Describe the bug

The Postgres documentation specifically mentions that default values should be evaluated when a record is inserted, not when the table is created: https://www.postgresql.org/docs/current/ddl-default.html The current implementation seems to evaluate default values at table creation time, breaking many common default value use cases.

QueryError: insert into "devices" ("value") values ($1), ($2) - ERROR: insert into "devices" (value, id) values ($1, $2) returning "id" - duplicate key value violates unique constraint "devices_pkey"
DETAIL: Key (id)=(cfeea46a-be4d-45f4-ba47-21fe2316eeb9) already exists.

🐜 This seems to be an execution error, which means that your request syntax seems okay,
    but the resulting statement cannot be executed → Probably not a pg-mem error.

*️⃣ Failed SQL statement: insert into "devices" ("value") values ('1'), ('2');

👉 You can file an issue at https://github.com/oguimbal/pg-mem along with a way to reproduce this error (if you can), and  the stacktrace:

    at BIndex.add (/node_modules/pg-mem/index.js:9506:19)
    at MemoryTable.indexElt (/node_modules/pg-mem/index.js:8276:21)
    at MemoryTable.doInsert (/node_modules/pg-mem/index.js:8155:14)
    at Insert.performMutation (/node_modules/pg-mem/index.js:9239:33)
    at Insert._doExecuteOnce (/node_modules/pg-mem/index.js:4335:29)
    at Insert.enumerate (/node_modules/pg-mem/index.js:4343:31)
    at enumerate.next (<anonymous>)
    at SelectExec.execute (/node_modules/pg-mem/index.js:3451:75)
    at /node_modules/pg-mem/index.js:5975:42
    at pushExecutionCtx (/node_modules/pg-mem/index.js:588:16) {
  data: {
    error: 'insert into "devices" (value, id) values ($1, $2) returning "id" - duplicate key value violates unique constraint "devices_pkey"',
    details: 'Key (id)=(cfeea46a-be4d-45f4-ba47-21fe2316eeb9) already exists.',
    code: '23505'
  },
  code: '23505',
  location: { start: 0, end: 0 },
  [Symbol(errorDetailsIncluded)]: true
}

To Reproduce

package.json:

{
  "type": "module",
  "dependencies": {
    "knex": "2.5.1",
    "pg-mem": "2.6.13"
  }
}

index.js:

import { newDb, DataType } from 'pg-mem';
import { randomUUID } from 'node:crypto';

const database = newDb();
const knex = database.adapters.createKnex();

database.public.registerFunction({
  name: 'uuid',
  args: [],
  returns: DataType.uuid,
  implementation: () => randomUUID(),
});

await knex.schema.createTable('devices', (table) => {
  table.uuid('id').primary().notNullable().defaultsTo(knex.raw('UUID()'));
  table.string('value');
});
await knex.table('devices').insert([{value: '1'}, {value: '2'}]);

knex.destroy();

pg-mem version

2.6.13

zookatron avatar Sep 13 '23 00:09 zookatron

Same issue using :

  • typeorm: 9.0.1
  • pg-mem: 2.7.1

grenard59 avatar Oct 10 '23 14:10 grenard59

@oguimbal Any hints to where we can start if we want to fix this? I just started using pg-mem and am open to contributing to this issue.

BasitAli avatar Dec 04 '23 14:12 BasitAli

Found a workaround. Since it's evaluating only once, a peek through the code suggested that it's simply checking for the type of function and evaluating in case it's a function. So, double wrapping the evaluator works. For the above example, this should work,

database.public.registerFunction({
  name: 'uuid',
  args: [],
  returns: DataType.uuid,
  implementation: () => () => randomUUID(),
});

Or when using the uuid package,

    schema.registerFunction({
      name: 'uuid_generate_v4',
      returns: DataType.uuid,
      implementation: () => v4
    });

BasitAli avatar Dec 04 '23 15:12 BasitAli