//
// See the file LICENSE for redistribution information.
//
// Copyright (c) 2002-2003
//	Sleepycat Software.  All rights reserved.
//

static const char revid[] = "$Id: Cursor.cpp,v 1.36 2003/09/30 11:05:42 merrells Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "Cursor.hpp"
#include "Key.hpp"
#include "Log.hpp"

#include <cassert>
#include <sstream>

using namespace DbXml;

// Cursor
Cursor::Cursor(DbEnv *environment, Db &db, DbTxn *txn)
	: dbc_(0),
	error_( -1)
{
	u_int32_t flags = 0;
	if (environment != 0) {
		// If we're in a CDS environment then the cursor used to
		// delete the keys must be created with the DB_WRITECURSOR flag.
		//
		if (environment->get_DB_ENV()->flags & DB_ENV_CDB) {
			flags = DB_WRITECURSOR;
		}
	}
	error_ = db.cursor(txn, &dbc_, flags);
}

// PrimaryCursor

PrimaryCursor::PrimaryCursor(PrimaryDatabase &db, DbTxn *txn, Database::Operation operation, Key *k1)
	: operation_(operation),
	cursor_(db.getEnvironment(), db.getDb(), txn),
	done_(false),
	swap_(db.swap())
{
	switch (operation_) {
	case Database::ALL:
		break;
	case Database::EQUALITY:
		k1->setDbtFromThis(key_, db.swap());
		break;
	case Database::LTX:  // a<b
	case Database::LTE:  // a<=b
	case Database::GTX:  // a>b
	case Database::GTE:  // a>=b
	case Database::NONE:
	case Database::RANGE:
	case Database::PREFIX:
	case Database::SUBSTRING:
		// These just won't happen.
		assert(0);
		break;
	}
}

PrimaryCursor::~PrimaryCursor()
{}

int PrimaryCursor::first(ID &id)
{
	int flags = 0;
	int err = 0;
	switch (operation_) {
	case Database::ALL:
		flags = DB_FIRST;
		// Use DB_DBT_PARTIAL and len=0 to prevent retrieval of the data.
		data_.set_flags(DB_DBT_PARTIAL);
		break;
	case Database::EQUALITY:
		flags = DB_SET;
		break;
	case Database::LTX:  // a<b
	case Database::LTE:  // a<=b
	case Database::GTX:  // a>b
	case Database::GTE:  // a>=b
	case Database::NONE:
	case Database::RANGE:
	case Database::PREFIX:
	case Database::SUBSTRING:
		// These just won't happen.
		err = DB_NOTFOUND;
		assert(0);
		break;
	}
	err = doNext(id, flags);
	return err;
}

int PrimaryCursor::next(ID &id)
{
	int err = 0;
	if (done_) {
		id = 0;
	} else {
		int flags = 0;
		switch (operation_) {
		case Database::ALL:
			flags = DB_NEXT;
			// Use DB_DBT_PARTIAL and len=0 to prevent retrieval of the data.
			data_.set_flags(DB_DBT_PARTIAL);
			break;
		case Database::EQUALITY:
			flags = DB_NEXT_DUP;
			break;
		case Database::LTX:  // a<b
		case Database::LTE:  // a<=b
		case Database::GTX:  // a>b
		case Database::GTE:  // a>=b
		case Database::NONE:
		case Database::RANGE:
		case Database::PREFIX:
		case Database::SUBSTRING:
			// These just won't happen.
			err = DB_NOTFOUND;
			assert(0);
			break;
		}
		err = doNext(id, flags);
	}
	return err;
}

int PrimaryCursor::doNext(ID &id, int flags)
{
	int err = cursor_.get(&key_, &data_, flags); // no throw for NOTFOUND and KEYEMPTY
	if (err == 0) {
		id.setThisFromDbt(key_, swap_);
	} else {
		if (err == DB_NOTFOUND || err == DB_KEYEMPTY) {
			err = 0;
		}
		done_ = true;
		id = 0;
	}
	return err;
}

// SecondaryCursor

SecondaryCursor::SecondaryCursor(SecondaryDatabase &db, DbTxn *txn, Database::Operation operation, Key *k1, const Syntax *syntax)
	: environment_(db.getEnvironment()),
	syntax_(syntax),
	operation_(operation),
	greaterThanOperation_(Database::NONE),
	lessThanOperation_(Database::NONE),
	cursor_(db.getEnvironment(), db.getDb(), txn),
	done_(false),
	swap_(db.swap())
{
	if (operation_ != Database::ALL) {
		k1->setDbtFromThis(key_, db.swap());
		originalKey_.set(key_.get_data(), key_.get_size());
	}
}

SecondaryCursor::SecondaryCursor(SecondaryDatabase &db, DbTxn *txn, Database::Operation gto, Key *gtk, Database::Operation lto, Key *ltk, const Syntax *syntax)
	: environment_(db.getEnvironment()),
	syntax_(syntax),
	operation_(Database::RANGE),
	greaterThanOperation_(gto),
	lessThanOperation_(lto),
	cursor_(db.getEnvironment(), db.getDb(), txn),
	done_(false),
	swap_(db.swap())
{
	assert(lto == Database::LTX || lto == Database::LTE);
	assert(gto == Database::GTX || gto == Database::GTE);
	gtk->setDbtFromThis(key_, db.swap());
	ltk->setDbtFromThis(key2_, db.swap());
}

SecondaryCursor::~SecondaryCursor()
{
	environment_ = 0;
	syntax_ = 0;
}

