mirror of
				https://github.com/psycopg/psycopg2.git
				synced 2025-10-31 07:47:30 +03:00 
			
		
		
		
	Merge branch 'fix-512'
This commit is contained in:
		
						commit
						451e1e2e73
					
				
							
								
								
									
										1
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								NEWS
									
									
									
									
									
								
							|  | @ -47,6 +47,7 @@ Bug fixes: | |||
| 
 | ||||
| - Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection` | ||||
|   (:ticket:`#483`). | ||||
| - Fixed integer overflow in :sql:`interval` seconds (:ticket:`#512`). | ||||
| 
 | ||||
| Other changes: | ||||
| 
 | ||||
|  |  | |||
|  | @ -60,6 +60,6 @@ please check the `project homepage`__. | |||
|     :target: https://travis-ci.org/psycopg/psycopg2 | ||||
|     :alt: Linux and OSX build status | ||||
| 
 | ||||
| .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/psycopg/psycopg2?svg=true | ||||
| .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/psycopg/psycopg2?branch=master&svg=true | ||||
|     :target: https://ci.appveyor.com/project/psycopg/psycopg2 | ||||
|     :alt: Windows build status | ||||
|  |  | |||
|  | @ -220,11 +220,9 @@ typecast_PYTIME_cast(const char *str, Py_ssize_t len, PyObject *curs) | |||
| static PyObject * | ||||
| typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) | ||||
| { | ||||
|     long years = 0, months = 0, days = 0; | ||||
|     double hours = 0.0, minutes = 0.0, seconds = 0.0, hundredths = 0.0; | ||||
|     double v = 0.0, sign = 1.0, denominator = 1.0; | ||||
|     int part = 0, sec; | ||||
|     double micro; | ||||
|     long v = 0, years = 0, months = 0, hours = 0, minutes = 0, micros = 0; | ||||
|     PY_LONG_LONG days = 0, seconds = 0; | ||||
|     int sign = 1, denom = 1, part = 0; | ||||
| 
 | ||||
|     if (str == NULL) { Py_RETURN_NONE; } | ||||
| 
 | ||||
|  | @ -234,56 +232,68 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) | |||
|         switch (*str) { | ||||
| 
 | ||||
|         case '-': | ||||
|             sign = -1.0; | ||||
|             sign = -1; | ||||
|             break; | ||||
| 
 | ||||
|         case '0': case '1': case '2': case '3': case '4': | ||||
|         case '5': case '6': case '7': case '8': case '9': | ||||
|             v = v * 10.0 + (double)(*str - '0'); | ||||
|             if (part == 6){ | ||||
|                 denominator *= 10; | ||||
|             { | ||||
|                 long v1; | ||||
|                 v1 = v * 10 + (*str - '0'); | ||||
|                 /* detect either a rollover, happening if v is really too short,
 | ||||
|                  * or too big value. On Win where long == int the 2nd check | ||||
|                  * is useless. */ | ||||
|                 if (v1 < v || v1 > (long)INT_MAX) { | ||||
|                     PyErr_SetString( | ||||
|                         PyExc_OverflowError, "interval component too big"); | ||||
|                     return NULL; | ||||
|                 } | ||||
|                 v = v1; | ||||
|             } | ||||
|             if (part == 6) { | ||||
|                 denom *= 10; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case 'y': | ||||
|             if (part == 0) { | ||||
|                 years = (long)(v*sign); | ||||
|                 years = v * sign; | ||||
|                 v = 0; sign = 1; part = 1; | ||||
|                 str = skip_until_space2(str, &len); | ||||
|                 v = 0.0; sign = 1.0; part = 1; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case 'm': | ||||
|             if (part <= 1) { | ||||
|                 months = (long)(v*sign); | ||||
|                 months = v * sign; | ||||
|                 v = 0; sign = 1; part = 2; | ||||
|                 str = skip_until_space2(str, &len); | ||||
|                 v = 0.0; sign = 1.0; part = 2; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case 'd': | ||||
|             if (part <= 2) { | ||||
|                 days = (long)(v*sign); | ||||
|                 days = v * sign; | ||||
|                 v = 0; sign = 1; part = 3; | ||||
|                 str = skip_until_space2(str, &len); | ||||
|                 v = 0.0; sign = 1.0; part = 3; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case ':': | ||||
|             if (part <= 3) { | ||||
|                 hours = v; | ||||
|                 v = 0.0; part = 4; | ||||
|                 v = 0; part = 4; | ||||
|             } | ||||
|             else if (part == 4) { | ||||
|                 minutes = v; | ||||
|                 v = 0.0; part = 5; | ||||
|                 v = 0; part = 5; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|         case '.': | ||||
|             if (part == 5) { | ||||
|                 seconds = v; | ||||
|                 v = 0.0; part = 6; | ||||
|                 v = 0; part = 6; | ||||
|             } | ||||
|             break; | ||||
| 
 | ||||
|  | @ -294,7 +304,7 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) | |||
|         str++; | ||||
|     } | ||||
| 
 | ||||
|     /* manage last value, be it minutes or seconds or hundredths of a second */ | ||||
|     /* manage last value, be it minutes or seconds or microseconds */ | ||||
|     if (part == 4) { | ||||
|         minutes = v; | ||||
|     } | ||||
|  | @ -302,25 +312,30 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs) | |||
|         seconds = v; | ||||
|     } | ||||
|     else if (part == 6) { | ||||
|         hundredths = v; | ||||
|         hundredths = hundredths/denominator; | ||||
|         micros = v; | ||||
|         if (denom < 1000000L) { | ||||
|             do { | ||||
|                 micros *= 10; | ||||
|                 denom *= 10; | ||||
|             } while (denom < 1000000L); | ||||
|         } | ||||
|         else if (denom > 1000000L) { | ||||
|             micros = (long)round((double)micros / denom * 1000000.0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* calculates seconds */ | ||||
|     if (sign < 0.0) { | ||||
|         seconds = - (hundredths + seconds + minutes*60 + hours*3600); | ||||
|     } | ||||
|     else { | ||||
|         seconds += hundredths + minutes*60 + hours*3600; | ||||
|     /* add hour, minutes, seconds together and include the sign */ | ||||
|     seconds += 60 * (PY_LONG_LONG)minutes + 3600 * (PY_LONG_LONG)hours; | ||||
|     if (sign < 0) { | ||||
|         seconds = -seconds; | ||||
|         micros = -micros; | ||||
|     } | ||||
| 
 | ||||
|     /* calculates days */ | ||||
|     days += years*365 + months*30; | ||||
|     /* add the days, months years together - they already include a sign */ | ||||
|     days += 30 * (PY_LONG_LONG)months + 365 * (PY_LONG_LONG)years; | ||||
| 
 | ||||
|     micro = (seconds - floor(seconds)) * 1000000.0; | ||||
|     sec = (int)floor(seconds); | ||||
|     return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "iii", | ||||
|                                  days, sec, (int)round(micro)); | ||||
|     return PyObject_CallFunction((PyObject*)PyDateTimeAPI->DeltaType, "LLl", | ||||
|                                  days, seconds, micros); | ||||
| } | ||||
| 
 | ||||
| /* psycopg defaults to using python datetime types */ | ||||
|  |  | |||
|  | @ -28,6 +28,11 @@ from psycopg2.tz import FixedOffsetTimezone, ZERO | |||
| from testutils import unittest, ConnectingTestCase, skip_before_postgres | ||||
| 
 | ||||
| 
 | ||||
| def total_seconds(d): | ||||
|     """Return total number of seconds of a timedelta as a float.""" | ||||
|     return d.days * 24 * 60 * 60 + d.seconds + d.microseconds / 1000000.0 | ||||
| 
 | ||||
| 
 | ||||
| class CommonDatetimeTestsMixin: | ||||
| 
 | ||||
|     def execute(self, *args): | ||||
|  | @ -333,6 +338,59 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin): | |||
|         t = self.execute("select '24:00+05:30'::timetz;") | ||||
|         self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(330))) | ||||
| 
 | ||||
|     def test_large_interval(self): | ||||
|         t = self.execute("select '999999:00:00'::interval") | ||||
|         self.assertEqual(total_seconds(t), 999999 * 60 * 60) | ||||
| 
 | ||||
|         t = self.execute("select '-999999:00:00'::interval") | ||||
|         self.assertEqual(total_seconds(t), -999999 * 60 * 60) | ||||
| 
 | ||||
|         t = self.execute("select '999999:00:00.1'::interval") | ||||
|         self.assertEqual(total_seconds(t), 999999 * 60 * 60 + 0.1) | ||||
| 
 | ||||
|         t = self.execute("select '999999:00:00.9'::interval") | ||||
|         self.assertEqual(total_seconds(t), 999999 * 60 * 60 + 0.9) | ||||
| 
 | ||||
|         t = self.execute("select '-999999:00:00.1'::interval") | ||||
|         self.assertEqual(total_seconds(t), -999999 * 60 * 60 - 0.1) | ||||
| 
 | ||||
|         t = self.execute("select '-999999:00:00.9'::interval") | ||||
|         self.assertEqual(total_seconds(t), -999999 * 60 * 60 - 0.9) | ||||
| 
 | ||||
|     def test_micros_rounding(self): | ||||
|         t = self.execute("select '0.1'::interval") | ||||
|         self.assertEqual(total_seconds(t), 0.1) | ||||
| 
 | ||||
|         t = self.execute("select '0.01'::interval") | ||||
|         self.assertEqual(total_seconds(t), 0.01) | ||||
| 
 | ||||
|         t = self.execute("select '0.000001'::interval") | ||||
|         self.assertEqual(total_seconds(t), 1e-6) | ||||
| 
 | ||||
|         t = self.execute("select '0.0000004'::interval") | ||||
|         self.assertEqual(total_seconds(t), 0) | ||||
| 
 | ||||
|         t = self.execute("select '0.0000006'::interval") | ||||
|         self.assertEqual(total_seconds(t), 1e-6) | ||||
| 
 | ||||
|     def test_interval_overflow(self): | ||||
|         cur = self.conn.cursor() | ||||
|         # hack a cursor to receive values too extreme to be represented | ||||
|         # but still I want an error, not a random number | ||||
|         psycopg2.extensions.register_type( | ||||
|             psycopg2.extensions.new_type( | ||||
|                 psycopg2.STRING.values, 'WAT', psycopg2.extensions.INTERVAL), | ||||
|             cur) | ||||
| 
 | ||||
|         def f(val): | ||||
|             cur.execute("select '%s'::text" % val) | ||||
|             return cur.fetchone()[0] | ||||
| 
 | ||||
|         self.assertRaises(OverflowError, f, '100000000000000000:00:00') | ||||
|         self.assertRaises(OverflowError, f, '00:100000000000000000:00:00') | ||||
|         self.assertRaises(OverflowError, f, '00:00:100000000000000000:00') | ||||
|         self.assertRaises(OverflowError, f, '00:00:00.100000000000000000') | ||||
| 
 | ||||
| 
 | ||||
| # Only run the datetime tests if psycopg was compiled with support. | ||||
| if not hasattr(psycopg2.extensions, 'PYDATETIME'): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user