Commit 33724e68 authored by unknown's avatar unknown

Merge bk-internal.mysql.com:/home/bk/mysql-4.1

into neptunus.(none):/home/msvensson/mysql/mysql-4.1


sql/ha_ndbcluster.cc:
  Auto merged
parents 457693a1 41503b63
...@@ -51,6 +51,7 @@ georg@lmy002.wdf.sap.corp ...@@ -51,6 +51,7 @@ georg@lmy002.wdf.sap.corp
gerberb@ou800.zenez.com gerberb@ou800.zenez.com
gluh@gluh.(none) gluh@gluh.(none)
gluh@gluh.mysql.r18.ru gluh@gluh.mysql.r18.ru
gluh@mysql.com
gordon@zero.local.lan gordon@zero.local.lan
greg@gcw.ath.cx greg@gcw.ath.cx
greg@mysql.com greg@mysql.com
......
...@@ -566,6 +566,7 @@ static void print_result() ...@@ -566,6 +566,7 @@ static void print_result()
my_bool found_error=0; my_bool found_error=0;
res = mysql_use_result(sock); res = mysql_use_result(sock);
prev[0] = '\0'; prev[0] = '\0';
for (i = 0; (row = mysql_fetch_row(res)); i++) for (i = 0; (row = mysql_fetch_row(res)); i++)
{ {
...@@ -595,7 +596,7 @@ static void print_result() ...@@ -595,7 +596,7 @@ static void print_result()
putchar('\n'); putchar('\n');
} }
if (found_error && opt_auto_repair && what_to_do != DO_REPAIR && if (found_error && opt_auto_repair && what_to_do != DO_REPAIR &&
(!opt_fast || strcmp(row[3],"OK"))) !opt_fast)
insert_dynamic(&tables4repair, prev); insert_dynamic(&tables4repair, prev);
mysql_free_result(res); mysql_free_result(res);
} }
......
...@@ -65,6 +65,8 @@ log. */ ...@@ -65,6 +65,8 @@ log. */
#define OS_FILE_OVERWRITE 53 #define OS_FILE_OVERWRITE 53
#define OS_FILE_OPEN_RAW 54 #define OS_FILE_OPEN_RAW 54
#define OS_FILE_CREATE_PATH 55 #define OS_FILE_CREATE_PATH 55
#define OS_FILE_OPEN_RETRY 56 /* for os_file_create() on
the first ibdata file */
#define OS_FILE_READ_ONLY 333 #define OS_FILE_READ_ONLY 333
#define OS_FILE_READ_WRITE 444 #define OS_FILE_READ_WRITE 444
......
...@@ -402,8 +402,6 @@ os_file_lock( ...@@ -402,8 +402,6 @@ os_file_lock(
"InnoDB: using the same InnoDB data or log files.\n"); "InnoDB: using the same InnoDB data or log files.\n");
} }
close(fd);
return(-1); return(-1);
} }
...@@ -978,6 +976,7 @@ os_file_create_simple( ...@@ -978,6 +976,7 @@ os_file_create_simple(
} else if (access_type == OS_FILE_READ_WRITE } else if (access_type == OS_FILE_READ_WRITE
&& os_file_lock(file, name)) { && os_file_lock(file, name)) {
*success = FALSE; *success = FALSE;
close(file);
file = -1; file = -1;
#endif #endif
} else { } else {
...@@ -1090,6 +1089,7 @@ os_file_create_simple_no_error_handling( ...@@ -1090,6 +1089,7 @@ os_file_create_simple_no_error_handling(
} else if (access_type == OS_FILE_READ_WRITE } else if (access_type == OS_FILE_READ_WRITE
&& os_file_lock(file, name)) { && os_file_lock(file, name)) {
*success = FALSE; *success = FALSE;
close(file);
file = -1; file = -1;
#endif #endif
} else { } else {
...@@ -1141,7 +1141,8 @@ os_file_create( ...@@ -1141,7 +1141,8 @@ os_file_create(
if (create_mode == OS_FILE_OPEN_RAW) { if (create_mode == OS_FILE_OPEN_RAW) {
create_flag = OPEN_EXISTING; create_flag = OPEN_EXISTING;
share_mode = FILE_SHARE_WRITE; share_mode = FILE_SHARE_WRITE;
} else if (create_mode == OS_FILE_OPEN) { } else if (create_mode == OS_FILE_OPEN
|| create_mode == OS_FILE_OPEN_RETRY) {
create_flag = OPEN_EXISTING; create_flag = OPEN_EXISTING;
} else if (create_mode == OS_FILE_CREATE) { } else if (create_mode == OS_FILE_CREATE) {
create_flag = CREATE_NEW; create_flag = CREATE_NEW;
...@@ -1232,7 +1233,8 @@ os_file_create( ...@@ -1232,7 +1233,8 @@ os_file_create(
try_again: try_again:
ut_a(name); ut_a(name);
if (create_mode == OS_FILE_OPEN || create_mode == OS_FILE_OPEN_RAW) { if (create_mode == OS_FILE_OPEN || create_mode == OS_FILE_OPEN_RAW
|| create_mode == OS_FILE_OPEN_RETRY) {
mode_str = "OPEN"; mode_str = "OPEN";
create_flag = O_RDWR; create_flag = O_RDWR;
} else if (create_mode == OS_FILE_CREATE) { } else if (create_mode == OS_FILE_CREATE) {
...@@ -1305,6 +1307,23 @@ os_file_create( ...@@ -1305,6 +1307,23 @@ os_file_create(
} else if (create_mode != OS_FILE_OPEN_RAW } else if (create_mode != OS_FILE_OPEN_RAW
&& os_file_lock(file, name)) { && os_file_lock(file, name)) {
*success = FALSE; *success = FALSE;
if (create_mode == OS_FILE_OPEN_RETRY) {
int i;
ut_print_timestamp(stderr);
fputs(" InnoDB: Retrying to lock the first data file\n",
stderr);
for (i = 0; i < 100; i++) {
os_thread_sleep(1000000);
if (!os_file_lock(file, name)) {
*success = TRUE;
return(file);
}
}
ut_print_timestamp(stderr);
fputs(" InnoDB: Unable to open the first data file\n",
stderr);
}
close(file);
file = -1; file = -1;
#endif #endif
} else { } else {
......
...@@ -789,6 +789,11 @@ open_or_create_data_files( ...@@ -789,6 +789,11 @@ open_or_create_data_files(
files[i] = os_file_create( files[i] = os_file_create(
name, OS_FILE_OPEN_RAW, OS_FILE_NORMAL, name, OS_FILE_OPEN_RAW, OS_FILE_NORMAL,
OS_DATA_FILE, &ret); OS_DATA_FILE, &ret);
} else if (i == 0) {
files[i] = os_file_create(
name, OS_FILE_OPEN_RETRY,
OS_FILE_NORMAL,
OS_DATA_FILE, &ret);
} else { } else {
files[i] = os_file_create( files[i] = os_file_create(
name, OS_FILE_OPEN, OS_FILE_NORMAL, name, OS_FILE_OPEN, OS_FILE_NORMAL,
......
...@@ -32,21 +32,21 @@ endif ...@@ -32,21 +32,21 @@ endif
benchdir_root= $(prefix) benchdir_root= $(prefix)
testdir = $(benchdir_root)/mysql-test testdir = $(benchdir_root)/mysql-test
EXTRA_SCRIPTS = mysql-test-run.sh install_test_db.sh EXTRA_SCRIPTS = mysql-test-run.sh mysql-test-run.pl install_test_db.sh
EXTRA_DIST = $(EXTRA_SCRIPTS) EXTRA_DIST = $(EXTRA_SCRIPTS)
test_SCRIPTS = mysql-test-run install_test_db test_SCRIPTS = mysql-test-run install_test_db
test_DATA = std_data/client-key.pem std_data/client-cert.pem std_data/cacert.pem test_DATA = std_data/client-key.pem std_data/client-cert.pem std_data/cacert.pem
CLEANFILES = $(test_SCRIPTS) $(test_DATA) CLEANFILES = $(test_SCRIPTS) $(test_DATA)
INCLUDES = -I$(srcdir)/../include -I../include -I.. INCLUDES = -I$(srcdir)/../include -I../include -I..
EXTRA_PROGRAMS = mysql_test_run_new EXTRA_PROGRAMS = mysql_test_run_new
noinst_HEADERS = my_manage.h noinst_HEADERS = my_manage.h
mysql_test_run_new_SOURCES= mysql_test_run_new.c my_manage.c my_create_tables.c mysql_test_run_new_SOURCES= mysql_test_run_new.c my_manage.c my_create_tables.c
dist-hook: dist-hook:
mkdir -p $(distdir)/t $(distdir)/r $(distdir)/include \ mkdir -p $(distdir)/t $(distdir)/r $(distdir)/include \
$(distdir)/std_data $(distdir)/std_data $(distdir)/lib
$(INSTALL_DATA) $(srcdir)/t/*.test $(srcdir)/t/*.opt $(srcdir)/t/*.sh $(srcdir)/t/*.slave-mi $(distdir)/t $(INSTALL_DATA) $(srcdir)/t/*.test $(srcdir)/t/*.opt $(srcdir)/t/*.sh $(srcdir)/t/*.slave-mi $(distdir)/t
$(INSTALL_DATA) $(srcdir)/include/*.inc $(distdir)/include $(INSTALL_DATA) $(srcdir)/include/*.inc $(distdir)/include
$(INSTALL_DATA) $(srcdir)/r/*.result $(srcdir)/r/*.result.es $(srcdir)/r/*.require $(distdir)/r $(INSTALL_DATA) $(srcdir)/r/*.result $(srcdir)/r/*.result.es $(srcdir)/r/*.require $(distdir)/r
...@@ -54,6 +54,8 @@ dist-hook: ...@@ -54,6 +54,8 @@ dist-hook:
$(INSTALL_DATA) $(srcdir)/std_data/*.dat $(srcdir)/std_data/*.000001 $(distdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/*.dat $(srcdir)/std_data/*.000001 $(distdir)/std_data
$(INSTALL_DATA) $(srcdir)/std_data/des_key_file $(distdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/des_key_file $(distdir)/std_data
$(INSTALL_DATA) $(srcdir)/std_data/*.pem $(distdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/*.pem $(distdir)/std_data
$(INSTALL_DATA) $(srcdir)/lib/init_db.sql $(distdir)/lib
$(INSTALL_DATA) $(srcdir)/lib/*.pl $(distdir)/lib
install-data-local: install-data-local:
...@@ -61,7 +63,8 @@ install-data-local: ...@@ -61,7 +63,8 @@ install-data-local:
$(DESTDIR)$(testdir)/t \ $(DESTDIR)$(testdir)/t \
$(DESTDIR)$(testdir)/r \ $(DESTDIR)$(testdir)/r \
$(DESTDIR)$(testdir)/include \ $(DESTDIR)$(testdir)/include \
$(DESTDIR)$(testdir)/std_data $(DESTDIR)$(testdir)/std_data \
$(DESTDIR)$(testdir)/lib
$(INSTALL_DATA) $(srcdir)/README $(DESTDIR)$(testdir) $(INSTALL_DATA) $(srcdir)/README $(DESTDIR)$(testdir)
$(INSTALL_DATA) $(srcdir)/t/*.test $(DESTDIR)$(testdir)/t $(INSTALL_DATA) $(srcdir)/t/*.test $(DESTDIR)$(testdir)/t
$(INSTALL_DATA) $(srcdir)/t/*.opt $(DESTDIR)$(testdir)/t $(INSTALL_DATA) $(srcdir)/t/*.opt $(DESTDIR)$(testdir)/t
...@@ -75,6 +78,8 @@ install-data-local: ...@@ -75,6 +78,8 @@ install-data-local:
$(INSTALL_DATA) $(srcdir)/std_data/des_key_file $(DESTDIR)$(testdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/des_key_file $(DESTDIR)$(testdir)/std_data
$(INSTALL_DATA) $(srcdir)/std_data/Moscow_leap $(DESTDIR)$(testdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/Moscow_leap $(DESTDIR)$(testdir)/std_data
$(INSTALL_DATA) $(srcdir)/std_data/*.pem $(DESTDIR)$(testdir)/std_data $(INSTALL_DATA) $(srcdir)/std_data/*.pem $(DESTDIR)$(testdir)/std_data
$(INSTALL_DATA) $(srcdir)/lib/init_db.sql $(DESTDIR)$(testdir)/lib
$(INSTALL_DATA) $(srcdir)/lib/*.pl $(DESTDIR)$(testdir)/lib
std_data/%.pem: std_data/%.pem:
@CP@ $(top_srcdir)/SSL/$(@F) $(srcdir)/std_data @CP@ $(top_srcdir)/SSL/$(@F) $(srcdir)/std_data
......
...@@ -635,7 +635,6 @@ sub command_line_setup () { ...@@ -635,7 +635,6 @@ sub command_line_setup () {
{ {
mtr_error("Can't use --extern with --embedded-server"); mtr_error("Can't use --extern with --embedded-server");
} }
$opt_result_ext= ".es";
} }
# FIXME don't understand what this is # FIXME don't understand what this is
......
...@@ -607,3 +607,33 @@ primary key (a)) ...@@ -607,3 +607,33 @@ primary key (a))
engine=ndb engine=ndb
max_rows=1; max_rows=1;
drop table t1; drop table t1;
create table t1
(counter int(64) NOT NULL auto_increment,
datavalue char(40) default 'XXXX',
primary key (counter)
) ENGINE=ndbcluster;
insert into t1 (datavalue) values ('newval');
insert into t1 (datavalue) values ('newval');
select * from t1 order by counter;
counter datavalue
1 newval
2 newval
insert into t1 (datavalue) select datavalue from t1 where counter < 100;
select * from t1 order by counter;
counter datavalue
1 newval
2 newval
3 newval
4 newval
insert into t1 (datavalue) select datavalue from t1 where counter < 100;
select * from t1 order by counter;
counter datavalue
1 newval
2 newval
3 newval
4 newval
35 newval
36 newval
37 newval
38 newval
drop table t1;
...@@ -89,4 +89,5 @@ a b ...@@ -89,4 +89,5 @@ a b
2 row 2 2 row 2
3 row 3 3 row 3
0 0
drop database rewrite;
drop table t1; drop table t1;
...@@ -175,3 +175,7 @@ set @v1=null, @v2=1, @v3=1.1, @v4=now(); ...@@ -175,3 +175,7 @@ set @v1=null, @v2=1, @v3=1.1, @v4=now();
select coercibility(@v1),coercibility(@v2),coercibility(@v3),coercibility(@v4); select coercibility(@v1),coercibility(@v2),coercibility(@v3),coercibility(@v4);
coercibility(@v1) coercibility(@v2) coercibility(@v3) coercibility(@v4) coercibility(@v1) coercibility(@v2) coercibility(@v3) coercibility(@v4)
2 2 2 2 2 2 2 2
set session @honk=99;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '@honk=99' at line 1
set one_shot @honk=99;
ERROR HY000: The SET ONE_SHOT syntax is reserved for purposes internal to the MySQL server
...@@ -577,3 +577,28 @@ create table t1 ...@@ -577,3 +577,28 @@ create table t1
engine=ndb engine=ndb
max_rows=1; max_rows=1;
drop table t1; drop table t1;
#
# Test auto_increment
#
connect (con1,localhost,,,test);
connect (con2,localhost,,,test);
create table t1
(counter int(64) NOT NULL auto_increment,
datavalue char(40) default 'XXXX',
primary key (counter)
) ENGINE=ndbcluster;
connection con1;
insert into t1 (datavalue) values ('newval');
insert into t1 (datavalue) values ('newval');
select * from t1 order by counter;
insert into t1 (datavalue) select datavalue from t1 where counter < 100;
select * from t1 order by counter;
connection con2;
insert into t1 (datavalue) select datavalue from t1 where counter < 100;
select * from t1 order by counter;
drop table t1;
...@@ -73,5 +73,8 @@ connection slave; ...@@ -73,5 +73,8 @@ connection slave;
# The empty line last comes from the end line field in the file # The empty line last comes from the end line field in the file
select * from rewrite.t1; select * from rewrite.t1;
drop database rewrite;
connection master; connection master;
drop table t1; drop table t1;
...@@ -111,3 +111,11 @@ select FIELD( @var,'1it','Hit') as my_column; ...@@ -111,3 +111,11 @@ select FIELD( @var,'1it','Hit') as my_column;
select @v, coercibility(@v); select @v, coercibility(@v);
set @v1=null, @v2=1, @v3=1.1, @v4=now(); set @v1=null, @v2=1, @v3=1.1, @v4=now();
select coercibility(@v1),coercibility(@v2),coercibility(@v3),coercibility(@v4); select coercibility(@v1),coercibility(@v2),coercibility(@v3),coercibility(@v4);
#
# Bug #9286 SESSION/GLOBAL should be disallowed for user variables
#
--error 1064
set session @honk=99;
--error 1105
set one_shot @honk=99;
...@@ -722,26 +722,28 @@ Remark: Returns a new TupleId to the application. ...@@ -722,26 +722,28 @@ Remark: Returns a new TupleId to the application.
Uint64 Uint64
Ndb::getAutoIncrementValue(const char* aTableName, Uint32 cacheSize) Ndb::getAutoIncrementValue(const char* aTableName, Uint32 cacheSize)
{ {
DEBUG_TRACE("getAutoIncrementValue"); DBUG_ENTER("getAutoIncrementValue");
const char * internalTableName = internalizeTableName(aTableName); const char * internalTableName = internalizeTableName(aTableName);
Ndb_local_table_info *info= Ndb_local_table_info *info=
theDictionary->get_local_table_info(internalTableName, false); theDictionary->get_local_table_info(internalTableName, false);
if (info == 0) if (info == 0)
return ~0; DBUG_RETURN(~0);
const NdbTableImpl *table= info->m_table_impl; const NdbTableImpl *table= info->m_table_impl;
Uint64 tupleId = getTupleIdFromNdb(table->m_tableId, cacheSize); Uint64 tupleId = getTupleIdFromNdb(table->m_tableId, cacheSize);
return tupleId; DBUG_PRINT("info", ("value %u", tupleId));
DBUG_RETURN(tupleId);
} }
Uint64 Uint64
Ndb::getAutoIncrementValue(const NdbDictionary::Table * aTable, Uint32 cacheSize) Ndb::getAutoIncrementValue(const NdbDictionary::Table * aTable, Uint32 cacheSize)
{ {
DEBUG_TRACE("getAutoIncrementValue"); DBUG_ENTER("getAutoIncrementValue");
if (aTable == 0) if (aTable == 0)
return ~0; DBUG_RETURN(~0);
const NdbTableImpl* table = & NdbTableImpl::getImpl(*aTable); const NdbTableImpl* table = & NdbTableImpl::getImpl(*aTable);
Uint64 tupleId = getTupleIdFromNdb(table->m_tableId, cacheSize); Uint64 tupleId = getTupleIdFromNdb(table->m_tableId, cacheSize);
return tupleId; DBUG_PRINT("info", ("value %u", tupleId));
DBUG_RETURN(tupleId);
} }
Uint64 Uint64
...@@ -756,39 +758,45 @@ Ndb::getTupleIdFromNdb(const char* aTableName, Uint32 cacheSize) ...@@ -756,39 +758,45 @@ Ndb::getTupleIdFromNdb(const char* aTableName, Uint32 cacheSize)
Uint64 Uint64
Ndb::getTupleIdFromNdb(Uint32 aTableId, Uint32 cacheSize) Ndb::getTupleIdFromNdb(Uint32 aTableId, Uint32 cacheSize)
{ {
DBUG_ENTER("getTupleIdFromNdb");
if ( theFirstTupleId[aTableId] != theLastTupleId[aTableId] ) if ( theFirstTupleId[aTableId] != theLastTupleId[aTableId] )
{ {
theFirstTupleId[aTableId]++; theFirstTupleId[aTableId]++;
return theFirstTupleId[aTableId]; DBUG_PRINT("info", ("next cached value %u", theFirstTupleId[aTableId]));
DBUG_RETURN(theFirstTupleId[aTableId]);
} }
else // theFirstTupleId == theLastTupleId else // theFirstTupleId == theLastTupleId
{ {
return opTupleIdOnNdb(aTableId, cacheSize, 0); DBUG_PRINT("info",("reading %u values from database",
(cacheSize == 0) ? 1 : cacheSize));
DBUG_RETURN(opTupleIdOnNdb(aTableId, (cacheSize == 0) ? 1 : cacheSize, 0));
} }
} }
Uint64 Uint64
Ndb::readAutoIncrementValue(const char* aTableName) Ndb::readAutoIncrementValue(const char* aTableName)
{ {
DEBUG_TRACE("readtAutoIncrementValue"); DBUG_ENTER("readtAutoIncrementValue");
const NdbTableImpl* table = theDictionary->getTable(aTableName); const NdbTableImpl* table = theDictionary->getTable(aTableName);
if (table == 0) { if (table == 0) {
theError= theDictionary->getNdbError(); theError= theDictionary->getNdbError();
return ~0; DBUG_RETURN(~0);
} }
Uint64 tupleId = readTupleIdFromNdb(table->m_tableId); Uint64 tupleId = readTupleIdFromNdb(table->m_tableId);
return tupleId; DBUG_PRINT("info", ("value %u", tupleId));
DBUG_RETURN(tupleId);
} }
Uint64 Uint64
Ndb::readAutoIncrementValue(const NdbDictionary::Table * aTable) Ndb::readAutoIncrementValue(const NdbDictionary::Table * aTable)
{ {
DEBUG_TRACE("readtAutoIncrementValue"); DBUG_ENTER("readtAutoIncrementValue");
if (aTable == 0) if (aTable == 0)
return ~0; DBUG_RETURN(~0);
const NdbTableImpl* table = & NdbTableImpl::getImpl(*aTable); const NdbTableImpl* table = & NdbTableImpl::getImpl(*aTable);
Uint64 tupleId = readTupleIdFromNdb(table->m_tableId); Uint64 tupleId = readTupleIdFromNdb(table->m_tableId);
return tupleId; DBUG_PRINT("info", ("value %u", tupleId));
DBUG_RETURN(tupleId);
} }
Uint64 Uint64
......
...@@ -1501,7 +1501,7 @@ NdbDictInterface::createOrAlterTable(Ndb & ndb, ...@@ -1501,7 +1501,7 @@ NdbDictInterface::createOrAlterTable(Ndb & ndb,
if (col->m_autoIncrement) { if (col->m_autoIncrement) {
if (haveAutoIncrement) { if (haveAutoIncrement) {
m_error.code = 4335; m_error.code = 4335;
return -1; DBUG_RETURN(-1);
} }
haveAutoIncrement = true; haveAutoIncrement = true;
autoIncrementValue = col->m_autoIncrementInitialValue; autoIncrementValue = col->m_autoIncrementInitialValue;
...@@ -1622,7 +1622,7 @@ NdbDictInterface::createOrAlterTable(Ndb & ndb, ...@@ -1622,7 +1622,7 @@ NdbDictInterface::createOrAlterTable(Ndb & ndb,
ret= createTable(&tSignal, ptr); ret= createTable(&tSignal, ptr);
if (ret) if (ret)
return ret; DBUG_RETURN(ret);
if (haveAutoIncrement) { if (haveAutoIncrement) {
if (!ndb.setAutoIncrementValue(impl.m_externalName.c_str(), if (!ndb.setAutoIncrementValue(impl.m_externalName.c_str(),
......
...@@ -4914,7 +4914,7 @@ void Field_datetime::sql_type(String &res) const ...@@ -4914,7 +4914,7 @@ void Field_datetime::sql_type(String &res) const
int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) int Field_string::store(const char *from,uint length,CHARSET_INFO *cs)
{ {
int error= 0; int error= 0, well_formed_error;
uint32 not_used; uint32 not_used;
char buff[80]; char buff[80];
String tmpstr(buff,sizeof(buff), &my_charset_bin); String tmpstr(buff,sizeof(buff), &my_charset_bin);
...@@ -4942,7 +4942,7 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs) ...@@ -4942,7 +4942,7 @@ int Field_string::store(const char *from,uint length,CHARSET_INFO *cs)
from,from+length, from,from+length,
field_length/ field_length/
field_charset->mbmaxlen, field_charset->mbmaxlen,
&error); &well_formed_error);
memcpy(ptr,from,copy_length); memcpy(ptr,from,copy_length);
if (copy_length < field_length) // Append spaces if shorter if (copy_length < field_length) // Append spaces if shorter
field_charset->cset->fill(field_charset,ptr+copy_length, field_charset->cset->fill(field_charset,ptr+copy_length,
...@@ -5545,7 +5545,7 @@ void Field_blob::put_length(char *pos, uint32 length) ...@@ -5545,7 +5545,7 @@ void Field_blob::put_length(char *pos, uint32 length)
int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs)
{ {
int error= 0; int error= 0, well_formed_error;
if (!length) if (!length)
{ {
bzero(ptr,Field_blob::pack_length()); bzero(ptr,Field_blob::pack_length());
...@@ -5580,7 +5580,7 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs) ...@@ -5580,7 +5580,7 @@ int Field_blob::store(const char *from,uint length,CHARSET_INFO *cs)
from,from + from,from +
min(length, copy_length), min(length, copy_length),
copy_length, copy_length,
&error); &well_formed_error);
if (copy_length < length) if (copy_length < length)
error= 1; error= 1;
Field_blob::store_length(copy_length); Field_blob::store_length(copy_length);
......
...@@ -2946,7 +2946,11 @@ void ha_ndbcluster::start_bulk_insert(ha_rows rows) ...@@ -2946,7 +2946,11 @@ void ha_ndbcluster::start_bulk_insert(ha_rows rows)
DBUG_PRINT("enter", ("rows: %d", (int)rows)); DBUG_PRINT("enter", ("rows: %d", (int)rows));
m_rows_inserted= 0; m_rows_inserted= 0;
m_rows_to_insert= rows; if (rows == 0)
/* We don't know how many will be inserted, guess */
m_rows_to_insert= m_autoincrement_prefetch;
else
m_rows_to_insert= rows;
/* /*
Calculate how many rows that should be inserted Calculate how many rows that should be inserted
...@@ -3955,6 +3959,10 @@ longlong ha_ndbcluster::get_auto_increment() ...@@ -3955,6 +3959,10 @@ longlong ha_ndbcluster::get_auto_increment()
DBUG_ENTER("get_auto_increment"); DBUG_ENTER("get_auto_increment");
DBUG_PRINT("enter", ("m_tabname: %s", m_tabname)); DBUG_PRINT("enter", ("m_tabname: %s", m_tabname));
Ndb *ndb= get_ndb(); Ndb *ndb= get_ndb();
if (m_rows_inserted > m_rows_to_insert)
/* We guessed too low */
m_rows_to_insert+= m_autoincrement_prefetch;
int cache_size= int cache_size=
(m_rows_to_insert - m_rows_inserted < m_autoincrement_prefetch) ? (m_rows_to_insert - m_rows_inserted < m_autoincrement_prefetch) ?
m_rows_to_insert - m_rows_inserted m_rows_to_insert - m_rows_inserted
......
...@@ -1995,6 +1995,11 @@ typedef struct key_field_t { // Used when finding key fields ...@@ -1995,6 +1995,11 @@ typedef struct key_field_t { // Used when finding key fields
uint level; uint level;
uint optimize; uint optimize;
bool eq_func; bool eq_func;
/*
If true, the condition this struct represents will not be satisfied
when val IS NULL.
*/
bool null_rejecting;
} KEY_FIELD; } KEY_FIELD;
/* Values in optimize */ /* Values in optimize */
...@@ -2011,6 +2016,12 @@ typedef struct key_field_t { // Used when finding key fields ...@@ -2011,6 +2016,12 @@ typedef struct key_field_t { // Used when finding key fields
that are internally transformed to something like: that are internally transformed to something like:
SELECT * FROM t1 WHERE t1.key=outer_ref_field or t1.key IS NULL SELECT * FROM t1 WHERE t1.key=outer_ref_field or t1.key IS NULL
KEY_FIELD::null_rejecting is processed as follows:
result has null_rejecting=true if it is set for both ORed references.
for example:
(t2.key = t1.field OR t2.key = t1.field) -> null_rejecting=true
(t2.key = t1.field OR t2.key <=> t1.field) -> null_rejecting=false
*/ */
static KEY_FIELD * static KEY_FIELD *
...@@ -2044,6 +2055,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, ...@@ -2044,6 +2055,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
KEY_OPTIMIZE_EXISTS) | KEY_OPTIMIZE_EXISTS) |
((old->optimize | new_fields->optimize) & ((old->optimize | new_fields->optimize) &
KEY_OPTIMIZE_REF_OR_NULL)); KEY_OPTIMIZE_REF_OR_NULL));
old->null_rejecting= old->null_rejecting &&
new_fields->null_rejecting;
} }
} }
else if (old->eq_func && new_fields->eq_func && else if (old->eq_func && new_fields->eq_func &&
...@@ -2055,6 +2068,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, ...@@ -2055,6 +2068,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
KEY_OPTIMIZE_EXISTS) | KEY_OPTIMIZE_EXISTS) |
((old->optimize | new_fields->optimize) & ((old->optimize | new_fields->optimize) &
KEY_OPTIMIZE_REF_OR_NULL)); KEY_OPTIMIZE_REF_OR_NULL));
old->null_rejecting= old->null_rejecting &&
new_fields->null_rejecting;
} }
else if (old->eq_func && new_fields->eq_func && else if (old->eq_func && new_fields->eq_func &&
(old->val->is_null() || new_fields->val->is_null())) (old->val->is_null() || new_fields->val->is_null()))
...@@ -2065,6 +2080,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, ...@@ -2065,6 +2080,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
/* Remember the NOT NULL value */ /* Remember the NOT NULL value */
if (old->val->is_null()) if (old->val->is_null())
old->val= new_fields->val; old->val= new_fields->val;
/* The referred expression can be NULL: */
old->null_rejecting= false;
} }
else else
{ {
...@@ -2119,7 +2136,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, ...@@ -2119,7 +2136,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
*/ */
static void static void
add_key_field(KEY_FIELD **key_fields,uint and_level, COND *cond, add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond,
Field *field,bool eq_func,Item **value, uint num_values, Field *field,bool eq_func,Item **value, uint num_values,
table_map usable_tables) table_map usable_tables)
{ {
...@@ -2221,12 +2238,29 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, COND *cond, ...@@ -2221,12 +2238,29 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, COND *cond,
(*key_fields)->val= *value; (*key_fields)->val= *value;
(*key_fields)->level= and_level; (*key_fields)->level= and_level;
(*key_fields)->optimize= exists_optimize; (*key_fields)->optimize= exists_optimize;
/*
If the condition has form "tbl.keypart = othertbl.field" and
othertbl.field can be NULL, there will be no matches if othertbl.field
has NULL value.
*/
(*key_fields)->null_rejecting= (cond->functype() == Item_func::EQ_FUNC) &&
((*value)->type() == Item::FIELD_ITEM) &&
((Item_field*)*value)->field->maybe_null();
(*key_fields)++; (*key_fields)++;
} }
/*
SYNOPSIS
add_key_fields()
key_fields Add KEY_FIELD entries to this array (and move the
pointer)
and_level AND-level (a value that is different for every n-way
AND operation)
cond Condition to analyze
usable_tables Value to pass to add_key_field
*/
static void static void
add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level, add_key_fields(KEY_FIELD **key_fields,uint *and_level,
COND *cond, table_map usable_tables) COND *cond, table_map usable_tables)
{ {
if (cond->type() == Item_func::COND_ITEM) if (cond->type() == Item_func::COND_ITEM)
...@@ -2238,20 +2272,20 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level, ...@@ -2238,20 +2272,20 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
{ {
Item *item; Item *item;
while ((item=li++)) while ((item=li++))
add_key_fields(stat,key_fields,and_level,item,usable_tables); add_key_fields(key_fields,and_level,item,usable_tables);
for (; org_key_fields != *key_fields ; org_key_fields++) for (; org_key_fields != *key_fields ; org_key_fields++)
org_key_fields->level= *and_level; org_key_fields->level= *and_level;
} }
else else
{ {
(*and_level)++; (*and_level)++;
add_key_fields(stat,key_fields,and_level,li++,usable_tables); add_key_fields(key_fields,and_level,li++,usable_tables);
Item *item; Item *item;
while ((item=li++)) while ((item=li++))
{ {
KEY_FIELD *start_key_fields= *key_fields; KEY_FIELD *start_key_fields= *key_fields;
(*and_level)++; (*and_level)++;
add_key_fields(stat,key_fields,and_level,item,usable_tables); add_key_fields(key_fields,and_level,item,usable_tables);
*key_fields=merge_key_fields(org_key_fields,start_key_fields, *key_fields=merge_key_fields(org_key_fields,start_key_fields,
*key_fields,++(*and_level)); *key_fields,++(*and_level));
} }
...@@ -2363,6 +2397,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field) ...@@ -2363,6 +2397,7 @@ add_key_part(DYNAMIC_ARRAY *keyuse_array,KEY_FIELD *key_field)
keyuse.keypart_map= (key_part_map) 1 << part; keyuse.keypart_map= (key_part_map) 1 << part;
keyuse.used_tables=key_field->val->used_tables(); keyuse.used_tables=key_field->val->used_tables();
keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL; keyuse.optimize= key_field->optimize & KEY_OPTIMIZE_REF_OR_NULL;
keyuse.null_rejecting= key_field->null_rejecting;
VOID(insert_dynamic(keyuse_array,(gptr) &keyuse)); VOID(insert_dynamic(keyuse_array,(gptr) &keyuse));
} }
} }
...@@ -2456,8 +2491,22 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) ...@@ -2456,8 +2491,22 @@ sort_keyuse(KEYUSE *a,KEYUSE *b)
/* /*
Update keyuse array with all possible keys we can use to fetch rows Update keyuse array with all possible keys we can use to fetch rows
join_tab is a array in tablenr_order
stat is a reference array in 'prefered' order. SYNOPSIS
update_ref_and_keys()
thd
keyuse OUT Put here ordered array of KEYUSE structures
join_tab Array in tablenr_order
tables Number of tables in join
cond WHERE condition (note that the function analyzes
join_tab[i]->on_expr too)
normal_tables tables not inner w.r.t some outer join (ones for which
we can make ref access based the WHERE clause)
select_lex current SELECT
RETURN
0 - OK
1 - Out of memory.
*/ */
static bool static bool
...@@ -2478,7 +2527,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, ...@@ -2478,7 +2527,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
return TRUE; return TRUE;
if (cond) if (cond)
{ {
add_key_fields(join_tab,&end,&and_level,cond,normal_tables); add_key_fields(&end,&and_level,cond,normal_tables);
for (; field != end ; field++) for (; field != end ; field++)
{ {
add_key_part(keyuse,field); add_key_part(keyuse,field);
...@@ -2492,7 +2541,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, ...@@ -2492,7 +2541,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
{ {
if (join_tab[i].on_expr) if (join_tab[i].on_expr)
{ {
add_key_fields(join_tab,&end,&and_level,join_tab[i].on_expr, add_key_fields(&end,&and_level,join_tab[i].on_expr,
join_tab[i].table->map); join_tab[i].table->map);
} }
} }
...@@ -3232,6 +3281,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, ...@@ -3232,6 +3281,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
} }
j->ref.key_buff2=j->ref.key_buff+ALIGN_SIZE(length); j->ref.key_buff2=j->ref.key_buff+ALIGN_SIZE(length);
j->ref.key_err=1; j->ref.key_err=1;
j->ref.null_rejecting= 0;
keyuse=org_keyuse; keyuse=org_keyuse;
store_key **ref_key= j->ref.key_copy; store_key **ref_key= j->ref.key_copy;
...@@ -3256,6 +3306,8 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, ...@@ -3256,6 +3306,8 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
uint maybe_null= test(keyinfo->key_part[i].null_bit); uint maybe_null= test(keyinfo->key_part[i].null_bit);
j->ref.items[i]=keyuse->val; // Save for cond removal j->ref.items[i]=keyuse->val; // Save for cond removal
if (keyuse->null_rejecting)
j->ref.null_rejecting |= 1 << i;
keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables; keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables;
if (!keyuse->used_tables && if (!keyuse->used_tables &&
!(join->select_options & SELECT_DESCRIBE)) !(join->select_options & SELECT_DESCRIBE))
...@@ -3410,12 +3462,98 @@ make_simple_join(JOIN *join,TABLE *tmp_table) ...@@ -3410,12 +3462,98 @@ make_simple_join(JOIN *join,TABLE *tmp_table)
} }
inline void add_cond_and_fix(Item **e1, Item *e2)
{
if (*e1)
{
Item *res;
if ((res= new Item_cond_and(*e1, e2)))
{
*e1= res;
res->quick_fix_field();
}
}
else
*e1= e2;
}
/*
Add to join_tab->select_cond[i] "table.field IS NOT NULL" conditions we've
inferred from ref/eq_ref access performed.
SYNOPSIS
add_not_null_conds()
join Join to process
NOTES
This function is a part of "Early NULL-values filtering for ref access"
optimization.
Example of this optimization:
For query SELECT * FROM t1,t2 WHERE t2.key=t1.field
and plan " any-access(t1), ref(t2.key=t1.field) "
add "t1.field IS NOT NULL" to t1's table condition.
Description of the optimization:
We look through equalities choosen to perform ref/eq_ref access,
pick equalities that have form "tbl.part_of_key = othertbl.field"
(where othertbl is a non-const table and othertbl.field may be NULL)
and add them to conditions on correspoding tables (othertbl in this
example).
This optimization doesn't affect the choices that ref, range, or join
optimizer make. This was intentional because this was added after 4.1
was GA.
Implementation overview
1. update_ref_and_keys() accumulates info about null-rejecting
predicates in in KEY_FIELD::null_rejecting
1.1 add_key_part saves these to KEYUSE.
2. create_ref_for_key copies them to TABLE_REF.
3. add_not_null_conds adds "x IS NOT NULL" to join_tab->select_cond of
appropiate JOIN_TAB members.
*/
static void add_not_null_conds(JOIN *join)
{
DBUG_ENTER("add_not_null_conds");
for (uint i=join->const_tables ; i < join->tables ; i++)
{
JOIN_TAB *tab=join->join_tab+i;
if ((tab->type == JT_REF || tab->type == JT_REF_OR_NULL) &&
!tab->table->maybe_null)
{
for (uint keypart= 0; keypart < tab->ref.key_parts; keypart++)
{
if (tab->ref.null_rejecting & (1 << keypart))
{
Item *item= tab->ref.items[keypart];
DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
Item_field *not_null_item= (Item_field*)item;
JOIN_TAB *referred_tab= not_null_item->field->table->reginfo.join_tab;
Item_func_isnotnull *notnull;
if (!(notnull= new Item_func_isnotnull(not_null_item)))
DBUG_VOID_RETURN;
notnull->quick_fix_field();
DBUG_EXECUTE("where",print_where(notnull,
referred_tab->table->table_name););
add_cond_and_fix(&referred_tab->select_cond, notnull);
}
}
}
}
DBUG_VOID_RETURN;
}
static bool static bool
make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
{ {
DBUG_ENTER("make_join_select"); DBUG_ENTER("make_join_select");
if (select) if (select)
{ {
add_not_null_conds(join);
table_map used_tables; table_map used_tables;
if (join->tables > 1) if (join->tables > 1)
cond->update_used_tables(); // Tablenr may have changed cond->update_used_tables(); // Tablenr may have changed
...@@ -3472,13 +3610,14 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) ...@@ -3472,13 +3610,14 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
} }
if (tmp) if (tmp)
{ {
DBUG_EXECUTE("where",print_where(tmp,tab->table->table_name););
SQL_SELECT *sel=tab->select=(SQL_SELECT*) SQL_SELECT *sel=tab->select=(SQL_SELECT*)
join->thd->memdup((gptr) select, sizeof(SQL_SELECT)); join->thd->memdup((gptr) select, sizeof(SQL_SELECT));
if (!sel) if (!sel)
DBUG_RETURN(1); // End of memory DBUG_RETURN(1); // End of memory
tab->select_cond=sel->cond=tmp; add_cond_and_fix(&tab->select_cond, tmp);
sel->cond= tab->select_cond;
sel->head=tab->table; sel->head=tab->table;
DBUG_EXECUTE("where",print_where(tmp,tab->table->table_name););
if (tab->quick) if (tab->quick)
{ {
/* Use quick key read if it's a constant and it's not used /* Use quick key read if it's a constant and it's not used
......
...@@ -31,6 +31,11 @@ typedef struct keyuse_t { ...@@ -31,6 +31,11 @@ typedef struct keyuse_t {
uint key, keypart, optimize; uint key, keypart, optimize;
key_part_map keypart_map; key_part_map keypart_map;
ha_rows ref_table_rows; ha_rows ref_table_rows;
/*
If true, the comparison this value was created from will not be
satisfied if val has NULL 'value'.
*/
bool null_rejecting;
} KEYUSE; } KEYUSE;
class store_key; class store_key;
...@@ -45,6 +50,11 @@ typedef struct st_table_ref ...@@ -45,6 +50,11 @@ typedef struct st_table_ref
byte *key_buff2; // key_buff+key_length byte *key_buff2; // key_buff+key_length
store_key **key_copy; // store_key **key_copy; //
Item **items; // val()'s for each keypart Item **items; // val()'s for each keypart
/*
(null_rejecting & (1<<i)) means the condition is '=' and no matching
rows will be produced if items[i] IS NULL (see add_not_null_conds())
*/
key_part_map null_rejecting;
table_map depend_map; // Table depends on these tables. table_map depend_map; // Table depends on these tables.
byte *null_ref_key; // null byte position in the key_buf. byte *null_ref_key; // null byte position in the key_buf.
// used for REF_OR_NULL optimization. // used for REF_OR_NULL optimization.
......
...@@ -5361,17 +5361,26 @@ opt_option: ...@@ -5361,17 +5361,26 @@ opt_option:
| OPTION {}; | OPTION {};
option_value_list: option_value_list:
option_type option_value option_value_ext
| option_value_list ',' option_type option_value; | option_value_list ',' option_value_ext;
option_type: option_value_ext:
/* empty */ {} option_type_ext sys_option_value {}
| option_type option_value {}
;
option_type_ext:
option_type {}
| GLOBAL_SYM { Lex->option_type= OPT_GLOBAL; } | GLOBAL_SYM { Lex->option_type= OPT_GLOBAL; }
| LOCAL_SYM { Lex->option_type= OPT_SESSION; } | LOCAL_SYM { Lex->option_type= OPT_SESSION; }
| SESSION_SYM { Lex->option_type= OPT_SESSION; } | SESSION_SYM { Lex->option_type= OPT_SESSION; }
| ONE_SHOT_SYM { Lex->option_type= OPT_SESSION; Lex->one_shot_set= 1; }
; ;
option_type:
/* empty */ {}
| ONE_SHOT_SYM { Lex->option_type= OPT_SESSION; Lex->one_shot_set= 1; }
;
opt_var_type: opt_var_type:
/* empty */ { $$=OPT_SESSION; } /* empty */ { $$=OPT_SESSION; }
| GLOBAL_SYM { $$=OPT_GLOBAL; } | GLOBAL_SYM { $$=OPT_GLOBAL; }
...@@ -5386,34 +5395,37 @@ opt_var_ident_type: ...@@ -5386,34 +5395,37 @@ opt_var_ident_type:
| SESSION_SYM '.' { $$=OPT_SESSION; } | SESSION_SYM '.' { $$=OPT_SESSION; }
; ;
sys_option_value:
internal_variable_name equal set_expr_or_default
{
LEX *lex=Lex;
lex->var_list.push_back(new set_var(lex->option_type, $1.var,
&$1.base_name, $3));
}
| TRANSACTION_SYM ISOLATION LEVEL_SYM isolation_types
{
LEX *lex=Lex;
LEX_STRING tmp;
tmp.str=0;
tmp.length=0;
lex->var_list.push_back(new set_var(lex->option_type,
find_sys_var("tx_isolation"),
&tmp,
new Item_int((int32) $4)));
}
;
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))); 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(lex->option_type, $1.var,
&$1.base_name, $3));
}
| '@' '@' 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;
lex->var_list.push_back(new set_var((enum_var_type) $3, $4.var, lex->var_list.push_back(new set_var((enum_var_type) $3, $4.var,
&$4.base_name, $6)); &$4.base_name, $6));
} }
| TRANSACTION_SYM ISOLATION LEVEL_SYM isolation_types
{
LEX *lex=Lex;
LEX_STRING tmp;
tmp.str=0;
tmp.length=0;
lex->var_list.push_back(new set_var(lex->option_type,
find_sys_var("tx_isolation"),
&tmp,
new Item_int((int32) $4)));
}
| charset old_or_new_charset_name_or_default | charset old_or_new_charset_name_or_default
{ {
THD *thd= YYTHD; THD *thd= YYTHD;
......
...@@ -276,7 +276,7 @@ uint my_well_formed_len_mb(CHARSET_INFO *cs, const char *b, const char *e, ...@@ -276,7 +276,7 @@ uint my_well_formed_len_mb(CHARSET_INFO *cs, const char *b, const char *e,
if ((mblen= cs->cset->mb_wc(cs, &wc, (uchar*) b, (uchar*) e)) <= 0) if ((mblen= cs->cset->mb_wc(cs, &wc, (uchar*) b, (uchar*) e)) <= 0)
{ {
*error= 1; *error= b < e ? 1 : 0;
break; break;
} }
b+= mblen; b+= mblen;
......
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