1 /* 2 * $Source$ 3 * $Revision$ 4 * 5 * Copyright (C) 2000 David Warnock 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 * David Warnock (david At sundayta.co.uk) 42 * Sundayta Ltd 43 * International House, 44 * 174 Three Bridges Road, 45 * Crawley, West Sussex 46 * RH10 1LE, UK 47 * 48 */ 49 package org.melati.poem.dbms; 50 51 import java.sql.DatabaseMetaData; 52 import java.sql.PreparedStatement; 53 import java.sql.ResultSet; 54 import java.sql.SQLException; 55 56 import org.melati.poem.BinaryPoemType; 57 import org.melati.poem.BooleanPoemType; 58 import org.melati.poem.Column; 59 import org.melati.poem.DatePoemType; 60 import org.melati.poem.DoublePoemType; 61 import org.melati.poem.PoemType; 62 import org.melati.poem.SQLPoemType; 63 import org.melati.poem.SQLType; 64 import org.melati.poem.StringPoemType; 65 import org.melati.poem.TimestampPoemType; 66 import org.melati.poem.util.StringUtils; 67 68 /** 69 * A Driver for the Microsoft SQL server. 70 */ 71 public class SQLServer extends AnsiStandard { 72 73 /** 74 * SQL Server does not have a pleasant <code>TEXT</code> datatype, so we use 75 * an arbetary value in a <code>VARCHAR</code>. 76 */ 77 public static final int sqlServerTextHack = 2333; 78 79 /** 80 * Maximum size of a binary field. 81 */ 82 public static final int sqlServerMaxBinarySize = 2147483647; 83 84 /** 85 * Constructor. 86 */ 87 public SQLServer() { 88 // buggy 89 // setDriverClassName("com.merant.datadirect.jdbc.sqlserver.SQLServerDriver"); 90 // setDriverClassName("sun.jdbc.odbc.JdbcOdbcDriver"); //does not work 91 // setDriverClassName("com.ashna.jturbo.driver.Driver"); //works 92 // setDriverClassName("com.jnetdirect.jsql.JSQLDriver"); //works 93 94 // 2003 95 // does not return indices without schema name ? 96 // setDriverClassName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); 97 // FreeTDS driver has many unimplemented features and => does not work. 98 99 // Works with 2003 100 // setDriverClassName("com.inet.tds.TdsDriver"); 101 102 // Not working 103 //setDriverClassName("easysoft.sql.jobDriver"); 104 // 2005 105 setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 106 } 107 108 /** 109 * Get the user we are connected as and return that as the schema. 110 * 111 * @see org.melati.poem.dbms.Dbms#getSchema() 112 * @see org.melati.poem.dbms.AnsiStandard#getSchema() 113 */ 114 public String getSchema() { 115 return null; 116 } 117 118 // Commented out as PMD objects to over riding method which only call 119 // super. 120 // public String getQuotedName(String name) { 121 // if you don't want to set 'use ANSI quoted identifiers' database property 122 // to 'true' (on SQL Server) 123 124 /* 125 * if(name.equalsIgnoreCase("nullable")) return "\"" + name+"\""; 126 * if(name.equalsIgnoreCase("unique")) return "\"" + name+"\""; 127 * if(name.equalsIgnoreCase("user")) return "q" + name; 128 * if(name.equalsIgnoreCase("group")) return "q" + name; return name; 129 */ 130 131 // if you already set 'use ANSI quoted identifiers' property to 'true' 132 // return super.getQuotedName(name); 133 // } 134 /** 135 * {@inheritDoc} 136 * 137 * @see org.melati.poem.dbms.AnsiStandard#getSqlDefinition(java.lang.String) 138 */ 139 public String getSqlDefinition(String sqlTypeName) { 140 if (sqlTypeName.equals("BOOLEAN")) { 141 return ("BIT"); 142 } 143 if (sqlTypeName.equals("DATE")) { 144 return ("DATETIME"); 145 } 146 if (sqlTypeName.equals("TIMESTAMP")) { 147 return ("DATETIME"); 148 } 149 return super.getSqlDefinition(sqlTypeName); 150 } 151 152 /** 153 * {@inheritDoc} 154 * 155 * @see org.melati.poem.dbms.AnsiStandard#getStringSqlDefinition(int) 156 */ 157 public String getStringSqlDefinition(int size) throws SQLException { 158 if (size < 0) { // Don't use TEXT as it doesn't support 159 // indexing or comparison 160 return "VARCHAR(" + sqlServerTextHack + ")"; 161 } 162 return super.getStringSqlDefinition(size); 163 } 164 165 /** 166 * Translates a MSSQL String into a Poem <code>StringPoemType</code>. 167 */ 168 //public static class SQLServerStringPoemType extends StringPoemType { 169 170 /** 171 * Constructor. 172 * 173 * @param nullable 174 * nullability 175 * @param size 176 * length 177 */ 178 //public SQLServerStringPoemType(boolean nullable, int size) { 179 // super(nullable, size); 180 //} 181 182 // MSSQL returns metadata info size 2147483647 for its TEXT type 183 // We set size to sqlServerTextHack for our Text type 184 //protected boolean _canRepresent(SQLPoemType other) { 185 // return (getSize() < 0 || getSize() == 2147483647 186 // || getSize() == sqlServerTextHack || getSize() >= ((StringPoemType)other) 187 // .getSize()); 188 //} 189 190 /** 191 * {@inheritDoc} 192 * 193 * @see org.melati.poem.BasePoemType#canRepresent(PoemType) 194 */ 195 /* 196 * public PoemType canRepresent(PoemType other) { return other instanceof 197 * StringPoemType && _canRepresent((StringPoemType) other) && 198 * !(!getNullable() && ((StringPoemType) other).getNullable()) ? other : 199 * null; } 200 */ 201 //} 202 203 /** 204 * Accomodate our String size hack. {@inheritDoc} 205 * 206 * @see org.melati.poem.dbms.AnsiStandard#canRepresent 207 */ 208 public <S,O>PoemType<O> canRepresent(PoemType<S> storage, PoemType<O> type) { 209 if (storage instanceof StringPoemType && type instanceof StringPoemType) { 210 if (((StringPoemType)storage).getSize() == sqlServerTextHack 211 && ((StringPoemType)type).getSize() == -1 212 && !(!storage.getNullable() && type.getNullable())) { 213 return type; 214 } else { 215 return storage.canRepresent(type); 216 } 217 } else if (storage instanceof BinaryPoemType 218 && type instanceof BinaryPoemType) { 219 if (((BinaryPoemType)storage).getSize() == sqlServerMaxBinarySize 220 && ((BinaryPoemType)type).getSize() == -1 221 && !(!storage.getNullable() && type.getNullable())) { 222 return type; 223 } else { 224 return storage.canRepresent(type); 225 } 226 } else if (storage instanceof DatePoemType 227 && type instanceof TimestampPoemType) { 228 if (!(!storage.getNullable() && type.getNullable())) { 229 return type; 230 } else { 231 return storage.canRepresent(type); 232 } 233 } else { 234 return super.canRepresent(storage, type); 235 } 236 } 237 238 /** 239 * Translates a MSSQL Date into a Poem <code>DatePoemType</code>. 240 */ 241 // public static class SQLServerDatePoemType extends DatePoemType { 242 243 /** 244 * Constructor. 245 * 246 * @param nullable 247 * nullability 248 */ 249 // public SQLServerDatePoemType(boolean nullable) { 250 // super(Types.DATE, "DATETIME", nullable); 251 // } 252 253 // protected boolean _canRepresent(SQLPoemType other) { 254 // return other instanceof DatePoemType 255 // || other instanceof TimestampPoemType; 256 // } 257 258 // } 259 260 /** 261 * Translates a MSSQL Date into a Poem <code>TimestampPoemType</code>. 262 */ 263 //public static class SQLServerTimestampPoemType extends TimestampPoemType { 264 265 /** 266 * Constructor. 267 * 268 * @param nullable 269 * nullability 270 */ 271 //public SQLServerTimestampPoemType(boolean nullable) { 272 // super(Types.TIMESTAMP, "DATETIME", nullable); 273 //} 274 //} 275 276 /** 277 * {@inheritDoc} 278 * 279 * @see org.melati.poem.dbms.AnsiStandard#sqlBooleanValueOfRaw(java.lang.Object) 280 */ 281 public String sqlBooleanValueOfRaw(Object raw) { 282 if (((Boolean)raw).booleanValue()) 283 return "1"; 284 else 285 return "0"; 286 } 287 288 /** 289 * Translates an SQLServer Boolean into a Poem <code>BooleanPoemType</code>. 290 */ 291 public static class SQLServerBooleanPoemType extends BooleanPoemType { 292 293 /** 294 * Constructor. 295 * 296 * @param nullable 297 * nullability 298 */ 299 public SQLServerBooleanPoemType(boolean nullable) { 300 super(nullable); 301 } 302 303 protected Boolean _getRaw(ResultSet rs, int col) throws SQLException { 304 synchronized (rs) { 305 boolean v = rs.getBoolean(col); 306 return rs.wasNull() ? null : (v ? Boolean.TRUE : Boolean.FALSE); 307 } 308 } 309 310 protected void _setRaw(PreparedStatement ps, int col, Object bool) 311 throws SQLException { 312 ps.setInt(col, ((Boolean)bool).booleanValue() ? 1 : 0); 313 } 314 315 } 316 317 /** 318 * {@inheritDoc} 319 * 320 * @see org.melati.poem.dbms.Dbms#getLongSqlDefinition() 321 */ 322 public String getLongSqlDefinition() { 323 return "BIGINT"; 324 } 325 326 /** 327 * {@inheritDoc} 328 * 329 * @see org.melati.poem.dbms.Dbms#getBinarySqlDefinition(int) 330 */ 331 public String getBinarySqlDefinition(int size) throws SQLException { 332 if (size < 0) 333 return "VARBINARY(MAX)"; 334 335 return "VARBINARY(" + size + ")"; 336 } 337 338 /** 339 * {@inheritDoc} 340 * 341 * @see org.melati.poem.dbms.AnsiStandard#defaultPoemTypeOfColumnMetaData( 342 * java.sql.ResultSet) 343 */ 344 public SQLPoemType<?> defaultPoemTypeOfColumnMetaData(ResultSet md) 345 throws SQLException { 346 347 /* 348 ResultSetMetaData rsmd = md.getMetaData(); 349 int cols = rsmd.getColumnCount(); 350 for (int i = 1; i <= cols; i++) { 351 String table = rsmd.getTableName(i); 352 System.err.println("table name: " + table); 353 String column = rsmd.getColumnName(i); 354 System.err.println("column name: " + column); 355 int type = rsmd.getColumnType(i); 356 System.err.println("type: " + type); 357 String typeName = rsmd.getColumnTypeName(i); 358 System.err.println("type Name: " + typeName); 359 String className = rsmd.getColumnClassName(i); 360 System.err.println("class Name: " + className); 361 System.err.println("String val: " + md.getString(i)); 362 System.err.println(""); 363 } 364 */ 365 // Not used in Poem 366 //if (md.getString("TYPE_NAME").equals("text")) 367 // return new SQLServerStringPoemType( 368 // md.getInt("NULLABLE") == DatabaseMetaData.columnNullable, md 369 // .getInt("COLUMN_SIZE")); 370 // We use a magic number for text fields 371 if (md.getString("TYPE_NAME").equals("varchar") 372 && md.getInt("COLUMN_SIZE") == sqlServerTextHack) 373 return new StringPoemType( 374 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable, -1); 375 376 // Not used in Poem 377 if (md.getString("TYPE_NAME").equals("char")) 378 return new StringPoemType( 379 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable, md 380 .getInt("COLUMN_SIZE")); 381 if (md.getString("TYPE_NAME").equals("float")) 382 return new DoublePoemType( 383 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable); 384 if (md.getString("TYPE_NAME").equals("datetime")) 385 return new DatePoemType( 386 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable); 387 if (md.getString("TYPE_NAME").equals("bit")) 388 return new SQLServerBooleanPoemType( 389 md.getInt("NULLABLE") == DatabaseMetaData.columnNullable); 390 /* 391 * // MSSQL returns type -2 (BINARY) not 93 (TIMESTAMP) 392 * // They don't mean what we mean by timestamp 393 * // They mean a one-per-record record creation timestamp 394 * if( md.getString("TYPE_NAME").equals("timestamp")) 395 * return new TimestampPoemType(md.getInt("NULLABLE")== DatabaseMetaData.columnNullable); 396 */ 397 return super.defaultPoemTypeOfColumnMetaData(md); 398 } 399 400 /** 401 * Ignore <TT>dtproperties</TT> as it is a 'System' table used to store 402 * Entity Relationship diagrams which have a jdbc type of TABLE when it should 403 * probably have a jdbc type of 'SYSTEM TABLE'. 404 * 405 * {@inheritDoc} 406 * @see org.melati.poem.dbms.AnsiStandard#melatiName(java.lang.String) 407 */ 408 public String melatiName(String name) { 409 if (name == null) 410 return null; 411 if (name.equalsIgnoreCase("dtproperties")) 412 return null; 413 return name; 414 } 415 416 /** 417 * MSSQL cannot index TEXT fields. Probably means that if you are serious 418 * about using MSSQL you should use a varchar. 419 * 420 * If a field is defined as Text in the DSD we use a VARCHAR. Not sure what 421 * happens if a legacy db really uses TEXT. 422 * 423 * @return whether it is allowed. 424 */ 425 public boolean canBeIndexed(Column<?> column) { 426 PoemType<?> t = column.getType(); 427 if (t instanceof StringPoemType && ((StringPoemType)t).getSize() < 0) 428 return false; 429 return true; 430 } 431 432 /** 433 * {@inheritDoc} 434 * 435 * @see org.melati.poem.dbms.AnsiStandard#caseInsensitiveRegExpSQL 436 */ 437 public String caseInsensitiveRegExpSQL(String term1, String term2) { 438 if (StringUtils.isQuoted(term2)) { 439 term2 = term2.substring(1, term2.length() - 1); 440 } 441 term2 = StringUtils.quoted(StringUtils.quoted(term2, '%'), '\''); 442 443 return term1 + " LIKE " + term2; 444 } 445 446 /** 447 * Slightly different - embrace and extend. 448 */ 449 public String getForeignKeyDefinition(String tableName, String fieldName, 450 String targetTableName, String targetTableFieldName, String fixName) { 451 StringBuffer sb = new StringBuffer(); 452 sb.append(" ADD FOREIGN KEY (" + getQuotedName(fieldName) + ") REFERENCES " 453 + getQuotedName(targetTableName) + "(" 454 + getQuotedName(targetTableFieldName) + ")"); 455 if (fixName.equals("prevent")) 456 sb.append(" ON DELETE NO ACTION"); 457 if (fixName.equals("delete")) 458 sb.append(" ON DELETE CASCADE"); 459 if (fixName.equals("clear")) 460 sb.append(" ON DELETE SET NULL"); 461 return sb.toString(); 462 } 463 464 /** 465 * Accommodate SQLServer syntax. 466 */ 467 public String alterColumnNotNullableSQL(String tableName, Column<?> column) { 468 return "ALTER TABLE " + getQuotedName(tableName) + " ALTER COLUMN " 469 + getQuotedName(column.getName()) + " " 470 + column.getSQLType().sqlDefinition(this); 471 } 472 473 /** 474 * {@inheritDoc} 475 * 476 * @see org.melati.poem.dbms.Dbms#selectLimit(java.lang.String, int) 477 */ 478 public String selectLimit(String querySelection, int limit) { 479 return "SELECT TOP " + limit + querySelection; 480 } 481 482 483 /** 484 * {@inheritDoc} 485 * @see org.melati.poem.dbms.Dbms#booleanTrueExpression(org.melati.poem.Column) 486 */ 487 public String booleanTrueExpression(Column<Boolean> booleanColumn) { 488 return booleanColumn.fullQuotedName() + "=1"; 489 } 490 491 /** 492 * {@inheritDoc} 493 * @see org.melati.poem.dbms.AnsiStandard#getSqlDefaultValue(org.melati.poem.SQLType) 494 */ 495 public String getSqlDefaultValue(SQLType<?> sqlType) { 496 if (sqlType instanceof BooleanPoemType) { 497 return ("0"); 498 } 499 if (sqlType instanceof BinaryPoemType) { 500 return "convert(varbinary, '')"; 501 } 502 return super.getSqlDefaultValue(sqlType); 503 } 504 505 public String getQuotedValue(SQLType<?> sqlType, String value) { 506 if (sqlType instanceof BinaryPoemType) { 507 return value; 508 } 509 return super.getQuotedValue(sqlType, value); 510 } 511 }