package org.openlca.core.database.references; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import org.openlca.core.database.IDatabase; import org.openlca.core.database.NativeSql; import org.openlca.core.database.references.IReferenceSearch.Reference; import org.openlca.core.model.AbstractEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class Search { final static Logger log = LoggerFactory.getLogger(Search.class); private final IDatabase database; private final Class<? extends AbstractEntity> type; static Search on(IDatabase database, Class<? extends AbstractEntity> type) { return new Search(database, type); } private Search(IDatabase database, Class<? extends AbstractEntity> type) { this.database = database; this.type = type; } List<Reference> findReferences(String table, String idField, Set<Long> ids, Ref[] refs, boolean includeOptional) { return findReferences(table, idField, ids, null, refs, includeOptional); } List<Reference> findReferences(String table, String idField, Set<Long> ids, Map<Long, Long> idToOwnerId, Ref[] refs, boolean includeOptional) { List<Reference> references = new ArrayList<Reference>(); List<String> queries = createQueries(table, idField, ids, refs); for (String query : queries) { query(query, result -> { long ownerId = result.getLong(1); long nestedOwnerId = 0; if (idToOwnerId != null) { nestedOwnerId = ownerId; ownerId = idToOwnerId.get(ownerId); } for (int i = 0; i < refs.length; i++) { Ref ref = refs[i]; if (ref.optional && !includeOptional) continue; long id = result.getLong(i + 2); if (id == 0) if (ref.longReference || result.isNull(i + 2)) continue; references.add(createReference(ref, id, ownerId, nestedOwnerId)); } }); } return references; } private Reference createReference(Ref ref, long id, long ownerId, long nestedOwnerId) { return new Reference(ref.property, ref.type, id, type, ownerId, ref.nestedProperty, ref.nestedType, nestedOwnerId, ref.optional); } void query(String query, Consumer<ResultSetWrapper> handler) { try { NativeSql.on(database).query(query, (resultSet) -> { handler.accept(new ResultSetWrapper(resultSet)); return true; }); } catch (SQLException e) { log.error("Error executing native query '" + query + "'", e); } } private List<String> createQueries(String table, String idField, Set<Long> ids, Ref[] references) { List<String> queries = new ArrayList<>(); StringBuilder subquery = new StringBuilder(); subquery.append("SELECT DISTINCT " + idField); for (int i = 0; i < references.length; i++) { subquery.append(", "); subquery.append(references[i].field); } subquery.append(" FROM " + table); if (ids.isEmpty()) return Collections.singletonList(subquery.toString()); subquery.append(" WHERE " + idField + " IN "); List<String> idLists = asSqlLists(ids.toArray()); for (String idList : idLists) queries.add(subquery + "(" + idList + ")"); return queries; } static List<String> createQueries(String base, String where, Collection<Long> ids) { if (ids.isEmpty()) return Collections.singletonList(base); base += " " + where + " "; List<String> idLists = Search.asSqlLists(ids.toArray()); List<String> queries = new ArrayList<>(); for (String idList : idLists) { queries.add(base + "(" + idList + ")"); } return queries; } /** * Creates comma separated lists, each containing a thousand ids */ static List<String> asSqlLists(Object[] values) { List<String> idLists = new ArrayList<>(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < values.length; i++) { if (i % 1000 != 0) builder.append(','); builder.append(values[i].toString()); if ((i + 1) % 1000 == 0 || (i + 1) == values.length) { idLists.add(builder.toString()); builder = new StringBuilder(); } } return idLists; } static List<Reference> applyOwnerMaps( List<Reference> references, Map<Long, Class<? extends AbstractEntity>> ownerTypes, Map<Long, Long> ownerIds, Map<Class<? extends AbstractEntity>, Map<Class<? extends AbstractEntity>, String>> nestedProperties) { List<Reference> results = new ArrayList<>(); for (Reference r : references) { if (!ownerTypes.containsKey(r.ownerId) || !ownerIds.containsKey(r.ownerId)) { results.add(r); continue; } Class<? extends AbstractEntity> ownerType = ownerTypes .get(r.ownerId); long ownerId = ownerIds.get(r.ownerId); String nestedProperty = getNestedProperty(r.getType(), ownerType, nestedProperties); results.add(new Reference(r.property, r.getType(), r.id, ownerType, ownerId, nestedProperty, r.getOwnerType(), r.ownerId, r.optional)); } return results; } private static String getNestedProperty( Class<? extends AbstractEntity> type, Class<? extends AbstractEntity> ownerType, Map<Class<? extends AbstractEntity>, Map<Class<? extends AbstractEntity>, String>> nestedProperties) { String defaultValue = "unknown"; Map<Class<? extends AbstractEntity>, String> map = nestedProperties .get(ownerType); if (map == null) return defaultValue; String value = map.get(type); if (value == null) return defaultValue; return value; } }