This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch PHOENIX-7876-feature in repository https://gitbox.apache.org/repos/asf/phoenix.git
commit b2bdfc51df59693bfd5b88132f8fc6ea3bc02039 Author: Andrew Purtell <[email protected]> AuthorDate: Sat Jun 13 23:18:25 2026 -0700 PHOENIX-7923 Simplify EXPLAIN value classes (#2530) Co-authored-by: Claude Opus 4.8[1m] <[email protected]> --- phoenix-core-client/src/main/antlr3/PhoenixSQL.g | 54 +++- .../phoenix/compile/ExplainPlanAttributes.java | 271 +++++++-------------- .../org/apache/phoenix/iterate/ExplainTable.java | 43 +--- .../org/apache/phoenix/parse/ExplainOptions.java | 45 ---- 4 files changed, 129 insertions(+), 284 deletions(-) diff --git a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g index c84d8f3b54..768eb60249 100644 --- a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g +++ b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g @@ -203,6 +203,7 @@ import java.lang.Boolean; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import java.util.Stack; @@ -319,29 +320,60 @@ package org.apache.phoenix.parse; } /** - * Parse a single EXPLAIN option into the given builder. The option name is matched + * Closed-set keys for the EXPLAIN option list, used to reject duplicate options as they are + * encountered while parsing. + */ + private enum ExplainOpt { REGIONS, VERBOSE, FORMAT } + + /** + * Mutable accumulator for the EXPLAIN option list. Lifetime is one explain_node parse. + */ + private static final class ExplainOptsAcc { + boolean regions; + boolean verbose; + ExplainOptions.Format format; + final EnumSet<ExplainOpt> seen = EnumSet.noneOf(ExplainOpt.class); + + void mark(ExplainOpt opt) { + if (!seen.add(opt)) { + throw new RuntimeException("Duplicate EXPLAIN option: " + opt); + } + } + + ExplainOptions build() { + return new ExplainOptions(regions, verbose, + format == null ? ExplainOptions.Format.TEXT : format); + } + } + + /** + * Parse a single EXPLAIN option into the given accumulator. The option name is matched * case-insensitively against the closed set {REGIONS, VERBOSE, FORMAT}. REGIONS and VERBOSE are - * flags and must not carry a value; FORMAT requires a value of TEXT or JSON. + * flags and must not carry a value; FORMAT requires a value of TEXT or JSON. Duplicate options + * are rejected. */ - private void parseExplainOption(ExplainOptions.Builder opts, String name, String value) { + private void parseExplainOption(ExplainOptsAcc opts, String name, String value) { if ("REGIONS".equalsIgnoreCase(name)) { if (value != null) { throw new RuntimeException("EXPLAIN option REGIONS does not take a value"); } - opts.setRegions(true); + opts.mark(ExplainOpt.REGIONS); + opts.regions = true; } else if ("VERBOSE".equalsIgnoreCase(name)) { if (value != null) { throw new RuntimeException("EXPLAIN option VERBOSE does not take a value"); } - opts.setVerbose(true); + opts.mark(ExplainOpt.VERBOSE); + opts.verbose = true; } else if ("FORMAT".equalsIgnoreCase(name)) { if (value == null) { throw new RuntimeException("EXPLAIN option FORMAT requires a value: TEXT or JSON"); } + opts.mark(ExplainOpt.FORMAT); if ("TEXT".equalsIgnoreCase(value)) { - opts.setFormat(ExplainOptions.Format.TEXT); + opts.format = ExplainOptions.Format.TEXT; } else if ("JSON".equalsIgnoreCase(value)) { - opts.setFormat(ExplainOptions.Format.JSON); + opts.format = ExplainOptions.Format.JSON; } else { throw new RuntimeException("Unknown EXPLAIN FORMAT: " + value); } @@ -509,11 +541,11 @@ oneStatement returns [BindableStatement ret] finally{ contextStack.pop(); } explain_node returns [BindableStatement ret] -@init { ExplainOptions.Builder opts = new ExplainOptions.Builder(); } +@init { ExplainOptsAcc opts = new ExplainOptsAcc(); } : EXPLAIN ( (LPAREN explain_option[opts] (COMMA explain_option[opts])* RPAREN) - | (WITH REGIONS { opts.setRegions(true); }) + | (WITH REGIONS { opts.mark(ExplainOpt.REGIONS); opts.regions = true; }) )? q=oneStatement { @@ -523,8 +555,8 @@ explain_node returns [BindableStatement ret] // A single EXPLAIN option. REGIONS is a global keyword token. The remaining option keywords // (VERBOSE, FORMAT, TEXT, JSON) are not reserved and arrive as NAME tokens, validated in the action. -explain_option[ExplainOptions.Builder opts] - : REGIONS { opts.setRegions(true); } +explain_option[ExplainOptsAcc opts] + : REGIONS { opts.mark(ExplainOpt.REGIONS); opts.regions = true; } | k=NAME (v=NAME)? { parseExplainOption(opts, k.getText(), v == null ? null : v.getText()); } ; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java index 24a74a771b..29be54c6e0 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ExplainPlanAttributes.java @@ -141,188 +141,95 @@ public class ExplainPlanAttributes { private final Integer regionLocationsTotalSize; private final int numRegionLocationLookups; - private static final ExplainPlanAttributes EXPLAIN_PLAN_INSTANCE = new ExplainPlanAttributes(); - - private ExplainPlanAttributes() { - this.tenantId = null; - this.viewName = null; - this.viewBaseName = null; - this.cdcScopes = null; - this.txnProvider = null; - this.rewrites = null; - this.estimatedRows = null; - this.estimatedSizeInBytes = null; - this.estimateInfoTs = null; - this.abstractExplainPlan = null; - this.onDuplicateKeyAction = null; - this.serverUpdateSet = null; - this.returningRow = false; - this.hint = null; - this.explainScanType = null; - this.consistency = null; - this.tableName = null; - this.keyRanges = null; - this.indexName = null; - this.indexKind = null; - this.indexRule = null; - this.indexRejected = null; - this.saltBuckets = null; - this.regionsPlanned = null; - this.scanTimeRangeMin = null; - this.scanTimeRangeMax = null; - this.splitsChunk = null; - this.useRoundRobinIterator = false; - this.samplingRate = null; - this.hexStringRVCOffset = null; - this.iteratorTypeAndScanSize = null; - this.scanEstimatedRows = null; - this.scanEstimatedSizeInBytes = null; - this.serverWhereFilter = null; - this.serverDistinctFilter = null; - this.serverMergeColumns = null; - this.serverParsedProjections = null; - this.serverProject = null; - this.serverFilters = null; - this.ignoredHints = null; - this.serverFirstKeyOnlyProjection = false; - this.serverEmptyColumnOnlyProjection = false; - this.serverAggregate = null; - this.serverGroupByLimit = null; - this.serverSortedBy = null; - this.serverOffset = null; - this.serverRowLimit = null; - this.clientFilterBy = null; - this.clientFilters = null; - this.clientAggregate = null; - this.clientDistinctFilter = null; - this.clientAfterAggregate = null; - this.clientSortAlgo = null; - this.clientSortedBy = null; - this.clientOffset = null; - this.clientRowLimit = null; - this.clientSequenceCount = null; - this.clientCursorName = null; - this.clientSteps = null; - this.lhsJoinQueryExplainPlan = null; - this.rhsJoinQueryExplainPlan = null; - this.subPlans = null; - this.dynamicServerFilter = null; - this.afterJoinFilter = null; - this.joinScannerLimit = null; - this.sortMergeSkipMerge = false; - this.regionLocations = null; - this.regionLocationsTotalSize = null; - this.numRegionLocationLookups = 0; - } - - public ExplainPlanAttributes(String tenantId, String viewName, String viewBaseName, - String cdcScopes, String txnProvider, List<String> rewrites, Long estimatedRows, - Long estimatedSizeInBytes, Long estimateInfoTs, String abstractExplainPlan, - OnDuplicateKeyType onDuplicateKeyAction, List<String> serverUpdateSet, boolean returningRow, - Hint hint, String explainScanType, Consistency consistency, String tableName, String keyRanges, - String indexName, String indexKind, String indexRule, List<RejectedIndexEntry> indexRejected, - Integer saltBuckets, Integer regionsPlanned, Long scanTimeRangeMin, Long scanTimeRangeMax, - Integer splitsChunk, boolean useRoundRobinIterator, Double samplingRate, - String hexStringRVCOffset, String iteratorTypeAndScanSize, Long scanEstimatedRows, - Long scanEstimatedSizeInBytes, String serverWhereFilter, String serverDistinctFilter, - Set<PColumn> serverMergeColumns, Map<String, List<String>> serverParsedProjections, - boolean serverFirstKeyOnlyProjection, boolean serverEmptyColumnOnlyProjection, - String serverAggregate, Integer serverGroupByLimit, String serverSortedBy, Integer serverOffset, - Long serverRowLimit, String clientFilterBy, String clientAggregate, String clientDistinctFilter, - String clientAfterAggregate, String clientSortAlgo, String clientSortedBy, Integer clientOffset, - Integer clientRowLimit, Integer clientSequenceCount, String clientCursorName, - List<String> clientSteps, ExplainPlanAttributes lhsJoinQueryExplainPlan, - ExplainPlanAttributes rhsJoinQueryExplainPlan, List<ExplainPlanAttributes> subPlans, - String dynamicServerFilter, String afterJoinFilter, Long joinScannerLimit, - boolean sortMergeSkipMerge, List<HRegionLocation> regionLocations, - Integer regionLocationsTotalSize, int numRegionLocationLookups, List<String> serverProject, - List<ExplainFilter> serverFilters, Map<String, String> ignoredHints, - List<ExplainFilter> clientFilters) { - this.tenantId = tenantId; - this.viewName = viewName; - this.viewBaseName = viewBaseName; - this.cdcScopes = cdcScopes; - this.txnProvider = txnProvider; - this.rewrites = (rewrites == null || rewrites.isEmpty()) + private static final ExplainPlanAttributes EXPLAIN_PLAN_INSTANCE = + new ExplainPlanAttributesBuilder().build(); + + private ExplainPlanAttributes(ExplainPlanAttributesBuilder b) { + this.tenantId = b.tenantId; + this.viewName = b.viewName; + this.viewBaseName = b.viewBaseName; + this.cdcScopes = b.cdcScopes; + this.txnProvider = b.txnProvider; + this.rewrites = (b.rewrites == null || b.rewrites.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(rewrites)); - this.estimatedRows = estimatedRows; - this.estimatedSizeInBytes = estimatedSizeInBytes; - this.estimateInfoTs = estimateInfoTs; - this.abstractExplainPlan = abstractExplainPlan; - this.onDuplicateKeyAction = onDuplicateKeyAction; - this.serverUpdateSet = (serverUpdateSet == null || serverUpdateSet.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.rewrites)); + this.estimatedRows = b.estimatedRows; + this.estimatedSizeInBytes = b.estimatedSizeInBytes; + this.estimateInfoTs = b.estimateInfoTs; + this.abstractExplainPlan = b.abstractExplainPlan; + this.onDuplicateKeyAction = b.onDuplicateKeyAction; + this.serverUpdateSet = (b.serverUpdateSet == null || b.serverUpdateSet.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(serverUpdateSet)); - this.returningRow = returningRow; - this.hint = hint; - this.explainScanType = explainScanType; - this.consistency = consistency; - this.tableName = tableName; - this.keyRanges = keyRanges; - this.indexName = indexName; - this.indexKind = indexKind; - this.indexRule = indexRule; - this.indexRejected = (indexRejected == null || indexRejected.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.serverUpdateSet)); + this.returningRow = b.returningRow; + this.hint = b.hint; + this.explainScanType = b.explainScanType; + this.consistency = b.consistency; + this.tableName = b.tableName; + this.keyRanges = b.keyRanges; + this.indexName = b.indexName; + this.indexKind = b.indexKind; + this.indexRule = b.indexRule; + this.indexRejected = (b.indexRejected == null || b.indexRejected.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(indexRejected)); - this.saltBuckets = saltBuckets; - this.regionsPlanned = regionsPlanned; - this.scanTimeRangeMin = scanTimeRangeMin; - this.scanTimeRangeMax = scanTimeRangeMax; - this.splitsChunk = splitsChunk; - this.useRoundRobinIterator = useRoundRobinIterator; - this.samplingRate = samplingRate; - this.hexStringRVCOffset = hexStringRVCOffset; - this.iteratorTypeAndScanSize = iteratorTypeAndScanSize; - this.scanEstimatedRows = scanEstimatedRows; - this.scanEstimatedSizeInBytes = scanEstimatedSizeInBytes; - this.serverWhereFilter = serverWhereFilter; - this.serverDistinctFilter = serverDistinctFilter; - this.serverMergeColumns = serverMergeColumns; - this.serverParsedProjections = copyServerParsedProjections(serverParsedProjections); - this.serverFirstKeyOnlyProjection = serverFirstKeyOnlyProjection; - this.serverEmptyColumnOnlyProjection = serverEmptyColumnOnlyProjection; - this.serverAggregate = serverAggregate; - this.serverGroupByLimit = serverGroupByLimit; - this.serverSortedBy = serverSortedBy; - this.serverOffset = serverOffset; - this.serverRowLimit = serverRowLimit; - this.clientFilterBy = clientFilterBy; - this.clientFilters = (clientFilters == null || clientFilters.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.indexRejected)); + this.saltBuckets = b.saltBuckets; + this.regionsPlanned = b.regionsPlanned; + this.scanTimeRangeMin = b.scanTimeRangeMin; + this.scanTimeRangeMax = b.scanTimeRangeMax; + this.splitsChunk = b.splitsChunk; + this.useRoundRobinIterator = b.useRoundRobinIterator; + this.samplingRate = b.samplingRate; + this.hexStringRVCOffset = b.hexStringRVCOffset; + this.iteratorTypeAndScanSize = b.iteratorTypeAndScanSize; + this.scanEstimatedRows = b.scanEstimatedRows; + this.scanEstimatedSizeInBytes = b.scanEstimatedSizeInBytes; + this.serverWhereFilter = b.serverWhereFilter; + this.serverDistinctFilter = b.serverDistinctFilter; + this.serverMergeColumns = b.serverMergeColumns; + this.serverParsedProjections = copyServerParsedProjections(b.serverParsedProjections); + this.serverProject = (b.serverProject == null || b.serverProject.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(clientFilters)); - this.clientAggregate = clientAggregate; - this.clientDistinctFilter = clientDistinctFilter; - this.clientAfterAggregate = clientAfterAggregate; - this.clientSortAlgo = clientSortAlgo; - this.clientSortedBy = clientSortedBy; - this.clientOffset = clientOffset; - this.clientRowLimit = clientRowLimit; - this.clientSequenceCount = clientSequenceCount; - this.clientCursorName = clientCursorName; - this.clientSteps = (clientSteps == null || clientSteps.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.serverProject)); + this.serverFilters = (b.serverFilters == null || b.serverFilters.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(clientSteps)); - this.lhsJoinQueryExplainPlan = lhsJoinQueryExplainPlan; - this.rhsJoinQueryExplainPlan = rhsJoinQueryExplainPlan; - this.subPlans = subPlans; - this.dynamicServerFilter = dynamicServerFilter; - this.afterJoinFilter = afterJoinFilter; - this.joinScannerLimit = joinScannerLimit; - this.sortMergeSkipMerge = sortMergeSkipMerge; - this.regionLocations = regionLocations; - this.regionLocationsTotalSize = regionLocationsTotalSize; - this.numRegionLocationLookups = numRegionLocationLookups; - this.serverProject = (serverProject == null || serverProject.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.serverFilters)); + this.ignoredHints = (b.ignoredHints == null || b.ignoredHints.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(serverProject)); - this.serverFilters = (serverFilters == null || serverFilters.isEmpty()) + : Collections.unmodifiableMap(new LinkedHashMap<>(b.ignoredHints)); + this.serverFirstKeyOnlyProjection = b.serverFirstKeyOnlyProjection; + this.serverEmptyColumnOnlyProjection = b.serverEmptyColumnOnlyProjection; + this.serverAggregate = b.serverAggregate; + this.serverGroupByLimit = b.serverGroupByLimit; + this.serverSortedBy = b.serverSortedBy; + this.serverOffset = b.serverOffset; + this.serverRowLimit = b.serverRowLimit; + this.clientFilterBy = b.clientFilterBy; + this.clientFilters = (b.clientFilters == null || b.clientFilters.isEmpty()) ? null - : Collections.unmodifiableList(new ArrayList<>(serverFilters)); - this.ignoredHints = (ignoredHints == null || ignoredHints.isEmpty()) + : Collections.unmodifiableList(new ArrayList<>(b.clientFilters)); + this.clientAggregate = b.clientAggregate; + this.clientDistinctFilter = b.clientDistinctFilter; + this.clientAfterAggregate = b.clientAfterAggregate; + this.clientSortAlgo = b.clientSortAlgo; + this.clientSortedBy = b.clientSortedBy; + this.clientOffset = b.clientOffset; + this.clientRowLimit = b.clientRowLimit; + this.clientSequenceCount = b.clientSequenceCount; + this.clientCursorName = b.clientCursorName; + this.clientSteps = (b.clientSteps == null || b.clientSteps.isEmpty()) ? null - : Collections.unmodifiableMap(new LinkedHashMap<>(ignoredHints)); + : Collections.unmodifiableList(new ArrayList<>(b.clientSteps)); + this.lhsJoinQueryExplainPlan = b.lhsJoinQueryExplainPlan; + this.rhsJoinQueryExplainPlan = b.rhsJoinQueryExplainPlan; + this.subPlans = b.subPlans; + this.dynamicServerFilter = b.dynamicServerFilter; + this.afterJoinFilter = b.afterJoinFilter; + this.joinScannerLimit = b.joinScannerLimit; + this.sortMergeSkipMerge = b.sortMergeSkipMerge; + this.regionLocations = b.regionLocations; + this.regionLocationsTotalSize = b.regionLocationsTotalSize; + this.numRegionLocationLookups = b.numRegionLocationLookups; } public String getTenantId() { @@ -1236,21 +1143,7 @@ public class ExplainPlanAttributes { } public ExplainPlanAttributes build() { - return new ExplainPlanAttributes(tenantId, viewName, viewBaseName, cdcScopes, txnProvider, - rewrites, estimatedRows, estimatedSizeInBytes, estimateInfoTs, abstractExplainPlan, - onDuplicateKeyAction, serverUpdateSet, returningRow, hint, explainScanType, consistency, - tableName, keyRanges, indexName, indexKind, indexRule, indexRejected, saltBuckets, - regionsPlanned, scanTimeRangeMin, scanTimeRangeMax, splitsChunk, useRoundRobinIterator, - samplingRate, hexStringRVCOffset, iteratorTypeAndScanSize, scanEstimatedRows, - scanEstimatedSizeInBytes, serverWhereFilter, serverDistinctFilter, serverMergeColumns, - serverParsedProjections, serverFirstKeyOnlyProjection, serverEmptyColumnOnlyProjection, - serverAggregate, serverGroupByLimit, serverSortedBy, serverOffset, serverRowLimit, - clientFilterBy, clientAggregate, clientDistinctFilter, clientAfterAggregate, clientSortAlgo, - clientSortedBy, clientOffset, clientRowLimit, clientSequenceCount, clientCursorName, - clientSteps, lhsJoinQueryExplainPlan, rhsJoinQueryExplainPlan, subPlans, - dynamicServerFilter, afterJoinFilter, joinScannerLimit, sortMergeSkipMerge, regionLocations, - regionLocationsTotalSize, numRegionLocationLookups, serverProject, serverFilters, - ignoredHints, clientFilters); + return new ExplainPlanAttributes(this); } } } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java index dff466b3c8..1045d492fc 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/iterate/ExplainTable.java @@ -22,7 +22,6 @@ import java.text.Format; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -804,14 +803,13 @@ public abstract class ExplainTable { } try { StringBuilder buf = new StringBuilder().append(REGION_LOCATIONS); - Set<RegionBoundary> regionBoundaries = new HashSet<>(); + Set<String> regionBoundaries = new LinkedHashSet<>(); List<HRegionLocation> regionLocations = new ArrayList<>(); for (HRegionLocation regionLocation : regionLocationsFromResultIterator) { - RegionBoundary regionBoundary = new RegionBoundary(regionLocation.getRegion().getStartKey(), - regionLocation.getRegion().getEndKey()); - if (!regionBoundaries.contains(regionBoundary)) { + String regionBoundary = Bytes.toStringBinary(regionLocation.getRegion().getStartKey()) + ":" + + Bytes.toStringBinary(regionLocation.getRegion().getEndKey()); + if (regionBoundaries.add(regionBoundary)) { regionLocations.add(regionLocation); - regionBoundaries.add(regionBoundary); } } int maxLimitRegionLoc = context.getConnection().getQueryServices().getConfiguration().getInt( @@ -845,39 +843,6 @@ public abstract class ExplainTable { } } - /** - * Region boundary class with start and end key of the region. - */ - private static class RegionBoundary { - private final byte[] startKey; - private final byte[] endKey; - - RegionBoundary(byte[] startKey, byte[] endKey) { - this.startKey = startKey; - this.endKey = endKey; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RegionBoundary that = (RegionBoundary) o; - return Bytes.compareTo(startKey, that.startKey) == 0 - && Bytes.compareTo(endKey, that.endKey) == 0; - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(startKey); - result = 31 * result + Arrays.hashCode(endKey); - return result; - } - } - @SuppressWarnings("rawtypes") private void appendPKColumnValue(StringBuilder buf, byte[] range, Boolean isNull, int slotIndex, boolean changeViewIndexId) { diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ExplainOptions.java b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ExplainOptions.java index 776f4a7059..95b1bce97c 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ExplainOptions.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ExplainOptions.java @@ -79,49 +79,4 @@ public final class ExplainOptions { return "ExplainOptions{regions=" + regions + ", verbose=" + verbose + ", format=" + format + "}"; } - - /** - * Mutable builder used by the parser to accumulate options as they are encountered in the option - * list. Rejects duplicate options. - */ - public static final class Builder { - private boolean regions; - private boolean regionsSet; - private boolean verbose; - private boolean verboseSet; - private Format format; - - public Builder setRegions(boolean regions) { - if (regionsSet) { - throw new RuntimeException("Duplicate EXPLAIN option: REGIONS"); - } - this.regions = regions; - this.regionsSet = true; - return this; - } - - public Builder setVerbose(boolean verbose) { - if (verboseSet) { - throw new RuntimeException("Duplicate EXPLAIN option: VERBOSE"); - } - this.verbose = verbose; - this.verboseSet = true; - return this; - } - - public Builder setFormat(Format format) { - if (format == null) { - throw new RuntimeException("EXPLAIN option FORMAT requires a value: TEXT or JSON"); - } - if (this.format != null) { - throw new RuntimeException("Duplicate EXPLAIN option: FORMAT"); - } - this.format = format; - return this; - } - - public ExplainOptions build() { - return new ExplainOptions(regions, verbose, format == null ? Format.TEXT : format); - } - } }
