Проектирование Документо-Ориентированного Api В Postgresql: Найдите То, Что Ищете (Часть 3)

В первой и второй частях этой короткой серии статей я показал различные способы сохранение документа и последующие обновление поля поиска .

Кроме того, я показал, как сохранять множество документов транзакционно.

В этой статье я рассмотрю варианты выполнения запросов.



Лучший способ поиска документов

В первой части мы создали таблицу, которая выглядит следующим образом:
  
  
  
  
  
  
  
  
   

create table my_docs( id serial primary key, body jsonb not null, search tsvector, created_at timestamptz not null default now(), updated_at timestamptz not null default now() )

Поскольку у нас есть контроль над тем, как хранятся данные, мы можем написать собственные функции для получения этих данных разными интересными способами! Вся тяжелая работа окончена (сохранение, обновление и т. д.) — теперь давайте немного повеселимся.



Получение документа по идентификатору

В каждом документе есть поле идентификатор связан с ним полностью, благодаря функции сохранить_документ .

Это по-прежнему Postgres, поэтому каждому полю нужен первичный ключ, и мы помещаем его внутрь самого документа.

Я сделал свое целое число, но вы также можете использовать bigint Твиттер снежинка , если хотите.

Сейчас мы будем использовать тип сериал .

Функция для этого довольно проста:

create function find_document(tbl varchar, id int, out jsonb) as $$ //find by the id of the row var result = plv8.execute("select * from " + tbl + " where id=$1;",id); return result[0] ? result[0].

body : null; $$ language plv8; select * from find_document('customers',20);

Это самая простая из возможных функций — она берет имя таблицы и идентификатор, который необходимо найти, и выполняет самый быстрый запрос из всех (что нам очень нравится!): поиск по первичному ключу .

Скорость: нам нравится .

Теперь давайте создадим его для массового запроса.

Для этого я хочу ввести некоторый критерий и вернуть первое совпадение.

Это будет работать правильно, только если я отсортирую результат, поэтому добавляю СОРТИРОВАТЬ ПО и я указываю для него ID в качестве параметра по умолчанию:

create function find_document( tbl varchar, criteria varchar, orderby varchar default 'id' ) returns jsonb as $$ var valid = JSON.parse(criteria); //this will throw if it invalid var results = plv8.execute("select body from " + tbl + " where body @> $1 order by body ->> '" + orderby + "' limit 1;",criteria); return results[0] ? results[0].

body : null $$ language plv8; select * from find_document('customers','{"last": "Conery"}', 'first');

Более того, мы можем ожидать странного поведения в зависимости от используемого нами драйвера.

Первое, что хотелось бы отметить, это то, что я перегружаю функцию найти_документ потому что Postgres позволяет мне это сделать.

Это, в свою очередь, означает, что единственная разница между нашей исходной функцией, которая выполняет поиск по идентификатору, и этой функцией — это список аргументов.

Это не проблема для драйвера Npgsql. Для драйвера node_pg это еще одна проблема.

Поскольку я устанавливаю значение по умолчанию для параметра Сортировать по , возникает некоторая путаница при выборе функции для запуска.

Насколько я могу судить, драйвер node_pg не заботится о типе аргументов функции.

но только по их количеству .

Таким образом, если мы попытаемся запустить упомянутую выше функцию «поиск по идентификатору», то сработает наша вторая функция.

Еще раз: Npgsql (драйвер .

NET) не имеет этой проблемы.

Поэтому если у вас возникли проблемы, просто переименуйте одну из функций или удалите значение параметра по умолчанию.

Еще я хотел бы отметить, что я установил параметр критерии тип варчар / Это сделано, хоть и технически не правильно, но делает API немного лучше.

Если бы я установил его как jsonb, запрос должен был бы быть выполнен в следующем виде:

select * from find_document('customers','{"last": "Conery"}'::jsonb, 'first');

Разница невелика, поскольку API мы будем использовать преимущественно из кода (о котором речь пойдет в следующем посте).



Фильтрация

Давайте сделаем то же самое еще раз, только для нескольких возвращаемых документов:

create function filter_documents( tbl varchar, criteria varchar, orderby varchar default 'id' ) returns setof jsonb as $$ var valid = JSON.parse(criteria);//this will throw if it invalid var results = plv8.execute("select body from " + tbl + " where body @> $1 order by body ->> '" + orderby + "'",criteria); var out = []; for(var i = 0;i < results.length; i++){ out.push(results[i].

