1
2
3
4 """User interaction with pysql
5 @author: Sébastien Renard (sebastien.renard@digitalfox.org)
6 @author: Sébastien Delcros (Sebastien.Delcros@gmail.com)
7 @license: GNU GPL V3
8 """
9
10
11 import cmd
12 import sys
13 import os
14 import readline
15 from re import findall, match, sub
16 from os.path import expandvars
17 from time import sleep, time
18 from getpass import getpass
19 import csv
20
21
22 from pysqldb import PysqlDb, BgQuery
23 import pysqlfunctions
24 import pysqlgraphics
25 import pysqlaudit
26 from pysqlexception import PysqlException, PysqlNotImplemented, PysqlOptionParserNormalExitException
27 from pysqlconf import PysqlConf
28 from pysqlcolor import BOLD, CYAN, GREEN, GREY, RED, RESET
29 from pysqlhelpers import itemLength, removeComment, printStackTrace, setTitle, getTitle, getTermWidth, WaitCursor
30 from pysqloptionparser import PysqlOptionParser
31
33 """Main class that handle user interaction"""
34
35 - def __init__(self, completekey='tab', stdin=None, stdout=None, silent=False, argv=[]):
36 """Shell initialisation"""
37
38
39 self.db = None
40 self.fetching = False
41 self.multilineCmd = False
42 self.plBloc = False
43 self.comment = False
44 self.cmdBuffer = []
45 self.lastStatement = ""
46 self.tnsnamesAvailable = None
47 self.conf = None
48 self.cmds = []
49 self.bgQueries = []
50 self.exceptions = []
51 self.useCompletion = True
52 self.showBanner = not silent
53 self.showPrompt = not silent
54 self.trace = {}
55 self.rc = 0
56 self.oldTermName = ""
57 self.waitCursor = None
58 self.tty = sys.stdin.isatty()
59 self.allowAnimatedCursor = True
60 self.notConnectedPrompt = RED + _("(not connected) ") + RESET
61
62
63 self.conf = PysqlConf.getConfig()
64
65
66
67 if not self.tty:
68 self.useCompletion = False
69 self.allowAnimatedCursor = False
70
71
72 cmd.Cmd.__init__(self, "tab", stdin, stdout)
73
74
75 readline.set_completer_delims(readline.get_completer_delims().replace("-", ""))
76
77
78 self.oldTermName = getTitle()
79
80 if len(argv) == 0:
81
82 self.__setPrompt()
83 else:
84
85 try:
86 self.do_connect(" ".join(argv))
87 except PysqlException, e:
88
89 print RED + BOLD + _("\nConnection failed:\n\t %s") % e + RESET
90 self.exceptions.append(e)
91 self.db = None
92 self.__setPrompt()
93 except KeyboardInterrupt:
94 print RED + BOLD + _("Break !") + RESET
95
96 self.__exit()
97
99 """Prepares shell interactive loop"""
100
101 self.cmds = [i[3:] for i in self.get_names() if i.startswith("do_")]
102 self.cmds.remove("explain")
103 self.cmds.remove("csv")
104 if self.showBanner:
105 banner = _("\nWelcome to pysql shell\n")
106 banner += _("""Type "help" for some help.\nUse Tab for completion\n""")
107 print banner
108
110 """Starts shell interactive loop"""
111 try:
112 self.cmdloop()
113 except KeyboardInterrupt:
114
115
116 print RED + BOLD + _("Break !") + RESET
117 self.showBanner = False
118 self.loop()
119
120 - def postloop(self):
121 """End of command loop"""
122
123 setTitle(self.oldTermName, self.conf.getCodec())
124
126 """Fetches next result if a request is running
127 or do nothning"""
128 if self.fetching:
129 try:
130 self.__fetchNext()
131 except PysqlException, e:
132 self.fetching = False
133 raise PysqlException(e)
134
136 """This method is subclassed just to be
137 able to encapsulate it with a try/except bloc"""
138 try:
139 return cmd.Cmd.onecmd(self, line)
140 except PysqlOptionParserNormalExitException:
141
142 pass
143 except PysqlException, e:
144 print RED + BOLD + "*** " + _("Pysql error") + " ***\n\t%s" % e + RESET
145 self.exceptions.append(e)
146 if e.oraCode == "ORA-03114":
147 self.db = None
148 except KeyboardInterrupt:
149 print RED + BOLD + _("Break !") + RESET
150 except StandardError, e:
151
152 print RED + BOLD + _("\n==> Unhandled error. Sorry <==") + RESET
153 printStackTrace()
154
156 """Hook executed just before any command execution.
157 This is used to parse command and dispatch it to Oracle or internal pysql functions"""
158
159
160 line = line.decode(self.conf.getCodec())
161
162 if self.conf.get("echo") == "yes":
163
164 print line
165
166 line, self.comment = removeComment(line, self.comment)
167
168
169 line = line.strip()
170
171
172 if len(line) == 0 or line == "EOF":
173 return line
174
175
176 if line[0] == "@":
177 return "script " + line[1:]
178
179 firstWord = line.split()[0]
180
181
182 if self.aliases.has_key(firstWord):
183 line = line.replace(firstWord, self.aliases[firstWord], 1)
184 firstWord = self.aliases[firstWord]
185
186
187 if (firstWord in self.cmds or line[0] == "!") and not self.multilineCmd:
188
189 if firstWord != "set":
190
191 line = line.rstrip(";")
192 return line
193
194 if firstWord.lower() in ("declare", "begin"):
195
196 self.plBloc = True
197 elif firstWord == "/" and not self.plBloc and not self.multilineCmd:
198
199 if self.lastStatement:
200 return self.lastStatement.rstrip(";")
201 else:
202 return ""
203
204 if (line[-1] in [";", "&", "/"] and not self.plBloc) or (line[-1] == "/" and self.plBloc):
205
206
207 line = line.rstrip(";")
208 line = line.rstrip("/")
209 self.cmdBuffer.append(line)
210 line = " ".join(self.cmdBuffer)
211 self.cmdBuffer = []
212 self.__setPrompt()
213 if self.multilineCmd:
214
215 try:
216 length = readline.get_current_history_length()
217 if length > 1:
218 readline.replace_history_item(length - 1, line)
219 except AttributeError:
220
221 pass
222 self.multilineCmd = False
223 self.plBloc = False
224 else:
225
226 if not self.multilineCmd:
227 result = match("(.+?)\s.+", line)
228 if result:
229 firstWord = result.group(1)
230 else:
231 firstWord = line
232 if not self.multilineCmd \
233 and firstWord.lower() not in ("select", "insert", "update", "delete",
234 "alter", "truncate", "drop", "begin",
235 "declare", "comment", "create", "grant",
236 "revoke", "analyze", "explain", "csv"):
237 print RED + BOLD + _("""Unknown command or sql order. Type "help" for help""") + RESET
238 else:
239
240 self.multilineCmd = True
241 self.cmdBuffer.append(line)
242 self.fetching = False
243 self.__setPrompt(multiline=True)
244 try:
245 length = readline.get_current_history_length()
246 if length > 1:
247
248 readline.remove_history_item(length - 1)
249 except AttributeError:
250
251 pass
252
253 line = ""
254 return line
255
256 - def postcmd(self, stop, line):
257 """Hook executed just after command processing.
258 Used to notify running and finished background queries
259 @return: stop flag to end loop"""
260 if self.waitCursor:
261 self.waitCursor.stop()
262 self.waitCursor = None
263 if self.multilineCmd:
264 self.__setPrompt(multiline=True)
265 else:
266 queries = [i for i in self.bgQueries if not i.isAlive()]
267 if len(queries) != 0:
268 self.__setPrompt(finishedQuery=True)
269 else:
270 self.__setPrompt()
271 return stop
272
274 """Default method if no command is recognized.
275 We assume it is a pure SQL request"""
276 if arg == "EOF":
277 return self.__exit()
278 else:
279 if self.tty:
280 self.__executeSQL(arg, output="tty")
281 else:
282 self.__executeSQL(arg, output="notty")
283
285 """
286 Overload do_help to show help from the command parser if it exists:
287 if there is a parser_foo() method, assume this method returns a
288 PysqlOptionParser for the do_foo() method and show the help of the
289 parser, instead of standard help (do_foo() docstring or help_foo())
290 """
291 if self.aliases.has_key(arg):
292 arg = self.aliases[arg]
293 if hasattr(self, "parser_" + arg):
294 parserMethod = getattr(self, "parser_" + arg)
295 parserMethod().print_help(sys.stderr)
296 else:
297 cmd.Cmd.do_help(self, arg)
298
300 """Complete commands names. Same as Cmd.cmd one but with support
301 for command aliases"""
302 dotext = 'do_' + text
303 names = [a[3:] for a in self.get_names() if a.startswith(dotext)]
304 names.extend([a for a in self.aliases.keys() if a.startswith(text)])
305 return names
306
308 """pysql specific completion with self.completeList"""
309 if not self.useCompletion:
310 return
311
312 lastKeyWord = line[:begidx].split()[-1].lower()
313 if lastKeyWord in ["select", "where", "by",
314 "sum(", "abs(", "round(", "upper(", "lower(", "set"]:
315 themes = ["columns"]
316 elif lastKeyWord in ["update"]:
317 themes = ["table"]
318 elif lastKeyWord in ["from", "into"]:
319 themes = ["table", "view", "synonym"]
320 elif lastKeyWord == "index":
321 themes = ["index"]
322 elif lastKeyWord == "table":
323 themes = ["table"]
324 elif lastKeyWord == "sequence":
325 themes = ["sequence"]
326 elif lastKeyWord == "directory":
327 themes = ["directory"]
328 elif lastKeyWord == "index":
329 themes = ["trigger"]
330 elif lastKeyWord == "view":
331 themes = ["view"]
332 elif lastKeyWord == "user":
333 themes = ["user"]
334 else:
335 themes = ["table", "view", "synonym", "columns",
336 "directory", "sequence", "user"]
337
338 if text.count(".") == 1:
339 prefix, text = text.split(".")
340 prefix += "."
341 else:
342 prefix = ""
343 return self.__getCompletionItems(text, themes, prefix)
344
346 """Completion of SID for connect method"""
347 if not self.useCompletion:
348 return
349
350 if line.count("@"):
351 sid = line.split("@")[-1]
352 else:
353
354 return []
355
356 if self.tnsnamesAvailable is None:
357
358 try:
359 tnsnames = file(expandvars("$ORACLE_HOME/network/admin/tnsnames.ora")).readlines()
360 self.conf.completeLists["SID"] = sum([findall("^(\w+)\s*=", line) for line in tnsnames], [])
361 self.tnsnamesAvailable = True
362 except Exception, e:
363
364 print RED + BOLD + _("Cannot open tnsnames.ora file (%s)") % e + RESET
365 self.tnsnamesAvailable = False
366 if self.tnsnamesAvailable:
367 return self.__getCompletionItems(sid, ["SID"])
368 else:
369 return []
370
372 """Completion for command desc"""
373 if not self.useCompletion:
374 return
375 return self.__getCompletionItems(text, ["table", "view", "index",
376 "synonym", "sequence",
377 "tablespace", "datafile"
378 "directory"])
379
381 """Completion for command edit"""
382 if not self.useCompletion:
383 return
384 return self.__getCompletionItems(text, ["view", "package"])
385
387 """Completion for command get"""
388 if not self.useCompletion:
389 return
390 return self.__getCompletionItems(text, ["parameters"])
391
393 """Completion for command set"""
394 if not self.useCompletion:
395 return
396 return self.__getCompletionItems(text, ["parameters"])
397
399 """Completion for library command"""
400 return [k for k in self.conf.sqlLibrary.keys() if k.startswith(text)]
401
402
403
405 """Connect to instance"""
406 self.__checkArg(arg, ">=1")
407 arg = arg.split()
408
409 try:
410 self.__disconnect()
411 except PysqlException, e:
412 print RED + BOLD + _("Error while closing previous connection:\n\t %s") % e + RESET
413 self.exceptions.append(e)
414 self.db = None
415 if len(arg) == 1:
416 mode = ""
417 elif arg[1].lower() == "sysdba" or (len(arg) > 2 and " ".join(arg[1:3]).lower() == "as sysdba"):
418 mode = "sysdba"
419 elif arg[1].lower() == "sysoper" or (len(arg) > 2 and " ".join(arg[1:3]).lower() == "as sysoper"):
420 mode = "sysoper"
421 else:
422 mode = ""
423 print RED + BOLD + _("Invalid Oracle mode: %s (ignored)") % (" ".join(arg[1:])) + RESET
424 self.__connect(arg[0], mode)
425
434
435
437 """Shows completion list"""
438 for theme in self.conf.completeLists.keys():
439 print GREEN + "***** " + theme + " *****" + RESET
440 self.__displayCol(self.conf.completeLists[theme])
441 print
442
443 - def do_history(self, arg):
444 """Display shell history"""
445 self.__checkArg(arg, "<=1")
446
447
448 depth = 20
449 try:
450 length = readline.get_current_history_length()
451 except AttributeError:
452 message = _("History not available on Windows platform (readline limitation)")
453 raise PysqlException(message)
454 arg = arg.split()
455 if len(arg) == 0:
456
457 for i in xrange(depth):
458 position = length - depth + i
459
460 print "%d: %s " % (position, readline.get_history_item(position))
461 elif len(arg) == 1:
462
463 try:
464 position = int(arg[0])
465 except ValueError:
466 raise PysqlException(_("Argument must be an integer"))
467
468 if position > length:
469 raise PysqlException(_("This is the future ! Cannot execute. Sorry"))
470 elif position <= 0:
471 raise PysqlException(_("Argument should be stricly positive"))
472 else:
473 command = readline.get_history_item(position)
474 command = self.precmd(command)
475 print command
476 self.onecmd(command)
477 else:
478 raise PysqlException(_("See help history for usage"))
479
481 """Manage user sql request library"""
482 nArgs = len(arg.split())
483 if nArgs == 0:
484
485 self.__displayTab(self.conf.sqlLibrary.items(), (_("Name"), _("SQL request")))
486 elif nArgs == 1:
487
488 if self.conf.sqlLibrary.has_key(arg):
489 readline.add_history(self.conf.sqlLibrary[arg])
490 print GREEN + \
491 _("SQL request was loaded in your history. Use up arrow to get it now") \
492 + RESET
493 else:
494 msg = _("Request %s does not exist. ") % name + \
495 _("""Type "lib" without argument to see all requests""")
496 raise PysqlException(msg)
497 elif nArgs > 1:
498
499 name = arg.split()[0]
500 text = " ".join(arg.split()[1:])
501 if text == "remove":
502 if self.conf.sqlLibrary.has_key(name):
503 del self.conf.sqlLibrary[name]
504 print GREEN + _("Request has been removed") + RESET
505 else:
506 msg = _("Request %s does not exist. ") % name + \
507 _("""Type "lib" without argument to see all requests""")
508 raise PysqlException(msg)
509 else:
510
511 self.conf.sqlLibrary[name] = text
512 print GREEN + _("Request has been saved") + RESET
513
514
516 """Manages background queries"""
517 arg = arg.split()
518 if len(arg) == 0:
519
520 result = [(i.getName(), i.query, not i.isAlive(), i.error) for i in self.bgQueries]
521 self.__displayTab(result, [_("#"), _("SQL request"), _("Finished?"), _("Error")])
522 elif len(arg) == 1:
523
524 bgQuery = [i for i in self.bgQueries if i.getName() == arg[0]]
525 if len(bgQuery) == 1:
526 bgQuery = bgQuery[0]
527
528 self.__animateCursor()
529 bgQuery.join()
530
531 if bgQuery.query.upper().split()[0].startswith("SELECT"):
532 self.db = bgQuery.db
533 self.__toScreen(bgQuery.result, bgQuery.moreRows)
534 else:
535 print GREEN + _("Statement executed") + RESET
536
537 self.bgQueries.remove(bgQuery)
538 else:
539 raise PysqlException(_("Unknown background query. Use bg without arg to see all queries"))
540 else:
541 raise PysqlException(_("See help bg for description"))
542
543
550
557
558
560 """Starts database up (Oracle v10R2 or upper required)"""
561 self.__checkConnection()
562 self.__checkArg(arg, "<=1")
563 if arg == "mount":
564 self.db.startup("mount")
565 print GREEN + _("Database mounted") + RESET
566 else:
567 self.db.startup("normal")
568 print GREEN + _("Database opened") + RESET
569
571 """Shuts database down (Oracle v10R2 or upper required)"""
572 self.__checkConnection()
573 self.__checkArg(arg, "<=1")
574 if arg == "abort":
575 self.db.shutdown("abort")
576 elif arg == "immediate":
577 self.db.shutdown("immediate")
578 else:
579 self.db.shutdown("normal")
580 print GREEN + _("Instance shut down") + RESET
581
582
583
591
593 """Compares schema or object structure and data"""
594 self.__checkArg(arg, ">=2")
595 schemaNames = []
596 tableNames = []
597 schemas = []
598 withData = False
599
600 arg = arg.split()
601
602 if arg[0] == "data":
603 withData = True
604 arg.pop(0)
605 elif arg[0] == "structure":
606 withData = False
607 arg.pop(0)
608
609 for item in arg:
610 result = match("(.+?)/(.+?)@(\w+):?(.*)", item)
611 if result:
612
613 schemaNames.append(result.group(1) + "@" + result.group(3))
614 if result.group(4):
615
616 tableNames.append(result.group(4))
617 schemas.append(item.split(":")[0])
618 else:
619 schemas.append(item)
620 else:
621
622 tableNames.append(item)
623
624 if not schemas:
625
626 self.__checkConnection()
627 schemas = ["A", "B"]
628
629 dbList = { schemas[0] : PysqlDb(self.db.getConnectString()),
630 schemas[1] : PysqlDb(self.db.getConnectString()) }
631 else:
632
633 dbList = None
634
635 if tableNames:
636
637 if len(tableNames) != 2:
638 raise PysqlException(_("Cannot compare a table and a schema!"))
639 result = pysqlfunctions.compareTables(schemas[0], schemas[1],
640 tableNames[0], tableNames[1],
641 dbList, data=withData)
642 if result:
643 print CYAN + \
644 """Table %s (marked with "-") differ from table %s (marked with "+")""" \
645 % (tableNames[0], tableNames[1]) + RESET
646 print "\n".join(result)
647 else:
648 print _("Tables are identical")
649 else:
650
651 result = pysqlfunctions.compare(schemas[0], schemas[1])
652
653 print GREEN + "**** " + _("Tables found in %s but not in %s ") + "****" \
654 % (schemaNames[0], schemaNames[1]) + RESET
655 print ", ".join(result[0])
656 print
657
658 print GREEN + "**** " + _("Tables found in %s but not in %s ") + "****" \
659 % (schemaNames[1], schemaNames[0]) + RESET
660 print ", ".join(result[1])
661 print
662
663 print GREEN + "**** " + _("Tables identical in both schema ") + "****" + RESET
664 print ", ".join([i[0] for i in result[2].items() if not i[1]])
665 print
666
667 print GREEN + "**** " + _("Tables not identical in both schema") + "****" + RESET
668 for tableName, tableDiff in result[2].items():
669 if tableDiff:
670 print CYAN + _("""Table %s differ from schema %s""") \
671 % (tableName, schemaNames[0]),
672 print _("""(marked with "-") and schema %s (marked with "+")""") \
673 % schemaNames[1] + RESET
674 print "\n".join(tableDiff)
675 print
676
678 parser = PysqlOptionParser()
679 parser.set_usage(CYAN + "desc[ribe] " + _("[options] <object name>") + RESET)
680 parser.set_description(_("Describes any Oracle object"))
681 parser.add_option("-d", "--details", dest="printDetails",
682 default=False, action="store_true",
683 help=_("displays detailed information about the object"))
684 parser.add_option("-t", "--stats", dest="printStats",
685 default=False, action="store_true",
686 help=_("displays statistics on the object if any"))
687 parser.add_option("-s", "--sort", dest="sort",
688 default=False, action="store_true",
689 help=_("sorts column alphabetically instead of Oracle native order"))
690 return parser
691
705
706
708 parser = PysqlOptionParser()
709 parser.set_usage(CYAN + "addm[rpt] " + _("[options]") + RESET)
710 parser.set_description(_("Generates tuning advice report based on AWR statistics. ") +
711 _("10g or upper is required. ") +
712 _("Before starting, please ensure that you have the required license to use it."))
713
714
715
716
717 parser.add_option("-l", "--level", dest="level",
718 default="TYPICAL",
719 help=_("level: BASIC | TYPICAL (default) | ALL"))
720 parser.add_option("-b", "--begin", dest="begin_snap",
721 default=u"0",
722 help=_("begin snapshot identifier"))
723 parser.add_option("-e", "--end", dest="end_snap",
724 default=u"0",
725 help=_("end snapshot identifier"))
726 parser.add_option("-o", "--output-file", dest="filename",
727 default=u"",
728 help=_("output file"))
729 return parser
730
732 """Generates ADDM report"""
733 parser = self.parser_addmrpt()
734 options, args = parser.parse_args(arg)
735 self.__checkConnection()
736 self.__checkArg(arg, "<=6")
737 if self.tty:
738 if options.begin_snap == "0":
739 (numDays, options.begin_snap) = self.__askForSnapshotId(0, _("begin"))
740 if options.end_snap == "0":
741 (numDays, options.end_snap) = self.__askForSnapshotId(numDays, _("end"))
742 else:
743
744 if options.begin_snap == "0" or options.end_snap == "0":
745 raise PysqlException(_("Missing arguments. Please, specify snaphsot identifiers."))
746 self.__animateCursor()
747 result = pysqlaudit.addmReport(self.db,
748 options.begin_snap,
749 options.end_snap,
750
751 level=options.level)
752 if options.filename == "":
753 self.__toScreen(result, moreRows=False, header=False)
754 else:
755 self.__toCsv(result, options.filename, header=False)
756 print GREEN + _("(Completed)") + RESET
757
759 parser = PysqlOptionParser()
760 parser.set_usage(CYAN + "awr[rpt] " + _("[options]") + RESET)
761 parser.set_description(_("Generates performance report based on AWR statistics. ") +
762 _("10g or upper is required. ") +
763 _("Before starting, please ensure that you have the required license to use it."))
764 parser.add_option("-t", "--type", dest="type",
765 default="TEXT",
766 help=_("output type: HTML | TEXT (default)"))
767 parser.add_option("-b", "--begin", dest="begin_snap",
768 default=u"0",
769 help=_("begin snapshot identifier"))
770 parser.add_option("-e", "--end", dest="end_snap",
771 default=u"0",
772 help=_("end snapshot identifier"))
773 parser.add_option("-o", "--output-file", dest="filename",
774 default=u"",
775 help=_("output file"))
776 return parser
777
779 """Generates AWR report"""
780 parser = self.parser_awrrpt()
781 options, args = parser.parse_args(arg)
782 self.__checkConnection()
783 self.__checkArg(arg, "<=8")
784 if self.tty:
785 if options.begin_snap == "0":
786 (numDays, options.begin_snap) = self.__askForSnapshotId(0, _("begin"))
787 if options.end_snap == "0":
788 (numDays, options.end_snap) = self.__askForSnapshotId(numDays, _("end"))
789 else:
790
791 if options.begin_snap == "0" or options.end_snap == "0":
792 raise PysqlException(_("Missing arguments. Please, specify snaphsot identifiers."))
793 self.__animateCursor()
794 result = pysqlaudit.awrReport(self.db,
795 options.type,
796 options.begin_snap,
797 options.end_snap)
798 if options.filename == "":
799 self.__toScreen(result, moreRows=False, header=False)
800 else:
801 self.__toCsv(result, options.filename, header=False)
802 print GREEN + _("(Completed)") + RESET
803
805 parser = PysqlOptionParser()
806 parser.set_usage(CYAN + "sqltune " + _("[options] <sql statement>") + RESET)
807 parser.set_description(_("Generates tuning advice report for an SQL query. ") +
808 _("10g or upper is required. "))
809
810
811
812
813 parser.add_option("-l", "--level", dest="level",
814 default="TYPICAL",
815 help=_("level: BASIC | TYPICAL (default) | ALL"))
816 parser.add_option("-o", "--output-file", dest="filename",
817 default=u"",
818 help=_("output file"))
819 return parser
820
836
838 parser = PysqlOptionParser()
839 parser.set_usage(CYAN + "durpt " + _("[options]") + RESET)
840 parser.set_description(_("Generates storage report based on segment statistics. ") +
841 _("DBA grants are required. "))
842 parser.add_option("-s", "--segment-type", dest="type",
843 default=u"both",
844 help=_("type of segment: TABLE | INDEX | BOTH (default)"))
845 parser.add_option("-t", "--tablespace", dest="tbs",
846 default=u"%",
847 help=_("filters by tablespace"))
848 parser.add_option("-u", "--user", dest="user",
849 default=u"%",
850 help=_("filters by user"))
851 parser.add_option("-n", "--nbLines", dest="nbLines",
852 default= -1,
853 help=_("filters resultset to the n first rows"))
854 parser.add_option("-o", "--output-file", dest="filename",
855 default=u"",
856 help=_("output file") + _(" (1 distinct file per segment type)"))
857 return parser
858
860 """Generates disk usage report"""
861 parser = self.parser_durpt()
862 options, args = parser.parse_args(arg)
863 self.__checkConnection()
864 self.__checkArg(arg, "<=8")
865 self.__animateCursor()
866 for type in ("table", "index"):
867 if options.type.lower() in ("both", type):
868 (result, header) = pysqlaudit.duReport(self.db,
869 type,
870 options.tbs.upper().replace('*', '%'),
871 options.user.upper().replace('*', '%'),
872 int(options.nbLines))
873 if options.filename == "":
874 print GREEN + "***** " + type.upper() + " *****" + RESET
875 self.__displayTab(result, header)
876 print
877 else:
878 if options.filename.split(".")[-1] == "csv":
879 filename = options.filename.replace(".csv", "_%s.csv" % type.lower())
880 elif options.filename.split(".")[-1] == "CSV":
881 filename = options.filename.replace(".CSV", "_%s.CSV" % type.upper())
882 else:
883 filename = options.filename + "_" + type
884 self.__toCsv(result, filename)
885 print GREEN + _("(Completed)") + RESET
886
894
895
897 parser = PysqlOptionParser()
898 parser.set_usage(CYAN + "datamodel " + _("[options] [filters on table name]]") + RESET)
899 parser.set_description(
900 _("Extracts the datamodel of a user filtered on selected table pattern. ") +
901 _("The generation of the output is powered by Graphviz (http://www.graphviz.org)")
902 )
903 if self.db:
904 defaultUser = self.db.getUsername()
905 else:
906 defaultUser = ""
907 parser.add_option("-c", "--columns", dest="columns",
908 default=False, action="store_true",
909 help=_("also draws table's columns"))
910 parser.add_option("-u", "--user", dest="user",
911 default=defaultUser,
912 help=_("user owner of tables (schema)"))
913 return parser
914
923
925 parser = PysqlOptionParser()
926 parser.set_usage(CYAN + "dep[endencies] " + _("[options] <object name>") + RESET)
927 parser.set_description(
928 _("Displays object dependencies as a picture. ") +
929 _("The generation of the output is powered by Graphviz (http://www.graphviz.org)")
930 )
931 directions = ("onto", "from", "both")
932 parser.add_option("-d", "--direction", dest="direction",
933 default="both", type="choice",
934 metavar="<direction>", choices=directions,
935 help=_("direction of dependency tracking: %s" % ", ".join(directions)))
936 parser.add_option("-r", "--recursion", dest="maxDepth",
937 default=self.conf.get("graph_depmaxdepth"), type="int",
938 help=_("maximum level of recursion"))
939 parser.add_option("-n", "--nodes", dest="maxNodes",
940 default=self.conf.get("graph_depmaxnodes"), type="int",
941 help=_("maximum number of nodes on graph"))
942 return parser
943
954
956 parser = PysqlOptionParser()
957 parser.set_usage(CYAN + "diskusage|du " + _("[options] <schema name>") + RESET)
958 parser.set_description(
959 _("Extracts the physical storage of a user as a picture based on Oracle statistics. ") +
960 _("The generation of the output is powered by Graphviz (http://www.graphviz.org)")
961 )
962 parser.add_option("-i", "--index", dest="index",
963 default=False, action="store_true",
964 help=_("also draws index segment"))
965 parser.add_option("-p", "--percent", dest="percent",
966 default=False, action="store_true",
967 help=_("draws object sizes against others"))
968
969 return parser
970
981
992
994 """Edits properties of an Orale object or last SQL statement"""
995 self.__checkConnection()
996 nArgs = len(arg.split())
997 if nArgs == 0:
998
999 result = pysqlfunctions.editor(self.lastStatement + ";")
1000 if result:
1001 result = result.rstrip("\n")
1002 readline.add_history(result)
1003 self.lastStatement = result
1004 elif nArgs == 1:
1005
1006 if pysqlfunctions.edit(self.db, arg):
1007 print GREEN + _("(Update successful)") + RESET
1008 else:
1009 print CYAN + _("(no result)") + RESET
1010 else:
1011 raise PysqlException(_("Incorrect arguments. See help edit"))
1012
1019
1021 """Explain SQL exec plan"""
1022 self.__checkConnection()
1023 self.__checkArg(arg, ">1")
1024 self.__animateCursor()
1025 importantWords = ["TABLE ACCESS FULL", "FULL SCAN"]
1026 result = pysqlfunctions.explain(self.db, arg)
1027 for line in result:
1028 line = line[0]
1029 for word in importantWords:
1030 line = sub("(.*)(" + word + ")(.*)", r"\1" + RED + r"\2" + RESET + r"\3", line)
1031 print line
1032
1034 parser = PysqlOptionParser()
1035 parser.set_usage(CYAN + "session " + _("[options] <session id>") + RESET)
1036 parser.set_description(_("Displays Oracle sessions. ") +
1037 _("If a session id is provided, display detailed session informations, ") +
1038 _("else display all session summary"))
1039 parser.add_option("-a", "--all", dest="all",
1040 default=False, action="store_true",
1041 help=_("displays all foreground sessions, both active and inactive"))
1042 parser.add_option("-s", "--search", dest="search",
1043 action="append",
1044 help=_("filters session display with given search term. Multiple searches can be given to make 'and' search"))
1045 return parser
1046
1048 """Display Oracle session"""
1049 self.__checkConnection()
1050 parser = self.parser_session()
1051 options, args = parser.parse_args(arg)
1052 if options.all and args:
1053 print CYAN + _("Note: the all (-a / --all) option is useless when display one session") + RESET
1054 if not args:
1055
1056 (header, result) = pysqlfunctions.sessions(self.db, all=options.all, search=options.search)
1057 self.__displayTab(result, header)
1058 else:
1059 sessionId = args[0]
1060
1061 print CYAN + "*****" + _("Input/Output statistics") + "*****" + RESET
1062 result = pysqlfunctions.sessionStat(self.db, sessionId, stat="ios")
1063 self.__displayTab(result, self.db.getDescription())
1064 print CYAN + "*****" + _("Wait events") + "*****" + RESET
1065 result = pysqlfunctions.sessionStat(self.db, sessionId, stat="waitEvents")
1066 self.__displayTab(result, self.db.getDescription())
1067 print CYAN + "*****" + _("Current statement") + "*****" + RESET
1068 result = pysqlfunctions.sessionStat(self.db, sessionId, stat="currentStatement")
1069 if result and result[0][0]:
1070 result = "".join([i[0] for i in result])
1071 result = sub("\s+", " ", result)
1072 print result
1073 try:
1074 if not result.upper().startswith("ALTER"):
1075 self.do_explain(result)
1076 except PysqlException, e:
1077
1078 print _("Cannot explain plan (%s)") % e
1079 else:
1080 print _("No statement")
1081 print CYAN + "*****" + _("Open cursors") + "*****" + RESET
1082 result = pysqlfunctions.sessionStat(self.db, sessionId, stat="openCursors")
1083 self.__displayTab(result, self.db.getDescription())
1084 print CYAN + "*****" + _("Locks") + "*****" + RESET
1085 result = pysqlfunctions.sessionStat(self.db, sessionId, stat="locks")
1086 self.__displayTab(result, self.db.getDescription())
1087
1089 parser = PysqlOptionParser()
1090 parser.set_usage(CYAN + "kill [options] " + _("<session-id> <session-serial>") + RESET)
1091 parser.set_description(_("Kills the session given in parameter. ") +
1092 _("Uses the 'session' command to find session-id and session-serial (two first columns)"))
1093 parser.add_option("-i", "--immediate", dest="immediate",
1094 default=False, action="store_true",
1095 help=_("kills immediatly the current session"))
1096 return parser
1097
1106
1114
1116 """Trace a session"""
1117 self.__checkConnection()
1118 self.__checkArg(sid, "==1")
1119 try:
1120 if self.trace.has_key(sid):
1121
1122 ios = list(pysqlfunctions.sessionStat(self.db, sid, "ios")[0])
1123 iosHeader = self.db.getDescription()
1124 waits = list(pysqlfunctions.sessionStat(self.db, sid, "waitEvents")[0])
1125 waitsHeader = self.db.getDescription()
1126 def conv(item):
1127 """Sub function to convert str to int and NULL to 0"""
1128 if item is None:
1129 return 0
1130 else:
1131 try:
1132 return int(item)
1133 except ValueError:
1134 return 0
1135 ios = [conv(i) for i in ios]
1136 waits = [conv(i) for i in waits]
1137 self.trace[sid][0] = [conv(i) for i in self.trace[sid][0]]
1138 self.trace[sid][1] = [conv(i) for i in self.trace[sid][1]]
1139 resultIos = []
1140 resultWaits = []
1141 for i in xrange(len(ios)):
1142 resultIos.append(ios[i] - self.trace[sid][0][i])
1143 resultWaits.append(waits[i] - self.trace[sid][1][i])
1144 print CYAN + "*****" + _("Input/Output delta for session %s") % sid + "*****" + RESET
1145 self.__displayTab([resultIos], iosHeader)
1146 print CYAN + "*****" + _("Wait events delta for session %s") % sid + "*****" + RESET
1147 self.__displayTab([resultWaits], waitsHeader)
1148
1149 del self.trace[sid]
1150 else:
1151
1152 print CYAN + _("Starting trace capture for session %s") % sid + RESET
1153 print CYAN + _("""Type "trace %s" again to stop trace on this sesssion""") % sid + RESET
1154 ios = list(pysqlfunctions.sessionStat(self.db, sid, "ios")[0])
1155 waits = list(pysqlfunctions.sessionStat(self.db, sid, "waitEvents")[0])
1156
1157 self.trace[sid] = [ios, waits]
1158 except IndexError:
1159 msg = _("Session %s does not exist or you are not allowed to see session details") % sid
1160 raise PysqlException(msg)
1161
1168
1169
1171 """Display datafile"""
1172 self.__searchObjet("datafile", arg)
1173
1175 """Display directories"""
1176 self.__searchObjet("directory", arg)
1177
1179 """Display functions"""
1180 self.__searchObjet("function", arg)
1181
1185
1187 """Display PL/SQL packages"""
1188 self.__searchObjet("package", arg)
1189
1191 """Display PL/SQL procedures"""
1192 self.__searchObjet("procedure", arg)
1193
1195 """Display segments (tables, index)"""
1196 print CYAN + "***** " + _("Tables") + " *****" + RESET
1197 self.__searchObjet("table", arg)
1198 print CYAN + "\n***** " + _("Indexes") + " *****" + RESET
1199 self.__searchObjet("index", arg)
1200
1202 """Display sequences"""
1203 self.__searchObjet("sequence", arg)
1204
1208
1210 """Display triggers"""
1211 self.__searchObjet("trigger", arg)
1212
1216
1218 """Display tablespaces"""
1219 self.__searchObjet("tablespace", arg)
1220
1222 """Display users (aka schema)"""
1223 self.__searchObjet("user", arg)
1224
1226 """Display profiles"""
1227 self.__searchObjet("profile", arg)
1228
1232
1233
1235 """Display last lines of query set"""
1236 self.__checkConnection()
1237 self.__checkArg(arg, "<=1")
1238 self.__animateCursor()
1239 try:
1240 nbLines = int(arg)
1241 if nbLines < self.conf.get("fetchSize"):
1242
1243 fetchSize = self.conf.get("fetchSize")
1244 else:
1245 fetchSize = nbLines
1246 except (ValueError, TypeError):
1247 nbLines = self.conf.get("fetchSize")
1248 fetchSize = nbLines
1249
1250 moreRows = True
1251 result = []
1252 while moreRows:
1253 previousResult = result
1254 (result, moreRows) = self.db.fetchNext(fetchSize)
1255 previousResult.extend(result)
1256 result = previousResult[-nbLines:]
1257 self.__displayTab(result, self.db.getDescription())
1258
1269
1270
1272 """Get pysql parameter"""
1273 if arg in ("all", ""):
1274 result = self.conf.getAll()
1275
1276 for i in xrange(len(result)):
1277 result[i] = [str(result[i][j]) for j in xrange(len(result[i]))]
1278 self.__addToCompleteList([i[0] for i in result], "parameters")
1279 self.__displayTab(result,
1280 [_("Parameter"), _("User defined value"), _("Default value")])
1281 else:
1282 print self.conf.get(arg)
1283
1285 """Set a pysql parameter"""
1286 if arg == "":
1287 self.do_get("all")
1288 else:
1289 try:
1290 (key, value) = arg.split("=")
1291 self.conf.set(key, value)
1292 except ValueError, e:
1293 self.help_set()
1294
1296 """Write configuration to disk"""
1297 self.conf.write()
1298
1299
1301 """Execute a shell command or open a shell"""
1302
1303 if arg == "":
1304 if os.name == "posix":
1305 arg = os.environ["SHELL"]
1306 elif os.name == "nt":
1307 arg = "cmd"
1308 else:
1309 raise PysqlNotImplemented()
1310
1311 exitStatus = os.system(arg)
1312
1313 if exitStatus != 0:
1314 print CYAN + _("Exited with code ") + str(exitStatus) + RESET
1315
1317 """A simple local ls"""
1318 self.__checkArg(arg, "<=1")
1319 if os.name == "posix":
1320 cmd = "ls "
1321 elif os.name == "nt":
1322 cmd = "dir "
1323 else:
1324 raise PysqlNotImplemented()
1325
1326 if len(arg) == 0:
1327 exitStatus = os.system(cmd)
1328 else:
1329 exitStatus = os.system(cmd + arg)
1330
1331 if exitStatus != 0:
1332 print CYAN + "Exited with code " + str(exitStatus) + RESET
1333
1335 """Change local directory"""
1336 self.__checkArg(arg, "<=1")
1337 if arg == "":
1338 if os.name == "posix":
1339 arg = os.environ["HOME"]
1340 elif os.name == "nt":
1341 arg = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
1342 else:
1343 raise PysqlNotImplemented()
1344 try:
1345 os.chdir(arg)
1346 except OSError:
1347 raise PysqlException(_("No such directory"))
1348
1350 """Display current work directory"""
1351 self.__checkArg(arg, "==0")
1352 print os.getcwd()
1353
1354
1355
1357 """Execute an external sql file, similar to sql*plus @"""
1358 self.__checkConnection()
1359 self.__checkArg(arg, "==1")
1360 try:
1361
1362 if os.access(arg, os.R_OK):
1363 fileName = arg
1364 else:
1365 fileName = arg + ".sql"
1366 script = file(fileName, "r")
1367 for line in script.readlines():
1368 line = line.rstrip("\n")
1369 line = self.precmd(line)
1370 self.onecmd(line)
1371 self.postcmd(None, line)
1372 except IOError, e:
1373 raise PysqlException(e)
1374
1375
1377 """Repeat a command"""
1378 self.__checkConnection()
1379 self.__checkArg(arg, ">=1")
1380
1381 interval = arg.split()[0]
1382 try:
1383 interval = int(interval)
1384 arg = " ".join(arg.split()[1:])
1385 except ValueError:
1386
1387 interval = 3
1388 try:
1389 while True:
1390 self.onecmd(arg)
1391 sleep(interval)
1392 except KeyboardInterrupt:
1393
1394
1395
1396 print _("exit watch")
1397 pass
1398
1399
1401 """Dumps sql request to file"""
1402 self.__checkConnection()
1403 self.__checkArg(arg, ">=3")
1404 (fileName, sql) = match("(.+?)\s(.+)", arg).groups()
1405 self.__executeSQL(sql, output="csv", fileName=fileName)
1406
1407
1417
1418
1420 """Show parameters"""
1421 self.__checkConnection()
1422 self.__checkArg(arg, ">=1")
1423 argList = arg.split()
1424 argList[0] = argList[0].lower()
1425 if argList[0] in ("parameter", "parameters"):
1426 self.__checkArg(arg, "<=2")
1427 param = ""
1428 if len(argList) == 2:
1429 param = argList[1]
1430 (header, result) = pysqlfunctions.showParameter(self.db, param)
1431 self.__displayTab(result, header)
1432 elif argList[0] in ("spparameter", "spparameters"):
1433 self.__checkArg(arg, "<=2")
1434 param = ""
1435 if len(argList) == 2:
1436 param = argList[1]
1437 (header, result) = pysqlfunctions.showServerParameter(self.db, param)
1438 self.__displayTab(result, header)
1439 elif argList[0] == "instance":
1440 self.__checkArg(arg, "==1")
1441 print _("Connected to ") + self.db.getDSN()
1442 elif argList[0] == "version":
1443 self.__checkArg(arg, "==1")
1444 print _("Oracle ") + self.db.getVersion()
1445 else:
1446 print RED + _("Invalid argument") + RESET
1447
1448
1450 """ Close current connection and exit pysql"""
1451 return self.__exit()
1452
1453
1455 """online help"""
1456 print _("Usage:")
1457 print "\t" + CYAN + "assmrpt " + _("<table name>") + RESET
1458 print _("Computes block clustering introduced by Oracle ASSM")
1459
1461 """online help"""
1462 print _("Usage:")
1463 print "\t" + CYAN + "bg " + _("[order id]") + RESET
1464 print _("Manages background queries")
1465 print
1466 print _("Sample usages:")
1467 print "\t" + _("To display all background queries:")
1468 print "\t\t" + CYAN + "bg" + RESET
1469 print "\t" + _("To call back a background query:")
1470 print "\t\t" + CYAN + "bg " + _("<id>") + RESET
1471
1473 """online help"""
1474 print _("Usage:")
1475 print "\t" + CYAN + "compare [data] " + _("<user>[/<password>@<SID>][:<table>] <user>[/<password>@<SID>][:<table>]") + RESET
1476 print _("Compares structure or data of two schemas or two tables. Objects could be in two distinct databases.")
1477 print _("By default, only structure is compared.")
1478 print
1479 print _("Sample usages:")
1480 print "\t" + _("To compare two schemas:")
1481 print "\t\t" + CYAN + "compare " + _("user/password@SID user/password@SID") + RESET
1482 print "\t" + _("To compare two tables:")
1483 print "\t\t" + CYAN + "compare " + _("user/password@SID:table user/password@SID:table") + RESET
1484 print "\t" + _("To compare two tables in current schema:")
1485 print "\t\t" + CYAN + "compare " + _("table table") + RESET
1486 print "\t" + _("""To compare table data, use the "data" keyword this way:""")
1487 print "\t\t" + CYAN + "compare data " + _("user/password@SID:table user/password@SID:table") + RESET
1488
1490 """online help"""
1491 print _("Usage:")
1492 print "\t" + CYAN + "conn[ect] " + _("user[/password][@[host[:port]/]SID]") + " [sysdba|sysoper]" + RESET
1493 print _("Connects to Oracle and closes previous connection if any")
1494
1496 """online help"""
1497 print _("Usage:")
1498 print "\t" + CYAN + "count " + _("<table/view name>") + RESET
1499 print _("Counts the number of lines in a table or a view")
1500
1502 """online help"""
1503 print _("Usage:")
1504 print "\t" + CYAN + "csv " + _("<output file> <sql query>") + RESET
1505 print _("Dumps sql query to file")
1506 print
1507 print _("Example:")
1508 print "\t" + CYAN + "csv " + _("out.csv select * from dummy;") + RESET
1509
1513
1515 """online help"""
1516 print _("Usage:")
1517 print "\t" + CYAN + _("ddl <table|view>") + RESET
1518 print _("Prints Oracle object DDL")
1519 print
1520 print _("Example:")
1521 print "\t" + CYAN + _("ddl DUAL") + RESET
1522
1526
1528 """online help"""
1529 print _("Usage:")
1530 print "\t" + CYAN + "disc[connect]" + RESET
1531 print _("Closes current connection if any")
1532
1534 """online help"""
1535 print _("Usage:")
1536 print "\t" + CYAN + "ed[it] " + _("<object name>") + RESET
1537 print _("Edits (view or modify) an object)")
1538 print _("If no arg is provided, edits last SQL statement")
1539
1541 """online help"""
1542 print _("Usage:")
1543 print "\t" + CYAN + "exit" + RESET
1544 print _("Well, it seems rather explicit, isn't it?")
1545
1547 """online help"""
1548 print _("Usage:")
1549 print "\t" + CYAN + "explain " + _("<sql statement>") + RESET
1550 print _("Computes and displays explain plan for the statement")
1551
1555
1557 """online help"""
1558 print _("Usage:")
1559 print "\t" + CYAN + "get " + _("<key>") + RESET
1560 print _("Prints the value of the parameter <key>")
1561 print _("The special key « all » allows to print all parameters")
1562
1564 """online help"""
1565 print _("Usage:")
1566 print "\t" + CYAN + "help " + _("<pysql command>") + RESET
1567 print _("Brings some help like usage and a short description")
1568 print _("about the command and its parameters")
1569
1570 - def help_history(self):
1571 """online help"""
1572 print _("Usage:")
1573 print "\t" + CYAN + "h[istory] " + _("<n>") + RESET
1574 print _("Without any argument, prints the last 20 commands")
1575 print _("If argument is supplied, executes the nth command")
1576
1580
1582 """online help"""
1583 print _("Usage:")
1584 print "\t" + CYAN + "last " + _("<number of lines>") + RESET
1585 print _("Fetches all lines of current result set and display only the last lines")
1586 print _("Default number of lines default to cursor array size")
1587
1589 """online help"""
1590 print _("Usage:")
1591 print "\t" + CYAN + "lcd " + _("<path>") + RESET
1592 print _("Changes working directory")
1593
1595 """online help"""
1596 print _("Usage:")
1597 print "\t" + CYAN + "lib[rary] " + _("<sqlName> <sqlText>") + RESET
1598 print "\t" + _("Handles user custom sql library. Allows user to save and recall sql requests.")
1599 print
1600 print _("Sample usages: ")
1601 print "\t" + _("To see all saved request:")
1602 print "\t\t" + CYAN + "lib" + RESET
1603 print "\t" + _("To save a request as 'employeeNumber':")
1604 print "\t\t" + CYAN + "lib " + _("employeeNumber select count(*) from employee") + RESET
1605 print "\t" + _("recall a saved request:")
1606 print "\t\t" + CYAN + "lib " + _("employeNumber") + RESET
1607 print "\t" + _("To remove the foo request:")
1608 print "\t\t" + CYAN + "lib " + _("foo remove") + RESET
1609
1611 """online help"""
1612 print _("Usage:")
1613 print "\t" + CYAN + "lls " + _("[path/][file]") + RESET
1614 print _("Lists directory contents")
1615
1617 """online help"""
1618 print _("Usage:")
1619 print "\t" + CYAN + "lock" + RESET
1620 print _("Displays the locked objects")
1621
1623 """online help"""
1624 print _("Usage:")
1625 print "\t" + CYAN + "lpwd" + RESET
1626 print _("Prints local directory")
1627
1629 """online help"""
1630 print _("Usage:")
1631 print "\t" + CYAN + "next " + _("<number of lines>") + RESET
1632 print _("Fetches the n next lines of current result set")
1633 print _("Default number of lines default to cursor array size")
1634 print _("Just press enter is equivalent to next without arguments")
1635
1639
1643
1645 """online help"""
1646 print _("Usage:")
1647 print "\t" + CYAN + "@ " + _("<script>") + RESET
1648 print _("Executes a PL/SQL script and displays the output on the standard output")
1649
1653
1657
1659 """online help"""
1660 print _("Usage:")
1661 print "\t" + CYAN + "set " + _("<key>=<value>") + RESET
1662 print _("Sets <value> to the parameter <key>")
1663
1665 """online help"""
1666 print _("Usage:")
1667 print "\t" + CYAN + "! " + _("<command line>") + RESET
1668 print _("Executes a command into the system terminal (depending on your system profile)")
1669 print _("If no commands are given then a subshell is openned")
1670
1672 """online help"""
1673 print _("Usage:")
1674 print "\t" + CYAN + "show instance" + RESET
1675 print _("Displays the database service name (DSN) of the current connection")
1676 print
1677 print "\t" + CYAN + "show version" + RESET
1678 print _("Displays the database server version")
1679 print
1680 print "\t" + CYAN + "show parameter[s] " + _("<partial parameter name>") + RESET
1681 print _("Looks for session parameters with name like the partial name given.")
1682 print _("Wilcard % can be used.")
1683 print _("If none is provided, pysql adds a % at the begining and the end.")
1684 print
1685 print "\t" + CYAN + "show spparameter[s] " + _("<partial parameter name>") + RESET
1686 print _("Looks for server parameters with name like the partial name given.")
1687 print _("These parameters are defined in spfile. Wilcard % can be used.")
1688 print _("If none is provided, pysql adds a % at the begining and the end.")
1689
1691 """online help"""
1692 print _("Usage:")
1693 print "\t" + CYAN + "commit" + RESET
1694 print _("Commits pending transaction")
1695
1697 """online help"""
1698 print _("Usage:")
1699 print "\t" + CYAN + "rollback" + RESET
1700 print _("Cancels pending transaction")
1701
1703 """online help"""
1704 print _("Usage:")
1705 print "\t" + CYAN + "shutdown [abort|immediate|normal]" + RESET
1706 print _("Shuts instance down")
1707
1709 """online help"""
1710 print _("Usage:")
1711 print "\t" + CYAN + "startup [mount]" + RESET
1712 print _("Starts instance up")
1713
1717
1721
1723 """online help"""
1724 print _("Usage:")
1725 print "\t" + CYAN + "time " + _("<sql query>") + RESET
1726 print _("Time request execution time")
1727
1731
1735
1739
1741 """online help"""
1742 print _("Usage:")
1743 print "\t" + CYAN + "watch " + _("<n>") + " " + _("<pysql command or sql order>") + RESET
1744 print _("Repeats the command each n seconds")
1745 print _("If n is ommited, repeat each 3 seconds")
1746
1748 """online help"""
1749 print _("Usage:")
1750 print "\t" + CYAN + "write" + RESET
1751 print _("Writes configuration to disk")
1752 print _("Path is $HOME/.pysql/pysqlrc on Unix, %APPDATA%/pysql/pysqrc on Windows")
1753 print _("This command takes no argument")
1754
1756 """generic online help all object search method"""
1757 print _("Usage:")
1758 print "\t" + CYAN + _("%s <search pattern on %s name>") % (searchObject, searchObject) + RESET
1759 print _("Looks for %s which match the search pattern") % searchObject
1760 print _("Wilcard % and boolean operators (and/or) can be used.")
1761 print _("If a single word and no % is provided, pysql adds a % at the begining and the end")
1762 print _("Ex. : %s FOO or (BAR%% and %%TEST%%)") % searchObject
1763
1764
1765
1766
1768 """Animate cursor to tell user something is really happening
1769 End of animation and output flushing is done automatically in postcmd hook"""
1770 if self.allowAnimatedCursor:
1771 self.waitCursor = WaitCursor()
1772 self.waitCursor.start()
1773
1775 """Adds wordList the completion list "theme"
1776 @param wordList: list of item to completion
1777 @param theme: string theme
1778 @return: None
1779 """
1780 if not self.conf.completeLists.has_key(theme):
1781
1782 self.conf.completeLists[theme] = []
1783
1784 for word in [unicode(j).upper() for j in wordList]:
1785 if word not in self.conf.completeLists[theme]:
1786 self.conf.completeLists[theme].append(word)
1787
1788 limit = self.conf.get("completionListSize")
1789 self.conf.completeLists[theme] = self.conf.completeLists[theme][-limit:]
1790
1792 """Returns list of item matching text for lists of theme
1793 @param text: word to match for completion
1794 @type text: string
1795 @param themes: list of theme of completion to user
1796 @type themes: list of string
1797 @param prefix: text prefix that should be add to completed text
1798 @return:list of string"""
1799 completeList = []
1800 for theme in themes:
1801 try:
1802 completeList += self.conf.completeLists[theme]
1803 except KeyError:
1804
1805 pass
1806 return [prefix + i for i in completeList if i.startswith(text.upper())]
1807
1808 - def __connect(self, connectString, mode=""):
1809 """Calls the PysqlDb class to connect to Oracle"""
1810
1811 if connectString == "/" and mode == "sysdba":
1812 self.db = PysqlDb("/", "sysdba")
1813 self.__setPrompt()
1814 return
1815
1816 count = connectString.count("@")
1817 if count == 0:
1818 sid = os.environ["ORACLE_SID"]
1819 elif count == 1:
1820 (connectString, sid) = connectString.split("@")
1821 else:
1822 raise PysqlException(_("Invalid connection string"))
1823
1824 count = connectString.count("/")
1825 if count == 0:
1826 user = connectString
1827 try:
1828 passwd = getpass()
1829 except (Exception):
1830 raise PysqlException(_("Invalid connection string"))
1831 elif count == 1:
1832 (user, passwd) = connectString.split("/")
1833 else:
1834 raise PysqlException(_("Invalid connection string"))
1835
1836 connectString = user + "/" + passwd + "@" + sid
1837 self.db = PysqlDb(connectString, mode)
1838 self.__setPrompt()
1839
1841 """Disconnects from Oracle and update prompt"""
1842 if self.db:
1843 self.db.close()
1844 self.db = None
1845 self.__setPrompt()
1846
1847 - def __setPrompt(self, blank=False, multiline=False, finishedQuery=False):
1848 """Sets the prompt according to the connexion state
1849 @param blank: if true, no prompt is issue (default is False)
1850 @param finishedQuery: if true mark prompt with a * to notify a query is finished
1851 @type blank: bool
1852 @type finishedQuery: bool"""
1853
1854 codec = self.conf.getCodec()
1855 if blank or not self.showPrompt:
1856 prompt = ""
1857 elif multiline:
1858 prompt = ""
1859 else:
1860 if self.db is None:
1861 prompt = self.notConnectedPrompt
1862
1863 setTitle(_("Pysql - Not connected"), codec)
1864 else:
1865 if self.db.getDSN() == "None":
1866 prompt = self.db.getConnectString() + " "
1867 else:
1868 prompt = self.db.getUsername() + "@" + self.db.getDSN() + " "
1869 if finishedQuery:
1870 prompt += "* "
1871 setTitle("Pysql - %s" % prompt, codec)
1872 self.prompt = prompt.encode(codec, "replace")
1873
1875 """Searches Oracle object"""
1876 self.__checkConnection()
1877
1878 try:
1879 (objectOwner, objectName) = objectName.split(".")
1880 except ValueError:
1881 objectOwner = self.db.getUsername()
1882 objectOwner = objectOwner.upper()
1883
1884 if objectName == "":
1885 objectName = "%"
1886 if self.conf.get("case_sensitive") == "no":
1887 objectName = objectName.upper()
1888 result = pysqlfunctions.searchObject(self.db, objectType, objectName, objectOwner)
1889 for owner in result.keys():
1890 print GREEN + "***** " + owner + " *****" + RESET
1891 self.__addToCompleteList(result[owner], objectType)
1892 self.__displayCol(result[owner])
1893
1895 """Displays on column the list of strings"""
1896
1897 termWidth = self.conf.get("termWidth")
1898 if termWidth == "auto":
1899 termWidth = getTermWidth()
1900
1901 listOfString = [i.encode(self.conf.getCodec(), "replace") for i in listOfString]
1902 self.columnize(listOfString, displaywidth=termWidth)
1903
1905 """Displays in tabular the array using correct width for each column"""
1906 termWidth = self.conf.get("termWidth")
1907 if termWidth == "auto":
1908 termWidth = getTermWidth()
1909 widthMin = int(self.conf.get("widthMin"))
1910 transpose = (self.conf.get("transpose") == "yes")
1911 colsep = self.conf.get("colsep")
1912 if colsep == "space":
1913 colsep = " "
1914
1915
1916 if header and self.tty:
1917
1918 shrink = (self.conf.get("shrink") == "yes")
1919 else:
1920
1921 shrink = False
1922
1923 nbLine = len(array)
1924 if len(array) == 0:
1925 print CYAN + _("(no result)") + RESET
1926 return
1927 nbColumn = len(array[0])
1928
1929 if header:
1930
1931 array.insert(0, header)
1932 nbLine += 1
1933
1934 if transpose:
1935
1936 array = [[array[i][j] for i in range(len(array))] for j in range(nbColumn)]
1937
1938 nbLine = nbColumn
1939 nbColumn = len(array[0])
1940
1941
1942 for i in xrange(nbLine):
1943 for j in xrange(nbColumn):
1944 if array[i][j] is None:
1945
1946 array[i][j] = ""
1947
1948
1949 width = [max([itemLength(i[j]) for i in array]) for j in range(nbColumn)]
1950 shrinked = False
1951 widthMax = max(width)
1952 if shrink:
1953 while sum(width) + nbColumn >= termWidth and widthMax > widthMin:
1954
1955
1956 shrinked = True
1957 widthMax = max(width)
1958 width[width.index(widthMax)] = widthMax - 1
1959
1960
1961 if header and not transpose:
1962 array.insert(1, ["-"*width[i] for i in range(nbColumn)])
1963
1964
1965 coloredLines = 0
1966 for line in array:
1967 if header and coloredLines < 2 and not transpose:
1968
1969 sys.stdout.write(GREY + BOLD)
1970 coloredLines += 1
1971 for i in range(nbColumn):
1972 if header and i == 0 and transpose:
1973
1974 sys.stdout.write(GREY + BOLD)
1975
1976
1977 if isinstance(line[i], (int, long, float)):
1978 sys.stdout.write(str(line[i])[:width[i]].rjust(width[i]))
1979 else:
1980 sys.stdout.write(line[i][:width[i]].ljust(width[i]).replace('\r', ' '))
1981 if header and i == 0 and transpose:
1982 print RESET,
1983 sys.stdout.write(colsep)
1984 print RESET
1985 if shrinked:
1986
1987 print CYAN + _("(some columns have been shrinked to fit your terminal size)") + RESET
1988
1990 """Raises an exception is there's no connection defined
1991 The test is light (db object defined), no real connection test
1992 is done."""
1993 if self.db is None:
1994 raise PysqlException(_("Not connected to Oracle"))
1995
1997 """Checks if arg respect argTest else raise a PysqlException
1998 @param arg: argument to check. Blank is the arg separator
1999 @type arg: str or list of str
2000 @param argTest: test with syntaxe like: ">2", "==1", "<=3"
2001 @type argTest: str
2002 @return: None
2003 """
2004
2005 if match("=\d+", argTest):
2006
2007 argTest = "=" + argTest
2008 if isinstance(arg, basestring):
2009 arg = arg.split()
2010 try:
2011 if not eval(unicode(len(arg)) + argTest):
2012 raise PysqlException(_("Invalid argument. Use help <command name> for usage"))
2013 except SyntaxError, e:
2014 raise PysqlException(_("Invalid syntax for argument checking"))
2015
2016 - def __executeSQL(self, sql, output="tty", fileName="pysql.csv"):
2017 """Executes SQL request
2018 @param sql: SQL request to executed
2019 @type sql: str
2020 @param output: output type. Only affect select queries. Null means all result are sent to paradise
2021 @type output: str (screen, csv, xml or null)
2022 @param fileName: name of the file for csv and xml extract
2023 @type fileName: str"""
2024
2025 self.__checkConnection()
2026 if len(sql) < 2:
2027 raise PysqlException(_("SQL command is too short"))
2028
2029 self.__animateCursor()
2030
2031
2032 self.lastStatement = sql
2033
2034
2035 if sql[-1] == "&":
2036 sql = sql.rstrip("&")
2037 query = BgQuery(self.db.getConnectString(), sql, self.exceptions)
2038 query.start()
2039 self.bgQueries.append(query)
2040 print GREEN + _("Background query launched") + RESET
2041 return
2042
2043
2044 keyword = sql.upper().split()[0]
2045 if keyword.startswith("SELECT"):
2046 if output == "tty":
2047 (result, moreRows) = self.db.execute(sql)
2048 self.__toScreen(result, moreRows)
2049 elif output == "notty":
2050 (result, moreRows) = self.db.execute(sql, fetch=False)
2051 self.__toScreen(result, False)
2052 elif output == "csv":
2053 (result, moreRows) = self.db.execute(sql, fetch=False)
2054 self.__toCsv(result, fileName)
2055 print GREEN + _("(Completed)") + RESET
2056 elif output == "xml":
2057 raise PysqlNotImplemented()
2058 elif output == "null":
2059 self.db.execute(sql, fetch=False)
2060 for i in self.db.getCursor().fetchmany():
2061 pass
2062 else:
2063 raise PysqlException(_("Unknown output type!"))
2064 elif keyword.startswith("INSERT"):
2065 lines = self.db.execute(sql)
2066 print GREEN + unicode(lines) + _(" line(s) inserted") + RESET
2067 elif keyword.startswith("UPDATE"):
2068 lines = self.db.execute(sql)
2069 print GREEN + unicode(lines) + _(" line(s) updated") + RESET
2070 elif keyword.startswith("DELETE"):
2071 lines = self.db.execute(sql)
2072 print GREEN + unicode(lines) + _(" line(s) deleted") + RESET
2073 elif (keyword.startswith("DROP")
2074 or keyword.startswith("CREATE")
2075 or keyword.startswith("TRUNCATE")
2076 or keyword.startswith("ALTER")
2077 or keyword.startswith("ANALYZE")
2078 or keyword.startswith("BEGIN")
2079 or keyword.startswith("DECLARE")
2080 or keyword.startswith("COMMENT")
2081 or keyword.startswith("EXECUTE")
2082 or keyword.startswith("GRANT")
2083 or keyword.startswith("REVOKE")):
2084 self.db.execute(sql)
2085 print GREEN + _("Statement executed") + RESET
2086 result = self.db.getServerOuput()
2087
2088 for line in result:
2089 print line
2090 else:
2091 print RED + BOLD + _("""Unknown command or sql order. Type "help" for help""") + RESET
2092
2093 - def __toScreen(self, result, moreRows, header=True):
2094 """Displays first part of fetch on screen
2095 @param result: array of tabular data
2096 @type result: list of list of str
2097 @param moreRows: indicates if there's more data to fetching
2098 @type moreRows: bool
2099 @param header: indicates if header must be displayed or not
2100 @type header: bool
2101 """
2102 if result:
2103 if header:
2104 self.__displayTab(result, self.db.getDescription())
2105 else:
2106 self.__displayTab(result)
2107 else:
2108 print CYAN + _("(no result)") + RESET
2109
2110 if moreRows:
2111 self.fetching = True
2112 print CYAN + _("(press enter to see next results)") + RESET
2113 else:
2114 self.fetching = False
2115
2116 - def __toCsv(self, result, fileName, header=True):
2117 """Writes query result to a file"""
2118 try:
2119 fileHandle = file(fileName, "w")
2120 csv_writer = csv.writer(fileHandle, dialect="excel")
2121 if header:
2122 csv_writer.writerow(self.db.getDescription())
2123 for line in result:
2124 csv_writer.writerow(line)
2125 except Exception, e:
2126 raise PysqlException(e)
2127 fileHandle.close()
2128
2130 """ Fetches next result of current cursor"""
2131 (result, moreRows) = self.db.fetchNext(nbLines)
2132 self.__toScreen(result, moreRows)
2133
2135 """ Closes current connection and exits pysql"""
2136 if len(self.exceptions) > 0:
2137 print CYAN + "******* " + _("Error sum up") + " *******" + RESET
2138 errors = [(e.getTimeStamp(), e.msg.replace("\n", " "), e.oraCode) for e in self.exceptions]
2139 self.__displayTab(errors, (_("Date"), _("Error message"), _("Oracle error Code")))
2140
2141 if self.showBanner:
2142 print CYAN + "\n\n" + _("Bye !") + "\n" + RESET
2143
2144 rc = 0
2145
2146 try:
2147 self.conf.writeCache()
2148 except PysqlException, e:
2149 print e
2150 rc = 1
2151
2152 try:
2153 self.conf.writeHistory()
2154 except PysqlException, e:
2155 print e
2156 rc = 1
2157
2158 try:
2159 self.conf.writeSqlLibrary()
2160 except PysqlException, e:
2161 print e
2162 rc = 1
2163 try:
2164 self.__disconnect()
2165 except PysqlException, e:
2166 print e
2167 rc = 1
2168
2169 self.rc = rc
2170 return True
2171
2173 """ Prompts user and asks him to choose a snapshot id"""
2174 if numDays == 0:
2175 try:
2176 answer = raw_input(CYAN + _("Specify the number of days of snapshots to choose from: ") + RESET)
2177 numDays = int(answer)
2178 except (SyntaxError, ValueError), e:
2179 raise PysqlException(_("Invalid number of days"))
2180 if numDays <= 0:
2181 raise PysqlException(_("Invalid number of days"))
2182 result = pysqlaudit.listSnapshotId(self.db, numDays)
2183 self.__toScreen(result, False)
2184 try:
2185 answer = raw_input(CYAN + _("Enter %s snapshot identifier: ") % text + RESET)
2186 return (numDays, str(int(answer)))
2187 except (SyntaxError, ValueError), e:
2188 raise PysqlException(_("Invalid snaphsot id"))
2189
2190
2191 complete_conn = complete_connect
2192 complete_lib = complete_library
2193
2194
2195 aliases = {
2196 "addm" : "addmrpt",
2197 "awr" : "awrrpt",
2198 "conn" : "connect",
2199 "dep" : "dependencies",
2200 "desc" : "describe",
2201 "disc" : "disconnect",
2202 "du" : "diskusage",
2203 "ed" : "edit",
2204 "exec" : "execute",
2205 "h" : "history",
2206 "lib" : "library",
2207 "start": "script",
2208 "q" : "exit",
2209 "quit" : "exit"
2210 }
2211