GncBusiness v. GNCSession

Derek Atkins warlord@MIT.EDU
22 Nov 2001 21:24:38 -0500


linas@linas.org (Linas Vepstas) writes:

> > The query isn't EXECUTED in the core-object; the query is BUILT in the
> > core-object (i.e. a char* is returned).  The core-object builds up
> > it's part of the query, which is eventually executed in the "Central
> > Backend".
> 
> How do I build that query, using C?
> 
> say I have the following:
> 
> struct GNCPhoneBook {
>     void (*query) (...);
>     ...
> }
> 
> struct GNCAccountHeirarchy {
>     void (*query) (...);
>     ...
> }
> 
> What C code would I write to construct the query
> (account='xyz') AND (phone='555-1212')

Well, as I said in another message, the query() method looks more
like:
	char * (*query) (GNCSession *, QueryTerm *, char *);

The 'user' would generate a GNCQuery that fits the bill, and
then would call gncQueryExecute() which would basically do
the following (note that this is pseudocode, not C):

	char *query_str = NULL;	

	for (term = QueryTerms(query); term; term = term->next) {
		obj = object_type(term);
		query_str = obj->query (session, term, query_str);
		if (term->next)
			query_str = g_strdup_printf ("%s AND ", query_str);
	}

We would obviously need a recursive loop to deal with nested ANDs and
ORs.

> > Which implies we need a generic method to tell gnc-session all of the
> > core-objects...  Which is my suggested "core-object registration" :)
> 
> sounds good.  Its some user-configurable table listing url-types,
> object names, and .so library names ...

Well, not quite.  core-object registration is orthogonal to data-store-type.

The registration of "postgres:/..." and "rpc:/..." is different
than the registration of GncOrder and GncInvoice.

> > Fair enough.  What I really meant was:
> > 
> > 	user calls 
> > 		gnc_open_session("url:/path/to/data");
> > 
> > 	how does some the backend know how to load some random core-object?
> 
> Hmm.  I guess we need to have something at the top of the dataset that
> declares: 'this dataset requires the use of the following core objects:'.
> This would be the book.  The book, when represented as an XML file,
> would have a section that says "this dataset has sections for commodities, 
> for transactions, for phone numbers and for invoices".  The book,
> when set up in RAM, would have the API we previously discussed.

This works for XML.  What do you do for SQL?  RPC?  HTTP?

> > The description is used for generic searches, to present a printable
> > string to the user when the generic widget says "Choose FOO".
> 
> I think I will avoid responding to this until you say "yeah verily,
> here is the design document, and this is the the formal documentation".
> 
> Is there a length limit on this string?  Who does the translation of the
> string to other languages (the gui above the object, or does the object
> itself hold teh different translations)?

Good questions...  Currently the GUI does the translations, but we
could change the 'char * description' to 'char * (*description)
(void)' and have the module hold the translations.

> > > query() -- highly debatable, because of the join issue.
> > 
> > Yea.. I'm not sure about this, either..  But there needs to be some
> > way to extend the backend (which includes queries) without requiring
> > the backend to know about all data structures.
> 
> No, quite the opposite.  There needs to be a way for the user to be able
> to specify arbitrarly compilcated queries, including joins across 
> objects.    
> 
> The backend is not "required" to know anything.  It has the permission
> to know everything.

I think we're talking past each other again.  Yes, user's need to be
able to specify arbitrarily-complicated queries, including joins
across objects.  On the other hand, I do not want to require a single
piece of code to know about all the data structures.

Currently the backends need to know about all objects.  The Postgres
backend needs to know about everything.  The XML/file backend needs to
know about everything.

I want to change that.  I don't want ANY part of Gnucash to have a
built-in registry of all data objects/structures.  I want to be able
to dynamically add new data-types to the engine, but at the same time
I want to be able to query on those new objects too.  And queries
should be able to join across existing objects.

> > It we could assume SQL, then the query() method could be used to build
> > a char* query string 
> 
> In that case, there would be one query method, on the book, and not a
> one query method per object.

No no no.. There is gncQueryRun() which calls into the backend's
query() routine, and then the backend query() routine would build up
the query-string by calling into the individual object methods.
Finally, once the query-string is built, the backend would execute it,
and return back to the caller.

In other words, there is the Backend query() method, and the
individual object query() method.  They are used together.

> > and then the real backend would do that.
> 
> As mentioned above, things like "query the bank balance" present formal
> difficulties.  The mapping between the actual SQL tables and the 
> conceptual structure defined by te engine objects is not 1-1.

Well, how does the GNCQuery signify "query the bank balance"?  Sure,
it's a complicated query, but SOMEWHERE you need the logic to build
it.

The code that converts the GNCQuery "query the bank balance" to
the SQL "query the bank balance" would obviously need to create
the same query-string.  But don't you agree that there could be
multiple algorithms for generating that query-string?

For example, to generate the query string:

(account='xyz') AND (phone='555-1212')

I could do:
	g_strdup_printf ("(account='%s') AND (phone='%s')",
			account_name, phone_number);

Or I could do:
	g_strdup_printf ("(%s) AND (%s)",
		g_strdup_printf ("account='%s'", account_name),
		g_strdup_printf ("phone='%s'", phone_number));

Obviously both methods will result in the same string, but you
can clearly see how the second algorithm could be extended to
something like:

	for (term; term; term=term->next) {
		get_query_term(term)
		append_and_if_needed;
	}

And if we can do this, then the 'get_query_term' could easily
be provided by the object module instead of hard-coded in,
e.g., src/backend/postgres/query.c

-derek

-- 
       Derek Atkins, SB '93 MIT EE, SM '95 MIT Media Laboratory
       Member, MIT Student Information Processing Board  (SIPB)
       URL: http://web.mit.edu/warlord/    PP-ASEL-IA     N1NWH
       warlord@MIT.EDU                        PGP key available