001    //$HeadURL: svn+ssh://aschmitz@wald.intevation.org/deegree/base/trunk/src/org/deegree/tools/datastore/PostGISDDLGenerator.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    package org.deegree.tools.datastore;
037    
038    import java.io.IOException;
039    import java.net.MalformedURLException;
040    import java.net.URL;
041    import java.util.ArrayList;
042    import java.util.Collection;
043    import java.util.HashMap;
044    import java.util.Iterator;
045    import java.util.Map;
046    
047    import org.deegree.datatypes.Types;
048    import org.deegree.datatypes.UnknownTypeException;
049    import org.deegree.framework.xml.XMLParsingException;
050    import org.deegree.framework.xml.schema.XMLSchemaException;
051    import org.deegree.model.crs.UnknownCRSException;
052    import org.xml.sax.SAXException;
053    
054    /**
055     * Generator for Oracle DDL (CREATE / DROP) operations to create Oracle database schemas from annotated GML schema
056     * files.
057     *
058     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
059     * @author last edited by: $Author: aschmitz $
060     *
061     * @version $Revision: 8252 $, $Date: 2007-10-05 11:54:33 +0200 (Fr, 05 Okt 2007) $
062     */
063    public class OracleDDLGenerator extends DDLGenerator {
064    
065        /**
066         * Generates a new instance of <code>OracleDDLGenerator</code>, ready to generate DDL for the given schema.
067         *
068         * @param schemaURL
069         * @throws MalformedURLException
070         * @throws IOException
071         * @throws SAXException
072         * @throws XMLParsingException
073         * @throws XMLSchemaException
074         * @throws UnknownCRSException
075         */
076        public OracleDDLGenerator( URL schemaURL ) throws MalformedURLException, IOException, SAXException,
077                                XMLParsingException, XMLSchemaException, UnknownCRSException {
078            super( schemaURL );
079        }
080    
081        @Override
082        protected StringBuffer generateSetSchemaStmt( String dbSchema ) {
083            throw new UnsupportedOperationException();
084        }
085    
086        @Override
087        protected StringBuffer generateDropTableStmt( TableDefinition table ) {
088            StringBuffer sb = new StringBuffer();
089            sb.append( "DROP TABLE " );
090            sb.append( table.getName() );
091            sb.append( " CASCADE CONSTRAINTS;\n" );
092    
093            ColumnDefinition[] columns = table.getColumns();
094            for ( int i = 0; i < columns.length; i++ ) {
095                if ( columns[i].isGeometry() ) {
096                    sb.append( "DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME='" );
097                    sb.append( table.getName() );
098                    sb.append( "';\n" );
099                    break;
100                }
101            }
102            return sb;
103        }
104    
105        @Override
106        protected StringBuffer generateCreateTableStmt( TableDefinition table ) {
107    
108            Collection<ColumnDefinition> geometryColumns = new ArrayList<ColumnDefinition>();
109            StringBuffer sb = new StringBuffer( "CREATE TABLE " );
110            sb.append( table.getName() );
111            sb.append( " (" );
112            ColumnDefinition[] columns = table.getColumns();
113            boolean needComma = false;
114            for ( int i = 0; i < columns.length; i++ ) {
115                if ( !columns[i].isGeometry() ) {
116                    if ( needComma ) {
117                        sb.append( ',' );
118                    } else {
119                        needComma = true;
120                    }
121                    sb.append( "\n    " );
122                    sb.append( columns[i].getName() );
123                    sb.append( ' ' );
124                    String typeName;
125                    try {
126                        typeName = Types.getTypeNameForSQLTypeCode( columns[i].getType() );
127                        if ( typeName.equals( "DOUBLE" ) ) {
128                            typeName = "DOUBLE PRECISION";
129                        } else if ( typeName.equals( "VARCHAR" ) ) {
130                            // always prefer VARCHAR2 to VARCHAR:
131                            // http://www.orafaq.com/faq/what_is_the_difference_between_varchar_varchar2_and_char_data_types
132                            typeName = "VARCHAR2(2000)";
133                        } else if ( typeName.equals( "BOOLEAN" ) ) {
134                            typeName = "CHAR(1)";
135                        } else if ( typeName.equals( "BIGINT" ) ) {
136                            // not available in all Oracle versions. using the same precision as Oracle 9i Lite
137                            // http://download.oracle.com/docs/html/A90108_01/sqdatyp.htm#631368
138                            typeName = "DECIMAL(19)";
139                        } else if ( typeName.equals( "NUMERIC" ) ) {
140                            typeName = "NUMBER";
141                        }
142                    } catch ( UnknownTypeException e ) {
143                        typeName = "" + columns[i].getType();
144                    }
145                    sb.append( typeName );
146                    if ( !columns[i].isNullable() ) {
147                        sb.append( " NOT NULL" );
148                    }
149                } else {
150                    if ( needComma ) {
151                        sb.append( ',' );
152                    } else {
153                        needComma = true;
154                    }
155                    sb.append( "\n    " );
156                    sb.append( columns[i].getName() );
157                    sb.append( " MDSYS.SDO_GEOMETRY" );
158                    if ( !columns[i].isNullable() ) {
159                        sb.append( " NOT NULL" );
160                    }
161    
162                    geometryColumns.add( columns[i] );
163                }
164            }
165            ColumnDefinition[] pkColumns = table.getPKColumns();
166            if ( pkColumns.length > 0 ) {
167                sb.append( ",\n    PRIMARY KEY (" );
168                for ( int i = 0; i < pkColumns.length; i++ ) {
169                    sb.append( pkColumns[i].getName() );
170                    if ( i != pkColumns.length - 1 ) {
171                        sb.append( ',' );
172                    }
173                }
174                sb.append( ')' );
175            }
176            sb.append( "\n);\n" );
177    
178            // add geometry columns
179            for ( ColumnDefinition column : geometryColumns ) {
180                sb.append( "INSERT INTO USER_SDO_GEOM_METADATA VALUES ('" );
181                sb.append( table.getName() );
182                sb.append( "', '" );
183                sb.append( column.getName() );
184                sb.append( "', SDO_DIM_ARRAY ( SDO_DIM_ELEMENT ( 'LONGITUDE', -180, 180, 0.00005), SDO_DIM_ELEMENT ('LATITUDE',-90,90,0.00005))," );
185                sb.append( column.getSRS() );
186                sb.append( ");\n" );
187            }
188            return sb;
189        }
190    
191        @Override
192        protected StringBuffer generateCreateIndexStmts( TableDefinition table ) {
193            StringBuffer sb = new StringBuffer();
194    
195            // build create statements for spatial indexes
196            Collection<ColumnDefinition> geometryColumns = new ArrayList<ColumnDefinition>();
197            for ( ColumnDefinition column : table.getColumns() ) {
198                if ( column.isGeometry() ) {
199                    geometryColumns.add( column );
200                }
201            }
202    
203            Iterator<ColumnDefinition> iter = geometryColumns.iterator();
204            int spatialIdxCnt = 1;
205            while ( iter.hasNext() ) {
206                ColumnDefinition column = iter.next();
207                sb.append( "CREATE INDEX " );
208                String idxSuffix = ( spatialIdxCnt++ ) + "_SPIDX";
209                String idxName = truncate( table.getName().toUpperCase(), idxSuffix );
210                sb.append( idxName );
211                sb.append( " ON " );
212                sb.append( table.getName() );
213                sb.append( "(" );
214                sb.append( column.getName() );
215                sb.append( ") INDEXTYPE IS MDSYS.SPATIAL_INDEX;" );
216                sb.append( '\n' );
217            }
218    
219            // build create statements for indexes on all fk columns
220            ColumnDefinition[] columns = table.getColumns();
221            for ( int i = 0; i < columns.length; i++ ) {
222                if ( columns[i].isFK() ) {
223                    sb.append( "CREATE INDEX " );
224                    String idxSuffix = '_' + columns[i].getName() + "_IDX";
225                    String idxName = truncate( table.getName().toUpperCase(), idxSuffix );
226                    sb.append( idxName );
227                    sb.append( " ON " );
228                    sb.append( table.getName() );
229                    sb.append( '(' );
230                    sb.append( columns[i].getName() );
231                    sb.append( ");" );
232                    sb.append( '\n' );
233                }
234            }
235            return sb;
236        }
237    
238        @Override
239        protected StringBuffer generateDropIndexStmts( TableDefinition table ) {
240            StringBuffer sb = new StringBuffer();
241    
242            // build drop statements for geometry indexes
243            Collection<ColumnDefinition> geometryColumns = new ArrayList<ColumnDefinition>();
244            for ( ColumnDefinition column : table.getColumns() ) {
245                if ( column.isGeometry() ) {
246                    geometryColumns.add( column );
247                }
248            }
249    
250            Iterator<ColumnDefinition> iter = geometryColumns.iterator();
251            int spatialIdxCnt = 1;
252            while ( iter.hasNext() ) {
253                iter.next();
254                sb.append( "DROP INDEX " );
255                String idxSuffix = ( spatialIdxCnt++ ) + "_SPIDX";
256                String idxName = truncate( table.getName().toUpperCase(), idxSuffix );
257                sb.append( idxName );
258                sb.append( ";\n" );
259            }
260    
261            // build table type specific drop index statements
262            switch ( table.getType() ) {
263            case JOIN_TABLE: {
264                // create an index on every column
265                ColumnDefinition[] columns = table.getColumns();
266                for ( int i = 0; i < columns.length; i++ ) {
267                    sb.append( "DROP INDEX " );
268                    String idxSuffix = '_' + columns[i].getName() + "_IDX";
269                    String idxName = truncate( table.getName().toUpperCase(), idxSuffix );
270                    sb.append( idxName );
271                    sb.append( ";\n" );
272                }
273                break;
274            }
275            default: {
276                break;
277            }
278            }
279            return sb;
280        }
281    
282        Map<String, String> idToTruncatedId = new HashMap<String, String>();
283    
284        int currentId;
285    
286        /**
287         * Ensures that the given identifier does not exceed Oracle's limit (30 characters).
288         *
289         * @param identifier
290         * @param suffix
291         * @return the identifier (may be truncated)
292         */
293        private String truncate( String identifier, String suffix ) {
294            String truncatedIdentifier = idToTruncatedId.get( identifier + suffix );
295            if ( truncatedIdentifier == null ) {
296                truncatedIdentifier = identifier + suffix;
297                if ( truncatedIdentifier.length() > 30 ) {
298                    System.out.print( "Generated identifier name '" + truncatedIdentifier
299                                      + "' exceeds 30 characters (Oracle limit)." );
300                    truncatedIdentifier = identifier.substring( 0, 30 - suffix.length() - ( "" + currentId ).length() )
301                                          + ( currentId++ ) + suffix;
302                    System.out.println( " Truncated to: '" + truncatedIdentifier + "'." );
303                }
304                idToTruncatedId.put( identifier + suffix, truncatedIdentifier );
305            }
306            return truncatedIdentifier;
307        }
308    }