/* OpenLDAP WiredTiger backend */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2002-2021 The OpenLDAP Foundation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* ACKNOWLEDGEMENTS:
 * This work was developed by HAMANO Tsukasa <hamano@osstech.co.jp>
 * based on back-bdb for inclusion in OpenLDAP Software.
 * WiredTiger is a product of MongoDB Inc.
 */

#include "portable.h"

#include <stdio.h>
#include "back-wt.h"
#include "slap-config.h"
#include "idl.h"

static char *
mkrevdn(struct berval src){
	char *dst, *p;
	char *rdn;
	size_t rdn_len;

	p = dst = ch_malloc(src.bv_len + 2);
	while(src.bv_len){
		rdn = ber_bvrchr( &src, ',' );
		if (rdn) {
			rdn_len = src.bv_len;
			src.bv_len = rdn - src.bv_val;
			rdn_len -= src.bv_len + 1;
			rdn++;
		}else{
			/* first rdn */
			rdn_len = src.bv_len;
			rdn = src.bv_val;
			src.bv_len = 0;
		}
		memcpy( p, rdn, rdn_len );
		p += rdn_len;
		*p++ = ',';
	}
	*p = '\0';
	return dst;
}

int
wt_dn2id_add(
	Operation *op,
	wt_ctx *wc,
	ID pid,
	Entry *e)
{
	struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
	int rc;
	WT_SESSION *session = wc->session;
	WT_CURSOR *cursor = wc->dn2id_w;
	char *revdn = NULL;

	Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id_add 0x%lx: \"%s\"\n",
		   e->e_id, e->e_ndn );
	assert( e->e_id != NOID );

	/* make reverse dn */
	revdn = mkrevdn(e->e_nname);

	if(!cursor){
		rc = session->open_cursor(session, WT_TABLE_DN2ID, NULL,
								  "overwrite=false", &cursor);
		if(rc){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2id_add: open_cursor failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		wc->dn2id_w = cursor;
	}
	cursor->set_key(cursor, revdn);
	cursor->set_value(cursor, e->e_ndn, e->e_id, pid);
	rc = cursor->insert(cursor);
	if(rc){
		Debug( LDAP_DEBUG_ANY,
			   "wt_dn2id_add: insert failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
		goto done;
    }

	if (wi->wi_flags & WT_USE_IDLCACHE) {
		wt_idlcache_clear(op, wc, &e->e_nname);
	}

done:
	if(revdn){
		ch_free(revdn);
	}

#ifdef WT_CURSOR_CACHE
	if(cursor){
		cursor->reset(cursor);
	}
#else
	if(cursor){
		cursor->close(cursor);
		wc->dn2id_w = NULL;
	}
#endif

	Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id_add 0x%lx: %d\n", e->e_id, rc );
	return rc;
}

int
wt_dn2id_delete(
	Operation *op,
	wt_ctx *wc,
	struct berval *ndn)
{
	struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
	int rc = 0;
	WT_SESSION *session = wc->session;
	WT_CURSOR *cursor = wc->dn2id_w;
	char *revdn = NULL;

	Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id_delete %s\n", ndn->bv_val );

	/* make reverse dn */
	revdn = mkrevdn(*ndn);

	if(!cursor){
		rc = session->open_cursor(session, WT_TABLE_DN2ID, NULL,
								  "overwrite=false", &cursor);
		if ( rc ) {
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2id_delete: open_cursor failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		wc->dn2id_w = cursor;
	}

	cursor->set_key(cursor, revdn);
	rc = cursor->remove(cursor);
	if ( rc ) {
		Debug( LDAP_DEBUG_ANY,
			   "wt_dn2id_delete: remove failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
		goto done;
	}

	if (wi->wi_flags & WT_USE_IDLCACHE) {
		wt_idlcache_clear(op, wc, ndn);
	}

	Debug( LDAP_DEBUG_TRACE,
		   "<= wt_dn2id_delete %s: %d\n", ndn->bv_val, rc );
done:
	if(revdn){
		ch_free(revdn);
	}

#ifdef WT_CURSOR_CACHE
	if(cursor){
		cursor->reset(cursor);
	}
#else
	if(cursor){
		cursor->close(cursor);
		wc->dn2id_w = NULL;
	}
#endif
	return rc;
}

int
wt_dn2id(
	Operation *op,
	wt_ctx *wc,
    struct berval *ndn,
    ID *id)
{
	WT_SESSION *session = wc->session;
	WT_CURSOR *cursor = wc->dn2id_ndn;
	int rc = LDAP_SUCCESS;

	Debug( LDAP_DEBUG_TRACE, "=> wt_dn2id(\"%s\")\n", ndn->bv_val );

	if ( ndn->bv_len == 0 ) {
		*id = 0;
		goto done;
	}

	if(!cursor){
		rc = session->open_cursor(session, WT_INDEX_NDN
								  "(id)",
								  NULL, NULL, &cursor);
		if( rc ){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2id: cursor open failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		wc->dn2id_ndn = cursor;
	}

	cursor->set_key(cursor, ndn->bv_val);
	rc = cursor->search(cursor);
	switch( rc ){
	case 0:
		break;
	case WT_NOTFOUND:
		goto done;
	default:
		Debug( LDAP_DEBUG_ANY,
			   "wt_dn2id: search failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
		goto done;
	}
	rc = cursor->get_value(cursor, id);
	if( rc ){
		Debug( LDAP_DEBUG_ANY,
			   "wt_dn2id: get_value failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
		goto done;
	}

done:

#ifdef WT_CURSOR_CACHE
	if(cursor){
		cursor->reset(cursor);
	}
#else
	if(cursor){
		cursor->close(cursor);
		wc->dn2id_ndn = NULL;
	}
#endif

	if( rc ) {
		Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id: get failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
	} else {
		Debug( LDAP_DEBUG_TRACE, "<= wt_dn2id: got id=0x%lx\n",
			   *id );
	}

	return rc;
}

int
wt_dn2id_has_children(
	Operation *op,
	wt_ctx *wc,
	ID id )
{
	WT_SESSION *session = wc->session;
	WT_CURSOR *cursor = wc->index_pid;
	int rc;
	uint64_t key = id;

	if(!cursor){
		rc = session->open_cursor(session, WT_INDEX_PID,
								  NULL, NULL, &cursor);
		if( rc ){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2id_has_children: cursor open failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		wc->index_pid = cursor;
	}

	cursor->set_key(cursor, key);
	rc = cursor->search(cursor);

done:

#ifdef WT_CURSOR_CACHE
	if(cursor){
		cursor->reset(cursor);
	}
#else
	if(cursor){
		cursor->close(cursor);
		wc->index_pid = NULL;
	}
#endif

	return rc;
}

int
wt_dn2idl_db(
	Operation *op,
	wt_ctx *wc,
	struct berval *ndn,
	Entry *e,
	ID *ids,
	ID *stack)
{
	WT_SESSION *session = wc->session;
	WT_CURSOR *cursor = wc->dn2id;
	int rc;
	char *revdn = NULL;
	size_t revdn_len;
	char *key;
	ID id, pid;

	Debug( LDAP_DEBUG_TRACE,
		   "=> wt_dn2idl(\"%s\")\n",
		   ndn->bv_val );

	revdn = mkrevdn(*ndn);
	revdn_len = strlen(revdn);

	if ( !cursor ) {
		rc = session->open_cursor(session, WT_TABLE_DN2ID"(id, pid)",
								  NULL, NULL, &cursor);
		if( rc ){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2idl: cursor open failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		wc->dn2id = cursor;
	}
	cursor->set_key(cursor, revdn);
	rc = cursor->search(cursor);
	if( rc ){
		Debug( LDAP_DEBUG_ANY,
			   "wt_dn2idl: search failed: %s (%d)\n",
			   wiredtiger_strerror(rc), rc );
		goto done;
	}

	if( op->ors_scope == LDAP_SCOPE_CHILDREN ) {
		cursor->next(cursor);
	}

	do {
		rc = cursor->get_key(cursor, &key);
		if( rc ){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2idl: get_key failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}
		rc = cursor->get_value(cursor, &id, &pid);
		if( rc ){
			Debug( LDAP_DEBUG_ANY,
				   "wt_dn2id: get_value failed: %s (%d)\n",
				   wiredtiger_strerror(rc), rc );
			goto done;
		}

		if( strncmp(revdn, key, revdn_len) ){
			break;
		}

		if( op->ors_scope == LDAP_SCOPE_ONELEVEL && e->e_id != pid ){
			goto next;
		}
		wt_idl_append_one(ids, id);
	next:
		rc = cursor->next(cursor);
	}while(rc == 0);

	if (rc == WT_NOTFOUND ) {
		rc = LDAP_SUCCESS;
	}

	wt_idl_sort(ids, stack);
	Debug( LDAP_DEBUG_TRACE,
		   "<= wt_dn2idl_db: size=%ld first=%ld last=%ld\n",
		   (long) ids[0],
		   (long) WT_IDL_FIRST(ids),
		   (long) WT_IDL_LAST(ids) );

done:
	if(revdn){
		ch_free(revdn);
	}
#ifdef WT_CURSOR_CACHE
	if(cursor){
		cursor->reset(cursor);
	}
#else
	if(cursor){
		cursor->close(cursor);
		wc->dn2id = NULL;
	}
#endif
	return rc;
}

int
wt_dn2idl(
	Operation *op,
	wt_ctx *wc,
	struct berval *ndn,
	Entry *e,
	ID *ids,
	ID *stack)
{
	struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
	int rc;

	Debug( LDAP_DEBUG_TRACE,
		   "=> wt_dn2idl(\"%s\")\n", ndn->bv_val );

	if(op->ors_scope != LDAP_SCOPE_ONELEVEL &&
	   be_issuffix( op->o_bd, &e->e_nname )){
		WT_IDL_ALL(wi, ids);
		return 0;
	}

	if (wi->wi_flags & WT_USE_IDLCACHE) {
		rc = wt_idlcache_get(wc, ndn, op->ors_scope, ids);
		if (rc == 0) {
			/* cache hit */
			return rc;
		}
		/* cache miss */
	}

	if ( wi->wi_flags & WT_USE_IDLCACHE ) {
		wt_idlcache_begin(wc, ndn, op->ors_scope);
	}
	rc = wt_dn2idl_db(op, wc, ndn, e, ids, stack);
	if ( rc == 0 && wi->wi_flags & WT_USE_IDLCACHE ) {
		wt_idlcache_set(wc, ndn, op->ors_scope, ids);
	}

	return rc;
}

/*
 * Local variables:
 * indent-tabs-mode: t
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 */
