/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1998, 2017 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/heap.h"
#include "dbinc/lock.h"
#include "dbinc/mp.h"

/*
 * __heap_truncate --
 *	Truncate a database.
 *
 * PUBLIC: int __heap_truncate __P((DBC *, u_int32_t *));
 */
int
__heap_truncate(dbc, countp)
	DBC *dbc;
	u_int32_t *countp;
{
	DB *dbp;
	DB_LOCK lock, meta_lock;
	DB_MPOOLFILE *mpf;
	DBT log_dbt;
	HEAPHDR *hdr;
	HEAPMETA *meta;
	HEAPPG *pg;
	db_pgno_t pgno;
	int i, ret, t_ret;
	u_int32_t count, next_region, region_size;

	LOCK_INIT(lock);
	dbp = dbc->dbp;
	mpf = dbp->mpf;
	count = 0;
	next_region = FIRST_HEAP_RPAGE;
	region_size = HEAP_REGION_SIZE(dbp);

	/* Traverse the entire database, starting with the metadata pg. */
	pgno = PGNO_BASE_MD;
	if ((ret = __db_lget(dbc,
	    LCK_ALWAYS, pgno, DB_LOCK_WRITE, 0, &meta_lock)) != 0)
		return (ret);
	if ((ret = __memp_fget(mpf, &pgno,
	    dbc->thread_info, dbc->txn, DB_MPOOL_DIRTY, &meta)) != 0) {
		(void)__TLPUT(dbc, lock);
		goto err;
	}

	for (;;) {
		pgno++;
		if ((ret = __db_lget(dbc,
		    LCK_COUPLE, pgno, DB_LOCK_WRITE, 0, &lock)) != 0)
			break;
		if ((ret = __memp_fget(mpf, &pgno,
		    dbc->thread_info, dbc->txn, DB_MPOOL_DIRTY, &pg)) != 0) {
			if (ret == DB_PAGE_NOTFOUND)
				ret = 0;
			break;
		}
		if (DBC_LOGGING(dbc)) {
			memset(&log_dbt, 0, sizeof(DBT));
			log_dbt.data = pg;
			log_dbt.size = dbp->pgsize;
			if ((ret = __heap_trunc_page_log(dbp, dbc->txn,
			    &LSN(pg), 0, pgno,
			    &log_dbt, (pgno == next_region), &LSN(pg))) != 0)
				goto err;
		} else
			LSN_NOT_LOGGED(LSN(pg));

		if (pgno == next_region) {
			DB_ASSERT(dbp->env, TYPE(pg) == P_IHEAP);
			next_region += region_size + 1;
		} else {
			/*
			 * We can't use pg->entries to calculate the record
			 * count, because it can include split records.  So we
			 * check the header for each entry and only count
			 * non-split records and the first piece of split
			 * records. But if the page is empty, there's no work to
			 * do.
			 */
			if (NUM_ENT(pg) != 0)
				for (i = 0; i <= HEAP_HIGHINDX(pg); i++) {
					if (HEAP_OFFSETTBL(dbp, pg)[i] == 0)
						continue;
					hdr = (HEAPHDR *)P_ENTRY(dbp, pg, i);
					if (!F_ISSET(hdr, HEAP_RECSPLIT) ||
					    F_ISSET(hdr, HEAP_RECFIRST))
						count++;
				}
		}
		if ((ret = __memp_fput(mpf,
		    dbc->thread_info, pg, dbc->priority)) != 0)
			break;
		if ((ret = __memp_fget(mpf, &pgno,
		    dbc->thread_info, dbc->txn, DB_MPOOL_FREE, &pg)) != 0)
			break;
	}
	if ((t_ret = __TLPUT(dbc, lock)) != 0 && ret == 0)
		ret = t_ret;

	if (countp != NULL && ret == 0)
		*countp = count;

	if (DBC_LOGGING(dbc)) {
		if ((ret = __heap_trunc_meta_log(dbp, dbc->txn, &LSN(meta), 0,
		    meta->dbmeta.pgno, meta->dbmeta.last_pgno,
		    meta->dbmeta.key_count, meta->dbmeta.record_count,
		    meta->curregion, meta->nregions, &LSN(meta))) != 0)
			goto err;
	} else
		LSN_NOT_LOGGED(LSN(meta));
	meta->dbmeta.key_count = 0;
	meta->dbmeta.record_count = 0;
	meta->dbmeta.last_pgno = PGNO_BASE_MD + 1;
	meta->curregion = 1;
	meta->nregions = 1;

	if ((ret = __memp_ftruncate(mpf, dbc->txn,
	    dbc->thread_info, PGNO_BASE_MD + 1, MP_TRUNC_NOCACHE)) != 0)
		goto err;

	/* Create the first region. */
	pgno = PGNO_BASE_MD + 1;
	if ((ret = __memp_fget(mpf, &pgno, dbc->thread_info,
	    dbc->txn, DB_MPOOL_CREATE | DB_MPOOL_DIRTY, &pg)) != 0)
		goto err;

	memset(pg, 0, dbp->pgsize);
	P_INIT(pg,
	    dbp->pgsize, 1, PGNO_INVALID, PGNO_INVALID, 0, P_IHEAP);
	ret = __db_log_page(dbp, dbc->txn, &pg->lsn, pgno, (PAGE *)pg);
	if ((t_ret = __memp_fput(
	    mpf, dbc->thread_info, pg, dbp->priority)) != 0 && ret == 0)
		ret = t_ret;

err:	if ((t_ret = __memp_fput(mpf,
	    dbc->thread_info, meta, dbc->priority)) != 0 && ret == 0)
		ret = t_ret;
	if ((t_ret = __TLPUT(dbc, meta_lock)) && ret == 0)
		ret = t_ret;
	return (ret);
}
