Monthly Archives: Октябрь 2018

Пишем на C++ свою агрегационную функцию для HP Vertica

Предыстория

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

У каждого поставщика есть перечень товаров с указанием регионов поставки.
Есть таблица товаров, назовем ее product.
Регионов в Казахстане всего 17, поэтому я решил хранить данные о регионах поставки в одном числовом поле c помощью битов.

Таблица product выглядит следующим образом:

CREATE TABLE product (
	code VARCHAR(20),
	name VARCHAR(50),
	regions BIGINT,
	company_code VARCHAR(20),
	company_name VARCHAR(50)
);

code — код продукта;
name — наименование продукта;
regions — регионы поставки;
company_code — код компании;
company_name — наименование компании;

Теперь о битовом хранение регионов. Каждому региону соответствует свой бит:

Число

Битовое представление числа

Наименование региона

1

000000000000000001

По всей территории РК

2

000000000000000010

г. Астана

4

000000000000000100

г. Алматы

8

000000000000001000

Акмолинская область

16

000000000000010000

Актюбинская область

32

000000000000100000

Алматинская область

64

000000000001000000

Атырауская область

128

000000000010000000

Западно-Казахстанская область

256

000000000100000000

Жамбылская область

512

000000001000000000

Карагандинская область

1024

000000010000000000

Костанайская область

2048

000000100000000000

Кызылординская область

4096

000001000000000000

Мангистауская область

8192

000010000000000000

Туркестанская область

16384

000100000000000000

Павлодарская область

32768

001000000000000000

Северо-Казахстанская область

65536

010000000000000000

Восточно-Казахстанская область

131072

100000000000000000

г. Шымкент

Например, если нужно указать, что товар поставляется в три региона: г. Астана, г. Алматы и Карагандинскую область, то нужно применить бинарную операцию побитового «ИЛИ» к битам регионов, в итоге регионы закодируются в число 518:

ИЛИ

000000000000000010

2

000000000000000100

4

000000001000000000

512

Результат

000000001000000110

518

Заполним таблицу тестовыми данными:

INSERT INTO product VALUES ('001', 'Трансформатор тока', 6, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('001', 'Трансформатор тока', 2, 'xyz', 'Power Inc.');
INSERT INTO product VALUES ('002', 'Усилитель низкой частоты', 1, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('003', 'Набор резисторов', 20, 'abc', 'Energy Corp.');
INSERT INTO product VALUES ('003', 'Набор резисторов', 160, 'volt', 'Volt Group');
INSERT INTO product VALUES ('003', 'Набор резисторов', 36, 'xyz', 'Power Inc.');

Данные готовы, но проблема в том, что в Vertica нет подходящей агрегационной функции, позволяющей делать побитовое «ИЛИ» чисел. В Vertica есть функция bit_or, но по ряду причин она не подходит:

  1. Функция bit_or работает с бинарными данными
  2. Напрямую преобразовать число в бинарный вид нет возможности, только через hex
  3. Функция bit_or производит побитовые операции слева направо, поэтому если битов не хватает правая часть дополняется нулями, что дает не тот результат, который мы ожидаем.

Таким образом, мы пришли к необходимости создания своей функции.
Vertica позволяется писать свои функции на разных языках: C++, Java и Python.
Но именного агрегационные функции User Defined Aggregate Function (UDAF) можно почему-то писать только на С++.

В каталоге, где вы установили Vertica, есть SDK с примерами.
Например, в CentOS 7 это директория: /opt/vertica/sdk/examples

У вас должна быть установлена среда разработки g++.
Чтобы установить среду разработки g++, запустите:

yum install gcc gcc-c++ make

Если у вас Ubuntu, библиотеки могу собираться, но не работать выдавая ошибки вида:

Failure in UDx RPC call InvokeGetLibraryManifest()

Насколько я понял, это из-за версий gcc и g++, которые в Ubuntu по умолчанию 5.4.0, а в Centos 7 они по умолчанию 4.8.5.
Поэтому, если у вас возникли подобные проблемы, можно доставить нужные версии gcc и g++:

sudo apt-get install gcc-4.8
sudo apt-get install g++-4.8

Создайте файл OrSum.cpp со следующим содержимым:

 
#include "Vertica.h"
#include <time.h>
#include <sstream>
#include <iostream>
 
using namespace Vertica;
 
class OrSum : public AggregateFunction
{
 
public:
 
    virtual void initAggregate(ServerInterface &srvInterface,
                               IntermediateAggs &aggs)
    {
        vint &sum = aggs.getIntRef(0);
        sum = 0;
    }
 
    void aggregate(ServerInterface &srvInterface,
                   BlockReader &arg_reader,
                   IntermediateAggs &aggs)
    {
        vint &sum = aggs.getIntRef(0);
        do {
            const vint &val = arg_reader.getIntRef(0);
            if (val != Vertica::vint_null) {
                sum |= val;
            }
        } while (arg_reader.next());
    }
 
    virtual void combine(ServerInterface &srvInterface,
                         IntermediateAggs &aggs,
                         MultipleIntermediateAggs &aggs_other)
    {
        vint &sum = aggs.getIntRef(0);
 
        do {
            const vint &otherSum = aggs_other.getIntRef(0);
            if (otherSum != Vertica::vint_null) {
                sum |= otherSum;
            }
        } while (aggs_other.next());
    }
 
    virtual void terminate(ServerInterface &srvInterface,
                           BlockWriter &res_writer,
                           IntermediateAggs &aggs)
    {
        res_writer.setInt(aggs.getIntRef(0));
    }
 
    InlineAggregate()
};
 
class OrSumFactory : public AggregateFunctionFactory
{
    virtual void getPrototype(ServerInterface &srvInterface,
                              ColumnTypes &argTypes,
                              ColumnTypes &returnType)
    {
        argTypes.addInt();
        returnType.addInt();
    }
 
    virtual void getReturnType(ServerInterface &srvInterface,
                               const SizedColumnTypes &input_types,
                               SizedColumnTypes &output_types)
    {
        output_types.addInt();
    }
 
    virtual void getIntermediateTypes(ServerInterface &srvInterface,
                                      const SizedColumnTypes &input_types,
                                      SizedColumnTypes &intermediateTypeMetaData)
    {
        intermediateTypeMetaData.addInt();
    }
 
    virtual AggregateFunction *createAggregateFunction(ServerInterface &srvInterface)
    { return vt_createFuncObject<OrSum>(srvInterface.allocator); }
};
 
RegisterFactory(OrSumFactory);

Сборка библиотеки
В разработке…

Создание функции

CREATE OR REPLACE LIBRARY KESH_LIB AS '/opt/vertica/sdk/examples/build/KeshFunctions.so' LANGUAGE 'C++';
CREATE OR REPLACE AGGREGATE FUNCTION KESH_OR_SUM AS LANGUAGE 'C++' NAME 'KeshOrSumFactory' LIBRARY EPROC_LIB;

Использование функции

SELECT code, kesh_or_sum(regions) FROM product GROUP BY code

Удаление функции

DROP AGGREGATE FUNCTION KESH_OR_SUM(BIGINT);
DROP LIBRARY KESH_LIB;