]> git.sur5r.net Git - openldap/blob - servers/slapd/back-wt/filterindex.c
Happy New Year
[openldap] / servers / slapd / back-wt / filterindex.c
1 /* OpenLDAP WiredTiger backend */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 2002-2018 The OpenLDAP Foundation.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16 /* ACKNOWLEDGEMENTS:
17  * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
18  * based on back-bdb for inclusion in OpenLDAP Software.
19  * WiredTiger is a product of MongoDB Inc.
20  */
21
22 #include "portable.h"
23
24 #include <stdio.h>
25 #include <ac/string.h>
26 #include "back-wt.h"
27 #include "idl.h"
28
29 static int
30 presence_candidates(
31         Operation *op,
32         wt_ctx *wc,
33         AttributeDescription *desc,
34         ID *ids )
35 {
36         struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
37         slap_mask_t mask;
38         struct berval prefix = {0, NULL};
39         int rc;
40         WT_CURSOR *cursor = NULL;
41
42         Debug( LDAP_DEBUG_TRACE, "=> wt_presence_candidates (%s)\n",
43                    desc->ad_cname.bv_val, 0, 0 );
44
45         WT_IDL_ALL( wi, ids );
46
47         if( desc == slap_schema.si_ad_objectClass ) {
48                 return 0;
49         }
50
51         rc = wt_index_param( op->o_bd, desc, LDAP_FILTER_PRESENT,
52                                                  &mask, &prefix );
53
54         if( rc == LDAP_INAPPROPRIATE_MATCHING ) {
55                 /* not indexed */
56                 Debug( LDAP_DEBUG_TRACE,
57                            "<= wt_presence_candidates: (%s) not indexed\n",
58                            desc->ad_cname.bv_val, 0, 0 );
59                 return 0;
60         }
61
62         if( rc != LDAP_SUCCESS ) {
63                 Debug( LDAP_DEBUG_TRACE,
64                            "<= wt_presence_candidates: (%s) index_param "
65                            "returned=%d\n",
66                            desc->ad_cname.bv_val, rc, 0 );
67                 return 0;
68         }
69
70         if( prefix.bv_val == NULL ) {
71                 Debug( LDAP_DEBUG_TRACE,
72                            "<= wt_presence_candidates: (%s) no prefix\n",
73                            desc->ad_cname.bv_val, 0, 0 );
74                 return -1;
75         }
76
77         /* open index cursor */
78         cursor = wt_ctx_index_cursor(wc, &desc->ad_type->sat_cname, 0);
79         if( !cursor ) {
80                 Debug( LDAP_DEBUG_ANY,
81                            "<= wt_presence_candidates: open index cursor failed: %s\n",
82                            desc->ad_type->sat_cname.bv_val, 0, 0 );
83                 return 0;
84         }
85
86         rc = wt_key_read( op->o_bd, cursor, &prefix, ids, NULL, 0 );
87
88         if(cursor){
89                 cursor->close(cursor);
90         }
91         Debug(LDAP_DEBUG_TRACE,
92                   "<= wt_presence_candidates: id=%ld first=%ld last=%ld\n",
93                   (long) ids[0],
94                   (long) WT_IDL_FIRST(ids),
95                   (long) WT_IDL_LAST(ids) );
96
97         return 0;
98 }
99
100 static int
101 equality_candidates(
102         Operation *op,
103         wt_ctx *wc,
104         AttributeAssertion *ava,
105         ID *ids,
106         ID *tmp)
107 {
108         struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
109         slap_mask_t mask;
110         struct berval prefix = {0, NULL};
111         struct berval *keys = NULL;
112         int i;
113         int rc;
114         MatchingRule *mr;
115         WT_CURSOR *cursor = NULL;
116
117         Debug( LDAP_DEBUG_TRACE, "=> wt_equality_candidates (%s)\n",
118                    ava->aa_desc->ad_cname.bv_val, 0, 0 );
119
120         if ( ava->aa_desc == slap_schema.si_ad_entryDN ) {
121                 ID id = NOID;
122                 rc = wt_dn2id(op, wc->session, &ava->aa_value, &id);
123                 if( rc == 0 ){
124                         wt_idl_append_one(ids, id);
125                 }else if ( rc == WT_NOTFOUND ) {
126                         WT_IDL_ZERO( ids );
127                         rc = 0;
128                 }
129                 return rc;
130         }
131
132         WT_IDL_ALL( wi, ids );
133
134         rc = wt_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_EQUALITY,
135                                                  &mask, &prefix );
136
137         if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
138                 Debug( LDAP_DEBUG_ANY,
139                            "<= wt_equality_candidates: (%s) not indexed\n",
140                            ava->aa_desc->ad_cname.bv_val, 0, 0 );
141                 return 0;
142         }
143
144         if( rc != LDAP_SUCCESS ) {
145                 Debug( LDAP_DEBUG_ANY,
146                            "<= wt_equality_candidates: (%s) index_param failed (%d)\n",
147                            ava->aa_desc->ad_cname.bv_val, rc, 0 );
148                 return 0;
149         }
150
151         mr = ava->aa_desc->ad_type->sat_equality;
152         if( !mr ) {
153                 return 0;
154         }
155
156         if( !mr->smr_filter ) {
157                 return 0;
158         }
159
160         rc = (mr->smr_filter)(
161                 LDAP_FILTER_EQUALITY,
162                 mask,
163                 ava->aa_desc->ad_type->sat_syntax,
164                 mr,
165                 &prefix,
166                 &ava->aa_value,
167                 &keys, op->o_tmpmemctx );
168
169         if( rc != LDAP_SUCCESS ) {
170                 Debug( LDAP_DEBUG_TRACE,
171                            "<= wt_equality_candidates: (%s, %s) "
172                            "MR filter failed (%d)\n",
173                            prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
174                 return 0;
175         }
176
177         if( keys == NULL ) {
178                 Debug( LDAP_DEBUG_TRACE,
179                            "<= wt_equality_candidates: (%s) no keys\n",
180                            ava->aa_desc->ad_cname.bv_val, 0, 0 );
181                 return 0;
182         }
183
184         /* open index cursor */
185         cursor = wt_ctx_index_cursor(wc, &ava->aa_desc->ad_type->sat_cname, 0);
186         if( !cursor ) {
187                 Debug( LDAP_DEBUG_ANY,
188                            "<= wt_equality_candidates: open index cursor failed: %s\n",
189                            ava->aa_desc->ad_type->sat_cname.bv_val, 0, 0 );
190                 return 0;
191         }
192
193         for ( i= 0; keys[i].bv_val != NULL; i++ ) {
194                 rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
195                 if( rc == WT_NOTFOUND ) {
196                         WT_IDL_ZERO( ids );
197                         rc = 0;
198                         break;
199                 } else if( rc != LDAP_SUCCESS ) {
200                         Debug( LDAP_DEBUG_TRACE,
201                                    "<= wt_equality_candidates: (%s) "
202                                    "key read failed (%d)\n",
203                                    ava->aa_desc->ad_cname.bv_val, rc, 0 );
204                         break;
205                 }
206                 if ( i == 0 ) {
207                         WT_IDL_CPY( ids, tmp );
208                 } else {
209                         wt_idl_intersection( ids, tmp );
210                 }
211
212                 if( WT_IDL_IS_ZERO( ids ) )
213                         break;
214         }
215
216         ber_bvarray_free_x( keys, op->o_tmpmemctx );
217
218         if(cursor){
219                 cursor->close(cursor);
220         }
221
222         Debug( LDAP_DEBUG_TRACE,
223                    "<= wt_equality_candidates: id=%ld, first=%ld, last=%ld\n",
224                    (long) ids[0],
225                    (long) WT_IDL_FIRST(ids),
226                    (long) WT_IDL_LAST(ids) );
227
228         return rc;
229 }
230
231 static int
232 approx_candidates(
233         Operation *op,
234         wt_ctx *wc,
235         AttributeAssertion *ava,
236         ID *ids,
237         ID *tmp )
238 {
239         struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
240         int i;
241         int rc;
242     slap_mask_t mask;
243         struct berval prefix = {0, NULL};
244         struct berval *keys = NULL;
245         MatchingRule *mr;
246         WT_CURSOR *cursor = NULL;
247
248         Debug( LDAP_DEBUG_TRACE, "=> wt_approx_candidates (%s)\n",
249                    ava->aa_desc->ad_cname.bv_val, 0, 0 );
250
251         WT_IDL_ALL( wi, ids );
252
253         rc = wt_index_param( op->o_bd, ava->aa_desc, LDAP_FILTER_APPROX,
254                                                  &mask, &prefix );
255
256         if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
257                 Debug( LDAP_DEBUG_ANY,
258                            "<= wt_approx_candidates: (%s) not indexed\n",
259                            ava->aa_desc->ad_cname.bv_val, 0, 0 );
260                 return 0;
261         }
262
263         if( rc != LDAP_SUCCESS ) {
264                 Debug( LDAP_DEBUG_ANY,
265                            "<= wt_approx_candidates: (%s) index_param failed (%d)\n",
266                            ava->aa_desc->ad_cname.bv_val, rc, 0 );
267                 return 0;
268         }
269
270         mr = ava->aa_desc->ad_type->sat_approx;
271         if( !mr ) {
272                 /* no approx matching rule, try equality matching rule */
273                 mr = ava->aa_desc->ad_type->sat_equality;
274         }
275
276         if( !mr ) {
277                 return 0;
278         }
279
280         if( !mr->smr_filter ) {
281                 return 0;
282         }
283
284         rc = (mr->smr_filter)(
285                 LDAP_FILTER_APPROX,
286                 mask,
287                 ava->aa_desc->ad_type->sat_syntax,
288                 mr,
289                 &prefix,
290                 &ava->aa_value,
291                 &keys, op->o_tmpmemctx );
292
293         if( rc != LDAP_SUCCESS ) {
294                 Debug( LDAP_DEBUG_TRACE,
295                            "<= wt_approx_candidates: (%s, %s) MR filter failed (%d)\n",
296                            prefix.bv_val, ava->aa_desc->ad_cname.bv_val, rc );
297                 return 0;
298         }
299
300         if( keys == NULL ) {
301                 Debug( LDAP_DEBUG_TRACE,
302                            "<= wt_approx_candidates: (%s) no keys (%s)\n",
303                            prefix.bv_val, ava->aa_desc->ad_cname.bv_val, 0 );
304                 return 0;
305         }
306
307         /* open index cursor */
308         cursor = wt_ctx_index_cursor(wc, &ava->aa_desc->ad_type->sat_cname, 0);
309         if( !cursor ) {
310                 Debug( LDAP_DEBUG_ANY,
311                            "<= wt_approx_candidates: open index cursor failed: %s\n",
312                            ava->aa_desc->ad_type->sat_cname.bv_val, 0, 0 );
313                 return 0;
314         }
315
316         for ( i= 0; keys[i].bv_val != NULL; i++ ) {
317                 rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
318                 if( rc == WT_NOTFOUND ) {
319                         WT_IDL_ZERO( ids );
320                         rc = 0;
321                         break;
322                 } else if( rc != LDAP_SUCCESS ) {
323                         Debug( LDAP_DEBUG_TRACE,
324                                    "<= wt_approx_candidates: (%s) key read failed (%d)\n",
325                                    ava->aa_desc->ad_cname.bv_val, rc, 0 );
326                         break;
327                 }
328
329                 if( WT_IDL_IS_ZERO( tmp ) ) {
330                         Debug( LDAP_DEBUG_TRACE,
331                                    "<= wt_approx_candidates: (%s) NULL\n",
332                                    ava->aa_desc->ad_cname.bv_val, 0, 0 );
333                         WT_IDL_ZERO( ids );
334                         break;
335                 }
336
337                 if ( i == 0 ) {
338                         WT_IDL_CPY( ids, tmp );
339                 } else {
340                         wt_idl_intersection( ids, tmp );
341                 }
342
343                 if( WT_IDL_IS_ZERO( ids ) )
344                         break;
345         }
346
347         ber_bvarray_free_x( keys, op->o_tmpmemctx );
348
349         if(cursor){
350                 cursor->close(cursor);
351         }
352
353         Debug( LDAP_DEBUG_TRACE,
354                    "<= wt_approx_candidates %ld, first=%ld, last=%ld\n",
355                    (long) ids[0],
356                    (long) WT_IDL_FIRST(ids),
357                    (long) WT_IDL_LAST(ids) );
358
359         return rc;
360 }
361
362 static int
363 substring_candidates(
364         Operation *op,
365         wt_ctx *wc,
366         SubstringsAssertion *sub,
367         ID *ids,
368         ID *tmp )
369 {
370         struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
371         int i;
372         int rc;
373     slap_mask_t mask;
374         struct berval prefix = {0, NULL};
375         struct berval *keys = NULL;
376         MatchingRule *mr;
377         WT_CURSOR *cursor = NULL;
378
379         Debug( LDAP_DEBUG_TRACE, "=> wt_substring_candidates (%s)\n",
380                    sub->sa_desc->ad_cname.bv_val, 0, 0 );
381
382         WT_IDL_ALL( wi, ids );
383
384         rc = wt_index_param( op->o_bd, sub->sa_desc, LDAP_FILTER_SUBSTRINGS,
385                                                  &mask, &prefix );
386
387         if ( rc == LDAP_INAPPROPRIATE_MATCHING ) {
388                 Debug( LDAP_DEBUG_ANY,
389                            "<= wt_substring_candidates: (%s) not indexed\n",
390                            sub->sa_desc->ad_cname.bv_val, 0, 0 );
391                 return 0;
392         }
393
394         if( rc != LDAP_SUCCESS ) {
395                 Debug( LDAP_DEBUG_ANY,
396                            "<= wt_substring_candidates: (%s) "
397                            "index_param failed (%d)\n",
398                            sub->sa_desc->ad_cname.bv_val, rc, 0 );
399                 return 0;
400         }
401
402         mr = sub->sa_desc->ad_type->sat_substr;
403
404         if( !mr ) {
405                 return 0;
406         }
407
408         if( !mr->smr_filter ) {
409                 return 0;
410         }
411
412         rc = (mr->smr_filter)(
413                 LDAP_FILTER_SUBSTRINGS,
414                 mask,
415                 sub->sa_desc->ad_type->sat_syntax,
416                 mr,
417                 &prefix,
418                 sub,
419                 &keys, op->o_tmpmemctx );
420
421         if( rc != LDAP_SUCCESS ) {
422                 Debug( LDAP_DEBUG_TRACE,
423                            "<= wt_substring_candidates: (%s) MR filter failed (%d)\n",
424                            sub->sa_desc->ad_cname.bv_val, rc, 0 );
425                 return 0;
426         }
427
428         if( keys == NULL ) {
429                 Debug( LDAP_DEBUG_TRACE,
430                            "<= wt_substring_candidates: (0x%04lx) no keys (%s)\n",
431                            mask, sub->sa_desc->ad_cname.bv_val, 0 );
432                 return 0;
433         }
434
435         /* open index cursor */
436         cursor = wt_ctx_index_cursor(wc, &sub->sa_desc->ad_cname, 0);
437         if( !cursor ) {
438                 Debug( LDAP_DEBUG_ANY,
439                            "<= wt_substring_candidates: open index cursor failed: %s\n",
440                            sub->sa_desc->ad_cname.bv_val, 0, 0 );
441                 return 0;
442         }
443
444         for ( i= 0; keys[i].bv_val != NULL; i++ ) {
445                 rc = wt_key_read( op->o_bd, cursor, &keys[i], tmp, NULL, 0 );
446
447                 if( rc == WT_NOTFOUND ) {
448                         WT_IDL_ZERO( ids );
449                         rc = 0;
450                         break;
451                 } else if( rc != LDAP_SUCCESS ) {
452                         Debug( LDAP_DEBUG_TRACE,
453                                    "<= wt_substring_candidates: (%s) key read failed (%d)\n",
454                                    sub->sa_desc->ad_cname.bv_val, rc, 0 );
455                         break;
456                 }
457
458                 if( WT_IDL_IS_ZERO( tmp ) ) {
459                         Debug( LDAP_DEBUG_TRACE,
460                                    "<= wt_substring_candidates: (%s) NULL\n",
461                                    sub->sa_desc->ad_cname.bv_val, 0, 0 );
462                         WT_IDL_ZERO( ids );
463                         break;
464                 }
465
466                 if ( i == 0 ) {
467                         WT_IDL_CPY( ids, tmp );
468                 } else {
469                         wt_idl_intersection( ids, tmp );
470                 }
471
472                 if( WT_IDL_IS_ZERO( ids ) )
473                         break;
474         }
475
476         ber_bvarray_free_x( keys, op->o_tmpmemctx );
477
478         if(cursor){
479                 cursor->close(cursor);
480         }
481
482         Debug( LDAP_DEBUG_TRACE,
483                    "<= wt_substring_candidates: %ld, first=%ld, last=%ld\n",
484                    (long) ids[0],
485                    (long) WT_IDL_FIRST(ids),
486                    (long) WT_IDL_LAST(ids));
487         return rc;
488 }
489
490
491 static int
492 list_candidates(
493         Operation *op,
494         wt_ctx *wc,
495         Filter *flist,
496         int ftype,
497         ID *ids,
498         ID *tmp,
499         ID *save )
500 {
501         int rc = 0;
502         Filter  *f;
503
504         Debug( LDAP_DEBUG_FILTER, "=> wt_list_candidates 0x%x\n", ftype, 0, 0 );
505         for ( f = flist; f != NULL; f = f->f_next ) {
506                 /* ignore precomputed scopes */
507                 if ( f->f_choice == SLAPD_FILTER_COMPUTED &&
508                          f->f_result == LDAP_SUCCESS ) {
509                         continue;
510                 }
511                 WT_IDL_ZERO( save );
512                 rc = wt_filter_candidates( op, wc, f, save, tmp,
513                                                                    save+WT_IDL_UM_SIZE );
514
515                 if ( rc != 0 ) {
516                         /* TODO: error handling */
517                         /*
518                         if ( rc == DB_LOCK_DEADLOCK )
519                                 return rc;
520                         */
521                         if ( ftype == LDAP_FILTER_AND ) {
522                                 rc = 0;
523                                 continue;
524                         }
525                         break;
526                 }
527
528
529                 if ( ftype == LDAP_FILTER_AND ) {
530                         if ( f == flist ) {
531                                 WT_IDL_CPY( ids, save );
532                         } else {
533                                 wt_idl_intersection( ids, save );
534                         }
535                         if( WT_IDL_IS_ZERO( ids ) )
536                                 break;
537                 } else {
538                         if ( f == flist ) {
539                                 WT_IDL_CPY( ids, save );
540                         } else {
541                                 wt_idl_union( ids, save );
542                         }
543                 }
544         }
545
546         if( rc == LDAP_SUCCESS ) {
547                 Debug( LDAP_DEBUG_FILTER,
548                            "<= wt_list_candidates: id=%ld first=%ld last=%ld\n",
549                            (long) ids[0],
550                            (long) WT_IDL_FIRST(ids),
551                            (long) WT_IDL_LAST(ids) );
552
553         } else {
554                 Debug( LDAP_DEBUG_FILTER,
555                            "<= wt_list_candidates: undefined rc=%d\n",
556                            rc, 0, 0 );
557         }
558
559         return 0;
560 }
561
562 int
563 wt_filter_candidates(
564         Operation *op,
565         wt_ctx *wc,
566         Filter *f,
567         ID *ids,
568         ID *tmp,
569         ID *stack )
570 {
571         struct wt_info *wi = (struct wt_info *)op->o_bd->be_private;
572         int rc = 0;
573         Debug( LDAP_DEBUG_FILTER, "=> wt_filter_candidates\n", 0, 0, 0 );
574
575         if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
576                 WT_IDL_ZERO( ids );
577                 goto done;
578         }
579
580         switch ( f->f_choice ) {
581         case SLAPD_FILTER_COMPUTED:
582                 switch( f->f_result ) {
583                 case SLAPD_COMPARE_UNDEFINED:
584                         /* This technically is not the same as FALSE, but it
585                          * certainly will produce no matches.
586                          */
587                         /* FALL THRU */
588                 case LDAP_COMPARE_FALSE:
589                         WT_IDL_ZERO( ids );
590                         break;
591                 case LDAP_COMPARE_TRUE: {
592
593                         WT_IDL_ALL( wi, ids );
594                 } break;
595                 case LDAP_SUCCESS:
596                         /* this is a pre-computed scope, leave it alone */
597                         break;
598                 }
599                 break;
600         case LDAP_FILTER_PRESENT:
601                 Debug( LDAP_DEBUG_FILTER, "\tPRESENT\n", 0, 0, 0 );
602                 rc = presence_candidates( op, wc, f->f_desc, ids );
603                 break;
604
605         case LDAP_FILTER_EQUALITY:
606                 Debug( LDAP_DEBUG_FILTER, "\tEQUALITY\n", 0, 0, 0 );
607                 rc = equality_candidates( op, wc, f->f_ava, ids, tmp );
608                 break;
609
610         case LDAP_FILTER_APPROX:
611                 Debug( LDAP_DEBUG_FILTER, "\tAPPROX\n", 0, 0, 0 );
612                 rc = approx_candidates( op, wc, f->f_ava, ids, tmp );
613                 break;
614
615         case LDAP_FILTER_SUBSTRINGS:
616                 Debug( LDAP_DEBUG_FILTER, "\tSUBSTRINGS\n", 0, 0, 0 );
617                 rc = substring_candidates( op, wc, f->f_sub, ids, tmp );
618                 break;
619
620         case LDAP_FILTER_GE:
621                 /* if no GE index, use pres */
622                 /* TODO: not implement yet */
623                 rc = presence_candidates( op, wc, f->f_ava->aa_desc, ids );
624                 break;
625
626     case LDAP_FILTER_LE:
627                 /* if no LE index, use pres */
628                 /* TODO: not implement yet */
629                 Debug( LDAP_DEBUG_FILTER, "\tLE\n", 0, 0, 0 );
630                 rc = presence_candidates( op, wc, f->f_ava->aa_desc, ids );
631                 break;
632
633         case LDAP_FILTER_NOT:
634                 /* no indexing to support NOT filters */
635                 Debug( LDAP_DEBUG_FILTER, "\tNOT\n", 0, 0, 0 );
636                 WT_IDL_ALL( wi, ids );
637                 break;
638
639         case LDAP_FILTER_AND:
640                 Debug( LDAP_DEBUG_FILTER, "\tAND\n", 0, 0, 0 );
641                 rc = list_candidates( op, wc,
642                                                           f->f_and, LDAP_FILTER_AND, ids, tmp, stack );
643                 break;
644
645         case LDAP_FILTER_OR:
646                 Debug( LDAP_DEBUG_FILTER, "\tOR\n", 0, 0, 0 );
647                 rc = list_candidates( op, wc,
648                                                           f->f_or, LDAP_FILTER_OR, ids, tmp, stack );
649                 break;
650
651         case LDAP_FILTER_EXT:
652                 /* TODO: not implement yet */
653                 Debug( LDAP_DEBUG_FILTER, "\tEXT\n", 0, 0, 0 );
654                 rc = presence_candidates( op, wc, f->f_ava->aa_desc, ids );
655                 break;
656
657         default:
658                 Debug( LDAP_DEBUG_FILTER, "\tUNKNOWN %lu\n",
659                            (unsigned long) f->f_choice, 0, 0 );
660                 /* Must not return NULL, otherwise extended filters break */
661                 WT_IDL_ALL( wi, ids );
662         }
663
664 done:
665         Debug( LDAP_DEBUG_FILTER,
666                    "<= wt_filter_candidates: id=%ld first=%ld last=%ld\n",
667                    (long) ids[0],
668                    (long) WT_IDL_FIRST( ids ),
669                    (long) WT_IDL_LAST( ids ) );
670         return 0;
671 }
672
673 /*
674  * Local variables:
675  * indent-tabs-mode: t
676  * tab-width: 4
677  * c-basic-offset: 4
678  * End:
679  */