Классифицируйте записи оборудования и поместите неопределённые в карантин

MongoDBBeginner
Практиковаться сейчас

Введение

FIELDNOTE сдаёт напрокат небольшие комплекты оборудования. В унаследованном каталоге MongoDB полезные остатки соседствуют со старыми строками и неопределёнными значениями. Известное количество ноль означает, что комплект недоступен; отсутствие количества означает, что магазин его не знает. Превращение обоих случаев в ноль скрыло бы проблему данных. Ваша задача — разделить эти случаи и сохранить ненадёжные записи для последующей проверки поставщиком.

Вы сохраните политику классификации для повторного использования и скрипт карантина, который сохраняет полный исходный документ перед удалением из активной коллекции. Нужны базовые навыки навигации в терминале и знакомство с документами MongoDB, вложенными полями, find и обычными обновлениями. Конструкции JavaScript, правила схемы и особенности BSON объясняются перед использованием. MongoDB, его оболочка и синтетические данные каталога подготовлены. На протяжении всей работы используйте Terminal. Длительность — оценка автора с учётом чтения и обычных ошибок.

Создайте policy.js и quarantine.js в /home/labex/project/catalog-desk. Сохраните каждый исходный ID, все исходные поля целиком и их типы, посторонние расширения, идентичность коллекций и предоставленный инструмент. В этом занятии оставьте допустимые и исправимые записи неизменными. Не удаляйте и не пересоздавайте коллекции и не перезаписывайте существующий оригинал в карантине. Работайте только с каталогом этого курса. Это контролируемое окно обслуживания без конкурирующих записей; копирование и удаление записи — отдельные операции, а не транзакция, охватывающая весь каталог.

Прочитайте и классифицируйте унаследованный каталог

На этом шаге вы прочитаете обычную пригодную запись, различите сохранённые типы количества и сохраните явную политику классификации. Начните во вкладке Terminal. cd меняет каталог; этот абсолютный путь также поможет вернуться из ошибочно выбранного каталога:

cd /home/labex/project/catalog-desk

./catalog — предоставленная программа запуска соединений в этом каталоге; ./ выбирает локальный исполняемый файл. Её команда show читает фактически сохранённые документы. Передайте ей ID одного документа:

./catalog show KIT-101

Найдите name: "Tripod", stock.rack: "A" и stock.available, содержащее "$numberInt": "12". Вывод использует канонический Extended JSON — текстовое представление с явными метками типов BSON. BSON — формат хранения документов MongoDB. $numberInt обозначает 32-битное целое; $numberLong — 64-битное целое. Эти метки в выводе описывают сохранённые числа, а не обычные вложенные поля остатков, которые нужно добавить.

Магазин принимает любой из двух целочисленных типов для доступного количества от 0 до 50 включительно. Исходные поля stock.rack и supplier содержат полезную информацию и должны сохраниться. _id идентифицирует запись. Две записи с названием Tripod всё равно остаются разными.

Изучите фактический серверный тип каждого количества:

./catalog types

Это средство чтения использует выражение агрегирования MongoDB $type для stock.available. Столбец actualType различает int, long, string, null, missing, array и double. double — тип BSON с плавающей точкой; даже выглядящее целым значение Double 12 находится вне контракта магазина, допускающего только целые числа. missing означает отсутствие поля; null — наличие поля с явным значением null. Массив вроде [12] не является одним скалярным количеством.

Прочитайте полный каталог, чтобы связать типы с исходными записями:

./catalog show

Перед написанием запроса используйте эту таблицу решений:

Сохранённое количество Решение в этом занятии Причина
Int32 12 или Long 0 Оставить пригодным Допустимое скалярное целое, включая известный ноль
Строка "12" или "0" Оставить для утверждённого исправления Каноническая десятичная запись в согласованном диапазоне
"12 kits", "012" или "51" Поместить в карантин Неизвестный смысл, неутверждённая запись или выход за диапазон
Null, отсутствие, массив или Double 12 Поместить в карантин Не соответствует согласованному контракту скалярного целого

Исправимая строка должна содержать только канонические цифры ASCII для числа от 0 до 50: без знака, пробелов, ведущего нуля, десятичной точки или суффикса единицы измерения. Это явная договорённость для этих унаследованных данных, а не обещание безопасного преобразования любой строки, похожей на число. Карантин означает сохранение полного оригинала для проверки, а не выдумывание нового количества.

