/*-------------------------------------------------------------------------
 *
 * pg_lsn.c
 *	  Operations for the pg_lsn datatype.
 *
 * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/pg_lsn.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "funcapi.h"
#include "libpq/pqformat.h"
#include "utils/builtins.h"
#include "utils/numeric.h"
#include "utils/pg_lsn.h"

#define MAXPG_LSNLEN			17
#define MAXPG_LSNCOMPONENT	8

/*----------------------------------------------------------
 * Formatting and conversion routines.
 *---------------------------------------------------------*/

XLogRecPtr
pg_lsn_in_internal(const char *str, bool *have_error)
{
	int			len1,
				len2;
	uint32		id,
				off;
	XLogRecPtr	result;

	Assert(have_error != NULL);
	*have_error = false;

	/* Sanity check input format. */
	len1 = strspn(str, "0123456789abcdefABCDEF");
	if (len1 < 1 || len1 > MAXPG_LSNCOMPONENT || str[len1] != '/')
	{
		*have_error = true;
		return InvalidXLogRecPtr;
	}
	len2 = strspn(str + len1 + 1, "0123456789abcdefABCDEF");
	if (len2 < 1 || len2 > MAXPG_LSNCOMPONENT || str[len1 + 1 + len2] != '\0')
	{
		*have_error = true;
		return InvalidXLogRecPtr;
	}

	/* Decode result. */
	id = (uint32) strtoul(str, NULL, 16);
	off = (uint32) strtoul(str + len1 + 1, NULL, 16);
	result = ((uint64) id << 32) | off;

	return result;
}

Datum
pg_lsn_in(PG_FUNCTION_ARGS)
{
	char	   *str = PG_GETARG_CSTRING(0);
	XLogRecPtr	result;
	bool		have_error = false;

	result = pg_lsn_in_internal(str, &have_error);
	if (have_error)
		ereturn(fcinfo->context, (Datum) 0,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input syntax for type %s: \"%s\"",
						"pg_lsn", str)));

	PG_RETURN_LSN(result);
}

Datum
pg_lsn_out(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn = PG_GETARG_LSN(0);
	char		buf[MAXPG_LSNLEN + 1];
	char	   *result;

	snprintf(buf, sizeof buf, "%X/%X", LSN_FORMAT_ARGS(lsn));
	result = pstrdup(buf);
	PG_RETURN_CSTRING(result);
}

Datum
pg_lsn_recv(PG_FUNCTION_ARGS)
{
	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
	XLogRecPtr	result;

	result = pq_getmsgint64(buf);
	PG_RETURN_LSN(result);
}

Datum
pg_lsn_send(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn = PG_GETARG_LSN(0);
	StringInfoData buf;

	pq_begintypsend(&buf);
	pq_sendint64(&buf, lsn);
	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}


/*----------------------------------------------------------
 *	Operators for PostgreSQL LSNs
 *---------------------------------------------------------*/

Datum
pg_lsn_eq(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 == lsn2);
}

Datum
pg_lsn_ne(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 != lsn2);
}

Datum
pg_lsn_lt(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 < lsn2);
}

Datum
pg_lsn_gt(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 > lsn2);
}

Datum
pg_lsn_le(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 <= lsn2);
}

Datum
pg_lsn_ge(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_BOOL(lsn1 >= lsn2);
}

Datum
pg_lsn_larger(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_LSN((lsn1 > lsn2) ? lsn1 : lsn2);
}

Datum
pg_lsn_smaller(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);

	PG_RETURN_LSN((lsn1 < lsn2) ? lsn1 : lsn2);
}

/* btree index opclass support */
Datum
pg_lsn_cmp(PG_FUNCTION_ARGS)
{
	XLogRecPtr	a = PG_GETARG_LSN(0);
	XLogRecPtr	b = PG_GETARG_LSN(1);

	if (a > b)
		PG_RETURN_INT32(1);
	else if (a == b)
		PG_RETURN_INT32(0);
	else
		PG_RETURN_INT32(-1);
}

/* hash index opclass support */
Datum
pg_lsn_hash(PG_FUNCTION_ARGS)
{
	/* We can use hashint8 directly */
	return hashint8(fcinfo);
}

Datum
pg_lsn_hash_extended(PG_FUNCTION_ARGS)
{
	return hashint8extended(fcinfo);
}


/*----------------------------------------------------------
 *	Arithmetic operators on PostgreSQL LSNs.
 *---------------------------------------------------------*/

Datum
pg_lsn_mi(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn1 = PG_GETARG_LSN(0);
	XLogRecPtr	lsn2 = PG_GETARG_LSN(1);
	char		buf[256];
	Datum		result;

	/* Output could be as large as plus or minus 2^63 - 1. */
	if (lsn1 < lsn2)
		snprintf(buf, sizeof buf, "-" UINT64_FORMAT, lsn2 - lsn1);
	else
		snprintf(buf, sizeof buf, UINT64_FORMAT, lsn1 - lsn2);

	/* Convert to numeric. */
	result = DirectFunctionCall3(numeric_in,
								 CStringGetDatum(buf),
								 ObjectIdGetDatum(0),
								 Int32GetDatum(-1));

	return result;
}

/*
 * Add the number of bytes to pg_lsn, giving a new pg_lsn.
 * Must handle both positive and negative numbers of bytes.
 */
Datum
pg_lsn_pli(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn = PG_GETARG_LSN(0);
	Numeric		nbytes = PG_GETARG_NUMERIC(1);
	Datum		num;
	Datum		res;
	char		buf[32];

	if (numeric_is_nan(nbytes))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot add NaN to pg_lsn")));

	/* Convert to numeric */
	snprintf(buf, sizeof(buf), UINT64_FORMAT, lsn);
	num = DirectFunctionCall3(numeric_in,
							  CStringGetDatum(buf),
							  ObjectIdGetDatum(0),
							  Int32GetDatum(-1));

	/* Add two numerics */
	res = DirectFunctionCall2(numeric_add,
							  num,
							  NumericGetDatum(nbytes));

	/* Convert to pg_lsn */
	return DirectFunctionCall1(numeric_pg_lsn, res);
}

/*
 * Subtract the number of bytes from pg_lsn, giving a new pg_lsn.
 * Must handle both positive and negative numbers of bytes.
 */
Datum
pg_lsn_mii(PG_FUNCTION_ARGS)
{
	XLogRecPtr	lsn = PG_GETARG_LSN(0);
	Numeric		nbytes = PG_GETARG_NUMERIC(1);
	Datum		num;
	Datum		res;
	char		buf[32];

	if (numeric_is_nan(nbytes))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot subtract NaN from pg_lsn")));

	/* Convert to numeric */
	snprintf(buf, sizeof(buf), UINT64_FORMAT, lsn);
	num = DirectFunctionCall3(numeric_in,
							  CStringGetDatum(buf),
							  ObjectIdGetDatum(0),
							  Int32GetDatum(-1));

	/* Subtract two numerics */
	res = DirectFunctionCall2(numeric_sub,
							  num,
							  NumericGetDatum(nbytes));

	/* Convert to pg_lsn */
	return DirectFunctionCall1(numeric_pg_lsn, res);
}
