1 /*
2 * Copyright (c) 2014, 2021, Oracle and/or its affiliates.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2.0, as
6 * published by the Free Software Foundation.
7 *
8 * This program is also distributed with certain software (including
9 * but not limited to OpenSSL) that is licensed under separate terms,
10 * as designated in a particular file or component or in included license
11 * documentation. The authors of MySQL hereby grant you an
12 * additional permission to link the program and your derivative works
13 * with the separately licensed software that they have included with
14 * MySQL.
15 *
16 * Without limiting anything contained in the foregoing, this file,
17 * which is part of MySQL Connector/Python, is also subject to the
18 * Universal FOSS Exception, version 1.0, a copy of which can be found at
19 * http://oss.oracle.com/licenses/universal-foss-exception.
20 *
21 * This program is distributed in the hope that it will be useful, but
22 * WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24 * See the GNU General Public License, version 2.0, for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software Foundation, Inc.,
28 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29 */
30
31 #include <ctype.h>
32 #include <stdio.h>
33
34 #include <Python.h>
35 #include <datetime.h>
36
37 #ifdef MS_WINDOWS
38 #include <windows.h>
39 #endif
40 #include <mysql.h>
41
42 #include "exceptions.h"
43
44 #define MINYEAR 1
45 #define MAXYEAR 9999
46
47 /**
48 Check whether a year is a leap year.
49
50 Check whether a year is a leap year or not.
51
52 Year is not checked. This helper function is used by other
53 functions which validate temporal values.
54
55 @param year year
56
57 @return 1 if year is leap year, 0 otherwise.
58 @retval 1 Leap year
59 @retval 0 Not a leap year
60 */
61 static int
leap_year(int year)62 leap_year(int year)
63 {
64 if ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0))
65 {
66 return 1;
67 }
68
69 return 0;
70 }
71
72 /**
73 Return number of days in month.
74
75 Return the number of days in a month considering leap years.
76
77 Year and month are not checked. This helper function is used
78 by other functions which validate temporal values.
79
80 @param year year
81 @param month month
82
83 @return Number of days in month
84 @retval int number of days
85 */
86 static int
nr_days_month(int year,int month)87 nr_days_month(int year, int month)
88 {
89 int days[]= {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
90 if (month == 2 && leap_year(year))
91 {
92 return 29;
93 }
94 return days[month];
95 }
96
97 /**
98 Check whether a date is valid or not.
99
100 Check whether the date defined by the arguments year,
101 month, and day, is valid or not.
102
103 @param day day
104 @param month month
105 @param day day
106
107 @return 1 if date is valid, 0 otherwise.
108 @retval 1 Valid
109 @retval 0 Invalid
110 */
111 static int
is_valid_date(int year,int month,int day)112 is_valid_date(int year, int month, int day)
113 {
114 if ((year < MINYEAR || year > MAXYEAR)
115 || (month < 1 || month > 12)
116 || (day < 1 || day > nr_days_month(year, month)))
117 {
118 return 0;
119 }
120
121 return 1;
122 }
123
124 /**
125 Check whether a time is valid or not.
126
127 Check whether the time defined by the arguments hours,
128 mins, secs and usecs, is valid or not.
129
130 @param hours hours
131 @param mins minutes
132 @param secs secs
133 @param usecs microsecons
134
135 @return 1 if time is valid, 0 otherwise.
136 @retval 1 Valid
137 @retval 0 Invalid
138 */
139 static int
is_valid_time(int hours,int mins,int secs,int usecs)140 is_valid_time(int hours, int mins, int secs, int usecs)
141 {
142 if ((hours < 0 || hours > 23)
143 || (mins < 0 || mins > 59)
144 || (secs < 0 || secs > 59)
145 || (usecs < 0 || usecs > 999999))
146 {
147 return 0;
148 }
149
150 return 1;
151 }
152
153 /**
154 Convert a Python datetime.timedelta to MySQL TIME.
155
156 Convert the PyObject obj, which must be a datetime.timedelta,
157 to MySQL TIME value.
158
159 Raises TypeError when obj is not a PyDelta_Type.
160
161 @param obj the PyObject to be converted
162
163 @return Converted timedelta object.
164 @retval PyBytes Python v3
165 @retval NULL Exception
166 */
167 PyObject*
pytomy_timedelta(PyObject * obj)168 pytomy_timedelta(PyObject *obj)
169 {
170 int days= 0, secs= 0 , micro_secs= 0, total_secs= 0;
171 int hours= 0, mins= 0, remainder= 0;
172 char fmt[32]= {0};
173 char result[17]= {0};
174
175 PyDateTime_IMPORT;
176
177 if (!obj || !PyDelta_Check(obj))
178 {
179 PyErr_SetString(PyExc_ValueError,
180 "Object must be a datetime.timedelta");
181 return NULL;
182 }
183
184 // Cannot use PyDateTime_DELTA_* (new in Python v3.3)
185 days= ((PyDateTime_Delta*)obj)->days;
186 secs= ((PyDateTime_Delta*)obj)->seconds;
187 micro_secs= ((PyDateTime_Delta*)obj)->microseconds;
188
189 total_secs= abs(days * 86400 + secs);
190
191 #pragma warning(push)
192 // result of strncpy does not accept direct user input
193 #pragma warning(disable: 4996)
194
195
196 if (micro_secs)
197 {
198 strncpy(fmt, "%02d:%02d:%02d.%06d", 19);
199 if (days < 0)
200 {
201 micro_secs= 1000000 - micro_secs;
202 total_secs-= 1;
203 }
204 }
205 else
206 {
207 strncpy(fmt, "%02d:%02d:%02d", 14);
208 }
209
210 if (days < 0)
211 {
212 int index;
213 for(index = 31; index > 0; index--){
214 fmt[index] = fmt[index - 1];
215 }
216 fmt[0] = '-';
217 }
218 #pragma warning(pop)
219
220 hours= total_secs / 3600;
221 remainder= total_secs % 3600;
222 mins= remainder / 60;
223 secs= remainder % 60;
224
225 if (micro_secs)
226 {
227 PyOS_snprintf(result, 17, fmt, hours, mins, secs, micro_secs);
228 }
229 else
230 {
231 PyOS_snprintf(result, 17, fmt, hours, mins, secs);
232 }
233
234 return PyBytes_FromString(result);
235 }
236
237 /**
238 Convert a Python datetime.time to MySQL TIME.
239
240 Convert the PyObject obj, which must be a datetime.time,
241 to MySQL TIME value.
242
243 Raises TypeError when obj is not a PyTime_Type.
244
245 @param obj the PyObject to be converted
246
247 @return Converted time object.
248 @retval PyBytes Python v3
249 @retval NULL Exception
250 */
251 PyObject*
pytomy_time(PyObject * obj)252 pytomy_time(PyObject *obj)
253 {
254 char result[17]= {0};
255
256 PyDateTime_IMPORT;
257
258 if (!obj || !PyTime_Check(obj))
259 {
260 PyErr_SetString(PyExc_ValueError,
261 "Object must be a datetime.time");
262 return NULL;
263 }
264
265 if (PyDateTime_TIME_GET_MICROSECOND(obj))
266 {
267 PyOS_snprintf(result, 17, "%02d:%02d:%02d.%06d",
268 PyDateTime_TIME_GET_HOUR(obj),
269 PyDateTime_TIME_GET_MINUTE(obj),
270 PyDateTime_TIME_GET_SECOND(obj),
271 PyDateTime_TIME_GET_MICROSECOND(obj));
272 }
273 else
274 {
275 PyOS_snprintf(result, 17, "%02d:%02d:%02d",
276 PyDateTime_TIME_GET_HOUR(obj),
277 PyDateTime_TIME_GET_MINUTE(obj),
278 PyDateTime_TIME_GET_SECOND(obj));
279 }
280
281 return PyBytes_FromString(result);
282 }
283
284 /**
285 Convert a Python datetime.datetime to MySQL DATETIME.
286
287 Convert the PyObject obj, which must be a datetime.datetime,
288 to MySQL DATETIME value.
289
290 Raises TypeError when obj is not a PyDateTime_Type.
291
292 @param obj the PyObject to be converted
293
294 @return Converted datetime object.
295 @retval PyBytes Python v3
296 @retval NULL Exception
297 */
298 PyObject*
pytomy_datetime(PyObject * obj)299 pytomy_datetime(PyObject *obj)
300 {
301 char result[27]= {0};
302 PyDateTime_IMPORT;
303
304 if (!obj || !PyDateTime_Check(obj))
305 {
306 PyErr_SetString(PyExc_ValueError,
307 "Object must be a datetime.datetime");
308 return NULL;
309 }
310
311 if (PyDateTime_DATE_GET_MICROSECOND(obj))
312 {
313 PyOS_snprintf(result, 27, "%04d-%02d-%02d %02d:%02d:%02d.%06d",
314 PyDateTime_GET_YEAR(obj),
315 PyDateTime_GET_MONTH(obj),
316 PyDateTime_GET_DAY(obj),
317 PyDateTime_DATE_GET_HOUR(obj),
318 PyDateTime_DATE_GET_MINUTE(obj),
319 PyDateTime_DATE_GET_SECOND(obj),
320 PyDateTime_DATE_GET_MICROSECOND(obj));
321 }
322 else
323 {
324 PyOS_snprintf(result, 27, "%04d-%02d-%02d %02d:%02d:%02d",
325 PyDateTime_GET_YEAR(obj),
326 PyDateTime_GET_MONTH(obj),
327 PyDateTime_GET_DAY(obj),
328 PyDateTime_DATE_GET_HOUR(obj),
329 PyDateTime_DATE_GET_MINUTE(obj),
330 PyDateTime_DATE_GET_SECOND(obj));
331 }
332 return PyBytes_FromString(result);
333 }
334
335 /**
336 Convert a Python datetime.date to MySQL DATE.
337
338 Convert the PyObject obj, which must be a datetime.date,
339 to MySQL DATE value.
340
341 Raises TypeError when obj is not a PyDate_Type.
342
343 @param date the PyObject to be converted
344
345 @return Converted date object.
346 @retval PyBytes Python v3
347 @retval NULL Exception
348 */
349 PyObject*
pytomy_date(PyObject * obj)350 pytomy_date(PyObject *obj)
351 {
352 PyDateTime_IMPORT;
353
354 if (!obj || !PyDate_Check(obj))
355 {
356 PyErr_SetString(PyExc_TypeError, "Object must be a datetime.date");
357 return NULL;
358 }
359
360 return PyBytes_FromFormat("%04d-%02d-%02d",
361 PyDateTime_GET_YEAR(obj),
362 PyDateTime_GET_MONTH(obj),
363 PyDateTime_GET_DAY(obj));
364 }
365
366 /**
367 Convert a DATE MySQL value to Python datetime.date.
368
369 Convert a DATETIME MySQL value to Python datetime.date. When a date
370 can be parsed, but it is invalid, None is returned.
371
372 Raises ValueError when the date is not for format %d-%d-%d.
373
374 @param data string to be converted
375
376 @return datetime.date object.
377 @retval PyDate OK
378 @retval None Invalid date
379 @retval NULL Exception
380 */
381 PyObject*
mytopy_date(const char * data)382 mytopy_date(const char *data)
383 {
384 int year= 0, month= 0, day= 0;
385
386 PyDateTime_IMPORT;
387
388 #pragma warning(push)
389 // sscanf data comes from MySQL and is fixed
390 #pragma warning(disable: 4996)
391 if (3 == sscanf(data, "%d-%d-%d", &year, &month, &day))
392 #pragma warning(pop)
393 {
394 // Invalid dates are returned as None instead of raising ValueError
395 if (!is_valid_date(year, month, day))
396 {
397 Py_RETURN_NONE;
398 }
399 return PyDate_FromDate(year, month, day);
400 }
401
402 PyErr_SetString(PyExc_ValueError,
403 "Received incorrect DATE value from MySQL server");
404 return NULL;
405 }
406
407 /**
408 Convert a DATETIME MySQL value to Python datetime.datetime.
409
410 Convert a DATETIME MySQL value to Python datetime.datetime. The
411 fractional part is supported.
412
413 @param data string to be converted
414 @param length length of data
415
416 @return datetime.datetime object.
417 @retval PyDateTime OK
418 */
419 PyObject*
mytopy_datetime(const char * data,const unsigned long length)420 mytopy_datetime(const char *data, const unsigned long length)
421 {
422 int year= 0, month= 0, day= 0;
423 int hours= 0, mins= 0, secs= 0, usecs= 0;
424 int value= 0;
425 int parts[7]= {0};
426 int part= 0;
427 const char *end= data + length;
428
429 PyDateTime_IMPORT;
430
431 /* Parse year, month, days, hours, minutes and seconds */
432 for (;;)
433 {
434 for (value= 0; data != end && isdigit(*data) ; data++)
435 {
436 value= (value * 10) + (unsigned int)(*data - '0');
437 }
438 parts[part++]= (unsigned int)value;
439 if (part == 8 || (end-data) < 2
440 || (*data != '-' && *data != ':' && *data != ' ')
441 || !isdigit(data[1]))
442 {
443 break;
444 }
445 data++; // skip separators '-' and ':'
446 }
447
448 if (data != end && end - data >= 2 && *data == '.')
449 {
450 // Fractional part
451 int field_length= 5;
452 data++;
453 value= (unsigned int)(*data - '0');
454 while (data++ != end && isdigit(*data))
455 {
456 if (field_length-- > 0)
457 {
458 value= (value * 10) + (unsigned int)(*data - '0');
459 }
460 }
461 if (field_length >= 0)
462 {
463 while (field_length-- > 0)
464 {
465 value*= 10;
466 }
467 }
468 parts[6]= value;
469 }
470
471 year= parts[0];
472 month= parts[1];
473 day= parts[2];
474 hours= parts[3];
475 mins= parts[4];
476 secs= parts[5];
477 usecs= parts[6];
478
479 if (!is_valid_date(year, month, day))
480 {
481 Py_RETURN_NONE;
482 }
483
484 if (!is_valid_time(hours, mins, secs, usecs))
485 {
486 Py_RETURN_NONE;
487 }
488
489 return PyDateTime_FromDateAndTime(year, month, day,
490 hours, mins, secs, usecs);
491 }
492
493 /**
494 Convert a TIME MySQL value to Python datetime.timedelta.
495
496 Convert a TIME MySQL value to a Python datetime.timedelta returned
497 as PyDelta_FromDSU object.
498
499 @param data string to be converted
500 @param length length of data
501
502 @return datetime.timedelta object.
503 @retval PyDelta_FromDSU OK
504 */
505 PyObject*
mytopy_time(const char * data,const unsigned long length)506 mytopy_time(const char *data, const unsigned long length)
507 {
508 int hr= 0, min= 0, sec= 0, usec= 0;
509 int days= 0, hours= 0, seconds= 0;
510 int negative= 0;
511 int value= 0;
512 int parts[4]= {0};
513 int part= 0;
514 const char *end= data + length;
515
516 PyDateTime_IMPORT;
517
518 // Negative times
519 if (*data == '-')
520 {
521 negative= 1;
522 data++;
523 }
524
525 /* Parse hours, minutes and seconds */
526 for (;;)
527 {
528 for (value= 0; data != end && isdigit(*data) ; data++)
529 {
530 value= (value * 10) + (unsigned int)(*data - '0');
531 }
532 parts[part++]= (unsigned int)value;
533 if (part == 4 || (end-data) < 2 || *data != ':' || !isdigit(data[1]))
534 {
535 break;
536 }
537 data++; // skip time separator ':'
538 }
539
540 if (data != end && end - data >= 2 && *data == '.')
541 {
542 // Fractional part
543 int field_length= 5;
544 data++;
545 value= (unsigned int)(*data - '0');
546 while (data++ != end && isdigit(*data))
547 {
548 if (field_length-- > 0)
549 {
550 value= (value * 10) + (unsigned int)(*data - '0');
551 }
552 }
553 if (field_length >= 0)
554 {
555 while (field_length-- > 0)
556 {
557 value*= 10;
558 }
559 }
560 parts[3]= value;
561 }
562
563 hr= parts[0];
564 min= parts[1];
565 sec= parts[2];
566 usec= parts[3];
567
568 // negative time
569 if (negative) {
570 hr= hr * -1;
571 min= min * -1;
572 sec= sec * -1;
573 usec= usec * -1;
574 }
575
576 days= hr / 24;
577 hours= hr % 24;
578
579 seconds= (hours * 3600) + (min * 60) + sec;
580
581 return PyDelta_FromDSU(days, seconds, usec);
582 }
583
584 /**
585 Convert a Python datetime.datetime to MySQL DATETIME.
586
587 Convert a Python datetime.datetime to MySQL DATETIME using the
588 pytomy_date()function.
589
590 datetime_to_mysql() is a module function and can be used as
591 _mysql_connector.datetime_to_mysql.
592
593 Raises TypeError when obj is not a PyDateTime_Type.
594
595 @param self module instance
596 @param datetime the PyObject to be converted
597
598 @return Converted datetime object.
599 @retval PyBytes Python v3
600 @retval NULL Exception
601 */
602 PyObject*
datetime_to_mysql(PyObject * self,PyObject * datetime)603 datetime_to_mysql(PyObject *self, PyObject *datetime)
604 {
605 return pytomy_datetime(datetime);
606 }
607
608 /**
609 Convert a Python datetime.time to MySQL TIME.
610
611 Convert a Python datetime.time to MySQL TIME using the
612 pytomy_time()function.
613
614 time_to_mysql() is a module function and can be used as
615 _mysql_connector.time_to_mysql.
616
617 Raises TypeError when obj is not a PyTime_Type.
618
619 @param self module instance
620 @param time the PyObject to be converted
621
622 @return Converted time object.
623 @retval PyBytes Python v3
624 @retval NULL Exception
625 */
626 PyObject*
time_to_mysql(PyObject * self,PyObject * time)627 time_to_mysql(PyObject *self, PyObject *time)
628 {
629 return pytomy_time(time);
630 }
631
632 /**
633 Convert a Python datetime.date to MySQL DATE.
634
635 Convert a Python datetime.date to MySQL DATE using the
636 pytomy_date()function.
637
638 date_to_mysql() is a module function and can be used as
639 _mysql_connector.date_to_mysql.
640
641 Raises TypeError when obj is not a PyDate_Type.
642
643 @param self module instance
644 @param date the PyObject to be converted
645
646 @return Converted date object.
647 @retval PyBytes Python v3
648 @retval NULL Exception
649 */
650 PyObject*
date_to_mysql(PyObject * self,PyObject * date)651 date_to_mysql(PyObject *self, PyObject *date)
652 {
653 return pytomy_date(date);
654 }
655
656 /**
657 Convert a MySQL BIT to Python int/long.
658
659 @param obj PyObject to be converted
660
661 @return Converted decimal as string
662 @retval PyInt Python v3
663 @retval PyLong Python v2
664 */
665 PyObject*
mytopy_bit(const char * data,const unsigned long length)666 mytopy_bit(const char *data, const unsigned long length)
667 {
668 #ifdef HAVE_LONG_LONG
669 unsigned PY_LONG_LONG value= 0;
670 #else
671 unsigned PY_LONG value= 0;
672 #endif
673 const unsigned char *d= (const unsigned char*)data;
674 unsigned long size= length;
675 while (size > 0)
676 {
677 value= (value << 8) | *d++;
678 size--;
679 }
680 #ifdef HAVE_LONG_LONG
681 return PyLong_FromUnsignedLongLong(value);
682 #else
683 return PyLong_FromUnsignedLong(value);
684 #endif
685 }
686
687 /**
688 Convert a Python decimal.Decimal to MySQL DECIMAL.
689
690 Convert a Python decimal.Decimal to MySQL DECIMAL. This function also
691 removes the 'L' suffix from the resulting string when using Python v2.
692
693 @param obj PyObject to be converted
694
695 @return Converted decimal as string
696 @retval PyBytes Python v3
697 */
698 PyObject*
pytomy_decimal(PyObject * obj)699 pytomy_decimal(PyObject *obj)
700 {
701 PyObject *str= PyObject_Str(obj);
702 PyObject *tmp= (const char *)PyUnicode_1BYTE_DATA(str);
703 PyObject *ret= PyBytes_FromString(tmp);
704 Py_DECREF(tmp);
705 return ret;
706 }
707
708 /**
709 Convert a string MySQL value to Python str or bytes.
710
711 Convert, and decode if needed, a string MySQL value to
712 Python str or bytes.
713
714 @param data string to be converted
715 @param field_type field type
716 @param field_charsetnr charset number
717 @param field_length length of data
718 @param charset character used for decoding
719 @param use_unicode use unicode
720
721 @return Converted string
722 @retval PyUnicode if use unicode
723 @retval PyBytes if not use_unicode or charset is 'binary'
724 @retval NULL Exception
725 */
726 PyObject*
mytopy_string(const char * data,enum_field_types field_type,const unsigned int field_charsetnr,const unsigned long field_length,const char * charset,unsigned int use_unicode)727 mytopy_string(const char *data,
728 enum_field_types field_type,
729 const unsigned int field_charsetnr,
730 const unsigned long field_length,
731 const char *charset,
732 unsigned int use_unicode)
733 {
734 if (!charset || !data) {
735 return NULL;
736 }
737
738 if (strcmp(charset, "binary") == 0)
739 {
740 return PyByteArray_FromStringAndSize(data, field_length);
741 }
742
743 /* 'binary' charset = 63 */
744 if (use_unicode && (field_type == MYSQL_TYPE_JSON || field_charsetnr != 63))
745 {
746 return PyUnicode_Decode(data, field_length, charset, NULL);
747 }
748
749 return PyByteArray_FromStringAndSize(data, field_length);
750 }
751