001package io.ebean.config.dbplatform; 002 003import io.ebean.annotation.DbDefault; 004 005import java.sql.Types; 006import java.util.LinkedHashMap; 007import java.util.Map; 008 009/** 010 * DB Column default values mapping to database platform specific literals. 011 */ 012public class DbDefaultValue { 013 014 /** 015 * The key for FALSE. 016 */ 017 public static final String FALSE = "false"; 018 019 /** 020 * The key for TRUE. 021 */ 022 public static final String TRUE = "true"; 023 024 /** 025 * The key for the NOW / current timestamp. 026 */ 027 public static final String NOW = "now"; 028 029 /** 030 * The 'null' literal. 031 */ 032 public static final String NULL = "null"; 033 034 protected final Map<String, String> map = new LinkedHashMap<>(); 035 036 /** 037 * Set the DB now function. 038 */ 039 public void setNow(String dbFunction) { 040 put(NOW, dbFunction); 041 } 042 043 /** 044 * Set the DB false literal. 045 */ 046 public void setFalse(String dbFalseLiteral) { 047 put(FALSE, dbFalseLiteral); 048 } 049 050 /** 051 * Set the DB true literal. 052 */ 053 public void setTrue(String dbTrueLiteral) { 054 put(TRUE, dbTrueLiteral); 055 } 056 057 /** 058 * Add an translation entry. 059 */ 060 public void put(String dbLiteral, String dbTranslated) { 061 map.put(dbLiteral, dbTranslated); 062 } 063 064 /** 065 * Convert the DB default literal to platform specific type or function. 066 * <p> 067 * This is intended for the DB column default clause in DDL. 068 * </p> 069 */ 070 public String convert(String dbDefaultLiteral) { 071 if (dbDefaultLiteral == null) { 072 return null; 073 } 074 if (dbDefaultLiteral.startsWith("$RAW:")) { 075 return dbDefaultLiteral.substring(5); 076 } 077 String val = map.get(dbDefaultLiteral); 078 return val != null ? val : dbDefaultLiteral; 079 } 080 081 /** 082 * This method checks and converts the {@link DbDefault#value()} to a valid SQL literal. 083 * 084 * This is mainly to quote string literals and verify integer/dates for correctness. 085 * <p> 086 * Note: There are some special cases: 087 * </p> 088 * <ul> 089 * <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery 090 * returns: <code>default 'User''s default'</code><br/> 091 * (the same on an integer property will throw a NumberFormatException)</li> 092 * <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/> 093 * If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code> 094 * which gives you the <code>default 'null'</code> statement.</li> 095 * <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li> 096 * <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to 097 * a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li> 098 * </ul> 099 */ 100 public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) { 101 if (propertyType == null 102 || defaultValue == null 103 || NULL.equals(defaultValue) 104 || (defaultValue.startsWith("'") && defaultValue.endsWith("'")) 105 || (defaultValue.startsWith("$RAW:"))) { 106 return defaultValue; 107 } 108 109 if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) { 110 return toBooleanLiteral(defaultValue); 111 } 112 113 if (Number.class.isAssignableFrom(propertyType) 114 || Byte.TYPE.equals(propertyType) 115 || Short.TYPE.equals(propertyType) 116 || Integer.TYPE.equals(propertyType) 117 || Long.TYPE.equals(propertyType) 118 || Float.TYPE.equals(propertyType) 119 || Double.TYPE.equals(propertyType) 120 || (propertyType.isEnum() && sqlType == Types.INTEGER)) { 121 Double.valueOf(defaultValue); // verify if it is a number 122 return defaultValue; 123 } 124 125 // check if it is a date/time - in all other cases return quoted defaultValue 126 switch (sqlType) { 127 // date 128 case Types.DATE: 129 return toDateLiteral(defaultValue); 130 // time 131 case Types.TIME: 132 case Types.TIME_WITH_TIMEZONE: 133 return toTimeLiteral(defaultValue); 134 // timestamp 135 case Types.TIMESTAMP: 136 case Types.TIMESTAMP_WITH_TIMEZONE: 137 return toDateTimeLiteral(defaultValue); 138 139 default: 140 return toTextLiteral(defaultValue); // do not check other datatypes 141 } 142 } 143 144 /** 145 * Checks if specified value is either 'true' or 'false'. The literal is translated later. 146 */ 147 private static String toBooleanLiteral(String value) { 148 if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) { 149 return value; 150 } 151 throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean"); 152 } 153 154 /** 155 * This adds single qoutes around the <code>value</code> and doubles single quotes. 156 * "User's home" will return "'User''s home'" 157 */ 158 private static String toTextLiteral(String value) { 159 StringBuilder sb = new StringBuilder(value.length()+10); 160 sb.append('\''); 161 for (int i = 0; i < value.length(); i++) { 162 char ch = value.charAt(i); 163 if (ch == '\'') { 164 sb.append("''"); 165 } else { 166 sb.append(ch); 167 } 168 } 169 sb.append('\''); 170 return sb.toString(); 171 } 172 173 private static String toDateLiteral(String value) { 174 if (NOW.equals(value)) { 175 return value; // this will get translated later 176 } 177 return toTextLiteral(value); 178 } 179 180 private static String toTimeLiteral(String value) { 181 if (NOW.equals(value)) { 182 return value; // this will get translated later 183 } 184 return toTextLiteral(value); 185 } 186 187 private static String toDateTimeLiteral(String value) { 188 if (NOW.equals(value)) { 189 return value; // this will get translated later 190 } 191 return toTextLiteral(value); 192 } 193}