Package henplus.commands

Source Code of henplus.commands.DescribeCommand

/*
* This is free software, licensed under the Gnu Public License (GPL) get a copy from <http://www.gnu.org/licenses/gpl.html>
*
* author: Henner Zeller <H.Zeller@acm.org>
*/
package henplus.commands;

import henplus.AbstractCommand;
import henplus.CommandDispatcher;
import henplus.HenPlus;
import henplus.Interruptable;
import henplus.SQLSession;
import henplus.SigIntHandler;
import henplus.logging.Logger;
import henplus.view.Column;
import henplus.view.ColumnMetaData;
import henplus.view.TableRenderer;

import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;

/**
* document me.
*/
public class DescribeCommand extends AbstractCommand implements Interruptable {

    private static final String[] LIST_TABLES = { "TABLE", "VIEW" };
    private static final ColumnMetaData[] DESC_META;
    static {
        DESC_META = new ColumnMetaData[9];
        DESC_META[0] = new ColumnMetaData("#", ColumnMetaData.ALIGN_RIGHT);
        DESC_META[1] = new ColumnMetaData("table");
        DESC_META[2] = new ColumnMetaData("column");
        DESC_META[3] = new ColumnMetaData("type");
        DESC_META[4] = new ColumnMetaData("null");
        DESC_META[5] = new ColumnMetaData("default");
        DESC_META[6] = new ColumnMetaData("pk");
        DESC_META[7] = new ColumnMetaData("fk");
        DESC_META[8] = new ColumnMetaData("remark", ColumnMetaData.ALIGN_LEFT, 60);
    }

    private volatile boolean _interrupted;
    private final ListUserObjectsCommand _tableCompleter;

    public DescribeCommand(final ListUserObjectsCommand tc) {
        _tableCompleter = tc;
    }

    /**
     * returns the command-strings this command can handle.
     */
    @Override
    public String[] getCommandList() {
        return new String[] { "describe", "idescribe" };
    }

