mirror of
https://github.com/postgres/postgres.git
synced 2025-07-20 05:03:10 +03:00
Make contrib/seg work with flex 2.5.31. Fix it up to have a real
btree operator class, too, since in PG 7.4 you can't GROUP without one.
This commit is contained in:
@ -1,11 +1,11 @@
|
|||||||
# $Header: /cvsroot/pgsql/contrib/seg/Makefile,v 1.9 2003/05/14 03:27:22 tgl Exp $
|
# $Header: /cvsroot/pgsql/contrib/seg/Makefile,v 1.10 2003/09/14 02:18:49 tgl Exp $
|
||||||
|
|
||||||
subdir = contrib/seg
|
subdir = contrib/seg
|
||||||
top_builddir = ../..
|
top_builddir = ../..
|
||||||
include $(top_builddir)/src/Makefile.global
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
MODULE_big = seg
|
MODULE_big = seg
|
||||||
OBJS = seg.o segparse.o buffer.o
|
OBJS = seg.o segparse.o
|
||||||
DATA_built = seg.sql
|
DATA_built = seg.sql
|
||||||
DOCS = README.seg
|
DOCS = README.seg
|
||||||
REGRESS = seg
|
REGRESS = seg
|
||||||
@ -27,7 +27,7 @@ endif
|
|||||||
|
|
||||||
segscan.c: segscan.l
|
segscan.c: segscan.l
|
||||||
ifdef FLEX
|
ifdef FLEX
|
||||||
$(FLEX) $(FLEXFLAGS) -Pseg_yy -o'$@' $<
|
$(FLEX) $(FLEXFLAGS) -o'$@' $<
|
||||||
else
|
else
|
||||||
@$(missing) flex $< $@
|
@$(missing) flex $< $@
|
||||||
endif
|
endif
|
||||||
|
@ -56,12 +56,6 @@ Makefile building instructions for the shared library
|
|||||||
|
|
||||||
README.seg the file you are now reading
|
README.seg the file you are now reading
|
||||||
|
|
||||||
buffer.c global variables and buffer access utilities
|
|
||||||
shared between the parser (segparse.y) and the
|
|
||||||
scanner (segscan.l)
|
|
||||||
|
|
||||||
buffer.h function prototypes for buffer.c
|
|
||||||
|
|
||||||
seg.c the implementation of this data type in c
|
seg.c the implementation of this data type in c
|
||||||
|
|
||||||
seg.sql.in SQL code needed to register this type with postgres
|
seg.sql.in SQL code needed to register this type with postgres
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
/* This module defines the parse buffer and routines for setting/reading it */
|
|
||||||
|
|
||||||
#include "postgres.h"
|
|
||||||
|
|
||||||
static char *PARSE_BUFFER;
|
|
||||||
static char *PARSE_BUFFER_PTR;
|
|
||||||
static unsigned int PARSE_BUFFER_SIZE;
|
|
||||||
static unsigned int SCANNER_POS;
|
|
||||||
|
|
||||||
void set_parse_buffer(char *s);
|
|
||||||
void reset_parse_buffer(void);
|
|
||||||
int read_parse_buffer(void);
|
|
||||||
char *parse_buffer(void);
|
|
||||||
char *parse_buffer_ptr(void);
|
|
||||||
unsigned int parse_buffer_curr_char(void);
|
|
||||||
unsigned int parse_buffer_size(void);
|
|
||||||
unsigned int parse_buffer_pos(void);
|
|
||||||
|
|
||||||
extern void seg_flush_scanner_buffer(void); /* defined in segscan.l */
|
|
||||||
|
|
||||||
void
|
|
||||||
set_parse_buffer(char *s)
|
|
||||||
{
|
|
||||||
PARSE_BUFFER = s;
|
|
||||||
PARSE_BUFFER_SIZE = strlen(s);
|
|
||||||
if (PARSE_BUFFER_SIZE == 0)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
|
|
||||||
errmsg("can't parse an empty string")));
|
|
||||||
PARSE_BUFFER_PTR = PARSE_BUFFER;
|
|
||||||
SCANNER_POS = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
reset_parse_buffer(void)
|
|
||||||
{
|
|
||||||
PARSE_BUFFER_PTR = PARSE_BUFFER;
|
|
||||||
SCANNER_POS = 0;
|
|
||||||
seg_flush_scanner_buffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
read_parse_buffer(void)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* c = *PARSE_BUFFER_PTR++; SCANNER_POS++;
|
|
||||||
*/
|
|
||||||
c = PARSE_BUFFER[SCANNER_POS];
|
|
||||||
if (SCANNER_POS < PARSE_BUFFER_SIZE)
|
|
||||||
SCANNER_POS++;
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
parse_buffer(void)
|
|
||||||
{
|
|
||||||
return PARSE_BUFFER;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
parse_buffer_curr_char(void)
|
|
||||||
{
|
|
||||||
return PARSE_BUFFER[SCANNER_POS];
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
parse_buffer_ptr(void)
|
|
||||||
{
|
|
||||||
return PARSE_BUFFER_PTR;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
parse_buffer_pos(void)
|
|
||||||
{
|
|
||||||
return SCANNER_POS;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int
|
|
||||||
parse_buffer_size(void)
|
|
||||||
{
|
|
||||||
return PARSE_BUFFER_SIZE;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
extern void set_parse_buffer(char *s);
|
|
||||||
extern void reset_parse_buffer(void);
|
|
||||||
extern int read_parse_buffer(void);
|
|
||||||
extern char *parse_buffer(void);
|
|
||||||
extern char *parse_buffer_ptr(void);
|
|
||||||
extern unsigned int parse_buffer_curr_char(void);
|
|
||||||
extern unsigned int parse_buffer_pos(void);
|
|
||||||
extern unsigned int parse_buffer_size(void);
|
|
@ -394,35 +394,29 @@ SELECT '100(+-)1'::seg AS seg;
|
|||||||
|
|
||||||
-- invalid input
|
-- invalid input
|
||||||
SELECT ''::seg AS seg;
|
SELECT ''::seg AS seg;
|
||||||
ERROR: can't parse an empty string
|
ERROR: bad seg representation
|
||||||
|
DETAIL: syntax error at end of input
|
||||||
SELECT 'ABC'::seg AS seg;
|
SELECT 'ABC'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 1, character ('A', \101), input: 'ABC'
|
DETAIL: syntax error at or near "A"
|
||||||
|
|
||||||
SELECT '1ABC'::seg AS seg;
|
SELECT '1ABC'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 2, character ('A', \101), input: '1ABC'
|
DETAIL: syntax error at or near "A"
|
||||||
|
|
||||||
SELECT '1.'::seg AS seg;
|
SELECT '1.'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 2, character ('.', \056), input: '1.'
|
DETAIL: syntax error at or near "."
|
||||||
|
|
||||||
SELECT '1.....'::seg AS seg;
|
SELECT '1.....'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 6, character ('.', \056), input: '1.....'
|
DETAIL: syntax error at or near ".."
|
||||||
|
|
||||||
SELECT '.1'::seg AS seg;
|
SELECT '.1'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 2, character ('1', \061), input: '.1'
|
DETAIL: syntax error at or near "."
|
||||||
|
|
||||||
SELECT '1..2.'::seg AS seg;
|
SELECT '1..2.'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 5, character ('.', \056), input: '1..2.'
|
DETAIL: syntax error at or near "."
|
||||||
|
|
||||||
SELECT '1 e7'::seg AS seg;
|
SELECT '1 e7'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: bad seg representation
|
||||||
DETAIL: syntax error at or near position 3, character ('e', \145), input: '1 e7'
|
DETAIL: syntax error at or near "e"
|
||||||
|
|
||||||
SELECT '1e700'::seg AS seg;
|
SELECT '1e700'::seg AS seg;
|
||||||
ERROR: syntax error
|
ERROR: syntax error
|
||||||
DETAIL: numeric value 1e700 unrepresentable
|
DETAIL: numeric value 1e700 unrepresentable
|
||||||
|
@ -23,8 +23,10 @@
|
|||||||
#define GIST_QUERY_DEBUG
|
#define GIST_QUERY_DEBUG
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern void set_parse_buffer(char *str);
|
|
||||||
extern int seg_yyparse();
|
extern int seg_yyparse();
|
||||||
|
extern void seg_yyerror(const char *message);
|
||||||
|
extern void seg_scanner_init(const char *str);
|
||||||
|
extern void seg_scanner_finish(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
extern int seg_yydebug;
|
extern int seg_yydebug;
|
||||||
@ -99,16 +101,13 @@ seg_in(char *str)
|
|||||||
{
|
{
|
||||||
SEG *result = palloc(sizeof(SEG));
|
SEG *result = palloc(sizeof(SEG));
|
||||||
|
|
||||||
set_parse_buffer(str);
|
seg_scanner_init(str);
|
||||||
|
|
||||||
/*
|
|
||||||
* seg_yydebug = 1;
|
|
||||||
*/
|
|
||||||
if (seg_yyparse(result) != 0)
|
if (seg_yyparse(result) != 0)
|
||||||
{
|
seg_yyerror("bogus input");
|
||||||
pfree(result);
|
|
||||||
return NULL;
|
seg_scanner_finish();
|
||||||
}
|
|
||||||
return (result);
|
return (result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -880,7 +879,6 @@ seg_gt(SEG * a, SEG * b)
|
|||||||
return seg_cmp(a, b) > 0;
|
return seg_cmp(a, b) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
seg_ge(SEG * a, SEG * b)
|
seg_ge(SEG * a, SEG * b)
|
||||||
{
|
{
|
||||||
|
@ -7,12 +7,12 @@ SET search_path = public;
|
|||||||
CREATE FUNCTION seg_in(cstring)
|
CREATE FUNCTION seg_in(cstring)
|
||||||
RETURNS seg
|
RETURNS seg
|
||||||
AS 'MODULE_PATHNAME'
|
AS 'MODULE_PATHNAME'
|
||||||
LANGUAGE 'C';
|
LANGUAGE 'C' IMMUTABLE STRICT;
|
||||||
|
|
||||||
CREATE FUNCTION seg_out(seg)
|
CREATE FUNCTION seg_out(seg)
|
||||||
RETURNS cstring
|
RETURNS cstring
|
||||||
AS 'MODULE_PATHNAME'
|
AS 'MODULE_PATHNAME'
|
||||||
LANGUAGE 'C';
|
LANGUAGE 'C' IMMUTABLE STRICT;
|
||||||
|
|
||||||
CREATE TYPE seg (
|
CREATE TYPE seg (
|
||||||
INTERNALLENGTH = 12,
|
INTERNALLENGTH = 12,
|
||||||
@ -138,6 +138,13 @@ COMMENT ON FUNCTION seg_different(seg, seg) IS
|
|||||||
|
|
||||||
-- support routines for indexing
|
-- support routines for indexing
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION seg_cmp(seg, seg)
|
||||||
|
RETURNS int4
|
||||||
|
AS 'MODULE_PATHNAME'
|
||||||
|
LANGUAGE 'C' STRICT;
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION seg_cmp(seg, seg) IS 'btree comparison function';
|
||||||
|
|
||||||
CREATE FUNCTION seg_union(seg, seg)
|
CREATE FUNCTION seg_union(seg, seg)
|
||||||
RETURNS seg
|
RETURNS seg
|
||||||
AS 'MODULE_PATHNAME'
|
AS 'MODULE_PATHNAME'
|
||||||
@ -263,8 +270,7 @@ CREATE OPERATOR = (
|
|||||||
NEGATOR = '<>',
|
NEGATOR = '<>',
|
||||||
RESTRICT = eqsel,
|
RESTRICT = eqsel,
|
||||||
JOIN = eqjoinsel,
|
JOIN = eqjoinsel,
|
||||||
SORT1 = '<',
|
MERGES
|
||||||
SORT2 = '<'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OPERATOR <> (
|
CREATE OPERATOR <> (
|
||||||
@ -333,7 +339,16 @@ AS 'MODULE_PATHNAME'
|
|||||||
LANGUAGE 'C';
|
LANGUAGE 'C';
|
||||||
|
|
||||||
|
|
||||||
-- Create the operator class for indexing
|
-- Create the operator classes for indexing
|
||||||
|
|
||||||
|
CREATE OPERATOR CLASS seg_ops
|
||||||
|
DEFAULT FOR TYPE seg USING btree AS
|
||||||
|
OPERATOR 1 < ,
|
||||||
|
OPERATOR 2 <= ,
|
||||||
|
OPERATOR 3 = ,
|
||||||
|
OPERATOR 4 >= ,
|
||||||
|
OPERATOR 5 > ,
|
||||||
|
FUNCTION 1 seg_cmp(seg, seg);
|
||||||
|
|
||||||
CREATE OPERATOR CLASS gist_seg_ops
|
CREATE OPERATOR CLASS gist_seg_ops
|
||||||
DEFAULT FOR TYPE seg USING gist
|
DEFAULT FOR TYPE seg USING gist
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "segdata.h"
|
#include "segdata.h"
|
||||||
#include "buffer.h"
|
|
||||||
|
|
||||||
#ifdef __CYGWIN__
|
#ifdef __CYGWIN__
|
||||||
#define HUGE HUGE_VAL
|
#define HUGE HUGE_VAL
|
||||||
@ -19,7 +18,7 @@
|
|||||||
extern int yylex(); /* defined as seg_yylex in segscan.c */
|
extern int yylex(); /* defined as seg_yylex in segscan.c */
|
||||||
extern int significant_digits( char *str ); /* defined in seg.c */
|
extern int significant_digits( char *str ); /* defined in seg.c */
|
||||||
|
|
||||||
int seg_yyerror( char *msg );
|
void seg_yyerror(const char *message);
|
||||||
int seg_yyparse( void *result );
|
int seg_yyparse( void *result );
|
||||||
|
|
||||||
float seg_atof( char *value );
|
float seg_atof( char *value );
|
||||||
@ -72,7 +71,6 @@ range:
|
|||||||
((SEG *)result)->lower = $1.val;
|
((SEG *)result)->lower = $1.val;
|
||||||
((SEG *)result)->upper = $3.val;
|
((SEG *)result)->upper = $3.val;
|
||||||
if ( ((SEG *)result)->lower > ((SEG *)result)->upper ) {
|
if ( ((SEG *)result)->lower > ((SEG *)result)->upper ) {
|
||||||
reset_parse_buffer();
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
errmsg("swapped boundaries: %g is greater than %g",
|
errmsg("swapped boundaries: %g is greater than %g",
|
||||||
@ -145,7 +143,6 @@ float seg_atof ( char *value ) {
|
|||||||
|
|
||||||
if ( errno ) {
|
if ( errno ) {
|
||||||
snprintf(buf, 256, "numeric value %s unrepresentable", value);
|
snprintf(buf, 256, "numeric value %s unrepresentable", value);
|
||||||
reset_parse_buffer();
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("syntax error"),
|
errmsg("syntax error"),
|
||||||
@ -156,35 +153,4 @@ float seg_atof ( char *value ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int seg_yyerror ( char *msg ) {
|
|
||||||
char *buf = (char *) palloc(256);
|
|
||||||
int position;
|
|
||||||
|
|
||||||
yyclearin;
|
|
||||||
|
|
||||||
if ( !strcmp(msg, "parse error, expecting `$'") ) {
|
|
||||||
msg = "expecting end of input";
|
|
||||||
}
|
|
||||||
|
|
||||||
position = parse_buffer_pos() > parse_buffer_size() ? parse_buffer_pos() - 1 : parse_buffer_pos();
|
|
||||||
|
|
||||||
snprintf(
|
|
||||||
buf,
|
|
||||||
256,
|
|
||||||
"%s at or near position %d, character ('%c', \\%03o), input: '%s'\n",
|
|
||||||
msg,
|
|
||||||
position,
|
|
||||||
parse_buffer()[position - 1],
|
|
||||||
parse_buffer()[position - 1],
|
|
||||||
parse_buffer()
|
|
||||||
);
|
|
||||||
|
|
||||||
reset_parse_buffer();
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
||||||
errmsg("syntax error"),
|
|
||||||
errdetail("%s", buf)));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "segscan.c"
|
#include "segscan.c"
|
||||||
|
@ -5,32 +5,29 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "buffer.h"
|
/* No reason to constrain amount of data slurped */
|
||||||
|
#define YY_READ_BUF_SIZE 16777216
|
||||||
|
|
||||||
/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
|
/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */
|
||||||
#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
|
#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
|
||||||
|
|
||||||
|
/* Handles to the buffer that the lexer uses internally */
|
||||||
|
static YY_BUFFER_STATE scanbufhandle;
|
||||||
|
static char *scanbuf;
|
||||||
|
static int scanbuflen;
|
||||||
|
|
||||||
/* flex screws a couple symbols when used with the -P option; fix those */
|
/* flex 2.5.4 doesn't bother with a decl for this */
|
||||||
#define YY_DECL int seg_yylex YY_PROTO(( void )); \
|
int seg_yylex(void);
|
||||||
int seg_yylex YY_PROTO(( void ))
|
|
||||||
#define yylval seg_yylval
|
|
||||||
|
|
||||||
/* redefined YY_INPUT reads byte-wise from the memory area defined in buffer.c */
|
void seg_scanner_init(const char *str);
|
||||||
#undef YY_INPUT
|
void seg_scanner_finish(void);
|
||||||
#define YY_INPUT(buf,result,max_size) \
|
|
||||||
{ \
|
|
||||||
int c = read_parse_buffer(); \
|
|
||||||
result = (c == '\0') ? YY_NULL : (buf[0] = c, 1); \
|
|
||||||
}
|
|
||||||
|
|
||||||
void seg_flush_scanner_buffer(void);
|
|
||||||
%}
|
%}
|
||||||
|
|
||||||
%option 8bit
|
%option 8bit
|
||||||
%option never-interactive
|
%option never-interactive
|
||||||
%option nounput
|
%option nounput
|
||||||
%option noyywrap
|
%option noyywrap
|
||||||
|
%option prefix="seg_yy"
|
||||||
|
|
||||||
|
|
||||||
range (\.\.)(\.)?
|
range (\.\.)(\.)?
|
||||||
@ -52,8 +49,61 @@ float ({integer}|{real})([eE]{integer})?
|
|||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
int seg_yylex();
|
void
|
||||||
|
yyerror(const char *message)
|
||||||
void seg_flush_scanner_buffer(void) {
|
{
|
||||||
YY_FLUSH_BUFFER;
|
if (*yytext == YY_END_OF_BUFFER_CHAR)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("bad seg representation"),
|
||||||
|
/* translator: %s is typically "syntax error" */
|
||||||
|
errdetail("%s at end of input", message)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("bad seg representation"),
|
||||||
|
/* translator: first %s is typically "syntax error" */
|
||||||
|
errdetail("%s at or near \"%s\"", message, yytext)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called before any actual parsing is done
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
seg_scanner_init(const char *str)
|
||||||
|
{
|
||||||
|
Size slen = strlen(str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Might be left over after ereport()
|
||||||
|
*/
|
||||||
|
if (YY_CURRENT_BUFFER)
|
||||||
|
yy_delete_buffer(YY_CURRENT_BUFFER);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a scan buffer with special termination needed by flex.
|
||||||
|
*/
|
||||||
|
scanbuflen = slen;
|
||||||
|
scanbuf = palloc(slen + 2);
|
||||||
|
memcpy(scanbuf, str, slen);
|
||||||
|
scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
|
||||||
|
scanbufhandle = yy_scan_buffer(scanbuf, slen + 2);
|
||||||
|
|
||||||
|
BEGIN(INITIAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called after parsing is done to clean up after seg_scanner_init()
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
seg_scanner_finish(void)
|
||||||
|
{
|
||||||
|
yy_delete_buffer(scanbufhandle);
|
||||||
|
pfree(scanbuf);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user