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