1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.db.databases;
22  
23  import java.sql.Connection;
24  import java.sql.ResultSet;
25  import java.sql.SQLException;
26  import java.sql.Statement;
27  import java.util.Map;
28  
29  import org.apache.commons.dbutils.BasicRowProcessor;
30  import org.apache.commons.dbutils.RowProcessor;
31  import org.efaps.db.databases.information.TableInformation;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36  
37  /**
38   * The class implements Apache Derby specific methods for data base access.
39   *
40   * @author The eFaps Team
41   * @version $Id$
42   */
43  public class DerbyDatabase
44      extends AbstractDatabase<DerbyDatabase>
45  {
46      /**
47       * Logging instance used in this class.
48       */
49      private static final Logger LOG = LoggerFactory.getLogger(DerbyDatabase.class);
50  
51      /**
52       * SQL Select statement for all foreign keys and constraints.
53       *
54       * @see #deleteAll(Connection)
55       */
56      private static final String SELECT_ALL_KEYS
57          = "select t.TABLENAME, c.CONSTRAINTNAME "
58                + "from SYS.SYSSCHEMAS s, SYS.SYSTABLES t, SYS.SYSCONSTRAINTS c "
59                + "where s.AUTHORIZATIONID<>'DBA' "
60                      + "and s.SCHEMAID=t.SCHEMAID "
61                      + "and t.TABLEID=c.TABLEID "
62                      + "and c.TYPE='F'";
63  
64      /**
65       * SQL Select statement for all views.
66       *
67       * @see #deleteAll(Connection)
68       */
69      private static final String SELECT_ALL_VIEWS
70          = "select t.TABLENAME "
71                + "from SYS.SYSSCHEMAS s, SYS.SYSTABLES t "
72                + "where s.AUTHORIZATIONID<>'DBA' "
73                      + "and s.SCHEMAID=t.SCHEMAID "
74                      + "and t.TABLETYPE='V'";
75  
76      /**
77       * SQL Select statement for all tables.
78       *
79       * @see #deleteAll(Connection)
80       */
81      private static final String SELECT_ALL_TABLES
82          = "select t.TABLENAME "
83                + "from SYS.SYSSCHEMAS s, SYS.SYSTABLES t "
84                + "where s.AUTHORIZATIONID<>'DBA' "
85                      + "and s.SCHEMAID=t.SCHEMAID "
86                      + "and t.TABLETYPE='T'";
87  
88  
89      /**
90       * Singleton processor instance that handlers share to save memory. Notice
91       * the default scoping to allow only classes in this package to use this
92       * instance.
93       */
94      private static final RowProcessor ROWPROCESSOR = new BasicRowProcessor();
95  
96      /**
97       * Constructur.
98       * TODO: specificy real column type
99       */
100     public DerbyDatabase()
101     {
102         addMapping(ColumnType.INTEGER,      "bigint",     "cast(null as bigint)",     "bigint");
103 //    this.columnMap.put(ColumnType.REAL,         "real");
104         addMapping(ColumnType.STRING_SHORT, "char",       "cast(null as char)",       "char");
105         addMapping(ColumnType.STRING_LONG,  "varchar",    "cast(null as varchar)",    "varchar");
106         addMapping(ColumnType.DATETIME,     "timestamp",  "cast(null as timestamp)",  "timestamp");
107         addMapping(ColumnType.BLOB,         "blob(2G)",   "cast(null as blob)",       "blob");
108         addMapping(ColumnType.CLOB,         "clob(2G)",   "cast(null as clob)",       "clob");
109         addMapping(ColumnType.BOOLEAN,      "smallint",   "cast(null as smallint)",   "smallint");
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public boolean isConnected(final Connection _connection)
117     {
118         // FIXME must be implemented
119         return false;
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
126     public String getCurrentTimeStamp()
127     {
128         return "current_timestamp";
129     }
130 
131     /**
132      * {@inheritDoc}
133      */
134     @Override
135     public String getTimestampValue(final String _isoDateTime)
136     {
137         return "timestamp '" + _isoDateTime + "'";
138     }
139 
140     @Override
141     public Object getBooleanValue(final Boolean _value)
142     {
143         // TODO Auto-generated method stub
144         return _value;
145     }
146 
147     /**
148      * This is the Derby specific implementation of an all deletion. Following
149      * order is used to remove all eFaps specific information:
150      * <ul>
151      * <li>remove all foreign keys of the user</li>
152      * <li>remove all views of the user</li>
153      * <li>remove all tables of the user</li>
154      * </ul>
155      * Attention! If application specific tables, views or constraints are
156      * defined, this database objects are also removed!
157      *
158      * @param _con  sql connection
159      * @throws SQLException if remove of keys, views or tables failed
160      */
161     @Override
162     @SuppressFBWarnings("SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE")
163     public void deleteAll(final Connection _con)
164         throws SQLException
165     {
166         final Statement stmtSel = _con.createStatement();
167         final Statement stmtExec = _con.createStatement();
168 
169         try  {
170             // remove all foreign keys
171             if (DerbyDatabase.LOG.isInfoEnabled())  {
172                 DerbyDatabase.LOG.info("Remove all Foreign Keys");
173             }
174             ResultSet rs = stmtSel.executeQuery(DerbyDatabase.SELECT_ALL_KEYS);
175             while (rs.next())  {
176                 final String tableName = rs.getString(1);
177                 final String constrName = rs.getString(2);
178                 if (DerbyDatabase.LOG.isDebugEnabled())  {
179                     DerbyDatabase.LOG.debug("  - Table '" + tableName + "' Constraint '" + constrName + "'");
180                 }
181                 stmtExec.execute("alter table " + tableName + " drop constraint " + constrName);
182             }
183             rs.close();
184 
185             // remove all views
186             if (DerbyDatabase.LOG.isInfoEnabled())  {
187                 DerbyDatabase.LOG.info("Remove all Views");
188             }
189             rs = stmtSel.executeQuery(DerbyDatabase.SELECT_ALL_VIEWS);
190             while (rs.next())  {
191                 final String viewName = rs.getString(1);
192                 if (DerbyDatabase.LOG.isDebugEnabled())  {
193                     DerbyDatabase.LOG.debug("  - View '" + viewName + "'");
194                 }
195                 stmtExec.execute("drop view " + viewName);
196             }
197             rs.close();
198 
199             // remove all tables
200             if (DerbyDatabase.LOG.isInfoEnabled())  {
201                 DerbyDatabase.LOG.info("Remove all Tables");
202             }
203             rs = stmtSel.executeQuery(DerbyDatabase.SELECT_ALL_TABLES);
204             while (rs.next())  {
205                 final String tableName = rs.getString(1);
206                 if (DerbyDatabase.LOG.isDebugEnabled())  {
207                     DerbyDatabase.LOG.debug("  - Table '" + tableName + "'");
208                 }
209                 stmtExec.execute("drop table " + tableName);
210             }
211             rs.close();
212         } finally  {
213             stmtSel.close();
214             stmtExec.close();
215         }
216     }
217 
218     /**
219      * {@inheritDoc}
220      */
221     @Override
222     public DerbyDatabase deleteView(final Connection _con,
223                                     final String _name)
224         throws SQLException
225     {
226         throw new Error("not implemented");
227     }
228 
229     /**
230      * {@inheritDoc}
231      */
232     @Override
233     public DerbyDatabase createTable(final Connection _con,
234                                      final String _table/*,
235                                      final String _parentTable*/)
236         throws SQLException
237     {
238         final Statement stmt = _con.createStatement();
239 
240         try  {
241 
242             // create table itself
243             final StringBuilder cmd = new StringBuilder()
244                 .append("create table ").append(_table).append(" (")
245                 .append("  ID bigint not null");
246 /* TODO
247             // auto increment
248             if (_parentTable == null)  {
249                 cmd.append(" generated always as identity (start with 1, increment by 1)");
250             }
251 */
252             cmd.append(",")
253                 .append("  constraint ").append(_table).append("_UK_ID unique(ID)");
254 /* TODO
255             // foreign key to parent sql table
256             if (_parentTable != null)  {
257                 cmd.append(",")
258                     .append("constraint ").append(_table).append("_FK_ID ")
259                     .append("  foreign key(ID) ")
260                     .append("  references ").append(_parentTable).append("(ID)");
261             }
262 */
263             cmd.append(")");
264             stmt.executeUpdate(cmd.toString());
265         } finally  {
266             stmt.close();
267         }
268 
269         return this;
270     }
271 
272     /**
273      * {@inheritDoc}
274      *
275      */
276     @Override
277     public DerbyDatabase defineTableAutoIncrement(final Connection _con,
278                                                   final String _table)
279         throws SQLException
280     {
281         throw new Error("not implemented");
282     }
283 
284     /**
285      * Adds a column to a SQL table. The method overrides the original method,
286      * because Derby does not allow for <code>NOT NULL</code> columns that no
287      * default value is defined. Is such column is created, the default value
288      * for real and integer is <code>0</code>, for date time, short and long
289      * string a zero length string.
290      *
291      * @param _con          SQL connection
292      * @param _tableName    name of table to update
293      * @param _columnName   column to add
294      * @param _columnType   type of column to add
295      * @param _defaultValue default value of the column (or null if not
296      *                      specified)
297      * @param _length       length of column to add (or 0 if not specified)
298      * @param _scale        scale of the column to add (or 0 if not
299      *                      specified)
300      * @throws SQLException if the column could not be added to the tables
301      * @return this
302      */
303     @Override
304     //CHECKSTYLE:OFF
305     public DerbyDatabase addTableColumn(final Connection _con,
306                                         final String _tableName,
307                                         final String _columnName,
308                                         final ColumnType _columnType,
309                                         final String _defaultValue,
310                                         final int _length,
311                                         final int _scale)
312         throws SQLException
313     {
314       //CHECKSTYLE:ON
315         String defaultValue = _defaultValue;
316 
317         if (defaultValue == null)  {
318             switch (_columnType)  {
319                 case INTEGER:
320                 case REAL:
321                     defaultValue = "0";
322                     break;
323                 case DATETIME:
324                 case STRING_LONG:
325                 case STRING_SHORT:
326                     defaultValue = "''";
327                     break;
328                 default:
329                     break;
330             }
331         }
332         return super.addTableColumn(_con, _tableName, _columnName, _columnType,
333                                     defaultValue, _length, _scale);
334     }
335 
336     /**
337      * @return always <i>true</i> because supported by Derby database
338      */
339     @Override
340     public boolean supportsGetGeneratedKeys()
341     {
342         return true;
343     }
344 
345     /**
346      * @return always <i>true</i> because supported by PostgreSQL database
347      */
348     @Override
349     public boolean supportsBinaryInputStream()
350     {
351         return false;
352     }
353 
354     /**
355      * @return always <i>false</i> because Apache Derby has some problems to
356      *         handle to big transactions
357      */
358     @Override
359     public boolean supportsBigTransactions()
360     {
361         return false;
362     }
363 
364     /**
365      * {@inheritDoc}
366      */
367     @Override
368     public DerbyDatabase createSequence(final Connection _con,
369                                         final String _name,
370                                         final long _startValue)
371     {
372         throw new Error("not implemented");
373     }
374 
375     /**
376      * {@inheritDoc}
377      */
378     @Override
379     public DerbyDatabase deleteSequence(final Connection _con,
380                                         final String _name)
381     {
382         throw new Error("not implemented");
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388     @Override
389     public boolean existsSequence(final Connection _con,
390                                   final String _name)
391     {
392         throw new Error("not implemented");
393     }
394 
395     /**
396      * {@inheritDoc}
397      */
398     @Override
399     public long nextSequence(final Connection _con,
400                              final String _name)
401         throws SQLException
402     {
403         throw new Error("not implemented");
404     }
405 
406     /**
407      * {@inheritDoc}
408      */
409     @Override
410     public DerbyDatabase setSequence(final Connection _con,
411                                      final String _name,
412                                      final long _value)
413         throws SQLException
414     {
415         // TODO Auto-generated method stub
416         throw new Error("not implemented");
417     }
418 
419     /**
420      * {@inheritDoc}
421      */
422     @Override
423     public String getHibernateDialect()
424     {
425         return "org.hibernate.dialect.DerbyTenSevenDialect";
426     }
427 
428     /**
429      * Evaluates for given table name all information about the table and returns
430      * them as instance of {@link TableInformation}.<br/>
431      * This method overwrites the original method because the standard JDBC
432      * methods do not work for the Derby database to get unique keys.
433      *
434      * @param _con        SQL connection
435      * @param _tableName  name of SQL table for which the information is fetched
436      * @return instance of {@link TableInformation} with table information
437      * @throws SQLException if information about the table could not be fetched
438      * @see TableInformation
439      */
440     /**
441      * Fetches all unique keys for this table. Instead of using the JDBC
442      * meta data functionality, a SQL statement on system tables are used,
443      * because the JDBC meta data functionality returns for unique keys
444      * internal names and not the real names. Also if a unique key includes
445      * also columns with null values, this unique keys are not included.
446      *
447      * @param _con        Connection
448      * @param _sql        Statement
449      * @param _cache4Name   cache
450      * @throws SQLException if unique keys could not be fetched
451      */
452     @Override
453     protected void initTableInfoUniqueKeys(final Connection _con,
454                                            final String _sql,
455                                            final Map<String, TableInformation> _cache4Name)
456         throws SQLException
457     {
458         final String sqlStmt = new StringBuilder()
459             .append("select t.tablename as TABLE_NAME, c.CONSTRAINTNAME as INDEX_NAME, g.DESCRIPTOR as COLUMN_NAME")
460             .append(" from SYS.SYSTABLES t, SYS.SYSCONSTRAINTS c, SYS.SYSKEYS k, SYS.SYSCONGLOMERATES g ")
461             .append(" where t.TABLEID=c.TABLEID")
462                 .append(" AND c.TYPE='U'")
463                 .append(" AND c.CONSTRAINTID = k.CONSTRAINTID")
464                 .append(" AND k.CONGLOMERATEID = g.CONGLOMERATEID")
465             .toString();
466         super.initTableInfoUniqueKeys(_con, sqlStmt, _cache4Name);
467     }
468 
469     /**
470      * {@inheritDoc}
471      */
472     @Override
473     protected StringBuilder getAlterColumn(final String _columnName,
474                                            final org.efaps.db.databases.AbstractDatabase.ColumnType _columnType)
475     {
476         final StringBuilder ret = new StringBuilder()
477             .append(" alter ").append(getColumnQuote()).append(_columnName).append(getColumnQuote())
478             .append(" type ")
479             .append(getWriteSQLTypeName(_columnType));
480         return ret;
481     }
482 
483     /**
484      * {@inheritDoc}
485      */
486     @Override
487     protected StringBuilder getAlterColumnIsNotNull(final String _columnName,
488                                                     final boolean _isNotNull)
489     {
490         final StringBuilder ret = new StringBuilder()
491             .append(" alter column ").append(getColumnQuote()).append(_columnName).append(getColumnQuote());
492         if (_isNotNull) {
493             ret.append(" set ");
494         } else {
495             ret.append(" drop ");
496         }
497         ret.append(" not null");
498         return ret;
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
505     protected boolean check4NullValues(final Connection _con,
506                                        final String _tableName,
507                                        final String _columnName)
508         throws SQLException
509     {
510         boolean ret = true;
511         final StringBuilder cmd = new StringBuilder();
512         cmd.append("select count(*) from ").append(getTableQuote()).append(_tableName).append(getTableQuote())
513                 .append(" where ").append(getColumnQuote()).append(_columnName).append(getColumnQuote())
514                 .append(" is null");
515 
516         DerbyDatabase.LOG.debug("    ..SQL> {}", cmd);
517 
518         final Statement stmt = _con.createStatement();
519         ResultSet rs = null;
520         try {
521             rs = stmt.executeQuery(cmd.toString());
522             rs.next();
523             ret = rs.getInt(1) > 0;
524         } finally {
525             if (rs != null) {
526                 rs.close();
527             }
528             stmt.close();
529         }
530         return ret;
531     }
532 
533     /**
534      * {@inheritDoc}
535      */
536     @Override
537     public RowProcessor getRowProcessor()
538     {
539         return DerbyDatabase.ROWPROCESSOR;
540     }
541 }