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
aa7f0046
Commit
aa7f0046
authored
Dec 07, 2008
by
Alvaro Herrera
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Desultorily enclose programlisting tags in CDATA, to get rid of some obnoxious
SGML-escaping.
parent
b2971e20
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
238 additions
and
209 deletions
+238
-209
doc/src/sgml/auto-explain.sgml
doc/src/sgml/auto-explain.sgml
+7
-6
doc/src/sgml/ecpg.sgml
doc/src/sgml/ecpg.sgml
+23
-18
doc/src/sgml/libpq.sgml
doc/src/sgml/libpq.sgml
+47
-39
doc/src/sgml/lobj.sgml
doc/src/sgml/lobj.sgml
+41
-40
doc/src/sgml/trigger.sgml
doc/src/sgml/trigger.sgml
+17
-16
doc/src/sgml/xfunc.sgml
doc/src/sgml/xfunc.sgml
+33
-27
doc/src/sgml/xindex.sgml
doc/src/sgml/xindex.sgml
+53
-49
doc/src/sgml/xtypes.sgml
doc/src/sgml/xtypes.sgml
+17
-14
No files found.
doc/src/sgml/auto-explain.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.
1 2008/11/19 02:59:28 tgl
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/auto-explain.sgml,v 1.
2 2008/12/07 23:46:39 alvherre
Exp $ -->
<sect1 id="auto-explain">
<title>auto_explain</title>
...
...
@@ -150,18 +150,19 @@ explain.log_min_duration = '3s'
This might produce log output such as:
</para>
<programlisting>
<programlisting>
<![CDATA[
LOG: duration: 0.986 ms plan:
Aggregate (cost=14.90..14.91 rows=1 width=0)
-
>
Hash Join (cost=3.91..14.70 rows=81 width=0)
-
>
Hash Join (cost=3.91..14.70 rows=81 width=0)
Hash Cond: (pg_class.oid = pg_index.indrelid)
-
>
Seq Scan on pg_class (cost=0.00..8.27 rows=227 width=4)
-
>
Hash (cost=2.90..2.90 rows=81 width=4)
-
>
Seq Scan on pg_index (cost=0.00..2.90 rows=81 width=4)
-
>
Seq Scan on pg_class (cost=0.00..8.27 rows=227 width=4)
-
>
Hash (cost=2.90..2.90 rows=81 width=4)
-
>
Seq Scan on pg_index (cost=0.00..2.90 rows=81 width=4)
Filter: indisunique
STATEMENT: SELECT count(*)
FROM pg_class, pg_index
WHERE oid = indrelid AND indisunique;
]]>
</programlisting>
</sect2>
...
...
doc/src/sgml/ecpg.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/ecpg.sgml,v 1.8
6 2008/06/12 19:15:40 momjian
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/ecpg.sgml,v 1.8
7 2008/12/07 23:46:39 alvherre
Exp $ -->
<chapter id="ecpg">
<title><application>ECPG</application> - Embedded <acronym>SQL</acronym> in C</title>
...
...
@@ -717,9 +717,9 @@ EXEC SQL EXECUTE mystmt USING 42, 'foobar';
</programlisting>
If the statement you are executing returns values, then add an
<literal>INTO</literal> clause:
<programlisting>
<programlisting>
<![CDATA[
EXEC SQL BEGIN DECLARE SECTION;
const char *stmt = "SELECT a, b, c FROM test1 WHERE a
>
?";
const char *stmt = "SELECT a, b, c FROM test1 WHERE a
>
?";
int v1, v2;
VARCHAR v3;
EXEC SQL END DECLARE SECTION;
...
...
@@ -727,6 +727,7 @@ EXEC SQL END DECLARE SECTION;
EXEC SQL PREPARE mystmt FROM :stmt;
...
EXEC SQL EXECUTE mystmt INTO v1, v2, v3 USING 37;
]]>
</programlisting>
An <command>EXECUTE</command> command can have an
<literal>INTO</literal> clause, a <literal>USING</literal> clause,
...
...
@@ -752,7 +753,7 @@ EXEC SQL DEALLOCATE PREPARE <replaceable>name</replaceable>;
functions to do basic calculations with those types within C, i.e. without
the help of the <productname>PostgreSQL</productname> server. See the
following example:
<programlisting>
<programlisting>
<![CDATA[
EXEC SQL BEGIN DECLARE SECTION;
date date1;
timestamp ts1, tsout;
...
...
@@ -760,12 +761,13 @@ EXEC SQL BEGIN DECLARE SECTION;
char *out;
EXEC SQL END DECLARE SECTION;
PGTYPESdate_today(&
amp;
date1);
PGTYPESdate_today(&date1);
EXEC SQL SELECT started, duration INTO :ts1, :iv1 FROM datetbl WHERE d=:date1;
PGTYPEStimestamp_add_interval(&
amp;ts1, &iv1, &
tsout);
out = PGTYPEStimestamp_to_asc(&
amp;
tsout);
PGTYPEStimestamp_add_interval(&
ts1, &iv1, &
tsout);
out = PGTYPEStimestamp_to_asc(&tsout);
printf("Started + duration: %s\n", out);
free(out);
]]>
</programlisting>
</para>
...
...
@@ -3449,14 +3451,15 @@ int rsetnull(int t, char *ptr);
<para>
Here is an example of a call to this function:
<programlisting>
<programlisting>
<![CDATA[
$char c[] = "abc ";
$short s = 17;
$int i = -74874;
rsetnull(CCHARTYPE, (char *) c);
rsetnull(CSHORTTYPE, (char *) &s);
rsetnull(CINTTYPE, (char *) &i);
rsetnull(CSHORTTYPE, (char *) &s);
rsetnull(CINTTYPE, (char *) &i);
]]>
</programlisting>
</para>
</listitem>
...
...
@@ -3477,14 +3480,15 @@ int risnull(int t, char *ptr);
</para>
<para>
Here is an example of how to use this function:
<programlisting>
<programlisting>
<![CDATA[
$char c[] = "abc ";
$short s = 17;
$int i = -74874;
risnull(CCHARTYPE, (char *) c);
risnull(CSHORTTYPE, (char *) &s);
risnull(CINTTYPE, (char *) &i);
risnull(CSHORTTYPE, (char *) &s);
risnull(CINTTYPE, (char *) &i);
]]>
</programlisting>
</para>
</listitem>
...
...
@@ -4960,11 +4964,11 @@ EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT res INTO :result FROM mytable WHERE index = :index;
</programlisting>
is translated into:
<programlisting>
<programlisting>
<![CDATA[
/* Processed by ecpg (2.6.0) */
/* These two include files are added by the preprocessor */
#include
<ecpgtype.h>
;
#include
<ecpglib.h>
;
#include
<ecpgtype.h>
;
#include
<ecpglib.h>
;
/* exec sql begin declare section */
...
...
@@ -4975,11 +4979,12 @@ EXEC SQL SELECT res INTO :result FROM mytable WHERE index = :index;
/* exec sql end declare section */
...
ECPGdo(__LINE__, NULL, "SELECT res FROM mytable WHERE index = ? ",
ECPGt_int,&
amp;
(index),1L,1L,sizeof(int),
ECPGt_int,&(index),1L,1L,sizeof(int),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT,
ECPGt_int,&
amp;
(result),1L,1L,sizeof(int),
ECPGt_int,&(result),1L,1L,sizeof(int),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
#line 147 "foo.pgc"
]]>
</programlisting>
(The indentation here is added for readability and not
something the preprocessor does.)
...
...
doc/src/sgml/libpq.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.27
2 2008/12/02 12:42:11 mha
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.27
3 2008/12/07 23:46:39 alvherre
Exp $ -->
<chapter id="libpq">
<title><application>libpq</application> - C Library</title>
...
...
@@ -5415,8 +5415,9 @@ int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
<programlisting>
<![CDATA[
/* required header for libpq events (note: includes libpq-fe.h) */
#include
<libpq-events.h>
#include
<libpq-events.h>
/* The instanceData */
typedef struct
...
...
@@ -5488,17 +5489,17 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
case PGEVT_REGISTER:
{
PGEventRegister *e = (PGEventRegister *)evtInfo;
mydata *data = get_mydata(e-
>
conn);
mydata *data = get_mydata(e-
>
conn);
/* associate app specific data with connection */
PQsetInstanceData(e-
>
conn, myEventProc, data);
PQsetInstanceData(e-
>
conn, myEventProc, data);
break;
}
case PGEVT_CONNRESET:
{
PGEventConnReset *e = (PGEventConnReset *)evtInfo;
mydata *data = PQinstanceData(e-
>
conn, myEventProc);
mydata *data = PQinstanceData(e-
>
conn, myEventProc);
if (data)
memset(data, 0, sizeof(mydata));
...
...
@@ -5508,7 +5509,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
case PGEVT_CONNDESTROY:
{
PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
mydata *data = PQinstanceData(e-
>
conn, myEventProc);
mydata *data = PQinstanceData(e-
>
conn, myEventProc);
/* free instance data because the conn is being destroyed */
if (data)
...
...
@@ -5519,29 +5520,29 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
case PGEVT_RESULTCREATE:
{
PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
mydata *conn_data = PQinstanceData(e-
>
conn, myEventProc);
mydata *conn_data = PQinstanceData(e-
>
conn, myEventProc);
mydata *res_data = dup_mydata(conn_data);
/* associate app specific data with result (copy it from conn) */
PQsetResultInstanceData(e-
>
result, myEventProc, res_data);
PQsetResultInstanceData(e-
>
result, myEventProc, res_data);
break;
}
case PGEVT_RESULTCOPY:
{
PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
mydata *src_data = PQresultInstanceData(e-
>
src, myEventProc);
mydata *src_data = PQresultInstanceData(e-
>
src, myEventProc);
mydata *dest_data = dup_mydata(src_data);
/* associate app specific data with result (copy it from a result) */
PQsetResultInstanceData(e-
>
dest, myEventProc, dest_data);
PQsetResultInstanceData(e-
>
dest, myEventProc, dest_data);
break;
}
case PGEVT_RESULTDESTROY:
{
PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
mydata *data = PQresultInstanceData(e-
>
result, myEventProc);
mydata *data = PQresultInstanceData(e-
>
result, myEventProc);
/* free instance data because the result is being destroyed */
if (data)
...
...
@@ -5556,6 +5557,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
return TRUE; /* event processing succeeded */
}
]]>
</programlisting>
</sect2>
</sect1>
...
...
@@ -6407,13 +6409,14 @@ testlibpq.o(.text+0xa4): undefined reference to `PQerrorMessage'
<title><application>libpq</application> Example Program 1</title>
<programlisting>
<![CDATA[
/*
* testlibpq.c
*
* Test the C version of libpq, the PostgreSQL frontend library.
*/
#include
<stdio.h>
#include
<stdlib.h>
#include
<stdio.h>
#include
<stdlib.h>
#include "libpq-fe.h"
static void
...
...
@@ -6438,7 +6441,7 @@ main(int argc, char **argv)
* conninfo string; otherwise default to setting dbname=postgres and using
* environment variables or defaults for all other connection parameters.
*/
if (argc
>
1)
if (argc
>
1)
conninfo = argv[1];
else
conninfo = "dbname = postgres";
...
...
@@ -6498,14 +6501,14 @@ main(int argc, char **argv)
/* first, print out the attribute names */
nFields = PQnfields(res);
for (i = 0; i
<
nFields; i++)
for (i = 0; i
<
nFields; i++)
printf("%-15s", PQfname(res, i));
printf("\n\n");
/* next, print out the rows */
for (i = 0; i
<
PQntuples(res); i++)
for (i = 0; i
<
PQntuples(res); i++)
{
for (j = 0; j
<
nFields; j++)
for (j = 0; j
<
nFields; j++)
printf("%-15s", PQgetvalue(res, i, j));
printf("\n");
}
...
...
@@ -6525,6 +6528,7 @@ main(int argc, char **argv)
return 0;
}
]]>
</programlisting>
</example>
...
...
@@ -6532,6 +6536,7 @@ main(int argc, char **argv)
<title><application>libpq</application> Example Program 2</title>
<programlisting>
<![CDATA[
/*
* testlibpq2.c
* Test of the asynchronous notification interface
...
...
@@ -6555,11 +6560,11 @@ main(int argc, char **argv)
*
* INSERT INTO TBL1 VALUES (10);
*/
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<sys/time.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<sys/time.h>
#include "libpq-fe.h"
static void
...
...
@@ -6583,7 +6588,7 @@ main(int argc, char **argv)
* conninfo string; otherwise default to setting dbname=postgres and using
* environment variables or defaults for all other connection parameters.
*/
if (argc
>
1)
if (argc
>
1)
conninfo = argv[1];
else
conninfo = "dbname = postgres";
...
...
@@ -6618,7 +6623,7 @@ main(int argc, char **argv)
/* Quit after four notifies are received. */
nnotifies = 0;
while (nnotifies
<
4)
while (nnotifies
<
4)
{
/*
* Sleep until something happens on the connection. We use select(2)
...
...
@@ -6630,13 +6635,13 @@ main(int argc, char **argv)
sock = PQsocket(conn);
if (sock
<
0)
if (sock
<
0)
break; /* shouldn't happen */
FD_ZERO(&
amp;
input_mask);
FD_SET(sock, &
amp;
input_mask);
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
if (select(sock + 1, &
amp;input_mask, NULL, NULL, NULL) <
0)
if (select(sock + 1, &
input_mask, NULL, NULL, NULL) <
0)
{
fprintf(stderr, "select() failed: %s\n", strerror(errno));
exit_nicely(conn);
...
...
@@ -6648,7 +6653,7 @@ main(int argc, char **argv)
{
fprintf(stderr,
"ASYNC NOTIFY of '%s' received from backend pid %d\n",
notify-
>relname, notify->
be_pid);
notify-
>relname, notify->
be_pid);
PQfreemem(notify);
nnotifies++;
}
...
...
@@ -6661,6 +6666,7 @@ main(int argc, char **argv)
return 0;
}
]]>
</programlisting>
</example>
...
...
@@ -6668,6 +6674,7 @@ main(int argc, char **argv)
<title><application>libpq</application> Example Program 3</>
<programlisting>
<![CDATA[
/*
* testlibpq3.c
* Test out-of-line parameters and binary I/O.
...
...
@@ -6692,15 +6699,15 @@ main(int argc, char **argv)
* t = (8 bytes) 'ho there'
* b = (5 bytes) \004\003\002\001\000
*/
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<sys/types.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<sys/types.h>
#include "libpq-fe.h"
/* for ntohl/htonl */
#include
<netinet/in.h>
#include
<arpa/inet.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
static void
...
...
@@ -6729,7 +6736,7 @@ show_binary_results(PGresult *res)
t_fnum = PQfnumber(res, "t");
b_fnum = PQfnumber(res, "b");
for (i = 0; i
<
PQntuples(res); i++)
for (i = 0; i
<
PQntuples(res); i++)
{
char *iptr;
char *tptr;
...
...
@@ -6764,7 +6771,7 @@ show_binary_results(PGresult *res)
printf(" t = (%d bytes) '%s'\n",
PQgetlength(res, i, t_fnum), tptr);
printf(" b = (%d bytes) ", blen);
for (j = 0; j
<
blen; j++)
for (j = 0; j
<
blen; j++)
printf("\\%03o", bptr[j]);
printf("\n\n");
}
...
...
@@ -6786,7 +6793,7 @@ main(int argc, char **argv)
* conninfo string; otherwise default to setting dbname=postgres and using
* environment variables or defaults for all other connection parameters.
*/
if (argc
>
1)
if (argc
>
1)
conninfo = argv[1];
else
conninfo = "dbname = postgres";
...
...
@@ -6850,7 +6857,7 @@ main(int argc, char **argv)
binaryIntVal = htonl((uint32_t) 2);
/* Set up parameter arrays for PQexecParams */
paramValues[0] = (char *) &
amp;
binaryIntVal;
paramValues[0] = (char *) &binaryIntVal;
paramLengths[0] = sizeof(binaryIntVal);
paramFormats[0] = 1; /* binary */
...
...
@@ -6879,6 +6886,7 @@ main(int argc, char **argv)
return 0;
}
]]>
</programlisting>
</example>
...
...
doc/src/sgml/lobj.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.4
8 2008/03/22 01:55:14 ishii
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/lobj.sgml,v 1.4
9 2008/12/07 23:46:39 alvherre
Exp $ -->
<chapter id="largeObjects">
<title id="largeObjects-title">Large Objects</title>
...
...
@@ -457,7 +457,7 @@ SELECT lo_export(image.raster, '/tmp/motd') FROM image
<example id="lo-example">
<title>Large Objects with <application>libpq</application> Example Program</title>
<programlisting>
<programlisting>
<![CDATA[
/*--------------------------------------------------------------
*
* testlo.c--
...
...
@@ -467,15 +467,15 @@ SELECT lo_export(image.raster, '/tmp/motd') FROM image
*
*--------------------------------------------------------------
*/
#include
<stdio.h>
#include
"libpq-fe.h"
#include
"libpq/libpq-fs.h"
#include
<stdio.h>
#include
"libpq-fe.h"
#include
"libpq/libpq-fs.h"
#define BUFSIZE 1024
/*
* importFile
* import file
"in_filename" into database as large object "lobjOid"
* import file
"in_filename" into database as large object "lobjOid"
*
*/
Oid
...
...
@@ -492,9 +492,9 @@ importFile(PGconn *conn, char *filename)
* open the file to be read in
*/
fd = open(filename, O_RDONLY, 0666);
if (fd
<
0)
if (fd
<
0)
{ /* error */
fprintf(stderr,
"cannot open unix file %s\n"
, filename);
fprintf(stderr,
"cannot open unix file %s\n"
, filename);
}
/*
...
...
@@ -502,18 +502,18 @@ importFile(PGconn *conn, char *filename)
*/
lobjId = lo_creat(conn, INV_READ | INV_WRITE);
if (lobjId == 0)
fprintf(stderr,
"cannot create large object\n"
);
fprintf(stderr,
"cannot create large object\n"
);
lobj_fd = lo_open(conn, lobjId, INV_WRITE);
/*
* read in from the Unix file and write to the inversion file
*/
while ((nbytes = read(fd, buf, BUFSIZE))
>
0)
while ((nbytes = read(fd, buf, BUFSIZE))
>
0)
{
tmp = lo_write(conn, lobj_fd, buf, nbytes);
if (tmp
<
nbytes)
fprintf(stderr,
"error while reading large object\n"
);
if (tmp
<
nbytes)
fprintf(stderr,
"error while reading large object\n"
);
}
(void) close(fd);
...
...
@@ -531,9 +531,9 @@ pickout(PGconn *conn, Oid lobjId, int start, int len)
int nread;
lobj_fd = lo_open(conn, lobjId, INV_READ);
if (lobj_fd
<
0)
if (lobj_fd
<
0)
{
fprintf(stderr,
"cannot open large object %d\n"
,
fprintf(stderr,
"cannot open large object %d\n"
,
lobjId);
}
...
...
@@ -541,15 +541,15 @@ pickout(PGconn *conn, Oid lobjId, int start, int len)
buf = malloc(len + 1);
nread = 0;
while (len - nread
>
0)
while (len - nread
>
0)
{
nbytes = lo_read(conn, lobj_fd, buf, len - nread);
buf[nbytes] = ' ';
fprintf(stderr,
">>> %s"
, buf);
fprintf(stderr,
">>> %s"
, buf);
nread += nbytes;
}
free(buf);
fprintf(stderr,
"\n"
);
fprintf(stderr,
"\n"
);
lo_close(conn, lobj_fd);
}
...
...
@@ -563,33 +563,33 @@ overwrite(PGconn *conn, Oid lobjId, int start, int len)
int i;
lobj_fd = lo_open(conn, lobjId, INV_WRITE);
if (lobj_fd
<
0)
if (lobj_fd
<
0)
{
fprintf(stderr,
"cannot open large object %d\n"
,
fprintf(stderr,
"cannot open large object %d\n"
,
lobjId);
}
lo_lseek(conn, lobj_fd, start, SEEK_SET);
buf = malloc(len + 1);
for (i = 0; i
<
len; i++)
for (i = 0; i
<
len; i++)
buf[i] = 'X';
buf[i] = ' ';
nwritten = 0;
while (len - nwritten
>
0)
while (len - nwritten
>
0)
{
nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten);
nwritten += nbytes;
}
free(buf);
fprintf(stderr,
"\n"
);
fprintf(stderr,
"\n"
);
lo_close(conn, lobj_fd);
}
/*
* exportFile
* export large object
"lobjOid" to file "out_filename"
* export large object
"lobjOid" to file "out_filename"
*
*/
void
...
...
@@ -605,9 +605,9 @@ exportFile(PGconn *conn, Oid lobjId, char *filename)
* open the large object
*/
lobj_fd = lo_open(conn, lobjId, INV_READ);
if (lobj_fd
<
0)
if (lobj_fd
<
0)
{
fprintf(stderr,
"cannot open large object %d\n"
,
fprintf(stderr,
"cannot open large object %d\n"
,
lobjId);
}
...
...
@@ -615,21 +615,21 @@ exportFile(PGconn *conn, Oid lobjId, char *filename)
* open the file to be written to
*/
fd = open(filename, O_CREAT | O_WRONLY, 0666);
if (fd
<
0)
if (fd
<
0)
{ /* error */
fprintf(stderr,
"cannot open unix file %s\n"
,
fprintf(stderr,
"cannot open unix file %s\n"
,
filename);
}
/*
* read in from the inversion file and write to the Unix file
*/
while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE))
>
0)
while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE))
>
0)
{
tmp = write(fd, buf, nbytes);
if (tmp
<
nbytes)
if (tmp
<
nbytes)
{
fprintf(stderr,
"error while writing %s\n"
,
fprintf(stderr,
"error while writing %s\n"
,
filename);
}
}
...
...
@@ -659,7 +659,7 @@ main(int argc, char **argv)
if (argc != 4)
{
fprintf(stderr,
"Usage: %s database_name in_filename out_filename\n"
,
fprintf(stderr,
"Usage: %s database_name in_filename out_filename\n"
,
argv[0]);
exit(1);
}
...
...
@@ -676,36 +676,37 @@ main(int argc, char **argv)
/* check to see that the backend connection was successfully made */
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr,
"Connection to database '%s' failed.\n"
, database);
fprintf(stderr,
"%s"
, PQerrorMessage(conn));
fprintf(stderr,
"Connection to database '%s' failed.\n"
, database);
fprintf(stderr,
"%s"
, PQerrorMessage(conn));
exit_nicely(conn);
}
res = PQexec(conn,
"begin"
);
res = PQexec(conn,
"begin"
);
PQclear(res);
printf(
"importing file %s\n"
, in_filename);
printf(
"importing file %s\n"
, in_filename);
/* lobjOid = importFile(conn, in_filename); */
lobjOid = lo_import(conn, in_filename);
/*
printf(
"as large object %d.\n"
, lobjOid);
printf(
"as large object %d.\n"
, lobjOid);
printf(
"picking out bytes 1000-2000 of the large object\n"
);
printf(
"picking out bytes 1000-2000 of the large object\n"
);
pickout(conn, lobjOid, 1000, 1000);
printf(
"overwriting bytes 1000-2000 of the large object with X's\n"
);
printf(
"overwriting bytes 1000-2000 of the large object with X's\n"
);
overwrite(conn, lobjOid, 1000, 1000);
*/
printf(
"exporting large object to file %s\n"
, out_filename);
printf(
"exporting large object to file %s\n"
, out_filename);
/* exportFile(conn, lobjOid, out_filename); */
lo_export(conn, lobjOid, out_filename);
res = PQexec(conn,
"end"
);
res = PQexec(conn,
"end"
);
PQclear(res);
PQfinish(conn);
exit(0);
}
]]>
</programlisting>
</example>
...
...
doc/src/sgml/trigger.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.5
2 2008/03/28 00:21:55 tgl
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.5
3 2008/12/07 23:46:39 alvherre
Exp $ -->
<chapter id="triggers">
<title>Triggers</title>
...
...
@@ -559,7 +559,7 @@ CREATE TABLE ttest (
<para>
This is the source code of the trigger function:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
#include "executor/spi.h" /* this is what you need to work with SPI */
#include "commands/trigger.h" /* ... and triggers */
...
...
@@ -571,7 +571,7 @@ PG_FUNCTION_INFO_V1(trigf);
Datum
trigf(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo-
>
context;
TriggerData *trigdata = (TriggerData *) fcinfo-
>
context;
TupleDesc tupdesc;
HeapTuple rettuple;
char *when;
...
...
@@ -584,38 +584,38 @@ trigf(PG_FUNCTION_ARGS)
elog(ERROR, "trigf: not called by trigger manager");
/* tuple to return to executor */
if (TRIGGER_FIRED_BY_UPDATE(trigdata-
>
tg_event))
rettuple = trigdata-
>
tg_newtuple;
if (TRIGGER_FIRED_BY_UPDATE(trigdata-
>
tg_event))
rettuple = trigdata-
>
tg_newtuple;
else
rettuple = trigdata-
>
tg_trigtuple;
rettuple = trigdata-
>
tg_trigtuple;
/* check for null values */
if (!TRIGGER_FIRED_BY_DELETE(trigdata-
>
tg_event)
&
amp;& TRIGGER_FIRED_BEFORE(trigdata->
tg_event))
if (!TRIGGER_FIRED_BY_DELETE(trigdata-
>
tg_event)
&
& TRIGGER_FIRED_BEFORE(trigdata->
tg_event))
checknull = true;
if (TRIGGER_FIRED_BEFORE(trigdata-
>
tg_event))
if (TRIGGER_FIRED_BEFORE(trigdata-
>
tg_event))
when = "before";
else
when = "after ";
tupdesc = trigdata-
>tg_relation->
rd_att;
tupdesc = trigdata-
>tg_relation->
rd_att;
/* connect to SPI manager */
if ((ret = SPI_connect())
<
0)
if ((ret = SPI_connect())
<
0)
elog(INFO, "trigf (fired %s): SPI_connect returned %d", when, ret);
/* get number of rows in table */
ret = SPI_exec("SELECT count(*) FROM ttest", 0);
if (ret
<
0)
if (ret
<
0)
elog(NOTICE, "trigf (fired %s): SPI_exec returned %d", when, ret);
/* count(*) returns int8, so be careful to convert */
i = DatumGetInt64(SPI_getbinval(SPI_tuptable-
>
vals[0],
SPI_tuptable-
>
tupdesc,
i = DatumGetInt64(SPI_getbinval(SPI_tuptable-
>
vals[0],
SPI_tuptable-
>
tupdesc,
1,
&
amp;
isnull));
&isnull));
elog (INFO, "trigf (fired %s): there are %d rows in ttest", when, i);
...
...
@@ -623,13 +623,14 @@ trigf(PG_FUNCTION_ARGS)
if (checknull)
{
SPI_getbinval(rettuple, tupdesc, 1, &
amp;
isnull);
SPI_getbinval(rettuple, tupdesc, 1, &isnull);
if (isnull)
rettuple = NULL;
}
return PointerGetDatum(rettuple);
}
]]>
</programlisting>
</para>
...
...
doc/src/sgml/xfunc.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.13
4 2008/12/04 17:51:26 pete
re Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.13
5 2008/12/07 23:46:39 alvher
re Exp $ -->
<sect1 id="xfunc">
<title>User-Defined Functions</title>
...
...
@@ -1621,15 +1621,16 @@ typedef struct {
For example, if we wanted to store 40 bytes in a <structname>text</>
structure, we might use a code fragment like this:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
...
char buffer[40]; /* our source data */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
destination-
>
length = VARHDRSZ + 40;
memcpy(destination-
>
data, buffer, 40);
destination-
>
length = VARHDRSZ + 40;
memcpy(destination-
>
data, buffer, 40);
...
]]>
</programlisting>
<literal>VARHDRSZ</> is the same as <literal>sizeof(int4)</>, but
...
...
@@ -1842,9 +1843,9 @@ memcpy(destination->data, buffer, 40);
<para>
Here are some examples:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
#include
<string.h>
#include
<string.h>
/* by value */
...
...
@@ -1871,8 +1872,8 @@ makepoint(Point *pointx, Point *pointy)
{
Point *new_point = (Point *) palloc(sizeof(Point));
new_point-
>x = pointx->
x;
new_point-
>y = pointy->
y;
new_point-
>x = pointx->
x;
new_point-
>y = pointy->
y;
return new_point;
}
...
...
@@ -1908,6 +1909,7 @@ concat_text(text *arg1, text *arg2)
VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
return new_text;
}
]]>
</programlisting>
</para>
...
...
@@ -2014,9 +2016,9 @@ PG_FUNCTION_INFO_V1(funcname);
<para>
Here we show the same functions as above, coded in version-1 style:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
#include
<string.h>
#include
<string.h>
#include "fmgr.h"
/* by value */
...
...
@@ -2054,8 +2056,8 @@ makepoint(PG_FUNCTION_ARGS)
Point *pointy = PG_GETARG_POINT_P(1);
Point *new_point = (Point *) palloc(sizeof(Point));
new_point-
>x = pointx->
x;
new_point-
>y = pointy->
y;
new_point-
>x = pointx->
x;
new_point-
>y = pointy->
y;
PG_RETURN_POINT_P(new_point);
}
...
...
@@ -2098,6 +2100,7 @@ concat_text(PG_FUNCTION_ARGS)
VARDATA(arg2), VARSIZE(arg2) - VARHDRSZ);
PG_RETURN_TEXT_P(new_text);
}
]]>
</programlisting>
</para>
...
...
@@ -2552,7 +2555,7 @@ SELECT name, c_overpaid(emp, 1500) AS overpaid
Using call conventions version 0, we can define
<function>c_overpaid</> as:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
#include "executor/executor.h" /* for GetAttributeByName() */
...
...
@@ -2563,16 +2566,17 @@ c_overpaid(HeapTupleHeader t, /* the current row of emp */
bool isnull;
int32 salary;
salary = DatumGetInt32(GetAttributeByName(t, "salary", &
amp;
isnull));
salary = DatumGetInt32(GetAttributeByName(t, "salary", &isnull));
if (isnull)
return false;
return salary
>
limit;
return salary
>
limit;
}
]]>
</programlisting>
In version-1 coding, the above would look like this:
<programlisting>
<programlisting>
<![CDATA[
#include "postgres.h"
#include "executor/executor.h" /* for GetAttributeByName() */
...
...
@@ -2586,13 +2590,14 @@ c_overpaid(PG_FUNCTION_ARGS)
bool isnull;
Datum salary;
salary = GetAttributeByName(t, "salary", &
amp;
isnull);
salary = GetAttributeByName(t, "salary", &isnull);
if (isnull)
PG_RETURN_BOOL(false);
/* Alternatively, we might prefer to do PG_RETURN_NULL() for null salary. */
PG_RETURN_BOOL(DatumGetInt32(salary)
>
limit);
PG_RETURN_BOOL(DatumGetInt32(salary)
>
limit);
}
]]>
</programlisting>
</para>
...
...
@@ -2974,7 +2979,7 @@ my_set_returning_function(PG_FUNCTION_ARGS)
<para>
A complete example of a simple <acronym>SRF</> returning a composite type
looks like:
<programlisting>
<programlisting>
<![CDATA[
PG_FUNCTION_INFO_V1(retcomposite);
Datum
...
...
@@ -2995,13 +3000,13 @@ retcomposite(PG_FUNCTION_ARGS)
funcctx = SRF_FIRSTCALL_INIT();
/* switch to memory context appropriate for multiple function calls */
oldcontext = MemoryContextSwitchTo(funcctx-
>
multi_call_memory_ctx);
oldcontext = MemoryContextSwitchTo(funcctx-
>
multi_call_memory_ctx);
/* total number of tuples to be returned */
funcctx-
>
max_calls = PG_GETARG_UINT32(0);
funcctx-
>
max_calls = PG_GETARG_UINT32(0);
/* Build a tuple descriptor for our result type */
if (get_call_result_type(fcinfo, NULL, &
amp;
tupdesc) != TYPEFUNC_COMPOSITE)
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
...
...
@@ -3012,7 +3017,7 @@ retcomposite(PG_FUNCTION_ARGS)
* C strings
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx-
>
attinmeta = attinmeta;
funcctx-
>
attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
...
...
@@ -3020,11 +3025,11 @@ retcomposite(PG_FUNCTION_ARGS)
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx-
>
call_cntr;
max_calls = funcctx-
>
max_calls;
attinmeta = funcctx-
>
attinmeta;
call_cntr = funcctx-
>
call_cntr;
max_calls = funcctx-
>
max_calls;
attinmeta = funcctx-
>
attinmeta;
if (call_cntr
<
max_calls) /* do when there is more left to send */
if (call_cntr
<
max_calls) /* do when there is more left to send */
{
char **values;
HeapTuple tuple;
...
...
@@ -3063,6 +3068,7 @@ retcomposite(PG_FUNCTION_ARGS)
SRF_RETURN_DONE(funcctx);
}
}
]]>
</programlisting>
One way to declare this function in SQL is:
...
...
doc/src/sgml/xindex.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/xindex.sgml,v 1.6
3 2008/05/16 16:31:01 tgl
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/xindex.sgml,v 1.6
4 2008/12/07 23:46:39 alvherre
Exp $ -->
<sect1 id="xindex">
<title>Interfacing Extensions To Indexes</title>
...
...
@@ -499,8 +499,8 @@
reduces the odds of getting inconsistent results for corner cases.
Following this approach, we first write:
<programlisting>
#define Mag(c) ((c)-
>x*(c)->x + (c)->y*(c)->
y)
<programlisting>
<![CDATA[
#define Mag(c) ((c)-
>x*(c)->x + (c)->y*(c)->
y)
static int
complex_abs_cmp_internal(Complex *a, Complex *b)
...
...
@@ -508,17 +508,18 @@ complex_abs_cmp_internal(Complex *a, Complex *b)
double amag = Mag(a),
bmag = Mag(b);
if (amag
<
bmag)
if (amag
<
bmag)
return -1;
if (amag
>
bmag)
if (amag
>
bmag)
return 1;
return 0;
}
]]>
</programlisting>
Now the less-than function looks like:
<programlisting>
<programlisting>
<![CDATA[
PG_FUNCTION_INFO_V1(complex_abs_lt);
Datum
...
...
@@ -527,8 +528,9 @@ complex_abs_lt(PG_FUNCTION_ARGS)
Complex *a = (Complex *) PG_GETARG_POINTER(0);
Complex *b = (Complex *) PG_GETARG_POINTER(1);
PG_RETURN_BOOL(complex_abs_cmp_internal(a, b)
<
0);
PG_RETURN_BOOL(complex_abs_cmp_internal(a, b)
<
0);
}
]]>
</programlisting>
The other four functions differ only in how they compare the internal
...
...
@@ -617,15 +619,16 @@ CREATE FUNCTION complex_abs_cmp(complex, complex)
Now that we have the required operators and support routine,
we can finally create the operator class:
<programlisting>
<programlisting>
<![CDATA[
CREATE OPERATOR CLASS complex_abs_ops
DEFAULT FOR TYPE complex USING btree AS
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 3 = ,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
FUNCTION 1 complex_abs_cmp(complex, complex);
]]>
</programlisting>
</para>
...
...
@@ -708,87 +711,88 @@ CREATE OPERATOR CLASS complex_abs_ops
on one of these types can be searched using a comparison value of another
type. The family could be duplicated by these definitions:
<programlisting>
<programlisting>
<![CDATA[
CREATE OPERATOR FAMILY integer_ops USING btree;
CREATE OPERATOR CLASS int8_ops
DEFAULT FOR TYPE int8 USING btree FAMILY integer_ops AS
-- standard int8 comparisons
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 3 = ,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
FUNCTION 1 btint8cmp(int8, int8) ;
CREATE OPERATOR CLASS int4_ops
DEFAULT FOR TYPE int4 USING btree FAMILY integer_ops AS
-- standard int4 comparisons
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 3 = ,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
FUNCTION 1 btint4cmp(int4, int4) ;
CREATE OPERATOR CLASS int2_ops
DEFAULT FOR TYPE int2 USING btree FAMILY integer_ops AS
-- standard int2 comparisons
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 1
<
,
OPERATOR 2
<
= ,
OPERATOR 3 = ,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
OPERATOR 4
>
= ,
OPERATOR 5
>
,
FUNCTION 1 btint2cmp(int2, int2) ;
ALTER OPERATOR FAMILY integer_ops USING btree ADD
-- cross-type comparisons int8 vs int2
OPERATOR 1
<
(int8, int2) ,
OPERATOR 2
<
= (int8, int2) ,
OPERATOR 1
<
(int8, int2) ,
OPERATOR 2
<
= (int8, int2) ,
OPERATOR 3 = (int8, int2) ,
OPERATOR 4
>
= (int8, int2) ,
OPERATOR 5
>
(int8, int2) ,
OPERATOR 4
>
= (int8, int2) ,
OPERATOR 5
>
(int8, int2) ,
FUNCTION 1 btint82cmp(int8, int2) ,
-- cross-type comparisons int8 vs int4
OPERATOR 1
<
(int8, int4) ,
OPERATOR 2
<
= (int8, int4) ,
OPERATOR 1
<
(int8, int4) ,
OPERATOR 2
<
= (int8, int4) ,
OPERATOR 3 = (int8, int4) ,
OPERATOR 4
>
= (int8, int4) ,
OPERATOR 5
>
(int8, int4) ,
OPERATOR 4
>
= (int8, int4) ,
OPERATOR 5
>
(int8, int4) ,
FUNCTION 1 btint84cmp(int8, int4) ,
-- cross-type comparisons int4 vs int2
OPERATOR 1
<
(int4, int2) ,
OPERATOR 2
<
= (int4, int2) ,
OPERATOR 1
<
(int4, int2) ,
OPERATOR 2
<
= (int4, int2) ,
OPERATOR 3 = (int4, int2) ,
OPERATOR 4
>
= (int4, int2) ,
OPERATOR 5
>
(int4, int2) ,
OPERATOR 4
>
= (int4, int2) ,
OPERATOR 5
>
(int4, int2) ,
FUNCTION 1 btint42cmp(int4, int2) ,
-- cross-type comparisons int4 vs int8
OPERATOR 1
<
(int4, int8) ,
OPERATOR 2
<
= (int4, int8) ,
OPERATOR 1
<
(int4, int8) ,
OPERATOR 2
<
= (int4, int8) ,
OPERATOR 3 = (int4, int8) ,
OPERATOR 4
>
= (int4, int8) ,
OPERATOR 5
>
(int4, int8) ,
OPERATOR 4
>
= (int4, int8) ,
OPERATOR 5
>
(int4, int8) ,
FUNCTION 1 btint48cmp(int4, int8) ,
-- cross-type comparisons int2 vs int8
OPERATOR 1
<
(int2, int8) ,
OPERATOR 2
<
= (int2, int8) ,
OPERATOR 1
<
(int2, int8) ,
OPERATOR 2
<
= (int2, int8) ,
OPERATOR 3 = (int2, int8) ,
OPERATOR 4
>
= (int2, int8) ,
OPERATOR 5
>
(int2, int8) ,
OPERATOR 4
>
= (int2, int8) ,
OPERATOR 5
>
(int2, int8) ,
FUNCTION 1 btint28cmp(int2, int8) ,
-- cross-type comparisons int2 vs int4
OPERATOR 1
<
(int2, int4) ,
OPERATOR 2
<
= (int2, int4) ,
OPERATOR 1
<
(int2, int4) ,
OPERATOR 2
<
= (int2, int4) ,
OPERATOR 3 = (int2, int4) ,
OPERATOR 4
>
= (int2, int4) ,
OPERATOR 5
>
(int2, int4) ,
OPERATOR 4
>
= (int2, int4) ,
OPERATOR 5
>
(int2, int4) ,
FUNCTION 1 btint24cmp(int2, int4) ;
]]>
</programlisting>
Notice that this definition <quote>overloads</> the operator strategy and
...
...
doc/src/sgml/xtypes.sgml
View file @
aa7f0046
<!-- $PostgreSQL: pgsql/doc/src/sgml/xtypes.sgml,v 1.3
0 2008/02/23 19:11:45 tgl
Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/xtypes.sgml,v 1.3
1 2008/12/07 23:46:39 alvherre
Exp $ -->
<sect1 id="xtypes">
<title>User-Defined Types</title>
...
...
@@ -75,7 +75,7 @@ typedef struct Complex {
write a complete and robust parser for that representation as your
input function. For instance:
<programlisting>
<programlisting>
<![CDATA[
PG_FUNCTION_INFO_V1(complex_in);
Datum
...
...
@@ -86,22 +86,23 @@ complex_in(PG_FUNCTION_ARGS)
y;
Complex *result;
if (sscanf(str, " ( %lf , %lf )", &
amp;x, &
y) != 2)
if (sscanf(str, " ( %lf , %lf )", &
x, &
y) != 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for complex: \"%s\"",
str)));
result = (Complex *) palloc(sizeof(Complex));
result-
>
x = x;
result-
>
y = y;
result-
>
x = x;
result-
>
y = y;
PG_RETURN_POINTER(result);
}
]]>
</programlisting>
The output function can simply be:
<programlisting>
<programlisting>
<![CDATA[
PG_FUNCTION_INFO_V1(complex_out);
Datum
...
...
@@ -111,9 +112,10 @@ complex_out(PG_FUNCTION_ARGS)
char *result;
result = (char *) palloc(100);
snprintf(result, 100, "(%g,%g)", complex-
>x, complex->
y);
snprintf(result, 100, "(%g,%g)", complex-
>x, complex->
y);
PG_RETURN_CSTRING(result);
}
]]>
</programlisting>
</para>
...
...
@@ -134,7 +136,7 @@ complex_out(PG_FUNCTION_ARGS)
<type>complex</type>, we will piggy-back on the binary I/O converters
for type <type>float8</>:
<programlisting>
<programlisting>
<![CDATA[
PG_FUNCTION_INFO_V1(complex_recv);
Datum
...
...
@@ -144,8 +146,8 @@ complex_recv(PG_FUNCTION_ARGS)
Complex *result;
result = (Complex *) palloc(sizeof(Complex));
result-
>
x = pq_getmsgfloat8(buf);
result-
>
y = pq_getmsgfloat8(buf);
result-
>
x = pq_getmsgfloat8(buf);
result-
>
y = pq_getmsgfloat8(buf);
PG_RETURN_POINTER(result);
}
...
...
@@ -157,11 +159,12 @@ complex_send(PG_FUNCTION_ARGS)
Complex *complex = (Complex *) PG_GETARG_POINTER(0);
StringInfoData buf;
pq_begintypsend(&
amp;
buf);
pq_sendfloat8(&
amp;buf, complex->
x);
pq_sendfloat8(&
amp;buf, complex->
y);
PG_RETURN_BYTEA_P(pq_endtypsend(&
amp;
buf));
pq_begintypsend(&buf);
pq_sendfloat8(&
buf, complex->
x);
pq_sendfloat8(&
buf, complex->
y);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
]]>
</programlisting>
</para>
...
...
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