File: search.html

package info (click to toggle)
picolisp 25.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 7,388 kB
  • sloc: ansic: 3,092; javascript: 1,004; makefile: 107; sh: 2
file content (439 lines) | stat: -rw-r--r-- 13,026 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
<!--
# VIP @lib/vip/html.l
# 13dec25 Software Lab. Alexander Burger
-->

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>The 'search' Function</title>
<link rel="stylesheet" href="doc.css" type="text/css">
</head>
<body>
<a href="mailto:abu@software-lab.de">abu@software-lab.de</a>

<h1>The 'search' Function</h1>

<p style="text-align: right">(c) Software Lab. Alexander Burger

<p>The <a href="refS.html#search">search</a> function allows to search the <a
href="ref.html#dbase">database</a> for a combination of search criteria.

<p>It finds all objects - directly from the criteria or after traversing all
associations - which fulfill <i>all</i> given search criteria, and returns them
one at a time.

<p><ul>
<li><a href="#examples">Examples</a>
<li><a href="#syntax">Syntax</a>
   <ul>
   <li><a href="#crit">Search Criteria</a>
      <ul>
      <li><a href="#num">Numbers</a>
      <li><a href="#str">Strings</a>
      <li><a href="#obj">Objects</a>
      </ul>
   <li><a href="#spec">Relation Specifications</a>
   </ul>
<li><a href="#custom">Custom Generators and Filters</a>
   <ul>
   <li><a href="#relQs">Multiple Indexes</a>
   </ul>
<li><a href="#extract">Extraction Function</a>
<li><a href="#sort">Sorting</a>
</ul>


<p><hr>
<h2><a id="examples">Examples</a></h2>

<p>The examples in this document will use the demo application in "app/*.l" (in
<a href="https://software-lab.de/demoApp.tgz">demoApp.tgz</a>).

<p>To get an interactive prompt, start it as:

<pre>
$ pil app/main.l -main +
:
</pre>

<p>As ever, you can terminate the interpreter by hitting <code>Ctrl-D</code>.


<p><hr>
<h2><a id="syntax">Syntax</a></h2>

<p><code>search</code> is called in two different forms.

<p>The first form initializes a query. It takes two or more arguments, and
returns a query structure (a list).

<p>The second form can then be called repeatedly with that structure, and will
subsequently return the next resulting object, or <code>NIL</code> if no more
results can be found.

<p>To start a new query, <code>search</code> is called with an arbitrary number
of argument pairs, each consisting of a search criterion and a list of relation
specifications, and an optional extraction function which filters and possibly
modifies the results.

<p>For example, to find the item with the number 2:

