MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

Cara membuat item jika tidak ada dan mengembalikan kesalahan jika ada

Seperti disebutkan dalam komentar sebelumnya, Anda memiliki dua pendekatan dasar untuk mengetahui apakah sesuatu itu "dibuat" atau tidak. Ini adalah untuk:

  • Kembalikan rawResult dalam tanggapan dan periksa updatedExisting properti yang memberi tahu Anda apakah itu "upsert" atau bukan

  • Setel new: false sehingga "tidak ada dokumen" yang benar-benar dikembalikan dalam hasil padahal sebenarnya itu adalah "upsert"

Sebagai daftar untuk menunjukkan:

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


})()

Dan hasilnya:

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"
}

Jadi kasus pertama benar-benar mempertimbangkan kode ini:

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

Sebagian besar opsi standar di sini sebagai "semua" "upsert" tindakan akan menghasilkan konten bidang yang digunakan untuk "cocok" (yaitu username ) adalah "selalu" dibuat di dokumen baru, jadi Anda tidak perlu $set bidang itu. Agar tidak benar-benar "memodifikasi" bidang lain pada permintaan berikutnya, Anda dapat menggunakan $setOnInsert , yang hanya menambahkan properti ini selama "upsert" tindakan di mana tidak ada kecocokan yang ditemukan.

Di sini standar new: true digunakan untuk mengembalikan dokumen "dimodifikasi" dari tindakan, tetapi perbedaannya adalah pada rawResult seperti yang ditunjukkan dalam respons yang dikembalikan:

{
  "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
    }
  }
}

Alih-alih "dokumen luwak" Anda mendapatkan respons "mentah" yang sebenarnya dari pengemudi. Isi dokumen sebenarnya berada di bawah "value" properti, tapi itu adalah "lastErrorObject" kami tertarik.

Di sini kita melihat properti updatedExisting: false . Ini menunjukkan bahwa "tidak ada kecocokan" yang benar-benar ditemukan, sehingga dokumen baru "dibuat". Jadi Anda dapat menggunakan ini untuk menentukan bahwa penciptaan benar-benar terjadi.

Saat Anda mengeluarkan opsi kueri yang sama lagi, hasilnya akan berbeda:

{
  "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 nilainya sekarang true , dan ini karena sudah ada dokumen yang cocok dengan username: 'Bill' dalam pernyataan kueri. Ini memberi tahu Anda bahwa dokumen itu sudah ada di sana, sehingga Anda dapat mencabangkan logika Anda untuk mengembalikan "Kesalahan" atau respons apa pun yang Anda inginkan.

Dalam kasus lain, mungkin diinginkan untuk "tidak" mengembalikan respons "mentah" dan menggunakan "dokumen luwak" yang dikembalikan sebagai gantinya. Dalam hal ini kami memvariasikan nilainya menjadi new: false tanpa rawResult pilihan.

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

Sebagian besar hal yang sama berlaku kecuali bahwa sekarang tindakannya adalah asli status dokumen dikembalikan sebagai lawan dari status "dimodifikasi" dokumen "setelah" tindakan. Oleh karena itu ketika tidak ada dokumen yang benar-benar cocok dengan pernyataan "query", hasil yang dikembalikan adalah null :

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

Ini memberi tahu Anda bahwa dokumen itu "dibuat", dan dapat dikatakan bahwa Anda sudah tahu apa isi dokumen itu karena Anda mengirim data itu dengan pernyataan ( idealnya di $setOnInsert ). Intinya, Anda sudah tahu apa yang harus dikembalikan "harus" yang Anda perlukan untuk benar-benar mengembalikan konten dokumen.

Sebaliknya, dokumen "ditemukan" mengembalikan "keadaan asli" yang menunjukkan dokumen "sebelum" diubah:

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

Oleh karena itu, respons apa pun yang "bukan null " oleh karena itu merupakan indikasi bahwa dokumen sudah ada, dan sekali lagi Anda dapat mencabangkan logika Anda tergantung pada apa yang sebenarnya diterima sebagai tanggapan.

Jadi itu adalah dua pendekatan dasar untuk apa yang Anda minta, dan mereka pasti "berhasil"! Dan seperti yang ditunjukkan dan direproduksi dengan pernyataan yang sama di sini.

Addendum - Cadangan Kunci Duplikat untuk sandi yang salah

Ada satu lagi pendekatan yang valid yang diisyaratkan dalam daftar lengkap juga, yang pada dasarnya hanya .insert() ( atau .create() dari model luwak ) data baru dan memiliki lemparan kesalahan "kunci duplikat" di mana properti "unik" menurut indeks sebenarnya ditemukan. Ini adalah pendekatan yang valid tetapi ada satu kasus penggunaan khusus dalam "validasi pengguna" yang merupakan bagian dari penanganan logika yang praktis, dan itu adalah "memvalidasi kata sandi".

Jadi ini adalah pola yang cukup umum untuk mengambil informasi pengguna dengan username dan password kombinasi. Dalam kasus "upsert" kombinasi ini dibenarkan sebagai "unik" dan oleh karena itu "insert" dicoba jika tidak ditemukan kecocokan. Inilah tepatnya yang membuat pencocokan kata sandi menjadi implementasi yang berguna di sini.

Pertimbangkan hal berikut:

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

Pada upaya pertama, kami sebenarnya tidak memiliki username untuk "Fred" , sehingga "upsert" akan terjadi dan semua hal lain seperti yang telah dijelaskan di atas terjadi untuk mengidentifikasi apakah itu adalah kreasi atau dokumen yang ditemukan.

Pernyataan berikut menggunakan username yang sama nilai tetapi memberikan kata sandi yang berbeda dengan apa yang direkam. Di sini MongoDB mencoba untuk "membuat" dokumen baru karena tidak cocok dengan kombinasinya, tetapi karena username diharapkan menjadi "unique" Anda menerima "Kesalahan kunci duplikat":

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

Jadi yang harus Anda sadari adalah sekarang Anda mendapatkan tiga kondisi untuk mengevaluasi untuk "gratis". Menjadi:

  • "Upsert" direkam oleh updatedExisting: false atau null hasil tergantung pada metodenya.
  • Anda tahu dokumen ( dengan kombinasi ) "ada" baik melalui updatedExisting: true atau di mana dokumen yang dikembalikan adalah "bukan null ".
  • Jika password yang diberikan tidak cocok dengan apa yang sudah ada untuk username , maka Anda akan mendapatkan "kesalahan kunci duplikat" yang dapat dijebak dan ditanggapi dengan tepat, memberi tahu pengguna sebagai tanggapan bahwa "kata sandi salah".

Semua itu dari satu permintaan.

Itulah alasan utama untuk menggunakan "upserts" daripada sekadar melempar sisipan ke koleksi, karena Anda bisa mendapatkan percabangan logika yang berbeda tanpa membuat permintaan tambahan ke database untuk menentukan "yang" dari kondisi tersebut yang harus menjadi respons aktual.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Apakah ada alat untuk migrasi skema untuk database NoSQL?

  2. Keamanan Basis Data 101:Memahami Hak Istimewa Akses Basis Data

  3. Pengecualian upsert MongoDb bidang BSON tidak valid

  4. Terapkan pemetaan tipe dengan mgo

  5. Perbedaan antara mayoritas dan dapat dilinierkan