sql >> Databáze >  >> NoSQL >> MongoDB

Jak vytvořit položku, pokud neexistuje, a vrátit chybu, pokud existuje

Jak bylo uvedeno v komentáři dříve, máte dva základní přístupy, jak zjistit, zda bylo něco „vytvořeno“ nebo ne. Jsou to buď:

  • Vraťte rawResult v odpovědi a zkontrolujte updatedExisting vlastnost, která vám řekne, zda je to "upsert" nebo ne

  • Nastavte new: false takže ve výsledku se ve skutečnosti vrátí "žádný dokument", když je to ve skutečnosti "upsert"

Jako výpis pro demonstraci:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/thereornot';

mongoose.set('debug', true);
mongoose.Promise = global.Promise;

const userSchema = new Schema({
  username: { type: String, unique: true },   // Just to prove a point really
  password: String
});

const User = mongoose.model('User', userSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Shows updatedExisting as false - Therefore "created"

    let bill1 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill1);

    // Shows updatedExisting as true - Therefore "existing"

    let bill2 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill2);

    // Test with something like:
    // if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");


    // Return will be null on "created"
    let ted1 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted1);

    // Return will be an object where "existing" and found
    let ted2 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted2);

    // Test with something like:
    // if (ted2 !== null) throw new Error("already there");

    // Demonstrating "why" we reserve the "Duplicate" error
    let fred1 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'password' },
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );
    log(fred1);       // null - so okay

    let fred2 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }


})()

A výstup:

Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Takže první případ ve skutečnosti uvažuje tento kód:

User.findOneAndUpdate(
  { username: 'Bill' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: true, rawResult: true }
)

Většina možností je zde standardní jako "vše" "upsert" akce povedou k tomu, že se obsah pole „shoduje“ (tj. username ) je „vždy“ vytvořené v novém dokumentu, takže nemusíte $set to pole. Abyste ve skutečnosti „neupravovali“ další pole při následných požadavcích, můžete použít $setOnInsert , který tyto vlastnosti přidává pouze během "upsert" akce, kde není nalezena žádná shoda.

Zde standardní new: true se používá k vrácení "upraveného" dokumentu z akce, ale rozdíl je v rawResult jak je uvedeno ve vrácené odpovědi:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

Místo „mongoose dokumentu“ dostanete skutečnou „surovou“ odpověď od ovladače. Skutečný obsah dokumentu je pod "value" vlastnost, ale je to "lastErrorObject" nás to zajímá.

Zde vidíme vlastnost updatedExisting: false . To znamená, že „žádná shoda“ nebyla skutečně nalezena, takže byl „vytvořen nový dokument“. Takže to můžete použít k určení, že k vytvoření skutečně došlo.

Když znovu zadáte stejné možnosti dotazu, výsledek bude jiný:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true             // <--- Now I'm true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

updatedExisting hodnota je nyní true , a to proto, že již existoval dokument, který odpovídal username: 'Bill' ve výpisu dotazu. To vám řekne, že dokument už tam byl, takže pak můžete větvit svou logiku tak, aby vrátila „Chybu“ nebo jakoukoli odpověď, kterou chcete.

V druhém případě může být žádoucí „ne“ vrátit „raw“ odpověď a místo toho použít vrácený „mongoose document“. V tomto případě změníme hodnotu tak, aby byla new: false bez rawResult možnost.

User.findOneAndUpdate(
  { username: 'Ted' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: false }
)

Většina stejných věcí platí kromě toho, že nyní je akce původní stav dokumentu je vrácen na rozdíl od "upraveného" stavu dokumentu "po" akci. Pokud tedy neexistuje žádný dokument, který by skutečně odpovídal příkazu „query“, vrácený výsledek je null :

Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null           // <-- Got null in response :(

To vám říká, že dokument byl „vytvořen“, a je možné tvrdit, že již víte, jaký by měl být obsah dokumentu, protože jste tato data odeslali s prohlášením (ideálně v $setOnInsert ). Pointa je, že už víte, co vrátit „měli byste“, abyste skutečně vrátili obsah dokumentu.

Naproti tomu „nalezený“ dokument vrací „původní stav“ ukazující dokument „před“ úpravou:

{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Proto každá odpověď, která není null " je tedy známkou toho, že dokument již byl přítomen, a opět můžete svou logiku větvit podle toho, co bylo skutečně přijato jako odpověď.

To jsou tedy dva základní přístupy k tomu, na co se ptáte, a zcela jistě „fungují“! A právě tak, jak je demonstrováno a reprodukováno se stejnými výroky zde.

Dodatek – rezervace duplicitního klíče pro špatná hesla

Existuje ještě jeden platný přístup, který je také naznačen v úplném seznamu, kterým je v podstatě jednoduše .insert() (nebo .create() z modelů mongoose ) nová data a dojde k chybě „duplicitního klíče“, kde se skutečně vyskytuje vlastnost „unikátní“ podle indexu. Je to platný přístup, ale existuje jeden konkrétní případ použití v „ověření uživatele“, což je šikovná logická manipulace, a to je „ověřování hesel“.

Takže je to docela běžný vzorec pro získávání informací o uživateli pomocí username a password kombinace. V případě "upsert" se tato kombinace ospravedlňuje jako "unikátní", a proto se pokusí "vložit", pokud není nalezena žádná shoda. To je přesně to, co dělá shodu s heslem užitečnou implementací zde.

Zvažte následující:

    // Demonstrating "why" we reserve the "Duplicate" error
    let fred1 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'password' },
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );
    log(fred1);       // null - so okay

    let fred2 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );

Na první pokus ve skutečnosti nemáme username pro "Fred" , takže by došlo k "upsert" a všechny ostatní věci, které již byly popsány výše, se stanou identifikací, zda šlo o výtvor nebo nalezený dokument.

Následující příkaz používá stejné username hodnota, ale poskytuje jiné heslo než to, co je zaznamenáno. Zde se MongoDB pokouší "vytvořit" nový dokument, protože se neshodoval v kombinaci, ale protože username očekává se, že bude "unique" zobrazí se "Chyba duplicitního klíče":

{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }

Měli byste si tedy uvědomit, že nyní máte tři podmínky vyhodnotit „zdarma“. Bytí:

  • „upsert“ byl zaznamenán buď pomocí updatedExisting: false nebo null výsledek závisí na metodě.
  • Víte, že dokument (v kombinaci ) "existuje" buď pomocí updatedExisting: true nebo kde dokument vrací bylo "not null ".
  • Pokud password poskytnuté neodpovídá tomu, co již existovalo pro username , pak byste dostali "chybu duplicitního klíče", kterou můžete zachytit a odpovídajícím způsobem reagovat a upozornit uživatele, že "heslo je nesprávné".

To vše z jednoho žádost.

To je hlavní důvod pro použití „upserts“ na rozdíl od prostého házení insertů na kolekci, protože můžete získat různé větvení logiky, aniž byste museli do databáze zadávat další požadavky na určení „která“ z těchto podmínek by měla být skutečnou odpovědí.



  1. Může redis zpracovat více příkazů, které závisí na předchozích?

  2. Mongoose:Seřadit podle vnořeného pole

  3. Pokud klíč neexistuje Mongoose, vložte hodnotu do pole

  4. Jak zobrazit nějaké změny (nový řádek) v mongoDB?