View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2006 Tim Pizey
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Tim Pizey (timp At paneris.org)
42   *
43   */
44  package org.melati.poem.dbms;
45  import java.sql.Connection;
46  import java.sql.DatabaseMetaData;
47  import java.sql.ResultSet;
48  import java.sql.SQLException;
49  
50  import org.melati.poem.BigDecimalPoemType;
51  import org.melati.poem.BinaryPoemType;
52  import org.melati.poem.BooleanPoemType;
53  import org.melati.poem.Column;
54  import org.melati.poem.DatePoemType;
55  import org.melati.poem.DoublePoemType;
56  import org.melati.poem.IntegerPoemType;
57  import org.melati.poem.LongPoemType;
58  import org.melati.poem.PoemType;
59  import org.melati.poem.SQLPoemType;
60  import org.melati.poem.SQLType;
61  import org.melati.poem.StringPoemType;
62  import org.melati.poem.TimestampPoemType;
63  import org.melati.poem.dbms.SQLServer.SQLServerBooleanPoemType;
64  import org.melati.poem.util.StringUtils;
65  /**
66   * A Driver for the Microsoft Access database server.
67   * http://www.tobychampion.co.uk/Access2000Dialect.java.txt
68   * 
69   */
70  public class MSAccess extends AnsiStandard {
71    /** Size of text fields. */
72    public static final int msAccessTextHack = 250;
73    /** Size of memo fields. */
74    public static final int msAccessMemoSize = 1073741823;
75    /** Size of binary fields. */
76    public static final int msAccessBinarySize = 510;
77  
78    /** Constructor. */
79    public MSAccess() {
80      // Appears to work 
81       setDriverClassName("sun.jdbc.odbc.JdbcOdbcDriver");
82      // Worked once or twice
83      // Need new licence keys all the time
84      //setDriverClassName("easysoft.sql.jobDriver"); 
85    }
86  
87    /** 
88     * {@inheritDoc}
89     * @see org.melati.poem.dbms.AnsiStandard#canDropColumns()
90     */
91    public boolean canDropColumns() {
92      return false;
93    }
94    /** 
95     * {@inheritDoc}
96     * @see org.melati.poem.dbms.Dbms#canStoreBlobs()
97     */
98    public boolean canStoreBlobs(){
99      return false;
100   }
101 
102 
103   @Override
104   public void shutdown(Connection connection) throws SQLException {
105     // FIXME Something wrong here
106     connection.commit();
107   }
108 
109   @Override
110   public String unreservedName(String name) {
111     if(name.equalsIgnoreCase("GROUP")) name = "MELATI_" + name.toUpperCase();
112     if(name.equalsIgnoreCase("USER")) name = "MELATI_" + name.toUpperCase();
113     return name;
114   }
115 
116   /**
117    * Ignore tables starting with '~', which should 
118    * probably have a jdbc type of 'SYSTEM TABLE'.
119    */
120   @Override
121   public String melatiName(String name) {
122     if (name == null) return name;
123     if (name.startsWith("~"))
124       return null;
125     if(name.equalsIgnoreCase("MELATI_GROUP")) name = "group";
126     if(name.equalsIgnoreCase("MELATI_USER")) name = "user";
127     return name;
128   }
129 
130 
131   @Override
132   public String getSqlDefinition(String sqlTypeName) {
133     if (sqlTypeName.equals("BOOLEAN")) {
134       return ("BIT");
135     }
136     if (sqlTypeName.equals("DOUBLE PRECISION")) {
137       return ("DOUBLE");
138     }
139     if (sqlTypeName.equals("INT8")) {
140       return ("INTEGER");
141     }
142     if (sqlTypeName.equals("Big Decimal")) {
143       return ("NUMERIC");
144     }
145     return super.getSqlDefinition(sqlTypeName);
146   }
147 
148   @Override
149   public String getLongSqlDefinition() {
150     return "INTEGER";
151   }
152 
153   @Override
154   public String getStringSqlDefinition(int size) throws SQLException {
155     if (size < 0) { 
156       // Don't use TEXT as it doesn't seem to work as documented
157       return "VARCHAR(" + msAccessTextHack + ")";
158     }
159     return super.getStringSqlDefinition(size);
160   }
161   
162   /**
163    * Cludge?
164    */
165   @Override
166   public String getFixedPtSqlDefinition(int scale, int precision)
167   throws SQLException {
168     if (scale < 0 || precision <= 0)
169       throw new SQLException(
170       "negative scale or nonpositive precision not supported in AnsiStandard DECIMALs");
171     return "NUMERIC";
172   }
173 
174   @Override
175   public String getBinarySqlDefinition(int size) throws SQLException {
176     if (size < 0)
177       return "BINARY"; // 512
178     return "BINARY(" + size + ")";
179   }
180 
181   @Override
182   public String sqlBooleanValueOfRaw(Object raw) {
183     if (((Boolean)raw).booleanValue())
184       return "1";
185     else
186       return "0";
187   }
188 
189   @Override
190   public <S,O>PoemType<O> canRepresent(PoemType<S> storage, PoemType<O> type) {
191     if (storage instanceof StringPoemType && type instanceof StringPoemType) {
192       if (((StringPoemType)storage).getSize() == msAccessTextHack
193               && ((StringPoemType) type).getSize() == -1) {
194         return type;
195       } else if (((StringPoemType)storage).getSize() == msAccessMemoSize
196                  && ((StringPoemType)type).getSize() == -1) {
197               return type;
198       } else {
199         return storage.canRepresent(type);
200       }
201     } else if (storage instanceof BinaryPoemType && type instanceof BinaryPoemType) {
202       if (((BinaryPoemType)storage).getSize() == msAccessBinarySize
203               && ((BinaryPoemType) type).getSize() == -1) {
204         return type;
205       } else {
206         return storage.canRepresent(type);
207       }
208     } else if (storage instanceof TimestampPoemType && type instanceof DatePoemType) {
209       return type;
210     } else if (storage instanceof BooleanPoemType && type instanceof BooleanPoemType) {
211       // ignore nullability
212       return type;
213     } else if (storage instanceof DoublePoemType && type instanceof BigDecimalPoemType
214             && !(!storage.getNullable() && type.getNullable())  // Nullable may represent not nullable
215     ) {
216       return type;
217     } else if (storage instanceof IntegerPoemType && type instanceof LongPoemType
218             && !(!storage.getNullable() && type.getNullable())  // Nullable may represent not nullable
219     ) {
220       return type;
221     } else {
222       return storage.canRepresent(type);
223     }
224   }
225 
226   /**
227    * Translates a MSSQL String into a Poem <code>StringPoemType</code>.
228    */
229   public static class MSAccessStringPoemType extends StringPoemType {
230     /**
231      * Constructor.
232      * @param nullable nullability
233      * @param size length normally
234      */
235     public MSAccessStringPoemType(boolean nullable, int size) {
236       super(nullable, size);
237     }
238     // We set size to sqlServerTextHack for our Text type
239     protected boolean _canRepresent(SQLPoemType<?> other) {
240       return sqlTypeCode() == other.sqlTypeCode() &&
241              (getSize() < 0 || 
242              getSize() == msAccessTextHack || 
243              getSize() >= ((StringPoemType) other).getSize());
244     }
245 
246     @Override
247     public <O>PoemType<O> canRepresent(PoemType<O> other) {
248       return other instanceof StringPoemType
249               && _canRepresent((StringPoemType) other)
250               && !(!getNullable() && ((StringPoemType) other).getNullable()) ? other
251               : null;
252     }
253   }
254 
255   @Override
256   public SQLPoemType<?> defaultPoemTypeOfColumnMetaData(ResultSet md)
257       throws SQLException {
258     /*<pre>
259      * ResultSetMetaData rsmd = md.getMetaData(); 
260      * int cols = rsmd.getColumnCount(); 
261      * for (int i = 1; i <= cols; i++) { 
262      *  String table = rsmd.getTableName(i); 
263      *  System.err.println("table name: " + table); 
264      *  String column = rsmd.getColumnName(i); 
265      *  System.err.println("column name: " + column); 
266      *  int type = rsmd.getColumnType(i); 
267      *  System.err.println("type: " + type);
268      *  String typeName = rsmd.getColumnTypeName(i);
269      *  System.err.println("type Name: " + typeName); 
270      *  String className = rsmd.getColumnClassName(i); 
271      *  System.err.println("class Name: " + className);
272      *  System.err.println("String val: " + md.getString(i));
273      *  System.err.println(""); 
274      * }
275      * </pre>
276      */
277     String typeName = md.getString("TYPE_NAME");
278     if (typeName.equals("text"))
279       return new MSAccessStringPoemType(
280                                      md.getInt("NULLABLE") == DatabaseMetaData.columnNullable,
281                                      md.getInt("COLUMN_SIZE"));
282     // We use a magic number for text fields
283 /*
284     if (md.getString("TYPE_NAME").equals("VARCHAR")
285             && md.getInt("COLUMN_SIZE") == msAccessTextHack)
286       return new MSAccessStringPoemType(
287       md.getInt("NULLABLE") == DatabaseMetaData.columnNullable, md
288               .getInt("COLUMN_SIZE"));
289     if (md.getString("TYPE_NAME").equals("CHAR"))
290       return new StringPoemType(
291                                 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable,
292                                 md.getInt("COLUMN_SIZE"));
293 */                               
294     if (typeName.equals("BINARY"))
295       return new BinaryPoemType(
296                    md.getInt("NULLABLE") == DatabaseMetaData.columnNullable,
297                    md.getInt("COLUMN_SIZE"));
298     if (typeName.equals("INT"))
299       return new LongPoemType(
300                    md.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
301     if (typeName.equals("SMALLINT"))
302       return new IntegerPoemType(
303                    md.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
304     if (typeName.equals("bit"))
305       return new SQLServerBooleanPoemType(
306               md.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
307     return super.defaultPoemTypeOfColumnMetaData(md);
308   }
309 
310   @Override
311   public String caseInsensitiveRegExpSQL(String term1, String term2) {
312     if (StringUtils.isQuoted(term2)) {
313       term2 = term2.substring(1, term2.length() - 1);
314     }
315     term2 = StringUtils.quoted(StringUtils.quoted(term2, '%'), '\'');
316 
317     return term1 + " LIKE " + term2;
318   }
319 
320   
321   /**
322    * Accommodate SQLServer syntax. {@inheritDoc}
323    */
324   @Override
325   public String alterColumnNotNullableSQL(String tableName, Column<?> column) {
326     return "ALTER TABLE " + getQuotedName(tableName) + " ALTER COLUMN "
327             + getQuotedName(column.getName()) + " "
328             + column.getSQLType().sqlDefinition(this);
329   }
330 
331   @Override
332   public String selectLimit(String querySelection, int limit) {
333     return "SELECT TOP " + limit + " " + querySelection;
334   }
335 
336 
337   @Override
338   public String getSqlDefaultValue(SQLType<?> sqlType) {
339     if (sqlType instanceof BooleanPoemType) {
340       return ("0");
341     }
342     return super.getSqlDefaultValue(sqlType);
343   }
344 }