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 }