Merged 4.1 into 5.0.
BitKeeper/etc/gone: auto-union BitKeeper/etc/logging_ok: auto-union client/mysql.cc: Auto merged client/mysqltest.c: Auto merged configure.in: Auto merged libmysqld/Makefile.am: Auto merged sql/item.h: Auto merged sql/item_func.h: Auto merged sql/lex.h: Auto merged sql/mysql_priv.h: Auto merged sql/sql_class.cc: Auto merged sql/sql_class.h: Auto merged sql/sql_lex.cc: Auto merged sql/sql_lex.h: Auto merged sql/share/polish/errmsg.txt: Auto merged sql/share/russian/errmsg.txt: Auto merged sql/sql_select.cc: Auto merged
This commit is contained in:
commit
8db55dcf86
@ -6,6 +6,7 @@ FROM=$USER@mysql.com
|
|||||||
INTERNALS=internals@lists.mysql.com
|
INTERNALS=internals@lists.mysql.com
|
||||||
DOCS=docs-commit@mysql.com
|
DOCS=docs-commit@mysql.com
|
||||||
LIMIT=10000
|
LIMIT=10000
|
||||||
|
REPOV=5.0
|
||||||
|
|
||||||
if [ "$REAL_EMAIL" = "" ]
|
if [ "$REAL_EMAIL" = "" ]
|
||||||
then
|
then
|
||||||
@ -27,15 +28,15 @@ CHANGESET=`bk -R prs -r+ -h -d':I:' ChangeSet`
|
|||||||
echo "Commit successful, notifying developers at $TO"
|
echo "Commit successful, notifying developers at $TO"
|
||||||
(
|
(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
List-ID: <bk.mysql-4.1>
|
List-ID: <bk.mysql-$REPOV>
|
||||||
From: $FROM
|
From: $FROM
|
||||||
To: $TO
|
To: $TO
|
||||||
Subject: bk commit - 4.1 tree ($CHANGESET)
|
Subject: bk commit - $REPOV tree ($CHANGESET)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
bk changes -v -r+
|
bk changes -v -r+
|
||||||
bk cset -r+ -d
|
bk cset -r+ -d
|
||||||
) | head -n $LIMIT | /usr/sbin/sendmail -t
|
) | /usr/sbin/sendmail -t
|
||||||
|
|
||||||
#++
|
#++
|
||||||
# internals@ mail
|
# internals@ mail
|
||||||
@ -43,13 +44,13 @@ EOF
|
|||||||
echo "Notifying internals list at $INTERNALS"
|
echo "Notifying internals list at $INTERNALS"
|
||||||
(
|
(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
List-ID: <bk.mysql-4.1>
|
List-ID: <bk.mysql-$REPOV>
|
||||||
From: $FROM
|
From: $FROM
|
||||||
To: $INTERNALS
|
To: $INTERNALS
|
||||||
Subject: bk commit into 4.1 tree ($CHANGESET)
|
Subject: bk commit into $REPOV tree ($CHANGESET)
|
||||||
|
|
||||||
Below is the list of changes that have just been committed into a local
|
Below is the list of changes that have just been committed into a local
|
||||||
4.1 repository of $USER. When $USER does a push these changes will
|
$REPOV repository of $USER. When $USER does a push these changes will
|
||||||
be propagated to the main repository and, within 24 hours after the
|
be propagated to the main repository and, within 24 hours after the
|
||||||
push, to the public repository.
|
push, to the public repository.
|
||||||
For information on how to access the public repository
|
For information on how to access the public repository
|
||||||
@ -70,15 +71,15 @@ EOF
|
|||||||
echo "Notifying docs list at $DOCS"
|
echo "Notifying docs list at $DOCS"
|
||||||
(
|
(
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
List-ID: <bk.mysql-4.1>
|
List-ID: <bk.mysql-$REPOV>
|
||||||
From: $FROM
|
From: $FROM
|
||||||
To: $DOCS
|
To: $DOCS
|
||||||
Subject: bk commit - 4.1 tree (Manual) ($CHANGESET)
|
Subject: bk commit - $REPOV tree (Manual) ($CHANGESET)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
bk changes -v -r+
|
bk changes -v -r+
|
||||||
bk cset -r+ -d
|
bk cset -r+ -d
|
||||||
) | head -n $LIMIT | /usr/sbin/sendmail -t
|
) | /usr/sbin/sendmail -t
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
|
622
Docs/sp-imp-spec.txt
Normal file
622
Docs/sp-imp-spec.txt
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
|
||||||
|
Implementation specification for Stored Procedures
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
|
||||||
|
- How parsing and execution of queries work
|
||||||
|
|
||||||
|
In order to execute a query, the function sql_parse.cc:mysql_parse() is
|
||||||
|
called, which in turn calls the parser (yyparse()) with an updated Lex
|
||||||
|
structure as the result. mysql_parse() then calls mysql_execute_command()
|
||||||
|
which dispatches on the command code (in Lex) to the corresponding code for
|
||||||
|
executing that particular query.
|
||||||
|
|
||||||
|
There are thre structures involved in the execution of a query which are of
|
||||||
|
interest to the stored procedure implementation:
|
||||||
|
|
||||||
|
- Lex (mentioned above) is the "compiled" query, that is the output from
|
||||||
|
the parser and what is then interpreted to do the actual work.
|
||||||
|
It constains an enum value (sql_command) which is the query type, and
|
||||||
|
all the data collected by the parser needed for the execution (table
|
||||||
|
names, fields, values, etc).
|
||||||
|
- THD is the "run-time" state of a connection, containing all that is
|
||||||
|
needed for a particular client connection, and, among other things, the
|
||||||
|
Lex structure currently being executed.
|
||||||
|
- Item_*: During parsing, all data is translated into "items", objects of
|
||||||
|
the subclasses of "Item", such as Item_int, Item_real, Item_string, etc,
|
||||||
|
for basic datatypes, and also various more specialized Item types for
|
||||||
|
expressions to be evaluated (Item_func objects).
|
||||||
|
|
||||||
|
|
||||||
|
- How to fit Stored Procedure into this scheme
|
||||||
|
|
||||||
|
- An overview of the classes and files for stored procedures
|
||||||
|
(More detailed APIs at the end of this file)
|
||||||
|
|
||||||
|
- class sp_head (sp_head.{cc,h})
|
||||||
|
This contains, among other things, an array of "instructions" and the
|
||||||
|
method for executing the procedure.
|
||||||
|
|
||||||
|
- class sp_pcontext (sp_pcontext.{cc,h}
|
||||||
|
This is the parse context for the procedure. It's primarily used during
|
||||||
|
parsing to keep track of local parameters, variables and labels, but
|
||||||
|
it's also used at CALL time do find parameters mode (IN, OUT or INOUT)
|
||||||
|
and type when setting up the runtime context.
|
||||||
|
|
||||||
|
- class sp_instr (sp_head.{cc,h})
|
||||||
|
This is the base class for "instructions", that is, what is generated
|
||||||
|
by the parser. It turns out that we only need 4 different sub classes:
|
||||||
|
- sp_instr_stmt
|
||||||
|
Execute a statement. This is the "call-out" any normal SQL statement,
|
||||||
|
like a SELECT, INSERT etc. It contains the Lex structure for the
|
||||||
|
statement in question.
|
||||||
|
- sp_instr_set
|
||||||
|
Set the value of a local variable (or parameter)
|
||||||
|
- sp_instr_jump
|
||||||
|
An unconditional jump.
|
||||||
|
- sp_instr_jump_if_not
|
||||||
|
Jump if condition is not true. It turns out that the negative test is
|
||||||
|
most convenient when generating the code for the flow control
|
||||||
|
constructs.
|
||||||
|
|
||||||
|
- class sp_rcontext (sp_rcontext.h)
|
||||||
|
This is the runtime context in the THD structure.
|
||||||
|
It contains an array of items, the parameters and local variables for
|
||||||
|
the currently executing stored procedure.
|
||||||
|
This means that variable value lookup is in runtime is constant time,
|
||||||
|
a simple index operation.
|
||||||
|
|
||||||
|
- class Item_splocal (Item.{cc,h})
|
||||||
|
This is a subclass of Item. Its sole purpose is to hide the fact that
|
||||||
|
the real Item is actually in the current frame (runtime context).
|
||||||
|
It contains the frame offset and defers all methods to the real Item
|
||||||
|
in the frame. This is what the parser generates for local variables.
|
||||||
|
|
||||||
|
- Utility functions (sp.{cc,h})
|
||||||
|
This contains functions for creating, dropping and finding a stored
|
||||||
|
procedure in the mysql.proc table (or internal cache, when it is
|
||||||
|
implemented).
|
||||||
|
|
||||||
|
|
||||||
|
- Parsing CREATE PROCEDURE ...
|
||||||
|
|
||||||
|
When parsing a CREATE PROCEDURE the parser first initializes the
|
||||||
|
sphead and spcont (runtime context) fields in the Lex.
|
||||||
|
The sql_command code for the result of parsing a is
|
||||||
|
SQLCOM_CREATE_PROCEDURE.
|
||||||
|
|
||||||
|
The parsing of the parameter list and body is relatively
|
||||||
|
straight-forward:
|
||||||
|
|
||||||
|
- Parameters:
|
||||||
|
name, type and mode (IN/OUT/INOUT) is pushed to spcont
|
||||||
|
- Declared local variables:
|
||||||
|
Same as parameters (mode is then IN)
|
||||||
|
- Local Variable references:
|
||||||
|
If an identifier is found in in spcont, an Item_splocal is created
|
||||||
|
with the variable's frame index, otherwise an Item_field or Item_ref
|
||||||
|
is created (as before).
|
||||||
|
- Statements:
|
||||||
|
The Lex in THD is replaced by a new Lex structure and the statement,
|
||||||
|
is parsed as usual. A sp_instr_stmt is created, containing the new
|
||||||
|
Lex, and added to added to the instructions in sphead.
|
||||||
|
Afterwards, the procedure's Lex is restored in THD.
|
||||||
|
- SET var:
|
||||||
|
Setting a local variable generates a sp_instr_set instruction,
|
||||||
|
containing the variable's frame offset, the expression (an Item),
|
||||||
|
and the type.
|
||||||
|
- Flow control:
|
||||||
|
Flow control constructs like, IF, WHILE, etc, generate a conditional
|
||||||
|
and unconditional jumps in the "obvious" way, but a few notes may
|
||||||
|
be required:
|
||||||
|
- Forward jumps: When jumping forward, the exact destination is not
|
||||||
|
known at the time of the creation of the jump instruction. The
|
||||||
|
sphead therefore contains list of instruction-label pairs for
|
||||||
|
each forward reference. When the position later is known, the
|
||||||
|
instructions in the list are updated with the correct location.
|
||||||
|
- Loop constructs have optional labels. If a loop doesn't have a
|
||||||
|
label, an anonymous label is generated to simplify the parsing.
|
||||||
|
- There are two types of CASE. The "simple" case is implemented
|
||||||
|
with an anonymous variable bound to the value to be tested.
|
||||||
|
|
||||||
|
|
||||||
|
- An example
|
||||||
|
|
||||||
|
Parsing the procedure:
|
||||||
|
|
||||||
|
create procedure a(s char(16))
|
||||||
|
begin
|
||||||
|
declare x int;
|
||||||
|
set x = 3;
|
||||||
|
while x > 0 do
|
||||||
|
set x = x-1;
|
||||||
|
insert into db.tab values (x, s);
|
||||||
|
end while
|
||||||
|
end
|
||||||
|
|
||||||
|
would generate the following structures:
|
||||||
|
______
|
||||||
|
thd: | | _________
|
||||||
|
| lex -+--->| | ___________________
|
||||||
|
|______| | spcont -+------------------->| "s",in,char(16):0 |
|
||||||
|
| sphead -+------ |("x",in,int :1)|
|
||||||
|
|_________| | |___________________|
|
||||||
|
____V__________________
|
||||||
|
| m_name: "a" |
|
||||||
|
| m_defstr: "create ..."|
|
||||||
|
| m_instr: ... |
|
||||||
|
|_______________________|
|
||||||
|
|
||||||
|
Note that the contents of the spcont is changing during the parsing,
|
||||||
|
at all times reflecting the state of the would-be runtime frame.
|
||||||
|
The m_instr is an array of instructions:
|
||||||
|
|
||||||
|
Pos. Instruction
|
||||||
|
0 sp_instr_set(1, '3')
|
||||||
|
1 sp_instr_jump_if_not(5, 'x>0')
|
||||||
|
2 sp_instr_set(1, 'x-1')
|
||||||
|
3 sp_instr_stmt('insert into ...')
|
||||||
|
4 sp_instr_jump(1)
|
||||||
|
5 <end>
|
||||||
|
|
||||||
|
Here, '3', 'x>0', etc, represent the Items or Lex for the respective
|
||||||
|
expressions or statements.
|
||||||
|
|
||||||
|
|
||||||
|
- Parsing CREATE FUNCTION ...
|
||||||
|
|
||||||
|
Creating a functions is essensially the same thing as for a PROCEDURE,
|
||||||
|
with the addition that a FUNCTION has a return type and a RETURN
|
||||||
|
statement, but no OUT or INOUT parameters.
|
||||||
|
|
||||||
|
The main difference during parsing is that we store the result type
|
||||||
|
in the sp_head. However, there are big differences when it comes to
|
||||||
|
invoking a FUNCTION. (See below.)
|
||||||
|
|
||||||
|
|
||||||
|
- Storing, caching, dropping...
|
||||||
|
|
||||||
|
As seen above, the entired definition string, including the "CREATE
|
||||||
|
PROCEDURE" (or "FUNCTION") is kept. The procedure definition string is
|
||||||
|
stored in the table mysql.proc with the name and type as the key, the
|
||||||
|
type being one of the enum ("procedure","function").
|
||||||
|
|
||||||
|
A PROCEDURE is just stored int the mysql.proc table. A FUNCTION has an
|
||||||
|
additional requirement. They will be called in expressions with the same
|
||||||
|
syntax as UDFs, so UDFs and stored FUNCTIONs share the namespace. Thus,
|
||||||
|
we must make sure that we do not have UDFs and FUNCTIONs with the same
|
||||||
|
name (even if they are storded in different places).
|
||||||
|
|
||||||
|
This means that we can reparse the procedure as many time as we want.
|
||||||
|
The first time, the resulting Lex is used to store the procedure in
|
||||||
|
the database (using the function sp.c:sp_create_procedure()).
|
||||||
|
|
||||||
|
The simplest way would be to just leave it at that, and re-read the
|
||||||
|
procedure from the database each time it is called. (And in fact, that's
|
||||||
|
the way the earliest implementation will work.)
|
||||||
|
However, this is not very efficient, and we can do better. The full
|
||||||
|
implementation should work like this:
|
||||||
|
|
||||||
|
1) Upon creation time, parse and store the procedure. Note that we still
|
||||||
|
need to parse it to catch syntax errors, but we can't check if called
|
||||||
|
procedures exists for instance.
|
||||||
|
2) Upon first CALL, read from the database, parse it, and cache the
|
||||||
|
resulting Lex in memory. This time we can do more error checking.
|
||||||
|
3) Upon subsequent CALLs, use the cached Lex.
|
||||||
|
|
||||||
|
Note that this implies that the Lex structure with its sphead must be
|
||||||
|
reentrant, that is, reusable and shareable between different threads
|
||||||
|
and calls. The runtime state for a procedure is kept in the sp_rcontext
|
||||||
|
in THD.
|
||||||
|
|
||||||
|
The mechanisms of storing, finding, and dropping procedures are
|
||||||
|
encapsulated in the files sp.{cc,h}.
|
||||||
|
|
||||||
|
|
||||||
|
- CALLing a procedure
|
||||||
|
|
||||||
|
A CALL is parsed just like any statement. The resulting Lex has the
|
||||||
|
sql_command SQLCOM_CALL, the procedure's name and the parameters are
|
||||||
|
pushed to the Lex' value_list.
|
||||||
|
|
||||||
|
sql_parse.cc:mysql_execute_command() then uses sp.cc:sp_find() to
|
||||||
|
get the sp_head for the procedure (which may have been read from the
|
||||||
|
database or feetched from the in-memory cache) and calls the sp_head's
|
||||||
|
method execute().
|
||||||
|
Note: It's important that substatements called by the procedure do not
|
||||||
|
do send_ok(). Fortunately, there is a flag in THD->net to disable
|
||||||
|
this during CALLs. If a substatement fails, it will however send
|
||||||
|
an error back to the client, so the CALL mechanism must return
|
||||||
|
immediately and without sending an error.
|
||||||
|
|
||||||
|
The sp_head::execute() method works as follows:
|
||||||
|
|
||||||
|
1) Keep a pointer to the old runtime context in THD (if any)
|
||||||
|
2) Create a new runtime context. The information about the required size
|
||||||
|
is in sp_head's parse time context.
|
||||||
|
3) Push each parameter (from the CALL's Lex->value_list) to the new
|
||||||
|
context. If it's an OUT or INOUT parameter, the parameter's offset
|
||||||
|
in the caller's frame is set in the new context as well.
|
||||||
|
4) For each instruction, call its execute() method.
|
||||||
|
The result is a pointer to the next instruction to execute (or NULL)
|
||||||
|
if an error occured.
|
||||||
|
5) On success, set the new values of the OUT and INOUT parameters in
|
||||||
|
the caller's frame.
|
||||||
|
|
||||||
|
- USE database
|
||||||
|
|
||||||
|
Before executing the instruction we also keeps the current default
|
||||||
|
database (if any). If this was changed during execution (i.e. a "USE"
|
||||||
|
statement has been executed), we restore the current database to the
|
||||||
|
original.
|
||||||
|
|
||||||
|
This is the most useful way to handle USE in procedures. If we didn't,
|
||||||
|
the caller would find himself in a different database after calling
|
||||||
|
a function, which can be confusing.
|
||||||
|
Restoring the database also gives full freedom to the procedure writer:
|
||||||
|
- It's possible to write "general" procedures that are independent of
|
||||||
|
the actual database name.
|
||||||
|
- It's possible to write procedures that work on a particular database
|
||||||
|
by calling USE, without having to use fully qualified table names
|
||||||
|
everywhere (which doesn't help if you want to call other, "general",
|
||||||
|
procedures anyway).
|
||||||
|
|
||||||
|
- Evaluating Items
|
||||||
|
|
||||||
|
There are three occasions where we need to evaluate an expression:
|
||||||
|
|
||||||
|
- When SETing a variable
|
||||||
|
- When CALLing a procedure
|
||||||
|
- When testing an expression for a branch (in IF, WHILE, etc)
|
||||||
|
|
||||||
|
The semantics in stored procedures is "call-by-value", so we have to
|
||||||
|
evaluate any "func" Items at the point of the CALL or SET, otherwise
|
||||||
|
we would get a kind of "lazy" evaluation with unexpected results with
|
||||||
|
respect to OUT parameters for instance.
|
||||||
|
For this the support function, sp_head.cc:eval_func_item() is needed.
|
||||||
|
|
||||||
|
|
||||||
|
- Calling a FUNCTION
|
||||||
|
|
||||||
|
Functions don't have an explicit call keyword like procedures. Instead,
|
||||||
|
they appear in expressions with the conventional syntax "fun(arg, ...)".
|
||||||
|
The problem is that we already have User Defined Functions (UDFs) which
|
||||||
|
are called the same way. A UDF is detected by the lexical analyzer (not
|
||||||
|
the parser!), in the find_keyword() function, and returns a UDF_*_FUNC
|
||||||
|
or UDA_*_SUM token with the udf_func object as the yylval.
|
||||||
|
|
||||||
|
So, stored functions must be handled in a simpilar way, and as a
|
||||||
|
consequence, UDFs and functions must not have the same name.
|
||||||
|
|
||||||
|
- Detecting and parsing a FUNCTION invokation
|
||||||
|
|
||||||
|
The existance of UDFs are checked during the lexical analysis (in
|
||||||
|
sql_lex.cc:find_keyword()). This has the drawback that they must
|
||||||
|
exist before they are refered to, which was ok before SPs existed,
|
||||||
|
but then it becomes a problem. The first implementation of SP FUNCTIONs
|
||||||
|
will work the same way, but this should be fixed a.s.a.p. (This will
|
||||||
|
required some reworking of the way UDFs are handled, which is why it's
|
||||||
|
not done from the start.)
|
||||||
|
For the time being, a FUNCTION is detected the same way, and returns
|
||||||
|
the token SP_FUNC. During the parsing we only check for the *existance*
|
||||||
|
of the function, we don't parse it, since wa can't call the parser
|
||||||
|
recursively.
|
||||||
|
|
||||||
|
When encountering a SP_FUNC with parameters in the expression parser,
|
||||||
|
an instance of the new Item_func_sp class is created. Unlike UDFs, we
|
||||||
|
don't have different classes for different return types, since we at
|
||||||
|
this point don't know the type.
|
||||||
|
|
||||||
|
- Collecting FUNCTIONs to invoke
|
||||||
|
|
||||||
|
A FUNCTION differs from a PROCEDURE in one important aspect: Whereas a
|
||||||
|
PROCEDURE is CALLed as statement by itself, a FUNCTION is invoked
|
||||||
|
"on-the-fly" during the execution of *another* statement.
|
||||||
|
This makes things a lot more complicated compared to CALL:
|
||||||
|
- We can't read and parse the FUNCTION from the mysql.proc table at the
|
||||||
|
point of invokation; the server requires that all tables used are
|
||||||
|
opened and locked at the beginning of the query execution.
|
||||||
|
One "obvious" solution would be to simply push "mysql.proc" to the list
|
||||||
|
of tables used by the query, but this implies a "join" with this table
|
||||||
|
if the query is a select, so it doesn't work (and we can't exclude this
|
||||||
|
table easily; since a priviledged used might in fact want to search
|
||||||
|
the proc table).
|
||||||
|
Another solution would of course be to allow the opening and closing
|
||||||
|
of the mysql.proc table during a query execution, but this it not
|
||||||
|
possible at the present.
|
||||||
|
|
||||||
|
So, the solution is to collect the names of the refered FUNCTIONs during
|
||||||
|
parsing in the lex.
|
||||||
|
Then, before doing anything else in mysql_execute_command(), read all
|
||||||
|
functions from the database an keep them in the THD, where the function
|
||||||
|
sp_find_function() can find them during the execution.
|
||||||
|
Note: Even when a global in-memory cache is implemented, we must still
|
||||||
|
make sure that the functions are indeed read and cached at this point.
|
||||||
|
The code that read and cache functions from the database must also be
|
||||||
|
invoked recursively for each read FUNCTION to make sure we have *all* the
|
||||||
|
functions we need.
|
||||||
|
|
||||||
|
In the absence of the real in-memory cache for SPs, a temporary solution
|
||||||
|
has been implemented with a per-THD cache for just FUNCTIONs. This is
|
||||||
|
handled by the functions
|
||||||
|
|
||||||
|
void sp_add_fun_to_lex(LEX *lex, LEX_STRING fun);
|
||||||
|
void sp_merge_funs(LEX *dst, LEX *src);
|
||||||
|
int sp_cache_functions(THD *thd, LEX *lex);
|
||||||
|
void sp_clear_function_cache(THD *thd);
|
||||||
|
|
||||||
|
in sp.cc.
|
||||||
|
|
||||||
|
|
||||||
|
- Parsing DROP PROCEDURE/FUNCTION
|
||||||
|
|
||||||
|
The procedure name is pushed to Lex->value_list.
|
||||||
|
The sql_command code for the result of parsing a is
|
||||||
|
SQLCOM_DROP_PROCEDURE/SQLCOM_DROP_FUNCTION.
|
||||||
|
|
||||||
|
Dropping is done by simply getting the procedure with the sp_find()
|
||||||
|
function and calling sp_drop() (both in sp.{cc,h}).
|
||||||
|
|
||||||
|
DROP PROCEDURE/FUNCTION also supports the non-standard "IF EXISTS",
|
||||||
|
analogous to other DROP statements in MySQL.
|
||||||
|
|
||||||
|
|
||||||
|
- Class and function APIs
|
||||||
|
|
||||||
|
- The parser context: sp_pcontext.h
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sp_param_in,
|
||||||
|
sp_param_out,
|
||||||
|
sp_param_inout
|
||||||
|
} sp_param_mode_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Item_string *name;
|
||||||
|
enum enum_field_types type;
|
||||||
|
sp_param_mode_t mode;
|
||||||
|
uint offset; // Offset in current frame
|
||||||
|
my_bool isset;
|
||||||
|
} sp_pvar_t;
|
||||||
|
|
||||||
|
class sp_pcontext
|
||||||
|
{
|
||||||
|
sp_pcontext();
|
||||||
|
|
||||||
|
// Return the maximum frame size
|
||||||
|
uint max_framesize();
|
||||||
|
|
||||||
|
// Return the current frame size
|
||||||
|
uint current_framesize();
|
||||||
|
|
||||||
|
// Return the number of parameters
|
||||||
|
uint params();
|
||||||
|
|
||||||
|
// Set the number of parameters to the current frame size
|
||||||
|
void set_params();
|
||||||
|
|
||||||
|
// Set type of the variable at offset 'i' in the frame
|
||||||
|
void set_type(uint i, enum enum_field_types type);
|
||||||
|
|
||||||
|
// Mark the i:th variable to "set" (i.e. having a value) with
|
||||||
|
// 'val' true.
|
||||||
|
void set_isset(uint i, my_bool val);
|
||||||
|
|
||||||
|
// Push the variable 'name' to the frame.
|
||||||
|
void push(LEX_STRING *name,
|
||||||
|
enum enum_field_types type, sp_param_mode_t mode);
|
||||||
|
|
||||||
|
// Pop 'num' variables from the frame.
|
||||||
|
void pop(uint num = 1);
|
||||||
|
|
||||||
|
// Find variable by name
|
||||||
|
sp_pvar_t *find_pvar(LEX_STRING *name);
|
||||||
|
|
||||||
|
// Find variable by index
|
||||||
|
sp_pvar_t *find_pvar(uint i);
|
||||||
|
|
||||||
|
// Push label 'name' of instruction index 'ip' to the label context
|
||||||
|
sp_label_t *push_label(char *name, uint ip);
|
||||||
|
|
||||||
|
// Find label 'name' in the context
|
||||||
|
sp_label_t *find_label(char *name);
|
||||||
|
|
||||||
|
// Return the last pushed label
|
||||||
|
sp_label_t *last_label();
|
||||||
|
|
||||||
|
// Return and remove the last pushed label.
|
||||||
|
sp_label_t *pop_label();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- The run-time context (call frame): sp_rcontext.h
|
||||||
|
|
||||||
|
class sp_rcontext
|
||||||
|
{
|
||||||
|
// 'size' is the max size of the context
|
||||||
|
sp_rcontext(uint size);
|
||||||
|
|
||||||
|
// Push value (parameter) 'i' to the frame
|
||||||
|
void push_item(Item *i);
|
||||||
|
|
||||||
|
// Set slot 'idx' to value 'i'
|
||||||
|
void set_item(uint idx, Item *i);
|
||||||
|
|
||||||
|
// Return the item in slot 'idx'
|
||||||
|
Item *get_item(uint idx);
|
||||||
|
|
||||||
|
// Set the "out" index 'oidx' for slot 'idx. If it's an IN slot,
|
||||||
|
// use 'oidx' -1.
|
||||||
|
void set_oindex(uint idx, int oidx);
|
||||||
|
|
||||||
|
// Return the "out" index for slot 'idx'
|
||||||
|
int get_oindex(uint idx);
|
||||||
|
|
||||||
|
// Set the FUNCTION result
|
||||||
|
void set_result(Item *i);
|
||||||
|
|
||||||
|
// Get the FUNCTION result
|
||||||
|
Item *get_result();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- The procedure: sp_head.h
|
||||||
|
|
||||||
|
#define TYPE_ENUM_FUNCTION 1
|
||||||
|
#define TYPE_ENUM_PROCEDURE 2
|
||||||
|
|
||||||
|
class sp_head
|
||||||
|
{
|
||||||
|
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
|
||||||
|
|
||||||
|
sp_head(LEX_STRING *name, LEX*);
|
||||||
|
|
||||||
|
// Store this procedure in the database. This is a wrapper around
|
||||||
|
// the function sp_create_procedure().
|
||||||
|
int create(THD *);
|
||||||
|
|
||||||
|
// Invoke a FUNCTION
|
||||||
|
int
|
||||||
|
execute_function(THD *thd, Item **args, uint argcount, Item **resp);
|
||||||
|
|
||||||
|
// CALL a PROCEDURE
|
||||||
|
int
|
||||||
|
execute_procedure(THD *thd, List<Item> *args);
|
||||||
|
|
||||||
|
// Add the instruction to this procedure.
|
||||||
|
void add_instr(sp_instr *);
|
||||||
|
|
||||||
|
// Return the number of instructions.
|
||||||
|
uint instructions();
|
||||||
|
|
||||||
|
// Resets lex in 'thd' and keeps a copy of the old one.
|
||||||
|
void reset_lex(THD *);
|
||||||
|
|
||||||
|
// Restores lex in 'thd' from our copy, but keeps some status from the
|
||||||
|
// one in 'thd', like ptr, tables, fields, etc.
|
||||||
|
void restore_lex(THD *);
|
||||||
|
|
||||||
|
// Put the instruction on the backpatch list, associated with
|
||||||
|
// the label.
|
||||||
|
void push_backpatch(sp_instr *, struct sp_label *);
|
||||||
|
|
||||||
|
// Update all instruction with this label in the backpatch list to
|
||||||
|
// the current position.
|
||||||
|
void backpatch(struct sp_label *);
|
||||||
|
}
|
||||||
|
|
||||||
|
- Instructions
|
||||||
|
|
||||||
|
- The base class:
|
||||||
|
class sp_instr
|
||||||
|
{
|
||||||
|
// 'ip' is the index of this instruction
|
||||||
|
sp_instr(uint ip);
|
||||||
|
|
||||||
|
// Execute this instrution.
|
||||||
|
// '*nextp' will be set to the index of the next instruction
|
||||||
|
// to execute. (For most instruction this will be the
|
||||||
|
// instruction following this one.)
|
||||||
|
// Returns 0 on success, non-zero if some error occured.
|
||||||
|
virtual int execute(THD *, uint *nextp)
|
||||||
|
}
|
||||||
|
|
||||||
|
- Statement instruction:
|
||||||
|
class sp_instr_stmt : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_stmt(uint ip);
|
||||||
|
|
||||||
|
int execute(THD *, uint *nextp);
|
||||||
|
|
||||||
|
// Set the statement's Lex
|
||||||
|
void set_lex(LEX *);
|
||||||
|
|
||||||
|
// Return the statement's Lex
|
||||||
|
LEX *get_lex();
|
||||||
|
}
|
||||||
|
|
||||||
|
- SET instruction:
|
||||||
|
class sp_instr_set : public sp_instr
|
||||||
|
{
|
||||||
|
// 'offset' is the variable's frame offset, 'val' the value,
|
||||||
|
// and 'type' the variable type.
|
||||||
|
sp_instr_set(uint ip,
|
||||||
|
uint offset, Item *val, enum enum_field_types type);
|
||||||
|
|
||||||
|
int execute(THD *, uint *nextp);
|
||||||
|
}
|
||||||
|
|
||||||
|
- Unconditional jump
|
||||||
|
class sp_instr_jump : public sp_instr
|
||||||
|
{
|
||||||
|
// No destination, must be set.
|
||||||
|
sp_instr_jump(uint ip);
|
||||||
|
|
||||||
|
// 'dest' is the destination instruction index.
|
||||||
|
sp_instr_jump(uint ip, uint dest);
|
||||||
|
|
||||||
|
int execute(THD *, uint *nextp);
|
||||||
|
|
||||||
|
// Set the destination instruction 'dest'.
|
||||||
|
void set_destination(uint dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
- Conditional jump
|
||||||
|
class sp_instr_jump_if_not : public sp_instr_jump
|
||||||
|
{
|
||||||
|
// Jump if 'i' evaluates to false. Destination not set yet.
|
||||||
|
sp_instr_jump_if_not(uint ip, Item *i);
|
||||||
|
|
||||||
|
// Jump to 'dest' if 'i' evaluates to false.
|
||||||
|
sp_instr_jump_if_not(uint ip, Item *i, uint dest)
|
||||||
|
|
||||||
|
int execute(THD *, uint *nextp);
|
||||||
|
}
|
||||||
|
|
||||||
|
- Return a function value
|
||||||
|
class sp_instr_return : public sp_instr
|
||||||
|
{
|
||||||
|
// Return the value 'val'
|
||||||
|
sp_instr_return(uint ip, Item *val, enum enum_field_types type);
|
||||||
|
|
||||||
|
int execute(THD *thd, uint *nextp);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- Utility functions: sp.h
|
||||||
|
|
||||||
|
#define SP_OK 0
|
||||||
|
#define SP_KEY_NOT_FOUND -1
|
||||||
|
#define SP_OPEN_TABLE_FAILED -2
|
||||||
|
#define SP_WRITE_ROW_FAILED -3
|
||||||
|
#define SP_DELETE_ROW_FAILED -4
|
||||||
|
#define SP_GET_FIELD_FAILED -5
|
||||||
|
#define SP_PARSE_ERROR -6
|
||||||
|
|
||||||
|
// Finds a stored procedure given its name. Returns NULL if not found.
|
||||||
|
sp_head *sp_find_procedure(THD *, LEX_STRING *name);
|
||||||
|
|
||||||
|
// Store the procedure 'name' in the database. 'def' is the complete
|
||||||
|
// definition string ("create procedure ...").
|
||||||
|
int sp_create_procedure(THD *,
|
||||||
|
char *name, uint namelen,
|
||||||
|
char *def, uint deflen);
|
||||||
|
|
||||||
|
// Drop the procedure 'name' from the database.
|
||||||
|
int sp_drop_procedure(THD *, char *name, uint namelen);
|
||||||
|
|
||||||
|
// Finds a stored function given its name. Returns NULL if not found.
|
||||||
|
sp_head *sp_find_function(THD *, LEX_STRING *name);
|
||||||
|
|
||||||
|
// Store the function 'name' in the database. 'def' is the complete
|
||||||
|
// definition string ("create function ...").
|
||||||
|
int sp_create_function(THD *,
|
||||||
|
char *name, uint namelen,
|
||||||
|
char *def, uint deflen);
|
||||||
|
|
||||||
|
// Drop the function 'name' from the database.
|
||||||
|
int sp_drop_function(THD *, char *name, uint namelen);
|
||||||
|
|
||||||
|
--
|
99
Docs/sp-implemented.txt
Normal file
99
Docs/sp-implemented.txt
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
Stored Procedures implemented 2003-03-07:
|
||||||
|
|
||||||
|
|
||||||
|
Summary of Not Yet Implemented:
|
||||||
|
|
||||||
|
- SQL queries (like SELECT, INSERT, UPDATE etc) in FUNCTION bodies
|
||||||
|
- External languages
|
||||||
|
- Access control
|
||||||
|
- Routine characteristics (mostly used for external languages)
|
||||||
|
- Prepared SP caching; SPs are fetched and reparsed at each call
|
||||||
|
- SQL-99 COMMIT (related to BEGIN/END)
|
||||||
|
- DECLARE CURSOR ...
|
||||||
|
- FOR-loops (as it requires cursors)
|
||||||
|
- CASCADE/RESTRICT for ALTER and DROP
|
||||||
|
- ALTER/DROP METHOD (as it implies User Defined Types)
|
||||||
|
- CONDITIONs, HANDLERs, SIGNAL and RESIGNAL (will probably not be implemented)
|
||||||
|
|
||||||
|
|
||||||
|
Summary of what's implemented:
|
||||||
|
|
||||||
|
- SQL PROCEDUREs/FUNCTIONs (CREATE/DROP)
|
||||||
|
- CALL
|
||||||
|
- DECLARE of local variables
|
||||||
|
- BEGIN/END, SET, CASE, IF, LOOP, WHILE, REPEAT, ITERATE, LEAVE
|
||||||
|
- SELECT INTO local variables
|
||||||
|
- "Non-query" FUNCTIONs only
|
||||||
|
|
||||||
|
|
||||||
|
List of what's implemented:
|
||||||
|
|
||||||
|
- CREATE PROCEDURE|FUNCTION name ( args ) body
|
||||||
|
No routine characteristics yet.
|
||||||
|
|
||||||
|
- ALTER PROCEDURE|FUNCTION name ...
|
||||||
|
Is parsed, but a no-op (as there are no characteristics implemented yet).
|
||||||
|
CASCADE/RESTRICT is not implemented (and CASCADE probably will not be).
|
||||||
|
|
||||||
|
- DROP PROCEDURE|FUNCTION [IF EXISTS] name
|
||||||
|
CASCADE/RESTRICT is not implemented (and CASCADE probably will not be).
|
||||||
|
|
||||||
|
- CALL name (args)
|
||||||
|
OUT and INOUT parameters are only supported for local variables, and
|
||||||
|
therefore only useful when calling such procedures from within another
|
||||||
|
procedure.
|
||||||
|
Note: For the time being, when a procedure with OUT/INOUT parameter is
|
||||||
|
called, the out values are silently discarded. In the future, this
|
||||||
|
will either generate an error message, or it might even work to
|
||||||
|
call all procedures from the top-level.
|
||||||
|
|
||||||
|
- Function/Procedure body:
|
||||||
|
- BEGIN/END
|
||||||
|
Is parsed, but not the real thing with (optional) transaction
|
||||||
|
control, it only serves as block syntax for multiple statements (and
|
||||||
|
local variable binding).
|
||||||
|
Note: Multiple statements requires a client that can send bodies
|
||||||
|
containing ";". This is handled in the CLI clients mysql and
|
||||||
|
mysqltest with the "delimiter" command. Changing the end-of-query
|
||||||
|
delimiter ";" to for instance "|" allows ";" to be used in the
|
||||||
|
routine body.
|
||||||
|
- SET of local variables
|
||||||
|
Implemented as part of the pre-existing SET syntax. This allows an
|
||||||
|
extended syntax of "SET a=x, b=y, ..." where different variable types
|
||||||
|
(SP local and global) can be mixed. This also allows combinations
|
||||||
|
of local variables and some options that only make sense for
|
||||||
|
global/system variables; in that case the options are accepted but
|
||||||
|
ignored.
|
||||||
|
- The flow control constructs: CASE, IF, LOOP, WHILE, ITERATE and LEAVE
|
||||||
|
are fully implemented.
|
||||||
|
- SELECT ... INTO local variables (as well as global session variables)
|
||||||
|
is implemented. (Note: This is not SQL-99 feature, but common in other
|
||||||
|
databases.)
|
||||||
|
- A FUNCTION can have flow control contructs, but must not contain
|
||||||
|
an SQL query, like SELECT, INSERT, UPDATE, etc. The reason is that it's
|
||||||
|
hard to allow this is that a FUNCTION is executed as part of another
|
||||||
|
query (unlike a PROCEDURE, which is called as a statement). The table
|
||||||
|
locking scheme used makes it difficult to allow "subqueries" during
|
||||||
|
FUNCTION invokation.
|
||||||
|
|
||||||
|
|
||||||
|
Closed questions:
|
||||||
|
|
||||||
|
- What is the expected result when creating a procedure with a name that
|
||||||
|
already exists? An error or overwrite?
|
||||||
|
Answer: Error
|
||||||
|
|
||||||
|
- Do PROCEDUREs and FUNCTIONs share namespace or not? I think not, but the
|
||||||
|
we need to flag the type in the mysql.proc table and the name alone is
|
||||||
|
not a unique key any more, or, we have separate tables.
|
||||||
|
(Unfortunately, mysql.func is already taken. Use "sfunc" and maybe even
|
||||||
|
rename "proc" into "sproc" while we still can, for consistency?)
|
||||||
|
Answer: Same tables, with an additional key-field for the type.
|
||||||
|
|
||||||
|
|
||||||
|
Open questions/issues:
|
||||||
|
|
||||||
|
- SQL-99 variables and parameters are typed. For the present we don't do
|
||||||
|
any type checking, since this is the way MySQL works. I still don't know
|
||||||
|
if we should keep it this way, or implement type checking. Possibly we
|
||||||
|
should have optional, uset-settable, type checking.
|
@ -104,6 +104,7 @@ extern "C" {
|
|||||||
#include "completion_hash.h"
|
#include "completion_hash.h"
|
||||||
|
|
||||||
#define PROMPT_CHAR '\\'
|
#define PROMPT_CHAR '\\'
|
||||||
|
#define DEFAULT_DELIMITER ';'
|
||||||
|
|
||||||
typedef struct st_status
|
typedef struct st_status
|
||||||
{
|
{
|
||||||
@ -156,6 +157,7 @@ static char pager[FN_REFLEN], outfile[FN_REFLEN];
|
|||||||
static FILE *PAGER, *OUTFILE;
|
static FILE *PAGER, *OUTFILE;
|
||||||
static MEM_ROOT hash_mem_root;
|
static MEM_ROOT hash_mem_root;
|
||||||
static uint prompt_counter;
|
static uint prompt_counter;
|
||||||
|
static char delimiter= DEFAULT_DELIMITER;
|
||||||
|
|
||||||
#ifdef HAVE_SMEM
|
#ifdef HAVE_SMEM
|
||||||
static char *shared_memory_base_name=0;
|
static char *shared_memory_base_name=0;
|
||||||
@ -183,7 +185,7 @@ static int com_quit(String *str,char*),
|
|||||||
com_use(String *str,char*), com_source(String *str, char*),
|
com_use(String *str,char*), com_source(String *str, char*),
|
||||||
com_rehash(String *str, char*), com_tee(String *str, char*),
|
com_rehash(String *str, char*), com_tee(String *str, char*),
|
||||||
com_notee(String *str, char*),
|
com_notee(String *str, char*),
|
||||||
com_prompt(String *str, char*);
|
com_prompt(String *str, char*), com_delimiter(String *str, char*);
|
||||||
|
|
||||||
#ifdef USE_POPEN
|
#ifdef USE_POPEN
|
||||||
static int com_nopager(String *str, char*), com_pager(String *str, char*),
|
static int com_nopager(String *str, char*), com_pager(String *str, char*),
|
||||||
@ -251,7 +253,8 @@ static COMMANDS commands[] = {
|
|||||||
"Set outfile [to_outfile]. Append everything into given outfile." },
|
"Set outfile [to_outfile]. Append everything into given outfile." },
|
||||||
{ "use", 'u', com_use, 1,
|
{ "use", 'u', com_use, 1,
|
||||||
"Use another database. Takes database name as argument." },
|
"Use another database. Takes database name as argument." },
|
||||||
|
{ "delimiter", 'd', com_delimiter, 1,
|
||||||
|
"Set query delimiter. " },
|
||||||
/* Get bash-like expansion for some commands */
|
/* Get bash-like expansion for some commands */
|
||||||
{ "create table", 0, 0, 0, ""},
|
{ "create table", 0, 0, 0, ""},
|
||||||
{ "create database", 0, 0, 0, ""},
|
{ "create database", 0, 0, 0, ""},
|
||||||
@ -921,7 +924,7 @@ static COMMANDS *find_command (char *name,char cmd_char)
|
|||||||
{
|
{
|
||||||
while (my_isspace(charset_info,*name))
|
while (my_isspace(charset_info,*name))
|
||||||
name++;
|
name++;
|
||||||
if (strchr(name,';') || strstr(name,"\\g"))
|
if (strchr(name, delimiter) || strstr(name,"\\g"))
|
||||||
return ((COMMANDS *) 0);
|
return ((COMMANDS *) 0);
|
||||||
if ((end=strcont(name," \t")))
|
if ((end=strcont(name," \t")))
|
||||||
{
|
{
|
||||||
@ -999,7 +1002,7 @@ static bool add_line(String &buffer,char *line,char *in_string,
|
|||||||
return 1; // Quit
|
return 1; // Quit
|
||||||
if (com->takes_params)
|
if (com->takes_params)
|
||||||
{
|
{
|
||||||
for (pos++ ; *pos && *pos != ';' ; pos++) ; // Remove parameters
|
for (pos++ ; *pos && *pos != delimiter; pos++) ; // Remove parameters
|
||||||
if (!*pos)
|
if (!*pos)
|
||||||
pos--;
|
pos--;
|
||||||
}
|
}
|
||||||
@ -1015,7 +1018,7 @@ static bool add_line(String &buffer,char *line,char *in_string,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!*ml_comment && inchar == ';' && !*in_string)
|
else if (!*ml_comment && inchar == delimiter && !*in_string)
|
||||||
{ // ';' is end of command
|
{ // ';' is end of command
|
||||||
if (out != line)
|
if (out != line)
|
||||||
buffer.append(line,(uint) (out-line)); // Add this line
|
buffer.append(line,(uint) (out-line)); // Add this line
|
||||||
@ -1532,7 +1535,7 @@ com_help(String *buffer __attribute__((unused)),
|
|||||||
for (i = 0; commands[i].name; i++)
|
for (i = 0; commands[i].name; i++)
|
||||||
{
|
{
|
||||||
if (commands[i].func)
|
if (commands[i].func)
|
||||||
tee_fprintf(stdout, "%s\t(\\%c)\t%s\n", commands[i].name,
|
tee_fprintf(stdout, "%-10s(\\%c)\t%s\n", commands[i].name,
|
||||||
commands[i].cmd_char, commands[i].doc);
|
commands[i].cmd_char, commands[i].doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2369,6 +2372,37 @@ static int com_source(String *buffer, char *line)
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ARGSUSED */
|
||||||
|
static int
|
||||||
|
com_delimiter(String *buffer __attribute__((unused)), char *line)
|
||||||
|
{
|
||||||
|
char *tmp;
|
||||||
|
char buff[256];
|
||||||
|
|
||||||
|
if (strlen(line)> 255)
|
||||||
|
{
|
||||||
|
put_info("'DELIMITER' command was too long.", INFO_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
bzero(buff, sizeof(buff));
|
||||||
|
strmov(buff, line);
|
||||||
|
tmp= get_arg(buff, 0);
|
||||||
|
|
||||||
|
if (!tmp || !*tmp)
|
||||||
|
{
|
||||||
|
put_info("DELIMITER must be followed by a 'delimiter' char", INFO_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(tmp)> 1)
|
||||||
|
{
|
||||||
|
put_info("Argument must be one char", INFO_ERROR);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
delimiter= *tmp;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ARGSUSED */
|
/* ARGSUSED */
|
||||||
static int
|
static int
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
|
|
||||||
#define SLAVE_POLL_INTERVAL 300000 /* 0.3 of a sec */
|
#define SLAVE_POLL_INTERVAL 300000 /* 0.3 of a sec */
|
||||||
|
|
||||||
|
#define DEFAULT_DELIMITER ';'
|
||||||
|
|
||||||
enum {OPT_MANAGER_USER=256,OPT_MANAGER_HOST,OPT_MANAGER_PASSWD,
|
enum {OPT_MANAGER_USER=256,OPT_MANAGER_HOST,OPT_MANAGER_PASSWD,
|
||||||
OPT_MANAGER_PORT,OPT_MANAGER_WAIT_TIMEOUT, OPT_SKIP_SAFEMALLOC,
|
OPT_MANAGER_PORT,OPT_MANAGER_WAIT_TIMEOUT, OPT_SKIP_SAFEMALLOC,
|
||||||
@ -126,6 +127,8 @@ static int block_ok_stack[BLOCK_STACK_DEPTH];
|
|||||||
static uint global_expected_errno[MAX_EXPECTED_ERRORS], global_expected_errors;
|
static uint global_expected_errno[MAX_EXPECTED_ERRORS], global_expected_errors;
|
||||||
|
|
||||||
static CHARSET_INFO *charset_info= &my_charset_latin1;
|
static CHARSET_INFO *charset_info= &my_charset_latin1;
|
||||||
|
static char delimiter= DEFAULT_DELIMITER;
|
||||||
|
|
||||||
DYNAMIC_ARRAY q_lines;
|
DYNAMIC_ARRAY q_lines;
|
||||||
|
|
||||||
#include "sslopt-vars.h"
|
#include "sslopt-vars.h"
|
||||||
@ -202,7 +205,7 @@ Q_SERVER_START, Q_SERVER_STOP,Q_REQUIRE_MANAGER,
|
|||||||
Q_WAIT_FOR_SLAVE_TO_STOP,
|
Q_WAIT_FOR_SLAVE_TO_STOP,
|
||||||
Q_REQUIRE_VERSION,
|
Q_REQUIRE_VERSION,
|
||||||
Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS,
|
Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS,
|
||||||
Q_ENABLE_INFO, Q_DISABLE_INFO,
|
Q_ENABLE_INFO, Q_DISABLE_INFO, Q_DELIMITER,
|
||||||
Q_UNKNOWN, /* Unknown command. */
|
Q_UNKNOWN, /* Unknown command. */
|
||||||
Q_COMMENT, /* Comments, ignored. */
|
Q_COMMENT, /* Comments, ignored. */
|
||||||
Q_COMMENT_WITH_COMMAND
|
Q_COMMENT_WITH_COMMAND
|
||||||
@ -265,6 +268,7 @@ const char *command_names[]=
|
|||||||
"disable_warnings",
|
"disable_warnings",
|
||||||
"enable_info",
|
"enable_info",
|
||||||
"disable_info",
|
"disable_info",
|
||||||
|
"delimiter",
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1532,6 +1536,16 @@ int do_while(struct st_query* q)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int do_delimiter(char *p)
|
||||||
|
{
|
||||||
|
while (*p && my_isspace(charset_info,*p))
|
||||||
|
p++;
|
||||||
|
if (!*p)
|
||||||
|
die("Missing delimiter character\n");
|
||||||
|
delimiter=*p;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int safe_copy_unescape(char* dest, char* src, int size)
|
int safe_copy_unescape(char* dest, char* src, int size)
|
||||||
{
|
{
|
||||||
@ -1611,7 +1625,7 @@ int read_line(char* buf, int size)
|
|||||||
switch(state) {
|
switch(state) {
|
||||||
case R_NORMAL:
|
case R_NORMAL:
|
||||||
/* Only accept '{' in the beginning of a line */
|
/* Only accept '{' in the beginning of a line */
|
||||||
if (c == ';')
|
if (c == delimiter)
|
||||||
{
|
{
|
||||||
*p = 0;
|
*p = 0;
|
||||||
return 0;
|
return 0;
|
||||||
@ -1651,7 +1665,7 @@ int read_line(char* buf, int size)
|
|||||||
*buf = 0;
|
*buf = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (c == ';' || c == '{')
|
else if (c == delimiter || c == '{')
|
||||||
{
|
{
|
||||||
*p = 0;
|
*p = 0;
|
||||||
return 0;
|
return 0;
|
||||||
@ -1671,7 +1685,7 @@ int read_line(char* buf, int size)
|
|||||||
state = R_ESC_SLASH_Q1;
|
state = R_ESC_SLASH_Q1;
|
||||||
break;
|
break;
|
||||||
case R_ESC_Q_Q1:
|
case R_ESC_Q_Q1:
|
||||||
if (c == ';')
|
if (c == delimiter)
|
||||||
{
|
{
|
||||||
*p = 0;
|
*p = 0;
|
||||||
return 0;
|
return 0;
|
||||||
@ -1692,7 +1706,7 @@ int read_line(char* buf, int size)
|
|||||||
state = R_ESC_SLASH_Q2;
|
state = R_ESC_SLASH_Q2;
|
||||||
break;
|
break;
|
||||||
case R_ESC_Q_Q2:
|
case R_ESC_Q_Q2:
|
||||||
if (c == ';')
|
if (c == delimiter)
|
||||||
{
|
{
|
||||||
*p = 0;
|
*p = 0;
|
||||||
return 0;
|
return 0;
|
||||||
@ -2545,6 +2559,9 @@ int main(int argc, char** argv)
|
|||||||
do_sync_with_master2("");
|
do_sync_with_master2("");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Q_DELIMITER:
|
||||||
|
do_delimiter(q->first_argument);
|
||||||
|
break;
|
||||||
case Q_COMMENT: /* Ignore row */
|
case Q_COMMENT: /* Ignore row */
|
||||||
case Q_COMMENT_WITH_COMMAND:
|
case Q_COMMENT_WITH_COMMAND:
|
||||||
break;
|
break;
|
||||||
|
@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script.
|
|||||||
AC_INIT(sql/mysqld.cc)
|
AC_INIT(sql/mysqld.cc)
|
||||||
AC_CANONICAL_SYSTEM
|
AC_CANONICAL_SYSTEM
|
||||||
# The Docs Makefile.am parses this line!
|
# The Docs Makefile.am parses this line!
|
||||||
AM_INIT_AUTOMAKE(mysql, 4.1.0-alpha)
|
AM_INIT_AUTOMAKE(mysql, 5.0.0-alpha)
|
||||||
AM_CONFIG_HEADER(config.h)
|
AM_CONFIG_HEADER(config.h)
|
||||||
|
|
||||||
PROTOCOL_VERSION=10
|
PROTOCOL_VERSION=10
|
||||||
|
@ -270,4 +270,16 @@
|
|||||||
#define ER_COLLATION_CHARSET_MISMATCH 1251
|
#define ER_COLLATION_CHARSET_MISMATCH 1251
|
||||||
#define ER_SLAVE_WAS_RUNNING 1252
|
#define ER_SLAVE_WAS_RUNNING 1252
|
||||||
#define ER_SLAVE_WAS_NOT_RUNNING 1253
|
#define ER_SLAVE_WAS_NOT_RUNNING 1253
|
||||||
#define ER_ERROR_MESSAGES 254
|
#define ER_SP_NO_RECURSIVE_CREATE 1254
|
||||||
|
#define ER_SP_ALREADY_EXISTS 1255
|
||||||
|
#define ER_SP_DOES_NOT_EXIST 1256
|
||||||
|
#define ER_SP_DROP_FAILED 1257
|
||||||
|
#define ER_SP_STORE_FAILED 1258
|
||||||
|
#define ER_SP_LILABEL_MISMATCH 1259
|
||||||
|
#define ER_SP_LABEL_REDEFINE 1260
|
||||||
|
#define ER_SP_LABEL_MISMATCH 1261
|
||||||
|
#define ER_SP_UNINIT_VAR 1262
|
||||||
|
#define ER_SP_BADSELECT 1263
|
||||||
|
#define ER_SP_BADRETURN 1264
|
||||||
|
#define ER_SP_BADQUERY 1265
|
||||||
|
#define ER_ERROR_MESSAGES 266
|
||||||
|
@ -55,7 +55,8 @@ sqlsources = convert.cc derror.cc field.cc field_conv.cc filesort.cc \
|
|||||||
sql_string.cc sql_table.cc sql_test.cc sql_udf.cc \
|
sql_string.cc sql_table.cc sql_test.cc sql_udf.cc \
|
||||||
sql_update.cc sql_yacc.cc table.cc thr_malloc.cc time.cc \
|
sql_update.cc sql_yacc.cc table.cc thr_malloc.cc time.cc \
|
||||||
unireg.cc uniques.cc stacktrace.c sql_union.cc hash_filo.cc \
|
unireg.cc uniques.cc stacktrace.c sql_union.cc hash_filo.cc \
|
||||||
spatial.cc gstream.cc sql_help.cc
|
spatial.cc gstream.cc sql_help.cc \
|
||||||
|
sp_head.cc sp_pcontext.cc sp.cc
|
||||||
|
|
||||||
EXTRA_DIST = lib_vio.c
|
EXTRA_DIST = lib_vio.c
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ c_t="" c_c=""
|
|||||||
c_hl="" c_hl=""
|
c_hl="" c_hl=""
|
||||||
c_hc="" c_hc=""
|
c_hc="" c_hc=""
|
||||||
c_clr="" c_clr=""
|
c_clr="" c_clr=""
|
||||||
|
c_p=""
|
||||||
|
|
||||||
# Check for old tables
|
# Check for old tables
|
||||||
if test ! -f $mdata/db.frm
|
if test ! -f $mdata/db.frm
|
||||||
@ -246,6 +247,17 @@ then
|
|||||||
c_hc="$c_hc comment='categories of help topics';"
|
c_hc="$c_hc comment='categories of help topics';"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test ! -f $mdata/proc.frm
|
||||||
|
then
|
||||||
|
c_p="$c_p CREATE TABLE proc ("
|
||||||
|
c_p="$c_p name char(64) binary DEFAULT '' NOT NULL,"
|
||||||
|
c_p="$c_p type enum('function','procedure') NOT NULL,"
|
||||||
|
c_p="$c_p body blob DEFAULT '' NOT NULL,"
|
||||||
|
c_p="$c_p PRIMARY KEY (name,type)"
|
||||||
|
c_p="$c_p )"
|
||||||
|
c_p="$c_p comment='Stored Procedures';"
|
||||||
|
fi
|
||||||
|
|
||||||
mysqld_boot=" $execdir/mysqld --no-defaults --bootstrap --skip-grant-tables \
|
mysqld_boot=" $execdir/mysqld --no-defaults --bootstrap --skip-grant-tables \
|
||||||
--basedir=$basedir --datadir=$ldata --skip-innodb --skip-bdb $EXTRA_ARG"
|
--basedir=$basedir --datadir=$ldata --skip-innodb --skip-bdb $EXTRA_ARG"
|
||||||
echo "running $mysqld_boot"
|
echo "running $mysqld_boot"
|
||||||
@ -270,6 +282,9 @@ $c_c
|
|||||||
$c_hl
|
$c_hl
|
||||||
$c_hc
|
$c_hc
|
||||||
$c_clr
|
$c_clr
|
||||||
|
|
||||||
|
$c_p
|
||||||
|
|
||||||
END_OF_DATA
|
END_OF_DATA
|
||||||
then
|
then
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -7,6 +7,7 @@ help_category
|
|||||||
help_relation
|
help_relation
|
||||||
help_topic
|
help_topic
|
||||||
host
|
host
|
||||||
|
proc
|
||||||
tables_priv
|
tables_priv
|
||||||
user
|
user
|
||||||
show tables;
|
show tables;
|
||||||
@ -22,6 +23,7 @@ help_category
|
|||||||
help_relation
|
help_relation
|
||||||
help_topic
|
help_topic
|
||||||
host
|
host
|
||||||
|
proc
|
||||||
tables_priv
|
tables_priv
|
||||||
user
|
user
|
||||||
show tables;
|
show tables;
|
||||||
@ -37,6 +39,7 @@ help_category
|
|||||||
help_relation
|
help_relation
|
||||||
help_topic
|
help_topic
|
||||||
host
|
host
|
||||||
|
proc
|
||||||
tables_priv
|
tables_priv
|
||||||
user
|
user
|
||||||
show tables;
|
show tables;
|
||||||
|
@ -64,3 +64,16 @@ use test_$1;
|
|||||||
create table t1 (c int);
|
create table t1 (c int);
|
||||||
insert into test_$1.t1 set test_$1.t1.c = '1';
|
insert into test_$1.t1 set test_$1.t1.c = '1';
|
||||||
drop database test_$1;
|
drop database test_$1;
|
||||||
|
use test;
|
||||||
|
drop table if exists t1,t2,t3;
|
||||||
|
create table t1(id1 int not null auto_increment primary key, t char(12));
|
||||||
|
create table t2(id2 int not null, t char(12));
|
||||||
|
create table t3(id3 int not null, t char(12), index(id3));
|
||||||
|
select count(*) from t2;
|
||||||
|
count(*)
|
||||||
|
500
|
||||||
|
insert into t2 select t1.* from t1, t2 t, t3 where t1.id1 = t.id2 and t.id2 = t3.id3;
|
||||||
|
select count(*) from t2;
|
||||||
|
count(*)
|
||||||
|
25500
|
||||||
|
drop table if exists t1,t2,t3;
|
||||||
|
@ -127,6 +127,7 @@ insert into t1 values (1);
|
|||||||
show open tables;
|
show open tables;
|
||||||
Database Table In_use Name_locked
|
Database Table In_use Name_locked
|
||||||
test t1 0 0
|
test t1 0 0
|
||||||
|
mysql proc 0 0
|
||||||
drop table t1;
|
drop table t1;
|
||||||
create table t1 (a int not null, b VARCHAR(10), INDEX (b) ) AVG_ROW_LENGTH=10 CHECKSUM=1 COMMENT="test" TYPE=MYISAM MIN_ROWS=10 MAX_ROWS=100 PACK_KEYS=1 DELAY_KEY_WRITE=1 ROW_FORMAT=fixed;
|
create table t1 (a int not null, b VARCHAR(10), INDEX (b) ) AVG_ROW_LENGTH=10 CHECKSUM=1 COMMENT="test" TYPE=MYISAM MIN_ROWS=10 MAX_ROWS=100 PACK_KEYS=1 DELAY_KEY_WRITE=1 ROW_FORMAT=fixed;
|
||||||
show create table t1;
|
show create table t1;
|
||||||
|
73
mysql-test/r/sp-error.result
Normal file
73
mysql-test/r/sp-error.result
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
delete from mysql.proc;
|
||||||
|
create procedure proc1()
|
||||||
|
set @x = 42;
|
||||||
|
create function func1() returns int
|
||||||
|
return 42;
|
||||||
|
create procedure foo()
|
||||||
|
create procedure bar() set @x=3;
|
||||||
|
Can't create a PROCEDURE from within another stored routine
|
||||||
|
create procedure foo()
|
||||||
|
create function bar() returns double return 2.3;
|
||||||
|
Can't create a FUNCTION from within another stored routine
|
||||||
|
create procedure proc1()
|
||||||
|
set @x = 42;
|
||||||
|
PROCEDURE proc1 already exists
|
||||||
|
create function func1() returns int
|
||||||
|
return 42;
|
||||||
|
FUNCTION func1 already exists
|
||||||
|
alter procedure foo;
|
||||||
|
PROCEDURE foo does not exist
|
||||||
|
alter function foo;
|
||||||
|
FUNCTION foo does not exist
|
||||||
|
drop procedure foo;
|
||||||
|
PROCEDURE foo does not exist
|
||||||
|
drop function foo;
|
||||||
|
FUNCTION foo does not exist
|
||||||
|
call foo();
|
||||||
|
PROCEDURE foo does not exist
|
||||||
|
drop procedure if exists foo;
|
||||||
|
Warnings:
|
||||||
|
Warning 1256 PROCEDURE foo does not exist
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
leave bar;
|
||||||
|
end loop;
|
||||||
|
LEAVE with no matching label: bar
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
iterate bar;
|
||||||
|
end loop;
|
||||||
|
ITERATE with no matching label: bar
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
foo: loop
|
||||||
|
set @x=2;
|
||||||
|
end loop foo;
|
||||||
|
end loop foo;
|
||||||
|
Redefining label foo
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
set @x=2;
|
||||||
|
end loop bar;
|
||||||
|
End-label bar without match
|
||||||
|
create procedure foo(out x int)
|
||||||
|
begin
|
||||||
|
declare y int;
|
||||||
|
set x = y;
|
||||||
|
end;
|
||||||
|
Referring to uninitialized variable y
|
||||||
|
create procedure foo(x int)
|
||||||
|
select * from test.t1;
|
||||||
|
SELECT in a stored procedure must have INTO
|
||||||
|
create procedure foo()
|
||||||
|
return 42;
|
||||||
|
RETURN is only allowed in a FUNCTION
|
||||||
|
create function foo() returns int
|
||||||
|
begin
|
||||||
|
declare x int;
|
||||||
|
select max(c) into x from test.t;
|
||||||
|
return x;
|
||||||
|
end;
|
||||||
|
Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION
|
||||||
|
drop procedure proc1;
|
||||||
|
drop function func1;
|
507
mysql-test/r/sp.result
Normal file
507
mysql-test/r/sp.result
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
use test;
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
create table t1 (
|
||||||
|
id char(16) not null,
|
||||||
|
data int not null
|
||||||
|
);
|
||||||
|
create table t2 (
|
||||||
|
s char(16) not null,
|
||||||
|
i int not null,
|
||||||
|
d double not null
|
||||||
|
);
|
||||||
|
create procedure foo42()
|
||||||
|
insert into test.t1 values ("foo", 42);
|
||||||
|
call foo42();
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
foo 42
|
||||||
|
delete from t1;
|
||||||
|
drop procedure foo42;
|
||||||
|
create procedure u()
|
||||||
|
use sptmp;
|
||||||
|
create database sptmp;
|
||||||
|
use test;
|
||||||
|
call u();
|
||||||
|
select database();
|
||||||
|
database()
|
||||||
|
test
|
||||||
|
drop database sptmp;
|
||||||
|
drop procedure u;
|
||||||
|
create procedure bar(x char(16), y int)
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
call bar("bar", 666);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
bar 666
|
||||||
|
delete from t1;
|
||||||
|
create procedure two(x1 char(16), x2 char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x1, y);
|
||||||
|
insert into test.t1 values (x2, y);
|
||||||
|
end;
|
||||||
|
call two("one", "two", 3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
one 3
|
||||||
|
two 3
|
||||||
|
delete from t1;
|
||||||
|
drop procedure two;
|
||||||
|
create procedure locset(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z1, z2 int;
|
||||||
|
set z1 = y;
|
||||||
|
set z2 = z1+2;
|
||||||
|
insert into test.t1 values (x, z2);
|
||||||
|
end;
|
||||||
|
call locset("locset", 19);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
locset 21
|
||||||
|
delete from t1;
|
||||||
|
drop procedure locset;
|
||||||
|
create procedure mixset(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
set @z = y, z = 666, max_join_size = 100;
|
||||||
|
insert into test.t1 values (x, z);
|
||||||
|
end;
|
||||||
|
call mixset("mixset", 19);
|
||||||
|
show variables like 'max_join_size';
|
||||||
|
Variable_name Value
|
||||||
|
max_join_size 100
|
||||||
|
select id,data,@z from t1;
|
||||||
|
id data @z
|
||||||
|
mixset 666 19
|
||||||
|
delete from t1;
|
||||||
|
drop procedure mixset;
|
||||||
|
create procedure zip(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
call zap(y, z);
|
||||||
|
call bar(x, z);
|
||||||
|
end;
|
||||||
|
create procedure zap(x int, out y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
set z = x+1, y = z;
|
||||||
|
end;
|
||||||
|
call zip("zip", 99);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
zip 100
|
||||||
|
delete from t1;
|
||||||
|
drop procedure zip;
|
||||||
|
drop procedure zap;
|
||||||
|
drop procedure bar;
|
||||||
|
create procedure iotest(x1 char(16), x2 char(16), y int)
|
||||||
|
begin
|
||||||
|
call inc2(x2, y);
|
||||||
|
insert into test.t1 values (x1, y);
|
||||||
|
end;
|
||||||
|
create procedure inc2(x char(16), y int)
|
||||||
|
begin
|
||||||
|
call inc(y);
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
end;
|
||||||
|
create procedure inc(inout io int)
|
||||||
|
set io = io + 1;
|
||||||
|
call iotest("io1", "io2", 1);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
io2 2
|
||||||
|
io1 1
|
||||||
|
delete from t1;
|
||||||
|
drop procedure iotest;
|
||||||
|
drop procedure inc2;
|
||||||
|
drop procedure inc;
|
||||||
|
create procedure cbv1()
|
||||||
|
begin
|
||||||
|
declare y int;
|
||||||
|
set y = 3;
|
||||||
|
call cbv2(y+1, y);
|
||||||
|
insert into test.t1 values ("cbv1", y);
|
||||||
|
end;
|
||||||
|
create procedure cbv2(y1 int, inout y2 int)
|
||||||
|
begin
|
||||||
|
set y2 = 4711;
|
||||||
|
insert into test.t1 values ("cbv2", y1);
|
||||||
|
end;
|
||||||
|
call cbv1();
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
cbv2 4
|
||||||
|
cbv1 4711
|
||||||
|
delete from t1;
|
||||||
|
drop procedure cbv1;
|
||||||
|
drop procedure cbv2;
|
||||||
|
create procedure a0(x int)
|
||||||
|
while x do
|
||||||
|
set x = x-1;
|
||||||
|
insert into test.t1 values ("a0", x);
|
||||||
|
end while;
|
||||||
|
call a0(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
a0 2
|
||||||
|
a0 1
|
||||||
|
a0 0
|
||||||
|
delete from t1;
|
||||||
|
drop procedure a0;
|
||||||
|
create procedure a(x int)
|
||||||
|
while x > 0 do
|
||||||
|
set x = x-1;
|
||||||
|
insert into test.t1 values ("a", x);
|
||||||
|
end while;
|
||||||
|
call a(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
a 2
|
||||||
|
a 1
|
||||||
|
a 0
|
||||||
|
delete from t1;
|
||||||
|
drop procedure a;
|
||||||
|
create procedure b(x int)
|
||||||
|
repeat
|
||||||
|
insert into test.t1 values (repeat("b",3), x);
|
||||||
|
set x = x-1;
|
||||||
|
until x = 0 end repeat;
|
||||||
|
call b(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
bbb 3
|
||||||
|
bbb 2
|
||||||
|
bbb 1
|
||||||
|
delete from t1;
|
||||||
|
drop procedure b;
|
||||||
|
create procedure b2(x int)
|
||||||
|
repeat(select 1 into outfile 'b2');
|
||||||
|
insert into test.t1 values (repeat("b2",3), x);
|
||||||
|
set x = x-1;
|
||||||
|
until x = 0 end repeat;
|
||||||
|
drop procedure b2;
|
||||||
|
create procedure c(x int)
|
||||||
|
hmm: while x > 0 do
|
||||||
|
insert into test.t1 values ("c", x);
|
||||||
|
set x = x-1;
|
||||||
|
iterate hmm;
|
||||||
|
insert into test.t1 values ("x", x);
|
||||||
|
end while hmm;
|
||||||
|
call c(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
c 3
|
||||||
|
c 2
|
||||||
|
c 1
|
||||||
|
delete from t1;
|
||||||
|
drop procedure c;
|
||||||
|
create procedure d(x int)
|
||||||
|
hmm: while x > 0 do
|
||||||
|
insert into test.t1 values ("d", x);
|
||||||
|
set x = x-1;
|
||||||
|
leave hmm;
|
||||||
|
insert into test.t1 values ("x", x);
|
||||||
|
end while hmm;
|
||||||
|
call d(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
d 3
|
||||||
|
delete from t1;
|
||||||
|
drop procedure d;
|
||||||
|
create procedure e(x int)
|
||||||
|
foo: loop
|
||||||
|
if x = 0 then
|
||||||
|
leave foo;
|
||||||
|
end if;
|
||||||
|
insert into test.t1 values ("e", x);
|
||||||
|
set x = x-1;
|
||||||
|
end loop foo;
|
||||||
|
call e(3);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
e 3
|
||||||
|
e 2
|
||||||
|
e 1
|
||||||
|
delete from t1;
|
||||||
|
drop procedure e;
|
||||||
|
create procedure f(x int)
|
||||||
|
if x < 0 then
|
||||||
|
insert into test.t1 values ("f", 0);
|
||||||
|
elseif x = 0 then
|
||||||
|
insert into test.t1 values ("f", 1);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("f", 2);
|
||||||
|
end if;
|
||||||
|
call f(-2);
|
||||||
|
call f(0);
|
||||||
|
call f(4);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
f 0
|
||||||
|
f 1
|
||||||
|
f 2
|
||||||
|
delete from t1;
|
||||||
|
drop procedure f;
|
||||||
|
create procedure g(x int)
|
||||||
|
case
|
||||||
|
when x < 0 then
|
||||||
|
insert into test.t1 values ("g", 0);
|
||||||
|
when x = 0 then
|
||||||
|
insert into test.t1 values ("g", 1);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("g", 2);
|
||||||
|
end case;
|
||||||
|
call g(-42);
|
||||||
|
call g(0);
|
||||||
|
call g(1);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
g 0
|
||||||
|
g 1
|
||||||
|
g 2
|
||||||
|
delete from t1;
|
||||||
|
drop procedure g;
|
||||||
|
create procedure h(x int)
|
||||||
|
case x
|
||||||
|
when 0 then
|
||||||
|
insert into test.t1 values ("h0", x);
|
||||||
|
when 1 then
|
||||||
|
insert into test.t1 values ("h1", x);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("h?", x);
|
||||||
|
end case;
|
||||||
|
call h(0);
|
||||||
|
call h(1);
|
||||||
|
call h(17);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
h0 0
|
||||||
|
h1 1
|
||||||
|
h? 17
|
||||||
|
delete from t1;
|
||||||
|
drop procedure h;
|
||||||
|
create procedure into_test(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select id,data into x,y from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end;
|
||||||
|
call into_test("into", 100);
|
||||||
|
select * from t1;
|
||||||
|
id data
|
||||||
|
into 100
|
||||||
|
into2 102
|
||||||
|
delete from t1;
|
||||||
|
drop procedure into_test;
|
||||||
|
create procedure into_test2(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select id,data into x,@z from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end;
|
||||||
|
call into_test2("into", 100);
|
||||||
|
select id,data,@z from t1;
|
||||||
|
id data @z
|
||||||
|
into 100 100
|
||||||
|
into2 102 100
|
||||||
|
delete from t1;
|
||||||
|
drop procedure into_test2;
|
||||||
|
create procedure into_outfile(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select * into outfile "/tmp/spout" from test.t1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end;
|
||||||
|
call into_outfile("ofile", 1);
|
||||||
|
delete from t1;
|
||||||
|
drop procedure into_outfile;
|
||||||
|
create procedure into_dumpfile(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select * into dumpfile "/tmp/spdump" from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end;
|
||||||
|
call into_dumpfile("dfile", 1);
|
||||||
|
delete from t1;
|
||||||
|
drop procedure into_dumpfile;
|
||||||
|
create procedure create_select(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
create table test.t2 select * from test.t1;
|
||||||
|
insert into test.t2 values (concat(x, "2"), y+2);
|
||||||
|
end;
|
||||||
|
drop procedure create_select;
|
||||||
|
create function e() returns double
|
||||||
|
return 2.7182818284590452354;
|
||||||
|
set @e = e();
|
||||||
|
select e(), @e;
|
||||||
|
e() @e
|
||||||
|
2.718281828459 2.718281828459
|
||||||
|
create function inc(i int) returns int
|
||||||
|
return i+1;
|
||||||
|
select inc(1), inc(99), inc(-71);
|
||||||
|
inc(1) inc(99) inc(-71)
|
||||||
|
2 100 -70
|
||||||
|
create function mul(x int, y int) returns int
|
||||||
|
return x*y;
|
||||||
|
select mul(1,1), mul(3,5), mul(4711, 666);
|
||||||
|
mul(1,1) mul(3,5) mul(4711, 666)
|
||||||
|
1 15 3137526
|
||||||
|
create function append(s1 char(8), s2 char(8)) returns char(16)
|
||||||
|
return concat(s1, s2);
|
||||||
|
select append("foo", "bar");
|
||||||
|
append("foo", "bar")
|
||||||
|
foobar
|
||||||
|
create function fac(n int unsigned) returns bigint unsigned
|
||||||
|
begin
|
||||||
|
declare f bigint unsigned;
|
||||||
|
set f = 1;
|
||||||
|
while n > 1 do
|
||||||
|
set f = f * n;
|
||||||
|
set n = n - 1;
|
||||||
|
end while;
|
||||||
|
return f;
|
||||||
|
end;
|
||||||
|
select fac(1), fac(2), fac(5), fac(10);
|
||||||
|
fac(1) fac(2) fac(5) fac(10)
|
||||||
|
1 2 120 3628800
|
||||||
|
create function fun(d double, i int, u int unsigned) returns double
|
||||||
|
return mul(inc(i), fac(u)) / e();
|
||||||
|
select fun(2.3, 3, 5);
|
||||||
|
fun(2.3, 3, 5)
|
||||||
|
176.58213176229
|
||||||
|
insert into t2 values (append("xxx", "yyy"), mul(4,3), e());
|
||||||
|
insert into t2 values (append("a", "b"), mul(2,mul(3,4)), fun(1.7, 4, 6));
|
||||||
|
select * from t2 where s = append("a", "b");
|
||||||
|
s i d
|
||||||
|
ab 24 1324.36598821719
|
||||||
|
select * from t2 where i = mul(4,3) or i = mul(mul(3,4),2);
|
||||||
|
s i d
|
||||||
|
xxxyyy 12 2.71828182845905
|
||||||
|
ab 24 1324.36598821719
|
||||||
|
select * from t2 where d = e();
|
||||||
|
s i d
|
||||||
|
xxxyyy 12 2.71828182845905
|
||||||
|
select * from t2;
|
||||||
|
s i d
|
||||||
|
xxxyyy 12 2.71828182845905
|
||||||
|
ab 24 1324.36598821719
|
||||||
|
delete from t2;
|
||||||
|
drop function e;
|
||||||
|
drop function inc;
|
||||||
|
drop function mul;
|
||||||
|
drop function append;
|
||||||
|
drop function fun;
|
||||||
|
drop table if exists fac;
|
||||||
|
create table fac (n int unsigned not null primary key, f bigint unsigned);
|
||||||
|
create procedure ifac(n int unsigned)
|
||||||
|
begin
|
||||||
|
declare i int unsigned;
|
||||||
|
set i = 1;
|
||||||
|
if n > 20 then
|
||||||
|
set n = 20; # bigint overflow otherwise
|
||||||
|
end if;
|
||||||
|
while i <= n do
|
||||||
|
begin
|
||||||
|
insert into test.fac values (i, fac(i));
|
||||||
|
set i = i + 1;
|
||||||
|
end;
|
||||||
|
end while;
|
||||||
|
end;
|
||||||
|
call ifac(20);
|
||||||
|
select * from fac;
|
||||||
|
n f
|
||||||
|
1 1
|
||||||
|
2 2
|
||||||
|
3 6
|
||||||
|
4 24
|
||||||
|
5 120
|
||||||
|
6 720
|
||||||
|
7 5040
|
||||||
|
8 40320
|
||||||
|
9 362880
|
||||||
|
10 3628800
|
||||||
|
11 39916800
|
||||||
|
12 479001600
|
||||||
|
13 6227020800
|
||||||
|
14 87178291200
|
||||||
|
15 1307674368000
|
||||||
|
16 20922789888000
|
||||||
|
17 355687428096000
|
||||||
|
18 6402373705728000
|
||||||
|
19 121645100408832000
|
||||||
|
20 2432902008176640000
|
||||||
|
drop table fac;
|
||||||
|
drop procedure ifac;
|
||||||
|
drop function fac;
|
||||||
|
drop table if exists primes;
|
||||||
|
create table primes (
|
||||||
|
i int unsigned not null primary key,
|
||||||
|
p bigint unsigned not null
|
||||||
|
);
|
||||||
|
insert into primes values
|
||||||
|
( 0, 3), ( 1, 5), ( 2, 7), ( 3, 11), ( 4, 13),
|
||||||
|
( 5, 17), ( 6, 19), ( 7, 23), ( 8, 29), ( 9, 31),
|
||||||
|
(10, 37), (11, 41), (12, 43), (13, 47), (14, 53),
|
||||||
|
(15, 59), (16, 61), (17, 67), (18, 71), (19, 73),
|
||||||
|
(20, 79), (21, 83), (22, 89), (23, 97), (24, 101),
|
||||||
|
(25, 103), (26, 107), (27, 109), (28, 113), (29, 127),
|
||||||
|
(30, 131), (31, 137), (32, 139), (33, 149), (34, 151),
|
||||||
|
(35, 157), (36, 163), (37, 167), (38, 173), (39, 179),
|
||||||
|
(40, 181), (41, 191), (42, 193), (43, 197), (44, 199);
|
||||||
|
create procedure opp(n bigint unsigned, out pp bool)
|
||||||
|
begin
|
||||||
|
declare r double;
|
||||||
|
declare b, s bigint unsigned;
|
||||||
|
set b = 0, s = 0;
|
||||||
|
set r = sqrt(n);
|
||||||
|
again:
|
||||||
|
loop
|
||||||
|
if s = 45 then
|
||||||
|
set b = b+200, s = 0;
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
declare p bigint unsigned;
|
||||||
|
select t.p into p from test.primes t where t.i = s;
|
||||||
|
if b+p > r then
|
||||||
|
set pp = 1;
|
||||||
|
leave again;
|
||||||
|
end if;
|
||||||
|
if mod(n, b+p) = 0 then
|
||||||
|
set pp = 0;
|
||||||
|
leave again;
|
||||||
|
end if;
|
||||||
|
set s = s+1;
|
||||||
|
end;
|
||||||
|
end if;
|
||||||
|
end loop again;
|
||||||
|
end;
|
||||||
|
create procedure ip(m int unsigned)
|
||||||
|
begin
|
||||||
|
declare p bigint unsigned;
|
||||||
|
declare i int unsigned;
|
||||||
|
set i=45, p=201;
|
||||||
|
while i < m do
|
||||||
|
begin
|
||||||
|
declare pp bool;
|
||||||
|
set pp = 0;
|
||||||
|
call opp(p, pp);
|
||||||
|
if pp then
|
||||||
|
insert into test.primes values (i, p);
|
||||||
|
set i = i+1;
|
||||||
|
end if;
|
||||||
|
set p = p+2;
|
||||||
|
end;
|
||||||
|
end while;
|
||||||
|
end;
|
||||||
|
call ip(200);
|
||||||
|
select * from primes where i=45 or i=100 or i=199;
|
||||||
|
i p
|
||||||
|
45 211
|
||||||
|
100 557
|
||||||
|
199 1229
|
||||||
|
drop table primes;
|
||||||
|
drop procedure opp;
|
||||||
|
drop procedure ip;
|
||||||
|
drop table t1;
|
||||||
|
drop table t2;
|
@ -14,6 +14,6 @@ update t1 set n = 3;
|
|||||||
unlock tables;
|
unlock tables;
|
||||||
show status like 'Table_lock%';
|
show status like 'Table_lock%';
|
||||||
Variable_name Value
|
Variable_name Value
|
||||||
Table_locks_immediate 3
|
Table_locks_immediate 4
|
||||||
Table_locks_waited 1
|
Table_locks_waited 1
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
@ -593,7 +593,6 @@ x
|
|||||||
3
|
3
|
||||||
3
|
3
|
||||||
INSERT INTO t1 (x) select (SELECT SUM(x)+2 FROM t1) FROM t2;
|
INSERT INTO t1 (x) select (SELECT SUM(x)+2 FROM t1) FROM t2;
|
||||||
You can't specify target table 't1' for update in FROM clause
|
|
||||||
INSERT DELAYED INTO t1 (x) VALUES ((SELECT SUM(x) FROM t2));
|
INSERT DELAYED INTO t1 (x) VALUES ((SELECT SUM(x) FROM t2));
|
||||||
select * from t1;
|
select * from t1;
|
||||||
x
|
x
|
||||||
@ -601,6 +600,8 @@ x
|
|||||||
2
|
2
|
||||||
3
|
3
|
||||||
3
|
3
|
||||||
|
11
|
||||||
|
11
|
||||||
0
|
0
|
||||||
drop table t1, t2, t3;
|
drop table t1, t2, t3;
|
||||||
CREATE TABLE t1 (x int not null, y int, primary key (x));
|
CREATE TABLE t1 (x int not null, y int, primary key (x));
|
||||||
|
@ -65,3 +65,34 @@ use test_$1;
|
|||||||
create table t1 (c int);
|
create table t1 (c int);
|
||||||
insert into test_$1.t1 set test_$1.t1.c = '1';
|
insert into test_$1.t1 set test_$1.t1.c = '1';
|
||||||
drop database test_$1;
|
drop database test_$1;
|
||||||
|
use test;
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists t1,t2,t3;
|
||||||
|
--enable_warnings
|
||||||
|
create table t1(id1 int not null auto_increment primary key, t char(12));
|
||||||
|
create table t2(id2 int not null, t char(12));
|
||||||
|
create table t3(id3 int not null, t char(12), index(id3));
|
||||||
|
disable_query_log;
|
||||||
|
let $1 = 100;
|
||||||
|
while ($1)
|
||||||
|
{
|
||||||
|
let $2 = 5;
|
||||||
|
eval insert into t1(t) values ('$1');
|
||||||
|
while ($2)
|
||||||
|
{
|
||||||
|
eval insert into t2(id2,t) values ($1,'$2');
|
||||||
|
let $3 = 10;
|
||||||
|
while ($3)
|
||||||
|
{
|
||||||
|
eval insert into t3(id3,t) values ($1,'$2');
|
||||||
|
dec $3;
|
||||||
|
}
|
||||||
|
dec $2;
|
||||||
|
}
|
||||||
|
dec $1;
|
||||||
|
}
|
||||||
|
enable_query_log;
|
||||||
|
select count(*) from t2;
|
||||||
|
insert into t2 select t1.* from t1, t2 t, t3 where t1.id1 = t.id2 and t.id2 = t3.id3;
|
||||||
|
select count(*) from t2;
|
||||||
|
drop table if exists t1,t2,t3;
|
||||||
|
111
mysql-test/t/sp-error.test
Normal file
111
mysql-test/t/sp-error.test
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#
|
||||||
|
# Stored PROCEDURE error tests
|
||||||
|
#
|
||||||
|
|
||||||
|
# Make sure we don't have any procedures left.
|
||||||
|
delete from mysql.proc;
|
||||||
|
|
||||||
|
delimiter |;
|
||||||
|
|
||||||
|
# Check that we get the right error, i.e. UDF declaration parses correctly,
|
||||||
|
# but foo.so doesn't exist.
|
||||||
|
# QQ This generates an error message containing a misleading errno which
|
||||||
|
# might vary between systems (it usually doesn't have anything to do with
|
||||||
|
# the actual failing dlopen()).
|
||||||
|
#--error 1126
|
||||||
|
#create function foo returns real soname "foo.so"|
|
||||||
|
|
||||||
|
create procedure proc1()
|
||||||
|
set @x = 42|
|
||||||
|
|
||||||
|
create function func1() returns int
|
||||||
|
return 42|
|
||||||
|
|
||||||
|
# Can't create recursively
|
||||||
|
--error 1254
|
||||||
|
create procedure foo()
|
||||||
|
create procedure bar() set @x=3|
|
||||||
|
--error 1254
|
||||||
|
create procedure foo()
|
||||||
|
create function bar() returns double return 2.3|
|
||||||
|
|
||||||
|
# Already exists
|
||||||
|
--error 1255
|
||||||
|
create procedure proc1()
|
||||||
|
set @x = 42|
|
||||||
|
--error 1255
|
||||||
|
create function func1() returns int
|
||||||
|
return 42|
|
||||||
|
|
||||||
|
# Does not exist
|
||||||
|
--error 1256
|
||||||
|
alter procedure foo|
|
||||||
|
--error 1256
|
||||||
|
alter function foo|
|
||||||
|
--error 1256
|
||||||
|
drop procedure foo|
|
||||||
|
--error 1256
|
||||||
|
drop function foo|
|
||||||
|
--error 1256
|
||||||
|
call foo()|
|
||||||
|
drop procedure if exists foo|
|
||||||
|
|
||||||
|
# LEAVE/ITERATE with no match
|
||||||
|
--error 1259
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
leave bar;
|
||||||
|
end loop|
|
||||||
|
--error 1259
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
iterate bar;
|
||||||
|
end loop|
|
||||||
|
|
||||||
|
# Redefining label
|
||||||
|
--error 1260
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
foo: loop
|
||||||
|
set @x=2;
|
||||||
|
end loop foo;
|
||||||
|
end loop foo|
|
||||||
|
|
||||||
|
# End label mismatch
|
||||||
|
--error 1261
|
||||||
|
create procedure foo()
|
||||||
|
foo: loop
|
||||||
|
set @x=2;
|
||||||
|
end loop bar|
|
||||||
|
|
||||||
|
# Referring to undef variable
|
||||||
|
--error 1262
|
||||||
|
create procedure foo(out x int)
|
||||||
|
begin
|
||||||
|
declare y int;
|
||||||
|
set x = y;
|
||||||
|
end|
|
||||||
|
|
||||||
|
# We require INTO in SELECTs (for now; this might change in the future)
|
||||||
|
--error 1263
|
||||||
|
create procedure foo(x int)
|
||||||
|
select * from test.t1|
|
||||||
|
|
||||||
|
# RETURN in FUNCTION only
|
||||||
|
--error 1264
|
||||||
|
create procedure foo()
|
||||||
|
return 42|
|
||||||
|
|
||||||
|
# Doesn't allow queries in FUNCTIONs (for now :-( )
|
||||||
|
--error 1265
|
||||||
|
create function foo() returns int
|
||||||
|
begin
|
||||||
|
declare x int;
|
||||||
|
select max(c) into x from test.t;
|
||||||
|
return x;
|
||||||
|
end|
|
||||||
|
|
||||||
|
drop procedure proc1|
|
||||||
|
drop function func1|
|
||||||
|
|
||||||
|
delimiter ;|
|
589
mysql-test/t/sp.test
Normal file
589
mysql-test/t/sp.test
Normal file
@ -0,0 +1,589 @@
|
|||||||
|
#
|
||||||
|
# Basic stored PROCEDURE tests
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
use test;
|
||||||
|
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
--enable_warnings
|
||||||
|
|
||||||
|
create table t1 (
|
||||||
|
id char(16) not null,
|
||||||
|
data int not null
|
||||||
|
);
|
||||||
|
create table t2 (
|
||||||
|
s char(16) not null,
|
||||||
|
i int not null,
|
||||||
|
d double not null
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
# Single statement, no params.
|
||||||
|
create procedure foo42()
|
||||||
|
insert into test.t1 values ("foo", 42);
|
||||||
|
|
||||||
|
call foo42();
|
||||||
|
select * from t1;
|
||||||
|
delete from t1;
|
||||||
|
drop procedure foo42;
|
||||||
|
|
||||||
|
|
||||||
|
# USE test: Make sure we remain in the same DB.
|
||||||
|
create procedure u()
|
||||||
|
use sptmp;
|
||||||
|
|
||||||
|
create database sptmp;
|
||||||
|
use test;
|
||||||
|
call u();
|
||||||
|
select database();
|
||||||
|
drop database sptmp;
|
||||||
|
drop procedure u;
|
||||||
|
|
||||||
|
|
||||||
|
# Single statement, two IN params.
|
||||||
|
create procedure bar(x char(16), y int)
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
|
||||||
|
call bar("bar", 666);
|
||||||
|
select * from t1;
|
||||||
|
delete from t1;
|
||||||
|
# Don't drop procedure yet...
|
||||||
|
|
||||||
|
|
||||||
|
# Now for multiple statements...
|
||||||
|
delimiter |;
|
||||||
|
|
||||||
|
# Two statements.
|
||||||
|
create procedure two(x1 char(16), x2 char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x1, y);
|
||||||
|
insert into test.t1 values (x2, y);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call two("one", "two", 3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure two|
|
||||||
|
|
||||||
|
|
||||||
|
# Simple test of local variables and SET.
|
||||||
|
create procedure locset(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z1, z2 int;
|
||||||
|
set z1 = y;
|
||||||
|
set z2 = z1+2;
|
||||||
|
insert into test.t1 values (x, z2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call locset("locset", 19)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure locset|
|
||||||
|
|
||||||
|
|
||||||
|
# The peculiar (non-standard) mixture of variables types in SET.
|
||||||
|
create procedure mixset(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
|
||||||
|
set @z = y, z = 666, max_join_size = 100;
|
||||||
|
insert into test.t1 values (x, z);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call mixset("mixset", 19)|
|
||||||
|
show variables like 'max_join_size'|
|
||||||
|
select id,data,@z from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure mixset|
|
||||||
|
|
||||||
|
|
||||||
|
# Multiple CALL statements, one with OUT parameter.
|
||||||
|
create procedure zip(x char(16), y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
call zap(y, z);
|
||||||
|
call bar(x, z);
|
||||||
|
end|
|
||||||
|
|
||||||
|
# SET local variables and OUT parameter.
|
||||||
|
create procedure zap(x int, out y int)
|
||||||
|
begin
|
||||||
|
declare z int;
|
||||||
|
set z = x+1, y = z;
|
||||||
|
end|
|
||||||
|
|
||||||
|
call zip("zip", 99)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure zip|
|
||||||
|
drop procedure zap|
|
||||||
|
drop procedure bar|
|
||||||
|
|
||||||
|
|
||||||
|
# INOUT test
|
||||||
|
create procedure iotest(x1 char(16), x2 char(16), y int)
|
||||||
|
begin
|
||||||
|
call inc2(x2, y);
|
||||||
|
insert into test.t1 values (x1, y);
|
||||||
|
end|
|
||||||
|
|
||||||
|
create procedure inc2(x char(16), y int)
|
||||||
|
begin
|
||||||
|
call inc(y);
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
end|
|
||||||
|
|
||||||
|
create procedure inc(inout io int)
|
||||||
|
set io = io + 1|
|
||||||
|
|
||||||
|
call iotest("io1", "io2", 1)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure iotest|
|
||||||
|
drop procedure inc2|
|
||||||
|
drop procedure inc|
|
||||||
|
|
||||||
|
|
||||||
|
# Call-by-value test
|
||||||
|
# The expected result is:
|
||||||
|
# ("cbv2", 4)
|
||||||
|
# ("cbv1", 4711)
|
||||||
|
create procedure cbv1()
|
||||||
|
begin
|
||||||
|
declare y int;
|
||||||
|
|
||||||
|
set y = 3;
|
||||||
|
call cbv2(y+1, y);
|
||||||
|
insert into test.t1 values ("cbv1", y);
|
||||||
|
end|
|
||||||
|
|
||||||
|
create procedure cbv2(y1 int, inout y2 int)
|
||||||
|
begin
|
||||||
|
set y2 = 4711;
|
||||||
|
insert into test.t1 values ("cbv2", y1);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call cbv1()|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure cbv1|
|
||||||
|
drop procedure cbv2|
|
||||||
|
|
||||||
|
|
||||||
|
# Basic tests of the flow control constructs
|
||||||
|
|
||||||
|
# Just test on 'x'...
|
||||||
|
create procedure a0(x int)
|
||||||
|
while x do
|
||||||
|
set x = x-1;
|
||||||
|
insert into test.t1 values ("a0", x);
|
||||||
|
end while|
|
||||||
|
|
||||||
|
call a0(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure a0|
|
||||||
|
|
||||||
|
|
||||||
|
# The same, but with a more traditional test.
|
||||||
|
create procedure a(x int)
|
||||||
|
while x > 0 do
|
||||||
|
set x = x-1;
|
||||||
|
insert into test.t1 values ("a", x);
|
||||||
|
end while|
|
||||||
|
|
||||||
|
call a(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure a|
|
||||||
|
|
||||||
|
|
||||||
|
# REPEAT
|
||||||
|
create procedure b(x int)
|
||||||
|
repeat
|
||||||
|
insert into test.t1 values (repeat("b",3), x);
|
||||||
|
set x = x-1;
|
||||||
|
until x = 0 end repeat|
|
||||||
|
|
||||||
|
call b(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure b|
|
||||||
|
|
||||||
|
|
||||||
|
# Check that repeat isn't parsed the wrong way
|
||||||
|
create procedure b2(x int)
|
||||||
|
repeat(select 1 into outfile 'b2');
|
||||||
|
insert into test.t1 values (repeat("b2",3), x);
|
||||||
|
set x = x-1;
|
||||||
|
until x = 0 end repeat|
|
||||||
|
|
||||||
|
# We don't actually want to call it.
|
||||||
|
drop procedure b2|
|
||||||
|
|
||||||
|
|
||||||
|
# Labelled WHILE with ITERATE (pointless really)
|
||||||
|
create procedure c(x int)
|
||||||
|
hmm: while x > 0 do
|
||||||
|
insert into test.t1 values ("c", x);
|
||||||
|
set x = x-1;
|
||||||
|
iterate hmm;
|
||||||
|
insert into test.t1 values ("x", x);
|
||||||
|
end while hmm|
|
||||||
|
|
||||||
|
call c(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure c|
|
||||||
|
|
||||||
|
|
||||||
|
# Labelled WHILE with LEAVE
|
||||||
|
create procedure d(x int)
|
||||||
|
hmm: while x > 0 do
|
||||||
|
insert into test.t1 values ("d", x);
|
||||||
|
set x = x-1;
|
||||||
|
leave hmm;
|
||||||
|
insert into test.t1 values ("x", x);
|
||||||
|
end while hmm|
|
||||||
|
|
||||||
|
call d(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure d|
|
||||||
|
|
||||||
|
|
||||||
|
# LOOP, with simple IF statement
|
||||||
|
create procedure e(x int)
|
||||||
|
foo: loop
|
||||||
|
if x = 0 then
|
||||||
|
leave foo;
|
||||||
|
end if;
|
||||||
|
insert into test.t1 values ("e", x);
|
||||||
|
set x = x-1;
|
||||||
|
end loop foo|
|
||||||
|
|
||||||
|
call e(3)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure e|
|
||||||
|
|
||||||
|
|
||||||
|
# A full IF statement
|
||||||
|
create procedure f(x int)
|
||||||
|
if x < 0 then
|
||||||
|
insert into test.t1 values ("f", 0);
|
||||||
|
elseif x = 0 then
|
||||||
|
insert into test.t1 values ("f", 1);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("f", 2);
|
||||||
|
end if|
|
||||||
|
|
||||||
|
call f(-2)|
|
||||||
|
call f(0)|
|
||||||
|
call f(4)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure f|
|
||||||
|
|
||||||
|
|
||||||
|
# This form of CASE is really just syntactic sugar for IF-ELSEIF-...
|
||||||
|
create procedure g(x int)
|
||||||
|
case
|
||||||
|
when x < 0 then
|
||||||
|
insert into test.t1 values ("g", 0);
|
||||||
|
when x = 0 then
|
||||||
|
insert into test.t1 values ("g", 1);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("g", 2);
|
||||||
|
end case|
|
||||||
|
|
||||||
|
call g(-42)|
|
||||||
|
call g(0)|
|
||||||
|
call g(1)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure g|
|
||||||
|
|
||||||
|
|
||||||
|
# The "simple CASE"
|
||||||
|
create procedure h(x int)
|
||||||
|
case x
|
||||||
|
when 0 then
|
||||||
|
insert into test.t1 values ("h0", x);
|
||||||
|
when 1 then
|
||||||
|
insert into test.t1 values ("h1", x);
|
||||||
|
else
|
||||||
|
insert into test.t1 values ("h?", x);
|
||||||
|
end case|
|
||||||
|
|
||||||
|
call h(0)|
|
||||||
|
call h(1)|
|
||||||
|
call h(17)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure h|
|
||||||
|
|
||||||
|
|
||||||
|
# SELECT INTO local variables
|
||||||
|
create procedure into_test(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select id,data into x,y from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call into_test("into", 100)|
|
||||||
|
select * from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure into_test|
|
||||||
|
|
||||||
|
|
||||||
|
# SELECT INTO with a mix of local and global variables
|
||||||
|
create procedure into_test2(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select id,data into x,@z from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
call into_test2("into", 100)|
|
||||||
|
select id,data,@z from t1|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure into_test2|
|
||||||
|
|
||||||
|
|
||||||
|
# These two (and the two procedures above) caused an assert() to fail in
|
||||||
|
# sql_base.cc:lock_tables() at some point.
|
||||||
|
|
||||||
|
create procedure into_outfile(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select * into outfile "/tmp/spout" from test.t1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
system rm -f /tmp/spout|
|
||||||
|
call into_outfile("ofile", 1)|
|
||||||
|
system rm -f /tmp/spout|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure into_outfile|
|
||||||
|
|
||||||
|
create procedure into_dumpfile(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
select * into dumpfile "/tmp/spdump" from test.t1 limit 1;
|
||||||
|
insert into test.t1 values (concat(x, "2"), y+2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
system rm -f /tmp/spdump|
|
||||||
|
call into_dumpfile("dfile", 1)|
|
||||||
|
system rm -f /tmp/spdump|
|
||||||
|
delete from t1|
|
||||||
|
drop procedure into_dumpfile|
|
||||||
|
|
||||||
|
|
||||||
|
create procedure create_select(x char(16), y int)
|
||||||
|
begin
|
||||||
|
insert into test.t1 values (x, y);
|
||||||
|
create table test.t2 select * from test.t1;
|
||||||
|
insert into test.t2 values (concat(x, "2"), y+2);
|
||||||
|
end|
|
||||||
|
|
||||||
|
# This doesn't work right now. It suffers from the same problem as the ones
|
||||||
|
# above, but the fix caused create.test to hang. :-(
|
||||||
|
#call create_select("cs", 90)|
|
||||||
|
#select * from t1, t2|
|
||||||
|
#delete from t1|
|
||||||
|
#drop table t2|
|
||||||
|
drop procedure create_select|
|
||||||
|
|
||||||
|
# A minimal, constant FUNCTION.
|
||||||
|
create function e() returns double
|
||||||
|
return 2.7182818284590452354|
|
||||||
|
|
||||||
|
set @e = e()|
|
||||||
|
select e(), @e|
|
||||||
|
|
||||||
|
# A minimal function with one argument
|
||||||
|
create function inc(i int) returns int
|
||||||
|
return i+1|
|
||||||
|
|
||||||
|
select inc(1), inc(99), inc(-71)|
|
||||||
|
|
||||||
|
# A minimal function with two arguments
|
||||||
|
create function mul(x int, y int) returns int
|
||||||
|
return x*y|
|
||||||
|
|
||||||
|
select mul(1,1), mul(3,5), mul(4711, 666)|
|
||||||
|
|
||||||
|
# A minimal string function
|
||||||
|
create function append(s1 char(8), s2 char(8)) returns char(16)
|
||||||
|
return concat(s1, s2)|
|
||||||
|
|
||||||
|
select append("foo", "bar")|
|
||||||
|
|
||||||
|
# A function with flow control
|
||||||
|
create function fac(n int unsigned) returns bigint unsigned
|
||||||
|
begin
|
||||||
|
declare f bigint unsigned;
|
||||||
|
|
||||||
|
set f = 1;
|
||||||
|
while n > 1 do
|
||||||
|
set f = f * n;
|
||||||
|
set n = n - 1;
|
||||||
|
end while;
|
||||||
|
return f;
|
||||||
|
end|
|
||||||
|
|
||||||
|
select fac(1), fac(2), fac(5), fac(10)|
|
||||||
|
|
||||||
|
# Nested calls
|
||||||
|
create function fun(d double, i int, u int unsigned) returns double
|
||||||
|
return mul(inc(i), fac(u)) / e()|
|
||||||
|
|
||||||
|
select fun(2.3, 3, 5)|
|
||||||
|
|
||||||
|
|
||||||
|
# Various function calls in differen statements
|
||||||
|
|
||||||
|
insert into t2 values (append("xxx", "yyy"), mul(4,3), e())|
|
||||||
|
insert into t2 values (append("a", "b"), mul(2,mul(3,4)), fun(1.7, 4, 6))|
|
||||||
|
|
||||||
|
# These don't work yet.
|
||||||
|
select * from t2 where s = append("a", "b")|
|
||||||
|
select * from t2 where i = mul(4,3) or i = mul(mul(3,4),2)|
|
||||||
|
select * from t2 where d = e()|
|
||||||
|
select * from t2|
|
||||||
|
delete from t2|
|
||||||
|
|
||||||
|
drop function e|
|
||||||
|
drop function inc|
|
||||||
|
drop function mul|
|
||||||
|
drop function append|
|
||||||
|
drop function fun|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Some "real" examples
|
||||||
|
#
|
||||||
|
|
||||||
|
# fac
|
||||||
|
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists fac|
|
||||||
|
--enable_warnings
|
||||||
|
create table fac (n int unsigned not null primary key, f bigint unsigned)|
|
||||||
|
|
||||||
|
create procedure ifac(n int unsigned)
|
||||||
|
begin
|
||||||
|
declare i int unsigned;
|
||||||
|
set i = 1;
|
||||||
|
if n > 20 then
|
||||||
|
set n = 20; # bigint overflow otherwise
|
||||||
|
end if;
|
||||||
|
while i <= n do
|
||||||
|
begin
|
||||||
|
insert into test.fac values (i, fac(i));
|
||||||
|
set i = i + 1;
|
||||||
|
end;
|
||||||
|
end while;
|
||||||
|
end|
|
||||||
|
|
||||||
|
call ifac(20)|
|
||||||
|
select * from fac|
|
||||||
|
drop table fac|
|
||||||
|
drop procedure ifac|
|
||||||
|
drop function fac|
|
||||||
|
|
||||||
|
|
||||||
|
# primes
|
||||||
|
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists primes|
|
||||||
|
--enable_warnings
|
||||||
|
|
||||||
|
create table primes (
|
||||||
|
i int unsigned not null primary key,
|
||||||
|
p bigint unsigned not null
|
||||||
|
)|
|
||||||
|
|
||||||
|
insert into primes values
|
||||||
|
( 0, 3), ( 1, 5), ( 2, 7), ( 3, 11), ( 4, 13),
|
||||||
|
( 5, 17), ( 6, 19), ( 7, 23), ( 8, 29), ( 9, 31),
|
||||||
|
(10, 37), (11, 41), (12, 43), (13, 47), (14, 53),
|
||||||
|
(15, 59), (16, 61), (17, 67), (18, 71), (19, 73),
|
||||||
|
(20, 79), (21, 83), (22, 89), (23, 97), (24, 101),
|
||||||
|
(25, 103), (26, 107), (27, 109), (28, 113), (29, 127),
|
||||||
|
(30, 131), (31, 137), (32, 139), (33, 149), (34, 151),
|
||||||
|
(35, 157), (36, 163), (37, 167), (38, 173), (39, 179),
|
||||||
|
(40, 181), (41, 191), (42, 193), (43, 197), (44, 199)|
|
||||||
|
|
||||||
|
create procedure opp(n bigint unsigned, out pp bool)
|
||||||
|
begin
|
||||||
|
declare r double;
|
||||||
|
declare b, s bigint unsigned;
|
||||||
|
|
||||||
|
set b = 0, s = 0;
|
||||||
|
set r = sqrt(n);
|
||||||
|
|
||||||
|
again:
|
||||||
|
loop
|
||||||
|
if s = 45 then
|
||||||
|
set b = b+200, s = 0;
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
declare p bigint unsigned;
|
||||||
|
|
||||||
|
select t.p into p from test.primes t where t.i = s;
|
||||||
|
if b+p > r then
|
||||||
|
set pp = 1;
|
||||||
|
leave again;
|
||||||
|
end if;
|
||||||
|
if mod(n, b+p) = 0 then
|
||||||
|
set pp = 0;
|
||||||
|
leave again;
|
||||||
|
end if;
|
||||||
|
set s = s+1;
|
||||||
|
end;
|
||||||
|
end if;
|
||||||
|
end loop again;
|
||||||
|
end|
|
||||||
|
|
||||||
|
create procedure ip(m int unsigned)
|
||||||
|
begin
|
||||||
|
declare p bigint unsigned;
|
||||||
|
declare i int unsigned;
|
||||||
|
|
||||||
|
set i=45, p=201;
|
||||||
|
|
||||||
|
while i < m do
|
||||||
|
begin
|
||||||
|
declare pp bool;
|
||||||
|
|
||||||
|
set pp = 0;
|
||||||
|
call opp(p, pp);
|
||||||
|
if pp then
|
||||||
|
insert into test.primes values (i, p);
|
||||||
|
set i = i+1;
|
||||||
|
end if;
|
||||||
|
set p = p+2;
|
||||||
|
end;
|
||||||
|
end while;
|
||||||
|
end|
|
||||||
|
|
||||||
|
# This isn't the fastest way in the world to compute prime numbers, so
|
||||||
|
# don't be too ambition. ;-)
|
||||||
|
call ip(200)|
|
||||||
|
# We don't want to select the entire table here, just pick a few
|
||||||
|
# examples.
|
||||||
|
select * from primes where i=45 or i=100 or i=199|
|
||||||
|
drop table primes|
|
||||||
|
drop procedure opp|
|
||||||
|
drop procedure ip|
|
||||||
|
|
||||||
|
delimiter ;|
|
||||||
|
drop table t1;
|
||||||
|
drop table t2;
|
@ -343,7 +343,6 @@ INSERT DELAYED INTO t1 (x) VALUES ((SELECT SUM(a) FROM t2));
|
|||||||
select * from t1;
|
select * from t1;
|
||||||
INSERT INTO t1 (x) select (SELECT SUM(a)+1 FROM t2) FROM t2;
|
INSERT INTO t1 (x) select (SELECT SUM(a)+1 FROM t2) FROM t2;
|
||||||
select * from t1;
|
select * from t1;
|
||||||
-- error 1093
|
|
||||||
INSERT INTO t1 (x) select (SELECT SUM(x)+2 FROM t1) FROM t2;
|
INSERT INTO t1 (x) select (SELECT SUM(x)+2 FROM t1) FROM t2;
|
||||||
INSERT DELAYED INTO t1 (x) VALUES ((SELECT SUM(x) FROM t2));
|
INSERT DELAYED INTO t1 (x) VALUES ((SELECT SUM(x) FROM t2));
|
||||||
-- sleep 1
|
-- sleep 1
|
||||||
|
@ -353,6 +353,19 @@ then
|
|||||||
c_c="$c_c comment='Column privileges';"
|
c_c="$c_c comment='Column privileges';"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test ! -f $mdata/proc.frm
|
||||||
|
then
|
||||||
|
echo "Preparing proc table"
|
||||||
|
|
||||||
|
c_p="$c_p CREATE TABLE proc ("
|
||||||
|
c_p="$c_p name char(64) binary DEFAULT '' NOT NULL,"
|
||||||
|
c_p="$c_p type enum('function','procedure') NOT NULL,"
|
||||||
|
c_p="$c_p body blob DEFAULT '' NOT NULL,"
|
||||||
|
c_p="$c_p PRIMARY KEY (name,type)"
|
||||||
|
c_p="$c_p )"
|
||||||
|
c_p="$c_p comment='Stored Procedures';"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Installing privilege tables"
|
echo "Installing privilege tables"
|
||||||
if (
|
if (
|
||||||
cat << END_OF_DATA
|
cat << END_OF_DATA
|
||||||
@ -371,6 +384,7 @@ $i_f
|
|||||||
|
|
||||||
$c_t
|
$c_t
|
||||||
$c_c
|
$c_c
|
||||||
|
$c_p
|
||||||
END_OF_DATA
|
END_OF_DATA
|
||||||
if test -n "$fill_help_tables"
|
if test -n "$fill_help_tables"
|
||||||
then
|
then
|
||||||
|
@ -57,7 +57,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
|
|||||||
lex.h lex_symbol.h sql_acl.h sql_crypt.h \
|
lex.h lex_symbol.h sql_acl.h sql_crypt.h \
|
||||||
log_event.h mini_client.h sql_repl.h slave.h \
|
log_event.h mini_client.h sql_repl.h slave.h \
|
||||||
stacktrace.h sql_sort.h sql_cache.h set_var.h \
|
stacktrace.h sql_sort.h sql_cache.h set_var.h \
|
||||||
spatial.h gstream.h
|
spatial.h gstream.h sp_head.h sp_pcontext.h \
|
||||||
|
sp_rcontext.h sp.h
|
||||||
mysqld_SOURCES = sql_lex.cc sql_handler.cc \
|
mysqld_SOURCES = sql_lex.cc sql_handler.cc \
|
||||||
item.cc item_sum.cc item_buff.cc item_func.cc \
|
item.cc item_sum.cc item_buff.cc item_func.cc \
|
||||||
item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \
|
item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \
|
||||||
@ -85,7 +86,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
|
|||||||
slave.cc sql_repl.cc sql_union.cc sql_derived.cc \
|
slave.cc sql_repl.cc sql_union.cc sql_derived.cc \
|
||||||
mini_client.cc mini_client_errors.c \
|
mini_client.cc mini_client_errors.c \
|
||||||
stacktrace.c repl_failsafe.h repl_failsafe.cc sql_olap.cc\
|
stacktrace.c repl_failsafe.h repl_failsafe.cc sql_olap.cc\
|
||||||
gstream.cc spatial.cc sql_help.cc
|
gstream.cc spatial.cc sql_help.cc \
|
||||||
|
sp_head.cc sp_pcontext.cc sp.cc
|
||||||
gen_lex_hash_SOURCES = gen_lex_hash.cc
|
gen_lex_hash_SOURCES = gen_lex_hash.cc
|
||||||
gen_lex_hash_LDADD = $(LDADD) $(CXXLDFLAGS)
|
gen_lex_hash_LDADD = $(LDADD) $(CXXLDFLAGS)
|
||||||
|
|
||||||
|
18
sql/item.cc
18
sql/item.cc
@ -22,6 +22,7 @@
|
|||||||
#include "mysql_priv.h"
|
#include "mysql_priv.h"
|
||||||
#include <m_ctype.h>
|
#include <m_ctype.h>
|
||||||
#include "my_dir.h"
|
#include "my_dir.h"
|
||||||
|
#include "sp_rcontext.h"
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
** Item functions
|
** Item functions
|
||||||
@ -175,6 +176,23 @@ CHARSET_INFO * Item::default_charset() const
|
|||||||
return current_thd->db_charset;
|
return current_thd->db_charset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Item *
|
||||||
|
Item_splocal::this_item()
|
||||||
|
{
|
||||||
|
THD *thd= current_thd;
|
||||||
|
|
||||||
|
return thd->spcont->get_item(m_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Item *
|
||||||
|
Item_splocal::this_const_item() const
|
||||||
|
{
|
||||||
|
THD *thd= current_thd;
|
||||||
|
|
||||||
|
return thd->spcont->get_item(m_offset);
|
||||||
|
}
|
||||||
|
|
||||||
bool Item::set_charset(CHARSET_INFO *cs1, enum coercion co1,
|
bool Item::set_charset(CHARSET_INFO *cs1, enum coercion co1,
|
||||||
CHARSET_INFO *cs2, enum coercion co2)
|
CHARSET_INFO *cs2, enum coercion co2)
|
||||||
{
|
{
|
||||||
|
53
sql/item.h
53
sql/item.h
@ -123,6 +123,8 @@ public:
|
|||||||
bool set_charset(CHARSET_INFO *cs1, enum coercion co1,
|
bool set_charset(CHARSET_INFO *cs1, enum coercion co1,
|
||||||
CHARSET_INFO *cs2, enum coercion co2);
|
CHARSET_INFO *cs2, enum coercion co2);
|
||||||
virtual void set_outer_resolving() {}
|
virtual void set_outer_resolving() {}
|
||||||
|
virtual Item *this_item() { return this; } /* For SPs mostly. */
|
||||||
|
virtual Item *this_const_item() const { return const_cast<Item*>(this); } /* For SPs mostly. */
|
||||||
|
|
||||||
// Row emulation
|
// Row emulation
|
||||||
virtual uint cols() { return 1; }
|
virtual uint cols() { return 1; }
|
||||||
@ -136,6 +138,57 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// A local SP variable (incl. parameters), used in runtime
|
||||||
|
class Item_splocal : public Item
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint m_offset;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Item_splocal(uint offset)
|
||||||
|
: m_offset(offset)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Item *this_item();
|
||||||
|
Item *this_const_item() const;
|
||||||
|
|
||||||
|
inline uint get_offset()
|
||||||
|
{
|
||||||
|
return m_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abstract methods inherited from Item. Just defer the call to
|
||||||
|
// the item in the frame
|
||||||
|
inline enum Type type() const
|
||||||
|
{
|
||||||
|
return this_const_item()->type();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double val()
|
||||||
|
{
|
||||||
|
return this_item()->val();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline longlong val_int()
|
||||||
|
{
|
||||||
|
return this_item()->val_int();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String *val_str(String *sp)
|
||||||
|
{
|
||||||
|
return this_item()->val_str(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void make_field(Send_field *field)
|
||||||
|
{
|
||||||
|
this_item()->make_field(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class st_select_lex;
|
class st_select_lex;
|
||||||
class Item_ident :public Item
|
class Item_ident :public Item
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
#ifdef HAVE_COMPRESS
|
#ifdef HAVE_COMPRESS
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sp_rcontext.h"
|
||||||
|
#include "sp.h"
|
||||||
|
|
||||||
/* return TRUE if item is a constant */
|
/* return TRUE if item is a constant */
|
||||||
|
|
||||||
@ -2839,3 +2842,74 @@ longlong Item_func_srid::val_int()
|
|||||||
uint32 res= uint4korr(swkb->ptr());
|
uint32 res= uint4korr(swkb->ptr());
|
||||||
return (longlong) res;
|
return (longlong) res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Item_func_sp::execute(Item **itp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("Item_func_sp::execute");
|
||||||
|
THD *thd= current_thd;
|
||||||
|
|
||||||
|
if (! m_sp)
|
||||||
|
m_sp= sp_find_function(thd, &m_name);
|
||||||
|
if (! m_sp)
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
|
||||||
|
DBUG_RETURN(m_sp->execute_function(thd, args, arg_count, itp));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum enum_field_types
|
||||||
|
Item_func_sp::field_type() const
|
||||||
|
{
|
||||||
|
DBUG_ENTER("Item_func_sp::field_type");
|
||||||
|
|
||||||
|
if (! m_sp)
|
||||||
|
m_sp= sp_find_function(current_thd, const_cast<LEX_STRING*>(&m_name));
|
||||||
|
if (m_sp)
|
||||||
|
{
|
||||||
|
DBUG_PRINT("info", ("m_returns = %d", m_sp->m_returns));
|
||||||
|
DBUG_RETURN(m_sp->m_returns);
|
||||||
|
}
|
||||||
|
DBUG_RETURN(MYSQL_TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
Item_result
|
||||||
|
Item_func_sp::result_type() const
|
||||||
|
{
|
||||||
|
DBUG_ENTER("Item_func_sp::result_type");
|
||||||
|
DBUG_PRINT("info", ("m_sp = %p", m_sp));
|
||||||
|
|
||||||
|
if (! m_sp)
|
||||||
|
m_sp= sp_find_function(current_thd, const_cast<LEX_STRING*>(&m_name));
|
||||||
|
if (m_sp)
|
||||||
|
{
|
||||||
|
DBUG_RETURN(m_sp->result());
|
||||||
|
}
|
||||||
|
DBUG_RETURN(STRING_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Item_func_sp::fix_length_and_dec()
|
||||||
|
{
|
||||||
|
DBUG_ENTER("Item_func_sp::fix_length_and_dec");
|
||||||
|
|
||||||
|
if (! m_sp)
|
||||||
|
m_sp= sp_find_function(current_thd, &m_name);
|
||||||
|
if (m_sp)
|
||||||
|
{
|
||||||
|
switch (m_sp->result()) {
|
||||||
|
case STRING_RESULT:
|
||||||
|
maybe_null= 1;
|
||||||
|
max_length= 0;
|
||||||
|
break;
|
||||||
|
case REAL_RESULT:
|
||||||
|
decimals= NOT_FIXED_DEC;
|
||||||
|
max_length= float_length(decimals);
|
||||||
|
break;
|
||||||
|
case INT_RESULT:
|
||||||
|
decimals= 0;
|
||||||
|
max_length= 21;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBUG_VOID_RETURN;
|
||||||
|
}
|
||||||
|
@ -1179,3 +1179,69 @@ enum Item_cast
|
|||||||
ITEM_CAST_BINARY, ITEM_CAST_SIGNED_INT, ITEM_CAST_UNSIGNED_INT,
|
ITEM_CAST_BINARY, ITEM_CAST_SIGNED_INT, ITEM_CAST_UNSIGNED_INT,
|
||||||
ITEM_CAST_DATE, ITEM_CAST_TIME, ITEM_CAST_DATETIME, ITEM_CAST_CHAR
|
ITEM_CAST_DATE, ITEM_CAST_TIME, ITEM_CAST_DATETIME, ITEM_CAST_CHAR
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Stored FUNCTIONs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class sp_head;
|
||||||
|
|
||||||
|
class Item_func_sp :public Item_func
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
LEX_STRING m_name;
|
||||||
|
mutable sp_head *m_sp;
|
||||||
|
|
||||||
|
int execute(Item **itp);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Item_func_sp(LEX_STRING name)
|
||||||
|
:Item_func(), m_name(name), m_sp(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Item_func_sp(LEX_STRING name, List<Item> &list)
|
||||||
|
:Item_func(list), m_name(name), m_sp(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~Item_func_sp()
|
||||||
|
{}
|
||||||
|
|
||||||
|
const char *func_name() const
|
||||||
|
{
|
||||||
|
return m_name.str;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum enum_field_types field_type() const;
|
||||||
|
|
||||||
|
Item_result result_type() const;
|
||||||
|
|
||||||
|
longlong val_int()
|
||||||
|
{
|
||||||
|
return (longlong)Item_func_sp::val();
|
||||||
|
}
|
||||||
|
|
||||||
|
double val()
|
||||||
|
{
|
||||||
|
Item *it;
|
||||||
|
|
||||||
|
if (execute(&it))
|
||||||
|
return 0.0;
|
||||||
|
return it->val();
|
||||||
|
}
|
||||||
|
|
||||||
|
String *val_str(String *str)
|
||||||
|
{
|
||||||
|
Item *it;
|
||||||
|
|
||||||
|
if (execute(&it))
|
||||||
|
return NULL;
|
||||||
|
return it->val_str(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fix_length_and_dec();
|
||||||
|
|
||||||
|
};
|
||||||
|
22
sql/lex.h
22
sql/lex.h
@ -60,6 +60,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "AS", SYM(AS),0,0},
|
{ "AS", SYM(AS),0,0},
|
||||||
{ "ASC", SYM(ASC),0,0},
|
{ "ASC", SYM(ASC),0,0},
|
||||||
{ "ASCII", SYM(ASCII_SYM),0,0},
|
{ "ASCII", SYM(ASCII_SYM),0,0},
|
||||||
|
{ "ASENSITIVE", SYM(ASENSITIVE_SYM),0,0},
|
||||||
{ "AVG", SYM(AVG_SYM),0,0},
|
{ "AVG", SYM(AVG_SYM),0,0},
|
||||||
{ "AVG_ROW_LENGTH", SYM(AVG_ROW_LENGTH),0,0},
|
{ "AVG_ROW_LENGTH", SYM(AVG_ROW_LENGTH),0,0},
|
||||||
{ "AUTO_INCREMENT", SYM(AUTO_INC),0,0},
|
{ "AUTO_INCREMENT", SYM(AUTO_INC),0,0},
|
||||||
@ -81,6 +82,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "BY", SYM(BY),0,0},
|
{ "BY", SYM(BY),0,0},
|
||||||
{ "BYTE", SYM(BYTE_SYM), 0, 0},
|
{ "BYTE", SYM(BYTE_SYM), 0, 0},
|
||||||
{ "CACHE", SYM(CACHE_SYM),0,0},
|
{ "CACHE", SYM(CACHE_SYM),0,0},
|
||||||
|
{ "CALL", SYM(CALL_SYM),0,0},
|
||||||
{ "CASCADE", SYM(CASCADE),0,0},
|
{ "CASCADE", SYM(CASCADE),0,0},
|
||||||
{ "CASE", SYM(CASE_SYM),0,0},
|
{ "CASE", SYM(CASE_SYM),0,0},
|
||||||
{ "CHAR", SYM(CHAR_SYM),0,0},
|
{ "CHAR", SYM(CHAR_SYM),0,0},
|
||||||
@ -109,6 +111,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "CURRENT_DATE", SYM(CURDATE),0,0},
|
{ "CURRENT_DATE", SYM(CURDATE),0,0},
|
||||||
{ "CURRENT_TIME", SYM(CURTIME),0,0},
|
{ "CURRENT_TIME", SYM(CURTIME),0,0},
|
||||||
{ "CURRENT_TIMESTAMP", SYM(NOW_SYM),0,0},
|
{ "CURRENT_TIMESTAMP", SYM(NOW_SYM),0,0},
|
||||||
|
{ "CURSOR", SYM(CURSOR_SYM),0,0},
|
||||||
{ "DATA", SYM(DATA_SYM),0,0},
|
{ "DATA", SYM(DATA_SYM),0,0},
|
||||||
{ "DATABASE", SYM(DATABASE),0,0},
|
{ "DATABASE", SYM(DATABASE),0,0},
|
||||||
{ "DATABASES", SYM(DATABASES),0,0},
|
{ "DATABASES", SYM(DATABASES),0,0},
|
||||||
@ -120,6 +123,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "DAY_SECOND", SYM(DAY_SECOND_SYM),0,0},
|
{ "DAY_SECOND", SYM(DAY_SECOND_SYM),0,0},
|
||||||
{ "DEC", SYM(DECIMAL_SYM),0,0},
|
{ "DEC", SYM(DECIMAL_SYM),0,0},
|
||||||
{ "DECIMAL", SYM(DECIMAL_SYM),0,0},
|
{ "DECIMAL", SYM(DECIMAL_SYM),0,0},
|
||||||
|
{ "DECLARE", SYM(DECLARE_SYM),0,0},
|
||||||
{ "DES_KEY_FILE", SYM(DES_KEY_FILE),0,0},
|
{ "DES_KEY_FILE", SYM(DES_KEY_FILE),0,0},
|
||||||
{ "DEFAULT", SYM(DEFAULT),0,0},
|
{ "DEFAULT", SYM(DEFAULT),0,0},
|
||||||
{ "DELAYED", SYM(DELAYED_SYM),0,0},
|
{ "DELAYED", SYM(DELAYED_SYM),0,0},
|
||||||
@ -142,6 +146,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "ERRORS", SYM(ERRORS),0,0},
|
{ "ERRORS", SYM(ERRORS),0,0},
|
||||||
{ "END", SYM(END),0,0},
|
{ "END", SYM(END),0,0},
|
||||||
{ "ELSE", SYM(ELSE),0,0},
|
{ "ELSE", SYM(ELSE),0,0},
|
||||||
|
{ "ELSEIF", SYM(ELSEIF_SYM),0,0},
|
||||||
{ "ESCAPE", SYM(ESCAPE_SYM),0,0},
|
{ "ESCAPE", SYM(ESCAPE_SYM),0,0},
|
||||||
{ "ESCAPED", SYM(ESCAPED),0,0},
|
{ "ESCAPED", SYM(ESCAPED),0,0},
|
||||||
{ "ENABLE", SYM(ENABLE_SYM),0,0},
|
{ "ENABLE", SYM(ENABLE_SYM),0,0},
|
||||||
@ -172,7 +177,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "FOR", SYM(FOR_SYM),0,0},
|
{ "FOR", SYM(FOR_SYM),0,0},
|
||||||
{ "FULL", SYM(FULL),0,0},
|
{ "FULL", SYM(FULL),0,0},
|
||||||
{ "FULLTEXT", SYM(FULLTEXT_SYM),0,0},
|
{ "FULLTEXT", SYM(FULLTEXT_SYM),0,0},
|
||||||
{ "FUNCTION", SYM(UDF_SYM),0,0},
|
{ "FUNCTION", SYM(FUNCTION_SYM),0,0},
|
||||||
{ "GEOMETRY", SYM(GEOMETRY_SYM),0,0},
|
{ "GEOMETRY", SYM(GEOMETRY_SYM),0,0},
|
||||||
{ "GEOMETRYCOLLECTION",SYM(GEOMETRYCOLLECTION),0,0},
|
{ "GEOMETRYCOLLECTION",SYM(GEOMETRYCOLLECTION),0,0},
|
||||||
{ "GLOBAL", SYM(GLOBAL_SYM),0,0},
|
{ "GLOBAL", SYM(GLOBAL_SYM),0,0},
|
||||||
@ -198,6 +203,8 @@ static SYMBOL symbols[] = {
|
|||||||
{ "INNER", SYM(INNER_SYM),0,0},
|
{ "INNER", SYM(INNER_SYM),0,0},
|
||||||
{ "INNOBASE", SYM(INNOBASE_SYM),0,0},
|
{ "INNOBASE", SYM(INNOBASE_SYM),0,0},
|
||||||
{ "INNODB", SYM(INNOBASE_SYM),0,0},
|
{ "INNODB", SYM(INNOBASE_SYM),0,0},
|
||||||
|
{ "INOUT", SYM(INOUT_SYM),0,0},
|
||||||
|
{ "INSENSITIVE", SYM(INSENSITIVE_SYM),0,0},
|
||||||
{ "INSERT", SYM(INSERT),0,0},
|
{ "INSERT", SYM(INSERT),0,0},
|
||||||
{ "INSERT_METHOD", SYM(INSERT_METHOD),0,0},
|
{ "INSERT_METHOD", SYM(INSERT_METHOD),0,0},
|
||||||
{ "INT", SYM(INT_SYM),0,0},
|
{ "INT", SYM(INT_SYM),0,0},
|
||||||
@ -215,12 +222,14 @@ static SYMBOL symbols[] = {
|
|||||||
{ "ISOLATION", SYM(ISOLATION),0,0},
|
{ "ISOLATION", SYM(ISOLATION),0,0},
|
||||||
{ "ISAM", SYM(ISAM_SYM),0,0},
|
{ "ISAM", SYM(ISAM_SYM),0,0},
|
||||||
{ "ISSUER", SYM(ISSUER_SYM),0,0},
|
{ "ISSUER", SYM(ISSUER_SYM),0,0},
|
||||||
|
{ "ITERATE", SYM(ITERATE_SYM),0,0},
|
||||||
{ "JOIN", SYM(JOIN_SYM),0,0},
|
{ "JOIN", SYM(JOIN_SYM),0,0},
|
||||||
{ "KEY", SYM(KEY_SYM),0,0},
|
{ "KEY", SYM(KEY_SYM),0,0},
|
||||||
{ "KEYS", SYM(KEYS),0,0},
|
{ "KEYS", SYM(KEYS),0,0},
|
||||||
{ "KILL", SYM(KILL_SYM),0,0},
|
{ "KILL", SYM(KILL_SYM),0,0},
|
||||||
{ "LAST", SYM(LAST_SYM),0,0},
|
{ "LAST", SYM(LAST_SYM),0,0},
|
||||||
{ "LEADING", SYM(LEADING),0,0},
|
{ "LEADING", SYM(LEADING),0,0},
|
||||||
|
{ "LEAVE", SYM(LEAVE_SYM),0,0},
|
||||||
{ "LEFT", SYM(LEFT),0,0},
|
{ "LEFT", SYM(LEFT),0,0},
|
||||||
{ "LEVEL", SYM(LEVEL_SYM),0,0},
|
{ "LEVEL", SYM(LEVEL_SYM),0,0},
|
||||||
{ "LIKE", SYM(LIKE),0,0},
|
{ "LIKE", SYM(LIKE),0,0},
|
||||||
@ -236,6 +245,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "LOGS", SYM(LOGS_SYM),0,0},
|
{ "LOGS", SYM(LOGS_SYM),0,0},
|
||||||
{ "LONG", SYM(LONG_SYM),0,0},
|
{ "LONG", SYM(LONG_SYM),0,0},
|
||||||
{ "LONGBLOB", SYM(LONGBLOB),0,0},
|
{ "LONGBLOB", SYM(LONGBLOB),0,0},
|
||||||
|
{ "LOOP", SYM(LOOP_SYM),0,0},
|
||||||
{ "LONGTEXT", SYM(LONGTEXT),0,0},
|
{ "LONGTEXT", SYM(LONGTEXT),0,0},
|
||||||
{ "LOW_PRIORITY", SYM(LOW_PRIORITY),0,0},
|
{ "LOW_PRIORITY", SYM(LOW_PRIORITY),0,0},
|
||||||
{ "MASTER", SYM(MASTER_SYM),0,0},
|
{ "MASTER", SYM(MASTER_SYM),0,0},
|
||||||
@ -290,6 +300,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "OPTIONALLY", SYM(OPTIONALLY),0,0},
|
{ "OPTIONALLY", SYM(OPTIONALLY),0,0},
|
||||||
{ "OR", SYM(OR),0,0},
|
{ "OR", SYM(OR),0,0},
|
||||||
{ "ORDER", SYM(ORDER_SYM),0,0},
|
{ "ORDER", SYM(ORDER_SYM),0,0},
|
||||||
|
{ "OUT", SYM(OUT_SYM),0,0},
|
||||||
{ "OUTER", SYM(OUTER),0,0},
|
{ "OUTER", SYM(OUTER),0,0},
|
||||||
{ "OUTFILE", SYM(OUTFILE),0,0},
|
{ "OUTFILE", SYM(OUTFILE),0,0},
|
||||||
{ "PACK_KEYS", SYM(PACK_KEYS_SYM),0,0},
|
{ "PACK_KEYS", SYM(PACK_KEYS_SYM),0,0},
|
||||||
@ -319,13 +330,15 @@ static SYMBOL symbols[] = {
|
|||||||
{ "REPAIR", SYM(REPAIR),0,0},
|
{ "REPAIR", SYM(REPAIR),0,0},
|
||||||
{ "REPLACE", SYM(REPLACE),0,0},
|
{ "REPLACE", SYM(REPLACE),0,0},
|
||||||
{ "REPLICATION", SYM(REPLICATION),0,0},
|
{ "REPLICATION", SYM(REPLICATION),0,0},
|
||||||
|
{ "REPEAT", SYM(REPEAT_SYM),0,0},
|
||||||
{ "REPEATABLE", SYM(REPEATABLE_SYM),0,0},
|
{ "REPEATABLE", SYM(REPEATABLE_SYM),0,0},
|
||||||
{ "REQUIRE", SYM(REQUIRE_SYM),0,0},
|
{ "REQUIRE", SYM(REQUIRE_SYM),0,0},
|
||||||
{ "RESET", SYM(RESET_SYM),0,0},
|
{ "RESET", SYM(RESET_SYM),0,0},
|
||||||
{ "USER_RESOURCES", SYM(RESOURCES),0,0},
|
{ "USER_RESOURCES", SYM(RESOURCES),0,0},
|
||||||
{ "RESTORE", SYM(RESTORE_SYM),0,0},
|
{ "RESTORE", SYM(RESTORE_SYM),0,0},
|
||||||
{ "RESTRICT", SYM(RESTRICT),0,0},
|
{ "RESTRICT", SYM(RESTRICT),0,0},
|
||||||
{ "RETURNS", SYM(UDF_RETURNS_SYM),0,0},
|
{ "RETURN", SYM(RETURN_SYM),0,0},
|
||||||
|
{ "RETURNS", SYM(RETURNS_SYM),0,0},
|
||||||
{ "REVOKE", SYM(REVOKE),0,0},
|
{ "REVOKE", SYM(REVOKE),0,0},
|
||||||
{ "RIGHT", SYM(RIGHT),0,0},
|
{ "RIGHT", SYM(RIGHT),0,0},
|
||||||
{ "RLIKE", SYM(REGEXP),0,0}, /* Like in mSQL2 */
|
{ "RLIKE", SYM(REGEXP),0,0}, /* Like in mSQL2 */
|
||||||
@ -336,6 +349,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "RTREE", SYM(RTREE_SYM),0,0},
|
{ "RTREE", SYM(RTREE_SYM),0,0},
|
||||||
{ "SECOND", SYM(SECOND_SYM),0,0},
|
{ "SECOND", SYM(SECOND_SYM),0,0},
|
||||||
{ "SELECT", SYM(SELECT_SYM),0,0},
|
{ "SELECT", SYM(SELECT_SYM),0,0},
|
||||||
|
{ "SENSITIVE", SYM(SENSITIVE_SYM),0,0},
|
||||||
{ "SERIAL", SYM(SERIAL_SYM),0,0},
|
{ "SERIAL", SYM(SERIAL_SYM),0,0},
|
||||||
{ "SERIALIZABLE", SYM(SERIALIZABLE_SYM),0,0},
|
{ "SERIALIZABLE", SYM(SERIALIZABLE_SYM),0,0},
|
||||||
{ "SESSION", SYM(SESSION_SYM),0,0},
|
{ "SESSION", SYM(SESSION_SYM),0,0},
|
||||||
@ -350,6 +364,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "SOME", SYM(ANY_SYM),0,0},
|
{ "SOME", SYM(ANY_SYM),0,0},
|
||||||
{ "SONAME", SYM(UDF_SONAME_SYM),0,0},
|
{ "SONAME", SYM(UDF_SONAME_SYM),0,0},
|
||||||
{ "SPATIAL", SYM(SPATIAL_SYM),0,0},
|
{ "SPATIAL", SYM(SPATIAL_SYM),0,0},
|
||||||
|
{ "SPECIFIC", SYM(SPECIFIC_SYM),0,0},
|
||||||
{ "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT),0,0},
|
{ "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT),0,0},
|
||||||
{ "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT),0,0},
|
{ "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT),0,0},
|
||||||
{ "SQL_CACHE", SYM(SQL_CACHE_SYM), 0, 0},
|
{ "SQL_CACHE", SYM(SQL_CACHE_SYM), 0, 0},
|
||||||
@ -392,6 +407,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "UNIQUE", SYM(UNIQUE_SYM),0,0},
|
{ "UNIQUE", SYM(UNIQUE_SYM),0,0},
|
||||||
{ "UNLOCK", SYM(UNLOCK_SYM),0,0},
|
{ "UNLOCK", SYM(UNLOCK_SYM),0,0},
|
||||||
{ "UNSIGNED", SYM(UNSIGNED),0,0},
|
{ "UNSIGNED", SYM(UNSIGNED),0,0},
|
||||||
|
{ "UNTIL", SYM(UNTIL_SYM),0,0},
|
||||||
{ "USE", SYM(USE_SYM),0,0},
|
{ "USE", SYM(USE_SYM),0,0},
|
||||||
{ "USE_FRM", SYM(USE_FRM),0,0},
|
{ "USE_FRM", SYM(USE_FRM),0,0},
|
||||||
{ "USING", SYM(USING),0,0},
|
{ "USING", SYM(USING),0,0},
|
||||||
@ -410,6 +426,7 @@ static SYMBOL symbols[] = {
|
|||||||
{ "WRITE", SYM(WRITE_SYM),0,0},
|
{ "WRITE", SYM(WRITE_SYM),0,0},
|
||||||
{ "WHEN", SYM(WHEN_SYM),0,0},
|
{ "WHEN", SYM(WHEN_SYM),0,0},
|
||||||
{ "WHERE", SYM(WHERE),0,0},
|
{ "WHERE", SYM(WHERE),0,0},
|
||||||
|
{ "WHILE", SYM(WHILE_SYM),0,0},
|
||||||
{ "XOR", SYM(XOR),0,0},
|
{ "XOR", SYM(XOR),0,0},
|
||||||
{ "X509", SYM(X509_SYM),0,0},
|
{ "X509", SYM(X509_SYM),0,0},
|
||||||
{ "YEAR", SYM(YEAR_SYM),0,0},
|
{ "YEAR", SYM(YEAR_SYM),0,0},
|
||||||
@ -568,7 +585,6 @@ static SYMBOL sql_functions[] = {
|
|||||||
{ "RADIANS", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_radians)},
|
{ "RADIANS", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_radians)},
|
||||||
{ "RAND", SYM(RAND),0,0},
|
{ "RAND", SYM(RAND),0,0},
|
||||||
{ "RELEASE_LOCK", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_release_lock)},
|
{ "RELEASE_LOCK", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_release_lock)},
|
||||||
{ "REPEAT", SYM(FUNC_ARG2),0,CREATE_FUNC(create_func_repeat)},
|
|
||||||
{ "REVERSE", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_reverse)},
|
{ "REVERSE", SYM(FUNC_ARG1),0,CREATE_FUNC(create_func_reverse)},
|
||||||
{ "ROUND", SYM(ROUND),0,0},
|
{ "ROUND", SYM(ROUND),0,0},
|
||||||
{ "RPAD", SYM(FUNC_ARG3),0,CREATE_FUNC(create_func_rpad)},
|
{ "RPAD", SYM(FUNC_ARG3),0,CREATE_FUNC(create_func_rpad)},
|
||||||
|
@ -362,7 +362,7 @@ extern "C" pthread_handler_decl(handle_one_connection,arg);
|
|||||||
extern "C" pthread_handler_decl(handle_bootstrap,arg);
|
extern "C" pthread_handler_decl(handle_bootstrap,arg);
|
||||||
void end_thread(THD *thd,bool put_in_cache);
|
void end_thread(THD *thd,bool put_in_cache);
|
||||||
void flush_thread_cache();
|
void flush_thread_cache();
|
||||||
void mysql_execute_command(THD *thd);
|
int mysql_execute_command(THD *thd);
|
||||||
bool do_command(THD *thd);
|
bool do_command(THD *thd);
|
||||||
bool dispatch_command(enum enum_server_command command, THD *thd,
|
bool dispatch_command(enum enum_server_command command, THD *thd,
|
||||||
char* packet, uint packet_length);
|
char* packet, uint packet_length);
|
||||||
|
@ -264,3 +264,15 @@ v/*
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -258,3 +258,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -266,3 +266,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -260,3 +260,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -265,3 +265,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -257,3 +257,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -257,3 +257,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -257,3 +257,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -257,3 +257,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -259,3 +259,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -259,3 +259,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -257,3 +257,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -251,3 +251,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -263,3 +263,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -256,3 +256,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -255,3 +255,15 @@
|
|||||||
"COLLATION '%s' är inte tillåtet för CHARACTER SET '%s'"
|
"COLLATION '%s' är inte tillåtet för CHARACTER SET '%s'"
|
||||||
"Slaven har redan startat"
|
"Slaven har redan startat"
|
||||||
"Slaven har redan stoppat"
|
"Slaven har redan stoppat"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
@ -260,3 +260,15 @@
|
|||||||
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
"COLLATION '%s' is not valid for CHARACTER SET '%s'"
|
||||||
"The slave was already running"
|
"The slave was already running"
|
||||||
"The slave was already stopped"
|
"The slave was already stopped"
|
||||||
|
"Can't create a %s from within another stored routine"
|
||||||
|
"%s %s already exists"
|
||||||
|
"%s %s does not exist"
|
||||||
|
"Failed to DROP %s %s"
|
||||||
|
"Failed to CREATE %s %s"
|
||||||
|
"%s with no matching label: %s"
|
||||||
|
"Redefining label %s"
|
||||||
|
"End-label %s without match"
|
||||||
|
"Referring to uninitialized variable %s"
|
||||||
|
"SELECT in a stored procedure must have INTO"
|
||||||
|
"RETURN is only allowed in a FUNCTION"
|
||||||
|
"Queries, like SELECT, INSERT, UPDATE (and others), are not allowed in a FUNCTION"
|
||||||
|
391
sql/sp.cc
Normal file
391
sql/sp.cc
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
|
||||||
|
#include "mysql_priv.h"
|
||||||
|
#include "sp.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
|
||||||
|
static sp_head *
|
||||||
|
sp_find_cached_function(THD *thd, char *name, uint namelen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* DB storage of Stored PROCEDUREs and FUNCTIONs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// *openeed=true means we opened ourselves
|
||||||
|
static int
|
||||||
|
db_find_routine_aux(THD *thd, int type, char *name, uint namelen,
|
||||||
|
enum thr_lock_type ltype, TABLE **tablep, bool *opened)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("db_find_routine_aux");
|
||||||
|
DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
|
||||||
|
TABLE *table;
|
||||||
|
byte key[65]; // We know name is 64 and the enum is 1 byte
|
||||||
|
uint keylen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// Put the key together
|
||||||
|
keylen= namelen;
|
||||||
|
if (keylen > sizeof(key)-1)
|
||||||
|
keylen= sizeof(key)-1;
|
||||||
|
memcpy(key, name, keylen);
|
||||||
|
memset(key+keylen, (int)' ', sizeof(key)-1 - keylen); // Pad with space
|
||||||
|
key[sizeof(key)-1]= type;
|
||||||
|
keylen= sizeof(key);
|
||||||
|
|
||||||
|
for (table= thd->open_tables ; table ; table= table->next)
|
||||||
|
if (strcmp(table->table_cache_key, "mysql") == 0 &&
|
||||||
|
strcmp(table->real_name, "proc") == 0)
|
||||||
|
break;
|
||||||
|
if (table)
|
||||||
|
*opened= FALSE;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TABLE_LIST tables;
|
||||||
|
|
||||||
|
memset(&tables, 0, sizeof(tables));
|
||||||
|
tables.db= (char*)"mysql";
|
||||||
|
tables.real_name= tables.alias= (char*)"proc";
|
||||||
|
if (! (table= open_ltable(thd, &tables, ltype)))
|
||||||
|
{
|
||||||
|
*tablep= NULL;
|
||||||
|
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
|
||||||
|
}
|
||||||
|
*opened= TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table->file->index_read_idx(table->record[0], 0,
|
||||||
|
key, keylen,
|
||||||
|
HA_READ_KEY_EXACT))
|
||||||
|
{
|
||||||
|
*tablep= NULL;
|
||||||
|
DBUG_RETURN(SP_KEY_NOT_FOUND);
|
||||||
|
}
|
||||||
|
*tablep= table;
|
||||||
|
|
||||||
|
DBUG_RETURN(SP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
db_find_routine(THD *thd, int type, char *name, uint namelen, sp_head **sphp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("db_find_routine");
|
||||||
|
DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
|
||||||
|
extern int yyparse(void *thd);
|
||||||
|
LEX *tmplex;
|
||||||
|
TABLE *table;
|
||||||
|
const char *defstr;
|
||||||
|
int ret;
|
||||||
|
bool opened;
|
||||||
|
|
||||||
|
// QQ Set up our own mem_root here???
|
||||||
|
ret= db_find_routine_aux(thd, type, name, namelen, TL_READ, &table, &opened);
|
||||||
|
if (ret != SP_OK)
|
||||||
|
goto done;
|
||||||
|
if ((defstr= get_field(&thd->mem_root, table->field[2])) == NULL)
|
||||||
|
{
|
||||||
|
ret= SP_GET_FIELD_FAILED;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (opened)
|
||||||
|
{
|
||||||
|
close_thread_tables(thd);
|
||||||
|
table= NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmplex= lex_start(thd, (uchar*)defstr, strlen(defstr));
|
||||||
|
if (yyparse(thd) || thd->is_fatal_error || tmplex->sphead == NULL)
|
||||||
|
ret= SP_PARSE_ERROR;
|
||||||
|
else
|
||||||
|
*sphp= tmplex->sphead;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (table && opened)
|
||||||
|
close_thread_tables(thd);
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
db_create_routine(THD *thd, int type,
|
||||||
|
char *name, uint namelen, char *def, uint deflen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("db_create_routine");
|
||||||
|
DBUG_PRINT("enter", ("type: %d name: %*s def: %*s", type, namelen, name, deflen, def));
|
||||||
|
int ret;
|
||||||
|
TABLE *table;
|
||||||
|
TABLE_LIST tables;
|
||||||
|
|
||||||
|
memset(&tables, 0, sizeof(tables));
|
||||||
|
tables.db= (char*)"mysql";
|
||||||
|
tables.real_name= tables.alias= (char*)"proc";
|
||||||
|
|
||||||
|
if (! (table= open_ltable(thd, &tables, TL_WRITE)))
|
||||||
|
ret= SP_OPEN_TABLE_FAILED;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
restore_record(table, 2); // Get default values for fields
|
||||||
|
|
||||||
|
table->field[0]->store(name, namelen, system_charset_info);
|
||||||
|
table->field[1]->store((longlong)type);
|
||||||
|
table->field[2]->store(def, deflen, system_charset_info);
|
||||||
|
|
||||||
|
if (table->file->write_row(table->record[0]))
|
||||||
|
ret= SP_WRITE_ROW_FAILED;
|
||||||
|
else
|
||||||
|
ret= SP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
close_thread_tables(thd);
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
db_drop_routine(THD *thd, int type, char *name, uint namelen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("db_drop_routine");
|
||||||
|
DBUG_PRINT("enter", ("type: %d name: %*s", type, namelen, name));
|
||||||
|
TABLE *table;
|
||||||
|
int ret;
|
||||||
|
bool opened;
|
||||||
|
|
||||||
|
ret= db_find_routine_aux(thd, type, name, namelen, TL_WRITE, &table, &opened);
|
||||||
|
if (ret == SP_OK)
|
||||||
|
{
|
||||||
|
if (table->file->delete_row(table->record[0]))
|
||||||
|
ret= SP_DELETE_ROW_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opened)
|
||||||
|
close_thread_tables(thd);
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* PROCEDURE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
sp_head *
|
||||||
|
sp_find_procedure(THD *thd, LEX_STRING *name)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_find_procedure");
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
DBUG_PRINT("enter", ("name: %*s", name->length, name->str));
|
||||||
|
|
||||||
|
if (db_find_routine(thd, TYPE_ENUM_PROCEDURE,
|
||||||
|
name->str, name->length, &sp) != SP_OK)
|
||||||
|
sp= NULL;
|
||||||
|
|
||||||
|
DBUG_RETURN(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_create_procedure(THD *thd, char *name, uint namelen, char *def, uint deflen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_create_procedure");
|
||||||
|
DBUG_PRINT("enter", ("name: %*s def: %*s", namelen, name, deflen, def));
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret= db_create_routine(thd, TYPE_ENUM_PROCEDURE, name, namelen, def, deflen);
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_drop_procedure(THD *thd, char *name, uint namelen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_drop_procedure");
|
||||||
|
DBUG_PRINT("enter", ("name: %*s", namelen, name));
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret= db_drop_routine(thd, TYPE_ENUM_PROCEDURE, name, namelen);
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* FUNCTION
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
sp_head *
|
||||||
|
sp_find_function(THD *thd, LEX_STRING *name)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_find_function");
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
DBUG_PRINT("enter", ("name: %*s", name->length, name->str));
|
||||||
|
|
||||||
|
sp= sp_find_cached_function(thd, name->str, name->length);
|
||||||
|
if (! sp)
|
||||||
|
{
|
||||||
|
if (db_find_routine(thd, TYPE_ENUM_FUNCTION,
|
||||||
|
name->str, name->length, &sp) != SP_OK)
|
||||||
|
sp= NULL;
|
||||||
|
}
|
||||||
|
DBUG_RETURN(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_create_function(THD *thd, char *name, uint namelen, char *def, uint deflen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_create_function");
|
||||||
|
DBUG_PRINT("enter", ("name: %*s def: %*s", namelen, name, deflen, def));
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret= db_create_routine(thd, TYPE_ENUM_FUNCTION, name, namelen, def, deflen);
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_drop_function(THD *thd, char *name, uint namelen)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_drop_function");
|
||||||
|
DBUG_PRINT("enter", ("name: %*s", namelen, name));
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret= db_drop_routine(thd, TYPE_ENUM_FUNCTION, name, namelen);
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
// QQ Temporary until the function call detection in sql_lex has been reworked.
|
||||||
|
bool
|
||||||
|
sp_function_exists(THD *thd, LEX_STRING *name)
|
||||||
|
{
|
||||||
|
TABLE *table;
|
||||||
|
bool ret= FALSE;
|
||||||
|
bool opened;
|
||||||
|
|
||||||
|
if (db_find_routine_aux(thd, TYPE_ENUM_FUNCTION,
|
||||||
|
name->str, name->length, TL_READ,
|
||||||
|
&table, &opened) == SP_OK)
|
||||||
|
{
|
||||||
|
ret= TRUE;
|
||||||
|
}
|
||||||
|
if (opened)
|
||||||
|
close_thread_tables(thd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* The temporary FUNCTION cache. (QQ This will be rehacked later, but
|
||||||
|
* it's needed now to make functions work at all.)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_add_fun_to_lex(LEX *lex, LEX_STRING fun)
|
||||||
|
{
|
||||||
|
List_iterator_fast<char> li(lex->spfuns);
|
||||||
|
char *fn;
|
||||||
|
|
||||||
|
while ((fn= li++))
|
||||||
|
{
|
||||||
|
if (strncasecmp(fn, fun.str, fun.length) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (! fn)
|
||||||
|
{
|
||||||
|
char *s= sql_strmake(fun.str, fun.length);
|
||||||
|
lex->spfuns.push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_merge_funs(LEX *dst, LEX *src)
|
||||||
|
{
|
||||||
|
List_iterator_fast<char> li(src->spfuns);
|
||||||
|
char *fn;
|
||||||
|
|
||||||
|
while ((fn= li++))
|
||||||
|
{
|
||||||
|
LEX_STRING lx;
|
||||||
|
|
||||||
|
lx.str= fn; lx.length= strlen(fn);
|
||||||
|
sp_add_fun_to_lex(dst, lx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QQ Not terribly efficient right now, but it'll do for starters.
|
||||||
|
We should actually open the mysql.proc table just once. */
|
||||||
|
int
|
||||||
|
sp_cache_functions(THD *thd, LEX *lex)
|
||||||
|
{
|
||||||
|
List_iterator<char> li(lex->spfuns);
|
||||||
|
char *fn;
|
||||||
|
enum_sql_command cmd= lex->sql_command;
|
||||||
|
int ret= 0;
|
||||||
|
|
||||||
|
while ((fn= li++))
|
||||||
|
{
|
||||||
|
List_iterator_fast<sp_head> lisp(thd->spfuns);
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
while ((sp= lisp++))
|
||||||
|
{
|
||||||
|
if (strcasecmp(fn, sp->name()) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (sp)
|
||||||
|
continue;
|
||||||
|
if (db_find_routine(thd, TYPE_ENUM_FUNCTION, fn, strlen(fn), &sp) == SP_OK)
|
||||||
|
{
|
||||||
|
ret= sp_cache_functions(thd, &thd->lex);
|
||||||
|
if (ret)
|
||||||
|
break;
|
||||||
|
thd->spfuns.push_back(sp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
send_error(thd, ER_SP_DOES_NOT_EXIST);
|
||||||
|
ret= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lex->sql_command= cmd;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_clear_function_cache(THD *thd)
|
||||||
|
{
|
||||||
|
thd->spfuns.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static sp_head *
|
||||||
|
sp_find_cached_function(THD *thd, char *name, uint namelen)
|
||||||
|
{
|
||||||
|
List_iterator_fast<sp_head> li(thd->spfuns);
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
while ((sp= li++))
|
||||||
|
{
|
||||||
|
if (strncasecmp(name, sp->name(), namelen) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return sp;
|
||||||
|
}
|
65
sql/sp.h
Normal file
65
sql/sp.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* -*- C++ -*- */
|
||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifndef _SP_H_
|
||||||
|
#define _SP_H_
|
||||||
|
|
||||||
|
// Return codes from sp_create_* and sp_drop_*:
|
||||||
|
#define SP_OK 0
|
||||||
|
#define SP_KEY_NOT_FOUND -1
|
||||||
|
#define SP_OPEN_TABLE_FAILED -2
|
||||||
|
#define SP_WRITE_ROW_FAILED -3
|
||||||
|
#define SP_DELETE_ROW_FAILED -4
|
||||||
|
#define SP_GET_FIELD_FAILED -5
|
||||||
|
#define SP_PARSE_ERROR -6
|
||||||
|
|
||||||
|
sp_head *
|
||||||
|
sp_find_procedure(THD *thd, LEX_STRING *name);
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_create_procedure(THD *thd, char *name, uint namelen, char *def, uint deflen);
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_drop_procedure(THD *thd, char *name, uint namelen);
|
||||||
|
|
||||||
|
|
||||||
|
sp_head *
|
||||||
|
sp_find_function(THD *thd, LEX_STRING *name);
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_create_function(THD *thd, char *name, uint namelen, char *def, uint deflen);
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_drop_function(THD *thd, char *name, uint namelen);
|
||||||
|
|
||||||
|
// QQ Temporary until the function call detection in sql_lex has been reworked.
|
||||||
|
bool
|
||||||
|
sp_function_exists(THD *thd, LEX_STRING *name);
|
||||||
|
|
||||||
|
|
||||||
|
// QQ More temporary stuff until the real cache is implemented. This is
|
||||||
|
// needed since we have to read the functions before we do anything else.
|
||||||
|
void
|
||||||
|
sp_add_fun_to_lex(LEX *lex, LEX_STRING fun);
|
||||||
|
void
|
||||||
|
sp_merge_funs(LEX *dst, LEX *src);
|
||||||
|
int
|
||||||
|
sp_cache_functions(THD *thd, LEX *lex);
|
||||||
|
void
|
||||||
|
sp_clear_function_cache(THD *thd);
|
||||||
|
|
||||||
|
#endif /* _SP_H_ */
|
525
sql/sp_head.cc
Normal file
525
sql/sp_head.cc
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma implementation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "mysql_priv.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sp.h"
|
||||||
|
#include "sp_pcontext.h"
|
||||||
|
#include "sp_rcontext.h"
|
||||||
|
|
||||||
|
Item_result
|
||||||
|
sp_map_result_type(enum enum_field_types type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MYSQL_TYPE_TINY:
|
||||||
|
case MYSQL_TYPE_SHORT:
|
||||||
|
case MYSQL_TYPE_LONG:
|
||||||
|
case MYSQL_TYPE_LONGLONG:
|
||||||
|
case MYSQL_TYPE_INT24:
|
||||||
|
return INT_RESULT;
|
||||||
|
case MYSQL_TYPE_DECIMAL:
|
||||||
|
case MYSQL_TYPE_FLOAT:
|
||||||
|
case MYSQL_TYPE_DOUBLE:
|
||||||
|
return REAL_RESULT;
|
||||||
|
default:
|
||||||
|
return STRING_RESULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Evaluate a (presumed) func item. Always returns an item, the parameter
|
||||||
|
** if nothing else.
|
||||||
|
*/
|
||||||
|
static Item *
|
||||||
|
eval_func_item(THD *thd, Item *it, enum enum_field_types type)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("eval_func_item");
|
||||||
|
it= it->this_item();
|
||||||
|
DBUG_PRINT("info", ("type: %d", type));
|
||||||
|
|
||||||
|
if (it->fix_fields(thd, 0, NULL))
|
||||||
|
{
|
||||||
|
DBUG_PRINT("info", ("fix_fields() failed"));
|
||||||
|
DBUG_RETURN(it); // Shouldn't happen?
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QQ How do we do this? Is there some better way? */
|
||||||
|
if (type == MYSQL_TYPE_NULL)
|
||||||
|
it= new Item_null();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (sp_map_result_type(type)) {
|
||||||
|
case INT_RESULT:
|
||||||
|
DBUG_PRINT("info", ("INT_RESULT: %d", it->val_int()));
|
||||||
|
it= new Item_int(it->val_int());
|
||||||
|
break;
|
||||||
|
case REAL_RESULT:
|
||||||
|
DBUG_PRINT("info", ("REAL_RESULT: %g", it->val()));
|
||||||
|
it= new Item_real(it->val());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
char buffer[MAX_FIELD_WIDTH];
|
||||||
|
String tmp(buffer, sizeof(buffer), it->charset());
|
||||||
|
String *s= it->val_str(&tmp);
|
||||||
|
|
||||||
|
DBUG_PRINT("info", ("default result: %*s", s->length(), s->c_ptr_quick()))
|
||||||
|
it= new Item_string(sql_strmake(s->c_ptr_quick(), s->length()),
|
||||||
|
s->length(), it->charset());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG_RETURN(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
sp_head::sp_head(LEX_STRING *name, LEX *lex)
|
||||||
|
: m_simple_case(FALSE)
|
||||||
|
{
|
||||||
|
const char *dstr = (const char*)lex->buf;
|
||||||
|
|
||||||
|
m_name= new Item_string(name->str, name->length, system_charset_info);
|
||||||
|
m_defstr= new Item_string(dstr, lex->end_of_query - lex->buf,
|
||||||
|
system_charset_info);
|
||||||
|
m_pcont= lex->spcont;
|
||||||
|
my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8);
|
||||||
|
m_backpatch.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_head::create(THD *thd)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_head::create");
|
||||||
|
String *name= m_name->const_string();
|
||||||
|
String *def= m_defstr->const_string();
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DBUG_PRINT("info", ("type: %d name: %s def: %s",
|
||||||
|
m_type, name->c_ptr(), def->c_ptr()));
|
||||||
|
if (m_type == TYPE_ENUM_FUNCTION)
|
||||||
|
ret= sp_create_function(thd,
|
||||||
|
name->c_ptr(), name->length(),
|
||||||
|
def->c_ptr(), def->length());
|
||||||
|
else
|
||||||
|
ret= sp_create_procedure(thd,
|
||||||
|
name->c_ptr(), name->length(),
|
||||||
|
def->c_ptr(), def->length());
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_head::execute(THD *thd)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_head::execute");
|
||||||
|
char *olddbname;
|
||||||
|
char *olddbptr= thd->db;
|
||||||
|
int ret= 0;
|
||||||
|
uint ip= 0;
|
||||||
|
|
||||||
|
if (olddbptr)
|
||||||
|
olddbname= my_strdup(olddbptr, MYF(MY_WME));
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sp_instr *i;
|
||||||
|
|
||||||
|
i = get_instr(ip); // Returns NULL when we're done.
|
||||||
|
if (i == NULL)
|
||||||
|
break;
|
||||||
|
DBUG_PRINT("execute", ("Instruction %u", ip));
|
||||||
|
ret= i->execute(thd, &ip);
|
||||||
|
} while (ret == 0);
|
||||||
|
|
||||||
|
/* If the DB has changed, the pointer has changed too, but the
|
||||||
|
original thd->db will then have been freed */
|
||||||
|
if (olddbptr && olddbptr != thd->db && olddbname)
|
||||||
|
{
|
||||||
|
/* QQ Maybe we should issue some special error message or warning here,
|
||||||
|
if this fails?? */
|
||||||
|
ret= mysql_change_db(thd, olddbname);
|
||||||
|
my_free(olddbname, MYF(0));
|
||||||
|
}
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_head::execute_function");
|
||||||
|
DBUG_PRINT("info", ("function %s", ((String *)m_name->const_string())->c_ptr()));
|
||||||
|
uint csize = m_pcont->max_framesize();
|
||||||
|
uint params = m_pcont->params();
|
||||||
|
sp_rcontext *octx = thd->spcont;
|
||||||
|
sp_rcontext *nctx = NULL;
|
||||||
|
uint i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// QQ Should have some error checking here? (no. of args, types, etc...)
|
||||||
|
nctx= new sp_rcontext(csize);
|
||||||
|
for (i= 0 ; i < params && i < argcount ; i++)
|
||||||
|
{
|
||||||
|
sp_pvar_t *pvar = m_pcont->find_pvar(i);
|
||||||
|
|
||||||
|
nctx->push_item(eval_func_item(thd, *argp++, pvar->type));
|
||||||
|
}
|
||||||
|
// The rest of the frame are local variables which are all IN.
|
||||||
|
// QQ See comment in execute_procedure below.
|
||||||
|
for (; i < csize ; i++)
|
||||||
|
nctx->push_item(NULL);
|
||||||
|
thd->spcont= nctx;
|
||||||
|
|
||||||
|
ret= execute(thd);
|
||||||
|
if (ret == 0)
|
||||||
|
*resp= nctx->get_result();
|
||||||
|
|
||||||
|
thd->spcont= octx;
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sp_head::execute_procedure(THD *thd, List<Item> *args)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_head::execute_procedure");
|
||||||
|
DBUG_PRINT("info", ("procedure %s", ((String *)m_name->const_string())->c_ptr()));
|
||||||
|
int ret;
|
||||||
|
sp_instr *p;
|
||||||
|
uint csize = m_pcont->max_framesize();
|
||||||
|
uint params = m_pcont->params();
|
||||||
|
sp_rcontext *octx = thd->spcont;
|
||||||
|
sp_rcontext *nctx = NULL;
|
||||||
|
my_bool tmp_octx = FALSE; // True if we have allocated a temporary octx
|
||||||
|
|
||||||
|
if (csize > 0)
|
||||||
|
{
|
||||||
|
uint i;
|
||||||
|
List_iterator_fast<Item> li(*args);
|
||||||
|
Item *it;
|
||||||
|
|
||||||
|
nctx = new sp_rcontext(csize);
|
||||||
|
if (! octx)
|
||||||
|
{ // Create a temporary old context
|
||||||
|
octx = new sp_rcontext(csize);
|
||||||
|
tmp_octx = TRUE;
|
||||||
|
}
|
||||||
|
// QQ: No error checking whatsoever right now. Should do type checking?
|
||||||
|
for (i = 0 ; (it= li++) && i < params ; i++)
|
||||||
|
{
|
||||||
|
sp_pvar_t *pvar = m_pcont->find_pvar(i);
|
||||||
|
|
||||||
|
if (! pvar)
|
||||||
|
nctx->set_oindex(i, -1); // Shouldn't happen
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pvar->mode == sp_param_out)
|
||||||
|
nctx->push_item(NULL); // OUT
|
||||||
|
else
|
||||||
|
nctx->push_item(eval_func_item(thd, it, pvar->type)); // IN or INOUT
|
||||||
|
// Note: If it's OUT or INOUT, it must be a variable.
|
||||||
|
// QQ: Need to handle "global" user/host variables too!!!
|
||||||
|
if (pvar->mode == sp_param_in)
|
||||||
|
nctx->set_oindex(i, -1); // IN
|
||||||
|
else // OUT or INOUT
|
||||||
|
nctx->set_oindex(i, static_cast<Item_splocal *>(it)->get_offset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The rest of the frame are local variables which are all IN.
|
||||||
|
// QQ We haven't found any hint of what the value is when unassigned,
|
||||||
|
// so we set it to NULL for now. It's an error to refer to an
|
||||||
|
// unassigned variable anyway (which should be detected by the parser).
|
||||||
|
for (; i < csize ; i++)
|
||||||
|
nctx->push_item(NULL);
|
||||||
|
thd->spcont= nctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret= execute(thd);
|
||||||
|
|
||||||
|
// Don't copy back OUT values if we got an error
|
||||||
|
if (ret == 0 && csize > 0)
|
||||||
|
{
|
||||||
|
List_iterator_fast<Item> li(*args);
|
||||||
|
Item *it;
|
||||||
|
|
||||||
|
// Copy back all OUT or INOUT values to the previous frame, or
|
||||||
|
// set global user variables
|
||||||
|
for (uint i = 0 ; (it= li++) && i < params ; i++)
|
||||||
|
{
|
||||||
|
int oi = nctx->get_oindex(i);
|
||||||
|
|
||||||
|
if (oi >= 0)
|
||||||
|
{
|
||||||
|
if (! tmp_octx)
|
||||||
|
octx->set_item(nctx->get_oindex(i), nctx->get_item(i));
|
||||||
|
else
|
||||||
|
{ // A global user variable
|
||||||
|
#if 0
|
||||||
|
// QQ This works if the parameter really is a user variable, but
|
||||||
|
// for the moment we can't assure that, so it will crash if it's
|
||||||
|
// something else. So for now, we just do nothing, to avoid a crash.
|
||||||
|
// Note: This also assumes we have a get_name() method in
|
||||||
|
// the Item_func_get_user_var class.
|
||||||
|
Item *item= nctx->get_item(i);
|
||||||
|
Item_func_set_user_var *suv;
|
||||||
|
Item_func_get_user_var *guv= static_cast<Item_func_get_user_var*>(it);
|
||||||
|
|
||||||
|
suv= new Item_func_set_user_var(guv->get_name(), item);
|
||||||
|
suv->fix_fields(thd, NULL, &item);
|
||||||
|
suv->fix_length_and_dec();
|
||||||
|
suv->update();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmp_octx)
|
||||||
|
thd->spcont= NULL;
|
||||||
|
else
|
||||||
|
thd->spcont= octx;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBUG_RETURN(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Reset lex during parsing, before we parse a sub statement.
|
||||||
|
void
|
||||||
|
sp_head::reset_lex(THD *thd)
|
||||||
|
{
|
||||||
|
memcpy(&m_lex, &thd->lex, sizeof(LEX)); // Save old one
|
||||||
|
/* Reset most stuff. The length arguments doesn't matter here. */
|
||||||
|
lex_start(thd, m_lex.buf, m_lex.end_of_query - m_lex.ptr);
|
||||||
|
/* We must reset ptr and end_of_query again */
|
||||||
|
thd->lex.ptr= m_lex.ptr;
|
||||||
|
thd->lex.end_of_query= m_lex.end_of_query;
|
||||||
|
/* And keep the SP stuff too */
|
||||||
|
thd->lex.sphead = m_lex.sphead;
|
||||||
|
thd->lex.spcont = m_lex.spcont;
|
||||||
|
/* Clear all lists. (QQ Why isn't this reset by lex_start()?).
|
||||||
|
We may be overdoing this, but we know for sure that value_list must
|
||||||
|
be cleared at least. */
|
||||||
|
thd->lex.col_list.empty();
|
||||||
|
thd->lex.ref_list.empty();
|
||||||
|
thd->lex.drop_list.empty();
|
||||||
|
thd->lex.alter_list.empty();
|
||||||
|
thd->lex.interval_list.empty();
|
||||||
|
thd->lex.users_list.empty();
|
||||||
|
thd->lex.columns.empty();
|
||||||
|
thd->lex.key_list.empty();
|
||||||
|
thd->lex.create_list.empty();
|
||||||
|
thd->lex.insert_list= NULL;
|
||||||
|
thd->lex.field_list.empty();
|
||||||
|
thd->lex.value_list.empty();
|
||||||
|
thd->lex.many_values.empty();
|
||||||
|
thd->lex.var_list.empty();
|
||||||
|
thd->lex.param_list.empty();
|
||||||
|
thd->lex.proc_list.empty();
|
||||||
|
thd->lex.auxilliary_table_list.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore lex during parsing, after we have parsed a sub statement.
|
||||||
|
void
|
||||||
|
sp_head::restore_lex(THD *thd)
|
||||||
|
{
|
||||||
|
// Update some state in the old one first
|
||||||
|
m_lex.ptr= thd->lex.ptr;
|
||||||
|
m_lex.next_state= thd->lex.next_state;
|
||||||
|
|
||||||
|
// Collect some data from the sub statement lex.
|
||||||
|
sp_merge_funs(&m_lex, &thd->lex);
|
||||||
|
#if 0
|
||||||
|
// We're not using this at the moment.
|
||||||
|
if (thd->lex.sql_command == SQLCOM_CALL)
|
||||||
|
{
|
||||||
|
// It would be slightly faster to keep the list sorted, but we need
|
||||||
|
// an "insert before" method to do that.
|
||||||
|
char *proc= thd->lex.udf.name.str;
|
||||||
|
|
||||||
|
List_iterator_fast<char *> li(m_calls);
|
||||||
|
char **it;
|
||||||
|
|
||||||
|
while ((it= li++))
|
||||||
|
if (strcasecmp(proc, *it) == 0)
|
||||||
|
break;
|
||||||
|
if (! it)
|
||||||
|
m_calls.push_back(&proc);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Merge used tables
|
||||||
|
// QQ ...or just open tables in thd->open_tables?
|
||||||
|
// This is not entirerly clear at the moment, but for now, we collect
|
||||||
|
// tables here.
|
||||||
|
for (SELECT_LEX *sl= thd->lex.all_selects_list ;
|
||||||
|
sl ;
|
||||||
|
sl= sl->next_select())
|
||||||
|
{
|
||||||
|
for (TABLE_LIST *tables= sl->get_table_list() ;
|
||||||
|
tables ;
|
||||||
|
tables= tables->next)
|
||||||
|
{
|
||||||
|
List_iterator_fast<char *> li(m_tables);
|
||||||
|
char **tb;
|
||||||
|
|
||||||
|
while ((tb= li++))
|
||||||
|
if (strcasecmp(tables->real_name, *tb) == 0)
|
||||||
|
break;
|
||||||
|
if (! tb)
|
||||||
|
m_tables.push_back(&tables->real_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memcpy(&thd->lex, &m_lex, sizeof(LEX)); // Restore lex
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_head::push_backpatch(sp_instr *i, sp_label_t *lab)
|
||||||
|
{
|
||||||
|
bp_t *bp= (bp_t *)my_malloc(sizeof(bp_t), MYF(MY_WME));
|
||||||
|
|
||||||
|
if (bp)
|
||||||
|
{
|
||||||
|
bp->lab= lab;
|
||||||
|
bp->instr= i;
|
||||||
|
(void)m_backpatch.push_front(bp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_head::backpatch(sp_label_t *lab)
|
||||||
|
{
|
||||||
|
bp_t *bp;
|
||||||
|
uint dest= instructions();
|
||||||
|
List_iterator_fast<bp_t> li(m_backpatch);
|
||||||
|
|
||||||
|
while ((bp= li++))
|
||||||
|
if (bp->lab == lab)
|
||||||
|
{
|
||||||
|
sp_instr_jump *i= static_cast<sp_instr_jump *>(bp->instr);
|
||||||
|
|
||||||
|
i->set_destination(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_stmt
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_stmt::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_stmt::execute");
|
||||||
|
DBUG_PRINT("info", ("command: %d", m_lex.sql_command));
|
||||||
|
LEX olex; // The other lex
|
||||||
|
int res;
|
||||||
|
|
||||||
|
memcpy(&olex, &thd->lex, sizeof(LEX)); // Save the other lex
|
||||||
|
|
||||||
|
memcpy(&thd->lex, &m_lex, sizeof(LEX)); // Use my own lex
|
||||||
|
thd->lex.thd = thd;
|
||||||
|
|
||||||
|
res= mysql_execute_command(thd);
|
||||||
|
if (thd->lock || thd->open_tables || thd->derived_tables)
|
||||||
|
{
|
||||||
|
thd->proc_info="closing tables";
|
||||||
|
close_thread_tables(thd); /* Free tables */
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&thd->lex, &olex, sizeof(LEX)); // Restore the other lex
|
||||||
|
|
||||||
|
*nextp = m_ip+1;
|
||||||
|
DBUG_RETURN(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_set
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_set::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_set::execute");
|
||||||
|
DBUG_PRINT("info", ("offset: %u", m_offset));
|
||||||
|
thd->spcont->set_item(m_offset, eval_func_item(thd, m_value, m_type));
|
||||||
|
*nextp = m_ip+1;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_jump
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_jump::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_jump::execute");
|
||||||
|
DBUG_PRINT("info", ("destination: %u", m_dest));
|
||||||
|
|
||||||
|
*nextp= m_dest;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_jump_if
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_jump_if::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_jump_if::execute");
|
||||||
|
DBUG_PRINT("info", ("destination: %u", m_dest));
|
||||||
|
Item *it= eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
|
||||||
|
|
||||||
|
if (it->val_int())
|
||||||
|
*nextp = m_dest;
|
||||||
|
else
|
||||||
|
*nextp = m_ip+1;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_jump_if_not
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_jump_if_not::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_jump_if_not::execute");
|
||||||
|
DBUG_PRINT("info", ("destination: %u", m_dest));
|
||||||
|
Item *it= eval_func_item(thd, m_expr, MYSQL_TYPE_TINY);
|
||||||
|
|
||||||
|
if (! it->val_int())
|
||||||
|
*nextp = m_dest;
|
||||||
|
else
|
||||||
|
*nextp = m_ip+1;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// sp_instr_return
|
||||||
|
//
|
||||||
|
int
|
||||||
|
sp_instr_return::execute(THD *thd, uint *nextp)
|
||||||
|
{
|
||||||
|
DBUG_ENTER("sp_instr_return::execute");
|
||||||
|
thd->spcont->set_result(eval_func_item(thd, m_value, m_type));
|
||||||
|
*nextp= UINT_MAX;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
360
sql/sp_head.h
Normal file
360
sql/sp_head.h
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
/* -*- C++ -*- */
|
||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifndef _SP_HEAD_H_
|
||||||
|
#define _SP_HEAD_H_
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma interface /* gcc class implementation */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Values for the type enum. This reflects the order of the enum declaration
|
||||||
|
// in the CREATE TABLE command.
|
||||||
|
#define TYPE_ENUM_FUNCTION 1
|
||||||
|
#define TYPE_ENUM_PROCEDURE 2
|
||||||
|
|
||||||
|
Item_result
|
||||||
|
sp_map_result_type(enum enum_field_types type);
|
||||||
|
|
||||||
|
struct sp_label;
|
||||||
|
|
||||||
|
class sp_instr;
|
||||||
|
|
||||||
|
class sp_head : public Sql_alloc
|
||||||
|
{
|
||||||
|
sp_head(const sp_head &); /* Prevent use of these */
|
||||||
|
void operator=(sp_head &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE
|
||||||
|
enum enum_field_types m_returns; // For FUNCTIONs only
|
||||||
|
my_bool m_simple_case; // TRUE if parsing simple case, FALSE otherwise
|
||||||
|
#if 0
|
||||||
|
// We're not using this at the moment.
|
||||||
|
List<char *> m_calls; // Called procedures.
|
||||||
|
List<char *> m_tables; // Used tables.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void *operator new(size_t size)
|
||||||
|
{
|
||||||
|
return (void*) sql_alloc((uint) size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void operator delete(void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
/* Empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
sp_head(LEX_STRING *name, LEX *lex);
|
||||||
|
|
||||||
|
int
|
||||||
|
create(THD *thd);
|
||||||
|
|
||||||
|
int
|
||||||
|
execute_function(THD *thd, Item **args, uint argcount, Item **resp);
|
||||||
|
|
||||||
|
int
|
||||||
|
execute_procedure(THD *thd, List<Item> *args);
|
||||||
|
|
||||||
|
inline void
|
||||||
|
add_instr(sp_instr *i)
|
||||||
|
{
|
||||||
|
insert_dynamic(&m_instr, (gptr)&i);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint
|
||||||
|
instructions()
|
||||||
|
{
|
||||||
|
return m_instr.elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets lex in 'thd' and keeps a copy of the old one.
|
||||||
|
void
|
||||||
|
reset_lex(THD *thd);
|
||||||
|
|
||||||
|
// Restores lex in 'thd' from our copy, but keeps some status from the
|
||||||
|
// one in 'thd', like ptr, tables, fields, etc.
|
||||||
|
void
|
||||||
|
restore_lex(THD *thd);
|
||||||
|
|
||||||
|
// Put the instruction on the backpatch list, associated with the label.
|
||||||
|
void
|
||||||
|
push_backpatch(sp_instr *, struct sp_label *);
|
||||||
|
|
||||||
|
// Update all instruction with this label in the backpatch list to
|
||||||
|
// the current position.
|
||||||
|
void
|
||||||
|
backpatch(struct sp_label *);
|
||||||
|
|
||||||
|
char *name(uint *lenp = 0) const
|
||||||
|
{
|
||||||
|
String *n= m_name->const_string();
|
||||||
|
|
||||||
|
if (lenp)
|
||||||
|
*lenp= n->length();
|
||||||
|
return n->c_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Item_result result()
|
||||||
|
{
|
||||||
|
return sp_map_result_type(m_returns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Item_string *m_name;
|
||||||
|
Item_string *m_defstr;
|
||||||
|
sp_pcontext *m_pcont; // Parse context
|
||||||
|
LEX m_lex; // Temp. store for the other lex
|
||||||
|
DYNAMIC_ARRAY m_instr; // The "instructions"
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
struct sp_label *lab;
|
||||||
|
sp_instr *instr;
|
||||||
|
} bp_t;
|
||||||
|
List<bp_t> m_backpatch; // Instructions needing backpatching
|
||||||
|
|
||||||
|
inline sp_instr *
|
||||||
|
get_instr(uint i)
|
||||||
|
{
|
||||||
|
sp_instr *in= NULL;
|
||||||
|
|
||||||
|
if (i < m_instr.elements)
|
||||||
|
get_dynamic(&m_instr, (gptr)&in, i);
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
execute(THD *thd);
|
||||||
|
|
||||||
|
}; // class sp_head : public Sql_alloc
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// "Instructions"...
|
||||||
|
//
|
||||||
|
|
||||||
|
class sp_instr : public Sql_alloc
|
||||||
|
{
|
||||||
|
sp_instr(const sp_instr &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Should give each a name or type code for debugging purposes?
|
||||||
|
sp_instr(uint ip)
|
||||||
|
: Sql_alloc(), m_ip(ip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr()
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Execute this instrution. '*nextp' will be set to the index of the next
|
||||||
|
// instruction to execute. (For most instruction this will be the
|
||||||
|
// instruction following this one.)
|
||||||
|
// Returns 0 on success, non-zero if some error occured.
|
||||||
|
virtual int
|
||||||
|
execute(THD *thd, uint *nextp)
|
||||||
|
{ // Default is a no-op.
|
||||||
|
*nextp = m_ip+1; // Next instruction
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
uint m_ip; // My index
|
||||||
|
|
||||||
|
}; // class sp_instr : public Sql_alloc
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Call out to some prepared SQL statement.
|
||||||
|
//
|
||||||
|
class sp_instr_stmt : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_stmt(const sp_instr_stmt &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_stmt &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_stmt(uint ip)
|
||||||
|
: sp_instr(ip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_stmt()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_lex(LEX *lex)
|
||||||
|
{
|
||||||
|
memcpy(&m_lex, lex, sizeof(LEX));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline LEX *
|
||||||
|
get_lex()
|
||||||
|
{
|
||||||
|
return &m_lex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
LEX m_lex; // My own lex
|
||||||
|
|
||||||
|
}; // class sp_instr_stmt : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
|
class sp_instr_set : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_set(const sp_instr_set &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_set &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_set(uint ip, uint offset, Item *val, enum enum_field_types type)
|
||||||
|
: sp_instr(ip), m_offset(offset), m_value(val), m_type(type)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_set()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint m_offset; // Frame offset
|
||||||
|
Item *m_value;
|
||||||
|
enum enum_field_types m_type; // The declared type
|
||||||
|
|
||||||
|
}; // class sp_instr_set : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
|
class sp_instr_jump : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_jump(const sp_instr_jump &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_jump &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_jump(uint ip)
|
||||||
|
: sp_instr(ip)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sp_instr_jump(uint ip, uint dest)
|
||||||
|
: sp_instr(ip), m_dest(dest)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_jump()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
set_destination(uint dest)
|
||||||
|
{
|
||||||
|
m_dest= dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
int m_dest; // Where we will go
|
||||||
|
|
||||||
|
}; // class sp_instr_jump : public sp_instr
|
||||||
|
|
||||||
|
|
||||||
|
class sp_instr_jump_if : public sp_instr_jump
|
||||||
|
{
|
||||||
|
sp_instr_jump_if(const sp_instr_jump_if &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_jump_if &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_jump_if(uint ip, Item *i)
|
||||||
|
: sp_instr_jump(ip), m_expr(i)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sp_instr_jump_if(uint ip, Item *i, uint dest)
|
||||||
|
: sp_instr_jump(ip, dest), m_expr(i)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_jump_if()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Item *m_expr; // The condition
|
||||||
|
|
||||||
|
}; // class sp_instr_jump_if : public sp_instr_jump
|
||||||
|
|
||||||
|
|
||||||
|
class sp_instr_jump_if_not : public sp_instr_jump
|
||||||
|
{
|
||||||
|
sp_instr_jump_if_not(const sp_instr_jump_if_not &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_jump_if_not &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_jump_if_not(uint ip, Item *i)
|
||||||
|
: sp_instr_jump(ip), m_expr(i)
|
||||||
|
{}
|
||||||
|
|
||||||
|
sp_instr_jump_if_not(uint ip, Item *i, uint dest)
|
||||||
|
: sp_instr_jump(ip, dest), m_expr(i)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_jump_if_not()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Item *m_expr; // The condition
|
||||||
|
|
||||||
|
}; // class sp_instr_jump_if_not : public sp_instr_jump
|
||||||
|
|
||||||
|
|
||||||
|
class sp_instr_return : public sp_instr
|
||||||
|
{
|
||||||
|
sp_instr_return(const sp_instr_return &); /* Prevent use of these */
|
||||||
|
void operator=(sp_instr_return &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_instr_return(uint ip, Item *val, enum enum_field_types type)
|
||||||
|
: sp_instr(ip), m_value(val), m_type(type)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~sp_instr_return()
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual int execute(THD *thd, uint *nextp);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
Item *m_value;
|
||||||
|
enum enum_field_types m_type;
|
||||||
|
|
||||||
|
}; // class sp_instr_return : public sp_instr
|
||||||
|
|
||||||
|
#endif /* _SP_HEAD_H_ */
|
127
sql/sp_pcontext.cc
Normal file
127
sql/sp_pcontext.cc
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma implementation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(__WIN__)
|
||||||
|
#undef SAFEMALLOC /* Problems with threads */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "mysql_priv.h"
|
||||||
|
#include "sp_pcontext.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
|
||||||
|
sp_pcontext::sp_pcontext()
|
||||||
|
: m_params(0), m_framesize(0), m_i(0), m_genlab(0)
|
||||||
|
{
|
||||||
|
m_pvar_size = 16;
|
||||||
|
m_pvar = (sp_pvar_t *)my_malloc(m_pvar_size * sizeof(sp_pvar_t), MYF(MY_WME));
|
||||||
|
if (m_pvar)
|
||||||
|
memset(m_pvar, 0, m_pvar_size * sizeof(sp_pvar_t));
|
||||||
|
m_label.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_pcontext::grow()
|
||||||
|
{
|
||||||
|
uint sz = m_pvar_size + 8;
|
||||||
|
sp_pvar_t *a = (sp_pvar_t *)my_realloc((char *)m_pvar,
|
||||||
|
sz * sizeof(sp_pvar_t),
|
||||||
|
MYF(MY_WME | MY_ALLOW_ZERO_PTR));
|
||||||
|
|
||||||
|
if (a)
|
||||||
|
{
|
||||||
|
m_pvar_size = sz;
|
||||||
|
m_pvar = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This does a linear search (from newer to older variables, in case
|
||||||
|
** we have shadowed names).
|
||||||
|
** It's possible to have a more efficient allocation and search method,
|
||||||
|
** but it might not be worth it. The typical number of parameters and
|
||||||
|
** variables will in most cases be low (a handfull).
|
||||||
|
** And this is only called during parsing.
|
||||||
|
*/
|
||||||
|
sp_pvar_t *
|
||||||
|
sp_pcontext::find_pvar(LEX_STRING *name)
|
||||||
|
{
|
||||||
|
uint i = m_i;
|
||||||
|
|
||||||
|
while (i-- > 0)
|
||||||
|
{
|
||||||
|
uint len= (m_pvar[i].name.length > name->length ?
|
||||||
|
m_pvar[i].name.length : name->length);
|
||||||
|
|
||||||
|
if (my_strncasecmp(system_charset_info,
|
||||||
|
name->str,
|
||||||
|
m_pvar[i].name.str,
|
||||||
|
len) == 0)
|
||||||
|
{
|
||||||
|
return m_pvar + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sp_pcontext::push(LEX_STRING *name, enum enum_field_types type,
|
||||||
|
sp_param_mode_t mode)
|
||||||
|
{
|
||||||
|
if (m_i >= m_pvar_size)
|
||||||
|
grow();
|
||||||
|
if (m_i < m_pvar_size)
|
||||||
|
{
|
||||||
|
if (m_i == m_framesize)
|
||||||
|
m_framesize += 1;
|
||||||
|
m_pvar[m_i].name.str= name->str;
|
||||||
|
m_pvar[m_i].name.length= name->length,
|
||||||
|
m_pvar[m_i].type= type;
|
||||||
|
m_pvar[m_i].mode= mode;
|
||||||
|
m_pvar[m_i].offset= m_i;
|
||||||
|
m_pvar[m_i].isset= (mode == sp_param_out ? FALSE : TRUE);
|
||||||
|
m_i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sp_label_t *
|
||||||
|
sp_pcontext::push_label(char *name, uint ip)
|
||||||
|
{
|
||||||
|
sp_label_t *lab = (sp_label_t *)my_malloc(sizeof(sp_label_t), MYF(MY_WME));
|
||||||
|
|
||||||
|
if (lab)
|
||||||
|
{
|
||||||
|
lab->name= name;
|
||||||
|
lab->ip= ip;
|
||||||
|
m_label.push_front(lab);
|
||||||
|
}
|
||||||
|
return lab;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp_label_t *
|
||||||
|
sp_pcontext::find_label(char *name)
|
||||||
|
{
|
||||||
|
List_iterator_fast<sp_label_t> li(m_label);
|
||||||
|
sp_label_t *lab;
|
||||||
|
|
||||||
|
while ((lab= li++))
|
||||||
|
if (strcasecmp(name, lab->name) == 0)
|
||||||
|
return lab;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
155
sql/sp_pcontext.h
Normal file
155
sql/sp_pcontext.h
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/* -*- C++ -*- */
|
||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifndef _SP_PCONTEXT_H_
|
||||||
|
#define _SP_PCONTEXT_H_
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma interface /* gcc class implementation */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sp_param_in,
|
||||||
|
sp_param_out,
|
||||||
|
sp_param_inout
|
||||||
|
} sp_param_mode_t;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
LEX_STRING name;
|
||||||
|
enum enum_field_types type;
|
||||||
|
sp_param_mode_t mode;
|
||||||
|
uint offset; // Offset in current frame
|
||||||
|
my_bool isset;
|
||||||
|
} sp_pvar_t;
|
||||||
|
|
||||||
|
typedef struct sp_label
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
uint ip; // Instruction index
|
||||||
|
} sp_label_t;
|
||||||
|
|
||||||
|
class sp_pcontext : public Sql_alloc
|
||||||
|
{
|
||||||
|
sp_pcontext(const sp_pcontext &); /* Prevent use of these */
|
||||||
|
void operator=(sp_pcontext &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_pcontext();
|
||||||
|
|
||||||
|
inline uint
|
||||||
|
max_framesize()
|
||||||
|
{
|
||||||
|
return m_framesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint
|
||||||
|
current_framesize()
|
||||||
|
{
|
||||||
|
return m_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint
|
||||||
|
params()
|
||||||
|
{
|
||||||
|
return m_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the number of parameters to the current esize
|
||||||
|
inline void
|
||||||
|
set_params()
|
||||||
|
{
|
||||||
|
m_params= m_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_type(uint i, enum enum_field_types type)
|
||||||
|
{
|
||||||
|
if (i < m_i)
|
||||||
|
m_pvar[i].type= type;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_isset(uint i, my_bool val)
|
||||||
|
{
|
||||||
|
if (i < m_i)
|
||||||
|
m_pvar[i].isset= val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
push(LEX_STRING *name, enum enum_field_types type, sp_param_mode_t mode);
|
||||||
|
|
||||||
|
// Pop the last 'num' slots of the frame
|
||||||
|
inline void
|
||||||
|
pop(uint num = 1)
|
||||||
|
{
|
||||||
|
if (num < m_i)
|
||||||
|
m_i -= num;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find by name
|
||||||
|
sp_pvar_t *
|
||||||
|
find_pvar(LEX_STRING *name);
|
||||||
|
|
||||||
|
// Find by index
|
||||||
|
sp_pvar_t *
|
||||||
|
find_pvar(uint i)
|
||||||
|
{
|
||||||
|
if (i >= m_i)
|
||||||
|
return NULL;
|
||||||
|
return m_pvar+i;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp_label_t *
|
||||||
|
push_label(char *name, uint ip);
|
||||||
|
|
||||||
|
sp_label_t *
|
||||||
|
find_label(char *name);
|
||||||
|
|
||||||
|
inline sp_label_t *
|
||||||
|
last_label()
|
||||||
|
{
|
||||||
|
return m_label.head();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline sp_label_t *
|
||||||
|
pop_label()
|
||||||
|
{
|
||||||
|
return m_label.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint m_params; // The number of parameters
|
||||||
|
uint m_framesize; // The maximum framesize
|
||||||
|
uint m_i; // The current index (during parsing)
|
||||||
|
|
||||||
|
sp_pvar_t *m_pvar;
|
||||||
|
uint m_pvar_size; // Current size of m_pvar.
|
||||||
|
|
||||||
|
void
|
||||||
|
grow();
|
||||||
|
|
||||||
|
List<sp_label_t> m_label; // The label list
|
||||||
|
uint m_genlab; // Gen. label counter
|
||||||
|
|
||||||
|
}; // class sp_pcontext : public Sql_alloc
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* _SP_PCONTEXT_H_ */
|
95
sql/sp_rcontext.h
Normal file
95
sql/sp_rcontext.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/* -*- C++ -*- */
|
||||||
|
/* Copyright (C) 2002 MySQL AB
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
||||||
|
|
||||||
|
#ifndef _SP_RCONTEXT_H_
|
||||||
|
#define _SP_RCONTEXT_H_
|
||||||
|
|
||||||
|
class sp_rcontext : public Sql_alloc
|
||||||
|
{
|
||||||
|
sp_rcontext(const sp_rcontext &); /* Prevent use of these */
|
||||||
|
void operator=(sp_rcontext &);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
sp_rcontext(uint size)
|
||||||
|
: m_count(0), m_size(size), m_result(NULL)
|
||||||
|
{
|
||||||
|
m_frame = (Item **)sql_alloc(size * sizeof(Item*));
|
||||||
|
m_outs = (int *)sql_alloc(size * sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
~sp_rcontext()
|
||||||
|
{
|
||||||
|
// Not needed?
|
||||||
|
//sql_element_free(m_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
push_item(Item *i)
|
||||||
|
{
|
||||||
|
if (m_count < m_size)
|
||||||
|
m_frame[m_count++] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_item(uint idx, Item *i)
|
||||||
|
{
|
||||||
|
if (idx < m_count)
|
||||||
|
m_frame[idx] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Item *
|
||||||
|
get_item(uint idx)
|
||||||
|
{
|
||||||
|
return m_frame[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_oindex(uint idx, int oidx)
|
||||||
|
{
|
||||||
|
m_outs[idx] = oidx;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int
|
||||||
|
get_oindex(uint idx)
|
||||||
|
{
|
||||||
|
return m_outs[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
set_result(Item *it)
|
||||||
|
{
|
||||||
|
m_result= it;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Item *
|
||||||
|
get_result()
|
||||||
|
{
|
||||||
|
return m_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint m_count;
|
||||||
|
uint m_size;
|
||||||
|
Item **m_frame;
|
||||||
|
int *m_outs;
|
||||||
|
Item *m_result; // For FUNCTIONs
|
||||||
|
|
||||||
|
}; // class sp_rcontext : public Sql_alloc
|
||||||
|
|
||||||
|
#endif /* _SP_RCONTEXT_H_ */
|
@ -35,6 +35,7 @@
|
|||||||
#include <io.h>
|
#include <io.h>
|
||||||
#endif
|
#endif
|
||||||
#include <mysys_err.h>
|
#include <mysys_err.h>
|
||||||
|
#include <sp_rcontext.h>
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
@ -80,7 +81,7 @@ extern "C" void free_user_var(user_var_entry *entry)
|
|||||||
THD::THD():user_time(0), is_fatal_error(0),
|
THD::THD():user_time(0), is_fatal_error(0),
|
||||||
last_insert_id_used(0),
|
last_insert_id_used(0),
|
||||||
insert_id_used(0), rand_used(0), in_lock_tables(0),
|
insert_id_used(0), rand_used(0), in_lock_tables(0),
|
||||||
global_read_lock(0), bootstrap(0)
|
global_read_lock(0), bootstrap(0), spcont(NULL)
|
||||||
{
|
{
|
||||||
host=user=priv_user=db=query=ip=0;
|
host=user=priv_user=db=query=ip=0;
|
||||||
host_or_ip= "connecting host";
|
host_or_ip= "connecting host";
|
||||||
@ -1002,9 +1003,12 @@ bool select_exists_subselect::send_data(List<Item> &items)
|
|||||||
int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
|
int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
|
||||||
{
|
{
|
||||||
List_iterator_fast<Item> li(list);
|
List_iterator_fast<Item> li(list);
|
||||||
List_iterator_fast<LEX_STRING> gl(var_list);
|
List_iterator_fast<my_var> gl(var_list);
|
||||||
Item *item;
|
Item *item;
|
||||||
|
my_var *mv;
|
||||||
LEX_STRING *ls;
|
LEX_STRING *ls;
|
||||||
|
|
||||||
|
row_count= 0;
|
||||||
if (var_list.elements != list.elements)
|
if (var_list.elements != list.elements)
|
||||||
{
|
{
|
||||||
my_error(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, MYF(0));
|
my_error(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, MYF(0));
|
||||||
@ -1013,19 +1017,39 @@ int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
|
|||||||
unit=u;
|
unit=u;
|
||||||
while ((item=li++))
|
while ((item=li++))
|
||||||
{
|
{
|
||||||
ls= gl++;
|
mv=gl++;
|
||||||
Item_func_set_user_var *xx = new Item_func_set_user_var(*ls,item);
|
ls= &mv->s;
|
||||||
xx->fix_fields(thd,(TABLE_LIST*) thd->lex.select_lex.table_list.first,&item);
|
if (mv->local)
|
||||||
xx->fix_length_and_dec();
|
{
|
||||||
vars.push_back(xx);
|
(void)local_vars.push_back(new Item_splocal(mv->offset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Item_func_set_user_var *xx = new Item_func_set_user_var(*ls,item);
|
||||||
|
xx->fix_fields(thd,(TABLE_LIST*) thd->lex.select_lex.table_list.first,&item);
|
||||||
|
xx->fix_length_and_dec();
|
||||||
|
vars.push_back(xx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool select_dumpvar::send_data(List<Item> &items)
|
bool select_dumpvar::send_data(List<Item> &items)
|
||||||
{
|
{
|
||||||
List_iterator_fast<Item_func_set_user_var> li(vars);
|
List_iterator_fast<Item_func_set_user_var> li(vars);
|
||||||
|
List_iterator_fast<Item_splocal> var_li(local_vars);
|
||||||
|
List_iterator_fast<my_var> my_li(var_list);
|
||||||
|
List_iterator_fast<Item> it(items);
|
||||||
Item_func_set_user_var *xx;
|
Item_func_set_user_var *xx;
|
||||||
|
Item_splocal *yy;
|
||||||
|
Item *item;
|
||||||
|
my_var *zz;
|
||||||
DBUG_ENTER("send_data");
|
DBUG_ENTER("send_data");
|
||||||
|
if (unit->offset_limit_cnt)
|
||||||
|
{ // using limit offset,count
|
||||||
|
unit->offset_limit_cnt--;
|
||||||
|
DBUG_RETURN(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (unit->offset_limit_cnt)
|
if (unit->offset_limit_cnt)
|
||||||
{ // Using limit offset,count
|
{ // Using limit offset,count
|
||||||
@ -1037,8 +1061,21 @@ bool select_dumpvar::send_data(List<Item> &items)
|
|||||||
my_error(ER_TOO_MANY_ROWS, MYF(0));
|
my_error(ER_TOO_MANY_ROWS, MYF(0));
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
while ((xx=li++))
|
while ((zz=my_li++) && (item=it++))
|
||||||
xx->update();
|
{
|
||||||
|
if (zz->local)
|
||||||
|
{
|
||||||
|
if ((yy=var_li++))
|
||||||
|
{
|
||||||
|
thd->spcont->set_item(yy->get_offset(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((xx=li++))
|
||||||
|
xx->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
class Query_log_event;
|
class Query_log_event;
|
||||||
class Load_log_event;
|
class Load_log_event;
|
||||||
class Slave_log_event;
|
class Slave_log_event;
|
||||||
|
class sp_rcontext;
|
||||||
|
|
||||||
enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
|
enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
|
||||||
enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY };
|
enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY };
|
||||||
@ -526,6 +527,8 @@ public:
|
|||||||
bool volatile killed;
|
bool volatile killed;
|
||||||
bool prepare_command;
|
bool prepare_command;
|
||||||
bool tmp_table_used;
|
bool tmp_table_used;
|
||||||
|
sp_rcontext *spcont; // SP runtime context
|
||||||
|
List<sp_head> spfuns; // SP FUNCTIONs
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If we do a purge of binary logs, log index info of the threads
|
If we do a purge of binary logs, log index info of the threads
|
||||||
@ -999,13 +1002,22 @@ public:
|
|||||||
bool send_eof();
|
bool send_eof();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class my_var : public Sql_alloc {
|
||||||
|
public:
|
||||||
|
LEX_STRING s;
|
||||||
|
bool local;
|
||||||
|
uint offset;
|
||||||
|
my_var (LEX_STRING& j, bool i, uint o) : s(j), local(i), offset(o) {}
|
||||||
|
~my_var() {}
|
||||||
|
};
|
||||||
|
|
||||||
class select_dumpvar :public select_result {
|
class select_dumpvar :public select_result {
|
||||||
ha_rows row_count;
|
ha_rows row_count;
|
||||||
public:
|
public:
|
||||||
List<LEX_STRING> var_list;
|
List<my_var> var_list;
|
||||||
List<Item_func_set_user_var> vars;
|
List<Item_func_set_user_var> vars;
|
||||||
select_dumpvar(void) { var_list.empty(); vars.empty(); row_count=0;}
|
List<Item_splocal> local_vars;
|
||||||
|
select_dumpvar(void) { var_list.empty(); local_vars.empty(); vars.empty(); row_count=0;}
|
||||||
~select_dumpvar() {}
|
~select_dumpvar() {}
|
||||||
int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
|
int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
|
||||||
bool send_fields(List<Item> &list, uint flag) {return 0;}
|
bool send_fields(List<Item> &list, uint flag) {return 0;}
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
#include "item_create.h"
|
#include "item_create.h"
|
||||||
#include <m_ctype.h>
|
#include <m_ctype.h>
|
||||||
#include <hash.h>
|
#include <hash.h>
|
||||||
|
#include "sp.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
|
||||||
LEX_STRING tmp_table_alias= {(char*) "tmp-table",8};
|
LEX_STRING tmp_table_alias= {(char*) "tmp-table",8};
|
||||||
|
|
||||||
@ -109,6 +111,7 @@ LEX *lex_start(THD *thd, uchar *buf,uint length)
|
|||||||
LEX *lex= &thd->lex;
|
LEX *lex= &thd->lex;
|
||||||
lex->thd= thd;
|
lex->thd= thd;
|
||||||
lex->next_state=MY_LEX_START;
|
lex->next_state=MY_LEX_START;
|
||||||
|
lex->buf= buf;
|
||||||
lex->end_of_query=(lex->ptr=buf)+length;
|
lex->end_of_query=(lex->ptr=buf)+length;
|
||||||
lex->yylineno = 1;
|
lex->yylineno = 1;
|
||||||
lex->select_lex.create_refs=lex->in_comment=0;
|
lex->select_lex.create_refs=lex->in_comment=0;
|
||||||
@ -122,6 +125,9 @@ LEX *lex_start(THD *thd, uchar *buf,uint length)
|
|||||||
lex->yacc_yyss=lex->yacc_yyvs=0;
|
lex->yacc_yyss=lex->yacc_yyvs=0;
|
||||||
lex->ignore_space=test(thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
lex->ignore_space=test(thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
||||||
lex->sql_command=SQLCOM_END;
|
lex->sql_command=SQLCOM_END;
|
||||||
|
lex->sphead= NULL;
|
||||||
|
lex->spcont= NULL;
|
||||||
|
lex->spfuns.empty();
|
||||||
return lex;
|
return lex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,6 +151,17 @@ static int find_keyword(LEX *lex, uint len, bool function)
|
|||||||
lex->yylval->symbol.length=len;
|
lex->yylval->symbol.length=len;
|
||||||
return symbol->tok;
|
return symbol->tok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LEX_STRING ls;
|
||||||
|
ls.str = (char *)tok; ls.length= len;
|
||||||
|
if (function && sp_function_exists(current_thd, &ls)) // QQ temp fix
|
||||||
|
{
|
||||||
|
lex->safe_to_cache_query= 0;
|
||||||
|
lex->yylval->lex_str.str= lex->thd->strmake((char*)lex->tok_start, len);
|
||||||
|
lex->yylval->lex_str.length= len;
|
||||||
|
return SP_FUNC;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_DLOPEN
|
#ifdef HAVE_DLOPEN
|
||||||
udf_func *udf;
|
udf_func *udf;
|
||||||
if (function && using_udf_functions && (udf=find_udf((char*) tok, len)))
|
if (function && using_udf_functions && (udf=find_udf((char*) tok, len)))
|
||||||
|
@ -21,6 +21,9 @@
|
|||||||
class Table_ident;
|
class Table_ident;
|
||||||
class sql_exchange;
|
class sql_exchange;
|
||||||
class LEX_COLUMN;
|
class LEX_COLUMN;
|
||||||
|
class sp_head;
|
||||||
|
class sp_instr;
|
||||||
|
class sp_pcontext;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The following hack is needed because mysql_yacc.cc does not define
|
The following hack is needed because mysql_yacc.cc does not define
|
||||||
@ -72,6 +75,8 @@ enum enum_sql_command {
|
|||||||
SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS,
|
SQLCOM_SHOW_WARNS, SQLCOM_EMPTY_QUERY, SQLCOM_SHOW_ERRORS,
|
||||||
SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_TABLE_TYPES, SQLCOM_SHOW_PRIVILEGES,
|
SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_TABLE_TYPES, SQLCOM_SHOW_PRIVILEGES,
|
||||||
SQLCOM_HELP,
|
SQLCOM_HELP,
|
||||||
|
SQLCOM_CREATE_PROCEDURE, SQLCOM_CREATE_SPFUNCTION, SQLCOM_CALL,
|
||||||
|
SQLCOM_DROP_PROCEDURE, SQLCOM_ALTER_PROCEDURE,SQLCOM_ALTER_FUNCTION,
|
||||||
|
|
||||||
/* This should be the last !!! */
|
/* This should be the last !!! */
|
||||||
SQLCOM_END
|
SQLCOM_END
|
||||||
@ -419,6 +424,7 @@ typedef struct st_lex
|
|||||||
SELECT_LEX_NODE *current_select;
|
SELECT_LEX_NODE *current_select;
|
||||||
/* list of all SELECT_LEX */
|
/* list of all SELECT_LEX */
|
||||||
SELECT_LEX *all_selects_list;
|
SELECT_LEX *all_selects_list;
|
||||||
|
uchar *buf; /* The beginning of string, used by SPs */
|
||||||
uchar *ptr,*tok_start,*tok_end,*end_of_query;
|
uchar *ptr,*tok_start,*tok_end,*end_of_query;
|
||||||
char *length,*dec,*change,*name;
|
char *length,*dec,*change,*name;
|
||||||
char *backup_dir; /* For RESTORE/BACKUP */
|
char *backup_dir; /* For RESTORE/BACKUP */
|
||||||
@ -478,6 +484,9 @@ typedef struct st_lex
|
|||||||
uint slave_thd_opt;
|
uint slave_thd_opt;
|
||||||
CHARSET_INFO *charset;
|
CHARSET_INFO *charset;
|
||||||
char *help_arg;
|
char *help_arg;
|
||||||
|
sp_head *sphead;
|
||||||
|
sp_pcontext *spcont;
|
||||||
|
List<char> spfuns; /* Called functions */
|
||||||
|
|
||||||
inline void uncacheable()
|
inline void uncacheable()
|
||||||
{
|
{
|
||||||
|
241
sql/sql_parse.cc
241
sql/sql_parse.cc
@ -26,6 +26,9 @@
|
|||||||
#include "ha_innodb.h"
|
#include "ha_innodb.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sp.h"
|
||||||
|
|
||||||
#ifdef HAVE_OPENSSL
|
#ifdef HAVE_OPENSSL
|
||||||
/*
|
/*
|
||||||
Without SSL the handshake consists of one packet. This packet
|
Without SSL the handshake consists of one packet. This packet
|
||||||
@ -44,6 +47,15 @@
|
|||||||
#define MIN_HANDSHAKE_SIZE 6
|
#define MIN_HANDSHAKE_SIZE 6
|
||||||
#endif /* HAVE_OPENSSL */
|
#endif /* HAVE_OPENSSL */
|
||||||
|
|
||||||
|
/* Used in error handling only */
|
||||||
|
#define SP_TYPE_STRING(LP) \
|
||||||
|
((LP)->sphead->m_type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE")
|
||||||
|
#define SP_COM_STRING(LP) \
|
||||||
|
((LP)->sql_command == SQLCOM_CREATE_SPFUNCTION || \
|
||||||
|
(LP)->sql_command == SQLCOM_ALTER_FUNCTION || \
|
||||||
|
(LP)->sql_command == SQLCOM_DROP_FUNCTION ? \
|
||||||
|
"FUNCTION" : "PROCEDURE")
|
||||||
|
|
||||||
extern int yyparse(void *thd);
|
extern int yyparse(void *thd);
|
||||||
extern "C" pthread_mutex_t THR_LOCK_keycache;
|
extern "C" pthread_mutex_t THR_LOCK_keycache;
|
||||||
#ifdef SOLARIS
|
#ifdef SOLARIS
|
||||||
@ -1565,7 +1577,7 @@ bool alloc_query(THD *thd, char *packet, ulong packet_length)
|
|||||||
** Execute command saved in thd and current_lex->sql_command
|
** Execute command saved in thd and current_lex->sql_command
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
void
|
int
|
||||||
mysql_execute_command(THD *thd)
|
mysql_execute_command(THD *thd)
|
||||||
{
|
{
|
||||||
int res= 0;
|
int res= 0;
|
||||||
@ -1575,6 +1587,18 @@ mysql_execute_command(THD *thd)
|
|||||||
SELECT_LEX_UNIT *unit= &lex->unit;
|
SELECT_LEX_UNIT *unit= &lex->unit;
|
||||||
DBUG_ENTER("mysql_execute_command");
|
DBUG_ENTER("mysql_execute_command");
|
||||||
|
|
||||||
|
/*
|
||||||
|
Clear the SP function cache before each statement (QQ this is a temporary
|
||||||
|
solution; caching will be rehacked later), and the new ones.
|
||||||
|
*/
|
||||||
|
sp_clear_function_cache(thd);
|
||||||
|
if (lex->sql_command != SQLCOM_CREATE_PROCEDURE &&
|
||||||
|
lex->sql_command != SQLCOM_CREATE_SPFUNCTION)
|
||||||
|
{
|
||||||
|
if (sp_cache_functions(thd, lex))
|
||||||
|
DBUG_RETURN(-1);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Reset warning count for each query that uses tables
|
Reset warning count for each query that uses tables
|
||||||
A better approach would be to reset this for any commands
|
A better approach would be to reset this for any commands
|
||||||
@ -1597,7 +1621,7 @@ mysql_execute_command(THD *thd)
|
|||||||
given and the table list says the query should not be replicated
|
given and the table list says the query should not be replicated
|
||||||
*/
|
*/
|
||||||
if (table_rules_on && tables && !tables_ok(thd,tables))
|
if (table_rules_on && tables && !tables_ok(thd,tables))
|
||||||
DBUG_VOID_RETURN;
|
DBUG_RETURN(0);
|
||||||
#ifndef TO_BE_DELETED
|
#ifndef TO_BE_DELETED
|
||||||
/*
|
/*
|
||||||
This is a workaround to deal with the shortcoming in 3.23.44-3.23.46
|
This is a workaround to deal with the shortcoming in 3.23.44-3.23.46
|
||||||
@ -1632,7 +1656,7 @@ mysql_execute_command(THD *thd)
|
|||||||
{
|
{
|
||||||
if (res < 0 || thd->net.report_error)
|
if (res < 0 || thd->net.report_error)
|
||||||
send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0);
|
send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0);
|
||||||
DBUG_VOID_RETURN;
|
DBUG_RETURN(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1645,7 +1669,7 @@ mysql_execute_command(THD *thd)
|
|||||||
!tables_ok(thd,tables))
|
!tables_ok(thd,tables))
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
DBUG_VOID_RETURN;
|
DBUG_RETURN(0);
|
||||||
|
|
||||||
statistic_increment(com_stat[lex->sql_command],&LOCK_status);
|
statistic_increment(com_stat[lex->sql_command],&LOCK_status);
|
||||||
switch (lex->sql_command) {
|
switch (lex->sql_command) {
|
||||||
@ -1684,7 +1708,7 @@ mysql_execute_command(THD *thd)
|
|||||||
if (!(result= new select_send()))
|
if (!(result= new select_send()))
|
||||||
{
|
{
|
||||||
send_error(thd, ER_OUT_OF_RESOURCES);
|
send_error(thd, ER_OUT_OF_RESOURCES);
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
thd->send_explain_fields(result);
|
thd->send_explain_fields(result);
|
||||||
@ -1947,7 +1971,7 @@ mysql_execute_command(THD *thd)
|
|||||||
find_real_table_in_list(tables->next, tables->db, tables->real_name))
|
find_real_table_in_list(tables->next, tables->db, tables->real_name))
|
||||||
{
|
{
|
||||||
net_printf(thd,ER_UPDATE_TABLE_USED,tables->real_name);
|
net_printf(thd,ER_UPDATE_TABLE_USED,tables->real_name);
|
||||||
DBUG_VOID_RETURN;
|
DBUG_RETURN(-1);
|
||||||
}
|
}
|
||||||
if (tables->next)
|
if (tables->next)
|
||||||
{
|
{
|
||||||
@ -2029,7 +2053,7 @@ mysql_execute_command(THD *thd)
|
|||||||
if (thd->locked_tables || thd->active_transaction())
|
if (thd->locked_tables || thd->active_transaction())
|
||||||
{
|
{
|
||||||
send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION);
|
send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION);
|
||||||
break;
|
goto error;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
LOCK_ACTIVE_MI;
|
LOCK_ACTIVE_MI;
|
||||||
@ -2042,7 +2066,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_ALTER_TABLE:
|
case SQLCOM_ALTER_TABLE:
|
||||||
#if defined(DONT_ALLOW_SHOW_COMMANDS)
|
#if defined(DONT_ALLOW_SHOW_COMMANDS)
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
break;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
ulong priv=0;
|
ulong priv=0;
|
||||||
@ -2134,7 +2158,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_BINLOGS:
|
case SQLCOM_SHOW_BINLOGS:
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
if (check_global_access(thd, SUPER_ACL))
|
if (check_global_access(thd, SUPER_ACL))
|
||||||
@ -2147,7 +2171,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_CREATE:
|
case SQLCOM_SHOW_CREATE:
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
if (check_db_used(thd, tables) ||
|
if (check_db_used(thd, tables) ||
|
||||||
@ -2225,7 +2249,7 @@ mysql_execute_command(THD *thd)
|
|||||||
if (select_lex->item_list.elements != lex->value_list.elements)
|
if (select_lex->item_list.elements != lex->value_list.elements)
|
||||||
{
|
{
|
||||||
send_error(thd,ER_WRONG_VALUE_COUNT);
|
send_error(thd,ER_WRONG_VALUE_COUNT);
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
}
|
}
|
||||||
res= mysql_update(thd,tables,
|
res= mysql_update(thd,tables,
|
||||||
select_lex->item_list,
|
select_lex->item_list,
|
||||||
@ -2246,7 +2270,7 @@ mysql_execute_command(THD *thd)
|
|||||||
if (select_lex->item_list.elements != lex->value_list.elements)
|
if (select_lex->item_list.elements != lex->value_list.elements)
|
||||||
{
|
{
|
||||||
send_error(thd,ER_WRONG_VALUE_COUNT);
|
send_error(thd,ER_WRONG_VALUE_COUNT);
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const char *msg= 0;
|
const char *msg= 0;
|
||||||
@ -2282,7 +2306,7 @@ mysql_execute_command(THD *thd)
|
|||||||
if (select_lex->item_list.elements != lex->value_list.elements)
|
if (select_lex->item_list.elements != lex->value_list.elements)
|
||||||
{
|
{
|
||||||
send_error(thd,ER_WRONG_VALUE_COUNT);
|
send_error(thd,ER_WRONG_VALUE_COUNT);
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
}
|
}
|
||||||
res = mysql_insert(thd,tables,lex->field_list,lex->many_values,
|
res = mysql_insert(thd,tables,lex->field_list,lex->many_values,
|
||||||
select_lex->item_list, lex->value_list,
|
select_lex->item_list, lex->value_list,
|
||||||
@ -2323,8 +2347,7 @@ mysql_execute_command(THD *thd)
|
|||||||
|
|
||||||
if (find_real_table_in_list(tables->next, tables->db, tables->real_name))
|
if (find_real_table_in_list(tables->next, tables->db, tables->real_name))
|
||||||
{
|
{
|
||||||
net_printf(thd,ER_UPDATE_TABLE_USED,tables->real_name);
|
lex->select_lex.options |= OPTION_BUFFER_RESULT;
|
||||||
DBUG_VOID_RETURN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skip first table, which is the table we are inserting in */
|
/* Skip first table, which is the table we are inserting in */
|
||||||
@ -2486,7 +2509,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_DATABASES:
|
case SQLCOM_SHOW_DATABASES:
|
||||||
#if defined(DONT_ALLOW_SHOW_COMMANDS)
|
#if defined(DONT_ALLOW_SHOW_COMMANDS)
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
if ((specialflag & SPECIAL_SKIP_SHOW_DB) &&
|
if ((specialflag & SPECIAL_SKIP_SHOW_DB) &&
|
||||||
check_global_access(thd, SHOW_DB_ACL))
|
check_global_access(thd, SHOW_DB_ACL))
|
||||||
@ -2520,7 +2543,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_LOGS:
|
case SQLCOM_SHOW_LOGS:
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
if (grant_option && check_access(thd, FILE_ACL, any_db))
|
if (grant_option && check_access(thd, FILE_ACL, any_db))
|
||||||
@ -2533,7 +2556,7 @@ mysql_execute_command(THD *thd)
|
|||||||
/* FALL THROUGH */
|
/* FALL THROUGH */
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
char *db=select_lex->db ? select_lex->db : thd->db;
|
char *db=select_lex->db ? select_lex->db : thd->db;
|
||||||
@ -2572,7 +2595,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_FIELDS:
|
case SQLCOM_SHOW_FIELDS:
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
char *db=tables->db;
|
char *db=tables->db;
|
||||||
@ -2597,7 +2620,7 @@ mysql_execute_command(THD *thd)
|
|||||||
case SQLCOM_SHOW_KEYS:
|
case SQLCOM_SHOW_KEYS:
|
||||||
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
#ifdef DONT_ALLOW_SHOW_COMMANDS
|
||||||
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */
|
||||||
DBUG_VOID_RETURN;
|
goto error;
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
char *db=tables->db;
|
char *db=tables->db;
|
||||||
@ -2779,26 +2802,24 @@ mysql_execute_command(THD *thd)
|
|||||||
res=mysqld_show_create_db(thd,lex->name,&lex->create_info);
|
res=mysqld_show_create_db(thd,lex->name,&lex->create_info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SQLCOM_CREATE_FUNCTION:
|
case SQLCOM_CREATE_FUNCTION: // UDF function
|
||||||
if (check_access(thd,INSERT_ACL,"mysql",0,1))
|
{
|
||||||
break;
|
if (check_access(thd,INSERT_ACL,"mysql",0,1))
|
||||||
|
break;
|
||||||
#ifdef HAVE_DLOPEN
|
#ifdef HAVE_DLOPEN
|
||||||
if (!(res = mysql_create_function(thd,&lex->udf)))
|
sp_head *sph= sp_find_function(thd, &lex->udf.name);
|
||||||
send_ok(thd);
|
if (sph)
|
||||||
|
{
|
||||||
|
net_printf(thd, ER_UDF_EXISTS, lex->udf.name.str);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!(res = mysql_create_function(thd,&lex->udf)))
|
||||||
|
send_ok(thd);
|
||||||
#else
|
#else
|
||||||
res= -1;
|
res= -1;
|
||||||
#endif
|
#endif
|
||||||
break;
|
|
||||||
case SQLCOM_DROP_FUNCTION:
|
|
||||||
if (check_access(thd,DELETE_ACL,"mysql",0,1))
|
|
||||||
break;
|
break;
|
||||||
#ifdef HAVE_DLOPEN
|
}
|
||||||
if (!(res = mysql_drop_function(thd,&lex->udf.name)))
|
|
||||||
send_ok(thd);
|
|
||||||
#else
|
|
||||||
res= -1;
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
case SQLCOM_REVOKE:
|
case SQLCOM_REVOKE:
|
||||||
case SQLCOM_GRANT:
|
case SQLCOM_GRANT:
|
||||||
{
|
{
|
||||||
@ -2967,16 +2988,162 @@ mysql_execute_command(THD *thd)
|
|||||||
res= -1;
|
res= -1;
|
||||||
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
|
thd->options&= ~(ulong) (OPTION_BEGIN | OPTION_STATUS_NO_TRANS_UPDATE);
|
||||||
break;
|
break;
|
||||||
|
case SQLCOM_CREATE_PROCEDURE:
|
||||||
|
case SQLCOM_CREATE_SPFUNCTION:
|
||||||
|
if (!lex->sphead)
|
||||||
|
{
|
||||||
|
res= -1; // Shouldn't happen
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint namelen;
|
||||||
|
char *name= lex->sphead->name(&namelen);
|
||||||
|
#ifdef HAVE_DLOPEN
|
||||||
|
if (lex->sphead->m_type == TYPE_ENUM_FUNCTION)
|
||||||
|
{
|
||||||
|
udf_func *udf = find_udf(name, namelen);
|
||||||
|
|
||||||
|
if (udf)
|
||||||
|
{
|
||||||
|
net_printf(thd, ER_UDF_EXISTS, name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
res= lex->sphead->create(thd);
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case SP_OK:
|
||||||
|
send_ok(thd);
|
||||||
|
break;
|
||||||
|
case SP_WRITE_ROW_FAILED:
|
||||||
|
net_printf(thd, ER_SP_ALREADY_EXISTS, SP_TYPE_STRING(lex), name);
|
||||||
|
goto error;
|
||||||
|
default:
|
||||||
|
net_printf(thd, ER_SP_STORE_FAILED, SP_TYPE_STRING(lex), name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SQLCOM_CALL:
|
||||||
|
{
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
sp= sp_find_procedure(thd, &lex->udf.name);
|
||||||
|
if (! sp)
|
||||||
|
{
|
||||||
|
net_printf(thd, ER_SP_DOES_NOT_EXIST, "PROCEDURE", lex->udf.name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifndef EMBEDDED_LIBRARY
|
||||||
|
// When executing substatements, they're assumed to send_error when
|
||||||
|
// it happens, but not to send_ok.
|
||||||
|
my_bool nsok= thd->net.no_send_ok;
|
||||||
|
|
||||||
|
thd->net.no_send_ok= TRUE;
|
||||||
|
#endif
|
||||||
|
res= sp->execute_procedure(thd, &lex->value_list);
|
||||||
|
#ifndef EMBEDDED_LIBRARY
|
||||||
|
thd->net.no_send_ok= nsok;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (res == 0)
|
||||||
|
send_ok(thd);
|
||||||
|
else
|
||||||
|
goto error; // Substatement should already have sent error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SQLCOM_ALTER_PROCEDURE:
|
||||||
|
case SQLCOM_ALTER_FUNCTION:
|
||||||
|
{
|
||||||
|
sp_head *sp;
|
||||||
|
|
||||||
|
if (lex->sql_command == SQLCOM_ALTER_PROCEDURE)
|
||||||
|
sp= sp_find_procedure(thd, &lex->udf.name);
|
||||||
|
else
|
||||||
|
sp= sp_find_function(thd, &lex->udf.name);
|
||||||
|
if (! sp)
|
||||||
|
{
|
||||||
|
net_printf(thd, ER_SP_DOES_NOT_EXIST, SP_COM_STRING(lex),lex->udf.name);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* QQ This is an no-op right now, since we haven't
|
||||||
|
put the characteristics in yet. */
|
||||||
|
send_ok(thd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SQLCOM_DROP_PROCEDURE:
|
||||||
|
case SQLCOM_DROP_FUNCTION:
|
||||||
|
{
|
||||||
|
if (lex->sql_command == SQLCOM_DROP_PROCEDURE)
|
||||||
|
res= sp_drop_procedure(thd, lex->udf.name.str, lex->udf.name.length);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res= sp_drop_function(thd, lex->udf.name.str, lex->udf.name.length);
|
||||||
|
#ifdef HAVE_DLOPEN
|
||||||
|
if (res == SP_KEY_NOT_FOUND)
|
||||||
|
{
|
||||||
|
udf_func *udf = find_udf(lex->udf.name.str, lex->udf.name.length);
|
||||||
|
if (udf)
|
||||||
|
{
|
||||||
|
if (check_access(thd, DELETE_ACL, "mysql", 0, 1))
|
||||||
|
goto error;
|
||||||
|
if (!(res = mysql_drop_function(thd,&lex->udf.name)))
|
||||||
|
{
|
||||||
|
send_ok(thd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case SP_OK:
|
||||||
|
send_ok(thd);
|
||||||
|
break;
|
||||||
|
case SP_KEY_NOT_FOUND:
|
||||||
|
if (lex->drop_if_exists)
|
||||||
|
{
|
||||||
|
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||||
|
ER_SP_DOES_NOT_EXIST, ER(ER_SP_DOES_NOT_EXIST),
|
||||||
|
SP_COM_STRING(lex), lex->udf.name.str);
|
||||||
|
res= 0;
|
||||||
|
send_ok(thd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
net_printf(thd, ER_SP_DOES_NOT_EXIST, SP_COM_STRING(lex),
|
||||||
|
lex->udf.name.str);
|
||||||
|
goto error;
|
||||||
|
default:
|
||||||
|
net_printf(thd, ER_SP_DROP_FAILED, SP_COM_STRING(lex),
|
||||||
|
lex->udf.name.str);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default: /* Impossible */
|
default: /* Impossible */
|
||||||
send_ok(thd);
|
send_ok(thd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
thd->proc_info="query end"; // QQ
|
thd->proc_info="query end"; // QQ
|
||||||
|
|
||||||
|
// We end up here if res == 0 and send_ok() has been done,
|
||||||
|
// or res != 0 and no send_error() has yet been done.
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0);
|
send_error(thd,thd->killed ? ER_SERVER_SHUTDOWN : 0);
|
||||||
|
DBUG_RETURN(res);
|
||||||
|
|
||||||
error:
|
error:
|
||||||
DBUG_VOID_RETURN;
|
// We end up here if send_error() has already been done.
|
||||||
|
DBUG_RETURN(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +191,8 @@ int handle_select(THD *thd, LEX *lex, select_result *result)
|
|||||||
send_error(thd, 0, NullS);
|
send_error(thd, 0, NullS);
|
||||||
res= 1;
|
res= 1;
|
||||||
}
|
}
|
||||||
delete result;
|
if (result != lex->result)
|
||||||
|
delete result;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
678
sql/sql_yacc.yy
678
sql/sql_yacc.yy
@ -35,6 +35,9 @@
|
|||||||
#include "sql_acl.h"
|
#include "sql_acl.h"
|
||||||
#include "lex_symbol.h"
|
#include "lex_symbol.h"
|
||||||
#include "item_create.h"
|
#include "item_create.h"
|
||||||
|
#include "sp_head.h"
|
||||||
|
#include "sp_pcontext.h"
|
||||||
|
#include "sp.h"
|
||||||
#include <myisam.h>
|
#include <myisam.h>
|
||||||
#include <myisammrg.h>
|
#include <myisammrg.h>
|
||||||
|
|
||||||
@ -121,6 +124,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token AVG_SYM
|
%token AVG_SYM
|
||||||
%token BEGIN_SYM
|
%token BEGIN_SYM
|
||||||
%token BINLOG_SYM
|
%token BINLOG_SYM
|
||||||
|
%token CALL_SYM
|
||||||
%token CHANGE
|
%token CHANGE
|
||||||
%token CLIENT_SYM
|
%token CLIENT_SYM
|
||||||
%token COMMENT_SYM
|
%token COMMENT_SYM
|
||||||
@ -202,6 +206,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token CONVERT_SYM
|
%token CONVERT_SYM
|
||||||
%token DATABASES
|
%token DATABASES
|
||||||
%token DATA_SYM
|
%token DATA_SYM
|
||||||
|
%token DECLARE_SYM
|
||||||
%token DEFAULT
|
%token DEFAULT
|
||||||
%token DELAYED_SYM
|
%token DELAYED_SYM
|
||||||
%token DELAY_KEY_WRITE_SYM
|
%token DELAY_KEY_WRITE_SYM
|
||||||
@ -247,6 +252,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token INFILE
|
%token INFILE
|
||||||
%token INNER_SYM
|
%token INNER_SYM
|
||||||
%token INNOBASE_SYM
|
%token INNOBASE_SYM
|
||||||
|
%token INOUT_SYM
|
||||||
%token INTO
|
%token INTO
|
||||||
%token IN_SYM
|
%token IN_SYM
|
||||||
%token ISOLATION
|
%token ISOLATION
|
||||||
@ -261,6 +267,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token LIKE
|
%token LIKE
|
||||||
%token LINES
|
%token LINES
|
||||||
%token LOCAL_SYM
|
%token LOCAL_SYM
|
||||||
|
%token LOCATOR_SYM
|
||||||
%token LOG_SYM
|
%token LOG_SYM
|
||||||
%token LOGS_SYM
|
%token LOGS_SYM
|
||||||
%token LONG_NUM
|
%token LONG_NUM
|
||||||
@ -304,6 +311,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token OR
|
%token OR
|
||||||
%token OR_OR_CONCAT
|
%token OR_OR_CONCAT
|
||||||
%token ORDER_SYM
|
%token ORDER_SYM
|
||||||
|
%token OUT_SYM
|
||||||
%token OUTER
|
%token OUTER
|
||||||
%token OUTFILE
|
%token OUTFILE
|
||||||
%token DUMPFILE
|
%token DUMPFILE
|
||||||
@ -342,6 +350,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token SIMPLE_SYM
|
%token SIMPLE_SYM
|
||||||
%token SHUTDOWN
|
%token SHUTDOWN
|
||||||
%token SPATIAL_SYM
|
%token SPATIAL_SYM
|
||||||
|
%token SPECIFIC_SYM
|
||||||
%token SSL_SYM
|
%token SSL_SYM
|
||||||
%token STARTING
|
%token STARTING
|
||||||
%token STATUS_SYM
|
%token STATUS_SYM
|
||||||
@ -362,9 +371,10 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token FUNC_ARG1
|
%token FUNC_ARG1
|
||||||
%token FUNC_ARG2
|
%token FUNC_ARG2
|
||||||
%token FUNC_ARG3
|
%token FUNC_ARG3
|
||||||
%token UDF_RETURNS_SYM
|
%token RETURN_SYM
|
||||||
|
%token RETURNS_SYM
|
||||||
%token UDF_SONAME_SYM
|
%token UDF_SONAME_SYM
|
||||||
%token UDF_SYM
|
%token FUNCTION_SYM
|
||||||
%token UNCOMMITTED_SYM
|
%token UNCOMMITTED_SYM
|
||||||
%token UNDERSCORE_CHARSET
|
%token UNDERSCORE_CHARSET
|
||||||
%token UNICODE_SYM
|
%token UNICODE_SYM
|
||||||
@ -502,6 +512,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token ROUND
|
%token ROUND
|
||||||
%token SECOND_SYM
|
%token SECOND_SYM
|
||||||
%token SHARE_SYM
|
%token SHARE_SYM
|
||||||
|
%token SP_FUNC
|
||||||
%token SUBSTRING
|
%token SUBSTRING
|
||||||
%token SUBSTRING_INDEX
|
%token SUBSTRING_INDEX
|
||||||
%token TRIM
|
%token TRIM
|
||||||
@ -531,6 +542,18 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
%token SQL_SMALL_RESULT
|
%token SQL_SMALL_RESULT
|
||||||
%token SQL_BUFFER_RESULT
|
%token SQL_BUFFER_RESULT
|
||||||
|
|
||||||
|
%token CURSOR_SYM
|
||||||
|
%token ELSEIF_SYM
|
||||||
|
%token ITERATE_SYM
|
||||||
|
%token LEAVE_SYM
|
||||||
|
%token LOOP_SYM
|
||||||
|
%token REPEAT_SYM
|
||||||
|
%token UNTIL_SYM
|
||||||
|
%token WHILE_SYM
|
||||||
|
%token ASENSITIVE_SYM
|
||||||
|
%token INSENSITIVE_SYM
|
||||||
|
%token SENSITIVE_SYM
|
||||||
|
|
||||||
%token ISSUER_SYM
|
%token ISSUER_SYM
|
||||||
%token SUBJECT_SYM
|
%token SUBJECT_SYM
|
||||||
%token CIPHER_SYM
|
%token CIPHER_SYM
|
||||||
@ -563,6 +586,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
ULONGLONG_NUM field_ident select_alias ident ident_or_text
|
ULONGLONG_NUM field_ident select_alias ident ident_or_text
|
||||||
UNDERSCORE_CHARSET IDENT_sys TEXT_STRING_sys TEXT_STRING_db
|
UNDERSCORE_CHARSET IDENT_sys TEXT_STRING_sys TEXT_STRING_db
|
||||||
NCHAR_STRING
|
NCHAR_STRING
|
||||||
|
SP_FUNC ident_or_spfunc
|
||||||
|
|
||||||
%type <lex_str_ptr>
|
%type <lex_str_ptr>
|
||||||
opt_table_alias
|
opt_table_alias
|
||||||
@ -682,8 +706,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
|
|||||||
union_clause union_list union_option
|
union_clause union_list union_option
|
||||||
precision subselect_start opt_and charset
|
precision subselect_start opt_and charset
|
||||||
subselect_end select_var_list select_var_list_init help opt_len
|
subselect_end select_var_list select_var_list_init help opt_len
|
||||||
|
statement
|
||||||
END_OF_INPUT
|
END_OF_INPUT
|
||||||
|
|
||||||
|
%type <NONE> call sp_proc_stmts sp_proc_stmt
|
||||||
|
%type <num> sp_decls sp_decl sp_decl_idents sp_opt_inout
|
||||||
|
|
||||||
%type <NONE>
|
%type <NONE>
|
||||||
'-' '+' '*' '/' '%' '(' ')'
|
'-' '+' '*' '/' '%' '(' ')'
|
||||||
',' '!' '{' '}' '&' '|' AND OR OR_OR_CONCAT BETWEEN_SYM CASE_SYM
|
',' '!' '{' '}' '&' '|' AND OR OR_OR_CONCAT BETWEEN_SYM CASE_SYM
|
||||||
@ -709,10 +737,16 @@ query:
|
|||||||
| verb_clause END_OF_INPUT {};
|
| verb_clause END_OF_INPUT {};
|
||||||
|
|
||||||
verb_clause:
|
verb_clause:
|
||||||
|
statement
|
||||||
|
| begin
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Verb clauses, except begin */
|
||||||
|
statement:
|
||||||
alter
|
alter
|
||||||
| analyze
|
| analyze
|
||||||
| backup
|
| backup
|
||||||
| begin
|
| call
|
||||||
| change
|
| change
|
||||||
| check
|
| check
|
||||||
| commit
|
| commit
|
||||||
@ -881,20 +915,509 @@ create:
|
|||||||
lex->name=$4.str;
|
lex->name=$4.str;
|
||||||
lex->create_info.options=$3;
|
lex->create_info.options=$3;
|
||||||
}
|
}
|
||||||
| CREATE udf_func_type UDF_SYM IDENT_sys
|
| CREATE udf_func_type FUNCTION_SYM ident_or_spfunc
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
lex->sql_command = SQLCOM_CREATE_FUNCTION;
|
|
||||||
lex->udf.name = $4;
|
lex->udf.name = $4;
|
||||||
lex->udf.type= $2;
|
lex->udf.type= $2;
|
||||||
}
|
}
|
||||||
UDF_RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys
|
create_function_tail
|
||||||
|
{}
|
||||||
|
| CREATE PROCEDURE ident
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
if (lex->sphead)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_NO_RECURSIVE_CREATE, "PROCEDURE");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
lex->spcont= new sp_pcontext();
|
||||||
|
lex->sphead= new sp_head(&$3, lex);
|
||||||
|
lex->sphead->m_type= TYPE_ENUM_PROCEDURE;
|
||||||
|
}
|
||||||
|
'(' sp_pdparam_list ')'
|
||||||
|
{
|
||||||
|
Lex->spcont->set_params();
|
||||||
|
}
|
||||||
|
sp_proc_stmt
|
||||||
|
{
|
||||||
|
Lex->sql_command= SQLCOM_CREATE_PROCEDURE;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
ident_or_spfunc:
|
||||||
|
IDENT_sys { $$= $1; }
|
||||||
|
| SP_FUNC { $$= $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
create_function_tail:
|
||||||
|
RETURNS_SYM udf_type UDF_SONAME_SYM TEXT_STRING_sys
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
lex->udf.returns=(Item_result) $7;
|
lex->sql_command = SQLCOM_CREATE_FUNCTION;
|
||||||
lex->udf.dl=$9.str;
|
lex->udf.returns=(Item_result) $2;
|
||||||
|
lex->udf.dl=$4.str;
|
||||||
}
|
}
|
||||||
;
|
| '('
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
if (lex->sphead)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_NO_RECURSIVE_CREATE, "FUNCTION");
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
lex->spcont= new sp_pcontext();
|
||||||
|
lex->sphead= new sp_head(&lex->udf.name, lex);
|
||||||
|
lex->sphead->m_type= TYPE_ENUM_FUNCTION;
|
||||||
|
}
|
||||||
|
sp_fdparam_list ')'
|
||||||
|
{
|
||||||
|
Lex->spcont->set_params();
|
||||||
|
}
|
||||||
|
RETURNS_SYM type
|
||||||
|
{
|
||||||
|
Lex->sphead->m_returns= (enum enum_field_types)$7;
|
||||||
|
}
|
||||||
|
sp_proc_stmt
|
||||||
|
{
|
||||||
|
Lex->sql_command = SQLCOM_CREATE_SPFUNCTION;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
call:
|
||||||
|
CALL_SYM ident_or_spfunc
|
||||||
|
{
|
||||||
|
LEX *lex = Lex;
|
||||||
|
|
||||||
|
lex->sql_command= SQLCOM_CALL;
|
||||||
|
lex->udf.name= $2;
|
||||||
|
lex->value_list.empty();
|
||||||
|
}
|
||||||
|
'(' sp_cparam_list ')' {}
|
||||||
|
;
|
||||||
|
|
||||||
|
/* CALL parameters */
|
||||||
|
sp_cparam_list:
|
||||||
|
/* Empty */
|
||||||
|
| sp_cparams
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_cparams:
|
||||||
|
sp_cparams ',' expr
|
||||||
|
{
|
||||||
|
Lex->value_list.push_back($3);
|
||||||
|
}
|
||||||
|
| expr
|
||||||
|
{
|
||||||
|
Lex->value_list.push_back($1);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Stored FUNCTION parameter declaration list */
|
||||||
|
sp_fdparam_list:
|
||||||
|
/* Empty */
|
||||||
|
| sp_fdparams
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_fdparams:
|
||||||
|
sp_fdparams ',' sp_fdparam
|
||||||
|
| sp_fdparam
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_fdparam:
|
||||||
|
ident type sp_opt_locator
|
||||||
|
{
|
||||||
|
Lex->spcont->push(&$1, (enum enum_field_types)$2, sp_param_in);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Stored PROCEDURE parameter declaration list */
|
||||||
|
sp_pdparam_list:
|
||||||
|
/* Empty */
|
||||||
|
| sp_pdparams
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_pdparams:
|
||||||
|
sp_pdparams ',' sp_pdparam
|
||||||
|
| sp_pdparam
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_pdparam:
|
||||||
|
sp_opt_inout ident type sp_opt_locator
|
||||||
|
{
|
||||||
|
Lex->spcont->push(&$2,
|
||||||
|
(enum enum_field_types)$3,
|
||||||
|
(sp_param_mode_t)$1);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_opt_inout:
|
||||||
|
/* Empty */ { $$= sp_param_in; }
|
||||||
|
| IN_SYM { $$= sp_param_in; }
|
||||||
|
| OUT_SYM { $$= sp_param_out; }
|
||||||
|
| INOUT_SYM { $$= sp_param_inout; }
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_opt_locator:
|
||||||
|
/* Empty */
|
||||||
|
| AS LOCATOR_SYM
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_proc_stmts:
|
||||||
|
sp_proc_stmt ';'
|
||||||
|
| sp_proc_stmts sp_proc_stmt ';'
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_decls:
|
||||||
|
/* Empty */
|
||||||
|
{
|
||||||
|
$$= 0;
|
||||||
|
}
|
||||||
|
| sp_decls sp_decl ';'
|
||||||
|
{
|
||||||
|
$$= $1 + $2;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_decl:
|
||||||
|
DECLARE_SYM sp_decl_idents type
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
uint max= lex->spcont->current_framesize();
|
||||||
|
|
||||||
|
for (uint i = max-$2 ; i < max ; i++)
|
||||||
|
{
|
||||||
|
lex->spcont->set_type(i, (enum enum_field_types)$3);
|
||||||
|
lex->spcont->set_isset(i, FALSE);
|
||||||
|
}
|
||||||
|
$$= $2;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_decl_idents:
|
||||||
|
ident
|
||||||
|
{
|
||||||
|
Lex->spcont->push(&$1, (enum_field_types)0, sp_param_in);
|
||||||
|
$$= 1;
|
||||||
|
}
|
||||||
|
| sp_decl_idents ',' ident
|
||||||
|
{
|
||||||
|
Lex->spcont->push(&$3, (enum_field_types)0, sp_param_in);
|
||||||
|
$$= $1 + 1;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_proc_stmt:
|
||||||
|
{
|
||||||
|
Lex->sphead->reset_lex(YYTHD);
|
||||||
|
}
|
||||||
|
statement
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
if (lex->sql_command == SQLCOM_SELECT && !lex->result)
|
||||||
|
{
|
||||||
|
send_error(YYTHD, ER_SP_BADSELECT);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Don't add an instruction for empty SET statements.
|
||||||
|
** (This happens if the SET only contained local variables,
|
||||||
|
** which get their set instructions generated separately.)
|
||||||
|
*/
|
||||||
|
if (lex->sql_command != SQLCOM_SET_OPTION ||
|
||||||
|
!lex->var_list.is_empty())
|
||||||
|
{
|
||||||
|
/* Currently we can't handle queries inside a FUNCTION,
|
||||||
|
** because of the way table locking works.
|
||||||
|
** This is unfortunate, and limits the usefulness of functions
|
||||||
|
** a great deal, but it's nothing we can do about this at the
|
||||||
|
** moment.
|
||||||
|
*/
|
||||||
|
if (lex->sphead->m_type == TYPE_ENUM_FUNCTION &&
|
||||||
|
lex->sql_command != SQLCOM_SET_OPTION)
|
||||||
|
{
|
||||||
|
send_error(YYTHD, ER_SP_BADQUERY);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sp_instr_stmt *i= new sp_instr_stmt(lex->sphead->instructions());
|
||||||
|
|
||||||
|
i->set_lex(lex);
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lex->sphead->restore_lex(YYTHD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| RETURN_SYM expr
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
if (lex->sphead->m_type == TYPE_ENUM_PROCEDURE)
|
||||||
|
{
|
||||||
|
send_error(YYTHD, ER_SP_BADRETURN);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sp_instr_return *i=
|
||||||
|
new sp_instr_return(lex->sphead->instructions(),
|
||||||
|
$2, lex->sphead->m_returns);
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| IF sp_if END IF {}
|
||||||
|
| CASE_SYM WHEN_SYM
|
||||||
|
{
|
||||||
|
Lex->sphead->m_simple_case= FALSE;
|
||||||
|
}
|
||||||
|
sp_case END CASE_SYM {}
|
||||||
|
| CASE_SYM expr WHEN_SYM
|
||||||
|
{
|
||||||
|
/* We "fake" this by using an anonymous variable which we
|
||||||
|
set to the expression. Note that all WHENs are evaluate
|
||||||
|
at the same frame level, so we then know that it's the
|
||||||
|
top-most variable in the frame. */
|
||||||
|
LEX *lex= Lex;
|
||||||
|
uint offset= lex->spcont->current_framesize();
|
||||||
|
sp_instr_set *i = new sp_instr_set(lex->sphead->instructions(),
|
||||||
|
offset, $2, MYSQL_TYPE_STRING);
|
||||||
|
LEX_STRING dummy;
|
||||||
|
|
||||||
|
dummy.str= (char *)"";
|
||||||
|
dummy.length= 0;
|
||||||
|
lex->spcont->push(&dummy, MYSQL_TYPE_STRING, sp_param_in);
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
lex->sphead->m_simple_case= TRUE;
|
||||||
|
}
|
||||||
|
sp_case END CASE_SYM
|
||||||
|
{
|
||||||
|
Lex->spcont->pop();
|
||||||
|
}
|
||||||
|
| sp_labeled_control
|
||||||
|
{}
|
||||||
|
| { /* Unlabeled controls get a secret label. */
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
lex->spcont->push_label((char *)"", lex->sphead->instructions());
|
||||||
|
}
|
||||||
|
sp_unlabeled_control
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
lex->sphead->backpatch(lex->spcont->pop_label());
|
||||||
|
}
|
||||||
|
| LEAVE_SYM IDENT
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_head *sp = lex->sphead;
|
||||||
|
sp_label_t *lab= lex->spcont->find_label($2.str);
|
||||||
|
|
||||||
|
if (! lab)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_LILABEL_MISMATCH, "LEAVE", $2.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sp_instr_jump *i= new sp_instr_jump(sp->instructions());
|
||||||
|
|
||||||
|
sp->push_backpatch(i, lab); /* Jumping forward */
|
||||||
|
sp->add_instr(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| ITERATE_SYM IDENT
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_label_t *lab= lex->spcont->find_label($2.str);
|
||||||
|
|
||||||
|
if (! lab)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_LILABEL_MISMATCH, "ITERATE", $2.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint ip= lex->sphead->instructions();
|
||||||
|
sp_instr_jump *i= new sp_instr_jump(ip, lab->ip); /* Jump back */
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_if:
|
||||||
|
expr THEN_SYM
|
||||||
|
{
|
||||||
|
sp_head *sp= Lex->sphead;
|
||||||
|
sp_pcontext *ctx= Lex->spcont;
|
||||||
|
uint ip= sp->instructions();
|
||||||
|
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, $1);
|
||||||
|
|
||||||
|
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
|
||||||
|
sp->add_instr(i);
|
||||||
|
}
|
||||||
|
sp_proc_stmts
|
||||||
|
{
|
||||||
|
sp_head *sp= Lex->sphead;
|
||||||
|
sp_pcontext *ctx= Lex->spcont;
|
||||||
|
uint ip= sp->instructions();
|
||||||
|
sp_instr_jump *i = new sp_instr_jump(ip);
|
||||||
|
|
||||||
|
sp->add_instr(i);
|
||||||
|
sp->backpatch(ctx->pop_label());
|
||||||
|
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
|
||||||
|
}
|
||||||
|
sp_elseifs
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
lex->sphead->backpatch(lex->spcont->pop_label());
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_elseifs:
|
||||||
|
/* Empty */
|
||||||
|
| ELSEIF_SYM sp_if
|
||||||
|
| ELSE sp_proc_stmts
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_case:
|
||||||
|
expr THEN_SYM
|
||||||
|
{
|
||||||
|
sp_head *sp= Lex->sphead;
|
||||||
|
sp_pcontext *ctx= Lex->spcont;
|
||||||
|
uint ip= sp->instructions();
|
||||||
|
sp_instr_jump_if_not *i;
|
||||||
|
|
||||||
|
if (! sp->m_simple_case)
|
||||||
|
i= new sp_instr_jump_if_not(ip, $1);
|
||||||
|
else
|
||||||
|
{ /* Simple case: <caseval> = <whenval> */
|
||||||
|
Item *var= (Item*) new Item_splocal(ctx->current_framesize()-1);
|
||||||
|
Item *expr= Item_bool_func2::eq_creator(var, $1);
|
||||||
|
|
||||||
|
i= new sp_instr_jump_if_not(ip, expr);
|
||||||
|
}
|
||||||
|
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
|
||||||
|
sp->add_instr(i);
|
||||||
|
}
|
||||||
|
sp_proc_stmts
|
||||||
|
{
|
||||||
|
sp_head *sp= Lex->sphead;
|
||||||
|
sp_pcontext *ctx= Lex->spcont;
|
||||||
|
uint ip= sp->instructions();
|
||||||
|
sp_instr_jump *i = new sp_instr_jump(ip);
|
||||||
|
|
||||||
|
sp->add_instr(i);
|
||||||
|
sp->backpatch(ctx->pop_label());
|
||||||
|
sp->push_backpatch(i, ctx->push_label((char *)"", 0));
|
||||||
|
}
|
||||||
|
sp_whens
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
|
||||||
|
lex->sphead->backpatch(lex->spcont->pop_label());
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_whens:
|
||||||
|
/* Empty */ {}
|
||||||
|
| WHEN_SYM sp_case {}
|
||||||
|
| ELSE sp_proc_stmts {}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_labeled_control:
|
||||||
|
IDENT ':'
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_label_t *lab= lex->spcont->find_label($1.str);
|
||||||
|
|
||||||
|
if (lab)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_LABEL_REDEFINE, $1.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lex->spcont->push_label($1.str,
|
||||||
|
lex->sphead->instructions());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp_unlabeled_control IDENT
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_label_t *lab= lex->spcont->find_label($5.str);
|
||||||
|
|
||||||
|
if (! lab || strcasecmp($5.str, lab->name) != 0)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_LABEL_MISMATCH, $5.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lex->spcont->pop_label();
|
||||||
|
lex->sphead->backpatch(lab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
sp_unlabeled_control:
|
||||||
|
BEGIN_SYM
|
||||||
|
sp_decls
|
||||||
|
sp_proc_stmts
|
||||||
|
END
|
||||||
|
{ /* QQ This is just a dummy for grouping declarations and statements
|
||||||
|
together. No [[NOT] ATOMIC] yet, and we need to figure out how
|
||||||
|
make it coexist with the existing BEGIN COMMIT/ROLLBACK. */
|
||||||
|
Lex->spcont->pop($2);
|
||||||
|
}
|
||||||
|
| LOOP_SYM
|
||||||
|
sp_proc_stmts END LOOP_SYM
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
uint ip= lex->sphead->instructions();
|
||||||
|
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
|
||||||
|
sp_instr_jump *i = new sp_instr_jump(ip, lab->ip);
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
| WHILE_SYM expr DO_SYM
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_head *sp= lex->sphead;
|
||||||
|
uint ip= sp->instructions();
|
||||||
|
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, $2);
|
||||||
|
|
||||||
|
/* Jumping forward */
|
||||||
|
sp->push_backpatch(i, lex->spcont->last_label());
|
||||||
|
sp->add_instr(i);
|
||||||
|
}
|
||||||
|
sp_proc_stmts END WHILE_SYM
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
uint ip= lex->sphead->instructions();
|
||||||
|
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
|
||||||
|
sp_instr_jump *i = new sp_instr_jump(ip, lab->ip);
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
| REPEAT_SYM sp_proc_stmts UNTIL_SYM expr END REPEAT_SYM
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
uint ip= lex->sphead->instructions();
|
||||||
|
sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */
|
||||||
|
sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, $4, lab->ip);
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
create2:
|
create2:
|
||||||
'(' field_list ')' opt_create_table_options create3 {}
|
'(' field_list ')' opt_create_table_options create3 {}
|
||||||
@ -1524,8 +2047,30 @@ alter:
|
|||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
lex->sql_command=SQLCOM_ALTER_DB;
|
lex->sql_command=SQLCOM_ALTER_DB;
|
||||||
lex->name=$3.str;
|
lex->name=$3.str;
|
||||||
};
|
}
|
||||||
|
| ALTER PROCEDURE ident
|
||||||
|
/* QQ Characteristics missing for now */
|
||||||
|
opt_restrict
|
||||||
|
{
|
||||||
|
LEX *lex=Lex;
|
||||||
|
|
||||||
|
/* This is essensially an no-op right now, since we haven't
|
||||||
|
put the characteristics in yet. */
|
||||||
|
lex->sql_command= SQLCOM_ALTER_PROCEDURE;
|
||||||
|
lex->udf.name= $3;
|
||||||
|
}
|
||||||
|
| ALTER FUNCTION_SYM ident
|
||||||
|
/* QQ Characteristics missing for now */
|
||||||
|
opt_restrict
|
||||||
|
{
|
||||||
|
LEX *lex=Lex;
|
||||||
|
|
||||||
|
/* This is essensially an no-op right now, since we haven't
|
||||||
|
put the characteristics in yet. */
|
||||||
|
lex->sql_command= SQLCOM_ALTER_FUNCTION;
|
||||||
|
lex->udf.name= $3;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
alter_list:
|
alter_list:
|
||||||
| alter_list_item
|
| alter_list_item
|
||||||
@ -2213,6 +2758,8 @@ simple_expr:
|
|||||||
{ $$= ((Item*(*)(Item*,Item*))($1.symbol->create_func))($3,$5);}
|
{ $$= ((Item*(*)(Item*,Item*))($1.symbol->create_func))($3,$5);}
|
||||||
| FUNC_ARG3 '(' expr ',' expr ',' expr ')'
|
| FUNC_ARG3 '(' expr ',' expr ',' expr ')'
|
||||||
{ $$= ((Item*(*)(Item*,Item*,Item*))($1.symbol->create_func))($3,$5,$7);}
|
{ $$= ((Item*(*)(Item*,Item*,Item*))($1.symbol->create_func))($3,$5,$7);}
|
||||||
|
| REPEAT_SYM '(' expr ',' expr ')'
|
||||||
|
{ $$= new Item_func_repeat($3,$5); }
|
||||||
| ATAN '(' expr ')'
|
| ATAN '(' expr ')'
|
||||||
{ $$= new Item_func_atan($3); }
|
{ $$= new Item_func_atan($3); }
|
||||||
| ATAN '(' expr ',' expr ')'
|
| ATAN '(' expr ',' expr ')'
|
||||||
@ -2450,6 +2997,14 @@ simple_expr:
|
|||||||
{ $$= new Item_func_round($3,$5,1); }
|
{ $$= new Item_func_round($3,$5,1); }
|
||||||
| TRUE_SYM
|
| TRUE_SYM
|
||||||
{ $$= new Item_int((char*) "TRUE",1,1); }
|
{ $$= new Item_int((char*) "TRUE",1,1); }
|
||||||
|
| SP_FUNC '(' udf_expr_list ')'
|
||||||
|
{
|
||||||
|
sp_add_fun_to_lex(Lex, $1);
|
||||||
|
if ($3)
|
||||||
|
$$= new Item_func_sp($1, *$3);
|
||||||
|
else
|
||||||
|
$$= new Item_func_sp($1);
|
||||||
|
}
|
||||||
| UDA_CHAR_SUM '(' udf_expr_list ')'
|
| UDA_CHAR_SUM '(' udf_expr_list ')'
|
||||||
{
|
{
|
||||||
if ($3 != NULL)
|
if ($3 != NULL)
|
||||||
@ -3077,12 +3632,34 @@ select_var_list:
|
|||||||
| select_var_ident {}
|
| select_var_ident {}
|
||||||
;
|
;
|
||||||
|
|
||||||
select_var_ident: '@' ident_or_text
|
select_var_ident:
|
||||||
|
'@' ident_or_text
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
if (lex->result && ((select_dumpvar *)lex->result)->var_list.push_back((LEX_STRING*) sql_memdup(&$2,sizeof(LEX_STRING))))
|
if (lex->result)
|
||||||
|
((select_dumpvar *)lex->result)->var_list.push_back( new my_var($2,0,0));
|
||||||
|
else
|
||||||
YYABORT;
|
YYABORT;
|
||||||
}
|
}
|
||||||
|
| ident_or_text
|
||||||
|
{
|
||||||
|
LEX *lex=Lex;
|
||||||
|
if (!lex->spcont)
|
||||||
|
YYABORT;
|
||||||
|
sp_pvar_t *t;
|
||||||
|
if (!(t=lex->spcont->find_pvar(&$1)))
|
||||||
|
{
|
||||||
|
send_error(lex->thd, ER_SYNTAX_ERROR);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
if (! lex->result)
|
||||||
|
YYABORT;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
((select_dumpvar *)lex->result)->var_list.push_back( new my_var($1,1,t->offset));
|
||||||
|
t->isset= TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
into:
|
into:
|
||||||
@ -3160,13 +3737,21 @@ drop:
|
|||||||
lex->drop_if_exists=$3;
|
lex->drop_if_exists=$3;
|
||||||
lex->name=$4.str;
|
lex->name=$4.str;
|
||||||
}
|
}
|
||||||
| DROP UDF_SYM IDENT_sys
|
| DROP FUNCTION_SYM if_exists IDENT_sys opt_restrict
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
lex->sql_command = SQLCOM_DROP_FUNCTION;
|
lex->sql_command = SQLCOM_DROP_FUNCTION;
|
||||||
lex->udf.name = $3;
|
lex->drop_if_exists= $3;
|
||||||
};
|
lex->udf.name= $4;
|
||||||
|
}
|
||||||
|
| DROP PROCEDURE if_exists IDENT_sys opt_restrict
|
||||||
|
{
|
||||||
|
LEX *lex=Lex;
|
||||||
|
lex->sql_command = SQLCOM_DROP_PROCEDURE;
|
||||||
|
lex->drop_if_exists= $3;
|
||||||
|
lex->udf.name= $4;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
table_list:
|
table_list:
|
||||||
table_name
|
table_name
|
||||||
@ -3222,7 +3807,6 @@ replace:
|
|||||||
}
|
}
|
||||||
insert_field_spec
|
insert_field_spec
|
||||||
{}
|
{}
|
||||||
{}
|
|
||||||
;
|
;
|
||||||
|
|
||||||
insert_lock_option:
|
insert_lock_option:
|
||||||
@ -3961,8 +4545,25 @@ order_ident:
|
|||||||
simple_ident:
|
simple_ident:
|
||||||
ident
|
ident
|
||||||
{
|
{
|
||||||
SELECT_LEX_NODE *sel=Select;
|
sp_pvar_t *spv;
|
||||||
$$ = !sel->create_refs || sel->get_in_sum_expr() > 0 ? (Item*) new Item_field(NullS,NullS,$1.str) : (Item*) new Item_ref(NullS,NullS,$1.str);
|
LEX *lex = Lex;
|
||||||
|
sp_pcontext *spc = lex->spcont;
|
||||||
|
|
||||||
|
if (spc && (spv = spc->find_pvar(&$1)))
|
||||||
|
{ /* We're compiling a stored procedure and found a variable */
|
||||||
|
if (lex->sql_command != SQLCOM_CALL && ! spv->isset)
|
||||||
|
{
|
||||||
|
net_printf(YYTHD, ER_SP_UNINIT_VAR, $1.str);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$$ = (Item*) new Item_splocal(spv->offset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SELECT_LEX_NODE *sel=Select;
|
||||||
|
$$ = !sel->create_refs || sel->get_in_sum_expr() > 0 ? (Item*) new Item_field(NullS,NullS,$1.str) : (Item*) new Item_ref(NullS,NullS,$1.str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
| ident '.' ident
|
| ident '.' ident
|
||||||
{
|
{
|
||||||
@ -4275,7 +4876,7 @@ keyword:
|
|||||||
| TIMESTAMP {}
|
| TIMESTAMP {}
|
||||||
| TIME_SYM {}
|
| TIME_SYM {}
|
||||||
| TYPE_SYM {}
|
| TYPE_SYM {}
|
||||||
| UDF_SYM {}
|
| FUNCTION_SYM {}
|
||||||
| UNCOMMITTED_SYM {}
|
| UNCOMMITTED_SYM {}
|
||||||
| UNICODE_SYM {}
|
| UNICODE_SYM {}
|
||||||
| USE_FRM {}
|
| USE_FRM {}
|
||||||
@ -4330,15 +4931,12 @@ opt_var_ident_type:
|
|||||||
;
|
;
|
||||||
|
|
||||||
option_value:
|
option_value:
|
||||||
'@' ident_or_text equal expr
|
'@' ident_or_text equal expr
|
||||||
{
|
|
||||||
Lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4)));
|
|
||||||
}
|
|
||||||
| internal_variable_name equal set_expr_or_default
|
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
Lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4)));
|
||||||
lex->var_list.push_back(new set_var(lex->option_type, $1, $3));
|
|
||||||
}
|
}
|
||||||
|
| internal_or_splocal
|
||||||
|
{}
|
||||||
| '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
|
| '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
|
||||||
{
|
{
|
||||||
LEX *lex=Lex;
|
LEX *lex=Lex;
|
||||||
@ -4439,6 +5037,32 @@ internal_variable_name:
|
|||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
internal_or_splocal:
|
||||||
|
ident equal set_expr_or_default
|
||||||
|
{
|
||||||
|
LEX *lex= Lex;
|
||||||
|
sp_pcontext *spc= lex->spcont;
|
||||||
|
sp_pvar_t *spv;
|
||||||
|
|
||||||
|
if (!spc || !(spv = spc->find_pvar(&$1)))
|
||||||
|
{ /* Not an SP local variable */
|
||||||
|
sys_var *tmp= find_sys_var($1.str, $1.length);
|
||||||
|
|
||||||
|
if (!tmp)
|
||||||
|
YYABORT;
|
||||||
|
lex->var_list.push_back(new set_var(lex->option_type, tmp, $3));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ /* An SP local variable */
|
||||||
|
sp_instr_set *i= new sp_instr_set(lex->sphead->instructions(),
|
||||||
|
spv->offset, $3, spv->type);
|
||||||
|
|
||||||
|
lex->sphead->add_instr(i);
|
||||||
|
spv->isset= TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
isolation_types:
|
isolation_types:
|
||||||
READ_SYM UNCOMMITTED_SYM { $$= ISO_READ_UNCOMMITTED; }
|
READ_SYM UNCOMMITTED_SYM { $$= ISO_READ_UNCOMMITTED; }
|
||||||
| READ_SYM COMMITTED_SYM { $$= ISO_READ_COMMITTED; }
|
| READ_SYM COMMITTED_SYM { $$= ISO_READ_COMMITTED; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user