/*-
 * Copyright (c) 2014-2016 MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

/*
 * __lsm_meta_read_v0 --
 *	Read v0 of LSM metadata.
 */
static int
__lsm_meta_read_v0(
    WT_SESSION_IMPL *session, WT_LSM_TREE *lsm_tree, const char *lsmconf)
{
	WT_CONFIG cparser, lparser;
	WT_CONFIG_ITEM ck, cv, fileconf, lk, lv, metadata;
	WT_DECL_RET;
	WT_LSM_CHUNK *chunk;
	u_int nchunks;

	chunk = NULL;			/* -Wconditional-uninitialized */

	/* LSM trees inherit the merge setting from the connection. */
	if (F_ISSET(S2C(session), WT_CONN_LSM_MERGE))
		F_SET(lsm_tree, WT_LSM_TREE_MERGES);

	__wt_config_init(session, &cparser, lsmconf);
	while ((ret = __wt_config_next(&cparser, &ck, &cv)) == 0) {
		if (WT_STRING_MATCH("key_format", ck.str, ck.len)) {
			__wt_free(session, lsm_tree->key_format);
			WT_RET(__wt_strndup(session,
			    cv.str, cv.len, &lsm_tree->key_format));
		} else if (WT_STRING_MATCH("value_format", ck.str, ck.len)) {
			__wt_free(session, lsm_tree->value_format);
			WT_RET(__wt_strndup(session,
			    cv.str, cv.len, &lsm_tree->value_format));
		} else if (WT_STRING_MATCH("collator", ck.str, ck.len)) {
			if (cv.len == 0 ||
			    WT_STRING_MATCH("none", cv.str, cv.len))
				continue;
			/*
			 * Extract the application-supplied metadata (if any)
			 * from the file configuration.
			 */
			WT_RET(__wt_config_getones(
			    session, lsmconf, "file_config", &fileconf));
			WT_CLEAR(metadata);
			WT_RET_NOTFOUND_OK(__wt_config_subgets(
			    session, &fileconf, "app_metadata", &metadata));
			WT_RET(__wt_collator_config(session, lsm_tree->name,
			    &cv, &metadata,
			    &lsm_tree->collator, &lsm_tree->collator_owned));
			WT_RET(__wt_strndup(session,
			    cv.str, cv.len, &lsm_tree->collator_name));
		} else if (WT_STRING_MATCH("bloom_config", ck.str, ck.len)) {
			__wt_free(session, lsm_tree->bloom_config);
			/* Don't include the brackets. */
			WT_RET(__wt_strndup(session,
			    cv.str + 1, cv.len - 2, &lsm_tree->bloom_config));
		} else if (WT_STRING_MATCH("file_config", ck.str, ck.len)) {
			__wt_free(session, lsm_tree->file_config);
			/* Don't include the brackets. */
			WT_RET(__wt_strndup(session,
			    cv.str + 1, cv.len - 2, &lsm_tree->file_config));
		} else if (WT_STRING_MATCH("auto_throttle", ck.str, ck.len)) {
			if (cv.val)
				F_SET(lsm_tree, WT_LSM_TREE_THROTTLE);
			else
				F_CLR(lsm_tree, WT_LSM_TREE_THROTTLE);
		} else if (WT_STRING_MATCH("bloom", ck.str, ck.len))
			lsm_tree->bloom = (uint32_t)cv.val;
		else if (WT_STRING_MATCH("bloom_bit_count", ck.str, ck.len))
			lsm_tree->bloom_bit_count = (uint32_t)cv.val;
		else if (WT_STRING_MATCH("bloom_hash_count", ck.str, ck.len))
			lsm_tree->bloom_hash_count = (uint32_t)cv.val;
		else if (WT_STRING_MATCH("chunk_count_limit", ck.str, ck.len)) {
			lsm_tree->chunk_count_limit = (uint32_t)cv.val;
			if (cv.val != 0)
				F_CLR(lsm_tree, WT_LSM_TREE_MERGES);
		} else if (WT_STRING_MATCH("chunk_max", ck.str, ck.len))
			lsm_tree->chunk_max = (uint64_t)cv.val;
		else if (WT_STRING_MATCH("chunk_size", ck.str, ck.len))
			lsm_tree->chunk_size = (uint64_t)cv.val;
		else if (WT_STRING_MATCH("merge_max", ck.str, ck.len))
			lsm_tree->merge_max = (uint32_t)cv.val;
		else if (WT_STRING_MATCH("merge_min", ck.str, ck.len))
			lsm_tree->merge_min = (uint32_t)cv.val;
		else if (WT_STRING_MATCH("last", ck.str, ck.len))
			lsm_tree->last = (u_int)cv.val;
		else if (WT_STRING_MATCH("chunks", ck.str, ck.len)) {
			__wt_config_subinit(session, &lparser, &cv);
			for (nchunks = 0; (ret =
			    __wt_config_next(&lparser, &lk, &lv)) == 0; ) {
				if (WT_STRING_MATCH("id", lk.str, lk.len)) {
					WT_RET(__wt_realloc_def(session,
					    &lsm_tree->chunk_alloc,
					    nchunks + 1, &lsm_tree->chunk));
					WT_RET(
					    __wt_calloc_one(session, &chunk));
					lsm_tree->chunk[nchunks++] = chunk;
					chunk->id = (uint32_t)lv.val;
					WT_RET(__wt_lsm_tree_chunk_name(session,
					    lsm_tree, chunk->id, &chunk->uri));
					F_SET(chunk,
					    WT_LSM_CHUNK_ONDISK |
					    WT_LSM_CHUNK_STABLE);
				} else if (WT_STRING_MATCH(
				    "bloom", lk.str, lk.len)) {
					WT_RET(__wt_lsm_tree_bloom_name(
					    session, lsm_tree,
					    chunk->id, &chunk->bloom_uri));
					F_SET(chunk, WT_LSM_CHUNK_BLOOM);
					continue;
				} else if (WT_STRING_MATCH(
				    "chunk_size", lk.str, lk.len)) {
					chunk->size = (uint64_t)lv.val;
					continue;
				} else if (WT_STRING_MATCH(
				    "count", lk.str, lk.len)) {
					chunk->count = (uint64_t)lv.val;
					continue;
				} else if (WT_STRING_MATCH(
				    "generation", lk.str, lk.len)) {
					chunk->generation = (uint32_t)lv.val;
					continue;
				}
			}
			WT_RET_NOTFOUND_OK(ret);
			lsm_tree->nchunks = nchunks;
		} else if (WT_STRING_MATCH("old_chunks", ck.str, ck.len)) {
			__wt_config_subinit(session, &lparser, &cv);
			for (nchunks = 0; (ret =
			    __wt_config_next(&lparser, &lk, &lv)) == 0; ) {
				if (WT_STRING_MATCH("bloom", lk.str, lk.len)) {
					WT_RET(__wt_strndup(session,
					    lv.str, lv.len, &chunk->bloom_uri));
					F_SET(chunk, WT_LSM_CHUNK_BLOOM);
					continue;
				}
				WT_RET(__wt_realloc_def(session,
				    &lsm_tree->old_alloc, nchunks + 1,
				    &lsm_tree->old_chunks));
				WT_RET(__wt_calloc_one(session, &chunk));
				lsm_tree->old_chunks[nchunks++] = chunk;
				WT_RET(__wt_strndup(session,
				    lk.str, lk.len, &chunk->uri));
				F_SET(chunk, WT_LSM_CHUNK_ONDISK);
			}
			WT_RET_NOTFOUND_OK(ret);
			lsm_tree->nold_chunks = nchunks;
		}
		/*
		 * Ignore any other values: the metadata entry might have been
		 * created by a future release, with unknown options.
		 */
	}
	WT_RET_NOTFOUND_OK(ret);
	return (0);
}

/*
 * __lsm_meta_read_v1 --
 *	Read v1 of LSM metadata.
 */
static int
__lsm_meta_read_v1(
    WT_SESSION_IMPL *session, WT_LSM_TREE *lsm_tree, const char *lsmconf)
{
	WT_CONFIG lparser;
	WT_CONFIG_ITEM cv, lk, lv, metadata;
	WT_DECL_ITEM(buf);
	WT_DECL_RET;
	WT_LSM_CHUNK *chunk;
	const char *file_cfg[] = {
	    WT_CONFIG_BASE(session, file_config), NULL, NULL, NULL };
	char *fileconf;
	u_int nchunks;

	chunk = NULL;			/* -Wconditional-uninitialized */

	WT_ERR(__wt_config_getones(session, lsmconf, "key_format", &cv));
	WT_ERR(__wt_strndup(session, cv.str, cv.len, &lsm_tree->key_format));
	WT_ERR(__wt_config_getones(session, lsmconf, "value_format", &cv));
	WT_ERR(__wt_strndup(session, cv.str, cv.len, &lsm_tree->value_format));

	WT_ERR(__wt_config_getones(session, lsmconf, "collator", &cv));
	if (cv.len != 0 && !WT_STRING_MATCH("none", cv.str, cv.len)) {
		/* Extract the application-supplied metadata (if any). */
		WT_CLEAR(metadata);
		WT_ERR_NOTFOUND_OK(__wt_config_getones(
		    session, lsmconf, "app_metadata", &metadata));
		WT_ERR(__wt_collator_config(session, lsm_tree->name,
		    &cv, &metadata,
		    &lsm_tree->collator, &lsm_tree->collator_owned));
		WT_ERR(__wt_strndup(session,
		    cv.str, cv.len, &lsm_tree->collator_name));
	}

	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.auto_throttle", &cv));
	if (cv.val)
		F_SET(lsm_tree, WT_LSM_TREE_THROTTLE);
	else
		F_CLR(lsm_tree, WT_LSM_TREE_THROTTLE);

	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.bloom", &cv));
	FLD_SET(lsm_tree->bloom,
	    (cv.val == 0 ? WT_LSM_BLOOM_OFF : WT_LSM_BLOOM_MERGED));
	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.bloom_oldest", &cv));
	if (cv.val != 0)
		FLD_SET(lsm_tree->bloom, WT_LSM_BLOOM_OLDEST);

	if (FLD_ISSET(lsm_tree->bloom, WT_LSM_BLOOM_OFF) &&
	    FLD_ISSET(lsm_tree->bloom, WT_LSM_BLOOM_OLDEST))
		WT_ERR_MSG(session, EINVAL,
		    "Bloom filters can only be created on newest and oldest "
		    "chunks if bloom filters are enabled");

	WT_ERR(__wt_config_getones(
	    session, lsmconf, "lsm.bloom_bit_count", &cv));
	lsm_tree->bloom_bit_count = (uint32_t)cv.val;
	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.bloom_config", &cv));
	/* Don't include the brackets. */
	if (cv.type == WT_CONFIG_ITEM_STRUCT) {
		cv.str++;
		cv.len -= 2;
	}
	WT_ERR(__wt_config_check(session,
	    WT_CONFIG_REF(session, WT_SESSION_create), cv.str, cv.len));
	WT_ERR(__wt_strndup(session, cv.str, cv.len, &lsm_tree->bloom_config));
	WT_ERR(__wt_config_getones(
	    session, lsmconf, "lsm.bloom_hash_count", &cv));
	lsm_tree->bloom_hash_count = (uint32_t)cv.val;

	WT_ERR(__wt_config_getones(
	    session, lsmconf, "lsm.chunk_count_limit", &cv));
	lsm_tree->chunk_count_limit = (uint32_t)cv.val;
	if (cv.val == 0)
		F_SET(lsm_tree, WT_LSM_TREE_MERGES);
	else
		F_CLR(lsm_tree, WT_LSM_TREE_MERGES);
	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.chunk_max", &cv));
	lsm_tree->chunk_max = (uint64_t)cv.val;
	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.chunk_size", &cv));
	lsm_tree->chunk_size = (uint64_t)cv.val;

	if (lsm_tree->chunk_size > lsm_tree->chunk_max)
		WT_ERR_MSG(session, EINVAL,
		    "Chunk size (chunk_size) must be smaller than or equal to "
		    "the maximum chunk size (chunk_max)");

	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.merge_max", &cv));
	lsm_tree->merge_max = (uint32_t)cv.val;
	WT_ERR(__wt_config_getones(session, lsmconf, "lsm.merge_min", &cv));
	lsm_tree->merge_min = (uint32_t)cv.val;

	if (lsm_tree->merge_min > lsm_tree->merge_max)
		WT_ERR_MSG(session, EINVAL,
		    "LSM merge_min must be less than or equal to merge_max");

	WT_ERR(__wt_config_getones(session, lsmconf, "last", &cv));
	lsm_tree->last = (u_int)cv.val;
	WT_ERR(__wt_config_getones(session, lsmconf, "chunks", &cv));
	__wt_config_subinit(session, &lparser, &cv);
	for (nchunks = 0; (ret =
	    __wt_config_next(&lparser, &lk, &lv)) == 0; ) {
		if (WT_STRING_MATCH("id", lk.str, lk.len)) {
			WT_ERR(__wt_realloc_def(session,
			    &lsm_tree->chunk_alloc,
			    nchunks + 1, &lsm_tree->chunk));
			WT_ERR(__wt_calloc_one(session, &chunk));
			lsm_tree->chunk[nchunks++] = chunk;
			chunk->id = (uint32_t)lv.val;
			WT_ERR(__wt_lsm_tree_chunk_name(session,
			    lsm_tree, chunk->id, &chunk->uri));
			F_SET(chunk,
			    WT_LSM_CHUNK_ONDISK |
			    WT_LSM_CHUNK_STABLE);
		} else if (WT_STRING_MATCH("bloom", lk.str, lk.len)) {
			WT_ERR(__wt_lsm_tree_bloom_name(
			    session, lsm_tree, chunk->id, &chunk->bloom_uri));
			F_SET(chunk, WT_LSM_CHUNK_BLOOM);
			continue;
		} else if (WT_STRING_MATCH("chunk_size", lk.str, lk.len)) {
			chunk->size = (uint64_t)lv.val;
			continue;
		} else if (WT_STRING_MATCH("count", lk.str, lk.len)) {
			chunk->count = (uint64_t)lv.val;
			continue;
		} else if (WT_STRING_MATCH("generation", lk.str, lk.len)) {
			chunk->generation = (uint32_t)lv.val;
			continue;
		}
	}
	WT_ERR_NOTFOUND_OK(ret);
	lsm_tree->nchunks = nchunks;

	WT_ERR(__wt_config_getones(session, lsmconf, "old_chunks", &cv));
	__wt_config_subinit(session, &lparser, &cv);
	for (nchunks = 0; (ret =
	    __wt_config_next(&lparser, &lk, &lv)) == 0; ) {
		if (WT_STRING_MATCH("bloom", lk.str, lk.len)) {
			WT_ERR(__wt_strndup(session,
			    lv.str, lv.len, &chunk->bloom_uri));
			F_SET(chunk, WT_LSM_CHUNK_BLOOM);
			continue;
		}
		WT_ERR(__wt_realloc_def(session,
		    &lsm_tree->old_alloc, nchunks + 1,
		    &lsm_tree->old_chunks));
		WT_ERR(__wt_calloc_one(session, &chunk));
		lsm_tree->old_chunks[nchunks++] = chunk;
		WT_ERR(__wt_strndup(session,
		    lk.str, lk.len, &chunk->uri));
		F_SET(chunk, WT_LSM_CHUNK_ONDISK);
	}
	WT_ERR_NOTFOUND_OK(ret);
	lsm_tree->nold_chunks = nchunks;

	/*
	 * Set up the config for each chunk.
	 *
	 * Make the memory_page_max double the chunk size, so application
	 * threads don't immediately try to force evict the chunk when the
	 * worker thread clears the NO_EVICTION flag.
	 */
	file_cfg[1] = lsmconf;
	WT_ERR(__wt_scr_alloc(session, 0, &buf));
	WT_ERR(__wt_buf_fmt(session, buf,
	    "key_format=u,value_format=u,memory_page_max=%" PRIu64,
	    2 * lsm_tree->chunk_size));
	file_cfg[2] = buf->data;
	WT_ERR(__wt_config_collapse(session, file_cfg, &fileconf));
	lsm_tree->file_config = fileconf;

	/*
	 * Ignore any other values: the metadata entry might have been
	 * created by a future release, with unknown options.
	 */
err:	__wt_scr_free(session, &buf);
	return (ret);
}

/*
 * __lsm_meta_upgrade_v1 --
 *	Upgrade to v1 of LSM metadata.
 */
static int
__lsm_meta_upgrade_v1(WT_SESSION_IMPL *session, WT_LSM_TREE *lsm_tree)
{
	WT_DECL_ITEM(buf);
	WT_DECL_RET;
	const char *new_cfg[] = {
	    WT_CONFIG_BASE(session, lsm_meta), NULL, NULL, NULL };

	/* Include the custom config that used to be embedded in file_config. */
	new_cfg[1] = lsm_tree->file_config;

	WT_ERR(__wt_scr_alloc(session, 0, &buf));
	WT_ERR(__wt_buf_fmt(session, buf,
	    "key_format=%s,value_format=%s",
	    lsm_tree->key_format, lsm_tree->value_format));

	WT_ERR(__wt_buf_catfmt(session, buf, ",collator=%s",
	    lsm_tree->collator_name != NULL ?  lsm_tree->collator_name : ""));

	WT_ERR(__wt_buf_catfmt(session, buf, ",lsm=("));

	WT_ERR(__wt_buf_catfmt(session, buf, "auto_throttle=%d",
	    F_ISSET(lsm_tree, WT_LSM_TREE_THROTTLE)));

	WT_ERR(__wt_buf_catfmt(session, buf, ",bloom=%d",
	    FLD_ISSET(lsm_tree->bloom, WT_LSM_BLOOM_MERGED)));
	WT_ERR(__wt_buf_catfmt(session, buf, ",bloom_oldest=%d",
	    FLD_ISSET(lsm_tree->bloom, WT_LSM_BLOOM_OLDEST)));
	WT_ERR(__wt_buf_catfmt(session, buf, ",bloom_bit_count=%" PRIu32,
	    lsm_tree->bloom_bit_count));
	if (lsm_tree->bloom_config != NULL &&
	    strlen(lsm_tree->bloom_config) > 0)
		WT_ERR(__wt_buf_catfmt(session, buf, ",bloom_config=(%s)",
		    lsm_tree->bloom_config));
	else
		WT_ERR(__wt_buf_catfmt(session, buf, ",bloom_config="));
	WT_ERR(__wt_buf_catfmt(session, buf, ",bloom_hash_count=%" PRIu32,
	    lsm_tree->bloom_hash_count));

	WT_ERR(__wt_buf_catfmt(session, buf, ",chunk_count_limit=%" PRIu32,
	    lsm_tree->chunk_count_limit));
	WT_ERR(__wt_buf_catfmt(session, buf, ",chunk_max=%" PRIu64,
	    lsm_tree->chunk_max));
	WT_ERR(__wt_buf_catfmt(session, buf, ",merge_max=%" PRIu32,
	    lsm_tree->merge_max));
	WT_ERR(__wt_buf_catfmt(session, buf, ",merge_min=%" PRIu32,
	    lsm_tree->merge_min));

	WT_ERR(__wt_buf_catfmt(session, buf, ")"));

	new_cfg[2] = buf->data;
	WT_ERR(__wt_config_merge(session, new_cfg, NULL, &lsm_tree->config));

err:	__wt_scr_free(session, &buf);
	return (ret);
}

/*
 * __wt_lsm_meta_read --
 *	Read the metadata for an LSM tree.
 */
int
__wt_lsm_meta_read(WT_SESSION_IMPL *session, WT_LSM_TREE *lsm_tree)
{
	WT_CONFIG_ITEM cval;
	WT_DECL_RET;
	char *lsmconf;
	bool upgrade;

	/* LSM trees inherit the merge setting from the connection. */
	if (F_ISSET(S2C(session), WT_CONN_LSM_MERGE))
		F_SET(lsm_tree, WT_LSM_TREE_MERGES);

	WT_RET(__wt_metadata_search(session, lsm_tree->name, &lsmconf));

	upgrade = false;
	ret = __wt_config_getones(session, lsmconf, "file_config", &cval);
	if (ret == 0) {
		ret = __lsm_meta_read_v0(session, lsm_tree, lsmconf);
		__wt_free(session, lsmconf);
		WT_RET(ret);
		upgrade = true;
	} else if (ret == WT_NOTFOUND) {
		lsm_tree->config = lsmconf;
		ret = 0;
		WT_RET(__lsm_meta_read_v1(session, lsm_tree, lsmconf));
	}
	/*
	 * If the default merge_min was not overridden, calculate it now.
	 */
	if (lsm_tree->merge_min < 2)
		lsm_tree->merge_min = WT_MAX(2, lsm_tree->merge_max / 2);
	/*
	 * If needed, upgrade the configuration.  We need to do this after
	 * we have fixed the merge_min value.
	 */
	if (upgrade)
		WT_RET(__lsm_meta_upgrade_v1(session, lsm_tree));
	return (ret);
}

/*
 * __wt_lsm_meta_write --
 *	Write the metadata for an LSM tree.
 */
int
__wt_lsm_meta_write(WT_SESSION_IMPL *session, WT_LSM_TREE *lsm_tree,
    const char *newconfig)
{
	WT_DECL_ITEM(buf);
	WT_DECL_RET;
	WT_LSM_CHUNK *chunk;
	u_int i;
	const char *new_cfg[] = { NULL, NULL, NULL, NULL, NULL };
	char *new_metadata;
	bool first;

	new_metadata = NULL;

	WT_RET(__wt_scr_alloc(session, 0, &buf));
	WT_ERR(__wt_buf_catfmt(session, buf,
	    ",last=%" PRIu32, lsm_tree->last));
	WT_ERR(__wt_buf_catfmt(session, buf, ",chunks=["));
	for (i = 0; i < lsm_tree->nchunks; i++) {
		chunk = lsm_tree->chunk[i];
		if (i > 0)
			WT_ERR(__wt_buf_catfmt(session, buf, ","));
		WT_ERR(__wt_buf_catfmt(session, buf, "id=%" PRIu32, chunk->id));
		if (F_ISSET(chunk, WT_LSM_CHUNK_BLOOM))
			WT_ERR(__wt_buf_catfmt(session, buf, ",bloom"));
		if (chunk->size != 0)
			WT_ERR(__wt_buf_catfmt(session, buf,
			    ",chunk_size=%" PRIu64, chunk->size));
		if (chunk->count != 0)
			WT_ERR(__wt_buf_catfmt(
			    session, buf, ",count=%" PRIu64, chunk->count));
		WT_ERR(__wt_buf_catfmt(
		    session, buf, ",generation=%" PRIu32, chunk->generation));
	}
	WT_ERR(__wt_buf_catfmt(session, buf, "]"));
	WT_ERR(__wt_buf_catfmt(session, buf, ",old_chunks=["));
	first = true;
	for (i = 0; i < lsm_tree->nold_chunks; i++) {
		chunk = lsm_tree->old_chunks[i];
		WT_ASSERT(session, chunk != NULL);
		if (first)
			first = false;
		else
			WT_ERR(__wt_buf_catfmt(session, buf, ","));
		WT_ERR(__wt_buf_catfmt(session, buf, "\"%s\"", chunk->uri));
		if (F_ISSET(chunk, WT_LSM_CHUNK_BLOOM))
			WT_ERR(__wt_buf_catfmt(
			    session, buf, ",bloom=\"%s\"", chunk->bloom_uri));
	}
	WT_ERR(__wt_buf_catfmt(session, buf, "]"));

	/* Update the existing configuration with the new values. */
	new_cfg[0] = WT_CONFIG_BASE(session, lsm_meta);
	new_cfg[1] = lsm_tree->config;
	new_cfg[2] = buf->data;
	new_cfg[3] = newconfig;
	WT_ERR(__wt_config_collapse(session, new_cfg, &new_metadata));
	ret = __wt_metadata_update(session, lsm_tree->name, new_metadata);
	WT_ERR(ret);

err:	__wt_scr_free(session, &buf);
	__wt_free(session, new_metadata);
	return (ret);
}