<pre>
ap: (search 2 '((nr +Item)))
-> (NIL ...
</pre>

<p>The first argument <code>2</code> is a <i>search criterion</i> (the key to
look for), and <code>((nr +Item))</code> is a list with a single <i>relation
specification</i> (the <code>nr</code> index of the <code>+Item</code> class).

<p>The returned query structure is abbreviated here, because it is big and not
relevant. It can now be used to fetch the result:

<pre>
ap: (search @)
-> {B2}
ap: (show @)
{B2} (+Item)
   nr 2
   pr 1250
   inv 100
   sup {C2}
   nm "Spare Part"
-> {B2}
</pre>

<p>There are no further results, because <code>nr</code> is a unique key:

<pre>
ap: (search @@@)  # '@@@' refers to the third-last REPL result, the query
-> NIL
</pre>


<h3><a id="crit">Search Criteria</a></h3>

<p>Each criterion is an attribute of a database object (like name, e-mail,
address etc.), or some given database object. It may be used to find objects
directly, or as a starting point for a recursive search for other objects
reachable by this object.

<p>For every search criterion which is <code>NIL</code>, no search is performed,
and the following relation specification is ignored. If, however, <i>all</i>
search criteria are <code>NIL</code>, a full search over the last relation
specification is forced.


<h4><a id="num">Numbers</a></h4>

<p>If the search criterion is numeric (including derived types like date or
time), it can be atomic for an exact search, or a cons pair for searching a
range of numbers.

<p>Extending the above example, we may search for all items with a number
between 2 and (including) 6:

<pre>
ap: (search (2 . 6) '((nr +Item)))
-> (NIL ...
</pre>

<p>We may use a <a href="refF.html#for">for</a> loop to retrieve all results:

<pre>
ap: (for
   (Q
      (search (2 . 6) '((nr +Item)))
      (search Q) )
   (printsp @) )
{B2} {B3} {B4} {B5} {B6}
</pre>


<h4><a id="str">Strings</a></h4>

<p>If the search criterion is a string (transient symbol) or an internal symbol,
or a cons pair of those, the exact behavior depends on the relation type. It
includes all cases where it matches the heads of the result attributes (string
prefixes), but may also match substrings and/or tolerant (<a
href="refF.html#fold">fold</a>ed or soundex-encoded) searches.

<pre>
ap: (for
   (Q
      (search "Api" '((em +CuSu)))
      (search Q) )
   (println (; @ em)) )
"info@api.tld"
</pre>

<pre>
ap: (for
   (Q
      (search "part" '((nm +Item)))
      (search Q) )
   (with @
      (println (: nr) (: nm)) ) )
1 "Main Part"
2 "Spare Part"
</pre>

<p>Or, combined with a number range search:

<pre>
ap: (for
   (Q
      (search
         (2 . 6) '((nr +Item))
         "part" '((nm +Item)) )
      (search Q) )
   (with @
      (println (: nr) (: nm)) ) )
2 "Spare Part"
</pre>


<h4><a id="obj">Objects</a></h4>

<p>A database object can also be used as a search criterion. A cons pair (i.e. a
range) of objects makes no sense, because objects by themselves are not ordered.

<p>Searching for all items from a given supplier:

<pre>
ap: (for
   (Q
      (search '{C1} '((sup +Item)))
      (search Q) )
   (printsp @) )
{B1} {B3} {B5}
</pre>

<p>or for all positions in a given order:

<pre>
ap: (for
   (Q
      (search '{B7} '((ord . pos)))
      (search Q) )
   (printsp @) )
{A1} {A2} {A4} {A3} ...
</pre>


<h3><a id="spec"></a>Relation Specifications</h3>

<p>Every second argument to <code>search</code> is a list of relation
specifications. In typical use cases, a relation specification is either

<ul>
<li>a list <code>(var cls [hook])</code> for an index relation, or
<li> a cons pair <code>(sym . sym)</code> for a <code><a href="refJ.html#+Joint">+Joint</a></code> relation
</ul>

<p>For general cases, the <i>first</i> specification in the list may be replaced
by two custom functions: A generator function and a filter function. This allows
to start the search from arbitrary resources like remote databases or
coroutines.

<p>If a specification is <code>(var cls)</code> but <code>var</code> is not an
index of <code>cls</code>, a brute force search through the objects in the
database file of <code>cls</code> will be performed. This should only be done
for small files with ideally all objects of type <code>cls</code>.

<p>The rest of the list contains associations (which are also relation
specifications) to recursively search through associated objects. They are
typically <code>(<a href="refJ.html#+Joint">+Joint</a>)</code>, <code>(<a
href="refL.html#+List">+List</a> <a href="refJ.html#+Joint">+Joint</a>)</code>,
or <code>(<a href="refR.html#+Ref">+Ref</a> <a
href="refL.html#+Link">+Link</a>)</code> relations.

<p>Look for example at the <code>choOrd</code> ("choose Order") function in the
demo application. You can access it directly in the REPL with <code>(vi
'choOrd)</code>. The <code>search</code> call is

<pre>
(search
   *OrdCus '((nm +CuSu) (cus +Ord))
   *OrdOrt '((ort +CuSu) (cus +Ord))
   *OrdItem '((nm +Item) (itm +Pos) (pos . ord))
   *OrdSup '((nm +CuSu) (sup +Item) (itm +Pos) (pos . ord))
   (and *OrdNr (cons @)) '((nr +Ord))
   (and *OrdDat (cons @)) '((dat +Ord)) )
</pre>

<p>The global variables <code>*OrdCus</code> through <code>*OrdDat</code> hold
the search criteria from the search fields in the dialog GUI.

<p>The line with the longest chain of associations is:

<pre>
   *OrdSup '((nm +CuSu) (sup +Item) (itm +Pos) (pos . ord))
</pre>

<p>This means:

<ol>
<li>Search suppliers by name
<li>For each matching supplier, go through his items
<li>For each item, find order positions where it is referred
<li>For each position, return the order where it is in
</ol>

<p>Testing this line stand-alone, searching orders only by supplier name:

<pre>
ap: (for
   (Q
      (search
         "Seven Oaks"
         (quote
            (nm +CuSu)
            (sup +Item)
            (itm +Pos)
            (pos . ord) ) )
      (search Q) )
   (printsp @) )
{B7}
</pre>

<h2><a id="custom"></a>Custom Generators and Filters</h2>

<p>If the list of relation specifications does not start with an index relation
<code>(var cls)</code> or a joint relation <code>(sym . sym)</code>, but instead
with a function like <code>((X) (foo))</code>, the first <i>two</i> elements of
the list are taken as generator and filter functions, respectively.

<p>We could rewrite the last example in a slightly simplified form, but with
custom functions:

<pre>
ap: (for
   (Q
      (search
         "Seven Oaks"
         (quote
            ((X)  # Generator function
               (iter> (meta '(+CuSu) 'nm)
                  "Seven Oaks"
                  '(nm +CuSu) ) )
            ((This X)  # Filter function
               (pre? "Seven Oaks" (: nm)) )
            (sup +Item)
            (itm +Pos)
            (pos . ord) ) )
      (search Q) )
   (printsp @) )
{B7}
</pre>

<p>The <code>iter&gt;</code> method implements the mechanisms to produce the
internal query structures for individual relations. There is a convenience
function <code>relQ</code> for this. It can be used to simplify such standard
generators. Instead of:

<pre>
   ((X)  # Generator function
      (iter> (meta '(+CuSu) 'nm)
         "Seven Oaks"
         '(nm +CuSu) ) )
</pre>

<p>we can write:

<pre>
   ((X)  # Generator function
      (relQ "Seven Oaks" '(nm +CuSu)) )
</pre>


<h3><a id="relQs">Multiple Indexes</a></h3>

<p>While <code>relQ</code> is normally not used directly by application
programs, because its functionality is provided by the standard relation
specification syntax, there is a function <code>relQs</code> which is indeed
useful.

<p><code>relQs</code> produces proper generator and filter functions which can
search <i>multiple</i> indexes for a singe search criterion.

<p>The general syntax is:

<pre>
   (relQs '((var1 +Cls1) (var2 +Cls2) ..) (sym1 ..) (sym2 ..)..)
</pre>

<p>to search first the index <code>(var1 +Cls1)</code>, then <code>(var2
+Cls2)</code> etc., and then follow the optional associations <code>(sym1
..)</code>, <code>(sym2 ..)</code> etc.

<p>A typical use case is searching for a telephone number in both the landline
and mobile attributes. You find an example in the <code>choCuSu</code> ("choose
Customer/Supplier") function in the demo application, in the line:

<pre>
   *CuSuTel (relQs '((tel +CuSu) (mob +CuSu)))
</pre>

<p>Note that <code>(relQs ..)</code> must <i>not</i> be quoted, because it needs
to be evaluate to produce the right functions and query structure.


<h2><a id="extract"></a>Extraction Function</h2>

<p>Sometimes it is necessary to to do further checks on a search result, which
may not be covered by the standard matching of combined search criteria.

<p>An example can be found in the <a
href="https://software-lab.de/tut.tgz">tut.tgz</a> tutorial in the file
"db/family.l", in the <code>contemporaries</code> report. It searches for people
living roughly at the same time as the given person:

<pre>
   '(let @Fin
      (or
         (: home obj fin)
         (+ (: home obj dat) 36525) )
      (search
         (cons (- (: home obj dat) 36525) @Fin) '((dat +Person))
         (curry (@Fin) (Obj)
            (and
               (n== Obj (: home obj))
               (>= (; Obj fin) (: home obj dat))
               (>= @Fin (; Obj dat))
               Obj ) ) ) )
</pre>

<p><code>@Fin</code> is set to either the date when the given person died, or to
the birth date plus ten years if not known. Then all persons born in the range
of ten years before the given person and <code>@Fin</code> are searched.

<p><a href="refC.html#curry">curry</a> builds the filter function, taking an
object and doing range checks to see if that person died after the given person
was born, <i>and</i> that he or she was born before <code>@Fin</code>.

<p>If those conditions are not met, the function returns <code>NIL</code>, and
<code>search</code> continues to search for the next result.

<p>The extraction function may also - instead of returning the object or
<code>NIL</code>, return some other value as appropriate for the application.


<h2><a id="sort"></a>Sorting</h2>

<p>In general, the values returned by <code>search</code> are not sorted when
multiple search criteria are given. This is because the indexes are iterated
over in an unpredictable order.

<p>If, however, only a single search criterion is given, or only one of the
search criteria is non-<code>NIL</code>, then the results will be returned in
sorted order according to the given index.

<p>If <i>all</i> search criteria are <code>NIL</code>, and thus the last
relation specification is used (see above under <a href="#crit">Search
Criteria</a>), then the results will turn up in increasing order.

</body>
</html>