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 -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 tmp = u"%s:%d: %s" % (self.filename, self.lineno-1, aMsg) 55 tmp = tmp.replace(u'\r', u'') 56 #tmp = string.replace("%s:%d: %s" % (self.filename, self.lineno-1, aMsg), '\r', '') 57 return tmp.replace(u'\n', u'')
58 #--------------------------------------------------------------- 59 # def log (self, level, str): 60 # _log.Log (level, "%s: line %d: %s" % (self.filename, self.lineno-1, str)) 61 #---------------------------------------------------------------
62 - def run (self, filename):
63 """ 64 filename: a file, containg semicolon-separated SQL commands 65 """ 66 if re.match ("http://.*", filename) or re.match ("ftp://.*", filename) or re.match ("gopher://.*", filename): 67 try: 68 self.file = urllib2.urlopen (filename) 69 except URLError: 70 _log.error(u"cannot access %s" % filename) 71 return 1 72 else: 73 if os.access (filename, os.R_OK): 74 self.file = open(filename) 75 else: 76 _log.error(u"cannot open file [%s]" % filename) 77 return 1 78 79 self.lineno = 0 80 self.filename = filename 81 in_string = False 82 bracketlevel = 0 83 curr_cmd = '' 84 curs = self.conn.cursor () 85 # transaction_started = False 86 for self.line in self.file.readlines(): 87 self.lineno += 1 88 if len(self.line.strip()) == 0: 89 continue 90 91 # \echo 92 if self.match (r"^\\echo (.*)"): 93 _log.info(self.fmt_msg(shell(self.groups[0]))) 94 continue 95 # \qecho 96 if self.match (r"^\\qecho (.*)"): 97 _log.info(self.fmt_msg(shell (self.groups[0]))) 98 continue 99 # \q 100 if self.match (r"^\\q"): 101 _log.warning(self.fmt_msg(u"script terminated by \\q")) 102 return 0 103 # \set 104 if self.match (r"^\\set (\S+) (\S+)"): 105 self.vars[self.groups[0]] = shell (self.groups[1]) 106 if self.groups[0] == 'ON_ERROR_STOP': 107 self.vars['ON_ERROR_STOP'] = int (self.vars['ON_ERROR_STOP']) 108 continue 109 # \unset 110 if self.match (r"^\\unset (\S+)"): 111 self.vars[self.groups[0]] = None 112 continue 113 # \connect 114 if self.match (r"^\\connect.*"): 115 _log.error(self.fmt_msg(u"\\connect not yet supported in scripts")) 116 continue 117 # \lo_import 118 if self.match (r"^\\lo_import.*"): 119 _log.error(self.fmt_msg(u"\\lo_import not yet supported")) 120 # no sense to continue here 121 return 1 122 # \copy ... to ... 123 if self.match (r"^\\copy .* to '(\S+)' .*"): 124 _log.error(self.fmt_msg(u"\\copy to not implemented")) 125 return 1 126 # \copy ... from ... 127 if self.match (r"^\\copy .* from '(\S+)' .*"): 128 copyfile = self.groups[0] 129 try: 130 copyfd = file (os.path.join (os.path.dirname (self.filename), copyfile)) 131 except error: 132 _log.error(self.fmt_msg(error)) 133 return 1 134 self.line = self.line[1:].strip() # lop off leading slash 135 self.line.replace ("'%s'" % copyfile, 'stdin') 136 # now we have a command that the backend understands 137 copyline = 0 138 try: 139 curs = self.conn.cursor () 140 # send the COPY command 141 curs.execute (self.line) 142 # send the data 143 for i in copyfd.readlines (): 144 curs.execute (i) 145 copyline += 1 146 self.conn.commit () 147 curs.close () 148 except StandardError, error: 149 _log.error(u"%s: %d: %s" % (copyfile, copyline, error)) 150 if self.vars['ON_ERROR_STOP']: 151 return 1 152 continue 153 154 # \i 155 if self.match (r"^\\i (\S+)"): 156 # create another interpreter instance in same connection 157 Psql(self.conn).run (os.path.join (os.path.dirname (self.filename), self.groups[0])) 158 continue 159 160 # \encoding 161 if self.match (r"^\\encoding.*"): 162 _log.error(self.fmt_msg(u"\\encoding not yet supported")) 163 continue 164 165 # other '\' commands 166 if self.match (r"^\\(.*)") and not in_string: 167 # most other \ commands are for controlling output formats, don't make 168 # much sense in an installation script, so we gently ignore them 169 _log.warning(self.fmt_msg(u"psql command \"\\%s\" being ignored " % self.groups[0])) 170 continue 171 172 # non-'\' commands 173 this_char = self.line[0] 174 # loop over characters in line 175 for next_char in self.line[1:] + ' ': 176 177 # start/end of string detected 178 if this_char == "'": 179 in_string = not in_string 180 181 # detect -- style comments 182 if this_char == '-' and next_char == '-' and not in_string: 183 break 184 185 # detect bracketing 186 if this_char == '(' and not in_string: 187 bracketlevel += 1 188 if this_char == ')' and not in_string: 189 bracketlevel -= 1 190 191 # found end of command, not inside string, not inside bracket ? 192 if not (not in_string and (bracketlevel == 0) and (this_char == ';')): 193 curr_cmd += this_char 194 else: 195 try: 196 # if curr_cmd.strip ().upper () == 'COMMIT': 197 # if transaction_started: 198 # self.conn.commit () 199 # curs.close () 200 # curs = self.conn.cursor () 201 # _log.debug(self.fmt_msg ("transaction committed")) 202 # else: 203 # _log.warning(self.fmt_msg ("COMMIT without BEGIN: no actual transaction happened!")) 204 # transaction_started = False 205 206 # elif curr_cmd.strip ().upper () == 'BEGIN': 207 # if transaction_started: 208 # _log.warning(self.fmt_msg ("BEGIN inside transaction")) 209 # else: 210 # transaction_started = True 211 # _log.debug(self.fmt_msg ("starting transaction")) 212 213 # else: 214 if curr_cmd.strip() != '': 215 if curr_cmd.find('vacuum'): 216 self.conn.commit(); 217 curs.close() 218 old_iso_level = self.conn.isolation_level 219 self.conn.set_isolation_level(0) 220 curs = self.conn.cursor() 221 curs.execute (curr_cmd) 222 self.conn.set_isolation_level(old_iso_level) 223 else: 224 curs.execute (curr_cmd) 225 # if not transaction_started: 226 except StandardError, error: 227 _log.debug(curr_cmd) 228 if re.match (r"^NOTICE:.*", str(error)): 229 _log.warning(self.fmt_msg(error)) 230 else: 231 if self.vars['ON_ERROR_STOP']: 232 _log.error(self.fmt_msg(error)) 233 return 1 234 else: 235 _log.debug(self.fmt_msg(error)) 236 237 self.conn.commit() 238 curs.close() 239 curs = self.conn.cursor() 240 curr_cmd = '' 241 242 this_char = next_char 243 244 # end of loop over chars 245 246 # end of loop over lines 247 self.conn.commit() 248 curs.close() 249 return 0
250 #=================================================================== 251 # testing code 252 if __name__ == '__main__': 253 from pyPgSQL import PgSQL 254 conn = PgSQL.connect (user='gm-dbo', database = 'gnumed') 255 psql = Psql (conn) 256 psql.run (sys.argv[1]) 257 conn.close () 258 #=================================================================== 259