]> git.sur5r.net Git - openldap/blobdiff - servers/slapd/overlays/pcache.c
Fix typo in prev commit
[openldap] / servers / slapd / overlays / pcache.c
index 96664a62444ac306248a072456f4218af651a43a..710df5d789c2e4891f6172cc7a08122bce279516 100644 (file)
@@ -1,7 +1,7 @@
 /* $OpenLDAP$ */
 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
  *
- * Copyright 2003-2007 The OpenLDAP Foundation.
+ * Copyright 2003-2009 The OpenLDAP Foundation.
  * Portions Copyright 2003 IBM Corporation.
  * Portions Copyright 2003 Symas Corporation.
  * All rights reserved.
@@ -72,12 +72,16 @@ typedef struct cached_query_s {
        Qbase                                   *qbase;
        int                                             scope;
        struct berval                   q_uuid;         /* query identifier */
+       int                                             q_sizelimit;
        struct query_template_s         *qtemp; /* template of the query */
-       time_t                          expiry_time;    /* time till the query is considered valid */
+       time_t                                          expiry_time;    /* time till the query is considered valid */
+       unsigned long                   answerable_cnt; /* how many times it was answerable */
+       ldap_pvt_thread_mutex_t         answerable_cnt_mutex;
        struct cached_query_s           *next;          /* next query in the template */
        struct cached_query_s           *prev;          /* previous query in the template */
-       struct cached_query_s           *lru_up;        /* previous query in the LRU list */
-       struct cached_query_s           *lru_down;      /* next query in the LRU list */
+       struct cached_query_s           *lru_up;        /* previous query in the LRU list */
+       struct cached_query_s           *lru_down;      /* next query in the LRU list */
+       ldap_pvt_thread_rdwr_t          rwlock;
 } CachedQuery;
 
 /*
@@ -89,8 +93,7 @@ typedef struct cached_query_s {
  * <scope> ::= CachedQuery.scope
  * <filter> ::= filter2bv(CachedQuery.filter)
  * <uuid> ::= CachedQuery.q_uuid
- * <template> ::= CachedQuery.qtemp->querystr          [FIXME: better give it an ID?]
- * <attrset> ::= CachedQuery.qtemp->attr_set_index     [FIXME: better give it an ID?]
+ * <attrset> ::= CachedQuery.qtemp->attr_set_index
  * <expiry> ::= CachedQuery.expiry_time
  *
  * quick hack: parse URI, call add_query() and then fix
@@ -128,17 +131,37 @@ typedef struct query_template_s {
        int             no_of_queries;  /* Total number of queries in the template */
        time_t          ttl;            /* TTL for the queries of this template */
        time_t          negttl;         /* TTL for negative results */
+       time_t          limitttl;       /* TTL for sizelimit exceeding results */
+       time_t          ttr;    /* time to refresh */
        struct attr_set t_attrs;        /* filter attrs + attr_set */
 } QueryTemplate;
 
+typedef enum {
+       PC_IGNORE = 0,
+       PC_POSITIVE,
+       PC_NEGATIVE,
+       PC_SIZELIMIT
+} pc_caching_reason_t;
+
+static const char *pc_caching_reason_str[] = {
+       "IGNORE",
+       "POSITIVE",
+       "NEGATIVE",
+       "SIZELIMIT",
+
+       NULL
+};
+
 struct query_manager_s;
 
 /* prototypes for functions for 1) query containment
  * 2) query addition, 3) cache replacement
  */
