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 
 13  import os 
 14  import re 
 15  import logging 
 16  import io 
 17   
 18   
 19  _log = logging.getLogger('gm.bootstrapper') 
 20   
 21  unformattable_error_id = 12345 
 22   
 23  #=================================================================== 
24 -class Psql:
25
26 - def __init__ (self, conn):
27 """ 28 db : the interpreter to connect to, must be a DBAPI compliant interface 29 """ 30 self.conn = conn 31 self.vars = {'ON_ERROR_STOP': None}
32 33 #---------------------------------------------------------------
34 - def match(self, pattern):
35 match = re.match(pattern, self.line) 36 if match is None: 37 return 0 38 39 self.groups = match.groups() 40 return 1
41 42 #---------------------------------------------------------------
43 - def fmt_msg(self, aMsg):
44 try: 45 tmp = "%s:%d: %s" % (self.filename, self.lineno-1, aMsg) 46 tmp = tmp.replace('\r', '') 47 tmp = tmp.replace('\n', '') 48 except UnicodeDecodeError: 49 global unformattable_error_id 50 tmp = "%s:%d: <cannot str(msg), printing on console with ID [#%d]>" % (self.filename, self.lineno-1, unformattable_error_id) 51 try: 52 print('ERROR: GNUmed bootstrap #%d:' % unformattable_error_id) 53 print(aMsg) 54 except: pass 55 unformattable_error_id += 1 56 return tmp
57 58 #---------------------------------------------------------------
59 - def run (self, filename):
60 """ 61 filename: a file, containg semicolon-separated SQL commands 62 """ 63 _log.debug('processing [%s]', filename) 64 curs = self.conn.cursor() 65 curs.execute('show session authorization') 66 start_auth = curs.fetchall()[0][0] 67 curs.close() 68 _log.debug('session auth: %s', start_auth) 69 70 if os.access (filename, os.R_OK): 71 sql_file = io.open(filename, mode = 'rt', encoding = 'utf8') 72 else: 73 _log.error("cannot open file [%s]", filename) 74 return 1 75 76 self.lineno = 0 77 self.filename = filename 78 in_string = False 79 bracketlevel = 0 80 curr_cmd = '' 81 curs = self.conn.cursor() 82 83 for self.line in sql_file: 84 self.lineno += 1 85 if len(self.line.strip()) == 0: 86 continue 87 88 # \set 89 if self.match(r"^\\set (\S+) (\S+)"): 90 _log.debug('"\set" found: %s', self.groups) 91 self.vars[self.groups[0]] = self.groups[1] 92 if self.groups[0] == 'ON_ERROR_STOP': 93 # adjusting from string to int so that "1" -> 1 -> True 94 self.vars['ON_ERROR_STOP'] = int(self.vars['ON_ERROR_STOP']) 95 continue 96 97 # \unset 98 if self.match (r"^\\unset (\S+)"): 99 self.vars[self.groups[0]] = None 100 continue 101 102 # other '\' commands 103 if self.match (r"^\\(.*)") and not in_string: 104 # most other \ commands are for controlling output formats, don't make 105 # much sense in an installation script, so we gently ignore them 106 _log.warning(self.fmt_msg("psql command \"\\%s\" being ignored " % self.groups[0])) 107 continue 108 109 # non-'\' commands 110 this_char = self.line[0] 111 # loop over characters in line 112 for next_char in self.line[1:] + ' ': 113 114 # start/end of string detected 115 if this_char == "'": 116 in_string = not in_string 117 118 # detect "--"-style comments 119 if this_char == '-' and next_char == '-' and not in_string: 120 break 121 122 # detect bracketing 123 if this_char == '(' and not in_string: 124 bracketlevel += 1 125 if this_char == ')' and not in_string: 126 bracketlevel -= 1 127 128 # have we: 129 # - found end of command ? 130 # - are not inside a string ? 131 # - are not inside bracket pair ? 132 if not ((in_string is False) and (bracketlevel == 0) and (this_char == ';')): 133 curr_cmd += this_char 134 else: 135 if curr_cmd.strip() != '': 136 try: 137 curs.execute(curr_cmd) 138 try: 139 data = curs.fetchall() 140 _log.debug('cursor data: %s', data) 141 except Exception: # actually: psycopg2.ProgrammingError but no handle 142 pass 143 except Exception as error: 144 _log.exception(curr_cmd) 145 if re.match(r"^NOTICE:.*", str(error)): 146 _log.warning(self.fmt_msg(error)) 147 else: 148 _log.error(self.fmt_msg(error)) 149 if hasattr(error, 'diag'): 150 for prop in dir(error.diag): 151 if prop.startswith('__'): 152 continue 153 val = getattr(error.diag, prop) 154 if val is None: 155 continue 156 _log.error('PG diags %s: %s', prop, val) 157 if self.vars['ON_ERROR_STOP']: 158 self.conn.commit() 159 curs.close() 160 return 1 161 162 self.conn.commit() 163 curs.close() 164 curs = self.conn.cursor() 165 curr_cmd = '' 166 167 this_char = next_char 168 # end of loop over chars 169 170 # end of loop over lines 171 self.conn.commit() 172 curs.execute('show session authorization') 173 end_auth = curs.fetchall()[0][0] 174 curs.close() 175 _log.debug('session auth after sql file processing: %s', end_auth) 176 if start_auth != end_auth: 177 _log.error('session auth changed before/after processing sql file') 178 179 return 0
180 181 #=================================================================== 182 # testing code 183 if __name__ == '__main__': 184 185 if len(sys.argv) < 2: 186 sys.exit() 187 188 if sys.argv[1] != 'test': 189 sys.exit() 190 191 conn = PgSQL.connect(user='gm-dbo', database = 'gnumed') 192 psql = Psql(conn) 193 psql.run(sys.argv[1]) 194 conn.close() 195