diff options
Diffstat (limited to 'sql-common')
-rw-r--r-- | sql-common/client.c | 327 | ||||
-rw-r--r-- | sql-common/my_time.c | 778 |
2 files changed, 629 insertions, 476 deletions
diff --git a/sql-common/client.c b/sql-common/client.c index 6d3b5a69b44..86b05a206a8 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -127,10 +127,6 @@ char *mysql_unix_port= 0; const char *unknown_sqlstate= "HY000"; const char *not_error_sqlstate= "00000"; const char *cant_connect_sqlstate= "08001"; -#ifdef HAVE_SMEM -char *shared_memory_base_name= 0; -const char *def_shared_memory_base_name= default_shared_memory_base_name; -#endif static void mysql_close_free_options(MYSQL *mysql); static void mysql_close_free(MYSQL *mysql); @@ -326,248 +322,6 @@ HANDLE create_named_pipe(MYSQL *mysql, uint connect_timeout, char **arg_host, #endif -/* - Create new shared memory connection, return handler of connection - - SYNOPSIS - create_shared_memory() - mysql Pointer of mysql structure - net Pointer of net structure - connect_timeout Timeout of connection -*/ - -#ifdef HAVE_SMEM -HANDLE create_shared_memory(MYSQL *mysql,NET *net, uint connect_timeout) -{ - ulong smem_buffer_length = shared_memory_buffer_length + 4; - /* - event_connect_request is event object for start connection actions - event_connect_answer is event object for confirm, that server put data - handle_connect_file_map is file-mapping object, use for create shared - memory - handle_connect_map is pointer on shared memory - handle_map is pointer on shared memory for client - event_server_wrote, - event_server_read, - event_client_wrote, - event_client_read are events for transfer data between server and client - handle_file_map is file-mapping object, use for create shared memory - */ - HANDLE event_connect_request = NULL; - HANDLE event_connect_answer = NULL; - HANDLE handle_connect_file_map = NULL; - char *handle_connect_map = NULL; - - char *handle_map = NULL; - HANDLE event_server_wrote = NULL; - HANDLE event_server_read = NULL; - HANDLE event_client_wrote = NULL; - HANDLE event_client_read = NULL; - HANDLE event_conn_closed = NULL; - HANDLE handle_file_map = NULL; - ulong connect_number; - char connect_number_char[22], *p; - char *tmp= NULL; - char *suffix_pos; - DWORD error_allow = 0; - DWORD error_code = 0; - DWORD event_access_rights= SYNCHRONIZE | EVENT_MODIFY_STATE; - char *shared_memory_base_name = mysql->options.shared_memory_base_name; - static const char *name_prefixes[] = {"","Global\\"}; - const char *prefix; - uint i; - - /* - If this is NULL, somebody freed the MYSQL* options. mysql_close() - is a good candidate. We don't just silently (re)set it to - def_shared_memory_base_name as that would create really confusing/buggy - behavior if the user passed in a different name on the command-line or - in a my.cnf. - */ - DBUG_ASSERT(shared_memory_base_name != NULL); - - /* - get enough space base-name + '_' + longest suffix we might ever send - */ - if (!(tmp= (char *)my_malloc(strlen(shared_memory_base_name) + 32L, MYF(MY_FAE)))) - goto err; - - /* - The name of event and file-mapping events create agree next rule: - shared_memory_base_name+unique_part - Where: - shared_memory_base_name is unique value for each server - unique_part is uniquel value for each object (events and file-mapping) - */ - for (i = 0; i< array_elements(name_prefixes); i++) - { - prefix= name_prefixes[i]; - suffix_pos = strxmov(tmp, prefix , shared_memory_base_name, "_", NullS); - strmov(suffix_pos, "CONNECT_REQUEST"); - event_connect_request= OpenEvent(event_access_rights, FALSE, tmp); - if (event_connect_request) - { - break; - } - } - if (!event_connect_request) - { - error_allow = CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR; - goto err; - } - strmov(suffix_pos, "CONNECT_ANSWER"); - if (!(event_connect_answer= OpenEvent(event_access_rights,FALSE,tmp))) - { - error_allow = CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR; - goto err; - } - strmov(suffix_pos, "CONNECT_DATA"); - if (!(handle_connect_file_map= OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp))) - { - error_allow = CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR; - goto err; - } - if (!(handle_connect_map= MapViewOfFile(handle_connect_file_map, - FILE_MAP_WRITE,0,0,sizeof(DWORD)))) - { - error_allow = CR_SHARED_MEMORY_CONNECT_MAP_ERROR; - goto err; - } - - /* Send to server request of connection */ - if (!SetEvent(event_connect_request)) - { - error_allow = CR_SHARED_MEMORY_CONNECT_SET_ERROR; - goto err; - } - - /* Wait of answer from server */ - if (WaitForSingleObject(event_connect_answer,connect_timeout*1000) != - WAIT_OBJECT_0) - { - error_allow = CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR; - goto err; - } - - /* Get number of connection */ - connect_number = uint4korr(handle_connect_map);/*WAX2*/ - p= int10_to_str(connect_number, connect_number_char, 10); - - /* - The name of event and file-mapping events create agree next rule: - shared_memory_base_name+unique_part+number_of_connection - - Where: - shared_memory_base_name is uniquel value for each server - unique_part is uniquel value for each object (events and file-mapping) - number_of_connection is number of connection between server and client - */ - suffix_pos = strxmov(tmp, prefix , shared_memory_base_name, "_", connect_number_char, - "_", NullS); - strmov(suffix_pos, "DATA"); - if ((handle_file_map = OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_FILE_MAP_ERROR; - goto err2; - } - if ((handle_map = MapViewOfFile(handle_file_map,FILE_MAP_WRITE,0,0, - smem_buffer_length)) == NULL) - { - error_allow = CR_SHARED_MEMORY_MAP_ERROR; - goto err2; - } - - strmov(suffix_pos, "SERVER_WROTE"); - if ((event_server_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_EVENT_ERROR; - goto err2; - } - - strmov(suffix_pos, "SERVER_READ"); - if ((event_server_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_EVENT_ERROR; - goto err2; - } - - strmov(suffix_pos, "CLIENT_WROTE"); - if ((event_client_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_EVENT_ERROR; - goto err2; - } - - strmov(suffix_pos, "CLIENT_READ"); - if ((event_client_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_EVENT_ERROR; - goto err2; - } - - strmov(suffix_pos, "CONNECTION_CLOSED"); - if ((event_conn_closed = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) - { - error_allow = CR_SHARED_MEMORY_EVENT_ERROR; - goto err2; - } - /* - Set event that server should send data - */ - SetEvent(event_server_read); - -err2: - if (error_allow == 0) - { - net->vio= vio_new_win32shared_memory(handle_file_map,handle_map, - event_server_wrote, - event_server_read,event_client_wrote, - event_client_read,event_conn_closed); - } - else - { - error_code = GetLastError(); - if (event_server_read) - CloseHandle(event_server_read); - if (event_server_wrote) - CloseHandle(event_server_wrote); - if (event_client_read) - CloseHandle(event_client_read); - if (event_client_wrote) - CloseHandle(event_client_wrote); - if (event_conn_closed) - CloseHandle(event_conn_closed); - if (handle_map) - UnmapViewOfFile(handle_map); - if (handle_file_map) - CloseHandle(handle_file_map); - } -err: - my_free(tmp); - if (error_allow) - error_code = GetLastError(); - if (event_connect_request) - CloseHandle(event_connect_request); - if (event_connect_answer) - CloseHandle(event_connect_answer); - if (handle_connect_map) - UnmapViewOfFile(handle_connect_map); - if (handle_connect_file_map) - CloseHandle(handle_connect_file_map); - if (error_allow) - { - if (error_allow == CR_SHARED_MEMORY_EVENT_ERROR) - set_mysql_extended_error(mysql, error_allow, unknown_sqlstate, - ER(error_allow), suffix_pos, error_code); - else - set_mysql_extended_error(mysql, error_allow, unknown_sqlstate, - ER(error_allow), error_code); - return(INVALID_HANDLE_VALUE); - } - return(handle_map); -} -#endif - /** Read a packet from server. Give error message if socket was down or packet is an error message @@ -987,7 +741,7 @@ static const char *default_options[]= "ssl-key" ,"ssl-cert" ,"ssl-ca" ,"ssl-capath", "character-sets-dir", "default-character-set", "interactive-timeout", "connect-timeout", "local-infile", "disable-local-infile", - "ssl-cipher", "max-allowed-packet", "protocol", "shared-memory-base-name", + "ssl-cipher", "max-allowed-packet", "protocol", "multi-results", "multi-statements", "multi-queries", "secure-auth", "report-data-truncation", "plugin-dir", "default-auth", "bind-address", "ssl-crl", "ssl-crlpath", @@ -1000,7 +754,7 @@ enum option_id { OPT_ssl_key, OPT_ssl_cert, OPT_ssl_ca, OPT_ssl_capath, OPT_character_sets_dir, OPT_default_character_set, OPT_interactive_timeout, OPT_connect_timeout, OPT_local_infile, OPT_disable_local_infile, - OPT_ssl_cipher, OPT_max_allowed_packet, OPT_protocol, OPT_shared_memory_base_name, + OPT_ssl_cipher, OPT_max_allowed_packet, OPT_protocol, OPT_multi_results, OPT_multi_statements, OPT_multi_queries, OPT_secure_auth, OPT_report_data_truncation, OPT_plugin_dir, OPT_default_auth, OPT_bind_address, OPT_ssl_crl, OPT_ssl_crlpath, @@ -1241,13 +995,6 @@ void mysql_read_default_options(struct st_mysql_options *options, options->protocol= UINT_MAX32; } break; - case OPT_shared_memory_base_name: -#ifdef HAVE_SMEM - if (options->shared_memory_base_name != def_shared_memory_base_name) - my_free(options->shared_memory_base_name); - options->shared_memory_base_name=my_strdup(opt_arg,MYF(MY_WME)); -#endif - break; case OPT_multi_results: options->client_flag|= CLIENT_MULTI_RESULTS; break; @@ -1640,10 +1387,6 @@ mysql_init(MYSQL *mysql) ? WAIT_FOR_QUERY : ALWAYS_ACCEPT; #endif -#ifdef HAVE_SMEM - mysql->options.shared_memory_base_name= (char*) def_shared_memory_base_name; -#endif - mysql->options.methods_to_use= MYSQL_OPT_GUESS_CONNECTION; mysql->options.report_data_truncation= TRUE; /* default */ @@ -1833,9 +1576,15 @@ static int ssl_verify_server_cert(Vio *vio, const char* server_hostname, const c #ifdef HAVE_X509_check_host ret_validation= - (X509_check_host(server_cert, server_hostname, - strlen(server_hostname), 0, 0) != 1) && - (X509_check_ip_asc(server_cert, server_hostname, 0) != 1); + X509_check_host(server_cert, server_hostname, + strlen(server_hostname), 0, 0) != 1; +#ifndef HAVE_WOLFSSL + if (ret_validation) + { + ret_validation= + X509_check_ip_asc(server_cert, server_hostname, 0) != 1; + } +#endif #else subject= X509_get_subject_name(server_cert); cn_loc= X509_NAME_get_index_by_NID(subject, NID_commonName, -1); @@ -2610,12 +2359,6 @@ void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info) info->protocol= MYSQL_VIO_PIPE; info->handle= vio->hPipe; return; - case VIO_TYPE_SHARED_MEMORY: - info->protocol= MYSQL_VIO_MEMORY; -#ifdef HAVE_SMEM - info->handle= vio->handle_file_map; /* or what ? */ -#endif - return; #endif default: DBUG_ASSERT(0); } @@ -2950,42 +2693,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, /* Part 0: Grab a socket and connect it to the server */ -#if defined(HAVE_SMEM) - if ((!mysql->options.protocol || - mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) && - (!host || !strcmp(host,LOCAL_HOST)) && - mysql->options.shared_memory_base_name) - { - DBUG_PRINT("info", ("Using shared memory")); - if ((create_shared_memory(mysql,net, mysql->options.connect_timeout)) == - INVALID_HANDLE_VALUE) - { - DBUG_PRINT("error", - ("host: '%s' socket: '%s' shared memory: %s have_tcpip: %d", - host ? host : "<null>", - unix_socket ? unix_socket : "<null>", - mysql->options.shared_memory_base_name, - (int) have_tcpip)); - if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) - goto error; - /* - Try also with PIPE or TCP/IP. Clear the error from - create_shared_memory(). - */ - - net_clear_error(net); - } - else - { - mysql->options.protocol=MYSQL_PROTOCOL_MEMORY; - unix_socket = 0; - host=mysql->options.shared_memory_base_name; - my_snprintf(host_info=buff, sizeof(buff)-1, - ER(CR_SHARED_MEMORY_CONNECTION), host); - } - } -#endif /* HAVE_SMEM */ #if defined(HAVE_SYS_UN_H) if (!net->vio && (!mysql->options.protocol || @@ -3598,10 +3306,6 @@ static void mysql_close_free_options(MYSQL *mysql) #if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) mysql_ssl_free(mysql); #endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ -#ifdef HAVE_SMEM - if (mysql->options.shared_memory_base_name != def_shared_memory_base_name) - my_free(mysql->options.shared_memory_base_name); -#endif /* HAVE_SMEM */ if (mysql->options.extension) { struct mysql_async_context *ctxt= mysql->options.extension->async_context; @@ -4100,13 +3804,6 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) case MYSQL_OPT_PROTOCOL: mysql->options.protocol= *(uint*) arg; break; - case MYSQL_SHARED_MEMORY_BASE_NAME: -#ifdef HAVE_SMEM - if (mysql->options.shared_memory_base_name != def_shared_memory_base_name) - my_free(mysql->options.shared_memory_base_name); - mysql->options.shared_memory_base_name=my_strdup(arg,MYF(MY_WME)); -#endif - break; case MYSQL_OPT_USE_REMOTE_CONNECTION: case MYSQL_OPT_USE_EMBEDDED_CONNECTION: case MYSQL_OPT_GUESS_CONNECTION: @@ -4244,8 +3941,8 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) } } break; + case MYSQL_SHARED_MEMORY_BASE_NAME: default: - break; DBUG_RETURN(1); } DBUG_RETURN(0); diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 3a1f176485f..5c14b8071a3 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -58,6 +58,19 @@ uint calc_days_in_year(uint year) 366 : 365); } + +#ifdef DBUG_ASSERT_EXISTS + + +static const ulonglong C_KNOWN_FLAGS= C_TIME_NO_ZERO_IN_DATE | + C_TIME_NO_ZERO_DATE | + C_TIME_INVALID_DATES; + +#define C_FLAGS_OK(flags) (((flags) & ~C_KNOWN_FLAGS) == 0) + +#endif + + /** @brief Check datetime value for validity according to flags. @@ -82,13 +95,14 @@ uint calc_days_in_year(uint year) my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, ulonglong flags, int *was_cut) { + DBUG_ASSERT(C_FLAGS_OK(flags)); if (ltime->time_type == MYSQL_TIMESTAMP_TIME) return FALSE; if (not_zero_date) { - if (((flags & TIME_NO_ZERO_IN_DATE) && + if (((flags & C_TIME_NO_ZERO_IN_DATE) && (ltime->month == 0 || ltime->day == 0)) || ltime->neg || - (!(flags & TIME_INVALID_DATES) && + (!(flags & C_TIME_INVALID_DATES) && ltime->month && ltime->day > days_in_month[ltime->month-1] && (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 || ltime->day != 29))) @@ -97,12 +111,13 @@ my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, return TRUE; } } - else if (flags & TIME_NO_ZERO_DATE) + else if (flags & C_TIME_NO_ZERO_DATE) { /* We don't set *was_cut here to signal that the problem was a zero date and not an invalid date */ + *was_cut|= MYSQL_TIME_WARN_ZERO_DATE; return TRUE; } return FALSE; @@ -145,7 +160,8 @@ static int get_punct(const char **str, const char *end) return 1; } -static int get_date_time_separator(uint *number_of_fields, ulonglong flags, +static int get_date_time_separator(uint *number_of_fields, + my_bool punct_is_date_time_separator, const char **str, const char *end) { const char *s= *str; @@ -164,11 +180,11 @@ static int get_date_time_separator(uint *number_of_fields, ulonglong flags, but cast("11:11:11.12.12.12" as time) should give 11:11:11.12 that is, a punctuation character can be accepted as a date/time separator - only if TIME_DATETIME_ONLY (see str_to_time) is not set. + only if "punct_is_date_time_separator" is set. */ if (my_ispunct(&my_charset_latin1, *s)) { - if (flags & TIME_DATETIME_ONLY) + if (!punct_is_date_time_separator) { /* see above, returning 1 is not enough, we need hard abort here */ *number_of_fields= 0; @@ -241,25 +257,220 @@ static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status, *val= (ulong) (tmp * log_10_int[6 - (*str - start)]); else *val= tmp; + if (str[0] < end && my_isdigit(&my_charset_latin1, str[0][0])) + { + /* + We don't need the exact nanoseconds value. + Knowing the first digit is enough for rounding. + */ + status->nanoseconds= 100 * (uint)(str[0][0] - '0'); + } if (skip_digits(str, end)) status->warnings|= MYSQL_TIME_NOTE_TRUNCATED; } +static int check_time_range_internal(MYSQL_TIME *ltime, + ulong max_hour, ulong err_hour, + uint dec, int *warning); + +int check_time_range(MYSQL_TIME *ltime, uint dec, int *warning) +{ + return check_time_range_internal(ltime, TIME_MAX_HOUR, UINT_MAX32, + dec, warning); +} + + +static my_bool +set_neg(my_bool neg, MYSQL_TIME_STATUS *st, MYSQL_TIME *ltime) +{ + if ((ltime->neg= neg) && ltime->time_type != MYSQL_TIMESTAMP_TIME) + { + st->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + return TRUE; + } + return FALSE; +} + + + /* Remove trailing spaces and garbage */ +static my_bool get_suffix(const char *str, size_t length, size_t *new_length) +{ + /* + QQ: perhaps 'T' should be considered as a date/time delimiter only + if it's followed by a digit. Learn ISO 8601 details. + */ + my_bool garbage= FALSE; + for ( ; length > 0 ; length--) + { + char ch= str[length - 1]; + if (my_isdigit(&my_charset_latin1, ch) || + my_ispunct(&my_charset_latin1, ch)) + break; + if (my_isspace(&my_charset_latin1, ch)) + continue; + if (ch == 'T') + { + /* 'T' has a meaning only after a digit. Otherwise it's a garbage */ + if (length >= 2 && my_isdigit(&my_charset_latin1, str[length - 2])) + break; + } + garbage= TRUE; + } + *new_length= length; + return garbage; +} + + +static size_t get_prefix(const char *str, size_t length, const char **endptr) +{ + const char *str0= str, *end= str + length; + for (; str < end && my_isspace(&my_charset_latin1, *str) ; str++) + { } + *endptr= str; + return str - str0; +} + + +static size_t get_sign(my_bool *neg, const char *str, size_t length, + const char **endptr) +{ + const char *str0= str; + if (length) + { + if ((*neg= (*str == '-')) || (*str == '+')) + str++; + } + else + *neg= FALSE; + *endptr= str; + return str - str0; +} + + +static my_bool find_body(my_bool *neg, const char *str, size_t length, + MYSQL_TIME *to, int *warn, + const char **new_str, size_t *new_length) +{ + size_t sign_length; + *warn= 0; + length-= get_prefix(str, length, &str); + sign_length= get_sign(neg, str, length, &str); + length-= sign_length; + /* There can be a space after a sign again: '- 10:20:30' or '- 1 10:20:30' */ + length-= get_prefix(str, length, &str); + if (get_suffix(str, length, &length)) + *warn|= MYSQL_TIME_WARN_TRUNCATED; + *new_str= str; + *new_length= length; + if (!length || !my_isdigit(&my_charset_latin1, *str)) + { + *warn|= MYSQL_TIME_WARN_EDOM; + set_zero_time(to, MYSQL_TIMESTAMP_ERROR); + return TRUE; + } + return FALSE; +} + + +typedef struct +{ + uint count_punct; + uint count_colon; + uint count_iso_date_time_separator; +} MYSQL_TIME_USED_CHAR_STATISTICS; + + +static void +mysql_time_used_char_statistics_init(MYSQL_TIME_USED_CHAR_STATISTICS *to, + const char *str, const char *end) +{ + const char *s; + bzero((void *) to, sizeof(MYSQL_TIME_USED_CHAR_STATISTICS)); + for (s= str; s < end; s++) + { + if (my_ispunct(&my_charset_latin1, *s)) + to->count_punct++; + if (*s == ':') + to->count_colon++; + if (*s == 'T') + to->count_iso_date_time_separator++; + } +} + + +static my_bool +is_datetime_body_candidate(const char *str, size_t length, + my_bool allow_dates_delimited, + my_bool allow_dates_numeric) +{ + static uint min_date_length= 5; /* '1-1-1' -> '0001-01-01' */ + uint pos, count_punct= 0; + uint date_time_separator_length= MY_TEST(!allow_dates_delimited); + if (length >= 12) + return TRUE; + /* + The shortest possible DATE is '1-1-1', which is 5 characters. + To make a full datetime it should be at least followed by a space or a 'T'. + To make a date it should be just not less that 5 characters. + */ + if (length < min_date_length + date_time_separator_length && + !allow_dates_numeric) + return FALSE; + for (pos= 0; pos < length; pos++) + { + if (str[pos] == 'T') /* Date/time separator */ + return TRUE; + if (str[pos] == ' ') + { + /* + We found a space. If can be a DATE/TIME separator: + TIME('1-1-1 1:1:1.0) -> '0001-01-01 01:01:01.0' + + But it can be also a DAY/TIME separator: + TIME('1 11') -> 35:00:00 = 1 day 11 hours + TIME('1 111') -> 135:00:00 = 1 day 111 hours + TIME('11 11') -> 275:00:00 = 11 days 11 hours + TIME('111 11') -> 838:59:59 = 111 days 11 hours with overflow + TIME('1111 11') -> 838:59:59 = 1111 days 11 hours with overflow + */ + return count_punct > 0; /* Can be a DATE if already had separators*/ + } + if (my_ispunct(&my_charset_latin1, str[pos])) + { + if (allow_dates_delimited && str[pos] != ':') + return TRUE; + count_punct++; + } + } + return allow_dates_numeric && count_punct == 0; +} + + +static my_bool +str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length, + MYSQL_TIME *l_time, + ulong max_hour, ulong err_hour, + MYSQL_TIME_STATUS *status, + const char **endptr); + + /* Convert a timestamp string to a MYSQL_TIME value. SYNOPSIS - str_to_datetime() + str_to_datetime_or_date_body() str String to parse length Length of string l_time Date is stored here flags Bitmap of following items - TIME_FUZZY_DATE TIME_DATETIME_ONLY Set if we only allow full datetimes. TIME_NO_ZERO_IN_DATE Don't allow partial dates TIME_NO_ZERO_DATE Don't allow 0000-00-00 date TIME_INVALID_DATES Allow 2000-02-31 + punct_is_date_time_separator + Allow punctuation as a date/time separator, + or return a hard error. status Conversion status @@ -292,32 +503,22 @@ static void get_microseconds(ulong *val, MYSQL_TIME_STATUS *status, #define MAX_DATE_PARTS 8 -my_bool -str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, - ulonglong flags, MYSQL_TIME_STATUS *status) +static my_bool +str_to_datetime_or_date_body(const char *str, size_t length, MYSQL_TIME *l_time, + ulonglong flags, + my_bool punct_is_date_time_separator, + MYSQL_TIME_STATUS *status, + uint *number_of_fields, + const char **endptr) { const char *end=str+length, *pos; - uint number_of_fields= 0, digits, year_length, not_zero_date; - DBUG_ENTER("str_to_datetime"); + uint digits, year_length, not_zero_date; + int warn= 0; + DBUG_ENTER("str_to_datetime_or_date_body"); + DBUG_ASSERT(C_FLAGS_OK(flags)); bzero(l_time, sizeof(*l_time)); - - if (flags & TIME_TIME_ONLY) - { - my_bool ret= str_to_time(str, length, l_time, flags, status); - DBUG_RETURN(ret); - } - - my_time_status_init(status); - - /* Skip space at start */ - for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++) - ; - if (str == end || ! my_isdigit(&my_charset_latin1, *str)) - { - status->warnings= MYSQL_TIME_WARN_TRUNCATED; - l_time->time_type= MYSQL_TIMESTAMP_NONE; - DBUG_RETURN(1); - } + *number_of_fields= 0; + *endptr= str; /* Calculate number of digits in first part. @@ -345,49 +546,53 @@ str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, (only numbers like [YY]YYMMDD[T][hhmmss[.uuuuuu]]) */ year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; - if (get_digits(&l_time->year, &number_of_fields, &str, end, year_length) - || get_digits(&l_time->month, &number_of_fields, &str, end, 2) - || get_digits(&l_time->day, &number_of_fields, &str, end, 2) + if (get_digits(&l_time->year, number_of_fields, &str, end, year_length) + || get_digits(&l_time->month, number_of_fields, &str, end, 2) + || get_digits(&l_time->day, number_of_fields, &str, end, 2) || get_maybe_T(&str, end) - || get_digits(&l_time->hour, &number_of_fields, &str, end, 2) - || get_digits(&l_time->minute, &number_of_fields, &str, end, 2) - || get_digits(&l_time->second, &number_of_fields, &str, end, 2)) - status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + || get_digits(&l_time->hour, number_of_fields, &str, end, 2) + || get_digits(&l_time->minute, number_of_fields, &str, end, 2) + || get_digits(&l_time->second, number_of_fields, &str, end, 2)) + warn|= MYSQL_TIME_WARN_TRUNCATED; } else { const char *start= str; - if (get_number(&l_time->year, &number_of_fields, &str, end)) - status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + if (get_number(&l_time->year, number_of_fields, &str, end)) + warn|= MYSQL_TIME_WARN_TRUNCATED; year_length= (uint)(str - start); - if (!status->warnings && + if (!warn && (get_punct(&str, end) - || get_number(&l_time->month, &number_of_fields, &str, end) + || get_number(&l_time->month, number_of_fields, &str, end) || get_punct(&str, end) - || get_number(&l_time->day, &number_of_fields, &str, end) - || get_date_time_separator(&number_of_fields, flags, &str, end) - || get_number(&l_time->hour, &number_of_fields, &str, end) + || get_number(&l_time->day, number_of_fields, &str, end) + || get_date_time_separator(number_of_fields, + punct_is_date_time_separator, &str, end) + || get_number(&l_time->hour, number_of_fields, &str, end) || get_punct(&str, end) - || get_number(&l_time->minute, &number_of_fields, &str, end) + || get_number(&l_time->minute, number_of_fields, &str, end) || get_punct(&str, end) - || get_number(&l_time->second, &number_of_fields, &str, end))) - status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + || get_number(&l_time->second, number_of_fields, &str, end))) + warn|= MYSQL_TIME_WARN_TRUNCATED; } + status->warnings|= warn; + *endptr= str; /* we're ok if date part is correct. even if the rest is truncated */ - if (number_of_fields < 3) + if (*number_of_fields < 3) { l_time->time_type= MYSQL_TIMESTAMP_NONE; status->warnings|= MYSQL_TIME_WARN_TRUNCATED; DBUG_RETURN(TRUE); } - if (!status->warnings && str < end && *str == '.') + if (!warn && str < end && *str == '.') { str++; get_microseconds(&l_time->second_part, status, - &number_of_fields, &str, end); + number_of_fields, &str, end); + *endptr= str; } not_zero_date = l_time->year || l_time->month || l_time->day || @@ -407,23 +612,16 @@ str_to_datetime(const char *str, size_t length, MYSQL_TIME *l_time, if (check_date(l_time, not_zero_date, flags, &status->warnings)) goto err; - l_time->time_type= (number_of_fields <= 3 ? + l_time->time_type= (*number_of_fields <= 3 ? MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME); - for (; str != end ; str++) - { - if (!my_isspace(&my_charset_latin1,*str)) - { - status->warnings= MYSQL_TIME_WARN_TRUNCATED; - break; - } - } + if (str != end) + status->warnings= MYSQL_TIME_WARN_TRUNCATED; DBUG_RETURN(FALSE); err: - bzero((char*) l_time, sizeof(*l_time)); - l_time->time_type= MYSQL_TIMESTAMP_ERROR; + set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR); DBUG_RETURN(TRUE); } @@ -432,7 +630,7 @@ err: Convert a time string to a MYSQL_TIME struct. SYNOPSIS - str_to_time() + str_to_datetime_or_date_or_time_body() str A string in full TIMESTAMP format or [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, [M]MSS or [S]S @@ -457,45 +655,280 @@ err: TRUE on error */ -my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, - ulonglong fuzzydate, MYSQL_TIME_STATUS *status) +static my_bool +str_to_datetime_or_date_or_time_body(const char *str, size_t length, + MYSQL_TIME *l_time, + ulonglong fuzzydate, + MYSQL_TIME_STATUS *status, + ulong time_max_hour, + ulong time_err_hour, + my_bool allow_dates_delimited, + my_bool allow_dates_numeric) { - ulong date[5]; - ulonglong value; - const char *end=str+length, *end_of_days; - my_bool found_days,found_hours, neg= 0; - uint UNINIT_VAR(state); - - my_time_status_init(status); - for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++) - length--; - if (str != end && *str == '-') - { - neg=1; - str++; - length--; - } - if (str == end) - { - status->warnings|= MYSQL_TIME_WARN_TRUNCATED; - goto err; - } + const char *endptr; + DBUG_ASSERT(C_FLAGS_OK(fuzzydate)); /* Check first if this is a full TIMESTAMP */ - if (length >= 12) + if (is_datetime_body_candidate(str, length, + allow_dates_delimited, + allow_dates_numeric)) { /* Probably full timestamp */ - (void) str_to_datetime(str, length, l_time, - (fuzzydate & ~TIME_TIME_ONLY) | TIME_DATETIME_ONLY, - status); - if (l_time->time_type >= MYSQL_TIMESTAMP_ERROR) - return l_time->time_type == MYSQL_TIMESTAMP_ERROR; + int warn_copy= status->warnings; /* could already be set by find_body() */ + uint number_of_fields; + (void) str_to_datetime_or_date_body(str, length, l_time, fuzzydate, + FALSE, status, + &number_of_fields, &endptr); + DBUG_ASSERT(endptr >= str); + DBUG_ASSERT(endptr <= str + length); + switch (l_time->time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return FALSE; + case MYSQL_TIMESTAMP_DATE: + { + /* + Successfully parsed as DATE, but it can also be a TIME: + '24:02:03' - continue and parse as TIME + '24:02:03 garbage /////' - continue and parse as TIME + '24:02:03T' - return DATE + '24-02-03' - return DATE + '24/02/03' - return DATE + '11111' - return DATE + */ + MYSQL_TIME_USED_CHAR_STATISTICS used_chars; + mysql_time_used_char_statistics_init(&used_chars, str, endptr); + if (used_chars.count_iso_date_time_separator || !used_chars.count_colon) + return FALSE; + } + break; + case MYSQL_TIMESTAMP_ERROR: + { + MYSQL_TIME_USED_CHAR_STATISTICS used_chars; + /* + Check if it parsed as DATETIME but then failed as out of range: + '2011-02-32 8:46:06.23434' - return error + */ + if (number_of_fields > 3) + return TRUE; + /* + Check if it parsed as DATE but then failed as out of range: + '100000:02:03' - continue and parse as TIME + '100000:02:03T' - return error + '100000/02/03' - return error + '100000-02-03' - return error + */ + mysql_time_used_char_statistics_init(&used_chars, str, endptr); + if (used_chars.count_iso_date_time_separator || !used_chars.count_colon) + return TRUE; + } + break; + case MYSQL_TIMESTAMP_NONE: + { + if (allow_dates_numeric && endptr >= str + length) + { + /* + For backward compatibility this parses as DATE and fails: + EXTRACT(DAY FROM '1111') -- return error + EXTRACT(DAY FROM '1') -- return error + */ + MYSQL_TIME_USED_CHAR_STATISTICS used_chars; + mysql_time_used_char_statistics_init(&used_chars, str, endptr); + if (!used_chars.count_iso_date_time_separator && + !used_chars.count_colon && + !used_chars.count_punct) + return TRUE; + } + /* + - '256 10:30:30' - continue and parse as TIME + - '4294967296:59:59.123456456' - continue and parse as TIME + */ + } + break; + case MYSQL_TIMESTAMP_TIME: + DBUG_ASSERT(0); + break; + } + my_time_status_init(status); + status->warnings= warn_copy; + } + + if (!str_to_DDhhmmssff_internal(FALSE, str, length, l_time, + time_max_hour, time_err_hour, + status, &endptr)) + return FALSE; + + set_zero_time(l_time, MYSQL_TIMESTAMP_ERROR); + return TRUE; +} + + +/* + Convert a string with INTERVAL DAY TO SECOND to MYSQL_TIME. + Input format: [-][DD ]hh:mm:ss.ffffff + + If the input string appears to be a DATETIME, error is returned. +*/ +my_bool str_to_DDhhmmssff(const char *str, size_t length, MYSQL_TIME *ltime, + ulong max_hour, MYSQL_TIME_STATUS *status) +{ + my_bool neg; + const char *endptr; + + my_time_status_init(status); + if (find_body(&neg, str, length, ltime, &status->warnings, &str, &length)) + return TRUE; + + /* Reject anything that might be parsed as a full TIMESTAMP */ + if (is_datetime_body_candidate(str, length, FALSE, FALSE)) + { + uint number_of_fields; + (void) str_to_datetime_or_date_body(str, length, ltime, 0, FALSE, + status, &number_of_fields, &endptr); + if (ltime->time_type > MYSQL_TIMESTAMP_ERROR) + { + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; + ltime->time_type= MYSQL_TIMESTAMP_NONE; + return TRUE; + } my_time_status_init(status); } + /* + Scan DDhhmmssff then reject anything that can remind date/datetime. + For example, in case of '2001-01-01', str_to_DDhhmmssff_internal() + will scan only '2001'. + */ + if (str_to_DDhhmmssff_internal(neg, str, length, ltime, max_hour, + UINT_MAX32, status, &endptr) || + (endptr < str + length && endptr[0] == '-')) + return TRUE; + return FALSE; +} + + +my_bool +str_to_datetime_or_date_or_time(const char *str, size_t length, + MYSQL_TIME *to, ulonglong mode, + MYSQL_TIME_STATUS *status, + ulong time_max_hour, + ulong time_err_hour) +{ + my_bool neg; + DBUG_ASSERT(C_FLAGS_OK(mode)); + my_time_status_init(status); + return + find_body(&neg, str, length, to, &status->warnings, &str, &length) || + str_to_datetime_or_date_or_time_body(str, length, to, mode, status, + time_max_hour, time_err_hour, + FALSE, FALSE) || + set_neg(neg, status, to); +} + + +my_bool +str_to_datetime_or_date_or_interval_hhmmssff(const char *str, size_t length, + MYSQL_TIME *to, ulonglong mode, + MYSQL_TIME_STATUS *status, + ulong time_max_hour, + ulong time_err_hour) +{ + my_bool neg; + DBUG_ASSERT(C_FLAGS_OK(mode)); + my_time_status_init(status); + return + find_body(&neg, str, length, to, &status->warnings, &str, &length) || + str_to_datetime_or_date_or_time_body(str, length, to, mode, status, + time_max_hour, time_err_hour, + TRUE, FALSE) || + set_neg(neg, status, to); +} + + +my_bool +str_to_datetime_or_date_or_interval_day(const char *str, size_t length, + MYSQL_TIME *to, ulonglong mode, + MYSQL_TIME_STATUS *status, + ulong time_max_hour, + ulong time_err_hour) +{ + my_bool neg; + DBUG_ASSERT(C_FLAGS_OK(mode)); + my_time_status_init(status); + /* + For backward compatibility we allow to parse non-delimited + values as DATE rather than as TIME: + EXTRACT(DAY FROM '11111') + */ + return + find_body(&neg, str, length, to, &status->warnings, &str, &length) || + str_to_datetime_or_date_or_time_body(str, length, to, mode, status, + time_max_hour, time_err_hour, + TRUE, TRUE) || + set_neg(neg, status, to); +} + + +my_bool +str_to_datetime_or_date(const char *str, size_t length, MYSQL_TIME *l_time, + ulonglong flags, MYSQL_TIME_STATUS *status) +{ + my_bool neg; + uint number_of_fields; + const char *endptr; + DBUG_ASSERT(C_FLAGS_OK(flags)); + my_time_status_init(status); + return + find_body(&neg, str, length, l_time, &status->warnings, &str, &length) || + str_to_datetime_or_date_body(str, length, l_time, flags, TRUE, + status, &number_of_fields, &endptr) || + set_neg(neg, status, l_time); +} + + + +/** + Convert a string to INTERVAL DAY TO SECOND. + Input format: [DD ]hh:mm:ss.ffffff + + Datetime or date formats are not understood. + + Optional leading spaces and signs must be scanned by the caller. + "str" should point to the first digit. + + @param neg - set the value to be negative + @param str - the input string + @param length - length of "str" + @param[OUT] l_time - write the result here + @param max_hour - if the result hour value appears to be greater than + max_hour, then cut to result to 'max_hour:59:59.999999' + @param err_hour - if the hour appears to be greater than err_hour, + return an error (without cut) + @param status + @param endptr +*/ +static my_bool +str_to_DDhhmmssff_internal(my_bool neg, const char *str, size_t length, + MYSQL_TIME *l_time, + ulong max_hour, ulong err_hour, + MYSQL_TIME_STATUS *status, const char **endptr) +{ + ulong date[5]; + ulonglong value; + const char *end=str + length, *end_of_days; + my_bool found_days, found_hours; + uint UNINIT_VAR(state); + + *endptr= str; l_time->neg= neg; - /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ + /* Not a timestamp. Try to get this as a DAYS TO SECOND string */ for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++) + { value=value*10L + (long) (*str - '0'); + if (value >= 42949672955959ULL) /* i.e. UINT_MAX32 : 59 : 59 */ + { + status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + goto err; + } + } /* Skip all space after 'days' */ end_of_days= str; @@ -514,6 +947,11 @@ my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, my_isdigit(&my_charset_latin1, str[1])) { date[0]= 0; /* Assume we found hours */ + if (value >= UINT_MAX32) + { + status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + goto err; + } date[1]= (ulong) value; state=2; found_hours=1; @@ -523,6 +961,7 @@ my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, { /* String given as one number; assume HHMMSS format */ date[0]= 0; + DBUG_ASSERT(value <= ((ulonglong) UINT_MAX32) * 10000ULL); date[1]= (ulong) (value/10000); date[2]= (ulong) (value/100 % 100); date[3]= (ulong) (value % 100); @@ -557,7 +996,7 @@ my_bool str_to_time(const char *str, size_t length, MYSQL_TIME *l_time, fractional: /* Get fractional second part */ - if (!status->warnings && str < end && *str == '.') + if (str < end && *str == '.') { uint number_of_fields= 0; str++; @@ -605,6 +1044,11 @@ fractional: goto err; } + if ((ulonglong) date[0] * 24 + date[1] > (ulonglong) UINT_MAX32) + { + status->warnings|= MYSQL_TIME_WARN_OUT_OF_RANGE; + goto err; + } l_time->year= 0; /* For protocol::store_time */ l_time->month= 0; l_time->day= 0; @@ -614,27 +1058,20 @@ fractional: l_time->second_part= date[4]; l_time->time_type= MYSQL_TIMESTAMP_TIME; + *endptr= str; + /* Check if the value is valid and fits into MYSQL_TIME range */ - if (check_time_range(l_time, 6, &status->warnings)) + if (check_time_range_internal(l_time, max_hour, err_hour, + 6, &status->warnings)) return TRUE; /* Check if there is garbage at end of the MYSQL_TIME specification */ if (str != end) - { - do - { - if (!my_isspace(&my_charset_latin1,*str)) - { - status->warnings|= MYSQL_TIME_WARN_TRUNCATED; - break; - } - } while (++str != end); - } + status->warnings|= MYSQL_TIME_WARN_TRUNCATED; return FALSE; err: - bzero((char*) l_time, sizeof(*l_time)); - l_time->time_type= MYSQL_TIMESTAMP_ERROR; + *endptr= str; return TRUE; } @@ -643,8 +1080,11 @@ err: Check 'time' value to lie in the MYSQL_TIME range SYNOPSIS: - check_time_range() + check_time_range_internal() time pointer to MYSQL_TIME value + ulong max_hour - maximum allowed hour value. if the hour is greater, + cut the time value to 'max_hour:59:59.999999' + ulong err_hour - if hour is greater than this value, return an error uint dec warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range @@ -658,13 +1098,16 @@ err: 1 time value is invalid */ -int check_time_range(struct st_mysql_time *my_time, uint dec, int *warning) +int check_time_range_internal(struct st_mysql_time *my_time, + ulong max_hour, ulong err_hour, + uint dec, int *warning) { - longlong hour; + ulonglong hour; static ulong max_sec_part[TIME_SECOND_PART_DIGITS+1]= {000000, 900000, 990000, 999000, 999900, 999990, 999999}; - if (my_time->minute >= 60 || my_time->second >= 60) + if (my_time->minute >= 60 || my_time->second >= 60 || + my_time->hour > err_hour) { *warning|= MYSQL_TIME_WARN_TRUNCATED; return 1; @@ -675,14 +1118,14 @@ int check_time_range(struct st_mysql_time *my_time, uint dec, int *warning) if (dec == AUTO_SEC_PART_DIGITS) dec= TIME_SECOND_PART_DIGITS; - if (hour <= TIME_MAX_HOUR && - (hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE || + if (hour <= max_hour && + (hour != max_hour || my_time->minute != TIME_MAX_MINUTE || my_time->second != TIME_MAX_SECOND || my_time->second_part <= max_sec_part[dec])) return 0; my_time->day= 0; - my_time->hour= TIME_MAX_HOUR; + my_time->hour= max_hour; my_time->minute= TIME_MAX_MINUTE; my_time->second= TIME_MAX_SECOND; my_time->second_part= max_sec_part[dec]; @@ -1031,6 +1474,46 @@ static char* fmt_number(uint val, char *out, uint digits) } +static int my_mmssff_to_str(const MYSQL_TIME *ltime, char *to, uint fsp) +{ + char *pos= to; + if (fsp == AUTO_SEC_PART_DIGITS) + fsp= ltime->second_part ? TIME_SECOND_PART_DIGITS : 0; + DBUG_ASSERT(fsp <= TIME_SECOND_PART_DIGITS); + pos= fmt_number(ltime->minute, pos, 2); + *pos++= ':'; + pos= fmt_number(ltime->second, pos, 2); + if (fsp) + { + *pos++= '.'; + pos= fmt_number((uint)sec_part_shift(ltime->second_part, fsp), pos, fsp); + } + return (int) (pos - to); +} + + +int my_interval_DDhhmmssff_to_str(const MYSQL_TIME *ltime, char *to, uint fsp) +{ + uint hour= ltime->day * 24 + ltime->hour; + char *pos= to; + DBUG_ASSERT(!ltime->year); + DBUG_ASSERT(!ltime->month); + + if(ltime->neg) + *pos++= '-'; + if (hour >= 24) + { + pos= longlong10_to_str((longlong) hour / 24, pos, 10); + *pos++= ' '; + } + pos= fmt_number(hour % 24, pos, 2); + *pos++= ':'; + pos+= my_mmssff_to_str(ltime, pos, fsp); + *pos= 0; + return (int) (pos-to); +} + + /* Functions to convert time/date/datetime value to a string, using default format. @@ -1048,11 +1531,6 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to, uint digits) uint hour= day * 24 + l_time->hour; char*pos= to; - if (digits == AUTO_SEC_PART_DIGITS) - digits= l_time->second_part ? TIME_SECOND_PART_DIGITS : 0; - - DBUG_ASSERT(digits <= TIME_SECOND_PART_DIGITS); - if(l_time->neg) *pos++= '-'; @@ -1063,17 +1541,7 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to, uint digits) pos= fmt_number(hour, pos, 2); *pos++= ':'; - pos= fmt_number(l_time->minute, pos, 2); - *pos++= ':'; - pos= fmt_number(l_time->second, pos, 2); - - if (digits) - { - *pos++= '.'; - pos= fmt_number((uint)sec_part_shift(l_time->second_part, digits), - pos, digits); - } - + pos+= my_mmssff_to_str(l_time, pos, digits); *pos= 0; return (int) (pos-to); } @@ -1095,12 +1563,6 @@ int my_date_to_str(const MYSQL_TIME *l_time, char *to) int my_datetime_to_str(const MYSQL_TIME *l_time, char *to, uint digits) { char *pos= to; - - if (digits == AUTO_SEC_PART_DIGITS) - digits= l_time->second_part ? TIME_SECOND_PART_DIGITS : 0; - - DBUG_ASSERT(digits <= TIME_SECOND_PART_DIGITS); - pos= fmt_number(l_time->year, pos, 4); *pos++='-'; pos= fmt_number(l_time->month, pos, 2); @@ -1109,17 +1571,7 @@ int my_datetime_to_str(const MYSQL_TIME *l_time, char *to, uint digits) *pos++=' '; pos= fmt_number(l_time->hour, pos, 2); *pos++= ':'; - pos= fmt_number(l_time->minute, pos, 2); - *pos++= ':'; - pos= fmt_number(l_time->second, pos, 2); - - if (digits) - { - *pos++='.'; - pos= fmt_number((uint) sec_part_shift(l_time->second_part, digits), pos, - digits); - } - + pos+= my_mmssff_to_str(l_time, pos, digits); *pos= 0; return (int)(pos - to); } @@ -1185,7 +1637,7 @@ int my_timeval_to_str(const struct timeval *tm, char *to, uint dec) representation and form value of DATETIME type as side-effect. SYNOPSIS - number_to_datetime() + number_to_datetime_or_date() nr - datetime value as number time_res - pointer for structure for broken-down representation flags - flags to use in validating date, as in str_to_datetime() @@ -1206,10 +1658,12 @@ int my_timeval_to_str(const struct timeval *tm, char *to, uint dec) Datetime value in YYYYMMDDHHMMSS format. */ -longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, - ulonglong flags, int *was_cut) +longlong number_to_datetime_or_date(longlong nr, ulong sec_part, + MYSQL_TIME *time_res, + ulonglong flags, int *was_cut) { long part1,part2; + DBUG_ASSERT(C_FLAGS_OK(flags)); *was_cut= 0; time_res->time_type=MYSQL_TIMESTAMP_DATE; @@ -1274,13 +1728,17 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, !check_date(time_res, nr || sec_part, flags, was_cut)) { if (time_res->time_type == MYSQL_TIMESTAMP_DATE && sec_part != 0) - *was_cut= MYSQL_TIME_NOTE_TRUNCATED; + { + /* Date format, but with fractional digits, e.g. 20010203.5 */ + *was_cut= MYSQL_TIME_NOTE_TRUNCATED; + time_res->second_part= 0; + } return nr; } /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */ - if (nr || !(flags & TIME_NO_ZERO_DATE)) - *was_cut= 1; + if (nr || !(flags & C_TIME_NO_ZERO_DATE)) + *was_cut= MYSQL_TIME_WARN_TRUNCATED; return -1; err: @@ -1289,7 +1747,7 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, enum enum_mysql_timestamp_type save= time_res->time_type; bzero((char*) time_res, sizeof(*time_res)); time_res->time_type= save; /* Restore range */ - *was_cut= 1; /* Found invalid date */ + *was_cut= MYSQL_TIME_WARN_TRUNCATED; /* Found invalid date */ } return -1; } @@ -1310,22 +1768,20 @@ longlong number_to_datetime(longlong nr, ulong sec_part, MYSQL_TIME *time_res, 0 time value is valid, but was possibly truncated -1 time value is invalid */ -int number_to_time(my_bool neg, ulonglong nr, ulong sec_part, - MYSQL_TIME *ltime, int *was_cut) +int number_to_time_only(my_bool neg, ulonglong nr, ulong sec_part, + ulong max_hour, MYSQL_TIME *ltime, int *was_cut) { - if (nr > 9999999 && nr < 99991231235959ULL && neg == 0) - return number_to_datetime(nr, sec_part, ltime, - TIME_INVALID_DATES, was_cut) < 0 ? -1 : 0; - + static const ulonglong TIME_MAX_mmss= TIME_MAX_MINUTE*100 + TIME_MAX_SECOND; + ulonglong time_max_value= max_hour * 10000ULL + TIME_MAX_mmss; *was_cut= 0; ltime->year= ltime->month= ltime->day= 0; ltime->time_type= MYSQL_TIMESTAMP_TIME; ltime->neg= neg; - if (nr > TIME_MAX_VALUE) + if (nr > time_max_value) { - nr= TIME_MAX_VALUE; + nr= time_max_value; sec_part= TIME_MAX_SECOND_PART; *was_cut= MYSQL_TIME_WARN_OUT_OF_RANGE; } |