1
2
3
4 """ This module defines all high level graphical functions of pysql
5 @author: Sébastien Delcros (Sebastien.Delcros@gmail.com)
6 @author: Sébastien Renard (sebastien.renard@digitalfox.org)
7 @license: GNU GPL V3
8 """
9
10
11
12
13 import os
14 import sys
15 import re
16 import subprocess
17 from math import floor, log, sqrt
18
19
20 from pysqlqueries import datamodelSql, dependenciesSql, diskusageSql
21 from pysqlexception import PysqlException, PysqlActionDenied
22 from pysqlcolor import BOLD, CYAN, GREEN, GREY, RED, RESET
23 from pysqlconf import PysqlConf
24 from pysqloraobjects import OraObject
25 from pysqlhelpers import convert, generateWhere, getProg, removeComment, which
26
27
28 -def datamodel(db, userName, tableFilter=None, withColumns=True):
29 """Extracts the datamodel of the current user as a picture
30 The generation of the picture is powered by Graphviz (http://www.graphviz.org)
31 through the PyDot API (http://www.dkbza.org/pydot.html)
32 @param db: pysql db connection
33 @param userName: schema to be extracted
34 @param tableFilter: filter pattern (in pysql extended syntax to extract only some tables (None means all)
35 @param withColumns: Indicate whether columns are included or not in datamodel picture
36 """
37
38 try:
39 from pydot import find_graphviz, Dot, Edge, Node
40 except ImportError:
41 message = _("Function not available because pydot module is not installed.\n\t")
42 message += _("Go to http://dkbza.org/pydot.html to get it.")
43 raise PysqlException(message)
44
45
46 conf = PysqlConf.getConfig()
47 format = conf.get("graph_format")
48 fontname = conf.get("graph_fontname")
49 fontsize = conf.get("graph_fontsize")
50 fontcolor = conf.get("graph_fontcolor")
51 tablecolor = conf.get("graph_tablecolor")
52 bordercolor = conf.get("graph_bordercolor")
53 linkcolor = conf.get("graph_linkcolor")
54 linklabel = conf.get("graph_linklabel")
55
56
57 prog = getProg(find_graphviz(), conf.get("graph_program"), "fdp")
58
59 graph = Dot(splines="compound")
60
61
62 if tableFilter:
63 whereClause = generateWhere("table_name", tableFilter)
64 else:
65 whereClause = "1=1"
66 tables = db.executeAll(datamodelSql["tablesFromOwner"] % (userName, whereClause))
67 nbTables = len(tables)
68 if nbTables == 0:
69 raise PysqlException(_("No table found. Your filter clause is too restrictive or the schema is empty"))
70 tableList = ", ".join(["'%s'" % table[0] for table in tables])
71 print CYAN + _("Extracting %d tables... ") % nbTables + RESET,
72 current = 0
73 for table in tables:
74 tableName = table[0]
75 content = """<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">"""
76 content += """\n<TR><TD PORT="%s">""" % tableName
77 content += """<FONT FACE="%s" POINT-SIZE="%f" COLOR="%s">""" % (fontname, fontsize, fontcolor)
78 content += tableName
79 content += "</FONT></TD></TR>"
80 if withColumns:
81 columns = db.executeAll(datamodelSql["columnsFromOwnerAndTable"], [userName, tableName])
82 for column in columns:
83 columnName = column[0]
84 columnType = column[1]
85 content += """\n<TR><TD ALIGN="LEFT" PORT="%s_%s">""" % (tableName, columnName)
86 content += """<FONT FACE="%s" POINT-SIZE="%f" COLOR="%s">""" % \
87 (fontname, fontsize - 2, fontcolor)
88 if column[2] is None:
89 content += " "
90 else:
91 content += "PK%d" % int(column[2])
92 content += " %s (%s)" % (columnName, columnType)
93 content += "</FONT></TD></TR>"
94 content += "\n</TABLE>>"
95 graph.add_node(Node(tableName, shape="none", label=content, style="filled", \
96 fillcolor=tablecolor, color=bordercolor))
97 current += 1
98 sys.stdout.write("\b\b\b\b\b%4.1f%%" % round(100 * float(current) / nbTables, 1))
99 sys.stdout.flush()
100
101 print
102
103
104 links = db.executeAll(datamodelSql["constraintsFromOwner"] % (userName, tableList, tableList))
105 nbLinks = len(links)
106 print (CYAN + _("Extracting %d links... ") % nbLinks + RESET),
107 current = 0
108 for link in links:
109 if linklabel == "yes":
110 graph.add_edge(Edge(src=link[1], dst=link[2], color=linkcolor))
111 else:
112 graph.add_edge(Edge(src=link[1], dst=link[2], label=link[0], color=linkcolor, \
113 fontcolor=linkcolor, fontname=fontname, fontsize=str(fontsize - 3)))
114 current += 1
115 sys.stdout.write("\b\b\b\b\b%4.1f%%" % round(100 * float(current) / nbLinks, 1))
116
117 print
118 filename = db.getDSN() + "_" + userName + "." + format
119 generateImage(graph, filename, prog, format)
120 viewImage(filename)
121
122 -def dependencies(db, objectName, direction, maxDepth, maxNodes):
123 """Displays object dependencies as a picture
124 The generation of the picture is powered by Graphviz (http://www.graphviz.org)
125 through the PyDot API (http://www.dkbza.org/pydot.html)
126 @param db: pysql db connection
127 @param objectName: name of the oracle object on which dependancies are computed
128 @param direction: direction of the dependancy graph. Can be "onto", "from" or "both"
129 @param maxDepth: Override default maxDepth value. If None, use default value
130 @param maxNodes: Override default maxNodes value. If None, use default value
131 """
132
133 try:
134 from pydot import find_graphviz, Dot, Edge, Node
135 except ImportError:
136 message = _("Function not available because pydot module is not installed.\n\t")
137 message += _("Go to http://dkbza.org/pydot.html to get it.")
138 raise PysqlException(message)
139
140
141 conf = PysqlConf.getConfig()
142 format = conf.get("graph_format")
143 fontname = conf.get("graph_fontname")
144 fontsize = conf.get("graph_fontsize")
145
146
147 prog = getProg(find_graphviz(), conf.get("graph_program"), "dot")
148
149 graph = Dot(overlap="false", splines="true", rankdir="TB")
150
151 if direction == "onto" or direction == "from":
152 dirList = [direction]
153 elif direction == "both":
154 dirList = ["onto", "from"]
155 else:
156 dirList = []
157
158 for currentDir in dirList:
159 depth = 0
160 objectList = [OraObject(objectName=objectName)]
161 objectList[0].guessInfos(db)
162 objectOwner = objectList[0].getOwner()
163 objectName = objectList[0].getName()
164 objectType = objectList[0].getType()
165 label = objectOwner + "." + objectName + "\\n(" + objectType + ")"
166 graph.add_node(Node(objectName, label=label, fontname=fontname, fontsize=str(fontsize), shape="diamond"))
167 nodeList = [objectName]
168 edgeList = []
169 nextObjectList = []
170
171 while objectList != [] and depth <= maxDepth and len(nodeList) <= maxNodes:
172 depth += 1
173 for currentObject in objectList:
174 currentObjectOwner = currentObject.getOwner()
175 currentObjectName = currentObject.getName()
176 if currentDir == "onto":
177
178 result = db.executeAll(dependenciesSql["refOnFromOwnerAndName"], \
179 [currentObjectOwner, currentObjectName])
180 elif currentDir == "from":
181
182 result = db.executeAll(dependenciesSql["refByFromOwnerAndName"], \
183 [currentObjectOwner, currentObjectName])
184 refObjectList = [OraObject(objectOwner=i[0], objectName=i[1]) for i in result]
185 for currentRefObject in refObjectList:
186 currentRefObject.guessInfos(db)
187 currentRefObjectOwner = currentRefObject.getOwner()
188 currentRefObjectName = currentRefObject.getName()
189 currentRefObjectType = currentRefObject.getType()
190 if not currentRefObjectName in nodeList:
191 nodeList.append(currentRefObjectName)
192
193 if currentRefObjectType in ("TABLE", "VIEW", "SEQUENCE"):
194 shape = "box"
195 elif currentRefObjectType in ("PACKAGE", "FUNCTION", "PROCEDURE", "TRIGGER"):
196 shape = "ellipse"
197 else:
198 shape = "none"
199
200 if currentRefObjectOwner == db.getUsername().upper():
201 label = currentRefObjectName
202 else:
203 label = currentRefObjectOwner + "." + currentRefObjectName
204 label += "\\n(" + currentRefObjectType + ")"
205
206 graph.add_node(Node(currentRefObjectName, label=label, fontname=fontname, \
207 fontsize=str(fontsize), shape=shape))
208 if not [currentObjectName, currentRefObjectName] in edgeList:
209 if currentDir == "onto":
210 edgeList.append([currentObjectName, currentRefObjectName])
211 graph.add_edge(Edge(dst=currentObjectName, src=currentRefObjectName, \
212 color="red"))
213 elif currentDir == "from":
214 edgeList.append([currentObjectName, currentRefObjectName])
215 graph.add_edge(Edge(src=currentObjectName, dst=currentRefObjectName, \
216 color="darkgreen"))
217 nextObjectList += refObjectList
218 objectList = nextObjectList
219 nextObjectList = []
220
221 if len(nodeList) > maxNodes:
222 print RED + _("Warning: reach max node, references lookup stopped on direction %s") % currentDir + RESET
223 if depth > maxDepth:
224 print RED + _("Warning: reach max recursion limit, references lookup stopped on direction %s") % currentDir + RESET
225
226 filename = "dep_" + objectOwner + "." + objectName + "." + format
227 generateImage(graph, filename, prog, format)
228 viewImage(filename)
229
230
231 -def diskusage(db, userName, withIndexes=False, percent=True):
232 """Extracts the physical storage of the current user as a picture based on Oracle statistics
233 The generation of the picture is powered by Graphviz (http://www.graphviz.org)
234 through the PyDot API (http://www.dkbza.org/pydot.html)
235 """
236
237 try:
238 from pydot import find_graphviz, Dot, Subgraph, Cluster, Edge, Node
239 except ImportError:
240 message = _("Function not available because pydot module is not installed.\n\t")
241 message += _("Go to http://dkbza.org/pydot.html to get it.")
242 raise PysqlException(message)
243
244
245 conf = PysqlConf.getConfig()
246 unit = conf.get("unit")
247 format = conf.get("graph_format")
248 fontname = conf.get("graph_fontname")
249 fontsize = conf.get("graph_fontsize")
250 fontcolor = conf.get("graph_fontcolor")
251 tablecolor = conf.get("graph_tablecolor")
252 indexcolor = conf.get("graph_indexcolor")
253 bordercolor = conf.get("graph_bordercolor")
254
255
256 prog = getProg(find_graphviz(), conf.get("graph_program"), "fdp")
257
258
259
260 if userName == db.getUsername().upper():
261 tablespaces = db.executeAll(diskusageSql["Tablespaces"])
262 else:
263 tablespaces = db.executeAll(diskusageSql["TablespacesFromOwner"], [userName])
264 tbsBytes = 0
265 tbsList = []
266 for tablespace in tablespaces:
267 tablespaceName = unicode(tablespace[0])
268
269
270 if userName == db.getUsername().upper():
271 tables = db.executeAll(diskusageSql["TablesFromTbs"], [tablespaceName])
272 else:
273 tables = db.executeAll(diskusageSql["TablesFromOwnerAndTbs"], [userName, tablespaceName])
274 tabList = []
275 print CYAN + _("Extracting %3d tables from tablespace %s") % (len(tables), tablespaceName) + RESET
276 for table in tables:
277 tableName = table[0]
278 if table[1] is None:
279 print RED + _("""Warning: table "%s" removed because no statistics have been found""") \
280 % (tablespaceName + "/" + tableName) + RESET
281 continue
282 if table[1] == 0:
283 print RED + _("""Warning: table "%s" removed because it is empty""") \
284 % (tablespaceName + "/" + tableName) + RESET
285 continue
286 numRows = int(table[1])
287 avgRowLen = float(table[2])
288 bytes = int(table[3])
289 tbsBytes += bytes
290 tabList += [[tableName, bytes, numRows, avgRowLen]]
291
292 if withIndexes:
293
294 if userName == db.getUsername().upper():
295 indexes = db.executeAll(diskusageSql["IndexesFromTbs"], [tablespaceName])
296 else:
297 indexes = db.executeAll(diskusageSql["IndexesFromOwnerAndTbs"], [userName, tablespaceName])
298 idxList = []
299 print CYAN + _("Extracting %3d indexes from tablespace %s") % (len(indexes), tablespaceName) + RESET
300 for index in indexes:
301 indexName = index[0]
302 if index[1] is None:
303 print RED + _("""Warning: index "%s" removed because no statistics have been found""") \
304 % (tablespaceName + "/" + indexName) + RESET
305 continue
306 if index[1] == 0:
307 print RED + _("""Warning: index "%s" removed because it is empty""") \
308 % (tablespaceName + "/" + indexName) + RESET
309 continue
310 numRows = int(index[1])
311 distinctKeys = int(index[2])
312 bytes = int(index[3])
313 tabName = str(index[4])
314 tbsBytes += bytes
315 idxList += [[indexName, bytes, numRows, distinctKeys, tabName]]
316 else:
317 print CYAN + _("Not extracting indexes from tablespace %s (ignored)") % (tablespaceName) + RESET
318 idxList = []
319 tbsList += [[tablespaceName, tbsBytes, tabList, idxList]]
320
321
322 graph = Dot(label=userName, overlap="false", splines="true")
323
324 for tbs in tbsList:
325 tbsName = tbs[0]
326 tbsBytes = tbs[1]
327 tabList = tbs[2]
328 idxList = tbs[3]
329 subGraph = Subgraph("cluster_" + tbsName, bgcolor="palegreen", \
330 fontname=fontname, fontsize=str(fontsize - 1), \
331 label="%s\\n(%d %s)" % (tbsName, convert(tbsBytes, unit), unit.upper()))
332 graph.add_subgraph(subGraph)
333
334 print CYAN + _("Displaying %3d tables for tablespace %s") % (len(tabList), tbsName) + RESET
335 for tab in tabList:
336 name = tab[0]
337 bytes = tab[1]
338 numRows = tab[2]
339 avgRowLen = tab[3]
340
341
342 width = 0.2
343 height = 0.2
344 if percent:
345 height += 10 * round(float(bytes) / tbsBytes, 4)
346 label = "%s\\n(%.2f %s)" % (name, round(100 * float(bytes) / tbsBytes, 2), "%")
347
348 else:
349 height += round(sqrt(bytes) / 8192, 3)
350 width += round(sqrt(bytes) / 8192, 3)
351 label = "%s\\n(%3d %s)" % (name, convert(bytes, unit), unit.upper())
352 subGraph.add_node(Node(name, label=label, shape="box", style="filled", \
353 color="none", fillcolor=tablecolor, \
354 fontname=fontname, fontcolor=fontcolor, fixedsize="false", \
355 fontsize=str(fontsize - 2 - floor((len(label) - 7) / 15)), \
356 nodesep="0.01", height=str(height), width=str(max(width, 1))))
357
358 print CYAN + _("Displaying %3d indexes for tablespace %s") % (len(idxList), tbsName) + RESET
359 for idx in idxList:
360 name = idx[0]
361 bytes = idx[1]
362 numRows = idx[2]
363 distinctKeys = idx[3]
364 tabName = idx[4]
365
366
367 width = 0.2
368 height = 0.2
369 if percent:
370 height += 10 * round(float(bytes) / tbsBytes, 4)
371 label = "%s\\n(%.2f %s)" % (name, round(100 * float(bytes) / tbsBytes, 2), "%")
372 else:
373 height += round(sqrt(bytes) / 8192, 3)
374 width += round(sqrt(bytes) / 8192, 3)
375 label = "%s\\n(%3d %s)" % (name, convert(bytes, unit), unit.upper())
376
377 subGraph.add_node(Node(name, label=label, shape="box", style="filled", \
378 color="none", fillcolor=indexcolor, \
379 fontname=fontname, fontcolor=fontcolor, fixedsize="false", \
380 fontsize=str(fontsize - 2 - floor((len(label) - 7) / 15)), \
381 nodesep="0.01", height=str(height), width=str(max(width, 1))))
382
383
384
385 filename = "du_" + userName + "." + format
386 generateImage(graph, filename, prog, format)
387 viewImage(filename)
388
390 """Creates the call tree of internal package functions and procedures"""
391
392
393 try:
394 from pydot import find_graphviz, Dot, Edge, Node
395 except ImportError:
396 message = _("Function not available because pydot module is not installed.\n\t")
397 message += _("Go to http://dkbza.org/pydot.html to get it.")
398 raise PysqlException(message)
399
400
401 conf = PysqlConf.getConfig()
402 format = conf.get("graph_format")
403 fontname = conf.get("graph_fontname")
404 fontsize = conf.get("graph_fontsize")
405 fontcolor = conf.get("graph_fontcolor")
406
407
408 prog = getProg(find_graphviz(), conf.get("graph_program"), "fdp")
409
410 package = OraObject(objectName=packageName)
411 package.guessInfos(db)
412
413 graph = Dot(overlap="false", splines="true")
414
415
416 verbs = []
417
418
419
420 if package.getType() == "SYNONYM":
421 package = package.getTarget(db)
422 if package.getType() == "SYNONYM":
423 raise PysqlException(_("Too much synonym recursion"))
424
425 if package.getType() not in ("PACKAGE", "PACKAGE BODY"):
426 raise PysqlException(_("This is not a package or package not found"))
427
428
429 package.setType(u"PACKAGE BODY")
430 print CYAN + _("Extracting package source...") + RESET
431 content = package.getSQLAsList(db)
432
433
434 print CYAN + _("Parsing source and building graph...") + RESET
435 newContent = []
436 comment = False
437 for line in content:
438 line, comment = removeComment(line, comment)
439 newContent.append(line)
440 content = newContent
441
442
443 for line in content:
444 result = re.match("\s*(FUNCTION|PROCEDURE)\s+(.+?)[\s|\(]+", line, re.I)
445 if result:
446 verbs.append(re.escape(result.group(2)))
447 graph.add_node(Node(result.group(2).upper(), shape="box", label=result.group(2).upper(), \
448 fontsize=str(fontsize), fontname=fontname, fontcolor=fontcolor))
449
450 if not verbs:
451 raise PysqlException(_("This package does not have any readable function or procedure"))
452
453 verbs = "|".join(verbs)
454
455 currentVerb = ""
456 for line in content:
457
458 if re.match("\s*END.*;", line, re.I):
459 continue
460
461 result = re.match("\s*(FUNCTION|PROCEDURE)\s+(.+?)[\s|\(]+", line, re.I)
462 if result:
463 currentVerb = result.group(2)
464 continue
465 result = re.match(".*\s(%s).*" % verbs, line, re.I)
466 if result:
467 if graph.get_edge(currentVerb.upper(), result.group(1).upper()) is None:
468 graph.add_edge(Edge(src=currentVerb.upper(), dst=result.group(1).upper()))
469
470 filename = package.getName() + "_dep." + format
471 generateImage(graph, filename, prog, format)
472 viewImage(filename)
473
475 """Shows Image with prefered user image viewer
476 @param imagePath: path to image file"""
477 conf = PysqlConf.getConfig()
478 viewer = conf.get("graph_viewer")
479 if viewer == "off":
480 return
481 elif viewer == "auto":
482 if os.name == "nt":
483 viewers = ("mspaint.exe",)
484 else:
485 viewers = ("gwenview", "kview", "kuickshow", "eog", "gthumb", "gimp", "firefox")
486 for viewer in viewers:
487 viewer = which(viewer)
488 if viewer is not None:
489 break
490 else:
491 viewer = which(viewer)
492 if viewer is not None:
493 subprocess.Popen([viewer, imagePath], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
494 else:
495 raise PysqlException(_("Viewer was not found"))
496
498 """Generate graphviz image from graph
499 @param graph: pydot graph object
500 @param filename: image filename (str)
501 @param format: image format (str)
502 """
503 print CYAN + _("Generating picture using %s filter...") % prog + RESET
504 filepath = os.getcwd() + os.sep + filename
505 import pydot
506 oldstdout = sys.stdout
507 sys.stdout = open(os.devnull, 'a')
508 try:
509 graph.write(filepath, prog=prog, format=format)
510 except (IOError, OSError, pydot.InvocationException), e:
511 sys.stdout = sys.__stdout__
512 raise PysqlException(_("Graphviz failed to generate image:\n%s") % e)
513 sys.stdout = oldstdout
514 print GREEN + _("Image saved as ") + filepath + RESET
515