mirror of
https://github.com/python/cpython.git
synced 2025-10-17 12:18:23 +00:00
gh-79097: Add support for aggregate window functions in sqlite3 (GH-20903)
This commit is contained in:
parent
f45aa8f304
commit
9ebcece82f
10 changed files with 477 additions and 13 deletions
53
Modules/_sqlite/clinic/connection.c.h
generated
53
Modules/_sqlite/clinic/connection.c.h
generated
|
@ -235,6 +235,53 @@ exit:
|
|||
return return_value;
|
||||
}
|
||||
|
||||
#if defined(HAVE_WINDOW_FUNCTIONS)
|
||||
|
||||
PyDoc_STRVAR(create_window_function__doc__,
|
||||
"create_window_function($self, name, num_params, aggregate_class, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Creates or redefines an aggregate window function. Non-standard.\n"
|
||||
"\n"
|
||||
" name\n"
|
||||
" The name of the SQL aggregate window function to be created or\n"
|
||||
" redefined.\n"
|
||||
" num_params\n"
|
||||
" The number of arguments the step and inverse methods takes.\n"
|
||||
" aggregate_class\n"
|
||||
" A class with step(), finalize(), value(), and inverse() methods.\n"
|
||||
" Set to None to clear the window function.");
|
||||
|
||||
#define CREATE_WINDOW_FUNCTION_METHODDEF \
|
||||
{"create_window_function", (PyCFunction)(void(*)(void))create_window_function, METH_METHOD|METH_FASTCALL|METH_KEYWORDS, create_window_function__doc__},
|
||||
|
||||
static PyObject *
|
||||
create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls,
|
||||
const char *name, int num_params,
|
||||
PyObject *aggregate_class);
|
||||
|
||||
static PyObject *
|
||||
create_window_function(pysqlite_Connection *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"", "", "", NULL};
|
||||
static _PyArg_Parser _parser = {"siO:create_window_function", _keywords, 0};
|
||||
const char *name;
|
||||
int num_params;
|
||||
PyObject *aggregate_class;
|
||||
|
||||
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
|
||||
&name, &num_params, &aggregate_class)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = create_window_function_impl(self, cls, name, num_params, aggregate_class);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
#endif /* defined(HAVE_WINDOW_FUNCTIONS) */
|
||||
|
||||
PyDoc_STRVAR(pysqlite_connection_create_aggregate__doc__,
|
||||
"create_aggregate($self, /, name, n_arg, aggregate_class)\n"
|
||||
"--\n"
|
||||
|
@ -975,6 +1022,10 @@ exit:
|
|||
return return_value;
|
||||
}
|
||||
|
||||
#ifndef CREATE_WINDOW_FUNCTION_METHODDEF
|
||||
#define CREATE_WINDOW_FUNCTION_METHODDEF
|
||||
#endif /* !defined(CREATE_WINDOW_FUNCTION_METHODDEF) */
|
||||
|
||||
#ifndef PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
|
||||
#define PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF
|
||||
#endif /* !defined(PYSQLITE_CONNECTION_ENABLE_LOAD_EXTENSION_METHODDEF) */
|
||||
|
@ -990,4 +1041,4 @@ exit:
|
|||
#ifndef DESERIALIZE_METHODDEF
|
||||
#define DESERIALIZE_METHODDEF
|
||||
#endif /* !defined(DESERIALIZE_METHODDEF) */
|
||||
/*[clinic end generated code: output=d965a68f9229a56c input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=b9af1b52fda808bf input=a9049054013a1b77]*/
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
#define HAVE_TRACE_V2
|
||||
#endif
|
||||
|
||||
#if SQLITE_VERSION_NUMBER >= 3025000
|
||||
#define HAVE_WINDOW_FUNCTIONS
|
||||
#endif
|
||||
|
||||
static const char *
|
||||
get_isolation_level(const char *level)
|
||||
{
|
||||
|
@ -799,7 +803,7 @@ final_callback(sqlite3_context *context)
|
|||
goto error;
|
||||
}
|
||||
|
||||
/* Keep the exception (if any) of the last call to step() */
|
||||
// Keep the exception (if any) of the last call to step, value, or inverse
|
||||
PyErr_Fetch(&exception, &value, &tb);
|
||||
|
||||
callback_context *ctx = (callback_context *)sqlite3_user_data(context);
|
||||
|
@ -814,13 +818,20 @@ final_callback(sqlite3_context *context)
|
|||
Py_DECREF(function_result);
|
||||
}
|
||||
if (!ok) {
|
||||
set_sqlite_error(context,
|
||||
"user-defined aggregate's 'finalize' method raised error");
|
||||
}
|
||||
int attr_err = PyErr_ExceptionMatches(PyExc_AttributeError);
|
||||
_PyErr_ChainExceptions(exception, value, tb);
|
||||
|
||||
/* Restore the exception (if any) of the last call to step(),
|
||||
but clear also the current exception if finalize() failed */
|
||||
PyErr_Restore(exception, value, tb);
|
||||
/* Note: contrary to the step, value, and inverse callbacks, SQLite
|
||||
* does _not_, as of SQLite 3.38.0, propagate errors to sqlite3_step()
|
||||
* from the finalize callback. This implies that execute*() will not
|
||||
* raise OperationalError, as it normally would. */
|
||||
set_sqlite_error(context, attr_err
|
||||
? "user-defined aggregate's 'finalize' method not defined"
|
||||
: "user-defined aggregate's 'finalize' method raised error");
|
||||
}
|
||||
else {
|
||||
PyErr_Restore(exception, value, tb);
|
||||
}
|
||||
|
||||
error:
|
||||
PyGILState_Release(threadstate);
|
||||
|
@ -968,6 +979,159 @@ pysqlite_connection_create_function_impl(pysqlite_Connection *self,
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_WINDOW_FUNCTIONS
|
||||
/*
|
||||
* Regarding the 'inverse' aggregate callback:
|
||||
* This method is only required by window aggregate functions, not
|
||||
* ordinary aggregate function implementations. It is invoked to remove
|
||||
* a row from the current window. The function arguments, if any,
|
||||
* correspond to the row being removed.
|
||||
*/
|
||||
static void
|
||||
inverse_callback(sqlite3_context *context, int argc, sqlite3_value **params)
|
||||
{
|
||||
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||
|
||||
callback_context *ctx = (callback_context *)sqlite3_user_data(context);
|
||||
assert(ctx != NULL);
|
||||
|
||||
int size = sizeof(PyObject *);
|
||||
PyObject **cls = (PyObject **)sqlite3_aggregate_context(context, size);
|
||||
assert(cls != NULL);
|
||||
assert(*cls != NULL);
|
||||
|
||||
PyObject *method = PyObject_GetAttr(*cls, ctx->state->str_inverse);
|
||||
if (method == NULL) {
|
||||
set_sqlite_error(context,
|
||||
"user-defined aggregate's 'inverse' method not defined");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
PyObject *args = _pysqlite_build_py_params(context, argc, params);
|
||||
if (args == NULL) {
|
||||
set_sqlite_error(context,
|
||||
"unable to build arguments for user-defined aggregate's "
|
||||
"'inverse' method");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
PyObject *res = PyObject_CallObject(method, args);
|
||||
Py_DECREF(args);
|
||||
if (res == NULL) {
|
||||
set_sqlite_error(context,
|
||||
"user-defined aggregate's 'inverse' method raised error");
|
||||
goto exit;
|
||||
}
|
||||
Py_DECREF(res);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(method);
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
/*
|
||||
* Regarding the 'value' aggregate callback:
|
||||
* This method is only required by window aggregate functions, not
|
||||
* ordinary aggregate function implementations. It is invoked to return
|
||||
* the current value of the aggregate.
|
||||
*/
|
||||
static void
|
||||
value_callback(sqlite3_context *context)
|
||||
{
|
||||
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||
|
||||
callback_context *ctx = (callback_context *)sqlite3_user_data(context);
|
||||
assert(ctx != NULL);
|
||||
|
||||
int size = sizeof(PyObject *);
|
||||
PyObject **cls = (PyObject **)sqlite3_aggregate_context(context, size);
|
||||
assert(cls != NULL);
|
||||
assert(*cls != NULL);
|
||||
|
||||
PyObject *res = PyObject_CallMethodNoArgs(*cls, ctx->state->str_value);
|
||||
if (res == NULL) {
|
||||
int attr_err = PyErr_ExceptionMatches(PyExc_AttributeError);
|
||||
set_sqlite_error(context, attr_err
|
||||
? "user-defined aggregate's 'value' method not defined"
|
||||
: "user-defined aggregate's 'value' method raised error");
|
||||
}
|
||||
else {
|
||||
int rc = _pysqlite_set_result(context, res);
|
||||
Py_DECREF(res);
|
||||
if (rc < 0) {
|
||||
set_sqlite_error(context,
|
||||
"unable to set result from user-defined aggregate's "
|
||||
"'value' method");
|
||||
}
|
||||
}
|
||||
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_sqlite3.Connection.create_window_function as create_window_function
|
||||
|
||||
cls: defining_class
|
||||
name: str
|
||||
The name of the SQL aggregate window function to be created or
|
||||
redefined.
|
||||
num_params: int
|
||||
The number of arguments the step and inverse methods takes.
|
||||
aggregate_class: object
|
||||
A class with step(), finalize(), value(), and inverse() methods.
|
||||
Set to None to clear the window function.
|
||||
/
|
||||
|
||||
Creates or redefines an aggregate window function. Non-standard.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls,
|
||||
const char *name, int num_params,
|
||||
PyObject *aggregate_class)
|
||||
/*[clinic end generated code: output=5332cd9464522235 input=46d57a54225b5228]*/
|
||||
{
|
||||
if (sqlite3_libversion_number() < 3025000) {
|
||||
PyErr_SetString(self->NotSupportedError,
|
||||
"create_window_function() requires "
|
||||
"SQLite 3.25.0 or higher");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int flags = SQLITE_UTF8;
|
||||
int rc;
|
||||
if (Py_IsNone(aggregate_class)) {
|
||||
rc = sqlite3_create_window_function(self->db, name, num_params, flags,
|
||||
0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
callback_context *ctx = create_callback_context(cls, aggregate_class);
|
||||
if (ctx == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
rc = sqlite3_create_window_function(self->db, name, num_params, flags,
|
||||
ctx,
|
||||
&step_callback,
|
||||
&final_callback,
|
||||
&value_callback,
|
||||
&inverse_callback,
|
||||
&destructor_callback);
|
||||
}
|
||||
|
||||
if (rc != SQLITE_OK) {
|
||||
// Errors are not set on the database connection, so we cannot
|
||||
// use _pysqlite_seterror().
|
||||
PyErr_SetString(self->ProgrammingError, sqlite3_errstr(rc));
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*[clinic input]
|
||||
_sqlite3.Connection.create_aggregate as pysqlite_connection_create_aggregate
|
||||
|
||||
|
@ -2092,6 +2256,7 @@ static PyMethodDef connection_methods[] = {
|
|||
GETLIMIT_METHODDEF
|
||||
SERIALIZE_METHODDEF
|
||||
DESERIALIZE_METHODDEF
|
||||
CREATE_WINDOW_FUNCTION_METHODDEF
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
@ -630,8 +630,10 @@ module_clear(PyObject *module)
|
|||
Py_CLEAR(state->str___conform__);
|
||||
Py_CLEAR(state->str_executescript);
|
||||
Py_CLEAR(state->str_finalize);
|
||||
Py_CLEAR(state->str_inverse);
|
||||
Py_CLEAR(state->str_step);
|
||||
Py_CLEAR(state->str_upper);
|
||||
Py_CLEAR(state->str_value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -717,8 +719,10 @@ module_exec(PyObject *module)
|
|||
ADD_INTERNED(state, __conform__);
|
||||
ADD_INTERNED(state, executescript);
|
||||
ADD_INTERNED(state, finalize);
|
||||
ADD_INTERNED(state, inverse);
|
||||
ADD_INTERNED(state, step);
|
||||
ADD_INTERNED(state, upper);
|
||||
ADD_INTERNED(state, value);
|
||||
|
||||
/* Set error constants */
|
||||
if (add_error_constants(module) < 0) {
|
||||
|
|
|
@ -64,8 +64,10 @@ typedef struct {
|
|||
PyObject *str___conform__;
|
||||
PyObject *str_executescript;
|
||||
PyObject *str_finalize;
|
||||
PyObject *str_inverse;
|
||||
PyObject *str_step;
|
||||
PyObject *str_upper;
|
||||
PyObject *str_value;
|
||||
} pysqlite_state;
|
||||
|
||||
extern pysqlite_state pysqlite_global_state;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue