/*
 *      PROGRAM:        JRD Intl
 *      MODULE:         Collation.cpp
 *      DESCRIPTION:    International text support routines
 *
 * copyright (c) 1992, 1993 by Borland International
 */

/*************  history ************
*
*       COMPONENT: JRD  MODULE: INTL.CPP
*       generated by Marion V2.5     2/6/90
*       from dev              db        on 4-JAN-1995
*****************************************************************
*
*       PR	2002-06-02 Added ugly c hack in
*       intl_back_compat_alloc_func_lookup.
*       When someone has time we need to change the references to
*       return (void*) function to something more C++ like
*
*       42 4711 3 11 17  tamlin   2001
*       Added silly numbers before my name, and converted it to C++.
*
*       18850   daves   4-JAN-1995
*       Fix gds__alloc usage
*
*       18837   deej    31-DEC-1994
*       fixing up HARBOR_MERGE
*
*       18821   deej    27-DEC-1994
*       HARBOR MERGE
*
*       18789   jdavid  19-DEC-1994
*       Cast some functions
*
*       17508   jdavid  15-JUL-1994
*       Bring it up to date
*
*       17500   daves   13-JUL-1994
*       Bug 6645: Different calculation of partial keys
*
*       17202   katz    24-MAY-1994
*       PC_PLATFORM requires the .dll extension
*
*       17191   katz    23-MAY-1994
*       OS/2 requires the .dll extension
*
*       17180   katz    23-MAY-1994
*       Define location of DLL on OS/2
*
*       17149   katz    20-MAY-1994
*       In JRD, isc_arg_number arguments are SLONG's not int's
*
*       16633   daves   19-APR-1994
*       Bug 6202: International licensing uses INTERNATIONAL product code
*
*       16555   katz    17-APR-1994
*       The last argument of calls to ERR_post should be 0
*
*       16521   katz    14-APR-1994
*       Borland C needs a decorated symbol to lookup
*
*       16403   daves   8-APR-1994
*       Bug 6441: Emit an error whenever transliteration from ttype_binary attempted
*
*       16141   katz    28-MAR-1994
*       Don't declare return value from ISC_lookup_entrypoint as API_ROUTINE
*
 * The contents of this file are subject to the Interbase Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy
 * of the License at http://www.Inprise.com/IPL.html
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code was created by Inprise Corporation
 * and its predecessors. Portions created by Inprise Corporation are
 * Copyright (C) Inprise Corporation.
 *
 * All Rights Reserved.
 * Contributor(s): ______________________________________.
 *
 * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
 *
 * 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
 *
 * 2006.10.10 Adriano dos Santos Fernandes - refactored from intl.cpp
 *
*/

#include "firebird.h"
#include "gen/iberror.h"
#include "../jrd/jrd.h"
#include "../jrd/err_proto.h"
#include "../jrd/evl_string.h"
#include "../jrd/intl_classes.h"
#include "../jrd/lck_proto.h"

#include "../jrd/intl_classes.h"
#include "../jrd/TextType.h"

using namespace Jrd;


