GnuCash
Contact   Instructions
Bug 796137 - query.search_for outputs critical qof.object errors and prevents queries being run
Summary: query.search_for outputs critical qof.object errors and prevents queries bein...
Status: RESOLVED FIXED
Alias: None
Product: GnuCash
Classification: Unclassified
Component: Python Bindings (show other bugs)
Version: git-master
Hardware: Other Linux
: Normal normal
Target Milestone: future
Assignee: core
QA Contact: core
URL:
Whiteboard:
Keywords:
: 796753 (view as bug list)
Depends on:
Blocks: 796863
  Show dependency tree
 
Reported: 2018-05-15 08:55 EDT by Tom Lofts
Modified: 2019-01-26 05:13 EST (History)
5 users (show)

See Also:


Attachments
Example of issue (578 bytes, text/plain)
2018-05-15 08:55 EDT, Tom Lofts
no flags Details
patch to base-typemaps.i for python 3 (1.87 KB, patch)
2018-09-05 03:03 EDT, David
no flags Details

Description Tom Lofts 2018-05-15 08:55:14 EDT
Created attachment 372070 [details]
Example of issue

Hi there,

In the master version of Gnucash the query.search_for() function currently returns the following output:

E.g. the following code:

query = gnucash.Query()
query.search_for(gnucash.gnucash_core_c.GNC_ID_INVOICE)
query.set_book(session.book)
query.run()

Produces the following on the query.search_for line:

* 08:41:59  WARN <qof.class> [qof_class_get_parameter()] no object of type К
* 08:41:59  WARN <qof.class> [qof_class_get_parameter()] no object of type ▒ ▒
* 08:41:59  CRIT <qof.object> [qof_object_foreach()] No object of type ▒ ▒

The query.run line then produces no results.

I believe this issue was introduced in the migration to Python3 - while gnucash.gnucash_core_c.GNC_ID_INVOICE appears to be the correct representation of 'gncInvoice' which is required to be passed though to qof_query_search_for, this function expects it to be a char array, but the Python bindings/SWIG seems to convert it to a Python 3 unicode string somehow causing the errors.

Passing a string though instead of the constant e.g.:

query.search_for('gncInvoice')

Gives the same result

Attepting to pass a byte array though e.g.:

query.search_for(b'gncInvoice')

Gives an incorrect type error in the swig generated code.

I have a feeling a typemap needs to be added to convert from the Python3 string to the char array or something similar but I've been unable to get this to work.

Please find a complete example (which assumes certain MySQL credentials) of the issue which will fail when run in Python 3 (it will work when run in an older version built with Python 2 bindings).
Comment 1 Christoph Holtermann 2018-09-02 15:18:09 EDT
I came across the same issue. The problem starts very early.

query = gnucash.Query()
query.search_for(gnucash.gnucash_core_c.GNC_ID_INVOICE)
query.get_search_for()
>>> '\udca0y\x179\udcc4U'

debugging the method qof_query_search_for the string is being set correctly. But after leaving the method it gets lost. It seems to be overwritten or the memory is being freed by SWIG ?


As a workaround an addition in gnucash_core.i:
%{
void qof_query_search_for_invoice (QofQuery *q)
{   
    qof_query_search_for(q, GNC_INVOICE_MODULE_NAME);
}
%}

calling this 
gnucash._gnucash_core_c.qof_query_search_for_invoice(query.instance)
query.get_search_for()
returns
'gncInvoice'
Comment 2 Christoph Holtermann 2018-09-03 16:19:18 EDT
digging a bit further. This also works:

void qof_query_search_for2 (QofQuery *q, gchar *obj_type)
{  
    char *foo;
    foo = (char *) malloc(strlen(obj_type)+1);
    strcpy(foo, obj_type);
               
    qof_query_search_for(q, foo);
}

SWIG freeing the memory after exiting the wrapper actually seems to be the issue. With this version a new string is being allocated for every search. I guess it never gets freed.
Comment 3 Christoph Holtermann 2018-09-03 17:54:48 EDT
Thinking of memory a solution like this seems more solid. In that way python strings stay on that side and on the c/c++ side QofIdTypeConst. But I don't yet understand if the list of things that can be searched for is limited so that this would be a useful approach.

void qof_query_search_for3 (QofQuery *q, gchar *obj_type)
{ 
    if (strcmp(obj_type, GNC_INVOICE_MODULE_NAME) == 0) 
        {
                qof_query_search_for(q, GNC_INVOICE_MODULE_NAME);
        }
    else if (strcmp(obj_type, GNC_CUSTOMER_MODULE_NAME) == 0)
        {
                qof_query_search_for(q, GNC_CUSTOMER_MODULE_NAME);
        }
    else if (strcmp(obj_type, GNC_VENDOR_MODULE_NAME) == 0)
        {
                qof_query_search_for(q, GNC_VENDOR_MODULE_NAME);
        }
}
Comment 4 John Ralls 2018-09-03 18:41:19 EDT
I don't know why you're wasting your time researching this, the problem was discussed at length on the devel list about 6 weeks ago:

https://lists.gnucash.org/pipermail/gnucash-devel/2018-July/042318.html

If you're eager to fix it, change QofIdType to a gquark [1]. Please do a PR rather than a patch, it will be a fairly extensive change. If that's too involved for you then the python-only fix is to export the QofIdType static strings into python so that the value passed to qof_query_search_for isn't subject to garbage collection.

[1] https://developer.gnome.org/glib/stable/glib-Quarks.html
Comment 5 Christoph Holtermann 2018-09-04 02:02:55 EDT
Thank you for pointing me to the mails. I didn't see them. That's reason 1 for wasting time. The second is that I'm using the python bindings to write invoices which doesn't work because of this issue now :-)

I couldn't figure out how to add the string to pythons garbage collector. I tried %newobject but couldn't get it to work as I wanted.

I'll have a look at the mails next.
Comment 6 John Ralls 2018-09-04 10:13:30 EDT
You don't want to add it to the garbage collector, you want to *protect* it from the garbage collector. The way to do that in Python is to assign it to a variable somewhere that's in scope for the lifetime of the QofQuery. e.g.

  GNC_ID_INVOICE = gnucash.gnucash_core_c.GNC_ID_INVOICE
  query = gnucash.Query()
  query.search_for(GNC_ID_INVOICE)
  query.set_book(session.book)
  query.run()

Another way would be to change gnucash_core.i so that GNC_ID_INVOICE is a constant char*, see http://www.swig.org/Doc3.0/SWIGDocumentation.html#SWIG_nn12.
Comment 7 Christoph Holtermann 2018-09-04 14:18:09 EDT
John, thanks for the answers. You're example doesn't work for me.

GNC_ID_INVOICE = gnucash.gnucash_core_c.GNC_ID_INVOICE
query = gnucash.Query()
query.search_for(GNC_ID_INVOICE)
print(query.get_search_for())
>>>"0wy'\x1dV"

It seems that the wrappers copy is going to garbage at the end of the wrapper without taking into regard the garbage collection status of the python string. 

Going a bit further with the idea of creating an additional copy a buffer for the string could be created with the python constructor of the query and be freed at the destructor.

Python Getter and setter of search_for could use that buffer by calling specific wrappers.

