Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Postgres FD Implementation
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Abuhujair Javed
Postgres FD Implementation
Commits
9527ce3e
Commit
9527ce3e
authored
May 07, 1998
by
Bruce Momjian
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add missing file from Tom Lane.
parent
1c2d9cb6
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
714 additions
and
0 deletions
+714
-0
src/interfaces/libpq/fe-print.c
src/interfaces/libpq/fe-print.c
+714
-0
No files found.
src/interfaces/libpq/fe-print.c
0 → 100644
View file @
9527ce3e
/*-------------------------------------------------------------------------
*
* fe-print.c--
* functions for pretty-printing query results
*
* Copyright (c) 1994, Regents of the University of California
*
* These functions were formerly part of fe-exec.c, but they
* didn't really belong there.
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-print.c,v 1.1 1998/05/07 14:52:52 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "libpq-fe.h"
#ifndef HAVE_TERMIOS_H
#include <sys/termios.h>
#else
#include <termios.h>
#endif
#ifdef TIOCGWINSZ
static
struct
winsize
screen_size
;
#else
static
struct
winsize
{
int
ws_row
;
int
ws_col
;
}
screen_size
;
#endif
static
void
do_field
(
PQprintOpt
*
po
,
PGresult
*
res
,
const
int
i
,
const
int
j
,
char
*
buf
,
const
int
fs_len
,
char
*
fields
[],
const
int
nFields
,
char
*
fieldNames
[],
unsigned
char
fieldNotNum
[],
int
fieldMax
[],
const
int
fieldMaxLen
,
FILE
*
fout
);
static
char
*
do_header
(
FILE
*
fout
,
PQprintOpt
*
po
,
const
int
nFields
,
int
fieldMax
[],
char
*
fieldNames
[],
unsigned
char
fieldNotNum
[],
const
int
fs_len
,
PGresult
*
res
);
static
void
output_row
(
FILE
*
fout
,
PQprintOpt
*
po
,
const
int
nFields
,
char
*
fields
[],
unsigned
char
fieldNotNum
[],
int
fieldMax
[],
char
*
border
,
const
int
row_index
);
static
void
fill
(
int
length
,
int
max
,
char
filler
,
FILE
*
fp
);
/*
* PQprint()
*
* Format results of a query for printing.
*
* PQprintOpt is a typedef (structure) that containes
* various flags and options. consult libpq-fe.h for
* details
*
* Obsoletes PQprintTuples.
*/
void
PQprint
(
FILE
*
fout
,
PGresult
*
res
,
PQprintOpt
*
po
)
{
int
nFields
;
nFields
=
PQnfields
(
res
);
if
(
nFields
>
0
)
{
/* only print rows with at least 1 field. */
int
i
,
j
;
int
nTups
;
int
*
fieldMax
=
NULL
;
/* in case we don't use them */
unsigned
char
*
fieldNotNum
=
NULL
;
char
*
border
=
NULL
;
char
**
fields
=
NULL
;
char
**
fieldNames
;
int
fieldMaxLen
=
0
;
int
numFieldName
;
int
fs_len
=
strlen
(
po
->
fieldSep
);
int
total_line_length
=
0
;
int
usePipe
=
0
;
char
*
pagerenv
;
char
buf
[
8192
*
2
+
1
];
nTups
=
PQntuples
(
res
);
if
(
!
(
fieldNames
=
(
char
**
)
calloc
(
nFields
,
sizeof
(
char
*
))))
{
perror
(
"calloc"
);
exit
(
1
);
}
if
(
!
(
fieldNotNum
=
(
unsigned
char
*
)
calloc
(
nFields
,
1
)))
{
perror
(
"calloc"
);
exit
(
1
);
}
if
(
!
(
fieldMax
=
(
int
*
)
calloc
(
nFields
,
sizeof
(
int
))))
{
perror
(
"calloc"
);
exit
(
1
);
}
for
(
numFieldName
=
0
;
po
->
fieldName
&&
po
->
fieldName
[
numFieldName
];
numFieldName
++
)
;
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
int
len
;
char
*
s
=
(
j
<
numFieldName
&&
po
->
fieldName
[
j
][
0
])
?
po
->
fieldName
[
j
]
:
PQfname
(
res
,
j
);
fieldNames
[
j
]
=
s
;
len
=
s
?
strlen
(
s
)
:
0
;
fieldMax
[
j
]
=
len
;
len
+=
fs_len
;
if
(
len
>
fieldMaxLen
)
fieldMaxLen
=
len
;
total_line_length
+=
len
;
}
total_line_length
+=
nFields
*
strlen
(
po
->
fieldSep
)
+
1
;
if
(
fout
==
NULL
)
fout
=
stdout
;
if
(
po
->
pager
&&
fout
==
stdout
&&
isatty
(
fileno
(
stdin
))
&&
isatty
(
fileno
(
stdout
)))
{
/* try to pipe to the pager program if possible */
#ifdef TIOCGWINSZ
if
(
ioctl
(
fileno
(
stdout
),
TIOCGWINSZ
,
&
screen_size
)
==
-
1
||
screen_size
.
ws_col
==
0
||
screen_size
.
ws_row
==
0
)
{
#endif
screen_size
.
ws_row
=
24
;
screen_size
.
ws_col
=
80
;
#ifdef TIOCGWINSZ
}
#endif
pagerenv
=
getenv
(
"PAGER"
);
if
(
pagerenv
!=
NULL
&&
pagerenv
[
0
]
!=
'\0'
&&
!
po
->
html3
&&
((
po
->
expanded
&&
nTups
*
(
nFields
+
1
)
>=
screen_size
.
ws_row
)
||
(
!
po
->
expanded
&&
nTups
*
(
total_line_length
/
screen_size
.
ws_col
+
1
)
*
(
1
+
(
po
->
standard
!=
0
))
>=
screen_size
.
ws_row
-
(
po
->
header
!=
0
)
*
(
total_line_length
/
screen_size
.
ws_col
+
1
)
*
2
-
(
po
->
header
!=
0
)
*
2
/* row count and newline */
)))
{
fout
=
popen
(
pagerenv
,
"w"
);
if
(
fout
)
{
usePipe
=
1
;
pqsignal
(
SIGPIPE
,
SIG_IGN
);
}
else
fout
=
stdout
;
}
}
if
(
!
po
->
expanded
&&
(
po
->
align
||
po
->
html3
))
{
if
(
!
(
fields
=
(
char
**
)
calloc
(
nFields
*
(
nTups
+
1
),
sizeof
(
char
*
))))
{
perror
(
"calloc"
);
exit
(
1
);
}
}
else
if
(
po
->
header
&&
!
po
->
html3
)
{
if
(
po
->
expanded
)
{
if
(
po
->
align
)
fprintf
(
fout
,
"%-*s%s Value
\n
"
,
fieldMaxLen
-
fs_len
,
"Field"
,
po
->
fieldSep
);
else
fprintf
(
fout
,
"%s%sValue
\n
"
,
"Field"
,
po
->
fieldSep
);
}
else
{
int
len
=
0
;
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
char
*
s
=
fieldNames
[
j
];
fputs
(
s
,
fout
);
len
+=
strlen
(
s
)
+
fs_len
;
if
((
j
+
1
)
<
nFields
)
fputs
(
po
->
fieldSep
,
fout
);
}
fputc
(
'\n'
,
fout
);
for
(
len
-=
fs_len
;
len
--
;
fputc
(
'-'
,
fout
));
fputc
(
'\n'
,
fout
);
}
}
if
(
po
->
expanded
&&
po
->
html3
)
{
if
(
po
->
caption
)
fprintf
(
fout
,
"<centre><h2>%s</h2></centre>
\n
"
,
po
->
caption
);
else
fprintf
(
fout
,
"<centre><h2>"
"Query retrieved %d rows * %d fields"
"</h2></centre>
\n
"
,
nTups
,
nFields
);
}
for
(
i
=
0
;
i
<
nTups
;
i
++
)
{
if
(
po
->
expanded
)
{
if
(
po
->
html3
)
fprintf
(
fout
,
"<table %s><caption align=high>%d</caption>
\n
"
,
po
->
tableOpt
?
po
->
tableOpt
:
""
,
i
);
else
fprintf
(
fout
,
"-- RECORD %d --
\n
"
,
i
);
}
for
(
j
=
0
;
j
<
nFields
;
j
++
)
do_field
(
po
,
res
,
i
,
j
,
buf
,
fs_len
,
fields
,
nFields
,
fieldNames
,
fieldNotNum
,
fieldMax
,
fieldMaxLen
,
fout
);
if
(
po
->
html3
&&
po
->
expanded
)
fputs
(
"</table>
\n
"
,
fout
);
}
if
(
!
po
->
expanded
&&
(
po
->
align
||
po
->
html3
))
{
if
(
po
->
html3
)
{
if
(
po
->
header
)
{
if
(
po
->
caption
)
fprintf
(
fout
,
"<table %s><caption align=high>%s</caption>
\n
"
,
po
->
tableOpt
?
po
->
tableOpt
:
""
,
po
->
caption
);
else
fprintf
(
fout
,
"<table %s><caption align=high>"
"Retrieved %d rows * %d fields"
"</caption>
\n
"
,
po
->
tableOpt
?
po
->
tableOpt
:
""
,
nTups
,
nFields
);
}
else
fprintf
(
fout
,
"<table %s>"
,
po
->
tableOpt
?
po
->
tableOpt
:
""
);
}
if
(
po
->
header
)
border
=
do_header
(
fout
,
po
,
nFields
,
fieldMax
,
fieldNames
,
fieldNotNum
,
fs_len
,
res
);
for
(
i
=
0
;
i
<
nTups
;
i
++
)
output_row
(
fout
,
po
,
nFields
,
fields
,
fieldNotNum
,
fieldMax
,
border
,
i
);
free
(
fields
);
if
(
border
)
free
(
border
);
}
if
(
po
->
header
&&
!
po
->
html3
)
fprintf
(
fout
,
"(%d row%s)
\n\n
"
,
PQntuples
(
res
),
(
PQntuples
(
res
)
==
1
)
?
""
:
"s"
);
free
(
fieldMax
);
free
(
fieldNotNum
);
free
(
fieldNames
);
if
(
usePipe
)
{
pclose
(
fout
);
pqsignal
(
SIGPIPE
,
SIG_DFL
);
}
if
(
po
->
html3
&&
!
po
->
expanded
)
fputs
(
"</table>
\n
"
,
fout
);
}
}
/*
* PQdisplayTuples()
* kept for backward compatibility
*/
void
PQdisplayTuples
(
PGresult
*
res
,
FILE
*
fp
,
/* where to send the output */
int
fillAlign
,
/* pad the fields with spaces */
const
char
*
fieldSep
,
/* field separator */
int
printHeader
,
/* display headers? */
int
quiet
)
{
#define DEFAULT_FIELD_SEP " "
int
i
,
j
;
int
nFields
;
int
nTuples
;
int
fLength
[
MAX_FIELDS
];
if
(
fieldSep
==
NULL
)
fieldSep
=
DEFAULT_FIELD_SEP
;
/* Get some useful info about the results */
nFields
=
PQnfields
(
res
);
nTuples
=
PQntuples
(
res
);
if
(
fp
==
NULL
)
fp
=
stdout
;
/* Zero the initial field lengths */
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
fLength
[
j
]
=
strlen
(
PQfname
(
res
,
j
));
}
/* Find the max length of each field in the result */
/* will be somewhat time consuming for very large results */
if
(
fillAlign
)
{
for
(
i
=
0
;
i
<
nTuples
;
i
++
)
{
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
if
(
PQgetlength
(
res
,
i
,
j
)
>
fLength
[
j
])
fLength
[
j
]
=
PQgetlength
(
res
,
i
,
j
);
}
}
}
if
(
printHeader
)
{
/* first, print out the attribute names */
for
(
i
=
0
;
i
<
nFields
;
i
++
)
{
fputs
(
PQfname
(
res
,
i
),
fp
);
if
(
fillAlign
)
fill
(
strlen
(
PQfname
(
res
,
i
)),
fLength
[
i
],
' '
,
fp
);
fputs
(
fieldSep
,
fp
);
}
fprintf
(
fp
,
"
\n
"
);
/* Underline the attribute names */
for
(
i
=
0
;
i
<
nFields
;
i
++
)
{
if
(
fillAlign
)
fill
(
0
,
fLength
[
i
],
'-'
,
fp
);
fputs
(
fieldSep
,
fp
);
}
fprintf
(
fp
,
"
\n
"
);
}
/* next, print out the instances */
for
(
i
=
0
;
i
<
nTuples
;
i
++
)
{
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
fprintf
(
fp
,
"%s"
,
PQgetvalue
(
res
,
i
,
j
));
if
(
fillAlign
)
fill
(
strlen
(
PQgetvalue
(
res
,
i
,
j
)),
fLength
[
j
],
' '
,
fp
);
fputs
(
fieldSep
,
fp
);
}
fprintf
(
fp
,
"
\n
"
);
}
if
(
!
quiet
)
fprintf
(
fp
,
"
\n
Query returned %d row%s.
\n
"
,
PQntuples
(
res
),
(
PQntuples
(
res
)
==
1
)
?
""
:
"s"
);
fflush
(
fp
);
}
/*
* PQprintTuples()
*
* kept for backward compatibility
*
*/
void
PQprintTuples
(
PGresult
*
res
,
FILE
*
fout
,
/* output stream */
int
PrintAttNames
,
/* print attribute names or not */
int
TerseOutput
,
/* delimiter bars or not? */
int
colWidth
/* width of column, if 0, use variable
* width */
)
{
int
nFields
;
int
nTups
;
int
i
,
j
;
char
formatString
[
80
];
char
*
tborder
=
NULL
;
nFields
=
PQnfields
(
res
);
nTups
=
PQntuples
(
res
);
if
(
colWidth
>
0
)
{
sprintf
(
formatString
,
"%%s %%-%ds"
,
colWidth
);
}
else
sprintf
(
formatString
,
"%%s %%s"
);
if
(
nFields
>
0
)
{
/* only print rows with at least 1 field. */
if
(
!
TerseOutput
)
{
int
width
;
width
=
nFields
*
14
;
tborder
=
malloc
(
width
+
1
);
for
(
i
=
0
;
i
<=
width
;
i
++
)
tborder
[
i
]
=
'-'
;
tborder
[
i
]
=
'\0'
;
fprintf
(
fout
,
"%s
\n
"
,
tborder
);
}
for
(
i
=
0
;
i
<
nFields
;
i
++
)
{
if
(
PrintAttNames
)
{
fprintf
(
fout
,
formatString
,
TerseOutput
?
""
:
"|"
,
PQfname
(
res
,
i
));
}
}
if
(
PrintAttNames
)
{
if
(
TerseOutput
)
fprintf
(
fout
,
"
\n
"
);
else
fprintf
(
fout
,
"|
\n
%s
\n
"
,
tborder
);
}
for
(
i
=
0
;
i
<
nTups
;
i
++
)
{
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
char
*
pval
=
PQgetvalue
(
res
,
i
,
j
);
fprintf
(
fout
,
formatString
,
TerseOutput
?
""
:
"|"
,
pval
?
pval
:
""
);
}
if
(
TerseOutput
)
fprintf
(
fout
,
"
\n
"
);
else
fprintf
(
fout
,
"|
\n
%s
\n
"
,
tborder
);
}
}
}
static
void
do_field
(
PQprintOpt
*
po
,
PGresult
*
res
,
const
int
i
,
const
int
j
,
char
*
buf
,
const
int
fs_len
,
char
*
fields
[],
const
int
nFields
,
char
*
fieldNames
[],
unsigned
char
fieldNotNum
[],
int
fieldMax
[],
const
int
fieldMaxLen
,
FILE
*
fout
)
{
char
*
pval
,
*
p
,
*
o
;
int
plen
;
bool
skipit
;
plen
=
PQgetlength
(
res
,
i
,
j
);
pval
=
PQgetvalue
(
res
,
i
,
j
);
if
(
plen
<
1
||
!
pval
||
!*
pval
)
{
if
(
po
->
align
||
po
->
expanded
)
skipit
=
true
;
else
{
skipit
=
false
;
goto
efield
;
}
}
else
skipit
=
false
;
if
(
!
skipit
)
{
for
(
p
=
pval
,
o
=
buf
;
*
p
;
*
(
o
++
)
=
*
(
p
++
))
{
if
((
fs_len
==
1
&&
(
*
p
==
*
(
po
->
fieldSep
)))
||
*
p
==
'\\'
||
*
p
==
'\n'
)
*
(
o
++
)
=
'\\'
;
if
(
po
->
align
&&
(
*
pval
==
'E'
||
*
pval
==
'e'
||
!
((
*
p
>=
'0'
&&
*
p
<=
'9'
)
||
*
p
==
'.'
||
*
p
==
'E'
||
*
p
==
'e'
||
*
p
==
' '
||
*
p
==
'-'
)))
fieldNotNum
[
j
]
=
1
;
}
*
o
=
'\0'
;
if
(
!
po
->
expanded
&&
(
po
->
align
||
po
->
html3
))
{
int
n
=
strlen
(
buf
);
if
(
n
>
fieldMax
[
j
])
fieldMax
[
j
]
=
n
;
if
(
!
(
fields
[
i
*
nFields
+
j
]
=
(
char
*
)
malloc
(
n
+
1
)))
{
perror
(
"malloc"
);
exit
(
1
);
}
strcpy
(
fields
[
i
*
nFields
+
j
],
buf
);
}
else
{
if
(
po
->
expanded
)
{
if
(
po
->
html3
)
fprintf
(
fout
,
"<tr><td align=left><b>%s</b></td>"
"<td align=%s>%s</td></tr>
\n
"
,
fieldNames
[
j
],
fieldNotNum
[
j
]
?
"left"
:
"right"
,
buf
);
else
{
if
(
po
->
align
)
fprintf
(
fout
,
"%-*s%s %s
\n
"
,
fieldMaxLen
-
fs_len
,
fieldNames
[
j
],
po
->
fieldSep
,
buf
);
else
fprintf
(
fout
,
"%s%s%s
\n
"
,
fieldNames
[
j
],
po
->
fieldSep
,
buf
);
}
}
else
{
if
(
!
po
->
html3
)
{
fputs
(
buf
,
fout
);
efield:
if
((
j
+
1
)
<
nFields
)
fputs
(
po
->
fieldSep
,
fout
);
else
fputc
(
'\n'
,
fout
);
}
}
}
}
}
static
char
*
do_header
(
FILE
*
fout
,
PQprintOpt
*
po
,
const
int
nFields
,
int
fieldMax
[],
char
*
fieldNames
[],
unsigned
char
fieldNotNum
[],
const
int
fs_len
,
PGresult
*
res
)
{
int
j
;
/* for loop index */
char
*
border
=
NULL
;
if
(
po
->
html3
)
fputs
(
"<tr>"
,
fout
);
else
{
int
j
;
/* for loop index */
int
tot
=
0
;
int
n
=
0
;
char
*
p
=
NULL
;
for
(;
n
<
nFields
;
n
++
)
tot
+=
fieldMax
[
n
]
+
fs_len
+
(
po
->
standard
?
2
:
0
);
if
(
po
->
standard
)
tot
+=
fs_len
*
2
+
2
;
border
=
malloc
(
tot
+
1
);
if
(
!
border
)
{
perror
(
"malloc"
);
exit
(
1
);
}
p
=
border
;
if
(
po
->
standard
)
{
char
*
fs
=
po
->
fieldSep
;
while
(
*
fs
++
)
*
p
++
=
'+'
;
}
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
int
len
;
for
(
len
=
fieldMax
[
j
]
+
(
po
->
standard
?
2
:
0
);
len
--
;
*
p
++
=
'-'
);
if
(
po
->
standard
||
(
j
+
1
)
<
nFields
)
{
char
*
fs
=
po
->
fieldSep
;
while
(
*
fs
++
)
*
p
++
=
'+'
;
}
}
*
p
=
'\0'
;
if
(
po
->
standard
)
fprintf
(
fout
,
"%s
\n
"
,
border
);
}
if
(
po
->
standard
)
fputs
(
po
->
fieldSep
,
fout
);
for
(
j
=
0
;
j
<
nFields
;
j
++
)
{
char
*
s
=
PQfname
(
res
,
j
);
if
(
po
->
html3
)
{
fprintf
(
fout
,
"<th align=%s>%s</th>"
,
fieldNotNum
[
j
]
?
"left"
:
"right"
,
fieldNames
[
j
]);
}
else
{
int
n
=
strlen
(
s
);
if
(
n
>
fieldMax
[
j
])
fieldMax
[
j
]
=
n
;
if
(
po
->
standard
)
fprintf
(
fout
,
fieldNotNum
[
j
]
?
" %-*s "
:
" %*s "
,
fieldMax
[
j
],
s
);
else
fprintf
(
fout
,
fieldNotNum
[
j
]
?
"%-*s"
:
"%*s"
,
fieldMax
[
j
],
s
);
if
(
po
->
standard
||
(
j
+
1
)
<
nFields
)
fputs
(
po
->
fieldSep
,
fout
);
}
}
if
(
po
->
html3
)
fputs
(
"</tr>
\n
"
,
fout
);
else
fprintf
(
fout
,
"
\n
%s
\n
"
,
border
);
return
border
;
}
static
void
output_row
(
FILE
*
fout
,
PQprintOpt
*
po
,
const
int
nFields
,
char
*
fields
[],
unsigned
char
fieldNotNum
[],
int
fieldMax
[],
char
*
border
,
const
int
row_index
)
{
int
field_index
;
/* for loop index */
if
(
po
->
html3
)
fputs
(
"<tr>"
,
fout
);
else
if
(
po
->
standard
)
fputs
(
po
->
fieldSep
,
fout
);
for
(
field_index
=
0
;
field_index
<
nFields
;
field_index
++
)
{
char
*
p
=
fields
[
row_index
*
nFields
+
field_index
];
if
(
po
->
html3
)
fprintf
(
fout
,
"<td align=%s>%s</td>"
,
fieldNotNum
[
field_index
]
?
"left"
:
"right"
,
p
?
p
:
""
);
else
{
fprintf
(
fout
,
fieldNotNum
[
field_index
]
?
(
po
->
standard
?
" %-*s "
:
"%-*s"
)
:
(
po
->
standard
?
" %*s "
:
"%*s"
),
fieldMax
[
field_index
],
p
?
p
:
""
);
if
(
po
->
standard
||
field_index
+
1
<
nFields
)
fputs
(
po
->
fieldSep
,
fout
);
}
if
(
p
)
free
(
p
);
}
if
(
po
->
html3
)
fputs
(
"</tr>"
,
fout
);
else
if
(
po
->
standard
)
fprintf
(
fout
,
"
\n
%s"
,
border
);
fputc
(
'\n'
,
fout
);
}
/* simply send out max-length number of filler characters to fp */
static
void
fill
(
int
length
,
int
max
,
char
filler
,
FILE
*
fp
)
{
int
count
;
count
=
max
-
length
;
while
(
count
--
>=
0
)
putc
(
filler
,
fp
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment