Coverage Summary for Class: SessionHelper (net.sf.persism)

Class Method, % Line, %
SessionHelper 100% (24/24) 96.3% (367/381)
SessionHelper$1 100% (1/1) 100% (1/1)
Total 100% (25/25) 96.3% (368/382)


1 package net.sf.persism; 2  3 import net.sf.persism.annotations.Join; 4 import net.sf.persism.annotations.NotTable; 5 import net.sf.persism.annotations.View; 6  7 import java.math.BigDecimal; 8 import java.sql.*; 9 import java.util.*; 10 import java.util.stream.Collectors; 11  12 import static net.sf.persism.Parameters.params; 13 import static net.sf.persism.SQL.where; 14  15 // Non-public code Session uses. 16 final class SessionHelper { 17  18  // leave this using the Session.class for logging 19  private static final Log log = Log.getLogger(Session.class); 20  private static final Log blog = Log.getLogger("net.sf.persism.Benchmarks"); 21  private static final Log sqllog = Log.getLogger("net.sf.persism.SQL"); 22  23  private final Session session; 24  25  public SessionHelper(Session session) { 26  this.session = session; 27  } 28  29  JDBCResult executeQuery(Class<?> objectClass, SQL sql, Parameters parameters) throws SQLException { 30  31  JDBCResult result = new JDBCResult(); 32  String sqlQuery = sql.sql; 33  if (parameters.areNamed) { 34  if (sql.type == SQL.SQLType.StoredProc) { 35  //log.warnNoDuplicates(Message.NamedParametersUsedWithStoredProc.message(sql.sql)); 36  throw new PersismException(Message.NamedParametersUsedWithStoredProc.message(sql.sql)); 37  } 38  char delim = '@'; 39  Map<String, List<Integer>> paramMap = new HashMap<>(); 40  sqlQuery = parseParameters(delim, sqlQuery, paramMap); 41  parameters.setParameterMap(paramMap); 42  43  } else if (parameters.areKeys) { 44  // convert parameters - usually it's the UUID type that may need a conversion to byte[16] 45  // Probably we don't want to auto-convert here since it's inconsistent. DO WE? YES. 46  List<String> keys = session.metaData.getPrimaryKeys(objectClass, session.connection); 47  if (keys.size() == 1) { 48  Map<String, ColumnInfo> columns = session.metaData.getColumns(objectClass, session.connection); 49  String key = keys.get(0); 50  ColumnInfo columnInfo = columns.get(key); 51  for (int j = 0; j < parameters.size(); j++) { 52  if (parameters.get(j) != null) { 53  parameters.set(j, session.converter.convert(parameters.get(j), columnInfo.columnType.getJavaType(), columnInfo.columnName)); 54  } 55  } 56  } 57  } 58  59  if (sql.type == SQL.SQLType.Where) { 60  if (objectClass.getAnnotation(NotTable.class) != null) { 61  throw new PersismException(Message.WhereNotSupportedForNotTableQueries.message()); 62  } 63  sqlQuery = session.metaData.getSelectStatement(objectClass, session.connection) + parsePropertyNames(sqlQuery, objectClass, session.connection); 64  sql.processedSQL = sqlQuery; 65  } 66  exec(result, sqlQuery, parameters.toArray()); 67  return result; 68  } 69  70  // this method should only be used by query or fetch 71  void exec(JDBCResult result, String sql, Object... parameters) throws SQLException { 72  long now = System.currentTimeMillis(); 73  74  if (sqllog.isDebugEnabled()) { 75  sqllog.debug("%s params: %s", sql, Arrays.asList(parameters)); 76  } 77  try { 78  if (isSelect(sql)) { 79  if (session.metaData.getConnectionType() == ConnectionType.Firebird) { 80  // https://stackoverflow.com/questions/935511/how-can-i-avoid-resultset-is-closed-exception-in-java 81  result.st = session.connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); 82  } else { 83  result.st = session.connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 84  } 85  86  PreparedStatement pst = (PreparedStatement) result.st; 87  setParameters(pst, parameters); 88  result.rs = pst.executeQuery(); 89  } else { 90  if (!sql.trim().toLowerCase().startsWith("{call")) { 91  sql = "{call " + sql + "} "; 92  } 93  // Don't need If Firebird here. Firebird would call a selectable stored proc with SELECT anyway 94  result.st = session.connection.prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); 95  96  CallableStatement cst = (CallableStatement) result.st; 97  setParameters(cst, parameters); 98  result.rs = cst.executeQuery(); 99  } 100  101  } catch (SQLException e) { 102  throw new SQLException(e.getMessage() + " SQL: " + sql + " params: " + Arrays.asList(parameters), e); 103  } finally { 104  if (blog.isDebugEnabled()) { 105  blog.debug("exec time: " + (System.currentTimeMillis() - now) + " " + sql + " params: " + Arrays.asList(parameters)); 106  } 107  } 108  } 109  110  // For unit tests only for now. 111  void execute(String sql, Object... parameters) { 112  113  log.debug("execute: %s params: %s", sql, Arrays.asList(parameters)); 114  115  Statement st = null; 116  try { 117  118  if (parameters.length == 0) { 119  st = session.connection.createStatement(); 120  st.execute(sql); 121  } else { 122  st = session.connection.prepareStatement(sql); 123  PreparedStatement pst = (PreparedStatement) st; 124  setParameters(pst, parameters); 125  pst.execute(); 126  } 127  128  } catch (Exception e) { 129  Util.rollback(session.connection); 130  throw new PersismException(e.getMessage(), e); 131  } finally { 132  Util.cleanup(st, null); 133  } 134  } 135  136  /** 137  * Original from Adam Crume, JavaWorld.com, 04/03/07 138  * https://www.infoworld.com/article/2077706/named-parameters-for-preparedstatement.html 139  * https://archive.ph/au5XM and https://archive.ph/4OOze 140  * 141  * @param sql query to parse 142  * @param paramMap map to hold parameter-index mappings 143  * @return the parsed query 144  */ 145  String parseParameters(char delim, String sql, Map<String, List<Integer>> paramMap) { 146  log.debug("parseParameters using " + delim); 147  148  int length = sql.length(); 149  StringBuilder parsedQuery = new StringBuilder(length); 150  151  Set<Character> startDelims = new HashSet<>(4); 152  startDelims.add('"'); 153  154  String sd = session.metaData.getConnectionType().getKeywordStartDelimiter(); 155  String ed = session.metaData.getConnectionType().getKeywordEndDelimiter(); 156  157  if (Util.isNotEmpty(sd)) { 158  startDelims.add(sd.charAt(0)); 159  } 160  161  Set<Character> endDelims = new HashSet<>(4); 162  endDelims.add('"'); 163  164  if (Util.isNotEmpty(ed)) { 165  endDelims.add(ed.charAt(0)); 166  } 167  168  boolean inDelimiter = false; 169  int index = 1; 170  171  for (int i = 0; i < length; i++) { 172  char c = sql.charAt(i); 173  if (inDelimiter) { 174  if (endDelims.contains(c)) { 175  inDelimiter = false; 176  } 177  } else if (startDelims.contains(c)) { 178  inDelimiter = true; 179  } else if (c == delim && i + 1 < length && Character.isJavaIdentifierStart(sql.charAt(i + 1))) { 180  int j = i + 2; 181  while (j < length && Character.isJavaIdentifierPart(sql.charAt(j))) { 182  j++; 183  } 184  String name = sql.substring(i + 1, j); 185  c = '?'; // replace the parameter with a question mark 186  i += name.length(); // skip past the end of the parameter 187  188  List<Integer> indexList = paramMap.get(name); 189  if (indexList == null) { 190  indexList = new LinkedList<>(); 191  paramMap.put(name, indexList); 192  } 193  indexList.add(index); 194  195  index++; 196  } 197  parsedQuery.append(c); 198  } 199  200  return parsedQuery.toString(); 201  } 202  203  String parsePropertyNames(String sql, Class<?> objectClass, Connection connection) { 204  log.debug("parsePropertyNames using : with SQL: %s", sql); 205  206  if (session.metaData.whereClauses.containsKey(objectClass) && session.metaData.whereClauses.get(objectClass).containsKey(sql)) { 207  return session.metaData.whereClauses.get(objectClass).get(sql); 208  } 209  210  return determineWhereClause(sql, objectClass, connection); 211  } 212  213  private synchronized String determineWhereClause(String sql, Class<?> objectClass, Connection connection) { 214  215  if (session.metaData.whereClauses.containsKey(objectClass) && session.metaData.whereClauses.get(objectClass).containsKey(sql)) { 216  return session.metaData.whereClauses.get(objectClass).get(sql); 217  } 218  219  int length = sql.length(); 220  StringBuilder parsedQuery = new StringBuilder(length); 221  222  String sd = session.metaData.getConnectionType().getKeywordStartDelimiter(); 223  String ed = session.metaData.getConnectionType().getKeywordEndDelimiter(); 224  225  Set<Character> startDelims = new HashSet<>(4); 226  startDelims.add('"'); 227  startDelims.add('\''); 228  229  if (Util.isNotEmpty(sd)) { 230  startDelims.add(sd.charAt(0)); 231  } 232  233  Set<Character> endDelims = new HashSet<>(4); 234  endDelims.add('"'); 235  endDelims.add('\''); 236  237  if (Util.isNotEmpty(session.metaData.getConnectionType().getKeywordEndDelimiter())) { 238  endDelims.add(ed.charAt(0)); 239  } 240  241  Map<String, PropertyInfo> properties = session.metaData.getTableColumnsPropertyInfo(objectClass, connection); 242  243  Set<String> propertiesNotFound = new LinkedHashSet<>(); 244  245  boolean inDelimiter = false; 246  boolean appendChar; 247  for (int i = 0; i < length; i++) { 248  appendChar = true; 249  char c = sql.charAt(i); 250  if (inDelimiter) { 251  if (endDelims.contains(c)) { 252  inDelimiter = false; 253  } 254  } else if (startDelims.contains(c)) { 255  inDelimiter = true; 256  } else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(sql.charAt(i + 1))) { 257  int j = i + 2; 258  while (j < length && Character.isJavaIdentifierPart(sql.charAt(j))) { 259  j++; 260  } 261  String name = sql.substring(i + 1, j); 262  log.debug("parsePropertyNames property name: %s", name); 263  i += name.length(); // skip past the end if the property name 264  265  boolean found = false; 266  for (String col : properties.keySet()) { 267  PropertyInfo propertyInfo = properties.get(col); 268  // ignore case? 269  if (propertyInfo.propertyName.equals(name)) { 270  parsedQuery.append(sd).append(col).append(ed); 271  appendChar = false; 272  found = true; 273  break; 274  } 275  } 276  if (!found) { 277  propertiesNotFound.add(name); 278  } 279  } 280  if (appendChar) { 281  parsedQuery.append(c); 282  } 283  } 284  285  if (propertiesNotFound.size() > 0) { 286  throw new PersismException(Message.QueryPropertyNamesMissingOrNotFound.message(propertiesNotFound, sql)); 287  } 288  String parsedSql = " " + parsedQuery; 289  log.debug("parsePropertyNames SQL: %s", parsedSql); 290  session.metaData.whereClauses.putIfAbsent(objectClass, new HashMap<>()); 291  session.metaData.whereClauses.get(objectClass).put(sql, parsedSql); 292  return parsedSql; 293  } 294  295  void checkIfOkForWriteOperation(Class<?> objectClass, String operation) { 296  if (objectClass.getAnnotation(View.class) != null) { 297  throw new PersismException(Message.OperationNotSupportedForView.message(objectClass, operation)); 298  } 299  if (objectClass.getAnnotation(NotTable.class) != null) { 300  throw new PersismException(Message.OperationNotSupportedForNotTableQuery.message(objectClass, operation)); 301  } 302  if (JavaType.getType(objectClass) != null) { 303  throw new PersismException(Message.OperationNotSupportedForJavaType.message(objectClass, operation)); 304  } 305  } 306  307  Object getTypedValueReturnedFromGeneratedKeys(Class<?> objectClass, ResultSet rs) throws SQLException { 308  Object value; 309  JavaType type = JavaType.getType(objectClass); 310  311  if (type == null) { 312  log.warn(Message.UnknownTypeForPrimaryGeneratedKey.message(objectClass)); 313  return rs.getObject(1); 314  } 315  316  value = switch (type) { 317  case integerType, IntegerType -> rs.getInt(1); 318  case longType, LongType -> rs.getLong(1); 319  default -> rs.getObject(1); 320  }; 321  return value; 322  } 323  324  void setParameters(PreparedStatement st, Object[] parameters) throws SQLException { 325  if (log.isDebugEnabled()) { 326  log.debug("setParameters PARAMS: %s", Arrays.asList(parameters)); 327  } 328  329  Object value; // used for conversions 330  int n = 1; 331  for (Object param : parameters) { 332  333  if (param != null) { 334  335  JavaType paramType = JavaType.getType(param.getClass()); 336  if (paramType == null) { 337  log.warn(Message.UnknownTypeInSetParameters.message(param.getClass())); 338  paramType = JavaType.ObjectType; 339  } 340  341  switch (paramType) { 342  343  case booleanType: 344  case BooleanType: 345  st.setBoolean(n, (Boolean) param); 346  break; 347  348  case byteType: 349  case ByteType: 350  st.setByte(n, (Byte) param); 351  break; 352  353  case shortType: 354  case ShortType: 355  st.setShort(n, (Short) param); 356  break; 357  358  case integerType: 359  case IntegerType: 360  st.setInt(n, (Integer) param); 361  break; 362  363  case longType: 364  case LongType: 365  st.setLong(n, (Long) param); 366  break; 367  368  case floatType: 369  case FloatType: 370  st.setFloat(n, (Float) param); 371  break; 372  373  case doubleType: 374  case DoubleType: 375  st.setDouble(n, (Double) param); 376  break; 377  378  case BigDecimalType: 379  st.setBigDecimal(n, (BigDecimal) param); 380  break; 381  382  case BigIntegerType: 383  st.setString(n, "" + param); 384  break; 385  386  case StringType: 387  st.setString(n, (String) param); 388  break; 389  390  case characterType: 391  case CharacterType: 392  st.setObject(n, "" + param); 393  break; 394  395  case SQLDateType: 396  st.setDate(n, (java.sql.Date) param); 397  break; 398  399  case TimeType: 400  st.setTime(n, (Time) param); 401  break; 402  403  case TimestampType: 404  st.setTimestamp(n, (Timestamp) param); 405  break; 406  407  case LocalTimeType: 408  value = session.converter.convert(param, Time.class, "Parameter " + n); 409  st.setObject(n, value); 410  break; 411  412  case UtilDateType: 413  case LocalDateType: 414  case LocalDateTimeType: 415  value = session.converter.convert(param, Timestamp.class, "Parameter " + n); 416  st.setObject(n, value); 417  break; 418  419  case OffsetDateTimeType: 420  case ZonedDateTimeType: 421  case InstantType: 422  log.warn(Message.UnSupportedTypeInSetParameters.message(paramType)); 423  st.setObject(n, "" + param); 424  break; 425  426  case byteArrayType: 427  case ByteArrayType: 428  // Blob maps to byte array 429  st.setBytes(n, (byte[]) param); 430  break; 431  432  case ClobType: 433  case BlobType: 434  // Clob is converted to String Blob is converted to byte array 435  // so this should not occur unless they were passed in by the user. 436  // We are most probably about to fail here. 437  log.warn(Message.ParametersDoNotUseClobOrBlob.message(), new Throwable()); 438  st.setObject(n, param); 439  break; 440  441  case EnumType: 442  if (session.metaData.getConnectionType() == ConnectionType.PostgreSQL) { 443  st.setObject(n, param.toString(), java.sql.Types.OTHER); 444  } else { 445  st.setString(n, param.toString()); 446  } 447  break; 448  449  case UUIDType: 450  if (session.metaData.getConnectionType() == ConnectionType.PostgreSQL) { 451  st.setObject(n, param); 452  } else { 453  st.setString(n, param.toString()); 454  } 455  break; 456  457  default: 458  // Usually SQLite with util.date - setObject works 459  // Also if it's a custom non-standard type. 460  log.info("setParameters using setObject on parameter: " + n + " for " + param.getClass()); 461  st.setObject(n, param); 462  } 463  464  } else { 465  // param is null 466  if (session.metaData.getConnectionType() == ConnectionType.UCanAccess) { 467  st.setNull(n, java.sql.Types.OTHER); 468  } else { 469  st.setObject(n, null); 470  } 471  } 472  473  n++; 474  } 475  } 476  477  478  // todo we really need a way to detect infinite loops and FAIL FAST 479  // parent is a POJO or a List of POJOs 480  void handleJoins(Object parent, Class<?> parentClass, String parentSql, Parameters parentParams) { 481  // maybe we could add a check for fetch after insert. In those cases there's no need to query for child lists 482  // BUT we do need query for child SINGLES AND IT'S POSSIBLE THAT CHILD RECORDS GET INSERTED 1st! 483  // NOT really important. At most 1 extra query per type will be run (and no child queries after that) 484  485  List<PropertyInfo> joinProperties = MetaData.getPropertyInfo(parentClass).stream().filter(PropertyInfo::isJoin).toList(); 486  487  for (PropertyInfo joinProperty : joinProperties) { 488  Join joinAnnotation = (Join) joinProperty.getAnnotation(Join.class); 489  JoinInfo joinInfo = JoinInfo.getInstance(joinAnnotation, joinProperty, parent, parentClass); 490  if (joinInfo.parentIsAQuery()) { 491  // We expect this method not to be called if the result query has 0 rows. 492  assert ((Collection<?>) parent).size() > 0; 493  } 494 // todo what if we have a complex query with multiple where clauses???? 495  String parentWhere; 496  if (parentSql.toUpperCase().contains(" WHERE ")) { 497  parentWhere = parentSql.substring(parentSql.toUpperCase().indexOf(" WHERE ") + 7); 498  } else { 499  parentWhere = ""; 500  } 501  502  String whereClause = getChildWhereClause(joinInfo, parentWhere); 503  List<Object> params = new ArrayList<>(parentParams.parameters); 504 // if (params.size() > 0) { 505 // // normalize params to ? since we may have repeated the SELECT IN query 506 // long qmCount = whereClause.chars().filter(ch -> ch == '?').count(); 507 // int index = 0; 508 // while (qmCount > params.size()) { 509 // params.add(params.get(index)); 510 // index++; 511 // } 512 // } 513  514  // join to a collection 515  if (Collection.class.isAssignableFrom(joinProperty.field.getType())) { 516  // query 517  List<?> childList = session.query(joinInfo.childClass(), where(whereClause), params(params.toArray())); 518  519  if (joinInfo.parentIsAQuery()) { 520  // many to many 521  List<?> parentList = (List<?>) parent; 522  stitch(joinInfo, parentList, childList); 523  } else { 524  // one to many 525  assignJoinedList(joinProperty, parent, childList); 526  } 527  528  } else { // join to single object 529  530  if (joinInfo.parentIsAQuery()) { 531  // many to one 532  List<?> childList = session.query(joinInfo.childClass(), where(whereClause), params(params.toArray())); 533  stitch(joinInfo.swapParentAndChild(), childList, (List<?>) parent); 534  } else { 535  // one to one 536  Object child = session.fetch(joinInfo.childClass(), where(whereClause), params(params.toArray())); 537  if (child != null) { 538  joinProperty.setValue(parent, child); 539  } 540  } 541  } 542  } 543  } 544  545  // called with many to many or many to one 546  private void stitch(JoinInfo joinInfo, List<?> parentList, List<?> childList) { 547  blog.debug("STITCH " + joinInfo); 548  549  if (childList.size() == 0) { 550  return; 551  } 552  long now = System.currentTimeMillis(); 553  554  Map<Object, Object> parentMap; 555  if (joinInfo.parentProperties().size() == 1 && joinInfo.childProperties().size() == 1) { 556  557  PropertyInfo parentPropertyInfo = joinInfo.parentProperties().get(0); 558  PropertyInfo childPropertyInfo = joinInfo.childProperties().get(0); 559  560  // https://stackoverflow.com/questions/32312876/ignore-duplicates-when-producing-map-using-streams 561  parentMap = parentList.stream().collect(Collectors. 562  toMap(key -> { 563  Object value = parentPropertyInfo.getValue(key); 564  if (!joinInfo.caseSensitive() && value instanceof String s) { 565  return s.toUpperCase(); 566  } 567  return value; 568  }, o -> o, (o1, o2) -> o1)); 569  570  for (Object child : childList) { 571  Object parent = parentMap.get(childPropertyInfo.getValue(child)); 572  if (parent == null) { 573  log.warnNoDuplicates("parent not found: " + childPropertyInfo.getValue(child) + " : " + joinInfo + "DAO: " + child); // Should not usually occur. Why would we not find a parent? 574  } else { 575  setPropertyFromJoinInfo(joinInfo, parent, child); 576  } 577  } 578  579  } else { 580  parentMap = parentList.stream().collect(Collectors. 581  toMap(key -> { 582  List<Object> values = new ArrayList<>(); 583  for (int j = 0; j < joinInfo.parentProperties().size(); j++) { 584  values.add(joinInfo.parentProperties().get(j).getValue(key)); 585  } 586  return new KeyBox(joinInfo.caseSensitive(), values.toArray()); 587  }, o -> o, (o1, o2) -> o1)); 588  589  for (Object child : childList) { 590  List<Object> values = new ArrayList<>(); 591  for (int j = 0; j < joinInfo.childProperties().size(); j++) { 592  values.add(joinInfo.childProperties().get(j).getValue(child)); 593  } 594  595  KeyBox keyBox = new KeyBox(joinInfo.caseSensitive(), values.toArray()); 596  Object parent = parentMap.get(keyBox); 597  if (parent == null) { 598  log.warnNoDuplicates("parent not found: " + keyBox); // Should not usually occur. Why would we not find a parent? 599  } else { 600  setPropertyFromJoinInfo(joinInfo, parent, child); 601  } 602  } 603  } 604  605  blog.debug("stitch Time: " + (System.currentTimeMillis() - now)); 606  } 607  608  @SuppressWarnings({"rawtypes", "unchecked"}) 609  private void setPropertyFromJoinInfo(JoinInfo joinInfo, Object parent, Object child) { 610  if (Collection.class.isAssignableFrom(joinInfo.joinProperty().field.getType())) { 611  var list = (Collection) joinInfo.joinProperty().getValue(parent); 612  if (list == null) { 613  throw new PersismException(Message.CannotNotJoinToNullProperty.message(joinInfo.joinProperty().propertyName)); 614  } 615  list.add(child); 616  } else { 617  assert joinInfo.reversed(); // many to 1 which is reversed. 618  joinInfo.joinProperty().setValue(child, parent); 619  } 620  } 621  622  @SuppressWarnings({"rawtypes", "unchecked"}) 623  private void assignJoinedList(PropertyInfo joinProperty, Object parentObject, List list) { 624  625  // no null test - the object should have some List initialized. 626  Collection joinTo = (Collection) joinProperty.getValue(parentObject); 627  if (joinTo == null) { 628  throw new PersismException(Message.CannotNotJoinToNullProperty.message(joinProperty.propertyName)); 629  } 630  joinTo.clear(); 631  joinTo.addAll(list); 632  } 633  634  private String getChildWhereClause(JoinInfo joinInfo, String parentWhere) { 635  if (session.metaData.childWhereClauses.containsKey(joinInfo) && session.metaData.childWhereClauses.get(joinInfo).containsKey(parentWhere)) { 636  return session.metaData.childWhereClauses.get(joinInfo).get(parentWhere); 637  } 638  return determineChildWhereClause(joinInfo, parentWhere); 639  } 640  641  private synchronized String determineChildWhereClause(JoinInfo joinInfo, String parentWhere) { 642  if (session.metaData.childWhereClauses.containsKey(joinInfo) && session.metaData.childWhereClauses.get(joinInfo).containsKey(parentWhere)) { 643  return session.metaData.childWhereClauses.get(joinInfo).get(parentWhere); 644  } 645  646  StringBuilder where = new StringBuilder(); 647  String sd = session.metaData.getConnectionType().getKeywordStartDelimiter(); 648  String ed = session.metaData.getConnectionType().getKeywordEndDelimiter(); 649  650  TableInfo parentTable = session.metaData.getTableInfo(joinInfo.parentClass()); 651  TableInfo childTable = session.metaData.getTableInfo(joinInfo.childClass()); 652  653  Map<String, PropertyInfo> parentProperties = session.metaData.getTableColumnsPropertyInfo(joinInfo.parentClass(), session.connection); 654  Map<String, PropertyInfo> childProperties = session.metaData.getTableColumnsPropertyInfo(joinInfo.childClass(), session.connection); 655  String sep = ""; 656  657  int n = parentWhere.toUpperCase().indexOf("ORDER BY"); 658  if (n > -1) { 659  parentWhere = parentWhere.substring(0, n); 660  } 661  662  String parentAlias = ""; 663  if (parentTable.equals(childTable)) { 664  // for self join we need an alias todo verify if we can get duplicate aliases! 665  parentAlias = parentTable.name().substring(0, 1).toUpperCase(); 666  } 667  668  where.append("EXISTS (SELECT "); 669  for (int j = 0; j < joinInfo.parentPropertyNames().length; j++) { 670  String parentColumnName = sd + getColumnName(joinInfo.parentPropertyNames()[j], parentProperties) + ed; 671  where.append(sep).append(parentColumnName); 672  sep = ","; 673  } 674  675  where.append(" FROM "); 676  where.append(parentTable); 677  678  if (Util.isNotEmpty(parentAlias)) { 679  where.append(" ").append(parentAlias); 680  } 681  where.append(" WHERE ").append(parentWhere).append(" "); 682  683  sep = Util.isEmpty(parentWhere) ? "" : " AND "; 684  for (int j = 0; j < joinInfo.parentPropertyNames().length; j++) { 685  String parentColumnName = sd + getColumnName(joinInfo.parentPropertyNames()[j], parentProperties) + ed; 686  String childColumnName = sd + getColumnName(joinInfo.childPropertyNames()[j], childProperties) + ed; 687  688  if (Util.isNotEmpty(parentAlias)) { 689  where.append(sep).append(parentAlias); 690  } else { 691  where.append(sep).append(parentTable); 692  } 693  694  where.append(".").append(parentColumnName).append(" = "). 695  append(childTable).append(".").append(childColumnName); 696  sep = " AND "; 697  } 698  where.append(") "); 699  700  String sql = where.toString(); 701  session.metaData.childWhereClauses.putIfAbsent(joinInfo, new HashMap<>()); 702  session.metaData.childWhereClauses.get(joinInfo).put(parentWhere, sql); 703  704  if (log.isDebugEnabled()) { 705  log.debug("determineChildWhereClause: %s", sql); 706  } 707  return sql; 708  } 709  710  private String getColumnName(String propertyName, Map<String, PropertyInfo> properties) { 711  for (String key : properties.keySet()) { 712  if (properties.get(key).propertyName.equals(propertyName)) { 713  return key; 714  } 715  } 716  return null; 717  } 718  719  boolean isSelect(String sql) { 720  assert sql != null; 721  return sql.trim().substring(0, 7).equalsIgnoreCase("select "); 722  } 723  724  <T> void checkIfStoredProcOrSQL(Class<T> objectClass, SQL sql) { 725  boolean startsWithSelect = isSelect(sql.sql); 726  if (sql.type == SQL.SQLType.Select) { 727  if (!startsWithSelect) { 728  log.warnNoDuplicates(Message.InappropriateMethodUsedForSQLTypeInstance.message(objectClass, "proc()", "an SQL query", "sql()")); 729  } 730  } else { 731  if (startsWithSelect) { 732  log.warnNoDuplicates(Message.InappropriateMethodUsedForSQLTypeInstance.message(objectClass, "sql()", "a stored proc", "proc()")); 733  } 734  } 735  } 736  737  738 }