In his initial mail (that I had actually read but somehow didn't associate to my current doings :-) David had pointed out a potentially bigger dimension of the problem - like all char *. Is that the case ?

For this problem I'd like to have a small python fix for this function. Butif it's part of a bigger problem I'd like to approach that as well. At the moment I only see a problem at this function but I would look for other char * functions and check if the same problem occurs.

The idea with gquarks sounds good but as you discussed in the mailing list also to me it seems to make more sense to wait and go the c++ way ( at least I don't really feel competent&motivated to rewrite the c source ).

The idea about constant GNC_ID_INVOICE came to me as well. I didn't find out how to do it (thank you for the link to the docs). And I read that SWIG treats consts just like any other string so I stopped going that path.
It has the same limitations like my suggestion in Comment 3: every possible option has to be made a constant. Ok, maybe I'm just too lazy to look up all of them :) 
In addition the wrapper has to check if it is being presented such a const. If it weren't it would have to raise an error. And I actually like to be able to put in a string.

I added David to the people in this bug report.
Comment 8 Christoph Holtermann 2018-09-04 14:46:37 EDT
John, your suggestion in Comment 6 about consts in gnucash_core.i doesn't work for me. I tried

#define GNC_ID_INVOICE2  "gncInvoice"
#define _ID_INVOICE "gncInvoice";
%constant char * _ID_INVOICE2 = "gncInvoice";

and I remember actually having tried that before.

The resulting output if I feed that to search_for is the same as in Comment 7.
Comment 9 Christoph Holtermann 2018-09-04 17:36:13 EDT
David, thinking about your mail (https://lists.gnucash.org/pipermail/gnucash-devel/2018-July/042318.html) I suppose your typemap looks something like

%typemap(in) (QofIdTypeConst obj_type) {
        $1 = PyUnicode_AsUTF8($input);
}

using this Johns example from Comment 6 works.

The string needs to be saved as long as the query lives. Possibly like this (gnucash_core.py):

Query.add_method('qof_query_search_for', '_search_for') // modified

class Query(GnuCashCoreClass):

    def search_for(self, obj_type):
        self.__search_for_buf = obj_type
        self._search_for(self.__search_for_buf)
Comment 10 David 2018-09-05 03:03:50 EDT
Created attachment 372962 [details]
patch to base-typemaps.i for python 3
Comment 11 Christoph Holtermann 2018-09-06 04:05:57 EDT
David, do you know if PyBytes_AsString has the same memory allocation principle as  PyUnicode_AsUTF8 ?
Comment 12 David 2018-09-06 06:26:32 EDT
Not needed - the byte type in python 3 is a non-unicode string - same
as str type in python 2 - so no translation needed hence no temporary
string created by swig - you still need to keep the string around in
python for the duration of query - ie till the query run has been
done.
Comment 13 Christoph Holtermann 2018-09-06 19:20:50 EDT
I created a pull request https://github.com/Gnucash/gnucash/pull/408

It includes 

1) Davids patch as in comment 10.
2) modification to python Query class as suggested by me in comment 9 so that search_for is a python method that creates a buffer string that stays alive as long as the query lives
3) to prevent Query.search_for to be overwritten by add_constructor_and_methods_with_prefix I added an exclude option. The qof_query_search_for function gets included as _search_for.

This should make queries work again. A possible broader range of the issue could be looked at a bit later.
Comment 14 Christoph Holtermann 2018-09-07 04:26:13 EDT
1) Travis build failed for the gslist part https://travis-ci.org/Gnucash/gnucash/builds/425504058?utm_source=github_status&utm_medium=notification, I took it out of the pull request for the moment
2) I have to fix my branches history to make the pull request valid.
Comment 15 Christoph Holtermann 2018-09-07 09:30:56 EDT
Closed pull request https://github.com/Gnucash/gnucash/pull/408
Opened pull request with cleaned up history: https://github.com/Gnucash/gnucash/pull/410

The pull request does not include the GSList part.
Comment 16 John Ralls 2018-09-08 16:57:06 EDT
I've merged and pushed Christoff's second PR. It will be in 3.3.
Comment 17 Christoph Holtermann 2018-09-11 03:49:10 EDT
The GSList part is already present as https://bugs.gnucash.org/show_bug.cgi?id=796753 as I just see.
Comment 18 Christoph Holtermann 2018-09-11 03:53:53 EDT
The GSList part is already present as https://bugs.gnucash.org/show_bug.cgi?id=796753 as I just see.

And this is a duplicate of https://bugs.gnucash.org/show_bug.cgi?id=796750.
Comment 19 John Ralls 2018-09-11 09:52:53 EDT
*** Bug 796753 has been marked as a duplicate of this bug. ***
Comment 20 John Ralls 2018-09-11 09:55:13 EDT
Thanks, I've closed bug 796753 as a duplicate. Bug 796750 is more general as it applies to Guile bindings as well as Python and the Guile case isn't fixed.

Note You need to log in before you can comment on or make changes to this bug.