Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
MariaDB
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
MariaDB
Commits
edf59e54
Commit
edf59e54
authored
Mar 16, 2005
by
heikki@hundin.mysql.fi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Many files:
InnoDB true VARCHAR
parent
cb4553b7
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
654 additions
and
391 deletions
+654
-391
innobase/include/data0type.h
innobase/include/data0type.h
+20
-1
innobase/include/data0type.ic
innobase/include/data0type.ic
+13
-0
innobase/include/que0que.h
innobase/include/que0que.h
+2
-1
innobase/include/row0mysql.h
innobase/include/row0mysql.h
+61
-41
innobase/include/row0mysql.ic
innobase/include/row0mysql.ic
+0
-146
innobase/row/row0ins.c
innobase/row/row0ins.c
+4
-0
innobase/row/row0mysql.c
innobase/row/row0mysql.c
+230
-22
innobase/row/row0sel.c
innobase/row/row0sel.c
+72
-59
innobase/trx/trx0trx.c
innobase/trx/trx0trx.c
+1
-1
sql/ha_innodb.cc
sql/ha_innodb.cc
+238
-112
sql/ha_innodb.h
sql/ha_innodb.h
+13
-8
No files found.
innobase/include/data0type.h
View file @
edf59e54
...
@@ -24,7 +24,11 @@ extern dtype_t* dtype_binary;
...
@@ -24,7 +24,11 @@ extern dtype_t* dtype_binary;
/*-------------------------------------------*/
/*-------------------------------------------*/
/* The 'MAIN TYPE' of a column */
/* The 'MAIN TYPE' of a column */
#define DATA_VARCHAR 1
/* character varying of the
#define DATA_VARCHAR 1
/* character varying of the
latin1_swedish_ci charset-collation */
latin1_swedish_ci charset-collation; note
that the MySQL format for this, DATA_BINARY,
DATA_VARMYSQL, is also affected by whether the
'precise type' contains
DATA_MYSQL_TRUE_VARCHAR */
#define DATA_CHAR 2
/* fixed length character of the
#define DATA_CHAR 2
/* fixed length character of the
latin1_swedish_ci charset-collation */
latin1_swedish_ci charset-collation */
#define DATA_FIXBINARY 3
/* binary string of fixed length */
#define DATA_FIXBINARY 3
/* binary string of fixed length */
...
@@ -102,6 +106,8 @@ columns, and for them the precise type is usually not used at all.
...
@@ -102,6 +106,8 @@ columns, and for them the precise type is usually not used at all.
#define DATA_MYSQL_TYPE_MASK 255
/* AND with this mask to extract the MySQL
#define DATA_MYSQL_TYPE_MASK 255
/* AND with this mask to extract the MySQL
type from the precise type */
type from the precise type */
#define DATA_MYSQL_TRUE_VARCHAR 15
/* MySQL type code for the >= 5.0.3
format true VARCHAR */
/* Precise data types for system columns and the length of those columns;
/* Precise data types for system columns and the length of those columns;
NOTE: the values must run from 0 up in the order given! All codes must
NOTE: the values must run from 0 up in the order given! All codes must
...
@@ -134,6 +140,10 @@ be less than 256 */
...
@@ -134,6 +140,10 @@ be less than 256 */
In earlier versions this was set for some
In earlier versions this was set for some
BLOB columns.
BLOB columns.
*/
*/
#define DATA_LONG_TRUE_VARCHAR 4096
/* this is ORed to the precise data
type when the column is true VARCHAR where
MySQL uses 2 bytes to store the data len;
for shorter VARCHARs MySQL uses only 1 byte */
/*-------------------------------------------*/
/*-------------------------------------------*/
/* This many bytes we need to store the type information affecting the
/* This many bytes we need to store the type information affecting the
...
@@ -144,6 +154,15 @@ SQL null*/
...
@@ -144,6 +154,15 @@ SQL null*/
store the charset-collation number; one byte is left unused, though */
store the charset-collation number; one byte is left unused, though */
#define DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE 6
#define DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE 6
/*************************************************************************
Gets the MySQL type code from a dtype. */
UNIV_INLINE
ulint
dtype_get_mysql_type
(
/*=================*/
/* out: MySQL type code; this is NOT an InnoDB
type code! */
dtype_t
*
type
);
/* in: type struct */
/*************************************************************************
/*************************************************************************
Determine how many bytes the first n characters of the given string occupy.
Determine how many bytes the first n characters of the given string occupy.
If the string is shorter than n characters, returns the number of bytes
If the string is shorter than n characters, returns the number of bytes
...
...
innobase/include/data0type.ic
View file @
edf59e54
...
@@ -32,6 +32,19 @@ dtype_get_charset_coll(
...
@@ -32,6 +32,19 @@ dtype_get_charset_coll(
return((prtype >> 16) & 0xFFUL);
return((prtype >> 16) & 0xFFUL);
}
}
/*************************************************************************
Gets the MySQL type code from a dtype. */
UNIV_INLINE
ulint
dtype_get_mysql_type(
/*=================*/
/* out: MySQL type code; this is NOT an InnoDB
type code! */
dtype_t* type) /* in: type struct */
{
return(type->prtype & 0xFFUL);
}
/*************************************************************************
/*************************************************************************
Sets the mbminlen and mbmaxlen members of a data type structure. */
Sets the mbminlen and mbmaxlen members of a data type structure. */
UNIV_INLINE
UNIV_INLINE
...
...
innobase/include/que0que.h
View file @
edf59e54
...
@@ -359,7 +359,8 @@ struct que_thr_struct{
...
@@ -359,7 +359,8 @@ struct que_thr_struct{
the control came */
the control came */
ulint
resource
;
/* resource usage of the query thread
ulint
resource
;
/* resource usage of the query thread
thus far */
thus far */
ulint
lock_state
;
/* lock state of thread (table or row) */
ulint
lock_state
;
/* lock state of thread (table or
row) */
};
};
#define QUE_THR_MAGIC_N 8476583
#define QUE_THR_MAGIC_N 8476583
...
...
innobase/include/row0mysql.h
View file @
edf59e54
...
@@ -21,36 +21,6 @@ Created 9/17/2000 Heikki Tuuri
...
@@ -21,36 +21,6 @@ Created 9/17/2000 Heikki Tuuri
typedef
struct
row_prebuilt_struct
row_prebuilt_t
;
typedef
struct
row_prebuilt_struct
row_prebuilt_t
;
/***********************************************************************
Stores a variable-length field (like VARCHAR) length to dest, in the
MySQL format. */
UNIV_INLINE
byte
*
row_mysql_store_var_len
(
/*====================*/
/* out: dest + 2 */
byte
*
dest
,
/* in: where to store */
ulint
len
);
/* in: length, must fit in two bytes */
/***********************************************************************
Reads a MySQL format variable-length field (like VARCHAR) length and
returns pointer to the field data. */
UNIV_INLINE
byte
*
row_mysql_read_var_ref
(
/*===================*/
/* out: field + 2 */
ulint
*
len
,
/* out: variable-length field length */
byte
*
field
);
/* in: field */
/***********************************************************************
Reads a MySQL format variable-length field (like VARCHAR) length and
returns pointer to the field data. */
byte
*
row_mysql_read_var_ref_noninline
(
/*=============================*/
/* out: field + 2 */
ulint
*
len
,
/* out: variable-length field length */
byte
*
field
);
/* in: field */
/***********************************************************************
/***********************************************************************
Frees the blob heap in prebuilt when no longer needed. */
Frees the blob heap in prebuilt when no longer needed. */
...
@@ -60,6 +30,30 @@ row_mysql_prebuilt_free_blob_heap(
...
@@ -60,6 +30,30 @@ row_mysql_prebuilt_free_blob_heap(
row_prebuilt_t
*
prebuilt
);
/* in: prebuilt struct of a
row_prebuilt_t
*
prebuilt
);
/* in: prebuilt struct of a
ha_innobase:: table handle */
ha_innobase:: table handle */
/***********************************************************************
/***********************************************************************
Stores a >= 5.0.3 format true VARCHAR length to dest, in the MySQL row
format. */
byte
*
row_mysql_store_true_var_len
(
/*=========================*/
/* out: pointer to the data, we skip the 1 or 2 bytes
at the start that are used to store the len */
byte
*
dest
,
/* in: where to store */
ulint
len
,
/* in: length, must fit in two bytes */
ulint
lenlen
);
/* in: storage length of len: either 1 or 2 bytes */
/***********************************************************************
Reads a >= 5.0.3 format true VARCHAR length, in the MySQL row format, and
returns a pointer to the data. */
byte
*
row_mysql_read_true_varchar
(
/*========================*/
/* out: pointer to the data, we skip the 1 or 2 bytes
at the start that are used to store the len */
ulint
*
len
,
/* out: variable-length field length */
byte
*
field
,
/* in: field in the MySQL format */
ulint
lenlen
);
/* in: storage length of len: either 1 or 2 bytes */
/***********************************************************************
Stores a reference to a BLOB in the MySQL format. */
Stores a reference to a BLOB in the MySQL format. */
void
void
...
@@ -83,24 +77,40 @@ row_mysql_read_blob_ref(
...
@@ -83,24 +77,40 @@ row_mysql_read_blob_ref(
ulint
col_len
);
/* in: BLOB reference length (not BLOB
ulint
col_len
);
/* in: BLOB reference length (not BLOB
length) */
length) */
/******************************************************************
/******************************************************************
Stores a non-SQL-NULL field given in the MySQL format in the Innobase
Stores a non-SQL-NULL field given in the MySQL format in the InnoDB format.
format. */
The counterpart of this function is row_sel_field_store_in_mysql_format() in
UNIV_INLINE
row0sel.c. */
void
byte
*
row_mysql_store_col_in_innobase_format
(
row_mysql_store_col_in_innobase_format
(
/*===================================*/
/*===================================*/
dfield_t
*
dfield
,
/* in/out: dfield */
/* out: up to which byte we used
byte
*
buf
,
/* in/out: buffer for the converted
buf in the conversion */
value */
dfield_t
*
dfield
,
/* in/out: dfield where dtype
information must be already set when
this function is called! */
byte
*
buf
,
/* in/out: buffer for a converted
integer value; this must be at least
col_len long then! */
ibool
row_format_col
,
/* TRUE if the mysql_data is from
a MySQL row, FALSE if from a MySQL
key value;
in MySQL, a true VARCHAR storage
format differs in a row and in a
key value: in a key value the length
is always stored in 2 bytes! */
byte
*
mysql_data
,
/* in: MySQL column value, not
byte
*
mysql_data
,
/* in: MySQL column value, not
SQL NULL; NOTE that dfield may also
SQL NULL; NOTE that dfield may also
get a pointer to mysql_data,
get a pointer to mysql_data,
therefore do not discard this as long
therefore do not discard this as long
as dfield is used! */
as dfield is used! */
ulint
col_len
,
/* in: MySQL column length */
ulint
col_len
,
/* in: MySQL column length; NOTE that
ulint
type
,
/* in: data type */
this is the storage length of the
bool
comp
,
/* in: TRUE=compact format */
column in the MySQL format row, not
ulint
is_unsigned
);
/* in: != 0 if unsigned integer type */
necessarily the length of the actual
payload data; if the column is a true
VARCHAR then this is irrelevant */
ibool
comp
);
/* in: TRUE = compact format */
/********************************************************************
/********************************************************************
Handles user errors and lock waits detected by the database engine. */
Handles user errors and lock waits detected by the database engine. */
...
@@ -457,6 +467,16 @@ struct mysql_row_templ_struct {
...
@@ -457,6 +467,16 @@ struct mysql_row_templ_struct {
zero if column cannot be NULL */
zero if column cannot be NULL */
ulint
type
;
/* column type in Innobase mtype
ulint
type
;
/* column type in Innobase mtype
numbers DATA_CHAR... */
numbers DATA_CHAR... */
ulint
mysql_type
;
/* MySQL type code; this is always
< 256 */
ulint
mysql_length_bytes
;
/* if mysql_type
== DATA_MYSQL_TRUE_VARCHAR, this tells
whether we should use 1 or 2 bytes to
store the MySQL true VARCHAR data
length at the start of row in the MySQL
format (NOTE that the MySQL key value
format always uses 2 bytes for the data
len) */
ulint
charset
;
/* MySQL charset-collation code
ulint
charset
;
/* MySQL charset-collation code
of the column, or zero */
of the column, or zero */
ulint
mbminlen
;
/* minimum length of a char, in bytes,
ulint
mbminlen
;
/* minimum length of a char, in bytes,
...
...
innobase/include/row0mysql.ic
View file @
edf59e54
...
@@ -5,149 +5,3 @@ MySQL interface for Innobase
...
@@ -5,149 +5,3 @@ MySQL interface for Innobase
Created 1/23/2001 Heikki Tuuri
Created 1/23/2001 Heikki Tuuri
*******************************************************/
*******************************************************/
/***********************************************************************
Stores a variable-length field (like VARCHAR) length to dest, in the
MySQL format. No real var implemented in MySQL yet! */
UNIV_INLINE
byte*
row_mysql_store_var_len(
/*====================*/
/* out: dest + 2 */
byte* dest, /* in: where to store */
ulint len __attribute__((unused))) /* in: length, must fit in two
bytes */
{
ut_ad(len < 256 * 256);
/*
mach_write_to_2_little_endian(dest, len);
return(dest + 2);
*/
return(dest); /* No real var implemented in MySQL yet! */
}
/***********************************************************************
Reads a MySQL format variable-length field (like VARCHAR) length and
returns pointer to the field data. No real var implemented in MySQL yet! */
UNIV_INLINE
byte*
row_mysql_read_var_ref(
/*===================*/
/* out: field + 2 */
ulint* len, /* out: variable-length field length; does not work
yet! */
byte* field) /* in: field */
{
/*
*len = mach_read_from_2_little_endian(field);
return(field + 2);
*/
UT_NOT_USED(len);
return(field); /* No real var implemented in MySQL yet! */
}
/******************************************************************
Stores a non-SQL-NULL field given in the MySQL format in the Innobase
format. */
UNIV_INLINE
void
row_mysql_store_col_in_innobase_format(
/*===================================*/
dfield_t* dfield, /* in/out: dfield */
byte* buf, /* in/out: buffer for the converted
value; this must be at least col_len
long! */
byte* mysql_data, /* in: MySQL column value, not
SQL NULL; NOTE that dfield may also
get a pointer to mysql_data,
therefore do not discard this as long
as dfield is used! */
ulint col_len, /* in: MySQL column length */
ulint type, /* in: data type */
bool comp, /* in: TRUE=compact format */
ulint is_unsigned) /* in: != 0 if unsigned integer type */
{
byte* ptr = mysql_data;
if (type == DATA_INT) {
/* Store integer data in Innobase in a big-endian format,
sign bit negated */
ptr = buf + col_len;
for (;;) {
ptr--;
*ptr = *mysql_data;
if (ptr == buf) {
break;
}
mysql_data++;
}
if (!is_unsigned) {
*ptr = (byte) (*ptr ^ 128);
}
} else if (type == DATA_VARCHAR || type == DATA_VARMYSQL
|| type == DATA_BINARY) {
/* Remove trailing spaces. */
/* Handle UCS2 strings differently. */
ulint mbminlen = dtype_get_mbminlen(
dfield_get_type(dfield));
ptr = row_mysql_read_var_ref(&col_len, mysql_data);
if (mbminlen == 2) {
/* space=0x0020 */
/* Trim "half-chars", just in case. */
col_len &= ~1;
while (col_len >= 2 && ptr[col_len - 2] == 0x00
&& ptr[col_len - 1] == 0x20) {
col_len -= 2;
}
} else {
ut_a(mbminlen == 1);
/* space=0x20 */
while (col_len > 0 && ptr[col_len - 1] == 0x20) {
col_len--;
}
}
} else if (comp && type == DATA_MYSQL
&& dtype_get_mbminlen(dfield_get_type(dfield)) == 1
&& dtype_get_mbmaxlen(dfield_get_type(dfield)) > 1) {
/* We assume that this CHAR field is encoded in a
variable-length character set where spaces have
1:1 correspondence to 0x20 bytes, such as UTF-8.
Consider a CHAR(n) field, a field of n characters.
It will contain between n*mbminlen and n*mbmaxlen bytes.
We will try to truncate it to n bytes by stripping
space padding. If the field contains single-byte
characters only, it will be truncated to n characters.
Consider a CHAR(5) field containing the string ".a "
where "." denotes a 3-byte character represented by
the bytes "$%&". After our stripping, the string will
be stored as "$%&a " (5 bytes). The string ".abc "
will be stored as "$%&abc" (6 bytes).
The space padding will be restored in row0sel.c, function
row_sel_field_store_in_mysql_format(). */
ulint n_chars;
dtype_t* dtype = dfield_get_type(dfield);
ut_a(!(dtype_get_len(dtype) % dtype_get_mbmaxlen(dtype)));
n_chars = dtype_get_len(dtype) / dtype_get_mbmaxlen(dtype);
/* Strip space padding. */
while (col_len > n_chars && ptr[col_len - 1] == 0x20) {
col_len--;
}
} else if (type == DATA_BLOB) {
ptr = row_mysql_read_blob_ref(&col_len, mysql_data, col_len);
}
dfield_set_data(dfield, ptr, col_len);
}
innobase/row/row0ins.c
View file @
edf59e54
...
@@ -521,6 +521,10 @@ row_ins_cascade_calc_update_vec(
...
@@ -521,6 +521,10 @@ row_ins_cascade_calc_update_vec(
fixed_size
=
dtype_get_fixed_size
(
type
);
fixed_size
=
dtype_get_fixed_size
(
type
);
/* TODO: pad in UCS-2 with 0x0020.
TODO: How does the special truncation of
UTF-8 CHAR cols affect this? */
if
(
fixed_size
if
(
fixed_size
&&
ufield
->
new_val
.
len
!=
UNIV_SQL_NULL
&&
ufield
->
new_val
.
len
!=
UNIV_SQL_NULL
&&
ufield
->
new_val
.
len
<
fixed_size
)
{
&&
ufield
->
new_val
.
len
<
fixed_size
)
{
...
...
innobase/row/row0mysql.c
View file @
edf59e54
...
@@ -105,20 +105,6 @@ row_mysql_delay_if_needed(void)
...
@@ -105,20 +105,6 @@ row_mysql_delay_if_needed(void)
}
}
}
}
/***********************************************************************
Reads a MySQL format variable-length field (like VARCHAR) length and
returns pointer to the field data. */
byte
*
row_mysql_read_var_ref_noninline
(
/*=============================*/
/* out: field + 2 */
ulint
*
len
,
/* out: variable-length field length */
byte
*
field
)
/* in: field */
{
return
(
row_mysql_read_var_ref
(
len
,
field
));
}
/***********************************************************************
/***********************************************************************
Frees the blob heap in prebuilt when no longer needed. */
Frees the blob heap in prebuilt when no longer needed. */
...
@@ -132,6 +118,61 @@ row_mysql_prebuilt_free_blob_heap(
...
@@ -132,6 +118,61 @@ row_mysql_prebuilt_free_blob_heap(
prebuilt
->
blob_heap
=
NULL
;
prebuilt
->
blob_heap
=
NULL
;
}
}
/***********************************************************************
Stores a >= 5.0.3 format true VARCHAR length to dest, in the MySQL row
format. */
byte
*
row_mysql_store_true_var_len
(
/*=========================*/
/* out: pointer to the data, we skip the 1 or 2 bytes
at the start that are used to store the len */
byte
*
dest
,
/* in: where to store */
ulint
len
,
/* in: length, must fit in two bytes */
ulint
lenlen
)
/* in: storage length of len: either 1 or 2 bytes */
{
if
(
lenlen
==
2
)
{
ut_a
(
len
<
256
*
256
);
mach_write_to_2_little_endian
(
dest
,
len
);
return
(
dest
+
2
);
}
ut_a
(
lenlen
==
1
);
ut_a
(
len
<
256
);
mach_write_to_1
(
dest
,
len
);
return
(
dest
+
1
);
}
/***********************************************************************
Reads a >= 5.0.3 format true VARCHAR length, in the MySQL row format, and
returns a pointer to the data. */
byte
*
row_mysql_read_true_varchar
(
/*========================*/
/* out: pointer to the data, we skip the 1 or 2 bytes
at the start that are used to store the len */
ulint
*
len
,
/* out: variable-length field length */
byte
*
field
,
/* in: field in the MySQL format */
ulint
lenlen
)
/* in: storage length of len: either 1 or 2 bytes */
{
if
(
lenlen
==
2
)
{
*
len
=
mach_read_from_2_little_endian
(
field
);
return
(
field
+
2
);
}
ut_a
(
lenlen
==
1
);
*
len
=
mach_read_from_1
(
field
);
return
(
field
+
1
);
}
/***********************************************************************
/***********************************************************************
Stores a reference to a BLOB in the MySQL format. */
Stores a reference to a BLOB in the MySQL format. */
...
@@ -191,15 +232,177 @@ row_mysql_read_blob_ref(
...
@@ -191,15 +232,177 @@ row_mysql_read_blob_ref(
}
}
/******************************************************************
/******************************************************************
Convert a row in the MySQL format to a row in the Innobase format. */
Stores a non-SQL-NULL field given in the MySQL format in the InnoDB format.
The counterpart of this function is row_sel_field_store_in_mysql_format() in
row0sel.c. */
byte
*
row_mysql_store_col_in_innobase_format
(
/*===================================*/
/* out: up to which byte we used
buf in the conversion */
dfield_t
*
dfield
,
/* in/out: dfield where dtype
information must be already set when
this function is called! */
byte
*
buf
,
/* in/out: buffer for a converted
integer value; this must be at least
col_len long then! */
ibool
row_format_col
,
/* TRUE if the mysql_data is from
a MySQL row, FALSE if from a MySQL
key value;
in MySQL, a true VARCHAR storage
format differs in a row and in a
key value: in a key value the length
is always stored in 2 bytes! */
byte
*
mysql_data
,
/* in: MySQL column value, not
SQL NULL; NOTE that dfield may also
get a pointer to mysql_data,
therefore do not discard this as long
as dfield is used! */
ulint
col_len
,
/* in: MySQL column length; NOTE that
this is the storage length of the
column in the MySQL format row, not
necessarily the length of the actual
payload data; if the column is a true
VARCHAR then this is irrelevant */
ibool
comp
)
/* in: TRUE = compact format */
{
byte
*
ptr
=
mysql_data
;
dtype_t
*
dtype
;
ulint
type
;
ulint
lenlen
;
dtype
=
dfield_get_type
(
dfield
);
type
=
dtype
->
mtype
;
if
(
type
==
DATA_INT
)
{
/* Store integer data in Innobase in a big-endian format,
sign bit negated if the data is a signed integer. In MySQL,
integers are stored in a little-endian format. */
ptr
=
buf
+
col_len
;
for
(;;)
{
ptr
--
;
*
ptr
=
*
mysql_data
;
if
(
ptr
==
buf
)
{
break
;
}
mysql_data
++
;
}
if
(
!
(
dtype
->
prtype
&
DATA_UNSIGNED
))
{
*
ptr
=
(
byte
)
(
*
ptr
^
128
);
}
buf
+=
col_len
;
}
else
if
((
type
==
DATA_VARCHAR
||
type
==
DATA_VARMYSQL
||
type
==
DATA_BINARY
))
{
if
(
dtype_get_mysql_type
(
dtype
)
==
DATA_MYSQL_TRUE_VARCHAR
)
{
/* The length of the actual data is stored to 1 or 2
bytes at the start of the field */
if
(
row_format_col
)
{
if
(
dtype
->
prtype
&
DATA_LONG_TRUE_VARCHAR
)
{
lenlen
=
2
;
}
else
{
lenlen
=
1
;
}
}
else
{
/* In a MySQL key value, lenlen is always 2 */
lenlen
=
2
;
}
ptr
=
row_mysql_read_true_varchar
(
&
col_len
,
mysql_data
,
lenlen
);
}
else
{
/* Remove trailing spaces from old style VARCHAR
columns. */
/* Handle UCS2 strings differently. */
ulint
mbminlen
=
dtype_get_mbminlen
(
dtype
);
ptr
=
mysql_data
;
if
(
mbminlen
==
2
)
{
/* space=0x0020 */
/* Trim "half-chars", just in case. */
col_len
&=
~
1
;
while
(
col_len
>=
2
&&
ptr
[
col_len
-
2
]
==
0x00
&&
ptr
[
col_len
-
1
]
==
0x20
)
{
col_len
-=
2
;
}
}
else
{
ut_a
(
mbminlen
==
1
);
/* space=0x20 */
while
(
col_len
>
0
&&
ptr
[
col_len
-
1
]
==
0x20
)
{
col_len
--
;
}
}
}
}
else
if
(
comp
&&
type
==
DATA_MYSQL
&&
dtype_get_mbminlen
(
dtype
)
==
1
&&
dtype_get_mbmaxlen
(
dtype
)
>
1
)
{
/* In some cases we strip trailing spaces from UTF-8 and other
multibyte charsets, from FIXED-length CHAR columns, to save
space. UTF-8 would otherwise normally use 3 * the string length
bytes to store a latin1 string! */
/* We assume that this CHAR field is encoded in a
variable-length character set where spaces have
1:1 correspondence to 0x20 bytes, such as UTF-8.
Consider a CHAR(n) field, a field of n characters.
It will contain between n * mbminlen and n * mbmaxlen bytes.
We will try to truncate it to n bytes by stripping
space padding. If the field contains single-byte
characters only, it will be truncated to n characters.
Consider a CHAR(5) field containing the string ".a "
where "." denotes a 3-byte character represented by
the bytes "$%&". After our stripping, the string will
be stored as "$%&a " (5 bytes). The string ".abc "
will be stored as "$%&abc" (6 bytes).
The space padding will be restored in row0sel.c, function
row_sel_field_store_in_mysql_format(). */
ulint
n_chars
;
ut_a
(
!
(
dtype_get_len
(
dtype
)
%
dtype_get_mbmaxlen
(
dtype
)));
n_chars
=
dtype_get_len
(
dtype
)
/
dtype_get_mbmaxlen
(
dtype
);
/* Strip space padding. */
while
(
col_len
>
n_chars
&&
ptr
[
col_len
-
1
]
==
0x20
)
{
col_len
--
;
}
}
else
if
(
type
==
DATA_BLOB
&&
row_format_col
)
{
ptr
=
row_mysql_read_blob_ref
(
&
col_len
,
mysql_data
,
col_len
);
}
dfield_set_data
(
dfield
,
ptr
,
col_len
);
return
(
buf
);
}
/******************************************************************
Convert a row in the MySQL format to a row in the Innobase format. Note that
the function to convert a MySQL format key value to an InnoDB dtuple is
row_sel_convert_mysql_key_to_innobase() in row0sel.c. */
static
static
void
void
row_mysql_convert_row_to_innobase
(
row_mysql_convert_row_to_innobase
(
/*==============================*/
/*==============================*/
dtuple_t
*
row
,
/* in/out: Innobase row where the
dtuple_t
*
row
,
/* in/out: Innobase row where the
field type information is already
field type information is already
copied there, or will be copied
copied there! */
later */
row_prebuilt_t
*
prebuilt
,
/* in: prebuilt struct where template
row_prebuilt_t
*
prebuilt
,
/* in: prebuilt struct where template
must be of type ROW_MYSQL_WHOLE_ROW */
must be of type ROW_MYSQL_WHOLE_ROW */
byte
*
mysql_rec
)
/* in: row in the MySQL format;
byte
*
mysql_rec
)
/* in: row in the MySQL format;
...
@@ -236,10 +439,10 @@ row_mysql_convert_row_to_innobase(
...
@@ -236,10 +439,10 @@ row_mysql_convert_row_to_innobase(
row_mysql_store_col_in_innobase_format
(
dfield
,
row_mysql_store_col_in_innobase_format
(
dfield
,
prebuilt
->
ins_upd_rec_buff
prebuilt
->
ins_upd_rec_buff
+
templ
->
mysql_col_offset
,
+
templ
->
mysql_col_offset
,
TRUE
,
/* MySQL row format data */
mysql_rec
+
templ
->
mysql_col_offset
,
mysql_rec
+
templ
->
mysql_col_offset
,
templ
->
mysql_col_len
,
templ
->
mysql_col_len
,
templ
->
type
,
prebuilt
->
table
->
comp
,
prebuilt
->
table
->
comp
);
templ
->
is_unsigned
);
next_column:
next_column:
;
;
}
}
...
@@ -594,7 +797,8 @@ static
...
@@ -594,7 +797,8 @@ static
dtuple_t
*
dtuple_t
*
row_get_prebuilt_insert_row
(
row_get_prebuilt_insert_row
(
/*========================*/
/*========================*/
/* out: prebuilt dtuple */
/* out: prebuilt dtuple; the column
type information is also set in it */
row_prebuilt_t
*
prebuilt
)
/* in: prebuilt struct in MySQL
row_prebuilt_t
*
prebuilt
)
/* in: prebuilt struct in MySQL
handle */
handle */
{
{
...
@@ -784,6 +988,7 @@ row_unlock_tables_for_mysql(
...
@@ -784,6 +988,7 @@ row_unlock_tables_for_mysql(
lock_release_tables_off_kernel
(
trx
);
lock_release_tables_off_kernel
(
trx
);
mutex_exit
(
&
kernel_mutex
);
mutex_exit
(
&
kernel_mutex
);
}
}
/*************************************************************************
/*************************************************************************
Sets a table lock on the table mentioned in prebuilt. */
Sets a table lock on the table mentioned in prebuilt. */
...
@@ -962,10 +1167,13 @@ row_insert_for_mysql(
...
@@ -962,10 +1167,13 @@ row_insert_for_mysql(
if
(
err
!=
DB_SUCCESS
)
{
if
(
err
!=
DB_SUCCESS
)
{
que_thr_stop_for_mysql
(
thr
);
que_thr_stop_for_mysql
(
thr
);
thr
->
lock_state
=
QUE_THR_LOCK_ROW
;
/* TODO: what is this? */
thr
->
lock_state
=
QUE_THR_LOCK_ROW
;
was_lock_wait
=
row_mysql_handle_errors
(
&
err
,
trx
,
thr
,
was_lock_wait
=
row_mysql_handle_errors
(
&
err
,
trx
,
thr
,
&
savept
);
&
savept
);
thr
->
lock_state
=
QUE_THR_LOCK_NOLOCK
;
thr
->
lock_state
=
QUE_THR_LOCK_NOLOCK
;
if
(
was_lock_wait
)
{
if
(
was_lock_wait
)
{
goto
run_again
;
goto
run_again
;
}
}
...
...
innobase/row/row0sel.c
View file @
edf59e54
...
@@ -2119,10 +2119,10 @@ row_sel_convert_mysql_key_to_innobase(
...
@@ -2119,10 +2119,10 @@ row_sel_convert_mysql_key_to_innobase(
+
256
*
key_ptr
[
data_offset
+
1
];
+
256
*
key_ptr
[
data_offset
+
1
];
data_field_len
=
data_offset
+
2
+
field
->
prefix_len
;
data_field_len
=
data_offset
+
2
+
field
->
prefix_len
;
data_offset
+=
2
;
data_offset
+=
2
;
type
=
DATA_CHAR
;
/* now that we know the length, we
/* now that we know the length, we store the column
store the column value like it would
value like it would be a fixed char field */
be a fixed char field */
}
else
if
(
field
->
prefix_len
>
0
)
{
}
else
if
(
field
->
prefix_len
>
0
)
{
/* Looks like MySQL pads unused end bytes in the
/* Looks like MySQL pads unused end bytes in the
prefix with space. Therefore, also in UTF-8, it is ok
prefix with space. Therefore, also in UTF-8, it is ok
...
@@ -2146,11 +2146,12 @@ row_sel_convert_mysql_key_to_innobase(
...
@@ -2146,11 +2146,12 @@ row_sel_convert_mysql_key_to_innobase(
if
(
!
is_null
)
{
if
(
!
is_null
)
{
row_mysql_store_col_in_innobase_format
(
row_mysql_store_col_in_innobase_format
(
dfield
,
buf
,
key_ptr
+
data_offset
,
dfield
,
data_len
,
type
,
buf
,
index
->
table
->
comp
,
FALSE
,
/* MySQL key value format col */
dfield_get_type
(
dfield
)
->
prtype
key_ptr
+
data_offset
,
&
DATA_UNSIGNED
);
data_len
,
index
->
table
->
comp
);
buf
+=
data_len
;
buf
+=
data_len
;
}
}
...
@@ -2225,7 +2226,7 @@ row_sel_store_row_id_to_prebuilt(
...
@@ -2225,7 +2226,7 @@ row_sel_store_row_id_to_prebuilt(
dict_index_name_print
(
stderr
,
prebuilt
->
trx
,
index
);
dict_index_name_print
(
stderr
,
prebuilt
->
trx
,
index
);
fprintf
(
stderr
,
"
\n
"
fprintf
(
stderr
,
"
\n
"
"InnoDB: Field number %lu, record:
\n
"
,
"InnoDB: Field number %lu, record:
\n
"
,
(
ulong
)
dict_index_get_sys_col_pos
(
index
,
DATA_ROW_ID
));
(
ulong
)
dict_index_get_sys_col_pos
(
index
,
DATA_ROW_ID
));
rec_print_new
(
stderr
,
index_rec
,
offsets
);
rec_print_new
(
stderr
,
index_rec
,
offsets
);
putc
(
'\n'
,
stderr
);
putc
(
'\n'
,
stderr
);
ut_error
;
ut_error
;
...
@@ -2235,8 +2236,9 @@ row_sel_store_row_id_to_prebuilt(
...
@@ -2235,8 +2236,9 @@ row_sel_store_row_id_to_prebuilt(
}
}
/******************************************************************
/******************************************************************
Stores a non-SQL-NULL field in the MySQL format. */
Stores a non-SQL-NULL field in the MySQL format. The counterpart of this
UNIV_INLINE
function is row_mysql_store_col_in_innobase_format() in row0mysql.c. */
static
void
void
row_sel_field_store_in_mysql_format
(
row_sel_field_store_in_mysql_format
(
/*================================*/
/*================================*/
...
@@ -2251,6 +2253,8 @@ row_sel_field_store_in_mysql_format(
...
@@ -2251,6 +2253,8 @@ row_sel_field_store_in_mysql_format(
ulint
len
)
/* in: length of the data */
ulint
len
)
/* in: length of the data */
{
{
byte
*
ptr
;
byte
*
ptr
;
byte
*
field_end
;
byte
*
pad_ptr
;
ut_ad
(
len
!=
UNIV_SQL_NULL
);
ut_ad
(
len
!=
UNIV_SQL_NULL
);
...
@@ -2274,25 +2278,66 @@ row_sel_field_store_in_mysql_format(
...
@@ -2274,25 +2278,66 @@ row_sel_field_store_in_mysql_format(
}
}
ut_ad
(
templ
->
mysql_col_len
==
len
);
ut_ad
(
templ
->
mysql_col_len
==
len
);
}
else
if
(
templ
->
type
==
DATA_VARCHAR
||
templ
->
type
==
DATA_VARMYSQL
}
else
if
(
templ
->
type
==
DATA_VARCHAR
||
templ
->
type
==
DATA_BINARY
)
{
||
templ
->
type
==
DATA_VARMYSQL
/* Store the length of the data to the first two bytes of
||
templ
->
type
==
DATA_BINARY
)
{
dest; does not do anything yet because MySQL has
no real vars! */
field_end
=
dest
+
templ
->
mysql_col_len
;
if
(
templ
->
mysql_type
==
DATA_MYSQL_TRUE_VARCHAR
)
{
/* This is a >= 5.0.3 type true VARCHAR. Store the
length of the data to the first byte or the first
two bytes of dest. */
dest
=
row_mysql_store_var_len
(
dest
,
len
);
dest
=
row_mysql_store_true_var_len
(
dest
,
len
,
templ
->
mysql_length_bytes
);
}
/* Copy the actual data */
ut_memcpy
(
dest
,
data
,
len
);
ut_memcpy
(
dest
,
data
,
len
);
#if 0
/* No real var implemented in MySQL yet! */
ut_ad(templ->mysql_col_len >= len + 2);
#endif
/* Pad with trailing spaces. We pad with spaces also the
unused end of a >= 5.0.3 true VARCHAR column, just in case
MySQL expects its contents to be deterministic. */
pad_ptr
=
dest
+
len
;
ut_ad
(
templ
->
mbminlen
<=
templ
->
mbmaxlen
);
/* We handle UCS2 charset strings differently. */
if
(
templ
->
mbminlen
==
2
)
{
/* A space char is two bytes, 0x0020 in UCS2 */
if
(
len
&
1
)
{
/* A 0x20 has been stripped from the column.
Pad it back. */
if
(
pad_ptr
<
field_end
)
{
*
pad_ptr
=
0x20
;
pad_ptr
++
;
}
}
/* Pad the rest of the string with 0x0020 */
while
(
pad_ptr
<
field_end
)
{
*
pad_ptr
=
0x00
;
pad_ptr
++
;
*
pad_ptr
=
0x20
;
pad_ptr
++
;
}
}
else
{
ut_ad
(
templ
->
mbminlen
==
1
);
/* space=0x20 */
memset
(
pad_ptr
,
0x20
,
field_end
-
pad_ptr
);
}
}
else
if
(
templ
->
type
==
DATA_BLOB
)
{
}
else
if
(
templ
->
type
==
DATA_BLOB
)
{
/* Store a pointer to the BLOB buffer to dest: the BLOB was
/* Store a pointer to the BLOB buffer to dest: the BLOB was
already copied to the buffer in row_sel_store_mysql_rec */
already copied to the buffer in row_sel_store_mysql_rec */
row_mysql_store_blob_ref
(
dest
,
templ
->
mysql_col_len
,
row_mysql_store_blob_ref
(
dest
,
templ
->
mysql_col_len
,
data
,
data
,
len
);
len
);
}
else
if
(
templ
->
type
==
DATA_MYSQL
)
{
}
else
if
(
templ
->
type
==
DATA_MYSQL
)
{
memcpy
(
dest
,
data
,
len
);
memcpy
(
dest
,
data
,
len
);
...
@@ -2306,9 +2351,10 @@ row_sel_field_store_in_mysql_format(
...
@@ -2306,9 +2351,10 @@ row_sel_field_store_in_mysql_format(
ut_a
(
len
*
templ
->
mbmaxlen
>=
templ
->
mysql_col_len
);
ut_a
(
len
*
templ
->
mbmaxlen
>=
templ
->
mysql_col_len
);
if
(
templ
->
mbminlen
!=
templ
->
mbmaxlen
)
{
if
(
templ
->
mbminlen
!=
templ
->
mbmaxlen
)
{
/* Pad with spaces.
This undoes the stripping
/* Pad with spaces. This undoes the stripping
done in row0mysql.ic, function
done in row0mysql.ic, function
row_mysql_store_col_in_innobase_format(). */
row_mysql_store_col_in_innobase_format(). */
memset
(
dest
+
len
,
0x20
,
templ
->
mysql_col_len
-
len
);
memset
(
dest
+
len
,
0x20
,
templ
->
mysql_col_len
-
len
);
}
}
}
else
{
}
else
{
...
@@ -2320,6 +2366,7 @@ row_sel_field_store_in_mysql_format(
...
@@ -2320,6 +2366,7 @@ row_sel_field_store_in_mysql_format(
||
templ
->
type
==
DATA_DOUBLE
||
templ
->
type
==
DATA_DOUBLE
||
templ
->
type
==
DATA_DECIMAL
);
||
templ
->
type
==
DATA_DECIMAL
);
ut_ad
(
templ
->
mysql_col_len
==
len
);
ut_ad
(
templ
->
mysql_col_len
==
len
);
memcpy
(
dest
,
data
,
len
);
memcpy
(
dest
,
data
,
len
);
}
}
}
}
...
@@ -2436,40 +2483,6 @@ row_sel_store_mysql_rec(
...
@@ -2436,40 +2483,6 @@ row_sel_store_mysql_rec(
mysql_rec
+
templ
->
mysql_col_offset
,
mysql_rec
+
templ
->
mysql_col_offset
,
templ
,
data
,
len
);
templ
,
data
,
len
);
if
(
templ
->
type
==
DATA_VARCHAR
||
templ
->
type
==
DATA_VARMYSQL
||
templ
->
type
==
DATA_BINARY
)
{
/* Pad with trailing spaces */
data
=
mysql_rec
+
templ
->
mysql_col_offset
;
ut_ad
(
templ
->
mbminlen
<=
templ
->
mbmaxlen
);
/* Handle UCS2 strings differently. */
if
(
templ
->
mbminlen
==
2
)
{
/* space=0x0020 */
ulint
col_len
=
templ
->
mysql_col_len
;
ut_a
(
!
(
col_len
&
1
));
if
(
len
&
1
)
{
/* A 0x20 has been stripped
from the column.
Pad it back. */
goto
pad_0x20
;
}
/* Pad the rest of the string
with 0x0020 */
while
(
len
<
col_len
)
{
data
[
len
++
]
=
0x00
;
pad_0x20:
data
[
len
++
]
=
0x20
;
}
}
else
{
ut_ad
(
templ
->
mbminlen
==
1
);
/* space=0x20 */
memset
(
data
+
len
,
0x20
,
templ
->
mysql_col_len
-
len
);
}
}
/* Cleanup */
/* Cleanup */
if
(
extern_field_heap
)
{
if
(
extern_field_heap
)
{
mem_heap_free
(
extern_field_heap
);
mem_heap_free
(
extern_field_heap
);
...
...
innobase/trx/trx0trx.c
View file @
edf59e54
...
@@ -1958,7 +1958,7 @@ trx_recover_for_mysql(
...
@@ -1958,7 +1958,7 @@ trx_recover_for_mysql(
ut_print_timestamp
(
stderr
);
ut_print_timestamp
(
stderr
);
fprintf
(
stderr
,
fprintf
(
stderr
,
" InnoDB: %d transactions in prepare state after recovery
\n
"
,
" InnoDB: %d transactions in prepare
d
state after recovery
\n
"
,
count
);
count
);
return
(
count
);
return
(
count
);
...
...
sql/ha_innodb.cc
View file @
edf59e54
...
@@ -1074,6 +1074,8 @@ innobase_init(void)
...
@@ -1074,6 +1074,8 @@ innobase_init(void)
DBUG_ENTER
(
"innobase_init"
);
DBUG_ENTER
(
"innobase_init"
);
ut_a
(
DATA_MYSQL_TRUE_VARCHAR
==
(
ulint
)
MYSQL_TYPE_VARCHAR
);
os_innodb_umask
=
(
ulint
)
my_umask
;
os_innodb_umask
=
(
ulint
)
my_umask
;
/* First calculate the default path for innodb_data_home_dir etc.,
/* First calculate the default path for innodb_data_home_dir etc.,
...
@@ -2244,7 +2246,9 @@ innobase_mysql_cmp(
...
@@ -2244,7 +2246,9 @@ innobase_mysql_cmp(
}
}
/******************************************************************
/******************************************************************
Converts a MySQL type to an InnoDB type. */
Converts a MySQL type to an InnoDB type. Note that this function returns
the 'mtype' of InnoDB. InnoDB differentiates between MySQL's old <= 4.1
VARCHAR and the new true VARCHAR in >= 5.0.3 by the 'prtype'. */
inline
inline
ulint
ulint
get_innobase_type_from_mysql_type
(
get_innobase_type_from_mysql_type
(
...
@@ -2259,8 +2263,9 @@ get_innobase_type_from_mysql_type(
...
@@ -2259,8 +2263,9 @@ get_innobase_type_from_mysql_type(
switch
(
field
->
type
())
{
switch
(
field
->
type
())
{
/* NOTE that we only allow string types in DATA_MYSQL
/* NOTE that we only allow string types in DATA_MYSQL
and DATA_VARMYSQL */
and DATA_VARMYSQL */
case
MYSQL_TYPE_VAR_STRING
:
case
MYSQL_TYPE_VAR_STRING
:
/* old <= 4.1 VARCHAR */
case
MYSQL_TYPE_VARCHAR
:
if
(
field
->
binary
())
{
case
MYSQL_TYPE_VARCHAR
:
/* new >= 5.0.3 true VARCHAR */
if
(
field
->
binary
())
{
return
(
DATA_BINARY
);
return
(
DATA_BINARY
);
}
else
if
(
strcmp
(
}
else
if
(
strcmp
(
field
->
charset
()
->
name
,
field
->
charset
()
->
name
,
...
@@ -2313,6 +2318,35 @@ get_innobase_type_from_mysql_type(
...
@@ -2313,6 +2318,35 @@ get_innobase_type_from_mysql_type(
return
(
0
);
return
(
0
);
}
}
/***********************************************************************
Writes an unsigned integer value < 64k to 2 bytes, in the little-endian
storage format. */
inline
void
innobase_write_to_2_little_endian
(
/*==============================*/
byte
*
buf
,
/* in: where to store */
ulint
val
)
/* in: value to write, must be < 64k */
{
ut_a
(
val
<
256
*
256
);
buf
[
0
]
=
(
byte
)(
val
&
0xFF
);
buf
[
1
]
=
(
byte
)(
val
/
256
);
}
/***********************************************************************
Reads an unsigned integer value < 64k from 2 bytes, in the little-endian
storage format. */
inline
uint
innobase_read_from_2_little_endian
(
/*===============================*/
/* out: value */
const
mysql_byte
*
buf
)
/* in: from where to read */
{
return
((
ulint
)(
buf
[
0
])
+
256
*
((
ulint
)(
buf
[
1
])));
}
/***********************************************************************
/***********************************************************************
Stores a key value for a row to a buffer. */
Stores a key value for a row to a buffer. */
...
@@ -2352,9 +2386,14 @@ ha_innobase::store_key_val_for_row(
...
@@ -2352,9 +2386,14 @@ ha_innobase::store_key_val_for_row(
3. In a column prefix field, prefix_len next bytes are reserved for
3. In a column prefix field, prefix_len next bytes are reserved for
data. In a normal field the max field length next bytes are reserved
data. In a normal field the max field length next bytes are reserved
for data. For a VARCHAR(n) the max field length is n. If the stored
for data. For a VARCHAR(n) the max field length is n. If the stored
value is the SQL NULL then these data bytes are set to 0.
*/
value is the SQL NULL then these data bytes are set to 0.
/* We have to zero-fill the buffer so that MySQL is able to use a
4. We always use a 2 byte length for a true >= 5.0.3 VARCHAR. Note that
in the MySQL row format, the length is stored in 1 or 2 bytes,
depending on the maximum allowed length. But in the MySQL key value
format, the length always takes 2 bytes.
We have to zero-fill the buffer so that MySQL is able to use a
simple memcmp to compare two key values to determine if they are
simple memcmp to compare two key values to determine if they are
equal. MySQL does this to compare contents of two 'ref' values. */
equal. MySQL does this to compare contents of two 'ref' values. */
...
@@ -2377,7 +2416,43 @@ ha_innobase::store_key_val_for_row(
...
@@ -2377,7 +2416,43 @@ ha_innobase::store_key_val_for_row(
field
=
key_part
->
field
;
field
=
key_part
->
field
;
mysql_type
=
field
->
type
();
mysql_type
=
field
->
type
();
if
(
mysql_type
==
FIELD_TYPE_TINY_BLOB
if
(
mysql_type
==
MYSQL_TYPE_VARCHAR
)
{
/* >= 5.0.3 true VARCHAR */
ulint
lenlen
;
ulint
len
;
byte
*
data
;
if
(
is_null
)
{
buff
+=
key_part
->
length
+
2
;
continue
;
}
lenlen
=
(
ulint
)
(((
Field_varstring
*
)
field
)
->
length_bytes
);
data
=
row_mysql_read_true_varchar
(
&
len
,
(
byte
*
)
(
record
+
(
ulint
)
get_field_offset
(
table
,
field
)),
lenlen
);
/* The length in a key value is always stored in 2
bytes */
row_mysql_store_true_var_len
((
byte
*
)
buff
,
len
,
2
);
buff
+=
2
;
memcpy
(
buff
,
data
,
len
);
/* Note that we always reserve the maximum possible
length of the true VARCHAR in the key value, though
only len first bytes after the 2 length bytes contain
actual data. The rest of the space was reset to zero
in the bzero() call above. */
buff
+=
key_part
->
length
;
}
else
if
(
mysql_type
==
FIELD_TYPE_TINY_BLOB
||
mysql_type
==
FIELD_TYPE_MEDIUM_BLOB
||
mysql_type
==
FIELD_TYPE_MEDIUM_BLOB
||
mysql_type
==
FIELD_TYPE_BLOB
||
mysql_type
==
FIELD_TYPE_BLOB
||
mysql_type
==
FIELD_TYPE_LONG_BLOB
)
{
||
mysql_type
==
FIELD_TYPE_LONG_BLOB
)
{
...
@@ -2385,9 +2460,9 @@ ha_innobase::store_key_val_for_row(
...
@@ -2385,9 +2460,9 @@ ha_innobase::store_key_val_for_row(
ut_a
(
key_part
->
key_part_flag
&
HA_PART_KEY_SEG
);
ut_a
(
key_part
->
key_part_flag
&
HA_PART_KEY_SEG
);
if
(
is_null
)
{
if
(
is_null
)
{
buff
+=
key_part
->
length
+
2
;
buff
+=
key_part
->
length
+
2
;
continue
;
continue
;
}
}
blob_data
=
row_mysql_read_blob_ref
(
&
blob_len
,
blob_data
=
row_mysql_read_blob_ref
(
&
blob_len
,
...
@@ -2404,12 +2479,15 @@ ha_innobase::store_key_val_for_row(
...
@@ -2404,12 +2479,15 @@ ha_innobase::store_key_val_for_row(
/* MySQL reserves 2 bytes for the length and the
/* MySQL reserves 2 bytes for the length and the
storage of the number is little-endian */
storage of the number is little-endian */
ut_a
(
blob_len
<
256
);
innobase_write_to_2_little_endian
(
*
((
byte
*
)
buff
)
=
(
byte
)
blob_len
;
(
byte
*
)
buff
,
(
ulint
)
blob_len
)
;
buff
+=
2
;
buff
+=
2
;
memcpy
(
buff
,
blob_data
,
blob_len
);
memcpy
(
buff
,
blob_data
,
blob_len
);
/* Note that we always reserve the maximum possible
length of the BLOB prefix in the key value. */
buff
+=
key_part
->
length
;
buff
+=
key_part
->
length
;
}
else
{
}
else
{
if
(
is_null
)
{
if
(
is_null
)
{
...
@@ -2573,6 +2651,13 @@ build_template(
...
@@ -2573,6 +2651,13 @@ build_template(
templ
->
mysql_col_len
=
(
ulint
)
field
->
pack_length
();
templ
->
mysql_col_len
=
(
ulint
)
field
->
pack_length
();
templ
->
type
=
get_innobase_type_from_mysql_type
(
field
);
templ
->
type
=
get_innobase_type_from_mysql_type
(
field
);
templ
->
mysql_type
=
(
ulint
)
field
->
type
();
if
(
templ
->
mysql_type
==
DATA_MYSQL_TRUE_VARCHAR
)
{
templ
->
mysql_length_bytes
=
(
ulint
)
(((
Field_varstring
*
)
field
)
->
length_bytes
);
}
templ
->
charset
=
dtype_get_charset_coll_noninline
(
templ
->
charset
=
dtype_get_charset_coll_noninline
(
index
->
table
->
cols
[
i
].
type
.
prtype
);
index
->
table
->
cols
[
i
].
type
.
prtype
);
templ
->
mbminlen
=
index
->
table
->
cols
[
i
].
type
.
mbminlen
;
templ
->
mbminlen
=
index
->
table
->
cols
[
i
].
type
.
mbminlen
;
...
@@ -2810,54 +2895,6 @@ ha_innobase::write_row(
...
@@ -2810,54 +2895,6 @@ ha_innobase::write_row(
DBUG_RETURN
(
error
);
DBUG_RETURN
(
error
);
}
}
/******************************************************************
Converts field data for storage in an InnoDB update vector. */
inline
mysql_byte
*
innobase_convert_and_store_changed_col
(
/*===================================*/
/* out: pointer to the end of the converted
data in the buffer */
upd_field_t
*
ufield
,
/* in/out: field in the update vector */
mysql_byte
*
buf
,
/* in: buffer we can use in conversion */
mysql_byte
*
data
,
/* in: column data to store */
ulint
len
,
/* in: data len */
ulint
col_type
,
/* in: data type in InnoDB type numbers */
ulint
is_unsigned
)
/* in: != 0 if an unsigned integer type */
{
uint
i
;
if
(
len
==
UNIV_SQL_NULL
)
{
data
=
NULL
;
}
else
if
(
col_type
==
DATA_VARCHAR
||
col_type
==
DATA_BINARY
||
col_type
==
DATA_VARMYSQL
)
{
/* Remove trailing spaces */
while
(
len
>
0
&&
data
[
len
-
1
]
==
' '
)
{
len
--
;
}
}
else
if
(
col_type
==
DATA_INT
)
{
/* Store integer data in InnoDB in a big-endian
format, sign bit negated, if signed */
for
(
i
=
0
;
i
<
len
;
i
++
)
{
buf
[
len
-
1
-
i
]
=
data
[
i
];
}
if
(
!
is_unsigned
)
{
buf
[
0
]
=
buf
[
0
]
^
128
;
}
data
=
buf
;
buf
+=
len
;
}
ufield
->
new_val
.
data
=
data
;
ufield
->
new_val
.
len
=
len
;
return
(
buf
);
}
/**************************************************************************
/**************************************************************************
Checks which fields have changed in a row and stores information
Checks which fields have changed in a row and stores information
of them to an update vector. */
of them to an update vector. */
...
@@ -2878,9 +2915,11 @@ calc_row_difference(
...
@@ -2878,9 +2915,11 @@ calc_row_difference(
{
{
mysql_byte
*
original_upd_buff
=
upd_buff
;
mysql_byte
*
original_upd_buff
=
upd_buff
;
Field
*
field
;
Field
*
field
;
enum_field_types
field_mysql_type
;
uint
n_fields
;
uint
n_fields
;
ulint
o_len
;
ulint
o_len
;
ulint
n_len
;
ulint
n_len
;
ulint
col_pack_len
;
byte
*
o_ptr
;
byte
*
o_ptr
;
byte
*
n_ptr
;
byte
*
n_ptr
;
byte
*
buf
;
byte
*
buf
;
...
@@ -2888,6 +2927,7 @@ calc_row_difference(
...
@@ -2888,6 +2927,7 @@ calc_row_difference(
ulint
col_type
;
ulint
col_type
;
ulint
is_unsigned
;
ulint
is_unsigned
;
ulint
n_changed
=
0
;
ulint
n_changed
=
0
;
dfield_t
dfield
;
uint
i
;
uint
i
;
n_fields
=
table
->
s
->
fields
;
n_fields
=
table
->
s
->
fields
;
...
@@ -2907,9 +2947,13 @@ calc_row_difference(
...
@@ -2907,9 +2947,13 @@ calc_row_difference(
o_ptr
=
(
byte
*
)
old_row
+
get_field_offset
(
table
,
field
);
o_ptr
=
(
byte
*
)
old_row
+
get_field_offset
(
table
,
field
);
n_ptr
=
(
byte
*
)
new_row
+
get_field_offset
(
table
,
field
);
n_ptr
=
(
byte
*
)
new_row
+
get_field_offset
(
table
,
field
);
o_len
=
field
->
pack_length
();
n_len
=
field
->
pack_length
();
col_pack_len
=
field
->
pack_length
();
o_len
=
col_pack_len
;
n_len
=
col_pack_len
;
field_mysql_type
=
field
->
type
();
col_type
=
get_innobase_type_from_mysql_type
(
field
);
col_type
=
get_innobase_type_from_mysql_type
(
field
);
is_unsigned
=
(
ulint
)
(
field
->
flags
&
UNSIGNED_FLAG
);
is_unsigned
=
(
ulint
)
(
field
->
flags
&
UNSIGNED_FLAG
);
...
@@ -2918,14 +2962,29 @@ calc_row_difference(
...
@@ -2918,14 +2962,29 @@ calc_row_difference(
case
DATA_BLOB
:
case
DATA_BLOB
:
o_ptr
=
row_mysql_read_blob_ref
(
&
o_len
,
o_ptr
,
o_len
);
o_ptr
=
row_mysql_read_blob_ref
(
&
o_len
,
o_ptr
,
o_len
);
n_ptr
=
row_mysql_read_blob_ref
(
&
n_len
,
n_ptr
,
n_len
);
n_ptr
=
row_mysql_read_blob_ref
(
&
n_len
,
n_ptr
,
n_len
);
break
;
break
;
case
DATA_VARCHAR
:
case
DATA_VARCHAR
:
case
DATA_BINARY
:
case
DATA_BINARY
:
case
DATA_VARMYSQL
:
case
DATA_VARMYSQL
:
o_ptr
=
row_mysql_read_var_ref_noninline
(
&
o_len
,
if
(
field_mysql_type
==
MYSQL_TYPE_VARCHAR
)
{
o_ptr
);
/* This is a >= 5.0.3 type true VARCHAR where
n_ptr
=
row_mysql_read_var_ref_noninline
(
&
n_len
,
the real payload data length is stored in
n_ptr
);
1 or 2 bytes */
o_ptr
=
row_mysql_read_true_varchar
(
&
o_len
,
o_ptr
,
(
ulint
)
(((
Field_varstring
*
)
field
)
->
length_bytes
));
n_ptr
=
row_mysql_read_true_varchar
(
&
n_len
,
n_ptr
,
(
ulint
)
(((
Field_varstring
*
)
field
)
->
length_bytes
));
}
break
;
default:
default:
;
;
}
}
...
@@ -2947,12 +3006,29 @@ calc_row_difference(
...
@@ -2947,12 +3006,29 @@ calc_row_difference(
/* The field has changed */
/* The field has changed */
ufield
=
uvect
->
fields
+
n_changed
;
ufield
=
uvect
->
fields
+
n_changed
;
/* Let us use a dummy dfield to make the conversion
from the MySQL column format to the InnoDB format */
dfield
.
type
=
(
prebuilt
->
table
->
cols
+
i
)
->
type
;
if
(
n_len
!=
UNIV_SQL_NULL
)
{
buf
=
row_mysql_store_col_in_innobase_format
(
&
dfield
,
(
byte
*
)
buf
,
TRUE
,
n_ptr
,
col_pack_len
,
prebuilt
->
table
->
comp
);
ufield
->
new_val
.
data
=
dfield_get_data
(
&
dfield
);
ufield
->
new_val
.
len
=
dfield_get_len
(
&
dfield
);
}
else
{
ufield
->
new_val
.
data
=
NULL
;
ufield
->
new_val
.
len
=
UNIV_SQL_NULL
;
}
buf
=
(
byte
*
)
innobase_convert_and_store_changed_col
(
ufield
,
(
mysql_byte
*
)
buf
,
(
mysql_byte
*
)
n_ptr
,
n_len
,
col_type
,
is_unsigned
);
ufield
->
exp
=
NULL
;
ufield
->
exp
=
NULL
;
ufield
->
field_no
=
ufield
->
field_no
=
(
prebuilt
->
table
->
cols
+
i
)
->
clust_pos
;
(
prebuilt
->
table
->
cols
+
i
)
->
clust_pos
;
...
@@ -3701,7 +3777,7 @@ ha_innobase::rnd_pos(
...
@@ -3701,7 +3777,7 @@ ha_innobase::rnd_pos(
}
}
if
(
error
)
{
if
(
error
)
{
DBUG_PRINT
(
"error"
,
(
"Got error: %ld"
,
error
));
DBUG_PRINT
(
"error"
,
(
"Got error: %ld"
,
error
));
DBUG_RETURN
(
error
);
DBUG_RETURN
(
error
);
}
}
...
@@ -3709,10 +3785,11 @@ ha_innobase::rnd_pos(
...
@@ -3709,10 +3785,11 @@ ha_innobase::rnd_pos(
for the table, and it is == ref_length */
for the table, and it is == ref_length */
error
=
index_read
(
buf
,
pos
,
ref_length
,
HA_READ_KEY_EXACT
);
error
=
index_read
(
buf
,
pos
,
ref_length
,
HA_READ_KEY_EXACT
);
if
(
error
)
{
if
(
error
)
{
DBUG_PRINT
(
"error"
,(
"Got error: %ld"
,
error
));
DBUG_PRINT
(
"error"
,
(
"Got error: %ld"
,
error
));
}
}
change_active_index
(
keynr
);
change_active_index
(
keynr
);
DBUG_RETURN
(
error
);
DBUG_RETURN
(
error
);
...
@@ -3752,12 +3829,11 @@ ha_innobase::position(
...
@@ -3752,12 +3829,11 @@ ha_innobase::position(
ref_length
,
record
);
ref_length
,
record
);
}
}
/* Since we do not store len to the buffer 'ref', we must assume
/* We assume that the 'ref' value len is always fixed for the same
that len is always fixed for this table. The following assertion
table. */
checks this. */
if
(
len
!=
ref_length
)
{
if
(
len
!=
ref_length
)
{
fprintf
(
stderr
,
fprintf
(
stderr
,
"InnoDB: Error: stored ref len is %lu, but table ref len is %lu
\n
"
,
"InnoDB: Error: stored ref len is %lu, but table ref len is %lu
\n
"
,
(
ulong
)
len
,
(
ulong
)
ref_length
);
(
ulong
)
len
,
(
ulong
)
ref_length
);
}
}
...
@@ -3788,9 +3864,11 @@ create_table_def(
...
@@ -3788,9 +3864,11 @@ create_table_def(
ulint
n_cols
;
ulint
n_cols
;
int
error
;
int
error
;
ulint
col_type
;
ulint
col_type
;
ulint
col_len
;
ulint
nulls_allowed
;
ulint
nulls_allowed
;
ulint
unsigned_type
;
ulint
unsigned_type
;
ulint
binary_type
;
ulint
binary_type
;
ulint
long_true_varchar
;
ulint
charset_no
;
ulint
charset_no
;
ulint
i
;
ulint
i
;
...
@@ -3837,17 +3915,40 @@ create_table_def(
...
@@ -3837,17 +3915,40 @@ create_table_def(
charset_no
=
(
ulint
)
field
->
charset
()
->
number
;
charset_no
=
(
ulint
)
field
->
charset
()
->
number
;
ut_a
(
charset_no
<
256
);
/* in ut0type.h we assume that
ut_a
(
charset_no
<
256
);
/* in data0type.h we assume
the number fits in one byte */
that the number fits in one
byte */
}
ut_a
(
field
->
type
()
<
256
);
/* we assume in dtype_form_prtype()
that this fits in one byte */
col_len
=
field
->
pack_length
();
/* The MySQL pack length contains 1 or 2 bytes length field
for a true VARCHAR. Let us subtract that, so that the InnoDB
column length in the InnoDB data dictionary is the real
maximum byte length of the actual data. */
long_true_varchar
=
0
;
if
(
field
->
type
()
==
MYSQL_TYPE_VARCHAR
)
{
col_len
-=
((
Field_varstring
*
)
field
)
->
length_bytes
;
if
(((
Field_varstring
*
)
field
)
->
length_bytes
==
2
)
{
long_true_varchar
=
DATA_LONG_TRUE_VARCHAR
;
}
}
}
dict_mem_table_add_col
(
table
,
(
char
*
)
field
->
field_name
,
dict_mem_table_add_col
(
table
,
col_type
,
dtype_form_prtype
(
(
char
*
)
field
->
field_name
,
(
ulint
)
field
->
type
()
col_type
,
|
nulls_allowed
|
unsigned_type
dtype_form_prtype
(
|
binary_type
,
(
ulint
)
field
->
type
()
+
charset_no
),
|
nulls_allowed
|
unsigned_type
field
->
pack_length
(),
0
);
|
binary_type
|
long_true_varchar
,
charset_no
),
col_len
,
0
);
}
}
error
=
row_create_table_for_mysql
(
table
,
trx
);
error
=
row_create_table_for_mysql
(
table
,
trx
);
...
@@ -6125,54 +6226,79 @@ ha_innobase::get_auto_increment()
...
@@ -6125,54 +6226,79 @@ ha_innobase::get_auto_increment()
return
((
ulonglong
)
nr
);
return
((
ulonglong
)
nr
);
}
}
/***********************************************************************
Compares two 'refs'. A 'ref' is the (internal) primary key value of the row.
If there is no explicitly declared non-null unique key or a primary key, then
InnoDB internally uses the row id as the primary key. */
int
int
ha_innobase
::
cmp_ref
(
ha_innobase
::
cmp_ref
(
const
mysql_byte
*
ref1
,
/*=================*/
const
mysql_byte
*
ref2
)
/* out: < 0 if ref1 < ref2, 0 if equal, else
> 0 */
const
mysql_byte
*
ref1
,
/* in: an (internal) primary key value in the
MySQL key value format */
const
mysql_byte
*
ref2
)
/* in: an (internal) primary key value in the
MySQL key value format */
{
{
row_prebuilt_t
*
prebuilt
=
(
row_prebuilt_t
*
)
innobase_prebuilt
;
row_prebuilt_t
*
prebuilt
=
(
row_prebuilt_t
*
)
innobase_prebuilt
;
enum_field_types
mysql_type
;
enum_field_types
mysql_type
;
Field
*
field
;
Field
*
field
;
int
result
;
KEY_PART_INFO
*
key_part
;
KEY_PART_INFO
*
key_part_end
;
if
(
prebuilt
->
clust_index_was_generated
)
uint
len1
;
return
memcmp
(
ref1
,
ref2
,
DATA_ROW_ID_LEN
);
uint
len2
;
int
result
;
/* Do type-aware comparison of Primary Key members. PK members
are always NOT NULL, so no checks for NULL are performed */
if
(
prebuilt
->
clust_index_was_generated
)
{
KEY_PART_INFO
*
key_part
=
/* The 'ref' is an InnoDB row id */
table
->
key_info
[
table
->
s
->
primary_key
].
key_part
;
KEY_PART_INFO
*
key_part_end
=
return
(
memcmp
(
ref1
,
ref2
,
DATA_ROW_ID_LEN
));
key_part
+
table
->
key_info
[
table
->
s
->
primary_key
].
key_parts
;
}
/* Do a type-aware comparison of primary key fields. PK fields
are always NOT NULL, so no checks for NULL are performed. */
key_part
=
table
->
key_info
[
table
->
s
->
primary_key
].
key_part
;
key_part_end
=
key_part
+
table
->
key_info
[
table
->
s
->
primary_key
].
key_parts
;
for
(;
key_part
!=
key_part_end
;
++
key_part
)
{
for
(;
key_part
!=
key_part_end
;
++
key_part
)
{
field
=
key_part
->
field
;
field
=
key_part
->
field
;
mysql_type
=
field
->
type
();
mysql_type
=
field
->
type
();
if
(
mysql_type
==
FIELD_TYPE_TINY_BLOB
if
(
mysql_type
==
FIELD_TYPE_TINY_BLOB
||
mysql_type
==
FIELD_TYPE_MEDIUM_BLOB
||
mysql_type
==
FIELD_TYPE_MEDIUM_BLOB
||
mysql_type
==
FIELD_TYPE_BLOB
||
mysql_type
==
FIELD_TYPE_BLOB
||
mysql_type
==
FIELD_TYPE_LONG_BLOB
)
{
||
mysql_type
==
FIELD_TYPE_LONG_BLOB
)
{
ut_a
(
!
ref1
[
1
]);
/* In the MySQL key value format, a column prefix of
ut_a
(
!
ref2
[
1
]);
a BLOB is preceded by a 2-byte length field */
byte
len1
=
*
ref1
;
byte
len2
=
*
ref2
;
len1
=
innobase_read_from_2_little_endian
(
ref1
);
len2
=
innobase_read_from_2_little_endian
(
ref2
);
ref1
+=
2
;
ref1
+=
2
;
ref2
+=
2
;
ref2
+=
2
;
result
=
result
=
((
Field_blob
*
)
field
)
->
cmp
(
((
Field_blob
*
)
field
)
->
cmp
(
(
const
char
*
)
ref1
,
len1
,
(
const
char
*
)
ref1
,
len1
,
(
const
char
*
)
ref2
,
len2
);
(
const
char
*
)
ref2
,
len2
);
}
else
{
}
else
{
result
=
result
=
field
->
cmp
((
const
char
*
)
ref1
,
field
->
cmp
((
const
char
*
)
ref1
,
(
const
char
*
)
ref2
);
(
const
char
*
)
ref2
);
}
if
(
result
)
{
return
(
result
);
}
}
if
(
result
)
return
result
;
ref1
+=
key_part
->
length
;
ref1
+=
key_part
->
length
;
ref2
+=
key_part
->
length
;
ref2
+=
key_part
->
length
;
}
}
return
0
;
return
(
0
);
}
}
char
*
char
*
...
...
sql/ha_innodb.h
View file @
edf59e54
/* Copyright (C) 2000 MySQL AB && Innobase Oy
/* Copyright (C) 2000
-2005
MySQL AB && Innobase Oy
This program is free software; you can redistribute it and/or modify
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
it under the terms of the GNU General Public License as published by
...
@@ -40,9 +40,10 @@ my_bool innobase_query_caching_of_table_permitted(THD* thd, char* full_name,
...
@@ -40,9 +40,10 @@ my_bool innobase_query_caching_of_table_permitted(THD* thd, char* full_name,
/* The class defining a handle to an Innodb table */
/* The class defining a handle to an Innodb table */
class
ha_innobase
:
public
handler
class
ha_innobase
:
public
handler
{
{
void
*
innobase_prebuilt
;
/* (row_prebuilt_t*) prebuilt
void
*
innobase_prebuilt
;
/* (row_prebuilt_t*) prebuilt
struct in Innodb, used to save
struct in InnoDB, used to save
CPU */
CPU time with prebuilt data
structures*/
THD
*
user_thd
;
/* the thread handle of the user
THD
*
user_thd
;
/* the thread handle of the user
currently using the handle; this is
currently using the handle; this is
set in external_lock function */
set in external_lock function */
...
@@ -83,12 +84,12 @@ class ha_innobase: public handler
...
@@ -83,12 +84,12 @@ class ha_innobase: public handler
public:
public:
ha_innobase
(
TABLE
*
table
)
:
handler
(
table
),
ha_innobase
(
TABLE
*
table
)
:
handler
(
table
),
int_table_flags
(
HA_REC_NOT_IN_SEQ
|
int_table_flags
(
HA_REC_NOT_IN_SEQ
|
HA_NULL_IN_KEY
|
HA_FAST_KEY_READ
|
HA_NULL_IN_KEY
|
HA_FAST_KEY_READ
|
HA_CAN_INDEX_BLOBS
|
HA_CAN_INDEX_BLOBS
|
HA_CAN_SQL_HANDLER
|
HA_CAN_SQL_HANDLER
|
HA_NOT_EXACT_COUNT
|
HA_NOT_EXACT_COUNT
|
HA_PRIMARY_KEY_IN_READ_INDEX
|
HA_PRIMARY_KEY_IN_READ_INDEX
|
HA_NO_VARCHAR
|
HA_TABLE_SCAN_ON_INDEX
),
HA_TABLE_SCAN_ON_INDEX
),
last_dup_key
((
uint
)
-
1
),
last_dup_key
((
uint
)
-
1
),
start_of_scan
(
0
),
start_of_scan
(
0
),
...
@@ -108,7 +109,10 @@ class ha_innobase: public handler
...
@@ -108,7 +109,10 @@ class ha_innobase: public handler
ulong
table_flags
()
const
{
return
int_table_flags
;
}
ulong
table_flags
()
const
{
return
int_table_flags
;
}
ulong
index_flags
(
uint
idx
,
uint
part
,
bool
all_parts
)
const
ulong
index_flags
(
uint
idx
,
uint
part
,
bool
all_parts
)
const
{
{
return
(
HA_READ_NEXT
|
HA_READ_PREV
|
HA_READ_ORDER
|
HA_READ_RANGE
|
return
(
HA_READ_NEXT
|
HA_READ_PREV
|
HA_READ_ORDER
|
HA_READ_RANGE
|
HA_KEYREAD_ONLY
);
HA_KEYREAD_ONLY
);
}
}
uint
max_supported_keys
()
const
{
return
MAX_KEY
;
}
uint
max_supported_keys
()
const
{
return
MAX_KEY
;
}
...
@@ -163,7 +167,8 @@ class ha_innobase: public handler
...
@@ -163,7 +167,8 @@ class ha_innobase: public handler
int
start_stmt
(
THD
*
thd
);
int
start_stmt
(
THD
*
thd
);
void
position
(
byte
*
record
);
void
position
(
byte
*
record
);
ha_rows
records_in_range
(
uint
inx
,
key_range
*
min_key
,
key_range
*
max_key
);
ha_rows
records_in_range
(
uint
inx
,
key_range
*
min_key
,
key_range
*
max_key
);
ha_rows
estimate_rows_upper_bound
();
ha_rows
estimate_rows_upper_bound
();
int
create
(
const
char
*
name
,
register
TABLE
*
form
,
int
create
(
const
char
*
name
,
register
TABLE
*
form
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment