comparison roundup/backends/back_postgresql.py @ 7714:b41750bf9f03

fix: figure out dbname when using pg_service. See: https://issues.roundup-tracker.org/msg7890 where drop database is missing the dbname.
author John Rouillard <rouilj@ieee.org>
date Mon, 18 Dec 2023 16:56:49 -0500
parents 4af0d235b570
children 95f0002e85c4
comparison
equal deleted inserted replaced
7713:2c1d30467909 7714:b41750bf9f03
48 return d 48 return d
49 49
50 50
51 def db_create(config): 51 def db_create(config):
52 """Clear all database contents and drop database itself""" 52 """Clear all database contents and drop database itself"""
53 command = "CREATE DATABASE \"%s\" WITH ENCODING='UNICODE'" % config.RDBMS_NAME 53 command = ("CREATE DATABASE \"%s\" WITH ENCODING='UNICODE'" %
54 get_database_name(config))
54 if config.RDBMS_TEMPLATE: 55 if config.RDBMS_TEMPLATE:
55 command = command + " TEMPLATE=%s" % config.RDBMS_TEMPLATE 56 command = command + " TEMPLATE=%s" % config.RDBMS_TEMPLATE
56 logging.getLogger('roundup.hyperdb').info(command) 57 logging.getLogger('roundup.hyperdb').info(command)
57 db_command(config, command) 58 db_command(config, command)
58 59
59 60
60 def db_nuke(config): 61 def db_nuke(config):
61 """Clear all database contents and drop database itself""" 62 """Clear all database contents and drop database itself"""
62 command = 'DROP DATABASE "%s"' % config.RDBMS_NAME 63 command = 'DROP DATABASE "%s"' % get_database_name(config)
64
63 logging.getLogger('roundup.hyperdb').info(command) 65 logging.getLogger('roundup.hyperdb').info(command)
64 db_command(config, command) 66 db_command(config, command)
65 67
66 if os.path.exists(config.DATABASE): 68 if os.path.exists(config.DATABASE):
67 shutil.rmtree(config.DATABASE) 69 shutil.rmtree(config.DATABASE)
68 70
71
72 def get_database_name(config):
73 '''Get database name using config.RDBMS_NAME or config.RDBMS_SERVICE.
74
75 If database specifed using RDBMS_SERVICE does not exist,
76 the error message is parsed for the database name. This
77 will fail if the error message changes. The alternative is
78 to try to find and parse the .pg_service .ini style file on
79 unix/windows. This is less palatable.
80
81 If the database specified using RDBMS_SERVICE does exist, (i.e. we
82 are doing a nuke operation), use psycopg.extenstion.ConnectionInfo
83 to get the dbname. This requires psycopg2 > 2.8 from 2018.
84 '''
85
86 if config.RDBMS_NAME:
87 return config.RDBMS_NAME
88
89 template1 = connection_dict(config)
90 try:
91 conn = psycopg2.connect(**template1)
92 except psycopg2.OperationalError as message:
93 import re
94 # extract db name from error:
95 # 'connection to server at "127.0.0.1", port 5432 failed: \
96 # FATAL: database "rounduptest" does not exist\n'
97 # ugh.
98 #
99 # Database name is any character sequence not including a " or
100 # whitespace. Arguably both are allowed by:
101 #
102 # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
103 #
104 # with suitable quoting but ... really.
105 search = re.search(
106 'FATAL:\s+database\s+"([^"\s]*)"\s+does\s+not\s+exist',
107 message.args[0])
108 if search:
109 dbname = search.groups()[0]
110 return dbname
111
112 raise hyperdb.DatabaseError(
113 "Unable to determine database from service: %s" % message)
114
115 dbname = psycopg2.extensions.ConnectionInfo(conn).dbname
116 conn.close()
117 return dbname
69 118
70 def db_command(config, command, database='postgres'): 119 def db_command(config, command, database='postgres'):
71 '''Perform some sort of database-level command. Retry 10 times if we 120 '''Perform some sort of database-level command. Retry 10 times if we
72 fail by conflicting with another user. 121 fail by conflicting with another user.
73 122
91 for _n in range(10): 140 for _n in range(10):
92 if pg_command(cursor, command): 141 if pg_command(cursor, command):
93 return 142 return
94 finally: 143 finally:
95 conn.close() 144 conn.close()
96 raise RuntimeError('10 attempts to create database failed') 145 raise RuntimeError('10 attempts to create database failed when running: %s' % commandb)
97 146
98 147
99 def pg_command(cursor, command): 148 def pg_command(cursor, command):
100 '''Execute the postgresql command, which may be blocked by some other 149 '''Execute the postgresql command, which may be blocked by some other
101 user connecting to the database, and return a true value if it succeeds. 150 user connecting to the database, and return a true value if it succeeds.

Roundup Issue Tracker: http://roundup-tracker.org/