Knex: Não é possível criar vários PKs (mixin .incrementos com .primary)

Criado em 18 jul. 2014  ·  31Comentários  ·  Fonte: knex/knex

Quero criar uma tabela com base no seguinte despejo do MySQL:

CREATE TABLE `my_table` (
  `cmdId` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `deviceId` char(16) NOT NULL,
  `fnNumber` int(10) unsigned DEFAULT NULL,
  `chNumber` int(10) unsigned DEFAULT NULL,
  `cmd` varchar(50) DEFAULT NULL,
  `cmdDate` datetime DEFAULT NULL,
  `delivered` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`cmdId`,`deviceId`),
  KEY `deviceId` (`deviceId`),
  CONSTRAINT `tblcommands_ibfk_1` FOREIGN KEY (`deviceId`) REFERENCES `second_table` (`deviceId`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Meu knex se parece com:

knex.schema.createTable('my_table', function(t) {
    t.primary(['cmdId', 'deviceId']);
    t.increments('cmdId');
    t.string('deviceId', 16).notNullable().references('second_table.deviceId');
    t.integer('fnNumber').unsigned();
    t.integer('chNumber').unsigned();
    t.string('cmd96', 50);
    t.dateTime('cmdDate');
    t.boolean('delivered');
});

No entanto, há um problema com as chaves primárias. Recebo o seguinte erro:

Error: Multiple primary key defined

Presumo que o método increments já cria PK e o método primary cria outro que causa um erro.

É possível conseguir este formato de mesa?

bug

Comentários muito úteis

Ou você pode criar uma coluna de incremento automático com

table.specificType('myid','serial');

(apenas para Postgresql)

Mas acho que é muito melhor table.increments() criará o PK somente se eu especificar manualmente com table.increments().primary()

Todos 31 comentários

Não, mas provavelmente deveria ser. Vou procurar algo para isso.

De acordo com https://github.com/tgriesser/knex/blob/master/lib/dialects/mysql/schema/column.js#L30

ColumnCompiler_MySQL.prototype.increments = 'int unsigned not null auto_increment primary key';

Portanto, .increments() cria PK. Não é um comportamento muito forte? Acho que será melhor se criar uma coluna AUTO_INCREMENT sem PK, então:

int unsigned not null auto_increment

E para criar PK, temos que chamar .primary() :

table.increments('id').primary();

Isso permitirá criar muitos PKs com a coluna AUTO_INCREMENT .

Como uma workaroud que uso agora:

table.primary(['cmdId', 'deviceId']);
table.integer('cmdId').notNullable();
table.string('deviceId', 16).notNullable().references('second_table.deviceId');

E em then callback:

knex.schema.raw('ALTER TABLE my_table MODIFY cmdId INT UNSIGNED AUTO_INCREMENT');

Eu tenho um problema semelhante - quero usar .increments() sem que seja uma chave primária. No meu caso, é por motivos de desempenho. Quero criar a tabela sem índices (o que significa nenhuma chave primária), inserir muitas linhas nela e, em seguida, adicionar um índice na coluna de incremento automático. Isso tem mais desempenho e, na verdade, é recomendado pela documentação do Postgres (consulte http://www.postgresql.org/docs/9.4/static/populate.html#POPULATE-RM-INDEXES), mas atualmente parece impossível de fazer com knex.

Eu também pareço ter um problema semelhante. Não consigo criar uma tabela que tenha:

knex.schema.createTable("numeric_table", function (table) {
    table.integer("integer_key").primary;

   // Both increments & bigIncrements tries to create primary key.
    table.increments("increment_key");
    table.bigIncrements("big_increment_key");
});

O mesmo problema aqui .increments () tenta definir uma chave primária, mesmo se eu especificar manualmente

Você pode usar uma consulta bruta para adicionar colunas de incrementos sem sua chave primária:

exports.up = function (knex) {
  return Promise.all([
    knex.raw('alter table "my_table" add column "my_seq" bigserial'),
  ]);
};

exports.down = (knex) => {
  return Promise.all([
    knex.schema.table('my_table', (t) => {
      t.dropColumn('my_seq');
    }),
  ]);
};

Ou você pode criar uma coluna de incremento automático com

table.specificType('myid','serial');

(apenas para Postgresql)

Mas acho que é muito melhor table.increments() criará o PK somente se eu especificar manualmente com table.increments().primary()

Por que não sair
increments: 'serial'
em vez de
increments: 'serial primary key'

Qual é o problema com isso?

Ainda obtenho o mesmo erro.
IMHO .increments não deve criar PK.

Alguma notícia sobre este assunto?

Concordo - embora seja certamente possível contornar esse problema de várias maneiras, é bastante bizarro que a API não tenha suporte de primeira classe para a criação de uma coluna de incremento automático sem torná-la uma chave primária.

Dito isso, imagino que isso ainda não tenha sido corrigido, porque corrigir isso da maneira mais sensata seria uma alteração significativa e, portanto, teria que ser parte de um solavanco da versão principal.

@zacronos
O fato é que se usarmos .primary() em uma coluna ao criar uma tabela, o resultado seria uma consulta separada para criar a tabela e outra consulta alter table para definir as chaves primárias.
Acho que no MySQL (ou talvez em outros DBMSs também), você não pode ter um campo auto_incremement sem ser PK.
Então, o knex cria a tabela com a chave primária, e então não pode apenas alter definir duas chaves primárias, uma vez que a tabela já tem uma.
Se pudéssemos corrigir isso, talvez .increments ainda criasse PK, mas permite que outras colunas também sejam PK em uma consulta create table , então provavelmente não precisaríamos de um aumento de versão principal.

Ou podemos adicionar outro tipo de coluna para isso e deixar .increments como está para não quebrar a compatibilidade com versões anteriores.

@ amir-s: bem, isso explica por que knex se comporta dessa forma - aparentemente, ele está tentando ter um comportamento equivalente nos diferentes SGBDs suportados. Eu particularmente não gosto disso, mas pelo menos não parece bizarro agora.

O problema com sua sugestão é que, por definição, você não pode ter várias chaves primárias em uma mesa. (Você pode ter 1 PK composta em várias colunas, mas isso é muito diferente de ter 2 PKs de coluna única).

Portanto, se .increments criar um PK, é impossível permitir que outras colunas também sejam marcadas como PK. Seria possível criar uma opção que pode ser passada para .increments para desabilitar o comportamento da chave primária no Postgres e em qualquer outro SGBD que suporte tal comportamento. Talvez essa seja a melhor solução, já que não quebra a compatibilidade com versões anteriores e também não quebra a compatibilidade entre DBMS. Eu ainda não gosto disso e também de alterar o comportamento padrão no Postgres para que .increments também não o torne um PK, mas seria um PR mais facilmente aceito.

Se alguém quiser enviar um PR nesse sentido, argumentarei a seu favor. :-)

@zacronos Desculpe, por "vários PKs" eu quis dizer "um PK em várias colunas".

De acordo com todos os comentários acima. Esse comportamento foi inesperado ao tentar criar uma chave primária composta, bem como uma coluna de incremento automático sem chave. Seria ótimo ver isso implementado.

Também estou tendo esse problema, mas no caso de usar Postgres e usar campos seriais como chaves estrangeiras - a implementação do knex de um campo serial Postgres é increments() e isso os torna chaves primárias por padrão. Eu quero ser capaz de construir sqlite3 assim como postgres, então usar uma consulta knex bruta me limitaria a ou. Alguma sugestão de solução alternativa?

Este tópico não tem recebido muita atenção há algum tempo. Gostaria de reiterar o que considero uma solução ideal e compatível com versões anteriores (que pode, portanto, fazer parte de qualquer versão menor): adicionar uma nova opção a .increments() , talvez primaryKey: false , que (para DBMSs onde isso é possível) desativa a parte da chave primária de sua funcionalidade.

Isso é totalmente compatível com as versões anteriores, pois .increments() mantém seu comportamento padrão.

@zacronos Deu uma olhada rápida no código. Parece trivial. Vou fazer uma RP
https://github.com/DeedMob/knex/tree/master/src

Mesmo problema aqui. Tem que usar consulta bruta para evitar que bigIncrements() tente criar outra chave primária.

Gambiarra:

table.bigincrements();
table.biginteger('other').notNullable();
table.dropPrimary();
table.primary(['id', 'other']);

Se eu entendi a investigação anterior sobre este tópico corretamente, então, presumivelmente, knex força uma chave primária estritamente porque alguns bancos de dados (ou seja, MySQL) não podem criar séries sem que seja uma chave primária? Se for esse o caso, eu não me preocuparia muito com a compatibilidade com versões anteriores no que diz respeito ao Postgres.

Um usuário MySQL então espera que seja um primário.
Um usuário Postgres não. _ (O que é evidente acima) _

Por esta razão, acho que é bastante seguro fazer um ajuste no Postgres para _não_ forçar a chave primária e adicioná-la ao log de mudanças. Não como um patch, mas para a próxima versão principal.

Por esta razão, eu acho que é bastante seguro fazer um ajuste no Postgres para não forçar a chave primária e adicioná-la ao changelog. Não como um patch, mas para a próxima versão principal.

Eu conheço dezenas de pessoas que sempre escreveram suas migrações escrevendo apenas table.bigincrements('id') sem .primary() também para o postgresql, porque sempre funcionou assim.

Eu pensei que havia uma opção extra, que deveria ser usada para dizer que a chave primária não deveria ser criada, mas parece que ela não está lá. Pelo menos eu não encontrei nada para implementações postgres / mysql sobre pular a criação da chave primária.

Isso seria uma grande mudança de quebra de compatibilidade com versões anteriores em migrações que eu odiaria ver isso acontecendo. Se quisermos continuar corrigindo esta API de migração antiga, primeiro precisamos adicionar suporte de controle de versão para evitar que todas as pessoas consertem suas migrações antigas em cada versão do Knex (essas alterações no código de migração são realmente difíceis de verificar se funcionam exatamente da mesma forma com testes automáticos, por isso é parte muito vulnerável no código dos usuários do knex).

Qualquer solução?

Alguma novidade sobre isso?

Este tópico não tem recebido muita atenção há algum tempo. Gostaria de reiterar o que considero uma solução ideal e compatível com versões anteriores (que pode, portanto, fazer parte de qualquer versão menor): adicionar uma nova opção a .increments() , talvez primaryKey: false , que (para DBMSs onde isso é possível) desativa a parte da chave primária de sua funcionalidade.

Isso é totalmente compatível com as versões anteriores, pois .increments() mantém seu comportamento padrão.

Podemos usar incrementos como este? Qualquer atualização?

Como vai isso? é realmente uma dor, especialmente em MSSQL, a restrição de PK não é nomeada por padrão, mas como "PK__requests__DD771E3CC0CD0A5E", portanto, não posso nem mesmo descartá-la e criar PK em outro lugar.
Tentei especificar o nome da restrição, mas não funciona para incrementos ()

Ainda nada sobre isso?

Também gostaria muito do parâmetro opcional em .increments! Consultas brutas obviamente poderiam funcionar, mas são irritantes para aqueles que executam vários bancos de dados diferentes (por exemplo, mariadb para produção, sqlite para testes). Alguma atualização sobre isso? Parece que já tem um

Eu novamente concordo, eu já precisei daquelas 3 vezes em um único projeto.

O mesmo problema aqui, tendo um parâmetro extra para desabilitar as costuras PK razoáveis.

Semelhante ao # 2896, você pode usar para MySQL:

t.primary(["cmdId", "deviceId"]);
t.specificType("cmdId", "int(10) unsigned AUTO_INCREMENT").notNullable();
t.string("deviceId", 16).notNullable().references("second_table.deviceId");
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

koskimas picture koskimas  ·  3Comentários

npow picture npow  ·  3Comentários

ghost picture ghost  ·  3Comentários

mtom55 picture mtom55  ·  3Comentários

legomind picture legomind  ·  3Comentários