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 }