-typedef CachedQuery *  (QCfunc)(Operation *op, struct query_manager_s*, Query*, QueryTemplate*);
-typedef CachedQuery *  (AddQueryfunc)(Operation *op, struct query_manager_s*, Query*, QueryTemplate*, int positive);
-typedef void   (CRfunc)(struct query_manager_s*, struct berval * );
+typedef CachedQuery *(QCfunc)(Operation *op, struct query_manager_s*,
+       Query*, QueryTemplate*);
+typedef CachedQuery *(AddQueryfunc)(Operation *op, struct query_manager_s*,
+       Query*, QueryTemplate*, pc_caching_reason_t, int wlock);
+typedef void (CRfunc)(struct query_manager_s*, struct berval*);
 
 /* LDAP query cache */
 typedef struct query_manager_s {
@@ -162,18 +185,22 @@ typedef struct cache_manager_s {
        unsigned long   num_cached_queries;             /* total number of cached queries */
        unsigned long   max_queries;                    /* upper bound on # of cached queries */
        int             save_queries;                   /* save cached queries across restarts */
+       int     check_cacheability;             /* check whether a query is cacheable */
        int     numattrsets;                    /* number of attribute sets */
        int     cur_entries;                    /* current number of entries cached */
        int     max_entries;                    /* max number of entries cached */
-        int     num_entries_limit;             /* max # of entries in a cacheable query */
+       int     num_entries_limit;              /* max # of entries in a cacheable query */
 
        char    response_cb;                    /* install the response callback
                                                 * at the tail of the callback list */
 #define PCACHE_RESPONSE_CB_HEAD        0
 #define PCACHE_RESPONSE_CB_TAIL        1
+       char    defer_db_open;                  /* defer open for online add */
 
        time_t  cc_period;              /* interval between successive consistency checks (sec) */
-       int     cc_paused;
+#define PCACHE_CC_PAUSED       1
+#define PCACHE_CC_OFFLINE      2
+       int     cc_paused;
        void    *cc_arg;
 
        ldap_pvt_thread_mutex_t         cache_mutex;
@@ -194,9 +221,9 @@ static struct {
 } as[] = {
        { "( 1.3.6.1.4.1.4203.666.11.9.1.1 "
                "NAME 'queryId' "
-               "DESC 'List of queries the entry belongs to' "
-               "EQUALITY UUIDMatch "
-               "SYNTAX 1.3.6.1.1.16.1 "
+               "DESC 'ID of query the entry belongs to, formatted as a UUID' "
+               "EQUALITY octetStringMatch "
+               "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64} "
                "NO-USER-MODIFICATION "
                "USAGE directoryOperation )",
                &ad_queryId },
@@ -226,7 +253,8 @@ add_query(
        query_manager* qm,
        Query* query,
        QueryTemplate *templ,
-       int positive);
+       pc_caching_reason_t why,
+       int wlock);
 
 static int
 remove_query_data(
@@ -238,22 +266,30 @@ remove_query_data(
  * Turn a cached query into its URL representation
  */
 static int
-query2url( Operation *op, CachedQuery *q, struct berval *urlbv )
+query2url( Operation *op, CachedQuery *q, struct berval *urlbv, int dolock )
 {
        struct berval   bv_scope,
                        bv_filter;
-       char            attrset_buf[ 32 ],
-                       expiry_buf[ 32 ],
+       char            attrset_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+                       expiry_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+                       answerable_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
                        *ptr;
        ber_len_t       attrset_len,
-                       expiry_len;
+                       expiry_len,
+                       answerable_len;
+
+       if ( dolock ) {
+               ldap_pvt_thread_rdwr_rlock( &q->rwlock );
+       }
 
        ldap_pvt_scope2bv( q->scope, &bv_scope );
        filter2bv_x( op, q->filter, &bv_filter );
-       attrset_len = snprintf( attrset_buf, sizeof( attrset_buf ),
+       attrset_len = sprintf( attrset_buf,
                "%lu", (unsigned long)q->qtemp->attr_set_index );
-       expiry_len = snprintf( expiry_buf, sizeof( expiry_buf ),
+       expiry_len = sprintf( expiry_buf,
                "%lu", (unsigned long)q->expiry_time );
+       answerable_len = snprintf( answerable_buf, sizeof( answerable_buf ),
+               "%lu", q->answerable_cnt );
 
        urlbv->bv_len = STRLENOF( "ldap:///" )
                + q->qbase->base.bv_len
@@ -266,7 +302,9 @@ query2url( Operation *op, CachedQuery *q, struct berval *urlbv )
                + STRLENOF( ",x-attrset=" )
                + attrset_len
                + STRLENOF( ",x-expiry=" )
-               + expiry_len;
+               + expiry_len
+               + STRLENOF( ",x-answerable=" )
+               + answerable_len;
        ptr = urlbv->bv_val = ber_memalloc_x( urlbv->bv_len + 1, op->o_tmpmemctx );
        ptr = lutil_strcopy( ptr, "ldap:///" );
        ptr = lutil_strcopy( ptr, q->qbase->base.bv_val );
@@ -280,9 +318,15 @@ query2url( Operation *op, CachedQuery *q, struct berval *urlbv )
        ptr = lutil_strcopy( ptr, attrset_buf );
        ptr = lutil_strcopy( ptr, ",x-expiry=" );
        ptr = lutil_strcopy( ptr, expiry_buf );
+       ptr = lutil_strcopy( ptr, ",x-answerable=" );
+       ptr = lutil_strcopy( ptr, answerable_buf );
 
        ber_memfree_x( bv_filter.bv_val, op->o_tmpmemctx );
 
+       if ( dolock ) {
+               ldap_pvt_thread_rdwr_runlock( &q->rwlock );
+       }
+
        return 0;
 }
 
@@ -305,10 +349,14 @@ url2query(
                        uuid;
        int             attrset;
        time_t          expiry_time;
+       unsigned long   answerable_cnt;
        int             i,
-                       got_uuid = 0,
-                       got_attrset = 0,
-                       got_expiry = 0,
+                       got = 0,
+#define GOT_UUID       0x1U
+#define GOT_ATTRSET    0x2U
+#define GOT_EXPIRY     0x4U
+#define GOT_ANSWERABLE 0x8U
+#define GOT_ALL                (GOT_UUID|GOT_ATTRSET|GOT_EXPIRY|GOT_ANSWERABLE)
                        rc = 0;
 
        rc = ldap_url_parse( url, &lud );
@@ -366,29 +414,56 @@ url2query(
                        struct berval   tmpUUID;
                        Syntax          *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax;
 
+                       if ( got & GOT_UUID ) {
+                               rc = 1;
+                               goto error;
+                       }
+
                        ber_str2bv( &lud->lud_exts[ i ][ STRLENOF( "x-uuid=" ) ], 0, 0, &tmpUUID );
                        rc = syn_UUID->ssyn_pretty( syn_UUID, &tmpUUID, &uuid, NULL );
                        if ( rc != LDAP_SUCCESS ) {
                                goto error;
                        }
-                       got_uuid = 1;
+                       got |= GOT_UUID;
 
                } else if ( strncmp( lud->lud_exts[ i ], "x-attrset=", STRLENOF( "x-attrset=" ) ) == 0 ) {
+                       if ( got & GOT_ATTRSET ) {
+                               rc = 1;
+                               goto error;
+                       }
+
                        rc = lutil_atoi( &attrset, &lud->lud_exts[ i ][ STRLENOF( "x-attrset=" ) ] );
                        if ( rc ) {
                                goto error;
                        }
-                       got_attrset = 1;
+                       got |= GOT_ATTRSET;
 
                } else if ( strncmp( lud->lud_exts[ i ], "x-expiry=", STRLENOF( "x-expiry=" ) ) == 0 ) {
                        unsigned long l;
 
+                       if ( got & GOT_EXPIRY ) {
+                               rc = 1;
+                               goto error;
+                       }
+
                        rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-expiry=" ) ] );
                        if ( rc ) {
                                goto error;
                        }
                        expiry_time = (time_t)l;
-                       got_expiry = 1;
+                       got |= GOT_EXPIRY;
+
+               } else if ( strncmp( lud->lud_exts[ i ], "x-answerable=", STRLENOF( "x-answerable=" ) ) == 0 ) {
+                       if ( got & GOT_ANSWERABLE ) {
+                               rc = 1;
+                               goto error;
+                       }
+
+                       rc = lutil_atoul( &answerable_cnt, &lud->lud_exts[ i ][ STRLENOF( "x-answerable=" ) ] );
+                       if ( rc ) {
+                               goto error;
+                       }
+                       got |= GOT_ANSWERABLE;
 
                } else {
                        rc = -1;
@@ -396,17 +471,7 @@ url2query(
                }
        }
 
-       if ( !got_uuid ) {
-               rc = 1;
-               goto error;
-       }
-
-       if ( !got_attrset ) {
-               rc = 1;
-               goto error;
-       }
-
-       if ( !got_expiry ) {
+       if ( got != GOT_ALL ) {
                rc = 1;
                goto error;
        }
@@ -453,10 +518,11 @@ url2query(
                        goto error;
                }
 
-               cq = add_query( op, qm, &query, qt, 1 );
+               cq = add_query( op, qm, &query, qt, PC_POSITIVE, 0 );
                if ( cq != NULL ) {
                        cq->expiry_time = expiry_time;
                        cq->q_uuid = uuid;
+                       cq->answerable_cnt = answerable_cnt;
 
                        /* it's now into cq->filter */
                        BER_BVZERO( &uuid );
@@ -565,13 +631,12 @@ static int lex_bvcmp( struct berval *bv1, struct berval *bv2 )
        return len;
 }
 
-/* compare the first value in each filter */
-static int pcache_filter_cmp( const void *v1, const void *v2 )
+/* compare the current value in each filter */
+static int pcache_filter_cmp( Filter *f1, Filter *f2 )
 {
-       const CachedQuery *q1 = v1, *q2 =v2;
        int rc, weight1, weight2;
 
-       switch( q1->first->f_choice ) {
+       switch( f1->f_choice ) {
        case LDAP_FILTER_PRESENT:
                weight1 = 0;
                break;
@@ -583,7 +648,7 @@ static int pcache_filter_cmp( const void *v1, const void *v2 )
        default:
                weight1 = 2;
        }
-       switch( q2->first->f_choice ) {
+       switch( f2->f_choice ) {
        case LDAP_FILTER_PRESENT:
                weight2 = 0;
                break;
@@ -598,56 +663,80 @@ static int pcache_filter_cmp( const void *v1, const void *v2 )
        rc = weight1 - weight2;
        if ( !rc ) {
                switch( weight1 ) {
-               case 0: return 0;
+               case 0:
+                       break;
                case 1:
-                       rc = lex_bvcmp( &q1->first->f_av_value, &q2->first->f_av_value );
+                       rc = lex_bvcmp( &f1->f_av_value, &f2->f_av_value );
                        break;
                case 2:
-                       if ( q1->first->f_choice == LDAP_FILTER_SUBSTRINGS ) {
+                       if ( f1->f_choice == LDAP_FILTER_SUBSTRINGS ) {
                                rc = 0;
-                               if ( !BER_BVISNULL( &q1->first->f_sub_initial )) {
-                                       if ( !BER_BVISNULL( &q2->first->f_sub_initial )) {
-                                               rc = lex_bvcmp( &q1->first->f_sub_initial,
-                                                       &q2->first->f_sub_initial );
+                               if ( !BER_BVISNULL( &f1->f_sub_initial )) {
+                                       if ( !BER_BVISNULL( &f2->f_sub_initial )) {
+                                               rc = lex_bvcmp( &f1->f_sub_initial,
+                                                       &f2->f_sub_initial );
                                        } else {
                                                rc = 1;
                                        }
-                               } else if ( !BER_BVISNULL( &q2->first->f_sub_initial )) {
+                               } else if ( !BER_BVISNULL( &f2->f_sub_initial )) {
                                        rc = -1;
                                }
                                if ( rc ) break;
-                               if ( q1->first->f_sub_any ) {
-                                       if ( q2->first->f_sub_any ) {
-                                               rc = lex_bvcmp( q1->first->f_sub_any,
-                                                       q2->first->f_sub_any );
+                               if ( f1->f_sub_any ) {
+                                       if ( f2->f_sub_any ) {
+                                               rc = lex_bvcmp( f1->f_sub_any,
+                                                       f2->f_sub_any );
                                        } else {
                                                rc = 1;
                                        }
-                               } else if ( q2->first->f_sub_any ) {
+                               } else if ( f2->f_sub_any ) {
                                        rc = -1;
                                }
                                if ( rc ) break;
-                               if ( !BER_BVISNULL( &q1->first->f_sub_final )) {
-                                       if ( !BER_BVISNULL( &q2->first->f_sub_final )) {
-                                               rc = lex_bvcmp( &q1->first->f_sub_final,
-                                                       &q2->first->f_sub_final );
+                               if ( !BER_BVISNULL( &f1->f_sub_final )) {
+                                       if ( !BER_BVISNULL( &f2->f_sub_final )) {
+                                               rc = lex_bvcmp( &f1->f_sub_final,
+                                                       &f2->f_sub_final );
                                        } else {
                                                rc = 1;
                                        }
-                               } else if ( !BER_BVISNULL( &q2->first->f_sub_final )) {
+                               } else if ( !BER_BVISNULL( &f2->f_sub_final )) {
                                        rc = -1;
                                }
                        } else {
-                               rc = lex_bvcmp( &q1->first->f_mr_value,
-                                       &q2->first->f_mr_value );
+                               rc = lex_bvcmp( &f1->f_mr_value,
+                                       &f2->f_mr_value );
                        }
                        break;
                }
+               if ( !rc ) {
+                       f1 = f1->f_next;
+                       f2 = f2->f_next;
+                       if ( f1 || f2 ) {
+                               if ( !f1 )
+                                       rc = -1;
+                               else if ( !f2 )
+                                       rc = 1;
+                               else {
+                                       while ( f1->f_choice == LDAP_FILTER_AND || f1->f_choice == LDAP_FILTER_OR )
+                                               f1 = f1->f_and;
+                                       while ( f2->f_choice == LDAP_FILTER_AND || f2->f_choice == LDAP_FILTER_OR )
+                                               f2 = f2->f_and;
+                                       rc = pcache_filter_cmp( f1, f2 );
+                               }
+                       }
+               }
        }
-
        return rc;
 }
 
+/* compare filters in each query */
+static int pcache_query_cmp( const void *v1, const void *v2 )
+{
+       const CachedQuery *q1 = v1, *q2 =v2;
+       return pcache_filter_cmp( q1->first, q2->first );
+}
+
 /* add query on top of LRU list */
 static void
 add_query_on_top (query_manager* qm, CachedQuery* qc)
@@ -899,7 +988,7 @@ find_filter( Operation *op, Avlnode *root, Filter *inputf, Filter *first )
                ptr = tavl_end( root, 1 );
                dir = TAVL_DIR_LEFT;
        } else {
-               ptr = tavl_find3( root, &cq, pcache_filter_cmp, &ret );
+               ptr = tavl_find3( root, &cq, pcache_query_cmp, &ret );
                dir = (first->f_choice == LDAP_FILTER_GE) ? TAVL_DIR_LEFT :
                        TAVL_DIR_RIGHT;
        }
@@ -1094,6 +1183,10 @@ query_containment(Operation *op, query_manager *qm,
                                        qc = find_filter( op, qbptr->scopes[tscope],
                                                        query->filter, first );
                                        if ( qc ) {
+                                               if ( qc->q_sizelimit ) {
+                                                       ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock);
+                                                       return NULL;
+                                               }
                                                ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
                                                if (qm->lru_top != qc) {
                                                        remove_query(qm, qc);
@@ -1125,39 +1218,68 @@ free_query (CachedQuery* qc)
 {
        free(qc->q_uuid.bv_val);
        filter_free(qc->filter);
+       ldap_pvt_thread_mutex_destroy(&qc->answerable_cnt_mutex);
+       ldap_pvt_thread_rdwr_destroy( &qc->rwlock );
+       memset(qc, 0, sizeof(*qc));
        free(qc);
 }
 
 
-/* Add query to query cache */
+/* Add query to query cache, the returned Query is locked for writing */
 static CachedQuery *
 add_query(
        Operation *op,
        query_manager* qm,
        Query* query,
        QueryTemplate *templ,
-       int positive)
+       pc_caching_reason_t why,
+       int wlock)
 {
        CachedQuery* new_cached_query = (CachedQuery*) ch_malloc(sizeof(CachedQuery));
        Qbase *qbase, qb;
        Filter *first;
        int rc;
+       time_t ttl = 0;;
 
        new_cached_query->qtemp = templ;
        BER_BVZERO( &new_cached_query->q_uuid );
-       if ( positive ) {
-               new_cached_query->expiry_time = slap_get_time() + templ->ttl;
-       } else {
-               new_cached_query->expiry_time = slap_get_time() + templ->negttl;
+       new_cached_query->q_sizelimit = 0;
+
+       switch ( why ) {
+       case PC_POSITIVE:
+               ttl = templ->ttl;
+               break;
+
+       case PC_NEGATIVE:
+               ttl = templ->negttl;
+               break;
+
+       case PC_SIZELIMIT:
+               ttl = templ->limitttl;
+               break;
+
+       default:
+               assert( 0 );
+               break;
        }
+       new_cached_query->expiry_time = slap_get_time() + ttl;
+
+       new_cached_query->answerable_cnt = 0;
+       ldap_pvt_thread_mutex_init(&new_cached_query->answerable_cnt_mutex);
+
        new_cached_query->lru_up = NULL;
        new_cached_query->lru_down = NULL;
-       Debug( pcache_debug, "Added query expires at %ld\n",
-                       (long) new_cached_query->expiry_time, 0, 0 );
+       Debug( pcache_debug, "Added query expires at %ld (%s)\n",
+                       (long) new_cached_query->expiry_time,
+                       pc_caching_reason_str[ why ], 0 );
 
        new_cached_query->scope = query->scope;
        new_cached_query->filter = query->filter;
        new_cached_query->first = first = filter_first( query->filter );
+       
+       ldap_pvt_thread_rdwr_init(&new_cached_query->rwlock);
+       if (wlock)
+               ldap_pvt_thread_rdwr_wlock(&new_cached_query->rwlock);
 
        qb.base = query->base;
 
@@ -1178,7 +1300,7 @@ add_query(
        new_cached_query->prev = NULL;
        new_cached_query->qbase = qbase;
        rc = tavl_insert( &qbase->scopes[query->scope], new_cached_query,
-               pcache_filter_cmp, avl_dup_error );
+               pcache_query_cmp, avl_dup_error );
        if ( rc == 0 ) {
                qbase->queries++;
                if (templ->query == NULL)
@@ -1192,6 +1314,7 @@ add_query(
                new_cached_query = find_filter( op, qbase->scopes[query->scope],
                                                        query->filter, first );
                filter_free( query->filter );
+               query->filter = NULL;
        }
        Debug( pcache_debug, "TEMPLATE %p QUERIES++ %d\n",
                        (void *) templ, templ->no_of_queries, 0 );
@@ -1224,7 +1347,7 @@ remove_from_template (CachedQuery* qc, QueryTemplate* template)
                qc->next->prev = qc->prev;
                qc->prev->next = qc->next;
        }
-       tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_filter_cmp );
+       tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp );
        qc->qbase->queries--;
        if ( qc->qbase->queries == 0 ) {
                avl_delete( &template->qbase, qc->qbase, pcache_dn_cmp );
@@ -1287,7 +1410,7 @@ cache_replacement(query_manager* qm, struct berval *result)
        ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
 
        *result = bottom->q_uuid;
-       bottom->q_uuid.bv_val = NULL;
+       BER_BVZERO( &bottom->q_uuid );
 
        Debug( pcache_debug, "Lock CR index = %p\n", (void *) temp, 0, 0 );
        ldap_pvt_thread_rdwr_wlock(&temp->t_rwlock);
@@ -1320,8 +1443,7 @@ remove_func (
        attr = attr_find( rs->sr_entry->e_attrs,  ad_queryId );
        if ( attr == NULL ) return 0;
 
-       for ( count = 0; !BER_BVISNULL( &attr->a_vals[count] ); count++ )
-               ;
+       count = attr->a_numvals;
        assert( count > 0 );
        qi = op->o_tmpalloc( sizeof( struct query_info ), op->o_tmpmemctx );
        qi->next = op->o_callback->sc_private;
@@ -1340,11 +1462,7 @@ remove_query_data(
 {
        struct query_info       *qi, *qnext;
        char                    filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(queryId=)" ) ];
-#ifdef LDAP_COMP_MATCH
-       AttributeAssertion      ava = { NULL, BER_BVNULL, NULL };
-#else
-       AttributeAssertion      ava = { NULL, BER_BVNULL };
-#endif
+       AttributeAssertion      ava = ATTRIBUTEASSERTION_INIT;
        Filter                  filter = {LDAP_FILTER_EQUALITY};
        SlapReply               sreply = {REP_RESULT};
        slap_callback cb = { NULL, remove_func, NULL, NULL };
@@ -1407,6 +1525,7 @@ remove_query_data(
                        mod.sml_type = ad_queryId->ad_cname;
                        mod.sml_values = vals;
                        mod.sml_nvalues = NULL;
+                        mod.sml_numvals = 1;
                        mod.sml_next = NULL;
                        Debug( pcache_debug,
                                "REMOVING TEMP ATTR : TEMPLATE=%s\n",
@@ -1439,67 +1558,91 @@ filter2template(
        int*                    filter_got_oc )
 {
        AttributeDescription *ad;
+       int len, ret;
 
        switch ( f->f_choice ) {
        case LDAP_FILTER_EQUALITY:
                ad = f->f_av_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s=)", ad->ad_cname.bv_val );
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(=)") - 1 );
+               len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_GE:
                ad = f->f_av_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s>=)", ad->ad_cname.bv_val);
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(>=)") - 1 );
+               len = STRLENOF( "(>=)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s>=)", ad->ad_cname.bv_val);
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_LE:
                ad = f->f_av_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s<=)", ad->ad_cname.bv_val);
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(<=)") - 1 );
+               len = STRLENOF( "(<=)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s<=)", ad->ad_cname.bv_val);
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_APPROX:
                ad = f->f_av_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s~=)", ad->ad_cname.bv_val);
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(~=)") - 1 );
+               len = STRLENOF( "(~=)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s~=)", ad->ad_cname.bv_val);
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_SUBSTRINGS:
                ad = f->f_sub_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s=)", ad->ad_cname.bv_val );
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(=)") - 1 );
+               len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_PRESENT:
                ad = f->f_desc;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%s=*)", ad->ad_cname.bv_val );
-               fstr->bv_len += ad->ad_cname.bv_len + ( sizeof("(=*)") - 1 );
+               len = STRLENOF( "(=*)" ) + ad->ad_cname.bv_len;
+               ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=*)", ad->ad_cname.bv_val );
+               assert( ret == len );
+               fstr->bv_len += len;
                break;
 
        case LDAP_FILTER_AND:
        case LDAP_FILTER_OR:
        case LDAP_FILTER_NOT: {
                int rc = 0;
-               sprintf( fstr->bv_val+fstr->bv_len, "(%c",
-                       f->f_choice == LDAP_FILTER_AND ? '&' :
-                       f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
-               fstr->bv_len += sizeof("(%") - 1;
+               fstr->bv_val[fstr->bv_len++] = '(';
+               switch ( f->f_choice ) {
+               case LDAP_FILTER_AND:
+                       fstr->bv_val[fstr->bv_len] = '&';
+                       break;
+               case LDAP_FILTER_OR:
+                       fstr->bv_val[fstr->bv_len] = '|';
+                       break;
+               case LDAP_FILTER_NOT:
+                       fstr->bv_val[fstr->bv_len] = '!';
+                       break;
+               }
+               fstr->bv_len++;
 
                for ( f = f->f_list; f != NULL; f = f->f_next ) {
                        rc = filter2template( op, f, fstr, filter_attrs, filter_cnt,
                                filter_got_oc );
                        if ( rc ) break;
                }
-               sprintf( fstr->bv_val+fstr->bv_len, ")" );
-               fstr->bv_len += sizeof(")") - 1;
+               fstr->bv_val[fstr->bv_len++] = ')';
+               fstr->bv_val[fstr->bv_len] = '\0';
 
                return rc;
                }
 
        default:
-               strcpy( fstr->bv_val, "(?=?)" );
-               fstr->bv_len += sizeof("(?=?)")-1;
+               /* a filter should at least have room for "()",
+                * an "=" and for a 1-char attr */
+               strcpy( fstr->bv_val, "(?=)" );
+               fstr->bv_len += STRLENOF("(?=)");
                return -1;
        }
 
@@ -1510,7 +1653,7 @@ filter2template(
                (*filter_attrs)[*filter_cnt].an_desc = ad;
                (*filter_attrs)[*filter_cnt].an_name = ad->ad_cname;
                (*filter_attrs)[*filter_cnt].an_oc = NULL;
-               (*filter_attrs)[*filter_cnt].an_oc_exclude = 0;
+               (*filter_attrs)[*filter_cnt].an_flags = 0;
                BER_BVZERO( &(*filter_attrs)[*filter_cnt+1].an_name );
                (*filter_cnt)++;
                if ( ad == slap_schema.si_ad_objectClass )
@@ -1525,9 +1668,13 @@ struct search_info {
        Query query;
        QueryTemplate *qtemp;
        AttributeName*  save_attrs;     /* original attributes, saved for response */
+       int swap_saved_attrs;
        int max;
        int over;
        int count;
+       int slimit;
+       int slimit_exceeded;
+       pc_caching_reason_t caching_reason;
        Entry *head, *tail;
 };
 
@@ -1614,7 +1761,7 @@ int
 pcache_remove_entries_from_cache(
        Operation       *op,
        cache_manager   *cm,
-       BerVarray       UUIDs )
+       BerVarray       entryUUIDs )
 {
        Connection      conn = { 0 };
        OperationBuffer opbuf;
@@ -1623,12 +1770,8 @@ pcache_remove_entries_from_cache(
        SlapReply       rs = { REP_RESULT };
        Filter          f = { 0 };
        char            filtbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(entryUUID=)" ) ];
-#ifdef LDAP_COMP_MATCH
-       AttributeAssertion ava = { NULL, BER_BVNULL, NULL };
-#else
-       AttributeAssertion ava = { NULL, BER_BVNULL };
-#endif
-       AttributeName   attrs[ 2 ] = { 0 };
+       AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+       AttributeName   attrs[ 2 ] = {{{ 0 }}};
        int             s, rc;
 
        if ( op == NULL ) {
@@ -1668,13 +1811,13 @@ pcache_remove_entries_from_cache(
        sc.sc_response = fetch_queryId_cb;
        op->o_callback = &sc;
 
-       for ( s = 0; !BER_BVISNULL( &UUIDs[ s ] ); s++ ) {
+       for ( s = 0; !BER_BVISNULL( &entryUUIDs[ s ] ); s++ ) {
                BerVarray       vals = NULL;
 
                op->ors_filterstr.bv_len = snprintf( filtbuf, sizeof( filtbuf ),
-                       "(entryUUID=%s)", UUIDs[ s ].bv_val );
+                       "(entryUUID=%s)", entryUUIDs[ s ].bv_val );
                op->ors_filterstr.bv_val = filtbuf;
-               ava.aa_value = UUIDs[ s ];
+               ava.aa_value = entryUUIDs[ s ];
 
                rc = op->o_bd->be_search( op, &rs );
                if ( rc != LDAP_SUCCESS ) {
@@ -1704,14 +1847,36 @@ pcache_remove_entries_from_cache(
 }
 
 /*
- * Call that allows to remove a set of queries from the cache
+ * Call that allows to remove a query from the cache.
+ */
+int
+pcache_remove_query_from_cache(
+       Operation       *op,
+       cache_manager   *cm,
+       struct berval   *queryid )
+{
+       Operation       op2 = *op;
+       SlapReply       rs2 = { 0 };
+
+       op2.o_bd = &cm->db;
+
+       /* remove the selected query */
+       remove_query_and_data( &op2, &rs2, cm, queryid );
+
+       return LDAP_SUCCESS;
+}
+
+/*
+ * Call that allows to remove a set of queries related to an entry 
+ * from the cache; if queryid is not null, the entry must belong to
+ * the query indicated by queryid.
  */
 int
 pcache_remove_entry_queries_from_cache(
        Operation       *op,
        cache_manager   *cm,
        struct berval   *ndn,
-       struct berval   *uuid )
+       struct berval   *queryid )
 {
        Connection              conn = { 0 };
        OperationBuffer         opbuf;
@@ -1720,12 +1885,8 @@ pcache_remove_entry_queries_from_cache(
        SlapReply               rs = { REP_RESULT };
        Filter                  f = { 0 };
        char                    filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(queryId=)" ) ];
-#ifdef LDAP_COMP_MATCH
-       AttributeAssertion      ava = { NULL, BER_BVNULL, NULL };
-#else
-       AttributeAssertion      ava = { NULL, BER_BVNULL };
-#endif
-       AttributeName           attrs[ 2 ] = { 0 };
+       AttributeAssertion      ava = ATTRIBUTEASSERTION_INIT;
+       AttributeName           attrs[ 2 ] = {{{ 0 }}};
        int                     rc;
 
        BerVarray               vals = NULL;
@@ -1744,7 +1905,7 @@ pcache_remove_entry_queries_from_cache(
        memset( &op->oq_search, 0, sizeof( op->oq_search ) );
        op->ors_scope = LDAP_SCOPE_BASE;
        op->ors_deref = LDAP_DEREF_NEVER;
-       if ( uuid == NULL || BER_BVISNULL( uuid ) ) {
+       if ( queryid == NULL || BER_BVISNULL( queryid ) ) {
                BER_BVSTR( &op->ors_filterstr, "(objectClass=*)" );
                f.f_choice = LDAP_FILTER_PRESENT;
                f.f_desc = slap_schema.si_ad_objectClass;
@@ -1752,11 +1913,11 @@ pcache_remove_entry_queries_from_cache(
        } else {
                op->ors_filterstr.bv_len = snprintf( filter_str,
                        sizeof( filter_str ), "(%s=%s)",
-                       ad_queryId->ad_cname.bv_val, uuid->bv_val );
+                       ad_queryId->ad_cname.bv_val, queryid->bv_val );
                f.f_choice = LDAP_FILTER_EQUALITY;
                f.f_ava = &ava;
                f.f_av_desc = ad_queryId;
-               f.f_av_value = *uuid;
+               f.f_av_value = *queryid;
        }
        op->ors_filter = &f;
        op->ors_slimit = 1;
@@ -1816,14 +1977,19 @@ cache_entries(
        Entry           *e;
        struct berval   crp_uuid;
        char            uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
-       Operation op_tmp = *op;
+       Operation       *op_tmp;
+       Connection      conn = {0};
+       OperationBuffer opbuf;
+       void            *thrctx = ldap_pvt_thread_pool_context();
 
        query_uuid->bv_len = lutil_uuidstr(uuidbuf, sizeof(uuidbuf));
        ber_str2bv(uuidbuf, query_uuid->bv_len, 1, query_uuid);
 
-       op_tmp.o_bd = &cm->db;
-       op_tmp.o_dn = cm->db.be_rootdn;
-       op_tmp.o_ndn = cm->db.be_rootndn;
+       connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+       op_tmp = &opbuf.ob_op;
+       op_tmp->o_bd = &cm->db;
+       op_tmp->o_dn = cm->db.be_rootdn;
+       op_tmp->o_ndn = cm->db.be_rootndn;
 
        Debug( pcache_debug, "UUID for query being added = %s\n",
                        uuidbuf, 0, 0 );
@@ -1833,10 +1999,10 @@ cache_entries(
                e->e_private = NULL;
                while ( cm->cur_entries > (cm->max_entries) ) {
                        BER_BVZERO( &crp_uuid );
-                       remove_query_and_data( &op_tmp, rs, cm, &crp_uuid );
+                       remove_query_and_data( op_tmp, rs, cm, &crp_uuid );
                }
 
-               return_val = merge_entry(&op_tmp, e, query_uuid);
+               return_val = merge_entry(op_tmp, e, query_uuid);
                ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
                cm->cur_entries += return_val;
                Debug( pcache_debug,
@@ -1853,44 +2019,53 @@ static int
 pcache_op_cleanup( Operation *op, SlapReply *rs ) {
        slap_callback   *cb = op->o_callback;
        struct search_info *si = cb->sc_private;
-       if ( si->save_attrs != NULL ) {
-               rs->sr_attrs = si->save_attrs;
-               op->ors_attrs = si->save_attrs;
-       }
-       op->o_callback = op->o_callback->sc_next;
-       op->o_tmpfree( cb, op->o_tmpmemctx );
-       return SLAP_CB_CONTINUE;
-}
-
-static int
-pcache_response(
-       Operation       *op,
-       SlapReply       *rs )
-{
-       struct search_info *si = op->o_callback->sc_private;
        slap_overinst *on = si->on;
        cache_manager *cm = on->on_bi.bi_private;
        query_manager*          qm = cm->qm;
 
-       if ( si->save_attrs != NULL ) {
-               rs->sr_attrs = si->save_attrs;
-               op->ors_attrs = si->save_attrs;
-       }
-
        if ( rs->sr_type == REP_SEARCH ) {
                Entry *e;
+
+               /* don't return more entries than requested by the client */
+               if ( si->slimit > 0 && rs->sr_nentries >= si->slimit ) {
+                       si->slimit_exceeded = 1;
+               }
+
                /* If we haven't exceeded the limit for this query,
                 * build a chain of answers to store. If we hit the
                 * limit, empty the chain and ignore the rest.
                 */
                if ( !si->over ) {
+                       /* check if the entry contains undefined
+                        * attributes/objectClasses (ITS#5680) */
+                       if ( cm->check_cacheability && test_filter( op, rs->sr_entry, si->query.filter ) != LDAP_COMPARE_TRUE ) {
+                               Debug( pcache_debug, "%s: query not cacheable because of schema issues in DN \"%s\"\n",
+                                       op->o_log_prefix, rs->sr_entry->e_name.bv_val, 0 );
+                               goto over;
+                       }
+
+                       /* check for malformed entries: attrs with no values */
+                       {
+                               Attribute *a = rs->sr_entry->e_attrs;
+                               for (; a; a=a->a_next) {
+                                       if ( !a->a_numvals ) {
+                                               Debug( pcache_debug, "%s: query not cacheable because of attrs without values in DN \"%s\" (%s)\n",
+                                               op->o_log_prefix, rs->sr_entry->e_name.bv_val,
+                                               a->a_desc->ad_cname.bv_val );
+                                               goto over;
+                                       }
+                               }
+                       }
+
                        if ( si->count < si->max ) {
                                si->count++;
                                e = entry_dup( rs->sr_entry );
                                if ( !si->head ) si->head = e;
                                if ( si->tail ) si->tail->e_private = e;
                                si->tail = e;
+
                        } else {
+over:;
                                si->over = 1;
                                si->count = 0;
                                for (;si->head; si->head=e) {
@@ -1902,16 +2077,52 @@ pcache_response(
                        }
                }
 
-       } else if ( rs->sr_type == REP_RESULT ) {
-               if ( si->count ||
-                       ( si->qtemp->negttl && !si->count && !si->over &&
-                               rs->sr_err == LDAP_SUCCESS )) {
-                       CachedQuery *qc = qm->addfunc(op, qm, &si->query, si->qtemp,
-                               si->count);
+       }
+
+       if ( rs->sr_type == REP_RESULT || 
+               op->o_abandon || rs->sr_err == SLAPD_ABANDON )
+       {
+               if ( si->swap_saved_attrs ) {
+                       rs->sr_attrs = si->save_attrs;
+                       op->ors_attrs = si->save_attrs;
+               }
+               if ( (op->o_abandon || rs->sr_err == SLAPD_ABANDON) && 
+                               si->caching_reason == PC_IGNORE )
+               {
+                       filter_free( si->query.filter );
+                       if ( si->count ) {
+                               /* duplicate query, free it */
+                               Entry *e;
+                               for (;si->head; si->head=e) {
+                                       e = si->head->e_private;
+                                       si->head->e_private = NULL;
+                                       entry_free(si->head);
+                               }
+                       }
+                       op->o_callback = op->o_callback->sc_next;
+                       op->o_tmpfree( cb, op->o_tmpmemctx );
+               } else if ( si->caching_reason != PC_IGNORE ) {
+                       CachedQuery *qc = qm->addfunc(op, qm, &si->query,
+                               si->qtemp, si->caching_reason, 1 );
 
                        if ( qc != NULL ) {
-                               if ( si->count )
+                               switch ( si->caching_reason ) {
+                               case PC_POSITIVE:
                                        cache_entries( op, rs, &qc->q_uuid );
+                                       break;
+
+                               case PC_SIZELIMIT:
+                                       qc->q_sizelimit = rs->sr_nentries;
+                                       break;
+
+                               case PC_NEGATIVE:
+                                       break;
+
+                               default:
+                                       assert( 0 );
+                                       break;
+                               }
+                               ldap_pvt_thread_rdwr_wunlock(&qc->rwlock);
                                ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
                                cm->num_cached_queries++;
                                Debug( pcache_debug, "STORED QUERIES = %lu\n",
@@ -1921,14 +2132,15 @@ pcache_response(
                                /* If the consistency checker suspended itself,
                                 * wake it back up
                                 */
-                               if ( cm->cc_paused ) {
+                               if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
                                        ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
-                                       if ( cm->cc_paused ) {
+                                       if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
                                                cm->cc_paused = 0;
                                                ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 );
                                        }
                                        ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
                                }
+
                        } else if ( si->count ) {
                                /* duplicate query, free it */
                                Entry *e;
@@ -1938,12 +2150,57 @@ pcache_response(
                                        entry_free(si->head);
                                }
                        }
+
                } else {
                        filter_free( si->query.filter );
                }
+       }
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_response(
+       Operation       *op,
+       SlapReply       *rs )
+{
+       struct search_info *si = op->o_callback->sc_private;
+
+       if ( si->swap_saved_attrs ) {
+               rs->sr_attrs = si->save_attrs;
+               op->ors_attrs = si->save_attrs;
+       }
+
+       if ( rs->sr_type == REP_SEARCH ) {
+               /* don't return more entries than requested by the client */
+               if ( si->slimit_exceeded ) {
+                       return 0;
+               }
+
+       } else if ( rs->sr_type == REP_RESULT ) {
 
-               op->o_callback->sc_cleanup = pcache_op_cleanup;
+               if ( si->count ) {
+                       if ( rs->sr_err == LDAP_SUCCESS ) {
+                               si->caching_reason = PC_POSITIVE;
+
+                       } else if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED
+                               && si->qtemp->limitttl )
+                       {
+                               si->caching_reason = PC_SIZELIMIT;
+                       }
+
+               } else if ( si->qtemp->negttl && !si->count && !si->over &&
+                               rs->sr_err == LDAP_SUCCESS )
+               {
+                       si->caching_reason = PC_NEGATIVE;
+               }
+
+
+               if ( si->slimit_exceeded ) {
+                       rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+               }
        }
+
        return SLAP_CB_CONTINUE;
 }
 
@@ -1976,8 +2233,8 @@ add_filter_attrs(
                (*new_attrs)[i].an_desc = attrs->attrs[i].an_desc;
        }
        BER_BVZERO( &(*new_attrs)[i].an_name );
-       alluser = an_find(*new_attrs, &AllUser);
-       allop = an_find(*new_attrs, &AllOper);
+       alluser = an_find( *new_attrs, slap_bv_all_user_attrs );
+       allop = an_find( *new_attrs, slap_bv_all_operational_attrs );
 
        j = i;
        for ( i=0; i<fattr_cnt; i++ ) {
@@ -1994,19 +2251,19 @@ add_filter_attrs(
                (*new_attrs)[j].an_name = filter_attrs[i].an_name;
                (*new_attrs)[j].an_desc = filter_attrs[i].an_desc;
                (*new_attrs)[j].an_oc = NULL;
-               (*new_attrs)[j].an_oc_exclude = 0;
+               (*new_attrs)[j].an_flags = 0;
                j++;
        }
        if ( addoc ) {
                (*new_attrs)[j].an_name = slap_schema.si_ad_objectClass->ad_cname;
                (*new_attrs)[j].an_desc = slap_schema.si_ad_objectClass;
                (*new_attrs)[j].an_oc = NULL;
-               (*new_attrs)[j].an_oc_exclude = 0;
+               (*new_attrs)[j].an_flags = 0;
                j++;
        }
        BER_BVZERO( &(*new_attrs)[j].an_name );
 
-       return count;
+       return j;
 }
 
 /* NOTE: this is a quick workaround to let pcache minimally interact
@@ -2061,6 +2318,13 @@ pcache_op_privdb(
                return SLAP_CB_CONTINUE;
        }
 
+       /* The cache DB isn't open yet */
+       if ( cm->defer_db_open ) {
+               send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+                       "pcachePrivDB: cacheDB not available" );
+               return rs->sr_err;
+       }
+
        /* FIXME: might be a little bit exaggerated... */
        if ( !be_isroot( op ) ) {
                save_cb = op->o_callback;
@@ -2076,6 +2340,7 @@ pcache_op_privdb(
        type = slap_req2op( op->o_tag );
        if ( type != SLAP_OP_LAST ) {
                BI_op_func      **func;
+               int             rc;
 
                /* execute, if possible */
                func = &cm->db.be_bind;
@@ -2084,7 +2349,10 @@ pcache_op_privdb(
        
                        op2.o_bd = &cm->db;
 
-                       return func[ type ]( &op2, rs );
+                       rc = func[ type ]( &op2, rs );
+                       if ( type == SLAP_OP_BIND && rc == LDAP_SUCCESS ) {
+                               op->o_conn->c_authz_cookie = cm->db.be_private;
+                       }
                }
        }
 
@@ -2129,10 +2397,21 @@ pcache_op_search(
        }
 #endif /* PCACHE_CONTROL_PRIVDB */
 
+       /* The cache DB isn't open yet */
+       if ( cm->defer_db_open ) {
+               send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+                       "pcachePrivDB: cacheDB not available" );
+               return rs->sr_err;
+       }
+
+       /* pickup runtime ACL changes */
+       cm->db.be_acl = op->o_bd->be_acl;
+
        tempstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len+1, op->o_tmpmemctx );
        tempstr.bv_len = 0;
        if ( filter2template( op, op->ors_filter, &tempstr, &filter_attrs,
-               &fattr_cnt, &fattr_got_oc )) {
+               &fattr_cnt, &fattr_got_oc ))
+       {
                op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
                return SLAP_CB_CONTINUE;
        }
@@ -2154,15 +2433,15 @@ pcache_op_search(
                QueryTemplate *qt = qm->attr_sets[attr_set].templates;
                for (; qt; qt = qt->qtnext ) {
                        /* find if template i can potentially answer tempstr */
-                       if (qt->querystr.bv_len != tempstr.bv_len ||
-                               strcasecmp( qt->querystr.bv_val, tempstr.bv_val ))
+                       if ( ber_bvstrcasecmp( &qt->querystr, &tempstr ) != 0 )
                                continue;
                        cacheable = 1;
                        qtemp = qt;
                        Debug( pcache_debug, "Entering QC, querystr = %s\n",
                                        op->ors_filterstr.bv_val, 0, 0 );
-                       answerable = (*(qm->qcfunc))(op, qm, &query, qt);
+                       answerable = qm->qcfunc(op, qm, &query, qt);
 
+                       /* if != NULL, rlocks qtemp->t_rwlock */
                        if (answerable)
                                break;
                }
@@ -2170,22 +2449,33 @@ pcache_op_search(
        op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
 
        if (answerable) {
-               /* Need to clear the callbacks of the original operation,
-                * in case there are other overlays */
                BackendDB       *save_bd = op->o_bd;
                slap_callback   *save_cb = op->o_callback;
 
-               Debug( pcache_debug, "QUERY ANSWERABLE\n", 0, 0, 0 );
+               ldap_pvt_thread_mutex_lock( &answerable->answerable_cnt_mutex );
+               answerable->answerable_cnt++;
+               Debug( pcache_debug, "QUERY ANSWERABLE (answered %lu times)\n",
+                       answerable->answerable_cnt, 0, 0 );
+               ldap_pvt_thread_mutex_unlock( &answerable->answerable_cnt_mutex );
+
                op->o_tmpfree( filter_attrs, op->o_tmpmemctx );
+               ldap_pvt_thread_rdwr_rlock(&answerable->rwlock);
                if ( BER_BVISNULL( &answerable->q_uuid )) {
                        /* No entries cached, just an empty result set */
                        i = rs->sr_err = 0;
                        send_ldap_result( op, rs );
                } else {
                        op->o_bd = &cm->db;
-                       op->o_callback = NULL;
+                       if ( cm->response_cb == PCACHE_RESPONSE_CB_TAIL ) {
+                               /* The cached entry was already processed by any
+                                * other overlays, so don't let it get processed again.
+                                */
+                               op->o_callback = NULL;
+                       }
                        i = cm->db.bd_info->bi_op_search( op, rs );
                }
+               ldap_pvt_thread_rdwr_runlock(&answerable->rwlock);
+               /* locked by qtemp->qcfunc (query_containment) */
                ldap_pvt_thread_rdwr_runlock(&qtemp->t_rwlock);
                op->o_bd = save_bd;
                op->o_callback = save_cb;
@@ -2220,7 +2510,7 @@ pcache_op_search(
 
                cb = op->o_tmpalloc( sizeof(*cb) + sizeof(*si), op->o_tmpmemctx );
                cb->sc_response = pcache_response;
-               cb->sc_cleanup = NULL;
+               cb->sc_cleanup = pcache_op_cleanup;
                cb->sc_private = (cb+1);
                si = cb->sc_private;
                si->on = on;
@@ -2229,8 +2519,16 @@ pcache_op_search(
                si->max = cm->num_entries_limit ;
                si->over = 0;
                si->count = 0;
+               si->slimit = 0;
+               si->slimit_exceeded = 0;
+               si->caching_reason = PC_IGNORE;
+               if ( op->ors_slimit > 0 && op->ors_slimit < cm->num_entries_limit ) {
+                       si->slimit = op->ors_slimit;
+                       op->ors_slimit = cm->num_entries_limit;
+               }
                si->head = NULL;
                si->tail = NULL;
+               si->swap_saved_attrs = 1;
                si->save_attrs = op->ors_attrs;
 
                op->ors_attrs = qtemp->t_attrs.attrs;
@@ -2278,7 +2576,7 @@ get_attr_set(
                count = 1;
                attrs = slap_anlist_all_user_attributes;
 
-       } else if ( count == 1 && strcmp( attrs[0].an_name.bv_val, LDAP_NO_ATTRS ) == 0 ) {
+       } else if ( count == 1 && bvmatch( &attrs[0].an_name, slap_bv_no_attrs ) ) {
                count = 0;
                attrs = NULL;
        }
@@ -2332,9 +2630,15 @@ consistency_check(
 
        SlapReply rs = {REP_RESULT};
        CachedQuery* query;
-       int return_val, pause = 1;
+       int return_val, pause = PCACHE_CC_PAUSED;
        QueryTemplate* templ;
 
+       /* Don't expire anything when we're offline */
+       if ( cm->cc_paused & PCACHE_CC_OFFLINE ) {
+               pause = PCACHE_CC_OFFLINE;
+               goto leave;
+       }
+
        connection_fake_init( &conn, &opbuf, ctx );
        op = &opbuf.ob_op;
 
@@ -2342,7 +2646,7 @@ consistency_check(
        op->o_dn = cm->db.be_rootdn;
        op->o_ndn = cm->db.be_rootndn;
 
-       cm->cc_arg = arg;
+       cm->cc_arg = arg;
 
        for (templ = qm->templates; templ; templ=templ->qmnext) {
                query = templ->query_last;
@@ -2389,12 +2693,15 @@ consistency_check(
                        query = templ->query_last;
                }
        }
+
+leave:
        ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
        if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
                ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
        }
        /* If there were no queries, defer processing for a while */
-       cm->cc_paused = pause;
+       if ( cm->cc_paused != pause )
+               cm->cc_paused = pause;
        ldap_pvt_runqueue_resched( &slapd_rq, rtask, pause );
 
        ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
@@ -2409,7 +2716,9 @@ enum {
        PC_ATTR,
        PC_TEMP,
        PC_RESP,
-       PC_QUERIES
+       PC_QUERIES,
+       PC_OFFLINE,
+       PC_PRIVATE_DB
 };
 
 static ConfigDriver pc_cf_gen;
@@ -2417,37 +2726,77 @@ static ConfigLDAPadd pc_ldadd;
 static ConfigCfAdd pc_cfadd;
 
 static ConfigTable pccfg[] = {
-       { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> "
+       { "pcache", "backend> <max_entries> <numattrsets> <entry limit> "
                                "<cycle_time",
                6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
-               "( OLcfgOvAt:2.1 NAME 'olcProxyCache' "
-                       "DESC 'ProxyCache basic parameters' "
+               "( OLcfgOvAt:2.1 NAME ( 'olcPcache' 'olcProxyCache' ) "
+                       "DESC 'Proxy Cache basic parameters' "
                        "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
-       { "proxyattrset", "index> <attributes...",
+       { "pcacheAttrset", "index> <attributes...",
                2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
-               "( OLcfgOvAt:2.2 NAME 'olcProxyAttrset' "
+               "( OLcfgOvAt:2.2 NAME ( 'olcPcacheAttrset' 'olcProxyAttrset' ) "
                        "DESC 'A set of attributes to cache' "
                        "SYNTAX OMsDirectoryString )", NULL, NULL },
-       { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL",
-               4, 5, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
-               "( OLcfgOvAt:2.3 NAME 'olcProxyTemplate' "
-                       "DESC 'Filter template, attrset, cache TTL, optional negative TTL' "
+       { "pcacheTemplate", "filter> <attrset-index> <TTL> <negTTL> "
+                       "<limitTTL> <TTR",
+               4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+               "( OLcfgOvAt:2.3 NAME ( 'olcPcacheTemplate' 'olcProxyCacheTemplate' ) "
+                       "DESC 'Filter template, attrset, cache TTL, "
+                               "optional negative TTL, optional sizelimit TTL, "
+                               "optional TTR' "
                        "SYNTAX OMsDirectoryString )", NULL, NULL },
-       { "response-callback", "head|tail(default)",
+       { "pcachePosition", "head|tail(default)",
                2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
-               "( OLcfgOvAt:2.4 NAME 'olcProxyResponseCB' "
+               "( OLcfgOvAt:2.4 NAME 'olcPcachePosition' "
                        "DESC 'Response callback position in overlay stack' "
                        "SYNTAX OMsDirectoryString )", NULL, NULL },
-       { "proxyCacheQueries", "queries",
+       { "pcacheMaxQueries", "queries",
                2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
-               "( OLcfgOvAt:2.5 NAME 'olcProxyCacheQueries' "
+               "( OLcfgOvAt:2.5 NAME ( 'olcPcacheMaxQueries' 'olcProxyCacheQueries' ) "
                        "DESC 'Maximum number of queries to cache' "
                        "SYNTAX OMsInteger )", NULL, NULL },
-       { "proxySaveQueries", "TRUE|FALSE",
+       { "pcachePersist", "TRUE|FALSE",
                2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
-               "( OLcfgOvAt:2.6 NAME 'olcProxySaveQueries' "
+               "( OLcfgOvAt:2.6 NAME ( 'olcPcachePersist' 'olcProxySaveQueries' ) "
                        "DESC 'Save cached queries for hot restart' "
                        "SYNTAX OMsBoolean )", NULL, NULL },
+       { "pcacheValidate", "TRUE|FALSE",
+               2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+               "( OLcfgOvAt:2.7 NAME ( 'olcPcacheValidate' 'olcProxyCheckCacheability' ) "
+                       "DESC 'Check whether the results of a query are cacheable, e.g. for schema issues' "
+                       "SYNTAX OMsBoolean )", NULL, NULL },
+       { "pcacheOffline", "TRUE|FALSE",
+               2, 2, 0, ARG_ON_OFF|ARG_MAGIC|PC_OFFLINE, pc_cf_gen,
+               "( OLcfgOvAt:2.8 NAME 'olcPcacheOffline' "
+                       "DESC 'Set cache to offline mode and disable expiration' "
+                       "SYNTAX OMsBoolean )", NULL, NULL },
+       { "pcache-", "private database args",
+               1, 0, STRLENOF("pcache-"), ARG_MAGIC|PC_PRIVATE_DB, pc_cf_gen,
+               NULL, NULL, NULL },
+
+       /* Legacy keywords */
+       { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> "
+                               "<cycle_time",
+               6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
+               NULL, NULL, NULL },
+       { "proxyattrset", "index> <attributes...",
+               2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
+               NULL, NULL, NULL },
+       { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL",
+               4, 6, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+               NULL, NULL, NULL },
+       { "response-callback", "head|tail(default)",
+               2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
+               NULL, NULL, NULL },
+       { "proxyCacheQueries", "queries",
+               2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
+               NULL, NULL, NULL },
+       { "proxySaveQueries", "TRUE|FALSE",
+               2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
+               NULL, NULL, NULL },
+       { "proxyCheckCacheability", "TRUE|FALSE",
+               2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+               NULL, NULL, NULL },
 
        { NULL, NULL, 0, 0, 0, ARG_IGNORED }
 };
@@ -2457,16 +2806,26 @@ static ConfigOCs pcocs[] = {
                "NAME 'olcPcacheConfig' "
                "DESC 'ProxyCache configuration' "
                "SUP olcOverlayConfig "
-               "MUST ( olcProxyCache $ olcProxyAttrset $ olcProxyTemplate ) "
-               "MAY ( olcProxyResponseCB $ olcProxyCacheQueries $ olcProxySaveQueries ) )",
+               "MUST ( olcPcache $ olcPcacheAttrset $ olcPcacheTemplate ) "
+               "MAY ( olcPcachePosition $ olcPcacheMaxQueries $ olcPcachePersist $ "
+                       "olcPcacheValidate $ olcPcacheOffline ) )",
                Cft_Overlay, pccfg, NULL, pc_cfadd },
        { "( OLcfgOvOc:2.2 "
                "NAME 'olcPcacheDatabase' "
                "DESC 'Cache database configuration' "
-               "AUXILIARY )", Cft_Misc, pccfg, pc_ldadd },
+               "AUXILIARY )", Cft_Misc, olcDatabaseDummy, pc_ldadd },
        { NULL, 0, NULL }
 };
 
+static int pcache_db_open2( slap_overinst *on, ConfigReply *cr );
+
+static int
+pc_ldadd_cleanup( ConfigArgs *c )
+{
+       slap_overinst *on = c->ca_private;
+       return pcache_db_open2( on, &c->reply );
+}
+
 static int
 pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
 {
@@ -2480,6 +2839,12 @@ pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
        on = (slap_overinst *)p->ce_bi;
        cm = on->on_bi.bi_private;
        ca->be = &cm->db;
+       /* Defer open if this is an LDAPadd */
+       if ( CONFIG_ONLINE_ADD( ca ))
+               ca->cleanup = pc_ldadd_cleanup;
+       else
+               cm->defer_db_open = 0;
+       ca->ca_private = on;
        return LDAP_SUCCESS;
 }
 
@@ -2492,9 +2857,15 @@ pc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
        struct berval bv;
 
        /* FIXME: should not hardcode "olcDatabase" here */
-       bv.bv_len = sprintf( ca->cr_msg, "olcDatabase=%s", cm->db.bd_info->bi_type );
+       bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+               "olcDatabase=" SLAP_X_ORDERED_FMT "%s",
+               0, cm->db.bd_info->bi_type );
+       if ( bv.bv_len >= sizeof( ca->cr_msg ) ) {
+               return -1;
+       }
        bv.bv_val = ca->cr_msg;
        ca->be = &cm->db;
+       cm->defer_db_open = 0;
 
        /* We can only create this entry if the database is table-driven
         */
@@ -2554,17 +2925,15 @@ pc_cf_gen( ConfigArgs *c )
                        break;
                case PC_TEMP:
                        for (temp=qm->templates; temp; temp=temp->qmnext) {
-                               if ( temp->negttl ) {
-                                       bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
-                                               " %d %ld %ld",
-                                               temp->attr_set_index,
-                                               temp->ttl,
-                                               temp->negttl );
-                               } else {
-                                       bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), " %d %ld",
-                                               temp->attr_set_index,
-                                               temp->ttl );
-                               }
+                               /* HEADS-UP: always print all;
+                                * if optional == 0, ignore */
+                               bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       " %d %ld %ld %ld %ld",
+                                       temp->attr_set_index,
+                                       temp->ttl,
+                                       temp->negttl,
+                                       temp->limitttl,
+                                       temp->ttr );
                                bv.bv_len += temp->querystr.bv_len + 2;
                                bv.bv_val = ch_malloc( bv.bv_len+1 );
                                ptr = bv.bv_val;
@@ -2588,17 +2957,23 @@ pc_cf_gen( ConfigArgs *c )
                case PC_QUERIES:
                        c->value_int = cm->max_queries;
                        break;
+               case PC_OFFLINE:
+                       c->value_int = (cm->cc_paused & PCACHE_CC_OFFLINE) != 0;
+                       break;
                }
                return rc;
        } else if ( c->op == LDAP_MOD_DELETE ) {
-               return 1;       /* FIXME */
-#if 0
+               rc = 1;
                switch( c->type ) {
-               case PC_ATTR:
+               case PC_ATTR: /* FIXME */
                case PC_TEMP:
+                       break;
+               case PC_OFFLINE:
+                       cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+                       rc = 0;
+                       break;
                }
                return rc;
-#endif
        }
 
        switch( c->type ) {
@@ -2667,6 +3042,7 @@ pc_cf_gen( ConfigArgs *c )
                        Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
                        return( 1 );
                }
+
                cm->cc_period = (time_t)t;
                Debug( pcache_debug,
                                "Total # of attribute sets to be cached = %d.\n",
@@ -2772,7 +3148,7 @@ pc_cf_gen( ConfigArgs *c )
                                        attr_name->an_name = attr_name->an_desc->ad_cname;
                                }
                                attr_name->an_oc = NULL;
-                               attr_name->an_oc_exclude = 0;
+                               attr_name->an_flags = 0;
                                if ( attr_name->an_desc == slap_schema.si_ad_objectClass )
                                        qm->attr_sets[num].flags |= PC_GOT_OC;
                                attr_name++;
@@ -2813,23 +3189,49 @@ pc_cf_gen( ConfigArgs *c )
                ldap_pvt_thread_rdwr_init( &temp->t_rwlock );
                temp->query = temp->query_last = NULL;
                if ( lutil_parse_time( c->argv[3], &t ) != 0 ) {
-                       snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template ttl=\"%s\"",
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "unable to parse template ttl=\"%s\"",
                                c->argv[3] );
                        Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
                        return( 1 );
                }
                temp->ttl = (time_t)t;
-               if ( c->argc == 5 ) {
+               temp->negttl = (time_t)0;
+               temp->limitttl = (time_t)0;
+               temp->ttr = (time_t)0;
+               switch ( c->argc ) {
+               case 7:
+                       if ( lutil_parse_time( c->argv[6], &t ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse template ttr=\"%s\"",
+                                       c->argv[6] );
+                               Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       return( 1 );
+                       }
+                       temp->ttr = (time_t)t;
+                       /* fallthru */
+
+               case 6:
+                       if ( lutil_parse_time( c->argv[5], &t ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "unable to parse template sizelimit ttl=\"%s\"",
+                                       c->argv[5] );
+                               Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                                       return( 1 );
+                       }
+                       temp->limitttl = (time_t)t;
+                       /* fallthru */
+
+               case 5:
                        if ( lutil_parse_time( c->argv[4], &t ) != 0 ) {
                                snprintf( c->cr_msg, sizeof( c->cr_msg ),
-                                       "unable to parse template negttl=\"%s\"",
+                                       "unable to parse template negative ttl=\"%s\"",
                                        c->argv[4] );
                                Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
                                        return( 1 );
                        }
                        temp->negttl = (time_t)t;
-               } else {
-                       temp->negttl = 0;
+                       break;
                }
 
                temp->no_of_queries = 0;
@@ -2870,7 +3272,68 @@ pc_cf_gen( ConfigArgs *c )
                }
                cm->max_queries = c->value_int;
                break;
+       case PC_OFFLINE:
+               if ( c->value_int )
+                       cm->cc_paused |= PCACHE_CC_OFFLINE;
+               else
+                       cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+               break;
+       case PC_PRIVATE_DB:
+               if ( cm->db.be_private == NULL ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "private database must be defined before setting database specific options" );
+                       Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return( 1 );
+               }
+
+               if ( cm->db.bd_info->bi_cf_ocs ) {
+                       ConfigTable     *ct;
+                       ConfigArgs      c2 = *c;
+                       char            *argv0 = c->argv[ 0 ];
+
+                       c->argv[ 0 ] = &argv0[ STRLENOF( "proxycache-" ) ];
+
+                       ct = config_find_keyword( cm->db.bd_info->bi_cf_ocs->co_table, c );
+                       if ( ct == NULL ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "private database does not recognize specific option '%s'",
+                                       c->argv[ 0 ] );
+                               Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                               rc = 1;
+
+                       } else {
+                               c->table = cm->db.bd_info->bi_cf_ocs->co_type;
+                               c->be = &cm->db;
+                               c->bi = c->be->bd_info;
+
+                               rc = config_add_vals( ct, c );
+
+                               c->bi = c2.bi;
+                               c->be = c2.be;
+                               c->table = c2.table;
+                       }
+
+                       c->argv[ 0 ] = argv0;
+
+               } else if ( cm->db.be_config != NULL ) {
+                       char    *argv0 = c->argv[ 0 ];
+
+                       c->argv[ 0 ] = &argv0[ STRLENOF( "proxycache-" ) ];
+                       rc = cm->db.be_config( &cm->db, c->fname, c->lineno, c->argc, c->argv );
+                       c->argv[ 0 ] = argv0;
+
+               } else {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "no means to set private database specific options" );
+                       Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+                       return 1;
+               }
+               break;
+       default:
+               rc = SLAP_CONF_UNKNOWN;
+               break;
        }
+
        return rc;
 }
 
@@ -2910,7 +3373,7 @@ pcache_db_init(
        cm->db = *be;
        SLAP_DBFLAGS(&cm->db) |= SLAP_DBFLAG_NO_SCHEMA_CHECK;
        cm->db.be_private = NULL;
-       cm->db.be_pcl_mutexp = &cm->db.be_pcl_mutex;
+       cm->db.bd_self = &cm->db;
        cm->qm = qm;
        cm->numattrsets = 0;
        cm->num_entries_limit = 5;
@@ -2919,9 +3382,12 @@ pcache_db_init(
        cm->cur_entries = 0;
        cm->max_queries = 10000;
        cm->save_queries = 0;
+       cm->check_cacheability = 0;
        cm->response_cb = PCACHE_RESPONSE_CB_TAIL;
+       cm->defer_db_open = 1;
        cm->cc_period = 1000;
        cm->cc_paused = 0;
+       cm->cc_arg = NULL;
 
        qm->attr_sets = NULL;
        qm->templates = NULL;
@@ -2962,64 +3428,39 @@ pcache_cachedquery_open_cb( Operation *op, SlapReply *rs )
 }
 
 static int
-pcache_db_open(
-       BackendDB *be,
-       ConfigReply *cr )
+pcache_cachedquery_count_cb( Operation *op, SlapReply *rs )
 {
-       slap_overinst   *on = (slap_overinst *)be->bd_info;
-       cache_manager   *cm = on->on_bi.bi_private;
-       query_manager*  qm = cm->qm;
-       int             i, ncf = 0, rf = 0, nrf = 0, rc = 0;
-
-       /* check attr sets */
-       for ( i = 0; i < cm->numattrsets; i++) {
-               if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) {
-                       if ( qm->attr_sets[i].flags & PC_REFERENCED ) {
-                               Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i, 0, 0 );
-                               rf++;
-
-                       } else {
-                               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i, 0, 0 );
-                       }
-                       ncf++;
-
-               } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) {
-                       Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i, 0, 0 );
-                       nrf++;
-               }
-       }
+       assert( op->o_tag == LDAP_REQ_SEARCH );
 
-       if ( ncf || rf || nrf ) {
-               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf, 0, 0 );
-               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf, 0, 0 );
-               Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf, 0, 0 );
+       if ( rs->sr_type == REP_SEARCH ) {
+               int     *countp = (int *)op->o_callback->sc_private;
 
-               if ( rf > 0 ) {
-                       return 1;
-               }
+               (*countp)++;
        }
 
-       /* need to inherit something from the original database... */
-       cm->db.be_def_limit = be->be_def_limit;
-       cm->db.be_limits = be->be_limits;
-       cm->db.be_acl = be->be_acl;
-       cm->db.be_dfltaccess = be->be_dfltaccess;
+       return 0;
+}
 
-       if ( SLAP_DBMONITORING( be ) ) {
-               SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING;
+static int
+pcache_db_open2(
+       slap_overinst *on,
+       ConfigReply *cr )
+{
+       cache_manager   *cm = on->on_bi.bi_private;
+       query_manager*  qm = cm->qm;
+       int rc;
 
-       } else {
-               SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING;
+       rc = backend_startup_one( &cm->db, cr );
+       if ( rc == 0 ) {
+               cm->defer_db_open = 0;
        }
 
-       rc = backend_startup_one( &cm->db, NULL );
-
        /* There is no runqueue in TOOL mode */
-       if ( slapMode & SLAP_SERVER_MODE ) {
+       if (( slapMode & SLAP_SERVER_MODE ) && rc == 0 ) {
                ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
                ldap_pvt_runqueue_insert( &slapd_rq, cm->cc_period,
                        consistency_check, on,
-                       "pcache_consistency", be->be_suffix[0].bv_val );
+                       "pcache_consistency", cm->db.be_suffix[0].bv_val );
                ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
 
                /* Cached database must have the rootdn */
@@ -3043,8 +3484,9 @@ pcache_db_open(
                        slap_callback   cb = { 0 };
                        SlapReply       rs = { 0 };
                        BerVarray       vals = NULL;
-                       Filter          f = { 0 };
-                       AttributeName   attrs[ 2 ] = { 0 };
+                       Filter          f = { 0 }, f2 = { 0 };
+                       AttributeAssertion      ava = ATTRIBUTEASSERTION_INIT;
+                       AttributeName   attrs[ 2 ] = {{{ 0 }}};
 
                        connection_fake_init( &conn, &opbuf, thrctx );
                        op = &opbuf.ob_op;
@@ -3091,10 +3533,87 @@ pcache_db_open(
                                ber_bvarray_free_x( vals, op->o_tmpmemctx );
                        }
 
+                       /* count cached entries */
+                       f.f_choice = LDAP_FILTER_NOT;
+                       f.f_not = &f2;
+                       f2.f_choice = LDAP_FILTER_EQUALITY;
+                       f2.f_ava = &ava;
+                       f2.f_av_desc = slap_schema.si_ad_objectClass;
+                       BER_BVSTR( &f2.f_av_value, "glue" );
+                       ber_str2bv( "(!(objectClass=glue))", 0, 0, &op->ors_filterstr );
+
+                       op->ors_slimit = SLAP_NO_LIMIT;
+                       op->ors_scope = LDAP_SCOPE_SUBTREE;
+                       op->ors_attrs = slap_anlist_no_attrs;
+
+                       op->o_callback->sc_response = pcache_cachedquery_count_cb;
+                       rs.sr_nentries = 0;
+                       op->o_callback->sc_private = &rs.sr_nentries;
+
+                       rc = op->o_bd->be_search( op, &rs );
+
+                       cm->cur_entries = rs.sr_nentries;
+
                        /* ignore errors */
                        rc = 0;
                }
        }
+       return rc;
+}
+
+static int
+pcache_db_open(
+       BackendDB *be,
+       ConfigReply *cr )
+{
+       slap_overinst   *on = (slap_overinst *)be->bd_info;
+       cache_manager   *cm = on->on_bi.bi_private;
+       query_manager*  qm = cm->qm;
+       int             i, ncf = 0, rf = 0, nrf = 0, rc = 0;
+
+       /* check attr sets */
+       for ( i = 0; i < cm->numattrsets; i++) {
+               if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) {
+                       if ( qm->attr_sets[i].flags & PC_REFERENCED ) {
+                               Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i, 0, 0 );
+                               rf++;
+
+                       } else {
+                               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i, 0, 0 );
+                       }
+                       ncf++;
+
+               } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) {
+                       Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i, 0, 0 );
+                       nrf++;
+               }
+       }
+
+       if ( ncf || rf || nrf ) {
+               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf, 0, 0 );
+               Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf, 0, 0 );
+               Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf, 0, 0 );
+
+               if ( rf > 0 ) {
+                       return 1;
+               }
+       }
+
+       /* need to inherit something from the original database... */
+       cm->db.be_def_limit = be->be_def_limit;
+       cm->db.be_limits = be->be_limits;
+       cm->db.be_acl = be->be_acl;
+       cm->db.be_dfltaccess = be->be_dfltaccess;
+
+       if ( SLAP_DBMONITORING( be ) ) {
+               SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING;
+
+       } else {
+               SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING;
+       }
+
+       if ( !cm->defer_db_open )
+               rc = pcache_db_open2( on, cr );
 
        return rc;
 }
@@ -3133,7 +3652,7 @@ pcache_db_close(
                slap_callback   cb = { 0 };
 
                SlapReply       rs = { REP_RESULT };
-               Modifications   mod = { 0 };
+               Modifications   mod = {{ 0 }};
 
                thrctx = ldap_pvt_thread_pool_context();
 
@@ -3145,7 +3664,7 @@ pcache_db_close(
                                for ( qc = tm->query; qc; qc = qc->next ) {
                                        struct berval   bv;
 
-                                       if ( query2url( op, qc, &bv ) == 0 ) {
+                                       if ( query2url( op, qc, &bv, 0 ) == 0 ) {
                                                ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx );
                                        }
                                }
@@ -3173,6 +3692,7 @@ pcache_db_close(
                mod.sml_type = ad_cachedQueryURL->ad_cname;
                mod.sml_values = vals;
                mod.sml_nvalues = NULL;
+                mod.sml_numvals = 1;
                mod.sml_next = NULL;
                Debug( pcache_debug,
                        "%sSETTING CACHED QUERY URLS\n",
@@ -3426,7 +3946,7 @@ pcache_parse_query_delete(
        if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_UUID ) {
                if ( uuid != NULL ) {
                        struct berval   bv;
-                       Syntax          *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax;
+                       char            uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
 
                        tag = ber_scanf( ber, "m", &bv );
                        if ( tag == LBER_ERROR ) {
@@ -3443,7 +3963,14 @@ pcache_parse_query_delete(
                                goto decoding_error;
                        }
 
-                       ber_dupbv_x( uuid, &bv, ctx );
+                       rc = lutil_uuidstr_from_normalized(
+                               bv.bv_val, bv.bv_len,
+                               uuidbuf, sizeof( uuidbuf ) );
+                       if ( rc == -1 ) {
+                               goto decoding_error;
+                       }
+                       ber_str2bv( uuidbuf, rc, 1, uuid );
+                       rc = LDAP_SUCCESS;
 
                } else {
                        tag = ber_skip_tag( ber, &len );
@@ -3496,37 +4023,35 @@ pcache_exop_query_delete(
 {
        BackendDB       *bd = op->o_bd;
 
-       struct berval   uuid = BER_BVNULL;
-       char            buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
-       int             len = 0;
+       struct berval   uuid = BER_BVNULL,
+                       *uuidp = NULL;
+       char            buf[ SLAP_TEXT_BUFLEN ];
+       unsigned        len;
        ber_tag_t       tag = LBER_DEFAULT;
 
+       if ( LogTest( LDAP_DEBUG_STATS ) ) {
+               uuidp = &uuid;
+       }
+
        rs->sr_err = pcache_parse_query_delete( op->ore_reqdata,
-               &tag, &op->o_req_ndn, &uuid,
+               &tag, &op->o_req_ndn, uuidp,
                &rs->sr_text, op->o_tmpmemctx );
        if ( rs->sr_err != LDAP_SUCCESS ) {
                return rs->sr_err;
        }
 
        if ( LogTest( LDAP_DEBUG_STATS ) ) {
-               if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
-                       len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val );
-               }
-
-               if ( !BER_BVISNULL( &uuid ) ) {
-                       char    uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
-
-                       lutil_uuidstr_from_normalized(
-                               uuid.bv_val, uuid.bv_len,
-                               uuidbuf, sizeof( uuidbuf ) );
+               assert( !BER_BVISNULL( &op->o_req_ndn ) );
+               len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val );
 
-                       snprintf( &buf[ len ], sizeof( buf ) - len, " UUID=\"%s\"", uuidbuf );
+               if ( !BER_BVISNULL( &uuid ) && len < sizeof( buf ) ) {
+                       snprintf( &buf[ len ], sizeof( buf ) - len, " queryId=\"%s\"", uuid.bv_val );
                }
 
                Debug( LDAP_DEBUG_STATS, "%s QUERY DELETE%s\n",
                        op->o_log_prefix, buf, 0 );
-               op->o_req_dn = op->o_req_ndn;
        }
+       op->o_req_dn = op->o_req_ndn;
 
        op->o_bd = select_backend( &op->o_req_ndn, 0 );
        rs->sr_err = backend_check_restrictions( op, rs,
@@ -3587,9 +4112,8 @@ pcache_op_extended( Operation *op, SlapReply *rs )
                } else if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE ) {
                        if ( !BER_BVISNULL( &uuid ) ) {
                                /* remove the selected query */
-                               remove_query_and_data( op, rs, cm, &uuid );
-                               op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
-                               rs->sr_err = LDAP_SUCCESS;
+                               rs->sr_err = pcache_remove_query_from_cache( op,
+                                       cm, &uuid );
 
                        } else {
                                /* TODO: remove all queries */
@@ -3597,6 +4121,8 @@ pcache_op_extended( Operation *op, SlapReply *rs )
                                rs->sr_text = "deletion of all queries not implemented";
                        }
                }
+
+               op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
        }
 
        return rs->sr_err;
@@ -3655,6 +4181,7 @@ pcache_initialize()
                                "pcache_initialize: register_at #%d failed\n", i, 0, 0 );
                        return code;
                }
+               (*as[i].adp)->ad_type->sat_flags |= SLAP_AT_HIDE;
        }
 
        pcache.on_bi.bi_type = "pcache";