Coverage for .tox/coverage/lib/python3.13/site-packages/wuttamess/postgres.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-20 15:16 -0500

1# -*- coding: utf-8; -*- 

2################################################################################ 

3# 

4# WuttaMess -- Fabric Automation Helpers 

5# Copyright © 2024-2025 Lance Edgar 

6# 

7# This file is part of Wutta Framework. 

8# 

9# Wutta Framework is free software: you can redistribute it and/or modify it 

10# under the terms of the GNU General Public License as published by the Free 

11# Software Foundation, either version 3 of the License, or (at your option) any 

12# later version. 

13# 

14# Wutta Framework is distributed in the hope that it will be useful, but 

15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 

17# more details. 

18# 

19# You should have received a copy of the GNU General Public License along with 

20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>. 

21# 

22################################################################################ 

23""" 

24PostgreSQL DB utilities 

25""" 

26 

27 

28def sql(c, sql_, database="", port=None, **kwargs): 

29 """ 

30 Execute some SQL as the ``postgres`` user. 

31 

32 :param c: Fabric connection. 

33 

34 :param sql_: SQL string to execute. 

35 

36 :param database: Name of the database on which to execute the SQL. 

37 If not specified, default ``postgres`` is assumed. 

38 

39 :param port: Optional port for PostgreSQL; default is 5432. 

40 

41 :returns: Result of the ``c.sudo()`` call (``psql -c`` command). 

42 """ 

43 port = f" --port={port}" if port else "" 

44 return c.sudo( 

45 f'psql{port} --tuples-only --no-align --command="{sql_}" {database}', 

46 user="postgres", 

47 **kwargs, 

48 ) 

49 

50 

51def user_exists(c, name, port=None): 

52 """ 

53 Determine if a given PostgreSQL user exists. 

54 

55 :param c: Fabric connection. 

56 

57 :param name: Username to check for. 

58 

59 :param port: Optional port for PostgreSQL; default is 5432. 

60 

61 :returns: ``True`` if user exists, else ``False``. 

62 """ 

63 user = sql( 

64 c, f"SELECT rolname FROM pg_roles WHERE rolname = '{name}'", port=port 

65 ).stdout.strip() 

66 return bool(user) 

67 

68 

69def create_user(c, name, password=None, port=None, checkfirst=True): 

70 """ 

71 Create a PostgreSQL user account. 

72 

73 :param c: Fabric connection. 

74 

75 :param name: Username to create. 

76 

77 :param password: Optional password for the new user. If set, will 

78 call :func:`set_user_password()`. 

79 

80 :param port: Optional port for PostgreSQL; default is 5432. 

81 

82 :param checkfirst: If true (the default), first call 

83 :func:`user_exists()` and skip creating if already present. If 

84 false, then try to create user with no check. 

85 """ 

86 if not checkfirst or not user_exists(c, name, port=port): 

87 portarg = f" --port={port}" if port else "" 

88 c.sudo( 

89 f"createuser{portarg} --no-createrole --no-superuser {name}", 

90 user="postgres", 

91 ) 

92 if password: 

93 set_user_password(c, name, password, port=port) 

94 

95 

96def set_user_password(c, name, password, port=None): 

97 """ 

98 Set the password for a PostgreSQL user account. 

99 

100 :param c: Fabric connection. 

101 

102 :param name: Username whose password is to be set. 

103 

104 :param password: New password for the user. 

105 

106 :param port: Optional port for PostgreSQL; default is 5432. 

107 """ 

108 sql( 

109 c, 

110 f"ALTER USER \\\"{name}\\\" PASSWORD '{password}';", 

111 port=port, 

112 hide=True, 

113 echo=False, 

114 ) 

115 

116 

117def db_exists(c, name, port=None): 

118 """ 

119 Determine if a given PostgreSQL database exists. 

120 

121 :param c: Fabric connection. 

122 

123 :param name: Name of the database to check for. 

124 

125 :param port: Optional port for PostgreSQL; default is 5432. 

126 

127 :returns: ``True`` if database exists, else ``False``. 

128 """ 

129 db = sql( 

130 c, f"SELECT datname FROM pg_database WHERE datname = '{name}'", port=port 

131 ).stdout.strip() 

132 return db == name 

133 

134 

135def create_db(c, name, owner=None, port=None, checkfirst=True): 

136 """ 

137 Create a PostgreSQL database. 

138 

139 :param c: Fabric connection. 

140 

141 :param name: Name of the database to create. 

142 

143 :param owner: Optional role name to set as owner for the database. 

144 

145 :param port: Optional port for PostgreSQL; default is 5432. 

146 

147 :param checkfirst: If true (the default), first call 

148 :func:`db_exists()` and skip creating if already present. If 

149 false, then try to create DB with no check. 

150 """ 

151 if not checkfirst or not db_exists(c, name, port=port): 

152 port = f" --port={port}" if port else "" 

153 owner = f" --owner={owner}" if owner else "" 

154 c.sudo(f"createdb{port}{owner} {name}", user="postgres") 

155 

156 

157def drop_db(c, name, checkfirst=True): 

158 """ 

159 Drop a PostgreSQL database. 

160 

161 :param c: Fabric connection. 

162 

163 :param name: Name of the database to drop. 

164 

165 :param checkfirst: If true (the default), first call 

166 :func:`db_exists()` and skip dropping if not present. If 

167 false, then try to drop DB with no check. 

168 """ 

169 if not checkfirst or db_exists(c, name): 

170 c.sudo(f"dropdb {name}", user="postgres") 

171 

172 

173def dump_db(c, name): 

174 """ 

175 Dump a PostgreSQL database to file. 

176 

177 This uses the ``pg_dump`` and ``gzip`` commands to produce a 

178 compressed SQL dump. The filename returned is based on the 

179 ``name`` provided, e.g. ``mydbname.sql.gz``. 

180 

181 :param c: Fabric connection. 

182 

183 :param name: Name of the database to dump. 

184 

185 :returns: Base name of the output file. We only return the 

186 filename and not the path, since the file is expected to exist 

187 in the connected user's home folder. 

188 """ 

189 sql_name = f"{name}.sql" 

190 gz_name = f"{sql_name}.gz" 

191 tmp_name = f"/tmp/{gz_name}" 

192 

193 # TODO: when pg_dump fails the command still succeeds! (would this work?) 

194 # cmd = f'set -e && pg_dump {name} | gzip -c > {tmp_name}' 

195 cmd = f"pg_dump {name} | gzip -c > {tmp_name}" 

196 

197 c.sudo(cmd, user="postgres") 

198 c.run(f"cp {tmp_name} {gz_name}") 

199 c.run(f"rm {tmp_name}") 

200 

201 return gz_name