libpq PostgreSQL: кодирование для двоичной передачи данных ARRAY []?

после часов документации/досок/списков рассылки и отсутствия прогресса я могу спросить вас: как мне «закодировать» мои данные, чтобы использовать их для двоичного транспорта с помощью PQexecParams(.) libpq?

Простые переменные просто в обратном порядке:

PGconn *conn;
PGresult *res;
char *paramValues[1];
int paramLengths[1];
int paramFormats[1];

conn = PQconnectdb(CONNINFO);

// -- (1) -- send a float value
float val_f = 0.12345678901234567890; // float precision: ~7 decimal digits
// alloc some memory & write float (in big endian) into
paramValues[0] = (char *) malloc(sizeof(val_f));
*((uint32_t*) paramValues[0]) = htobe32(*((uint32_t*) &val_f)); // host to big endian

paramLengths[0] = sizeof(val_f);
paramFormats[0] = 1; // binary

res = PQexecParams(conn, "SELECT $1::real ;", //
        1, // number parameters
        NULL, // let the backend deduce param type
        paramValues, //
        paramLengths, //
        paramFormats, //
        0); // return text
printf("sent float: %s \n", PQgetvalue(res, 0, 0));
// --> sent float: 0.123457

и вот так тоже double, int и т.д...

Но как насчет МАССИВОВ?

    float vals_f[] = {1.23, 9.87};
    // alloc some memory
    paramValues[0] = (char *) malloc(sizeof(float) * 2);

//  ???? paramValues[0] = ??????

    paramLengths[0] = sizeof(float) * 2;
    paramFormats[0] = 1; // binary


    res = PQexecParams(conn, "SELECT $1::real[] ;", //
            1, // number parameters
            NULL, // let the backend deduce param type
            paramValues, //
            paramLengths, //
            paramFormats, //
            0); // return text
    printf("sent float array: %s \n", PQgetvalue(res, 0, 0));

Есть ли рабочий пример передачи данных ARRAY в двоичном формате PostgreSQL? Код в backend/utils/adt/ мне мало помогает (за исключением того, что теперь я знаю, что есть ARRAYTYPE, но не знаю, как их использовать) :-(

Мне просто нужна функция char* to_PQbin(float [] input, int length) для перехода к paramValues[.]...

Большое спасибо, Тебас

PS: Каков предлагаемый способ преобразования простых переменных (а не моего htobe32(.))?


person Tebas    schedule 25.10.2010    source источник


Ответы (3)


Как уже упоминалось ccuter, вам необходимо создать собственный API. Следующий код извлекает одномерный массив int4, игнорируя любые значения NULL.

#define   INT4OID   23

/*! Structure of array header to determine array type */
struct array_int4 {
  int32_t ndim; /* Number of dimensions */
  int32_t _ign; /* offset for data, removed by libpq */
  Oid elemtype; /* type of element in the array */

  /* First dimension */
  int32_t size; /* Number of elements */
  int32_t index; /* Index of first element */
  int32_t first_value; /* Beginning of integer data */
};

static int extract_int4_array (char *raw_array, 
                               int32_t **values, 
                               int *num_values) {
  /* Array information header */
  struct array_int4 *array = (struct array_int4 *) raw_array; 
  /* Pointer to traverse int array */
  int32_t *p_value = &(array->first_value);
  /* int value in host byte order */
  int32_t hval;

  /* Check if we have a 1-dimensional INT4 array */
  if (ntohl(array->ndim) != 1 
  || ntohl(array->elemtype) != INT4OID) {
    return -1;
  }
  /* Number of elements including NULLs */
  int array_elements = ntohl (array->size);

  *num_values = 0;
  /* Get size of array */
  for (int i=0; i<array_elements; ++i) {
    /* Check size to see if this is a NULL value */
    hval = ntohl (*p_value);
    if (hval != -1) {
      ++p_value;
      (*num_values) += 1;
    } 

    ++p_value;
  }
  *values = malloc (*num_values * sizeof **values);

  /* Fill output int array. Skip every other value as it contains the size of 
   * the element */
  *num_values = 0; /* Use num_values as the index of the output array */
  p_value = &(array->first_value);
  for (int i=0; i<array_elements; ++i) {
    /* Check size to see if this is a NULL value */
    hval = ntohl (*p_value);
    if (hval != -1) {
      ++p_value;
  (*values)[*num_values] = ntohl (*p_value);
      (*num_values) += 1;
    } 

    ++p_value;
  }

  return 0;
}

Также существует библиотека с именем libpqtypes, которая помогает в таком преобразовании.

person Green    schedule 12.07.2011

http://git.postgresql.org/gitweb?p=postgresql.git;a=blob;f=src/include/utils/array.h;h=7f7e744cb12bc872f628f90dad99dfdf074eb314;hb=master описывает бинарный формат для массивов. При использовании libpq опустите часть vl_len_. Например, массив из 4 целых чисел будет выглядеть так:

0x00000001 0x00000000 0x00000017 0x00000004 0x00000001 0x00000004 0x00000004 0x00000004 0x00000004

Он имеет OID 1007 (INT4ARRAYOID). Первое целое число — это 1 измерение, второе целое число — это не растровое изображение NULL (поэтому ни одно из значений массива не равно NULL), третье целое число — это OID элементов (23, INT4OID), четвертое целое число — насколько велико первое измерение. (4) пятое целое число является начальным индексом первого измерения. После этого идут необработанные данные массива в последовательном порядке, каждый элемент имеет префикс своей длины (4 байта для каждого целого числа).

person ccutrer    schedule 28.01.2011
comment
Этот ответ отлично резюмирует структуру массива и был очень полезен! Я немного озадачен этими двумя утверждениями: [...] массив из 4 целых чисел [...] и [...] необработанные данные массива, каждый элемент имеет префикс своей длины (4 байта для каждого целого числа). В шестнадцатеричном дампе есть 4 целых числа со значением 4. Что-то здесь не так. Это должен быть либо размер массива 2, либо в шестнадцатеричном дампе должно быть 8 целых чисел, либо я получаю что-то ужасно неправильное. - person antipattern; 14.01.2019
comment
@антипаттерн, ты разобрался? ccutrer, не могли бы вы устранить несоответствие? - person DanielM; 05.03.2021
comment
Оглядываясь назад, я предполагаю, что это утверждение является ключевым: опустите часть vl_len_ - таким образом, никакой дополнительной длины, а только 4 целых числа со значением 4. Я полагаю, что использование числового значения 4 для значений данных здесь вводит в заблуждение; Так как можно было бы выбрать любое другое число, которое облегчило бы его различение. - person antipattern; 12.03.2021

Вот что мне удалось заставить работать в Node.js/TypeScript:

function writeInt4Array(buffer: Buffer, values: number[], offset: number): number {
  offset = buffer.writeInt32BE(1, offset) // Number of dimensions
  offset = buffer.writeInt32BE(0, offset) // Has nulls?
  offset = buffer.writeInt32BE(ObjectId.Int4, offset) // Element type
  offset = buffer.writeInt32BE(values.length, offset) // Size of first dimension
  offset = buffer.writeInt32BE(1, offset) // Offset (starting index) of first dimension
  for (const v of values) {
    offset = buffer.writeInt32BE(4, offset)
    offset = buffer.writeInt32BE(v, offset)
  }
  return offset
}
person DanielM    schedule 05.03.2021