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