// jcm - Cursor could have a getOrdering method that returns the ordering of the ids... in-order, random, reversed.
//
int SecondaryCursor::first(ID &id)
{
	int flags = 0;
	int err = 0;
	Database::Operation operation = (operation_ == Database::RANGE ? greaterThanOperation_ : operation_);	// For a RANGE we start with the greaterThanOperation_.
	switch (operation) {
	case Database::ALL:
		flags = DB_FIRST;
		break;
	case Database::EQUALITY:
		flags = DB_SET;
		break;
	case Database::LTX:  // a<b
		// Skip the first matching key.
		flags = DB_SET_RANGE;
		err = cursor_.get(&key_, &data_, flags); // no throw for NOTFOUND and KEYEMPTY
		if (err == DB_NOTFOUND) {
			err = 0;
			flags = DB_LAST;
		} else {
			flags = DB_PREV;
		}
		break;
	case Database::LTE:  // a<=b
		// LTE is a bit special, it behaves as equality until there are
		// no more equal keys, then switches to LTX. See the switcheroo
		// below in doNext.
		flags = DB_SET;
		break;
	case Database::GTX:  // a>b
		// Skip the first matching duplicate keys.
		flags = DB_SET;
		err = cursor_.get(&key_, &data_, flags); // no throw for NOTFOUND and KEYEMPTY
		if (err == DB_NOTFOUND) {
			err = 0;
			flags = DB_SET_RANGE;
		} else {
			flags = DB_NEXT_NODUP;
		}
		break;
	case Database::GTE:  // a>=b
		flags = DB_SET_RANGE;
		break;
	case Database::PREFIX:
		flags = DB_SET_RANGE; // Return both the key and data on each get.
		break;
	case Database::SUBSTRING:
		// This just won't happen.
		err = DB_NOTFOUND;
		assert(0);
		break;
	}
	if (err == 0) {
		err = doNext(id, flags);
	} else {
		if (err == DB_NOTFOUND || err == DB_KEYEMPTY) {
			err = 0;
		}
		done_ = true;
		id = 0;
	}
	return err;
}

int SecondaryCursor::next(ID &id)
{
	int err = 0;
	if (done_) {
		id = 0;
	} else {
		int flags = 0;
		switch (operation_) {
		case Database::ALL:
			flags = DB_NEXT;
			break;
		case Database::EQUALITY:
			flags = DB_NEXT_DUP;
			break;
		case Database::LTX:  // a<b
			flags = DB_PREV;
			break;
		case Database::LTE:  // a<=b
			// LTE is a bit special, it behaves as equality until there are
			// no more equal keys, then switches to LTX. See the switcheroo
			// below in doNext.
			flags = DB_NEXT_DUP;
			break;
		case Database::GTX:  // a>b
		case Database::GTE:  // a>=b
			flags = DB_NEXT;
			break;
		case Database::RANGE:
			flags = DB_NEXT;
			break;
		case Database::PREFIX:
			// We make a copy of the key because dbc->get() will return the actual key.
			flags = DB_NEXT;
			key_.set(originalKey_.get_data(), originalKey_.get_size());
			break;
		case Database::NONE:
		case Database::SUBSTRING:
			// This just won't happen.
			err = DB_NOTFOUND;
			assert(0);
			break;
		}
		err = doNext(id, flags);
	}
	return err;
}

int SecondaryCursor::doNext(ID &id, int flags)
{
	// jcm - We could use the bulk key interface here for a performance boost.
	int err = cursor_.get(&key_, &data_, flags);
	if (err == 0) {
		switch (operation_) {
		case Database::PREFIX:
			if (memcmp(originalKey_.get_data(), key_.get_data(), originalKey_.get_size()) != 0) {
				done_ = true;
				id = 0;
			}
			break;
		case Database::LTX:  // a<b
		case Database::GTX:  // a>b
		case Database::GTE:  // a>=b
			{
				if (syntax_ == 0)
				{
					std::ostringstream oss;
					oss << "No syntax provided for secondary cursor (<,>,>=)";
					DbXml::log(environment_, C_QUERY, L_ERROR, 0, oss.str().c_str());
					done_ = true;
					id = 0;
					assert(0);
				} else
				{
					char *p = (char*)originalKey_.get_data();
					Index index;
					index.setFromPrefix(*p);
 					size_t skl = syntax_->structureKeyLength(index, originalKey_.get_size());
					if (::memcmp(originalKey_.get_data(), key_.get_data(), skl) != 0) // Check the Prefix and VIDs are the same.
					{
						done_ = true;
						id = 0;
					}
				}
			}
			break;
		case Database::RANGE: {
				if (syntax_ == 0) {
					std::ostringstream oss;
					oss << "No syntax provided for secondary cursor (range).";
					DbXml::log(environment_, C_QUERY, L_ERROR, 0, oss.str().c_str());
					done_ = true;
					id = 0;
					assert(0);
				} else {
					Database::bt_compare_fn compare = syntax_->get_bt_compare();
					int r = compare(0, &key_, &key2_);
					if (!((lessThanOperation_ == Database::LTE && r <= 0) || (lessThanOperation_ == Database::LTX && r < 0))) {
						done_ = true;
						id = 0;
					}
				}
			}
			break;
		default:  // keep compilers quiet
			break;
		}
		if (!done_) {
			id.setThisFromDbt(data_, swap_);
		}
	} else {
		if (err == DB_NOTFOUND || err == DB_KEYEMPTY) {
			if (operation_ == Database::LTE) {
				// Once LTE has exhausted the equal keys it starts on the
				// less than keys.
				operation_ = Database::LTX;
				err = first(id);
			} else {
				done_ = true;
				id = 0;
				err = 0;
			}
		} else {
			done_ = true;
			id = 0;
		}
	}
	return err;
}

