Commit 38555201 authored by bar@mysql.com's avatar bar@mysql.com

WL#2928 Date Translation NRE

(implemented by by Josh Chamas)
parent 61348cac
...@@ -1058,3 +1058,4 @@ vio/test-sslclient ...@@ -1058,3 +1058,4 @@ vio/test-sslclient
vio/test-sslserver vio/test-sslserver
vio/viotest-ssl vio/viotest-ssl
libmysql/libmysql.ver libmysql/libmysql.ver
libmysqld/sql_locale.cc
...@@ -50,7 +50,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ ...@@ -50,7 +50,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
key.cc lock.cc log.cc log_event.cc sql_state.c \ key.cc lock.cc log.cc log_event.cc sql_state.c \
protocol.cc net_serv.cc opt_range.cc \ protocol.cc net_serv.cc opt_range.cc \
opt_sum.cc procedure.cc records.cc sql_acl.cc \ opt_sum.cc procedure.cc records.cc sql_acl.cc \
sql_load.cc discover.cc \ sql_load.cc discover.cc sql_locale.cc \
sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc \ sql_analyse.cc sql_base.cc sql_cache.cc sql_class.cc \
sql_crypt.cc sql_db.cc sql_delete.cc sql_error.cc sql_insert.cc \ sql_crypt.cc sql_db.cc sql_delete.cc sql_error.cc sql_insert.cc \
sql_lex.cc sql_list.cc sql_manager.cc sql_map.cc sql_parse.cc \ sql_lex.cc sql_list.cc sql_manager.cc sql_map.cc sql_parse.cc \
......
...@@ -456,6 +456,22 @@ f1 f2 ...@@ -456,6 +456,22 @@ f1 f2
Warnings: Warnings:
Warning 1292 Truncated incorrect date value: '2003-04-05 g' Warning 1292 Truncated incorrect date value: '2003-04-05 g'
Warning 1292 Truncated incorrect datetime value: '2003-04-05 10:11:12.101010234567' Warning 1292 Truncated incorrect datetime value: '2003-04-05 10:11:12.101010234567'
set names latin1;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
date_format('2004-01-01','%W (%a), %e %M (%b) %Y')
Thursday (Thu), 1 January (Jan) 2004
set lc_time_names=ru_RU;
set names koi8r;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
date_format('2004-01-01','%W (%a), %e %M (%b) %Y')
(), 1 () 2004
set lc_time_names=de_DE;
set names latin1;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
date_format('2004-01-01','%W (%a), %e %M (%b) %Y')
Donnerstag (Do), 1 Januar (Jan) 2004
set names latin1;
set lc_time_names=en_US;
create table t1 (f1 datetime); create table t1 (f1 datetime);
insert into t1 (f1) values ("2005-01-01"); insert into t1 (f1) values ("2005-01-01");
insert into t1 (f1) values ("2005-02-01"); insert into t1 (f1) values ("2005-02-01");
......
...@@ -260,6 +260,20 @@ select str_to_date("2003-04-05 g", "%Y-%m-%d") as f1, ...@@ -260,6 +260,20 @@ select str_to_date("2003-04-05 g", "%Y-%m-%d") as f1,
str_to_date("2003-04-05 10:11:12.101010234567", "%Y-%m-%d %H:%i:%S.%f") as f2; str_to_date("2003-04-05 10:11:12.101010234567", "%Y-%m-%d %H:%i:%S.%f") as f2;
--enable_ps_protocol --enable_ps_protocol
#
# Test of locale dependent date format (WL#2928 Date Translation NRE)
#
set names latin1;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
set lc_time_names=ru_RU;
set names koi8r;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
set lc_time_names=de_DE;
set names latin1;
select date_format('2004-01-01','%W (%a), %e %M (%b) %Y');
set names latin1;
set lc_time_names=en_US;
# #
# Bug #14016 # Bug #14016
# #
......
...@@ -73,7 +73,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \ ...@@ -73,7 +73,7 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc \
mysqld.cc password.c hash_filo.cc hostname.cc \ mysqld.cc password.c hash_filo.cc hostname.cc \
set_var.cc sql_parse.cc sql_yacc.yy \ set_var.cc sql_parse.cc sql_yacc.yy \
sql_base.cc table.cc sql_select.cc sql_insert.cc \ sql_base.cc table.cc sql_select.cc sql_insert.cc \
sql_prepare.cc sql_error.cc \ sql_prepare.cc sql_error.cc sql_locale.cc \
sql_update.cc sql_delete.cc uniques.cc sql_do.cc \ sql_update.cc sql_delete.cc uniques.cc sql_do.cc \
procedure.cc item_uniq.cc sql_test.cc \ procedure.cc item_uniq.cc sql_test.cc \
log.cc log_event.cc init.cc derror.cc sql_acl.cc \ log.cc log_event.cc init.cc derror.cc sql_acl.cc \
......
...@@ -30,25 +30,6 @@ ...@@ -30,25 +30,6 @@
/* Day number for Dec 31st, 9999 */ /* Day number for Dec 31st, 9999 */
#define MAX_DAY_NUMBER 3652424L #define MAX_DAY_NUMBER 3652424L
static const char *month_names[]=
{
"January", "February", "March", "April", "May", "June", "July", "August",
"September", "October", "November", "December", NullS
};
TYPELIB month_names_typelib=
{ array_elements(month_names)-1,"", month_names, NULL };
static const char *day_names[]=
{
"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" ,"Sunday", NullS
};
TYPELIB day_names_typelib=
{ array_elements(day_names)-1,"", day_names, NULL};
/* /*
OPTIMIZATION TODO: OPTIMIZATION TODO:
- Replace the switch with a function that should be called for each - Replace the switch with a function that should be called for each
...@@ -222,8 +203,12 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, ...@@ -222,8 +203,12 @@ static bool extract_date_time(DATE_TIME_FORMAT *format,
val= tmp; val= tmp;
break; break;
case 'M': case 'M':
if ((l_time->month= check_word(my_locale_en_US.month_names,
val, val_end, &val)) <= 0)
goto err;
break;
case 'b': case 'b':
if ((l_time->month= check_word(&month_names_typelib, if ((l_time->month= check_word(my_locale_en_US.ab_month_names,
val, val_end, &val)) <= 0) val, val_end, &val)) <= 0)
goto err; goto err;
break; break;
...@@ -298,8 +283,11 @@ static bool extract_date_time(DATE_TIME_FORMAT *format, ...@@ -298,8 +283,11 @@ static bool extract_date_time(DATE_TIME_FORMAT *format,
/* Exotic things */ /* Exotic things */
case 'W': case 'W':
if ((weekday= check_word(my_locale_en_US.day_names, val, val_end, &val)) <= 0)
goto err;
break;
case 'a': case 'a':
if ((weekday= check_word(&day_names_typelib, val, val_end, &val)) <= 0) if ((weekday= check_word(my_locale_en_US.ab_day_names, val, val_end, &val)) <= 0)
goto err; goto err;
break; break;
case 'w': case 'w':
...@@ -489,9 +477,15 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time, ...@@ -489,9 +477,15 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time,
uint weekday; uint weekday;
ulong length; ulong length;
const char *ptr, *end; const char *ptr, *end;
MY_LOCALE *locale;
THD *thd= current_thd;
char buf[128];
String tmp(buf, thd->variables.character_set_results);
uint errors= 0;
str->length(0); str->length(0);
str->set_charset(&my_charset_bin); str->set_charset(&my_charset_bin);
locale = thd->variables.lc_time_names;
if (l_time->neg) if (l_time->neg)
str->append("-", 1); str->append("-", 1);
...@@ -507,26 +501,38 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time, ...@@ -507,26 +501,38 @@ bool make_date_time(DATE_TIME_FORMAT *format, TIME *l_time,
case 'M': case 'M':
if (!l_time->month) if (!l_time->month)
return 1; return 1;
str->append(month_names[l_time->month-1]); tmp.copy(locale->month_names->type_names[l_time->month-1],
strlen(locale->month_names->type_names[l_time->month-1]),
system_charset_info, tmp.charset(), &errors);
str->append(tmp.ptr(), tmp.length());
break; break;
case 'b': case 'b':
if (!l_time->month) if (!l_time->month)
return 1; return 1;
str->append(month_names[l_time->month-1],3); tmp.copy(locale->ab_month_names->type_names[l_time->month-1],
strlen(locale->ab_month_names->type_names[l_time->month-1]),
system_charset_info, tmp.charset(), &errors);
str->append(tmp.ptr(), tmp.length());
break; break;
case 'W': case 'W':
if (type == MYSQL_TIMESTAMP_TIME) if (type == MYSQL_TIMESTAMP_TIME)
return 1; return 1;
weekday= calc_weekday(calc_daynr(l_time->year,l_time->month, weekday= calc_weekday(calc_daynr(l_time->year,l_time->month,
l_time->day),0); l_time->day),0);
str->append(day_names[weekday]); tmp.copy(locale->day_names->type_names[weekday],
strlen(locale->day_names->type_names[weekday]),
system_charset_info, tmp.charset(), &errors);
str->append(tmp.ptr(), tmp.length());
break; break;
case 'a': case 'a':
if (type == MYSQL_TIMESTAMP_TIME) if (type == MYSQL_TIMESTAMP_TIME)
return 1; return 1;
weekday=calc_weekday(calc_daynr(l_time->year,l_time->month, weekday=calc_weekday(calc_daynr(l_time->year,l_time->month,
l_time->day),0); l_time->day),0);
str->append(day_names[weekday],3); tmp.copy(locale->ab_day_names->type_names[weekday],
strlen(locale->ab_day_names->type_names[weekday]),
system_charset_info, tmp.charset(), &errors);
str->append(tmp.ptr(), tmp.length());
break; break;
case 'D': case 'D':
if (type == MYSQL_TIMESTAMP_TIME) if (type == MYSQL_TIMESTAMP_TIME)
...@@ -908,6 +914,7 @@ String* Item_func_monthname::val_str(String* str) ...@@ -908,6 +914,7 @@ String* Item_func_monthname::val_str(String* str)
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
const char *month_name; const char *month_name;
uint month= (uint) val_int(); uint month= (uint) val_int();
THD *thd= current_thd;
if (null_value || !month) if (null_value || !month)
{ {
...@@ -915,7 +922,7 @@ String* Item_func_monthname::val_str(String* str) ...@@ -915,7 +922,7 @@ String* Item_func_monthname::val_str(String* str)
return (String*) 0; return (String*) 0;
} }
null_value=0; null_value=0;
month_name= month_names[month-1]; month_name= thd->variables.lc_time_names->month_names->type_names[month-1];
str->set(month_name, strlen(month_name), system_charset_info); str->set(month_name, strlen(month_name), system_charset_info);
return str; return str;
} }
...@@ -1039,11 +1046,12 @@ String* Item_func_dayname::val_str(String* str) ...@@ -1039,11 +1046,12 @@ String* Item_func_dayname::val_str(String* str)
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
uint weekday=(uint) val_int(); // Always Item_func_daynr() uint weekday=(uint) val_int(); // Always Item_func_daynr()
const char *name; const char *name;
THD *thd= current_thd;
if (null_value) if (null_value)
return (String*) 0; return (String*) 0;
name= day_names[weekday]; name= thd->variables.lc_time_names->day_names->type_names[weekday];
str->set(name, strlen(name), system_charset_info); str->set(name, strlen(name), system_charset_info);
return str; return str;
} }
...@@ -1572,7 +1580,7 @@ uint Item_func_date_format::format_length(const String *format) ...@@ -1572,7 +1580,7 @@ uint Item_func_date_format::format_length(const String *format)
switch(*++ptr) { switch(*++ptr) {
case 'M': /* month, textual */ case 'M': /* month, textual */
case 'W': /* day (of the week), textual */ case 'W': /* day (of the week), textual */
size += 9; size += 64; /* large for UTF8 locale data */
break; break;
case 'D': /* day (of the month), numeric plus english suffix */ case 'D': /* day (of the month), numeric plus english suffix */
case 'Y': /* year, numeric, 4 digits */ case 'Y': /* year, numeric, 4 digits */
...@@ -1582,6 +1590,8 @@ uint Item_func_date_format::format_length(const String *format) ...@@ -1582,6 +1590,8 @@ uint Item_func_date_format::format_length(const String *format)
break; break;
case 'a': /* locale's abbreviated weekday name (Sun..Sat) */ case 'a': /* locale's abbreviated weekday name (Sun..Sat) */
case 'b': /* locale's abbreviated month name (Jan.Dec) */ case 'b': /* locale's abbreviated month name (Jan.Dec) */
size += 32; /* large for UTF8 locale data */
break;
case 'j': /* day of year (001..366) */ case 'j': /* day of year (001..366) */
size += 3; size += 3;
break; break;
......
...@@ -68,6 +68,23 @@ char* query_table_status(THD *thd,const char *db,const char *table_name); ...@@ -68,6 +68,23 @@ char* query_table_status(THD *thd,const char *db,const char *table_name);
extern CHARSET_INFO *system_charset_info, *files_charset_info ; extern CHARSET_INFO *system_charset_info, *files_charset_info ;
extern CHARSET_INFO *national_charset_info, *table_alias_charset; extern CHARSET_INFO *national_charset_info, *table_alias_charset;
typedef struct my_locale_st
{
const char *name;
const char *description;
const bool is_ascii;
TYPELIB *month_names;
TYPELIB *ab_month_names;
TYPELIB *day_names;
TYPELIB *ab_day_names;
} MY_LOCALE;
extern MY_LOCALE my_locale_en_US;
extern MY_LOCALE *my_locales[];
MY_LOCALE *my_locale_by_name(const char *name);
/*************************************************************************** /***************************************************************************
Configuration parameters Configuration parameters
****************************************************************************/ ****************************************************************************/
...@@ -407,6 +424,7 @@ struct Query_cache_query_flags ...@@ -407,6 +424,7 @@ struct Query_cache_query_flags
ulong sql_mode; ulong sql_mode;
ulong max_sort_length; ulong max_sort_length;
ulong group_concat_max_len; ulong group_concat_max_len;
MY_LOCALE *lc_time_names;
}; };
#define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags) #define QUERY_CACHE_FLAGS_SIZE sizeof(Query_cache_query_flags)
#include "sql_cache.h" #include "sql_cache.h"
......
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
#include <my_getopt.h> #include <my_getopt.h>
#include <thr_alarm.h> #include <thr_alarm.h>
#include <myisam.h> #include <myisam.h>
#ifdef HAVE_BERKELEY_DB #ifdef HAVE_BERKELEY_DB
#include "ha_berkeley.h" #include "ha_berkeley.h"
#endif #endif
...@@ -468,6 +469,9 @@ static sys_var_thd_ha_rows sys_select_limit("sql_select_limit", ...@@ -468,6 +469,9 @@ static sys_var_thd_ha_rows sys_select_limit("sql_select_limit",
static sys_var_timestamp sys_timestamp("timestamp"); static sys_var_timestamp sys_timestamp("timestamp");
static sys_var_last_insert_id sys_last_insert_id("last_insert_id"); static sys_var_last_insert_id sys_last_insert_id("last_insert_id");
static sys_var_last_insert_id sys_identity("identity"); static sys_var_last_insert_id sys_identity("identity");
static sys_var_thd_lc_time_names sys_lc_time_names("lc_time_names");
static sys_var_insert_id sys_insert_id("insert_id"); static sys_var_insert_id sys_insert_id("insert_id");
static sys_var_readonly sys_error_count("error_count", static sys_var_readonly sys_error_count("error_count",
OPT_SESSION, OPT_SESSION,
...@@ -558,6 +562,7 @@ sys_var *sys_variables[]= ...@@ -558,6 +562,7 @@ sys_var *sys_variables[]=
&sys_key_cache_division_limit, &sys_key_cache_division_limit,
&sys_key_cache_age_threshold, &sys_key_cache_age_threshold,
&sys_last_insert_id, &sys_last_insert_id,
&sys_lc_time_names,
&sys_license, &sys_license,
&sys_local_infile, &sys_local_infile,
&sys_log_binlog, &sys_log_binlog,
...@@ -780,6 +785,7 @@ struct show_var_st init_vars[]= { ...@@ -780,6 +785,7 @@ struct show_var_st init_vars[]= {
SHOW_SYS}, SHOW_SYS},
{"language", language, SHOW_CHAR}, {"language", language, SHOW_CHAR},
{"large_files_support", (char*) &opt_large_files, SHOW_BOOL}, {"large_files_support", (char*) &opt_large_files, SHOW_BOOL},
{sys_lc_time_names.name, (char*) &sys_lc_time_names, SHOW_SYS},
{sys_license.name, (char*) &sys_license, SHOW_SYS}, {sys_license.name, (char*) &sys_license, SHOW_SYS},
{sys_local_infile.name, (char*) &sys_local_infile, SHOW_SYS}, {sys_local_infile.name, (char*) &sys_local_infile, SHOW_SYS},
#ifdef HAVE_MLOCKALL #ifdef HAVE_MLOCKALL
...@@ -2562,6 +2568,43 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type) ...@@ -2562,6 +2568,43 @@ void sys_var_thd_time_zone::set_default(THD *thd, enum_var_type type)
pthread_mutex_unlock(&LOCK_global_system_variables); pthread_mutex_unlock(&LOCK_global_system_variables);
} }
bool sys_var_thd_lc_time_names::check(THD *thd, set_var *var)
{
char *locale_str =var->value->str_value.c_ptr();
MY_LOCALE *locale_match= my_locale_by_name(locale_str);
if(locale_match == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, "Unknown locale: '%s'", MYF(0), locale_str);
return 1;
}
else
{
var->save_result.locale_value= locale_match;
return 0;
}
}
bool sys_var_thd_lc_time_names::update(THD *thd, set_var *var)
{
thd->variables.lc_time_names= var->save_result.locale_value;
return 0;
}
byte *sys_var_thd_lc_time_names::value_ptr(THD *thd, enum_var_type type,
LEX_STRING *base)
{
return (byte *)(thd->variables.lc_time_names->name);
}
void sys_var_thd_lc_time_names::set_default(THD *thd, enum_var_type type)
{
thd->variables.lc_time_names = &my_locale_en_US;
}
/* /*
Functions to update thd->options bits Functions to update thd->options bits
*/ */
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
class sys_var; class sys_var;
class set_var; class set_var;
typedef struct system_variables SV; typedef struct system_variables SV;
typedef struct my_locale_st MY_LOCALE;
extern TYPELIB bool_typelib, delay_key_write_typelib, sql_mode_typelib; extern TYPELIB bool_typelib, delay_key_write_typelib, sql_mode_typelib;
typedef int (*sys_check_func)(THD *, set_var *); typedef int (*sys_check_func)(THD *, set_var *);
...@@ -757,6 +759,24 @@ class sys_var_thd_time_zone :public sys_var_thd ...@@ -757,6 +759,24 @@ class sys_var_thd_time_zone :public sys_var_thd
virtual void set_default(THD *thd, enum_var_type type); virtual void set_default(THD *thd, enum_var_type type);
}; };
class sys_var_thd_lc_time_names :public sys_var_thd
{
public:
sys_var_thd_lc_time_names(const char *name_arg):
sys_var_thd(name_arg)
{}
bool check(THD *thd, set_var *var);
SHOW_TYPE type() { return SHOW_CHAR; }
bool check_update_type(Item_result type)
{
return type != STRING_RESULT; /* Only accept strings */
}
bool check_default(enum_var_type type) { return 0; }
bool update(THD *thd, set_var *var);
byte *value_ptr(THD *thd, enum_var_type type, LEX_STRING *base);
virtual void set_default(THD *thd, enum_var_type type);
};
/**************************************************************************** /****************************************************************************
Classes for parsing of the SET command Classes for parsing of the SET command
****************************************************************************/ ****************************************************************************/
...@@ -791,6 +811,7 @@ class set_var :public set_var_base ...@@ -791,6 +811,7 @@ class set_var :public set_var_base
ulonglong ulonglong_value; ulonglong ulonglong_value;
DATE_TIME_FORMAT *date_time_format; DATE_TIME_FORMAT *date_time_format;
Time_zone *time_zone; Time_zone *time_zone;
MY_LOCALE *locale_value;
} save_result; } save_result;
LEX_STRING base; /* for structs */ LEX_STRING base; /* for structs */
......
...@@ -813,6 +813,7 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) ...@@ -813,6 +813,7 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used)
flags.sql_mode= thd->variables.sql_mode; flags.sql_mode= thd->variables.sql_mode;
flags.max_sort_length= thd->variables.max_sort_length; flags.max_sort_length= thd->variables.max_sort_length;
flags.group_concat_max_len= thd->variables.group_concat_max_len; flags.group_concat_max_len= thd->variables.group_concat_max_len;
flags.lc_time_names= thd->variables.lc_time_names;
STRUCT_LOCK(&structure_guard_mutex); STRUCT_LOCK(&structure_guard_mutex);
if (query_cache_size == 0) if (query_cache_size == 0)
...@@ -1015,6 +1016,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length) ...@@ -1015,6 +1016,7 @@ Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
flags.sql_mode= thd->variables.sql_mode; flags.sql_mode= thd->variables.sql_mode;
flags.max_sort_length= thd->variables.max_sort_length; flags.max_sort_length= thd->variables.max_sort_length;
flags.group_concat_max_len= thd->variables.group_concat_max_len; flags.group_concat_max_len= thd->variables.group_concat_max_len;
flags.lc_time_names= thd->variables.lc_time_names;
memcpy((void *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)), memcpy((void *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)),
&flags, QUERY_CACHE_FLAGS_SIZE); &flags, QUERY_CACHE_FLAGS_SIZE);
query_block = (Query_cache_block *) hash_search(&queries, (byte*) sql, query_block = (Query_cache_block *) hash_search(&queries, (byte*) sql,
......
...@@ -297,6 +297,7 @@ void THD::init(void) ...@@ -297,6 +297,7 @@ void THD::init(void)
bzero((char*) warn_count, sizeof(warn_count)); bzero((char*) warn_count, sizeof(warn_count));
total_warn_count= 0; total_warn_count= 0;
update_charset(); update_charset();
variables.lc_time_names = &my_locale_en_US;
} }
......
...@@ -423,6 +423,9 @@ struct system_variables ...@@ -423,6 +423,9 @@ struct system_variables
CHARSET_INFO *collation_database; CHARSET_INFO *collation_database;
CHARSET_INFO *collation_connection; CHARSET_INFO *collation_connection;
/* Locale Support */
MY_LOCALE *lc_time_names;
Time_zone *time_zone; Time_zone *time_zone;
/* DATE, DATETIME and TIME formats */ /* DATE, DATETIME and TIME formats */
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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