Approach to construct queries

26 views
Skip to first unread message

jtuchel

unread,
Jan 30, 2018, 2:25:59 AM1/30/18
to glorp-group
Dear Glorp Community,

I am wondering about an approach to construct Glorp Queries from Filter objects. Let me tell you a bit what I mean by that...

In many web shops and sites you find things like sliders for a price range, a few clickable options like "on stock", "blue/red/greed", "rated higher that x stars" and whatever. And there are nice GUIs for combining these, like using a slider, date pickers, input fields etc. You can add these filters and remove them and then start a search.

We need to build these and the result should end up as a Glorp Query returning objects. We currently only have a few such places in our app and use code like this (from my head, not actual code) to construct queries:

self session read: Product where: [:p| p active = true AND: (self onlyOnStock = false OR: (p onStock=true))].

This work quite well for, say, up to 5 filters, but it doesn't really scale up to 10 or more. The code becomes extremely hard to maintain.

I'd rather like to use something like a visitor pattern, where each filter knows how to add its condition(s) to a query. Somthing like:

|query|
query := Query returningManyOf: Product.
self filters do: [:f|

  query addToWhereCondition: f condition.

].
products := self session execute: query.


Glorp is such a nice framework, but I cannot find a way to do this. Has anybody solved this or found something that could help me on my journey? The idea is that I define Filter objects for concrete values, NULL, ranges (between and) etc. and each of them constructs its little part of the query.

Any ideas? Any code samples?

Joachim

Benoit St-Jean