    /**
     * execute the command given.
     */
    @Override
    public int execute(final SQLSession session, final String cmd, final String param) {
        // make use of properties for these properties?
        // (since the options just toggle, this may be convenient)
        boolean showDescriptions = true;
        boolean showIndex = "idescribe".equals(cmd);
        boolean showTime = true;

        final StringTokenizer st = new StringTokenizer(param);
        if (st.countTokens() < 1) {
            return SYNTAX_ERROR;
        }

        // this was a flag to ensure that all options come before the tablenames
        // can probably be removed...
        final boolean moreOptions = true;
        while (st.hasMoreTokens()) {
            String tabName = st.nextToken();
            if (moreOptions && tabName.startsWith("-")) {
                if (tabName.indexOf('i') > -1) {
                    showIndex = !showIndex;
                }
                if (tabName.indexOf('v') > -1) {
                    showDescriptions = !showDescriptions;
                }
                if (tabName.indexOf('t') > -1) {
                    showTime = !showTime;
                }
            } else {
                // more_options = false; // options can stand at every position
                // --> toggle
                boolean correctName = true;

                if (tabName.startsWith("\"")) {
                    tabName = stripQuotes(tabName);
                    correctName = false;
                }

                // separate schama and table.
                String schema = null;
                final int schemaDelim = tabName.indexOf('.');
                if (schemaDelim > 0) {
                    schema = tabName.substring(0, schemaDelim);
                    tabName = tabName.substring(schemaDelim + 1);
                }

                // FIXME: provide correct name as well for schema!
                if (correctName) {
                    final String alternative = _tableCompleter.correctTableName(tabName);
                    if (alternative != null && !alternative.equals(tabName)) {
                        tabName = alternative;
                        HenPlus.out().println("describing table: '" + tabName + "' (corrected name)");
                    }
                }

                ResultSet rset = null;
                final Set<String> doubleCheck = new HashSet<String>();
                try {
                    _interrupted = false;
                    SigIntHandler.getInstance().pushInterruptable(this);
                    boolean anyLeftArrow = false;
                    boolean anyRightArrow = false;
                    final long startTime = System.currentTimeMillis();
                    final String catalog = session.getConnection().getCatalog();
                    String description = null;
                    String tableType = null;

                    if (_interrupted) {
                        return SUCCESS;
                    }

                    final DatabaseMetaData meta = session.getConnection().getMetaData();
                    for (int i = 0; i < DESC_META.length; ++i) {
                        DESC_META[i].resetWidth();
                    }

                    rset = meta.getTables(catalog, schema, tabName, LIST_TABLES);
                    if (rset != null && rset.next()) {
                        tableType = rset.getString(4);
                        description = rset.getString(5); // remark
                    }
                    rset.close();

                    /*
                     * get primary keys.
                     */
                    if (_interrupted) {
                        return SUCCESS;
                    }
                    final Map<String, String> pks = new HashMap<String, String>();
                    rset = meta.getPrimaryKeys(null, schema, tabName);
                    if (rset != null) {
                        while (!_interrupted && rset.next()) {
                            final String col = rset.getString(4);
                            final int pkseq = rset.getInt(5);
                            final String pkname = rset.getString(6);
                            String desc = pkname != null ? pkname : "*";
                            if (pkseq > 1) {
                                desc = new StringBuilder().append(desc).append("{").append(pkseq).append("}").toString();
                            }
                            pks.put(col, desc);
                        }
                        rset.close();
                    }

                    /*
                     * get referenced primary keys.
                     */
                    if (_interrupted) {
                        return SUCCESS;
                    }
                    rset = meta.getExportedKeys(null, schema, tabName);
                    if (rset != null) {
                        while (!_interrupted && rset.next()) {
                            final String col = rset.getString(4);
                            String fktable = rset.getString(7);
                            final String fkcolumn = rset.getString(8);
                            fktable = new StringBuilder().append(fktable).append("(").append(fkcolumn).append(")").toString();
                            String desc = pks.get(col);
                            desc = desc == null ? new StringBuilder().append(" <- ").append(fktable).toString()
                                    : new StringBuilder().append(desc).append("\n <- ").append(fktable).toString();
                            anyLeftArrow = true;
                            pks.put(col, desc);
                        }
                        rset.close();
                    }

                    /*
                     * get foreign keys.
                     */
                    if (_interrupted) {
                        return SUCCESS;
                    }
                    final Map<String, String> fks = new HashMap<String, String>();

                    // some jdbc version 2 drivers (connector/j) have problems
                    // with foreign keys...
                    try {
                        rset = meta.getImportedKeys(null, schema, tabName);
                    } catch (final NoSuchElementException e) {
                        Logger.debug("Database problem reading meta data: ", e);
                    }
                    if (rset != null) {
                        while (!_interrupted && rset.next()) {
                            String table = rset.getString(3);
                            final String pkcolumn = rset.getString(4);
                            table = table + "(" + pkcolumn + ")";
                            final String col = rset.getString(8);
                            final String fkname = rset.getString(12);
                            String desc = fkname != null ? new StringBuilder().append(fkname).append("\n -> ").toString() : " -> ";
                            desc += table;
                            anyRightArrow = true;
                            fks.put(col, desc);
                        }
                        rset.close();
                    }

                    HenPlus.out().println(("VIEW".equals(tableType) ? "View: " : "Table: ") + tabName);
                    if (description != null) {
                        HenPlus.out().println(description);
                    }

                    if (catalog != null) {
                        HenPlus.msg().println("catalog: " + catalog);
                    }
                    if (anyLeftArrow) {
                        HenPlus.msg().println(" '<-' : referenced by");
                    }
                    if (anyRightArrow) {
                        HenPlus.msg().println(" '->' : referencing");
                    }

                    /*
                     * if all columns belong to the same table name, then don't
                     * report it. A different table name may only occur in rare
                     * circumstance like object oriented databases.
                     */
                    boolean allSameTableName = true;

                    /*
                     * build up actual describe table.
                     */
                    if (_interrupted) {
                        return SUCCESS;
                    }

                    rset = meta.getColumns(catalog, schema, tabName, null);
                    final List<Column[]> rows = new ArrayList<Column[]>();
                    int colNum = 0;
                    boolean anyDescription = false;
                    if (rset != null) {
                        while (!_interrupted && rset.next()) {
                            final Column[] row = new Column[9];
                            row[0] = new Column(++colNum);
                            final String thisTabName = rset.getString(3);
                            row[1] = new Column(thisTabName);
                            allSameTableName &= tabName.equals(thisTabName);
                            final String colname = rset.getString(4);
                            if (doubleCheck.contains(colname)) {
                                continue;
                            }
                            doubleCheck.add(colname);
                            row[2] = new Column(colname);
                            String type = rset.getString(6);
                            final int colSize = rset.getInt(7);
                            final int colDp = rset.getInt(9);
                            if (colSize > 0) {
                                if (colDp == 0) {
                                    type = type + "(" + colSize +")";
                                } else {
                                    type = type + "(" + colSize + "," + colDp + ")";
                                }
                            }

                            row[3] = new Column(type);
                            final String defaultVal = rset.getString(13);
                            row[4] = new Column(rset.getString(18));
                            // oracle appends newline to default values for some
                            // reason.
                            row[5] = new Column((defaultVal != null ? defaultVal.trim() : null));
                            final String pkdesc = pks.get(colname);
                            row[6] = new Column(pkdesc != null ? pkdesc : "");
                            final String fkdesc = fks.get(colname);
                            row[7] = new Column(fkdesc != null ? fkdesc : "");

                            final String colDesc = showDescriptions ? rset.getString(12) : null;
                            row[8] = new Column(colDesc);
                            anyDescription |= colDesc != null;
                            rows.add(row);
                        }
                    }
                    rset.close();

                    /*
                     * we render the table now, since we only know now, whether
                     * we will show the first column and the description column
                     * or not.
                     */
                    DESC_META[1].setDisplay(!allSameTableName);
                    DESC_META[8].setDisplay(anyDescription);
                    final TableRenderer table = new TableRenderer(DESC_META, HenPlus.out());
                    final Iterator<Column[]> it = rows.iterator();
                    while (it.hasNext()) {
                        table.addRow(it.next());
                    }
                    table.closeTable();

                    if (_interrupted) {
                        return SUCCESS;
                    }

                    if (showIndex) {
                        showIndexInformation(tabName, schema, meta);
                    }

                    if (showTime) {
                        TimeRenderer.printTime(System.currentTimeMillis() - startTime, HenPlus.out());
                        HenPlus.out().println();
                    }

                } catch (final Exception e) {
                    final String ex = e.getMessage() != null ? e.getMessage().trim() : e.toString();
                    Logger.error("Database problem reading meta data: ", ex);
                    return EXEC_FAILED;
                } finally {
                    if (rset != null) {
                        try {
                            rset.close();
                        } catch (final Exception e) {
                        }
                    }
                }

            }
        }
        return SUCCESS;
    }

    /**
     * @param tabName
     * @param schema
     * @param meta
     * @return @throws SQLException
     */
    private void showIndexInformation(final String tabName, final String schema, final DatabaseMetaData meta) throws SQLException {
        ResultSet rset;
        HenPlus.out().println("index information:");
        boolean anyIndex = false;
        rset = meta.getIndexInfo(null, schema, tabName, false, true);
        if (rset != null) {
            while (!_interrupted && rset.next()) {
                boolean nonUnique;
                String idxName = null;
                nonUnique = rset.getBoolean(4);
                idxName = rset.getString(6);
                if (idxName == null) {
                    continue; // statistics, otherwise.
                }
                // output part.
                anyIndex = true;
                HenPlus.out().print("\t");
                if (!nonUnique) {
                    HenPlus.out().print("unique ");
                }
                HenPlus.out().print("index " + idxName);
                final String colName = rset.getString(9);
                // work around postgres-JDBC-driver bug:
                if (colName != null && colName.length() > 0) {
                    HenPlus.out().print(" on " + colName);
                }
                HenPlus.out().println();
            }
        }
        rset.close();
        if (!anyIndex) {
            HenPlus.out().println("\t<none>");
        }
    }

    /**
     * complete the table name.
     */
    @Override
    public Iterator<String> complete(final CommandDispatcher disp, final String partialCommand, String lastWord) {
        final StringTokenizer st = new StringTokenizer(partialCommand);
        st.nextElement(); // consume first element.
        if (lastWord.startsWith("\"")) {
            lastWord = lastWord.substring(1);
        }
        return _tableCompleter.completeTableName(HenPlus.getInstance().getCurrentSession(), lastWord);
    }

    private String stripQuotes(String value) {
        if (value.startsWith("\"") && value.endsWith("\"")) {
            value = value.substring(1, value.length() - 1);
        }
        return value;
    }

    // -- Interruptable interface
    @Override
    public synchronized void interrupt() {
        _interrupted = true;
    }

    /**
     * return a descriptive string.
     */
    @Override
    public String getShortDescription() {
        return "describe a database object";
    }

    @Override
    public String getSynopsis(final String cmd) {
        return cmd + " [options] <tablenames>";
    }

    @Override
    public String getLongDescription(final String cmd) {
        String dsc;
        dsc = "\tDescribe the meta information of the named user object\n"
                + "\t(only tables for now). The name you type is case sensitive\n"
                + "\tbut henplus tries its best to correct it.\n" + "\tThe 'describe' command just describes the table, the\n"
                + "\t'idescribe' command determines the index information as\n"
                + "\twell; some databases are really slow in this, so this is\n" + "\tan extra command\n\n"
                // include the command line options:
                + super.getLongDescription(cmd)
                + "\n\tIf an option is positioned between two tablenames, its current state is toggled." + "\n";
        return dsc;
    }

}

/*
* Emacs: Local variables: c-basic-offset: 4 tab-width: 8 indent-tabs-mode: nil
* compile-command: "ant -emacs -find build.xml" End: vi:set tabstop=8
* shiftwidth=4 nowrap:
*/ 
TOP

Related Classes of henplus.commands.DescribeCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.