| 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 # Copyright (C) 2003, 2004 GNUMed developers
5 # Licence: GPL
6 #===================================================================
7 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmPsql.py,v $
8 # $Id: gmPsql.py,v 1.10 2007/12/12 16:17:15 ncq Exp $
9 __version__ = "$Revision: 1.10 $"
10 __author__ = "Ian Haywood"
11 __license__ = "GPL (details at http://www.gnu.org)"
12
13 # stdlib
14 import sys, os, string, re, urllib2, logging
15
16
17 _log = logging.getLogger('gm.bootstrapper')
18 _log.info(__version__)
19 #===================================================================
21 """
22 runs the shell command and returns a string
23 """
24 stdin, stdout = os.popen4 (cmd.group (1))
25 r = stdout.read ()
26 stdout.close()
27 stdin.close()
28 return r
29 #-------------------------------------------------------------------
31 """
32 performs backtick shell extension in a string
33 """
34 return re.sub (r"`(.*)`", shellrun, str)
35 #===================================================================
37
39 """
40 db : the interpreter to connect to, must be a DBAPI compliant interface
41 """
42 self.conn = conn
43 self.vars = {'ON_ERROR_STOP':None}
44 #---------------------------------------------------------------
46 match = re.match (str, self.line)
47 if match is None:
48 ret = 0
49 else:
50 ret = 1
51 self.groups = match.groups ()
52 return ret
53 #---------------------------------------------------------------
55 tmp = string.replace("%s:%d: %s" % (self.filename, self.lineno-1, aMsg), '\r', '')
56 return string.replace(tmp, '\n', '')
57 #---------------------------------------------------------------
60 #---------------------------------------------------------------
62 """
63 filename: a file, containg semicolon-separated SQL commands
64 """
65 if re.match ("http://.*", filename) or re.match ("ftp://.*", filename) or re.match ("gopher://.*", filename):
66 try:
67 self.file = urllib2.urlopen (filename)
68 except URLError:
69 _log.error("cannot access %s" % filename)
70 return 1
71 else:
72 if os.access (filename, os.R_OK):
73 self.file = open(filename)
74 else:
75 _log.error("cannot open file [%s]" % filename)
76 return 1
77
78 self.lineno = 0
79 self.filename = filename
80 in_string = False
81 bracketlevel = 0
82 curr_cmd = ''
83 curs = self.conn.cursor ()
84 # transaction_started = False
85 for self.line in self.file.readlines():
86 self.lineno += 1
87 if len(self.line.strip()) == 0:
88 continue
89
90 # \echo
91 if self.match (r"^\\echo (.*)"):
92 _log.info(self.fmt_msg(shell(self.groups[0])))
93 continue
94 # \qecho
95 if self.match (r"^\\qecho (.*)"):
96 _log.info(self.fmt_msg(shell (self.groups[0])))
97 continue
98 # \q
99 if self.match (r"^\\q"):
100 _log.warning(self.fmt_msg("script terminated by \\q"))
101 return 0
102 # \set
103 if self.match (r"^\\set (\S+) (\S+)"):
104 self.vars[self.groups[0]] = shell (self.groups[1])
105 if self.groups[0] == 'ON_ERROR_STOP':
106 self.vars['ON_ERROR_STOP'] = int (self.vars['ON_ERROR_STOP'])
107 continue
108 # \unset
109 if self.match (r"^\\unset (\S+)"):
110 self.vars[self.groups[0]] = None
111 continue
112 # \connect
113 if self.match (r"^\\connect.*"):
114 _log.error(self.fmt_msg("\\connect not yet supported in scripts"))
115 continue
116 # \lo_import
117 if self.match (r"^\\lo_import.*"):
118 _log.error(self.fmt_msg("\\lo_import not yet supported"))
119 # no sense to continue here
120 return 1
121 # \copy ... to ...
122 if self.match (r"^\\copy .* to '(\S+)' .*"):
123 _log.error(self.fmt_msg("\\copy to not implemented"))
124 return 1
125 # \copy ... from ...
126 if self.match (r"^\\copy .* from '(\S+)' .*"):
127 copyfile = self.groups[0]
128 try:
129 copyfd = file (os.path.join (os.path.dirname (self.filename), copyfile))
130 except error:
131 _log.error(self.fmt_msg(error))
132 return 1
133 self.line = self.line[1:].strip() # lop off leading slash
134 self.line.replace ("'%s'" % copyfile, 'stdin')
135 # now we have a command that the backend understands
136 copyline = 0
137 try:
138 curs = self.conn.cursor ()
139 # send the COPY command
140 curs.execute (self.line)
141 # send the data
142 for i in copyfd.readlines ():
143 curs.execute (i)
144 copyline += 1
145 self.conn.commit ()
146 curs.close ()
147 except StandardError, error:
148 _log.error("%s: %d: %s" % (copyfile, copyline, error))
149 if self.vars['ON_ERROR_STOP']:
150 return 1
151 continue
152
153 # \i
154 if self.match (r"^\\i (\S+)"):
155 # create another interpreter instance in same connection
156 Psql(self.conn).run (os.path.join (os.path.dirname (self.filename), self.groups[0]))
157 continue
158
159 # \encoding
160 if self.match (r"^\\encoding.*"):
161 _log.error(self.fmt_msg("\\encoding not yet supported"))
162 continue
163
164 # other '\' commands
165 if self.match (r"^\\(.*)") and not in_string:
166 # most other \ commands are for controlling output formats, don't make
167 # much sense in an installation script, so we gently ignore them
168 _log.warning(self.fmt_msg("psql command \"\\%s\" being ignored " % self.groups[0]))
169 continue
170
171 # non-'\' commands
172 this_char = self.line[0]
173 # loop over characters in line
174 for next_char in self.line[1:] + ' ':
175
176 # start/end of string detected
177 if this_char == "'":
178 in_string = not in_string
179
180 # detect -- style comments
181 if this_char == '-' and next_char == '-' and not in_string:
182 break
183
184 # detect bracketing
185 if this_char == '(' and not in_string:
186 bracketlevel += 1
187 if this_char == ')' and not in_string:
188 bracketlevel -= 1
189
190 # found end of command, not inside string, not inside bracket ?
191 if not (not in_string and (bracketlevel == 0) and (this_char == ';')):
192 curr_cmd += this_char
193 else:
194 try:
195 # if curr_cmd.strip ().upper () == 'COMMIT':
196 # if transaction_started:
197 # self.conn.commit ()
198 # curs.close ()
199 # curs = self.conn.cursor ()
200 # _log.debug(self.fmt_msg ("transaction committed"))
201 # else:
202 # _log.warning(self.fmt_msg ("COMMIT without BEGIN: no actual transaction happened!"))
203 # transaction_started = False
204
205 # elif curr_cmd.strip ().upper () == 'BEGIN':
206 # if transaction_started:
207 # _log.warning(self.fmt_msg ("BEGIN inside transaction"))
208 # else:
209 # transaction_started = True
210 # _log.debug(self.fmt_msg ("starting transaction"))
211
212 # else:
213 if curr_cmd.strip() != '':
214 if curr_cmd.find('vacuum'):
215 self.conn.commit();
216 curs.close()
217 old_iso_level = self.conn.isolation_level
218 self.conn.set_isolation_level(0)
219 curs = self.conn.cursor()
220 curs.execute (curr_cmd)
221 self.conn.set_isolation_level(old_iso_level)
222 else:
223 curs.execute (curr_cmd)
224 # if not transaction_started:
225 except StandardError, error:
226 _log.debug(curr_cmd)
227 if re.match (r"^NOTICE:.*", str(error)):
228 _log.warning(self.fmt_msg(error))
229 else:
230 if self.vars['ON_ERROR_STOP']:
231 _log.error(self.fmt_msg(error))
232 return 1
233 else:
234 _log.debug(self.fmt_msg(error))
235
236 self.conn.commit()
237 curs.close()
238 curs = self.conn.cursor()
239 curr_cmd = ''
240
241 this_char = next_char
242
243 # end of loop over chars
244
245 # end of loop over lines
246 self.conn.commit()
247 curs.close()
248 return 0
249 #===================================================================
250 # testing code
251 if __name__ == '__main__':
252 from pyPgSQL import PgSQL
253 conn = PgSQL.connect (user='gm-dbo', database = 'gnumed')
254 psql = Psql (conn)
255 psql.run (sys.argv[1])
256 conn.close ()
257 #===================================================================
258 # $Log: gmPsql.py,v $
259 # Revision 1.10 2007/12/12 16:17:15 ncq
260 # - better logger names
261 #
262 # Revision 1.9 2007/12/11 14:33:48 ncq
263 # - use standard logging module
264 #
265 # Revision 1.8 2007/08/20 14:22:05 ncq
266 # - support "vacuum"
267 #
268 # Revision 1.7 2006/12/06 16:45:37 ncq
269 # - remove debugging printk()s
270 #
271 # Revision 1.6 2006/12/06 16:07:51 ncq
272 # - cleanup/simplify somewhat
273 # - remove explicit commit handling
274 #
275 # Revision 1.5 2005/11/29 18:57:03 ncq
276 # - cleanup
277 #
278 # Revision 1.4 2005/01/12 14:47:48 ncq
279 # - in DB speak the database owner is customarily called dbo, hence use that
280 #
281 # Revision 1.3 2004/12/14 09:50:21 ncq
282 # - somewhat reformatted from improved readability
283 #
284 #
285
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:43 2010 | http://epydoc.sourceforge.net |