11import 'dart:async' ;
2+ import 'dart:collection' ;
23import 'dart:convert' ;
34import 'dart:io' ;
45import 'dart:typed_data' ;
@@ -258,7 +259,6 @@ class PgConnectionImplementation extends _PgSessionBase implements Connection {
258259 ),
259260 async .StreamSinkTransformer .fromHandlers (handleData: (msg, sink) {
260261 print ('[$hash ][out] $msg ' );
261- print ('[out] $msg ' );
262262 sink.add (msg);
263263 }),
264264 ));
@@ -646,6 +646,14 @@ class _PreparedStatement extends Statement {
646646 final String _name;
647647 final _PgSessionBase _session;
648648
649+ /// Apparently, when we are in a transaction and using extended query mode,
650+ /// one needs to close the portals to release the locks on tables.
651+ /// This queue will collect the portal names to close and they will be closed
652+ /// when the prepared statement is disposed or a run call completes.
653+ ///
654+ /// See more in https://github.com/isoos/postgresql-dart/issues/390
655+ Queue <String >? _portalsToClose;
656+
649657 _PreparedStatement (this ._description, this ._name, this ._session);
650658
651659 @override
@@ -674,17 +682,33 @@ class _PreparedStatement extends Statement {
674682 );
675683 } finally {
676684 await subscription.cancel ();
685+ await _closePendingPortals ();
677686 }
678687 }
679688
680689 @override
681690 Future <void > dispose () async {
682691 // Don't send a dispose message if the connection is already closed.
683692 if (! _session._connection._isClosing) {
693+ await _closePendingPortals ();
684694 await _session._sendAndWaitForQuery <CloseCompleteMessage >(
685695 CloseMessage .statement (_name));
686696 }
687697 }
698+
699+ void _addPortalToClose (String portalName) {
700+ _portalsToClose ?? = Queue ();
701+ _portalsToClose! .add (portalName);
702+ }
703+
704+ Future <void > _closePendingPortals () async {
705+ final list = _portalsToClose;
706+ while (list != null && list.isNotEmpty) {
707+ final portalName = list.removeFirst ();
708+ await _session._sendAndWaitForQuery <CloseCompleteMessage >(
709+ CloseMessage .portal (portalName));
710+ }
711+ }
688712}
689713
690714class _BoundStatement extends Stream <ResultRow > implements ResultStream {
@@ -718,6 +742,7 @@ class _PgResultStreamSubscription
718742 final _schema = Completer <ResultSchema >();
719743 final _done = Completer <void >();
720744 ResultSchema ? _resultSchema;
745+ _BoundStatement ? _boundStatement;
721746
722747 @override
723748 PgConnectionImplementation get connection => session._connection;
@@ -729,6 +754,7 @@ class _PgResultStreamSubscription
729754 _BoundStatement statement, this ._controller, this ._source)
730755 : session = statement.statement._session,
731756 ignoreRows = false ,
757+ _boundStatement = statement,
732758 _trace = StackTrace .current {
733759 _scheduleStatement (() async {
734760 connection._pending = this ;
@@ -885,6 +911,11 @@ class _PgResultStreamSubscription
885911 // we'll get this more than once.
886912 _affectedRowsSoFar += message.rowsAffected;
887913 case ReadyForQueryMessage ():
914+ // It looks like simple query protocol statements, or statements outside of a transaction
915+ // do not need the portal to be closed explicitly.
916+ if (message.state == ReadyForQueryMessageState .transaction) {
917+ _boundStatement? .statement._addPortalToClose (_portalName);
918+ }
888919 await _completeQuery ();
889920 case CopyBothResponseMessage ():
890921 // This message indicates a successful start for Streaming Replication.
0 commit comments