001/* 002 * JKNIV, SQLegance keeping queries maintainable. 003 * 004 * Copyright (C) 2017, the original author or authors. 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software Foundation, Inc., 018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 019 */ 020package net.sf.jkniv.sqlegance.dialect; 021 022import java.util.HashMap; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import net.sf.jkniv.asserts.Assertable; 027import net.sf.jkniv.asserts.AssertsFactory; 028import net.sf.jkniv.reflect.beans.PropertyAccess; 029import net.sf.jkniv.sqlegance.params.ParamMarkType; 030 031/** 032 * Represents the support from SQL ANSI that are queries cross-platform. 033 * 034 * Represents a dialect of SQL implemented by a particular RDBMS. Subclasses implement Hibernate compatibility 035 * with different systems. Subclasses should provide a public default constructor that register a set of type 036 * mappings and default Hibernate properties. Subclasses should be immutable. 037 * 038 * <ul> 039 * <li>Supports limits? false</li> 040 * <li>Supports limit off set? false</li> 041 * <li>Supports rownum? false</li> 042 * </ul> 043 * 044 * @author Alisson Gomes 045 * 046 */ 047public class AnsiDialect implements SqlDialect 048{ 049 private static final Assertable notNull = AssertsFactory.getNotNull(); 050 // find the pattern start with 'with' 051 private static final String REGEX_START_WITH = "^\\s*(with)\\s"; 052 // find the pattern start with 'select' 053 private static final String REGEX_START_SELECT = "^\\s*(select)\\s"; 054 055 // find the pattern start with 'select distinct' 056 private static final String REGEX_START_SELECT_DISTINCT = "^\\s*(select\\s+distinct|select)\\s"; 057 058 // find the pattern end with'for update' 059 private static final String REGEX_ENDS_FORUPDATE = "\\s+(for\\s+update)\\s*$"; 060 061 private static final String REGEX_ENDS_ORDERBY = "\\s*(order\\s+by)\\s*[a-zA-Z0-9,_\\.\\)\\s]*$"; 062 063 private static final Pattern patternWith = Pattern.compile(REGEX_START_WITH, 064 Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 065 private static final Pattern patternSelect = Pattern 066 .compile(REGEX_START_SELECT, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 067 private static final Pattern patternSelectDistinct = Pattern 068 .compile(REGEX_START_SELECT_DISTINCT, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 069 private static final Pattern patternForUpdate = Pattern 070 .compile(REGEX_ENDS_FORUPDATE, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 071 public static final Pattern patternORDER_BY = Pattern 072 .compile(REGEX_ENDS_ORDERBY, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); 073 074 protected String name; 075 private final HashMap<SqlFeatureSupport, SqlFeature> sqlFeatures; 076 private int maxOfParameters; 077 private PropertyAccess propertyAccessId; 078 private PropertyAccess propertyAccessRevision; 079 080 public AnsiDialect() 081 { 082 this.name = getClass().getSimpleName(); 083 this.sqlFeatures = new HashMap<SqlFeatureSupport, SqlFeature>(); 084 this.sqlFeatures.put(SqlFeatureSupport.LIMIT, 085 SqlFeatureFactory.newInstance(SqlFeatureSupport.LIMIT)); 086 this.sqlFeatures.put(SqlFeatureSupport.LIMIT_OFF_SET, 087 SqlFeatureFactory.newInstance(SqlFeatureSupport.LIMIT_OFF_SET)); 088 this.sqlFeatures.put(SqlFeatureSupport.ROWNUM, 089 SqlFeatureFactory.newInstance(SqlFeatureSupport.ROWNUM)); 090 this.sqlFeatures.put(SqlFeatureSupport.STMT_HOLDABILITY, 091 SqlFeatureFactory.newInstance(SqlFeatureSupport.STMT_HOLDABILITY)); 092 this.sqlFeatures.put(SqlFeatureSupport.CONN_HOLDABILITY, 093 SqlFeatureFactory.newInstance(SqlFeatureSupport.CONN_HOLDABILITY, true)); 094 this.sqlFeatures.put(SqlFeatureSupport.BOOKMARK_QUERY, 095 SqlFeatureFactory.newInstance(SqlFeatureSupport.BOOKMARK_QUERY)); 096 this.sqlFeatures.put(SqlFeatureSupport.PAGING_ROUNDTRIP, 097 SqlFeatureFactory.newInstance(SqlFeatureSupport.PAGING_ROUNDTRIP, true)); 098 this.sqlFeatures.put(SqlFeatureSupport.SEQUENCE, 099 SqlFeatureFactory.newInstance(SqlFeatureSupport.SEQUENCE)); 100 this.maxOfParameters = Integer.MAX_VALUE; 101 this.propertyAccessId = new PropertyAccess("id", "getId", "setId"); 102 this.propertyAccessRevision = new PropertyAccess("rev", "getRev", "setRev"); 103 //this.countParams = 0; 104 } 105 106 public String name() 107 { 108 return this.name; 109 } 110 111 @Override 112 public boolean supportsFeature(SqlFeatureSupport feature) 113 { 114 boolean answer = false; 115 SqlFeature sqlFeature = sqlFeatures.get(feature); 116 if (sqlFeature != null) 117 answer = sqlFeature.supports(); 118 119 return answer; 120 } 121 122 @Override 123 public boolean supportsParmMark(ParamMarkType paramParse) 124 { 125 return true; 126 } 127 128 @Override 129 public SqlFeature addFeature(SqlFeature sqlFeature) 130 { 131 return this.sqlFeatures.put(sqlFeature.getSqlFeature(), sqlFeature); 132 } 133 134 @Override 135 public int getMaxOfParameters() 136 { 137 return maxOfParameters; 138 } 139 140 @Override 141 public void setMaxOfParameters(int max) 142 { 143 if (max > 0) 144 this.maxOfParameters = max; 145 } 146 147 @Override 148 public String getSqlPatternCount() 149 { 150 // using String.format argument index 151 return "select count(1) from (%1$s) jkniv_ct_tmp_table"; 152 } 153 154 @Override 155 public String getSqlPatternPaging() 156 { 157 StringBuilder pattern = new StringBuilder("%1$s"); 158 if (supportsFeature(SqlFeatureSupport.LIMIT)) 159 { 160 pattern.append(" LIMIT %2$s"); 161 if (supportsFeature(SqlFeatureSupport.LIMIT_OFF_SET)) 162 pattern.append(", %3$s"); 163 } 164 return pattern.toString();//%1$s LIMIT %2$s, %3$s 165 } 166 167 //protected void buildSqlLimits() 168 @Override 169 public String buildQueryPaging(final String sqlText, int offset, int max) 170 { 171 String sqlTextPaginated = null; 172 if (supportsFeature(SqlFeatureSupport.LIMIT)) 173 { 174 String pagingSelectTemplate = getSqlPatternPaging(); 175 //sqlText = queryable.getSql().getSql(queryable.getParams()); 176 //assertSelect(this.sql); 177 Matcher matcher = sqlEndWithForUpdate(sqlText); 178 String forUpdate = ""; 179 String sqlTextWithouForUpdate = sqlText; 180 if (matcher.find()) 181 { 182 forUpdate = sqlText.substring(matcher.start(), matcher.end());// select name from author ^for update^ 183 sqlTextWithouForUpdate = sqlText.substring(0, matcher.start());// ^select name from author^ for update 184 } 185 if (supportsFeature(SqlFeatureSupport.LIMIT_OFF_SET)) 186 sqlTextPaginated = String.format(pagingSelectTemplate, sqlTextWithouForUpdate, max, offset) + forUpdate; 187 else 188 sqlTextPaginated = String.format(pagingSelectTemplate, sqlTextWithouForUpdate, max) + forUpdate; 189 190 } 191 //replaceForQuestionMark(); 192 return sqlTextPaginated; 193 } 194 195 @Override 196 public String buildQueryPaging(final String sqlText, int offset, int max, String bookmark) 197 { 198 return buildQueryPaging(sqlText, offset, max); 199 } 200 201 @Override 202 public PropertyAccess getAccessId() 203 { 204 return this.propertyAccessId; 205 } 206 207 @Override 208 public PropertyAccess getAccessRevision() 209 { 210 return this.propertyAccessRevision; 211 } 212 213 /* 214 protected void replaceForQuestionMark() 215 { 216 this.paramsNames = queryable.getSql().getParamParser().find(sql); 217 if(sqlWithLimit != null) 218 this.sqlWithLimit = queryable.getSql().getParamParser().replaceForQuestionMark(sqlWithLimit, queryable.getParams()); 219 this.sql = queryable.getSql().getParamParser().replaceForQuestionMark(sql, queryable.getParams()); 220 this.countParams = countOccurrencesOf(this.sql, "?"); 221 } 222 */ 223 224 @Override 225 public String buildQueryCount(String sqlText) 226 { 227 String sqlToCount = null; 228 Matcher matcher = sqlEndWithForUpdate(sqlText); 229 if (matcher.find()) 230 sqlToCount = String.format(getSqlPatternCount(), sqlText.substring(0, matcher.start()));// ^select name from author^ for update 231 else 232 sqlToCount = String.format(getSqlPatternCount(), removeOrderBy(sqlText)); 233 234 return sqlToCount; 235 } 236 237 protected Matcher sqlStartWithSelect(String sql) 238 { 239 Matcher matcher = patternSelect.matcher(sql); 240 return matcher; 241 } 242 243 protected Matcher sqlStartWithSelectOrDistinct(String sql) 244 { 245 Matcher matcher = patternSelectDistinct.matcher(sql); 246 return matcher; 247 } 248 249 protected Matcher sqlEndsWithOrderBy(String sql) 250 { 251 Matcher matcher = patternORDER_BY.matcher(sql); 252 return matcher; 253 } 254 255 protected Matcher sqlEndWithForUpdate(String sql) 256 { 257 Matcher matcher = patternForUpdate.matcher(sql); 258 return matcher; 259 } 260 261 /** 262 * Remove the order by clause from the query. 263 * 264 * @param hql 265 * SQL, JPQL or HQL 266 * @return return the query without order by clause. 267 */ 268 private String removeOrderBy(String hql)// TODO Single Responsibility 269 { 270 Matcher m = patternORDER_BY.matcher(hql); 271 StringBuffer sb = new StringBuffer(); 272 while (m.find()) 273 { 274 if (m.hitEnd()) 275 m.appendReplacement(sb, ""); 276 } 277 m.appendTail(sb); 278 return sb.toString(); 279 } 280 // protected boolean isSelect() 281 // { 282 // return queryable.getSql().isSelect(); 283 // } 284 285 /** 286 * Count the occurrences of the substring in string s. 287 * @param str string to search in. Return 0 if this is null. 288 * @param sub string to search for. Return 0 if this is null. 289 * @return number of occurrences from {@code str} 290 */ 291 protected int countOccurrencesOf(String str, String sub) 292 { 293 if (str == null || sub == null || str.length() == 0 || sub.length() == 0) 294 { 295 return 0; 296 } 297 int count = 0; 298 int pos = 0; 299 int idx; 300 while ((idx = str.indexOf(sub, pos)) != -1) 301 { 302 ++count; 303 pos = idx + sub.length(); 304 } 305 return count; 306 } 307 308 /* 309 protected void assertSelect(String sql) 310 { 311 if (sqlStartWithSelectDistinct(sql).find() || sqlStartWithSelect(sql).find()) 312 return; 313 else if (patternWith.matcher(sql).find()) 314 throw new RepositoryException("JDBC repository doesn't support paginate query started with WITH clause"); 315 316 throw new RepositoryException( 317 "JDBC repository cannot paginate [DELETE | UPDATE | INSERT] queries, just SELECT"); 318 } 319 320 protected void assertSelect(ISql sql) 321 { 322 if (sql.getSqlCommandType() != SqlCommandType.SELECT) 323 throw new RepositoryException( 324 "JDBC repository cannot paginate [DELETE | UPDATE | INSERT] queries, just SELECT"); 325 } 326 */ 327}