Все последующие команды остаются в этой вкладке Terminal и каталоге. При опечатке в имени файла ls перечисляет настоящие локальные имена. Исправьте написание и повторите команду; ошибка файла — не отказ базы данных и не требует сброса окружения.

Сохраните политику классификации

Селектор MongoDB — документ, описывающий подходящие записи. $jsonSchema проверяет структуру документа и типы BSON. required перечисляет поля, которые должны существовать; само по себе это не запрещает null в их значениях. properties описывает каждое поле, bsonType ограничивает его тип BSON, а minimum/maximum включают граничные значения. Вложенное правило stock отдельно требует available. Неупомянутые поля остаются разрешёнными, поэтому rack, supplier и последующие расширения могут сохраниться.

Здесь используйте правило скалярного типа JSON Schema. Обычный предикат запроса вроде {"stock.available": {$type: "int"}} может найти массив с целочисленным элементом. Это не доказывает наличие скалярного количества, необходимого каталогу.

Вы напишете JavaScript для mongosh, оболочки MongoDB. const даёт значению имя; = присваивает начальное значение, фигурные скобки образуют объект, квадратные — массив, а запятые разделяют элементы. Текст в кавычках — строка; 0 и 50 без кавычек — числа. Точка с запятой завершает инструкцию. schema описывает целую допустимую запись, а valid оборачивает её в селектор MongoDB.

Политика утверждённых строк имеет ту же структуру с изменением только правила количества. JSON.stringify превращает этот обычный объект политики в текст JSON, а JSON.parse читает отдельную копию; изменение копии не затрагивает исходную политику. Такой способ копирования предназначен для обычной политики, а не для сохранения записей BSON. stringSchema.properties.stock.properties.available обращается к заменяемому вложенному правилу.

pattern — регулярное выражение для разрешённого текста. ^ закрепляет начало, а \z означает абсолютный конец строки; | разделяет альтернативы, [1-9] допускает одну цифру от 1 до 9, [1-4][0-9] — две цифры от 10 до 49. Вместе с 0 и 50 эти альтернативы покрывают ровно утверждённый диапазон. Строки JavaScript используют обратную косую черту для экранирования, поэтому исходник пишет \\z, чтобы передать \z в MongoDB. Этот якорь абсолютного конца также отклоняет завершающий перевод строки; обычный конечный якорь $ может допустить совпадение перед ним. $nor: [valid, repairable] выбирает записи, не подходящие ни одному селектору.

Создайте файл политики с помощью here-document. cat > policy.js записывает следующий текст в файл, заменяя прежнее содержимое. Разделитель <<'POLICY' в кавычках сохраняет знаки доллара и остальной JavaScript буквальными для оболочки. Завершите отдельной строкой POLICY. Скопируйте полный блок:

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

Предоставленный классификатор выполняет этот сохранённый файл без изменений под учётной записью базы данных только для чтения. Затем он использует ваши селекторы valid, repairable и bad в настоящих запросах MongoDB:

./catalog classify policy.js

Изучите фактические группы документов, а не только их размеры. valid должен содержать KIT-101 и KIT-102, включая исходный ноль типа Long. repairable должен содержать KIT-103 и KIT-104 с неизменными исходными строками. outside должен содержать KIT-105–KIT-111, включая исходный null, отсутствующее поле, массив и Double. Полное расширение для проверки поставщиком в KIT-105 должно оставаться видимым.

Если появилась синтаксическая ошибка JavaScript, файл не принят как работающая политика. Повторите полный here-document, чтобы заменить повреждённый файл, затем снова запустите классификатор. Синтаксическая ошибка обозначает проблему файла или выражения; недоступное соединение с базой — другая ошибка и не означает отсутствие совпадений классификации. Этот классификатор не исправлял и не удалял данные.

Сохраните оригиналы перед удалением неопределённых записей

На этом шаге вы сохраните скрипт карантина, увидите настоящее прерывание после первой копии и повторите неизменённый скрипт для завершения. Начните во вкладке Terminal, в /home/labex/project/catalog-desk.

Каждый документ карантина будет иметь исходный _id, поле original с полной сохранённой записью и reason: "outside-approved-catalog-contract". Причина объясняет необходимость проверки поставщиком; она не утверждает, что остаток равен нулю. Исходное количество может отсутствовать, быть null, массивом или другим типом, поэтому копирование только показанного значения или имени потеряло бы свидетельства.

