Generic ordered value support

This commit is contained in:
Howard Chu 2005-04-14 10:31:51 +00:00
parent 562916939e
commit d882ce511d
7 changed files with 399 additions and 194 deletions

View File

@ -504,56 +504,34 @@ slap_mods2entry(
if( mods->sml_values[1].bv_val != NULL ) {
/* check for duplicates */
int i, j;
int i, j, rc, match;
MatchingRule *mr = mods->sml_desc->ad_type->sat_equality;
/* check if the values we're adding already exist */
if( mr == NULL || !mr->smr_match ) {
for ( i = 1; mods->sml_values[i].bv_val != NULL; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
if ( bvmatch( &mods->sml_values[i],
&mods->sml_values[j] ) )
{
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
mods->sml_desc->ad_cname.bv_val, j );
return LDAP_TYPE_OR_VALUE_EXISTS;
}
}
}
for ( i = 1; mods->sml_values[i].bv_val != NULL; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
rc = ordered_value_match( &match, mods->sml_desc, mr,
SLAP_MR_EQUALITY
| SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
mods->sml_nvalues
? &mods->sml_nvalues[i]
: &mods->sml_values[i],
mods->sml_nvalues
? &mods->sml_nvalues[j]
: &mods->sml_values[j],
text );
} else {
int rc;
int match;
if ( rc == LDAP_SUCCESS && match == 0 ) {
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
mods->sml_desc->ad_cname.bv_val, j );
return LDAP_TYPE_OR_VALUE_EXISTS;
for ( i = 1; mods->sml_values[i].bv_val != NULL; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
rc = value_match( &match, mods->sml_desc, mr,
SLAP_MR_EQUALITY
| SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
mods->sml_nvalues
? &mods->sml_nvalues[i]
: &mods->sml_values[i],
mods->sml_nvalues
? &mods->sml_nvalues[j]
: &mods->sml_values[j],
text );
if ( rc == LDAP_SUCCESS && match == 0 ) {
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
mods->sml_desc->ad_cname.bv_val, j );
return LDAP_TYPE_OR_VALUE_EXISTS;
} else if ( rc != LDAP_SUCCESS ) {
return rc;
}
} else if ( rc != LDAP_SUCCESS ) {
return rc;
}
}
}

View File

