mirror of
https://github.com/postgres/postgres.git
synced 2025-05-28 05:21:27 +03:00
Move generic slot support functions from heaptuple.c into execTuples.c.
heaptuple.c was never a particular good fit for slot_getattr(), slot_getsomeattrs() and slot_getmissingattrs(), but in upcoming changes slots will be made more abstract (allowing slots that contain different types of tuples), making it clearly the wrong place. Note that slot_deform_tuple() remains in it's current place, as it clearly deals with a HeapTuple. getmissingattrs() also remains, but it's less clear that that's correct - but execTuples.c wouldn't be the right place. Author: Ashutosh Bapat. Discussion: https://postgr.es/m/20180220224318.gw4oe5jadhpmcdnm@alap3.anarazel.de
This commit is contained in:
parent
e73ca79fc7
commit
9d906f1119
@ -80,7 +80,7 @@
|
|||||||
/*
|
/*
|
||||||
* Return the missing value of an attribute, or NULL if there isn't one.
|
* Return the missing value of an attribute, or NULL if there isn't one.
|
||||||
*/
|
*/
|
||||||
static Datum
|
Datum
|
||||||
getmissingattr(TupleDesc tupleDesc,
|
getmissingattr(TupleDesc tupleDesc,
|
||||||
int attnum, bool *isnull)
|
int attnum, bool *isnull)
|
||||||
{
|
{
|
||||||
@ -111,43 +111,6 @@ getmissingattr(TupleDesc tupleDesc,
|
|||||||
return PointerGetDatum(NULL);
|
return PointerGetDatum(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Fill in missing values for a TupleTableSlot.
|
|
||||||
*
|
|
||||||
* This is only exposed because it's needed for JIT compiled tuple
|
|
||||||
* deforming. That exception aside, there should be no callers outside of this
|
|
||||||
* file.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
|
|
||||||
{
|
|
||||||
AttrMissing *attrmiss = NULL;
|
|
||||||
int missattnum;
|
|
||||||
|
|
||||||
if (slot->tts_tupleDescriptor->constr)
|
|
||||||
attrmiss = slot->tts_tupleDescriptor->constr->missing;
|
|
||||||
|
|
||||||
if (!attrmiss)
|
|
||||||
{
|
|
||||||
/* no missing values array at all, so just fill everything in as NULL */
|
|
||||||
memset(slot->tts_values + startAttNum, 0,
|
|
||||||
(lastAttNum - startAttNum) * sizeof(Datum));
|
|
||||||
memset(slot->tts_isnull + startAttNum, 1,
|
|
||||||
(lastAttNum - startAttNum) * sizeof(bool));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* if there is a missing values array we must process them one by one */
|
|
||||||
for (missattnum = startAttNum;
|
|
||||||
missattnum < lastAttNum;
|
|
||||||
missattnum++)
|
|
||||||
{
|
|
||||||
slot->tts_values[missattnum] = attrmiss[missattnum].am_value;
|
|
||||||
slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* heap_compute_data_size
|
* heap_compute_data_size
|
||||||
* Determine size of the data area of a tuple to be constructed
|
* Determine size of the data area of a tuple to be constructed
|
||||||
@ -1398,7 +1361,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
|
|||||||
* re-computing information about previously extracted attributes.
|
* re-computing information about previously extracted attributes.
|
||||||
* slot->tts_nvalid is the number of attributes already extracted.
|
* slot->tts_nvalid is the number of attributes already extracted.
|
||||||
*/
|
*/
|
||||||
static void
|
void
|
||||||
slot_deform_tuple(TupleTableSlot *slot, int natts)
|
slot_deform_tuple(TupleTableSlot *slot, int natts)
|
||||||
{
|
{
|
||||||
HeapTuple tuple = slot->tts_tuple;
|
HeapTuple tuple = slot->tts_tuple;
|
||||||
@ -1492,153 +1455,6 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
|
|||||||
slot->tts_slow = slow;
|
slot->tts_slow = slow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* slot_getattr
|
|
||||||
* This function fetches an attribute of the slot's current tuple.
|
|
||||||
* It is functionally equivalent to heap_getattr, but fetches of
|
|
||||||
* multiple attributes of the same tuple will be optimized better,
|
|
||||||
* because we avoid O(N^2) behavior from multiple calls of
|
|
||||||
* nocachegetattr(), even when attcacheoff isn't usable.
|
|
||||||
*
|
|
||||||
* A difference from raw heap_getattr is that attnums beyond the
|
|
||||||
* slot's tupdesc's last attribute will be considered NULL even
|
|
||||||
* when the physical tuple is longer than the tupdesc.
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
|
|
||||||
{
|
|
||||||
HeapTuple tuple = slot->tts_tuple;
|
|
||||||
TupleDesc tupleDesc = slot->tts_tupleDescriptor;
|
|
||||||
HeapTupleHeader tup;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* system attributes are handled by heap_getsysattr
|
|
||||||
*/
|
|
||||||
if (attnum <= 0)
|
|
||||||
{
|
|
||||||
if (tuple == NULL) /* internal error */
|
|
||||||
elog(ERROR, "cannot extract system attribute from virtual tuple");
|
|
||||||
if (tuple == &(slot->tts_minhdr)) /* internal error */
|
|
||||||
elog(ERROR, "cannot extract system attribute from minimal tuple");
|
|
||||||
return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fast path if desired attribute already cached
|
|
||||||
*/
|
|
||||||
if (attnum <= slot->tts_nvalid)
|
|
||||||
{
|
|
||||||
*isnull = slot->tts_isnull[attnum - 1];
|
|
||||||
return slot->tts_values[attnum - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return NULL if attnum is out of range according to the tupdesc
|
|
||||||
*/
|
|
||||||
if (attnum > tupleDesc->natts)
|
|
||||||
{
|
|
||||||
*isnull = true;
|
|
||||||
return (Datum) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* otherwise we had better have a physical tuple (tts_nvalid should equal
|
|
||||||
* natts in all virtual-tuple cases)
|
|
||||||
*/
|
|
||||||
if (tuple == NULL) /* internal error */
|
|
||||||
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
|
||||||
|
|
||||||
/*
|
|
||||||
* return NULL or missing value if attnum is out of range according to the
|
|
||||||
* tuple
|
|
||||||
*
|
|
||||||
* (We have to check this separately because of various inheritance and
|
|
||||||
* table-alteration scenarios: the tuple could be either longer or shorter
|
|
||||||
* than the tupdesc.)
|
|
||||||
*/
|
|
||||||
tup = tuple->t_data;
|
|
||||||
if (attnum > HeapTupleHeaderGetNatts(tup))
|
|
||||||
return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* check if target attribute is null: no point in groveling through tuple
|
|
||||||
*/
|
|
||||||
if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
|
|
||||||
{
|
|
||||||
*isnull = true;
|
|
||||||
return (Datum) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the attribute's column has been dropped, we force a NULL result.
|
|
||||||
* This case should not happen in normal use, but it could happen if we
|
|
||||||
* are executing a plan cached before the column was dropped.
|
|
||||||
*/
|
|
||||||
if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
|
|
||||||
{
|
|
||||||
*isnull = true;
|
|
||||||
return (Datum) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Extract the attribute, along with any preceding attributes.
|
|
||||||
*/
|
|
||||||
slot_deform_tuple(slot, attnum);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The result is acquired from tts_values array.
|
|
||||||
*/
|
|
||||||
*isnull = slot->tts_isnull[attnum - 1];
|
|
||||||
return slot->tts_values[attnum - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* slot_getsomeattrs
|
|
||||||
* This function forces the entries of the slot's Datum/isnull
|
|
||||||
* arrays to be valid at least up through the attnum'th entry.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
slot_getsomeattrs(TupleTableSlot *slot, int attnum)
|
|
||||||
{
|
|
||||||
HeapTuple tuple;
|
|
||||||
int attno;
|
|
||||||
|
|
||||||
/* Quick out if we have 'em all already */
|
|
||||||
if (slot->tts_nvalid >= attnum)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Check for caller error */
|
|
||||||
if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
|
|
||||||
elog(ERROR, "invalid attribute number %d", attnum);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* otherwise we had better have a physical tuple (tts_nvalid should equal
|
|
||||||
* natts in all virtual-tuple cases)
|
|
||||||
*/
|
|
||||||
tuple = slot->tts_tuple;
|
|
||||||
if (tuple == NULL) /* internal error */
|
|
||||||
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
|
||||||
|
|
||||||
/*
|
|
||||||
* load up any slots available from physical tuple
|
|
||||||
*/
|
|
||||||
attno = HeapTupleHeaderGetNatts(tuple->t_data);
|
|
||||||
attno = Min(attno, attnum);
|
|
||||||
|
|
||||||
slot_deform_tuple(slot, attno);
|
|
||||||
|
|
||||||
attno = slot->tts_nvalid;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If tuple doesn't have all the atts indicated by attnum, read the rest
|
|
||||||
* as NULLs or missing values
|
|
||||||
*/
|
|
||||||
if (attno < attnum)
|
|
||||||
slot_getmissingattrs(slot, attno, attnum);
|
|
||||||
|
|
||||||
slot->tts_nvalid = attnum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* slot_attisnull
|
* slot_attisnull
|
||||||
* Detect whether an attribute of the slot is null, without
|
* Detect whether an attribute of the slot is null, without
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
|
#include "access/tupdesc_details.h"
|
||||||
#include "access/tuptoaster.h"
|
#include "access/tuptoaster.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
@ -959,6 +960,195 @@ ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
|
|||||||
return ExecStoreAllNullTuple(slot);
|
return ExecStoreAllNullTuple(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------
|
||||||
|
* Routines for setting/accessing attributes in a slot.
|
||||||
|
* ---------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fill in missing values for a TupleTableSlot.
|
||||||
|
*
|
||||||
|
* This is only exposed because it's needed for JIT compiled tuple
|
||||||
|
* deforming. That exception aside, there should be no callers outside of this
|
||||||
|
* file.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
|
||||||
|
{
|
||||||
|
AttrMissing *attrmiss = NULL;
|
||||||
|
int missattnum;
|
||||||
|
|
||||||
|
if (slot->tts_tupleDescriptor->constr)
|
||||||
|
attrmiss = slot->tts_tupleDescriptor->constr->missing;
|
||||||
|
|
||||||
|
if (!attrmiss)
|
||||||
|
{
|
||||||
|
/* no missing values array at all, so just fill everything in as NULL */
|
||||||
|
memset(slot->tts_values + startAttNum, 0,
|
||||||
|
(lastAttNum - startAttNum) * sizeof(Datum));
|
||||||
|
memset(slot->tts_isnull + startAttNum, 1,
|
||||||
|
(lastAttNum - startAttNum) * sizeof(bool));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* if there is a missing values array we must process them one by one */
|
||||||
|
for (missattnum = startAttNum;
|
||||||
|
missattnum < lastAttNum;
|
||||||
|
missattnum++)
|
||||||
|
{
|
||||||
|
slot->tts_values[missattnum] = attrmiss[missattnum].am_value;
|
||||||
|
slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* slot_getattr
|
||||||
|
* This function fetches an attribute of the slot's current tuple.
|
||||||
|
* It is functionally equivalent to heap_getattr, but fetches of
|
||||||
|
* multiple attributes of the same tuple will be optimized better,
|
||||||
|
* because we avoid O(N^2) behavior from multiple calls of
|
||||||
|
* nocachegetattr(), even when attcacheoff isn't usable.
|
||||||
|
*
|
||||||
|
* A difference from raw heap_getattr is that attnums beyond the
|
||||||
|
* slot's tupdesc's last attribute will be considered NULL even
|
||||||
|
* when the physical tuple is longer than the tupdesc.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = slot->tts_tuple;
|
||||||
|
TupleDesc tupleDesc = slot->tts_tupleDescriptor;
|
||||||
|
HeapTupleHeader tup;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* system attributes are handled by heap_getsysattr
|
||||||
|
*/
|
||||||
|
if (attnum <= 0)
|
||||||
|
{
|
||||||
|
if (tuple == NULL) /* internal error */
|
||||||
|
elog(ERROR, "cannot extract system attribute from virtual tuple");
|
||||||
|
if (tuple == &(slot->tts_minhdr)) /* internal error */
|
||||||
|
elog(ERROR, "cannot extract system attribute from minimal tuple");
|
||||||
|
return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fast path if desired attribute already cached
|
||||||
|
*/
|
||||||
|
if (attnum <= slot->tts_nvalid)
|
||||||
|
{
|
||||||
|
*isnull = slot->tts_isnull[attnum - 1];
|
||||||
|
return slot->tts_values[attnum - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* return NULL if attnum is out of range according to the tupdesc
|
||||||
|
*/
|
||||||
|
if (attnum > tupleDesc->natts)
|
||||||
|
{
|
||||||
|
*isnull = true;
|
||||||
|
return (Datum) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* otherwise we had better have a physical tuple (tts_nvalid should equal
|
||||||
|
* natts in all virtual-tuple cases)
|
||||||
|
*/
|
||||||
|
if (tuple == NULL) /* internal error */
|
||||||
|
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* return NULL or missing value if attnum is out of range according to the
|
||||||
|
* tuple
|
||||||
|
*
|
||||||
|
* (We have to check this separately because of various inheritance and
|
||||||
|
* table-alteration scenarios: the tuple could be either longer or shorter
|
||||||
|
* than the tupdesc.)
|
||||||
|
*/
|
||||||
|
tup = tuple->t_data;
|
||||||
|
if (attnum > HeapTupleHeaderGetNatts(tup))
|
||||||
|
return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check if target attribute is null: no point in groveling through tuple
|
||||||
|
*/
|
||||||
|
if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
|
||||||
|
{
|
||||||
|
*isnull = true;
|
||||||
|
return (Datum) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the attribute's column has been dropped, we force a NULL result.
|
||||||
|
* This case should not happen in normal use, but it could happen if we
|
||||||
|
* are executing a plan cached before the column was dropped.
|
||||||
|
*/
|
||||||
|
if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
|
||||||
|
{
|
||||||
|
*isnull = true;
|
||||||
|
return (Datum) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract the attribute, along with any preceding attributes.
|
||||||
|
*/
|
||||||
|
slot_deform_tuple(slot, attnum);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The result is acquired from tts_values array.
|
||||||
|
*/
|
||||||
|
*isnull = slot->tts_isnull[attnum - 1];
|
||||||
|
return slot->tts_values[attnum - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* slot_getsomeattrs
|
||||||
|
* This function forces the entries of the slot's Datum/isnull
|
||||||
|
* arrays to be valid at least up through the attnum'th entry.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
slot_getsomeattrs(TupleTableSlot *slot, int attnum)
|
||||||
|
{
|
||||||
|
HeapTuple tuple;
|
||||||
|
int attno;
|
||||||
|
|
||||||
|
/* Quick out if we have 'em all already */
|
||||||
|
if (slot->tts_nvalid >= attnum)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Check for caller error */
|
||||||
|
if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
|
||||||
|
elog(ERROR, "invalid attribute number %d", attnum);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* otherwise we had better have a physical tuple (tts_nvalid should equal
|
||||||
|
* natts in all virtual-tuple cases)
|
||||||
|
*/
|
||||||
|
tuple = slot->tts_tuple;
|
||||||
|
if (tuple == NULL) /* internal error */
|
||||||
|
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* load up any slots available from physical tuple
|
||||||
|
*/
|
||||||
|
attno = HeapTupleHeaderGetNatts(tuple->t_data);
|
||||||
|
attno = Min(attno, attnum);
|
||||||
|
|
||||||
|
slot_deform_tuple(slot, attno);
|
||||||
|
|
||||||
|
attno = slot->tts_nvalid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If tuple doesn't have all the atts indicated by attnum, read the rest
|
||||||
|
* as NULLs or missing values
|
||||||
|
*/
|
||||||
|
if (attno < attnum)
|
||||||
|
slot_getmissingattrs(slot, attno, attnum);
|
||||||
|
|
||||||
|
slot->tts_nvalid = attnum;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* ExecTypeFromTL
|
* ExecTypeFromTL
|
||||||
*
|
*
|
||||||
|
@ -835,5 +835,7 @@ extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
|
|||||||
extern size_t varsize_any(void *p);
|
extern size_t varsize_any(void *p);
|
||||||
extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
||||||
extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
||||||
|
struct TupleTableSlot;
|
||||||
|
extern void slot_deform_tuple(struct TupleTableSlot *slot, int natts);
|
||||||
|
|
||||||
#endif /* HTUP_DETAILS_H */
|
#endif /* HTUP_DETAILS_H */
|
||||||
|
@ -173,14 +173,18 @@ extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
|
|||||||
extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
|
extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
|
||||||
extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
|
extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
|
||||||
TupleTableSlot *srcslot);
|
TupleTableSlot *srcslot);
|
||||||
|
extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum,
|
||||||
|
int lastAttNum);
|
||||||
|
extern Datum slot_getattr(TupleTableSlot *slot, int attnum,
|
||||||
|
bool *isnull);
|
||||||
|
extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
|
||||||
|
|
||||||
/* in access/common/heaptuple.c */
|
/* in access/common/heaptuple.c */
|
||||||
extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
|
|
||||||
extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
|
|
||||||
extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
|
extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
|
||||||
extern bool slot_getsysattr(TupleTableSlot *slot, int attnum,
|
extern bool slot_getsysattr(TupleTableSlot *slot, int attnum,
|
||||||
Datum *value, bool *isnull);
|
Datum *value, bool *isnull);
|
||||||
extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum);
|
extern Datum getmissingattr(TupleDesc tupleDesc,
|
||||||
|
int attnum, bool *isnull);
|
||||||
|
|
||||||
#ifndef FRONTEND
|
#ifndef FRONTEND
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user