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