Commit 2a25b261 authored by konstantin@oak.local's avatar konstantin@oak.local

Prepared_statement deployed instead of PREP_STMT.

parent d37da004
......@@ -743,90 +743,3 @@ bool Protocol::convert_str(const char *from, uint length)
}
#endif
bool setup_params_data(st_prep_stmt *stmt)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
ulong param_no= 0;
MYSQL_BIND *client_param= thd->client_params;
DBUG_ENTER("setup_params_data");
for (;(param= (Item_param *)param_iterator++); client_param++)
{
setup_param_functions(param, client_param->buffer_type);
if (!param->long_data_supplied)
{
if (*client_param->is_null)
param->maybe_null= param->null_value= 1;
else
{
uchar *buff= (uchar*)client_param->buffer;
param->maybe_null= param->null_value= 0;
param->setup_param_func(param,&buff,
client_param->length ?
*client_param->length :
client_param->buffer_length);
}
}
param_no++;
}
DBUG_RETURN(0);
}
bool setup_params_data_withlog(st_prep_stmt *stmt)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
MYSQL_BIND *client_param= thd->client_params;
DBUG_ENTER("setup_params_data");
String str, *res, *query= new String(stmt->query->alloced_length());
query->copy(*stmt->query);
ulong param_no= 0;
uint32 length= 0;
for (;(param= (Item_param *)param_iterator++); client_param++)
{
setup_param_functions(param, client_param->buffer_type);
if (param->long_data_supplied)
res= param->query_val_str(&str);
else
{
if (*client_param->is_null)
{
param->maybe_null= param->null_value= 1;
res= &my_null_string;
}
else
{
uchar *buff= (uchar*)client_param->buffer;
param->maybe_null= param->null_value= 0;
param->setup_param_func(param,&buff,
client_param->length ?
*client_param->length :
client_param->buffer_length);
res= param->query_val_str(&str);
}
}
if (query->replace(param->pos_in_query+length, 1, *res))
DBUG_RETURN(1);
length+= res->length()-1;
param_no++;
}
if (alloc_query(stmt->thd, (char *)query->ptr(), query->length()+1))
DBUG_RETURN(1);
query->free();
DBUG_RETURN(0);
}
......@@ -612,8 +612,6 @@ int mysqld_show_column_types(THD *thd);
int mysqld_help (THD *thd, const char *text);
/* sql_prepare.cc */
int compare_prep_stmt(void *not_used, PREP_STMT *stmt, ulong *key);
void free_prep_stmt(PREP_STMT *stmt, TREE_FREE mode, void *not_used);
bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length);
void mysql_stmt_execute(THD *thd, char *packet);
void mysql_stmt_free(THD *thd, char *packet);
......@@ -855,7 +853,7 @@ extern I_List<THD> threads;
extern I_List<NAMED_LIST> key_caches;
extern MY_BITMAP temp_pool;
extern String my_empty_string;
extern String my_null_string;
extern const String my_null_string;
extern SHOW_VAR init_vars[],status_vars[], internal_vars[];
extern SHOW_COMP_OPTION have_isam;
extern SHOW_COMP_OPTION have_innodb;
......
......@@ -3111,7 +3111,6 @@ slave_begin:
sql_print_error("Failed during slave thread initialization");
goto err;
}
thd->init_for_queries();
rli->sql_thd= thd;
thd->temporary_tables = rli->save_temporary_tables; // restore temp tables
pthread_mutex_lock(&LOCK_thread_count);
......
......@@ -88,21 +88,20 @@ THD::THD():user_time(0), is_fatal_error(0),
insert_id_used(0), rand_used(0), in_lock_tables(0),
global_read_lock(0), bootstrap(0)
{
host=user=priv_user=db=query=ip=0;
lex= &main_lex;
host= user= priv_user= db= ip=0;
host_or_ip= "connecting host";
locked=killed=some_tables_deleted=no_errors=password= 0;
query_start_used= 0;
count_cuted_fields= CHECK_FIELD_IGNORE;
db_length=query_length=col_access=0;
db_length= col_access= 0;
query_error= tmp_table_used= 0;
next_insert_id=last_insert_id=0;
open_tables= temporary_tables= handler_tables= derived_tables= 0;
handler_items=0;
tmp_table=0;
lock=locked_tables=0;
used_tables=0;
cuted_fields= sent_row_count= current_stmt_id= 0L;
cuted_fields= sent_row_count= 0L;
statement_id_counter= 0UL;
// Must be reset to handle error with THD's created for init of mysqld
lex->current_select= 0;
start_time=(time_t) 0;
......@@ -138,7 +137,6 @@ THD::THD():user_time(0), is_fatal_error(0),
server_id = ::server_id;
slave_net = 0;
command=COM_CONNECT;
set_query_id=1;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
db_access=NO_ACCESS;
#endif
......@@ -146,10 +144,11 @@ THD::THD():user_time(0), is_fatal_error(0),
*scramble= '\0';
init();
init_sql_alloc(&mem_root, // must be after init()
variables.query_alloc_block_size,
variables.query_prealloc_size);
/* Initialize sub structures */
bzero((char*) &mem_root,sizeof(mem_root));
bzero((char*) &transaction.mem_root,sizeof(transaction.mem_root));
bzero((char*) &con_root,sizeof(con_root));
bzero((char*) &warn_root,sizeof(warn_root));
init_alloc_root(&warn_root, WARN_ALLOC_BLOCK_SIZE, WARN_ALLOC_PREALLOC_SIZE);
user_connect=(USER_CONN *)0;
......@@ -166,12 +165,6 @@ THD::THD():user_time(0), is_fatal_error(0),
else
bzero((char*) &user_var_events, sizeof(user_var_events));
/* Prepared statements */
last_prepared_stmt= 0;
init_tree(&prepared_statements, 0, 0, sizeof(PREP_STMT),
(qsort_cmp2) compare_prep_stmt, 1,
(tree_element_free) free_prep_stmt, 0);
/* Protocol */
protocol= &protocol_simple; // Default protocol
protocol_simple.init(this);
......@@ -189,7 +182,9 @@ THD::THD():user_time(0), is_fatal_error(0),
transaction.trans_log.end_of_file= max_binlog_cache_size;
}
#endif
init_sql_alloc(&transaction.mem_root,
variables.trans_alloc_block_size,
variables.trans_prealloc_size);
/*
We need good random number initialization for new thread
Just coping global one will not work
......@@ -232,22 +227,6 @@ void THD::init(void)
}
/*
Init THD for query processing
This has to be called once before we call mysql_parse()
*/
void THD::init_for_queries()
{
init_sql_alloc(&mem_root, variables.query_alloc_block_size,
variables.query_prealloc_size);
init_sql_alloc(&transaction.mem_root,
variables.trans_alloc_block_size,
variables.trans_prealloc_size);
}
/*
Do what's needed when one invokes change user
......@@ -276,7 +255,6 @@ void THD::cleanup(void)
{
DBUG_ENTER("THD::cleanup");
ha_rollback(this);
delete_tree(&prepared_statements);
if (locked_tables)
{
lock=locked_tables; locked_tables=0;
......@@ -340,8 +318,6 @@ THD::~THD()
safeFree(user);
safeFree(db);
safeFree(ip);
free_root(&mem_root,MYF(0));
free_root(&con_root,MYF(0));
free_root(&warn_root,MYF(0));
free_root(&transaction.mem_root,MYF(0));
mysys_var=0; // Safety (shouldn't be needed)
......@@ -1193,6 +1169,102 @@ int select_dumpvar::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
}
return 0;
}
/*
Statement functions
*/
Statement::Statement(THD *thd)
:id(++thd->statement_id_counter),
query_id(thd->query_id),
set_query_id(1),
allow_sum_func(0),
command(thd->command),
lex(&main_lex),
query(0),
query_length(0),
free_list(0)
{
init_sql_alloc(&mem_root,
thd->variables.query_alloc_block_size,
thd->variables.query_prealloc_size);
}
/*
This constructor is called when statement is a subobject of THD:
Some variables are initialized in THD::init due to locking problems
This statement object will be used to
*/
Statement::Statement()
:id(0),
query_id(0), /* initialized later */
set_query_id(1),
allow_sum_func(0), /* initialized later */
command(COM_SLEEP), /* initialized later */
lex(&main_lex),
query(0), /* these two are set */
query_length(0), /* in alloc_query() */
free_list(0)
{
bzero((char *) &mem_root, sizeof(mem_root));
}
Statement::Type Statement::type() const
{
return STATEMENT;
}
void Statement::set_statement(Statement *stmt)
{
id= stmt->id;
query_id= stmt->query_id;
set_query_id= stmt->set_query_id;
allow_sum_func= stmt->allow_sum_func;
command= stmt->command;
lex= stmt->lex;
query= stmt->query;
query_length= stmt->query_length;
free_list= stmt->free_list;
mem_root= stmt->mem_root;
}
Statement::~Statement()
{
free_root(&mem_root, MYF(0));
}
C_MODE_START
static byte *
get_statement_id_as_hash_key(const byte *record, uint *key_length,
my_bool not_used __attribute__((unused)))
{
const Statement *statement= (const Statement *) record;
*key_length= sizeof(statement->id);
return (byte *) &((const Statement *) statement)->id;
}
static void delete_statement_as_hash_key(void *key)
{
delete (Statement *) key;
}
C_MODE_END
Statement_map::Statement_map() :
last_found_statement(0)
{
enum { START_HASH_SIZE = 16 };
hash_init(&st_hash, default_charset_info, START_HASH_SIZE, 0, 0,
get_statement_id_as_hash_key,
delete_statement_as_hash_key, MYF(0));
}
bool select_dumpvar::send_data(List<Item> &items)
{
List_iterator_fast<Item_func_set_user_var> li(vars);
......
......@@ -328,30 +328,6 @@ public:
};
/* This is a struct as it's allocated in tree_insert */
typedef struct st_prep_stmt
{
THD *thd;
LEX lex;
Item_param **param;
Item *free_list;
MEM_ROOT mem_root;
String *query;
ulong stmt_id;
uint param_count;
uint last_errno;
char last_error[MYSQL_ERRMSG_SIZE];
bool error_in_prepare, long_data_used;
bool log_full_query;
#ifndef EMBEDDED_LIBRARY
bool (*setup_params)(st_prep_stmt *stmt, uchar *pos, uchar *read_pos);
#else
bool (*setup_params_data)(st_prep_stmt *stmt);
#endif
} PREP_STMT;
class delayed_insert;
class select_result;
......@@ -428,12 +404,158 @@ struct system_variables
};
void free_tmp_table(THD *thd, TABLE *entry);
class Prepared_statement;
/*
State of a single command executed against this connection.
One connection can contain a lot of simultaneously running statements,
some of which could be:
- prepared, that is, contain placeholders,
- opened as cursors. We maintain 1 to 1 relationship between
statement and cursor - if user wants to create another cursor for his
query, we create another statement for it.
To perform some action with statement we reset THD part to the state of
that statement, do the action, and then save back modified state from THD
to the statement. It will be changed in near future, and Statement will
be used explicitly.
*/
class Statement
{
Statement(const Statement &rhs); /* not implemented: */
Statement &operator=(const Statement &rhs); /* non-copyable */
public:
/* FIXME: must be private */
LEX main_lex;
public:
/*
Uniquely identifies each statement object in thread scope; change during
statement lifetime. FIXME: must be const
*/
ulong id;
/*
Id of current query. Statement can be reused to execute several queries
query_id is global in context of the whole MySQL server.
ID is automatically generated from mutex-protected counter.
It's used in handler code for various purposes: to check which columns
from table are necessary for this select, to check if it's necessary to
update auto-updatable fields (like auto_increment and timestamp).
*/
ulong query_id;
/*
- if set_query_id=1, we set field->query_id for all fields. In that case
field list can not contain duplicates.
*/
bool set_query_id;
/*
This variable is used in post-parse stage to declare that sum-functions,
or functions which have sense only if GROUP BY is present, are allowed.
For example in queries
SELECT MIN(i) FROM foo
SELECT GROUP_CONCAT(a, b, MIN(i)) FROM ... GROUP BY ...
MIN(i) have no sense.
Though it's grammar-related issue, it's hard to catch it out during the
parse stage because GROUP BY clause goes in the end of query. This
variable is mainly used in setup_fields/fix_fields.
See item_sum.cc for details.
*/
bool allow_sum_func;
/*
Type of current query: COM_PREPARE, COM_QUERY, etc. Set from
first byte of the packet in do_command()
*/
enum enum_server_command command;
LEX *lex; // parse tree descriptor
/*
Points to the query associated with this statement. It's const, but
we need to declare it char * because all table handlers are written
in C and need to point to it.
*/
char *query;
uint32 query_length; // current query length
/*
List of items created in the parser for this query. Every item puts
itself to the list on creation (see Item::Item() for details))
*/
Item *free_list;
MEM_ROOT mem_root;
public:
/* We build without RTTI, so dynamic_cast can't be used. */
enum Type
{
STATEMENT,
PREPARED_STATEMENT
};
/*
This constructor is called when statement is a subobject of THD:
some variables are initialized in THD::init due to locking problems
*/
Statement();
Statement(THD *thd);
virtual ~Statement();
/* Assign execution context (note: not all members) of given stmt to self */
void set_statement(Statement *stmt);
/* return class type */
virtual Type type() const;
};
/*
Used to seek all existing statements in the connection
Deletes all statements in destructor.
*/
class Statement_map
{
public:
Statement_map();
int insert(Statement *statement)
{
int rc= my_hash_insert(&st_hash, (byte *) statement);
if (rc == 0)
last_found_statement= statement;
return rc;
}
Statement *find(ulong id)
{
if (last_found_statement == 0 || id != last_found_statement->id)
last_found_statement= (Statement *) hash_search(&st_hash, (byte *) &id,
sizeof(id));
return last_found_statement;
}
void erase(Statement *statement)
{
if (statement == last_found_statement)
last_found_statement= 0;
hash_delete(&st_hash, (byte *) statement);
}
~Statement_map()
{
hash_free(&st_hash);
}
private:
HASH st_hash;
Statement *last_found_statement;
};
/*
For each client connection we create a separate thread with THD serving as
a thread/connection descriptor
*/
class THD :public ilink
class THD :public ilink,
public Statement
{
public:
#ifdef EMBEDDED_LIBRARY
......@@ -446,23 +568,25 @@ public:
ulong extra_length;
#endif
NET net; // client connection descriptor
LEX main_lex;
LEX *lex; // parse tree descriptor
MEM_ROOT mem_root; // 1 command-life memory pool
MEM_ROOT con_root; // connection-life memory
MEM_ROOT warn_root; // For warnings and errors
Protocol *protocol; // Current protocol
Protocol_simple protocol_simple; // Normal protocol
Protocol_prep protocol_prep; // Binary protocol
HASH user_vars; // hash for user variables
TREE prepared_statements;
String packet; // dynamic buffer for network I/O
struct sockaddr_in remote; // client socket address
struct rand_struct rand; // used for authentication
struct system_variables variables; // Changeable local variables
pthread_mutex_t LOCK_delete; // Locked before thd is deleted
char *query; // Points to the current query,
/* all prepared statements and cursors of this connection */
Statement_map stmt_map;
/*
keeps THD state while it is used for active statement
Note, that double free_root() is safe, so we don't need to do any
special cleanup for it in THD destructor.
*/
Statement stmt_backup;
/*
A pointer to the stack frame of handle_one_connection(),
which is called first in the thread for handling a client
......@@ -502,7 +626,6 @@ public:
and are still in use by this thread
*/
TABLE *open_tables,*temporary_tables, *handler_tables, *derived_tables;
// TODO: document the variables below
MYSQL_LOCK *lock; /* Current locks */
MYSQL_LOCK *locked_tables; /* Tables locked with LOCK */
/*
......@@ -511,12 +634,10 @@ public:
chapter 'Miscellaneous functions', for functions GET_LOCK, RELEASE_LOCK.
*/
ULL *ull;
PREP_STMT *last_prepared_stmt;
#ifndef DBUG_OFF
uint dbug_sentry; // watch out for memory corruption
#endif
struct st_my_thread_var *mysys_var;
enum enum_server_command command;
uint32 server_id;
uint32 file_id; // for LOAD DATA INFILE
/*
......@@ -549,7 +670,6 @@ public:
free_root(&mem_root,MYF(MY_KEEP_PREALLOC));
}
} transaction;
Item *free_list, *handler_items;
Field *dupp_field;
#ifndef __WIN__
sigset_t signals,block_signals;
......@@ -580,18 +700,25 @@ public:
USER_CONN *user_connect;
CHARSET_INFO *db_charset;
List<TABLE> temporary_tables_should_be_free; // list of temporary tables
/*
FIXME: this, and some other variables like 'count_cuted_fields'
maybe should be statement/cursor local, that is, moved to Statement
class. With current implementation warnings produced in each prepared
statement/cursor settle here.
*/
List <MYSQL_ERROR> warn_list;
uint warn_count[(uint) MYSQL_ERROR::WARN_LEVEL_END];
uint total_warn_count;
ulong query_id, warn_id, version, options, thread_id, col_access;
ulong current_stmt_id;
ulong warn_id, version, options, thread_id, col_access;
/* Statement id is thread-wide. This counter is used to generate ids */
ulong statement_id_counter;
ulong rand_saved_seed1, rand_saved_seed2;
ulong row_count; // Row counter, mainly for errors and warnings
long dbug_thread_id;
pthread_t real_id;
uint current_tablenr,tmp_table;
uint server_status,open_options,system_thread;
uint32 query_length;
uint32 db_length;
uint select_number; //number of select (used for EXPLAIN)
/* variables.transaction_isolation is reset to this after each commit */
......@@ -604,9 +731,9 @@ public:
char scramble[SCRAMBLE_LENGTH+1];
bool slave_thread;
bool set_query_id,locked,some_tables_deleted;
bool locked, some_tables_deleted;
bool last_cuted_field;
bool no_errors, allow_sum_func, password, is_fatal_error;
bool no_errors, password, is_fatal_error;
bool query_start_used,last_insert_id_used,insert_id_used,rand_used;
bool in_lock_tables,global_read_lock;
bool query_error, bootstrap, cleanup_done;
......@@ -634,7 +761,6 @@ public:
void init(void);
void change_user(void);
void init_for_queries();
void cleanup(void);
bool store_globals();
#ifdef SIGNAL_WITH_VIO_CLOSE
......
......@@ -974,7 +974,6 @@ pthread_handler_decl(handle_one_connection,arg)
thd->proc_info=0;
thd->set_time();
thd->init_for_queries();
while (!net->error && net->vio != 0 && !thd->killed)
{
if (do_command(thd))
......@@ -1055,7 +1054,6 @@ extern "C" pthread_handler_decl(handle_bootstrap,arg)
thd->priv_user=thd->user=(char*) my_strdup("boot", MYF(MY_WME));
buff= (char*) thd->net.buff;
thd->init_for_queries();
while (fgets(buff, thd->net.max_packet, file))
{
uint length=(uint) strlen(buff);
......@@ -1221,13 +1219,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
{
NET *net= &thd->net;
bool error= 0;
DBUG_ENTER("dispatch_command");
thd->command=command;
/*
Commands which will always take a long time should be marked with
this so that they will not get logged to the slow query log
*/
DBUG_ENTER("dispatch_command");
thd->command=command;
thd->slow_command=FALSE;
thd->set_time();
VOID(pthread_mutex_lock(&LOCK_thread_count));
......
......@@ -71,127 +71,110 @@ Long data handling:
#include "sql_acl.h"
#include "sql_select.h" // for JOIN
#include <m_ctype.h> // for isspace()
#ifdef EMBEDDED_LIBRARY
/* include MYSQL_BIND headers */
#include <mysql.h>
#endif
#define IS_PARAM_NULL(pos, param_no) (pos[param_no/8] & (1 << (param_no & 7)))
const String my_null_string("NULL", 4, default_charset_info);
#define STMT_QUERY_LOG_LENGTH 8192
/******************************************************************************
Prepared_statement: statement which can contain placeholders
******************************************************************************/
#ifdef EMBEDDED_LIBRARY
#define SETUP_PARAM_FUNCTION(fn_name) \
static void fn_name(Item_param *param, uchar **pos, ulong data_len)
class Prepared_statement: public Statement
{
public:
THD *thd;
Item_param **param; /* array of all placeholders */
uint param_count;
uint last_errno;
char last_error[MYSQL_ERRMSG_SIZE];
bool error_in_prepare, long_data_used;
bool log_full_query;
#ifndef EMBEDDED_LIBRARY
bool (*setup_params)(Prepared_statement *st, uchar *pos, uchar *read_pos);
#else
#define SETUP_PARAM_FUNCTION(fn_name) \
static void fn_name(Item_param *param, uchar **pos)
bool (*setup_params_data)(Prepared_statement *st);
#endif
public:
Prepared_statement(THD *thd_arg);
virtual ~Prepared_statement();
virtual Statement::Type type() const;
};
String my_null_string("NULL", 4, default_charset_info);
/*
Find prepared statement in thd
/******************************************************************************
Implementation
******************************************************************************/
SYNOPSIS
find_prepared_statement()
thd Thread handler
stmt_id Statement id server specified to the client on prepare
RETURN VALUES
0 error. In this case the error is sent with my_error()
ptr Pointer to statement
*/
static PREP_STMT *find_prepared_statement(THD *thd, ulong stmt_id,
const char *when)
inline bool is_param_null(const uchar *pos, ulong param_no)
{
PREP_STMT *stmt;
DBUG_ENTER("find_prepared_statement");
DBUG_PRINT("enter",("stmt_id: %d", stmt_id));
if (thd->last_prepared_stmt && thd->last_prepared_stmt->stmt_id == stmt_id)
DBUG_RETURN(thd->last_prepared_stmt);
if ((stmt= (PREP_STMT*) tree_search(&thd->prepared_statements, &stmt_id,
(void*) 0)))
DBUG_RETURN (thd->last_prepared_stmt= stmt);
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), stmt_id, when);
DBUG_RETURN(0);
return pos[param_no/8] & (1 << (param_no & 7));
}
/*
Compare two prepared statements; Used to find a prepared statement
*/
enum { STMT_QUERY_LOG_LENGTH= 8192 };
int compare_prep_stmt(void *not_used, PREP_STMT *stmt, ulong *key)
{
return (stmt->stmt_id == *key) ? 0 : (stmt->stmt_id < *key) ? -1 : 1;
}
#ifdef EMBEDDED_LIBRARY
#define SETUP_PARAM_FUNCTION(fn_name) \
static void fn_name(Item_param *param, uchar **pos, ulong data_len)
#else
#define SETUP_PARAM_FUNCTION(fn_name) \
static void fn_name(Item_param *param, uchar **pos)
#endif
/*
Free prepared statement.
SYNOPSIS
standard tree_element_free function.
DESCRIPTION
We don't have to free the stmt itself as this was stored in the tree
and will be freed when the node is deleted
Seek prepared statement in statement map by id: returns zero if statement
was not found, pointer otherwise.
*/
void free_prep_stmt(PREP_STMT *stmt, TREE_FREE mode, void *not_used)
{
my_free((char *)stmt->param, MYF(MY_ALLOW_ZERO_PTR));
if (stmt->query)
stmt->query->free();
free_items(stmt->free_list);
free_root(&stmt->mem_root, MYF(0));
static Prepared_statement *
find_prepared_statement(THD *thd, ulong id, const char *where)
{
Statement *stmt= thd->stmt_map.find(id);
if (stmt == 0 || stmt->type() != Statement::PREPARED_STATEMENT)
{
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), id, where);
send_error(thd);
return 0;
}
return (Prepared_statement *) stmt;
}
/*
Send prepared stmt info to client after prepare
*/
#ifndef EMBEDDED_LIBRARY
static bool send_prep_stmt(PREP_STMT *stmt, uint columns)
static bool send_prep_stmt(Prepared_statement *stmt, uint columns)
{
NET *net=&stmt->thd->net;
NET *net= &stmt->thd->net;
char buff[9];
buff[0]= 0;
int4store(buff+1, stmt->stmt_id);
int4store(buff+1, stmt->id);
int2store(buff+5, columns);
int2store(buff+7, stmt->param_count);
/* This should be fixed to work with prepared statements
*/
/* This should be fixed to work with prepared statements */
return (my_net_write(net, buff, sizeof(buff)) || net_flush(net));
}
#else
static bool send_prep_stmt(PREP_STMT *stmt, uint columns __attribute__((unused)))
static bool send_prep_stmt(Prepared_statement *stmt,
uint columns __attribute__((unused)))
{
THD *thd= stmt->thd;
thd->client_stmt_id= stmt->stmt_id;
thd->client_stmt_id= stmt->id;
thd->client_param_count= stmt->param_count;
thd->net.last_errno= 0;
return 0;
}
#endif /*!EMBEDDED_LIBRAYR*/
/*
Send information about all item parameters
#endif /*!EMBEDDED_LIBRARY*/
TODO: Not yet ready
*/
static bool send_item_params(PREP_STMT *stmt)
{
#if 0
char buff[1];
buff[0]=0;
if (my_net_write(&stmt->thd->net, buff, sizeof(buff)))
return 1;
send_eof(stmt->thd);
#endif
return 0;
}
/*
Read the length of the parameter data and retun back to
......@@ -418,17 +401,20 @@ void setup_param_functions(Item_param *param, uchar param_type)
and if binary/update log is set, generate the valid query.
*/
static bool insert_params_withlog(PREP_STMT *stmt, uchar *pos, uchar *read_pos)
static bool insert_params_withlog(Prepared_statement *stmt, uchar *pos,
uchar *read_pos)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
List<Item> &params= stmt->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
DBUG_ENTER("insert_params_withlog");
String str, query, *res;
String str, query;
const String *res;
DBUG_ENTER("insert_params_withlog");
if (query.copy(*stmt->query))
if (query.copy(stmt->query, stmt->query_length, default_charset_info))
DBUG_RETURN(1);
ulong param_no= 0;
......@@ -438,10 +424,9 @@ static bool insert_params_withlog(PREP_STMT *stmt, uchar *pos, uchar *read_pos)
{
if (param->long_data_supplied)
res= param->query_val_str(&str);
else
{
if (IS_PARAM_NULL(pos,param_no))
if (is_param_null(pos,param_no))
{
param->maybe_null= param->null_value= 1;
res= &my_null_string;
......@@ -461,23 +446,26 @@ static bool insert_params_withlog(PREP_STMT *stmt, uchar *pos, uchar *read_pos)
}
if (alloc_query(thd, (char *)query.ptr(), query.length()+1))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
static bool insert_params(PREP_STMT *stmt, uchar *pos, uchar *read_pos)
static bool insert_params(Prepared_statement *stmt, uchar *pos,
uchar *read_pos)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
List<Item> &params= stmt->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
DBUG_ENTER("insert_params");
ulong param_no= 0;
DBUG_ENTER("insert_params");
while ((param= (Item_param *)param_iterator++))
{
if (!param->long_data_supplied)
{
if (IS_PARAM_NULL(pos,param_no))
if (is_param_null(pos,param_no))
param->maybe_null= param->null_value= 1;
else
{
......@@ -490,17 +478,18 @@ static bool insert_params(PREP_STMT *stmt, uchar *pos, uchar *read_pos)
DBUG_RETURN(0);
}
static bool setup_params_data(PREP_STMT *stmt)
static bool setup_params_data(Prepared_statement *stmt)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
List<Item> &params= stmt->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
DBUG_ENTER("setup_params_data");
uchar *pos=(uchar*) thd->net.read_pos+1+MYSQL_STMT_HEADER; //skip header
uchar *pos= (uchar*) stmt->thd->net.read_pos + 1 +
MYSQL_STMT_HEADER; //skip header
uchar *read_pos= pos+(stmt->param_count+7) / 8; //skip null bits
DBUG_ENTER("setup_params_data");
if (*read_pos++) //types supplied / first execute
{
/*
......@@ -518,6 +507,91 @@ static bool setup_params_data(PREP_STMT *stmt)
DBUG_RETURN(0);
}
#else
bool setup_params_data(Prepared_statement *stmt)
{
List<Item> &params= stmt->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
MYSQL_BIND *client_param= stmt->thd->client_params;
DBUG_ENTER("setup_params_data");
for (;(param= (Item_param *)param_iterator++); client_param++)
{
setup_param_functions(param, client_param->buffer_type);
if (!param->long_data_supplied)
{
if (*client_param->is_null)
param->maybe_null= param->null_value= 1;
else
{
uchar *buff= (uchar*)client_param->buffer;
param->maybe_null= param->null_value= 0;
param->setup_param_func(param,&buff,
client_param->length ?
*client_param->length :
client_param->buffer_length);
}
}
}
DBUG_RETURN(0);
}
bool setup_params_data_withlog(Prepared_statement *stmt)
{
THD *thd= stmt->thd;
List<Item> &params= stmt->lex->param_list;
List_iterator<Item> param_iterator(params);
Item_param *param;
MYSQL_BIND *client_param= thd->client_params;
String str, query;
const String *res;
DBUG_ENTER("setup_params_data_withlog");
if (query.copy(stmt->query, stmt->query_length, default_charset_info))
DBUG_RETURN(1);
uint32 length= 0;
for (;(param= (Item_param *)param_iterator++); client_param++)
{
setup_param_functions(param, client_param->buffer_type);
if (param->long_data_supplied)
res= param->query_val_str(&str);
else
{
if (*client_param->is_null)
{
param->maybe_null= param->null_value= 1;
res= &my_null_string;
}
else
{
uchar *buff= (uchar*)client_param->buffer;
param->maybe_null= param->null_value= 0;
param->setup_param_func(param,&buff,
client_param->length ?
*client_param->length :
client_param->buffer_length);
res= param->query_val_str(&str);
}
}
if (query.replace(param->pos_in_query+length, 1, *res))
DBUG_RETURN(1);
length+= res->length()-1;
}
if (alloc_query(thd, (char *) query.ptr(), query.length()+1))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
#endif /*!EMBEDDED_LIBRARY*/
/*
......@@ -526,7 +600,7 @@ static bool setup_params_data(PREP_STMT *stmt)
- fields count
*/
static bool mysql_test_insert_fields(PREP_STMT *stmt,
static bool mysql_test_insert_fields(Prepared_statement *stmt,
TABLE_LIST *table_list,
List<Item> &fields,
List<List_item> &values_list)
......@@ -535,18 +609,18 @@ static bool mysql_test_insert_fields(PREP_STMT *stmt,
TABLE *table;
List_iterator_fast<List_item> its(values_list);
List_item *values;
DBUG_ENTER("mysql_test_insert_fields");
#ifndef NO_EMBEDDED_ACCESS_CHECKS
my_bool update=(thd->lex->value_list.elements ? UPDATE_ACL : 0);
ulong privilege= (thd->lex->duplicates == DUP_REPLACE ?
my_bool update=(stmt->lex->value_list.elements ? UPDATE_ACL : 0);
ulong privilege= (stmt->lex->duplicates == DUP_REPLACE ?
INSERT_ACL | DELETE_ACL : INSERT_ACL | update);
if (check_access(thd,privilege,table_list->db,
&table_list->grant.privilege,0,0) ||
(grant_option && check_grant(thd,privilege,table_list,0,0)))
DBUG_RETURN(1);
#endif
#endif
if (open_and_lock_tables(thd, table_list))
DBUG_RETURN(1);
table= table_list->table;
......@@ -574,7 +648,7 @@ static bool mysql_test_insert_fields(PREP_STMT *stmt,
}
}
}
if (send_prep_stmt(stmt, 0) || send_item_params(stmt))
if (send_prep_stmt(stmt, 0))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
......@@ -589,13 +663,14 @@ static bool mysql_test_insert_fields(PREP_STMT *stmt,
and return no fields information back to client.
*/
static bool mysql_test_upd_fields(PREP_STMT *stmt, TABLE_LIST *table_list,
static bool mysql_test_upd_fields(Prepared_statement *stmt,
TABLE_LIST *table_list,
List<Item> &fields, List<Item> &values,
COND *conds)
{
THD *thd= stmt->thd;
DBUG_ENTER("mysql_test_upd_fields");
DBUG_ENTER("mysql_test_upd_fields");
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (check_access(thd,UPDATE_ACL,table_list->db,
&table_list->grant.privilege,0,0) ||
......@@ -613,7 +688,7 @@ static bool mysql_test_upd_fields(PREP_STMT *stmt, TABLE_LIST *table_list,
Currently return only column list info only, and we are not
sending any info on where clause.
*/
if (send_prep_stmt(stmt, 0) || send_item_params(stmt))
if (send_prep_stmt(stmt, 0))
DBUG_RETURN(1);
DBUG_RETURN(0);
}
......@@ -630,7 +705,9 @@ static bool mysql_test_upd_fields(PREP_STMT *stmt, TABLE_LIST *table_list,
And send column list fields info back to client.
*/
static bool mysql_test_select_fields(PREP_STMT *stmt, TABLE_LIST *tables,
static bool mysql_test_select_fields(Prepared_statement *stmt,
TABLE_LIST *tables,
uint wild_num,
List<Item> &fields, COND *conds,
uint og_num, ORDER *order, ORDER *group,
......@@ -640,8 +717,9 @@ static bool mysql_test_select_fields(PREP_STMT *stmt, TABLE_LIST *tables,
SELECT_LEX *select_lex)
{
THD *thd= stmt->thd;
LEX *lex= &thd->main_lex;
select_result *result= thd->lex->result;
LEX *lex= stmt->lex;
select_result *result= lex->result;
DBUG_ENTER("mysql_test_select_fields");
#ifndef NO_EMBEDDED_ACCESS_CHECKS
......@@ -663,12 +741,12 @@ static bool mysql_test_select_fields(PREP_STMT *stmt, TABLE_LIST *tables,
if (lex->describe)
{
if (send_prep_stmt(stmt, 0) || send_item_params(stmt))
if (send_prep_stmt(stmt, 0))
DBUG_RETURN(1);
}
}
else
{
fix_tables_pointers(thd->lex->all_selects_list);
fix_tables_pointers(lex->all_selects_list);
if (!result && !(result= new select_send()))
{
send_error(thd, ER_OUT_OF_RESOURCES);
......@@ -683,11 +761,11 @@ static bool mysql_test_select_fields(PREP_STMT *stmt, TABLE_LIST *tables,
select_lex, unit))
DBUG_RETURN(1);
if (send_prep_stmt(stmt, fields.elements) ||
thd->protocol_simple.send_fields(&fields, 0) ||
thd->protocol_simple.send_fields(&fields, 0)
#ifndef EMBEDDED_LIBRARY
net_flush(&thd->net) ||
|| net_flush(&thd->net)
#endif
send_item_params(stmt))
)
DBUG_RETURN(1);
join->cleanup();
}
......@@ -699,19 +777,18 @@ static bool mysql_test_select_fields(PREP_STMT *stmt, TABLE_LIST *tables,
Send the prepare query results back to client
*/
static bool send_prepare_results(PREP_STMT *stmt)
static bool send_prepare_results(Prepared_statement *stmt)
{
THD *thd= stmt->thd;
LEX *lex= &thd->main_lex;
enum enum_sql_command sql_command= thd->lex->sql_command;
LEX *lex= stmt->lex;
enum enum_sql_command sql_command= lex->sql_command;
DBUG_ENTER("send_prepare_results");
DBUG_PRINT("enter",("command: %d, param_count: %ld",
sql_command, lex->param_count));
/* Setup prepared stmt */
stmt->param_count= lex->param_count;
stmt->free_list= thd->free_list; // Save items used in stmt
thd->free_list= 0;
SELECT_LEX *select_lex= &lex->select_lex;
TABLE_LIST *tables=(TABLE_LIST*) select_lex->table_list.first;
......@@ -768,57 +845,14 @@ abort:
DBUG_RETURN(1);
}
/*
Parse the prepare query
*/
static bool parse_prepare_query(PREP_STMT *stmt,
char *packet, uint length)
{
bool error= 1;
THD *thd= stmt->thd;
DBUG_ENTER("parse_prepare_query");
mysql_log.write(thd,COM_PREPARE,"%s",packet);
mysql_init_query(thd);
LEX *lex=lex_start(thd, (uchar*) packet, length);
lex->safe_to_cache_query= 0;
thd->lex->param_count= 0;
if (!yyparse((void *)thd) && !thd->is_fatal_error)
error= send_prepare_results(stmt);
lex_end(lex);
DBUG_RETURN(error);
}
/*
Initialize parameter items in statement
*/
static bool init_param_items(PREP_STMT *stmt)
static bool init_param_items(Prepared_statement *stmt)
{
THD *thd= stmt->thd;
List<Item> &params= thd->lex->param_list;
Item_param **to;
uint32 length= thd->query_length;
stmt->lex= thd->main_lex;
if (mysql_bin_log.is_open() || mysql_update_log.is_open())
{
stmt->log_full_query= 1;
#ifndef EMBEDDED_LIBRARY
stmt->setup_params= insert_params_withlog;
#else
stmt->setup_params_data= setup_params_data_withlog;
#endif
}
else
#ifndef EMBEDDED_LIBRARY
stmt->setup_params= insert_params; // not fully qualified query
#else
stmt->setup_params_data= setup_params_data;
#endif
if (!stmt->param_count)
stmt->param= (Item_param **)0;
else
......@@ -828,44 +862,12 @@ static bool init_param_items(PREP_STMT *stmt)
MYF(MY_WME))))
return 1;
if (stmt->log_full_query)
{
length= thd->query_length+(stmt->param_count*2)+1;
if ( length < STMT_QUERY_LOG_LENGTH )
length= STMT_QUERY_LOG_LENGTH;
}
List_iterator<Item> param_iterator(params);
List_iterator<Item> param_iterator(stmt->lex->param_list);
while ((*(to++)= (Item_param *)param_iterator++));
}
stmt->query= new String(length);
stmt->query->copy(thd->query, thd->query_length, default_charset_info);
return 0;
}
/*
Initialize stmt execution
*/
static void init_stmt_execute(PREP_STMT *stmt)
{
THD *thd= stmt->thd;
TABLE_LIST *tables= (TABLE_LIST*) thd->lex->select_lex.table_list.first;
/*
TODO: When the new table structure is ready, then have a status bit
to indicate the table is altered, and re-do the setup_*
and open the tables back.
*/
for (; tables ; tables= tables->next)
tables->table= 0; //safety - nasty init
if (!(stmt->log_full_query && stmt->param_count))
{
thd->query= stmt->query->c_ptr();
thd->query_length= stmt->query->length();
}
}
/*
Parse the query and send the total number of parameters
......@@ -877,58 +879,73 @@ static void init_stmt_execute(PREP_STMT *stmt)
If parameter markers are found in the query, then store
the information using Item_param along with maintaining a
list in lex->param_list, so that a fast and direct
retrieveal can be made without going through all field
retrieval can be made without going through all field
items.
*/
bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length)
{
MEM_ROOT thd_root= thd->mem_root;
PREP_STMT stmt;
SELECT_LEX *sl;
LEX *lex;
Prepared_statement *stmt= new Prepared_statement(thd);
DBUG_ENTER("mysql_stmt_prepare");
bzero((char*) &stmt, sizeof(stmt));
stmt.stmt_id= ++thd->current_stmt_id;
init_sql_alloc(&stmt.mem_root,
thd->variables.query_alloc_block_size,
thd->variables.query_prealloc_size);
stmt.thd= thd;
stmt.thd->mem_root= stmt.mem_root;
if (stmt == 0)
DBUG_RETURN(0);
if (thd->stmt_map.insert(stmt))
goto insert_stmt_err;
if (alloc_query(stmt.thd, packet, packet_length))
goto err;
thd->stmt_backup.set_statement(thd);
thd->set_statement(stmt);
if (parse_prepare_query(&stmt, thd->query, thd->query_length))
goto err;
if (alloc_query(thd, packet, packet_length))
goto alloc_query_err;
mysql_log.write(thd, COM_PREPARE, "%s", packet);
lex= lex_start(thd, (uchar *) thd->query, thd->query_length);
mysql_init_query(thd);
lex->safe_to_cache_query= 0;
lex->param_count= 0;
if (yyparse((void *)thd) || thd->is_fatal_error || send_prepare_results(stmt))
goto yyparse_err;
lex_end(lex);
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(),WAIT_PRIOR);
// save WHERE clause pointers to avoid damaging they by optimisation
for (sl= thd->lex->all_selects_list;
for (SELECT_LEX *sl= thd->lex->all_selects_list;
sl;
sl= sl->next_select_in_list())
{
sl->prep_where= sl->where;
}
if (init_param_items(&stmt))
goto err;
stmt->set_statement(thd);
thd->set_statement(&thd->stmt_backup);
if (init_param_items(stmt))
goto init_param_err;
stmt->command= COM_EXECUTE; // set it only once here
stmt.mem_root= stmt.thd->mem_root;
tree_insert(&thd->prepared_statements, (void *)&stmt, 0, (void *)0);
thd->mem_root= thd_root; // restore main mem_root
DBUG_RETURN(0);
err:
stmt.mem_root= stmt.thd->mem_root;
free_prep_stmt(&stmt, free_free, (void*) 0);
thd->mem_root= thd_root; // restore main mem_root
yyparse_err:
lex_end(lex);
stmt->set_statement(thd);
thd->set_statement(&thd->stmt_backup);
init_param_err:
alloc_query_err:
/* Statement map deletes statement on erase */
thd->stmt_map.erase(stmt);
DBUG_RETURN(1);
insert_stmt_err:
delete stmt;
DBUG_RETURN(1);
}
......@@ -936,7 +953,7 @@ err:
/*
Executes previously prepared query
If there is any parameters(thd->param_count), then replace
If there is any parameters (stmt->param_count), then replace
markers with the data supplied from client, and then
execute the query
*/
......@@ -944,15 +961,12 @@ err:
void mysql_stmt_execute(THD *thd, char *packet)
{
ulong stmt_id= uint4korr(packet);
PREP_STMT *stmt;
SELECT_LEX *sl;
DBUG_ENTER("mysql_stmt_execute");
Prepared_statement *stmt;
if (!(stmt=find_prepared_statement(thd, stmt_id, "execute")))
{
send_error(thd);
DBUG_ENTER("mysql_stmt_execute");
if (!(stmt= find_prepared_statement(thd, stmt_id, "execute")))
DBUG_VOID_RETURN;
}
/* Check if we got an error when sending long data */
if (stmt->error_in_prepare)
......@@ -961,10 +975,28 @@ void mysql_stmt_execute(THD *thd, char *packet)
DBUG_VOID_RETURN;
}
LEX thd_lex= thd->main_lex;
thd->main_lex= stmt->lex;
for (sl= stmt->lex.all_selects_list;
/*
XXX: while thd->query_id is incremented for each command, stmt->query_id
holds query_id of prepare stage. Keeping old query_id seems to be more
natural, but differs from the way prepared statements work in 4.1:
*/
/* stmt->query_id= thd->query_id; */
thd->stmt_backup.set_statement(thd);
thd->set_statement(stmt);
/*
To make sure that all runtime data is stored in its own memory root and
does not interfere with data possibly present in thd->mem_root.
This root is cleaned up in the end of execution.
FIXME: to be replaced with more efficient approach, and verified why we
can not use thd->mem_root safely.
*/
init_sql_alloc(&thd->mem_root,
thd->variables.query_alloc_block_size,
thd->variables.query_prealloc_size);
for (SELECT_LEX *sl= stmt->lex->all_selects_list;
sl;
sl= sl->next_select_in_list())
{
......@@ -975,7 +1007,16 @@ void mysql_stmt_execute(THD *thd, char *packet)
sl->where= sl->prep_where->copy_andor_structure(thd);
DBUG_ASSERT(sl->join == 0);
}
init_stmt_execute(stmt);
/*
TODO: When the new table structure is ready, then have a status bit
to indicate the table is altered, and re-do the setup_*
and open the tables back.
*/
for (TABLE_LIST *tables= (TABLE_LIST*) stmt->lex->select_lex.table_list.first;
tables;
tables= tables->next)
tables->table= 0; // safety - nasty init
#ifndef EMBEDDED_LIBRARY
if (stmt->param_count && setup_params_data(stmt))
......@@ -1001,7 +1042,8 @@ void mysql_stmt_execute(THD *thd, char *packet)
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(), WAIT_PRIOR);
thd->main_lex= thd_lex;
free_root(&thd->mem_root, MYF(0));
thd->set_statement(&thd->stmt_backup);
DBUG_VOID_RETURN;
}
......@@ -1023,14 +1065,12 @@ void mysql_stmt_execute(THD *thd, char *packet)
void mysql_stmt_reset(THD *thd, char *packet)
{
ulong stmt_id= uint4korr(packet);
PREP_STMT *stmt;
Prepared_statement *stmt;
DBUG_ENTER("mysql_stmt_reset");
if (!(stmt= find_prepared_statement(thd, stmt_id, "reset")))
{
send_error(thd);
DBUG_VOID_RETURN;
}
stmt->error_in_prepare= 0;
Item_param *item= *stmt->param, *end= item + stmt->param_count;
......@@ -1053,15 +1093,15 @@ void mysql_stmt_reset(THD *thd, char *packet)
void mysql_stmt_free(THD *thd, char *packet)
{
ulong stmt_id= uint4korr(packet);
Prepared_statement *stmt;
DBUG_ENTER("mysql_stmt_free");
if (!find_prepared_statement(thd, stmt_id, "close"))
{
send_error(thd); // Not seen by the client
if (!(stmt= find_prepared_statement(thd, stmt_id, "close")))
DBUG_VOID_RETURN;
}
tree_delete(&thd->prepared_statements, (void*) &stmt_id, (void *)0);
thd->last_prepared_stmt= (PREP_STMT *)0;
/* Statement map deletes statement on erase */
thd->stmt_map.erase(stmt);
DBUG_VOID_RETURN;
}
......@@ -1087,7 +1127,8 @@ void mysql_stmt_free(THD *thd, char *packet)
void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length)
{
PREP_STMT *stmt;
Prepared_statement *stmt;
DBUG_ENTER("mysql_stmt_get_longdata");
#ifndef EMBEDDED_LIBRARY
......@@ -1103,14 +1144,7 @@ void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length)
uint param_number= uint2korr(pos+4);
if (!(stmt=find_prepared_statement(thd, stmt_id, "get_longdata")))
{
/*
There is a chance that the client will never see this as
it doesn't expect an answer from this call...
*/
send_error(thd);
DBUG_VOID_RETURN;
}
#ifndef EMBEDDED_LIBRARY
if (param_number >= stmt->param_count)
......@@ -1134,3 +1168,46 @@ void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length)
DBUG_VOID_RETURN;
}
Prepared_statement::Prepared_statement(THD *thd_arg)
:Statement(thd_arg),
thd(thd_arg),
param(0),
param_count(0),
last_errno(0),
error_in_prepare(0),
long_data_used(0),
log_full_query(0)
{
*last_error= '\0';
if (mysql_bin_log.is_open())
{
log_full_query= 1;
#ifndef EMBEDDED_LIBRARY
setup_params= insert_params_withlog;
#else
setup_params_data= setup_params_data_withlog;
#endif
}
else
#ifndef EMBEDDED_LIBRARY
setup_params= insert_params; // not fully qualified query
#else
setup_params_data= setup_params_data;
#endif
}
Prepared_statement::~Prepared_statement()
{
my_free((char *) param, MYF(MY_ALLOW_ZERO_PTR));
free_items(free_list);
}
Statement::Type Prepared_statement::type() const
{
return PREPARED_STATEMENT;
}
......@@ -7350,7 +7350,7 @@ static void test_fetch_column()
rc = mysql_query(mysql, "insert into test_column(c2) values('venu'),('mysql')");
myquery(rc);
stmt = mysql_prepare(mysql,"select * from test_column",50);
stmt = mysql_prepare(mysql,"select * from test_column order by c2 desc", 50);
mystmt_init(stmt);
bind[0].buffer_type= MYSQL_TYPE_LONG;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment