001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.util.db;
020
021import java.sql.Connection;
022import java.sql.SQLException;
023
024import java.sql.Blob;
025import java.sql.Timestamp;
026import java.util.List;
027
028public class Schema {
029    /**
030     * Interface for DB Table
031     */
032    public static interface Table {
033        /**
034         * Name of the Table
035         *
036         * @return Name of the Table
037         */
038        String name();
039    }
040
041    /**
042     * Interface or table columns
043     */
044    public static interface Column {
045        /**
046         * Table to which the column belongs
047         *
048         * @return table name
049         */
050        Table table();
051
052        /**
053         * Alias to be used by the select statement for this column
054         *
055         * @return alias for column
056         */
057        String asLabel();
058
059        /**
060         * Name of the column
061         *
062         * @return column name
063         */
064        String columnName();
065
066        /**
067         * Returns the datatype of the column
068         *
069         * @return column type
070         */
071        Class<?> getType();
072
073        /**
074         * Returns the length of the column
075         *
076         * @return Returns the length of the column
077         */
078        int getLength();
079
080        /**
081         * Returns if the field is a primary key or not
082         *
083         * @return true if field is a primary key
084         */
085        boolean isPrimaryKey();
086    }
087
088    /**
089     * Interface for Index
090     */
091    public static interface Index {
092        /**
093         * Column that is to be indexed
094         *
095         * @return Column that is to be indexed
096         */
097        Column column();
098    }
099
100    /**
101     * DB types
102     */
103    public static enum DBType {
104        HSQL, MySQL, ORACLE;
105    }
106
107    //TODO Add the SQL Change catalog for different DBMS.
108    /**
109     * Returns the appropriate DB type for given column according to the DB Type
110     *
111     * @param column the column that need the type
112     * @param dbType the database type
113     * @return column type
114     */
115    public static String getDbDataType(Column column, DBType dbType) {
116        String retVal = null;
117        if (String.class.equals(column.getType())) {
118            if (column.getLength() < 0) {
119                retVal = (dbType.equals(DBType.HSQL) ? "VARCHAR" : (dbType.equals(DBType.ORACLE) ? "CLOB" : "TEXT"));
120            }
121            else {
122                retVal = (dbType.equals(DBType.ORACLE) ? "VARCHAR2(" + column.getLength() + ")" : "VARCHAR("
123            + column.getLength() + ")");
124            }
125        }
126        else {
127            if (Timestamp.class.equals(column.getType())) {
128                retVal = (dbType.equals(DBType.ORACLE) ? "DATE" : "DATETIME");
129            }
130            else {
131                if (Boolean.class.equals(column.getType())) {
132                    retVal = (dbType.equals(DBType.ORACLE) ? "NUMBER(3, 0)" : "BOOLEAN");
133                }
134                else {
135                    if (Long.class.equals(column.getType())) {
136                        retVal = (dbType.equals(DBType.ORACLE) ? "NUMBER (19,0)" : "BIGINT");
137                    }
138                    else {
139                        if (Blob.class.equals(column.getType())) {
140                            retVal = (dbType.equals(DBType.MySQL) ? "MEDIUMBLOB" : (dbType.equals(DBType.ORACLE)
141                                    ? "BLOB" : "LONGVARBINARY"));
142                        }
143                        else {
144                            throw new RuntimeException("Column Type[" + column.getType() + "] not mapped to any DB Data Type !!");
145                        }
146                    }
147                }
148            }
149        }
150        return retVal;
151    }
152
153    /**
154     * Generates the SQL Statement for creating the table
155     *
156     * @param table the table name
157     * @param dbType the database type
158     * @param tableColumns the columns of the table
159     * @return CREATE TABLE SQL Statement
160     */
161    public static String generateCreateTableScript(Table table, DBType dbType, List<Column> tableColumns) {
162        StringBuilder sb = new StringBuilder("CREATE TABLE ").append(table).append(" ( ");
163        StringBuilder pk = new StringBuilder(", PRIMARY KEY ( ");
164        boolean pkFlag = false;
165        String sep = "";
166        String psep = "";
167        for (Column column : tableColumns) {
168            sb.append(sep).append(column.columnName() + " ").append(Schema.getDbDataType(column, dbType));
169            if (column.isPrimaryKey()) {
170                pkFlag = true;
171                pk.append(psep).append(column.columnName());
172                psep = ", ";
173            }
174            sep = ", ";
175        }
176        if (pkFlag) {
177            pk.append(" )");
178            sb.append(pk.toString());
179        }
180        sb.append(" )");
181        if (dbType == DBType.MySQL) {
182            sb.append(" ENGINE=InnoDB");
183        }
184        return sb.toString();
185    }
186
187    /**
188     * Generates the SQL Statement for droping the table
189     *
190     * @param table the table name
191     * @param dbType the database type
192     * @return DROP TABLE SQL Statement
193     */
194    public static String generateDropTableScript(Table table, DBType dbType) {
195        StringBuilder sb = new StringBuilder("DROP TABLE ").append(table);
196        if (dbType == DBType.ORACLE) {
197            sb.append(" purge");
198        }
199        return sb.toString();
200    }
201
202
203    /**
204     * Generates the SQL statement for creating the Index
205     *
206     * @param index the index
207     * @param dbType the database type
208     * @return CREATE INDEX SQL Statement
209     */
210    public static String generateCreateIndexScript(Index index, DBType dbType) {
211        StringBuilder sb = new StringBuilder("CREATE INDEX ").append(index).append(" ON ").append(
212                index.column().table()).append("( " + index.column().columnName() + " )");
213        return sb.toString();
214    }
215
216    /**
217     * Checks if the given connection's driver is HSQL Database Driver
218     *
219     * @param conn the connection
220     * @return true if the driver is HSQL
221     * @throws SQLException if the connection type could not be determined
222     */
223    public static boolean isHsqlConnection(Connection conn) throws SQLException {
224        if (conn.getMetaData().getDriverName().toLowerCase().contains(DBType.HSQL.name().toLowerCase())) {
225            return true;
226        }
227        return false;
228    }
229
230    /**
231     * Checks if the given connection's driver is MySQL Database Driver
232     *
233     * @param conn the connection
234     * @return true if the driver is MySQL
235     * @throws SQLException if the connection type could not be determined
236     */
237    public static boolean isMySqlConnection(Connection conn) throws SQLException {
238        if (conn.getMetaData().getDriverName().toLowerCase().contains(DBType.MySQL.name().toLowerCase())) {
239            return true;
240        }
241        return false;
242    }
243}