Автор оригинала: Christian Amor Kvalheim.
Резюме
В этой статье мы собираемся исследовать использование битовая упаковка
Отказ Это позволяет нам сжать несколько значений в Целое число
или бинарный
поле в Монгодб
Потенциально позволяя нам сохранять размер документа в хранении и передаче через провод. Он имеет некоторые реальные преимущества, но поставляются с компромиссами, что вы должны быть четко вставлены до его применения.
* Невозможно использовать индексы для запросов BIT Упакоемого поля ввода. | * Уменьшить пространство, потребляемое документом. |
* Требуются комплексные операторы обновления. | * Может эффективно упаковать несколько значений и флаги в одно поле. |
* Требует битовых потрясающих знаний. | * Меньшие документы означают меньше данных, передаваемых через проволоку. |
В общем, вы должны подумать только битовая упаковка
Когда небольшой размер документа требуется атрибут, поскольку он вводит дополнительную сложность вашего приложения. Однако, если вам это нужно, это может быть бесценным инструментом.
Обзор
Давайте рассмотрим нашу разведочную упаковку битов с помощью гипотетической модели данных, которые моделируют файловую систему Unix, включая чтение, писать, выполнять биты для пользователя, групп и других. Давайте рассмотрим следующую модель.
{ name: "peter", isDirectory: true, path: "/home/user/peter", parentPath: "/home/user", group: 0, user: 10, permissions: { group: { read: true, write: false, execute: false }, user: { read: true, write: true, execute: true }, all: { read: true, write: false, execute: false } } }
В этой модели каждый документ представляет собой каталог или файл. Каждый из записей принадлежит Пользователь
и а Группа
Отказ В нашей системе мы можем иметь 256
Разные группы, представленные 8 бит
и 16384
Отличные пользователи, представленные 14 бит
Отказ
Теперь, если мы сможем представлять все эти поля и их значение одним 32-битным целым числом. Мы бы сохранили честный кусочек пространства. Вместо того, чтобы сохранить все пространство для полей Isdirectory
, Группа
, Пользователь
, Разрешения
, Разрешения. Группа
, Permissions.group.read/write/execute
, Разрешения.user
, permissions.user.read/write/execute
, Разрешения. Все
и permissions.all.read/write/execute
Мы могли бы создать одно поле, которое содержало все значения, упакованные в одно целое значение 32 бита, называемых Метаданные
Отказ
Для Метаданные
Поле мы определим каждую группу битов, чтобы содержать сжатую информацию.
1 // [0:0] isDirectory 11111111 // [1:9] Group id 11111111111111 // [10:23] User id 111 // [24:26] Group r/w/x 111 // [27:29] User r/w/x 111 // [29:31] All r/w/x
- Биты [0: 0] определяют
Isdirectory
значение. - Биты [1: 9] кодируют
8
битыГрупповой идентификатор
Отказ - Биты [10:23] кодируют
14
битыИдентификатор пользователя
Отказ - Биты [24:26] кодируют
Группа Прочитайте/Написать/Выполнить флаги
Отказ - Биты [27:29] кодируют
Пользователь чтение/запись/выполнение флагов
Отказ - Биты [29:31] кодируют
Все флаги чтения/записи/выполнения
Отказ
Полученный Метаданные
поле будет 32
Битовое значение, которое будет представлять флаги.
{ name: "peter", path: "/home/user/peter", parentPath: "/home/user", metadata: 0b11111111111111111111111111111111 }
Давайте посмотрим, как изменение влияет на размер получаемого документа MongoDB. Для этого мы собираемся использовать метод в оболочке под названием Объект .ssonsize
который позволяет нам пройти объект JS и получить BSON Binary размер
в байтах.
Оригинал | 244 |
Бит упакован | 93 |
Как мы видим, битовая упаковка уменьшает размер получаемого документа довольно немного. Тренование, конечно, более сложный код.
Изменение метаданных
Итак, как мы изменим разрешения на чтение или изменение идентификатора пользователя или группы? Для этого нам нужно использовать Обновить оператор $ Bit
выполнять и
и или
Операции на поле метаданных.
Мы собираемся использовать класс обертки под названием Вход
Упростить модификацию Метаданные
поле.
class Entry { constructor(collection, _id, name, path, parentPath, metadata) { this.collection = collection; this._id = _id; this.name = name; this.path = path; this.parentPath = parentPath; this.metadata = metadata; } setGroupId(id) { // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt( parseInt(`10000000011111111111111111111111`, 2)), NumberInt( parseInt(`0${createIntegerMask(id, 8)}00000000000000000000000`, 2)) ); } setUserId(id) { // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt( parseInt(`11111111100000000000000111111111`, 2)), NumberInt( parseInt(`000000000${createIntegerMask(id, 14)}000000000`, 2)) ); } setGroupBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt( parseInt(`11111111111111111111111${andBits}111111`, 2)), NumberInt( parseInt(`00000000000000000000000${orBits}000000`, 2)) ); } setUserBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt( parseInt(`11111111111111111111111111${andBits}111`, 2)), NumberInt( parseInt(`00000000000000000000000000${orBits}000`, 2)) ); } setAllBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt( parseInt(`11111111111111111111111111111${andBits}`, 2)), NumberInt( parseInt(`00000000000000000000000000000${orBits}`, 2)) ); } save() { this.collection.updateOne({ _id: this._id }, { $set: { name: this.name, path: this.path, parentPath: this.parentPath, metadata: this.metadata } }, { upsert: true }); } }
Класс содержит несколько методов.
Устанавливает групповую часть поля метаданных. | setgroupid (id) |
Устанавливает пользовательскую часть поля метаданных. | setuserid (id) |
Устанавливает биты для чтения, записи и выполнения для групповых разрешений, прохождение undefined или NULL пропустит бит. | setgroupbits (прочитать, писать, execute) |
Устанавливает биты считывания, записи и выполнения для пользовательских разрешений, прохождение undefined или NULL пропустит бит. | setuserbits (прочитать, писать, execute) |
Устанавливает биты для чтения, записи и выполнения для всех разрешений, прохождение undefined или null пропустит бит. | setallbits (чтение, запись, execute) |
Вставляет или обновления документа | спасти() |
Мы собираемся рассекать два метода для нашего дела. Первый, будучи setuserid (id)
метод и второй, будучи setusersbits (прочитать, писать, выполнять)
метод.
Давайте начнем с поиска setuserid (id)
метод.
setUserId(id) { // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt(parseInt(`11111111100000000000000111111111`, 2)), NumberInt(parseInt(`000000000${createIntegerMask(id, 14)}0000000000`, 2)) ); }
Мы называем два общих метода. Первый называется CreateIntEgermask
а второй называется UpdateMask
Отказ
CreateIntEgermask
Метод принимает значение, переданное в и « » биты
Разрешение поля.
UpdateMask
Метод занимает Коллекция
экземпляр, _id
Значение целевого документа и две маски. Первый, будучи И
маска и второе существо Или
маска.
Давайте посмотрим на каждого из них, начиная с createIntegermask (ID, 14)
Отказ
function createIntegerMask(id, bitResolution) { let bitsString = id.toString(2); if (bitsString.length > bitResolution) { throw Error(`value id must be between 0 and ${Math.pow(2, bitResolution) - 1}`); } // Ensure `bitResolution` bit string let missingValues = ""; for(let i = 0; i < (bitResolution - bitsString.length); i++) { missingValues = missingValues + "0"; } // Pad the bitString with 0s return missingValues + bitsString; }
CreateIntEgermask
Метод будет генерировать Строка
длины BitResolution
это бинарный
Представление стоимости ID
Отказ Чтобы получить возвращенную строку точно BitResolution
Длинный этот метод прокладывает переднюю часть битовой строки с 0
s до конца длины конца BitResolution
длинный.
Другими словами, говорят, что мы проходим в ID
Значение 1
с BitResolution
установить 4
Отказ Это будет превращено в следующие Строка
Представительство 0001
Отказ
Теперь давайте вернемся к сезонному методу.
setUserId(id) { // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt(parseInt(`11111111100000000000000111111111`, 2)), NumberInt(parseInt(`000000000${createIntegerMask(id, 14)}0000000000`, 2)) ); }
Помните битовую упаковку схемы, которую мы определили.
1 // [0:0] isDirectory 11111111 // [1:9] Group id 11111111111111 // [10:23] User id 111 // [24:26] Group r/w/x 111 // [27:29] User r/w/x 111 // [29:31] All r/w/x
- Биты [0: 0] определяют
Isdirectory
значение. - Биты [1: 9] кодируют
8
битыГрупповой идентификатор
Отказ - Биты [10:23] кодируют
14
битыИдентификатор пользователя
Отказ - Биты [24:26] кодируют
Группа Прочитайте/Написать/Выполнить флаги
Отказ - Биты [27:29] кодируют
Пользователь чтение/запись/выполнение флагов
Отказ - Биты [29:31] кодируют
Все флаги чтения/записи/выполнения
Отказ
Мы хотим обновить биты в позиции [10:23]
с новым UserID
передается как ID
параметр. Чтобы сделать его более ощутимым, позволяет предположить ID
имеет значение 1
Отказ
Мы создаем две маски здесь.
11111111100000000000000111111111 | А ТАКЖЕ |
000000000 $ {createictegermask (ID, 14)} 0000000000 | ИЛИ ЖЕ |
И
Маска имеет биты на позициях [10:23]
установить 0
и Или
Маска имеет то же самое [10:23]
Биты, установленные в двоичное представление ID
значение.
Так что же делает UpdateMask
на самом деле сделать. Давайте взглянем.
function updateMask(collection, _id, andMask, orMask) { // Update the fields collection.updateOne({ _id: _id }, { $bit: { metadata: { and: andMask, or: orMask } } }); }
Не так много, как вы можете видеть. Он в основном обновил документ с _id
поле, равное прошло в _id
параметр. Однако волшебство лежит в $ бит
оператор.
$ бит
Оператор позволяет нам применить три побитовый
Операции и
, или
и Хор
Отказ Что три операции делают на Метаданные
поле? Чтобы понять это, нам нужно понять, что a побитовый
и
, или
или Хор
делает.
И операция
Давайте начнем с поиска того, как И
Работа работает на одном битовом поле. Это наиболее легко выражено, используя Правда
Таблица.
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Оба А
и B
должно быть 1
для И
Операция для возврата 1
Отказ Если какая-то из двух сторон – 0
Результатом также 0
Отказ
Мы можем использовать И
Операции для очистки битов, предоставляя маску, где мы хотим очищенные биты, установлено на 0
Отказ
Давайте возьмем пример существующего значения и применяя маску, используя И
Отказ
10111101 | Оригинальное значение |
11100111 | И маска |
10100101 | Результирующее значение |
В И
Маска Мы устанавливаем биты, где мы хотим сохранить оригинальную настройку бита в 1
и те, которые мы хотим очистить до 0
Отказ Если вы посмотрите на стол, вы можете увидеть, что 1
Обеспечить не изменение для соответствующего бита в исходном значении, в то время как 0
всегда будет очистить это.
Или операция
Далее давайте посмотрим на то, как Или
побитовые работы работает. Давайте посмотрим на Правда
Таблица для Или
Отказ
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Мы можем видеть из таблицы, что если Маска
Битовое значение установлено на 1
Полученное значение бита всегда будет 1
Отказ
Мы можем использовать Или
Операция для обеспечения определенных битов установлена на конечное значение.
Давайте возьмем пример существующего значения и применяя маску, используя Или
Отказ
10100101 | Оригинальное значение |
10000 | Или маска |
10110101 | Результирующее значение |
В Или
Маска Мы устанавливаем биты, где мы хотим сохранить оригинальную настройку бита в 0
и те, которые мы хотим установить 1
Отказ Если вы посмотрите на стол, вы можете увидеть, что 0
Обеспечить не изменение для соответствующего бита в исходном значении, в то время как 1
всегда будет настроить это.
Работа XOR
Наконец-то давайте посмотрим на Хор
Операция или Эксклюзивный или
Отказ Вы можете думать о Хор
как способ обратить ценность одного бита. Давайте посмотрим на Правда
Таблица для Хор
Отказ
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Мы можем видеть из таблицы, что если Маска
бит установлен на 1
Он будет переворачивать значение оригинала к этому обратно. То есть как 0
станет 1
и а 1
станет 0
Отказ
Мы можем использовать Хор
перевернуть флаг от 0
к 1
или 1
к 0
Отказ
Давайте возьмем пример существующего значения и применяя маску, используя Или
Отказ
10110101 | Оригинальное значение |
11000 | Хорская маска |
10101101 | Результирующее значение |
Мы можем видеть, что значение 10
Получает его биты перевернуты и становится 01
Отказ
$ битовая работа в setuser (id)
Давайте посмотрим на $ бит
операция мы делаем в Обновить
утверждение.
$bit: { metadata: { and: andMask, or: orMask } }
Что именно это делает? Позволяет использовать полный пример для setuserid (id)
Метод, где ID
равно 1
Отказ
11111111100000001110000111111111 | Оригинальное значение |
11111111100000000000000111111111 | И маска |
11111111100000000000000111111111 | Результирующее значение |
11111111100000000000001111111111 | Или маска |
11111111100000000000001111111111 | Результирующее значение |
Первый И
Шаг, устанавливает все биты в UserID
к 0
, чтобы очистить 14 бит
значение.
Далее Или
Маска устанавливает все биты на 1
где его маска имеет немного, установленную на 1
При этом применяя новый UserID
к этому пространству в Метаданные
поле.
Операция позволяет нам применить набор битов в определенное место в 32 бит
Длинное целое число, сохранение таким образом ГРУПИД
как 14 бит
Значение внутри Метаданные
поле на позициях [10:23]
из 32
Битовое целочисленное значение.
Setuserbits (чтение, запись, выполнение) функция
setuserbits
Функция позволяет нам установить три флаги читать
, Написать
и Выполнить
для Пользователь
на Метаданные
поле.
setUserBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field updateMask(this.collection, this._id, NumberInt(parseInt(`11111111111111111111111111${andBits}111`, 2)), NumberInt(parseInt(`00000000000000000000000000${orBits}000`, 2)) ); }
Оптимизация производительности
Одна вещь, которая мучительно очевидна, в настоящее время мы должны выполнить обновление за каждый раз, когда мы хотим изменить поле в упаковке Метаданные
поле. Что если мы могли бы установить несколько полей значений одновременно?
Давайте изменим Вход
Класс, чтобы позволить нам выполнить несколько обновлений поля в одном операции.
class TurboEntry { constructor(collection, _id, name, path, parentPath, metadata) { this.collection = collection; this._id = _id; this.name = name; this.path = path; this.parentPath = parentPath; this.metadata = metadata; // Contains the masks we are going to apply this.updateMasks = { and: null, or: null } } applyMask(andMask, orMask) { if (!this.updateMasks.and) { this.updateMasks.and = andMask; } else { this.updateMasks.and = this.updateMasks.and & andMask; } if (!this.updateMasks.or) { this.updateMasks.or = orMask; } else { this.updateMasks.or = this.updateMasks.or | orMask; } } setGroupId(id) { this.applyMask( parseInt(`10000000011111111111111111111111`, 2), parseInt(`0${createIntegerMask(id, 8)}00000000000000000000000`, 2) ) } setUserId(id) { // Create AND and OR bitmasks and update metadata field this.applyMask( parseInt(`11111111100000000000000111111111`, 2), parseInt(`000000000${createIntegerMask(id, 14)}000000000`, 2) ); } setGroupBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field this.applyMask( parseInt(`11111111111111111111111${andBits}111111`, 2), parseInt(`00000000000000000000000${orBits}000000`, 2) ); } setUserBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field this.applyMask( parseInt(`11111111111111111111111111${andBits}111`, 2), parseInt(`00000000000000000000000000${orBits}000`, 2) ); } setAllBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field this.applyMask( parseInt(`11111111111111111111111111111${andBits}`, 2), parseInt(`00000000000000000000000000000${orBits}`, 2) ); } updateMetadata() { // If no mask applied do nothing if (!this.updateMasks.and || !this.updateMasks.or) { return; } // Update the fields this.collection.updateOne({ _id: this._id }, { $bit: { metadata: { and: NumberInt(this.updateMasks.and), or: NumberInt(this.updateMasks.or), } } }); } save() { this.collection.updateOne({ _id: this._id }, { $set: { name: this.name, path: this.path, parentPath: this.parentPath, metadata: this.metadata } }, { upsert: true }); } }
Давайте посмотрим на основные изменения здесь. Сначала мы держим нынешнее объединение и
и или
Маски в переменной экземпляра UpdateMasks
Отказ
// Contains the masks we are going to apply this.updateMasks = { and: null, or: null }
UpdateMasks
Переменная содержит и
и или
Поле входа, изначально устанавливается на NULL. Давайте посмотрим на setallbits
Метод нового Вход
класс.
setAllBits(read, write, execute) { // Get generated AND and OR flags var [andBits, orBits] = generateFlagBitMaps(read, write, execute); // Create AND and OR bitmasks and update metadata field this.applyMask( parseInt(`11111111111111111111111111111${andBits}`, 2), parseInt(`00000000000000000000000000000${orBits}`, 2) ); }
Мы видим, что это выглядит в основном то же самое. Единственным существенным изменением является то, что мы называем ApplyMask
Метод, проходящий в битах маски. Давайте посмотрим на ApplyMask
метод.
applyMask(andMask, orMask) { if (!this.updateMasks.and) { this.updateMasks.and = andMask; } else { this.updateMasks.and = this.updateMasks.and & andMask; } if (!this.updateMasks.or) { this.updateMasks.or = orMask; } else { this.updateMasks.or = this.updateMasks.or | orMask; } }
Как мы видим ApplyMask
Метод возьми Андмасс
и Ормасс
Отказ Если в существующих масок в
UpdateMasks
Если у нас уже есть маски в UpdateMasks
Переменная экземпляра Мы И
и
маска в UpdateMasks
с прохожденным в Андмасс
Параметр, чтобы объединить их. Тогда мы …| Или или
Маска в
UpdateMasks Переменная с прошедшим в
Ормасс параметр. Эти две операции объединяют две маски вместе, убедившись, что мы влияем только на биты
Метаданные поле мы планируем модифицировать.
Наконец, чтобы обновить Метаданные
поле мы создаем новый метод под названием UpdateMetadata
Отказ
updateMetadata() { // If no mask applied do nothing if (!this.updateMasks.and || !this.updateMasks.or) { return; } // Update the fields this.collection.updateOne({ _id: this._id }, { $bit: { metadata: { and: NumberInt(this.updateMasks.and), or: NumberInt(this.updateMasks.or), } } }); }
Если есть маски, присутствующие в UpdateMasks
Переменная экземпляра Мы применяем их к Метаданные
поле, используя $ бит
Оператор как раньше.
Запрос на метаданных
Теперь, когда мы знаем, как изменить поля, упакованные в Метаданные
Поле позволяет посмотреть, как мы можем запросить разные поля. Мы собираемся создать простой класс (просто сосредоточиться на аспектах запроса), чтобы позволить нам запросить полей, упакованные в Метаданные
Отказ Прежде чем мы сделаем, нам нужно понять, какие ограничения при запросе на Метаданные
поле.
- Мы можем только выполнять операции равенства. То есть мы можем сопоставить на
Групповой идентификатор
быть равным5
Но не наГрупповой идентификатор
больше чем5
Отказ - Запросы на
Метаданные
Поле не может использовать любыеИндексы
поэтому мы должны обеспечитьМетаданные
поле – последняя часть запроса, используя другиеКритерии
сузить количествоМетаданные
поля, которые должны быть проверены.
Итак, давайте посмотрим, какие операции доступны нам для выполнения побитовый
Запросы.
$ bitsalllear. | Соответствует числовым или двоичным значениям, в которых набор битовых позиций имеет значение 0. |
$ bitsallset. | Соответствует числовым или двоичным значениям, в которых набор битовых позиций имеет значение 1. |
$ bitsanyclear. | Соответствует числовым или двоичным значениям, в которых любая бита из набора битовых позиций имеет значение 0. |
$ bitsalllear. | Соответствует числовым или двоичным значениям, в которых любой бит из набора битовых позиций имеет значение 1. |
Что это значит на практике? Давайте посмотрим на каждый из операторов, в свою очередь, используя примеры. Сначала нам нужен образец документа с двоичным значением.
db.bittest.insertOne({ metadata: 0b011000011110110 });
$ bitsalllear.
Давайте выполним простое запрос, где мы собираемся совпадать со второй группой 4
биты.
db.bittest.findOne({ metadata: { $bitsAllClear: NumberInt(0b0000111100000000) } });
Это будет соответствовать вставленным документе выше, как вторая группа биты
В Метаданные
поле все настроено на 0
Отказ
$ bitsallset.
Давайте выполним простой операцию запроса, где мы собираемся соответствовать третьей группе 4
биты.
db.bittest.findOne({ metadata: { $bitsAllSet: NumberInt(0b0000000011110000) } });
Это будет соответствовать вставленным документе выше, как третья группа биты
В Метаданные
поле все настроено на 0
Отказ
$ bitsanyclear.
Давайте выполним простое операцию запроса, где мы собираемся соответствовать первой группе 4
биты.
db.bittest.findOne({ metadata: { $bitsAnyClear: NumberInt(0b1111000000000000) } });
Это будет соответствовать вставленным документе выше, как третья группа биты
В Метаданные
Поле содержит два бита, установленные на 0
Отказ
$ bitsanyset.
Давайте выполним простой операцию запроса, где мы собираемся соответствовать третьей группе 4
биты.
db.bittest.findOne({ metadata: { $bitsAnySet: NumberInt(0b0000000000001111) } });
Это будет соответствовать вставленным документе выше, как третья группа биты
В Метаданные
Поле содержит два бита, установленные на 1
Отказ
Найдите записи с группой со всеми разрешениями
В этом запросе мы стремимся получить все записи, где Метаданные
поле содержит Групповой идентификатор
равно 5
и Все разрешения чтения читаются
Отказ
db.entries.find({ metadata: { $bitsAllSet: NumberInt(0b00000010100000000000000000000100), $bitsAllClear: NumberInt(0b00000001000000000000000000000000) } }).toArray();
Ценность 5
выражается как двоичное значение 0b101
Отказ Для правильного соответствия нам нужно комбинировать $ bitsallset
и $ bitsalleglear
операторы. $ bitsallset
Маска содержит Групповой идентификатор
Значение 5
выражается как 0b101
Отказ $ bitsalleglear
Содержит обратный шаблон бита 0b101
который является 0b010
Отказ Таким образом, мы обеспечиваем идеальное совпадение на бит-шаблон. Только использовать $ bitsallset
не проверил, был ли средний бит установлен на 1
или 0
Отказ Вот почему мы должны проверить как для битов, установленных на 1
а также биты, установленные на 0
Отказ Точно так же мы устанавливаем Все разрешения читают
Флаг к 1
, пока Написать и выполнить
Флаги устанавливаются на 0
Отказ Это означает, что мы будем соответствовать любому документу, где Все разрешения читают
Флаг устанавливается, и мы не заботимся о значении Написать и выполнить
Флаги.
Выполнение запроса
Давайте создадим простой класс, который упростит запрос данных из Метаданные
поле.
class QueryClass { constructor(collection) { this.collection = collection; this.allSetBits = 0b0000000000000000; this.allClearBits = 0b0000000000000000; } groupId(id) { this.allSetBits = this.allSetBits | parseInt(`0${createIntegerQueryMask(id, 8)}00000000000000000000000`, 2); this.allClearBits = this.allClearBits | parseInt(`0${createIntegerQueryMask(id, 8, true)}00000000000000000000000`, 2); return this; } userId(id) { this.allSetBits = this.allSetBits | parseInt(`000000000${createIntegerQueryMask(id, 14)}000000000`, 2); this.allClearBits = this.allClearBits | parseInt(`000000000${createIntegerQueryMask(id, 14, true)}000000000`, 2); return this; } groupPermissions(read, write, execute) { let [mask, reverseMask] = createPermissionsQueryMasks(read, write, execute); this.allSetBits = this.allSetBits | parseInt(`00000000000000000000000${mask}000000`, 2); this.allClearBits = this.allClearBits | parseInt(`00000000000000000000000${reverseMask}000000`, 2); return this; } userPermissions(read, write, execute) { let [mask, reverseMask] = createPermissionsQueryMasks(read, write, execute); this.allSetBits = this.allSetBits | parseInt(`00000000000000000000000000${mask}000`, 2); this.allClearBits = this.allClearBits | parseInt(`00000000000000000000000000${reverseMask}000`, 2); return this; } allPermissions(read, write, execute) { let [mask, reverseMask] = createPermissionsQueryMasks(read, write, execute); this.allSetBits = this.allSetBits | parseInt(`00000000000000000000000000000${mask}`, 2); this.allClearBits = this.allClearBits | parseInt(`00000000000000000000000000000${reverseMask}`, 2); return this; } find() { return this.collection.find({ metadata: { $bitsAllSet: NumberInt(this.allSetBits), $bitsAllClear: NumberInt(this.allClearBits) } }).toArray(); } }
QueryClass
Позволяет нам построить запрос, который выполняет равенство совпадений на двоичных упакованных полях в Метаданные
поле. Мы можем цеплять запрос, как это.
new QueryClass(db.entries) .groupId(5) .allPermissions(true) .find();
Это соответствует любому документу, где Метаданные
Упакованное поле ГРУПИД
равно 5
и Все
Разрешение читать
(игнорирование значений написать
и Execute
Флаги).
Давайте посмотрим, что GroupID (ID)
и AllPermissions (читать, писать, выполнить)
Методы делают, начиная с GroupID (ID)
один.
groupId(id) { this.allSetBits = this.allSetBits | parseInt(`0${createIntegerMask(id, 8)}00000000000000000000000`, 2); this.allClearBits = this.allClearBits | parseInt(`0${createIntegerMask(id, 8, true)}00000000000000000000000`, 2); return this; }
Мы знаем ГРУПИД
закодирован в [1: 9]
биты. Нам нужно создать две маски, $ bitsallset
Маска создана, вызывая createintegerquerymask
Метод, проходящий в ID
и длина ГРУПИД
поле, которое является 8
биты. $ BitAllcearear
Маска создана, вызывая createintegerquerymask
метод, но прохождение правдой после ID
И длина битов, рассказывая к способу отменить биты маски перед их возвратом.
createintegerquerymask.
Давайте посмотрим на что createintegerquerymask
делает.
function createIntegerQueryMask(id, bitResolution, reverse) { let bitsString = id.toString(2); if (bitsString.length > bitResolution) throw Error(`value id must be between 0 and ${Math.pow(2, bitResolution) - 1}`); // Reverse the bit String if (reverse) { let bitStringArray = []; for (let i = 0; i < bitsString.length; i++) { bitStringArray[i] = bitsString[i] == "0" ? "1" : "0"; } // Save the reverse string bitsString = bitStringArray.join(''); } // Ensure `bitResolution` bit string let missingValues = ""; for(let i = 0; i < (bitResolution - bitsString.length); i++) { missingValues = missingValues + (reverse ? "1" : "0"); } // Pad the bitString with 0s return missingValues + bitsString; }
createintegerquerymask
Метод будет генерировать Строка
длины BitResolution
это бинарный
Представление стоимости ID
Отказ Чтобы получить возвращенную строку точно BitResolution
Длинный этот метод прокладывает переднюю часть битовой строки с 0
s (или 1
s Если мы установили обратный параметр на true
) до длины конца не будет BitResolution
длинный.
Другими словами, говорят, что мы проходим в ID
Значение 1
с BitResolution
установить 4
Отказ Это будет превращено в следующие Строка
Представительство 0001
Отказ
Если мы установим параметр Обратный
к правда
Возвращенная битовая маска для ID
Значение будет изменено (это используется для обеспечения AllClarbits сопоставлять на биты в
ID Это ноль). Это означает, что если
ID установлен на
5 , с
BitResolution 4
Возвращенное значение для
ID В бит-маске будет
1010 Как 3 последних бита перевернуты, и мы подуваем
1 на фронт, чтобы убедиться, что мы совпадаем только на значениях, где биты перед
ID Значение установлено на
0 В
Метаданные поле.
groupId(id) { this.allSetBits = this.allSetBits | parseInt(`0${createIntegerMask(id, 8)}00000000000000000000000`, 2); this.allClearBits = this.allClearBits | parseInt(`0${createIntegerMask(id, 8, true)}00000000000000000000000`, 2); return this; }
После звонка createentegermask (ID, 8)
Мы вставляем соответствующую битовую маску в полный 32
Бита битовая маска на позициях [1: 9]
Отказ Затем мы разбираем битовую маску к целым числу и Или
с Это. Всеssetbits
Отказ Как мы помним Или
Установит любые биты на 1
В результате, когда битовая маска справа содержит 1
в данном положении бит. Это будет слияние
Две битовые маски, позволяющие соответствуют нескольким полям.
Затем мы сделаем то же самое для Это. ALLCEARBITS
Использование обратного ID
битовая маска.
CreatePermissionsQueryMasks
Теперь давайте посмотрим на то, как мы запрашиваем для отдельных флагов. Давайте посмотрим на Все разрешения (прочитать, писать, выполнить)
метод.
allPermissions(read, write, execute) { let [mask, reverseMask] = createPermissionsQueryMasks(read, write, execute); this.allSetBits = this.allSetBits | parseInt(`00000000000000000000000000000${mask}`, 2); this.allClearBits = this.allClearBits | parseInt(`00000000000000000000000000000${reverseMask}`, 2); return this; }
Так же, как для createintegerquerymask
Нам нужно создать $ bitsallset
и а $ bitsalleglear
Маска для нашего запроса, а затем Или
Каждая из масок с значениями экземпляра Это. Всеssetbits
и Это. ALLCEARBITS
Отказ Давайте посмотрим на что CreatePermissionsQuerymasks (прочитайте, пишите, выполнить)
делает.
function createPermissionsQueryMasks(read, write, execute) { // Create $bitsAllSet mask let mask = [ read == true ? "1" : "0", write == true ? "1" : "0", execute == true ? "1" : "0" ]; // Create $bitsAllClear mask let reverseMask = [ read == true || read == null ? "0" : "1", write == true || write == null ? "0" : "1", execute == true || execute == null ? "0" : "1" ] return [mask.join(''), reverseMask.join('')]; }
Для каждого из прошедшихся флагов читать
, Написать
или Выполнить
Мы проверяем, равно ли значение равно правдам и добавьте его значение для массива битов, представляющих флаги.
Если мы позвоним методом с правда, ложь, правда
Мы получаем Маска
[«1», «0», «1»]
Отказ
Далее мы создаем обратное маску одним небольшим исключением. Если любой из читать, писать или выполнить
Параметры устанавливаются на null
Мы устанавливаем соответствующую запись маски в 0
Как мы не хотим совпадать с этим значением флага. Наконец мы вернем массив Маска
и Реверсмасс
Отказ
Метод findone.
Давайте посмотрим на Findone
Метод реальный быстрый.
find() { return this.collection.find({ metadata: { $bitsAllSet: NumberInt(this.allSetBits), $bitsAllClear: NumberInt(this.allClearBits) } }).toArray(); }
Как мы можем видеть все, что он делает его, используя Collection.findone
Метод против Метаданные
поле, используя $ bitsallset
Оператор установлен на объединенную битую маску Это. Всеssetbits
а также оператор $ BitAllcearear
Оператор установлен на объединенную битую маску Это. ALLCEARBITS
Отказ
Настройка некоторых тестовых документов
Давайте вставьте некоторые тестовые документы, которые мы можем запросить.
db.entries.insertMany([{ _id: 1, name: "peter", path: "/home/user/rowan", parentPath: "/home/rowan", metadata: NumberInt(0b00000010100000000000001111111100) }, { _id: 2, name: "peter", path: "/home/user/paul", parentPath: "/home/paul", metadata: NumberInt(0b00111010100000000000001111111100) }])
Выполнение запроса
Наконец, запустите запрос, показанный ранее, где мы совпадаем против всех документов, где Группа
и то Все разрешение на чтение верно
Отказ
new QueryClass(db.entries) .groupId(5) .allPermissions(true) .find();
Как и ожидалось, мы извлекаем документ с помощью _id
Отказ Документ содержит следующие значения поля метаданных.
0 // [0:0] isDirectory = false 00000101 // [1:9] Group id = 5 00000000000001 // [10:23] User id = 1 111 // [24:26] Group r/w/x = true/true/true 111 // [27:29] User r/w/x = true/true/true 100 // [29:31] All r/w/x = true/false/false
Мы можем видеть, что Группа
И что читать
Флаг в Все разрешения
установлен на правда
также.
Мы видим, что управление упаковкой битов довольно сложна по сравнению с обычными значениями базы данных, но мы можем добиться значительных преимуществ экономии космического пространства при необходимости. Это сказал, что вы должны подумать дважды, используя его, если космические проблемы не являются основными проблемами для вас.