PostgreSQL的libpq:用于二进制传输ARRAY[]数据的编码?

10

经过长时间的查阅文档、讨论区和邮件列表却毫无进展,我想问一下:如何使用 libpq 的 PQexecParams(.) 对我的数据进行编码以便进行二进制传输?

简单变量按大端序排列即可:

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));
有没有将数组数据以PostgreSQL二进制格式传输的工作示例? backend/utils/adt/中的代码对我不大有帮助(除了我现在知道有一个ARRAYTYPE,但不知道如何使用它):-(
我只需要一个函数char* to_PQbin(float [] input, int length), 以便传递给 paramValues[.] ...
谢谢, Tebas
附言:转换简单变量的建议方式是什么(而不是我的htobe32(.))?
3个回答

11
http://git.postgresql.org/gitweb?p=postgresql.git;a=blob;f=src/include/utils/array.h;h=7f7e744cb12bc872f628f90dad99dfdf074eb314;hb=master 介绍了Postgres的数组二进制格式。在使用libpq时,请省略vl_len_ 部分。例如,一个由4个整数组成的数组如下所示:

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

该数组有OID 1007 (INT4ARRAYOID)。第一个整数是1维,第二个整数是没有NULL位图(因此数组的值没有NULL),第三个整数是元素的OID(23,INT4OID),第四个整数是第一维的大小(4),第五个整数是第一维的起始索引。之后是原始数组数据,按顺序排列,每个元素的前缀为其长度(每个整数为4字节)。

1
这个回答很好地总结了数组结构,非常有帮助!但是我对这两个语句有点困惑:“[...]一个由4个整数组成的数组[...]”和“[...]原始数组数据,每个元素都以其长度为前缀(每个整数为4个字节)。”在十六进制转储中,有4个值为4的整数。这里有些奇怪。它应该是2个数组大小,或者在十六进制转储中应该有8个整数,否则我就搞错了什么。 - antipattern
@antipattern,你解决了吗?ccutrer,你能澄清一下这个不一致吗? - DanielM
回想起来,我认为这个语句是关键:“省略vl_len_部分”-因此,没有额外的长度,只有4个整数值为4。我建议在这里使用数字值4作为数据值是具有误导性的;因为可以选择任何其他数字,这将使其更容易辨别。 - antipattern

5
如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的库可以帮助进行这种转换。


0

这是我在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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接