unread,
Jan 30, 2018, 9:32:48 AM1/30/18
to glorp-group, jtuchel
I remember that in TOPLink (Glorp's ancestor), we had filtered reads (or something named similarly)...  Basically, we could stack WHERE conditions and the final query would be built at the very last moment before execution.  Perhaps, Glorp needs this (it was a useful feature as I remember it).  Alan, are you listening?  ;)


-----------------
Benoît St-Jean
Yahoo! Messenger: bstjean
Twitter: @BenLeChialeux
Pinterest: benoitstjean
Instagram: Chef_Benito
IRC: lamneth
Blogue: endormitoire.wordpress.com
"A standpoint is an intellectual horizon of radius zero".  (A. Einstein)


--
You received this message because you are subscribed to the Google Groups "glorp-group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group...@googlegroups.com.
To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.

jtuchel

unread,
Jan 30, 2018, 10:57:54 AM1/30/18
to glorp-group
Benoît,


sounds like what I am looking for.
I took another tour of the Query subclasses.

I found Query subclass FilteringQuery. Its class comment says:

This is a query that expects all the objects it might return to already be in memory, and in a collection from which it can pick them out by evaluating a block. This is used to implement filtered reads on a mapping. When one of the mappings fires, it reads all the relevant results, and so each instance  using that mapping can get the results out of memory without going to the database again.

So this is not what I am looking for.
Just to be sure I am not overlooking the obvious I checked for implementors of #where: and only found one. It directly sets an instance variable, so the feature you mention seems not to have made its way into "normal" Queries ;-)

So any more ideas?

Joachim

Alan Knight

unread,
Jan 30, 2018, 12:04:55 PM1/30/18
to glorp...@googlegroups.com
This is a very common usage, and one that's definitely supported, though apparently not as easy to find as it might be.

Filtered reads are something different. What you want is probably either virtual collection, or just that AND: and OR: are implemented on query.

The former lets you get a collection to which you can send reject:/select: and it just returns a new virtual collection which adds the new query criteria. It doesn't actually issue the query until you start iterating over the result. And the way this is implemented is just that you can send AND: to a query (with a where clause, not another query) and it gives you another query whose where clause is the AND of the two where clauses.

jtuchel

unread,
Jan 31, 2018, 3:17:35 AM1/31/18
to glorp-group
Alan,

do you happen to know if there are samples or some documentation on virtual collections or the AND:/OR:.
From looking at teh implementors of AND: it seems like everything is right in front of me, I just couldn't see it...

I will try playing with the AND: stuff. Starting point will be somethind along the lines of:

|query products|
query := SimpleQuery returningManyOf: Product.
query where: [:prod| prod color = 'blue'].
query AND: [:prod| prod price <= 1.00].

products := self session execute: query.

If this is the way to go, the rest is easy: just let the filter objects generate these blocks and visit each of them for the query building.

That would be great. I'll keep you updated ;-)

jtuchel

unread,
Jan 31, 2018, 3:22:24 AM1/31/18
to glorp-group
Okay, this is FANTASTIC!

thanks Alan. This AND: / OR: thingie works perfectly. Why didn't I see this?

Joachim

jtuchel

unread,
Feb 7, 2018, 7:43:54 AM2/7/18
to glorp-group
I'm coming back to this topic because I'm having problems getting my head around something here....

Let's say I have a EqualitiyFilter which tests for equality and can be configured to hold an attribute  along with a user-provided value to compare to name which will be used to construct an SQL query.
I'd like to configure it for use in our application doing something like:

self possibleFilters add: ((EqualityFilter for attributeNamed: #birthday) .
The user can then select the filter and enter a value for comparison (like today's date).

Later, when the user presses the "search" button, I want to do something like:

query := SimpleQuery read: Person.
query where: [:person| person isAlive].
self selectedFilters do: [:filter| query AND: filter selectBlock].

Sounds great, doesn't it?
Not yet, because I see a problem I cannot solve on my own: how do I use the attribute name to put it into the query block....?

How do I implement #selectBlock?

selectBlock

   ^[:eachRow| eachRow whatDoIPutHere??? = self valueToCompare]

Any ideas? I guess #perform: doesn't really work in this context...


Joachim












Am Dienstag, 30. Januar 2018 18:04:55 UTC+1 schrieb alan.knight:

jtuchel

unread,
Feb 7, 2018, 7:46:38 AM2/7/18
to glorp-group
Sorry, I edited this too fast:


>Let's say I have a EqualitiyFilter which tests for equality and can be configured to hold an attribute  along with a user-provided value to compare to name which will be used to >construct an SQL query.

Should be

Let's say I have a EqualitiyFilter which tests for equality and can be configured to hold an attribute name along with a user-provided value to compare to which will be used to construct an SQL query.

Alan Knight

unread,
Feb 7, 2018, 10:50:33 AM2/7/18
to glorp...@googlegroups.com
Actually I think perform: might well work. What's happening is that there's a proxy passed into the block and it just notes what messages were sent, essentially constructing a tree of the expression. If the proxy understands perform: normally then I think it would work. Alternatively, you could provide a block that just fetched the attribute and evaluate it passing in the proxy. Sorry, mobile keyboard that won't let me type square brackets easily. I also think you could construct a BaseExpression and sent a query to it, but that's probably more complicated.

jtuchel

unread,
Feb 8, 2018, 4:21:11 AM2/8/18
to glorp-group
This is so incredibly fantastic!
The follwong expression actually really works and thus absolutely blows my mind ;-)

session read: Person where: [:ea| (ea perform: #id)   = 1017 ].

I was so sure this can't possibly work I even didn't dare to try ;-)))) Of course, once you think about it, there is no magic involved. Both sending #id and doing a #perform: #id send the message #id to the receiver. So I am hopping around the room celebrating something lame and boring. It's Smalltalk, so what...? (Eat this, static fanboys!)

So all I need is right there in front of me ;-) Thanks a lot, Alan!

Now we need a solution for the #refresh: anomaly I described on this group a few days ago and we're back in full steam with Glorp on our production web server.

Joachim

jtuchel

unread,
Mar 8, 2018, 9:17:21 AM3/8/18
to glorp-group
This is really fun. I am in the middle of putting together a flexible GUI in which you configure a number or filters and when the user clicks "Search", I construct a query out of Blocks which each of the filters provides.

The beginning was smooth and I was very enthusiastic. Until I had to see if an invoice has payments, so I needed to navigate to related objects, which, in general is not a problem. But now I am stuck with this:

|q|
q := SimpleQuery read: Invoice .
q where: [:inv | (inv sum: [:ea | ea claim payments amount]) > inv claim amount].
dbSession execute: q

--> This works fine.

... But:


|q|
q := SimpleQuery read: Invoice .
q where: [:inv| inv date < Date today].
q AND: [:inv | (inv sum: [:ea | ea claim payments amount]) > inv claim amount].
KontolinoSession instance dbSession execute: q

fails with: UndefinedObject doesNotUnderstand isRelatiponship
in: BaseExpression>>aggregate: anExpression as: aggregateFunctionName where: whereExpression.

