mirror of
				https://github.com/psycopg/psycopg2.git
				synced 2025-10-31 07:47:30 +03:00 
			
		
		
		
	Merge branch 'withhold-transactions'
This commit is contained in:
		
						commit
						6a5f778361
					
				
							
								
								
									
										3
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								NEWS
									
									
									
									
									
								
							|  | @ -18,6 +18,9 @@ What's new in psycopg 2.5.4 | ||||||
|   proper methods (:ticket:`#219`). |   proper methods (:ticket:`#219`). | ||||||
| - Force conversion of pool arguments to integer to avoid potentially unbounded | - Force conversion of pool arguments to integer to avoid potentially unbounded | ||||||
|   pools (:ticket:`#220`). |   pools (:ticket:`#220`). | ||||||
|  | - Cursors :sql:`WITH HOLD` don't begin a new transaction upon move/fetch/close | ||||||
|  |   (:ticket:`#228`). | ||||||
|  | - Cursors :sql:`WITH HOLD` can be used in autocommit (:ticket:`#229`). | ||||||
| - Don't ignore silently the `cursor.callproc` argument without a length. | - Don't ignore silently the `cursor.callproc` argument without a length. | ||||||
| - Added a few errors missing from `~psycopg2.errorcodes`, defined by | - Added a few errors missing from `~psycopg2.errorcodes`, defined by | ||||||
|   PostgreSQL but not documented. |   PostgreSQL but not documented. | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ psyco_curs_close(cursorObject *self) | ||||||
| 
 | 
 | ||||||
|         EXC_IF_NO_MARK(self); |         EXC_IF_NO_MARK(self); | ||||||
|         PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name); |         PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name); | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) return NULL; |         if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     self->closed = 1; |     self->closed = 1; | ||||||
|  | @ -444,7 +444,7 @@ _psyco_curs_execute(cursorObject *self, | ||||||
| 
 | 
 | ||||||
|     /* At this point, the SQL statement must be str, not unicode */ |     /* At this point, the SQL statement must be str, not unicode */ | ||||||
| 
 | 
 | ||||||
|     tmp = pq_execute(self, Bytes_AS_STRING(self->query), async, no_result); |     tmp = pq_execute(self, Bytes_AS_STRING(self->query), async, no_result, 0); | ||||||
|     Dprintf("psyco_curs_execute: res = %d, pgres = %p", tmp, self->pgres); |     Dprintf("psyco_curs_execute: res = %d, pgres = %p", tmp, self->pgres); | ||||||
|     if (tmp < 0) { goto exit; } |     if (tmp < 0) { goto exit; } | ||||||
| 
 | 
 | ||||||
|  | @ -478,7 +478,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs) | ||||||
|                 "can't call .execute() on named cursors more than once"); |                 "can't call .execute() on named cursors more than once"); | ||||||
|             return NULL; |             return NULL; | ||||||
|         } |         } | ||||||
|         if (self->conn->autocommit) { |         if (self->conn->autocommit && !self->withhold) { | ||||||
|             psyco_set_error(ProgrammingError, self, |             psyco_set_error(ProgrammingError, self, | ||||||
|                 "can't use a named cursor outside of transactions"); |                 "can't use a named cursor outside of transactions"); | ||||||
|             return NULL; |             return NULL; | ||||||
|  | @ -766,7 +766,7 @@ psyco_curs_fetchone(cursorObject *self) | ||||||
|         EXC_IF_ASYNC_IN_PROGRESS(self, fetchone); |         EXC_IF_ASYNC_IN_PROGRESS(self, fetchone); | ||||||
|         EXC_IF_TPC_PREPARED(self->conn, fetchone); |         EXC_IF_TPC_PREPARED(self->conn, fetchone); | ||||||
|         PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name); |         PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name); | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) return NULL; |         if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; | ||||||
|         if (_psyco_curs_prefetch(self) < 0) return NULL; |         if (_psyco_curs_prefetch(self) < 0) return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -816,7 +816,7 @@ psyco_curs_next_named(cursorObject *self) | ||||||
| 
 | 
 | ||||||
|         PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM \"%s\"", |         PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM \"%s\"", | ||||||
|             self->itersize, self->name); |             self->itersize, self->name); | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) return NULL; |         if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; | ||||||
|         if (_psyco_curs_prefetch(self) < 0) return NULL; |         if (_psyco_curs_prefetch(self) < 0) return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -885,7 +885,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords) | ||||||
|         EXC_IF_TPC_PREPARED(self->conn, fetchone); |         EXC_IF_TPC_PREPARED(self->conn, fetchone); | ||||||
|         PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"", |         PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"", | ||||||
|             (int)size, self->name); |             (int)size, self->name); | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) { goto exit; } |         if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } | ||||||
|         if (_psyco_curs_prefetch(self) < 0) { goto exit; } |         if (_psyco_curs_prefetch(self) < 0) { goto exit; } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -960,7 +960,7 @@ psyco_curs_fetchall(cursorObject *self) | ||||||
|         EXC_IF_ASYNC_IN_PROGRESS(self, fetchall); |         EXC_IF_ASYNC_IN_PROGRESS(self, fetchall); | ||||||
|         EXC_IF_TPC_PREPARED(self->conn, fetchall); |         EXC_IF_TPC_PREPARED(self->conn, fetchall); | ||||||
|         PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name); |         PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name); | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) { goto exit; } |         if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; } | ||||||
|         if (_psyco_curs_prefetch(self) < 0) { goto exit; } |         if (_psyco_curs_prefetch(self) < 0) { goto exit; } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1178,7 +1178,7 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs) | ||||||
|         else { |         else { | ||||||
|             PyOS_snprintf(buffer, 127, "MOVE %d FROM \"%s\"", value, self->name); |             PyOS_snprintf(buffer, 127, "MOVE %d FROM \"%s\"", value, self->name); | ||||||
|         } |         } | ||||||
|         if (pq_execute(self, buffer, 0, 0) == -1) return NULL; |         if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL; | ||||||
|         if (_psyco_curs_prefetch(self) < 0) return NULL; |         if (_psyco_curs_prefetch(self) < 0) return NULL; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1391,7 +1391,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs) | ||||||
|     Py_INCREF(file); |     Py_INCREF(file); | ||||||
|     self->copyfile = file; |     self->copyfile = file; | ||||||
| 
 | 
 | ||||||
|     if (pq_execute(self, query, 0, 0) >= 0) { |     if (pq_execute(self, query, 0, 0, 0) >= 0) { | ||||||
|         res = Py_None; |         res = Py_None; | ||||||
|         Py_INCREF(Py_None); |         Py_INCREF(Py_None); | ||||||
|     } |     } | ||||||
|  | @ -1485,7 +1485,7 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs) | ||||||
|     Py_INCREF(file); |     Py_INCREF(file); | ||||||
|     self->copyfile = file; |     self->copyfile = file; | ||||||
| 
 | 
 | ||||||
|     if (pq_execute(self, query, 0, 0) >= 0) { |     if (pq_execute(self, query, 0, 0, 0) >= 0) { | ||||||
|         res = Py_None; |         res = Py_None; | ||||||
|         Py_INCREF(Py_None); |         Py_INCREF(Py_None); | ||||||
|     } |     } | ||||||
|  | @ -1559,7 +1559,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs) | ||||||
|     self->copyfile = file; |     self->copyfile = file; | ||||||
| 
 | 
 | ||||||