namespace {

// Below is former evl_like_h file
// It was not used except Collation.cpp

#define SLEUTH_insensitive	1

static const UCHAR special[128] =
{
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0,	// $%*+- (dollar, percent, star, plus, minus)
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,	// ?     (question)
	1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	// @     (at-sign)
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,	// [     (open square)
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0	// ~     (tilde)
};

template <class MATCHESTYPE>
bool MATCHESNAME(Jrd::thread_db* tdbb, Jrd::TextType* obj,
				 const MATCHESTYPE* p1, SLONG l1_bytes,
				 const MATCHESTYPE* p2, SLONG l2_bytes)
{
/**************************************
 *
 *	E V L _ ? ? _ m a t c h e s
 *	E V L _ w c _ m a t c h e s
 *	E V L _ n c _ m a t c h e s
 *
 **************************************
 *
 * Functional description
 *	Return true if a string (p1, l1) matches a given pattern (p2, l2).
 *	The character '?' in the pattern may match any single character
 *	in the the string, and the character '*' may match any sequence
 *	of characters.
 *
 *	Wide SCHAR version operates on short-based buffer,
 *	instead of SCHAR-based.
 *
 * Matches is not a case-sensitive operation, thus it has no
 * 8-bit international impact.
 *
 **************************************/
	fb_assert(p1 != NULL);
	fb_assert(p2 != NULL);
	fb_assert((l1_bytes % sizeof(MATCHESTYPE)) == 0);
	fb_assert((l2_bytes % sizeof(MATCHESTYPE)) == 0);
	fb_assert((obj->getCanonicalWidth() == sizeof(MATCHESTYPE)));

	SLONG l1 = l1_bytes / sizeof(MATCHESTYPE);
	SLONG l2 = l2_bytes / sizeof(MATCHESTYPE);

	while (l2-- > 0) {
		const MATCHESTYPE c = *p2++;
		if (c == *(MATCHESTYPE*)obj->getGdmlMatchAnyCanonic()) {
			while ((l2 > 0) && (*p2 == *(MATCHESTYPE*)obj->getGdmlMatchAnyCanonic())) {
				l2--;
				p2++;
			}
			if (l2 == 0)
				return true;
			while (l1)
			{
				if (MATCHESNAME(tdbb, obj, p1++,
								l1-- * sizeof(MATCHESTYPE), p2,
								l2 * sizeof(MATCHESTYPE)))
				{
					return true;
				}
			}
			return false;
		}
		if (l1-- == 0)
			return false;
		if (c != *(MATCHESTYPE*)obj->getGdmlMatchOneCanonic() && c != *p1)
			return false;
		p1++;
	}

	return !l1;
}


template <class SLEUTHTYPE>
bool SLEUTHNAME(Jrd::thread_db* tdbb_dummy, Jrd::TextType* obj, USHORT flags,
				const SLEUTHTYPE* search, SLONG search_len,
				const SLEUTHTYPE* match, SLONG match_len)
{
/**************************************
 *
 *	E V L _ ? ? _ s l e u t h _ c h e c k
 *
 **************************************
 *
 * Functional description
 *	Evaluate the "sleuth" search operator.
 *
 *	Turn the (pointer, byte length) input parameters into
 *	(pointer, end_pointer) for use in SLEUTH_AUX
 *
 **************************************/
	fb_assert((match_len % sizeof(SLEUTHTYPE)) == 0);
	fb_assert((search_len % sizeof(SLEUTHTYPE)) == 0);
	fb_assert(obj->getCanonicalWidth() == sizeof(SLEUTHTYPE));

	const SLEUTHTYPE* const end_match = match + (match_len / sizeof(SLEUTHTYPE));
	const SLEUTHTYPE* const end_search = search + (search_len / sizeof(SLEUTHTYPE));

	return SLEUTH_AUX(obj, flags, search, end_search, match, end_match);
}


template <class SLEUTHTYPE>
ULONG SLEUTH_MERGE_NAME(Jrd::thread_db* tdbb_dummy, Jrd::TextType* obj,
						const SLEUTHTYPE* match, SLONG match_bytes,
						const SLEUTHTYPE* control, SLONG control_bytes,
						SLEUTHTYPE* combined, SLONG combined_bytes)
{
/**************************************
 *
 *	E V L _ ? ? _ s l e u t h _ m e r g e
 *
 **************************************
 *
 * Functional description
 *	Merge the matching pattern and control strings to give a cannonical
 *	matching pattern.  Return the length of the combined string. 
 *
 * 	What this routine does is to take the language template, strip off 
 *	the prefix and put it in the output string, then parse the definitions
 *	into an array of character pointers.  The index array is the defined
 *	character.   The routine then takes the actual match pattern and uses
 *	the characters in it to index into the definitions to produce an equivalent
 *	pattern in the cannonical language.
 *
 *	The silly loop setting *v++ to zero initializes the array up to the
 *	highest character defined (also max_op).  Believe it or not, that part
 *	is not a bug.
 *
 **************************************/
	fb_assert(match != NULL);
	fb_assert(control != NULL);
	fb_assert(combined != NULL);

	fb_assert((match_bytes % sizeof(SLEUTHTYPE)) == 0);
	fb_assert((control_bytes % sizeof(SLEUTHTYPE)) == 0);
	fb_assert(obj->getCanonicalWidth() == sizeof(SLEUTHTYPE));

	const SLEUTHTYPE* const end_match = match + (match_bytes / sizeof(SLEUTHTYPE));
	const SLEUTHTYPE* const end_control = control + (control_bytes / sizeof(SLEUTHTYPE));

	SLEUTHTYPE max_op = 0;
	SLEUTHTYPE* comb = combined;
	SLEUTHTYPE* vector[256];
	SLEUTHTYPE** v = vector;
	SLEUTHTYPE temp[256];
	SLEUTHTYPE* t = temp;

/* Parse control string into substitution strings and initializing string */

	while (control < end_control) {
		SLEUTHTYPE c = *control++;
		if (*control == *(SLEUTHTYPE*)obj->getGdmlSubstituteCanonic()) {
			/* Note: don't allow substitution characters larger than vector */
			SLEUTHTYPE** const end_vector =
				vector + (((int)c < FB_NELEM(vector)) ? c : 0);
			while (v <= end_vector)
				*v++ = 0;
			*end_vector = t;
			++control;
			while (control < end_control) {
				c = *control++;
				if ((t > temp && t[-1] == *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic())
					|| ((c != *(SLEUTHTYPE*)obj->getGdmlCommaCanonic()) && (c != *(SLEUTHTYPE*)obj->getGdmlRParenCanonic())))
				{
					*t++ = c;
				}
				else
					break;
			}
			*t++ = 0;
		}
		else if (c == *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic() && control < end_control)
			*comb++ = *control++;
		else if (c == *(SLEUTHTYPE*)obj->getGdmlRParenCanonic())
			break;
		else if (c != *(SLEUTHTYPE*)obj->getGdmlLParenCanonic())
			*comb++ = c;
	}

	max_op = v - vector;

/* Interpret matching string, substituting where appropriate */

	while (match < end_match) {
		const SLEUTHTYPE c = *match++;

		/* if we've got a defined character, slurp the definition */

        SLEUTHTYPE* p;
		if (c <= max_op && (p = vector[c])) {
			while (*p)
				*comb++ = *p++;

			/* if we've got the definition of a quote character, 
			   slurp the next character too */

			if (comb > combined && comb[-1] == *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic() && *match)
				*comb++ = *match++;
		}

		/* at this point we've got a non-match, but as it might be one of ours, 
		   quote it. */

		else {
			if ((((size_t) c) < FB_NELEM(special)) && special[c] &&
				comb > combined && comb[-1] != *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic())
			{
				*comb++ = *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic();
			}
			*comb++ = c;
		}
	}

/* Put in trailing stuff */

	while (control < end_control)
		*comb++ = *control++;

	/* YYY - need to add code watching for overflow of combined */

	return (comb - combined) * sizeof(SLEUTHTYPE);
}


template <class SLEUTHTYPE>
static bool SLEUTH_AUX(
						  Jrd::TextType* obj,
						  USHORT flags,
						  const SLEUTHTYPE* search,
						  const SLEUTHTYPE* end_search,
	const SLEUTHTYPE* match, const SLEUTHTYPE* end_match)
{
/**************************************
 *
 *	s l e u t h _ a u x
 *
 **************************************
 *
 * Functional description
 *	Evaluate the "sleuth" search operator.
 *
 **************************************/
	fb_assert(search != NULL);
	fb_assert(end_search != NULL);
	fb_assert(match != NULL);
	fb_assert(end_match != NULL);
	fb_assert(search <= end_search);
	fb_assert(match <= end_match);
	fb_assert(obj->getCanonicalWidth() == sizeof(SLEUTHTYPE));

	while (match < end_match) {
		SLEUTHTYPE c = *match++;
		if ((c == *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic() && (c = *match++)) ||
			((((size_t) c) < FB_NELEM(special)) && !special[c]))
		{
			if (match >= end_match || *match != *(SLEUTHTYPE*)obj->getGdmlMatchAnyCanonic()) {
				if (search >= end_search)
					return false;
				const SLEUTHTYPE d = *search++;
				if (c != d)
					return false;
			}
			else {
				++match;
				for (;;)
				{
					if (SLEUTH_AUX(obj, flags, search, end_search, match, end_match))
						return true;
						
					if (search < end_search)
					{
						const SLEUTHTYPE d = *search++;
						if (c != d)
							return false;
					}
					else
						return false;
				}
			}
		}
		else if (c == *(SLEUTHTYPE*)obj->getGdmlMatchOneCanonic())
		{
			if (match >= end_match || *match != *(SLEUTHTYPE*)obj->getGdmlMatchAnyCanonic())
			{
				if (search >= end_search)
					return false;
					
				search++;
			}
			else {
				if (++match >= end_match)
					return true;
					
				for (;;)
				{
					if (SLEUTH_AUX(obj, flags, search, end_search, match, end_match))
						return true;
						
					if (++search >= end_search)
						return false;
				}
			}
		}
		else if (c == *(SLEUTHTYPE*)obj->getGdmlClassStartCanonic())
		{
			const SLEUTHTYPE* const char_class = match;
			while (*match++ != *(SLEUTHTYPE*)obj->getGdmlClassEndCanonic()) {
				if (match >= end_match)
					return false;
			}
			const SLEUTHTYPE* const end_class = match - 1;
			if (match >= end_match || *match != *(SLEUTHTYPE*)obj->getGdmlMatchAnyCanonic())
			{
				if (!SLEUTH_CLASS_NAME(obj, flags, char_class, end_class, *search++))
					return false;
			}
			else {
				++match;
				for (;;)
				{
					if (SLEUTH_AUX(obj, flags, search, end_search, match, end_match))
						return true;
						
					if (search < end_search)
					{
						if (!SLEUTH_CLASS_NAME(obj, flags, char_class, end_class, *search++))
							return false;
					}
					else
						return false;
				}
			}
		}
		else if (c == *(SLEUTHTYPE*)obj->getGdmlFlagSetCanonic())
		{
			c = *match++;
			if (c == *(SLEUTHTYPE*)obj->getGdmlLowerSCanonic() || c == *(SLEUTHTYPE*)obj->getGdmlUpperSCanonic())
				flags &= ~SLEUTH_insensitive;
		}
		else if (c == *(SLEUTHTYPE*)obj->getGdmlFlagClearCanonic())
		{
			c = *match++;
			if (c == *(SLEUTHTYPE*)obj->getGdmlLowerSCanonic() || c == *(SLEUTHTYPE*)obj->getGdmlUpperSCanonic())
				flags |= SLEUTH_insensitive;
		}
	}

	if (search < end_search)
		return false;

	return true;
}


template <class SLEUTHTYPE>
static bool SLEUTH_CLASS_NAME(
								 Jrd::TextType* obj,
								 USHORT flags,
								 const SLEUTHTYPE* char_class,
								 const SLEUTHTYPE* const end_class, 
								 SLEUTHTYPE character)
{
/**************************************
 *
 *	s l e u t h _ c l a s s
 *
 **************************************
 *
 * Functional description
 *	See if a character is a member of a class.
 *	Japanese version operates on short-based buffer,
 *	instead of SCHAR-based.
 *
 **************************************/
	fb_assert(char_class != NULL);
	fb_assert(end_class != NULL);
	fb_assert(char_class <= end_class);
	fb_assert(obj->getCanonicalWidth() == sizeof(SLEUTHTYPE));

	bool result = true;

	if (*char_class == *(SLEUTHTYPE*)obj->getGdmlNotCanonic()) {
		++char_class;
		result = false;
	}

	while (char_class < end_class) {
		const SLEUTHTYPE c = *char_class++;
		if (c == *(SLEUTHTYPE*)obj->getGdmlQuoteCanonic()) {
			if (*char_class++ == character)
				return true;
		}
		else if (*char_class == *(SLEUTHTYPE*)obj->getGdmlRangeCanonic()) {
			char_class += 2;
			if (character >= c && character <= char_class[-1])
				return result;
		}
		else if (character == c)
			return result;
	}

	return !result;
}


// Below are templates for functions used in Collation implementation

class NullStrConverter {
public:
	NullStrConverter(thread_db* tdbb, const TextType* obj, const UCHAR *str, SLONG len) { }
};

template <typename PrevConverter>
class UpcaseConverter : public PrevConverter {
public:
	UpcaseConverter(thread_db* tdbb, TextType* obj, const UCHAR* &str, SLONG &len) :
		PrevConverter(tdbb, obj, str, len)
	{
		if (len > (int) sizeof(tempBuffer))
			out_str = FB_NEW(*tdbb->getDefaultPool()) UCHAR[len];
		else
			out_str = tempBuffer;
		obj->str_to_upper(len, str, len, out_str);
		str = out_str;
	}
	~UpcaseConverter() {
		if (out_str != tempBuffer)
			delete[] out_str;
	}
private:
	UCHAR tempBuffer[100], *out_str;
};

template <typename PrevConverter>
class CanonicalConverter : public PrevConverter {
public:
	CanonicalConverter(thread_db* tdbb, TextType* obj, const UCHAR* &str, SLONG &len) :
		PrevConverter(tdbb, obj, str, len)
	{
		SLONG out_len = len / obj->getCharSet()->minBytesPerChar() * obj->getCanonicalWidth();

		if (out_len > (int) sizeof(tempBuffer))
			out_str = FB_NEW(*tdbb->getDefaultPool()) UCHAR[out_len];
		else
			out_str = tempBuffer;

		if (str)
		{
			len = obj->canonical(len, str, out_len, out_str) * obj->getCanonicalWidth();
			str = out_str;
		}
		else
			len = 0;
	}
	~CanonicalConverter() {
		if (out_str != tempBuffer)
			delete[] out_str;
	}
private:
	UCHAR tempBuffer[100], *out_str;
};

template <typename StrConverter, typename CharType>
class LikeObjectImpl : public LikeObject {
public:
	LikeObjectImpl(MemoryPool& pool, const CharType* str, SLONG str_len,
				   CharType escape, bool use_escape,
				   CharType sql_match_any, CharType sql_match_one)
		: evaluator(pool, str, str_len, escape, use_escape, sql_match_any, sql_match_one)
	{ }

	void reset() { evaluator.reset(); }

	bool result() { return evaluator.getResult(); }

	bool process(thread_db* tdbb, Jrd::TextType* ttype, const UCHAR* str, SLONG length) {
		StrConverter cvt(tdbb, ttype, str, length);
		fb_assert(length % sizeof(CharType) == 0);
		return evaluator.processNextChunk(
			reinterpret_cast<const CharType*>(str), length / sizeof(CharType));
	}

	~LikeObjectImpl() {}

	static LikeObject* create(thread_db* tdbb, TextType* ttype, const UCHAR* str, SLONG length,
		const UCHAR* escape, SLONG escape_length,
		const UCHAR* sql_match_any, SLONG match_any_length,
		const UCHAR* sql_match_one, SLONG match_one_length)
	{
		StrConverter cvt(tdbb, ttype, str, length),
					 cvt_escape(tdbb, ttype, escape, escape_length),
					 cvt_match_any(tdbb, ttype, sql_match_any, match_any_length),
					 cvt_match_one(tdbb, ttype, sql_match_one, match_one_length);

		fb_assert(length % sizeof(CharType) == 0);
		return FB_NEW(*tdbb->getDefaultPool()) LikeObjectImpl(*tdbb->getDefaultPool(),
			reinterpret_cast<const CharType*>(str), length / sizeof(CharType),
			(escape ? *reinterpret_cast<const CharType*>(escape) : 0), escape_length != 0,
			*reinterpret_cast<const CharType*>(sql_match_any),
			*reinterpret_cast<const CharType*>(sql_match_one));
	}

	static bool evaluate(thread_db* tdbb, TextType* ttype, const UCHAR* s, SLONG sl,
		const UCHAR* p, SLONG pl, const UCHAR* escape, SLONG escape_length, const UCHAR* sql_match_any, SLONG match_any_length, const UCHAR* sql_match_one, SLONG match_one_length)
	{
		StrConverter cvt1(tdbb, ttype, p, pl),
					 cvt2(tdbb, ttype, s, sl),
					 cvt_escape(tdbb, ttype, escape, escape_length),
					 cvt_match_any(tdbb, ttype, sql_match_any, match_any_length),
					 cvt_match_one(tdbb, ttype, sql_match_one, match_one_length);

		fb_assert(pl % sizeof(CharType) == 0);
		fb_assert(sl % sizeof(CharType) == 0);
		Firebird::LikeEvaluator<CharType> evaluator(*tdbb->getDefaultPool(),
			reinterpret_cast<const CharType*>(p), pl / sizeof(CharType),
			(escape ? *reinterpret_cast<const CharType*>(escape) : 0), escape_length != 0,
			*reinterpret_cast<const CharType*>(sql_match_any),
			*reinterpret_cast<const CharType*>(sql_match_one));
		evaluator.processNextChunk(reinterpret_cast<const CharType*>(s), sl / sizeof(CharType));
		return evaluator.getResult();
	}

private:
	Firebird::LikeEvaluator<CharType> evaluator;
};

template <typename StrConverter, typename CharType>
class ContainsObjectImpl : public ContainsObject
{
public:
	ContainsObjectImpl(MemoryPool& pool, const CharType* str, SLONG str_len)
		: evaluator(pool, str, str_len)
	{ }