body); } return out; $$ language plv8; select * from find_document('customer_docs','{"last": "Conery"}');

Это уже интереснее.

Результат, который я получаю, набор jsonb , это означает, что мне нужно вернуть некоторое количество строк jsonb .

Не совсем понятно, как это сделать с помощью PLV8, и может быть есть способ лучше моего — но я уверен, что именно этот сработает. Как только я получу результат (строки из нашей документо-ориентированной таблицы), мне нужно запустить цикл, который возьмет и вставит тело jsonb поля в массив, который я затем верну.

Все это работает, потому что поле body — это jsonb, которое, в свою очередь, является текстом.

Это не объект javascript, потому что если бы это было так, я бы получил ошибку (старая глупость анализа [объекта объекта]).



SQL-инъекция

Многие из вас заметят, что параметр Сортировать по здесь он объединяется непосредственно внутри.

Если вы разрешаете пользователям писать SQL в вашу базу данных, то да, это проблема.

Но, будем надеяться, вы выполните эту функцию из драйвера, который параметризует ваши запросы, примерно так:

db.filter("customers", { last : "Conery", orderBy : "a';DROP TABLE test; SELECT * FROM users WHERE 't' = 't" }, function(err, res){ console.log(err); console.log(res); });

… не будет работать.

Почему нет? Потому что в идеале вы делаете что-то вроде этого:

select * from filter_documents($1, $2, $3);

Если нет, то вы получите по заслугам :).



Полнотекстовый поиск

Закончим, как и положено, полнотекстовым поиском наших документов.

Это моя любимая часть:

create function search_documents(tbl varchar, query varchar) returns setof jsonb as $$ var sql = "select body, ts_rank_cd(search,to_tsquery($1)) as rank from " + tbl + " where search @@ to_tsquery($1) " + " order by rank desc;" var results = plv8.execute(sql,query); var out = []; for(var i = 0; i < results.length; i++){ out.push(results[i].

body); } return out; $$ language plv8; select * from search_documents('customers', 'jolene');

Это все довольно просто, если вы знаете, как работает индексация полнотекстового поиска в Postgres. Здесь мы просто работаем с поиск поле (которое индексируется индексом GIN для скорости), которое мы обновляем при каждом сохранении.

Этот запрос выполняется молниеносно и очень прост в использовании.



Делаем индексы более гибкими

В двух функциях, принимающих критерий (поиск и фильтр), я использую оператор «содержание» .

Это маленький символ @> .

Этот оператор специфичен для jsonb и позволяет нам использовать индекс GIN в поле тело .

Этот индекс выглядит следующим образом:

create index idx_customers on customers using GIN(body jsonb_path_ops);

Что придает здесь особую пикантность, так это jsonb_path_ops .

Он сообщает индексатору оптимизировать операции с «контентом».

jsonb (на самом деле: этот фрагмент jsonb содержится в другом фрагменте jsonb).

Это означает, что индекс работает быстрее и меньше.

Теперь я мог бы дать ссылку на кучу источников и статей о том, как PostgreSQL превосходит MongoDB и другие, когда дело доходит до записи/чтения.

Но это заблуждение.



Скорость чтения и записи

Если вы возьмете один сервер PostgreSQL против одного сервера MongoDB, MongoDB будет выглядеть гораздо глупее, а Postgres будет курить практически по всем показателям.

Это связано с тем, что Postgres был разработан как масштабируемая база данных.

Если оптимизировать MongoDB и добавить серверы для распределения нагрузки, цифры станут ближе друг к другу, но вдобавок вам придется иметь дело с горизонтальной системой, которая может вести себя так, как ты от нее не ожидаешь .

Это все очень спорно, конечно, но следует отметить следующее:

  • Индексация в Postgres происходит медленно.

    Поэтому, если это снижает производительность записи, вам следует установить индекс только для того, что вы хотите индексировать (указав (тело -> мое_поле) ) при указании индекса GIN

  • Если вы часто обращаетесь к чему-то (например, к адресу электронной почты), просто скопируйте это в отдельный столбец и сделайте УНИКАЛЬНЫЙ Синхронизацию можно выполнить в коде или в отдельной функции.

В следующей статье я расскажу о способах вызова этой штуки из кода! Теги: #postgresql #JavaScript #plv8 #JavaScript #postgresql #sql
Вместе с данным постом часто просматривают: