/*-------------------------------------------------------------------------
 *
 * libpq-events.c
 *	  functions for supporting the libpq "events" API
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/libpq-events.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include "libpq-fe.h"
#include "libpq-int.h"


/*
 * Registers an event proc with the given PGconn.
 *
 * The same proc can't be registered more than once in a PGconn.  This
 * restriction is required because we use the proc address to identify
 * the event for purposes such as PQinstanceData().
 *
 * The name argument is used within error messages to aid in debugging.
 * A name must be supplied, but it needn't be unique.  The string is
 * copied, so the passed value needn't be long-lived.
 *
 * The passThrough argument is an application specific pointer and can be set
 * to NULL if not required.  It is passed through to the event proc whenever
 * the event proc is called, and is not otherwise touched by libpq.
 *
 * The function returns a non-zero if successful.  If the function fails,
 * zero is returned.
 */
int
PQregisterEventProc(PGconn *conn, PGEventProc proc,
					const char *name, void *passThrough)
{
	int			i;
	PGEventRegister regevt;

	if (!proc || !conn || !name || !*name)
		return false;			/* bad arguments */

	for (i = 0; i < conn->nEvents; i++)
	{
		if (conn->events[i].proc == proc)
			return false;		/* already registered */
	}

	if (conn->nEvents >= conn->eventArraySize)
	{
		PGEvent    *e;
		int			newSize;

		newSize = conn->eventArraySize ? conn->eventArraySize * 2 : 8;
		if (conn->events)
			e = (PGEvent *) realloc(conn->events, newSize * sizeof(PGEvent));
		else
			e = (PGEvent *) malloc(newSize * sizeof(PGEvent));

		if (!e)
			return false;

		conn->eventArraySize = newSize;
		conn->events = e;
	}

	conn->events[conn->nEvents].proc = proc;
	conn->events[conn->nEvents].name = strdup(name);
	if (!conn->events[conn->nEvents].name)
		return false;
	conn->events[conn->nEvents].passThrough = passThrough;
	conn->events[conn->nEvents].data = NULL;
	conn->events[conn->nEvents].resultInitialized = false;
	conn->nEvents++;

	regevt.conn = conn;
	if (!proc(PGEVT_REGISTER, &regevt, passThrough))
	{
		conn->nEvents--;
		free(conn->events[conn->nEvents].name);
		return false;
	}

	return true;
}

/*
 * Set some "instance data" for an event within a PGconn.
 * Returns nonzero on success, zero on failure.
 */
int
PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data)
{
	int			i;

	if (!conn || !proc)
		return false;

	for (i = 0; i < conn->nEvents; i++)
	{
		if (conn->events[i].proc == proc)
		{
			conn->events[i].data = data;
			return true;
		}
	}

	return false;
}

/*
 * Obtain the "instance data", if any, for the event.
 */
void *
PQinstanceData(const PGconn *conn, PGEventProc proc)
{
	int			i;

	if (!conn || !proc)
		return NULL;

	for (i = 0; i < conn->nEvents; i++)
	{
		if (conn->events[i].proc == proc)
			return conn->events[i].data;
	}

	return NULL;
}

/*
 * Set some "instance data" for an event within a PGresult.
 * Returns nonzero on success, zero on failure.
 */
int
PQresultSetInstanceData(PGresult *result, PGEventProc proc, void *data)
{
	int			i;

	if (!result || !proc)
		return false;

	for (i = 0; i < result->nEvents; i++)
	{
		if (result->events[i].proc == proc)
		{
			result->events[i].data = data;
			return true;
		}
	}

	return false;
}

/*
 * Obtain the "instance data", if any, for the event.
 */
void *
PQresultInstanceData(const PGresult *result, PGEventProc proc)
{
	int			i;

	if (!result || !proc)
		return NULL;

	for (i = 0; i < result->nEvents; i++)
		if (result->events[i].proc == proc)
			return result->events[i].data;

	return NULL;
}

/*
 * Fire RESULTCREATE events for an application-created PGresult.
 *
 * The conn argument can be NULL if event procedures won't use it.
 */
int
PQfireResultCreateEvents(PGconn *conn, PGresult *res)
{
	int			result = true;
	int			i;

	if (!res)
		return false;

	for (i = 0; i < res->nEvents; i++)
	{
		/* It's possible event was already fired, if so don't repeat it */
		if (!res->events[i].resultInitialized)
		{
			PGEventResultCreate evt;

			evt.conn = conn;
			evt.result = res;
			if (res->events[i].proc(PGEVT_RESULTCREATE, &evt,
									res->events[i].passThrough))
				res->events[i].resultInitialized = true;
			else
				result = false;
		}
	}

	return result;
}
