@@ -24,7 +24,7 @@ def __init__(self):
2424
2525 def log_message (self , message , level = Qgis .Info ):
2626 """Log message to QGIS message log."""
27- QgsMessageLog .logMessage (message , 'PostgreSQL Template Manager ' , level )
27+ QgsMessageLog .logMessage (message , 'KGR Toolbox ' , level )
2828
2929 def set_connection_params (self , host , port , database , username , password ):
3030 """Set connection parameters."""
@@ -705,12 +705,148 @@ def _upload_project_content(self, database_name, schema, table, project_name, co
705705 except psycopg2 .Error as e :
706706 self .log_message (f"Error uploading project content: { str (e )} " , Qgis .Critical )
707707 return False
708-
708+
709+
710+ def get_active_connections (self , database_name ):
711+ """Get list of active connections to a specific database."""
712+ try :
713+ conn_params = self .connection_params .copy ()
714+ conn_params ['database' ] = 'postgres'
715+
716+ conn = psycopg2 .connect (** conn_params )
717+ cursor = conn .cursor ()
718+
719+ # Query to get active connections (excluding our own connection)
720+ query = """
721+ SELECT
722+ pid,
723+ usename,
724+ client_addr,
725+ client_hostname,
726+ client_port,
727+ backend_start,
728+ state,
729+ query
730+ FROM pg_stat_activity
731+ WHERE datname = %s
732+ AND pid != pg_backend_pid()
733+ AND state != 'idle'
734+ ORDER BY backend_start;
735+ """
736+
737+ cursor .execute (query , (database_name ,))
738+ connections = cursor .fetchall ()
739+
740+ cursor .close ()
741+ conn .close ()
742+
743+ return connections
744+
745+ except psycopg2 .Error as e :
746+ self .log_message (f"Error getting active connections: { str (e )} " , Qgis .Critical )
747+ return []
748+
749+ def get_connection_count (self , database_name ):
750+ """Get count of active connections to a specific database."""
751+ try :
752+ conn_params = self .connection_params .copy ()
753+ conn_params ['database' ] = 'postgres'
754+
755+ conn = psycopg2 .connect (** conn_params )
756+ cursor = conn .cursor ()
757+
758+ # Count connections excluding our own
759+ query = """
760+ SELECT COUNT(*)
761+ FROM pg_stat_activity
762+ WHERE datname = %s
763+ AND pid != pg_backend_pid();
764+ """
765+
766+ cursor .execute (query , (database_name ,))
767+ count = cursor .fetchone ()[0 ]
768+
769+ cursor .close ()
770+ conn .close ()
771+
772+ return count
773+
774+ except psycopg2 .Error as e :
775+ self .log_message (f"Error getting connection count: { str (e )} " , Qgis .Critical )
776+ return 0
777+
778+ def drop_database_connections (self , database_name ):
779+ """Drop all active connections to a specific database."""
780+ try :
781+ self .progress_updated .emit (f"Dropping active connections to '{ database_name } '..." )
782+
783+ conn_params = self .connection_params .copy ()
784+ conn_params ['database' ] = 'postgres'
785+
786+ conn = psycopg2 .connect (** conn_params )
787+ conn .set_isolation_level (ISOLATION_LEVEL_AUTOCOMMIT )
788+ cursor = conn .cursor ()
789+
790+ # Get connection details before dropping
791+ connections = self .get_active_connections (database_name )
792+
793+ if connections :
794+ self .progress_updated .emit (f"Found { len (connections )} active connections to drop" )
795+
796+ # Log connection details
797+ for conn_info in connections :
798+ pid , username , client_addr , client_hostname , client_port , backend_start , state , query = conn_info
799+ self .log_message (f"Dropping connection: PID={ pid } , User={ username } , Client={ client_addr or client_hostname } " , Qgis .Info )
800+
801+ # Drop all connections to the database (excluding our own)
802+ terminate_query = """
803+ SELECT pg_terminate_backend(pid)
804+ FROM pg_stat_activity
805+ WHERE datname = %s
806+ AND pid != pg_backend_pid();
807+ """
808+
809+ cursor .execute (terminate_query , (database_name ,))
810+ terminated_connections = cursor .fetchall ()
811+
812+ # Count successful terminations
813+ successful_terminations = sum (1 for result in terminated_connections if result [0 ])
814+
815+ cursor .close ()
816+ conn .close ()
817+
818+ self .progress_updated .emit (f"Successfully dropped { successful_terminations } connections" )
819+ return True
820+
821+ except psycopg2 .Error as e :
822+ error_msg = f"Error dropping database connections: { str (e )} "
823+ self .log_message (error_msg , Qgis .Critical )
824+ self .progress_updated .emit (error_msg )
825+ return False
826+
709827 def create_template (self , source_db , template_name ):
710828 """Create a template from source database."""
711829 try :
712830 self .progress_updated .emit (f"Creating template '{ template_name } ' from '{ source_db } '..." )
713831
832+ # Check for active connections first
833+ connection_count = self .get_connection_count (source_db )
834+ if connection_count > 0 :
835+ self .progress_updated .emit (f"Found { connection_count } active connections to '{ source_db } '" )
836+
837+ # Drop connections
838+ if not self .drop_database_connections (source_db ):
839+ raise Exception ("Failed to drop database connections" )
840+
841+ # Wait a moment for connections to be fully dropped
842+ import time
843+ time .sleep (1 )
844+
845+ # Verify connections are dropped
846+ remaining_connections = self .get_connection_count (source_db )
847+ if remaining_connections > 0 :
848+ raise Exception (f"Still { remaining_connections } active connections after termination" )
849+
714850 conn_params = self .connection_params .copy ()
715851 conn_params ['database' ] = 'postgres'
716852
@@ -770,7 +906,7 @@ def create_template(self, source_db, template_name):
770906 self .log_message (error_msg , Qgis .Critical )
771907 self .operation_finished .emit (False , error_msg )
772908 return False
773-
909+
774910 def create_database_from_template (self , template_name , new_db_name ):
775911 """Create a new database from template."""
776912 try :
0 commit comments