Repository: geode Updated Branches: refs/heads/feature/GEM-1483 ab13ac474 -> 24c004d8a (forced update)
GEODE-3217: Reimplement gfsh query as a single-step command Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/564a94b9 Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/564a94b9 Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/564a94b9 Branch: refs/heads/feature/GEM-1483 Commit: 564a94b912e7ad70b1f001d3a43a123ed45900ba Parents: 66cd31a Author: Jared Stewart <jstew...@pivotal.io> Authored: Mon Jul 17 11:14:40 2017 -0700 Committer: Jared Stewart <jstew...@pivotal.io> Committed: Fri Jul 21 10:30:40 2017 -0700 ---------------------------------------------------------------------- .../internal/cli/commands/DataCommands.java | 24 -- .../internal/cli/commands/QueryCommand.java | 132 +++++++++ .../internal/cli/commands/QueryInterceptor.java | 107 ++++++++ .../internal/cli/domain/DataCommandResult.java | 97 ++----- .../cli/functions/DataCommandFunction.java | 247 ----------------- .../management/internal/cli/shell/Gfsh.java | 55 ++-- .../web/controllers/DataCommandsController.java | 12 +- .../geode/management/DataCommandMBeanTest.java | 59 ---- .../internal/cli/commands/QueryCommandTest.java | 267 +++++++++++++++++++ .../cli/commands/QueryCommandUnitTest.java | 30 +++ .../internal/cli/result/ResultBuilderTest.java | 131 +++++++++ 11 files changed, 718 insertions(+), 443 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/DataCommands.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/DataCommands.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/DataCommands.java index 3f4397b..69daca6 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/DataCommands.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/DataCommands.java @@ -63,14 +63,10 @@ import org.apache.geode.management.internal.cli.LogWrapper; import org.apache.geode.management.internal.cli.domain.DataCommandRequest; import org.apache.geode.management.internal.cli.domain.DataCommandResult; import org.apache.geode.management.internal.cli.functions.DataCommandFunction; -import org.apache.geode.management.internal.cli.functions.DataCommandFunction.SelectExecStep; import org.apache.geode.management.internal.cli.functions.ExportDataFunction; import org.apache.geode.management.internal.cli.functions.ImportDataFunction; import org.apache.geode.management.internal.cli.functions.RebalanceFunction; import org.apache.geode.management.internal.cli.i18n.CliStrings; -import org.apache.geode.management.internal.cli.multistep.CLIMultiStepHelper; -import org.apache.geode.management.internal.cli.multistep.CLIStep; -import org.apache.geode.management.internal.cli.multistep.MultiStepCommand; import org.apache.geode.management.internal.cli.result.CompositeResultData; import org.apache.geode.management.internal.cli.result.ErrorResultData; import org.apache.geode.management.internal.cli.result.ResultBuilder; @@ -1092,26 +1088,6 @@ public class DataCommands implements GfshCommand { return makePresentationResult(dataResult); } - @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_DATA, CliStrings.TOPIC_GEODE_REGION}) - @MultiStepCommand(shellOnlyStep = {"ALL"}) - @CliCommand(value = {CliStrings.QUERY}, help = CliStrings.QUERY__HELP) - public Object query( - @CliOption(key = CliStrings.QUERY__QUERY, help = CliStrings.QUERY__QUERY__HELP, - mandatory = true) final String query, - @CliOption(key = CliStrings.QUERY__STEPNAME, help = "Step name", - unspecifiedDefaultValue = CliStrings.QUERY__STEPNAME__DEFAULTVALUE) String stepName, - @CliOption(key = CliStrings.QUERY__INTERACTIVE, help = CliStrings.QUERY__INTERACTIVE__HELP, - unspecifiedDefaultValue = "true") final boolean interactive) { - - Object[] arguments = new Object[] {query, stepName, interactive}; - CLIStep exec = new SelectExecStep(arguments); - CLIStep display = new DataCommandFunction.SelectDisplayStep(arguments); - CLIStep move = new DataCommandFunction.SelectMoveStep(arguments); - CLIStep quit = new DataCommandFunction.SelectQuitStep(arguments); - CLIStep[] steps = {exec, display, move, quit}; - return CLIMultiStepHelper.chooseStep(steps, stepName); - } - private static class MemberPRInfo { ArrayList<DistributedMember> dsMemberList; public String region; http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryCommand.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryCommand.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryCommand.java new file mode 100644 index 0000000..513cd65 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryCommand.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. 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. + */ + +package org.apache.geode.management.internal.cli.commands; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.Logger; +import org.apache.shiro.subject.Subject; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; + +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.query.QueryInvalidException; +import org.apache.geode.cache.query.internal.CompiledValue; +import org.apache.geode.cache.query.internal.QCompiler; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.management.cli.CliMetaData; +import org.apache.geode.management.cli.ConverterHint; +import org.apache.geode.management.cli.Result; +import org.apache.geode.management.internal.cli.domain.DataCommandRequest; +import org.apache.geode.management.internal.cli.domain.DataCommandResult; +import org.apache.geode.management.internal.cli.functions.DataCommandFunction; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.remote.CommandExecutionContext; +import org.apache.geode.management.internal.cli.result.CompositeResultData; +import org.apache.geode.management.internal.cli.result.ResultBuilder; + +public class QueryCommand implements GfshCommand { + private static final Logger logger = LogService.getLogger(); + + @CliCommand(value = "query", help = CliStrings.QUERY__HELP) + @CliMetaData(interceptor = "org.apache.geode.management.internal.cli.commands.QueryInterceptor") + public Result query( + @CliOption(key = CliStrings.QUERY__QUERY, help = CliStrings.QUERY__QUERY__HELP, + mandatory = true) final String query, + @CliOption(key = "file", help = "File in which to output the results.", + optionContext = ConverterHint.FILE) final File outputFile, + @CliOption(key = CliStrings.QUERY__INTERACTIVE, unspecifiedDefaultValue = "false", + help = CliStrings.QUERY__INTERACTIVE__HELP) final boolean interactive) { + DataCommandResult dataResult = select(query); + CompositeResultData rd = dataResult.toSelectCommandResult(); + return ResultBuilder.buildResult(rd); + } + + private DataCommandResult select(String query) { + InternalCache cache = (InternalCache) CacheFactory.getAnyInstance(); + DataCommandResult dataResult; + + if (StringUtils.isEmpty(query)) { + dataResult = DataCommandResult.createSelectInfoResult(null, null, -1, null, + CliStrings.QUERY__MSG__QUERY_EMPTY, false); + return dataResult; + } + + Object array[] = DataCommands.replaceGfshEnvVar(query, CommandExecutionContext.getShellEnv()); + query = (String) array[1]; + boolean limitAdded = false; + + if (!StringUtils.containsIgnoreCase(query, " limit") + && !StringUtils.containsIgnoreCase(query, " count(")) { + query = query + " limit " + CommandExecutionContext.getShellFetchSize(); + limitAdded = true; + } + + @SuppressWarnings("deprecation") + QCompiler compiler = new QCompiler(); + Set<String> regionsInQuery; + try { + CompiledValue compiledQuery = compiler.compileQuery(query); + Set<String> regions = new HashSet<>(); + compiledQuery.getRegionsInQuery(regions, null); + + // authorize data read on these regions + for (String region : regions) { + cache.getSecurityService().authorizeRegionRead(region); + } + + regionsInQuery = Collections.unmodifiableSet(regions); + if (regionsInQuery.size() > 0) { + Set<DistributedMember> members = + DataCommands.getQueryRegionsAssociatedMembers(regionsInQuery, cache, false); + if (members != null && members.size() > 0) { + DataCommandFunction function = new DataCommandFunction(); + DataCommandRequest request = new DataCommandRequest(); + request.setCommand(CliStrings.QUERY); + request.setQuery(query); + Subject subject = cache.getSecurityService().getSubject(); + if (subject != null) { + request.setPrincipal(subject.getPrincipal()); + } + dataResult = DataCommands.callFunctionForRegion(request, function, members); + dataResult.setInputQuery(query); + if (limitAdded) { + dataResult.setLimit(CommandExecutionContext.getShellFetchSize()); + } + return dataResult; + } else { + return DataCommandResult.createSelectInfoResult(null, null, -1, null, CliStrings + .format(CliStrings.QUERY__MSG__REGIONS_NOT_FOUND, regionsInQuery.toString()), false); + } + } else { + return DataCommandResult.createSelectInfoResult(null, null, -1, null, + CliStrings.format(CliStrings.QUERY__MSG__INVALID_QUERY, + "Region mentioned in query probably missing /"), + false); + } + } catch (QueryInvalidException qe) { + logger.error("{} Failed Error {}", query, qe.getMessage(), qe); + return DataCommandResult.createSelectInfoResult(null, null, -1, null, + CliStrings.format(CliStrings.QUERY__MSG__INVALID_QUERY, qe.getMessage()), false); + } + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryInterceptor.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryInterceptor.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryInterceptor.java new file mode 100644 index 0000000..559965f --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryInterceptor.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. 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. + */ + +package org.apache.geode.management.internal.cli.commands; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.SystemUtils; +import org.springframework.shell.event.ParseResult; + +import org.apache.geode.management.cli.Result; +import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor; +import org.apache.geode.management.internal.cli.GfshParseResult; +import org.apache.geode.management.internal.cli.result.CommandResult; +import org.apache.geode.management.internal.cli.result.CompositeResultData; +import org.apache.geode.management.internal.cli.result.InfoResultData; +import org.apache.geode.management.internal.cli.result.ResultBuilder; +import org.apache.geode.management.internal.cli.result.TabularResultData; + +public class QueryInterceptor extends AbstractCliAroundInterceptor { + public static final String FILE_ALREADY_EXISTS_MESSAGE = + "The specified output file already exists."; + + @Override + public Result preExecution(GfshParseResult parseResult) { + File outputFile = getOutputFile(parseResult); + + if (outputFile != null && outputFile.exists()) { + return ResultBuilder.createUserErrorResult(FILE_ALREADY_EXISTS_MESSAGE); + } + + return ResultBuilder.createInfoResult(""); + } + + @Override + public Result postExecution(GfshParseResult parseResult, Result result, Path tempFile) { + File outputFile = getOutputFile(parseResult); + + if (outputFile == null) { + return result; + } + + CommandResult commandResult = (CommandResult) result; + CompositeResultData resultData = (CompositeResultData) commandResult.getResultData(); + CompositeResultData.SectionResultData sectionResultData = resultData.retrieveSectionByIndex(0); + + String limit = sectionResultData.retrieveString("Limit"); + String resultString = sectionResultData.retrieveString("Result"); + String rows = sectionResultData.retrieveString("Rows"); + + if ("false".equalsIgnoreCase(resultString)) { + return result; + } + + TabularResultData tabularResultData = sectionResultData.retrieveTableByIndex(0); + CommandResult resultTable = new CommandResult(tabularResultData); + try { + writeResultTableToFile(outputFile, resultTable); + // return a result w/ message explaining limit + } catch (IOException e) { + throw new RuntimeException(e); + } + + InfoResultData infoResultData = ResultBuilder.createInfoResultData(); + infoResultData.addLine("Result : " + resultString); + if (StringUtils.isNotBlank(limit)) { + infoResultData.addLine("Limit : " + limit); + } + infoResultData.addLine("Rows : " + rows); + infoResultData.addLine(SystemUtils.LINE_SEPARATOR); + infoResultData.addLine("Query results output to " + outputFile.getAbsolutePath()); + + return new CommandResult(infoResultData); + } + + private File getOutputFile(ParseResult parseResult) { + return (File) parseResult.getArguments()[1]; + } + + private void writeResultTableToFile(File file, CommandResult commandResult) throws IOException { + try (FileWriter fileWriter = new FileWriter(file)) { + while (commandResult.hasNextLine()) { + fileWriter.write(commandResult.nextLine()); + + if (commandResult.hasNextLine()) { + fileWriter.write(SystemUtils.LINE_SEPARATOR); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandResult.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandResult.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandResult.java index fe88fc9..b682767 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandResult.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/domain/DataCommandResult.java @@ -14,16 +14,26 @@ */ package org.apache.geode.management.internal.cli.domain; -import static org.apache.geode.management.internal.cli.multistep.CLIMultiStepHelper.createBannerResult; -import static org.apache.geode.management.internal.cli.multistep.CLIMultiStepHelper.createPageResult; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; + import org.apache.geode.DataSerializer; import org.apache.geode.internal.ClassPathLoader; import org.apache.geode.management.cli.Result; import org.apache.geode.management.internal.cli.GfshParser; import org.apache.geode.management.internal.cli.i18n.CliStrings; -import org.apache.geode.management.internal.cli.json.GfJsonArray; import org.apache.geode.management.internal.cli.json.GfJsonException; import org.apache.geode.management.internal.cli.json.GfJsonObject; import org.apache.geode.management.internal.cli.result.CompositeResultData; @@ -31,26 +41,12 @@ import org.apache.geode.management.internal.cli.result.CompositeResultData.Secti import org.apache.geode.management.internal.cli.result.ResultBuilder; import org.apache.geode.management.internal.cli.result.TabularResultData; import org.apache.geode.management.internal.cli.util.JsonUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.json.JSONObject; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; /** * Domain object used for Data Commands Functions TODO : Implement DataSerializable */ public class DataCommandResult implements /* Data */ Serializable { - private static Logger logger = LogManager.getLogger(); private static final long serialVersionUID = 1L; @@ -560,7 +556,7 @@ public class DataCommandResult implements /* Data */ Serializable { section.addData("Message", infoString); } if (inputQuery != null) { - if (this.limit != -1) { + if (this.limit > 0) { section.addData("Limit", this.limit); } if (this.selectResult != null) { @@ -575,67 +571,6 @@ public class DataCommandResult implements /* Data */ Serializable { } } - /** - * This method returns a "Page" as dictated by arguments startCount and endCount. Returned result - * is not standard CommandResult and its consumed by Display Step - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public Result pageResult(int startCount, int endCount, String step) { - List<String> fields = new ArrayList<>(); - List values = new ArrayList<String>(); - fields.add(RESULT_FLAG); - values.add(operationCompletedSuccessfully); - fields.add(QUERY_PAGE_START); - values.add(startCount); - fields.add(QUERY_PAGE_END); - values.add(endCount); - if (errorString != null) { - fields.add("Message"); - values.add(errorString); - return createBannerResult(fields, values, step); - } else { - - if (infoString != null) { - fields.add("Message"); - values.add(infoString); - } - - if (selectResult != null) { - try { - TabularResultData table = ResultBuilder.createTabularResultData(); - String[] headers; - Object[][] rows; - int rowCount = buildTable(table, startCount, endCount); - GfJsonArray array = table.getHeaders(); - headers = new String[array.size()]; - rows = new Object[rowCount][array.size()]; - for (int i = 0; i < array.size(); i++) { - headers[i] = (String) array.get(i); - List<String> list = table.retrieveAllValues(headers[i]); - for (int j = 0; j < list.size(); j++) { - rows[j][i] = list.get(j); - } - } - fields.add(NUM_ROWS); - values.add((selectResult == null) ? 0 : selectResult.size()); - if (queryTraceString != null) { - fields.add(QUERY_TRACE); - values.add(queryTraceString); - } - return createPageResult(fields, values, step, headers, rows); - } catch (GfJsonException e) { - String[] headers = new String[] {"Error"}; - Object[][] rows = {{e.getMessage()}}; - String fieldsArray[] = {QUERY_PAGE_START, QUERY_PAGE_END}; - Object valuesArray[] = {startCount, endCount}; - return createPageResult(fieldsArray, valuesArray, step, headers, rows); - } - } else { - return createBannerResult(fields, values, step); - } - } - } - private int buildTable(TabularResultData table, int startCount, int endCount) { // Three steps: // 1a. Convert each row object to a Json object. @@ -729,6 +664,10 @@ public class DataCommandResult implements /* Data */ Serializable { this.inputQuery = inputQuery; } + public void setLimit(int limit) { + this.limit = limit; + } + public static class KeyInfo implements /* Data */ Serializable { private String memberId; http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DataCommandFunction.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DataCommandFunction.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DataCommandFunction.java index e0e4411..96f8815 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DataCommandFunction.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/functions/DataCommandFunction.java @@ -38,16 +38,13 @@ import org.apache.geode.cache.partition.PartitionRegionHelper; import org.apache.geode.cache.query.FunctionDomainException; import org.apache.geode.cache.query.NameResolutionException; import org.apache.geode.cache.query.Query; -import org.apache.geode.cache.query.QueryInvalidException; import org.apache.geode.cache.query.QueryInvocationTargetException; import org.apache.geode.cache.query.QueryService; import org.apache.geode.cache.query.SelectResults; import org.apache.geode.cache.query.Struct; import org.apache.geode.cache.query.TypeMismatchException; -import org.apache.geode.cache.query.internal.CompiledValue; import org.apache.geode.cache.query.internal.DefaultQuery; import org.apache.geode.cache.query.internal.IndexTrackingQueryObserver; -import org.apache.geode.cache.query.internal.QCompiler; import org.apache.geode.cache.query.internal.QueryObserver; import org.apache.geode.cache.query.internal.QueryObserverHolder; import org.apache.geode.cache.query.internal.StructImpl; @@ -60,21 +57,12 @@ import org.apache.geode.internal.cache.InternalCache; import org.apache.geode.internal.cache.PartitionedRegion; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.security.SecurityService; -import org.apache.geode.management.cli.Result; -import org.apache.geode.management.internal.cli.commands.DataCommands; import org.apache.geode.management.internal.cli.domain.DataCommandRequest; import org.apache.geode.management.internal.cli.domain.DataCommandResult; import org.apache.geode.management.internal.cli.domain.DataCommandResult.SelectResultRow; import org.apache.geode.management.internal.cli.i18n.CliStrings; import org.apache.geode.management.internal.cli.json.GfJsonException; import org.apache.geode.management.internal.cli.json.GfJsonObject; -import org.apache.geode.management.internal.cli.multistep.CLIMultiStepHelper; -import org.apache.geode.management.internal.cli.remote.CommandExecutionContext; -import org.apache.geode.management.internal.cli.result.CommandResult; -import org.apache.geode.management.internal.cli.result.CompositeResultData; -import org.apache.geode.management.internal.cli.result.CompositeResultData.SectionResultData; -import org.apache.geode.management.internal.cli.result.ResultBuilder; -import org.apache.geode.management.internal.cli.shell.Gfsh; import org.apache.geode.management.internal.cli.util.JsonUtil; import org.apache.geode.pdx.PdxInstance; @@ -88,10 +76,6 @@ public class DataCommandFunction extends FunctionAdapter implements InternalEnti private boolean optimizeForWrite = false; - protected static final String SELECT_STEP_DISPLAY = "SELECT_DISPLAY"; - protected static final String SELECT_STEP_MOVE = "SELECT_PAGE_MOVE"; - protected static final String SELECT_STEP_END = "SELECT_END"; - protected static final String SELECT_STEP_EXEC = "SELECT_EXEC"; private static final int NESTED_JSON_LENGTH = 20; @Override @@ -800,7 +784,6 @@ public class DataCommandFunction extends FunctionAdapter implements InternalEnti } - /** * Returns a sorted list of all region full paths found in the specified cache. * @@ -834,236 +817,6 @@ public class DataCommandFunction extends FunctionAdapter implements InternalEnti return list; } - static DataCommandResult cachedResult = null; - - public static class SelectDisplayStep extends CLIMultiStepHelper.LocalStep { - - public SelectDisplayStep(Object[] arguments) { - super(SELECT_STEP_DISPLAY, arguments); - } - - @Override - public Result exec() { - boolean interactive = (Boolean) commandArguments[2]; - GfJsonObject args = CLIMultiStepHelper.getStepArgs(); - int startCount = args.getInt(DataCommandResult.QUERY_PAGE_START); - int endCount = args.getInt(DataCommandResult.QUERY_PAGE_END); - int rows = args.getInt(DataCommandResult.NUM_ROWS); // returns Zero if no rows added so it - // works. - boolean flag = args.getBoolean(DataCommandResult.RESULT_FLAG); - CommandResult commandResult = CLIMultiStepHelper.getDisplayResultFromArgs(args); - Gfsh.println(); - while (commandResult.hasNextLine()) { - Gfsh.println(commandResult.nextLine()); - } - - if (flag) { - boolean paginationNeeded = startCount < rows && endCount < rows && interactive; - if (paginationNeeded) { - while (true) { - String message = ("Press n to move to next page, q to quit and p to previous page : "); - try { - String step = Gfsh.getCurrentInstance().interact(message); - if ("n".equals(step)) { - int nextStart = startCount + getPageSize(); - return CLIMultiStepHelper.createBannerResult( - new String[] {DataCommandResult.QUERY_PAGE_START, - DataCommandResult.QUERY_PAGE_END,}, - new Object[] {nextStart, (nextStart + getPageSize())}, SELECT_STEP_MOVE); - } else if ("p".equals(step)) { - int nextStart = startCount - getPageSize(); - if (nextStart < 0) { - nextStart = 0; - } - return CLIMultiStepHelper.createBannerResult( - new String[] {DataCommandResult.QUERY_PAGE_START, - DataCommandResult.QUERY_PAGE_END}, - new Object[] {nextStart, (nextStart + getPageSize())}, SELECT_STEP_MOVE); - } else if ("q".equals(step)) { - return CLIMultiStepHelper.createBannerResult(new String[] {}, new Object[] {}, - SELECT_STEP_END); - } else { - Gfsh.println("Unknown option "); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - } - return CLIMultiStepHelper.createBannerResult(new String[] {}, new Object[] {}, - SELECT_STEP_END); - } - } - - public static class SelectMoveStep extends CLIMultiStepHelper.RemoteStep { - - private static final long serialVersionUID = 1L; - - public SelectMoveStep(Object[] arguments) { - super(SELECT_STEP_MOVE, arguments); - } - - @Override - public Result exec() { - GfJsonObject args = CLIMultiStepHelper.getStepArgs(); - int startCount = args.getInt(DataCommandResult.QUERY_PAGE_START); - int endCount = args.getInt(DataCommandResult.QUERY_PAGE_END); - return cachedResult.pageResult(startCount, endCount, SELECT_STEP_DISPLAY); - } - } - - public static class SelectExecStep extends CLIMultiStepHelper.RemoteStep { - private static final Logger logger = LogService.getLogger(); - - private static final long serialVersionUID = 1L; - - public SelectExecStep(Object[] arguments) { - super(DataCommandFunction.SELECT_STEP_EXEC, arguments); - } - - @Override - public Result exec() { - String remainingQuery = (String) commandArguments[0]; - boolean interactive = (Boolean) commandArguments[2]; - DataCommandResult result = _select(remainingQuery); - int endCount = 0; - DataCommandFunction.cachedResult = result; - if (interactive) { - endCount = DataCommandFunction.getPageSize(); - } else { - if (result.getSelectResult() != null) { - endCount = result.getSelectResult().size(); - } - } - if (interactive) { - return result.pageResult(0, endCount, DataCommandFunction.SELECT_STEP_DISPLAY); - } else { - return CLIMultiStepHelper.createBannerResult(new String[] {}, new Object[] {}, - DataCommandFunction.SELECT_STEP_END); - } - } - - public DataCommandResult _select(String query) { - InternalCache cache = (InternalCache) CacheFactory.getAnyInstance(); - DataCommandResult dataResult; - - if (StringUtils.isEmpty(query)) { - dataResult = DataCommandResult.createSelectInfoResult(null, null, -1, null, - CliStrings.QUERY__MSG__QUERY_EMPTY, false); - return dataResult; - } - - Object array[] = DataCommands.replaceGfshEnvVar(query, CommandExecutionContext.getShellEnv()); - query = (String) array[1]; - query = addLimit(query); - - @SuppressWarnings("deprecation") - QCompiler compiler = new QCompiler(); - Set<String> regionsInQuery; - try { - CompiledValue compiledQuery = compiler.compileQuery(query); - Set<String> regions = new HashSet<>(); - compiledQuery.getRegionsInQuery(regions, null); - - // authorize data read on these regions - for (String region : regions) { - cache.getSecurityService().authorizeRegionRead(region); - } - - regionsInQuery = Collections.unmodifiableSet(regions); - if (regionsInQuery.size() > 0) { - Set<DistributedMember> members = - DataCommands.getQueryRegionsAssociatedMembers(regionsInQuery, cache, false); - if (members != null && members.size() > 0) { - DataCommandFunction function = new DataCommandFunction(); - DataCommandRequest request = new DataCommandRequest(); - request.setCommand(CliStrings.QUERY); - request.setQuery(query); - Subject subject = cache.getSecurityService().getSubject(); - if (subject != null) { - request.setPrincipal(subject.getPrincipal()); - } - dataResult = DataCommands.callFunctionForRegion(request, function, members); - dataResult.setInputQuery(query); - return dataResult; - } else { - return DataCommandResult.createSelectInfoResult(null, null, -1, null, CliStrings.format( - CliStrings.QUERY__MSG__REGIONS_NOT_FOUND, regionsInQuery.toString()), false); - } - } else { - return DataCommandResult.createSelectInfoResult(null, null, -1, null, - CliStrings.format(CliStrings.QUERY__MSG__INVALID_QUERY, - "Region mentioned in query probably missing /"), - false); - } - } catch (QueryInvalidException qe) { - logger.error("{} Failed Error {}", query, qe.getMessage(), qe); - return DataCommandResult.createSelectInfoResult(null, null, -1, null, - CliStrings.format(CliStrings.QUERY__MSG__INVALID_QUERY, qe.getMessage()), false); - } - } - - private String addLimit(String query) { - if (StringUtils.containsIgnoreCase(query, " limit") - || StringUtils.containsIgnoreCase(query, " count(")) { - return query; - } - return query + " limit " + DataCommandFunction.getFetchSize(); - } - } - - public static class SelectQuitStep extends CLIMultiStepHelper.RemoteStep { - - public SelectQuitStep(Object[] arguments) { - super(SELECT_STEP_END, arguments); - } - - private static final long serialVersionUID = 1L; - - @Override - public Result exec() { - boolean interactive = (Boolean) commandArguments[2]; - GfJsonObject args = CLIMultiStepHelper.getStepArgs(); - DataCommandResult dataResult = cachedResult; - cachedResult = null; - if (interactive) { - return CLIMultiStepHelper.createEmptyResult("END"); - } else { - CompositeResultData rd = dataResult.toSelectCommandResult(); - SectionResultData section = rd.addSection(CLIMultiStepHelper.STEP_SECTION); - section.addData(CLIMultiStepHelper.NEXT_STEP_NAME, "END"); - return ResultBuilder.buildResult(rd); - } - } - } - - public static int getPageSize() { - int pageSize = -1; - Map<String, String> session; - if (Gfsh.getCurrentInstance() != null) { - session = Gfsh.getCurrentInstance().getEnv(); - } else { - session = CommandExecutionContext.getShellEnv(); - } - if (session != null) { - String size = session.get(Gfsh.ENV_APP_COLLECTION_LIMIT); - if (StringUtils.isEmpty(size)) { - pageSize = Gfsh.DEFAULT_APP_COLLECTION_LIMIT; - } else { - pageSize = Integer.parseInt(size); - } - } - if (pageSize == -1) { - pageSize = Gfsh.DEFAULT_APP_COLLECTION_LIMIT; - } - return pageSize; - } - - static int getFetchSize() { - return CommandExecutionContext.getShellFetchSize(); - } - public static String getLogMessage(QueryObserver observer, long startTime, String query) { String usedIndexesString = null; float time = 0.0f; http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java b/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java index 8ab7c93..a69ce36 100755 --- a/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/shell/Gfsh.java @@ -14,8 +14,35 @@ */ package org.apache.geode.management.internal.cli.shell; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URL; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + import jline.Terminal; import jline.console.ConsoleReader; +import org.springframework.shell.core.AbstractShell; +import org.springframework.shell.core.ExecutionStrategy; +import org.springframework.shell.core.ExitShellRequest; +import org.springframework.shell.core.JLineLogHandler; +import org.springframework.shell.core.JLineShell; +import org.springframework.shell.core.Parser; +import org.springframework.shell.event.ShellStatus.Status; + import org.apache.geode.internal.Banner; import org.apache.geode.internal.GemFireVersion; import org.apache.geode.internal.lang.ClassUtils; @@ -40,32 +67,6 @@ import org.apache.geode.management.internal.cli.shell.jline.GfshHistory; import org.apache.geode.management.internal.cli.shell.jline.GfshUnsupportedTerminal; import org.apache.geode.management.internal.cli.shell.unsafe.GfshSignalHandler; import org.apache.geode.management.internal.cli.util.CommentSkipHelper; -import org.springframework.shell.core.AbstractShell; -import org.springframework.shell.core.ExecutionStrategy; -import org.springframework.shell.core.ExitShellRequest; -import org.springframework.shell.core.JLineLogHandler; -import org.springframework.shell.core.JLineShell; -import org.springframework.shell.core.Parser; -import org.springframework.shell.event.ShellStatus.Status; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintStream; -import java.net.URL; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.Logger; /** * Extends an interactive shell provided by @@ -85,7 +86,7 @@ import java.util.logging.Logger; * @since GemFire 7.0 */ public class Gfsh extends JLineShell { - public static final int DEFAULT_APP_FETCH_SIZE = 1000; + public static final int DEFAULT_APP_FETCH_SIZE = 100; public static final int DEFAULT_APP_LAST_EXIT_STATUS = 0; public static final int DEFAULT_APP_COLLECTION_LIMIT = 20; public static final boolean DEFAULT_APP_QUIET_EXECUTION = false; http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/DataCommandsController.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/DataCommandsController.java b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/DataCommandsController.java index fb63184..ff100b5 100644 --- a/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/DataCommandsController.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/web/controllers/DataCommandsController.java @@ -14,9 +14,8 @@ */ package org.apache.geode.management.internal.web.controllers; -import org.apache.geode.internal.lang.StringUtils; -import org.apache.geode.management.internal.cli.i18n.CliStrings; -import org.apache.geode.management.internal.cli.util.CommandStringBuilder; +import java.util.concurrent.Callable; + import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; @@ -26,7 +25,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.WebRequest; -import java.util.concurrent.Callable; +import org.apache.geode.internal.lang.StringUtils; +import org.apache.geode.management.internal.cli.i18n.CliStrings; +import org.apache.geode.management.internal.cli.util.CommandStringBuilder; /** * The DataCommandsController class implements GemFire Management REST API web service endpoints for @@ -185,8 +186,6 @@ public class DataCommandsController extends AbstractCommandsController { @RequestMapping(method = RequestMethod.GET, value = "/regions/data/query") public Callable<ResponseEntity<String>> query(final WebRequest request, @RequestParam(CliStrings.QUERY__QUERY) final String oql, - @RequestParam(value = CliStrings.QUERY__STEPNAME, - defaultValue = CliStrings.QUERY__STEPNAME__DEFAULTVALUE) final String stepName, @RequestParam(value = CliStrings.QUERY__INTERACTIVE, defaultValue = "true") final Boolean interactive) { // logRequest(request); @@ -194,7 +193,6 @@ public class DataCommandsController extends AbstractCommandsController { final CommandStringBuilder command = new CommandStringBuilder(CliStrings.QUERY); command.addOption(CliStrings.QUERY__QUERY, decode(oql)); - command.addOption(CliStrings.QUERY__STEPNAME, stepName); command.addOption(CliStrings.QUERY__INTERACTIVE, String.valueOf(Boolean.TRUE.equals(interactive))); http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/test/java/org/apache/geode/management/DataCommandMBeanTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/management/DataCommandMBeanTest.java b/geode-core/src/test/java/org/apache/geode/management/DataCommandMBeanTest.java deleted file mode 100644 index e5d6ce8..0000000 --- a/geode-core/src/test/java/org/apache/geode/management/DataCommandMBeanTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. 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. - */ - -package org.apache.geode.management; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.geode.cache.RegionShortcut; -import org.apache.geode.test.dunit.rules.MBeanServerConnectionRule; -import org.apache.geode.test.dunit.rules.ServerStarterRule; -import org.apache.geode.test.junit.categories.IntegrationTest; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -@Category(IntegrationTest.class) -public class DataCommandMBeanTest { - private MemberMXBean bean; - - @ClassRule - public static ServerStarterRule server = - new ServerStarterRule().withJMXManager().withRegion(RegionShortcut.REPLICATE, "testRegion"); - - @Rule - public MBeanServerConnectionRule mBeanConnector = new MBeanServerConnectionRule(); - - @Before - public void before() throws Exception { - mBeanConnector.connect(server.getJmxPort()); - bean = mBeanConnector.getProxyMBean(MemberMXBean.class); - } - - @Test - public void testQueryWithStepAll() throws Exception { - String result = bean.processCommand("query --query='SELECT * FROM /testRegion'"); - assertThat(result).contains("Only Remote command can be executed"); - } - - @Test - public void testQueryWithStepExec() throws Exception { - String result = - bean.processCommand("query --query='SELECT * FROM /testRegion' --step-name=SELECT_EXEC"); - assertThat(result).contains("Result : true"); - } -} http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandTest.java new file mode 100644 index 0000000..8f30bfe --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandTest.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. 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. + */ + +package org.apache.geode.management.internal.cli.commands; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import com.google.common.io.Files; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.management.cli.Result; +import org.apache.geode.management.internal.cli.result.CommandResult; +import org.apache.geode.management.internal.cli.shell.Gfsh; +import org.apache.geode.test.dunit.rules.GfshShellConnectionRule; +import org.apache.geode.test.dunit.rules.ServerStarterRule; +import org.apache.geode.test.junit.categories.IntegrationTest; + +@Category(IntegrationTest.class) +@RunWith(Parameterized.class) +public class QueryCommandTest { + @Parameterized.Parameters(name = "Connect via http: {0}") + public static Object[] data() { + return new Object[] {true, false}; + } + + @Parameterized.Parameter + public boolean useHttp; + + @ClassRule + public static ServerStarterRule server = + new ServerStarterRule().withJMXManager().withRegion(RegionShortcut.REPLICATE, "simpleRegion") + .withRegion(RegionShortcut.REPLICATE, "complexRegion"); + + @Rule + public GfshShellConnectionRule gfsh = new GfshShellConnectionRule(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void populateRegions() { + Cache cache = server.getCache(); + Region<String, String> simpleRegion = cache.getRegion("simpleRegion"); + Region<String, Customer> complexRegion = cache.getRegion("complexRegion"); + + for (int i = 0; i < Gfsh.DEFAULT_APP_FETCH_SIZE + 1; i++) { + String key = "key" + i; + + simpleRegion.put(key, "value" + i); + complexRegion.put(key, new Customer("name" + i, "address" + i)); + } + } + + @Before + public void connect() throws Exception { + if (useHttp) { + gfsh.connectAndVerify(server.getHttpPort(), GfshShellConnectionRule.PortType.http); + } else { + gfsh.connectAndVerify(server.getJmxPort(), GfshShellConnectionRule.PortType.jmxManger); + } + } + + @Test + public void doesShowLimitIfLimitNotInQuery() throws Exception { + String output = gfsh.execute("query --query='select * from /simpleRegion'"); + assertThat(output).contains("Rows : " + Gfsh.DEFAULT_APP_FETCH_SIZE); + assertThat(output).contains("Limit : " + Gfsh.DEFAULT_APP_FETCH_SIZE); + assertThatOutputHasResult(output); + } + + @Test + public void doesNotShowLimitIfLimitInQuery() throws Exception { + String output = gfsh.execute("query --query='select * from /simpleRegion limit 50'"); + assertThat(output).contains("Rows : 50"); + assertThat(output).doesNotContain("Limit"); + assertThatOutputHasResult(output); + } + + @Test + public void invalidQueryShouldNotCreateFile() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + FileUtils.deleteQuietly(outputFile); + + String output = + gfsh.execute("query --query='invalid query' --file=" + outputFile.getAbsolutePath()); + assertThat(outputFile).doesNotExist(); + + assertThatOutputHasNoResult(output); + assertThat(output).doesNotContain("Query results output to"); + } + + @Test + public void queryWithInvalidRegionNameDoesNotCreateFile() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + FileUtils.deleteQuietly(outputFile); + + String output = gfsh.execute( + "query --query='select * from /nonExistentRegion' --file=" + outputFile.getAbsolutePath()); + assertThat(outputFile).doesNotExist(); + + assertThatOutputHasNoResult(output); + assertThat(output).doesNotContain("Query results output to"); + } + + @Test + public void outputToFileStillDisplaysResultMetaData() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + FileUtils.deleteQuietly(outputFile); + + String output = gfsh.execute( + "query --query='select * from /simpleRegion' --file=" + outputFile.getAbsolutePath()); + + assertThat(output).contains("Rows"); + assertThat(output).contains("Limit"); + assertThat(output).contains("Query results output to"); + assertThatOutputHasResult(output); + } + + @Test + public void doesNotOverwriteExistingFile() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + assertThat(outputFile).exists(); + + CommandResult result = gfsh.executeCommand( + "query --query='select * from /simpleRegion' --file=" + outputFile.getAbsolutePath()); + + assertThat(result.getStatus()).isEqualTo(Result.Status.ERROR); + assertThat(result.getContent().getString("message")) + .contains("The specified output file already exists."); + } + + @Test + public void canOutputSimpleRegionToFile() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + FileUtils.deleteQuietly(outputFile); + + CommandResult result = gfsh.executeAndVerifyCommand( + "query --query='select * from /simpleRegion' --file=" + outputFile.getAbsolutePath()); + assertThat(outputFile).exists(); + assertThat(result.getContent().toString()).contains(outputFile.getAbsolutePath()); + + List<String> lines = Files.readLines(outputFile, StandardCharsets.UTF_8); + + assertThat(lines.get(0)).isEqualTo("Result"); + assertThat(lines.get(1)).isEqualTo("--------"); + lines.subList(2, lines.size()).forEach(line -> assertThat(line).matches("value\\d+")); + } + + @Test + public void canOutputComplexRegionToFile() throws Exception { + File outputFile = temporaryFolder.newFile("queryOutput.txt"); + FileUtils.deleteQuietly(outputFile); + + CommandResult result = gfsh.executeAndVerifyCommand( + "query --query='select c.name, c.address from /complexRegion c' --file=" + + outputFile.getAbsolutePath()); + assertThat(outputFile).exists(); + assertThat(result.getContent().toString()).contains(outputFile.getAbsolutePath()); + + List<String> lines = Files.readLines(outputFile, StandardCharsets.UTF_8); + + assertThat(lines.get(0)).containsPattern("name\\s+\\|\\s+address"); + lines.subList(2, lines.size()) + .forEach(line -> assertThat(line).matches("name\\d+\\s+\\|\\s+address\\d+")); + } + + @Test + public void outputDisplaysResultsFromComplexRegion() throws Exception { + String result = gfsh.execute("query --query='select c.name, c.address from /complexRegion c'"); + + String[] resultLines = splitOnLineBreaks(result); + + assertThat(resultLines[0]).containsPattern("Result\\s+:\\s+true"); + assertThat(resultLines[1]).containsPattern("Limit\\s+:\\s+100"); + assertThat(resultLines[2]).containsPattern("Rows\\s+:\\s+100"); + assertThat(resultLines[3]).containsPattern("name\\s+\\|\\s+address"); + Arrays.asList(resultLines).subList(5, resultLines.length) + .forEach(line -> assertThat(line).matches("name\\d+\\s+\\|\\s+address\\d+")); + } + + @Test + public void queryWithInvalidRegionNameGivesDescriptiveErrorMessage() throws Exception { + String output = gfsh.execute("query --query='select * from /nonExistentRegion'"); + assertThatOutputHasNoResult(output); + assertThatOutputHasMessage(output, + "Cannot find regions <[/nonExistentRegion]> in any of the members"); + } + + @Test + public void invalidQueryGivesDescriptiveErrorMessage() throws Exception { + String output = gfsh.execute("query --query='this is not a valid query'"); + + assertThatOutputHasNoResult(output); + assertThatOutputHasMessage(output, "Query is invalid due for error : <Syntax error in query:"); + } + + @Test + public void queryGivesDescriptiveErrorMessageIfNoQueryIsSpecified() throws Exception { + String output = gfsh.execute("query"); + + assertThat(output) + .contains("You should specify option (--query, --file, --interactive) for this command"); + } + + private void assertThatOutputHasResult(String output) { + String resultPattern = "Result\\s+:\\s+" + "true"; + assertThat(output).containsPattern(resultPattern); + } + + private void assertThatOutputHasNoResult(String output) { + String resultPattern = "Result\\s+:\\s+" + "false"; + assertThat(output).containsPattern(resultPattern); + } + + private void assertThatOutputHasMessage(String output, String message) { + String patternToMatch = "Message\\s+:\\s+" + Pattern.quote(message); + assertThat(output).containsPattern(patternToMatch); + } + + private String[] splitOnLineBreaks(String multilineString) { + return multilineString.split("[\\r\\n]+"); + } + + public static class Customer { + public String name; + public String address; + + public Customer(String name, String address) { + this.name = name; + this.address = address; + } + + public String toString() { + return name + address; + } + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandUnitTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandUnitTest.java new file mode 100644 index 0000000..11da4b7 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/QueryCommandUnitTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. 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. + */ + +package org.apache.geode.management.internal.cli.commands; + +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +public class QueryCommandUnitTest { + + @Test + public void query() throws Exception { + QueryCommand queryCommand = new QueryCommand(); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/564a94b9/geode-core/src/test/java/org/apache/geode/management/internal/cli/result/ResultBuilderTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/result/ResultBuilderTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/result/ResultBuilderTest.java new file mode 100644 index 0000000..0ce89c1 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/result/ResultBuilderTest.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. 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. + */ +package org.apache.geode.management.internal.cli.result; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.json.JSONArray; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.management.cli.Result; +import org.apache.geode.test.junit.categories.UnitTest; + +@Category(UnitTest.class) +public class ResultBuilderTest { + + @Test + public void messageExistsForString() throws Exception { + CommandResult result = (CommandResult) ResultBuilder.createInfoResult("test message"); + assertThat(result.getContent().get("message")).isInstanceOf(JSONArray.class); + assertThat(result.getContent().get("message").toString()).isEqualTo("[\"test message\"]"); + } + + @Test + public void messageExistsForEmpty() throws Exception { + CommandResult result = (CommandResult) ResultBuilder.createInfoResult(""); + assertThat(result.getContent().get("message")).isInstanceOf(JSONArray.class); + assertThat(result.getContent().get("message").toString()).isEqualTo("[\"\"]"); + + } + + @Test + public void messageExistsForNull() throws Exception { + CommandResult result = (CommandResult) ResultBuilder.createInfoResult(null); + assertThat(result.getContent().get("message")).isInstanceOf(JSONArray.class); + assertThat(result.getContent().get("message").toString()).isEqualTo("[null]"); + + } + + @Test + public void infoResultDataStructure() throws Exception { + InfoResultData result = ResultBuilder.createInfoResultData(); + result.addLine("line 1"); + result.addLine("line 2"); + result.setFooter("Feet!"); + result.setHeader("Header"); + CommandResult cmdResult = (CommandResult) ResultBuilder.buildResult(result); + + assertThat(cmdResult.getGfJsonObject().has("header")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("content")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("footer")).isTrue(); + + assertThat(cmdResult.getContent().has("message")).isTrue(); + + assertThat(cmdResult.getStatus()).isEqualTo(Result.Status.OK); + } + + @Test + public void errorResultDataStructure() throws Exception { + ErrorResultData result = ResultBuilder.createErrorResultData(); + result.addLine("line 1"); + result.addLine("line 2"); + result.setFooter("Feet!"); + result.setHeader("Header"); + CommandResult cmdResult = (CommandResult) ResultBuilder.buildResult(result); + + assertThat(cmdResult.getGfJsonObject().has("header")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("content")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("footer")).isTrue(); + + assertThat(cmdResult.getContent().has("message")).isTrue(); + + assertThat(cmdResult.getStatus()).isEqualTo(Result.Status.ERROR); + } + + @Test + public void tabularResultDataStructure() throws Exception { + TabularResultData result = ResultBuilder.createTabularResultData(); + result.accumulate("column1", "value11"); + result.accumulate("column1", "value12"); + result.accumulate("column2", "value21"); + result.accumulate("column2", "value22"); + + result.setFooter("Feet!"); + result.setHeader("Header"); + CommandResult cmdResult = (CommandResult) ResultBuilder.buildResult(result); + + assertThat(cmdResult.getGfJsonObject().has("header")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("content")).isTrue(); + assertThat(cmdResult.getGfJsonObject().has("footer")).isTrue(); + + assertThat(cmdResult.getContent().has("column1")).isTrue(); + assertThat(cmdResult.getContent().has("column2")).isTrue(); + + assertThat(cmdResult.getContent().getJSONArray("column1").toString()).contains("value11"); + assertThat(cmdResult.getContent().getJSONArray("column1").toString()).contains("value12"); + assertThat(cmdResult.getContent().getJSONArray("column2").toString()).contains("value21"); + assertThat(cmdResult.getContent().getJSONArray("column2").toString()).contains("value22"); + } + + @Test + public void compositeResultDataStructure() throws Exception { + CompositeResultData result = ResultBuilder.createCompositeResultData(); + + result.setFooter("Feet!"); + result.setHeader("Header"); + + assertThat(result.getGfJsonObject().has("header")).isTrue(); + assertThat(result.getGfJsonObject().has("content")).isTrue(); + assertThat(result.getGfJsonObject().has("footer")).isTrue(); + + // build up an example + result.addSection().addData("section 0 key", "section 0 value"); + result.addSection().addTable().accumulate("table 1 column", "table 1 value"); + + result.addSection(); + + } +}