|     /* At this point, the SQL statement must be str, not unicode */ |     /* At this point, the SQL statement must be str, not unicode */ | ||||||
|     if (pq_execute(self, Bytes_AS_STRING(sql), 0, 0) >= 0) { |     if (pq_execute(self, Bytes_AS_STRING(sql), 0, 0, 0) >= 0) { | ||||||
|         res = Py_None; |         res = Py_None; | ||||||
|         Py_INCREF(res); |         Py_INCREF(res); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -893,7 +893,7 @@ pq_flush(connectionObject *conn) | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| RAISES_NEG int | RAISES_NEG int | ||||||
| pq_execute(cursorObject *curs, const char *query, int async, int no_result) | pq_execute(cursorObject *curs, const char *query, int async, int no_result, int no_begin) | ||||||
| { | { | ||||||
|     PGresult *pgres = NULL; |     PGresult *pgres = NULL; | ||||||
|     char *error = NULL; |     char *error = NULL; | ||||||
|  | @ -916,7 +916,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result) | ||||||
|     Py_BEGIN_ALLOW_THREADS; |     Py_BEGIN_ALLOW_THREADS; | ||||||
|     pthread_mutex_lock(&(curs->conn->lock)); |     pthread_mutex_lock(&(curs->conn->lock)); | ||||||
| 
 | 
 | ||||||
|     if (pq_begin_locked(curs->conn, &pgres, &error, &_save) < 0) { |     if (!no_begin && pq_begin_locked(curs->conn, &pgres, &error, &_save) < 0) { | ||||||
|         pthread_mutex_unlock(&(curs->conn->lock)); |         pthread_mutex_unlock(&(curs->conn->lock)); | ||||||
|         Py_BLOCK_THREADS; |         Py_BLOCK_THREADS; | ||||||
|         pq_complete_error(curs->conn, &pgres, &error); |         pq_complete_error(curs->conn, &pgres, &error); | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ | ||||||
| HIDDEN PGresult *pq_get_last_result(connectionObject *conn); | HIDDEN PGresult *pq_get_last_result(connectionObject *conn); | ||||||
| RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs, int no_result); | RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs, int no_result); | ||||||
| RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query, | RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query, | ||||||
|                                  int async, int no_result); |                                  int async, int no_result, int no_begin); | ||||||
| HIDDEN int pq_send_query(connectionObject *conn, const char *query); | HIDDEN int pq_send_query(connectionObject *conn, const char *query); | ||||||
| HIDDEN int pq_begin_locked(connectionObject *conn, PGresult **pgres, | HIDDEN int pq_begin_locked(connectionObject *conn, PGresult **pgres, | ||||||
|                            char **error, PyThreadState **tstate); |                            char **error, PyThreadState **tstate); | ||||||
|  |  | ||||||
|  | @ -176,10 +176,7 @@ class CursorTests(ConnectingTestCase): | ||||||
|         curs.execute("select data from invname order by data") |         curs.execute("select data from invname order by data") | ||||||
|         self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)]) |         self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)]) | ||||||
| 
 | 
 | ||||||
|     def test_withhold(self): |     def _create_withhold_table(self): | ||||||
|         self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, |  | ||||||
|                           withhold=True) |  | ||||||
| 
 |  | ||||||
|         curs = self.conn.cursor() |         curs = self.conn.cursor() | ||||||
|         try: |         try: | ||||||
|             curs.execute("drop table withhold") |             curs.execute("drop table withhold") | ||||||
|  | @ -190,6 +187,11 @@ class CursorTests(ConnectingTestCase): | ||||||
|             curs.execute("insert into withhold values (%s)", (i,)) |             curs.execute("insert into withhold values (%s)", (i,)) | ||||||
|         curs.close() |         curs.close() | ||||||
| 
 | 
 | ||||||
|  |     def test_withhold(self): | ||||||
|  |         self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, | ||||||
|  |                           withhold=True) | ||||||
|  | 
 | ||||||
|  |         self._create_withhold_table() | ||||||
|         curs = self.conn.cursor("W") |         curs = self.conn.cursor("W") | ||||||
|         self.assertEqual(curs.withhold, False); |         self.assertEqual(curs.withhold, False); | ||||||
|         curs.withhold = True |         curs.withhold = True | ||||||
|  | @ -209,6 +211,52 @@ class CursorTests(ConnectingTestCase): | ||||||
|         curs.execute("drop table withhold") |         curs.execute("drop table withhold") | ||||||
|         self.conn.commit() |         self.conn.commit() | ||||||
| 
 | 
 | ||||||
|  |     def test_withhold_no_begin(self): | ||||||
|  |         self._create_withhold_table() | ||||||
|  |         curs = self.conn.cursor("w", withhold=True) | ||||||
|  |         curs.execute("select data from withhold order by data") | ||||||
|  |         self.assertEqual(curs.fetchone(), (10,)) | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_INTRANS) | ||||||
|  | 
 | ||||||
|  |         self.conn.commit() | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(curs.fetchone(), (20,)) | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|  |         curs.close() | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|  |     def test_withhold_autocommit(self): | ||||||
|  |         self._create_withhold_table() | ||||||
|  |         self.conn.commit() | ||||||
|  |         self.conn.autocommit = True | ||||||
|  |         curs = self.conn.cursor("w", withhold=True) | ||||||
|  |         curs.execute("select data from withhold order by data") | ||||||
|  | 
 | ||||||
|  |         self.assertEqual(curs.fetchone(), (10,)) | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|  |         self.conn.commit() | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|  |         curs.close() | ||||||
|  |         self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) | ||||||
|  |         self.assertEqual(self.conn.get_transaction_status(), | ||||||
|  |                          psycopg2.extensions.TRANSACTION_STATUS_IDLE) | ||||||
|  | 
 | ||||||
|     def test_scrollable(self): |     def test_scrollable(self): | ||||||
|         self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, |         self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, | ||||||
|                           scrollable=True) |                           scrollable=True) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user