oguimbal / pg-mem

An in memory postgres DB instance for your unit tests
MIT License
1.89k stars 94 forks source link

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

Open zookatron opened 10 months ago

zookatron commented 10 months ago

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

grenard59 commented 9 months ago

Same issue using :

BasitAli commented 7 months ago

@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 commented 7 months ago

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
    });