Есть ещё одна граница: при чтении оболочка может преобразовать сохранённые числовые значения BSON в обычные числа JavaScript. Сохранение выглядящего целым числа JavaScript затем может дать Int32, даже если оригинал был Double. Запись в EJSON не восстановит исходный тип, если он уже потерян при преобразовании в число JavaScript. Для каждой копии оригинала и сравнения используйте третий аргумент find/findOne — {promoteValues: false, bsonRegExp: true}. Он сохраняет числовые обёртки BSON и значения регулярных выражений вместо преобразования в эквиваленты JavaScript. Второй аргумент {} — пустая проекция, сохраняющая каждое поле. EJSON.stringify(value, {relaxed: false}) создаёт канонический Extended JSON из этих сохранённых типов, поэтому Double 12 и Int32 12 остаются разными при сравнении.

Цикл скрипта один раз читает группу вне контракта и обрабатывает каждую запись отдельно. sort({_id: 1}) даёт удобочитаемый порядок ID по возрастанию, а toArray() собирает результаты. for (const doc of ...) повторяет заключённые внутри инструкции для каждого результата. Операция upsert находит запись карантина по исходному ID; upsert: true создаёт её при отсутствии. $setOnInsert записывает оригинал и причину только при создании, не трогая существующую копию. findOne перечитывает сохранённую копию.

if проверяет условие. !== означает «не равно», а || — истинность любого из условий. Другой оригинал или причина должны вызвать throw Error(...), который останавливает скрипт с ненулевым кодом до удаления этой исходной записи. deleteOne удаляет только проверенный исходный ID. Предыдущие записи уже могли завершить обработку; это отдельные операции, а не пакет «всё или ничего». При отсутствии конкурирующих записей успешное полное сравнение перед каждым удалением обеспечивает используемый здесь путь повтора.

Запишите скрипт. Here-document QUARANTINE использует тот же синтаксис буквальной записи, что и раньше:

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

Сначала используйте предоставленное соединение copy-only. Это настоящая учётная запись базы данных, которой разрешено читать и копировать, но запрещено удалять. Программа запуска выполняет policy.js, затем quarantine.js без изменений в одном вызове оболочки. Ожидайте штатную ошибку авторизации и ненулевой код завершения, когда скрипт впервые попробует удалить:

./catalog copy-only policy.js quarantine.js

Сообщение включает not authorized и команду delete для equipment. Копирование успешно выполнилось до этого отказа. Прочитайте обе настоящие записи:

./catalog archive KIT-105
./catalog show KIT-105

Теперь KIT-105 существует в обоих местах. Оригинал в карантине должен включать текст остатка и полное extension, включая метки числовых типов, даты и ObjectId. Активный источник всё ещё нетронут. Поэтому неудачная команда не означает, что все предыдущие записи были отменены.

Повторите те же сохранённые файлы через обычное соединение курса с правом записи:

./catalog run policy.js quarantine.js

Успешный скрипт не обязан что-либо выводить. Первая существующая копия проверяется и сохраняется, её соответствующий источник удаляется. Остальные неопределённые записи по очереди копируются, проверяются и удаляются. Прочитайте фактические результаты:

./catalog show
./catalog archive

Активный каталог должен содержать только KIT-101–KIT-104 без изменений, включая две утверждённые строки, ожидающие исправления. Карантин должен содержать KIT-105–KIT-111 с полными исходными документами и согласованной причиной. В частности, исходный Double у KIT-109 всё ещё показывает $numberDouble, а у оригинала KIT-107 всё ещё нет поля available.

Запустите миграцию снова:

./catalog run policy.js quarantine.js
./catalog archive

Не осталось записей вне контракта для переноса, поэтому сохранённые оригиналы не меняются. Это идемпотентный повтор: повтор завершённой операции не меняет её рабочий результат. Прерывание после копирования также можно продолжить, поскольку ключ архива предотвращает дублирование копии, а сравнение отклоняет несогласованную существующую копию. При такой ошибке конфликта сохраните источник и прежний архив для расследования; не «исправляйте» её перезаписью архива или удалением источника. Скрипт не обеспечивает безопасность от одновременных изменений между чтением и удалением и не может откатить весь пакет.

Резюме

Вы разделили пригодные целочисленные количества, явно утверждённые строки и неопределённые записи с помощью политики MongoDB для повторного использования. Затем вы сохранили полные оригиналы с их типами перед удалением активных источников, увидели настоящий частичный отказ и продолжили тот же сохранённый скрипт. Теперь каталог хранит пригодные и исправимые записи, а карантин — свидетельства для проверки поставщиком. Преобразование и проверка будущих записей — задачи следующего независимого занятия.