Knex: Ajout de la capacité de type upsert

CrĂ©Ă© le 30 aoĂ»t 2013  Â·  54Commentaires  Â·  Source: knex/knex

ApportĂ© par @adamscybot dans tgriesser/bookshelf#55 - cela pourrait ĂȘtre une fonctionnalitĂ© intĂ©ressante Ă  ajouter.

feature request

Commentaire le plus utile

@NicolajKN Vous ne devriez pas utiliser toString(), cela pourrait causer de nombreux types de problÚmes et ne transmettra pas les valeurs via les liaisons à la base de données (trou de sécurité potentiel d'injection SQL).

MĂȘme ceci fait correctement serait comme ceci:

const query = knex('account').insert(accounts);
const safeQuery = knex.raw('? ON CONFLICT DO NOTHING', [query]);

Tous les 54 commentaires

Je suis d'accord, ce _serait_ une fonctionnalité intéressante !

:+1:

:+1:

J'importe des données à partir d'un fichier CSV et il y a de fortes chances que quelques-uns des enregistrements se chevauchent depuis la derniÚre importation (c'est-à-dire que la derniÚre fois a été importée du 1er janvier au 31 mai, cette fois importée du 31 mai au 18 juin).

Heureusement, le systĂšme tiers attribue des identifiants uniques de maniĂšre fiable.

Quelle est la meilleure façon d'insérer les nouveaux enregistrements et de mettre à jour les anciens ?

Je n'ai pas encore essayé, mais je pensais que ce serait quelque chose comme ça:

var ids = records.map(function (json) { return json.id })
  ;

Records.forge(ids).fetchAll().then(function () {
  records.forEach(function (record) {
    // now the existing records are loaded in the collection ?
    Object.keys(record).forEach(function (key) {
      Records.forge(record.id).set(key, record[key]);
    });
  });
  Records.invokeThen('save').then(function () {
    console.log('Records have been either inserted or updated');
  });
});

De plus, parfois, la chose que je stocke est stockée par une valeur d'identifiant déterminée, telle qu'un hachage. Dans ces cas, je veux juste ajouter ou remplacer les données.

Je n'utilise pas toujours SQL comme SQL traditionnel. Je l'utilise souvent comme NoSQL hybride avec l'avantage d'un mappage de relations et d'index clairs.

:+1:

Salut,
il y a des nouvelles sur cette nouvelle fonctionnalité?

Ou quelqu'un peut-il recommander des exemples montrant comment simuler cette fonctionnalité pour mysql ?

Merci

En ce moment, je le fais avec raw , mais je travaille dur pour que cela soit bientĂŽt disponible ici.

Au fait, Postgres vient d'implémenter le support upsert :+1 :

http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=168d5805e4c08bed7b95d351bf097cff7c07dd65

https://news.ycombinator.com/item?id=9509870

La syntaxe est INSERT ... ON CONFLICT DO UPDATE

Je cherchais un moyen de faire un REPLACE INTO dans MySql et j'ai trouvĂ© cette demande de fonctionnalitĂ©. Étant donnĂ© que REPLACE et INSERT ont exactement la mĂȘme syntaxe dans MySql, j'imagine qu'il est plus facile Ă  implĂ©menter qu'un ON DUPLICATE KEY UPDATE . Est-il prĂ©vu d'implĂ©menter un REPLACE ? Est-ce qu'un PR serait quelque chose de valeur?

Des mises à jour à ce sujet, en particulier avec PostreSQL 9.5 ?

Je pense qu'une question importante est de savoir s'il faut ou non exposer la mĂȘme signature de mĂ©thode upsert pour diffĂ©rents dialectes, tels que PostgreSQL et MySQL. Dans Sequelize, un problĂšme a Ă©tĂ© soulevĂ© concernant la valeur de retour de upsert : https://github.com/sequelize/sequelize/issues/3354.

Je me rends compte que certaines des mĂ©thodes de la bibliothĂšque KnexJS ont des distinctions concernant les valeurs de retour dans le contexte de diffĂ©rents dialectes (tels que insert , oĂč un tableau du premier identifiant insĂ©rĂ© est renvoyĂ© pour Sqlite et MySQL, tandis qu'un tableau de tous les identifiants insĂ©rĂ©s sont retournĂ©s avec PostgreSQL).

Selon la documentation, la syntaxe INSERT ... ON DUPLICATE KEY UPDATE dans MySQL a le comportement suivant (http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html) :

Avec ON DUPLICATE KEY UPDATE, la valeur des lignes affectées par ligne est 1 si la ligne est insérée en tant que nouvelle ligne, 2 si une ligne existante est mise à jour et 0 si une ligne existante est définie sur ses valeurs actuelles.

Alors que dans PostgreSQL (http://www.postgresql.org/docs/9.5/static/sql-insert.html):

En cas de réussite, une commande INSERT renvoie une balise de commande de la forme

INSERT oid count

Le nombre est le nombre de lignes insérées ou mises à jour. Si count est exactement égal à un et que la table cible a des OID, alors oid est l'OID affecté à la ligne insérée. La ligne unique doit avoir été insérée plutÎt que mise à jour. Sinon oid vaut zéro.

Si la commande INSERT contient une clause RETURNING, le résultat sera similaire à celui d'une instruction SELECT contenant les colonnes et les valeurs définies dans la liste RETURNING, calculées sur la ou les lignes insérées ou mises à jour par la commande.

Dans ce cas, les valeurs de retour peuvent ĂȘtre modifiĂ©es avec la clause RETURNING .

Les pensées?

J'ai patchĂ© Client_PG pour ajouter la mĂ©thode "onConflict" pour l'insertion. Supposons que nous voulions mettre Ă  jour les informations d'identification github oauth, nous pouvons Ă©crire la requĂȘte comme ceci :

const profile = {
    access_token: "blah blah",
    username: "foobar",
    // ... etc
  }

  const oauth = {
    uid: "13344398",
    provider: "github",
    created_at: new Date(),
    updated_at: new Date(),
    info: profile,
  };

  // todo: add a "timestamp" method

const insert = knex("oauths").insert(oauth).onConflict(["provider", "uid"],{
  info: profile,
  updated_at: new Date(),
});

console.log(insert.toString())

Le tableau de noms de colonnes spécifie la contrainte d'unicité.

insert into "authentications" ("created_at", "info", "provider", "uid", "updated_at") values ('2016-02-14T14:42:18.342+08:00', '{\"access_token\":\"blah blah\",\"username\":\"foobar\"}', 'github', '13344398', '2016-02-14T14:42:18.342+08:00') on conflict ("provider", "uid")  do update set "info" = '{\"access_token\":\"blah blah\",\"username\":\"foobar\"}', "updated_at" = '2016-02-14T14:42:18.343+08:00'

Voir l'essentiel : https://gist.github.com/hayeah/1c8d642df5cfeabc2a5b pour le patch de singe.

Il s'agit d'une expérience super hacky... alors ne copiez pas et ne collez pas exactement le patch du singe dans votre code de production : p

ProblÚmes connus :

  • Le correctif de singe est sur QueryBuilder affecte tous les dialectes, car Client_PG ne spĂ©cialise pas le constructeur.
  • Ne prend pas en charge la mise Ă  jour brute comme count = count + 1
  • onConflict devrait probablement ĂȘtre lancĂ© si la mĂ©thode de requĂȘte n'est pas insĂ©rĂ©e.

Retour?

@hayeah J'aime votre approche et cela convient à Postgres. Je vais essayer votre patch de singe dans un projet pour voir si je peux détecter empiriquement d'autres problÚmes que ceux que vous avez signalés.

Suggestion de syntaxe : knex('table').upsert(['col1','col2']).insert({...}).update({...}); oĂč upsert prendrait dans la dĂ©claration de condition. De cette façon, ce n'est pas spĂ©cifique Ă  la base de donnĂ©es.

Un rĂ©sumĂ© des diffĂ©rentes implĂ©mentations des upserts peut ĂȘtre trouvĂ© sur https://en.wikipedia.org/wiki/Merge_ (SQL)

Je suis également intéressé par cette capacité. Cas d'utilisation : création d'un systÚme qui dépend de nombreuses données externes provenant d'un service externe ; Je l'interroge périodiquement pour les données que j'enregistre dans une base de données MySQL locale. Sera probablement en utilisant knex.raw pour l'instant.

Également intĂ©ressĂ©, mais dans mon cas d'utilisation, il faudrait que cela fonctionne d'une maniĂšre qui ne soit pas basĂ©e sur des conflits, car les colonnes n'ont pas toujours des contraintes "uniques" - mettez simplement Ă  jour les entrĂ©es correspondant Ă  la requĂȘte si elles existent, sinon insĂ©rez nouvelles rangĂ©es.

@haywirez Je suis curieux de savoir pourquoi il n'y a pas de contraintes uniques ? Ne seriez-vous pas exposé à des conditions de course ?

@hayeah J'ai un cas d'utilisation spĂ©cifique avec des donnĂ©es Ă  fenĂȘtre temporelle, stockant des entrĂ©es qui ont une valeur liĂ©e Ă  un jour donnĂ©. Par consĂ©quent, j'insĂšre et mets Ă  jour des entrĂ©es qui ont une "clĂ© combinĂ©e" d'un horodatage correspondant (jour) et deux autres ID correspondant aux PK dans d'autres tables. Dans une fenĂȘtre de 24 heures, je dois soit les insĂ©rer, soit les mettre Ă  jour avec les derniers dĂ©comptes.

Ce serait une excellente fonctionnalité à avoir!

Salut à tous ceux qui ont déjà commenté ici. J'ajoute une étiquette PR Please.

Heureux de prendre un PR en ajoutant cette fonctionnalité, mais j'aimerais voir une discussion sur l'API souhaitée ici en premier.

PS.

^ D'accord.

Je vais supprimer des commentaires comme celui-ci, si vous voulez ajouter un +1, faites-le avec le petit truc de réaction emoji.

J'ai un petit problĂšme avec le tableau des contraintes de colonne comme dans les exemples de @willfarrell et @hayeah . Vous ne savez pas si ces exemples peuvent prendre en charge les propriĂ©tĂ©s json . Y a-t-il une raison pour laquelle aucune de ces propositions n'inclut les instructions where / les "requĂȘtes" appropriĂ©es pour correspondre Ă  l'enregistrement ?

proposition 1

knex('table')
  .where('id', '=', data.id)
  .upsert(data)

proposition 2

knex('table')
  .upsertQuery(knex => {
    return knex('table')
      .where('id', '=', data.id)
  })
  .upsertUpdate(knex => {
    return knex('table')
      .insert(data)
  })

proposition 3

knex('table')
  .where('id', '=', data.id)
  .insert(data)
  .upsert() // or .onConflictDoUpdate()

Je penche le plus vers quelque chose comme 3.

Juste pour ajouter voici comment mongodb le fait .

db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)

@reggi Je crois que mon patch de singe est compatible avec where ...

@reggi Je ne vois pas votre point.
Pouvez-vous en dire plus sur les fonctionnalités manquantes dans l'approche proposée dans les exemples de @willfarrell et @hayeah .
Pourquoi avez-vous besoin where ?
C'est juste une opération insert .

@reggi L'exemple MongoDB que vous avez fourni lit "Essayez d'abord de mettre Ă  jour WHERE ... puis faites un INSERT si aucun document ne correspond Ă  la requĂȘte" alors que SQL UPSERT lit "INSERT INTO ... UPDATING au cas oĂč une ligne avec cette clĂ© primaire existe dĂ©jĂ " .
Donc, je suppose que vous parlez d'un "upsert" complÚtement différent de celui implémenté dans les bases de données SQL.

Je proposerais cette API :

knex.createTable('test')
   .bigserial('id')
   .varchar('unique').notNull().unique()
   .varchar('whatever')

knex.table('test').insert(object, { upsert: ['unique'] })

La fonction .insert() analyserait le deuxiĂšme paramĂštre.
S'il s'agit d'une chaĂźne, il s'agit de l'ancien paramĂštre returning .
Si c'est un objet, alors c'est un paramĂštre options ayant options.returning et options.upsert , oĂč options.upsert est une liste des clĂ©s uniques (peut ĂȘtre > 1 dans cas d'une contrainte de clĂ© unique composĂ©e).
Ensuite, une requĂȘte SQL est gĂ©nĂ©rĂ©e qui exclut simplement la clĂ© primaire et toutes les clĂ©s options.upsert du object (via clone(object) && delete cloned_object.id && delete cloned_object.unique ) et utilise ensuite ce cloned_object dĂ©pouillĂ© de les clĂ©s primaires (et uniques) pour construire la clause SET dans la seconde partie de la requĂȘte SQL : ... ON CONFLICT DO UPDATE SET [iterate cloned_object] .

Je suppose que ce serait la solution la plus simple et sans ambiguïté homogÚne avec l'API actuelle.

@slavafomin @ScionOfBytes On dirait que mĂȘme l'API n'a pas encore Ă©tĂ© convenue. Ce serait la prochaine Ă©tape, puis quelqu'un qui aimerait le mettre en Ɠuvre pourrait le faire. Donc pas de nouvelles.

ps. J'ai commencĂ© Ă  supprimer toutes les demandes supplĂ©mentaires de nouvelles s'il n'y en a pas pour empĂȘcher ce fil d'ĂȘtre rempli de spam de demande de nouvelles et d'autres messages moins liĂ©s.

@amir-s Je suis d'accord, mais le sujet de ce problÚme est la capacité d'upsert.

IMO, le vrai problÚme n'est pas l'API, mais la façon peu courante de faire des upserts dans chaque base de données.

MySQL (ON DUPLICATE KEY UPDATE) et PostgreSQL 9.5+ (ON CONFLICT DO UPDATE) prennent en charge l'upsert par défaut.

MSSQL et Oracle peuvent le prendre en charge avec une clause de fusion, mais knex doit connaĂźtre les noms des colonnes en conflit pour pouvoir construire la requĂȘte.

-- in this case the conflict column is 'a'
merge into target
using (values (?)) as t(a)
on (t.a = target.a)
when matched then
  update set b = ?
when not matched then
  insert (a, b) values (?, ?);

Mais SQLite ne l'a pas fait. Nous avons besoin de deux requĂȘtes pour simuler l'upsert

-- 'a' is the conflict column
insert or ignore into target (a, b) values (?, ?);
update target set b = ?2 where changes() = 0 and a = ?1;

Ou en utilisant INSERT OR REPLACE , alias REPLACE

-- replace will delete the matched row then add a new one with the given data
replace into target (a, b) values (?, ?);

Malheureusement, si la table cible a plus de colonnes que a et b, leurs valeurs seront remplacées par les valeurs par défaut

insert or replace into target (a, b, c) values (?, ?, (select c from target where a = ?1))

Une autre solution utilisant CTE, regardez cette réponse stackoverflow

Je suis venu à ce problÚme plusieurs fois à la recherche d'un upsert Postgres basé sur knex. Si quelqu'un d'autre en a besoin, voici comment procéder. J'ai testé cela avec des clés uniques et composites uniques.

La mise en place

Créez une contrainte de clé unique sur la table en utilisant ce qui suit. J'avais besoin d'une contrainte de clé composite :

table.unique(['a', 'b'])

La fonction

(edit : mis à jour pour utiliser les liaisons de paramÚtres bruts)

const upsert = (params)=> {
  const {table, object, constraint} = params;
  const insert = knex(table).insert(object);
  const update = knex.queryBuilder().update(object);
  return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};

Usage

const objToUpsert = {a:1, b:2, c:3}

upsert({
    table: 'test',
    object: objToUpsert,
    constraint: '(a, b)',
})

Si votre contrainte n'est pas composite, alors, naturellement, cette ligne serait simplement constraint: '(a)' .

Cela renverra soit l'objet mis à jour, soit l'objet inséré.

Remarque sur les indices composites nullables

Si vous avez un index composite (a,b) et que b est nullable, alors les valeurs (1, NULL) et (1, NULL) sont considérées mutuellement uniques par Postgres (je ne comprends pas Soit). Si tel est votre cas d'utilisation, vous devrez créer un index unique partiel, puis tester null avant upsert pour déterminer la contrainte à utiliser. Voici comment créer l'index unique partiel : CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Si votre test détermine que b est nul, vous devrez utiliser cette contrainte dans votre upsert : constraint: '(a) WHERE b IS NULL' . Si a est également nullable, je suppose que vous auriez besoin de 3 index uniques et de 4 branches if/else (bien que ce ne soit pas mon cas d'utilisation, donc je ne suis pas sûr).

Voici le javascript compilé .

J'espÚre que quelqu'un trouvera cela utile. @elhigu Un commentaire sur l'utilisation de knex().update(object) ? (edit: ça ne fait rien - j'ai vu l'avertissement - j'utilise knex.queryBuilder() maintenant)

@timhuff a l'air bien, une chose Ă  changer serait de passer chaque requĂȘte Ă  raw, en utilisant la liaison de valeur. Sinon, query.toString() est utilisĂ© pour restituer chaque partie de la requĂȘte et ouvre un Ă©ventuel trou d'injection de dĂ©pendance (queryBuilder.toString() n'est pas aussi sĂ»r que de transmettre des paramĂštres au pilote en tant que liaisons).

@elhigu Attendez... query.toString() n'utilise pas de liaisons ? Pourriez-vous me donner un exemple approximatif de la modification que vous recommandez ? J'ai peut-ĂȘtre beaucoup de code Ă  mettre Ă  jour.

TrouvĂ© la partie de la documentation intitulĂ©e Raw bindings . Mise Ă  jour maintenant , j'ai mis Ă  jour l'exemple. Je pensais que query.toString Ă©tait sĂ»r. Ce serait bien d'avoir une section de la documentation intitulĂ©e quelque chose comme "Comment faire des requĂȘtes non sĂ©curisĂ©es". Il n'y a qu'une poignĂ©e de non-non et de cette façon, les gens peuvent utiliser la bibliothĂšque en sachant "tant que je ne fais pas ces choses, je suis en sĂ©curitĂ©".

J'ai créé l'upsert suivant : https://gist.github.com/adnanoner/b6c53482243b9d5d5da4e29e109af9bd
Il gÚre les upserts simples et par lots. Je l'ai un peu adapté de @plurch . Les améliorations sont toujours appréciées :)

Pour ce que ça vaut, j'ai utilisé ce format:

Modifier : mis Ă  jour pour ĂȘtre sĂ©curisĂ© pour tous ceux qui recherchent cela. Merci @elhigu

const query = knex( 'account' ).insert( accounts );
const safeQuery = knex.raw( '? ON CONFLICT DO NOTHING', [ query ]);

@NicolajKN Vous ne devriez pas utiliser toString(), cela pourrait causer de nombreux types de problÚmes et ne transmettra pas les valeurs via les liaisons à la base de données (trou de sécurité potentiel d'injection SQL).

MĂȘme ceci fait correctement serait comme ceci:

const query = knex('account').insert(accounts);
const safeQuery = knex.raw('? ON CONFLICT DO NOTHING', [query]);

Suppression de la discussion sur un problĂšme sans rapport.

@elhigu Attendez, cette requĂȘte d'insertion n'est-elle pas exĂ©cutĂ©e immĂ©diatement aprĂšs sa crĂ©ation? Cela ne crĂ©e-t-il pas une condition de concurrence ?

@cloutiertyler Tu ne me parlais pas mais peut-ĂȘtre que je peux faire gagner du temps Ă  @elhigu ici. Aucune de ces requĂȘtes ne serait exĂ©cutĂ©e. L'instruction knex('account').insert(accounts) n'exĂ©cute pas de requĂȘte. Il n'est pas exĂ©cutĂ© tant que les donnĂ©es ne sont pas rĂ©ellement appelĂ©es (par exemple via un .then ). Il envoie cela dans knex.raw('? ON CONFLICT DO NOTHING', [query]) qui appellera query.toString() , qui ne convertit que la requĂȘte dans l'instruction SQL qui serait exĂ©cutĂ©e.

@timhuff Merci Tim, j'ai supposĂ© que ça devait ĂȘtre quelque chose comme ça, mais ce n'est pas un comportement normal pour une promesse. Les promesses sont gĂ©nĂ©ralement exĂ©cutĂ©es lors de la crĂ©ation. La raison pour laquelle je pose la question est que j'obtenais des erreurs indiquant "Connexion terminĂ©e" de temps en temps lorsque j'essayais d'exĂ©cuter cet upsert. Une fois que je suis passĂ© Ă  la suppression de l'insert et Ă  la crĂ©ation d'une requĂȘte entiĂšrement brute, ils sont partis. Il semble que cela serait compatible avec une condition de concurrence.

knex QueryBuilder s ne sont pas Promise s, cependant. Lorsque vous commencez Ă  Ă©crire une requĂȘte knex, vous restez dans "knexland". Tout ce que vous faites consiste plus ou moins Ă  configurer une spĂ©cification JSON de la requĂȘte que vous souhaitez crĂ©er. Si vous exĂ©cutez .toString , il le construit et le sort. Cela ne devient pas une promesse ( bluebird ) tant que vous n'en exĂ©cutez pas une . Vous pourriez ĂȘtre intĂ©ressĂ© par l'utilisation .return si vous souhaitez exĂ©cuter l'instruction immĂ©diatement.

Ah, je vois, eh bien cela efface ma confusion. Merci pour les précisions et les indications ! Mon problÚme doit donc exister ailleurs.

En apartĂ©, le fait qu'il ne s'exĂ©cute pas immĂ©diatement est souvent utile. Parfois, vous voulez passer la chose, la configurer, avant de l'exĂ©cuter. Il y a aussi des situations oĂč vous pouvez faire des choses comme...

const medicalBuildings = knex.select('building_id').from('buildings').where({type: 'medical'})
const medicalWorkers = knex.select().from('workers').whereIn('building', medicalBuildings)

(exemple super artificiel mais allons-y)

Je ne veux pas réellement exécuter cette premiÚre déclaration - c'est juste une partie de ma 2Úme.

Sans oublier que si tous les gĂ©nĂ©rateurs de requĂȘtes s'exĂ©cutaient lors de la crĂ©ation, les requĂȘtes de modĂšle de gĂ©nĂ©rateur se dĂ©clencheraient avant la fin de la construction. Cela ne fonctionnerait pas du tout sans une mĂ©thode de terminaison (qui exĂ©cute la requĂȘte).

@elhigu Je veux dire... Je suppose que vous pouvez toujours l'exĂ©cuter au prochain tick, n'est-ce pas ? Je ne dis pas que ce serait en aucun cas une bonne idĂ©e, mais combien de requĂȘtes sont rĂ©ellement crĂ©Ă©es et exĂ©cutĂ©es sur diffĂ©rentes ticks?

@timhuff je n'y avais pas pensĂ©. Oui je pense que ce serait possible aussi. Je trouve le cas assez courant oĂč l'on commence Ă  crĂ©er une requĂȘte, puis Ă  rĂ©cupĂ©rer des donnĂ©es asynchrones et Ă  en crĂ©er d'autres. Je ne le fais pourtant pas trĂšs souvent.

@lukewlms cette mĂ©thode semblable Ă  'execute()' s'appelle '.then()', vous pouvez toujours l'appeler lorsque vous souhaitez exĂ©cuter une requĂȘte et obtenir une promesse. C'est juste comment 'thenable' fonctionne et c'est expliquĂ© dans la spĂ©cification de la promesse. C'est un concept important et largement utilisĂ© en javascript lorsqu'il s'agit de promesses et d'async/wait (qui ne sont Ă  peu prĂšs que des raccourcis glorifiĂ©s pour Promise.resolve et .then). De plus, si vous exĂ©cutez des requĂȘtes sans gĂ©rer les rĂ©sultats, vous recherchez des problĂšmes tels que le plantage de l'application.

En fait, il vaut mieux suivre ce PR sur l'implémentation de la fonctionnalité upsert https://github.com/tgriesser/knex/pull/2197 , il a déjà conçu l'API comment cela devrait fonctionner. Dans ce fil, il n'y a pas vraiment d'informations utiles qui ne soient pas déjà mentionnées dans les commentaires de ce PR. Si nécessaire (le PR est fermé et jamais terminé), ouvrons un nouveau problÚme pour celui-ci avec une description supplémentaire de l'API.

@elhigu Merci pour l'avertissement ! Je n'Ă©tais pas au courant de ce fil. C'est bon d'entendre que nous progressons sur un upsert Ă  venir sur l'API. On dirait qu'il y a 6 mois, il a Ă©chouĂ© Ă  1 des 802 tests et n'a donc jamais rĂ©ussi travis-ci. Est-ce que 1 cas de test dĂ©faillant est la seule chose qui l'empĂȘche de devenir une partie de l'API knex ?

@timhuff il n'y a eu qu'une implĂ©mentation initiale effectuĂ©e, elle doit ĂȘtre complĂštement rĂ©Ă©crite. La partie la plus importante de ce PR est la conception commune de l'API, qui peut ĂȘtre prise en charge par la plupart des dialectes. Ainsi, la fonctionnalitĂ© apparaĂźt lorsque quelqu'un dĂ©cide simplement d'implĂ©menter cette API. Si personne d'autre ne le fait et qu'un jour j'ai du temps supplĂ©mentaire ou que j'en ai vraiment besoin, je le ferai moi-mĂȘme. C'est l'une des fonctionnalitĂ©s les plus importantes que j'aimerais que knex obtienne (en plus des jointures dans les mises Ă  jour).

@elhigu Merci de m'avoir renseigné. Je devrai lire les progrÚs ici quand j'aurai un peu plus de temps.

Je ne sais pas si cela aide quelqu'un ou si je ne suis qu'un noob, mais pour la solution de @timhuff, j'ai dĂ» mettre ma contrainte entre guillemets car j'obtenais une erreur de syntaxe de requĂȘte.

const contraint = '("a", "b")'

Je suis venu à ce problÚme plusieurs fois à la recherche d'un upsert Postgres basé sur knex. Si quelqu'un d'autre en a besoin, voici comment procéder. J'ai testé cela avec des clés uniques et composites uniques.

La mise en place

Créez une contrainte de clé unique sur la table en utilisant ce qui suit. J'avais besoin d'une contrainte de clé composite :

table.unique(['a', 'b'])

La fonction

(edit : mis à jour pour utiliser les liaisons de paramÚtres bruts)

const upsert = (params)=> {
  const {table, object, constraint} = params;
  const insert = knex(table).insert(object);
  const update = knex.queryBuilder().update(object);
  return knex.raw(`? ON CONFLICT ${constraint} DO ? returning *`, [insert, update]).get('rows').get(0);
};

Usage

const objToUpsert = {a:1, b:2, c:3}

upsert({
  table: 'test',
  object: objToUpsert,
  constraint: '(a, b)',
})

Si votre contrainte n'est pas composite, alors, naturellement, cette ligne serait juste constraint: '(a)' .

Cela renverra soit l'objet mis à jour, soit l'objet inséré.

Remarque sur les indices composites nullables

Si vous avez un index composite (a,b) et que b est nullable, alors les valeurs (1, NULL) et (1, NULL) sont considérées mutuellement uniques par Postgres (je ne comprends pas Soit). Si tel est votre cas d'utilisation, vous devrez créer un index unique partiel, puis tester null avant upsert pour déterminer la contrainte à utiliser. Voici comment créer l'index unique partiel : CREATE UNIQUE INDEX unique_index_name ON table (a) WHERE b IS NULL . Si votre test détermine que b est nul, vous devrez utiliser cette contrainte dans votre upsert : constraint: '(a) WHERE b IS NULL' . Si a est également nullable, je suppose que vous auriez besoin de 3 index uniques et de 4 branches if/else (bien que ce ne soit pas mon cas d'utilisation, donc je ne suis pas sûr).

Voici le javascript compilé .

J'espÚre que quelqu'un trouvera cela utile. @elhigu ~Tout commentaire sur l'utilisation de knex().update(object) ?~ (modifier : ça ne fait rien - j'ai vu l'avertissement - j'utilise knex.queryBuilder() maintenant)

Suppression de certaines discussions sans rapport (sur le fonctionnement des promesses/thénables).

Cela a-t-il été ajouté ?

Non. Il y a une demande de fonctionnalité et des spécifications dans https://github.com/knex/knex/issues/3186

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

mtom55 picture mtom55  Â·  3Commentaires

koskimas picture koskimas  Â·  3Commentaires

legomind picture legomind  Â·  3Commentaires

saurabhghewari picture saurabhghewari  Â·  3Commentaires

lanceschi picture lanceschi  Â·  3Commentaires