	void reset() { evaluator.reset(); }

	bool result() { return evaluator.getResult(); }

	bool process(thread_db* tdbb, Jrd::TextType* ttype, const UCHAR* str, SLONG length) {
		StrConverter cvt(tdbb, ttype, str, length);
		fb_assert(length % sizeof(CharType) == 0);
		return evaluator.processNextChunk(
			reinterpret_cast<const CharType*>(str), length / sizeof(CharType));
	}

	~ContainsObjectImpl() {}

	static ContainsObject* create(thread_db* tdbb, TextType* ttype, const UCHAR* str, SLONG length) {
		StrConverter cvt(tdbb, ttype, str, length);
		fb_assert(length % sizeof(CharType) == 0);
		return FB_NEW(*tdbb->getDefaultPool()) ContainsObjectImpl(*tdbb->getDefaultPool(),
			reinterpret_cast<const CharType*>(str), length / sizeof(CharType));
	}

	static bool evaluate(thread_db* tdbb, TextType* ttype, const UCHAR* s, SLONG sl,
			const UCHAR* p, SLONG pl)
	{
		StrConverter cvt1(tdbb, ttype, p, pl), cvt2(tdbb, ttype, s, sl);
		fb_assert(pl % sizeof(CharType) == 0);
		fb_assert(sl % sizeof(CharType) == 0);
		Firebird::ContainsEvaluator<CharType> evaluator(*tdbb->getDefaultPool(),
			reinterpret_cast<const CharType*>(p), pl / sizeof(CharType));
		evaluator.processNextChunk(reinterpret_cast<const CharType*>(s), sl / sizeof(CharType));
		return evaluator.getResult();
	}

private:
	Firebird::ContainsEvaluator<CharType> evaluator;
};

template <typename StrConverter, typename CharType>
class MatchesObjectImpl
{
public:
	static bool evaluate(thread_db* tdbb, TextType* ttype, const UCHAR* s, SLONG sl,
			const UCHAR* p, SLONG pl)
	{
		StrConverter cvt1(tdbb, ttype, p, pl), cvt2(tdbb, ttype, s, sl);
		fb_assert(pl % sizeof(CharType) == 0);
		fb_assert(sl % sizeof(CharType) == 0);
		return MATCHESNAME(tdbb, ttype, reinterpret_cast<const CharType*>(s), sl,
						   reinterpret_cast<const CharType*>(p), pl);
	}
};

template <typename StrConverter, typename CharType>
class SleuthObjectImpl
{
public:
	static bool check(thread_db* tdbb, TextType* ttype, USHORT flags,
					  const UCHAR* search, SLONG search_len,
					  const UCHAR* match, SLONG match_len)
	{
		StrConverter cvt1(tdbb, ttype, search, search_len);//, cvt2(tdbb, ttype, match, match_len);
		fb_assert(search_len % sizeof(CharType) == 0);
		fb_assert(match_len % sizeof(CharType) == 0);
		return SLEUTHNAME(tdbb, ttype, flags,
						  reinterpret_cast<const CharType*>(search), search_len,
						  reinterpret_cast<const CharType*>(match), match_len);
	}

	static ULONG merge(thread_db* tdbb, TextType* ttype,
					  const UCHAR* match, SLONG match_bytes,
					  const UCHAR* control, SLONG control_bytes,
					  UCHAR* combined, SLONG combined_bytes)
	{
		StrConverter cvt1(tdbb, ttype, match, match_bytes), cvt2(tdbb, ttype, control, control_bytes);
		fb_assert(match_bytes % sizeof(CharType) == 0);
		fb_assert(control_bytes % sizeof(CharType) == 0);
		return SLEUTH_MERGE_NAME(tdbb, ttype,
						   reinterpret_cast<const CharType*>(match), match_bytes,
						   reinterpret_cast<const CharType*>(control), control_bytes,
						   reinterpret_cast<CharType*>(combined), combined_bytes);
	}
};


template <typename pContainsObjectImpl, typename pLikeObjectImpl,
		  typename pMatchesObjectImpl, typename pSleuthObjectImpl>
class CollationImpl : public Collation
{
public:
	CollationImpl(TTYPE_ID a_type, texttype* a_tt, CharSet* a_cs) : Collation(a_type, a_tt, a_cs) {}

	virtual bool matches(thread_db* tdbb, const UCHAR* a, SLONG b, const UCHAR* c, SLONG d)
	{
		return pMatchesObjectImpl::evaluate(tdbb, this, a, b, c, d);
	}

	virtual bool sleuth_check(thread_db* tdbb, USHORT a, const UCHAR* b, SLONG c, const UCHAR* d, SLONG e)
	{
		return pSleuthObjectImpl::check(tdbb, this, a, b, c, d, e);
	}

	virtual ULONG sleuth_merge(thread_db* tdbb, const UCHAR* a, SLONG b, const UCHAR* c, SLONG d, UCHAR* e, SLONG f)
	{
		return pSleuthObjectImpl::merge(tdbb, this, a, b, c, d, e, f);
	}

	virtual bool like(thread_db* tdbb, const UCHAR* s, SLONG sl, const UCHAR* p, SLONG pl, const UCHAR* escape, SLONG escape_length)
	{
		return pLikeObjectImpl::evaluate(tdbb, this, s, sl, p, pl, escape, escape_length, getCharSet()->getSqlMatchAny(), getCharSet()->getSqlMatchAnyLength(), getCharSet()->getSqlMatchOne(), getCharSet()->getSqlMatchOneLength());
	}

	virtual LikeObject *like_create(thread_db* tdbb, const UCHAR* p, SLONG pl, const UCHAR* escape, SLONG escape_length)
	{
		return pLikeObjectImpl::create(tdbb, this, p, pl, escape, escape_length, getCharSet()->getSqlMatchAny(), getCharSet()->getSqlMatchAnyLength(), getCharSet()->getSqlMatchOne(), getCharSet()->getSqlMatchOneLength());
	}

	virtual bool contains(thread_db* tdbb, const UCHAR* s, SLONG sl, const UCHAR* p, SLONG pl)
	{
		return pContainsObjectImpl::evaluate(tdbb, this, s, sl, p, pl);
	}

