| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. The ASF licenses this file to You |
| * under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. For additional information regarding |
| * copyright in this work, please see the NOTICE file in the top level |
| * directory of this distribution. |
| */ |
| package org.apache.usergrid.persistence.index; |
| |
| import org.apache.usergrid.persistence.index.query.ParsedQuery; |
| import org.apache.usergrid.persistence.index.query.tree.Operand; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class QueryAnalyzer { |
| |
| public static final String v_predicate_count = "sort_predicate_count_exceeded"; |
| public static final String v_operand_count = "operand_count_exceeded"; |
| public static final String v_large_collection = "large_collection_size_bytes"; |
| public static final String v_large_index = "large_index_size_bytes"; |
| public static final String v_full_collection_sort = "full_collection_sort"; |
| |
| public static final String k_violation = "violation"; |
| public static final String k_limit = "limit"; |
| public static final String k_actual = "actual"; |
| |
| |
| public static List<Map<String, Object>> analyze(final ParsedQuery parsedQuery, final long collectionSizeInBytes, |
| final long indexSizeInBytes, final IndexFig indexFig ) { |
| |
| List<Map<String, Object>> violations = new ArrayList<>(); |
| |
| // get configured breaker values |
| final int errorPredicateCount = indexFig.getQueryBreakerErrorSortPredicateCount(); |
| final int errorOperandCount = indexFig.getQueryBreakerErrorOperandCount(); |
| final long errorCollectionSizeBytes = indexFig.getQueryBreakerErrorCollectionSizeBytes(); |
| final long errorIndexSizeBytes = indexFig.getQueryBreakerErrorIndexSizeBytes(); |
| |
| |
| // get the actual values to compare against the configured enforcement values |
| int queryPredicatesSize = parsedQuery.getSortPredicates().size(); |
| int queryOperandCount = getTotalChildCount(parsedQuery.getRootOperand()); |
| |
| // large indexes can cause issues, this is never returned from the API and only logged |
| if( indexSizeInBytes > errorIndexSizeBytes ){ |
| violations.add(new HashMap<String, Object>(3){{ |
| put(k_violation, v_large_index); |
| put(k_limit, errorIndexSizeBytes); |
| put(k_actual, indexSizeInBytes); |
| }}); |
| } |
| |
| // large collections mean that sorts and other complex queries can impact the query service (Elasticsearch) |
| if (collectionSizeInBytes > errorCollectionSizeBytes ){ |
| violations.add(new HashMap<String, Object>(3){{ |
| put(k_violation, v_large_collection); |
| put(k_limit, errorCollectionSizeBytes); |
| put(k_actual, collectionSizeInBytes); |
| }}); |
| |
| // query like "select * order by created asc" |
| if(parsedQuery.getSelectFieldMappings().size() < 1 && |
| !parsedQuery.getOriginalQuery().toLowerCase().contains("where") && |
| parsedQuery.getSortPredicates().size() > 0 ){ |
| |
| violations.add(new HashMap<String, Object>(3){{ |
| put(k_violation, v_full_collection_sort); |
| put(k_limit, null); |
| put(k_actual, null); |
| }}); |
| } |
| |
| } |
| |
| // complex queries can be determined from the # of operands and sort predicates |
| if ( queryPredicatesSize > errorPredicateCount){ |
| violations.add(new HashMap<String, Object>(3){{ |
| put(k_violation, v_predicate_count); |
| put(k_limit, errorPredicateCount); |
| put(k_actual, queryPredicatesSize); |
| }}); |
| } |
| if (queryOperandCount > errorOperandCount){ |
| violations.add(new HashMap<String, Object>(3){{ |
| put(k_violation, v_operand_count); |
| put(k_limit, errorOperandCount); |
| put(k_actual, queryOperandCount); |
| }}); |
| } |
| |
| return violations; |
| |
| } |
| |
| public static String violationsAsString(List<Map<String, Object>> violations, String originalQuery){ |
| |
| final StringBuilder logMessage = new StringBuilder(); |
| logMessage.append( "QueryAnalyzer Violations Detected [").append(violations.size()).append("]: [" ); |
| violations.forEach(violation -> { |
| |
| final StringBuilder violationMessage = new StringBuilder(); |
| violation.forEach((k,v) -> { |
| violationMessage.append(k).append(":").append(v).append(","); |
| |
| }); |
| violationMessage.deleteCharAt(violationMessage.length()-1); |
| logMessage.append(" (").append(violationMessage).append(") "); |
| }); |
| logMessage.append("]"); |
| logMessage.append(" [Original Query: ").append(originalQuery).append("]"); |
| return logMessage.toString(); |
| |
| } |
| |
| private static int getTotalChildCount(Operand rootOperand){ |
| int count = 0; |
| if( rootOperand != null) { |
| count ++; |
| if (rootOperand.getChildren() != null) { |
| for (Object child : rootOperand.getChildren()) { |
| if (child instanceof Operand) { |
| count += getTotalChildCount((Operand) child); |
| } |
| } |
| } |
| } |
| return count; |
| } |
| |
| } |