MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Atualizar subdocumentos aninhados no MongoDB com arrayFilters


Portanto, os arrayFilters opção com $[<identifier>] filtrado posicional realmente funciona corretamente com a série de lançamentos de desenvolvimento desde o MongoDB 3.5.12 e também nos candidatos a lançamentos atuais para a série MongoDB 3.6, onde será lançado oficialmente. O único problema é, claro, que os "drivers" em uso ainda não alcançaram isso.

Reiterando o mesmo conteúdo que já coloquei em Atualizando uma matriz aninhada com o MongoDB:

OBSERVAÇÃO Um tanto ironicamente, já que isso é especificado no argumento "options" para .update() e como métodos, a sintaxe geralmente é compatível com todas as versões de driver de lançamento recentes.

No entanto, isso não é verdade para o mongo shell, já que a forma como o método é implementado lá ("ironicamente para compatibilidade com versões anteriores") o arrayFilters argumento não é reconhecido e removido por um método interno que analisa as opções para fornecer "compatibilidade com versões anteriores" com versões anteriores do servidor MongoDB e um .update() "legado" Sintaxe de chamada da API.

Então, se você quiser usar o comando no mongo shell ou outros produtos "baseados em shell" (principalmente Robo 3T), você precisa de uma versão mais recente da ramificação de desenvolvimento ou da versão de produção a partir de 3.6 ou superior.

Tudo isso significa que a implementação atual do "driver" de .update() na verdade "remove" os argumentos necessários com a definição de arrayFilters . Para o NodeJS, isso será abordado na série de lançamento 3.x do driver e, é claro, "mangusto" provavelmente levará algum tempo após esse lançamento para implementar suas próprias dependências no driver atualizado, o que não seria mais "strip" tais ações.

No entanto, você ainda pode executar isso em um suportado instância do servidor, retornando ao uso básico da sintaxe "comando de atualização", pois isso ignorou o método de driver implementado:
const mongoose = require('mongoose'),
      Schema = mongoose.Schema,
      ObjectId = mongoose.Types.ObjectId;

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

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const contactSchema = new Schema({
  data: String,
  type: String,
  priority: String,
  retries: String
});

const personSchema = new Schema({
  name: String,
  level: String,
  priority: String,
  enabled: Boolean,
  contacts: [contactSchema]
});

const groupSchema = new Schema({
  name: String,
  people: [personSchema],
  workingHours: { start: String, end: String },
  workingDays: { type: [Number], default: undefined },
  contactTypes: {
    workingHours: { type: [String], default: undefined },
    contactTypes: { type: [String], default: undefined }
  }
});

const Group = mongoose.model('Group', groupSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

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

    // Create sample

    await Group.create({
      name: "support",
      people: [
        {
          "_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
          "enabled": true,
          "level": "1",
          "name": "Someone",
          "contacts": [
            {
              "type": "email",
              "data": "[email protected]"
            },
            {
              "_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
              "retries": "1",
              "priority": "1",
              "type": "email",
              "data": "[email protected]"
            }
          ]
        }
      ]
    });

    let result = await conn.db.command({
      "update": Group.collection.name,
      "updates": [
        {
          "q": {},
          "u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
          "multi": true,
          "arrayFilters": [
            { "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
            { "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
          ]
        }
      ]
    });

    log(result);

    let group = await Group.findOne();
    log(group);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

Como isso envia o "comando" diretamente para o servidor, vemos que a atualização esperada de fato ocorre:
Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: '[email protected]', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: '[email protected]' } ] } ], __v: 0 })
{ n: 1,
  nModified: 1,
  opTime:
   { ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
     t: 24 },
  electionId: 7fffffff0000000000000018,
  ok: 1,
  operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
  '$clusterTime':
   { clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
     signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
  "_id": "5a06557fb568aa0ad793c5e4",
  "name": "support",
  "__v": 0,
  "people": [
    {
      "_id": "5a05a8c3e0ce3444f8ec5bd8",
      "enabled": true,
      "level": "1",
      "name": "Someone",
      "contacts": [
        {
          "type": "email",
          "data": "[email protected]",
          "_id": "5a06557fb568aa0ad793c5e5"
        },
        {
          "_id": "5a05a8dee0ce3444f8ec5bda",
          "retries": "1",
          "priority": "1",
          "type": "email",
          "data": "new data"            // <-- updated here
        }
      ]
    }
  ]
}

Então certo "agora" os drivers disponíveis "de prateleira" não implementam realmente .update() ou são outras contrapartes de implementação de uma maneira que seja compatível com a passagem pelos arrayFilters necessários argumento. Então, se você está "jogando" com uma série de desenvolvimento ou servidor de lançamento, então você realmente deve estar preparado para trabalhar com drivers "de ponta" e não lançados também.

Mas você pode realmente fazer isso como demonstrado em qualquer driver, na forma correta onde o comando que está sendo emitido não será alterado.

No momento da redação em 11 de novembro de 2017, não há "oficial" versão do MongoDB ou os drivers suportados que realmente implementam isso. O uso de produção deve ser baseado apenas em versões oficiais do servidor e drivers suportados.