mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
Support for MOVE in PL/PgSQL. Initial patch from Magnus, some improvements
by Pavel Stehule, and reviewed by Neil Conway.
This commit is contained in:
parent
f2321a3f37
commit
8690ebc26f
@ -1,4 +1,4 @@
|
|||||||
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.108 2007/04/28 23:54:58 neilc Exp $ -->
|
<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.109 2007/04/29 01:21:08 neilc Exp $ -->
|
||||||
|
|
||||||
<chapter id="plpgsql">
|
<chapter id="plpgsql">
|
||||||
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
<title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
|
||||||
@ -1522,6 +1522,13 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
|
|||||||
true if it returns a row, false if no row is returned.
|
true if it returns a row, false if no row is returned.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
A <command>MOVE</> statement sets <literal>FOUND</literal>
|
||||||
|
true if it successfully repositions the cursor, false otherwise.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
A <command>FOR</> statement sets <literal>FOUND</literal> true
|
||||||
@ -2562,6 +2569,53 @@ FETCH curs1 INTO rowvar;
|
|||||||
FETCH curs2 INTO foo, bar, baz;
|
FETCH curs2 INTO foo, bar, baz;
|
||||||
FETCH LAST FROM curs3 INTO x, y;
|
FETCH LAST FROM curs3 INTO x, y;
|
||||||
FETCH RELATIVE -2 FROM curs4 INTO x;
|
FETCH RELATIVE -2 FROM curs4 INTO x;
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</sect3>
|
||||||
|
|
||||||
|
<sect3>
|
||||||
|
<title><literal>MOVE</></title>
|
||||||
|
|
||||||
|
<synopsis>
|
||||||
|
MOVE <optional> <replaceable>direction</replaceable> { FROM | IN } </optional> <replaceable>cursor</replaceable>;
|
||||||
|
</synopsis>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
<command>MOVE</command> repositions a cursor without retrieving
|
||||||
|
any data. <command>MOVE</command> works exactly like the
|
||||||
|
<command>FETCH</command> command, except it only positions the
|
||||||
|
cursor and does not return rows. As with <command>SELECT
|
||||||
|
INTO</command>, the special variable <literal>FOUND</literal> can
|
||||||
|
be checked to see whether the cursor was successfully
|
||||||
|
repositioned or not.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <replaceable>direction</replaceable> clause can be any of the
|
||||||
|
variants allowed in the SQL <xref linkend="sql-move"
|
||||||
|
endterm="sql-move-title"> command except the ones that can move by
|
||||||
|
more than one row; namely, it can be
|
||||||
|
<literal>NEXT</>,
|
||||||
|
<literal>PRIOR</>,
|
||||||
|
<literal>FIRST</>,
|
||||||
|
<literal>LAST</>,
|
||||||
|
<literal>ABSOLUTE</> <replaceable>count</replaceable>,
|
||||||
|
<literal>RELATIVE</> <replaceable>count</replaceable>,
|
||||||
|
<literal>FORWARD</>, or
|
||||||
|
<literal>BACKWARD</>.
|
||||||
|
Omitting <replaceable>direction</replaceable> is the same
|
||||||
|
as specifying <literal>NEXT</>.
|
||||||
|
<replaceable>direction</replaceable> values that require moving
|
||||||
|
backward are likely to fail unless the cursor was declared or opened
|
||||||
|
with the <literal>SCROLL</> option.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Examples:
|
||||||
|
<programlisting>
|
||||||
|
MOVE curs1;
|
||||||
|
MOVE LAST FROM curs3;
|
||||||
|
MOVE RELATIVE -2 FROM curs4;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect3>
|
</sect3>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.101 2007/04/28 23:54:59 neilc Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.102 2007/04/29 01:21:09 neilc Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -125,7 +125,7 @@ static void check_labels(const char *start_label,
|
|||||||
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
|
%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
|
||||||
%type <stmt> stmt_return stmt_raise stmt_execsql stmt_execsql_insert
|
%type <stmt> stmt_return stmt_raise stmt_execsql stmt_execsql_insert
|
||||||
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
|
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
|
||||||
%type <stmt> stmt_open stmt_fetch stmt_close stmt_null
|
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
|
||||||
|
|
||||||
%type <list> proc_exceptions
|
%type <list> proc_exceptions
|
||||||
%type <exception_block> exception_sect
|
%type <exception_block> exception_sect
|
||||||
@ -179,6 +179,7 @@ static void check_labels(const char *start_label,
|
|||||||
%token K_IS
|
%token K_IS
|
||||||
%token K_LOG
|
%token K_LOG
|
||||||
%token K_LOOP
|
%token K_LOOP
|
||||||
|
%token K_MOVE
|
||||||
%token K_NEXT
|
%token K_NEXT
|
||||||
%token K_NOSCROLL
|
%token K_NOSCROLL
|
||||||
%token K_NOT
|
%token K_NOT
|
||||||
@ -635,6 +636,8 @@ proc_stmt : pl_block ';'
|
|||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_fetch
|
| stmt_fetch
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
|
| stmt_move
|
||||||
|
{ $$ = $1; }
|
||||||
| stmt_close
|
| stmt_close
|
||||||
{ $$ = $1; }
|
{ $$ = $1; }
|
||||||
| stmt_null
|
| stmt_null
|
||||||
@ -1478,6 +1481,19 @@ stmt_fetch : K_FETCH lno opt_fetch_direction cursor_variable K_INTO
|
|||||||
fetch->rec = rec;
|
fetch->rec = rec;
|
||||||
fetch->row = row;
|
fetch->row = row;
|
||||||
fetch->curvar = $4->varno;
|
fetch->curvar = $4->varno;
|
||||||
|
fetch->is_move = false;
|
||||||
|
|
||||||
|
$$ = (PLpgSQL_stmt *)fetch;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
stmt_move : K_MOVE lno opt_fetch_direction cursor_variable ';'
|
||||||
|
{
|
||||||
|
PLpgSQL_stmt_fetch *fetch = $3;
|
||||||
|
|
||||||
|
fetch->lineno = $2;
|
||||||
|
fetch->curvar = $4->varno;
|
||||||
|
fetch->is_move = true;
|
||||||
|
|
||||||
$$ = (PLpgSQL_stmt *)fetch;
|
$$ = (PLpgSQL_stmt *)fetch;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.195 2007/04/19 16:33:24 tgl Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.196 2007/04/29 01:21:09 neilc Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -3114,7 +3114,8 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
|
|||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* exec_stmt_fetch Fetch from a cursor into a target
|
* exec_stmt_fetch Fetch from a cursor into a target, or just
|
||||||
|
* move the current position of the cursor
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
@ -3163,46 +3164,57 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
|
|||||||
exec_eval_cleanup(estate);
|
exec_eval_cleanup(estate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------
|
if (!stmt->is_move)
|
||||||
* Determine if we fetch into a record or a row
|
|
||||||
* ----------
|
|
||||||
*/
|
|
||||||
if (stmt->rec != NULL)
|
|
||||||
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
|
||||||
else if (stmt->row != NULL)
|
|
||||||
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
|
||||||
else
|
|
||||||
elog(ERROR, "unsupported target");
|
|
||||||
|
|
||||||
/* ----------
|
|
||||||
* Fetch 1 tuple from the cursor
|
|
||||||
* ----------
|
|
||||||
*/
|
|
||||||
SPI_scroll_cursor_fetch(portal, stmt->direction, how_many);
|
|
||||||
tuptab = SPI_tuptable;
|
|
||||||
n = SPI_processed;
|
|
||||||
|
|
||||||
/* ----------
|
|
||||||
* Set the target and the global FOUND variable appropriately.
|
|
||||||
* ----------
|
|
||||||
*/
|
|
||||||
if (n == 0)
|
|
||||||
{
|
{
|
||||||
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
/* ----------
|
||||||
exec_set_found(estate, false);
|
* Determine if we fetch into a record or a row
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
if (stmt->rec != NULL)
|
||||||
|
rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
|
||||||
|
else if (stmt->row != NULL)
|
||||||
|
row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
|
||||||
|
else
|
||||||
|
elog(ERROR, "unsupported target");
|
||||||
|
|
||||||
|
/* ----------
|
||||||
|
* Fetch 1 tuple from the cursor
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
SPI_scroll_cursor_fetch(portal, stmt->direction, how_many);
|
||||||
|
tuptab = SPI_tuptable;
|
||||||
|
n = SPI_processed;
|
||||||
|
|
||||||
|
/* ----------
|
||||||
|
* Set the target and the global FOUND variable appropriately.
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
|
||||||
|
exec_set_found(estate, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
|
||||||
|
exec_set_found(estate, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPI_freetuptable(tuptab);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
|
/* Move the cursor */
|
||||||
exec_set_found(estate, true);
|
SPI_scroll_cursor_move(portal, stmt->direction, how_many);
|
||||||
}
|
n = SPI_processed;
|
||||||
|
|
||||||
SPI_freetuptable(tuptab);
|
/* Set the global FOUND variable appropriately. */
|
||||||
|
exec_set_found(estate, n != 0);
|
||||||
|
}
|
||||||
|
|
||||||
return PLPGSQL_RC_OK;
|
return PLPGSQL_RC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* exec_stmt_close Close a cursor
|
* exec_stmt_close Close a cursor
|
||||||
* ----------
|
* ----------
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.58 2007/03/18 05:36:49 neilc Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.59 2007/04/29 01:21:09 neilc Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -493,6 +493,7 @@ static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt);
|
|||||||
static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt);
|
static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt);
|
||||||
static void dump_open(PLpgSQL_stmt_open *stmt);
|
static void dump_open(PLpgSQL_stmt_open *stmt);
|
||||||
static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
|
static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
|
||||||
|
static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
|
||||||
static void dump_close(PLpgSQL_stmt_close *stmt);
|
static void dump_close(PLpgSQL_stmt_close *stmt);
|
||||||
static void dump_perform(PLpgSQL_stmt_perform *stmt);
|
static void dump_perform(PLpgSQL_stmt_perform *stmt);
|
||||||
static void dump_expr(PLpgSQL_expr *expr);
|
static void dump_expr(PLpgSQL_expr *expr);
|
||||||
@ -761,21 +762,64 @@ static void
|
|||||||
dump_fetch(PLpgSQL_stmt_fetch *stmt)
|
dump_fetch(PLpgSQL_stmt_fetch *stmt)
|
||||||
{
|
{
|
||||||
dump_ind();
|
dump_ind();
|
||||||
printf("FETCH curvar=%d\n", stmt->curvar);
|
|
||||||
|
if (!stmt->is_move)
|
||||||
|
{
|
||||||
|
printf("FETCH curvar=%d\n", stmt->curvar);
|
||||||
|
dump_cursor_direction(stmt);
|
||||||
|
|
||||||
|
dump_indent += 2;
|
||||||
|
if (stmt->rec != NULL)
|
||||||
|
{
|
||||||
|
dump_ind();
|
||||||
|
printf(" target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
|
||||||
|
}
|
||||||
|
if (stmt->row != NULL)
|
||||||
|
{
|
||||||
|
dump_ind();
|
||||||
|
printf(" target = %d %s\n", stmt->row->rowno, stmt->row->refname);
|
||||||
|
}
|
||||||
|
dump_indent -= 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("MOVE curvar=%d\n", stmt->curvar);
|
||||||
|
dump_cursor_direction(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_cursor_direction(PLpgSQL_stmt_fetch *stmt)
|
||||||
|
{
|
||||||
dump_indent += 2;
|
dump_indent += 2;
|
||||||
if (stmt->rec != NULL)
|
dump_ind();
|
||||||
|
switch (stmt->direction)
|
||||||
{
|
{
|
||||||
dump_ind();
|
case FETCH_FORWARD:
|
||||||
printf(" target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
|
printf(" FORWARD ");
|
||||||
|
break;
|
||||||
|
case FETCH_BACKWARD:
|
||||||
|
printf(" BACKWARD ");
|
||||||
|
break;
|
||||||
|
case FETCH_ABSOLUTE:
|
||||||
|
printf(" ABSOLUTE ");
|
||||||
|
break;
|
||||||
|
case FETCH_RELATIVE:
|
||||||
|
printf(" RELATIVE ");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("??? unknown cursor direction %d", stmt->direction);
|
||||||
}
|
}
|
||||||
if (stmt->row != NULL)
|
|
||||||
|
if (stmt->expr)
|
||||||
{
|
{
|
||||||
dump_ind();
|
dump_expr(stmt->expr);
|
||||||
printf(" target = %d %s\n", stmt->row->rowno, stmt->row->refname);
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
printf("%d\n", stmt->how_many);
|
||||||
|
|
||||||
dump_indent -= 2;
|
dump_indent -= 2;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1067,3 +1111,4 @@ plpgsql_dumptree(PLpgSQL_function *func)
|
|||||||
printf("\nEnd of execution tree of function %s\n\n", func->fn_name);
|
printf("\nEnd of execution tree of function %s\n\n", func->fn_name);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.87 2007/04/16 17:21:23 tgl Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.88 2007/04/29 01:21:09 neilc Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -446,7 +446,7 @@ typedef struct
|
|||||||
|
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{ /* FETCH statement */
|
{ /* FETCH or MOVE statement */
|
||||||
int cmd_type;
|
int cmd_type;
|
||||||
int lineno;
|
int lineno;
|
||||||
PLpgSQL_rec *rec; /* target, as record or row */
|
PLpgSQL_rec *rec; /* target, as record or row */
|
||||||
@ -455,6 +455,7 @@ typedef struct
|
|||||||
FetchDirection direction; /* fetch direction */
|
FetchDirection direction; /* fetch direction */
|
||||||
int how_many; /* count, if constant (expr is NULL) */
|
int how_many; /* count, if constant (expr is NULL) */
|
||||||
PLpgSQL_expr *expr; /* count, if expression */
|
PLpgSQL_expr *expr; /* count, if expression */
|
||||||
|
bool is_move; /* is this a fetch or move? */
|
||||||
} PLpgSQL_stmt_fetch;
|
} PLpgSQL_stmt_fetch;
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* IDENTIFICATION
|
||||||
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.56 2007/04/16 17:21:23 tgl Exp $
|
* $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.57 2007/04/29 01:21:09 neilc Exp $
|
||||||
*
|
*
|
||||||
*-------------------------------------------------------------------------
|
*-------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
@ -142,6 +142,7 @@ into { return K_INTO; }
|
|||||||
is { return K_IS; }
|
is { return K_IS; }
|
||||||
log { return K_LOG; }
|
log { return K_LOG; }
|
||||||
loop { return K_LOOP; }
|
loop { return K_LOOP; }
|
||||||
|
move { return K_MOVE; }
|
||||||
next { return K_NEXT; }
|
next { return K_NEXT; }
|
||||||
no{space}+scroll { return K_NOSCROLL; }
|
no{space}+scroll { return K_NOSCROLL; }
|
||||||
not { return K_NOT; }
|
not { return K_NOT; }
|
||||||
|
@ -3023,4 +3023,31 @@ select * from sc_test();
|
|||||||
0
|
0
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
|
create or replace function sc_test() returns setof integer as $$
|
||||||
|
declare
|
||||||
|
c cursor for select * from generate_series(1, 10);
|
||||||
|
x integer;
|
||||||
|
begin
|
||||||
|
open c;
|
||||||
|
loop
|
||||||
|
move relative 2 in c;
|
||||||
|
if not found then
|
||||||
|
exit;
|
||||||
|
end if;
|
||||||
|
fetch next from c into x;
|
||||||
|
if found then
|
||||||
|
return next x;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
close c;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
|
select * from sc_test();
|
||||||
|
sc_test
|
||||||
|
---------
|
||||||
|
3
|
||||||
|
6
|
||||||
|
9
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
drop function sc_test();
|
drop function sc_test();
|
||||||
|
@ -2511,4 +2511,27 @@ $$ language plpgsql;
|
|||||||
|
|
||||||
select * from sc_test();
|
select * from sc_test();
|
||||||
|
|
||||||
|
create or replace function sc_test() returns setof integer as $$
|
||||||
|
declare
|
||||||
|
c cursor for select * from generate_series(1, 10);
|
||||||
|
x integer;
|
||||||
|
begin
|
||||||
|
open c;
|
||||||
|
loop
|
||||||
|
move relative 2 in c;
|
||||||
|
if not found then
|
||||||
|
exit;
|
||||||
|
end if;
|
||||||
|
fetch next from c into x;
|
||||||
|
if found then
|
||||||
|
return next x;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
close c;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
|
|
||||||
|
select * from sc_test();
|
||||||
|
|
||||||
drop function sc_test();
|
drop function sc_test();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user