Skip to content

Commit 05c49e2

Browse files
committed
add archive mode
1 parent e50308c commit 05c49e2

13 files changed

+1516
-781
lines changed

README.md

Lines changed: 117 additions & 325 deletions
Large diffs are not rendered by default.

__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
def classFactory(iface):
2-
"""Load PostgreSQLTemplateManager class from file postgresql_template_manager.
2+
"""Load KgrToolbox class from file kgr_toolbox.
33
44
Args:
55
iface: A QGIS interface instance.
66
"""
7-
from .postgresql_template_manager import PostgreSQLTemplateManager
8-
return PostgreSQLTemplateManager(iface)
7+
from .kgr_toolbox import KgrToolbox
8+
return KgrToolbox(iface)

database_manager.py

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)