	virtual ContainsObject *contains_create(thread_db* tdbb, const UCHAR* p, SLONG pl)
	{
		return pContainsObjectImpl::create(tdbb, this, p, pl);
	}
};

typedef ContainsObjectImpl<UpcaseConverter<NullStrConverter>, UCHAR> uchar_contains_direct;
typedef ContainsObjectImpl<UpcaseConverter<NullStrConverter>, USHORT> ushort_contains_direct;
typedef ContainsObjectImpl<UpcaseConverter<NullStrConverter>, ULONG> ulong_contains_direct;

typedef MatchesObjectImpl<CanonicalConverter<NullStrConverter>, UCHAR> uchar_matches_canonical;
typedef SleuthObjectImpl<CanonicalConverter<NullStrConverter>, UCHAR> uchar_sleuth_canonical;
typedef LikeObjectImpl<CanonicalConverter<NullStrConverter>, UCHAR> uchar_like_canonical;
typedef ContainsObjectImpl<CanonicalConverter<UpcaseConverter<NullStrConverter> >, UCHAR> uchar_contains_canonical;

typedef MatchesObjectImpl<CanonicalConverter<NullStrConverter>, USHORT> ushort_matches_canonical;
typedef SleuthObjectImpl<CanonicalConverter<NullStrConverter>, USHORT> ushort_sleuth_canonical;
typedef LikeObjectImpl<CanonicalConverter<NullStrConverter>, USHORT> ushort_like_canonical;
typedef ContainsObjectImpl<CanonicalConverter<UpcaseConverter<NullStrConverter> >, USHORT> ushort_contains_canonical;

typedef MatchesObjectImpl<CanonicalConverter<NullStrConverter>, ULONG> ulong_matches_canonical;
typedef SleuthObjectImpl<CanonicalConverter<NullStrConverter>, ULONG> ulong_sleuth_canonical;
typedef LikeObjectImpl<CanonicalConverter<NullStrConverter>, ULONG> ulong_like_canonical;
typedef ContainsObjectImpl<CanonicalConverter<UpcaseConverter<NullStrConverter> >, ULONG> ulong_contains_canonical;

}	// namespace


//-------------


namespace Jrd {


Collation* Collation::createInstance(MemoryPool& pool, TTYPE_ID id, texttype* tt, CharSet* cs)
{
	fb_assert(tt->texttype_canonical_width == 1 ||
			  tt->texttype_canonical_width == 2 ||
			  tt->texttype_canonical_width == 4);

	switch (tt->texttype_canonical_width)
	{
		case 1:
			if (tt->texttype_flags & TEXTTYPE_DIRECT_MATCH)
			{
				return FB_NEW(pool) CollationImpl<uchar_contains_direct, uchar_like_canonical,
					uchar_matches_canonical, uchar_sleuth_canonical>(id, tt, cs);
			}

			return FB_NEW(pool) CollationImpl<uchar_contains_canonical, uchar_like_canonical,
				uchar_matches_canonical, uchar_sleuth_canonical>(id, tt, cs);
			break;

		case 2:
			if (tt->texttype_flags & TEXTTYPE_DIRECT_MATCH)
			{
				return FB_NEW(pool) CollationImpl<uchar_contains_direct, ushort_like_canonical,
					ushort_matches_canonical, ushort_sleuth_canonical>(id, tt, cs);
			}

			return FB_NEW(pool) CollationImpl<ushort_contains_canonical, ushort_like_canonical,
				ushort_matches_canonical, ushort_sleuth_canonical>(id, tt, cs);
			break;

		case 4:
			if (tt->texttype_flags & TEXTTYPE_DIRECT_MATCH)
			{
				return FB_NEW(pool) CollationImpl<uchar_contains_direct, ulong_like_canonical,
					ulong_matches_canonical, ulong_sleuth_canonical>(id, tt, cs);
			}

			return FB_NEW(pool) CollationImpl<ulong_contains_canonical, ulong_like_canonical,
				ulong_matches_canonical, ulong_sleuth_canonical>(id, tt, cs);
			break;
	}

	fb_assert(false);	
	return NULL;	// compiler silencer
}


void Collation::destroy()
{
	if (tt->texttype_fn_destroy)
		tt->texttype_fn_destroy(tt);

	delete tt;
	if (existenceLock)
	{
		thread_db thd_context, *tdbb;

		// Establish a thread context.
		JRD_set_thread_data(tdbb, thd_context);

		tdbb->setDatabase(existenceLock->lck_dbb);
		tdbb->setAttachment(existenceLock->lck_attachment);
		tdbb->tdbb_quantum = QUANTUM;
		tdbb->setRequest(NULL);
		tdbb->setTransaction(NULL);
		Jrd::ContextPoolHolder context(tdbb, 0);

		LCK_release(tdbb, existenceLock);

		delete existenceLock;

		// Restore the prior thread context
		JRD_restore_thread_data();

		existenceLock = 0;
	}
}


void Collation::incUseCount(thread_db* tdbb)
{
	++useCount;
}


void Collation::decUseCount(thread_db* tdbb)
{
	if (--useCount == 0)
	{
		if (existenceLock)
			LCK_re_post(existenceLock);
	}
}


}	// namespace Jrd
