← На главную

Расширения PostgreSQL: работа с массивами

На данный момент мы уже знакомы с написанием пользовательских функций для PostgreSQL, в том числе и полиморфных функций. Однако в PostgreSQL есть тип, заслуживающий особого внимания – это массивы. Давайте же разберемся, как работать с массивами в расширениях на языке C.

Основные свойства массивов в PostgreSQL следующие:

  • Массивы содержат в себе данные одного типа;
  • Массивы могут содержать значения NULL;
  • По умолчанию нумерация элементов начинается с единицы. Ее можно переопределить для заданного массива;
  • Поддерживаются многомерные массивы. Все под-массивы должны быть одинаковой длины;

Вот несколько примеров:

-- простой массив из целых чисел SELECT '{1,2,3}' :: INT[]; -- может содержать NULL'ы SELECT '{1,NULL,3}' :: INT[]; -- нумерация начиная с нуля SELECT '[0:2]={1,2,3}' :: INT[]; -- нумерация начиная с -99 SELECT '[-99:-97]={1,2,3}' :: INT[]; -- многомерный массив SELECT '{{1,2,3},{4,5,6}}' :: INT[]; -- многомерный массив с нумерацией начиная с нуля SELECT '[0:1][0:2]={{1,2,3},{4,5,6}}' :: INT[];

Код расширения должен корректно обрабатывать все перечисленное.

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

CREATE FUNCTION experiment_sum(arr INT[]) RETURNS INT

Вот ее код:

Datum experiment_sum(PG_FUNCTION_ARGS) { ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); int ndim = ARR_NDIM(array); Oid elmtyp = ARR_ELEMTYPE(array); TypeCacheEntry *typentry; Datum *elmvalues; bool *elmnulls; int i, sum, elmcount; if (ndim > 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("multi-dim arrays are not supported"))); /* Should never happen */ if(elmtyp != INT4OID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("only integer arrays are supported"))); /* Empty array? */ if (ndim < 1) PG_RETURN_INT32(0); typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; if (typentry == NULL) { typentry = lookup_type_cache(elmtyp, 0); fcinfo->flinfo->fn_extra = (void *) typentry; } deconstruct_array( array, elmtyp, typentry->typlen, typentry->typbyval, typentry->typalign, &elmvalues, &elmnulls, &elmcount ); sum = 0; for (i = 0; i < elmcount; i++) { if (!elmnulls[i]) sum += DatumGetInt32(elmvalues[i]); } pfree(elmvalues); pfree(elmnulls); PG_RETURN_INT32(sum); }

Работа с type cache на данном этапе нам уже знакома. В остальном же все довольно просто. С помощью макроса ARR_NDIM мы определяем размерность массива. Макрос ARR_ELEMTYPE позволяет узнать Oid типа элементов массива. Массив деконструируется при помощи deconstruct_array. Затем мы можем запросто посчитать сумму его элементов.

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

CREATE FUNCTION experiment_max(arr ANYARRAY) RETURNS ANYELEMENT

Реализация функции:

Datum experiment_max(PG_FUNCTION_ARGS) { Oid collation = PG_GET_COLLATION(); ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); int ndim = ARR_NDIM(array); Oid elmtyp = ARR_ELEMTYPE(array); TypeCacheEntry *typentry; Datum *elmvalues; bool *elmnulls; int i, elmcount; int32 cmp_result; bool resnull = true; Datum result = 0; if (ndim > 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("multi-dim arrays are not supported"))); /* Empty array? */ if (ndim < 1) PG_RETURN_NULL(); typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; if (typentry == NULL || typentry->type_id != elmtyp) { typentry = lookup_type_cache(elmtyp, TYPECACHE_CMP_PROC_FINFO); fcinfo->flinfo->fn_extra = (void *) typentry; } deconstruct_array( array, elmtyp, typentry->typlen, typentry->typbyval, typentry->typalign, &elmvalues, &elmnulls, &elmcount ); for (i = 0; i < elmcount; i++) { if (elmnulls[i]) continue; if(resnull) { result = elmvalues[i]; resnull = false; continue; } cmp_result = DatumGetInt32(FunctionCall2Coll( &typentry->cmp_proc_finfo, collation, result, elmvalues[i])); if(cmp_result < 0) result = elmvalues[i]; } pfree(elmvalues); pfree(elmnulls) if(resnull) PG_RETURN_NULL(); return result; }

Качественно эта функция не намного сложнее предыдущей. Основное отличие заключается в том, что здесь мы используем функцию сравнения из type cache, что делает experiment_max полиморфной. Это мы уже проходили.

С приемом массива в качестве аргумента все относительно ясно. Но что, если требуется не принять, а вернуть массив? Для примера рассмотрим функцию, возвращающую массив, где переданный аргумент повторяется count раз:

CREATE FUNCTION experiment_repeat(val ANYELEMENT, count INT) RETURNS ANYARRAY

Код функции:

Datum experiment_repeat(PG_FUNCTION_ARGS) { Datum value = PG_GETARG_DATUM(0); int32 count = PG_GETARG_INT32(1); Oid elmtyp = get_fn_expr_argtype(fcinfo->flinfo, 0); TypeCacheEntry *typentry; ArrayType* result; Datum *elems; int dims[1]; int lbs[1]; int i; if (count < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("count must not be negative"))); if (count == 0) { result = construct_empty_array(elmtyp); PG_RETURN_ARRAYTYPE_P(result); } typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; if (typentry == NULL || typentry->type_id != elmtyp) { typentry = lookup_type_cache(elmtyp, TYPECACHE_CMP_PROC_FINFO); fcinfo->flinfo->fn_extra = (void *) typentry; } elems = (Datum *) palloc(sizeof(Datum) * count); for (i = 0; i < count; i++) elems[i] = value; dims[0] = count; /* construct a 1-dimensional array */ lbs[0] = 1; /* array lower bounds */ result = construct_md_array(elems, NULL, 1, dims, lbs, elmtyp, typentry->typlen, typentry->typbyval, typentry->typalign); pfree(elems); PG_RETURN_ARRAYTYPE_P(result); }

Основными функциями здесь являются construct_empty_array, если мы хотим вернуть пустой массив, и construct_md_array, если хотим вернуть не пустой. В остальном же данный пример ничем не сложнее предыдущих.

Если вам нужны более интересные примеры, рекомендую обратить внимание на реализацию array_sort, array_reverse, а также array_sample и array_shuffle из кода ядра PostgreSQL. Все доступные функции и операторы, имеющие дело с массивами, перечислены в документации. Их код находится в array_userfuncs.c. Все функции и макросы для работы с массивами, которые можно вызывать из расширений, вы найдете в arrayfuncs.c и array.h.

Что же до полной версии исходников к посту, то она лежит на GitHub.