What am I doing wrong?

Joachim






Alan Knight

unread,
Mar 8, 2018, 9:21:45 AM3/8/18
to glorp...@googlegroups.com
I'm guessing here, but one of the unresolved issues in Glorp is timing of rebuilding expressions. There are times, especially when making complex queries, where you need information about the query before it's normally available. Two things you could try - rather than making a new query, ask the session for one. Then it will have access to the descriptors. The other is that you could convert the block to an expression explicitly, passing in the baseExpression of the query. Again, that will know about the descriptors already.


jtu...@objektfabrik.de

unread,
Mar 8, 2018, 10:16:56 AM3/8/18
to glorp...@googlegroups.com
Alan,

I must admit I am a bit lost at trying either of your suggestion.

Asking the session for an existing session to me implies there should be some QueryCache or methods like getQueryNamed: or anything. I can't find such a thing.

The seond I guess would be using some of the methods in the asGlorpExpression* family. Most likely  asGlorpExpressionOn:in: or asGlorpExpressionForDescriptor:. I will start my experiments with the second one...

Any additional hints are welcome ;-)

Joachim



Am 08.03.18 um 15:21 schrieb Alan Knight:
You received this message because you are subscribed to a topic in the Google Groups "glorp-group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/glorp-group/MHCdAZ9AzMg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to glorp-group...@googlegroups.com.

To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.


-- 
-----------------------------------------------------------------------
Objektfabrik Joachim Tuchel          mailto:jtu...@objektfabrik.de
Fliederweg 1                         http://www.objektfabrik.de
D-71640 Ludwigsburg                  http://joachimtuchel.wordpress.com
Telefon: +49 7141 56 10 86 0         Fax: +49 7141 56 10 86 1

jtuchel

unread,
Mar 8, 2018, 10:51:44 AM3/8/18
to glorp-group
So the first thing I tried is this:


|q sess|
sess := KontolinoSession  instance logout dbSession.
q := ReadQuery read: Invoice .
q baseExpression:  (BaseExpression new descriptor: (sess descriptorFor: Invoice new)).
q where: [:re | (re sum: [:ea | ea claim payments]) > re claim amount].
q AND: [:re| re date < Date today].
 sess execute: q


Same error as before. Funnily, if I comment out the AND: line, it works and returns the correct results...
To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group+unsubscribe@googlegroups.com.

To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to a topic in the Google Groups "glorp-group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/glorp-group/MHCdAZ9AzMg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to glorp-group+unsubscribe@googlegroups.com.

To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.

Alan Knight

unread,
Mar 8, 2018, 11:20:00 AM3/8/18
to glorp...@googlegroups.com
It's been a while, and I don't have a proper computer here, but I think it might just be session query: ..., corresponding to read:

And if that works, it'd be more like

  q AND: ([...] asGlorpExpressionOn: q baseExpression)


To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group...@googlegroups.com.

To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to a topic in the Google Groups "glorp-group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/glorp-group/MHCdAZ9AzMg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to glorp-group...@googlegroups.com.

To post to this group, send email to glorp...@googlegroups.com.
Visit this group at https://groups.google.com/group/glorp-group.
For more options, visit https://groups.google.com/d/optout.


-- 
-----------------------------------------------------------------------
Objektfabrik Joachim Tuchel          mailto:jtu...@objektfabrik.de
Fliederweg 1                         http://www.objektfabrik.de
D-71640 Ludwigsburg                  http://joachimtuchel.wordpress.com
Telefon: +49 7141 56 10 86 0         Fax: +49 7141 56 10 86 1

--
You received this message because you are subscribed to the Google Groups "glorp-group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group...@googlegroups.com.

jtuchel

unread,
Mar 9, 2018, 3:40:40 AM3/9/18
to glorp-group
Alan,






Am Donnerstag, 8. März 2018 17:20:00 UTC+1 schrieb alan.knight:
It's been a while, and I don't have a proper computer here, but I think it might just be session query: ..., corresponding to read:


I only find the possibility to assign a #session: to a query, but GlorpSession has nothing like #query:, #create*Query*, #privateGet*Query* or anything like that. I also checked in VisualWorks 8.2 to be sure it is not something that's missing from the VA port.


 
And if that works, it'd be more like

  q AND: ([...] asGlorpExpressionOn: q baseExpression)


