Compare commits

..

21 Commits

Author SHA1 Message Date
3e2fa74004 !33 解决python3.9连接数据库挂掉问题
Merge pull request !33 from zcl/6.0.0-bug1
2024-09-26 02:08:38 +00:00
zcl
b9d27b0dbc 解决python3.9连接数据库挂掉问题 2024-09-25 17:31:03 +08:00
1f46b0a708 !31 Cherry pick !29 to branch 6.0.0
Merge pull request !31 from vimiix/yq/fix-IAIE7I
2024-09-25 09:08:22 +00:00
6eb67283df fix: use timedelta instead of time to cast TIME column in B compatibility mode(#IAIE7I)
In mysql compatibility mode, we should convert the TIME column to a python datetime.timedelta type,
just like the behavior of PyMySQL and mysql-connector-python.

See also:
- PyMySQL: 95635f587b/pymysql/converters.py (L344)
- mysql-connector-python: 59817f3de4/mysql-connector-python/lib/mysql/connector/conversion.py (L593)
2024-09-04 15:49:24 +08:00
544701b4d8 !30 解决当数据库后台查询不到uint1、uint2、uint4、uint8类型的oid导致驱动挂掉问题
Merge pull request !30 from zcl/r-branch-v6
2024-09-04 02:12:13 +00:00
zcl
48e24e8b76 解决当数据库后台查询不到uint1、uint2、uint4、uint8类型的oid导致驱动挂掉问题
(cherry picked from commit de9de1acc92ea861875c96367132651c8420e26e)

静态检查

codecheck2
2024-08-28 15:56:42 +08:00
77d04c9e82 !28 cherry-pick: illegal datetime values are returned as str(#I90GIP)
Merge pull request !28 from vimiix/cherry-pick-3d2f2c2
2024-08-20 10:50:53 +00:00
901b322819 feat: illegal datetime values are returned as str(#I90GIP) 2024-08-20 17:46:56 +08:00
6f0b5ba942 !26 更新社区包名称,明确区分各个系统和架构
Merge pull request !26 from zhangxubo/fixver
2024-08-09 06:41:47 +00:00
07fa912c18 更新包名 2024-08-05 19:50:10 +08:00
ad6abb83b9 !23 rework: 解决uint数据类型查询报错
Merge pull request !23 from zcl/rework#bug#1
2024-04-10 01:36:58 +00:00
zcl
f3a182e1bb 增加注释和修改函数名称
修改查询oid的值的sql语句
2024-03-27 16:12:51 +08:00
ab15cbffd5 !22 解决uint数据类型查询报错
Merge pull request !22 from zcl/bug#1
2024-03-27 02:34:04 +00:00
5235f6399c 解决uint数据查询报错
连接数据库后动态注册uint类型

无效修改和少提交的修改

注册uint函数封装后再加入到connection_setup函数里

函数名改成pq_get_custom_type_oid; 去掉SHOW前缀

oid改成unsigned int类型
2024-03-27 10:20:42 +08:00
a63f319414 !19 fix:convert empty string to NULL in A compatibility mode
Merge pull request !19 from vimiix/dev
2023-09-26 03:00:24 +00:00
12b2823c32 fix:convert empty string to NULL in A compatibility mode 2023-09-25 15:41:38 +08:00
a818ac9ae9 !20 修复若干malloc与free的空间问题
Merge pull request !20 from Luan-233/master
2023-09-22 07:01:14 +00:00
19d1f3a5ed 增加边界检查,防止编译时报warning 2023-09-21 20:40:29 -07:00
1306df3963 修复若干malloc与free的空间问题 2023-09-21 10:06:52 -07:00
d91399d1ae !17 修复批量操作接口参数的数据类型错误
Merge pull request !17 from vimiix/dev
2023-09-12 03:44:55 +00:00
372e59e53a fix(extra):object type error, change to only support string or str-able params 2023-09-11 16:32:08 +08:00
12 changed files with 241 additions and 32 deletions

View File

@ -48,6 +48,7 @@ else
echo "Kernel is $kernel"
exit 1
fi
os_version=$(cat /etc/os-release | grep -w VERSION_ID | awk -F '"' '{print $2}')
#######################################################################
## print help information
@ -62,8 +63,8 @@ function print_help()
}
##default install version storage path
declare db_name_for_package='openGauss'
declare version_number='5.0.0'
declare db_name_for_package='openGauss-Python'
declare version_number='6.0.0'
if [ $# = 0 ] ; then
echo "missing option"
@ -118,8 +119,8 @@ done
## declare all package name
#######################################################################
declare version_string="${db_name_for_package}-${version_number}"
declare package_pre_name="${version_string}-${dist_version}-${PLATFORM}"
declare python_package_name="${package_pre_name}-Python.${install_package_format}.gz"
declare package_pre_name="${version_string}-${dist_version}${os_version}-${PLATFORM}"
declare python_package_name="${package_pre_name}.${install_package_format}.gz"
declare BUILD_DIR="${LOCAL_DIR}/build"
declare MKGS_OK=0

View File

@ -1142,7 +1142,7 @@ def register_composite(name, conn_or_curs, globally=False, factory=None):
return caster
def _paginate(seq, page_size):
def _paginate(seq, page_size, to_byte=False):
"""Consume an iterable and return it in chunks.
Every chunk is at most `page_size`. Never return an empty chunk.
@ -1152,7 +1152,16 @@ def _paginate(seq, page_size):
while True:
try:
for i in range(page_size):
page.append(next(it))
if not to_byte:
page.append(next(it))
continue
vs = next(it)
if isinstance(vs, (list, tuple)):
# Ignore None object
# Serialized params to bytes
page.append(list(map(lambda v: v if v is None else str(v).encode('utf-8'), vs)))
else:
page.append(vs)
yield page
page = []
except StopIteration:
@ -1308,16 +1317,17 @@ def execute_prepared_batch(cur, prepared_statement_name, args_list, page_size=10
r"""
[openGauss libpq only]
Execute prepared statement with api `PQexecPreparedBatch` (new api in openGauss)
Execute prepared statement with api `PQexecPreparedBatch` (new api in openGauss's libpq.so)
Param:
argslist: 2d list, do nothing if empty
Arguments:
argslist: Two-dimensional list, if empty, return directly
Each parameter in the argument list must be a string or be string-able(should implements `__str__` magic method)
"""
if len(args_list) == 0:
return
nparams = len(args_list[0])
for page in _paginate(args_list, page_size=page_size):
for page in _paginate(args_list, page_size=page_size, to_byte=True):
cur.execute_prepared_batch(prepared_statement_name, nparams, len(page), page)
@ -1325,14 +1335,15 @@ def execute_params_batch(cur, sql_format, args_list, page_size=100):
r"""
[openGauss libpq only]
Execute sql with api `PQexecParamsBatch` (new api in openGauss)
Execute sql with api `PQexecParamsBatch` (new api in openGauss's libpq.so)
Arguments:
argslist: 2d list, do nothing if empty
argslist: Two-dimensional list, if empty, return directly
Each parameter in the argument list must be a string or be string-able(should implements `__str__` magic method)
"""
if len(args_list) == 0:
return
nparams = len(args_list[0])
for page in _paginate(args_list, page_size=page_size):
for page in _paginate(args_list, page_size=page_size, to_byte=True):
cur.execute_params_batch(sql_format, nparams, len(page), page)

View File

@ -227,7 +227,7 @@ PyObject *Bytes_Format(PyObject *format, PyObject *args, char place_holder) {
} /* '%' */
} /* until end */
if (dict) { // if args' type is dict, the func ends
if (dict || (arglen < 0) || (argidx < 0)) { // args' type is dict, the func ends
if (args_owned) Py_DECREF(args);
if (!(result = resize_bytes(result, reslen - rescnt))) return NULL; // resize and return
if (place_holder != '%') {
@ -238,8 +238,8 @@ PyObject *Bytes_Format(PyObject *format, PyObject *args, char place_holder) {
}
args_list = (char **)malloc(sizeof(char *) * arglen); // buffer
memset(args_list, NULL, sizeof(char *) * arglen);
args_len = (Py_ssize_t *)malloc(sizeof(Py_ssize_t *) * arglen); // length of every argument
memset(args_list, 0, sizeof(char *) * arglen);
args_len = (Py_ssize_t *)malloc(sizeof(Py_ssize_t) * arglen); // length of every argument
while ((args_value = getnextarg(args, arglen, &argidx)) != NULL) { // stop when receive NULL
Py_ssize_t length = 0;
if (!Bytes_CheckExact(args_value)) {
@ -251,14 +251,14 @@ PyObject *Bytes_Format(PyObject *format, PyObject *args, char place_holder) {
length = Bytes_GET_SIZE(args_value);
// printf("type: %s, len: %d, value: %s\n", Py_TYPE(args_value)->tp_name, length, args_buffer);
args_len[argidx - 1] = length;
args_list[argidx - 1] = (char *)malloc(sizeof(char *) * (length + 1));
args_list[argidx - 1] = (char *)malloc(sizeof(char) * (length + 1));
Py_MEMCPY(args_list[argidx - 1], args_buffer, length);
args_list[argidx - 1][length] = '\0';
Py_XDECREF(args_value);
}
arg_usecnt = (int *)malloc(sizeof(int) * arglen);
memset(arg_usecnt, 0, sizeof(char *) * arglen);
memset(arg_usecnt, 0, sizeof(int) * arglen);
fmt = Bytes_AS_STRING(format); // get pointer of format
fmtcnt = Bytes_GET_SIZE(format); // get length of format

View File

@ -46,6 +46,13 @@ extern "C" {
#define STATE_ON 1
#define STATE_DEFAULT 2
/* sql_compatibility values */
#define SQL_COMPATIBILITY_A 1
#define SQL_COMPATIBILITY_OTHER 5
#define SQL_COMPATIBILITY_B 2
// #define SQL_COMPATIBILITY_C 3
// #define SQL_COMPATIBILITY_PG 4
/* connection status */
#define CONN_STATUS_SETUP 0
#define CONN_STATUS_READY 1
@ -148,6 +155,8 @@ struct connectionObject {
/* inside a with block */
int entered;
int sql_compatibility;
};
/* map isolation level values into a numeric const */

View File

@ -37,6 +37,8 @@
#include <string.h>
#include <ctype.h>
#include "psycopg/typecast.h"
#include "psycopg/typecast_basic.c"
extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
@ -1268,6 +1270,9 @@ static struct PyMemberDef connectionObject_members[] = {
{"server_version", T_INT,
offsetof(connectionObject, server_version), READONLY,
"Server version."},
{"sql_compatibility", T_INT,
offsetof(connectionObject, sql_compatibility), READONLY,
"Server sql_compatibility param value."},
{NULL}
};
@ -1311,12 +1316,79 @@ static struct PyGetSetDef connectionObject_getsets[] = {
};
#undef EXCEPTION_GETTER
/* register the uint typecasters */
static int
register_type_uint(connectionObject *self, PyThreadState **tstate)
{
int rv = -1;
typecastObject *obj = NULL;
PyObject *name = NULL, *values = NULL;
Py_ssize_t i, len = 0;
char* uint_arr[] = {"\'uint1\'", "\'uint2\'", "\'uint4\'", "\'uint8\'"};
int size = sizeof(uint_arr) / sizeof(uint_arr[0]);
long int* _typecast_INTEGER_types;
_typecast_INTEGER_types = (long int*)malloc((size+1)*sizeof(long int));
for (int i=0; i< size; i++) {
unsigned int uint_val = pq_get_pg_catalog_custom_type_oid(self, uint_arr[i], tstate);
_typecast_INTEGER_types[i] = (long int)uint_val;
}
typecastObject_initlist _typecast_builtins[] = {
{"INTEGER", _typecast_INTEGER_types, typecast_INTEGER_cast, NULL},
};
name = Text_FromUTF8(_typecast_builtins->name);
if (!name) goto end;
while (_typecast_builtins->values[len] != 0) len++;
values = PyTuple_New(len);
if (!values) goto end;
for (i = 0; i < len ; i++) {
PyTuple_SET_ITEM(values, i, PyInt_FromLong(_typecast_builtins->values[i]));
}
obj = (typecastObject *)typecast_new(name, values, NULL, NULL);
if (obj) {
obj->ccast = _typecast_builtins->cast;
obj->pcast = NULL;
}
if (typecast_add((PyObject *)obj, self->string_types, 0) < 0) { goto end; }
rv = 0;
end:
free(_typecast_INTEGER_types);
Py_XDECREF(values);
Py_XDECREF(name);
return rv;
}
static int set_sql_compatibility(connectionObject *self, char *value)
{
switch (value[0]) {
case 'A':
case 'a':
self->sql_compatibility = SQL_COMPATIBILITY_A;
break;
case 'B':
case 'b':
self->sql_compatibility = SQL_COMPATIBILITY_B;
break;
default:
self->sql_compatibility = SQL_COMPATIBILITY_OTHER;
break;
}
return 0;
}
/* initialization and finalization methods */
static int
connection_setup(connectionObject *self, const char *dsn, long int async)
{
int rv = -1;
char *sql_compatibility_value = NULL;
Dprintf("connection_setup: init connection object at %p, "
"async %ld, refcnt = " FORMAT_CODE_PY_SSIZE_T,
@ -1334,6 +1406,7 @@ connection_setup(connectionObject *self, const char *dsn, long int async)
self->isolevel = ISOLATION_LEVEL_DEFAULT;
self->readonly = STATE_DEFAULT;
self->deferrable = STATE_DEFAULT;
self->sql_compatibility = SQL_COMPATIBILITY_A;
#ifdef CONN_CHECK_PID
self->procpid = getpid();
#endif
@ -1355,8 +1428,30 @@ connection_setup(connectionObject *self, const char *dsn, long int async)
Dprintf("connection_setup: good connection object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self));
PyThreadState *tstate = (PyThreadState *)malloc(sizeof(PyThreadState));
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
sql_compatibility_value = pq_get_guc_locked(self, "sql_compatibility", &_save);
set_sql_compatibility(self, sql_compatibility_value);
memcpy(tstate, _save, sizeof(_save));
pthread_mutex_unlock(&self->lock);
Py_END_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
if (register_type_uint(self, &tstate)) {
goto exit;
}
if (tstate) {
free(tstate);
tstate = NULL;
}
pthread_mutex_unlock(&self->lock);
exit:
if (sql_compatibility_value){
free(sql_compatibility_value);
}
return rv;
}

View File

@ -57,6 +57,16 @@ curs_get_cast(cursorObject *self, PyObject *oid)
if (cast) { return cast; }
/* global lookup */
if (self->conn->sql_compatibility == SQL_COMPATIBILITY_B) {
/*
In mysql compatibility mode, we should convert the TIME column to a python datetime.timedelta type,
just like the behavior of PyMySQL and mysql-connector-python.
*/
if (PyLong_Check(oid)) {
long value = convert_time_oid_to_interval_oid(PyLong_AsLong(oid));
oid = PyLong_FromLong(value);
}
}
cast = PyDict_GetItem(psyco_types, oid);
Dprintf("curs_get_cast: global dict: %p", cast);
if (cast) { return cast; }

View File

@ -628,7 +628,7 @@ curs_execute_prepared_batch(cursorObject *self, PyObject *args)
int nParams = 0, nBatch = 0;
PyObject *argsList = NULL;
Py_ssize_t rowIdx, colIdx, total;
int rowIdx, colIdx, total;
char **paramValues = NULL;
PGresult *res = NULL;
@ -642,7 +642,7 @@ curs_execute_prepared_batch(cursorObject *self, PyObject *args)
}
Dprintf("execute_prepared_batch parsed statement_name: %s, nParams: %d, nBatch: %d",
stmtName, nParams, nBatch);
total = nBatch*nParams;
total = nBatch * nParams;
EXC_IF_CURS_CLOSED(self);
EXC_IF_CURS_ASYNC(self, execute_prepared_batch);
@ -679,11 +679,17 @@ curs_execute_prepared_batch(cursorObject *self, PyObject *args)
PyObject *argItem = PySequence_GetItem(rowArgs, colIdx);
if (argItem == Py_None) {
paramValues[rowIdx*nParams+colIdx] = "NULL";
paramValues[rowIdx * nParams + colIdx] = NULL;
} else {
PyObject *t = microprotocol_getquoted(argItem, self->conn);
paramValues[rowIdx*nParams+colIdx] = strdup(Bytes_AsString(t));
Py_XDECREF(t);
if (!(argItem = psyco_ensure_bytes(argItem))) {
goto exit;
}
// convert empty string to NULL in A compatibility mode
if (self->conn->sql_compatibility == SQL_COMPATIBILITY_A && PyObject_Length(argItem) == 0) {
paramValues[rowIdx * nParams + colIdx] = NULL;
} else {
paramValues[rowIdx * nParams + colIdx] = Bytes_AsString(argItem);
}
}
Py_XDECREF(argItem);
}
@ -715,7 +721,7 @@ curs_execute_params_batch(cursorObject *self, PyObject *args)
int nParams = 0, nBatch = 0;
PyObject *argsList = NULL;
Py_ssize_t rowIdx, colIdx, total;
int rowIdx, colIdx, total;
char **paramValues = NULL;
PGresult *res = NULL;
@ -729,7 +735,7 @@ curs_execute_params_batch(cursorObject *self, PyObject *args)
Dprintf("execute_params_batch parsed sql: %s, nParams: %d, nBatch: %d",
sql, nParams, nBatch);
total = nBatch*nParams;
total = nBatch * nParams;
EXC_IF_CURS_CLOSED(self);
EXC_IF_CURS_ASYNC(self, execute_params_batch);
@ -765,11 +771,17 @@ curs_execute_params_batch(cursorObject *self, PyObject *args)
PyObject *argItem = PySequence_GetItem(rowArgs, colIdx);
if (argItem == Py_None) {
paramValues[rowIdx*nParams+colIdx] = "NULL";
paramValues[rowIdx * nParams + colIdx] = NULL;
} else {
PyObject *t = microprotocol_getquoted(argItem, self->conn);
paramValues[rowIdx*nParams+colIdx] = strdup(Bytes_AsString(t));
Py_XDECREF(t);
if (!(argItem = psyco_ensure_bytes(argItem))) {
goto exit;
}
// convert empty string to NULL in A compatibility mode
if (self->conn->sql_compatibility == SQL_COMPATIBILITY_A && PyObject_Length(argItem) == 0) {
paramValues[rowIdx * nParams + colIdx] = NULL;
} else {
paramValues[rowIdx * nParams + colIdx] = Bytes_AsString(argItem);
}
}
Py_XDECREF(argItem);
}

View File

@ -43,7 +43,7 @@
#include "psycopg/pgtypes.h"
#include "psycopg/error.h"
#include "psycopg/column.h"
#include <stdlib.h>
#include "psycopg/libpq_support.h"
#include "libpq-fe.h"
@ -607,6 +607,54 @@ cleanup:
return rv;
}
/* Get the oid of uint type created by the dolphin plugin, which is only in the pg_catalog by default */
unsigned int
pq_get_pg_catalog_custom_type_oid(connectionObject *conn, const char *param, PyThreadState **tstate)
{
char query[256];
int size;
unsigned int rv = 0;
char *temp_oid = NULL;
size = PyOS_snprintf(query, sizeof(query), "select oid from pg_type where typnamespace = 11 and typname= %s", param);
if (size < 0 || (size_t)size >= sizeof(query)) {
conn_set_error(conn, "query too large");
goto cleanup;
}
if (!psyco_green()) {
conn_set_result(conn, PQexec(conn->pgconn, query));
} else {
PyEval_RestoreThread(*tstate);
conn_set_result(conn, psyco_exec_green(conn, query));
*tstate = PyEval_SaveThread();
}
if (!conn->pgres) {
Dprintf("pq_get_pg_catalog_custom_type_oid: PQexec returned NULL");
PyEval_RestoreThread(*tstate);
if (!PyErr_Occurred()) {
conn_set_error(conn, PQerrorMessage(conn->pgconn));
}
*tstate = PyEval_SaveThread();
goto cleanup;
}
if (PQresultStatus(conn->pgres) != PGRES_TUPLES_OK) {
Dprintf("pq_get_pg_catalog_custom_type_oid: result was not TUPLES_OK (%s)",
PQresStatus(PQresultStatus(conn->pgres)));
goto cleanup;
}
temp_oid = PQgetvalue(conn->pgres, 0, 0);
if (temp_oid) {
rv = (unsigned int)atoi(temp_oid);
}
CLEARPGRES(conn->pgres);
cleanup:
return rv;
}
/* Set a session parameter.
*
* The function should be called on a locked connection without

View File

@ -459,7 +459,7 @@ PyTypeObject typecastType = {
0, /*tp_new*/
};
static PyObject *
PyObject *
typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base)
{
typecastObject *obj;

View File

@ -88,4 +88,7 @@ HIDDEN PyObject *typecast_array_from_python(
HIDDEN PyObject *typecast_cast(
PyObject *self, const char *str, Py_ssize_t len, PyObject *curs);
HIDDEN long convert_time_oid_to_interval_oid(long value);
#endif /* !defined(PSYCOPG_TYPECAST_H) */
PyObject *typecast_new(PyObject *name, PyObject *values, PyObject *cast, PyObject *base);

View File

@ -31,6 +31,21 @@ static long int typecast_MACADDRARRAY_types[] = {1040, 0};
static long int typecast_UNKNOWN_types[] = {705, 0};
long convert_time_oid_to_interval_oid(long value)
{
int i = 0;
while (typecast_TIME_types[i] != 0) {
if (typecast_TIME_types[i] == value) {
Dprintf("convert time oid to interval oid");
value = typecast_INTERVAL_types[0];
break;
}
i++;
}
return value;
}
static typecastObject_initlist typecast_builtins[] = {
{"NUMBER", typecast_NUMBER_types, typecast_NUMBER_cast, NULL},
{"LONGINTEGER", typecast_LONGINTEGER_types, typecast_LONGINTEGER_cast, NULL},

View File

@ -203,6 +203,11 @@ _parse_noninftz(const char *str, Py_ssize_t len, PyObject *curs)
rv = PyObject_CallFunction(
(PyObject*)PyDateTimeAPI->DateTimeType, "iiiiiiiO",
y, m, d, hh, mm, ss, us, tzinfo);
if (rv == NULL) {
/* illegal values are returned as str */
PyErr_Clear();
rv = PyUnicode_FromString(str);
}
exit:
Py_XDECREF(tzoff);