利用DeepSeek为luadbi-duckdb添加hugeint支持

luadbi-duckdb只提供了最多64位整数的处理。duckdb的hugeint用一个包含64位整数高位和64位无符号整数低位的结构表示。C API未提供把它转为十进制字符串的导出函数,虽然C++ API有ToString函数,但luadbi-duckdb只有C接口,只能自己实现。

typedef struct {
	uint64_t lower;
	int64_t upper;
} duckdb_hugeint;

一开始想得简单了,以为把高位和低位分别输出就行了,但高位并非存储除以10的n次幂的商,而存储除以2的64次幂的商,无法直接输出,不但是我,美团龙猫开始也是这么处理的,结果不对。

还有一个问题,符号,当高位upper是负数时,整个hugeint的值= upper * 2^64 + lower,而不是 -(abs(upper) * 2^64 + lower),其实从hugeint能表示的最小值-2^127也能推导出来,lower不为0时,后一个算式的绝对值大于2^127,超出范围了。龙猫一开始的实现就犯了这个错误。在我指出后,还是没改对。而且它包含了大整数加法、乘法等的实现,比较冗余。

有没有更简单的实现?
我在dev59网站看到这个帖子如何使用GCC打印__uint128_t类型的数字?,它利用了gcc的int128_t类型,但不是c标准。原程序是直接输出,我让DeepSeek改写成了返回C字符串。

#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>

/*
** Using documented GCC type unsigned __int128 instead of undocumented
** obsolescent typedef name __uint128_t.  Works with GCC 4.7.1 but not
** GCC 4.1.2 (but __uint128_t works with GCC 4.1.2) on Mac OS X 10.7.4.
*/
typedef unsigned __int128 uint128_t;

/*      UINT64_MAX 18446744073709551615ULL */
#define P10_UINT64 10000000000000000000ULL   /* 19 zeroes */
#define E10_UINT64 19

#define STRINGIZER(x)   # x
#define TO_STRING(x)    STRINGIZER(x)

// 递归地将uint128_t转换为字符串并拼接到缓冲区
static void u128_to_string_recursive(uint128_t u128, char* buffer, int* pos) {
    if (u128 > UINT64_MAX) {
        uint128_t leading = u128 / P10_UINT64;
        uint64_t trailing = u128 % P10_UINT64;
        u128_to_string_recursive(leading, buffer, pos);
        // 格式化尾部,确保有19位数字(前导零)
        *pos += sprintf(buffer + *pos, "%0" TO_STRING(E10_UINT64) PRIu64, trailing);
    } else {
        uint64_t u64 = u128;
        *pos += sprintf(buffer + *pos, "%" PRIu64, u64);
    }
}

// 将uint128_t转换为字符串
// 安全版本,使用预分配的缓冲区
// 返回实际使用的字符数(不包括结束符),如果缓冲区不足返回-1
int u128_to_string_safe(uint128_t u128, char* buffer, size_t buffer_size) {
    if (buffer == NULL || buffer_size < 2) {
        return -1;
    }
    
    // 使用临时缓冲区确保安全
    char temp[42];
    int pos = 0;
    u128_to_string_recursive(u128, temp, &pos);
    temp[pos] = '\0';
    
    if (pos + 1 > buffer_size) {
        return -1; // 缓冲区不足
    }
    
    strcpy(buffer, temp);
    return pos;
}
// 测试函数
int main(void) {
    uint128_t u128a = ((uint128_t)UINT64_MAX + 1) * 0x1234567890ABCDEFULL +
                      0xFEDCBA9876543210ULL;
    uint128_t u128b = ((uint128_t)UINT64_MAX + 1) * 0xF234567890ABCDEFULL +
                      0x1EDCBA987654320FULL;
    
    // 测试安全版本
    char buffer[50];
    int len = u128_to_string_safe(u128a, buffer, sizeof(buffer));
    if (len >= 0) {
        printf("Safe u128a: %s (length: %d)\n", buffer, len);
    } else {
        printf("Buffer too small for u128a\n");
    }
 
    return 0;
}

duckdb自己的hugeint.cpp中的toString函数如下,需要调用高精度除法函数。不如上述程序简单。

string Hugeint::ToString(hugeint_t input) {
	uint64_t remainder;
	string result;
	if (input == NumericLimits<hugeint_t>::Minimum()) {
		return string(Hugeint::HUGEINT_MINIMUM_STRING);
	}
	bool negative = input.upper < 0;
	if (negative) {
		NegateInPlace(input);
	}
	while (true) {
		if (!input.lower && !input.upper) {
			break;
		}
		input = Hugeint::DivModPositive(input, 10, remainder);
		result = string(1, UnsafeNumericCast<char>('0' + remainder)) + result; // NOLINT
	}
	if (result.empty()) {
		// value is zero
		return "0";
	}
	return negative ? "-" + result : result;
}

