Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id B6003200CEC for ; Mon, 21 Aug 2017 21:56:19 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 967D016593E; Mon, 21 Aug 2017 19:56:19 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 9309916593B for ; Mon, 21 Aug 2017 21:56:17 +0200 (CEST) Received: (qmail 50545 invoked by uid 500); 21 Aug 2017 19:56:15 -0000 Mailing-List: contact commits-help@geode.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@geode.apache.org Delivered-To: mailing list commits@geode.apache.org Received: (qmail 50536 invoked by uid 99); 21 Aug 2017 19:56:15 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 21 Aug 2017 19:56:15 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 5AB5DE053D; Mon, 21 Aug 2017 19:56:15 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jstewart@apache.org To: commits@geode.apache.org Message-Id: <919face80dba47ea95719db3d6de629f@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: geode git commit: GEODE-3217: Reimplement gfsh query as a single-step command Date: Mon, 21 Aug 2017 19:56:15 +0000 (UTC) archived-at: Mon, 21 Aug 2017 19:56:19 -0000 Repository: geode Updated Branches: refs/heads/release/1.2.1 828b24697 -> 070ea49d2 GEODE-3217: Reimplement gfsh query as a single-step command (cherry picked from commit 564a94b) Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/070ea49d Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/070ea49d Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/070ea49d Branch: refs/heads/release/1.2.1 Commit: 070ea49d2633edff9992cf1d2558b98f73553144 Parents: 828b246 Author: Jared Stewart Authored: Mon Jul 17 11:14:40 2017 -0700 Committer: Jared Stewart Committed: Mon Aug 21 12:55:47 2017 -0700 ---------------------------------------------------------------------- .../internal/cli/commands/DataCommands.java | 78 ++---- .../internal/cli/commands/QueryCommand.java | 133 +++++++++ .../internal/cli/commands/QueryInterceptor.java | 107 ++++++++ .../internal/cli/domain/DataCommandResult.java | 97 ++----- .../cli/functions/DataCommandFunction.java | 273 +------------------ .../management/internal/cli/shell/Gfsh.java | 55 ++-- .../web/controllers/DataCommandsController.java | 12 +- .../internal/cli/commands/QueryCommandTest.java | 267 ++++++++++++++++++ .../cli/commands/QueryCommandUnitTest.java | 30 ++ .../internal/cli/result/ResultBuilderTest.java | 131 +++++++++ 10 files changed, 749 insertions(+), 434 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/070ea49d/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 cb9c4fe..ae65399 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 @@ -14,12 +14,30 @@ */ package org.apache.geode.management.internal.cli.commands; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; +import org.apache.shiro.subject.Subject; +import org.springframework.shell.core.annotation.CliCommand; +import org.springframework.shell.core.annotation.CliOption; + import org.apache.geode.LogWriter; import org.apache.geode.cache.CacheClosedException; -import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.Region; import org.apache.geode.cache.control.RebalanceFactory; @@ -50,36 +68,13 @@ 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; import org.apache.geode.management.internal.cli.result.TabularResultData; -import org.apache.geode.management.internal.cli.shell.Gfsh; import org.apache.geode.management.internal.security.ResourceOperation; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; -import org.apache.shiro.subject.Subject; -import org.springframework.shell.core.annotation.CliAvailabilityIndicator; -import org.springframework.shell.core.annotation.CliCommand; -import org.springframework.shell.core.annotation.CliOption; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** * @since GemFire 7.0 @@ -1091,41 +1086,6 @@ public class DataCommands implements GfshCommand { return makePresentationResult(dataResult); } - @CliMetaData(relatedTopic = {CliStrings.TOPIC_GEODE_DATA, CliStrings.TOPIC_GEODE_REGION}) - @MultiStepCommand - @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) { - - if (!CliUtil.isGfshVM() && stepName.equals(CliStrings.QUERY__STEPNAME__DEFAULTVALUE)) { - return ResultBuilder.createInfoResult(CliStrings.QUERY__MSG__NOT_SUPPORTED_ON_MEMBERS); - } - - Object[] arguments = new Object[] {query, stepName, interactive}; - CLIStep exec = new DataCommandFunction.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); - } - - @CliAvailabilityIndicator({CliStrings.REBALANCE, CliStrings.GET, CliStrings.PUT, - CliStrings.REMOVE, CliStrings.LOCATE_ENTRY, CliStrings.QUERY, CliStrings.IMPORT_DATA, - CliStrings.EXPORT_DATA}) - public boolean dataCommandsAvailable() { - boolean isAvailable = true; // always available on server - if (CliUtil.isGfshVM()) { // in gfsh check if connected - isAvailable = getGfsh() != null && getGfsh().isConnectedAndReady(); - } - return isAvailable; - } - private static class MemberPRInfo { public ArrayList dsMemberList; public String region; http://git-wip-us.apache.org/repos/asf/geode/blob/070ea49d/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..07d8f9d --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/management/internal/cli/commands/QueryCommand.java @@ -0,0 +1,133 @@ +/* + * 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.internal.security.IntegratedSecurityService; +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 regionsInQuery; + try { + CompiledValue compiledQuery = compiler.compileQuery(query); + Set regions = new HashSet<>(); + compiledQuery.getRegionsInQuery(regions, null); + + // authorize data read on these regions + for (String region : regions) { + IntegratedSecurityService.getSecurityService().authorizeRegionRead(region); + } + + regionsInQuery = Collections.unmodifiableSet(regions); + if (regionsInQuery.size() > 0) { + Set 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 = IntegratedSecurityService.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/070ea49d/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/070ea49d/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 fields = new ArrayList<>(); - List values = new ArrayList(); - 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 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/070ea49d/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 e2164a3..4199f9e 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 @@ -14,7 +14,18 @@ */ package org.apache.geode.management.internal.cli.functions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; + import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.Region; @@ -24,16 +35,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; @@ -46,37 +54,14 @@ 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.CliUtil; -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; -import org.apache.logging.log4j.Logger; -import org.apache.shiro.subject.Subject; -import org.json.JSONArray; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; /** * @since GemFire 7.0 @@ -88,10 +73,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; // this needs to be static so that it won't get serialized @@ -802,7 +783,6 @@ public class DataCommandFunction extends FunctionAdapter implements InternalEnti } - /** * Returns a sorted list of all region full paths found in the specified cache. * @@ -836,237 +816,6 @@ public class DataCommandFunction extends FunctionAdapter implements InternalEnti return list; } - private 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 long serialVersionUID = 1L; - - private static SecurityService securityService = SecurityService.getSecurityService(); - - public SelectExecStep(Object[] arguments) { - super(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; - cachedResult = result; - if (interactive) { - endCount = getPageSize(); - } else { - if (result.getSelectResult() != null) { - endCount = result.getSelectResult().size(); - } - } - if (interactive) { - return result.pageResult(0, endCount, SELECT_STEP_DISPLAY); - } else { - return CLIMultiStepHelper.createBannerResult(new String[] {}, new Object[] {}, - 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 regionsInQuery; - try { - CompiledValue compiledQuery = compiler.compileQuery(query); - Set regions = new HashSet<>(); - compiledQuery.getRegionsInQuery(regions, null); - - // authorize data read on these regions - for (String region : regions) { - securityService.authorizeRegionRead(region); - } - - regionsInQuery = Collections.unmodifiableSet(regions); - if (regionsInQuery.size() > 0) { - Set 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 = securityService.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 " + 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 session; - if (CliUtil.isGfshVM()) { - 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; - } - - private 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/070ea49d/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 c5ff6b6..2124d64 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; @@ -39,32 +66,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 @@ -84,7 +85,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/070ea49d/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 ce2ed54..9312051 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> 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/070ea49d/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 simpleRegion = cache.getRegion("simpleRegion"); + Region 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 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 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 :