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