Developers Manual 1.20

3. Querying

3.1 Enumerators and Blocks

The following enumeration methods work in the same way as their Collection counterparts

Some examples:

allPersons := aReStore instancesOf: Person.

"Retrieve all instances of Person with the surname Smith"
allPersons select: [ :each | each surname = 'Smith'].

"Are there any Persons with the name 'John Smith'? "
allPersons anySatisfy: [ :each | (each firstName = 'John') & (each surname = 'Smith')].

"Retrieve all Persons not living in London, Birmingham or Manchester"
allPersons reject: [ :each | #('London' 'Birmingham' 'Manchester') includes: each address city].

"Retrieve any Person living in the NW3 postal area"
allPersons detect: [ :each | 'NW3*' match: each address postcode] ifNone: [nil].


Object Identity
Before proceeding further, it is worth emphasising that ReStore will always maintain the identity of objects returned from the database - that is, if you have a persistent object in memory, and that object is also included in the results of a subsequent query, or is referenced from another persistent object, then the existing in-memory object will be used. 

This also means that cyclical structures - Object A references Object B references Object A - are not a problem for ReStore.

Limitations on Enumeration Blocks
Messages sent within an enumeration block may only relate to the instance variables of the receiver of that mesage (as defined in its class definition). This is because the block is not evaluated within the Smalltalk image - instead it is parsed by ReStore and converted into a database query. This query is then used by the database to extract only the required objects - thus only the objects matching the enumeration block are actually brought into memory, making querying quick and efficient.

As an example, consider the above check for a Person named 'John Smith':

allPersons anySatisfy: [ :each | (each firstName = 'John') & (each surname = 'Smith')].

(NB you cannot use the and: and or: Boolean messages in an instancesOf: block; they are inlined by the Dolphin compiler and so cannot be parsed. Instead use & and |, or alternatively qAnd: and qOr:)

Let's say that Person defines a method fullName:


        ^firstName, ' ', surname

Thus an alternative block to find 'John Smith' would be

  [ :each | each fullName = 'John Smith']

This would be invalid as an enumeration block for an instancesOf: collection, since 'fullName' cannot be directly related to a single instance variable.


As with a standard collection, it is possible to define a sort order on an instancesOf: collection by the use of a sort block - a two argument block defining the relative ordering of two instances (see the class comment of SortedCollection for further details). A sort block is defined on an instancesOf: collection by sending the message sortBlock: - once defined, all database activity against that collection will be ordered by use of equivalent SQL order directives. For example, the following sort block

[ :p1 :p2 | p1 surname <= p2 surname]

 translates to the following SQL


Limitations on Sort Blocks
As with enumeration blocks, sort blocks used within ReStore are subject to certain limitations. Firstly, the sorting must be defined only in terms of the instance variables of the class of object held by the collection. Secondly, the sort block must be defined logically, not procedurally. This is not normally a concern where the sorting is defined on a single attribute, but is important where more than one attribute is used. As an example, consider the following way of defining a sort order on (firstly) the surname of a Person object, and then (if surnames are equal) on the firstName:

[ :p1 :p2 | 
(p1 surname = p2 surname)
        ifTrue: [p1 firstName <= p2 firstName]
        ifFalse: [p1 surname < p2 surname]]

This is a procedural definition, in that a flow of program control is defined - firstly an = test is applied, then, depending on the result, either a firstName or surname comparison. Such a sort block is not suitable for use with an instancesOf: collection - it should be redefined logically as follows:

[ :p1 :p2 | 
(p1 surname < p2 surname) | 
((p1 surname = p2 surname) & (p1 firstName <= p2 firstName))]

In this implementation, the comparison is defined purely in terms of <, = , & (logical AND) and | (logical OR). ReStore is able to translate such a sort block into the equivalent SQL order directives.

These limitations also apply to the use of SortedCollections in a class definition, and to the implementation of <= in a persistent class, where such an implementation is used to define the sort order. 


Messages and Functions
When dealing with a basic object (String, Number, Date etc.) within an enumeration block, it is possible to send certain standard Smalltalk messages to these objects these are translated into SQL functions which are executed by the database. Translating messages to functions in this way transfers more of the work from the Smalltalk image to the database, reducing the amount of data transferred and making querying even more efficient.  

The actual messages supported vary from database to database (depending on the functions supported by that database) but commonly include the following:

Some examples: 

allPersons := aReStore instancesOf: Person.

"All Persons in the 020 telephone area"
allPersons select: [ :each | ( each homeTelephone leftString: 3) = '020'].

"All Persons born in 1970"
allPersons select: [ :each | each dateOfBirth year = 1970].

"All Persons with a large bank balance or overdraft"
allPersons select: [ :each | each annualSalary abs > 5000].

For more details on the conversion of Smalltalk messages to SQL, see Customising ReStore for Individual Databases

3. Querying

2000-2003 Solutions Software Ltd.

Home | Index | Next