/*-------------------------------------------------------------------------
 *
 * ginscan.c
 *	  routines to manage scans inverted index relations
 *
 *
 * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/gin/ginscan.c
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/gin.h"
#include "access/relscan.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "utils/memutils.h"
#include "utils/rel.h"


Datum
ginbeginscan(PG_FUNCTION_ARGS)
{
	Relation	rel = (Relation) PG_GETARG_POINTER(0);
	int			nkeys = PG_GETARG_INT32(1);
	int			norderbys = PG_GETARG_INT32(2);
	IndexScanDesc scan;
	GinScanOpaque so;

	/* no order by operators allowed */
	Assert(norderbys == 0);

	scan = RelationGetIndexScan(rel, nkeys, norderbys);

	/* allocate private workspace */
	so = (GinScanOpaque) palloc(sizeof(GinScanOpaqueData));
	so->keys = NULL;
	so->nkeys = 0;
	so->tempCtx = AllocSetContextCreate(CurrentMemoryContext,
										"Gin scan temporary context",
										ALLOCSET_DEFAULT_MINSIZE,
										ALLOCSET_DEFAULT_INITSIZE,
										ALLOCSET_DEFAULT_MAXSIZE);
	initGinState(&so->ginstate, scan->indexRelation);

	scan->opaque = so;

	PG_RETURN_POINTER(scan);
}

static void
fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query,
			Datum *entryValues, bool *partial_matches, uint32 nEntryValues,
			StrategyNumber strategy, Pointer *extra_data)
{
	uint32		i,
				j;

	key->nentries = nEntryValues;
	key->entryRes = (bool *) palloc0(sizeof(bool) * nEntryValues);
	key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nEntryValues);
	key->strategy = strategy;
	key->attnum = attnum;
	key->extra_data = extra_data;
	key->query = query;
	key->firstCall = TRUE;
	ItemPointerSetMin(&key->curItem);

	for (i = 0; i < nEntryValues; i++)
	{
		key->scanEntry[i].pval = key->entryRes + i;
		key->scanEntry[i].entry = entryValues[i];
		key->scanEntry[i].attnum = attnum;
		key->scanEntry[i].extra_data = (extra_data) ? extra_data[i] : NULL;
		ItemPointerSetMin(&key->scanEntry[i].curItem);
		key->scanEntry[i].isFinished = FALSE;
		key->scanEntry[i].offset = InvalidOffsetNumber;
		key->scanEntry[i].buffer = InvalidBuffer;
		key->scanEntry[i].partialMatch = NULL;
		key->scanEntry[i].partialMatchIterator = NULL;
		key->scanEntry[i].partialMatchResult = NULL;
		key->scanEntry[i].strategy = strategy;
		key->scanEntry[i].list = NULL;
		key->scanEntry[i].nlist = 0;
		key->scanEntry[i].isPartialMatch = (ginstate->canPartialMatch[attnum - 1] && partial_matches)
			? partial_matches[i] : false;

		/* link to the equals entry in current scan key */
		key->scanEntry[i].master = NULL;
		for (j = 0; j < i; j++)
			if (ginCompareEntries(ginstate, attnum,
								  entryValues[i], entryValues[j]) == 0 &&
				key->scanEntry[i].isPartialMatch == key->scanEntry[j].isPartialMatch &&
				key->scanEntry[i].strategy == key->scanEntry[j].strategy)
			{
				key->scanEntry[i].master = key->scanEntry + j;
				break;
			}
	}
}

#ifdef NOT_USED

static void
resetScanKeys(GinScanKey keys, uint32 nkeys)
{
	uint32		i,
				j;

	if (keys == NULL)
		return;

	for (i = 0; i < nkeys; i++)
	{
		GinScanKey	key = keys + i;

		key->firstCall = TRUE;
		ItemPointerSetMin(&key->curItem);

		for (j = 0; j < key->nentries; j++)
		{
			if (key->scanEntry[j].buffer != InvalidBuffer)
				ReleaseBuffer(key->scanEntry[i].buffer);

			ItemPointerSetMin(&key->scanEntry[j].curItem);
			key->scanEntry[j].isFinished = FALSE;
			key->scanEntry[j].offset = InvalidOffsetNumber;
			key->scanEntry[j].buffer = InvalidBuffer;
			key->scanEntry[j].list = NULL;
			key->scanEntry[j].nlist = 0;
			key->scanEntry[j].partialMatch = NULL;
			key->scanEntry[j].partialMatchIterator = NULL;
			key->scanEntry[j].partialMatchResult = NULL;
		}
	}
}
#endif

static void
freeScanKeys(GinScanKey keys, uint32 nkeys)
{
	uint32		i,
				j;

	if (keys == NULL)
		return;

	for (i = 0; i < nkeys; i++)
	{
		GinScanKey	key = keys + i;

		for (j = 0; j < key->nentries; j++)
		{
			if (key->scanEntry[j].buffer != InvalidBuffer)
				ReleaseBuffer(key->scanEntry[j].buffer);
			if (key->scanEntry[j].list)
				pfree(key->scanEntry[j].list);
			if (key->scanEntry[j].partialMatchIterator)
				tbm_end_iterate(key->scanEntry[j].partialMatchIterator);
			if (key->scanEntry[j].partialMatch)
				tbm_free(key->scanEntry[j].partialMatch);
		}

		pfree(key->entryRes);
		pfree(key->scanEntry);
	}

	pfree(keys);
}

void
ginNewScanKey(IndexScanDesc scan)
{
	ScanKey		scankey = scan->keyData;
	GinScanOpaque so = (GinScanOpaque) scan->opaque;
	int			i;
	uint32		nkeys = 0;

	if (scan->numberOfKeys < 1)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("GIN indexes do not support whole-index scans")));

	so->keys = (GinScanKey) palloc(scan->numberOfKeys * sizeof(GinScanKeyData));

	so->isVoidRes = false;

	for (i = 0; i < scan->numberOfKeys; i++)
	{
		ScanKey		skey = &scankey[i];
		Datum	   *entryValues;
		int32		nEntryValues = 0;
		bool	   *partial_matches = NULL;
		Pointer    *extra_data = NULL;

		/*
		 * Assume, that GIN-indexable operators are strict, so nothing could
		 * be found
		 */
		if (skey->sk_flags & SK_ISNULL)
		{
			so->isVoidRes = true;
			break;
		}

		entryValues = (Datum *)
			DatumGetPointer(FunctionCall5(&so->ginstate.extractQueryFn[skey->sk_attno - 1],
										  skey->sk_argument,
										  PointerGetDatum(&nEntryValues),
										  UInt16GetDatum(skey->sk_strategy),
										  PointerGetDatum(&partial_matches),
										  PointerGetDatum(&extra_data)));

		if (nEntryValues < 0)
		{
			/*
			 * extractQueryFn signals that nothing can match, so we can just
			 * set isVoidRes flag.	No need to examine any more keys.
			 */
			so->isVoidRes = true;
			break;
		}

		if (entryValues == NULL || nEntryValues == 0)
		{
			/*
			 * extractQueryFn signals that everything matches.	This would
			 * require a full scan, which we can't do, but perhaps there is
			 * another scankey that provides a restriction to use.	So we keep
			 * going and check only at the end.
			 */
			continue;
		}

		fillScanKey(&so->ginstate, &(so->keys[nkeys]),
					skey->sk_attno, skey->sk_argument,
					entryValues, partial_matches, nEntryValues,
					skey->sk_strategy, extra_data);
		nkeys++;
	}

	if (nkeys == 0 && !so->isVoidRes)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("GIN indexes do not support whole-index scans")));

	so->nkeys = nkeys;

	pgstat_count_index_scan(scan->indexRelation);
}

Datum
ginrescan(PG_FUNCTION_ARGS)
{
	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
	ScanKey		scankey = (ScanKey) PG_GETARG_POINTER(1);
	/* remaining arguments are ignored */
	GinScanOpaque so = (GinScanOpaque) scan->opaque;

	freeScanKeys(so->keys, so->nkeys);
	so->keys = NULL;

	if (scankey && scan->numberOfKeys > 0)
	{
		memmove(scan->keyData, scankey,
				scan->numberOfKeys * sizeof(ScanKeyData));
	}

	PG_RETURN_VOID();
}


Datum
ginendscan(PG_FUNCTION_ARGS)
{
	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
	GinScanOpaque so = (GinScanOpaque) scan->opaque;

	freeScanKeys(so->keys, so->nkeys);

	MemoryContextDelete(so->tempCtx);

	pfree(so);

	PG_RETURN_VOID();
}

Datum
ginmarkpos(PG_FUNCTION_ARGS)
{
	elog(ERROR, "GIN does not support mark/restore");
	PG_RETURN_VOID();
}

Datum
ginrestrpos(PG_FUNCTION_ARGS)
{
	elog(ERROR, "GIN does not support mark/restore");
	PG_RETURN_VOID();
}
