1 /* valsort.c - sort attribute values */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5 * Copyright 2005 The OpenLDAP Foundation.
6 * Portions copyright 2005 Symas Corporation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted only as authorized by the OpenLDAP
13 * A copy of this license is available in the file LICENSE in the
14 * top-level directory of the distribution or, alternatively, at
15 * <http://www.OpenLDAP.org/license.html>.
18 * This work was initially developed by Howard Chu for inclusion in
19 * OpenLDAP Software. This work was sponsored by Stanford University.
23 * This overlay sorts the values of multi-valued attributes when returning
24 * them in a search response.
28 #ifdef SLAPD_OVER_VALSORT
32 #include <ac/string.h>
39 #define VALSORT_ASCEND 0
40 #define VALSORT_DESCEND 1
42 #define VALSORT_ALPHA 2
43 #define VALSORT_NUMERIC 4
45 #define VALSORT_WEIGHTED 8
47 typedef struct valsort_info {
48 struct valsort_info *vi_next;
50 AttributeDescription *vi_ad;
54 static ConfigDriver valsort_cf_func;
56 static ConfigTable valsort_cfats[] = {
57 { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC,
58 valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' "
59 "DESC 'Sorting rule for attribute under given DN' "
60 "EQUALITY caseIgnoreMatch "
61 "SYNTAX OMsDirectoryString )", NULL, NULL },
65 static ConfigOCs valsort_cfocs[] = {
67 "NAME 'olcValSortConfig' "
68 "DESC 'Value Sorting configuration' "
69 "SUP olcOverlayConfig "
70 "MUST olcValSortAttr )",
71 Cft_Overlay, valsort_cfats },
75 static slap_verbmasks sorts[] = {
76 { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA },
77 { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA },
78 { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC },
79 { BER_BVC("numeric-ascend"), VALSORT_DESCEND|VALSORT_NUMERIC },
80 { BER_BVC("weighted"), VALSORT_WEIGHTED },
85 valsort_cf_func(ConfigArgs *c) {
86 slap_overinst *on = (slap_overinst *)c->bi;
87 valsort_info vitmp, *vi;
88 const char *text = NULL;
90 struct berval bv = BER_BVNULL;
92 if ( c->op == SLAP_CONFIG_EMIT ) {
93 for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) {
94 struct berval bv2 = BER_BVNULL, bvret;
98 len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 3;
100 if ( i & VALSORT_WEIGHTED ) {
101 enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 );
102 len += bv2.bv_len + 1;
103 i ^= VALSORT_WEIGHTED;
105 enum_to_verb( sorts, i, &bv );
107 bvret.bv_val = ch_malloc( len+1 );
110 ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val );
113 ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val );
115 if ( vi->vi_sort & VALSORT_WEIGHTED ) {
117 ptr = lutil_strcopy( ptr, bv2.bv_val );
119 if ( !BER_BVISNULL( &bv )) {
121 strcpy( ptr, bv.bv_val );
123 ber_bvarray_add( &c->rvalue_vals, &bvret );
125 i = ( c->rvalue_vals != NULL ) ? 0 : 1;
127 } else if ( c->op == LDAP_MOD_DELETE ) {
129 for ( vi = on->on_bi.bi_private; vi; vi = c->be->be_private ) {
130 on->on_bi.bi_private = vi->vi_next;
131 ch_free( vi->vi_dn.bv_val );
137 for (i=0, prev = (valsort_info **)&on->on_bi.bi_private,
138 vi = *prev; vi && i<c->valx;
139 prev = &vi->vi_next, vi = vi->vi_next, i++ );
140 (*prev)->vi_next = vi->vi_next;
141 ch_free( vi->vi_dn.bv_val );
147 i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text );
149 sprintf( c->msg, "<%s> %s", c->argv[0], text );
150 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
151 c->log, c->msg, c->argv[1] );
154 if ( is_at_single_value( vitmp.vi_ad->ad_type )) {
155 sprintf( c->msg, "<%s> %s is single-valued, ignoring", c->argv[0],
156 vitmp.vi_ad->ad_cname.bv_val );
157 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
158 c->log, c->msg, c->argv[1] );
161 ber_str2bv( c->argv[2], 0, 0, &bv );
162 i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL );
164 sprintf( c->msg, "<%s> unable to normalize DN", c->argv[0] );
165 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
166 c->log, c->msg, c->argv[2] );
169 i = verb_to_mask( c->argv[3], sorts );
170 if ( BER_BVISNULL( &sorts[i].word )) {
171 sprintf( c->msg, "<%s> unrecognized sort type", c->argv[0] );
172 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
173 c->log, c->msg, c->argv[3] );
176 vitmp.vi_sort = sorts[i].mask;
177 if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) {
178 i = verb_to_mask( c->argv[4], sorts );
179 if ( BER_BVISNULL( &sorts[i].word )) {
180 sprintf( c->msg, "<%s> unrecognized sort type", c->argv[0] );
181 Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
182 c->log, c->msg, c->argv[4] );
185 vitmp.vi_sort |= sorts[i].mask;
187 vi = ch_malloc( sizeof(valsort_info) );
189 vi->vi_next = on->on_bi.bi_private;
190 on->on_bi.bi_private = vi;
194 /* Use Insertion Sort algorithm on selected values */
196 do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort )
199 struct berval tmp, ntmp, *vals, *nvals;
201 gotnvals = (a->a_vals != a->a_nvals );
203 nvals = a->a_nvals + beg;
205 vals = a->a_vals + beg;
207 if ( sort & VALSORT_NUMERIC ) {
208 long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ),
210 for (i=0; i<num; i++)
211 numbers[i] = strtol( nvals[i].bv_val, NULL, 0 );
213 for (i=1; i<num; i++) {
216 if ( gotnvals ) tmp = vals[i];
219 int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx :
222 numbers[j] = numbers[j-1];
223 nvals[j] = nvals[j-1];
224 if ( gotnvals ) vals[j] = vals[j-1];
229 if ( gotnvals ) vals[j] = tmp;
231 op->o_tmpfree( numbers, op->o_tmpmemctx );
233 for (i=1; i<num; i++) {
235 if ( gotnvals ) tmp = vals[i];
238 int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val );
239 cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0);
242 nvals[j] = nvals[j-1];
243 if ( gotnvals ) vals[j] = vals[j-1];
247 if ( gotnvals ) vals[j] = tmp;
253 valsort_response( Operation *op, SlapReply *rs )
259 /* We only want search responses */
260 if ( rs->sr_type != REP_SEARCH ) return SLAP_CB_CONTINUE;
262 on = (slap_overinst *) op->o_bd->bd_info;
263 vi = on->on_bi.bi_private;
265 /* And we must have something configured */
266 if ( !vi ) return SLAP_CB_CONTINUE;
268 /* Find a rule whose baseDN matches this entry */
269 for (; vi; vi = vi->vi_next ) {
272 if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn ))
275 /* Find attr that this rule affects */
276 a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
279 if (( rs->sr_flags & ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) !=
280 ( REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED )) {
281 rs->sr_entry = entry_dup( rs->sr_entry );
282 rs->sr_flags |= REP_ENTRY_MODIFIABLE|REP_ENTRY_MUSTBEFREED;
283 a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
287 for ( n = 0; !BER_BVISNULL( &a->a_vals[n] ); n++ );
289 if ( vi->vi_sort & VALSORT_WEIGHTED ) {
291 long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx );
293 gotnvals = (a->a_vals != a->a_nvals );
295 for (i=0; i<n; i++) {
296 char *ptr = strchr( a->a_nvals[i].bv_val, '{' );
299 Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s "
300 "in entry %s\n", vi->vi_ad->ad_cname.bv_val,
301 rs->sr_entry->e_name.bv_val, 0 );
304 index[i] = strtol( ptr+1, &end, 0 );
306 Debug(LDAP_DEBUG_TRACE, "weights misformatted "
308 rs->sr_entry->e_name.bv_val, 0, 0 );
311 /* Strip out weights */
312 ptr = a->a_nvals[i].bv_val;
317 a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val;
319 if ( a->a_vals != a->a_nvals ) {
320 ptr = a->a_vals[i].bv_val;
321 end = strchr( ptr, '}' ) + 1;
325 a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val;
328 /* An attr was missing weights here, ignore it */
330 op->o_tmpfree( index, op->o_tmpmemctx );
334 for ( i=1; i<n; i++) {
336 struct berval tmp = a->a_vals[i], ntmp;
337 if ( gotnvals ) ntmp = a->a_nvals[i];
339 while (( j>0 ) && (index[j-1] > idx )) {
340 index[j] = index[j-1];
341 a->a_vals[j] = a->a_vals[j-1];
342 if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
347 if ( gotnvals ) a->a_nvals[j] = ntmp;
349 /* Check for secondary sort */
350 if ( vi->vi_sort ^ VALSORT_WEIGHTED ) {
352 for (j=i+1; j<n; j++) {
353 if (index[i] != index[j])
357 do_sort( op, a, i, j-i, vi->vi_sort );
361 op->o_tmpfree( index, op->o_tmpmemctx );
363 do_sort( op, a, 0, n, vi->vi_sort );
366 return SLAP_CB_CONTINUE;
370 valsort_add( Operation *op, SlapReply *rs )
372 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
373 valsort_info *vi = on->on_bi.bi_private;
379 /* See if any weighted sorting applies to this entry */
380 for ( ;vi;vi=vi->vi_next ) {
381 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
383 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
385 a = attr_find( op->ora_e->e_attrs, vi->vi_ad );
388 for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) {
389 ptr = strchr(a->a_vals[i].bv_val, '{' );
391 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
392 vi->vi_ad->ad_cname.bv_val, 0, 0);
393 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
394 "weight missing from attribute" );
397 strtol( ptr+1, &end, 0 );
399 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
400 vi->vi_ad->ad_cname.bv_val, 0, 0);
401 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
402 "weight is misformatted" );
407 return SLAP_CB_CONTINUE;
411 valsort_modify( Operation *op, SlapReply *rs )
413 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
414 valsort_info *vi = on->on_bi.bi_private;
420 /* See if any weighted sorting applies to this entry */
421 for ( ;vi;vi=vi->vi_next ) {
422 if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
424 if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
426 for (ml = op->orm_modlist; ml; ml=ml->sml_next ) {
427 if ( ml->sml_desc == vi->vi_ad )
432 for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) {
433 ptr = strchr(ml->sml_values[i].bv_val, '{' );
435 Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
436 vi->vi_ad->ad_cname.bv_val, 0, 0);
437 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
438 "weight missing from attribute" );
441 strtol( ptr+1, &end, 0 );
443 Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
444 vi->vi_ad->ad_cname.bv_val, 0, 0);
445 send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
446 "weight is misformatted" );
451 return SLAP_CB_CONTINUE;
459 slap_overinst *on = (slap_overinst *)be->bd_info;
460 valsort_info *vi = on->on_bi.bi_private, *next;
462 for (; vi; vi = next) {
464 ch_free( vi->vi_dn.bv_val );
471 static slap_overinst valsort;
477 valsort.on_bi.bi_type = "valsort";
478 valsort.on_bi.bi_db_destroy = valsort_destroy;
480 valsort.on_bi.bi_op_add = valsort_add;
481 valsort.on_bi.bi_op_modify = valsort_modify;
483 valsort.on_response = valsort_response;
485 valsort.on_bi.bi_cf_ocs = valsort_cfocs;
487 rc = config_register_schema( valsort_cfats, valsort_cfocs );
490 return overlay_register(&valsort);
493 #if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
494 int init_module( int argc, char *argv[]) {
495 return valsort_init();
499 #endif /* SLAPD_OVER_VALSORT */