@ -519,7 +519,9 @@ at_add(
if ( sat->sat_extensions ) {
for (i=0; sat->sat_extensions[i]; i++) {
if (!strcasecmp( sat->sat_extensions[i]->lsei_name,
"X-ORDERED" )) {
"X-ORDERED" ) && sat->sat_extensions[i]->lsei_values &&
!strcasecmp( sat->sat_extensions[i]->lsei_values[0],
"VALUES" )) {
sat->sat_flags |= SLAP_AT_ORDERED;
break;
}

View File

@ -2884,87 +2884,6 @@ config_find_table( CfOcInfo **colst, int nocs, AttributeDescription *ad )
return NULL;
}
/* Sort the values in an X-ORDERED VALUES attribute.
* If the values have no index, leave them in their given order.
* If the values have indexes, sort them.
* If some are indexed and some are not, return Error.
*
* FIXME: This function probably belongs in the frontend somewhere,
* like slap_mods_check.
*/
static int
sort_vals( Attribute *a )
{
int i;
int index = 0, noindex = 0;
/* count attrs, look for index */
for (i=0; a->a_vals[i].bv_val; i++) {
if ( a->a_vals[i].bv_val[0] == '{' ) {
char *ptr;
index = 1;
ptr = strchr( a->a_vals[i].bv_val, '}' );
if ( !ptr || !ptr[1] )
return LDAP_INVALID_SYNTAX;
if ( noindex )
return LDAP_INVALID_SYNTAX;
} else {
noindex = 1;
if ( index )
return LDAP_INVALID_SYNTAX;
}
}
if ( index ) {
int vals = i, *indexes, j, idx;
struct berval tmp, ntmp;
char *ptr;
#if 0
/* Strip index from normalized values */
if ( !a->a_nvals || a->a_vals == a->a_nvals ) {
a->a_nvals = ch_malloc( (vals+1)*sizeof(struct berval));
BER_BVZERO(a->a_nvals+vals);
for ( i=0; i<vals; i++ ) {
ptr = strchr(a->a_vals[i].bv_val, '}') + 1;
a->a_nvals[i].bv_len = a->a_vals[i].bv_len -
(ptr - a->a_vals[i].bv_val);
a->a_nvals[i].bv_val = ch_malloc( a->a_nvals[i].bv_len + 1);
strcpy(a->a_nvals[i].bv_val, ptr );
}
} else {
for ( i=0; i<vals; i++ ) {
ptr = strchr(a->a_nvals[i].bv_val, '}') + 1;
a->a_nvals[i].bv_len -= ptr - a->a_nvals[i].bv_val;
strcpy(a->a_nvals[i].bv_val, ptr);
}
}
#endif
indexes = ch_malloc( vals * sizeof(int) );
for ( i=0; i<vals; i++)
indexes[i] = atoi(a->a_vals[i].bv_val+1);
/* Insertion sort */
for ( i=1; i<vals; i++ ) {
idx = indexes[i];
tmp = a->a_vals[i];
ntmp = a->a_nvals[i];
j = i;
while ((j > 0) && (indexes[j-1] > idx)) {
indexes[j] = indexes[j-1];
a->a_vals[j] = a->a_vals[j-1];
a->a_nvals[j] = a->a_nvals[j-1];
j--;
}
indexes[j] = idx;
a->a_vals[j] = tmp;
a->a_nvals[j] = ntmp;
}
}
return 0;
}
/* Sort the attributes of the entry according to the order defined
* in the objectclass, with required attributes occurring before
* allowed attributes. For any attributes with sequencing dependencies
@ -3044,7 +2963,7 @@ check_vals( ConfigTable *ct, ConfigArgs *ca, void *ptr, int isAttr )
if ( a && ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
sort = 1;
rc = sort_vals( a );
rc = ordered_value_sort( a, 1 );
if ( rc )
return rc;
}

View File

@ -744,59 +744,37 @@ int slap_mods_check(
ml->sml_nvalues[nvals].bv_len = 0;
}
if( nvals ) {
/* check for duplicates */
int i, j;
/* check for duplicates, but ignore Deletes.
*/
if( nvals > 1 && ml->sml_op != LDAP_MOD_DELETE ) {
int i, j, rc, match;
MatchingRule *mr = ad->ad_type->sat_equality;
/* check if the values we're adding already exist */
if( mr == NULL || !mr->smr_match ) {
for ( i = 1; ml->sml_values[i].bv_val != NULL; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
if ( bvmatch( &ml->sml_values[i],
&ml->sml_values[j] ) )
{
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
ml->sml_desc->ad_cname.bv_val, j );
*text = textbuf;
return LDAP_TYPE_OR_VALUE_EXISTS;
}
}
}
for ( i = 1; i < nvals ; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
rc = ordered_value_match( &match, ml->sml_desc, mr,
SLAP_MR_EQUALITY
| SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
ml->sml_nvalues
? &ml->sml_nvalues[i]
: &ml->sml_values[i],
ml->sml_nvalues
? &ml->sml_nvalues[j]
: &ml->sml_values[j],
text );
if ( rc == LDAP_SUCCESS && match == 0 ) {
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
ml->sml_desc->ad_cname.bv_val, j );
*text = textbuf;
return LDAP_TYPE_OR_VALUE_EXISTS;
} else {
int rc;
int match;
for ( i = 1; ml->sml_values[i].bv_val != NULL; i++ ) {
/* test asserted values against themselves */
for( j = 0; j < i; j++ ) {
rc = value_match( &match, ml->sml_desc, mr,
SLAP_MR_EQUALITY
| SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
ml->sml_nvalues
? &ml->sml_nvalues[i]
: &ml->sml_values[i],
ml->sml_nvalues
? &ml->sml_nvalues[j]
: &ml->sml_values[j],
text );
if ( rc == LDAP_SUCCESS && match == 0 ) {
/* value exists already */
snprintf( textbuf, textlen,
"%s: value #%d provided more than once",
ml->sml_desc->ad_cname.bv_val, j );
*text = textbuf;
return LDAP_TYPE_OR_VALUE_EXISTS;
} else if ( rc != LDAP_SUCCESS ) {
return rc;
}
} else if ( rc != LDAP_SUCCESS ) {
return rc;
}
}
}

View File

@ -93,14 +93,14 @@ modify_add_values(
assert( a->a_vals[0].bv_val );
for ( j = 0; !BER_BVISNULL( &a->a_vals[j] ); j++ ) {
if ( mod->sm_nvalues ) {
rc = value_match( &match, mod->sm_desc, mr,
rc = ordered_value_match( &match, mod->sm_desc, mr,
SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
&a->a_nvals[j], &mod->sm_nvalues[i], text );
} else {
rc = value_match( &match, mod->sm_desc, mr,
SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
rc = ordered_value_match( &match, mod->sm_desc, mr,
SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
&a->a_vals[j], &mod->sm_values[i], text );
}
@ -143,7 +143,12 @@ modify_add_values(
}
/* no - add them */
rc = attr_merge( e, mod->sm_desc, pmod.sm_values, pmod.sm_nvalues );
if ( mod->sm_desc->ad_type->sat_flags & SLAP_AT_ORDERED ) {
rc = ordered_value_add( e, mod->sm_desc, a,
pmod.sm_values, pmod.sm_nvalues );
} else {
rc = attr_merge( e, mod->sm_desc, pmod.sm_values, pmod.sm_nvalues );
}
if ( a != NULL && permissive ) {
ch_free( pmod.sm_values );
@ -229,22 +234,18 @@ modify_delete_values(
if( mod->sm_nvalues ) {
assert( a->a_nvals );
rc = (*mr->smr_match)( &match,
SLAP_MR_VALUE_OF_ASSERTION_SYNTAX
rc = ordered_value_match( &match, a->a_desc, mr,
SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX
| SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH
| SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
a->a_desc->ad_type->sat_syntax,
mr, &a->a_nvals[j],
&mod->sm_nvalues[i] );
&a->a_nvals[j], &mod->sm_nvalues[i], text );
} else {
#if 0
assert( a->a_nvals == NULL );
#endif
rc = (*mr->smr_match)( &match,
SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
a->a_desc->ad_type->sat_syntax,
mr, &a->a_vals[j],
&mod->sm_values[i] );
rc = ordered_value_match( &match, a->a_desc, mr,
SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
&a->a_vals[j], &mod->sm_values[i], text );
}
if ( rc != LDAP_SUCCESS ) {
@ -290,7 +291,7 @@ modify_delete_values(
for ( k = 0, j = 0; !BER_BVISNULL( &a->a_vals[k] ); k++ ) {
/* skip dummies */
if( a->a_vals[k].bv_val == &dummy ) {
assert( a->a_nvals == NULL || a->a_nvals[k].bv_val == &dummy );
assert( a->a_nvals[k].bv_val == &dummy );
continue;
}
if ( j != k ) {
@ -317,6 +318,9 @@ modify_delete_values(
mod->sm_desc->ad_cname.bv_val );
rc = LDAP_NO_SUCH_ATTRIBUTE;
}
} else if ( a->a_desc->ad_type->sat_flags & SLAP_AT_ORDERED ) {
/* For an ordered attribute, renumber the value indices */
ordered_value_sort( a, 1 );
}
return_results:;

View File

@ -1421,6 +1421,30 @@ LDAP_SLAPD_F (int) value_find_ex LDAP_P((
struct berval *value,
void *ctx ));
LDAP_SLAPD_F (int) ordered_value_add LDAP_P((
Entry *e,
AttributeDescription *ad,
Attribute *a,
BerVarray vals,
BerVarray nvals ));
LDAP_SLAPD_F (int) ordered_value_match LDAP_P((
int *match,
AttributeDescription *ad,
MatchingRule *mr,
unsigned flags,
struct berval *v1,
struct berval *v2,
const char ** text ));
LDAP_SLAPD_F (void) ordered_value_renumber LDAP_P((
Attribute *a,
int vals ));
LDAP_SLAPD_F (int) ordered_value_sort LDAP_P((
Attribute *a,
int do_renumber ));
LDAP_SLAPD_F (int) value_add LDAP_P((
BerVarray *vals,
BerVarray addvals ));

View File

@ -256,3 +256,303 @@ int value_find_ex(
slap_sl_free( nval.bv_val, ctx );
return LDAP_NO_SUCH_ATTRIBUTE;
}
/* assign new indexes to an attribute's ordered values */
void
ordered_value_renumber( Attribute *a, int vals )
{
char *ptr, ibuf[64]; /* many digits */
struct berval ibv, tmp, vtmp;
int i;
ibv.bv_val = ibuf;
for (i=0; i<vals; i++) {
ibv.bv_len = sprintf(ibv.bv_val, "{%d}", i);
vtmp = a->a_vals[i];
if ( vtmp.bv_val[0] == '{' ) {
ptr = strchr(vtmp.bv_val, '}') + 1;
vtmp.bv_len -= ptr - vtmp.bv_val;
vtmp.bv_val = ptr;
}
tmp.bv_len = ibv.bv_len + vtmp.bv_len;
tmp.bv_val = ch_malloc( tmp.bv_len + 1 );
strcpy( tmp.bv_val, ibv.bv_val );
AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len );
tmp.bv_val[tmp.bv_len] = '\0';
ch_free( a->a_vals[i].bv_val );
a->a_vals[i] = tmp;
if ( a->a_nvals && a->a_nvals != a->a_vals ) {
vtmp = a->a_nvals[i];
if ( vtmp.bv_val[0] == '{' ) {
ptr = strchr(vtmp.bv_val, '}') + 1;
vtmp.bv_len -= ptr - vtmp.bv_val;
vtmp.bv_val = ptr;
}
tmp.bv_len = ibv.bv_len + vtmp.bv_len;
tmp.bv_val = ch_malloc( tmp.bv_len + 1 );
strcpy( tmp.bv_val, ibv.bv_val );
AC_MEMCPY( tmp.bv_val + ibv.bv_len, vtmp.bv_val, vtmp.bv_len );
tmp.bv_val[tmp.bv_len] = '\0';
ch_free( a->a_nvals[i].bv_val );
a->a_nvals[i] = tmp;
}
}
}
/* Sort the values in an X-ORDERED VALUES attribute.
* If the values have no index, index them in their given order.
* If the values have indexes, sort them.
* If some are indexed and some are not, return Error.
*/
int
ordered_value_sort( Attribute *a, int do_renumber )
{
int i, vals;
int index = 0, noindex = 0, renumber = 0, gotnvals = 0;
struct berval tmp;
char *ptr;
if ( a->a_nvals && a->a_nvals != a->a_vals )
gotnvals = 1;
/* count attrs, look for index */
for (i=0; a->a_vals[i].bv_val; i++) {
if ( a->a_vals[i].bv_val[0] == '{' ) {
char *ptr;
index = 1;
ptr = strchr( a->a_vals[i].bv_val, '}' );
if ( !ptr || !ptr[1] )
return LDAP_INVALID_SYNTAX;
if ( noindex )
return LDAP_INVALID_SYNTAX;
} else {
noindex = 1;
if ( index )
return LDAP_INVALID_SYNTAX;
}
}
vals = i;
/* If values have indexes, sort the values */
if ( index ) {
int *indexes, j, idx;
struct berval ntmp;
#if 0
/* Strip index from normalized values */
if ( !a->a_nvals || a->a_vals == a->a_nvals ) {
a->a_nvals = ch_malloc( (vals+1)*sizeof(struct berval));
BER_BVZERO(a->a_nvals+vals);
for ( i=0; i<vals; i++ ) {
ptr = strchr(a->a_vals[i].bv_val, '}') + 1;
a->a_nvals[i].bv_len = a->a_vals[i].bv_len -
(ptr - a->a_vals[i].bv_val);
a->a_nvals[i].bv_val = ch_malloc( a->a_nvals[i].bv_len + 1);
strcpy(a->a_nvals[i].bv_val, ptr );
}
} else {
for ( i=0; i<vals; i++ ) {
ptr = strchr(a->a_nvals[i].bv_val, '}') + 1;
a->a_nvals[i].bv_len -= ptr - a->a_nvals[i].bv_val;
strcpy(a->a_nvals[i].bv_val, ptr);
}
}
#endif
indexes = ch_malloc( vals * sizeof(int) );
for ( i=0; i<vals; i++)
indexes[i] = strtol(a->a_vals[i].bv_val+1, NULL, 0);
/* Insertion sort */
for ( i=1; i<vals; i++ ) {
idx = indexes[i];
tmp = a->a_vals[i];
if ( gotnvals ) ntmp = a->a_nvals[i];
j = i;
while ((j > 0) && (indexes[j-1] > idx)) {
indexes[j] = indexes[j-1];
a->a_vals[j] = a->a_vals[j-1];
if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
j--;
}
indexes[j] = idx;
a->a_vals[j] = tmp;
if ( gotnvals ) a->a_nvals[j] = ntmp;
}
/* If range is not contiguous, must renumber */
if ( indexes[0] != 0 || indexes[vals-1] != vals-1 ) {
renumber = 1;
}
} else {
renumber = 1;
}
if ( do_renumber && renumber )
ordered_value_renumber( a, vals );
return 0;
}
/* A wrapper for value match, handles Equality matches for attributes
* with ordered values.
*/
int
ordered_value_match(
int *match,
AttributeDescription *ad,
MatchingRule *mr,
unsigned flags,
struct berval *v1, /* stored value */
struct berval *v2, /* assertion */
const char ** text )
{
struct berval bv1, bv2;
/* X-ORDERED VALUES equality matching:
* If (SLAP_MR_IS_VALUE_OF_ATTRIBUTE_SYNTAX) that means we are
* comparing two attribute values. In this case, we want to ignore
* the ordering index of both values, we just want to know if their
* main values are equal.
*
* If (SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX) then we are comparing
* an assertion against an attribute value.
* If the assertion has no index, the index of the value is ignored.
* If the assertion has only an index, the remainder of the value is
* ignored.
* If the assertion has index and value, both are compared.
*/
if ( ad->ad_type->sat_flags & SLAP_AT_ORDERED ) {
char *ptr;
struct berval iv;
bv1 = *v1;
bv2 = *v2;
iv = bv2;
/* Skip past the assertion index */
if ( bv2.bv_val[0] == '{' ) {
ptr = strchr( bv2.bv_val, '}' ) + 1;
bv2.bv_len -= ptr - bv2.bv_val;
bv2.bv_val = ptr;
v2 = &bv2;
}
if ( SLAP_MR_IS_VALUE_OF_ASSERTION_SYNTAX( flags )) {
if ( iv.bv_val[0] == '{' && bv1.bv_val[0] == '{' ) {
/* compare index values first */
long l1, l2, ret;
l1 = strtol( bv1.bv_val+1, NULL, 0 );
l2 = strtol( iv.bv_val+1, &ptr, 0 );
ret = l1 - l2;
/* If not equal, or we're only comparing the index,
* return result now.
*/
if ( ret || ptr == iv.bv_val + iv.bv_len ) {
*match = ( ret < 0 ) ? -1 : (ret > 0 );
return LDAP_SUCCESS;
}
}
}
/* Skip past the attribute index */
if ( bv1.bv_val[0] == '{' ) {
ptr = strchr( bv1.bv_val, '}' ) + 1;
bv1.bv_len -= ptr - bv1.bv_val;
bv1.bv_val = ptr;
v1 = &bv1;
}
}
if ( !mr || !mr->smr_match ) {
*match = ber_bvcmp( v1, v2 );
return LDAP_SUCCESS;
}
return value_match( match, ad, mr, flags, v1, v2, text );
}
int
ordered_value_add(
Entry *e,
AttributeDescription *ad,
Attribute *a,
BerVarray vals,
BerVarray nvals
)
{
int i, j, k, anum, vnum;
BerVarray new, nnew = NULL;
/* count new vals */
for (i=0; !BER_BVISNULL( vals+i ); i++) ;
vnum = i;
if ( a ) {
for (i=0; !BER_BVISNULL( a->a_vals+i ); i++) ;
anum = i;
ordered_value_sort( a, 0 );
} else {
Attribute **ap;
anum = 0;
for ( ap=&e->e_attrs; *ap; ap = &(*ap)->a_next ) ;
a = ch_calloc( 1, sizeof(Attribute) );
a->a_desc = ad;
*ap = a;
}
new = ch_malloc( (anum+vnum+1) * sizeof(struct berval));
if ( a->a_nvals && a->a_nvals != a->a_vals ) {
nnew = ch_malloc( (anum+vnum+1) * sizeof(struct berval));
/* Shouldn't happen... */
if ( !nvals ) nvals = vals;
}
if ( anum ) {
AC_MEMCPY( new, a->a_vals, anum * sizeof(struct berval));
if ( nnew )
AC_MEMCPY( nnew, a->a_nvals, anum * sizeof(struct berval));
}
for (i=0; i<vnum; i++) {
k = -1;
if ( vals[i].bv_val[0] == '{' ) {
k = strtol( vals[i].bv_val+1, NULL, 0 );
if ( k > anum ) k = -1;
}
/* No index, or index is greater than current number of
* values, just tack onto the end
*/
if ( k < 0 ) {
ber_dupbv( new+anum, vals+i );
if ( nnew ) ber_dupbv( nnew+anum, nvals+i );
/* Indexed, push everything else down one and insert */
} else {
for (j=anum; j>k; j--) {
new[j] = new[j-1];
if ( nnew ) nnew[j] = nnew[j-1];
}
ber_dupbv( new+k, vals+i );
if ( nnew ) ber_dupbv( nnew+k, nvals+i );
}
anum++;
}
BER_BVZERO( new+anum );
ch_free( a->a_vals );
a->a_vals = new;
if ( nnew ) {
BER_BVZERO( nnew+anum );
ch_free( a->a_nvals );
a->a_nvals = nnew;
} else {
a->a_nvals = a->a_vals;
}
ordered_value_renumber( a, anum );
return 0;
}