接下来只要修改luadbi-duckdb的statement.c,在statement_fetch_impl函数增加下列内容,同时把上述dev59的两个函数复制到文件中,statement_fetch_impl之前。

				case DUCKDB_TYPE_HUGEINT:
				case DUCKDB_TYPE_UHUGEINT: {
  					int64_t *res = duckdb_vector_get_data(col1);
duckdb_hugeint huge_val = *(duckdb_hugeint*)res;
uint128_t u128a = ((uint128_t)UINT64_MAX + 1) * huge_val.upper + huge_val.lower;
char a[100];
u128_to_string_safe(u128a, a, sizeof(a));
duckdb_string_t str= convert_to_duckdb_string(a);
				  	if (named_columns) {
						LDB_PUSH_ATTRIB_INT( res[statement->cur_row] );
					} else {
  						LDB_PUSH_ARRAY_INT(i + 1, res[statement->cur_row]);
					}
				}
					break;

重新编译打包rock文件安装。

root@66d4e20ec1d7:/par/luadbi# luarocks make luadbi-duckdb-scm-0.rockspec --pack-binary-rock DUCKDB_DIR=/par/141 DUCKDB_INCDIR=/par/141

Packed: /par/luadbi/luadbi-duckdb-scm-0.linux-aarch64.rock

root@66d4e20ec1d7:/par/luadbi# luarocks install luadbi-duckdb-scm-0.linux-aarch64.rock --force

luadbi-duckdb scm-0 is now installed in /usr/local (license: MIT/X11)

相应的lua脚本改为:

DBI = require "DBI"

dbd, err = DBI.Connect( 'DuckDB', 'lua_duckdb', 'dbuser', 'password' )
assert(dbd, err)

dbd:autocommit(true)
statement = dbd:prepare( "create or replace table table_1 as select -15::hugeint id, timestamp'2025-09-25 12:26:55.123456'column1, '-1234567890123456789012345'::hugeint  column2,date'2025-09-22' column3 ;" )
statement:execute()

statement = assert(dbd:prepare( "select * from table_1 where id = $1;" ))
statement:execute( -15 )

for row in statement:rows(true) do
         print(row['id'])
	 print(row['column1'])
	 print(row['column2'])
	 print(row['column3'])

end

执行结果如下

root@66d4e20ec1d7:/par/luadbi# lua ../duck.lua
-15
2025-09-25 12:26:55.123456
-1096246371337559929
2025-09-22
root@66d4e20ec1d7:/par/luadbi# ../duckdb141 lua_duckdb -c "from table_1"
┌────────┬────────────────────────────┬────────────────────────────┬────────────┐
│   id   │          column1           │          column2           │  column3   │
│ int128 │         timestamp          │           int128           │    date    │
├────────┼────────────────────────────┼────────────────────────────┼────────────┤
│  -15   │ 2025-09-25 12:26:55.123456 │ -1234567890123456789012345 │ 2025-09-22 │
└────────┴────────────────────────────┴────────────────────────────┴────────────┘

绝对值小的hugeint 列id正确读出了,column2大整数则不正确。告诉DeepSeek要处理负数。它给出的修改如下:

int int128_to_string_safe(int128_t i128, char* buffer, size_t buffer_size) {
    if (buffer == NULL || buffer_size < 2) {
        return -1;
    }
    
    // 处理负数
    int is_negative = 0;
    uint128_t u128;
    
    if (i128 < 0) {
        is_negative = 1;
        u128 = (uint128_t)(-i128); // 取绝对值
    } else {
        u128 = (uint128_t)i128;
    }
    
    // 使用临时缓冲区确保安全
    char temp[42]; // uint128_t最大39位数字 + 负号 + 结束符
    int pos = 0;
    
    // 如果有负号,先添加负号
    if (is_negative) {
        temp[pos++] = '-';
    }
    
    u128_to_string_recursive(u128, temp, &pos);
    temp[pos] = '\0';
    
    if (pos + 1 > buffer_size) {
        return -1; // 缓冲区不足
    }
    
    strcpy(buffer, temp);
    return pos;
}

再把上面

uint128_t u128a = ((uint128_t)UINT64_MAX + 1) * huge_val.upper + huge_val.lower;

改为

int128_t u128a = ((int128_t)UINT64_MAX + 1) * huge_val.upper + huge_val.lower;

大整数仍有问题,经过调试,最后发现我case DUCKDB_TYPE_HUGEINT:最后复制了INT处理的代码,应该改为如下:

  					if (named_columns) {
						LDB_PUSH_ATTRIB_STRING( str );
					} else {
  						LDB_PUSH_ARRAY_STRING(i + 1, str);
					}
				}
  					break;

现在正确了。

root@66d4e20ec1d7:/par/luadbi# lua ../duck.lua
-15
2025-09-25 12:26:55.123456
-1234567890123456789012345
2025-09-22

补记:
DuckDB C API有转字符串函数,但是即将废弃,另一个lua驱动luasql-duckdb就是采用的它。

DUCKDB_C_API char *duckdb_value_varchar(duckdb_result *result, idx_t col, idx_t row);
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值