| Home | Trees | Indices | Help |
|
|---|
|
|
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 #===================================================================
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 #-------------------------------------------------------------------
30 """
31 performs backtick shell extension in a string
32 """
33 return re.sub (r"`(.*)`", shellrun, str)
34 #===================================================================
36
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 #---------------------------------------------------------------
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 #---------------------------------------------------------------
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 #---------------------------------------------------------------
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 25 03:58:21 2012 | http://epydoc.sourceforge.net |