c - 如何用 C 处理 PostgreSQL 中的数字数据类型?

标签 c postgresql libpq

我尝试将通过 libpq 查询的数据以二进制格式转换为 C 中的 Arrow 格式。为此,我通过 PQftype() 查询相应列的数据类型 Oids 并将它们与 Arrow 数据类型进行匹配。但我不知道如何处理数字。

查询SELECT oid,typname FROM pg_type;返回数字1700,但如何获得精度和小数位数?

我无法在文档中找到有用的信息。我是不是找错地方了? https://www.postgresql.org/docs/13/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL

我可以从二进制数字中得到什么?

提前致谢:)

编辑

因此,在 Laurenz 的帮助下,我成功地组合了一个函数,该函数将二进制形式的数字从 libpq 转换为基本的字符串表示形式。当然,只有当您不打算将数字转换为字符串时,二进制结果才有意义,但它有助于理解数字的格式。

二进制形式基本上是具有不同含义的 2 字节整数的列表,并且数字整数只是以字符串形式连接起来。这是 49273.64,二进制和字段的精度为 20,小数位数为 2:

ndigits | 00000000 | 00000011 | weight | 00000000 | 00000001 | sign | 00000000 | 00000000 | dscale | 00000000 | 00000010 | digits | 00000000 | 00000100 | 00100100 | 00111001 | 00011001 | 00000000 
ndigits: 3, weight: 1, sign: 0, dscale: 2, digits: 4 | 9273 | 6400
char *getStrFromNumeric(u_int16_t *numvar){
    u_int16_t ndigits = ntohs(numvar[0]); // how many u_int16_t at numvar[4]
    int16_t dscale = ntohs(numvar[3]); // how many char digits after decimal point
    int16_t weight = ntohs(numvar[1])+1; // weight+1 is how many u_int16_t from numvar[4] are before decimal point. here weight already gets +1 at initialization.
    char *result = (char *)malloc(sizeof(char)*(weight+dscale)+1+1+2); // +1+1 -> '\0' and '.'
    char *copyStr = (char *) malloc(sizeof (char)*5);
    int strindex = 0;
    int numvarindex = 0;
    while(weight>0){
        sprintf(copyStr, "%d", ntohs(numvar[numvarindex+4]));
        sprintf(&(result[strindex]), "%s", copyStr);
        strindex += strlen(copyStr);
        numvarindex++;
        weight--;
    }
    sprintf(&(result[strindex]), ".");
    strindex++;
    while(dscale>0){
        sprintf(copyStr, "%d", ntohs(numvar[numvarindex+4]));
        dscale -= strlen(copyStr);
        sprintf(&(result[strindex]), "%s", copyStr);
        strindex += strlen(copyStr);
        numvarindex++;
    }
    sprintf(&(result[strindex]), "\0");
    return result;
}

最佳答案

您可以在src/backend/utils/adt/numeric.c中找到实现细节。 numeric 的小数位数和精度不存储在 pg_type 中,因为它们不是数据类型的一部分。相关属性是 pg_attribute 中的 atttypmod,因为小数位数和精度是列定义的一部分。

您可以通过以下函数收集类型修饰符中小数位数和精度的编码方式:

Datum
numerictypmodout(PG_FUNCTION_ARGS)
{
    int32       typmod = PG_GETARG_INT32(0);
    char       *res = (char *) palloc(64);

    if (typmod >= 0)
        snprintf(res, 64, "(%d,%d)",
                 ((typmod - VARHDRSZ) >> 16) & 0xffff,
                 (typmod - VARHDRSZ) & 0xffff);
    else
        *res = '\0';

    PG_RETURN_CSTRING(res);
}

因此,要获取表 t 的列 n 的精度和小数位数,您可以运行

SELECT (atttypmod - 4) >> 16 & 65535 AS precision,
       (atttypmod - 4) & 65535 AS scale
FROM pg_attribute
WHERE attrelid = 't'::regclass
  AND attname = 'n';

