sequelize-typescript icon indicating copy to clipboard operation
sequelize-typescript copied to clipboard

@Unique and @Column decorators unique constraint name on postgresql/sqlite

Open ngruychev opened this issue 2 years ago • 0 comments

Issue

Using the @Unique constraint with postgresql/sqlite doesn't set the desired constraint name.

Versions

  • sequelize: 6.35.2
  • sequelize-typescript: 2.1.6
  • typescript: 5.3.3

Issue type

  • [x] bug report
  • [ ] feature request

Actual behavior

Indexes follow a default naming convention - e.g. TableName_fieldName_key (TableName_fieldName1_fieldName2_key in case of composite indexes) in the case of PostgreSQL

Expected behavior

Model fields annotated with @Unique("IndexName"), @Unique({ name: "IndexName", msg: "msg" }), and @Column({ type: DataType.WHATEVER, unique: "IndexName" }) should result in unique indexes with the appropriate names in PostgreSQL and sqlite.

Steps to reproduce

Run the code below with npx ts-node index.ts. PostgreSQL and sqlite (didn't test other engines) follow Actual Behavior, while MySQL follows Expected behavior for all 4 columns, confirming what the behavior should be.

Related code

Code:

import { Model, Table, Column, DataType, Unique, PrimaryKey, AutoIncrement, Sequelize, Index } from 'sequelize-typescript';

@Table({ tableName: 'Item' })
class Item extends Model {
  @PrimaryKey
  @AutoIncrement
  @Column(DataType.INTEGER)
  id: number = 0;

  @Unique("uniqueThingIndex")
  @Column(DataType.STRING)
  thing: string = '';

  @Column({ type: DataType.STRING, unique: 'uniqueThingIndexTwo' })
  thingTwo: string = '';

  @Column(DataType.STRING)
  @Index({ name: 'indexThingThree', unique: true })
  thingThree: string = '';
}

const dialects = [
  { dialect: 'sqlite', constraintsQuery: "PRAGMA index_list('Item')", options: { storage: ':memory:' } },
  {
    dialect: 'postgres',
    constraintsQuery: "SELECT indexname FROM pg_indexes WHERE tablename = 'Item'",
    options: {
      host: 'localhost',
      port: 5432,
      username: 'test',
      password: 'test',
      database: 'test',
    },
  },
  {
    dialect: 'mysql',
    constraintsQuery: "SELECT INDEX_NAME FROM information_schema.statistics WHERE table_name = 'Item'",
    options: {
      host: 'localhost',
      port: 3306,
      username: 'test',
      password: 'test',
      database: 'test',
    },
  },
] as const;


(async () => {
  for (const { dialect, constraintsQuery, options } of dialects) {
    console.log('---', dialect, '---');
    const db = new Sequelize({
      dialect,
      storage: 'database.sqlite',
      models: [Item],
      ...options,
    });
    await db.sync();

    // get constraints via sql query
    const result = await db.query(constraintsQuery);
    console.log(result[0]);
    await db.close();
  }
})();

Output:

--- sqlite ---
Executing (default): SELECT name FROM sqlite_master WHERE type='table' AND name='Item';
Executing (default): CREATE TABLE IF NOT EXISTS `Item` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `thing` VARCHAR(255), `thingTwo` VARCHAR(255), `thingThree` VARCHAR(255), `thingFour` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, UNIQUE (`thing`), UNIQUE (`thingTwo`), UNIQUE (`thingThree`));
Executing (default): PRAGMA INDEX_LIST(`Item`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Item_1`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Item_2`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Item_3`)
Executing (default): CREATE UNIQUE INDEX `indexThingFour` ON `Item` (`thingFour`)
Executing (default): PRAGMA index_list('Item')
[
  {
    seq: 0,
    name: 'indexThingFour',
    unique: 1,
    origin: 'c',
    partial: 0
  },
  {
    seq: 1,
    name: 'sqlite_autoindex_Item_3',
    unique: 1,
    origin: 'u',
    partial: 0
  },
  {
    seq: 2,
    name: 'sqlite_autoindex_Item_2',
    unique: 1,
    origin: 'u',
    partial: 0
  },
  {
    seq: 3,
    name: 'sqlite_autoindex_Item_1',
    unique: 1,
    origin: 'u',
    partial: 0
  }
]
--- postgres ---
Executing (default): SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'Item'
Executing (default): CREATE TABLE IF NOT EXISTS "Item" ("id"  SERIAL , "thing" VARCHAR(255), "thingTwo" VARCHAR(255), "thingThree" VARCHAR(255), "thingFour" VARCHAR(255), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, UNIQUE ("thing"), UNIQUE ("thingTwo"), UNIQUE ("thingThree"), PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'Item' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Executing (default): CREATE UNIQUE INDEX "indexThingFour" ON "Item" ("thingFour")
Executing (default): SELECT indexname FROM pg_indexes WHERE tablename = 'Item'
[
  { indexname: 'Item_pkey' },
  { indexname: 'Item_thing_key' },
  { indexname: 'Item_thingTwo_key' },
  { indexname: 'Item_thingThree_key' },
  { indexname: 'indexThingFour' }
]
--- mysql ---
Executing (default): SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = 'Item' AND TABLE_SCHEMA = 'test'
Executing (default): CREATE TABLE IF NOT EXISTS `Item` (`id` INTEGER auto_increment , `thing` VARCHAR(255), `thingTwo` VARCHAR(255), `thingThree` VARCHAR(255), `thingFour` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, UNIQUE `uniqueThingIndex` (`thing`), UNIQUE `uniqueThingIndexTwo` (`thingTwo`), UNIQUE `uniqueThingIndexThree` (`thingThree`), PRIMARY KEY (`id`)) ENGINE=InnoDB;
Executing (default): SHOW INDEX FROM `Item` FROM `test`
Executing (default): ALTER TABLE `Item` ADD UNIQUE INDEX `indexThingFour` (`thingFour`)
Executing (default): SELECT INDEX_NAME FROM information_schema.statistics WHERE table_name = 'Item'
[
  { INDEX_NAME: 'indexThingFour' },
  { INDEX_NAME: 'PRIMARY' },
  { INDEX_NAME: 'uniqueThingIndex' },
  { INDEX_NAME: 'uniqueThingIndexThree' },
  { INDEX_NAME: 'uniqueThingIndexTwo' }
]

tsconfig:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "lib": ["esnext", "dom"],
    "module": "nodenext",
    "moduleDetection": "force",
    "strict": true,
    "target": "es2019"
  },
  "ts-node": {
    "experimentalResolver": true
  },
}

ngruychev avatar Jan 05 '24 12:01 ngruychev