Tried it (with Queries created without the involvement of the session, of course) and got the same error:

UndefinedObject does not understand isRealtionship in  ObjectExpression>>#aggregate:as:where:

Any other ideas?

Joachim



 

-- 
-----------------------------------------------------------------------
Objektfabrik Joachim Tuchel          mailto:...@objektfabrik.de
Fliederweg 1                         http://www.objektfabrik.de
D-71640 Ludwigsburg                  http://joachimtuchel.wordpress.com
Telefon: +49 7141 56 10 86 0         Fax: +49 7141 56 10 86 1

jtuchel

unread,
Mar 9, 2018, 4:43:41 AM3/9/18
to glorp-group
Alan,

I finally had success with this:

|q sess exp |
sess := KontolinoSession  instance dbSession.
q := SimpleQuery read: Invoice.
q session: sess.
q baseExpression:  (exp := BaseExpression new descriptor: (sess descriptorFor: Invoice new)).
q where: ([:re | (re sum: [:ea | ea claim payments amount]) > re claim amount] asGlorpExpressionOn: exp).
q AND: ([:re| re date < Date today] asGlorpExpressionOn: exp).
 sess execute: q.


The important parts:

  • It is not enough to just do the asGlorpExpression for the AND: parameters, at least the one that aggregates needs it.
  • The temp var for the base expression! always asking the query for its base expression returns Base(Invoice) for the first statemet (where:) but Base(nil) for the second.


I am not sure I understand all that, but at least things work now ;-)



Thanks (in Capital Letters: THANKS!)


Joachim






Madhu

unread,
Mar 9, 2018, 7:39:24 AM3/9/18
to glorp...@googlegroups.com
Joachim,

Just in case if you don't want to use asGlorpExpressionOn: all over, try:

|q sess|
sess := KontolinoSession instance dbSession.
q := SimpleQuery read: Invoice.
q where: [:inv| inv date < Date today].
q baseExpression descriptor: (sess descriptorFor: Invoice). "Initialize descriptor before the first call to AND:"
q AND: [:inv | (inv sum: [:ea | ea claim payments amount]) > inv claim amount].
sess execute: q.

Regards,
Madhu.

--
You received this message because you are subscribed to the Google Groups "glorp-group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group+unsubscribe@googlegroups.com.

jtuchel

unread,
Mar 9, 2018, 8:35:24 AM3/9/18
to glorp-group
Madhu.

I remember trying that before the first #where: with no luck. If I remember correctly, the effect also was that after the first (or maybe each) #AND: or #where: the Query's baseExpression was again Base(nil). So I guess what you suggest works for this code snippet, but very likely doesn't work if you append another #AND:   ...  ?

Since I want to work an a list of unknown size of filters that add queryBlocks to the query, I will probably just stick with what I have now.


Joachim
To unsubscribe from this group and stop receiving emails from it, send an email to glorp-group...@googlegroups.com.

Wolfgang Eder

unread,
Mar 14, 2018, 6:01:59 AM3/14/18
to glorp...@googlegroups.com
Hi Joachim,
you can try to put the AND: into the block,
instead of sending it to the query.

q where: [:inv| inv date < Date today AND: [ (inv sum: [:ea | ea claim payments amount]) > inv claim amount]].

just my 2c
Wolfgang
signature.asc

jtuchel

unread,
Mar 14, 2018, 9:13:35 AM3/14/18
to glorp-group
Wolfgang,


you are correct. Building a complex query in a single Block never showed this error for me.

However, I was looking for an elegant way to construct complex queries out of individual small objects that are configured in a GUI. We've been using those monster blocks for a while, like this:

query read: Invoices where: [:inv| (self gui invoiceDate = nil or: [inv invoiceDate = self gui invoiceDate]) AND: ... ].

This gets quite messy once your GUI offers mor than, say, 5 different attributes for filtering.

So that is why i wanted to do it in pieces. Only GUI filters that are relevant for the query can then contribute a query block, while the others don't. This makes the code much mor readable. I am planning to write a blog post on this, but don't expect this to happen too soon... (unfortunately).

Joachim

Alan Knight

unread,
Mar 14, 2018, 3:17:25 PM3/14/18
to glorp...@googlegroups.com
I'm pretty sure that at some point I made an example with a GUI doing exactly this sort of thing for VW. 



Reply all
Reply to author
Forward
0 new messages