Package Gnumed :: Package pycommon :: Module gmPsql
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmPsql

  1  # A Python class to replace the PSQL command-line interpreter 
  2  # NOTE: this is not a full replacement for the interpeter, merely 
  3  # enough functionality to run gnumed installation scripts 
  4  # Copyright (C) 2003, 2004 GNUMed developers 
  5  # Licence: GPL 
  6  #=================================================================== 
  7  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmPsql.py,v $ 
  8  # $Id: gmPsql.py,v 1.10 2007/12/12 16:17:15 ncq Exp $ 
  9  __version__ = "$Revision: 1.10 $" 
 10  __author__ = "Ian Haywood" 
 11  __license__ = "GPL (details at http://www.gnu.org)" 
 12   
 13  # stdlib 
 14  import sys, os, string, re, urllib2, logging 
 15   
 16   
 17  _log = logging.getLogger('gm.bootstrapper') 
 18  _log.info(__version__) 
 19  #=================================================================== 
20 -def shellrun (cmd):
21 """ 22 runs the shell command and returns a string 23 """ 24 stdin, stdout = os.popen4 (cmd.group (1)) 25 r = stdout.read () 26 stdout.close() 27 stdin.close() 28 return r
29 #-------------------------------------------------------------------
30 -def shell(str):
31 """ 32 performs backtick shell extension in a string 33 """ 34 return re.sub (r"`(.*)`", shellrun, str)
35 #===================================================================
36 -class Psql:
37
38 - def __init__ (self, conn):
39 """ 40 db : the interpreter to connect to, must be a DBAPI compliant interface 41 """ 42 self.conn = conn 43 self.vars = {'ON_ERROR_STOP':None}
44 #---------------------------------------------------------------
45 - def match (self, str):
46 match = re.match (str, self.line) 47 if match is None: 48 ret = 0 49 else: 50 ret = 1 51 self.groups = match.groups () 52 return ret
53 #---------------------------------------------------------------
54 - def fmt_msg(self, aMsg):
55 tmp = string.replace("%s:%d: %s" % (self.filename, self.lineno-1, aMsg), '\r', '') 56 return string.replace(tmp, '\n', '')
57 #---------------------------------------------------------------
58 - def log (self, level, str):
59 _log.Log (level, "%s: line %d: %s" % (self.filename, self.lineno-1, str))
60 #---------------------------------------------------------------
61 - def run (self, filename):
62 """ 63 filename: a file, containg semicolon-separated SQL commands 64 """ 65 if re.match ("http://.*", filename) or re.match ("ftp://.*", filename) or re.match ("gopher://.*", filename): 66 try: 67 self.file = urllib2.urlopen (filename) 68 except URLError: 69 _log.error("cannot access %s" % filename) 70 return 1 71 else: 72 if os.access (filename, os.R_OK): 73 self.file = open(filename) 74 else: 75 _log.error("cannot open file [%s]" % filename) 76 return 1 77 78 self.lineno = 0 79 self.filename = filename 80 in_string = False 81 bracketlevel = 0 82 curr_cmd = '' 83 curs = self.conn.cursor () 84 # transaction_started = False 85 for self.line in self.file.readlines(): 86 self.lineno += 1 87 if len(self.line.strip()) == 0: 88 continue 89 90 # \echo 91 if self.match (r"^\\echo (.*)"): 92 _log.info(self.fmt_msg(shell(self.groups[0]))) 93 continue 94 # \qecho 95 if self.match (r"^\\qecho (.*)"): 96 _log.info(self.fmt_msg(shell (self.groups[0]))) 97 continue 98 # \q 99 if self.match (r"^\\q"): 100 _log.warning(self.fmt_msg("script terminated by \\q")) 101 return 0 102 # \set 103 if self.match (r"^\\set (\S+) (\S+)"): 104 self.vars[self.groups[0]] = shell (self.groups[1]) 105 if self.groups[0] == 'ON_ERROR_STOP': 106 self.vars['ON_ERROR_STOP'] = int (self.vars['ON_ERROR_STOP']) 107 continue 108 # \unset 109 if self.match (r"^\\unset (\S+)"): 110 self.vars[self.groups[0]] = None 111 continue 112 # \connect 113 if self.match (r"^\\connect.*"): 114 _log.error(self.fmt_msg("\\connect not yet supported in scripts")) 115 continue 116 # \lo_import 117 if self.match (r"^\\lo_import.*"): 118 _log.error(self.fmt_msg("\\lo_import not yet supported")) 119 # no sense to continue here 120 return 1 121 # \copy ... to ... 122 if self.match (r"^\\copy .* to '(\S+)' .*"): 123 _log.error(self.fmt_msg("\\copy to not implemented")) 124 return 1 125 # \copy ... from ... 126 if self.match (r"^\\copy .* from '(\S+)' .*"): 127 copyfile = self.groups[0] 128 try: 129 copyfd = file (os.path.join (os.path.dirname (self.filename), copyfile)) 130 except error: 131 _log.error(self.fmt_msg(error)) 132 return 1 133 self.line = self.line[1:].strip() # lop off leading slash 134 self.line.replace ("'%s'" % copyfile, 'stdin') 135 # now we have a command that the backend understands 136 copyline = 0 137 try: 138 curs = self.conn.cursor () 139 # send the COPY command 140 curs.execute (self.line) 141 # send the data 142 for i in copyfd.readlines (): 143 curs.execute (i) 144 copyline += 1 145 self.conn.commit () 146 curs.close () 147 except StandardError, error: 148 _log.error("%s: %d: %s" % (copyfile, copyline, error)) 149 if self.vars['ON_ERROR_STOP']: 150 return 1 151 continue 152 153 # \i 154 if self.match (r"^\\i (\S+)"): 155 # create another interpreter instance in same connection 156 Psql(self.conn).run (os.path.join (os.path.dirname (self.filename), self.groups[0])) 157 continue 158 159 # \encoding 160 if self.match (r"^\\encoding.*"): 161 _log.error(self.fmt_msg("\\encoding not yet supported")) 162 continue 163 164 # other '\' commands 165 if self.match (r"^\\(.*)") and not in_string: 166 # most other \ commands are for controlling output formats, don't make 167 # much sense in an installation script, so we gently ignore them 168 _log.warning(self.fmt_msg("psql command \"\\%s\" being ignored " % self.groups[0])) 169 continue 170 171 # non-'\' commands 172 this_char = self.line[0] 173 # loop over characters in line 174 for next_char in self.line[1:] + ' ': 175 176 # start/end of string detected 177 if this_char == "'": 178 in_string = not in_string 179 180 # detect -- style comments 181 if this_char == '-' and next_char == '-' and not in_string: 182 break 183 184 # detect bracketing 185 if this_char == '(' and not in_string: 186 bracketlevel += 1 187 if this_char == ')' and not in_string: 188 bracketlevel -= 1 189 190 # found end of command, not inside string, not inside bracket ? 191 if not (not in_string and (bracketlevel == 0) and (this_char == ';')): 192 curr_cmd += this_char 193 else: 194 try: 195 # if curr_cmd.strip ().upper () == 'COMMIT': 196 # if transaction_started: 197 # self.conn.commit () 198 # curs.close () 199 # curs = self.conn.cursor () 200 # _log.debug(self.fmt_msg ("transaction committed")) 201 # else: 202 # _log.warning(self.fmt_msg ("COMMIT without BEGIN: no actual transaction happened!")) 203 # transaction_started = False 204 205 # elif curr_cmd.strip ().upper () == 'BEGIN': 206 # if transaction_started: 207 # _log.warning(self.fmt_msg ("BEGIN inside transaction")) 208 # else: 209 # transaction_started = True 210 # _log.debug(self.fmt_msg ("starting transaction")) 211 212 # else: 213 if curr_cmd.strip() != '': 214 if curr_cmd.find('vacuum'): 215 self.conn.commit(); 216 curs.close() 217 old_iso_level = self.conn.isolation_level 218 self.conn.set_isolation_level(0) 219 curs = self.conn.cursor() 220 curs.execute (curr_cmd) 221 self.conn.set_isolation_level(old_iso_level) 222 else: 223 curs.execute (curr_cmd) 224 # if not transaction_started: 225 except StandardError, error: 226 _log.debug(curr_cmd) 227 if re.match (r"^NOTICE:.*", str(error)): 228 _log.warning(self.fmt_msg(error)) 229 else: 230 if self.vars['ON_ERROR_STOP']: 231 _log.error(self.fmt_msg(error)) 232 return 1 233 else: 234 _log.debug(self.fmt_msg(error)) 235 236 self.conn.commit() 237 curs.close() 238 curs = self.conn.cursor() 239 curr_cmd = '' 240 241 this_char = next_char 242 243 # end of loop over chars 244 245 # end of loop over lines 246 self.conn.commit() 247 curs.close() 248 return 0
249 #=================================================================== 250 # testing code 251 if __name__ == '__main__': 252 from pyPgSQL import PgSQL 253 conn = PgSQL.connect (user='gm-dbo', database = 'gnumed') 254 psql = Psql (conn) 255 psql.run (sys.argv[1]) 256 conn.close () 257 #=================================================================== 258 # $Log: gmPsql.py,v $ 259 # Revision 1.10 2007/12/12 16:17:15 ncq 260 # - better logger names 261 # 262 # Revision 1.9 2007/12/11 14:33:48 ncq 263 # - use standard logging module 264 # 265 # Revision 1.8 2007/08/20 14:22:05 ncq 266 # - support "vacuum" 267 # 268 # Revision 1.7 2006/12/06 16:45:37 ncq 269 # - remove debugging printk()s 270 # 271 # Revision 1.6 2006/12/06 16:07:51 ncq 272 # - cleanup/simplify somewhat 273 # - remove explicit commit handling 274 # 275 # Revision 1.5 2005/11/29 18:57:03 ncq 276 # - cleanup 277 # 278 # Revision 1.4 2005/01/12 14:47:48 ncq 279 # - in DB speak the database owner is customarily called dbo, hence use that 280 # 281 # Revision 1.3 2004/12/14 09:50:21 ncq 282 # - somewhat reformatted from improved readability 283 # 284 # 285