Source Code Cross Referenced for SqlFile.java in  » Database-DBMS » hsql » org » hsqldb » util » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Database DBMS » hsql » org.hsqldb.util 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /* Copyright (c) 2001-2007, The HSQL Development Group
0002:         * All rights reserved.
0003:         *
0004:         * Redistribution and use in source and binary forms, with or without
0005:         * modification, are permitted provided that the following conditions are met:
0006:         *
0007:         * Redistributions of source code must retain the above copyright notice, this
0008:         * list of conditions and the following disclaimer.
0009:         *
0010:         * Redistributions in binary form must reproduce the above copyright notice,
0011:         * this list of conditions and the following disclaimer in the documentation
0012:         * and/or other materials provided with the distribution.
0013:         *
0014:         * Neither the name of the HSQL Development Group nor the names of its
0015:         * contributors may be used to endorse or promote products derived from this
0016:         * software without specific prior written permission.
0017:         *
0018:         * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019:         * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0020:         * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0021:         * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
0022:         * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023:         * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024:         * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0025:         * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0026:         * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0027:         * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0028:         * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029:         */
0030:
0031:        package org.hsqldb.util;
0032:
0033:        import java.io.BufferedReader;
0034:        import java.io.ByteArrayOutputStream;
0035:        import java.io.File;
0036:        import java.io.FileInputStream;
0037:        import java.io.FileNotFoundException;
0038:        import java.io.FileOutputStream;
0039:        import java.io.IOException;
0040:        import java.io.InputStream;
0041:        import java.io.InputStreamReader;
0042:        import java.io.OutputStreamWriter;
0043:        import java.io.PrintStream;
0044:        import java.io.PrintWriter;
0045:        import java.io.UnsupportedEncodingException;
0046:        import java.sql.Connection;
0047:        import java.sql.DatabaseMetaData;
0048:        import java.sql.PreparedStatement;
0049:        import java.sql.ResultSet;
0050:        import java.sql.ResultSetMetaData;
0051:        import java.sql.SQLException;
0052:        import java.sql.Statement;
0053:        import java.util.ArrayList;
0054:        import java.util.HashMap;
0055:        import java.util.Iterator;
0056:        import java.util.List;
0057:        import java.util.Set;
0058:        import java.util.HashSet;
0059:        import java.util.Map;
0060:        import java.util.SortedMap;
0061:        import java.util.TreeMap;
0062:        import java.util.regex.Matcher;
0063:        import java.util.regex.Pattern;
0064:        import java.util.regex.PatternSyntaxException;
0065:
0066:        /* $Id: SqlFile.java,v 1.169 2007/08/02 18:15:32 unsaved Exp $ */
0067:
0068:        /**
0069:         * Encapsulation of a sql text file like 'myscript.sql'.
0070:         * The ultimate goal is to run the execute() method to feed the SQL
0071:         * commands within the file to a jdbc connection.
0072:         *
0073:         * Some implementation comments and variable names use keywords based
0074:         * on the following definitions.  <UL>
0075:         * <LI> COMMAND = Statement || SpecialCommand || BufferCommand
0076:         * Statement = SQL statement like "SQL Statement;"
0077:         * SpecialCommand =  Special Command like "\x arg..."
0078:         * BufferCommand =  Editing/buffer command like ":s/this/that/"
0079:         *
0080:         * When entering SQL statements, you are always "appending" to the
0081:         * "immediate" command (not the "buffer", which is a different thing).
0082:         * All you can do to the immediate command is append new lines to it,
0083:         * execute it, or save it to buffer.
0084:         * When you are entering a buffer edit command like ":s/this/that/",
0085:         * your immediate command is the buffer-edit-command.  The buffer
0086:         * is the command string that you are editing.
0087:         * The buffer usually contains either an exact copy of the last command
0088:         * executed or sent to buffer by entering a blank line,
0089:         * but BUFFER commands can change the contents of the buffer.
0090:         *
0091:         * In general, the special commands mirror those of Postgresql's psql,
0092:         * but SqlFile handles command editing much different from Postgresql
0093:         * because of Java's lack of support for raw tty I/O.
0094:         * The \p special command, in particular, is very different from psql's.
0095:         *
0096:         * Buffer commands are unique to SQLFile.  The ":" commands allow
0097:         * you to edit the buffer and to execute the buffer.
0098:         *
0099:         * \d commands are very poorly supported for Mysql because
0100:         * (a) Mysql lacks most of the most basic JDBC support elements, and
0101:         * the most basic role and schema features, and
0102:         * (b) to access the Mysql data dictionay, one must change the database
0103:         * instance (to do that would require work to restore the original state
0104:         * and could have disastrous effects upon transactions).
0105:         *
0106:         * To make changes to this class less destructive to external callers,
0107:         * the input parameters should be moved to setters (probably JavaBean
0108:         * setters would be best) instead of constructor args and System
0109:         * Properties.
0110:         *
0111:         * The process*() methods, other than processBuffHist() ALWAYS execute
0112:         * on "buffer", and expect it to contain the method specific prefix
0113:         * (if any).
0114:         *
0115:         * @version $Revision: 1.169 $
0116:         * @author Blaine Simpson unsaved@users
0117:         */
0118:
0119:        public class SqlFile {
0120:            private static final int DEFAULT_HISTORY_SIZE = 40;
0121:            private File file;
0122:            private boolean interactive;
0123:            private String primaryPrompt = "sql> ";
0124:            private String rawPrompt = null;
0125:            private String contPrompt = "  +> ";
0126:            private Connection curConn = null;
0127:            private boolean htmlMode = false;
0128:            private Map userVars; // Always a non-null map set in cons.
0129:            private List history = null;
0130:            private int rawMode = RAW_FALSE;
0131:            private String nullRepToken = null;
0132:            private String dsvTargetFile = null;
0133:            private String dsvTargetTable = null;
0134:            private String dsvConstCols = null;
0135:            private String dsvRejectFile = null;
0136:            private String dsvRejectReport = null;
0137:            public static String LS = System.getProperty("line.separator");
0138:            private int maxHistoryLength = 1;
0139:            private SqltoolRB rb = null;
0140:            private String magicPrefix = null;
0141:            // For append editing, this is automatically prefixed to what the
0142:            // user enters.
0143:
0144:            private static final int RAW_FALSE = 0; // Raw mode off
0145:            private static final int RAW_EMPTY = 1; // Raw mode on, but no raw input yet
0146:            private static final int RAW_DATA = 2; // Raw mode on and we have input
0147:
0148:            /**
0149:             * N.b. javax.util.regex Optional capture groups (...)? are completely
0150:             * unpredictable wrt whether you get a null capture group vs. no capture.
0151:             * Must always check count!
0152:             */
0153:            private static Pattern specialPattern = Pattern
0154:                    .compile("\\s*\\\\(\\S+)(?:\\s+(.*\\S))?\\s*");
0155:            private static Pattern plPattern = Pattern
0156:                    .compile("\\s*\\*\\s*(.*\\S)?\\s*");
0157:            private static Pattern foreachPattern = Pattern
0158:                    .compile("\\s*\\*\\s*foreach\\s+(\\S+)\\s*\\(([^)]*)\\)\\s*");
0159:            private static Pattern ifwhilePattern = Pattern
0160:                    .compile("\\s*\\*\\s*\\S+\\s*\\(([^)]*)\\)\\s*");
0161:            private static Pattern varsetPattern = Pattern
0162:                    .compile("\\s*\\*\\s*(\\S+)\\s*([=_~])\\s*(?:(.*\\S)\\s*)?");
0163:            private static Pattern substitutionPattern = Pattern
0164:                    .compile("(\\S)(.+?)\\1(.*?)\\1(.+)?\\s*");
0165:            // Note that this pattern does not include the leading ":s".
0166:            private static Pattern slashHistoryPattern = Pattern
0167:                    .compile("\\s*/([^/]+)/\\s*(\\S.*)?");
0168:            private static Pattern historyPattern = Pattern
0169:                    .compile("\\s*(-?\\d+)?\\s*(\\S.*)?");
0170:            // Note that this pattern does not include the leading ":".
0171:            private static Pattern wincmdPattern = null;
0172:
0173:            static {
0174:                if (System.getProperty("os.name").startsWith("Windows")) {
0175:                    wincmdPattern = Pattern.compile("([^\"]+)?(\"[^\"]*\")?");
0176:                }
0177:            }
0178:
0179:            // This can throw a runtime exception, but since the pattern
0180:            // Strings are constant, one test run of the program will tell
0181:            // if the patterns are good.
0182:
0183:            /**
0184:             * Encapsulate updating local variables which depend upon PL variables.
0185:             *
0186:             * Right now this is called whenever the user variable map is changed.
0187:             * It would be more efficient to do it JIT by keeping track of when
0188:             * the vars may be "dirty" by a variable map change, and having all
0189:             * methods that use the settings call a conditional updater, but that
0190:             * is less reliable since there is no way to guarantee that the vars
0191:             * are not used without checking.
0192:             */
0193:            private void updateUserSettings() {
0194:                dsvSkipPrefix = SqlFile.convertEscapes((String) userVars
0195:                        .get("*DSV_SKIP_PREFIX"));
0196:                if (dsvSkipPrefix == null) {
0197:                    dsvSkipPrefix = DEFAULT_SKIP_PREFIX;
0198:                }
0199:                dsvSkipCols = (String) userVars.get("*DSV_SKIP_COLS");
0200:                dsvColDelim = SqlFile.convertEscapes((String) userVars
0201:                        .get("*DSV_COL_DELIM"));
0202:                if (dsvColDelim == null) {
0203:                    dsvColDelim = SqlFile.convertEscapes((String) userVars
0204:                            .get("*CSV_COL_DELIM"));
0205:                }
0206:                if (dsvColDelim == null) {
0207:                    dsvColDelim = DEFAULT_COL_DELIM;
0208:                }
0209:
0210:                dsvRowDelim = SqlFile.convertEscapes((String) userVars
0211:                        .get("*DSV_ROW_DELIM"));
0212:                if (dsvRowDelim == null) {
0213:                    dsvRowDelim = SqlFile.convertEscapes((String) userVars
0214:                            .get("*CSV_ROW_DELIM"));
0215:                }
0216:                if (dsvRowDelim == null) {
0217:                    dsvRowDelim = DEFAULT_ROW_DELIM;
0218:                }
0219:
0220:                dsvTargetFile = (String) userVars.get("*DSV_TARGET_FILE");
0221:                if (dsvTargetFile == null) {
0222:                    dsvTargetFile = (String) userVars.get("*CSV_FILEPATH");
0223:                }
0224:                dsvTargetTable = (String) userVars.get("*DSV_TARGET_TABLE");
0225:                if (dsvTargetTable == null) {
0226:                    dsvTargetTable = (String) userVars.get("*CSV_TABLENAME");
0227:                    // This just for legacy variable name.
0228:                }
0229:
0230:                dsvConstCols = (String) userVars.get("*DSV_CONST_COLS");
0231:                dsvRejectFile = (String) userVars.get("*DSV_REJECT_FILE");
0232:                dsvRejectReport = (String) userVars.get("*DSV_REJECT_REPORT");
0233:
0234:                nullRepToken = (String) userVars.get("*NULL_REP_TOKEN");
0235:                if (nullRepToken == null) {
0236:                    nullRepToken = (String) userVars.get("*CSV_NULL_REP");
0237:                }
0238:                if (nullRepToken == null) {
0239:                    nullRepToken = DEFAULT_NULL_REP;
0240:                }
0241:            }
0242:
0243:            /**
0244:             * Private class to "share" a variable among a family of SqlFile
0245:             * instances.
0246:             */
0247:            private static class BooleanBucket {
0248:                BooleanBucket() {
0249:                }
0250:
0251:                private boolean bPriv = false;
0252:
0253:                public void set(boolean bIn) {
0254:                    bPriv = bIn;
0255:                }
0256:
0257:                public boolean get() {
0258:                    return bPriv;
0259:                }
0260:            }
0261:
0262:            BooleanBucket possiblyUncommitteds = new BooleanBucket();
0263:            // This is an imperfect solution since when user runs SQL they could
0264:            // be running DDL or a commit or rollback statement.  All we know is,
0265:            // they MAY run some DML that needs to be committed.
0266:
0267:            private static final String DIVIDER = "-----------------------------------------------------------------"
0268:                    + "-----------------------------------------------------------------";
0269:            // Needs to be at least as wide as the widest field or header displayed.
0270:            private static final String SPACES = "                                                                 "
0271:                    + "                                                                 ";
0272:            // Needs to be at least as wide as the widest field or header displayed.
0273:            private static String revnum = null;
0274:
0275:            static {
0276:                revnum = "354";
0277:            }
0278:
0279:            private String DSV_OPTIONS_TEXT = null;
0280:            private String D_OPTIONS_TEXT = null;
0281:            private String RAW_LEADIN_MSG = null;
0282:
0283:            /**
0284:             * Interpret lines of input file as SQL Statements, Comments,
0285:             * Special Commands, and Buffer Commands.
0286:             * Most Special Commands and many Buffer commands are only for
0287:             * interactive use.
0288:             *
0289:             * @param inFile  inFile of null means to read stdin.
0290:             * @param inInteractive  If true, prompts are printed, the interactive
0291:             *                       Special commands are enabled, and
0292:             *                       continueOnError defaults to true.
0293:             * @throws IOException  If can't open specified SQL file.
0294:             */
0295:            public SqlFile(File inFile, boolean inInteractive, Map inVars)
0296:                    throws IOException {
0297:                // Set up ResourceBundle first, so that any other errors may be
0298:                // reported with localized messages.
0299:                try {
0300:                    rb = new SqltoolRB();
0301:                    rb.validate();
0302:                    rb
0303:                            .setMissingPosValueBehavior(ValidatingResourceBundle.NOOP_BEHAVIOR);
0304:                    rb
0305:                            .setMissingPropertyBehavior(ValidatingResourceBundle.NOOP_BEHAVIOR);
0306:                } catch (RuntimeException re) {
0307:                    System.err.println("Failed to initialize resource bundle");
0308:                    throw re;
0309:                }
0310:                rawPrompt = rb.getString(SqltoolRB.RAWMODE_PROMPT) + "> ";
0311:                DSV_OPTIONS_TEXT = rb.getString(SqltoolRB.DSV_OPTIONS);
0312:                D_OPTIONS_TEXT = rb.getString(SqltoolRB.D_OPTIONS);
0313:                RAW_LEADIN_MSG = rb.getString(SqltoolRB.RAW_LEADIN);
0314:                DSV_X_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_X_SYNTAX);
0315:                DSV_M_SYNTAX_MSG = rb.getString(SqltoolRB.DSV_M_SYNTAX);
0316:                nobufferYetString = rb.getString(SqltoolRB.NOBUFFER_YET);
0317:
0318:                file = inFile;
0319:                interactive = inInteractive;
0320:                userVars = inVars;
0321:                if (userVars == null) {
0322:                    userVars = new HashMap();
0323:                }
0324:                updateUserSettings();
0325:
0326:                if (file != null && !file.canRead()) {
0327:                    throw new IOException(rb.getString(
0328:                            SqltoolRB.SQLFILE_READFAIL, file.toString()));
0329:                }
0330:                if (interactive) {
0331:                    history = new ArrayList();
0332:                    String histLenString = System
0333:                            .getProperty("sqltool.historyLength");
0334:                    if (histLenString != null)
0335:                        try {
0336:                            maxHistoryLength = Integer.parseInt(histLenString);
0337:                        } catch (Exception e) {
0338:                        }
0339:                    else {
0340:                        maxHistoryLength = DEFAULT_HISTORY_SIZE;
0341:                    }
0342:                }
0343:            }
0344:
0345:            /**
0346:             * Constructor for reading stdin instead of a file for commands.
0347:             *
0348:             * @see #SqlFile(File,boolean)
0349:             */
0350:            public SqlFile(boolean inInteractive, Map inVars)
0351:                    throws IOException {
0352:                this (null, inInteractive, inVars);
0353:            }
0354:
0355:            /**
0356:             * Process all the commands on stdin.
0357:             *
0358:             * @param conn The JDBC connection to use for SQL Commands.
0359:             * @see #execute(Connection,PrintStream,PrintStream,boolean)
0360:             */
0361:            public void execute(Connection conn, Boolean coeOverride)
0362:                    throws SqlToolError, SQLException {
0363:                execute(conn, System.out, System.err, coeOverride);
0364:            }
0365:
0366:            /**
0367:             * Process all the commands on stdin.
0368:             *
0369:             * @param conn The JDBC connection to use for SQL Commands.
0370:             * @see #execute(Connection,PrintStream,PrintStream,boolean)
0371:             */
0372:            public void execute(Connection conn, boolean coeOverride)
0373:                    throws SqlToolError, SQLException {
0374:                execute(conn, System.out, System.err, new Boolean(coeOverride));
0375:            }
0376:
0377:            // So we can tell how to handle quit and break commands.
0378:            public boolean recursed = false;
0379:            private String lastSqlStatement = null;
0380:            private int curLinenum = -1;
0381:            private PrintStream psStd = null;
0382:            private PrintStream psErr = null;
0383:            private PrintWriter pwQuery = null;
0384:            private PrintWriter pwDsv = null;
0385:            StringBuffer immCmdSB = new StringBuffer();
0386:            private boolean continueOnError = false;
0387:            /*
0388:             * This is reset upon each execute() invocation (to true if interactive,
0389:             * false otherwise).
0390:             */
0391:            private static final String DEFAULT_CHARSET = null;
0392:            // Change to Charset.defaultCharset().name(); once we can use Java 1.5!
0393:            private BufferedReader br = null;
0394:            private String charset = null;
0395:            private String buffer = null;
0396:
0397:            /**
0398:             * Process all the commands in the file (or stdin) associated with
0399:             * "this" object.
0400:             * Run SQL in the file through the given database connection.
0401:             *
0402:             * This is synchronized so that I can use object variables to keep
0403:             * track of current line number, command, connection, i/o streams, etc.
0404:             *
0405:             * Sets encoding character set to that specified with System Property
0406:             * 'sqlfile.charset'.  Defaults to "US-ASCII".
0407:             *
0408:             * @param conn The JDBC connection to use for SQL Commands.
0409:             * @throws SQLExceptions thrown by JDBC driver.
0410:             *                       Only possible if in "\c false" mode.
0411:             * @throws SqlToolError  all other errors.
0412:             *               This includes including QuitNow, BreakException,
0413:             *               ContinueException for recursive calls only.
0414:             */
0415:            public synchronized void execute(Connection conn,
0416:                    PrintStream stdIn, PrintStream errIn, Boolean coeOverride)
0417:                    throws SqlToolError, SQLException {
0418:                psStd = stdIn;
0419:                psErr = errIn;
0420:                curConn = conn;
0421:                curLinenum = -1;
0422:
0423:                String inputLine;
0424:                String trimmedInput;
0425:                String deTerminated;
0426:                boolean inComment = false; // Gobbling up a comment
0427:                int postCommentIndex;
0428:                boolean rollbackUncoms = true;
0429:
0430:                continueOnError = (coeOverride == null) ? interactive
0431:                        : coeOverride.booleanValue();
0432:
0433:                if (userVars.size() > 0) {
0434:                    plMode = true;
0435:                }
0436:
0437:                String specifiedCharSet = System.getProperty("sqlfile.charset");
0438:
0439:                charset = ((specifiedCharSet == null) ? DEFAULT_CHARSET
0440:                        : specifiedCharSet);
0441:
0442:                try {
0443:                    br = new BufferedReader(
0444:                            (charset == null) ? (new InputStreamReader(
0445:                                    (file == null) ? System.in
0446:                                            : (new FileInputStream(file))))
0447:                                    : (new InputStreamReader(
0448:                                            ((file == null) ? System.in
0449:                                                    : (new FileInputStream(file))),
0450:                                            charset)));
0451:                    // Replace with just "(new FileInputStream(file), charset)"
0452:                    // once use defaultCharset from Java 1.5 in charset init. above.
0453:                    curLinenum = 0;
0454:
0455:                    if (interactive) {
0456:                        stdprintln(rb.getString(SqltoolRB.SQLFILE_BANNER,
0457:                                revnum));
0458:                    }
0459:
0460:                    while (true) {
0461:                        if (interactive && magicPrefix == null) {
0462:                            psStd
0463:                                    .print((immCmdSB.length() > 0 || rawMode == RAW_DATA) ? contPrompt
0464:                                            : ((rawMode == RAW_FALSE) ? primaryPrompt
0465:                                                    : rawPrompt));
0466:                        }
0467:
0468:                        inputLine = br.readLine();
0469:                        if (magicPrefix != null) {
0470:                            inputLine = magicPrefix + inputLine;
0471:                            magicPrefix = null;
0472:                        }
0473:
0474:                        if (inputLine == null) {
0475:                            /*
0476:                             * This is because interactive EOD on some OSes doesn't
0477:                             * send a line-break, resulting in no linebreak at all
0478:                             * after the SqlFile prompt or whatever happens to be
0479:                             * on their screen.
0480:                             */
0481:                            if (interactive) {
0482:                                psStd.println();
0483:                            }
0484:
0485:                            break;
0486:                        }
0487:
0488:                        curLinenum++;
0489:
0490:                        if (inComment) {
0491:                            postCommentIndex = inputLine.indexOf("*/") + 2;
0492:
0493:                            if (postCommentIndex > 1) {
0494:                                // I see no reason to leave comments in history.
0495:                                inputLine = inputLine
0496:                                        .substring(postCommentIndex);
0497:
0498:                                // Empty the buffer.  The non-comment remainder of
0499:                                // this line is either the beginning of a new SQL
0500:                                // or Special command, or an empty line.
0501:                                immCmdSB.setLength(0);
0502:
0503:                                inComment = false;
0504:                            } else {
0505:                                // Just completely ignore the input line.
0506:                                continue;
0507:                            }
0508:                        }
0509:
0510:                        trimmedInput = inputLine.trim();
0511:
0512:                        try {
0513:                            if (rawMode != RAW_FALSE) {
0514:                                boolean rawExecute = inputLine.equals(".;");
0515:                                if (rawExecute || inputLine.equals(":.")) {
0516:                                    if (rawMode == RAW_EMPTY) {
0517:                                        rawMode = RAW_FALSE;
0518:                                        throw new SqlToolError(rb
0519:                                                .getString(SqltoolRB.RAW_EMPTY));
0520:                                    }
0521:                                    rawMode = RAW_FALSE;
0522:
0523:                                    setBuf(immCmdSB.toString());
0524:                                    immCmdSB.setLength(0);
0525:
0526:                                    if (rawExecute) {
0527:                                        historize();
0528:                                        processSQL();
0529:                                    } else if (interactive) {
0530:                                        stdprintln(rb
0531:                                                .getString(SqltoolRB.RAW_MOVEDTOBUFFER));
0532:                                    }
0533:                                } else {
0534:                                    if (rawMode == RAW_DATA) {
0535:                                        immCmdSB.append('\n');
0536:                                    }
0537:                                    rawMode = RAW_DATA;
0538:
0539:                                    if (inputLine.length() > 0) {
0540:                                        immCmdSB.append(inputLine);
0541:                                    }
0542:                                }
0543:
0544:                                continue;
0545:                            }
0546:
0547:                            if (immCmdSB.length() == 0) {
0548:                                // NEW Immediate Command (i.e., not appending).
0549:                                if (trimmedInput.startsWith("/*")) {
0550:                                    postCommentIndex = trimmedInput.indexOf(
0551:                                            "*/", 2) + 2;
0552:
0553:                                    if (postCommentIndex > 1) {
0554:                                        // I see no reason to leave comments in
0555:                                        // history.
0556:                                        inputLine = inputLine
0557:                                                .substring(postCommentIndex
0558:                                                        + inputLine.length()
0559:                                                        - trimmedInput.length());
0560:                                        trimmedInput = inputLine.trim();
0561:                                    } else {
0562:                                        // Just so we get continuation lines:
0563:                                        immCmdSB.append("COMMENT");
0564:
0565:                                        inComment = true;
0566:
0567:                                        continue;
0568:                                    }
0569:                                }
0570:
0571:                                if (trimmedInput.length() == 0) {
0572:                                    // This is just to filter out useless newlines at
0573:                                    // beginning of commands.
0574:                                    continue;
0575:                                }
0576:
0577:                                if ((trimmedInput.charAt(0) == '*' && (trimmedInput
0578:                                        .length() < 2 || trimmedInput.charAt(1) != '{'))
0579:                                        || trimmedInput.charAt(0) == '\\') {
0580:                                    setBuf(trimmedInput);
0581:                                    processFromBuffer();
0582:                                    continue;
0583:                                }
0584:
0585:                                if (trimmedInput.charAt(0) == ':'
0586:                                        && interactive) {
0587:                                    processBuffHist(trimmedInput.substring(1));
0588:                                    continue;
0589:                                }
0590:
0591:                                String ucased = trimmedInput.toUpperCase();
0592:
0593:                                if (ucased.startsWith("DECLARE")
0594:                                        || ucased.startsWith("BEGIN")) {
0595:                                    rawMode = RAW_EMPTY;
0596:
0597:                                    immCmdSB.append(inputLine);
0598:
0599:                                    if (interactive) {
0600:                                        stdprintln(RAW_LEADIN_MSG);
0601:                                    }
0602:
0603:                                    continue;
0604:                                }
0605:                            }
0606:
0607:                            if (trimmedInput.length() == 0 && interactive
0608:                                    && !inComment) {
0609:                                // Blank lines delimit commands ONLY IN INTERACTIVE
0610:                                // MODE!
0611:                                setBuf(immCmdSB.toString());
0612:                                immCmdSB.setLength(0);
0613:                                stdprintln(rb
0614:                                        .getString(SqltoolRB.INPUT_MOVEDTOBUFFER));
0615:                                continue;
0616:                            }
0617:
0618:                            deTerminated = SqlFile.deTerminated(inputLine);
0619:
0620:                            // A null terminal line (i.e., /\s*;\s*$/) is never useful.
0621:                            if (!trimmedInput.equals(";")) {
0622:                                if (immCmdSB.length() > 0) {
0623:                                    immCmdSB.append('\n');
0624:                                }
0625:
0626:                                immCmdSB
0627:                                        .append((deTerminated == null) ? inputLine
0628:                                                : deTerminated);
0629:                            }
0630:
0631:                            if (deTerminated == null) {
0632:                                continue;
0633:                            }
0634:
0635:                            // If we reach here, then immCmdSB contains a complete
0636:                            // SQL command.
0637:
0638:                            if (immCmdSB.toString().trim().length() == 0) {
0639:                                immCmdSB.setLength(0);
0640:                                throw new SqlToolError(
0641:                                        rb
0642:                                                .getString(SqltoolRB.SQLSTATEMENT_EMPTY));
0643:                                // There is nothing inherently wrong with issuing
0644:                                // an empty command, like to test DB server health.
0645:                                // But, this check effectively catches many syntax
0646:                                // errors early, and the DB check can be done by
0647:                                // sending a comment like "// comment".
0648:                            }
0649:
0650:                            setBuf(immCmdSB.toString());
0651:                            immCmdSB.setLength(0);
0652:                            historize();
0653:                            processSQL();
0654:                        } catch (BadSpecial bs) {
0655:                            // BadSpecials ALWAYS have non-null getMessage().
0656:                            errprintln(rb.getString(SqltoolRB.ERRORAT,
0657:                                    new String[] {
0658:                                            ((file == null) ? "stdin" : file
0659:                                                    .toString()),
0660:                                            Integer.toString(curLinenum),
0661:                                            inputLine, bs.getMessage(), }));
0662:                            Throwable cause = bs.getCause();
0663:                            if (cause != null) {
0664:                                errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
0665:                                        cause.toString()));
0666:
0667:                            }
0668:
0669:                            if (!continueOnError) {
0670:                                throw new SqlToolError(bs);
0671:                            }
0672:                        } catch (SQLException se) {
0673:                            errprintln("SQL "
0674:                                    + rb
0675:                                            .getString(
0676:                                                    SqltoolRB.ERRORAT,
0677:                                                    new String[] {
0678:                                                            ((file == null) ? "stdin"
0679:                                                                    : file
0680:                                                                            .toString()),
0681:                                                            Integer
0682:                                                                    .toString(curLinenum),
0683:                                                            lastSqlStatement,
0684:                                                            se.getMessage(), }));
0685:                            // It's possible that we could have
0686:                            // SQLException.getMessage() == null, but if so, I think
0687:                            // it reasonsable to show "null".  That's a DB inadequacy.
0688:
0689:                            if (!continueOnError) {
0690:                                throw se;
0691:                            }
0692:                        } catch (BreakException be) {
0693:                            String msg = be.getMessage();
0694:
0695:                            if (recursed) {
0696:                                rollbackUncoms = false;
0697:                                // Recursion level will exit by rethrowing the BE.
0698:                                // We set rollbackUncoms to false because only the
0699:                                // top level should detect break errors and
0700:                                // possibly roll back.
0701:                            } else if (msg == null || msg.equals("file")) {
0702:                                break;
0703:                            } else {
0704:                                errprintln(rb.getString(
0705:                                        SqltoolRB.BREAK_UNSATISFIED, msg));
0706:                            }
0707:
0708:                            if (recursed || !continueOnError) {
0709:                                throw be;
0710:                            }
0711:                        } catch (ContinueException ce) {
0712:                            String msg = ce.getMessage();
0713:
0714:                            if (recursed) {
0715:                                rollbackUncoms = false;
0716:                            } else {
0717:                                errprintln(rb.getString(
0718:                                        SqltoolRB.CONTINUE_UNSATISFIED, msg));
0719:                            }
0720:
0721:                            if (recursed || !continueOnError) {
0722:                                throw ce;
0723:                            }
0724:                        } catch (QuitNow qn) {
0725:                            throw qn;
0726:                        } catch (SqlToolError ste) {
0727:                            errprint(rb.getString(SqltoolRB.ERRORAT,
0728:                                    new String[] {
0729:                                            ((file == null) ? "stdin" : file
0730:                                                    .toString()),
0731:                                            Integer.toString(curLinenum),
0732:                                            inputLine,
0733:                                            ((ste.getMessage() == null) ? ""
0734:                                                    : ste.getMessage()) }));
0735:                            if (ste.getMessage() != null)
0736:                                errprintln("");
0737:                            Throwable cause = ste.getCause();
0738:                            if (cause != null) {
0739:                                errprintln(rb.getString(SqltoolRB.CAUSEREPORT,
0740:                                        cause.toString()));
0741:                            }
0742:                            if (!continueOnError) {
0743:                                throw ste;
0744:                            }
0745:                        }
0746:
0747:                        immCmdSB.setLength(0);
0748:                    }
0749:
0750:                    if (inComment || immCmdSB.length() != 0) {
0751:                        errprintln(rb.getString(SqltoolRB.INPUT_UNTERMINATED,
0752:                                immCmdSB.toString()));
0753:                        throw new SqlToolError(rb.getString(
0754:                                SqltoolRB.INPUT_UNTERMINATED, immCmdSB
0755:                                        .toString()));
0756:                    }
0757:
0758:                    rollbackUncoms = false;
0759:                    // Exiting gracefully, so don't roll back.
0760:                } catch (IOException ioe) {
0761:                    throw new SqlToolError(rb
0762:                            .getString(SqltoolRB.PRIMARYINPUT_ACCESSFAIL), ioe);
0763:                } catch (QuitNow qn) {
0764:                    if (recursed) {
0765:                        throw qn;
0766:                        // Will rollback if conditions otherwise require.
0767:                        // Otherwise top level will decide based upon qn.getMessage().
0768:                    }
0769:                    rollbackUncoms = (qn.getMessage() != null);
0770:
0771:                    if (rollbackUncoms) {
0772:                        errprintln(rb.getString(SqltoolRB.ABORTING, qn
0773:                                .getMessage()));
0774:                        throw new SqlToolError(qn.getMessage());
0775:                    }
0776:
0777:                    return;
0778:                } finally {
0779:                    closeQueryOutputStream();
0780:
0781:                    if (fetchingVar != null) {
0782:                        errprintln(rb.getString(SqltoolRB.PLVAR_SET_INCOMPLETE,
0783:                                fetchingVar));
0784:                        rollbackUncoms = true;
0785:                    }
0786:
0787:                    if (br != null)
0788:                        try {
0789:                            br.close();
0790:                        } catch (IOException ioe) {
0791:                            throw new SqlToolError(
0792:                                    rb
0793:                                            .getString(SqltoolRB.INPUTREADER_CLOSEFAIL),
0794:                                    ioe);
0795:                        }
0796:
0797:                    if (rollbackUncoms && possiblyUncommitteds.get()) {
0798:                        errprintln(rb.getString(SqltoolRB.ROLLINGBACK));
0799:                        curConn.rollback();
0800:                        possiblyUncommitteds.set(false);
0801:                    }
0802:                }
0803:            }
0804:
0805:            /**
0806:             * Returns a copy of given string without a terminating semicolon.
0807:             * If there is no terminating semicolon, null is returned.
0808:             *
0809:             * @param inString Base String, which will not be modified (because
0810:             *                 a "copy" will be returned).
0811:             * @returns Null if inString contains no terminating semi-colon.
0812:             */
0813:            private static String deTerminated(String inString) {
0814:                int index = inString.lastIndexOf(';');
0815:
0816:                if (index < 0) {
0817:                    return null;
0818:                }
0819:
0820:                for (int i = index + 1; i < inString.length(); i++) {
0821:                    if (!Character.isWhitespace(inString.charAt(i))) {
0822:                        return null;
0823:                    }
0824:                }
0825:
0826:                return inString.substring(0, index);
0827:            }
0828:
0829:            /**
0830:             * Utility nested Exception class for internal use only.
0831:             *
0832:             * Do not instantiate with null message.
0833:             */
0834:            static private class BadSpecial extends AppendableException {
0835:                static final long serialVersionUID = 7162440064026570590L;
0836:
0837:                BadSpecial(String s) {
0838:                    super (s);
0839:                    if (s == null)
0840:                        throw new RuntimeException(
0841:                                "Must construct BadSpecials with non-null message");
0842:                }
0843:
0844:                BadSpecial(String s, Throwable t) {
0845:                    super (s, t);
0846:                    if (s == null)
0847:                        throw new RuntimeException(
0848:                                "Must construct BadSpecials with non-null message");
0849:                }
0850:            }
0851:
0852:            /**
0853:             * Utility nested Exception class for internal use.
0854:             * This must extend SqlToolError because it has to percolate up from
0855:             * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0856:             * Therefore, external users have no reason to specifically handle
0857:             * QuitNow.
0858:             */
0859:            private class QuitNow extends SqlToolError {
0860:                static final long serialVersionUID = 1811094258670900488L;
0861:
0862:                public QuitNow(String s) {
0863:                    super (s);
0864:                }
0865:
0866:                public QuitNow() {
0867:                    super ();
0868:                }
0869:            }
0870:
0871:            /**
0872:             * Utility nested Exception class for internal use.
0873:             * This must extend SqlToolError because it has to percolate up from
0874:             * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0875:             * Therefore, external users have no reason to specifically handle
0876:             * BreakException.
0877:             */
0878:            private class BreakException extends SqlToolError {
0879:                static final long serialVersionUID = 351150072817675994L;
0880:
0881:                public BreakException() {
0882:                    super ();
0883:                }
0884:
0885:                public BreakException(String s) {
0886:                    super (s);
0887:                }
0888:            }
0889:
0890:            /**
0891:             * Utility nested Exception class for internal use.
0892:             * This must extend SqlToolError because it has to percolate up from
0893:             * recursions of SqlTool.execute(), yet SqlTool.execute() is public.
0894:             * Therefore, external users have no reason to specifically handle
0895:             * ContinueException.
0896:             */
0897:            private class ContinueException extends SqlToolError {
0898:                static final long serialVersionUID = 5064604160827106014L;
0899:
0900:                public ContinueException() {
0901:                    super ();
0902:                }
0903:
0904:                public ContinueException(String s) {
0905:                    super (s);
0906:                }
0907:            }
0908:
0909:            /**
0910:             * Utility nested Exception class for internal use only.
0911:             */
0912:            private class BadSubst extends Exception {
0913:                static final long serialVersionUID = 7325933736897253269L;
0914:
0915:                BadSubst(String s) {
0916:                    super (s);
0917:                }
0918:            }
0919:
0920:            /**
0921:             * Utility nested Exception class for internal use only.
0922:             */
0923:            private class RowError extends AppendableException {
0924:                static final long serialVersionUID = 754346434606022750L;
0925:
0926:                RowError(String s) {
0927:                    super (s);
0928:                }
0929:
0930:                RowError(Throwable t) {
0931:                    this (null, t);
0932:                }
0933:
0934:                RowError(String s, Throwable t) {
0935:                    super (s, t);
0936:                }
0937:            }
0938:
0939:            /**
0940:             * Execute processSql/processPL/processSpecial from buffer.
0941:             */
0942:            public void processFromBuffer() throws BadSpecial, SQLException,
0943:                    SqlToolError {
0944:                historize();
0945:                if (buffer.charAt(0) == '*'
0946:                        && (buffer.length() < 2 || buffer.charAt(1) != '{')) {
0947:                    // Test above just means commands starting with *, EXCEPT
0948:                    // for commands beginning with *{.
0949:                    processPL(buffer);
0950:                    return;
0951:                }
0952:
0953:                if (buffer.charAt(0) == '\\') {
0954:                    processSpecial(buffer);
0955:                    return;
0956:                }
0957:                processSQL();
0958:            }
0959:
0960:            /**
0961:             * Process a Buffer/History Command.
0962:             *
0963:             * Due to the nature of the goal here, we don't trim() "other" like
0964:             * we do for other kinds of commands.
0965:             *
0966:             * @param inString Complete command, less the leading ':' character.
0967:             * @throws SQLException  thrown by JDBC driver.
0968:             * @throws BadSpecial    special-command-specific errors.
0969:             * @throws SqlToolError  all other errors.
0970:             */
0971:            private void processBuffHist(String inString) throws BadSpecial,
0972:                    SQLException, SqlToolError {
0973:                if (inString.length() < 1) {
0974:                    throw new BadSpecial(rb
0975:                            .getString(SqltoolRB.BUFHIST_UNSPECIFIED));
0976:                }
0977:
0978:                // First handle the simple cases where user may not specify a
0979:                // command number.
0980:                char commandChar = inString.charAt(0);
0981:                String other = inString.substring(1);
0982:                if (other.trim().length() == 0) {
0983:                    other = null;
0984:                }
0985:                switch (commandChar) {
0986:                case 'l':
0987:                case 'b':
0988:                    enforce1charBH(other, 'l');
0989:                    if (buffer == null) {
0990:                        stdprintln(nobufferYetString);
0991:                    } else {
0992:                        stdprintln(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS,
0993:                                buffer));
0994:                    }
0995:
0996:                    return;
0997:
0998:                case 'h':
0999:                    enforce1charBH(other, 'h');
1000:                    showHistory();
1001:
1002:                    return;
1003:
1004:                case '?':
1005:                    stdprintln(rb.getString(SqltoolRB.BUFFER_HELP));
1006:
1007:                    return;
1008:                }
1009:
1010:                Integer histNum = null;
1011:                Matcher hm = slashHistoryPattern.matcher(inString);
1012:                if (hm.matches()) {
1013:                    histNum = historySearch(hm.group(1));
1014:                    if (histNum == null) {
1015:                        stdprintln(rb.getString(SqltoolRB.SUBSTITUTION_NOMATCH));
1016:                        return;
1017:                    }
1018:                } else {
1019:                    hm = historyPattern.matcher(inString);
1020:                    if (!hm.matches()) {
1021:                        throw new BadSpecial(rb
1022:                                .getString(SqltoolRB.EDIT_MALFORMAT));
1023:                        // Empirically, I find that his pattern always captures two
1024:                        // groups.  Unfortunately, there's no way to guarantee that :( .
1025:                    }
1026:                    histNum = ((hm.group(1) == null || hm.group(1).length() < 1) ? null
1027:                            : new Integer(hm.group(1)));
1028:                }
1029:                if (hm.groupCount() != 2) {
1030:                    throw new BadSpecial(rb.getString(SqltoolRB.EDIT_MALFORMAT));
1031:                    // Empirically, I find that his pattern always captures two
1032:                    // groups.  Unfortunately, there's no way to guarantee that :( .
1033:                }
1034:                commandChar = ((hm.group(2) == null || hm.group(2).length() < 1) ? '\0'
1035:                        : hm.group(2).charAt(0));
1036:                other = ((commandChar == '\0') ? null : hm.group(2)
1037:                        .substring(1));
1038:                if (other != null && other.length() < 1)
1039:                    other = null;
1040:                String targetCommand = ((histNum == null) ? null
1041:                        : commandFromHistory(histNum.intValue()));
1042:                // Every command below depends upon buffer content.
1043:
1044:                switch (commandChar) {
1045:                case '\0': // Special token set above.  Just history recall.
1046:                    setBuf(targetCommand);
1047:                    stdprintln(rb.getString(SqltoolRB.BUFFER_RESTORED, buffer));
1048:                    return;
1049:
1050:                case ';':
1051:                    enforce1charBH(other, ';');
1052:
1053:                    if (targetCommand != null)
1054:                        setBuf(targetCommand);
1055:                    if (buffer == null)
1056:                        throw new BadSpecial(rb
1057:                                .getString(SqltoolRB.NOBUFFER_YET));
1058:                    stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING, buffer));
1059:                    processFromBuffer();
1060:
1061:                    return;
1062:
1063:                case 'a':
1064:                    if (targetCommand == null)
1065:                        targetCommand = buffer;
1066:                    if (targetCommand == null)
1067:                        throw new BadSpecial(rb
1068:                                .getString(SqltoolRB.NOBUFFER_YET));
1069:                    immCmdSB.append(targetCommand);
1070:
1071:                    if (other != null) {
1072:                        String deTerminated = SqlFile.deTerminated(other);
1073:
1074:                        if (!other.equals(";")) {
1075:                            immCmdSB.append(((deTerminated == null) ? other
1076:                                    : deTerminated));
1077:                        }
1078:
1079:                        if (deTerminated != null) {
1080:                            // If we reach here, then immCmdSB contains a
1081:                            // complete command.
1082:
1083:                            setBuf(immCmdSB.toString());
1084:                            immCmdSB.setLength(0);
1085:                            stdprintln(rb.getString(SqltoolRB.BUFFER_EXECUTING,
1086:                                    buffer));
1087:                            processFromBuffer();
1088:
1089:                            return;
1090:                        }
1091:                    }
1092:
1093:                    magicPrefix = immCmdSB.toString();
1094:                    immCmdSB.setLength(0);
1095:                    if (interactive)
1096:                        stdprint(magicPrefix);
1097:
1098:                    return;
1099:
1100:                case 'w':
1101:                    if (targetCommand == null)
1102:                        targetCommand = buffer;
1103:                    if (targetCommand == null)
1104:                        throw new BadSpecial(rb
1105:                                .getString(SqltoolRB.NOBUFFER_YET));
1106:                    if (other == null) {
1107:                        throw new BadSpecial(rb
1108:                                .getString(SqltoolRB.DESTFILE_DEMAND));
1109:                    }
1110:                    String targetFile = dereference(other.trim(), false);
1111:                    // Dereference and trim the target file name
1112:                    // This is the only case where we dereference a : command.
1113:
1114:                    PrintWriter pw = null;
1115:                    try {
1116:                        pw = new PrintWriter(
1117:                                (charset == null) ? (new OutputStreamWriter(
1118:                                        new FileOutputStream(targetFile, true)))
1119:                                        : (new OutputStreamWriter(
1120:                                                new FileOutputStream(
1121:                                                        targetFile, true),
1122:                                                charset))
1123:                        // Appendmode so can append to an SQL script.
1124:                        );
1125:                        // Replace with just "(new FileOutputStream(file), charset)"
1126:                        // once use defaultCharset from Java 1.5 in charset init.
1127:                        // above.
1128:
1129:                        pw.print(targetCommand);
1130:                        if (!targetCommand.matches("\\s*[*:\\\\].*"))
1131:                            pw.print(';');
1132:                        pw.println();
1133:                        pw.flush();
1134:                    } catch (Exception e) {
1135:                        throw new BadSpecial(rb.getString(
1136:                                SqltoolRB.FILE_APPENDFAIL, targetFile), e);
1137:                    } finally {
1138:                        if (pw != null)
1139:                            pw.close();
1140:                    }
1141:
1142:                    return;
1143:
1144:                case 's':
1145:                    boolean modeExecute = false;
1146:                    boolean modeGlobal = false;
1147:                    if (targetCommand == null)
1148:                        targetCommand = buffer;
1149:                    if (targetCommand == null)
1150:                        throw new BadSpecial(rb
1151:                                .getString(SqltoolRB.NOBUFFER_YET));
1152:
1153:                    try {
1154:                        if (other == null || other.length() < 3) {
1155:                            throw new BadSubst(
1156:                                    rb
1157:                                            .getString(SqltoolRB.SUBSTITUTION_MALFORMAT));
1158:                        }
1159:                        Matcher m = substitutionPattern.matcher(other);
1160:                        if (!m.matches()) {
1161:                            throw new BadSubst(
1162:                                    rb
1163:                                            .getString(SqltoolRB.SUBSTITUTION_MALFORMAT));
1164:                        }
1165:
1166:                        // Note that this pattern does not include the leading :.
1167:                        if (m.groupCount() < 3 || m.groupCount() > 4) {
1168:                            // Assertion failed
1169:                            throw new RuntimeException(
1170:                                    "Matched substitution pattern, but "
1171:                                            + "captured " + m.groupCount()
1172:                                            + " groups");
1173:                        }
1174:                        String optionGroup = ((m.groupCount() > 3 && m.group(4) != null) ? (new String(
1175:                                m.group(4)))
1176:                                : null);
1177:
1178:                        if (optionGroup != null) {
1179:                            if (optionGroup.indexOf(';') > -1) {
1180:                                modeExecute = true;
1181:                                optionGroup = optionGroup.replaceFirst(";", "");
1182:                            }
1183:                            if (optionGroup.indexOf('g') > -1) {
1184:                                modeGlobal = true;
1185:                                optionGroup = optionGroup.replaceFirst("g", "");
1186:                            }
1187:                        }
1188:
1189:                        Matcher bufferMatcher = Pattern.compile(
1190:                                "(?s"
1191:                                        + ((optionGroup == null) ? ""
1192:                                                : optionGroup) + ')'
1193:                                        + m.group(2)).matcher(targetCommand);
1194:                        String newBuffer = (modeGlobal ? bufferMatcher
1195:                                .replaceAll(m.group(3)) : bufferMatcher
1196:                                .replaceFirst(m.group(3)));
1197:                        if (newBuffer.equals(targetCommand)) {
1198:                            stdprintln(rb
1199:                                    .getString(SqltoolRB.SUBSTITUTION_NOMATCH));
1200:                            return;
1201:                        }
1202:
1203:                        setBuf(newBuffer);
1204:                        stdprintln(rb.getString(
1205:                                (modeExecute ? SqltoolRB.BUFFER_EXECUTING
1206:                                        : SqltoolRB.EDITBUFFER_CONTENTS),
1207:                                buffer));
1208:                    } catch (PatternSyntaxException pse) {
1209:                        throw new BadSpecial(rb
1210:                                .getString(SqltoolRB.SUBSTITUTION_SYNTAX), pse);
1211:                    } catch (BadSubst badswitch) {
1212:                        throw new BadSpecial(rb
1213:                                .getString(SqltoolRB.SUBSTITUTION_SYNTAX));
1214:                    }
1215:
1216:                    if (modeExecute) {
1217:                        immCmdSB.setLength(0);
1218:                        processFromBuffer();
1219:                    }
1220:
1221:                    return;
1222:                }
1223:
1224:                throw new BadSpecial(rb.getString(SqltoolRB.BUFFER_UNKNOWN,
1225:                        Character.toString(commandChar)));
1226:            }
1227:
1228:            private boolean doPrepare = false;
1229:            private String prepareVar = null;
1230:            private String dsvColDelim = null;
1231:            private String dsvSkipPrefix = null;
1232:            private String dsvRowDelim = null;
1233:            private String dsvSkipCols = null;
1234:            private String DSV_X_SYNTAX_MSG = null;
1235:            private String DSV_M_SYNTAX_MSG = null;
1236:            private String nobufferYetString = null;
1237:
1238:            private void enforce1charSpecial(String token, char command)
1239:                    throws BadSpecial {
1240:                if (token.length() != 1) {
1241:                    throw new BadSpecial(rb.getString(
1242:                            SqltoolRB.SPECIAL_EXTRACHARS, Character
1243:                                    .toString(command), token.substring(1)));
1244:                }
1245:            }
1246:
1247:            private void enforce1charBH(String token, char command)
1248:                    throws BadSpecial {
1249:                if (token != null) {
1250:                    throw new BadSpecial(rb.getString(
1251:                            SqltoolRB.BUFFER_EXTRACHARS, Character
1252:                                    .toString(command), token));
1253:                }
1254:            }
1255:
1256:            /**
1257:             * Process a Special Command.
1258:             *
1259:             * @param inString TRIMMED complete command, including the leading
1260:             *                 '\' character.
1261:             * @throws SQLException thrown by JDBC driver.
1262:             * @throws BadSpecial special-command-specific errors.
1263:             * @throws SqlToolError all other errors, plus QuitNow,
1264:             *                      BreakException, ContinueException.
1265:             */
1266:            private void processSpecial(String inString) throws BadSpecial,
1267:                    QuitNow, SQLException, SqlToolError {
1268:                if (inString.equals("\\")) {
1269:                    throw new BadSpecial(rb
1270:                            .getString(SqltoolRB.SPECIAL_UNSPECIFIED));
1271:                }
1272:                Matcher m = specialPattern.matcher(plMode ? dereference(
1273:                        inString, false) : inString);
1274:                if (!m.matches()) {
1275:                    throw new BadSpecial(rb
1276:                            .getString(SqltoolRB.SPECIAL_MALFORMAT));
1277:                    // I think it's impossible to get here, since the pattern is
1278:                    // so liberal.
1279:                }
1280:                if (m.groupCount() < 1 || m.groupCount() > 2) {
1281:                    // Failed assertion
1282:                    throw new RuntimeException("Pattern matched, yet captured "
1283:                            + m.groupCount() + " groups");
1284:                }
1285:
1286:                String arg1 = m.group(1);
1287:                String other = ((m.groupCount() > 1) ? m.group(2) : null);
1288:
1289:                switch (arg1.charAt(0)) {
1290:                case 'q':
1291:                    enforce1charSpecial(arg1, 'q');
1292:                    if (other != null) {
1293:                        throw new QuitNow(other);
1294:                    }
1295:
1296:                    throw new QuitNow();
1297:                case 'H':
1298:                    enforce1charSpecial(arg1, 'H');
1299:                    htmlMode = !htmlMode;
1300:
1301:                    stdprintln(rb.getString(SqltoolRB.HTML_MODE, Boolean
1302:                            .toString(htmlMode)));
1303:
1304:                    return;
1305:
1306:                case 'm':
1307:                    if (arg1.equals("m?")
1308:                            || (arg1.equals("m") && other != null && other
1309:                                    .equals("?"))) {
1310:                        stdprintln(DSV_OPTIONS_TEXT + LS + DSV_M_SYNTAX_MSG);
1311:                        return;
1312:                    }
1313:                    if (arg1.length() != 1 || other == null) {
1314:                        throw new BadSpecial(DSV_M_SYNTAX_MSG);
1315:                    }
1316:                    boolean noComments = other.charAt(other.length() - 1) == '*';
1317:                    String skipPrefix = null;
1318:
1319:                    if (noComments) {
1320:                        other = other.substring(0, other.length() - 1).trim();
1321:                        if (other.length() < 1) {
1322:                            throw new BadSpecial(DSV_M_SYNTAX_MSG);
1323:                        }
1324:                    } else {
1325:                        skipPrefix = dsvSkipPrefix;
1326:                    }
1327:                    int colonIndex = other.indexOf(" :");
1328:                    if (colonIndex > -1 && colonIndex < other.length() - 2) {
1329:                        skipPrefix = other.substring(colonIndex + 2);
1330:                        other = other.substring(0, colonIndex).trim();
1331:                    }
1332:
1333:                    importDsv(other, skipPrefix);
1334:
1335:                    return;
1336:
1337:                case 'x':
1338:                    if (arg1.equals("x?")
1339:                            || (arg1.equals("x") && other != null && other
1340:                                    .equals("?"))) {
1341:                        stdprintln(DSV_OPTIONS_TEXT + LS + DSV_X_SYNTAX_MSG);
1342:                        return;
1343:                    }
1344:                    try {
1345:                        if (arg1.length() != 1 || other == null) {
1346:                            throw new BadSpecial(DSV_X_SYNTAX_MSG);
1347:                        }
1348:
1349:                        String tableName = ((other.indexOf(' ') > 0) ? null
1350:                                : other);
1351:
1352:                        if (dsvTargetFile == null && tableName == null) {
1353:                            throw new BadSpecial(rb
1354:                                    .getString(SqltoolRB.DSV_TARGETFILE_DEMAND));
1355:                        }
1356:                        File dsvFile = new File(
1357:                                (dsvTargetFile == null) ? (tableName + ".dsv")
1358:                                        : dsvTargetFile);
1359:
1360:                        pwDsv = new PrintWriter(
1361:                                (charset == null) ? (new OutputStreamWriter(
1362:                                        new FileOutputStream(dsvFile)))
1363:                                        : (new OutputStreamWriter(
1364:                                                new FileOutputStream(dsvFile),
1365:                                                charset)));
1366:                        // Replace with just "(new FileOutputStream(file), charset)"
1367:                        // once use defaultCharset from Java 1.5 in charset init.
1368:                        // above.
1369:
1370:                        ResultSet rs = curConn.createStatement().executeQuery(
1371:                                (tableName == null) ? other
1372:                                        : ("SELECT * FROM " + tableName));
1373:                        List colList = new ArrayList();
1374:                        int[] incCols = null;
1375:                        if (dsvSkipCols != null) {
1376:                            Set skipCols = new HashSet();
1377:                            String[] skipColsArray = dsvSkipCols
1378:                                    .split("\\s*\\Q" + dsvColDelim + "\\E\\s*");
1379:                            for (int i = 0; i < skipColsArray.length; i++) {
1380:                                skipCols.add(skipColsArray[i].toLowerCase());
1381:                            }
1382:                            ResultSetMetaData rsmd = rs.getMetaData();
1383:                            for (int i = 1; i <= rsmd.getColumnCount(); i++) {
1384:                                if (!skipCols.remove(rsmd.getColumnName(i)
1385:                                        .toLowerCase())) {
1386:                                    colList.add(new Integer(i));
1387:                                }
1388:                            }
1389:                            if (colList.size() < 1) {
1390:                                throw new BadSpecial(rb.getString(
1391:                                        SqltoolRB.DSV_NOCOLSLEFT, dsvSkipCols));
1392:                            }
1393:                            if (skipCols.size() > 0) {
1394:                                throw new BadSpecial(rb.getString(
1395:                                        SqltoolRB.DSV_SKIPCOLS_MISSING,
1396:                                        skipCols.toString()));
1397:                            }
1398:                            incCols = new int[colList.size()];
1399:                            for (int i = 0; i < incCols.length; i++) {
1400:                                incCols[i] = ((Integer) colList.get(i))
1401:                                        .intValue();
1402:                            }
1403:                        }
1404:                        displayResultSet(null, rs, incCols, null);
1405:                        pwDsv.flush();
1406:                        stdprintln(rb
1407:                                .getString(SqltoolRB.FILE_WROTECHARS, Long
1408:                                        .toString(dsvFile.length()), dsvFile
1409:                                        .toString()));
1410:                    } catch (FileNotFoundException e) {
1411:                        throw new BadSpecial(rb.getString(
1412:                                SqltoolRB.FILE_WRITEFAIL, other), e);
1413:                    } catch (UnsupportedEncodingException e) {
1414:                        throw new BadSpecial(rb.getString(
1415:                                SqltoolRB.FILE_WRITEFAIL, other), e);
1416:                    } finally {
1417:                        // Reset all state changes
1418:                        if (pwDsv != null) {
1419:                            pwDsv.close();
1420:                        }
1421:
1422:                        pwDsv = null;
1423:                    }
1424:
1425:                    return;
1426:
1427:                case 'd':
1428:                    if (arg1.equals("d?")
1429:                            || (arg1.equals("d") && other != null && other
1430:                                    .equals("?"))) {
1431:                        stdprintln(D_OPTIONS_TEXT);
1432:                        return;
1433:                    }
1434:                    if (arg1.length() == 2) {
1435:                        listTables(arg1.charAt(1), other);
1436:
1437:                        return;
1438:                    }
1439:
1440:                    if (arg1.length() == 1 && other != null)
1441:                        try {
1442:                            int space = other.indexOf(' ');
1443:
1444:                            if (space < 0) {
1445:                                describe(other, null);
1446:                            } else {
1447:                                describe(other.substring(0, space), other
1448:                                        .substring(space + 1).trim());
1449:                            }
1450:
1451:                            return;
1452:                        } catch (SQLException se) {
1453:                            throw new BadSpecial(rb
1454:                                    .getString(SqltoolRB.METADATA_FETCH_FAIL),
1455:                                    se);
1456:                        }
1457:
1458:                    throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_D_LIKE));
1459:                case 'o':
1460:                    enforce1charSpecial(arg1, 'o');
1461:                    if (other == null) {
1462:                        if (pwQuery == null) {
1463:                            throw new BadSpecial(
1464:                                    rb
1465:                                            .getString(SqltoolRB.OUTPUTFILE_NONETOCLOSE));
1466:                        }
1467:
1468:                        closeQueryOutputStream();
1469:
1470:                        return;
1471:                    }
1472:
1473:                    if (pwQuery != null) {
1474:                        stdprintln(rb.getString(SqltoolRB.OUTPUTFILE_REOPENING));
1475:                        closeQueryOutputStream();
1476:                    }
1477:
1478:                    try {
1479:                        pwQuery = new PrintWriter(
1480:                                (charset == null) ? (new OutputStreamWriter(
1481:                                        new FileOutputStream(other, true)))
1482:                                        : (new OutputStreamWriter(
1483:                                                new FileOutputStream(other,
1484:                                                        true), charset)));
1485:                        // Replace with just "(new FileOutputStream(file), charset)"
1486:                        // once use defaultCharset from Java 1.5 in charset init.
1487:                        // above.
1488:
1489:                        /* Opening in append mode, so it's possible that we will
1490:                         * be adding superfluous <HTML> and <BODY> tags.
1491:                         * I think that browsers can handle that */
1492:                        pwQuery.println((htmlMode ? ("<HTML>" + LS + "<!--")
1493:                                : "#")
1494:                                + " "
1495:                                + (new java.util.Date())
1496:                                + ".  "
1497:                                + rb.getString(SqltoolRB.OUTPUTFILE_HEADER,
1498:                                        getClass().getName())
1499:                                + (htmlMode ? (" -->" + LS + LS + "<BODY>")
1500:                                        : LS));
1501:                        pwQuery.flush();
1502:                    } catch (Exception e) {
1503:                        throw new BadSpecial(rb.getString(
1504:                                SqltoolRB.FILE_WRITEFAIL, other), e);
1505:                    }
1506:
1507:                    return;
1508:
1509:                case 'i':
1510:                    enforce1charSpecial(arg1, 'i');
1511:                    if (other == null) {
1512:                        throw new BadSpecial(rb
1513:                                .getString(SqltoolRB.SQLFILE_NAME_DEMAND));
1514:                    }
1515:
1516:                    try {
1517:                        SqlFile sf = new SqlFile(new File(other), false,
1518:                                userVars);
1519:
1520:                        sf.recursed = true;
1521:
1522:                        // Share the possiblyUncommitted state
1523:                        sf.possiblyUncommitteds = possiblyUncommitteds;
1524:                        sf.plMode = plMode;
1525:
1526:                        sf.execute(curConn, continueOnError);
1527:                    } catch (ContinueException ce) {
1528:                        throw ce;
1529:                    } catch (BreakException be) {
1530:                        String beMessage = be.getMessage();
1531:
1532:                        // Handle "file" and plain breaks (by doing nothing)
1533:                        if (beMessage != null && !beMessage.equals("file")) {
1534:                            throw be;
1535:                        }
1536:                    } catch (QuitNow qn) {
1537:                        throw qn;
1538:                    } catch (Exception e) {
1539:                        throw new BadSpecial(rb.getString(
1540:                                SqltoolRB.SQLFILE_EXECUTE_FAIL, other), e);
1541:                    }
1542:
1543:                    return;
1544:
1545:                case 'p':
1546:                    enforce1charSpecial(arg1, 'p');
1547:                    if (other == null) {
1548:                        stdprintln(true);
1549:                    } else {
1550:                        stdprintln(other, true);
1551:                    }
1552:
1553:                    return;
1554:
1555:                case 'a':
1556:                    enforce1charSpecial(arg1, 'a');
1557:                    if (other != null) {
1558:                        curConn.setAutoCommit(Boolean.valueOf(other)
1559:                                .booleanValue());
1560:                    }
1561:
1562:                    stdprintln(rb.getString(SqltoolRB.A_SETTING, Boolean
1563:                            .toString(curConn.getAutoCommit())));
1564:
1565:                    return;
1566:                case '=':
1567:                    enforce1charSpecial(arg1, '=');
1568:                    curConn.commit();
1569:                    possiblyUncommitteds.set(false);
1570:                    stdprintln(rb.getString(SqltoolRB.COMMITTED));
1571:
1572:                    return;
1573:
1574:                case 'b':
1575:                    if (arg1.length() == 1) {
1576:                        if (other != null) {
1577:                            throw new BadSpecial(rb
1578:                                    .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1579:                        }
1580:                        fetchBinary = true;
1581:
1582:                        return;
1583:                    }
1584:
1585:                    if (arg1.charAt(1) == 'p') {
1586:                        if (other != null) {
1587:                            throw new BadSpecial(rb
1588:                                    .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1589:                        }
1590:                        doPrepare = true;
1591:
1592:                        return;
1593:                    }
1594:
1595:                    if ((arg1.charAt(1) != 'd' && arg1.charAt(1) != 'l')
1596:                            || other == null) {
1597:                        throw new BadSpecial(rb
1598:                                .getString(SqltoolRB.SPECIAL_B_MALFORMAT));
1599:                    }
1600:
1601:                    File file = new File(other);
1602:
1603:                    try {
1604:                        if (arg1.charAt(1) == 'd') {
1605:                            dump(file);
1606:                        } else {
1607:                            binBuffer = SqlFile.loadBinary(file);
1608:                            stdprintln(rb.getString(
1609:                                    SqltoolRB.BINARY_LOADEDBYTESINTO,
1610:                                    binBuffer.length));
1611:                        }
1612:                    } catch (BadSpecial bs) {
1613:                        throw bs;
1614:                    } catch (IOException ioe) {
1615:                        throw new BadSpecial(rb.getString(
1616:                                SqltoolRB.BINARY_FILEFAIL, other), ioe);
1617:                    }
1618:
1619:                    return;
1620:
1621:                case '*':
1622:                case 'c':
1623:                    enforce1charSpecial(arg1, '=');
1624:                    if (other != null) {
1625:                        // But remember that we have to abort on some I/O errors.
1626:                        continueOnError = Boolean.valueOf(other).booleanValue();
1627:                    }
1628:
1629:                    stdprintln(rb.getString(SqltoolRB.C_SETTING, Boolean
1630:                            .toString(continueOnError)));
1631:
1632:                    return;
1633:
1634:                case '?':
1635:                    stdprintln(rb.getString(SqltoolRB.SPECIAL_HELP));
1636:
1637:                    return;
1638:
1639:                case '!':
1640:                    // N.b. This DOES NOT HANDLE UNIX shell wildcards, since there
1641:                    // is no UNIX shell involved.
1642:                    // Doesn't make sense to incur overhead of a shell without
1643:                    // stdin capability.
1644:                    // Can't provide stdin to the executed program because
1645:                    // the forked program could gobble up program input,
1646:                    // depending on how SqlTool was invoked, nested scripts,
1647:                    // etc.
1648:
1649:                    // I'd like to execute the user's default shell if they
1650:                    // ran "\!" with no argument, but (a) there is no portable
1651:                    // way to determine the user's default or login shell; and
1652:                    // (b) shell is useless without stdin ability.
1653:                    InputStream stream;
1654:                    byte[] ba = new byte[1024];
1655:                    String extCommand = ((arg1.length() == 1) ? "" : arg1
1656:                            .substring(1))
1657:                            + ((arg1.length() > 1 && other != null) ? " " : "")
1658:                            + ((other == null) ? "" : other);
1659:                    if (extCommand.trim().length() < 1)
1660:                        throw new BadSpecial(rb
1661:                                .getString(SqltoolRB.BANG_INCOMPLETE));
1662:
1663:                    try {
1664:                        Runtime runtime = Runtime.getRuntime();
1665:                        Process proc = ((wincmdPattern == null) ? runtime
1666:                                .exec(extCommand) : runtime
1667:                                .exec(genWinArgs(extCommand)));
1668:
1669:                        proc.getOutputStream().close();
1670:
1671:                        int i;
1672:
1673:                        stream = proc.getInputStream();
1674:
1675:                        while ((i = stream.read(ba)) > 0) {
1676:                            stdprint(new String(ba, 0, i));
1677:                        }
1678:
1679:                        stream.close();
1680:
1681:                        stream = proc.getErrorStream();
1682:
1683:                        while ((i = stream.read(ba)) > 0) {
1684:                            errprint(new String(ba, 0, i));
1685:                        }
1686:
1687:                        stream.close();
1688:
1689:                        if (proc.waitFor() != 0) {
1690:                            throw new BadSpecial(rb.getString(
1691:                                    SqltoolRB.BANG_COMMAND_FAIL, extCommand));
1692:                        }
1693:                    } catch (BadSpecial bs) {
1694:                        throw bs;
1695:                    } catch (Exception e) {
1696:                        throw new BadSpecial(rb.getString(
1697:                                SqltoolRB.BANG_COMMAND_FAIL, extCommand), e);
1698:                    }
1699:
1700:                    return;
1701:
1702:                case '.':
1703:                    enforce1charSpecial(arg1, '.');
1704:                    rawMode = RAW_EMPTY;
1705:
1706:                    if (interactive) {
1707:                        stdprintln(RAW_LEADIN_MSG);
1708:                    }
1709:
1710:                    return;
1711:                }
1712:
1713:                throw new BadSpecial(rb.getString(SqltoolRB.SPECIAL_UNKNOWN,
1714:                        Character.toString(arg1.charAt(0))));
1715:            }
1716:
1717:            private static final char[] nonVarChars = { ' ', '\t', '=', '}',
1718:                    '\n', '\r' };
1719:
1720:            /**
1721:             * Returns index specifying 1 past end of a variable name.
1722:             *
1723:             * @param inString String containing a variable name
1724:             * @param startIndex Index within inString where the variable name begins
1725:             * @returns Index within inString, 1 past end of the variable name
1726:             */
1727:            static int pastName(String inString, int startIndex) {
1728:                String workString = inString.substring(startIndex);
1729:                int e = inString.length(); // Index 1 past end of var name.
1730:                int nonVarIndex;
1731:
1732:                for (int i = 0; i < nonVarChars.length; i++) {
1733:                    nonVarIndex = workString.indexOf(nonVarChars[i]);
1734:
1735:                    if (nonVarIndex > -1 && nonVarIndex < e) {
1736:                        e = nonVarIndex;
1737:                    }
1738:                }
1739:
1740:                return startIndex + e;
1741:            }
1742:
1743:            /**
1744:             * Deference *{} PL variables and ${} System Property variables.
1745:             *
1746:             * @throws SqlToolError
1747:             */
1748:            private String dereference(String inString, boolean permitAlias)
1749:                    throws SqlToolError {
1750:                /* TODO:  Rewrite using java.util.regex. */
1751:                String varName, varValue;
1752:                StringBuffer expandBuffer = new StringBuffer(inString);
1753:                int b, e; // begin and end of name.  end really 1 PAST name
1754:                int iterations;
1755:
1756:                if (permitAlias && inString.trim().charAt(0) == '/') {
1757:                    int slashIndex = inString.indexOf('/');
1758:
1759:                    e = SqlFile.pastName(inString.substring(slashIndex + 1), 0);
1760:
1761:                    // In this case, e is the exact length of the var name.
1762:                    if (e < 1) {
1763:                        throw new SqlToolError(rb
1764:                                .getString(SqltoolRB.PLALIAS_MALFORMAT));
1765:                    }
1766:
1767:                    varName = inString.substring(slashIndex + 1, slashIndex + 1
1768:                            + e);
1769:                    varValue = (String) userVars.get(varName);
1770:
1771:                    if (varValue == null) {
1772:                        throw new SqlToolError(rb.getString(
1773:                                SqltoolRB.PLVAR_UNDEFINED, varName));
1774:                    }
1775:
1776:                    expandBuffer.replace(slashIndex, slashIndex + 1 + e,
1777:                            (String) userVars.get(varName));
1778:                }
1779:
1780:                String s;
1781:                boolean permitUnset;
1782:                // Permit unset with:     ${:varname}
1783:                // Prohibit unset with :  ${varnam}
1784:
1785:                iterations = 0;
1786:                while (true) {
1787:                    s = expandBuffer.toString();
1788:                    b = s.indexOf("${");
1789:
1790:                    if (b < 0) {
1791:                        // No more unexpanded variable uses
1792:                        break;
1793:                    }
1794:
1795:                    e = s.indexOf('}', b + 2);
1796:
1797:                    if (e == b + 2) {
1798:                        throw new SqlToolError(rb
1799:                                .getString(SqltoolRB.SYSPROP_EMPTY));
1800:                    }
1801:
1802:                    if (e < 0) {
1803:                        throw new SqlToolError(rb
1804:                                .getString(SqltoolRB.SYSPROP_UNTERMINATED));
1805:                    }
1806:
1807:                    permitUnset = (s.charAt(b + 2) == ':');
1808:
1809:                    varName = s.substring(b + (permitUnset ? 3 : 2), e);
1810:                    if (iterations++ > 10000)
1811:                        throw new SqlToolError(rb.getString(
1812:                                SqltoolRB.VAR_INFINITE, varName));
1813:
1814:                    varValue = System.getProperty(varName);
1815:                    if (varValue == null) {
1816:                        if (permitUnset) {
1817:                            varValue = "";
1818:                        } else {
1819:                            throw new SqlToolError(rb.getString(
1820:                                    SqltoolRB.SYSPROP_UNDEFINED, varName));
1821:                        }
1822:                    }
1823:
1824:                    expandBuffer.replace(b, e + 1, varValue);
1825:                }
1826:
1827:                iterations = 0;
1828:                while (true) {
1829:                    s = expandBuffer.toString();
1830:                    b = s.indexOf("*{");
1831:
1832:                    if (b < 0) {
1833:                        // No more unexpanded variable uses
1834:                        break;
1835:                    }
1836:
1837:                    e = s.indexOf('}', b + 2);
1838:
1839:                    if (e == b + 2) {
1840:                        throw new SqlToolError(rb
1841:                                .getString(SqltoolRB.PLVAR_NAMEEMPTY));
1842:                    }
1843:
1844:                    if (e < 0) {
1845:                        throw new SqlToolError(rb
1846:                                .getString(SqltoolRB.PLVAR_UNTERMINATED));
1847:                    }
1848:
1849:                    permitUnset = (s.charAt(b + 2) == ':');
1850:
1851:                    varName = s.substring(b + (permitUnset ? 3 : 2), e);
1852:                    if (iterations++ > 10000)
1853:                        throw new SqlToolError(rb.getString(
1854:                                SqltoolRB.VAR_INFINITE, varName));
1855:                    // TODO:  Use a smarter algorithm to handle (or prohibit)
1856:                    // recursion without this clumsy detection tactic.
1857:
1858:                    varValue = (String) userVars.get(varName);
1859:                    if (varValue == null) {
1860:                        if (permitUnset) {
1861:                            varValue = "";
1862:                        } else {
1863:                            throw new SqlToolError(rb.getString(
1864:                                    SqltoolRB.PLVAR_UNDEFINED, varName));
1865:                        }
1866:                    }
1867:
1868:                    expandBuffer.replace(b, e + 1, varValue);
1869:                }
1870:
1871:                return expandBuffer.toString();
1872:            }
1873:
1874:            public boolean plMode = false;
1875:
1876:            //  PL variable name currently awaiting query output.
1877:            private String fetchingVar = null;
1878:            private boolean silentFetch = false;
1879:            private boolean fetchBinary = false;
1880:
1881:            /**
1882:             * Process a Process Language Command.
1883:             * Nesting not supported yet.
1884:             *
1885:             * @param inString Complete command, including the leading '\' character.
1886:             * @throws BadSpecial special-command-specific errors.
1887:             * @throws SqlToolError all other errors, plus BreakException and
1888:             *                      ContinueException.
1889:             */
1890:            private void processPL(String inString) throws BadSpecial,
1891:                    SqlToolError {
1892:                Matcher m = plPattern.matcher(dereference(inString, false));
1893:                if (!m.matches()) {
1894:                    throw new BadSpecial(rb.getString(SqltoolRB.PL_MALFORMAT));
1895:                    // I think it's impossible to get here, since the pattern is
1896:                    // so liberal.
1897:                }
1898:                if (m.groupCount() < 1 || m.group(1) == null) {
1899:                    plMode = true;
1900:                    stdprintln(rb.getString(SqltoolRB.PL_EXPANSIONMODE, "on"));
1901:                    return;
1902:                }
1903:
1904:                String[] tokens = m.group(1).split("\\s+");
1905:
1906:                if (tokens[0].charAt(0) == '?') {
1907:                    stdprintln(rb.getString(SqltoolRB.PL_HELP));
1908:
1909:                    return;
1910:                }
1911:
1912:                // If user runs any PL command, we turn PL mode on.
1913:                plMode = true;
1914:
1915:                if (tokens[0].equals("end")) {
1916:                    throw new BadSpecial(rb.getString(SqltoolRB.END_NOBLOCK));
1917:                }
1918:
1919:                if (tokens[0].equals("continue")) {
1920:                    if (tokens.length > 1) {
1921:                        if (tokens.length == 2
1922:                                && (tokens[1].equals("foreach") || tokens[1]
1923:                                        .equals("while"))) {
1924:                            throw new ContinueException(tokens[1]);
1925:                        }
1926:                        throw new BadSpecial(rb
1927:                                .getString(SqltoolRB.CONTINUE_SYNTAX));
1928:                    }
1929:
1930:                    throw new ContinueException();
1931:                }
1932:
1933:                if (tokens[0].equals("break")) {
1934:                    if (tokens.length > 1) {
1935:                        if (tokens.length == 2
1936:                                && (tokens[1].equals("foreach")
1937:                                        || tokens[1].equals("if")
1938:                                        || tokens[1].equals("while") || tokens[1]
1939:                                        .equals("file"))) {
1940:                            throw new BreakException(tokens[1]);
1941:                        }
1942:                        throw new BadSpecial(rb
1943:                                .getString(SqltoolRB.BREAK_SYNTAX));
1944:                    }
1945:
1946:                    throw new BreakException();
1947:                }
1948:
1949:                if (tokens[0].equals("list") || tokens[0].equals("listvalues")
1950:                        || tokens[0].equals("listsysprops")) {
1951:                    boolean sysProps = tokens[0].equals("listsysprops");
1952:                    String s;
1953:                    boolean doValues = (tokens[0].equals("listvalues") || sysProps);
1954:                    // Always list System Property values.
1955:                    // They are unlikely to be very long, like PL variables may be.
1956:
1957:                    if (tokens.length == 1) {
1958:                        stdprint(formatNicely((sysProps ? System
1959:                                .getProperties() : userVars), doValues));
1960:                    } else {
1961:                        if (doValues) {
1962:                            stdprintln(rb.getString(SqltoolRB.PL_LIST_PARENS));
1963:                        } else {
1964:                            stdprintln(rb.getString(SqltoolRB.PL_LIST_LENGTHS));
1965:                        }
1966:
1967:                        for (int i = 1; i < tokens.length; i++) {
1968:                            s = (String) (sysProps ? System.getProperties()
1969:                                    : userVars).get(tokens[i]);
1970:                            if (s == null)
1971:                                continue;
1972:                            stdprintln("    "
1973:                                    + tokens[i]
1974:                                    + ": "
1975:                                    + (doValues ? ("(" + s + ')') : Integer
1976:                                            .toString(s.length())));
1977:                        }
1978:                    }
1979:
1980:                    return;
1981:                }
1982:
1983:                if (tokens[0].equals("dump") || tokens[0].equals("load")) {
1984:                    if (tokens.length != 3) {
1985:                        throw new BadSpecial(rb
1986:                                .getString(SqltoolRB.DUMPLOAD_MALFORMAT));
1987:                    }
1988:
1989:                    String varName = tokens[1];
1990:
1991:                    if (varName.indexOf(':') > -1) {
1992:                        throw new BadSpecial(rb
1993:                                .getString(SqltoolRB.PLVAR_NOCOLON));
1994:                    }
1995:                    File file = new File(tokens[2]);
1996:
1997:                    try {
1998:                        if (tokens[0].equals("dump")) {
1999:                            dump(varName, file);
2000:                        } else {
2001:                            load(varName, file, charset);
2002:                        }
2003:                    } catch (IOException ioe) {
2004:                        throw new BadSpecial(rb.getString(
2005:                                SqltoolRB.DUMPLOAD_FAIL, varName, file
2006:                                        .toString()), ioe);
2007:                    }
2008:
2009:                    return;
2010:                }
2011:
2012:                if (tokens[0].equals("prepare")) {
2013:                    if (tokens.length != 2) {
2014:                        throw new BadSpecial(rb
2015:                                .getString(SqltoolRB.PREPARE_MALFORMAT));
2016:                    }
2017:
2018:                    if (userVars.get(tokens[1]) == null) {
2019:                        throw new BadSpecial(rb.getString(
2020:                                SqltoolRB.PLVAR_UNDEFINED, tokens[1]));
2021:                    }
2022:
2023:                    prepareVar = tokens[1];
2024:                    doPrepare = true;
2025:
2026:                    return;
2027:                }
2028:
2029:                if (tokens[0].equals("foreach")) {
2030:                    Matcher foreachM = foreachPattern.matcher(dereference(
2031:                            inString, false));
2032:                    if (!foreachM.matches()) {
2033:                        throw new BadSpecial(rb
2034:                                .getString(SqltoolRB.FOREACH_MALFORMAT));
2035:                    }
2036:                    if (foreachM.groupCount() != 2) {
2037:                        throw new RuntimeException(
2038:                                "foreach pattern matched, but captured "
2039:                                        + foreachM.groupCount() + " groups");
2040:                    }
2041:
2042:                    String varName = foreachM.group(1);
2043:                    if (varName.indexOf(':') > -1) {
2044:                        throw new BadSpecial(rb
2045:                                .getString(SqltoolRB.PLVAR_NOCOLON));
2046:                    }
2047:                    String[] values = foreachM.group(2).split("\\s+");
2048:                    File tmpFile = null;
2049:                    String varVal;
2050:
2051:                    try {
2052:                        tmpFile = plBlockFile("foreach");
2053:                    } catch (IOException ioe) {
2054:                        throw new BadSpecial(rb
2055:                                .getString(SqltoolRB.PL_TEMPFILE_FAIL), ioe);
2056:                    }
2057:
2058:                    String origval = (String) userVars.get(varName);
2059:
2060:                    try {
2061:                        SqlFile sf;
2062:
2063:                        for (int i = 0; i < values.length; i++) {
2064:                            try {
2065:                                varVal = values[i];
2066:
2067:                                userVars.put(varName, varVal);
2068:                                updateUserSettings();
2069:
2070:                                sf = new SqlFile(tmpFile, false, userVars);
2071:                                sf.plMode = true;
2072:                                sf.recursed = true;
2073:
2074:                                // Share the possiblyUncommitted state
2075:                                sf.possiblyUncommitteds = possiblyUncommitteds;
2076:
2077:                                sf.execute(curConn, continueOnError);
2078:                            } catch (ContinueException ce) {
2079:                                String ceMessage = ce.getMessage();
2080:
2081:                                if (ceMessage != null
2082:                                        && !ceMessage.equals("foreach")) {
2083:                                    throw ce;
2084:                                }
2085:                            }
2086:                        }
2087:                    } catch (BreakException be) {
2088:                        String beMessage = be.getMessage();
2089:
2090:                        // Handle "foreach" and plain breaks (by doing nothing)
2091:                        if (beMessage != null && !beMessage.equals("foreach")) {
2092:                            throw be;
2093:                        }
2094:                    } catch (QuitNow qn) {
2095:                        throw qn;
2096:                    } catch (Exception e) {
2097:                        throw new BadSpecial(rb
2098:                                .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2099:                    }
2100:
2101:                    if (origval == null) {
2102:                        userVars.remove(varName);
2103:                        updateUserSettings();
2104:                    } else {
2105:                        userVars.put(varName, origval);
2106:                    }
2107:
2108:                    if (tmpFile != null && !tmpFile.delete()) {
2109:                        throw new BadSpecial(rb.getString(
2110:                                SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile
2111:                                        .toString()));
2112:                    }
2113:
2114:                    return;
2115:                }
2116:
2117:                if (tokens[0].equals("if") || tokens[0].equals("while")) {
2118:                    Matcher ifwhileM = ifwhilePattern.matcher(dereference(
2119:                            inString, false));
2120:                    if (!ifwhileM.matches()) {
2121:                        throw new BadSpecial(rb
2122:                                .getString(SqltoolRB.IFWHILE_MALFORMAT));
2123:                    }
2124:                    if (ifwhileM.groupCount() != 1) {
2125:                        throw new RuntimeException(
2126:                                "if/while pattern matched, but captured "
2127:                                        + ifwhileM.groupCount() + " groups");
2128:                    }
2129:
2130:                    String[] values = ifwhileM.group(1).replaceAll(
2131:                            "!([a-zA-Z0-9*])", "! $1").replaceAll(
2132:                            "([a-zA-Z0-9*])!", "$1 !").split("\\s+");
2133:                    File tmpFile = null;
2134:
2135:                    if (tokens[0].equals("if")) {
2136:                        try {
2137:                            tmpFile = plBlockFile("if");
2138:                        } catch (IOException ioe) {
2139:                            throw new BadSpecial(rb
2140:                                    .getString(SqltoolRB.PL_BLOCK_FAIL), ioe);
2141:                        }
2142:
2143:                        try {
2144:                            if (eval(values)) {
2145:                                SqlFile sf = new SqlFile(tmpFile, false,
2146:                                        userVars);
2147:
2148:                                sf.plMode = true;
2149:                                sf.recursed = true;
2150:
2151:                                // Share the possiblyUncommitted state
2152:                                sf.possiblyUncommitteds = possiblyUncommitteds;
2153:
2154:                                sf.execute(curConn, continueOnError);
2155:                            }
2156:                        } catch (BreakException be) {
2157:                            String beMessage = be.getMessage();
2158:
2159:                            // Handle "if" and plain breaks (by doing nothing)
2160:                            if (beMessage == null || !beMessage.equals("if")) {
2161:                                throw be;
2162:                            }
2163:                        } catch (ContinueException ce) {
2164:                            throw ce;
2165:                        } catch (QuitNow qn) {
2166:                            throw qn;
2167:                        } catch (BadSpecial bs) {
2168:                            bs.appendMessage(rb
2169:                                    .getString(SqltoolRB.IF_MALFORMAT));
2170:                            throw bs;
2171:                        } catch (Exception e) {
2172:                            throw new BadSpecial(rb
2173:                                    .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2174:                        }
2175:                    } else if (tokens[0].equals("while")) {
2176:                        try {
2177:                            tmpFile = plBlockFile("while");
2178:                        } catch (IOException ioe) {
2179:                            throw new BadSpecial(rb
2180:                                    .getString(SqltoolRB.PL_TEMPFILE_FAIL), ioe);
2181:                        }
2182:
2183:                        try {
2184:                            SqlFile sf;
2185:
2186:                            while (eval(values)) {
2187:                                try {
2188:                                    sf = new SqlFile(tmpFile, false, userVars);
2189:                                    sf.recursed = true;
2190:
2191:                                    // Share the possiblyUncommitted state
2192:                                    sf.possiblyUncommitteds = possiblyUncommitteds;
2193:                                    sf.plMode = true;
2194:
2195:                                    sf.execute(curConn, continueOnError);
2196:                                } catch (ContinueException ce) {
2197:                                    String ceMessage = ce.getMessage();
2198:
2199:                                    if (ceMessage != null
2200:                                            && !ceMessage.equals("while")) {
2201:                                        throw ce;
2202:                                    }
2203:                                }
2204:                            }
2205:                        } catch (BreakException be) {
2206:                            String beMessage = be.getMessage();
2207:
2208:                            // Handle "while" and plain breaks (by doing nothing)
2209:                            if (beMessage != null && !beMessage.equals("while")) {
2210:                                throw be;
2211:                            }
2212:                        } catch (QuitNow qn) {
2213:                            throw qn;
2214:                        } catch (BadSpecial bs) {
2215:                            bs.appendMessage(rb
2216:                                    .getString(SqltoolRB.WHILE_MALFORMAT));
2217:                            throw bs;
2218:                        } catch (Exception e) {
2219:                            throw new BadSpecial(rb
2220:                                    .getString(SqltoolRB.PL_BLOCK_FAIL), e);
2221:                        }
2222:                    } else {
2223:                        // Assertion
2224:                        throw new RuntimeException(rb.getString(
2225:                                SqltoolRB.PL_UNKNOWN, tokens[0]));
2226:                    }
2227:
2228:                    if (tmpFile != null && !tmpFile.delete()) {
2229:                        throw new BadSpecial(rb.getString(
2230:                                SqltoolRB.TEMPFILE_REMOVAL_FAIL, tmpFile
2231:                                        .toString()));
2232:                    }
2233:
2234:                    return;
2235:                }
2236:
2237:                m = varsetPattern.matcher(dereference(inString, false));
2238:                if (!m.matches()) {
2239:                    throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN,
2240:                            tokens[0]));
2241:                }
2242:                if (m.groupCount() < 2 || m.groupCount() > 3) {
2243:                    // Assertion
2244:                    throw new RuntimeException(
2245:                            "varset pattern matched but captured "
2246:                                    + m.groupCount() + " groups");
2247:                }
2248:
2249:                String varName = m.group(1);
2250:
2251:                if (varName.indexOf(':') > -1) {
2252:                    throw new BadSpecial(rb.getString(SqltoolRB.PLVAR_NOCOLON));
2253:                }
2254:
2255:                switch (m.group(2).charAt(0)) {
2256:                case '_':
2257:                    silentFetch = true;
2258:                case '~':
2259:                    if (m.groupCount() > 2 && m.group(3) != null) {
2260:                        throw new BadSpecial(rb.getString(
2261:                                SqltoolRB.PLVAR_TILDEDASH_NOMOREARGS, m
2262:                                        .group(3)));
2263:                    }
2264:
2265:                    userVars.remove(varName);
2266:                    updateUserSettings();
2267:
2268:                    fetchingVar = varName;
2269:
2270:                    return;
2271:
2272:                case '=':
2273:                    if (fetchingVar != null && fetchingVar.equals(varName)) {
2274:                        fetchingVar = null;
2275:                    }
2276:
2277:                    if (m.groupCount() > 2 && m.group(3) != null) {
2278:                        userVars.put(varName, m.group(3));
2279:                    } else {
2280:                        userVars.remove(varName);
2281:                    }
2282:                    updateUserSettings();
2283:
2284:                    return;
2285:                }
2286:
2287:                throw new BadSpecial(rb.getString(SqltoolRB.PL_UNKNOWN,
2288:                        tokens[0]));
2289:                // I think this would already be caught in the setvar block above.
2290:            }
2291:
2292:            /*
2293:             * Read a PL block into a new temp file.
2294:             *
2295:             * WARNING!!! foreach blocks are not yet smart about comments
2296:             * and strings.  We just look for a line beginning with a PL "end"
2297:             * command without worrying about comments or quotes (for now).
2298:             *
2299:             * WARNING!!! This is very rudimentary.
2300:             * Users give up all editing and feedback capabilities while
2301:             * in the foreach loop.
2302:             * A better solution would be to pass current input stream to a
2303:             * new SqlFile.execute() with a mode whereby commands are written
2304:             * to a separate history but not executed.
2305:             *
2306:             * @throws IOException
2307:             * @throws SqlToolError
2308:             */
2309:            private File plBlockFile(String seeking) throws IOException,
2310:                    SqlToolError {
2311:                String s;
2312:
2313:                // Have already read the if/while/foreach statement, so we are already
2314:                // at nest level 1.  When we reach nestlevel 1 (read 1 net "end"
2315:                // statement), we're at level 0 and return.
2316:                int nestlevel = 1;
2317:                String curPlCommand;
2318:
2319:                if (seeking == null
2320:                        || ((!seeking.equals("foreach"))
2321:                                && (!seeking.equals("if")) && (!seeking
2322:                                .equals("while")))) {
2323:                    throw new RuntimeException(
2324:                            "Assertion failed.  Unsupported PL block type:  "
2325:                                    + seeking);
2326:                }
2327:
2328:                File tmpFile = File.createTempFile("sqltool-", ".sql");
2329:                PrintWriter pw = new PrintWriter(
2330:                        (charset == null) ? (new OutputStreamWriter(
2331:                                new FileOutputStream(tmpFile)))
2332:                                : (new OutputStreamWriter(new FileOutputStream(
2333:                                        tmpFile), charset)));
2334:                // Replace with just "(new FileOutputStream(file), charset)"
2335:                // once use defaultCharset from Java 1.5 in charset init. above.
2336:
2337:                try {
2338:                    pw.println("/* " + (new java.util.Date()) + ". "
2339:                            + getClass().getName() + " PL block. */");
2340:                    pw.println();
2341:                    Matcher m;
2342:
2343:                    while (true) {
2344:                        s = br.readLine();
2345:
2346:                        if (s == null) {
2347:                            s = rb.getString(SqltoolRB.PL_BLOCK_UNTERMINATED,
2348:                                    seeking);
2349:                            errprintln(s);
2350:                            throw new SqlToolError(s);
2351:                        }
2352:
2353:                        curLinenum++;
2354:
2355:                        m = plPattern.matcher(s);
2356:                        if (m.matches() && m.groupCount() > 0
2357:                                && m.group(1) != null) {
2358:                            String[] tokens = m.group(1).split("\\s+");
2359:                            curPlCommand = tokens[0];
2360:
2361:                            // PL COMMAND of some sort.
2362:                            if (curPlCommand.equals(seeking)) {
2363:                                nestlevel++;
2364:                            } else if (curPlCommand.equals("end")) {
2365:                                if (tokens.length < 2) {
2366:                                    s = rb.getString(SqltoolRB.END_SYNTAX, "1");
2367:                                    errprintln(s);
2368:                                    throw new SqlToolError(s);
2369:                                }
2370:
2371:                                String inType = tokens[1];
2372:
2373:                                if (inType.equals(seeking)) {
2374:                                    nestlevel--;
2375:
2376:                                    if (nestlevel < 1) {
2377:                                        break;
2378:                                    }
2379:                                }
2380:
2381:                                if ((!inType.equals("foreach"))
2382:                                        && (!inType.equals("if"))
2383:                                        && (!inType.equals("while"))) {
2384:                                    s = rb.getString(SqltoolRB.END_SYNTAX, "2");
2385:                                    errprintln(s);
2386:                                    throw new SqlToolError(s);
2387:                                }
2388:                            }
2389:                        }
2390:
2391:                        pw.println(s);
2392:                    }
2393:
2394:                    pw.flush();
2395:                } finally {
2396:                    pw.close();
2397:                }
2398:
2399:                return tmpFile;
2400:            }
2401:
2402:            /**
2403:             * Wrapper methods so don't need to call x(..., false) in most cases.
2404:             */
2405:            /* Unused.  Enable when/if need.
2406:            private void stdprintln() {
2407:                stdprintln(false);
2408:            }
2409:             */
2410:
2411:            private void stdprint(String s) {
2412:                stdprint(s, false);
2413:            }
2414:
2415:            private void stdprintln(String s) {
2416:                stdprintln(s, false);
2417:            }
2418:
2419:            /**
2420:             * Encapsulates normal output.
2421:             *
2422:             * Conditionally HTML-ifies output.
2423:             */
2424:            private void stdprintln(boolean queryOutput) {
2425:                if (htmlMode) {
2426:                    psStd.println("<BR>");
2427:                } else {
2428:                    psStd.println();
2429:                }
2430:
2431:                if (queryOutput && pwQuery != null) {
2432:                    if (htmlMode) {
2433:                        pwQuery.println("<BR>");
2434:                    } else {
2435:                        pwQuery.println();
2436:                    }
2437:
2438:                    pwQuery.flush();
2439:                }
2440:            }
2441:
2442:            /**
2443:             * Encapsulates error output.
2444:             *
2445:             * Conditionally HTML-ifies error output.
2446:             */
2447:            private void errprint(String s) {
2448:                psErr
2449:                        .print(htmlMode ? ("<DIV style='color:white; background: red; "
2450:                                + "font-weight: bold'>" + s + "</DIV>")
2451:                                : s);
2452:            }
2453:
2454:            /**
2455:             * Encapsulates error output.
2456:             *
2457:             * Conditionally HTML-ifies error output.
2458:             */
2459:            private void errprintln(String s) {
2460:                psErr
2461:                        .println(htmlMode ? ("<DIV style='color:white; background: red; "
2462:                                + "font-weight: bold'>" + s + "</DIV>")
2463:                                : s);
2464:            }
2465:
2466:            /**
2467:             * Encapsulates normal output.
2468:             *
2469:             * Conditionally HTML-ifies output.
2470:             */
2471:            private void stdprint(String s, boolean queryOutput) {
2472:                psStd.print(htmlMode ? ("<P>" + s + "</P>") : s);
2473:
2474:                if (queryOutput && pwQuery != null) {
2475:                    pwQuery.print(htmlMode ? ("<P>" + s + "</P>") : s);
2476:                    pwQuery.flush();
2477:                }
2478:            }
2479:
2480:            /**
2481:             * Encapsulates normal output.
2482:             *
2483:             * Conditionally HTML-ifies output.
2484:             */
2485:            private void stdprintln(String s, boolean queryOutput) {
2486:                psStd.println(htmlMode ? ("<P>" + s + "</P>") : s);
2487:
2488:                if (queryOutput && pwQuery != null) {
2489:                    pwQuery.println(htmlMode ? ("<P>" + s + "</P>") : s);
2490:                    pwQuery.flush();
2491:                }
2492:            }
2493:
2494:            // Just because users may be used to seeing "[null]" in normal
2495:            // SqlFile output, we use the same default value for null in DSV
2496:            // files, but this DSV null representation can be changed to anything.
2497:            private static final String DEFAULT_NULL_REP = "[null]";
2498:            private static final String DEFAULT_ROW_DELIM = LS;
2499:            private static final String DEFAULT_COL_DELIM = "|";
2500:            private static final String DEFAULT_SKIP_PREFIX = "#";
2501:            private static final int DEFAULT_ELEMENT = 0, HSQLDB_ELEMENT = 1,
2502:                    ORACLE_ELEMENT = 2;
2503:
2504:            // These do not specify order listed, just inclusion.
2505:            private static final int[] listMDSchemaCols = { 1 };
2506:            private static final int[] listMDIndexCols = { 2, 6, 3, 9, 4, 10,
2507:                    11 };
2508:
2509:            /** Column numbering starting at 1. */
2510:            private static final int[][] listMDTableCols = { { 2, 3 }, // Default
2511:                    { 2, 3 }, // HSQLDB
2512:                    { 2, 3 }, // Oracle
2513:            };
2514:
2515:            /**
2516:             * SYS and SYSTEM are the only base system accounts in Oracle, however,
2517:             * from an empirical perspective, all of these other accounts are
2518:             * system accounts because <UL>
2519:             * <LI> they are hidden from the casual user
2520:             * <LI> they are created by the installer at installation-time
2521:             * <LI> they are used automatically by the Oracle engine when the
2522:             *      specific Oracle sub-product is used
2523:             * <LI> the accounts should not be <I>messed with</I> by database users
2524:             * <LI> the accounts should certainly not be used if the specific
2525:             *      Oracle sub-product is going to be used.
2526:             * </UL>
2527:             *
2528:             * General advice:  If you aren't going to use an Oracle sub-product,
2529:             * then <B>don't install it!</B>
2530:             * Don't blindly accept default when running OUI.
2531:             *
2532:             * If users also see accounts that they didn't create with names like
2533:             * SCOTT, ADAMS, JONES, CLARK, BLAKE, OE, PM, SH, QS, QS_*, these
2534:             * contain sample data and the schemas can safely be removed.
2535:             */
2536:            private static final String[] oracleSysSchemas = { "SYS", "SYSTEM",
2537:                    "OUTLN", "DBSNMP", "OUTLN", "MDSYS", "ORDSYS",
2538:                    "ORDPLUGINS", "CTXSYS", "DSSYS", "PERFSTAT", "WKPROXY",
2539:                    "WKSYS", "WMSYS", "XDB", "ANONYMOUS", "ODM", "ODM_MTR",
2540:                    "OLAPSYS", "TRACESVR", "REPADMIN" };
2541:
2542:            /**
2543:             * Lists available database tables.
2544:             *
2545:             * When a filter is given, we assume that there are no lower-case
2546:             * characters in the object names (which would require "quotes" when
2547:             * creating them).
2548:             *
2549:             * @throws BadSpecial usually wrap a cause (which cause is a
2550:             *                    SQLException in some cases).
2551:             * @throws SqlToolError passed through from other methods in this class.
2552:             */
2553:            private void listTables(char c, String inFilter) throws BadSpecial,
2554:                    SqlToolError {
2555:                String schema = null;
2556:                int[] listSet = null;
2557:                String[] types = null;
2558:
2559:                /** For workaround for \T for Oracle */
2560:                String[] additionalSchemas = null;
2561:
2562:                /** This is for specific non-getTable() queries */
2563:                Statement statement = null;
2564:                ResultSet rs = null;
2565:                String narrower = "";
2566:                /*
2567:                 * Doing case-sensitive filters now, for greater portability.
2568:                String                    filter = ((inFilter == null)
2569:                                                  ? null : inFilter.toUpperCase());
2570:                 */
2571:                String filter = inFilter;
2572:
2573:                try {
2574:                    DatabaseMetaData md = curConn.getMetaData();
2575:                    String dbProductName = md.getDatabaseProductName();
2576:
2577:                    //System.err.println("DB NAME = (" + dbProductName + ')');
2578:                    // Database-specific table filtering.
2579:
2580:                    /* 3 Types of actions:
2581:                     *    1) Special handling.  Return from the "case" block directly.
2582:                     *    2) Execute a specific query.  Set statement in the "case".
2583:                     *    3) Otherwise, set filter info for dbmd.getTable() in the
2584:                     *       "case".
2585:                     */
2586:                    types = new String[1];
2587:
2588:                    switch (c) {
2589:                    case '*':
2590:                        types = null;
2591:                        break;
2592:
2593:                    case 'S':
2594:                        if (dbProductName.indexOf("Oracle") > -1) {
2595:                            errprintln(rb.getString(SqltoolRB.VENDOR_ORACLE_DS));
2596:
2597:                            types[0] = "TABLE";
2598:                            schema = "SYS";
2599:                            additionalSchemas = oracleSysSchemas;
2600:                        } else {
2601:                            types[0] = "SYSTEM TABLE";
2602:                        }
2603:                        break;
2604:
2605:                    case 's':
2606:                        if (dbProductName.indexOf("HSQL") > -1) {
2607:                            //  HSQLDB does not consider Sequences as "tables",
2608:                            //  hence we do not list them in
2609:                            //  DatabaseMetaData.getTables().
2610:                            if (filter != null
2611:                                    && filter.charAt(filter.length() - 1) == '.') {
2612:                                narrower = "\nWHERE sequence_schema = '"
2613:                                        + filter.substring(0,
2614:                                                filter.length() - 1) + "'";
2615:                                filter = null;
2616:                            }
2617:
2618:                            statement = curConn.createStatement();
2619:
2620:                            statement
2621:                                    .execute("SELECT sequence_schema, sequence_name FROM "
2622:                                            + "information_schema.system_sequences"
2623:                                            + narrower);
2624:                        } else {
2625:                            types[0] = "SEQUENCE";
2626:                        }
2627:                        break;
2628:
2629:                    case 'r':
2630:                        if (dbProductName.indexOf("HSQL") > -1) {
2631:                            statement = curConn.createStatement();
2632:
2633:                            statement
2634:                                    .execute("SELECT authorization_name FROM "
2635:                                            + "information_schema.system_authorizations\n"
2636:                                            + "WHERE authorization_type = 'ROLE'\n"
2637:                                            + "ORDER BY authorization_name");
2638:                        } else if (dbProductName
2639:                                .indexOf("Adaptive Server Enterprise") > -1) {
2640:                            // This is the basic Sybase server.  Sybase also has
2641:                            // their "Anywhere", ASA (for embedded), and replication
2642:                            // databases, but I don't know the Metadata strings for
2643:                            // those.
2644:                            statement = curConn.createStatement();
2645:
2646:                            statement
2647:                                    .execute("SELECT name FROM syssrvroles ORDER BY name");
2648:                        } else if (dbProductName.indexOf("Apache Derby") > -1) {
2649:                            throw new BadSpecial(rb
2650:                                    .getString(SqltoolRB.VENDOR_DERBY_DR));
2651:                        } else {
2652:                            throw new BadSpecial(rb.getString(
2653:                                    SqltoolRB.VENDOR_NOSUP_D, "r"));
2654:                        }
2655:                        break;
2656:
2657:                    case 'u':
2658:                        if (dbProductName.indexOf("HSQL") > -1) {
2659:                            statement = curConn.createStatement();
2660:
2661:                            statement.execute("SELECT user, admin FROM "
2662:                                    + "information_schema.system_users\n"
2663:                                    + "ORDER BY user");
2664:                        } else if (dbProductName.indexOf("Oracle") > -1) {
2665:                            statement = curConn.createStatement();
2666:
2667:                            statement
2668:                                    .execute("SELECT username, created FROM all_users "
2669:                                            + "ORDER BY username");
2670:                        } else if (dbProductName.indexOf("PostgreSQL") > -1) {
2671:                            statement = curConn.createStatement();
2672:
2673:                            statement
2674:                                    .execute("SELECT usename, usesuper FROM pg_catalog.pg_user "
2675:                                            + "ORDER BY usename");
2676:                        } else if (dbProductName
2677:                                .indexOf("Adaptive Server Enterprise") > -1) {
2678:                            // This is the basic Sybase server.  Sybase also has
2679:                            // their "Anywhere", ASA (for embedded), and replication
2680:                            // databases, but I don't know the Metadata strings for
2681:                            // those.
2682:                            statement = curConn.createStatement();
2683:
2684:                            statement
2685:                                    .execute("SELECT name, accdate, fullname FROM syslogins "
2686:                                            + "ORDER BY name");
2687:                        } else if (dbProductName.indexOf("Apache Derby") > -1) {
2688:                            throw new BadSpecial(rb
2689:                                    .getString(SqltoolRB.VENDOR_DERBY_DU));
2690:                        } else {
2691:                            throw new BadSpecial(rb.getString(
2692:                                    SqltoolRB.VENDOR_NOSUP_D, "u"));
2693:                        }
2694:                        break;
2695:
2696:                    case 'a':
2697:                        if (dbProductName.indexOf("HSQL") > -1) {
2698:                            //  HSQLDB Aliases are not the same things as the
2699:                            //  aliases listed in DatabaseMetaData.getTables().
2700:                            if (filter != null
2701:                                    && filter.charAt(filter.length() - 1) == '.') {
2702:                                narrower = "\nWHERE alias_schem = '"
2703:                                        + filter.substring(0,
2704:                                                filter.length() - 1) + "'";
2705:                                filter = null;
2706:                            }
2707:
2708:                            statement = curConn.createStatement();
2709:
2710:                            statement.execute("SELECT alias_schem, alias FROM "
2711:                                    + "information_schema.system_aliases"
2712:                                    + narrower);
2713:                        } else {
2714:                            types[0] = "ALIAS";
2715:                        }
2716:                        break;
2717:
2718:                    case 't':
2719:                        excludeSysSchemas = (dbProductName.indexOf("Oracle") > -1);
2720:                        types[0] = "TABLE";
2721:                        break;
2722:
2723:                    case 'v':
2724:                        types[0] = "VIEW";
2725:                        break;
2726:
2727:                    case 'n':
2728:                        rs = md.getSchemas();
2729:
2730:                        if (rs == null) {
2731:                            throw new BadSpecial(
2732:                                    "Failed to get metadata from database");
2733:                        }
2734:
2735:                        displayResultSet(null, rs, listMDSchemaCols, filter);
2736:
2737:                        return;
2738:
2739:                    case 'i':
2740:
2741:                        // Some databases require to specify table, some don't.
2742:                        /*
2743:                        if (filter == null) {
2744:                            throw new BadSpecial("You must specify the index's "
2745:                                    + "table as argument to \\di");
2746:                        }
2747:                         */
2748:                        String table = null;
2749:
2750:                        if (filter != null) {
2751:                            int dotat = filter.indexOf('.');
2752:
2753:                            schema = ((dotat > 0) ? filter.substring(0, dotat)
2754:                                    : null);
2755:
2756:                            if (dotat < filter.length() - 1) {
2757:                                // Not a schema-only specifier
2758:                                table = ((dotat > 0) ? filter
2759:                                        .substring(dotat + 1) : filter);
2760:                            }
2761:
2762:                            filter = null;
2763:                        }
2764:
2765:                        // N.b. Oracle incorrectly reports the INDEX SCHEMA as
2766:                        // the TABLE SCHEMA.  The Metadata structure seems to
2767:                        // be designed with the assumption that the INDEX schema
2768:                        // will be the same as the TABLE schema.
2769:                        rs = md.getIndexInfo(null, schema, table, false, true);
2770:
2771:                        if (rs == null) {
2772:                            throw new BadSpecial(
2773:                                    "Failed to get metadata from database");
2774:                        }
2775:
2776:                        displayResultSet(null, rs, listMDIndexCols, null);
2777:
2778:                        return;
2779:
2780:                    default:
2781:                        throw new BadSpecial(rb.getString(
2782:                                SqltoolRB.SPECIAL_D_UNKNOWN, Character
2783:                                        .toString(c))
2784:                                + LS + D_OPTIONS_TEXT);
2785:                    }
2786:
2787:                    if (statement == null) {
2788:                        if (dbProductName.indexOf("HSQL") > -1) {
2789:                            listSet = listMDTableCols[HSQLDB_ELEMENT];
2790:                        } else if (dbProductName.indexOf("Oracle") > -1) {
2791:                            listSet = listMDTableCols[ORACLE_ELEMENT];
2792:                        } else {
2793:                            listSet = listMDTableCols[DEFAULT_ELEMENT];
2794:                        }
2795:
2796:                        if (schema == null && filter != null
2797:                                && filter.charAt(filter.length() - 1) == '.') {
2798:                            schema = filter.substring(0, filter.length() - 1);
2799:                            filter = null;
2800:                        }
2801:                    }
2802:
2803:                    rs = ((statement == null) ? md.getTables(null, schema,
2804:                            null, types) : statement.getResultSet());
2805:
2806:                    if (rs == null) {
2807:                        throw new BadSpecial(rb
2808:                                .getString(SqltoolRB.METADATA_FETCH_FAIL));
2809:                    }
2810:
2811:                    displayResultSet(null, rs, listSet, filter);
2812:
2813:                    if (additionalSchemas != null) {
2814:                        for (int i = 1; i < additionalSchemas.length; i++) {
2815:                            /*
2816:                             * Inefficient, but we have to do each successful query
2817:                             * twice in order to prevent calling displayResultSet
2818:                             * for empty/non-existent schemas
2819:                             */
2820:                            rs = md.getTables(null, additionalSchemas[i], null,
2821:                                    types);
2822:
2823:                            if (rs == null) {
2824:                                throw new BadSpecial(rb.getString(
2825:                                        SqltoolRB.METADATA_FETCH_FAILFOR,
2826:                                        additionalSchemas[i]));
2827:                            }
2828:
2829:                            if (!rs.next()) {
2830:                                continue;
2831:                            }
2832:
2833:                            displayResultSet(null, md.getTables(null,
2834:                                    additionalSchemas[i], null, types),
2835:                                    listSet, filter);
2836:                        }
2837:                    }
2838:                } catch (SQLException se) {
2839:                    throw new BadSpecial(rb
2840:                            .getString(SqltoolRB.METADATA_FETCH_FAIL), se);
2841:                } catch (NullPointerException npe) {
2842:                    throw new BadSpecial(rb
2843:                            .getString(SqltoolRB.METADATA_FETCH_FAIL), npe);
2844:                } finally {
2845:                    excludeSysSchemas = false;
2846:
2847:                    if (rs != null) {
2848:                        rs = null;
2849:                    }
2850:
2851:                    if (statement != null) {
2852:                        try {
2853:                            statement.close();
2854:                        } catch (Exception e) {
2855:                        }
2856:
2857:                        statement = null;
2858:                    }
2859:                }
2860:            }
2861:
2862:            private boolean excludeSysSchemas = false;
2863:
2864:            /**
2865:             * Process the contents of Edit Buffer as an SQL Statement
2866:             *
2867:             * @throws SQLException thrown by JDBC driver.
2868:             * @throws SqlToolError all other errors.
2869:             */
2870:            private void processSQL() throws SQLException, SqlToolError {
2871:                // Really don't know whether to take the network latency hit here
2872:                // in order to check autoCommit in order to set
2873:                // possiblyUncommitteds more accurately.
2874:                // I'm going with "NO" for now, since autoCommit will usually be off.
2875:                // If we do ever check autocommit, we have to keep track of the
2876:                // autocommit state when every SQL statement is run, since I may
2877:                // be able to have uncommitted DML, turn autocommit on, then run
2878:                // other DDL with autocommit on.  As a result, I could be running
2879:                // SQL commands with autotommit on but still have uncommitted mods.
2880:                // (For all I know, the database could commit or rollback whenever
2881:                // the autocommit option is changed, and that behavior could be
2882:                // DB-specific).
2883:                lastSqlStatement = (plMode ? dereference(buffer, true) : buffer);
2884:                Statement statement = null;
2885:
2886:                if (doPrepare) {
2887:                    if (lastSqlStatement.indexOf('?') < 1) {
2888:                        lastSqlStatement = null;
2889:                        throw new SqlToolError(rb
2890:                                .getString(SqltoolRB.PREPARE_DEMANDQM));
2891:                    }
2892:
2893:                    doPrepare = false;
2894:
2895:                    PreparedStatement ps = curConn
2896:                            .prepareStatement(lastSqlStatement);
2897:
2898:                    if (prepareVar == null) {
2899:                        if (binBuffer == null) {
2900:                            lastSqlStatement = null;
2901:                            throw new SqlToolError(rb
2902:                                    .getString(SqltoolRB.BINBUFFER_EMPTY));
2903:                        }
2904:
2905:                        ps.setBytes(1, binBuffer);
2906:                    } else {
2907:                        String val = (String) userVars.get(prepareVar);
2908:
2909:                        if (val == null) {
2910:                            lastSqlStatement = null;
2911:                            throw new SqlToolError(rb.getString(
2912:                                    SqltoolRB.PLVAR_UNDEFINED, prepareVar));
2913:                        }
2914:
2915:                        prepareVar = null;
2916:
2917:                        ps.setString(1, val);
2918:                    }
2919:
2920:                    ps.executeUpdate();
2921:
2922:                    statement = ps;
2923:                } else {
2924:                    statement = curConn.createStatement();
2925:
2926:                    statement.execute(lastSqlStatement);
2927:                }
2928:
2929:                possiblyUncommitteds.set(true);
2930:
2931:                try {
2932:                    displayResultSet(statement, statement.getResultSet(), null,
2933:                            null);
2934:                } finally {
2935:                    try {
2936:                        statement.close();
2937:                    } catch (Exception e) {
2938:                    }
2939:                }
2940:                lastSqlStatement = null;
2941:            }
2942:
2943:            /**
2944:             * Display the given result set for user.
2945:             * The last 3 params are to narrow down records and columns where
2946:             * that can not be done with a where clause (like in metadata queries).
2947:             *
2948:             * @param statement The SQL Statement that the result set is for.
2949:             *                  (This is so we can get the statement's update count.
2950:             *                  Can be null for non-update queries.)
2951:             * @param r         The ResultSet to display.
2952:             * @param incCols   Optional list of which columns to include (i.e., if
2953:             *                  given, then other columns will be skipped).
2954:             * @param incFilter Optional case-insensitive substring.
2955:             *                  Rows are skipped which to not contain this substring.
2956:             * @throws SQLException thrown by JDBC driver.
2957:             * @throws SqlToolError all other errors.
2958:             */
2959:            private void displayResultSet(Statement statement, ResultSet r,
2960:                    int[] incCols, String filter) throws SQLException,
2961:                    SqlToolError {
2962:                java.sql.Timestamp ts;
2963:                int dotAt;
2964:                int updateCount = (statement == null) ? -1 : statement
2965:                        .getUpdateCount();
2966:                boolean silent = silentFetch;
2967:                boolean binary = fetchBinary;
2968:
2969:                silentFetch = false;
2970:                fetchBinary = false;
2971:
2972:                if (excludeSysSchemas) {
2973:                    stdprintln(rb.getString(SqltoolRB.VENDOR_NOSUP_SYSSCHEMAS));
2974:                }
2975:
2976:                switch (updateCount) {
2977:                case -1:
2978:                    if (r == null) {
2979:                        stdprintln(rb.getString(SqltoolRB.NORESULT), true);
2980:
2981:                        break;
2982:                    }
2983:
2984:                    ResultSetMetaData m = r.getMetaData();
2985:                    int cols = m.getColumnCount();
2986:                    int incCount = (incCols == null) ? cols : incCols.length;
2987:                    String val;
2988:                    List rows = new ArrayList();
2989:                    String[] headerArray = null;
2990:                    String[] fieldArray;
2991:                    int[] maxWidth = new int[incCount];
2992:                    int insi;
2993:                    boolean skip;
2994:
2995:                    // STEP 1: GATHER DATA
2996:                    if (!htmlMode) {
2997:                        for (int i = 0; i < maxWidth.length; i++) {
2998:                            maxWidth[i] = 0;
2999:                        }
3000:                    }
3001:
3002:                    boolean[] rightJust = new boolean[incCount];
3003:                    int[] dataType = new int[incCount];
3004:                    boolean[] autonulls = new boolean[incCount];
3005:
3006:                    insi = -1;
3007:                    headerArray = new String[incCount];
3008:
3009:                    for (int i = 1; i <= cols; i++) {
3010:                        if (incCols != null) {
3011:                            skip = true;
3012:
3013:                            for (int j = 0; j < incCols.length; j++) {
3014:                                if (i == incCols[j]) {
3015:                                    skip = false;
3016:                                }
3017:                            }
3018:
3019:                            if (skip) {
3020:                                continue;
3021:                            }
3022:                        }
3023:
3024:                        headerArray[++insi] = m.getColumnLabel(i);
3025:                        dataType[insi] = m.getColumnType(i);
3026:                        rightJust[insi] = false;
3027:                        autonulls[insi] = true;
3028:
3029:                        switch (dataType[insi]) {
3030:                        case java.sql.Types.BIGINT:
3031:                        case java.sql.Types.BIT:
3032:                        case java.sql.Types.DECIMAL:
3033:                        case java.sql.Types.DOUBLE:
3034:                        case java.sql.Types.FLOAT:
3035:                        case java.sql.Types.INTEGER:
3036:                        case java.sql.Types.NUMERIC:
3037:                        case java.sql.Types.REAL:
3038:                        case java.sql.Types.SMALLINT:
3039:                        case java.sql.Types.TINYINT:
3040:                            rightJust[insi] = true;
3041:                            break;
3042:
3043:                        case java.sql.Types.VARBINARY:
3044:                        case java.sql.Types.VARCHAR:
3045:                        case java.sql.Types.ARRAY:
3046:                            // Guessing at how to handle ARRAY.
3047:                        case java.sql.Types.BLOB:
3048:                        case java.sql.Types.CLOB:
3049:                        case java.sql.Types.LONGVARBINARY:
3050:                        case java.sql.Types.LONGVARCHAR:
3051:                            autonulls[insi] = false;
3052:                            break;
3053:                        }
3054:
3055:                        if (htmlMode) {
3056:                            continue;
3057:                        }
3058:
3059:                        if (headerArray[insi].length() > maxWidth[insi]) {
3060:                            maxWidth[insi] = headerArray[insi].length();
3061:                        }
3062:                    }
3063:
3064:                    boolean filteredOut;
3065:
3066:                    while (r.next()) {
3067:                        fieldArray = new String[incCount];
3068:                        insi = -1;
3069:                        filteredOut = filter != null;
3070:
3071:                        for (int i = 1; i <= cols; i++) {
3072:                            // This is the only case where we can save a data
3073:                            // read by recognizing we don't need this datum early.
3074:                            if (incCols != null) {
3075:                                skip = true;
3076:
3077:                                for (int j = 0; j < incCols.length; j++) {
3078:                                    if (i == incCols[j]) {
3079:                                        skip = false;
3080:                                    }
3081:                                }
3082:
3083:                                if (skip) {
3084:                                    continue;
3085:                                }
3086:                            }
3087:
3088:                            // This row may still be ditched, but it is now
3089:                            // certain that we need to increment the fieldArray
3090:                            // index.
3091:                            ++insi;
3092:
3093:                            if (!SqlFile.canDisplayType(dataType[insi])) {
3094:                                binary = true;
3095:                            }
3096:
3097:                            val = null;
3098:
3099:                            if (!binary) {
3100:                                /*
3101:                                 * The special formatting for all time-related
3102:                                 * fields is because the most popular current
3103:                                 * databases are extremely inconsistent about
3104:                                 * what resolution is returned for the same types.
3105:                                 * In my experience so far, Dates MAY have
3106:                                 * resolution down to second, but only TIMESTAMPs
3107:                                 * support sub-second res. (and always can).
3108:                                 * On top of that there is no consistency across
3109:                                 * getObject().toString().  Oracle doesn't even
3110:                                 * implement it for their custom TIMESTAMP type.
3111:                                 */
3112:                                switch (dataType[insi]) {
3113:                                case java.sql.Types.TIMESTAMP:
3114:                                case java.sql.Types.DATE:
3115:                                case java.sql.Types.TIME:
3116:                                    ts = r.getTimestamp(i);
3117:                                    val = ((ts == null) ? null : ts.toString());
3118:                                    // Following block truncates non-zero
3119:                                    // sub-seconds from time types OTHER than
3120:                                    // TIMESTAMP.
3121:                                    if (dataType[insi] != java.sql.Types.TIMESTAMP
3122:                                            && val != null) {
3123:                                        dotAt = val.lastIndexOf('.');
3124:                                        for (int z = dotAt + 1; z < val
3125:                                                .length(); z++) {
3126:                                            if (val.charAt(z) != '0') {
3127:                                                dotAt = 0;
3128:                                                break;
3129:                                            }
3130:                                        }
3131:                                        if (dotAt > 1) {
3132:                                            val = val.substring(0, dotAt);
3133:                                        }
3134:                                    }
3135:                                    break;
3136:                                default:
3137:                                    val = r.getString(i);
3138:
3139:                                    // If we tried to get a String but it
3140:                                    // failed, try getting it with a String
3141:                                    // Stream
3142:                                    if (val == null) {
3143:                                        try {
3144:                                            val = streamToString(r
3145:                                                    .getAsciiStream(i), charset);
3146:                                        } catch (Exception e) {
3147:                                        }
3148:                                    }
3149:                                }
3150:                            }
3151:
3152:                            if (binary || (val == null && !r.wasNull())) {
3153:                                if (pwDsv != null) {
3154:                                    throw new SqlToolError(rb
3155:                                            .getString(SqltoolRB.DSV_BINCOL));
3156:                                }
3157:
3158:                                // DB has a value but we either explicitly want
3159:                                // it as binary, or we failed to get it as String.
3160:                                try {
3161:                                    binBuffer = SqlFile.streamToBytes(r
3162:                                            .getBinaryStream(i));
3163:                                } catch (IOException ioe) {
3164:                                    throw new SqlToolError(
3165:                                            "Failed to read value using stream",
3166:                                            ioe);
3167:                                }
3168:
3169:                                stdprintln(rb
3170:                                        .getString(
3171:                                                SqltoolRB.BINBUF_WRITE,
3172:                                                Integer
3173:                                                        .toString(binBuffer.length),
3174:                                                headerArray[insi],
3175:                                                SqlFile
3176:                                                        .sqlTypeToString(dataType[insi])));
3177:
3178:                                return;
3179:                            }
3180:
3181:                            if (excludeSysSchemas && val != null && i == 2) {
3182:                                for (int z = 0; z < oracleSysSchemas.length; z++) {
3183:                                    if (val.equals(oracleSysSchemas[z])) {
3184:                                        filteredOut = true;
3185:
3186:                                        break;
3187:                                    }
3188:                                }
3189:                            }
3190:
3191:                            userVars.put("?", ((val == null) ? nullRepToken
3192:                                    : val));
3193:                            if (fetchingVar != null) {
3194:                                userVars.put(fetchingVar, userVars.get("?"));
3195:                                updateUserSettings();
3196:
3197:                                fetchingVar = null;
3198:                            }
3199:
3200:                            if (silent) {
3201:                                return;
3202:                            }
3203:
3204:                            // We do not omit rows here.  We collect information
3205:                            // so we can make the decision after all rows are
3206:                            // read in.
3207:                            if (filter != null
3208:                                    && (val == null || val.indexOf(filter) > -1)) {
3209:                                filteredOut = false;
3210:                            }
3211:
3212:                            ///////////////////////////////
3213:                            // A little tricky here.  fieldArray[] MUST get set.
3214:                            if (val == null && pwDsv == null) {
3215:                                if (dataType[insi] == java.sql.Types.VARCHAR) {
3216:                                    fieldArray[insi] = (htmlMode ? "<I>null</I>"
3217:                                            : nullRepToken);
3218:                                } else {
3219:                                    fieldArray[insi] = "";
3220:                                }
3221:                            } else {
3222:                                fieldArray[insi] = val;
3223:                            }
3224:
3225:                            ///////////////////////////////
3226:                            if (htmlMode || pwDsv != null) {
3227:                                continue;
3228:                            }
3229:
3230:                            if (fieldArray[insi].length() > maxWidth[insi]) {
3231:                                maxWidth[insi] = fieldArray[insi].length();
3232:                            }
3233:                        }
3234:
3235:                        if (!filteredOut) {
3236:                            rows.add(fieldArray);
3237:                        }
3238:                    }
3239:
3240:                    // STEP 2: DISPLAY DATA  (= 2a OR 2b)
3241:                    // STEP 2a (Non-DSV)
3242:                    if (pwDsv == null) {
3243:                        condlPrintln("<TABLE border='1'>", true);
3244:
3245:                        if (incCount > 1) {
3246:                            condlPrint(SqlFile.htmlRow(COL_HEAD) + LS + PRE_TD,
3247:                                    true);
3248:
3249:                            for (int i = 0; i < headerArray.length; i++) {
3250:                                condlPrint("<TD>" + headerArray[i] + "</TD>",
3251:                                        true);
3252:                                condlPrint(
3253:                                        ((i > 0) ? SqlFile.spaces(2) : "")
3254:                                                + SqlFile
3255:                                                        .pad(
3256:                                                                headerArray[i],
3257:                                                                maxWidth[i],
3258:                                                                rightJust[i],
3259:                                                                (i < headerArray.length - 1 || rightJust[i])),
3260:                                        false);
3261:                            }
3262:
3263:                            condlPrintln(LS + PRE_TR + "</TR>", true);
3264:                            condlPrintln("", false);
3265:
3266:                            if (!htmlMode) {
3267:                                for (int i = 0; i < headerArray.length; i++) {
3268:                                    condlPrint(((i > 0) ? SqlFile.spaces(2)
3269:                                            : "")
3270:                                            + SqlFile.divider(maxWidth[i]),
3271:                                            false);
3272:                                }
3273:
3274:                                condlPrintln("", false);
3275:                            }
3276:                        }
3277:
3278:                        for (int i = 0; i < rows.size(); i++) {
3279:                            condlPrint(SqlFile
3280:                                    .htmlRow(((i % 2) == 0) ? COL_EVEN
3281:                                            : COL_ODD)
3282:                                    + LS + PRE_TD, true);
3283:
3284:                            fieldArray = (String[]) rows.get(i);
3285:
3286:                            for (int j = 0; j < fieldArray.length; j++) {
3287:                                condlPrint("<TD>" + fieldArray[j] + "</TD>",
3288:                                        true);
3289:                                condlPrint(
3290:                                        ((j > 0) ? SqlFile.spaces(2) : "")
3291:                                                + SqlFile
3292:                                                        .pad(
3293:                                                                fieldArray[j],
3294:                                                                maxWidth[j],
3295:                                                                rightJust[j],
3296:                                                                (j < fieldArray.length - 1 || rightJust[j])),
3297:                                        false);
3298:                            }
3299:
3300:                            condlPrintln(LS + PRE_TR + "</TR>", true);
3301:                            condlPrintln("", false);
3302:                        }
3303:
3304:                        condlPrintln("</TABLE>", true);
3305:
3306:                        if (rows.size() != 1) {
3307:                            stdprintln(LS
3308:                                    + rb.getString(SqltoolRB.ROWS_FETCHED, rows
3309:                                            .size()), true);
3310:                        }
3311:
3312:                        condlPrintln("<HR>", true);
3313:
3314:                        break;
3315:                    }
3316:
3317:                    // STEP 2b (DSV)
3318:                    if (incCount > 0) {
3319:                        for (int i = 0; i < headerArray.length; i++) {
3320:                            dsvSafe(headerArray[i]);
3321:                            pwDsv.print(headerArray[i]);
3322:
3323:                            if (i < headerArray.length - 1) {
3324:                                pwDsv.print(dsvColDelim);
3325:                            }
3326:                        }
3327:
3328:                        pwDsv.print(dsvRowDelim);
3329:                    }
3330:
3331:                    for (int i = 0; i < rows.size(); i++) {
3332:                        fieldArray = (String[]) rows.get(i);
3333:
3334:                        for (int j = 0; j < fieldArray.length; j++) {
3335:                            dsvSafe(fieldArray[j]);
3336:                            pwDsv
3337:                                    .print((fieldArray[j] == null) ? (autonulls[j] ? ""
3338:                                            : nullRepToken)
3339:                                            : fieldArray[j]);
3340:
3341:                            if (j < fieldArray.length - 1) {
3342:                                pwDsv.print(dsvColDelim);
3343:                            }
3344:                        }
3345:
3346:                        pwDsv.print(dsvRowDelim);
3347:                    }
3348:
3349:                    stdprintln(rb.getString(SqltoolRB.ROWS_FETCHED_DSV, rows
3350:                            .size()));
3351:                    break;
3352:
3353:                default:
3354:                    userVars.put("?", Integer.toString(updateCount));
3355:                    if (fetchingVar != null) {
3356:                        userVars.put(fetchingVar, userVars.get("?"));
3357:                        updateUserSettings();
3358:                        fetchingVar = null;
3359:                    }
3360:
3361:                    if (updateCount != 0) {
3362:                        stdprintln((updateCount == 1) ? rb
3363:                                .getString(SqltoolRB.ROW_UPDATE_SINGULAR) : rb
3364:                                .getString(SqltoolRB.ROW_UPDATE_MULTIPLE,
3365:                                        updateCount));
3366:                    }
3367:                    break;
3368:                }
3369:            }
3370:
3371:            private static final int COL_HEAD = 0, COL_ODD = 1, COL_EVEN = 2;
3372:            private static final String PRE_TR = spaces(4);
3373:            private static final String PRE_TD = spaces(8);
3374:
3375:            /**
3376:             * Print a properly formatted HTML &lt;TR&gt; command for the given
3377:             * situation.
3378:             *
3379:             * @param colType Column type:  COL_HEAD, COL_ODD or COL_EVEN.
3380:             */
3381:            private static String htmlRow(int colType) {
3382:                switch (colType) {
3383:                case COL_HEAD:
3384:                    return PRE_TR + "<TR style='font-weight: bold;'>";
3385:
3386:                case COL_ODD:
3387:                    return PRE_TR
3388:                            + "<TR style='background: #94d6ef; font: normal "
3389:                            + "normal 10px/10px Arial, Helvitica, sans-serif;'>";
3390:
3391:                case COL_EVEN:
3392:                    return PRE_TR
3393:                            + "<TR style='background: silver; font: normal "
3394:                            + "normal 10px/10px Arial, Helvitica, sans-serif;'>";
3395:                }
3396:
3397:                return null;
3398:            }
3399:
3400:            /**
3401:             * Returns a divider of hypens of requested length.
3402:             *
3403:             * @param len Length of output String.
3404:             */
3405:            private static String divider(int len) {
3406:                return (len > DIVIDER.length()) ? DIVIDER : DIVIDER.substring(
3407:                        0, len);
3408:            }
3409:
3410:            /**
3411:             * Returns a String of spaces of requested length.
3412:             *
3413:             * @param len Length of output String.
3414:             */
3415:            private static String spaces(int len) {
3416:                return (len > SPACES.length()) ? SPACES : SPACES.substring(0,
3417:                        len);
3418:            }
3419:
3420:            /**
3421:             * Pads given input string out to requested length with space
3422:             * characters.
3423:             *
3424:             * @param inString Base string.
3425:             * @param fulllen  Output String length.
3426:             * @param rightJustify  True to right justify, false to left justify.
3427:             */
3428:            private static String pad(String inString, int fulllen,
3429:                    boolean rightJustify, boolean doPad) {
3430:                if (!doPad) {
3431:                    return inString;
3432:                }
3433:
3434:                int len = fulllen - inString.length();
3435:
3436:                if (len < 1) {
3437:                    return inString;
3438:                }
3439:
3440:                String pad = SqlFile.spaces(len);
3441:
3442:                return ((rightJustify ? pad : "") + inString + (rightJustify ? ""
3443:                        : pad));
3444:            }
3445:
3446:            /**
3447:             * Display command history.
3448:             */
3449:            private void showHistory() throws BadSpecial {
3450:                if (history == null) {
3451:                    throw new BadSpecial(rb
3452:                            .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3453:                }
3454:                if (history.size() < 1) {
3455:                    throw new BadSpecial(rb.getString(SqltoolRB.HISTORY_NONE));
3456:                }
3457:                for (int i = 0; i < history.size(); i++) {
3458:                    psStd.println("#" + (i + oldestHist) + " or "
3459:                            + (i - history.size()) + ':');
3460:                    psStd.println((String) history.get(i));
3461:                }
3462:                if (buffer != null) {
3463:                    psStd.println(rb.getString(SqltoolRB.EDITBUFFER_CONTENTS,
3464:                            buffer));
3465:                }
3466:
3467:                psStd.println();
3468:                psStd.println(rb.getString(SqltoolRB.BUFFER_INSTRUCTIONS));
3469:            }
3470:
3471:            /**
3472:             * Return a Command from command history.
3473:             */
3474:            private String commandFromHistory(int inIndex) throws BadSpecial {
3475:                int index = inIndex; // Just to quiet compiler warnings.
3476:
3477:                if (history == null) {
3478:                    throw new BadSpecial(rb
3479:                            .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3480:                }
3481:                if (index == 0) {
3482:                    throw new BadSpecial(rb
3483:                            .getString(SqltoolRB.HISTORY_NUMBER_REQ));
3484:                }
3485:                if (index > 0) {
3486:                    // Positive command# given
3487:                    index -= oldestHist;
3488:                    if (index < 0) {
3489:                        throw new BadSpecial(rb.getString(
3490:                                SqltoolRB.HISTORY_BACKTO, oldestHist));
3491:                    }
3492:                    if (index >= history.size()) {
3493:                        throw new BadSpecial(rb.getString(
3494:                                SqltoolRB.HISTORY_UPTO, history.size()
3495:                                        + oldestHist - 1));
3496:                    }
3497:                } else {
3498:                    // Negative command# given
3499:                    index += history.size();
3500:                    if (index < 0) {
3501:                        throw new BadSpecial(rb.getString(
3502:                                SqltoolRB.HISTORY_BACK, history.size()));
3503:                    }
3504:                }
3505:                return (String) history.get(index);
3506:            }
3507:
3508:            /**
3509:             * Search Command History for a regex match.
3510:             *
3511:             * @returns Absolute command number, if any match.
3512:             */
3513:            private Integer historySearch(String findRegex) throws BadSpecial {
3514:                if (history == null) {
3515:                    throw new BadSpecial(rb
3516:                            .getString(SqltoolRB.HISTORY_UNAVAILABLE));
3517:                }
3518:                Pattern pattern = Pattern.compile("(?ims)" + findRegex);
3519:                // Make matching more liberal.  Users can customize search behavior
3520:                // by using "(?-OPTIONS)" or (?OPTIONS) in their regexes.
3521:                for (int index = history.size() - 1; index >= 0; index--)
3522:                    if (pattern.matcher((String) history.get(index)).find())
3523:                        return new Integer(index + oldestHist);
3524:                return null;
3525:            }
3526:
3527:            private void setBuf(String newContent) {
3528:                buffer = new String(newContent);
3529:                // System.err.println("Buffer is now (" + buffer + ')');
3530:            }
3531:
3532:            int oldestHist = 1;
3533:
3534:            /**
3535:             * Add a command onto the history list.
3536:             */
3537:            private void historize() {
3538:                if (history == null || buffer == null) {
3539:                    return;
3540:                }
3541:                if (history.size() > 0
3542:                        && history.get(history.size() - 1).equals(buffer)) {
3543:                    // Don't store two consecutive commands that are exactly the same.
3544:                    return;
3545:                }
3546:                history.add(buffer);
3547:                if (history.size() <= maxHistoryLength) {
3548:                    return;
3549:                }
3550:                history.remove(0);
3551:                oldestHist++;
3552:            }
3553:
3554:            /**
3555:             * Describe the columns of specified table.
3556:             *
3557:             * @param tableName  Table that will be described.
3558:             * @param filter  Substring to filter by
3559:             */
3560:            private void describe(String tableName, String inFilter)
3561:                    throws SQLException {
3562:                /*
3563:                 * Doing case-sensitive filters now, for greater portability.
3564:                String filter = ((inFilter == null) ? null : inFilter.toUpperCase());
3565:                 */
3566:                String filter = inFilter;
3567:                List rows = new ArrayList();
3568:                String[] headerArray = {
3569:                        rb.getString(SqltoolRB.DESCRIBE_TABLE_NAME),
3570:                        rb.getString(SqltoolRB.DESCRIBE_TABLE_DATATYPE),
3571:                        rb.getString(SqltoolRB.DESCRIBE_TABLE_WIDTH),
3572:                        rb.getString(SqltoolRB.DESCRIBE_TABLE_NONULLS), };
3573:                String[] fieldArray;
3574:                int[] maxWidth = { 0, 0, 0, 0 };
3575:                boolean[] rightJust = { false, false, true, false };
3576:
3577:                // STEP 1: GATHER DATA
3578:                for (int i = 0; i < headerArray.length; i++) {
3579:                    if (htmlMode) {
3580:                        continue;
3581:                    }
3582:
3583:                    if (headerArray[i].length() > maxWidth[i]) {
3584:                        maxWidth[i] = headerArray[i].length();
3585:                    }
3586:                }
3587:
3588:                Statement statement = curConn.createStatement();
3589:                ResultSet r = null;
3590:
3591:                try {
3592:                    statement.execute("SELECT * FROM " + tableName
3593:                            + " WHERE 1 = 2");
3594:
3595:                    r = statement.getResultSet();
3596:
3597:                    ResultSetMetaData m = r.getMetaData();
3598:                    int cols = m.getColumnCount();
3599:
3600:                    for (int i = 0; i < cols; i++) {
3601:                        fieldArray = new String[4];
3602:                        fieldArray[0] = m.getColumnName(i + 1);
3603:
3604:                        if (filter != null && fieldArray[0].indexOf(filter) < 0) {
3605:                            continue;
3606:                        }
3607:
3608:                        fieldArray[1] = m.getColumnTypeName(i + 1);
3609:                        fieldArray[2] = Integer.toString(m
3610:                                .getColumnDisplaySize(i + 1));
3611:                        fieldArray[3] = ((m.isNullable(i + 1) == java.sql.ResultSetMetaData.columnNullable) ? (htmlMode ? "&nbsp;"
3612:                                : "")
3613:                                : "*");
3614:
3615:                        rows.add(fieldArray);
3616:
3617:                        for (int j = 0; j < fieldArray.length; j++) {
3618:                            if (fieldArray[j].length() > maxWidth[j]) {
3619:                                maxWidth[j] = fieldArray[j].length();
3620:                            }
3621:                        }
3622:                    }
3623:
3624:                    // STEP 2: DISPLAY DATA
3625:                    condlPrint("<TABLE border='1'>" + LS
3626:                            + SqlFile.htmlRow(COL_HEAD) + LS + PRE_TD, true);
3627:
3628:                    for (int i = 0; i < headerArray.length; i++) {
3629:                        condlPrint("<TD>" + headerArray[i] + "</TD>", true);
3630:                        condlPrint(
3631:                                ((i > 0) ? SqlFile.spaces(2) : "")
3632:                                        + SqlFile
3633:                                                .pad(
3634:                                                        headerArray[i],
3635:                                                        maxWidth[i],
3636:                                                        rightJust[i],
3637:                                                        (i < headerArray.length - 1 || rightJust[i])),
3638:                                false);
3639:                    }
3640:
3641:                    condlPrintln(LS + PRE_TR + "</TR>", true);
3642:                    condlPrintln("", false);
3643:
3644:                    if (!htmlMode) {
3645:                        for (int i = 0; i < headerArray.length; i++) {
3646:                            condlPrint(((i > 0) ? SqlFile.spaces(2) : "")
3647:                                    + SqlFile.divider(maxWidth[i]), false);
3648:                        }
3649:
3650:                        condlPrintln("", false);
3651:                    }
3652:
3653:                    for (int i = 0; i < rows.size(); i++) {
3654:                        condlPrint(SqlFile.htmlRow(((i % 2) == 0) ? COL_EVEN
3655:                                : COL_ODD)
3656:                                + LS + PRE_TD, true);
3657:
3658:                        fieldArray = (String[]) rows.get(i);
3659:
3660:                        for (int j = 0; j < fieldArray.length; j++) {
3661:                            condlPrint("<TD>" + fieldArray[j] + "</TD>", true);
3662:                            condlPrint(
3663:                                    ((j > 0) ? SqlFile.spaces(2) : "")
3664:                                            + SqlFile
3665:                                                    .pad(
3666:                                                            fieldArray[j],
3667:                                                            maxWidth[j],
3668:                                                            rightJust[j],
3669:                                                            (j < fieldArray.length - 1 || rightJust[j])),
3670:                                    false);
3671:                        }
3672:
3673:                        condlPrintln(LS + PRE_TR + "</TR>", true);
3674:                        condlPrintln("", false);
3675:                    }
3676:
3677:                    condlPrintln(LS + "</TABLE>" + LS + "<HR>", true);
3678:                } finally {
3679:                    try {
3680:                        if (r != null) {
3681:                            r.close();
3682:
3683:                            r = null;
3684:                        }
3685:
3686:                        statement.close();
3687:                    } catch (Exception e) {
3688:                    }
3689:                }
3690:            }
3691:
3692:            private boolean eval(String[] inTokens) throws BadSpecial {
3693:                /* TODO:  Rewrite using java.util.regex.  */
3694:                // dereference *VARNAME variables.
3695:                // N.b. we work with a "copy" of the tokens.
3696:                boolean negate = inTokens.length > 0 && inTokens[0].equals("!");
3697:                String[] tokens = new String[negate ? (inTokens.length - 1)
3698:                        : inTokens.length];
3699:                String inToken;
3700:
3701:                for (int i = 0; i < tokens.length; i++) {
3702:                    inToken = inTokens[i + (negate ? 1 : 0)];
3703:                    if (inToken.length() > 1 && inToken.charAt(0) == '*') {
3704:                        tokens[i] = (String) userVars.get(inToken.substring(1));
3705:                    } else {
3706:                        tokens[i] = inTokens[i + (negate ? 1 : 0)];
3707:                    }
3708:
3709:                    // Unset variables permitted in expressions as long as use
3710:                    // the short *VARNAME form.
3711:                    if (tokens[i] == null) {
3712:                        tokens[i] = "";
3713:                    }
3714:                }
3715:
3716:                if (tokens.length == 1) {
3717:                    return (tokens[0].length() > 0 && !tokens[0].equals("0"))
3718:                            ^ negate;
3719:                }
3720:
3721:                if (tokens.length == 3) {
3722:                    if (tokens[1].equals("==")) {
3723:                        return tokens[0].equals(tokens[2]) ^ negate;
3724:                    }
3725:
3726:                    if (tokens[1].equals("!=") || tokens[1].equals("<>")
3727:                            || tokens[1].equals("><")) {
3728:                        return (!tokens[0].equals(tokens[2])) ^ negate;
3729:                    }
3730:
3731:                    if (tokens[1].equals(">")) {
3732:                        return (tokens[0].length() > tokens[2].length() || ((tokens[0]
3733:                                .length() == tokens[2].length()) && tokens[0]
3734:                                .compareTo(tokens[2]) > 0))
3735:                                ^ negate;
3736:                    }
3737:
3738:                    if (tokens[1].equals("<")) {
3739:                        return (tokens[2].length() > tokens[0].length() || ((tokens[2]
3740:                                .length() == tokens[0].length()) && tokens[2]
3741:                                .compareTo(tokens[0]) > 0))
3742:                                ^ negate;
3743:                    }
3744:                }
3745:
3746:                throw new BadSpecial(rb
3747:                        .getString(SqltoolRB.LOGICAL_UNRECOGNIZED));
3748:            }
3749:
3750:            private void closeQueryOutputStream() {
3751:                if (pwQuery == null) {
3752:                    return;
3753:                }
3754:
3755:                if (htmlMode) {
3756:                    pwQuery.println("</BODY></HTML>");
3757:                    pwQuery.flush();
3758:                }
3759:
3760:                pwQuery.close();
3761:
3762:                pwQuery = null;
3763:            }
3764:
3765:            /**
3766:             * Print to psStd and possibly pwQuery iff current HTML mode matches
3767:             * supplied printHtml.
3768:             */
3769:            private void condlPrintln(String s, boolean printHtml) {
3770:                if ((printHtml && !htmlMode) || (htmlMode && !printHtml)) {
3771:                    return;
3772:                }
3773:
3774:                psStd.println(s);
3775:
3776:                if (pwQuery != null) {
3777:                    pwQuery.println(s);
3778:                    pwQuery.flush();
3779:                }
3780:            }
3781:
3782:            /**
3783:             * Print to psStd and possibly pwQuery iff current HTML mode matches
3784:             * supplied printHtml.
3785:             */
3786:            private void condlPrint(String s, boolean printHtml) {
3787:                if ((printHtml && !htmlMode) || (htmlMode && !printHtml)) {
3788:                    return;
3789:                }
3790:
3791:                psStd.print(s);
3792:
3793:                if (pwQuery != null) {
3794:                    pwQuery.print(s);
3795:                    pwQuery.flush();
3796:                }
3797:            }
3798:
3799:            private String formatNicely(Map map, boolean withValues) {
3800:                String key;
3801:                StringBuffer sb = new StringBuffer();
3802:                Iterator it = (new TreeMap(map)).keySet().iterator();
3803:
3804:                if (withValues) {
3805:                    SqlFile.appendLine(sb, rb
3806:                            .getString(SqltoolRB.PL_LIST_PARENS));
3807:                } else {
3808:                    SqlFile.appendLine(sb, rb
3809:                            .getString(SqltoolRB.PL_LIST_LENGTHS));
3810:                }
3811:
3812:                while (it.hasNext()) {
3813:                    key = (String) it.next();
3814:
3815:                    String s = (String) map.get(key);
3816:
3817:                    SqlFile.appendLine(sb, "    "
3818:                            + key
3819:                            + ": "
3820:                            + (withValues ? ("(" + s + ')') : Integer
3821:                                    .toString(s.length())));
3822:                }
3823:
3824:                return sb.toString();
3825:            }
3826:
3827:            /**
3828:             * Ascii file dump.
3829:             *
3830:             * dumpFile must not be null.
3831:             */
3832:            private void dump(String varName, File dumpFile)
3833:                    throws IOException, BadSpecial {
3834:                String val = (String) userVars.get(varName);
3835:
3836:                if (val == null) {
3837:                    throw new BadSpecial(rb.getString(
3838:                            SqltoolRB.PLVAR_UNDEFINED, varName));
3839:                }
3840:
3841:                OutputStreamWriter osw = ((charset == null) ? (new OutputStreamWriter(
3842:                        new FileOutputStream(dumpFile)))
3843:                        : (new OutputStreamWriter(
3844:                                new FileOutputStream(dumpFile), charset)));
3845:                // Replace with just "(new FileOutputStream(file), charset)"
3846:                // once use defaultCharset from Java 1.5 in charset init. above.
3847:
3848:                try {
3849:                    osw.write(val);
3850:
3851:                    if (val.length() > 0) {
3852:                        char lastChar = val.charAt(val.length() - 1);
3853:
3854:                        if (lastChar != '\n' && lastChar != '\r') {
3855:                            osw.write(LS);
3856:                        }
3857:                    }
3858:
3859:                    osw.flush();
3860:                } finally {
3861:                    osw.close();
3862:                }
3863:
3864:                // Since opened in overwrite mode, since we didn't exception out,
3865:                // we can be confident that we wrote all the bytest in the file.
3866:                stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS, Long
3867:                        .toString(dumpFile.length()), dumpFile.toString()));
3868:            }
3869:
3870:            byte[] binBuffer = null;
3871:
3872:            /**
3873:             * Binary file dump
3874:             *
3875:             * dumpFile must not be null.
3876:             */
3877:            private void dump(File dumpFile) throws IOException, BadSpecial {
3878:                if (binBuffer == null) {
3879:                    throw new BadSpecial(rb
3880:                            .getString(SqltoolRB.BINBUFFER_EMPTY));
3881:                }
3882:
3883:                FileOutputStream fos = new FileOutputStream(dumpFile);
3884:                int len = 0;
3885:
3886:                try {
3887:                    fos.write(binBuffer);
3888:
3889:                    len = binBuffer.length;
3890:
3891:                    binBuffer = null;
3892:
3893:                    fos.flush();
3894:                } finally {
3895:                    fos.close();
3896:                }
3897:                stdprintln(rb.getString(SqltoolRB.FILE_WROTECHARS, len,
3898:                        dumpFile.toString()));
3899:            }
3900:
3901:            /**
3902:             * As the name says...
3903:             * This method always closes the input stream.
3904:             */
3905:            public String streamToString(InputStream is, String cs)
3906:                    throws IOException {
3907:                try {
3908:                    byte[] ba = null;
3909:                    int bytesread = 0;
3910:                    int retval;
3911:                    try {
3912:                        ba = new byte[is.available()];
3913:                    } catch (RuntimeException re) {
3914:                        throw new IOException(rb
3915:                                .getString(SqltoolRB.READ_TOOBIG));
3916:                    }
3917:                    while (bytesread < ba.length
3918:                            && (retval = is.read(ba, bytesread, ba.length
3919:                                    - bytesread)) > 0) {
3920:                        bytesread += retval;
3921:                    }
3922:                    if (bytesread != ba.length) {
3923:                        throw new IOException(rb.getString(
3924:                                SqltoolRB.READ_PARTIAL, bytesread, ba.length));
3925:                    }
3926:                    try {
3927:                        return (cs == null) ? (new String(ba)) : (new String(
3928:                                ba, cs));
3929:                    } catch (UnsupportedEncodingException uee) {
3930:                        throw new RuntimeException(uee);
3931:                    } catch (RuntimeException re) {
3932:                        throw new IOException(rb
3933:                                .getString(SqltoolRB.READ_CONVERTFAIL));
3934:                    }
3935:                } finally {
3936:                    is.close();
3937:                }
3938:            }
3939:
3940:            /**
3941:             * Ascii file load.
3942:             */
3943:            private void load(String varName, File asciiFile, String cs)
3944:                    throws IOException {
3945:                String string = streamToString(new FileInputStream(asciiFile),
3946:                        cs);
3947:                userVars.put(varName, string);
3948:                updateUserSettings();
3949:            }
3950:
3951:            static public byte[] streamToBytes(InputStream is)
3952:                    throws IOException {
3953:                byte[] xferBuffer = new byte[10240];
3954:                ByteArrayOutputStream baos = new ByteArrayOutputStream();
3955:                int i;
3956:
3957:                while ((i = is.read(xferBuffer)) > 0) {
3958:                    baos.write(xferBuffer, 0, i);
3959:                }
3960:
3961:                return baos.toByteArray();
3962:            }
3963:
3964:            /**
3965:             * Binary file load
3966:             */
3967:            static public byte[] loadBinary(File binFile) throws IOException {
3968:                byte[] xferBuffer = new byte[10240];
3969:                ByteArrayOutputStream baos = new ByteArrayOutputStream();
3970:                int i;
3971:                FileInputStream fis = new FileInputStream(binFile);
3972:
3973:                try {
3974:                    while ((i = fis.read(xferBuffer)) > 0) {
3975:                        baos.write(xferBuffer, 0, i);
3976:                    }
3977:                } finally {
3978:                    fis.close();
3979:                }
3980:
3981:                byte[] ba = baos.toByteArray();
3982:
3983:                return ba;
3984:            }
3985:
3986:            /**
3987:             * This method is used to tell SqlFile whether this Sql Type must
3988:             * ALWAYS be loaded to the binary buffer without displaying.
3989:             *
3990:             * N.b.:  If this returns "true" for a type, then the user can never
3991:             * "see" values for these columns.
3992:             * Therefore, if a type may-or-may-not-be displayable, better to return
3993:             * false here and let the user choose.
3994:             * In general, if there is a toString() operator for this Sql Type
3995:             * then return false, since the JDBC driver should know how to make the
3996:             * value displayable.
3997:             *
3998:             * The table on this page lists the most common SqlTypes, all of which
3999:             * must implement toString():
4000:             *     http://java.sun.com/docs/books/tutorial/jdbc/basics/retrieving.html
4001:             *
4002:             * @see java.sql.Types
4003:             */
4004:            public static boolean canDisplayType(int i) {
4005:                /* I don't now about some of the more obscure types, like REF and
4006:                 * DATALINK */
4007:                switch (i) {
4008:                //case java.sql.Types.BINARY :
4009:                case java.sql.Types.BLOB:
4010:                case java.sql.Types.JAVA_OBJECT:
4011:
4012:                    //case java.sql.Types.LONGVARBINARY :
4013:                    //case java.sql.Types.LONGVARCHAR :
4014:                case java.sql.Types.OTHER:
4015:                case java.sql.Types.STRUCT:
4016:
4017:                    //case java.sql.Types.VARBINARY :
4018:                    return false;
4019:                }
4020:
4021:                return true;
4022:            }
4023:
4024:            // won't compile with JDK 1.3 without these
4025:            private static final int JDBC3_BOOLEAN = 16;
4026:            private static final int JDBC3_DATALINK = 70;
4027:
4028:            public static String sqlTypeToString(int i) {
4029:                switch (i) {
4030:                case java.sql.Types.ARRAY:
4031:                    return "ARRAY";
4032:
4033:                case java.sql.Types.BIGINT:
4034:                    return "BIGINT";
4035:
4036:                case java.sql.Types.BINARY:
4037:                    return "BINARY";
4038:
4039:                case java.sql.Types.BIT:
4040:                    return "BIT";
4041:
4042:                case java.sql.Types.BLOB:
4043:                    return "BLOB";
4044:
4045:                case JDBC3_BOOLEAN:
4046:                    return "BOOLEAN";
4047:
4048:                case java.sql.Types.CHAR:
4049:                    return "CHAR";
4050:
4051:                case java.sql.Types.CLOB:
4052:                    return "CLOB";
4053:
4054:                case JDBC3_DATALINK:
4055:                    return "DATALINK";
4056:
4057:                case java.sql.Types.DATE:
4058:                    return "DATE";
4059:
4060:                case java.sql.Types.DECIMAL:
4061:                    return "DECIMAL";
4062:
4063:                case java.sql.Types.DISTINCT:
4064:                    return "DISTINCT";
4065:
4066:                case java.sql.Types.DOUBLE:
4067:                    return "DOUBLE";
4068:
4069:                case java.sql.Types.FLOAT:
4070:                    return "FLOAT";
4071:
4072:                case java.sql.Types.INTEGER:
4073:                    return "INTEGER";
4074:
4075:                case java.sql.Types.JAVA_OBJECT:
4076:                    return "JAVA_OBJECT";
4077:
4078:                case java.sql.Types.LONGVARBINARY:
4079:                    return "LONGVARBINARY";
4080:
4081:                case java.sql.Types.LONGVARCHAR:
4082:                    return "LONGVARCHAR";
4083:
4084:                case java.sql.Types.NULL:
4085:                    return "NULL";
4086:
4087:                case java.sql.Types.NUMERIC:
4088:                    return "NUMERIC";
4089:
4090:                case java.sql.Types.OTHER:
4091:                    return "OTHER";
4092:
4093:                case java.sql.Types.REAL:
4094:                    return "REAL";
4095:
4096:                case java.sql.Types.REF:
4097:                    return "REF";
4098:
4099:                case java.sql.Types.SMALLINT:
4100:                    return "SMALLINT";
4101:
4102:                case java.sql.Types.STRUCT:
4103:                    return "STRUCT";
4104:
4105:                case java.sql.Types.TIME:
4106:                    return "TIME";
4107:
4108:                case java.sql.Types.TIMESTAMP:
4109:                    return "TIMESTAMP";
4110:
4111:                case java.sql.Types.TINYINT:
4112:                    return "TINYINT";
4113:
4114:                case java.sql.Types.VARBINARY:
4115:                    return "VARBINARY";
4116:
4117:                case java.sql.Types.VARCHAR:
4118:                    return "VARCHAR";
4119:                }
4120:
4121:                return "Unknown type " + i;
4122:            }
4123:
4124:            /**
4125:             * Validate that String is safe to display in a DSV file.
4126:             *
4127:             * @throws SqlToolError if validation fails.
4128:             */
4129:            public void dsvSafe(String s) throws SqlToolError {
4130:                if (pwDsv == null || dsvColDelim == null || dsvRowDelim == null
4131:                        || nullRepToken == null) {
4132:                    throw new RuntimeException("Assertion failed.  \n"
4133:                            + "dsvSafe called when DSV settings are incomplete");
4134:                }
4135:
4136:                if (s == null) {
4137:                    return;
4138:                }
4139:
4140:                if (s.indexOf(dsvColDelim) > 0) {
4141:                    throw new SqlToolError(rb.getString(
4142:                            SqltoolRB.DSV_COLDELIM_PRESENT, dsvColDelim));
4143:                }
4144:
4145:                if (s.indexOf(dsvRowDelim) > 0) {
4146:                    throw new SqlToolError(rb.getString(
4147:                            SqltoolRB.DSV_ROWDELIM_PRESENT, dsvRowDelim));
4148:                }
4149:
4150:                if (s.trim().equals(nullRepToken)) {
4151:                    // The trim() is to avoid the situation where the contents of a
4152:                    // field "looks like" the null-rep token.
4153:                    throw new SqlToolError(rb.getString(
4154:                            SqltoolRB.DSV_NULLREP_PRESENT, nullRepToken));
4155:                }
4156:            }
4157:
4158:            /**
4159:             * Translates user-supplied escapes into the traditionaly corresponding
4160:             * corresponding binary characters.
4161:             *
4162:             * Allowed sequences:
4163:             * <UL>
4164:             *  <LI>\0\d+   (an octal digit)
4165:             *  <LI>\[0-9]\d*  (a decimal digit)
4166:             *  <LI>\[Xx][0-9]{2}  (a hex digit)
4167:             *  <LI>\n  Newline  (Ctrl-J)
4168:             *  <LI>\r  Carriage return  (Ctrl-M)
4169:             *  <LI>\t  Horizontal tab  (Ctrl-I)
4170:             *  <LI>\f  Form feed  (Ctrl-L)
4171:             * </UL>
4172:             *
4173:             * Java 1.4 String methods will make this into a 1 or 2 line task.
4174:             */
4175:            public static String convertEscapes(String inString) {
4176:                if (inString == null) {
4177:                    return null;
4178:                }
4179:                return convertNumericEscapes(convertEscapes(convertEscapes(
4180:                        convertEscapes(convertEscapes(convertEscapes(inString,
4181:                                "\\n", "\n"), "\\r", "\r"), "\\t", "\t"),
4182:                        "\\\\", "\\"), "\\f", "\f"));
4183:            }
4184:
4185:            /**
4186:             * @param string  Non-null String to modify.
4187:             */
4188:            private static String convertNumericEscapes(String string) {
4189:                String workString = string;
4190:                int i = 0;
4191:
4192:                for (char dig = '0'; dig <= '9'; dig++) {
4193:                    while ((i = workString.indexOf("\\" + dig, i)) > -1
4194:                            && i < workString.length() - 1) {
4195:                        workString = convertNumericEscape(string, i);
4196:                    }
4197:                    while ((i = workString.indexOf("\\x" + dig, i)) > -1
4198:                            && i < workString.length() - 1) {
4199:                        workString = convertNumericEscape(string, i);
4200:                    }
4201:                    while ((i = workString.indexOf("\\X" + dig, i)) > -1
4202:                            && i < workString.length() - 1) {
4203:                        workString = convertNumericEscape(string, i);
4204:                    }
4205:                }
4206:                return workString;
4207:            }
4208:
4209:            /**
4210:             * @offset  Position of the leading \.
4211:             */
4212:            private static String convertNumericEscape(String string, int offset) {
4213:                int post = -1;
4214:                int firstDigit = -1;
4215:                int radix = -1;
4216:                if (Character.toUpperCase(string.charAt(offset + 1)) == 'X') {
4217:                    firstDigit = offset + 2;
4218:                    radix = 16;
4219:                    post = firstDigit + 2;
4220:                    if (post > string.length())
4221:                        post = string.length();
4222:                } else {
4223:                    firstDigit = offset + 1;
4224:                    radix = (Character.toUpperCase(string.charAt(firstDigit)) == '0') ? 8
4225:                            : 10;
4226:                    for (post = firstDigit + 1; post < string.length()
4227:                            && Character.isDigit(string.charAt(post)); post++)
4228:                        ;
4229:                }
4230:                return string.substring(0, offset)
4231:                        + ((char) Integer.parseInt(string.substring(firstDigit,
4232:                                post), radix)) + string.substring(post);
4233:            }
4234:
4235:            /**
4236:             * @param string  Non-null String to modify.
4237:             */
4238:            private static String convertEscapes(String string, String from,
4239:                    String to) {
4240:                String workString = string;
4241:                int i = 0;
4242:                int fromLen = from.length();
4243:
4244:                while ((i = workString.indexOf(from, i)) > -1
4245:                        && i < workString.length() - 1) {
4246:                    workString = workString.substring(0, i) + to
4247:                            + workString.substring(i + fromLen);
4248:                }
4249:                return workString;
4250:            }
4251:
4252:            /**
4253:             * Name is self-explanatory.
4254:             *
4255:             * If there is user demand, open file in random access mode so don't
4256:             * need to load 2 copies of the entire file into memory.
4257:             * This will be difficult because can't use standard Java language
4258:             * features to search through a character array for multi-character
4259:             * substrings.
4260:             *
4261:             * @throws SqlToolError  Would prefer to throw an internal exception,
4262:             *                       but we want this method to have external
4263:             *                       visibility.
4264:             */
4265:            public void importDsv(String filePath, String skipPrefix)
4266:                    throws SqlToolError {
4267:                /* To make string comparisons, contains() methods, etc. a little
4268:                 * simpler and concise, just switch all column names to lower-case.
4269:                 * This is ok since we acknowledge up from that DSV import/export
4270:                 * assume no special characters or escaping in column names. */
4271:                byte[] bfr = null;
4272:                File file = new File(filePath);
4273:                SortedMap constColMap = null;
4274:                if (dsvConstCols != null) {
4275:                    // We trim col. names, but not values.  Must allow users to
4276:                    // specify values as spaces, empty string, null.
4277:                    constColMap = new TreeMap();
4278:                    String[] constPairs = dsvConstCols.split("\\Q"
4279:                            + dsvColDelim + "\\E\\s*");
4280:                    int firstEq;
4281:                    String n;
4282:                    for (int i = 0; i < constPairs.length; i++) {
4283:                        firstEq = constPairs[i].indexOf('=');
4284:                        n = constPairs[i].substring(0, firstEq).trim()
4285:                                .toLowerCase();
4286:                        if (n.trim().length() < 1) {
4287:                            throw new SqlToolError(rb
4288:                                    .getString(SqltoolRB.DSV_CONSTCOLS_NULLCOL));
4289:                        }
4290:                        constColMap
4291:                                .put(n, constPairs[i].substring(firstEq + 1));
4292:                    }
4293:                }
4294:                Set skipCols = null;
4295:                if (dsvSkipCols != null) {
4296:                    skipCols = new HashSet();
4297:                    String[] skipColsArray = dsvSkipCols.split("\\s*\\Q"
4298:                            + dsvColDelim + "\\E\\s*");
4299:                    for (int i = 0; i < skipColsArray.length; i++) {
4300:                        skipCols.add(skipColsArray[i].toLowerCase());
4301:                    }
4302:                }
4303:
4304:                if (!file.canRead()) {
4305:                    throw new SqlToolError(rb.getString(
4306:                            SqltoolRB.FILE_READFAIL, file.toString()));
4307:                }
4308:
4309:                try {
4310:                    bfr = new byte[(int) file.length()];
4311:                } catch (RuntimeException re) {
4312:                    throw new SqlToolError(rb.getString(SqltoolRB.READ_TOOBIG),
4313:                            re);
4314:                }
4315:
4316:                int bytesread = 0;
4317:                int retval;
4318:                InputStream is = null;
4319:
4320:                try {
4321:                    is = new FileInputStream(file);
4322:                    while (bytesread < bfr.length
4323:                            && (retval = is.read(bfr, bytesread, bfr.length
4324:                                    - bytesread)) > 0) {
4325:                        bytesread += retval;
4326:                    }
4327:
4328:                } catch (IOException ioe) {
4329:                    throw new SqlToolError(ioe);
4330:                } finally {
4331:                    if (is != null)
4332:                        try {
4333:                            is.close();
4334:                        } catch (IOException ioe) {
4335:                            errprintln(rb
4336:                                    .getString(SqltoolRB.INPUTFILE_CLOSEFAIL)
4337:                                    + ": " + ioe);
4338:                        }
4339:                }
4340:                if (bytesread != bfr.length) {
4341:                    throw new SqlToolError(rb.getString(SqltoolRB.READ_PARTIAL,
4342:                            bytesread, bfr.length));
4343:                }
4344:
4345:                String string = null;
4346:                String dateString;
4347:
4348:                try {
4349:                    string = ((charset == null) ? (new String(bfr))
4350:                            : (new String(bfr, charset)));
4351:                } catch (UnsupportedEncodingException uee) {
4352:                    throw new RuntimeException(uee);
4353:                } catch (RuntimeException re) {
4354:                    throw new SqlToolError(rb
4355:                            .getString(SqltoolRB.READ_CONVERTFAIL), re);
4356:                }
4357:
4358:                List headerList = new ArrayList();
4359:                String tableName = dsvTargetTable;
4360:
4361:                // N.b.  ENDs are the index of 1 PAST the current item
4362:                int recEnd = -1000; // Recognizable value incase something goes
4363:                // horrifically wrong.
4364:                int colStart;
4365:                int colEnd;
4366:
4367:                // First read one until we get one header line
4368:                int lineCount = 0; // Assume a 1 line header?
4369:                int recStart = -1;
4370:                String trimmedLine = null;
4371:                boolean switching = false;
4372:
4373:                while (true) {
4374:                    recStart = (recStart < 0) ? 0 : (recEnd + dsvRowDelim
4375:                            .length());
4376:                    if (recStart > string.length() - 2) {
4377:                        throw new SqlToolError(rb
4378:                                .getString(SqltoolRB.DSV_HEADER_NONE));
4379:                    }
4380:                    recEnd = string.indexOf(dsvRowDelim, recStart);
4381:                    lineCount++; // Increment when we have line start and end
4382:
4383:                    if (recEnd < 0) {
4384:                        // Last line in file.  No data records.
4385:                        recEnd = string.length();
4386:                    }
4387:                    trimmedLine = string.substring(recStart, recEnd).trim();
4388:                    if (trimmedLine.length() < 1
4389:                            || (skipPrefix != null && trimmedLine
4390:                                    .startsWith(skipPrefix))) {
4391:                        continue;
4392:                    }
4393:                    if (trimmedLine.startsWith("targettable=")) {
4394:                        if (tableName == null) {
4395:                            tableName = trimmedLine.substring(
4396:                                    "targettable=".length()).trim();
4397:                        }
4398:                        continue;
4399:                    }
4400:                    if (trimmedLine.equals("headerswitch{")) {
4401:                        if (tableName == null) {
4402:                            throw new SqlToolError(rb.getString(
4403:                                    SqltoolRB.DSV_HEADER_NOSWITCHTARG,
4404:                                    lineCount));
4405:                        }
4406:                        switching = true;
4407:                        continue;
4408:                    }
4409:                    if (trimmedLine.equals("}")) {
4410:                        throw new SqlToolError(rb.getString(
4411:                                SqltoolRB.DSV_HEADER_NOSWITCHMATCH, lineCount));
4412:                    }
4413:                    if (!switching) {
4414:                        break;
4415:                    }
4416:                    int colonAt = trimmedLine.indexOf(':');
4417:                    if (colonAt < 1 || colonAt == trimmedLine.length() - 1) {
4418:                        throw new SqlToolError(rb.getString(
4419:                                SqltoolRB.DSV_HEADER_NONSWITCHED, lineCount));
4420:                    }
4421:                    String matcher = trimmedLine.substring(0, colonAt).trim();
4422:                    // Need to be sure here that tableName is not null (in
4423:                    // which case it would be determined later on by the file name).
4424:                    if (matcher.equals("*")
4425:                            || matcher.equalsIgnoreCase(tableName)) {
4426:                        recStart = 1 + string.indexOf(':', recStart);
4427:                        break;
4428:                    }
4429:                    // Skip non-matched header line
4430:                }
4431:
4432:                String headerLine = string.substring(recStart, recEnd);
4433:                colStart = recStart;
4434:                colEnd = -1;
4435:                String colName;
4436:
4437:                while (true) {
4438:                    if (colEnd == recEnd) {
4439:                        // We processed final column last time through loop
4440:                        break;
4441:                    }
4442:
4443:                    colEnd = string.indexOf(dsvColDelim, colStart);
4444:
4445:                    if (colEnd < 0 || colEnd > recEnd) {
4446:                        colEnd = recEnd;
4447:                    }
4448:
4449:                    if (colEnd - colStart < 1) {
4450:                        throw new SqlToolError(rb.getString(
4451:                                SqltoolRB.DSV_NOCOLHEADER,
4452:                                headerList.size() + 1, lineCount));
4453:                    }
4454:
4455:                    colName = string.substring(colStart, colEnd).trim()
4456:                            .toLowerCase();
4457:                    headerList
4458:                            .add((colName.equals("-")
4459:                                    || (skipCols != null && skipCols
4460:                                            .remove(colName)) || (constColMap != null && constColMap
4461:                                    .containsKey(colName))) ? ((String) null)
4462:                                    : colName);
4463:
4464:                    colStart = colEnd + dsvColDelim.length();
4465:                }
4466:                if (skipCols != null && skipCols.size() > 0) {
4467:                    throw new SqlToolError(rb
4468:                            .getString(SqltoolRB.DSV_SKIPCOLS_MISSING, skipCols
4469:                                    .toString()));
4470:                }
4471:
4472:                boolean oneCol = false; // At least 1 non-null column
4473:                for (int i = 0; i < headerList.size(); i++) {
4474:                    if (headerList.get(i) != null) {
4475:                        oneCol = true;
4476:                        break;
4477:                    }
4478:                }
4479:                if (oneCol == false) {
4480:                    // Difficult call, but I think in any real-world situation, the
4481:                    // user will want to know if they are inserting records with no
4482:                    // data from their input file.
4483:                    throw new SqlToolError(rb.getString(
4484:                            SqltoolRB.DSV_NOCOLSLEFT, dsvSkipCols));
4485:                }
4486:
4487:                int inputColHeadCount = headerList.size();
4488:
4489:                if (constColMap != null) {
4490:                    headerList.addAll(constColMap.keySet());
4491:                }
4492:
4493:                String[] headers = (String[]) headerList.toArray(new String[0]);
4494:                // headers contains input headers + all constCols, some of these
4495:                // values may be nulls.
4496:
4497:                if (tableName == null) {
4498:                    tableName = file.getName();
4499:
4500:                    int i = tableName.lastIndexOf('.');
4501:
4502:                    if (i > 0) {
4503:                        tableName = tableName.substring(0, i);
4504:                    }
4505:                }
4506:
4507:                StringBuffer tmpSb = new StringBuffer();
4508:                List tmpList = new ArrayList();
4509:
4510:                int skippers = 0;
4511:                for (int i = 0; i < headers.length; i++) {
4512:                    if (headers[i] == null) {
4513:                        skippers++;
4514:                        continue;
4515:                    }
4516:                    if (tmpSb.length() > 0) {
4517:                        tmpSb.append(", ");
4518:                    }
4519:
4520:                    tmpSb.append(headers[i]);
4521:                    tmpList.add(headers[i]);
4522:                }
4523:                boolean[] autonulls = new boolean[headers.length - skippers];
4524:                boolean[] parseDate = new boolean[autonulls.length];
4525:                boolean[] parseBool = new boolean[autonulls.length];
4526:                String[] insertFieldName = (String[]) tmpList
4527:                        .toArray(new String[] {});
4528:                // Remember that the headers array has all columns in DSV file,
4529:                // even skipped columns.
4530:                // The autonulls array only has columns that we will insert into.
4531:
4532:                StringBuffer sb = new StringBuffer("INSERT INTO " + tableName
4533:                        + " (" + tmpSb + ") VALUES (");
4534:                StringBuffer typeQuerySb = new StringBuffer("SELECT " + tmpSb
4535:                        + " FROM " + tableName + " WHERE 1 = 2");
4536:
4537:                try {
4538:                    ResultSetMetaData rsmd = curConn.createStatement()
4539:                            .executeQuery(typeQuerySb.toString()).getMetaData();
4540:
4541:                    if (rsmd.getColumnCount() != autonulls.length) {
4542:                        throw new SqlToolError(rb
4543:                                .getString(SqltoolRB.DSV_METADATA_MISMATCH));
4544:                        // Don't know if it's possible to get here.
4545:                        // If so, it's probably a SqlTool problem, not a user or
4546:                        // data problem.
4547:                        // Should be researched and either return a user-friendly
4548:                        // message or a RuntimeExceptin.
4549:                    }
4550:
4551:                    for (int i = 0; i < autonulls.length; i++) {
4552:                        autonulls[i] = true;
4553:                        parseDate[i] = false;
4554:                        parseBool[i] = false;
4555:                        switch (rsmd.getColumnType(i + 1)) {
4556:                        case java.sql.Types.BOOLEAN:
4557:                            parseBool[i] = true;
4558:                            break;
4559:                        case java.sql.Types.VARBINARY:
4560:                        case java.sql.Types.VARCHAR:
4561:                        case java.sql.Types.ARRAY:
4562:                            // Guessing at how to handle ARRAY.
4563:                        case java.sql.Types.BLOB:
4564:                        case java.sql.Types.CLOB:
4565:                        case java.sql.Types.LONGVARBINARY:
4566:                        case java.sql.Types.LONGVARCHAR:
4567:                            autonulls[i] = false;
4568:                            // This means to preserve white space and to insert
4569:                            // "" for "".  Otherwise we trim white space and
4570:                            // insert null for \s*.
4571:                            break;
4572:                        case java.sql.Types.DATE:
4573:                        case java.sql.Types.TIME:
4574:                        case java.sql.Types.TIMESTAMP:
4575:                            parseDate[i] = true;
4576:                        }
4577:                    }
4578:                } catch (SQLException se) {
4579:                    throw new SqlToolError(rb.getString(
4580:                            SqltoolRB.QUERY_METADATAFAIL, typeQuerySb
4581:                                    .toString()), se);
4582:                }
4583:
4584:                for (int i = 0; i < autonulls.length; i++) {
4585:                    if (i > 0) {
4586:                        sb.append(", ");
4587:                    }
4588:
4589:                    sb.append('?');
4590:                }
4591:
4592:                // Initialize REJECT file(s)
4593:                int rejectCount = 0;
4594:                File rejectFile = null;
4595:                File rejectReportFile = null;
4596:                PrintWriter rejectWriter = null;
4597:                PrintWriter rejectReportWriter = null;
4598:                if (dsvRejectFile != null)
4599:                    try {
4600:                        rejectFile = new File(dsvRejectFile);
4601:                        rejectWriter = new PrintWriter(
4602:                                (charset == null) ? (new OutputStreamWriter(
4603:                                        new FileOutputStream(rejectFile)))
4604:                                        : (new OutputStreamWriter(
4605:                                                new FileOutputStream(rejectFile),
4606:                                                charset)));
4607:                        // Replace with just "(new FileOutputStream(file), charset)"
4608:                        // once use defaultCharset from Java 1.5 in charset init.
4609:                        // above.
4610:                        rejectWriter.print(headerLine + dsvRowDelim);
4611:                    } catch (IOException ioe) {
4612:                        throw new SqlToolError(rb.getString(
4613:                                SqltoolRB.DSV_REJECTFILE_SETUPFAIL,
4614:                                dsvRejectFile), ioe);
4615:                    }
4616:                if (dsvRejectReport != null)
4617:                    try {
4618:                        rejectReportFile = new File(dsvRejectReport);
4619:                        rejectReportWriter = new PrintWriter(
4620:                                (charset == null) ? (new OutputStreamWriter(
4621:                                        new FileOutputStream(rejectReportFile)))
4622:                                        : (new OutputStreamWriter(
4623:                                                new FileOutputStream(
4624:                                                        rejectReportFile),
4625:                                                charset)));
4626:                        // Replace with just "(new FileOutputStream(file), charset)"
4627:                        // once use defaultCharset from Java 1.5 in charset init.
4628:                        // above.
4629:                        rejectReportWriter.println(rb.getString(
4630:                                SqltoolRB.REJECTREPORT_TOP, new String[] {
4631:                                        (new java.util.Date()).toString(),
4632:                                        file.getPath(),
4633:                                        ((rejectFile == null) ? rb
4634:                                                .getString(SqltoolRB.NONE)
4635:                                                : rejectFile.getPath()),
4636:                                        ((rejectFile == null) ? null
4637:                                                : rejectFile.getPath()), }));
4638:                    } catch (IOException ioe) {
4639:                        throw new SqlToolError(rb.getString(
4640:                                SqltoolRB.DSV_REJECTREPORT_SETUPFAIL,
4641:                                dsvRejectReport), ioe);
4642:                    }
4643:
4644:                int recCount = 0;
4645:                int skipCount = 0;
4646:                PreparedStatement ps = null;
4647:                boolean importAborted = false;
4648:
4649:                try {
4650:                    try {
4651:                        ps = curConn.prepareStatement(sb.toString() + ')');
4652:                    } catch (SQLException se) {
4653:                        throw new SqlToolError(
4654:                                rb.getString(SqltoolRB.INSERTION_PREPAREFAIL,
4655:                                        sb.toString()), se);
4656:                    }
4657:                    String[] dataVals = new String[autonulls.length];
4658:                    // Length is number of cols to insert INTO, not nec. # in DSV file.
4659:                    int readColCount;
4660:                    int storeColCount;
4661:                    String currentFieldName = null;
4662:
4663:                    // Insert data rows 1-row-at-a-time
4664:                    while (true)
4665:                        try {
4666:                            try {
4667:                                recStart = recEnd + dsvRowDelim.length();
4668:
4669:                                if (recStart >= string.length()) {
4670:                                    break;
4671:                                }
4672:
4673:                                recEnd = string.indexOf(dsvRowDelim, recStart);
4674:                                lineCount++; // Increment when we have line start and end
4675:
4676:                                if (recEnd < 0) {
4677:                                    // Last record
4678:                                    recEnd = string.length();
4679:                                }
4680:                                trimmedLine = string
4681:                                        .substring(recStart, recEnd).trim();
4682:                                if (trimmedLine.length() < 1) {
4683:                                    continue; // Silently skip blank lines
4684:                                }
4685:                                if (skipPrefix != null
4686:                                        && trimmedLine.startsWith(skipPrefix)) {
4687:                                    skipCount++;
4688:                                    continue;
4689:                                }
4690:                                if (switching) {
4691:                                    if (trimmedLine.equals("}")) {
4692:                                        switching = false;
4693:                                        continue;
4694:                                    }
4695:                                    int colonAt = trimmedLine.indexOf(':');
4696:                                    if (colonAt < 1
4697:                                            || colonAt == trimmedLine.length() - 1) {
4698:                                        throw new SqlToolError(
4699:                                                rb
4700:                                                        .getString(
4701:                                                                SqltoolRB.DSV_HEADER_MATCHERNONHEAD,
4702:                                                                lineCount));
4703:                                    }
4704:                                    continue;
4705:                                }
4706:
4707:                                // Finally we will attempt to add a record!
4708:                                recCount++;
4709:                                // Remember that recCount counts both inserts + rejects
4710:
4711:                                colStart = recStart;
4712:                                colEnd = -1;
4713:                                readColCount = 0;
4714:                                storeColCount = 0;
4715:
4716:                                while (true) {
4717:                                    if (colEnd == recEnd) {
4718:                                        // We processed final column last time through loop
4719:                                        break;
4720:                                    }
4721:
4722:                                    colEnd = string.indexOf(dsvColDelim,
4723:                                            colStart);
4724:
4725:                                    if (colEnd < 0 || colEnd > recEnd) {
4726:                                        colEnd = recEnd;
4727:                                    }
4728:
4729:                                    if (readColCount == inputColHeadCount) {
4730:                                        throw new RowError(
4731:                                                rb
4732:                                                        .getString(
4733:                                                                SqltoolRB.DSV_COLCOUNT_MISMATCH,
4734:                                                                inputColHeadCount,
4735:                                                                1 + readColCount));
4736:                                    }
4737:
4738:                                    if (headers[readColCount++] != null) {
4739:                                        dataVals[storeColCount++] = string
4740:                                                .substring(colStart, colEnd);
4741:                                    }
4742:                                    colStart = colEnd + dsvColDelim.length();
4743:                                }
4744:                                if (readColCount < inputColHeadCount) {
4745:                                    throw new RowError(rb.getString(
4746:                                            SqltoolRB.DSV_COLCOUNT_MISMATCH,
4747:                                            inputColHeadCount, readColCount));
4748:                                }
4749:                                /* Already checked for readColCount too high in prev. block */
4750:
4751:                                if (constColMap != null) {
4752:                                    Iterator it = constColMap.values()
4753:                                            .iterator();
4754:                                    while (it.hasNext()) {
4755:                                        dataVals[storeColCount++] = (String) it
4756:                                                .next();
4757:                                    }
4758:                                }
4759:                                if (storeColCount != dataVals.length) {
4760:                                    throw new RowError(rb.getString(
4761:                                            SqltoolRB.DSV_INSERTCOL_MISMATCH,
4762:                                            dataVals.length, storeColCount));
4763:                                }
4764:
4765:                                for (int i = 0; i < dataVals.length; i++) {
4766:                                    currentFieldName = insertFieldName[i];
4767:                                    if (autonulls[i])
4768:                                        dataVals[i] = dataVals[i].trim();
4769:                                    // N.b. WE SPECIFICALLY DO NOT HANDLE TIMES WITHOUT
4770:                                    // DATES, LIKE "3:14:00", BECAUSE, WHILE THIS MAY BE
4771:                                    // USEFUL AND EFFICIENT, IT IS NOT PORTABLE.
4772:                                    //System.err.println("ps.setString(" + i + ", "
4773:                                    //      + dataVals[i] + ')');
4774:
4775:                                    if (parseDate[i]) {
4776:                                        if ((dataVals[i].length() < 1 && autonulls[i])
4777:                                                || dataVals[i]
4778:                                                        .equals(nullRepToken)) {
4779:                                            ps.setTimestamp(i + 1, null);
4780:                                        } else {
4781:                                            dateString = (dataVals[i]
4782:                                                    .indexOf(':') > 0) ? dataVals[i]
4783:                                                    : (dataVals[i] + " 0:00:00");
4784:                                            // BEWARE:  This may not work for some foreign
4785:                                            // date/time formats.
4786:                                            try {
4787:                                                ps
4788:                                                        .setTimestamp(
4789:                                                                i + 1,
4790:                                                                java.sql.Timestamp
4791:                                                                        .valueOf(dateString));
4792:                                            } catch (IllegalArgumentException iae) {
4793:                                                throw new RowError(
4794:                                                        rb
4795:                                                                .getString(
4796:                                                                        SqltoolRB.TIME_BAD,
4797:                                                                        dateString),
4798:                                                        iae);
4799:                                            }
4800:                                        }
4801:                                    } else if (parseBool[i]) {
4802:                                        if ((dataVals[i].length() < 1 && autonulls[i])
4803:                                                || dataVals[i]
4804:                                                        .equals(nullRepToken)) {
4805:                                            ps.setNull(i + 1,
4806:                                                    java.sql.Types.BOOLEAN);
4807:                                        } else {
4808:                                            try {
4809:                                                ps.setBoolean(i + 1, Boolean
4810:                                                        .valueOf(dataVals[i])
4811:                                                        .booleanValue());
4812:                                                // Boolean... is equivalent to Java 4's
4813:                                                // Boolean.parseBoolean().
4814:                                            } catch (IllegalArgumentException iae) {
4815:                                                throw new RowError(
4816:                                                        rb
4817:                                                                .getString(
4818:                                                                        SqltoolRB.BOOLEAN_BAD,
4819:                                                                        dataVals[i]),
4820:                                                        iae);
4821:                                            }
4822:                                        }
4823:                                    } else {
4824:                                        ps
4825:                                                .setString(
4826:                                                        i + 1,
4827:                                                        (((dataVals[i].length() < 1 && autonulls[i]) || dataVals[i]
4828:                                                                .equals(nullRepToken)) ? null
4829:                                                                : dataVals[i]));
4830:                                    }
4831:                                    currentFieldName = null;
4832:                                }
4833:
4834:                                retval = ps.executeUpdate();
4835:
4836:                                if (retval != 1) {
4837:                                    throw new RowError(
4838:                                            rb
4839:                                                    .getString(
4840:                                                            SqltoolRB.INPUTREC_MODIFIED,
4841:                                                            retval));
4842:                                }
4843:
4844:                                possiblyUncommitteds.set(true);
4845:                            } catch (SQLException se) {
4846:                                throw new RowError(null, se);
4847:                            }
4848:                        } catch (RowError re) {
4849:                            rejectCount++;
4850:                            if (rejectWriter != null
4851:                                    || rejectReportWriter != null) {
4852:                                if (rejectWriter != null) {
4853:                                    rejectWriter.print(string.substring(
4854:                                            recStart, recEnd)
4855:                                            + dsvRowDelim);
4856:                                }
4857:                                if (rejectReportWriter != null) {
4858:                                    genRejectReportRecord(rejectReportWriter,
4859:                                            rejectCount, lineCount,
4860:                                            currentFieldName, re.getMessage(),
4861:                                            re.getCause());
4862:                                }
4863:                            } else {
4864:                                importAborted = true;
4865:                                throw new SqlToolError(rb.getString(
4866:                                        SqltoolRB.DSV_RECIN_FAIL, lineCount,
4867:                                        currentFieldName)
4868:                                        + ((re.getMessage() == null) ? ""
4869:                                                : ("  " + re.getMessage())), re
4870:                                        .getCause());
4871:                            }
4872:                        }
4873:                } finally {
4874:                    String summaryString = null;
4875:                    if (recCount > 0) {
4876:                        summaryString = rb
4877:                                .getString(
4878:                                        SqltoolRB.DSV_IMPORT_SUMMARY,
4879:                                        new String[] {
4880:                                                ((skipPrefix == null) ? ""
4881:                                                        : ("'" + skipPrefix + "'-")),
4882:                                                Integer.toString(skipCount),
4883:                                                Integer.toString(rejectCount),
4884:                                                Integer.toString(recCount
4885:                                                        - rejectCount),
4886:                                                (importAborted ? "importAborted"
4887:                                                        : null) });
4888:                        stdprintln(summaryString);
4889:                    }
4890:                    try {
4891:                        if (recCount > rejectCount && !curConn.getAutoCommit()) {
4892:                            stdprintln(rb
4893:                                    .getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
4894:                        }
4895:                    } catch (SQLException se) {
4896:                        stdprintln(rb.getString(SqltoolRB.AUTOCOMMIT_FETCHFAIL));
4897:                        stdprintln(rb
4898:                                .getString(SqltoolRB.INSERTIONS_NOTCOMMITTED));
4899:                        // No reason to throw here.  If use attempts to use the
4900:                        // connection for anything significant, we will throw then.
4901:                    }
4902:                    if (rejectWriter != null) {
4903:                        rejectWriter.flush();
4904:                        rejectWriter.close();
4905:                    }
4906:                    if (rejectReportWriter != null && rejectCount > 0) {
4907:                        rejectReportWriter.println(rb.getString(
4908:                                SqltoolRB.REJECTREPORT_BOTTOM, summaryString,
4909:                                revnum));
4910:                        rejectReportWriter.flush();
4911:                        rejectReportWriter.close();
4912:                    }
4913:                    if (rejectCount == 0) {
4914:                        if (rejectFile != null && rejectFile.exists()
4915:                                && !rejectFile.delete())
4916:                            errprintln(rb.getString(
4917:                                    SqltoolRB.DSV_REJECTFILE_PURGEFAIL,
4918:                                    rejectFile.toString()));
4919:                        if (rejectReportFile != null
4920:                                && !rejectReportFile.delete())
4921:                            errprintln(rb.getString(
4922:                                    SqltoolRB.DSV_REJECTREPORT_PURGEFAIL,
4923:                                    (rejectFile == null) ? null : rejectFile
4924:                                            .toString()));
4925:                        // These are trivial errors.
4926:                    }
4927:                }
4928:            }
4929:
4930:            public static void appendLine(StringBuffer sb, String s) {
4931:                sb.append(s + LS);
4932:            }
4933:
4934:            /**
4935:             * Does a poor-man's parse of a MSDOS command line and parses it
4936:             * into a WIndows cmd.exe invocation to approximate.
4937:             */
4938:            static private String[] genWinArgs(String monolithic) {
4939:                List list = new ArrayList();
4940:                list.add("cmd.exe");
4941:                list.add("/y");
4942:                list.add("/c");
4943:                Matcher m = wincmdPattern.matcher(monolithic);
4944:                String[] internalTokens;
4945:                while (m.find()) {
4946:                    for (int i = 1; i <= m.groupCount(); i++) {
4947:                        if (m.group(i) == null)
4948:                            continue;
4949:                        if (m.group(i).length() > 1
4950:                                && m.group(i).charAt(0) == '"') {
4951:                            list.add(m.group(i).substring(1,
4952:                                    m.group(i).length() - 1));
4953:                            continue;
4954:                        }
4955:                        internalTokens = m.group(i).split("\\s+");
4956:                        for (int j = 0; j < internalTokens.length; j++)
4957:                            list.add(internalTokens[j]);
4958:                    }
4959:                }
4960:                return (String[]) list.toArray(new String[] {});
4961:            }
4962:
4963:            private void genRejectReportRecord(PrintWriter pw, int rCount,
4964:                    int lCount, String field, String eMsg, Throwable cause) {
4965:                pw
4966:                        .println(rb
4967:                                .getString(
4968:                                        SqltoolRB.REJECTREPORT_ROW,
4969:                                        new String[] {
4970:                                                ((rCount % 2 == 0) ? "even"
4971:                                                        : "odd")
4972:                                                        + "row",
4973:                                                Integer.toString(rCount),
4974:                                                Integer.toString(lCount),
4975:                                                ((field == null) ? "&nbsp;"
4976:                                                        : field),
4977:                                                (((eMsg == null) ? "" : eMsg)
4978:                                                        + ((eMsg == null || cause == null) ? ""
4979:                                                                : "<HR/>") + ((cause == null) ? ""
4980:                                                        : ((cause instanceof  SQLException && cause
4981:                                                                .getMessage() != null) ? cause
4982:                                                                .getMessage()
4983:                                                                : cause
4984:                                                                        .toString()))) }));
4985:            }
4986:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.