View Javadoc
1   package edu.internet2.middleware.grouper.ext.org.apache.ddlutils.platform.postgresql;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.sql.SQLException;
23  import java.sql.Types;
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.Platform;
28  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.model.Column;
29  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.model.ForeignKey;
30  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.model.Index;
31  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.model.Table;
32  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.model.TypeMap;
33  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.platform.DatabaseMetaDataWrapper;
34  import edu.internet2.middleware.grouper.ext.org.apache.ddlutils.platform.JdbcModelReader;
35  
36  /**
37   * Reads a database model from a PostgreSql database.
38   *
39   * @version $Revision: $
40   */
41  public class PostgreSqlModelReader extends JdbcModelReader
42  {
43      /**
44       * Creates a new model reader for PostgreSql databases.
45       * 
46       * @param platform The platform that this model reader belongs to
47       */
48      public PostgreSqlModelReader(Platform platform)
49      {
50          super(platform);
51          setDefaultCatalogPattern(null);
52          setDefaultSchemaPattern(null);
53          setDefaultTablePattern(null);
54      }
55  
56      /**
57       * {@inheritDoc}
58       */
59      protected Table readTable(DatabaseMetaDataWrapper metaData, Map values) throws SQLException
60      {
61          Table table = super.readTable(metaData, values);
62  
63          if (table != null)
64          {
65              // PostgreSQL also returns unique indics for non-pk auto-increment columns
66              // which are of the form "[table]_[column]_key"
67              HashMap uniquesByName = new HashMap();
68      
69              for (int indexIdx = 0; indexIdx < table.getIndexCount(); indexIdx++)
70              {
71                  Index index = table.getIndex(indexIdx);
72      
73                  if (index.isUnique() && (index.getName() != null))
74                  {
75                      uniquesByName.put(index.getName(), index);
76                  }
77              }
78              for (int columnIdx = 0; columnIdx < table.getColumnCount(); columnIdx++)
79              {
80                  Column column = table.getColumn(columnIdx);
81                  if (column.isAutoIncrement() && !column.isPrimaryKey())
82                  {
83                      String indexName = table.getName() + "_" + column.getName() + "_key";
84      
85                      if (uniquesByName.containsKey(indexName))
86                      {
87                          table.removeIndex((Index)uniquesByName.get(indexName));
88                          uniquesByName.remove(indexName);
89                      }
90                  }
91              }
92          }
93          return table;
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      protected Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException
100     {
101         Column column = super.readColumn(metaData, values);
102 
103         if (column.getSize() != null)
104         {
105             if (column.getSizeAsInt() <= 0)
106             {
107                 column.setSize(null);
108                 // PostgreSQL reports BYTEA and TEXT as BINARY(-1) and VARCHAR(-1) respectively
109                 // Since we cannot currently use the Blob/Clob interface with BYTEA, we instead
110                 // map them to LONGVARBINARY/LONGVARCHAR
111                 if (column.getTypeCode() == Types.BINARY)
112                 {
113                     column.setTypeCode(Types.LONGVARBINARY);
114                 }
115                 else if (column.getTypeCode() == Types.VARCHAR)
116                 {
117                     column.setTypeCode(Types.LONGVARCHAR);
118                 }
119             }
120             // fix issue DDLUTILS-165 as postgresql-8.2-504-jdbc3.jar seems to return Integer.MAX_VALUE
121             // on columns defined as TEXT.
122             else if (column.getSizeAsInt() == Integer.MAX_VALUE)
123             {
124                 column.setSize(null);
125                 if (column.getTypeCode() == Types.VARCHAR)
126                 {
127                     column.setTypeCode(Types.LONGVARCHAR);
128                 }
129                 else if (column.getTypeCode() == Types.BINARY)
130                 {
131                     column.setTypeCode(Types.LONGVARBINARY);
132                 }
133             }
134         }
135 
136         String defaultValue = column.getDefaultValue();
137 
138         if ((defaultValue != null) && (defaultValue.length() > 0))
139         {
140             // If the default value looks like "nextval('ROUNDTRIP_VALUE_seq'::text)"
141             // then it is an auto-increment column
142             if (defaultValue.startsWith("nextval("))
143             {
144                 column.setAutoIncrement(true);
145                 defaultValue = null;
146             }
147             else
148             {
149                 // PostgreSQL returns default values in the forms "-9000000000000000000::bigint" or
150                 // "'some value'::character varying" or "'2000-01-01'::date"
151                 switch (column.getTypeCode())
152                 {
153                     case Types.INTEGER:
154                     case Types.BIGINT:
155                     case Types.DECIMAL:
156                     case Types.NUMERIC:
157                         defaultValue = extractUndelimitedDefaultValue(defaultValue);
158                         break;
159                     case Types.CHAR:
160                     case Types.VARCHAR:
161                     case Types.LONGVARCHAR:
162                     case Types.DATE:
163                     case Types.TIME:
164                     case Types.TIMESTAMP:
165                         defaultValue = extractDelimitedDefaultValue(defaultValue);
166                         break;
167                 }
168                 if (TypeMap.isTextType(column.getTypeCode()))
169                 {
170                     // We assume escaping via double quote (see also the backslash_quote setting:
171                     // http://www.postgresql.org/docs/7.4/interactive/runtime-config.html#RUNTIME-CONFIG-COMPATIBLE)
172                     defaultValue = unescape(defaultValue, "'", "''");
173                 }
174             }
175             column.setDefaultValue(defaultValue);
176         }
177         return column;
178     }
179 
180     /**
181      * Extractes the default value from a default value spec of the form
182      * "'some value'::character varying" or "'2000-01-01'::date".
183      * 
184      * @param defaultValue The default value spec
185      * @return The default value
186      */
187     private String extractDelimitedDefaultValue(String defaultValue)
188     {
189         if (defaultValue.startsWith("'"))
190         {
191             int valueEnd = defaultValue.indexOf("'::");
192 
193             if (valueEnd > 0)
194             {
195                 return defaultValue.substring("'".length(), valueEnd);
196             }
197         }
198         return defaultValue;
199     }
200     
201     /**
202      * Extractes the default value from a default value spec of the form
203      * "-9000000000000000000::bigint".
204      * 
205      * @param defaultValue The default value spec
206      * @return The default value
207      */
208     private String extractUndelimitedDefaultValue(String defaultValue)
209     {
210         int valueEnd = defaultValue.indexOf("::");
211 
212         if (valueEnd > 0)
213         {
214             return defaultValue.substring(0, valueEnd);
215         }
216         else
217         {
218             return defaultValue;
219         }
220     }
221     
222     /**
223      * {@inheritDoc}
224      */
225     protected boolean isInternalForeignKeyIndex(DatabaseMetaDataWrapper metaData, Table table, ForeignKey fk, Index index)
226     {
227         // PostgreSQL does not return an index for a foreign key
228         return false;
229     }
230 
231     /**
232      * {@inheritDoc}
233      */
234     protected boolean isInternalPrimaryKeyIndex(DatabaseMetaDataWrapper metaData, Table table, Index index)
235     {
236         // PostgreSql uses the form "[tablename]_pkey"
237         return (table.getName() + "_pkey").equals(index.getName());
238     }
239 
240 }