numeric 的二进制格式在函数 numeric_send 中定义:

Datum
numeric_send(PG_FUNCTION_ARGS)
{
    Numeric     num = PG_GETARG_NUMERIC(0);
    NumericVar  x;
    StringInfoData buf;
    int         i;

    init_var_from_num(num, &x);

    pq_begintypsend(&buf);

    pq_sendint16(&buf, x.ndigits);
    pq_sendint16(&buf, x.weight);
    pq_sendint16(&buf, x.sign);
    pq_sendint16(&buf, x.dscale);
    for (i = 0; i < x.ndigits; i++)
        pq_sendint16(&buf, x.digits[i]);

    PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}

NumericVar 的文档中描述了各个部分:

/* ----------
 * NumericVar is the format we use for arithmetic.  The digit-array part
 * is the same as the NumericData storage format, but the header is more
 * complex.
 *
 * The value represented by a NumericVar is determined by the sign, weight,
 * ndigits, and digits[] array.  If it is a "special" value (NaN or Inf)
 * then only the sign field matters; ndigits should be zero, and the weight
 * and dscale fields are ignored.
 *
 * Note: the first digit of a NumericVar's value is assumed to be multiplied
 * by NBASE ** weight.  Another way to say it is that there are weight+1
 * digits before the decimal point.  It is possible to have weight < 0.
 *
 * buf points at the physical start of the palloc'd digit buffer for the
 * NumericVar.  digits points at the first digit in actual use (the one
 * with the specified weight).  We normally leave an unused digit or two
 * (preset to zeroes) between buf and digits, so that there is room to store
 * a carry out of the top digit without reallocating space.  We just need to
 * decrement digits (and increment weight) to make room for the carry digit.
 * (There is no such extra space in a numeric value stored in the database,
 * only in a NumericVar in memory.)
 *
 * If buf is NULL then the digit buffer isn't actually palloc'd and should
 * not be freed --- see the constants below for an example.
 *
 * dscale, or display scale, is the nominal precision expressed as number
 * of digits after the decimal point (it must always be >= 0 at present).
 * dscale may be more than the number of physically stored fractional digits,
 * implying that we have suppressed storage of significant trailing zeroes.
 * It should never be less than the number of stored digits, since that would
 * imply hiding digits that are present.  NOTE that dscale is always expressed
 * in *decimal* digits, and so it may correspond to a fractional number of
 * base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
 *
 * rscale, or result scale, is the target precision for a computation.
 * Like dscale it is expressed as number of *decimal* digits after the decimal
 * point, and is always >= 0 at present.
 * Note that rscale is not stored in variables --- it's figured on-the-fly
 * from the dscales of the inputs.
 *
 * While we consistently use "weight" to refer to the base-NBASE weight of
 * a numeric value, it is convenient in some scale-related calculations to
 * make use of the base-10 weight (ie, the approximate log10 of the value).
 * To avoid confusion, such a decimal-units weight is called a "dweight".
 *
 * NB: All the variable-level functions are written in a style that makes it
 * possible to give one and the same variable as argument and destination.
 * This is feasible because the digit buffer is separate from the variable.
 * ----------
 */

为了方便处理数字的二进制表示,您应该使用 libpgtypes library .

关于c - 如何用 C 处理 PostgreSQL 中的数字数据类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72788883/

相关文章:

c - 如何列出当前目录下的所有.txt文件?

sql - Postgresql ORDER BY - 选择正确的索引

postgresql - 使用 CMake 安装 libpq 依赖项

python - 从 SQLAlchemy 插入到 Postgres 数据库

sql - 如果第 3 个表为空,则不返回结果,为什么?

c - libpq-fe.h 和 c 程序关闭

postgresql - PQinitOpenSSL 函数是否需要用 libpq 调用?

c - 关联缓存有 4 种方式。错误的标签、集合、单词?

C 文件程序不执行 fopen

C 线性搜索误差