Introdução
A FIELDNOTE aluga pequenos kits de equipamentos. Seu catálogo herdado no MongoDB contém contagens úteis de estoque junto com strings antigas e valores incertos. Uma contagem conhecida de zero significa que um kit está indisponível; uma contagem ausente significa que a loja não sabe. Transformar ambas em zero ocultaria um problema de dados. Seu trabalho é separar esses casos e preservar registros não confiáveis para uma revisão posterior com o fornecedor.
Você salvará uma política reutilizável de classificação e um script de quarentena que mantém o documento original completo antes de removê-lo da coleção ativa. Você precisa saber navegar pelo terminal e ter familiaridade com documentos MongoDB, campos aninhados, find e atualizações comuns. As construções JavaScript, regras de esquema e detalhes de BSON usados aqui são explicados antes do uso. O MongoDB, seu shell e os dados sintéticos de catálogo estão preparados. Use Terminal durante todo o lab. A duração é uma estimativa do autor que inclui leitura e erros comuns.
Escreva policy.js e quarantine.js em /home/labex/project/catalog-desk. Preserve cada ID original, todos os campos originais completos e seus tipos, extensões não relacionadas, identidades de coleções e ferramentas fornecidas. Mantenha registros válidos e reparáveis inalterados neste lab. Não exclua/reconstrua coleções nem sobrescreva um original existente em quarentena. Trabalhe apenas no catálogo deste curso. Esta é uma janela controlada de manutenção sem gravadores concorrentes; a cópia e a exclusão de um registro são operações separadas, não uma transação que abranja o catálogo inteiro.
Consultar e classificar o catálogo herdado
Nesta etapa, você consultará um registro comum utilizável, distinguirá tipos de contagem armazenados e salvará uma política explícita de classificação. Comece em Terminal. cd muda o diretório; este caminho absoluto também permite se recuperar após entrar no diretório errado:
cd /home/labex/project/catalog-desk
./catalog é o executor de conexões fornecido neste diretório; ./ seleciona o executável local. Seu comando show consulta documentos realmente armazenados. Informe um ID de documento:
./catalog show KIT-101
Procure name: "Tripod", stock.rack: "A" e stock.available contendo "$numberInt": "12". A saída usa Extended JSON canônico: uma representação textual com marcadores explícitos de tipo BSON. BSON é o formato de documentos armazenados do MongoDB. $numberInt identifica um inteiro de 32 bits; $numberLong identifica um inteiro de 64 bits. Esses marcadores na saída exibida descrevem números armazenados, não campos aninhados comuns de estoque que você deva adicionar.
A loja aceita qualquer um dos tipos inteiros para uma contagem disponível entre 0 e 50, inclusive. Os campos originais stock.rack e supplier são informações úteis e devem ser preservados. _id identifica o registro. Dois registros chamados Tripod continuam sendo registros diferentes.
Inspecione o tipo real no servidor de cada contagem:
./catalog types
Este leitor usa a expressão $type de agregação do MongoDB sobre stock.available. Sua coluna actualType distingue int, long, string, null, missing, array e double. double é um tipo BSON de ponto flutuante; mesmo um valor Double 12 com aparência de inteiro está fora do contrato desta loja, que aceita apenas inteiros. missing significa que o campo não existe; null significa que ele existe com um valor null explícito. Um array como [12] não é uma contagem escalar.
Consulte o catálogo completo para relacionar os tipos aos registros originais:
./catalog show
Use esta tabela de decisão antes de escrever uma consulta:
| Contagem armazenada | Decisão neste lab | Motivo |
|---|---|---|
| Int32 12 ou Long 0 | Manter como utilizável | Inteiro escalar aceito, incluindo zero conhecido |
String "12" ou "0" |
Manter para reparo aprovado | Representação decimal canônica no intervalo acordado |
"12 kits", "012" ou "51" |
Colocar em quarentena | Significado desconhecido, representação não aprovada ou fora do intervalo |
| Null, ausente, array ou Double 12 | Colocar em quarentena | Não atende ao contrato acordado de inteiro escalar |
Uma string reparável deve conter apenas os dígitos ASCII canônicos de um número de 0 a 50: sem sinal, espaços, zero à esquerda, ponto decimal nem sufixo de unidade. Este é um acordo explícito para estes dados herdados, não uma promessa de que toda string com aparência numérica seja segura para converter. Quarentena significa manter um original completo para revisão; não significa inventar uma contagem substituta.
Todos os comandos seguintes permanecem neste Terminal e diretório. Se digitar um nome de arquivo incorretamente, ls lista os nomes locais reais. Corrija a grafia e execute o comando novamente; um erro de arquivo não é uma recusa do banco de dados e não exige reinicializar o ambiente.
Salvar a política de classificação
Um seletor MongoDB é um documento que descreve quais registros correspondem. $jsonSchema verifica a estrutura e os tipos BSON de um documento. required nomeia campos que devem existir; por si só, não torna seus valores diferentes de null. properties descreve cada campo, bsonType restringe seu tipo BSON, e minimum/maximum incluem seus valores-limite. A regra aninhada de stock exige available separadamente. Campos não mencionados continuam permitidos, portanto rack, supplier e extensões posteriores podem ser preservados.
Use aqui a regra de tipo escalar de JSON Schema. Um predicado comum de consulta, como {"stock.available": {$type: "int"}}, pode corresponder a um array que contém um inteiro. Isso não estabeleceria a contagem escalar exigida por este catálogo.
Você escreverá JavaScript para mongosh, o shell do MongoDB. const dá um nome a um valor; = atribui seu valor inicial, chaves formam um objeto, colchetes formam um array, e vírgulas separam entradas. Texto entre aspas é uma string; 0 e 50 sem aspas são números. Um ponto e vírgula encerra uma instrução. schema descreve um registro aceito completo, e valid o envolve como um seletor MongoDB.
A política de strings aprovadas tem a mesma estrutura, alterando apenas a regra da contagem. JSON.stringify transforma este objeto simples de política em texto JSON, e JSON.parse lê uma cópia separada; alterar a cópia deixa a política original intacta. Esse método de cópia é para a política simples, não para preservar registros BSON armazenados. stringSchema.properties.stock.properties.available acessa a regra aninhada que será substituída.
O pattern é uma expressão regular para texto permitido. ^ ancora o início, e \z significa o fim absoluto da string; | separa alternativas, [1-9] permite um dígito de 1 a 9, e [1-4][0-9] permite dois dígitos de 10 a 49. Junto com 0 e 50, essas alternativas cobrem exatamente o intervalo aprovado. Strings JavaScript usam uma barra invertida como escape, portanto a fonte escreve \\z para passar \z ao MongoDB. Essa âncora de fim absoluto também rejeita uma quebra de linha final; uma âncora comum de fim $ pode permitir uma correspondência antes dessa quebra de linha. $nor: [valid, repairable] seleciona registros que não correspondem a nenhum dos seletores.
Crie o arquivo de política com um here-document. cat > policy.js grava o texto a seguir no arquivo e substitui seu conteúdo anterior. O delimitador <<'POLICY' entre aspas mantém cifrões e outros textos JavaScript literais no shell. Termine com POLICY sozinho em uma linha. Copie o bloco completo:
cat > policy.js <<'POLICY'
const schema = {
bsonType: "object",
required: ["_id", "name", "stock"],
properties: {
_id: { bsonType: "string", minLength: 1 },
name: { bsonType: "string", minLength: 1 },
stock: {
bsonType: "object",
required: ["available"],
properties: {
available: { bsonType: ["int", "long"], minimum: 0, maximum: 50 }
}
}
}
};
const valid = { $jsonSchema: schema };
const stringSchema = JSON.parse(JSON.stringify(schema));
stringSchema.properties.stock.properties.available = {
bsonType: "string",
pattern: "^(0|[1-9]|[1-4][0-9]|50)\\z"
};
const repairable = { $jsonSchema: stringSchema };
const bad = { $nor: [valid, repairable] };
POLICY
O classificador fornecido avalia este arquivo salvo sem alterações usando uma identidade de banco de dados somente leitura. Depois, usa seus seletores valid, repairable e bad em consultas reais do MongoDB:
./catalog classify policy.js
Inspecione os grupos reais de documentos, não apenas seus tamanhos. valid deve conter KIT-101 e KIT-102, incluindo o Long zero original. repairable deve conter KIT-103 e KIT-104, com suas strings originais inalteradas. outside deve conter KIT-105 a KIT-111, incluindo o null original, o campo ausente, o array e o Double. A extensão completa de revisão com o fornecedor em KIT-105 ainda deve estar visível.
Se aparecer um erro de sintaxe JavaScript, o arquivo não foi aceito como uma política funcional. Execute novamente o here-document completo para substituir um arquivo danificado e execute o classificador outra vez. Um erro de sintaxe identifica um problema de arquivo/expressão; uma conexão de banco de dados indisponível é um erro diferente e não significa que a classificação não retornou correspondências. Nenhum dado foi reparado nem removido por este classificador.
Preservar originais antes de remover registros incertos
Nesta etapa, você salvará um script de quarentena, observará uma interrupção real após sua primeira cópia e executará novamente o script inalterado para concluir. Comece em Terminal, em /home/labex/project/catalog-desk.
Cada documento de quarentena terá o _id original, um original contendo o registro armazenado completo e reason: "outside-approved-catalog-contract". O motivo descreve por que é necessária uma revisão com o fornecedor; não afirma que o estoque seja zero. Uma contagem original pode estar ausente, ser null, um array ou outro tipo, portanto copiar apenas o valor exibido ou apenas o nome perderia evidências.
Existe outro limite: o shell pode converter valores numéricos BSON armazenados em números JavaScript comuns ao ler. Salvar um número JavaScript com aparência de inteiro pode então produzir Int32, mesmo quando o original era Double. Para cada cópia e comparação de original, use o terceiro argumento de find/findOne, {promoteValues: false, bsonRegExp: true}. Ele mantém os wrappers numéricos BSON e os valores de expressão regular em vez de convertê-los em equivalentes JavaScript. O segundo argumento {} é uma projeção vazia, que mantém todos os campos. EJSON.stringify(value, {relaxed: false}) produz Extended JSON canônico a partir desses tipos preservados, portanto um Double 12 e um Int32 12 permanecem diferentes na comparação.
O laço do script lê uma vez o grupo de registros que não são válidos nem reparáveis e trata cada registro separadamente. sort({_id: 1}) fornece IDs em ordem crescente legível, e toArray() reúne os resultados. for (const doc of ...) repete as instruções internas para cada resultado. A operação upsert encontra um registro de quarentena por seu ID original; upsert: true o cria se estiver ausente. $setOnInsert grava o original e o motivo apenas ao criar esse registro, deixando uma cópia existente intacta. findOne relê a cópia armazenada.
if testa uma condição. !== significa diferente, e || significa que uma das condições é verdadeira. Um original ou motivo diferente deve executar throw Error(...), que interrompe o script com status de saída diferente de zero antes de excluir esse registro de origem. deleteOne remove apenas o ID de origem verificado. Registros anteriores podem já ter sido concluídos; estas são operações individuais, não um lote de tudo ou nada. Sem gravadores concorrentes, uma comparação completa bem-sucedida antes de cada exclusão fornece o caminho de retomada usado aqui.
Grave o script. O here-document QUARANTINE usa a mesma sintaxe de escrita literal de antes:
cat > quarantine.js <<'QUARANTINE'
const typed = { promoteValues: false, bsonRegExp: true };
for (const doc of db.equipment.find(bad, {}, typed).sort({_id: 1}).toArray()) {
db.quarantine.updateOne(
{_id: doc._id},
{$setOnInsert: {original: doc, reason: "outside-approved-catalog-contract"}},
{upsert: true}
);
const saved = db.quarantine.findOne({_id: doc._id}, {}, typed);
if (EJSON.stringify(saved.original, {relaxed: false}) !== EJSON.stringify(doc, {relaxed: false}) ||
saved.reason !== "outside-approved-catalog-contract") {
throw Error("Existing quarantine copy differs; keep source and investigate");
}
db.equipment.deleteOne({_id: doc._id});
}
QUARANTINE
Primeiro, use a conexão fornecida copy-only. Esta é uma identidade real do banco de dados que permite consultar e copiar, mas não excluir. O executor avalia policy.js e depois quarantine.js sem alterações em uma execução do shell. Espere um erro nativo de autorização e um status de saída diferente de zero quando o script tentar excluir pela primeira vez:
./catalog copy-only policy.js quarantine.js
A mensagem inclui not authorized e um comando delete em equipment. A cópia teve sucesso antes dessa recusa. Consulte os dois registros reais:
./catalog archive KIT-105
./catalog show KIT-105
KIT-105 agora existe nos dois lugares. O original em quarentena deve incluir seu texto de estoque e a extension completa, incluindo seus marcadores de tipo numérico, data e ObjectId. A origem ativa ainda está intacta. Portanto, um comando com falha não implica que todas as escritas anteriores tenham sido desfeitas.
Execute novamente os mesmos arquivos salvos usando a conexão comum de escrita do curso:
./catalog run policy.js quarantine.js
Um script bem-sucedido não precisa produzir saída. A primeira cópia existente é verificada e mantida; sua origem correspondente é removida. Os registros incertos restantes são copiados, verificados e removidos em sequência. Consulte os resultados reais:
./catalog show
./catalog archive
O catálogo ativo deve conter apenas KIT-101 a KIT-104, inalterados, incluindo as duas strings aprovadas aguardando reparo. A quarentena deve conter KIT-105 a KIT-111 com documentos originais completos e o motivo acordado. Em particular, o Double original de KIT-109 ainda exibe $numberDouble, e o original de KIT-107 ainda não tem o campo available.
Execute a migração novamente:
./catalog run policy.js quarantine.js
./catalog archive
Não restam registros fora dos dois grupos aceitos — válidos e reparáveis — para mover, portanto os originais salvos permanecem iguais. Esta é uma reexecução idempotente: repetir uma operação concluída não altera seu resultado de negócio. Uma interrupção após uma cópia também pode ser retomada porque a chave da coleção de quarentena impede uma cópia duplicada, e a comparação recusa uma cópia existente inconsistente. Se esse erro de conflito aparecer, mantenha a origem e a cópia arquivada anterior para investigação; não o “corrija” sobrescrevendo a cópia arquivada nem excluindo a origem. O script não estabelece segurança contra edições concorrentes entre sua leitura e exclusão, nem consegue desfazer o lote inteiro.
Resumo
Você separou contagens inteiras utilizáveis, strings explicitamente aprovadas e registros incertos com uma política reutilizável do MongoDB. Depois, preservou originais completos com seus tipos antes de excluir suas origens ativas, observou uma falha parcial real e retomou o mesmo script salvo. O catálogo agora mantém registros utilizáveis e reparáveis, enquanto a quarentena retém evidências para revisão com o fornecedor. Conversão e validação de escritas futuras são o tema da próxima unidade independente.



