This is an automated email from the ASF dual-hosted git repository.

alsuliman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 5f048be  [ASTERIXDB-2635][*DB] Fix JSON plan pretty printing
5f048be is described below

commit 5f048be5375c80c858a48e7e1280c01d18ea4387
Author: Ali Alsuliman <ali.al.solai...@gmail.com>
AuthorDate: Sat Sep 21 12:55:14 2019 -0700

    [ASTERIXDB-2635][*DB] Fix JSON plan pretty printing
    
    - user model changes: no
    - storage format changes: no
    - interface changes: yes
    
    Details:
    Use Jackson to pretty print the logical plan as JSON.
    
    - fixed JsonLogicalPlanTest to validate JSON plans
      produced by optimizer tests.
    
    Change-Id: Ic5ada2f31afebc3f219b0584b5ae527ddf9e326e
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/3567
    Tested-by: Jenkins <jenk...@fulliautomatix.ics.uci.edu>
    Integration-Tests: Jenkins <jenk...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Ali Alsuliman <ali.al.solai...@gmail.com>
    Reviewed-by: Till Westmann <ti...@apache.org>
---
 .../optimizer/base/AsterixOptimizationContext.java |    6 +-
 .../apache/asterix/api/common/APIFramework.java    |   34 +-
 .../apache/asterix/api/java/AsterixJavaClient.java |    7 +
 .../asterix/test/jsonplan/JsonLogicalPlanTest.java |   21 +-
 .../api/HeuristicCompilerFactoryBuilder.java       |    7 +-
 .../algebricks/algebricks-core/pom.xml             |    8 +
 .../core/algebra/base/IOptimizationContext.java    |    4 +-
 .../operators/logical/IntersectOperator.java       |    8 +
 .../core/algebra/plan/ALogicalPlanImpl.java        |    9 +-
 .../AbstractLogicalOperatorPrettyPrintVisitor.java |   62 +-
 .../prettyprint/AlgebricksStringBuilderWriter.java |   58 +
 ...nPrettyPrinter.java => IPlanPrettyPrinter.java} |   27 +-
 .../LogicalExpressionPrettyPrintVisitor.java       |   15 +-
 .../LogicalOperatorPrettyPrintVisitor.java         |   71 +-
 .../LogicalOperatorPrettyPrintVisitorJson.java     | 1231 ++++++++++----------
 .../algebra/prettyprint/PlanPrettyPrinter.java     |   16 +-
 .../core/rewriter/base/AbstractRuleController.java |   10 +-
 .../base/AlgebricksOptimizationContext.java        |   14 +-
 .../core/rewriter/base/HeuristicOptimizer.java     |   10 +-
 .../rules/EnforceStructuralPropertiesRule.java     |   15 +-
 .../apache/hyracks/api/exceptions/ErrorCode.java   |    1 +
 .../src/main/resources/errormsg/en.properties      |    1 +
 22 files changed, 873 insertions(+), 762 deletions(-)

diff --git 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/AsterixOptimizationContext.java
 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/AsterixOptimizationContext.java
index ea3404d..20673eb 100644
--- 
a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/AsterixOptimizationContext.java
+++ 
b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/AsterixOptimizationContext.java
@@ -30,7 +30,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionEvalSiz
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionTypeComputer;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMergeAggregationExpressionFactory;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMissableTypeComputer;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.rewriter.base.AlgebricksOptimizationContext;
 import 
org.apache.hyracks.algebricks.core.rewriter.base.PhysicalOptimizationConfig;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
@@ -46,11 +46,11 @@ public final class AsterixOptimizationContext extends 
AlgebricksOptimizationCont
             IMergeAggregationExpressionFactory 
mergeAggregationExpressionFactory,
             IExpressionTypeComputer expressionTypeComputer, 
IMissableTypeComputer nullableTypeComputer,
             IConflictingTypeResolver conflictingTypeResovler, 
PhysicalOptimizationConfig physicalOptimizationConfig,
-            AlgebricksPartitionConstraint clusterLocations, 
LogicalOperatorPrettyPrintVisitor prettyPrintVisitor,
+            AlgebricksPartitionConstraint clusterLocations, IPlanPrettyPrinter 
prettyPrinter,
             IWarningCollector warningCollector) {
         super(varCounter, expressionEvalSizeComputer, 
mergeAggregationExpressionFactory, expressionTypeComputer,
                 nullableTypeComputer, conflictingTypeResovler, 
physicalOptimizationConfig, clusterLocations,
-                prettyPrintVisitor, warningCollector);
+                prettyPrinter, warningCollector);
     }
 
     public void addDataSource(DataSource dataSource) {
diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index 5069b9f..d573b52 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -95,10 +95,8 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionEvalSiz
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionTypeComputer;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMergeAggregationExpressionFactory;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMissableTypeComputer;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitorJson;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksStringBuilderWriter;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.rewriter.base.IOptimizationContextFactory;
 import 
org.apache.hyracks.algebricks.core.rewriter.base.PhysicalOptimizationConfig;
@@ -171,10 +169,10 @@ public class APIFramework {
                 IExpressionTypeComputer expressionTypeComputer, 
IMissableTypeComputer missableTypeComputer,
                 IConflictingTypeResolver conflictingTypeResolver, 
PhysicalOptimizationConfig physicalOptimizationConfig,
                 AlgebricksPartitionConstraint clusterLocations, 
IWarningCollector warningCollector) {
-            LogicalOperatorPrettyPrintVisitor prettyPrintVisitor = new 
LogicalOperatorPrettyPrintVisitor();
+            IPlanPrettyPrinter prettyPrinter = 
PlanPrettyPrinter.createStringPlanPrettyPrinter();
             return new AsterixOptimizationContext(varCounter, 
expressionEvalSizeComputer,
                     mergeAggregationExpressionFactory, expressionTypeComputer, 
missableTypeComputer,
-                    conflictingTypeResolver, physicalOptimizationConfig, 
clusterLocations, prettyPrintVisitor,
+                    conflictingTypeResolver, physicalOptimizationConfig, 
clusterLocations, prettyPrinter,
                     warningCollector);
         }
     }
@@ -258,8 +256,9 @@ public class APIFramework {
             if (conf.is(SessionConfig.OOB_OPTIMIZED_LOGICAL_PLAN) || 
isExplainOnly) {
                 if (conf.is(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS)) {
                     // For Optimizer tests.
-                    AlgebricksAppendable buffer = new 
AlgebricksAppendable(output.out());
-                    PlanPrettyPrinter.printPhysicalOps(plan, buffer, 0);
+                    AlgebricksStringBuilderWriter buf = new 
AlgebricksStringBuilderWriter(PlanPrettyPrinter.INIT_SIZE);
+                    PlanPrettyPrinter.printPhysicalOps(plan, buf, 0);
+                    output.out().write(buf.toString());
                 } else {
                     if (isQuery || isLoad) {
                         generateOptimizedLogicalPlan(plan, 
output.config().getPlanFormat());
@@ -341,10 +340,9 @@ public class APIFramework {
         }
     }
 
-    private AbstractLogicalOperatorPrettyPrintVisitor 
getPrettyPrintVisitor(SessionConfig.PlanFormat planFormat,
-            PrintWriter out) {
-        return planFormat.equals(SessionConfig.PlanFormat.JSON) ? new 
LogicalOperatorPrettyPrintVisitorJson(out)
-                : new LogicalOperatorPrettyPrintVisitor(out);
+    private IPlanPrettyPrinter getPrettyPrintVisitor(SessionConfig.PlanFormat 
planFormat) {
+        return planFormat.equals(SessionConfig.PlanFormat.JSON) ? 
PlanPrettyPrinter.createJsonPlanPrettyPrinter()
+                : PlanPrettyPrinter.createStringPlanPrettyPrinter();
     }
 
     public void executeJobArray(IHyracksClientConnection hcc, 
JobSpecification[] specs, PrintWriter out)
@@ -485,20 +483,12 @@ public class APIFramework {
     }
 
     private void generateLogicalPlan(ILogicalPlan plan, 
SessionConfig.PlanFormat format) throws AlgebricksException {
-        final StringWriter stringWriter = new StringWriter();
-        try (PrintWriter writer = new PrintWriter(stringWriter)) {
-            PlanPrettyPrinter.printPlan(plan, getPrettyPrintVisitor(format, 
writer), 0);
-            executionPlans.setLogicalPlan(stringWriter.toString());
-        }
+        
executionPlans.setLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan).toString());
     }
 
     private void generateOptimizedLogicalPlan(ILogicalPlan plan, 
SessionConfig.PlanFormat format)
             throws AlgebricksException {
-        final StringWriter stringWriter = new StringWriter();
-        try (PrintWriter writer = new PrintWriter(stringWriter)) {
-            PlanPrettyPrinter.printPlan(plan, getPrettyPrintVisitor(format, 
writer), 0);
-            executionPlans.setOptimizedLogicalPlan(stringWriter.toString());
-        }
+        
executionPlans.setOptimizedLogicalPlan(getPrettyPrintVisitor(format).printPlan(plan).toString());
     }
 
     private void generateJob(JobSpecification spec) {
diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
index 1f8e44c..1cdf6f7 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
@@ -37,6 +37,7 @@ import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.Statement;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.translator.ExecutionPlans;
 import org.apache.asterix.translator.IRequestParameters;
 import org.apache.asterix.translator.IStatementExecutor;
 import org.apache.asterix.translator.IStatementExecutorFactory;
@@ -63,6 +64,7 @@ public class AsterixJavaClient {
     private final IStorageComponentProvider storageComponentProvider;
     private ICcApplicationContext appCtx;
     private Map<String, IAObject> statementParams;
+    private ExecutionPlans executionPlans;
 
     public AsterixJavaClient(ICcApplicationContext appCtx, 
IHyracksClientConnection hcc, Reader queryText,
             PrintWriter writer, ILangCompilationProvider compilationProvider,
@@ -107,6 +109,7 @@ public class AsterixJavaClient {
             PlanFormat pformat) throws Exception {
         queryJobSpec = null;
         dmlJobs = null;
+        executionPlans = null;
 
         if (queryText == null) {
             return;
@@ -136,6 +139,7 @@ public class AsterixJavaClient {
                 new 
ResultProperties(IStatementExecutor.ResultDelivery.IMMEDIATE), new 
IStatementExecutor.Stats(), null,
                 null, null, statementParams, true);
         translator.compileAndExecute(hcc, requestParameters);
+        executionPlans = translator.getExecutionPlans();
         writer.flush();
     }
 
@@ -148,4 +152,7 @@ public class AsterixJavaClient {
         }
     }
 
+    public ExecutionPlans getExecutionPlans() {
+        return executionPlans;
+    }
 }
diff --git 
a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
 
b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
index 38b38c3..8663918 100644
--- 
a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
+++ 
b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
@@ -47,6 +47,7 @@ import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.test.base.AsterixTestHelper;
 import org.apache.asterix.test.common.TestHelper;
 import org.apache.asterix.test.runtime.HDFSCluster;
+import org.apache.asterix.translator.ExecutionPlans;
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.commons.io.FileUtils;
@@ -62,18 +63,24 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 @RunWith(Parameterized.class)
 public class JsonLogicalPlanTest {
 
     private static final Logger LOGGER = LogManager.getLogger();
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    static {
+        
OBJECT_MAPPER.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
+        OBJECT_MAPPER.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
+    }
 
     protected static final String SEPARATOR = File.separator;
     private static final String EXTENSION_AQL = "aql";
     private static final String EXTENSION_SQLPP = "sqlpp";
-    private static final String EXTENSION_RESULT = "plan";
+    private static final String EXTENSION_RESULT = "plan.json";
     private static final String FILENAME_IGNORE = "ignore.txt";
     private static final String FILENAME_ONLY = "only.txt";
     private static final String PATH_BASE =
@@ -189,13 +196,16 @@ public class JsonLogicalPlanTest {
                 provider = extensionLangCompilationProvider;
             }
             IHyracksClientConnection hcc = 
integrationUtil.getHyracksClientConnection();
-
+            String planStr;
             try (PrintWriter plan = new PrintWriter(actualFile)) {
                 AsterixJavaClient asterix = new AsterixJavaClient(
                         (ICcApplicationContext) 
integrationUtil.cc.getApplicationContext(), hcc,
                         new StringReader(query), plan, provider, 
statementExecutorFactory, storageComponentProvider);
                 asterix.setStatementParameters(queryParams);
                 asterix.compile(true, false, !optimized, optimized, false, 
false, false, PlanFormat.JSON);
+                ExecutionPlans executionPlans = asterix.getExecutionPlans();
+                planStr = optimized ? executionPlans.getOptimizedLogicalPlan() 
: executionPlans.getLogicalPlan();
+                plan.write(planStr);
             } catch (AsterixException e) {
                 throw new Exception("Compile ERROR for " + queryFile + ": " + 
e.getMessage(), e);
             }
@@ -217,8 +227,9 @@ public class JsonLogicalPlanTest {
             }
 
             try {
-                final JsonParser parser = new 
ObjectMapper().getJsonFactory().createJsonParser(objectActual);
-                while (parser.nextToken() != null) {
+                JsonNode jsonNode = OBJECT_MAPPER.readTree(planStr);
+                if (jsonNode == null || !jsonNode.isObject()) {
+                    throw new Exception("ERROR: No JSON plan or plan is 
malformed!");
                 }
             } finally {
                 readerActual.close();
diff --git 
a/hyracks-fullstack/algebricks/algebricks-compiler/src/main/java/org/apache/hyracks/algebricks/compiler/api/HeuristicCompilerFactoryBuilder.java
 
b/hyracks-fullstack/algebricks/algebricks-compiler/src/main/java/org/apache/hyracks/algebricks/compiler/api/HeuristicCompilerFactoryBuilder.java
index d51c363..ab68701 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-compiler/src/main/java/org/apache/hyracks/algebricks/compiler/api/HeuristicCompilerFactoryBuilder.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-compiler/src/main/java/org/apache/hyracks/algebricks/compiler/api/HeuristicCompilerFactoryBuilder.java
@@ -28,7 +28,8 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionTypeCom
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMergeAggregationExpressionFactory;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMissableTypeComputer;
 import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.config.AlgebricksConfig;
 import org.apache.hyracks.algebricks.core.jobgen.impl.JobGenContext;
 import org.apache.hyracks.algebricks.core.jobgen.impl.PlanCompiler;
@@ -56,10 +57,10 @@ public class HeuristicCompilerFactoryBuilder extends 
AbstractCompilerFactoryBuil
                 IExpressionTypeComputer expressionTypeComputer, 
IMissableTypeComputer missableTypeComputer,
                 IConflictingTypeResolver conflictingTypeResolver, 
PhysicalOptimizationConfig physicalOptimizationConfig,
                 AlgebricksPartitionConstraint clusterLocations, 
IWarningCollector warningCollector) {
-            LogicalOperatorPrettyPrintVisitor prettyPrintVisitor = new 
LogicalOperatorPrettyPrintVisitor();
+            IPlanPrettyPrinter prettyPrinter = 
PlanPrettyPrinter.createStringPlanPrettyPrinter();
             return new AlgebricksOptimizationContext(varCounter, 
expressionEvalSizeComputer,
                     mergeAggregationExpressionFactory, expressionTypeComputer, 
missableTypeComputer,
-                    conflictingTypeResolver, physicalOptimizationConfig, 
clusterLocations, prettyPrintVisitor,
+                    conflictingTypeResolver, physicalOptimizationConfig, 
clusterLocations, prettyPrinter,
                     warningCollector);
         }
     }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/pom.xml 
b/hyracks-fullstack/algebricks/algebricks-core/pom.xml
index 42e30dd..35acfb8 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/pom.xml
+++ b/hyracks-fullstack/algebricks/algebricks-core/pom.xml
@@ -87,6 +87,10 @@
       <artifactId>commons-lang3</artifactId>
     </dependency>
     <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-api</artifactId>
     </dependency>
@@ -94,5 +98,9 @@
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-collections4</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+    </dependency>
   </dependencies>
 </project>
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/IOptimizationContext.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/IOptimizationContext.java
index c31cb88..726a5df 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/IOptimizationContext.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/IOptimizationContext.java
@@ -25,7 +25,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionEvalSiz
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMergeAggregationExpressionFactory;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IVariableEvalSizeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.FunctionalDependency;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.ILogicalPropertiesVector;
 import org.apache.hyracks.algebricks.core.algebra.properties.INodeDomain;
@@ -82,7 +82,7 @@ public interface IOptimizationContext extends ITypingContext, 
IVariableContext {
 
     public void updatePrimaryKeys(Map<LogicalVariable, LogicalVariable> 
mappedVars);
 
-    public AbstractLogicalOperatorPrettyPrintVisitor getPrettyPrintVisitor();
+    public IPlanPrettyPrinter getPrettyPrinter();
 
     public INodeDomain getComputationNodeDomain();
 
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/IntersectOperator.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/IntersectOperator.java
index 36d2db5..f77d9db 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/IntersectOperator.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/IntersectOperator.java
@@ -158,6 +158,14 @@ public class IntersectOperator extends 
AbstractLogicalOperator {
         return inputExtraVars.get(inputIndex);
     }
 
+    public List<List<LogicalVariable>> getAllInputsCompareVariables() {
+        return inputCompareVars;
+    }
+
+    public List<List<LogicalVariable>> getAllInputsExtraVariables() {
+        return inputExtraVars;
+    }
+
     public List<LogicalVariable> getOutputCompareVariables() {
         return outputCompareVars;
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
index f8d0a50..b5b01a0 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/plan/ALogicalPlanImpl.java
@@ -25,14 +25,13 @@ import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
 import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 
 public class ALogicalPlanImpl implements ILogicalPlan {
     private List<Mutable<ILogicalOperator>> roots;
 
     public ALogicalPlanImpl() {
-        this.roots = new ArrayList<Mutable<ILogicalOperator>>();
+        this.roots = new ArrayList<>();
     }
 
     public ALogicalPlanImpl(List<Mutable<ILogicalOperator>> roots) {
@@ -40,7 +39,7 @@ public class ALogicalPlanImpl implements ILogicalPlan {
     }
 
     public ALogicalPlanImpl(Mutable<ILogicalOperator> root) {
-        roots = new ArrayList<Mutable<ILogicalOperator>>(1);
+        roots = new ArrayList<>(1);
         roots.add(root);
     }
 
@@ -54,9 +53,7 @@ public class ALogicalPlanImpl implements ILogicalPlan {
     }
 
     public static String prettyPrintPlan(ILogicalPlan plan) throws 
AlgebricksException {
-        LogicalOperatorPrettyPrintVisitor pvisitor = new 
LogicalOperatorPrettyPrintVisitor();
-        PlanPrettyPrinter.printPlan(plan, pvisitor, 0);
-        return pvisitor.get().toString();
+        return 
PlanPrettyPrinter.createStringPlanPrettyPrinter().printPlan(plan).toString();
     }
 
     @Override
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
index 5319e6a..9164e6d 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
@@ -28,36 +28,25 @@ import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOper
 import 
org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
 import 
org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
 
-public abstract class AbstractLogicalOperatorPrettyPrintVisitor implements 
ILogicalOperatorVisitor<Void, Integer> {
-    ILogicalExpressionVisitor<String, Integer> exprVisitor;
-    AlgebricksAppendable buffer;
+public abstract class AbstractLogicalOperatorPrettyPrintVisitor<T> implements 
ILogicalOperatorVisitor<Void, T> {
 
-    public AbstractLogicalOperatorPrettyPrintVisitor() {
-        this(new AlgebricksAppendable());
-    }
-
-    public AbstractLogicalOperatorPrettyPrintVisitor(Appendable app) {
-        this(new AlgebricksAppendable(app), new 
LogicalExpressionPrettyPrintVisitor());
-    }
+    protected final ILogicalExpressionVisitor<String, T> exprVisitor;
+    protected final AlgebricksStringBuilderWriter buffer;
 
-    public AbstractLogicalOperatorPrettyPrintVisitor(AlgebricksAppendable 
buffer) {
-        this(buffer, new LogicalExpressionPrettyPrintVisitor());
-    }
-
-    public AbstractLogicalOperatorPrettyPrintVisitor(AlgebricksAppendable 
buffer,
-            ILogicalExpressionVisitor<String, Integer> exprVisitor) {
-        this.buffer = buffer;
+    public 
AbstractLogicalOperatorPrettyPrintVisitor(ILogicalExpressionVisitor<String, T> 
exprVisitor) {
+        this.buffer = new 
AlgebricksStringBuilderWriter(PlanPrettyPrinter.INIT_SIZE);
         this.exprVisitor = exprVisitor;
     }
 
-    public AlgebricksAppendable reset(AlgebricksAppendable buffer) {
-        AlgebricksAppendable old = this.buffer;
-        this.buffer = buffer;
-        return old;
+    public static void printPhysicalOps(ILogicalPlan plan, 
AlgebricksStringBuilderWriter out, int indent)
+            throws AlgebricksException {
+        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+            printPhysicalOperator((AbstractLogicalOperator) root.getValue(), 
indent, out);
+        }
     }
 
-    public AlgebricksAppendable get() {
-        return buffer;
+    protected void resetState() {
+        buffer.getBuilder().setLength(0);
     }
 
     @Override
@@ -65,41 +54,33 @@ public abstract class 
AbstractLogicalOperatorPrettyPrintVisitor implements ILogi
         return buffer.toString();
     }
 
-    CharSequence str(Object o) {
+    String str(Object o) {
         return String.valueOf(o);
     }
 
-    protected static void appendln(AlgebricksAppendable buf, String s) throws 
AlgebricksException {
+    protected static void appendln(AlgebricksStringBuilderWriter buf, String 
s) {
         buf.append(s);
         buf.append("\n");
     }
 
-    protected static void append(AlgebricksAppendable buf, String s) throws 
AlgebricksException {
+    protected static void append(AlgebricksStringBuilderWriter buf, String s) {
         buf.append(s);
     }
 
-    protected static void pad(AlgebricksAppendable buf, int indent) throws 
AlgebricksException {
+    protected static void pad(AlgebricksStringBuilderWriter buf, int indent) {
         for (int i = 0; i < indent; ++i) {
             buf.append(' ');
         }
     }
 
-    protected AlgebricksAppendable addIndent(int level) throws 
AlgebricksException {
+    protected AlgebricksStringBuilderWriter addIndent(int level) {
         for (int i = 0; i < level; ++i) {
             buffer.append(' ');
         }
         return buffer;
     }
 
-    public void printPlan(ILogicalPlan plan, int indent) throws 
AlgebricksException {
-        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
-            printOperator((AbstractLogicalOperator) root.getValue(), indent);
-        }
-    }
-
-    public abstract void printOperator(AbstractLogicalOperator op, int indent) 
throws AlgebricksException;
-
-    public static void printPhysicalOperator(AbstractLogicalOperator op, int 
indent, AlgebricksAppendable out)
+    private static void printPhysicalOperator(AbstractLogicalOperator op, int 
indent, AlgebricksStringBuilderWriter out)
             throws AlgebricksException {
         IPhysicalOperator pOp = op.getPhysicalOperator();
         pad(out, indent);
@@ -118,11 +99,4 @@ public abstract class 
AbstractLogicalOperatorPrettyPrintVisitor implements ILogi
             printPhysicalOperator((AbstractLogicalOperator) i.getValue(), 
indent + 2, out);
         }
     }
-
-    public static void printPhysicalOps(ILogicalPlan plan, 
AlgebricksAppendable out, int indent)
-            throws AlgebricksException {
-        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
-            printPhysicalOperator((AbstractLogicalOperator) root.getValue(), 
indent, out);
-        }
-    }
 }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AlgebricksStringBuilderWriter.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AlgebricksStringBuilderWriter.java
new file mode 100644
index 0000000..2444ce8
--- /dev/null
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AlgebricksStringBuilderWriter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.hyracks.algebricks.core.algebra.prettyprint;
+
+import java.io.IOException;
+
+import org.apache.commons.io.output.StringBuilderWriter;
+import org.apache.hyracks.util.annotations.NotThreadSafe;
+
+/**
+ * String writer based on string builder to provide un-synchronized writer. It 
overrides append methods to allow
+ * chaining to the same string-builder-based writer to avoid throwing {@link 
IOException}
+ */
+@NotThreadSafe
+public class AlgebricksStringBuilderWriter extends StringBuilderWriter {
+
+    public AlgebricksStringBuilderWriter() {
+        super();
+    }
+
+    public AlgebricksStringBuilderWriter(final int capacity) {
+        super(capacity);
+    }
+
+    @Override
+    public AlgebricksStringBuilderWriter append(final char value) {
+        super.append(value);
+        return this;
+    }
+
+    @Override
+    public AlgebricksStringBuilderWriter append(final CharSequence value) {
+        super.append(value);
+        return this;
+    }
+
+    @Override
+    public AlgebricksStringBuilderWriter append(final CharSequence value, 
final int start, final int end) {
+        super.append(value, start, end);
+        return this;
+    }
+}
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
similarity index 61%
copy from 
hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
copy to 
hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
index 67640c5..922e0ee 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/IPlanPrettyPrinter.java
@@ -22,19 +22,20 @@ import 
org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 
-public class PlanPrettyPrinter {
-    public static void printOperator(AbstractLogicalOperator op, 
AbstractLogicalOperatorPrettyPrintVisitor pvisitor,
-            int indent) throws AlgebricksException {
-        pvisitor.printOperator(op, indent);
-    }
+/**
+ * Note: Some implementations may be stateful and not thread-safe.
+ */
+public interface IPlanPrettyPrinter {
+
+    /** Prints the plan rooted at the operator argument. */
+    IPlanPrettyPrinter printOperator(AbstractLogicalOperator operator) throws 
AlgebricksException;
+
+    /** Prints the whole logical plan. */
+    IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws AlgebricksException;
 
-    public static void printPlan(ILogicalPlan plan, 
AbstractLogicalOperatorPrettyPrintVisitor pvisitor, int indent)
-            throws AlgebricksException {
-        pvisitor.printPlan(plan, indent);
-    }
+    /** Resets the state of the pretty printer. */
+    IPlanPrettyPrinter reset() throws AlgebricksException;
 
-    public static void printPhysicalOps(ILogicalPlan plan, 
AlgebricksAppendable out, int indent)
-            throws AlgebricksException {
-        AbstractLogicalOperatorPrettyPrintVisitor.printPhysicalOps(plan, out, 
indent);
-    }
+    /** @return the string of the printed plan. */
+    String toString();
 }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalExpressionPrettyPrintVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalExpressionPrettyPrintVisitor.java
index 72f891a..6794c64 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalExpressionPrettyPrintVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalExpressionPrettyPrintVisitor.java
@@ -27,39 +27,38 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionC
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
 import 
org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
 
-public class LogicalExpressionPrettyPrintVisitor implements 
ILogicalExpressionVisitor<String, Integer> {
+public class LogicalExpressionPrettyPrintVisitor<T> implements 
ILogicalExpressionVisitor<String, T> {
 
     @Override
-    public String visitConstantExpression(ConstantExpression expr, Integer 
indent) throws AlgebricksException {
+    public String visitConstantExpression(ConstantExpression expr, T arg) 
throws AlgebricksException {
         return expr.toString();
     }
 
     @Override
-    public String visitVariableReferenceExpression(VariableReferenceExpression 
expr, Integer indent)
-            throws AlgebricksException {
+    public String visitVariableReferenceExpression(VariableReferenceExpression 
expr, T arg) throws AlgebricksException {
         return expr.toString();
     }
 
     @Override
-    public String 
visitAggregateFunctionCallExpression(AggregateFunctionCallExpression expr, 
Integer indent)
+    public String 
visitAggregateFunctionCallExpression(AggregateFunctionCallExpression expr, T 
arg)
             throws AlgebricksException {
         return expr.toString();
     }
 
     @Override
-    public String 
visitScalarFunctionCallExpression(ScalarFunctionCallExpression expr, Integer 
indent)
+    public String 
visitScalarFunctionCallExpression(ScalarFunctionCallExpression expr, T arg)
             throws AlgebricksException {
         return expr.toString();
     }
 
     @Override
-    public String 
visitStatefulFunctionCallExpression(StatefulFunctionCallExpression expr, 
Integer indent)
+    public String 
visitStatefulFunctionCallExpression(StatefulFunctionCallExpression expr, T arg)
             throws AlgebricksException {
         return expr.toString();
     }
 
     @Override
-    public String 
visitUnnestingFunctionCallExpression(UnnestingFunctionCallExpression expr, 
Integer indent)
+    public String 
visitUnnestingFunctionCallExpression(UnnestingFunctionCallExpression expr, T 
arg)
             throws AlgebricksException {
         return expr.toString();
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
index 142d4cc..52c7e2a 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
@@ -70,43 +70,55 @@ import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperat
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WindowOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteResultOperator;
-import 
org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
 
-public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPrettyPrintVisitor {
+public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPrettyPrintVisitor<Integer>
+        implements IPlanPrettyPrinter {
 
-    public LogicalOperatorPrettyPrintVisitor() {
-        super();
-    }
+    private static final int INIT_INDENT = 2;
+    private static final int SUBPLAN_INDENT = INIT_INDENT * 5;
 
-    public LogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer,
-            ILogicalExpressionVisitor<String, Integer> exprVisitor) {
-        super(buffer, exprVisitor);
+    LogicalOperatorPrettyPrintVisitor() {
+        super(new LogicalExpressionPrettyPrintVisitor<>());
     }
 
-    public LogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer) {
-        super(buffer);
+    @Override
+    public final IPlanPrettyPrinter reset() throws AlgebricksException {
+        resetState();
+        return this;
     }
 
-    public LogicalOperatorPrettyPrintVisitor(Appendable app) {
-        super(app);
+    @Override
+    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws 
AlgebricksException {
+        printPlanImpl(plan, 0);
+        return this;
     }
 
     @Override
-    public void printOperator(AbstractLogicalOperator op, int indent) throws 
AlgebricksException {
-        final AlgebricksAppendable out = this.get();
+    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op) 
throws AlgebricksException {
+        printOperatorImpl(op, 0);
+        return this;
+    }
+
+    private void printPlanImpl(ILogicalPlan plan, int indent) throws 
AlgebricksException {
+        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+            printOperatorImpl((AbstractLogicalOperator) root.getValue(), 
indent);
+        }
+    }
+
+    private void printOperatorImpl(AbstractLogicalOperator op, int indent) 
throws AlgebricksException {
         op.accept(this, indent);
         IPhysicalOperator pOp = op.getPhysicalOperator();
 
         if (pOp != null) {
-            out.append("\n");
-            pad(out, indent);
-            appendln(out, "-- " + pOp.toString() + "  |" + 
op.getExecutionMode() + "|");
+            buffer.append("\n");
+            pad(buffer, indent);
+            appendln(buffer, "-- " + pOp.toString() + "  |" + 
op.getExecutionMode() + "|");
         } else {
-            appendln(out, " -- |" + op.getExecutionMode() + "|");
+            appendln(buffer, " -- |" + op.getExecutionMode() + "|");
         }
 
         for (Mutable<ILogicalOperator> i : op.getInputs()) {
-            printOperator((AbstractLogicalOperator) i.getValue(), indent + 2);
+            printOperatorImpl((AbstractLogicalOperator) i.getValue(), indent + 
INIT_INDENT);
         }
     }
 
@@ -293,7 +305,7 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
 
     @Override
     public Void visitUnnestMapOperator(UnnestMapOperator op, Integer indent) 
throws AlgebricksException {
-        AlgebricksAppendable plan = printAbstractUnnestMapOperator(op, indent, 
"unnest-map");
+        AlgebricksStringBuilderWriter plan = 
printAbstractUnnestMapOperator(op, indent, "unnest-map");
         appendSelectConditionInformation(plan, op.getSelectCondition(), 
indent);
         appendLimitInformation(plan, op.getOutputLimit());
         return null;
@@ -306,9 +318,9 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
         return null;
     }
 
-    private AlgebricksAppendable 
printAbstractUnnestMapOperator(AbstractUnnestMapOperator op, Integer indent,
+    private AlgebricksStringBuilderWriter 
printAbstractUnnestMapOperator(AbstractUnnestMapOperator op, Integer indent,
             String opSignature) throws AlgebricksException {
-        AlgebricksAppendable plan = addIndent(indent).append(opSignature + " " 
+ op.getVariables() + " <- "
+        AlgebricksStringBuilderWriter plan = 
addIndent(indent).append(opSignature + " " + op.getVariables() + " <- "
                 + op.getExpressionRef().getValue().accept(exprVisitor, 
indent));
         appendFilterInformation(plan, op.getMinFilterVars(), 
op.getMaxFilterVars());
         return plan;
@@ -316,7 +328,7 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
 
     @Override
     public Void visitDataScanOperator(DataSourceScanOperator op, Integer 
indent) throws AlgebricksException {
-        AlgebricksAppendable plan = addIndent(indent).append(
+        AlgebricksStringBuilderWriter plan = addIndent(indent).append(
                 "data-scan " + op.getProjectVariables() + "<-" + 
op.getVariables() + " <- " + op.getDataSource());
         appendFilterInformation(plan, op.getMinFilterVars(), 
op.getMaxFilterVars());
         appendSelectConditionInformation(plan, op.getSelectCondition(), 
indent);
@@ -324,23 +336,21 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
         return null;
     }
 
-    private Void appendSelectConditionInformation(AlgebricksAppendable plan,
+    private void 
appendSelectConditionInformation(AlgebricksStringBuilderWriter plan,
             Mutable<ILogicalExpression> selectCondition, Integer indent) 
throws AlgebricksException {
         if (selectCondition != null) {
             plan.append(" condition 
(").append(selectCondition.getValue().accept(exprVisitor, indent)).append(")");
         }
-
-        return null;
     }
 
-    private Void appendLimitInformation(AlgebricksAppendable plan, long 
outputLimit) throws AlgebricksException {
+    private void appendLimitInformation(AlgebricksStringBuilderWriter plan, 
long outputLimit)
+            throws AlgebricksException {
         if (outputLimit >= 0) {
             plan.append(" limit ").append(String.valueOf(outputLimit));
         }
-        return null;
     }
 
-    private Void appendFilterInformation(AlgebricksAppendable plan, 
List<LogicalVariable> minFilterVars,
+    private void appendFilterInformation(AlgebricksStringBuilderWriter plan, 
List<LogicalVariable> minFilterVars,
             List<LogicalVariable> maxFilterVars) throws AlgebricksException {
         if (minFilterVars != null || maxFilterVars != null) {
             plan.append(" with filter on");
@@ -351,7 +361,6 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
         if (maxFilterVars != null) {
             plan.append(" max:" + maxFilterVars);
         }
-        return null;
     }
 
     @Override
@@ -565,7 +574,7 @@ public class LogicalOperatorPrettyPrintVisitor extends 
AbstractLogicalOperatorPr
                 } else {
                     addIndent(indent).append("       {\n");
                 }
-                printPlan(p, indent + 10);
+                printPlanImpl(p, indent + SUBPLAN_INDENT);
                 addIndent(indent).append("       }");
             }
         }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
index 14c3549..6422302 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
@@ -18,6 +18,7 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.prettyprint;
 
+import java.io.IOException;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -36,6 +37,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
+import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestNonMapOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
@@ -73,39 +75,59 @@ import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperat
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WindowOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteResultOperator;
+import org.apache.hyracks.api.exceptions.ErrorCode;
 
-public class LogicalOperatorPrettyPrintVisitorJson extends 
AbstractLogicalOperatorPrettyPrintVisitor {
-    Map<AbstractLogicalOperator, String> operatorIdentity = new HashMap<>();
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.util.DefaultIndenter;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
 
-    public LogicalOperatorPrettyPrintVisitorJson() {
-    }
+public class LogicalOperatorPrettyPrintVisitorJson extends 
AbstractLogicalOperatorPrettyPrintVisitor<Void>
+        implements IPlanPrettyPrinter {
 
-    public LogicalOperatorPrettyPrintVisitorJson(Appendable app) {
-        super(app);
-    }
+    private static final JsonFactory JSON_FACTORY = new JsonFactory();
+    private static final DefaultIndenter OBJECT_INDENT = new DefaultIndenter(" 
  ", DefaultIndenter.SYS_LF);
+    private static final String OPERATOR_FIELD = "operator";
+    private static final String VARIABLES_FIELD = "variables";
+    private static final String EXPRESSIONS_FIELD = "expressions";
+    private static final String EXPRESSION_FIELD = "expression";
+    private static final String CONDITION_FIELD = "condition";
 
-    IdCounter idCounter = new IdCounter();
+    private final Map<AbstractLogicalOperator, String> operatorIdentity = new 
HashMap<>();
+    private final IdCounter idCounter = new IdCounter();
+    private final JsonGenerator jsonGenerator;
 
-    public class IdCounter {
+    LogicalOperatorPrettyPrintVisitorJson() {
+        super(new LogicalExpressionPrettyPrintVisitor<>());
+        DefaultPrettyPrinter prettyPrinter = new 
DefaultPrettyPrinter(DefaultIndenter.SYS_LF);
+        prettyPrinter.indentObjectsWith(OBJECT_INDENT);
+        try {
+            jsonGenerator = 
JSON_FACTORY.createGenerator(buffer).setPrettyPrinter(prettyPrinter);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private class IdCounter {
         private int id;
         private final Deque<Integer> prefix;
 
-        public IdCounter() {
-            prefix = new LinkedList<Integer>();
+        private IdCounter() {
+            prefix = new LinkedList<>();
             prefix.add(1);
             this.id = 0;
         }
 
-        public void previousPrefix() {
+        private void previousPrefix() {
             this.id = prefix.removeLast();
         }
 
-        public void nextPrefix() {
+        private void nextPrefix() {
             prefix.add(this.id);
             this.id = 0;
         }
 
-        public String printOperatorId(AbstractLogicalOperator op) {
+        private String printOperatorId(AbstractLogicalOperator op) {
             String stringPrefix = "";
             Object[] values = this.prefix.toArray();
             for (Object val : values) {
@@ -121,531 +143,673 @@ public class LogicalOperatorPrettyPrintVisitorJson 
extends AbstractLogicalOperat
     }
 
     @Override
-    public AlgebricksAppendable reset(AlgebricksAppendable buffer) {
+    public final IPlanPrettyPrinter reset() throws AlgebricksException {
+        flushContentToWriter();
+        resetState();
         operatorIdentity.clear();
-        return super.reset(buffer);
-    }
-
-    @Override
-    public void printOperator(AbstractLogicalOperator op, int indent) throws 
AlgebricksException {
-        int currentIndent = indent;
-        final AlgebricksAppendable out = get();
-        pad(out, currentIndent);
-        appendln(out, "{");
-        currentIndent++;
-        op.accept(this, currentIndent);
-        appendln(out, ",");
-        pad(out, currentIndent);
-        append(out, "\"operatorId\": \"" + idCounter.printOperatorId(op) + 
"\"");
-        IPhysicalOperator pOp = op.getPhysicalOperator();
-        if (pOp != null) {
-            appendln(out, ",");
-            pad(out, currentIndent);
-            String pOperator = "\"physical-operator\": \"" + pOp.toString() + 
"\"";
-            append(out, pOperator);
-        }
-        appendln(out, ",");
-        pad(out, currentIndent);
-        append(out, "\"execution-mode\": \"" + op.getExecutionMode() + '"');
-        if (!op.getInputs().isEmpty()) {
-            appendln(out, ",");
-            pad(out, currentIndent);
-            appendln(out, "\"inputs\": [");
-            boolean moreInputes = false;
-            for (Mutable<ILogicalOperator> k : op.getInputs()) {
-                if (moreInputes) {
-                    append(out, ",");
-                }
-                printOperator((AbstractLogicalOperator) k.getValue(), 
currentIndent + 4);
-                moreInputes = true;
+        return this;
+    }
+
+    @Override
+    public final IPlanPrettyPrinter printPlan(ILogicalPlan plan) throws 
AlgebricksException {
+        printPlanImpl(plan);
+        flushContentToWriter();
+        return this;
+    }
+
+    @Override
+    public final IPlanPrettyPrinter printOperator(AbstractLogicalOperator op) 
throws AlgebricksException {
+        printOperatorImpl(op);
+        flushContentToWriter();
+        return this;
+    }
+
+    private void printPlanImpl(ILogicalPlan plan) throws AlgebricksException {
+        try {
+            boolean writeArrayOfRoots = plan.getRoots().size() > 1;
+            if (writeArrayOfRoots) {
+                jsonGenerator.writeStartArray();
+            }
+            for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+                printOperatorImpl((AbstractLogicalOperator) root.getValue());
+            }
+            if (writeArrayOfRoots) {
+                jsonGenerator.writeEndArray();
             }
-            pad(out, currentIndent + 2);
-            appendln(out, "]");
-        } else {
-            out.append("\n");
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        pad(out, currentIndent - 1);
-        appendln(out, "}");
     }
 
-    public void variablePrintHelper(List<LogicalVariable> variables, Integer 
indent) throws AlgebricksException {
-        if (!variables.isEmpty()) {
-            addIndent(0).append(",\n");
-            addIndent(indent).append("\"variables\": [");
-            appendVars(variables);
-            buffer.append("]");
+    private void printOperatorImpl(AbstractLogicalOperator op) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStartObject();
+            op.accept(this, null);
+            jsonGenerator.writeStringField("operatorId", 
idCounter.printOperatorId(op));
+            IPhysicalOperator pOp = op.getPhysicalOperator();
+            if (pOp != null) {
+                jsonGenerator.writeStringField("physical-operator", 
pOp.toString());
+            }
+            jsonGenerator.writeStringField("execution-mode", 
op.getExecutionMode().toString());
+            if (!op.getInputs().isEmpty()) {
+                jsonGenerator.writeArrayFieldStart("inputs");
+                for (Mutable<ILogicalOperator> k : op.getInputs()) {
+                    printOperatorImpl((AbstractLogicalOperator) k.getValue());
+                }
+                jsonGenerator.writeEndArray();
+            }
+            jsonGenerator.writeEndObject();
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
     }
 
     @Override
-    public Void visitAggregateOperator(AggregateOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"aggregate\"");
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitAggregateOperator(AggregateOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "aggregate");
+            writeVariablesAndExpressions(op.getVariables(), 
op.getExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        variablePrintHelper(op.getVariables(), indent);
-        return null;
     }
 
     @Override
-    public Void visitRunningAggregateOperator(RunningAggregateOperator op, 
Integer indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"running-aggregate\"");
-        variablePrintHelper(op.getVariables(), indent);
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitRunningAggregateOperator(RunningAggregateOperator op, 
Void indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
"running-aggregate");
+            writeVariablesAndExpressions(op.getVariables(), 
op.getExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitEmptyTupleSourceOperator(EmptyTupleSourceOperator op, 
Integer indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"empty-tuple-source\"");
-        return null;
+    public Void visitEmptyTupleSourceOperator(EmptyTupleSourceOperator op, 
Void indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
"empty-tuple-source");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitGroupByOperator(GroupByOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"group-by\"");
-
-        if (op.isGroupAll()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"option\": \"all\"");
-        }
-        if (!op.getGroupByList().isEmpty()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"group-by-list\": ");
-            pprintVeList(op.getGroupByList(), indent);
-        }
-        if (!op.getDecorList().isEmpty()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"decor-list\": ");
-            pprintVeList(op.getDecorList(), indent);
-        }
-        if (!op.getNestedPlans().isEmpty()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"subplan\": ");
-            printNestedPlans(op, indent);
+    public Void visitGroupByOperator(GroupByOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "group-by");
+            if (op.isGroupAll()) {
+                jsonGenerator.writeStringField("option", "all");
+            }
+            if (!op.getGroupByList().isEmpty()) {
+                writeArrayFieldOfVariableExpressionPairs("group-by-list", 
op.getGroupByList(), indent);
+            }
+            if (!op.getDecorList().isEmpty()) {
+                writeArrayFieldOfVariableExpressionPairs("decor-list", 
op.getDecorList(), indent);
+            }
+            if (!op.getNestedPlans().isEmpty()) {
+                writeNestedPlans(op, indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitDistinctOperator(DistinctOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"distinct\"");
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitDistinctOperator(DistinctOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "distinct");
+            List<Mutable<ILogicalExpression>> expressions = 
op.getExpressions();
+            if (!expressions.isEmpty()) {
+                writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, expressions, 
indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitInnerJoinOperator(InnerJoinOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"join\",\n");
-        addIndent(indent).append("\"condition\": \"" + 
op.getCondition().getValue().accept(exprVisitor, indent) + "\"");
-        return null;
+    public Void visitInnerJoinOperator(InnerJoinOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "join");
+            writeStringFieldExpression(CONDITION_FIELD, op.getCondition(), 
indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitLeftOuterJoinOperator(LeftOuterJoinOperator op, Integer 
indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"left-outer-join\",\n");
-        addIndent(indent).append("\"condition\": \"" + 
op.getCondition().getValue().accept(exprVisitor, indent) + "\"");
-        return null;
+    public Void visitLeftOuterJoinOperator(LeftOuterJoinOperator op, Void 
indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "left-outer-join");
+            writeStringFieldExpression(CONDITION_FIELD, op.getCondition(), 
indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitNestedTupleSourceOperator(NestedTupleSourceOperator op, 
Integer indent)
-            throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"nested-tuple-source\"");
-        return null;
+    public Void visitNestedTupleSourceOperator(NestedTupleSourceOperator op, 
Void indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
"nested-tuple-source");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitOrderOperator(OrderOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"order\"");
-        buffer.append(",\n");
-        int topK = op.getTopK();
-        if (topK != -1) {
-            addIndent(indent).append("\"topK\": \"" + topK + "\",\n");
+    public Void visitOrderOperator(OrderOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "order");
+            int topK = op.getTopK();
+            if (topK != -1) {
+                jsonGenerator.writeStringField("topK", String.valueOf(topK));
+            }
+            writeArrayFieldOfOrderExprList("order-by-list", 
op.getOrderExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        addIndent(indent).append("\"order-by-list\": ");
-        pprintOrderExprList(op.getOrderExpressions(), indent);
-        return null;
     }
 
     @Override
-    public Void visitAssignOperator(AssignOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"assign\"");
-        variablePrintHelper(op.getVariables(), indent);
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitAssignOperator(AssignOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "assign");
+            writeVariablesAndExpressions(op.getVariables(), 
op.getExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitWriteOperator(WriteOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"write\"");
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitWriteOperator(WriteOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "write");
+            List<Mutable<ILogicalExpression>> expressions = 
op.getExpressions();
+            if (!expressions.isEmpty()) {
+                writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, expressions, 
indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitDistributeResultOperator(DistributeResultOperator op, 
Integer indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"distribute-result\"");
-        if (!op.getExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getExpressions(), indent);
+    public Void visitDistributeResultOperator(DistributeResultOperator op, 
Void indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
"distribute-result");
+            List<Mutable<ILogicalExpression>> expressions = 
op.getExpressions();
+            if (!expressions.isEmpty()) {
+                writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, expressions, 
indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitWriteResultOperator(WriteResultOperator op, Integer 
indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"load\",\n");
-        addIndent(indent).append(str(op.getDataSource())).append("\"from\":")
-                
.append(op.getPayloadExpression().getValue().accept(exprVisitor, indent) + 
",\n");
-        addIndent(indent).append("\"partitioned-by\": {");
-        pprintExprList(op.getKeyExpressions(), indent);
-        addIndent(indent).append("}");
-        return null;
+    public Void visitWriteResultOperator(WriteResultOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "load");
+            jsonGenerator.writeStringField("data-source", 
String.valueOf(op.getDataSource()));
+            writeStringFieldExpression("from", op.getPayloadExpression(), 
indent);
+            writeObjectFieldWithExpressions("partitioned-by", 
op.getKeyExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitSelectOperator(SelectOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"select\",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + op.getCondition().getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
-        return null;
+    public Void visitSelectOperator(SelectOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "select");
+            writeStringFieldExpression(EXPRESSIONS_FIELD, op.getCondition(), 
indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitProjectOperator(ProjectOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"project\"");
-        variablePrintHelper(op.getVariables(), indent);
-        return null;
+    public Void visitProjectOperator(ProjectOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "project");
+            List<LogicalVariable> variables = op.getVariables();
+            if (!variables.isEmpty()) {
+                writeArrayFieldOfVariables(VARIABLES_FIELD, variables);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitSubplanOperator(SubplanOperator op, Integer indent) 
throws AlgebricksException {
+    public Void visitSubplanOperator(SubplanOperator op, Void indent) throws 
AlgebricksException {
         if (!op.getNestedPlans().isEmpty()) {
-            addIndent(indent).append("\"subplan\": ");
-            printNestedPlans(op, indent);
+            writeNestedPlans(op, indent);
         }
         return null;
     }
 
     @Override
-    public Void visitUnionOperator(UnionAllOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"union\"");
-        for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> v : 
op.getVariableMappings()) {
-            buffer.append(",\n");
-            addIndent(indent).append(
-                    "\"values\": [" + "\"" + v.first + "\"," + "\"" + v.second 
+ "\"," + "\"" + v.third + "\"]");
+    public Void visitUnionOperator(UnionAllOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "union");
+            jsonGenerator.writeArrayFieldStart("values");
+            for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> v : 
op.getVariableMappings()) {
+                jsonGenerator.writeStartArray();
+                jsonGenerator.writeString(String.valueOf(v.first));
+                jsonGenerator.writeString(String.valueOf(v.second));
+                jsonGenerator.writeString(String.valueOf(v.third));
+                jsonGenerator.writeEndArray();
+            }
+            jsonGenerator.writeEndArray();
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitIntersectOperator(IntersectOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"intersect\",\n");
-
-        addIndent(indent).append("\"output-compare-variables\": [");
-        appendVars(op.getOutputCompareVariables());
-        buffer.append(']');
-        if (op.hasExtraVariables()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"output-extra-variables\": [");
-            appendVars(op.getOutputExtraVariables());
-            buffer.append(']');
-        }
-        buffer.append(",\n");
-        addIndent(indent).append("\"input-compare-variables\": [");
-        for (int i = 0, n = op.getNumInput(); i < n; i++) {
-            if (i > 0) {
-                buffer.append(", ");
-            }
-            buffer.append('[');
-            appendVars(op.getInputCompareVariables(i));
-            buffer.append(']');
-        }
-        buffer.append(']');
-        if (op.hasExtraVariables()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"input-extra-variables\": [");
-            for (int i = 0, n = op.getNumInput(); i < n; i++) {
-                if (i > 0) {
-                    buffer.append(", ");
-                }
-                buffer.append('[');
-                appendVars(op.getInputExtraVariables(i));
-                buffer.append(']');
+    public Void visitIntersectOperator(IntersectOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "intersect");
+            writeArrayFieldOfVariables("output-compare-variables", 
op.getOutputCompareVariables());
+            if (op.hasExtraVariables()) {
+                writeArrayFieldOfVariables("output-extra-variables", 
op.getOutputExtraVariables());
+            }
+            writeArrayFieldOfNestedVariablesList("input-compare-variables", 
op.getAllInputsCompareVariables());
+            if (op.hasExtraVariables()) {
+                writeArrayFieldOfNestedVariablesList("input-extra-variables", 
op.getAllInputsExtraVariables());
             }
-            buffer.append(']');
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitUnnestOperator(UnnestOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"unnest\"");
-        variablePrintHelper(op.getVariables(), indent);
-        if (op.getPositionalVariable() != null) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"position\": \"" + 
op.getPositionalVariable() + "\"");
-        }
-        buffer.append(",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + op.getExpressionRef().getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
+    public Void visitUnnestOperator(UnnestOperator op, Void indent) throws 
AlgebricksException {
+        writeUnnestNonMapOperator(op, "unnest", indent);
         return null;
     }
 
     @Override
-    public Void visitLeftOuterUnnestOperator(LeftOuterUnnestOperator op, 
Integer indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"outer-unnest\",\n");
-        addIndent(indent).append("\"variables\": [\"" + op.getVariable() + 
"\"]");
-        if (op.getPositionalVariable() != null) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"position\": " + 
op.getPositionalVariable());
-        }
-        buffer.append(",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + op.getExpressionRef().getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
+    public Void visitLeftOuterUnnestOperator(LeftOuterUnnestOperator op, Void 
indent) throws AlgebricksException {
+        writeUnnestNonMapOperator(op, "outer-unnest", indent);
         return null;
     }
 
     @Override
-    public Void visitUnnestMapOperator(UnnestMapOperator op, Integer indent) 
throws AlgebricksException {
-        AlgebricksAppendable plan = printAbstractUnnestMapOperator(op, indent, 
"unnest-map");
-        appendSelectConditionInformation(plan, op.getSelectCondition(), 
indent);
-        appendLimitInformation(plan, op.getOutputLimit(), indent);
-        return null;
+    public Void visitUnnestMapOperator(UnnestMapOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            writeUnnestMapOperator(op, indent, "unnest-map");
+            writeSelectLimitInformation(op.getSelectCondition(), 
op.getOutputLimit(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Integer indent)
-            throws AlgebricksException {
-        printAbstractUnnestMapOperator(op, indent, "left-outer-unnest-map");
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, 
Void indent) throws AlgebricksException {
+        writeUnnestMapOperator(op, indent, "left-outer-unnest-map");
         return null;
     }
 
-    private AlgebricksAppendable 
printAbstractUnnestMapOperator(AbstractUnnestMapOperator op, Integer indent,
-            String opSignature) throws AlgebricksException {
-        AlgebricksAppendable plan = addIndent(indent).append("\"operator\": 
\"" + opSignature + "\"");
-        variablePrintHelper(op.getVariables(), indent);
-        buffer.append(",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + op.getExpressionRef().getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
-        appendFilterInformation(plan, op.getMinFilterVars(), 
op.getMaxFilterVars(), indent);
-        return plan;
+    @Override
+    public Void visitDataScanOperator(DataSourceScanOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "data-scan");
+            List<LogicalVariable> projectVariables = op.getProjectVariables();
+            if (!projectVariables.isEmpty()) {
+                writeArrayFieldOfVariables("project-variables", 
projectVariables);
+            }
+            List<LogicalVariable> variables = op.getVariables();
+            if (!variables.isEmpty()) {
+                writeArrayFieldOfVariables(VARIABLES_FIELD, variables);
+            }
+            if (op.getDataSource() != null) {
+                jsonGenerator.writeStringField("data-source", 
String.valueOf(op.getDataSource()));
+            }
+            writeFilterInformation(op.getMinFilterVars(), 
op.getMaxFilterVars());
+            writeSelectLimitInformation(op.getSelectCondition(), 
op.getOutputLimit(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitDataScanOperator(DataSourceScanOperator op, Integer 
indent) throws AlgebricksException {
-        AlgebricksAppendable plan = addIndent(indent).append("\"operator\": 
\"data-scan\"");
-        if (!op.getProjectVariables().isEmpty()) {
-            addIndent(0).append(",\n");
-            addIndent(indent).append("\"project-variables\": [");
-            appendVars(op.getProjectVariables());
-            buffer.append("]");
-        }
-        variablePrintHelper(op.getVariables(), indent);
-        if (op.getDataSource() != null) {
-            addIndent(0).append(",\n");
-            addIndent(indent).append("\"data-source\": \"" + 
op.getDataSource() + "\"");
+    public Void visitLimitOperator(LimitOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "limit");
+            writeStringFieldExpression("value", op.getMaxObjects(), indent);
+            Mutable<ILogicalExpression> offsetRef = op.getOffset();
+            if (offsetRef != null && offsetRef.getValue() != null) {
+                writeStringFieldExpression("offset", offsetRef, indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        appendFilterInformation(plan, op.getMinFilterVars(), 
op.getMaxFilterVars(), indent);
-        appendSelectConditionInformation(plan, op.getSelectCondition(), 
indent);
-        appendLimitInformation(plan, op.getOutputLimit(), indent);
-        return null;
     }
 
-    private Void appendFilterInformation(AlgebricksAppendable plan, 
List<LogicalVariable> minFilterVars,
-            List<LogicalVariable> maxFilterVars, Integer indent) throws 
AlgebricksException {
-        if (minFilterVars != null || maxFilterVars != null) {
-            plan.append(",\n");
-            addIndent(indent);
-            plan.append("\"with-filter-on\": {");
-        }
-        if (minFilterVars != null) {
-            buffer.append("\n");
-            addIndent(indent).append("\"min\": [");
-            appendVars(minFilterVars);
-            buffer.append("]");
-        }
-        if (minFilterVars != null && maxFilterVars != null) {
-            buffer.append(",");
+    @Override
+    public Void visitExchangeOperator(ExchangeOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "exchange");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        if (maxFilterVars != null) {
-            buffer.append("\n");
-            addIndent(indent).append("\"max\": [");
-            appendVars(maxFilterVars);
-            buffer.append("]");
+    }
+
+    @Override
+    public Void visitScriptOperator(ScriptOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "script");
+            List<LogicalVariable> inputVariables = op.getInputVariables();
+            if (!inputVariables.isEmpty()) {
+                writeArrayFieldOfVariables("in", inputVariables);
+            }
+            List<LogicalVariable> outputVariables = op.getOutputVariables();
+            if (!outputVariables.isEmpty()) {
+                writeArrayFieldOfVariables("out", outputVariables);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        if (minFilterVars != null || maxFilterVars != null) {
-            plan.append("\n");
-            addIndent(indent).append("}");
+    }
+
+    @Override
+    public Void visitReplicateOperator(ReplicateOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "replicate");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
-    private Void appendSelectConditionInformation(AlgebricksAppendable plan, 
Mutable<ILogicalExpression> condition,
-            Integer indent) throws AlgebricksException {
-        if (condition != null) {
-            plan.append(",\n");
-            addIndent(indent).append(
-                    "\"condition\": \"" + 
condition.getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+    @Override
+    public Void visitSplitOperator(SplitOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "split");
+            writeStringFieldExpression(EXPRESSION_FIELD, 
op.getBranchingExpression(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
-    private Void appendLimitInformation(AlgebricksAppendable plan, long 
outputLimit, Integer indent)
-            throws AlgebricksException {
-        if (outputLimit >= 0) {
-            plan.append(",\n");
-            addIndent(indent).append("\"limit\": \"" + outputLimit + "\"");
+    @Override
+    public Void visitMaterializeOperator(MaterializeOperator op, Void indent) 
throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "materialize");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
-    private void appendVars(List<LogicalVariable> minFilterVars) throws 
AlgebricksException {
-        boolean first = true;
-        for (LogicalVariable v : minFilterVars) {
-            if (!first) {
-                buffer.append(",");
+    @Override
+    public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, 
Void indent) throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
getIndexOpString(op.getOperation()));
+            jsonGenerator.writeStringField("data-source", 
String.valueOf(op.getDataSource()));
+            writeStringFieldExpression("from-record", 
op.getPayloadExpression(), indent);
+            if (op.getAdditionalNonFilteringExpressions() != null) {
+                writeObjectFieldWithExpressions("meta", 
op.getAdditionalNonFilteringExpressions(), indent);
+            }
+            writeObjectFieldWithExpressions("partitioned-by", 
op.getPrimaryKeyExpressions(), indent);
+            if (op.getOperation() == Kind.UPSERT) {
+                jsonGenerator.writeObjectFieldStart("out");
+                jsonGenerator.writeStringField("record-before-upsert", 
String.valueOf(op.getBeforeOpRecordVar()));
+                if (op.getBeforeOpAdditionalNonFilteringVars() != null) {
+                    jsonGenerator.writeStringField("additional-before-upsert",
+                            
String.valueOf(op.getBeforeOpAdditionalNonFilteringVars()));
+                }
+                jsonGenerator.writeEndObject();
+            }
+            if (op.isBulkload()) {
+                jsonGenerator.writeBooleanField("bulkload", true);
             }
-            buffer.append("\"" + str(v) + "\"");
-            first = false;
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
     }
 
     @Override
-    public Void visitLimitOperator(LimitOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"limit\",\n");
-        addIndent(indent).append("\"value\": \"" + 
op.getMaxObjects().getValue().accept(exprVisitor, indent) + "\"");
-        ILogicalExpression offset = op.getOffset().getValue();
-        if (offset != null) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"offset\": \"" + 
offset.accept(exprVisitor, indent) + "\"");
+    public Void 
visitIndexInsertDeleteUpsertOperator(IndexInsertDeleteUpsertOperator op, Void 
indent)
+            throws AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, 
getIndexOpString(op.getOperation()));
+            jsonGenerator.writeStringField("index", op.getIndexName());
+            jsonGenerator.writeStringField("on", 
String.valueOf(op.getDataSourceIndex().getDataSource()));
+            jsonGenerator.writeObjectFieldStart("from");
+            if (op.getOperation() == Kind.UPSERT) {
+                writeArrayFieldOfExpressions("replace", 
op.getPrevSecondaryKeyExprs(), indent);
+                writeArrayFieldOfExpressions("with", 
op.getSecondaryKeyExpressions(), indent);
+            } else {
+                writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, 
op.getSecondaryKeyExpressions(), indent);
+            }
+            jsonGenerator.writeEndObject();
+            if (op.isBulkload()) {
+                jsonGenerator.writeBooleanField("bulkload", true);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitExchangeOperator(ExchangeOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"exchange\"");
-        return null;
+    public Void visitTokenizeOperator(TokenizeOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "tokenize");
+            writeVariablesAndExpressions(op.getTokenizeVars(), 
op.getSecondaryKeyExpressions(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitScriptOperator(ScriptOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"script\"");
-        if (!op.getInputVariables().isEmpty()) {
-            addIndent(0).append(",\n");
-            addIndent(indent).append("\"in\": [");
-            appendVars(op.getInputVariables());
-            buffer.append("]");
-        }
-        if (!op.getOutputVariables().isEmpty()) {
-            addIndent(0).append(",\n");
-            addIndent(indent).append("\"out\": [");
-            appendVars(op.getOutputVariables());
-            buffer.append("]");
+    public Void visitForwardOperator(ForwardOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "forward");
+            writeStringFieldExpression(EXPRESSION_FIELD, 
op.getSideDataExpression(), indent);
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
     @Override
-    public Void visitReplicateOperator(ReplicateOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"replicate\"");
-        return null;
+    public Void visitSinkOperator(SinkOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "sink");
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitSplitOperator(SplitOperator op, Integer indent) throws 
AlgebricksException {
-        Mutable<ILogicalExpression> branchingExpression = 
op.getBranchingExpression();
-        addIndent(indent).append("\"operator\": \"split\",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + branchingExpression.getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
-        return null;
+    public Void visitDelegateOperator(DelegateOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, op.toString());
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
     @Override
-    public Void visitMaterializeOperator(MaterializeOperator op, Integer 
indent) throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"materialize\"");
-        return null;
+    public Void visitWindowOperator(WindowOperator op, Void indent) throws 
AlgebricksException {
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, "window-aggregate");
+            writeVariablesAndExpressions(op.getVariables(), 
op.getExpressions(), indent);
+            List<Mutable<ILogicalExpression>> partitionExpressions = 
op.getPartitionExpressions();
+            if (!partitionExpressions.isEmpty()) {
+                writeObjectFieldWithExpressions("partition-by", 
partitionExpressions, indent);
+            }
+            List<Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>>> 
orderExpressions = op.getOrderExpressions();
+            if (!orderExpressions.isEmpty()) {
+                writeArrayFieldOfOrderExprList("order-by", orderExpressions, 
indent);
+            }
+            if (op.hasNestedPlans()) {
+                List<Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>>> 
frameValueExpressions =
+                        op.getFrameValueExpressions();
+                if (!frameValueExpressions.isEmpty()) {
+                    writeArrayFieldOfOrderExprList("frame-on", 
frameValueExpressions, indent);
+                }
+                List<Mutable<ILogicalExpression>> frameStartExpressions = 
op.getFrameStartExpressions();
+                if (!frameStartExpressions.isEmpty()) {
+                    writeObjectFieldWithExpressions("frame-start", 
frameStartExpressions, indent);
+                }
+                List<Mutable<ILogicalExpression>> 
frameStartValidationExpressions =
+                        op.getFrameStartValidationExpressions();
+                if (!frameStartValidationExpressions.isEmpty()) {
+                    writeObjectFieldWithExpressions("frame-start-if", 
frameStartValidationExpressions, indent);
+                }
+                List<Mutable<ILogicalExpression>> frameEndExpressions = 
op.getFrameEndExpressions();
+                if (!frameEndExpressions.isEmpty()) {
+                    writeObjectFieldWithExpressions("frame-end", 
frameEndExpressions, indent);
+                }
+                List<Mutable<ILogicalExpression>> 
frameEndValidationExpressions = op.getFrameEndValidationExpressions();
+                if (!frameEndValidationExpressions.isEmpty()) {
+                    writeObjectFieldWithExpressions("frame-end-if", 
frameEndValidationExpressions, indent);
+                }
+                List<Mutable<ILogicalExpression>> frameExcludeExpressions = 
op.getFrameExcludeExpressions();
+                if (!frameExcludeExpressions.isEmpty()) {
+                    writeObjectFieldWithExpressions("frame-exclude", 
frameExcludeExpressions, indent);
+                    
jsonGenerator.writeStringField("frame-exclude-negation-start",
+                            
String.valueOf(op.getFrameExcludeNegationStartIdx()));
+                }
+                Mutable<ILogicalExpression> frameExcludeUnaryExpression = 
op.getFrameExcludeUnaryExpression();
+                if (frameExcludeUnaryExpression.getValue() != null) {
+                    writeStringFieldExpression("frame-exclude-unary", 
frameExcludeUnaryExpression, indent);
+                }
+                Mutable<ILogicalExpression> frameOffsetExpression = 
op.getFrameOffsetExpression();
+                if (frameOffsetExpression.getValue() != null) {
+                    writeStringFieldExpression("frame-offset", 
frameOffsetExpression, indent);
+                }
+                int frameMaxObjects = op.getFrameMaxObjects();
+                if (frameMaxObjects != -1) {
+                    jsonGenerator.writeStringField("frame-max-objects", 
String.valueOf(frameMaxObjects));
+                }
+                writeNestedPlans(op, indent);
+            }
+            return null;
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
     }
 
-    @Override
-    public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, 
Integer indent)
+    private void writeNestedPlans(AbstractOperatorWithNestedPlans op, Void 
indent) throws AlgebricksException {
+        try {
+            idCounter.nextPrefix();
+            jsonGenerator.writeArrayFieldStart("subplan");
+            List<ILogicalPlan> nestedPlans = op.getNestedPlans();
+            for (int i = 0, size = nestedPlans.size(); i < size; i++) {
+                printPlanImpl(nestedPlans.get(i));
+            }
+            jsonGenerator.writeEndArray();
+            idCounter.previousPrefix();
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
+    }
+
+    private void writeUnnestNonMapOperator(AbstractUnnestNonMapOperator op, 
String opName, Void indent)
             throws AlgebricksException {
-        String header = "\"operator\": \"" + 
getIndexOpString(op.getOperation()) + "\",\n";
-        addIndent(indent).append(header);
-        addIndent(indent).append(str("\"data-source\": \"" + 
op.getDataSource() + "\",\n"));
-        addIndent(indent).append("\"from-record\": \"")
-                
.append(op.getPayloadExpression().getValue().accept(exprVisitor, indent) + 
"\"");
-        if (op.getAdditionalNonFilteringExpressions() != null) {
-            buffer.append(",\n\"meta\": {");
-            pprintExprList(op.getAdditionalNonFilteringExpressions(), 0);
-            buffer.append("}");
-        }
-        buffer.append(",\n");
-        addIndent(indent).append("\"partitioned-by\": {");
-        pprintExprList(op.getPrimaryKeyExpressions(), 0);
-        buffer.append("}");
-        if (op.getOperation() == Kind.UPSERT) {
-            addIndent(indent).append(",\n\"out\": {\n");
-            addIndent(indent).append("\"record-before-upsert\": \"" + 
op.getBeforeOpRecordVar() + "\"");
-            if (op.getBeforeOpAdditionalNonFilteringVars() != null) {
-                buffer.append(",\n");
-                addIndent(indent)
-                        .append("\"additional-before-upsert\": \"" + 
op.getBeforeOpAdditionalNonFilteringVars() + "\"");
-            }
-            addIndent(indent).append("}");
-        }
-        if (op.isBulkload()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"bulkload\": true");
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, opName);
+            List<LogicalVariable> variables = op.getVariables();
+            if (!variables.isEmpty()) {
+                writeArrayFieldOfVariables(VARIABLES_FIELD, variables);
+            }
+            LogicalVariable positionalVariable = op.getPositionalVariable();
+            if (positionalVariable != null) {
+                jsonGenerator.writeStringField("position", 
String.valueOf(positionalVariable));
+            }
+            writeArrayFieldOfExpression(EXPRESSIONS_FIELD, 
op.getExpressionRef(), indent);
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
-        return null;
     }
 
-    @Override
-    public Void 
visitIndexInsertDeleteUpsertOperator(IndexInsertDeleteUpsertOperator op, 
Integer indent)
+    private void writeUnnestMapOperator(AbstractUnnestMapOperator op, Void 
indent, String opName)
             throws AlgebricksException {
-        String header = getIndexOpString(op.getOperation());
-        addIndent(indent).append("\"operator\": \"" + header + "\",\n");
-        addIndent(indent).append("\"index\": \"" + op.getIndexName() + 
"\",\n");
-        addIndent(indent).append("\"on\": 
\"").append(str(op.getDataSourceIndex().getDataSource()) + "\",\n");
-        addIndent(indent).append("\"from\": {");
-
-        if (op.getOperation() == Kind.UPSERT) {
-
-            addIndent(indent).append("[\"replace\": \"");
-            pprintExprList(op.getPrevSecondaryKeyExprs(), 0);
-            buffer.append("\",\n");
-            addIndent(indent).append("\"with\": \"");
-            pprintExprList(op.getSecondaryKeyExpressions(), 0);
-            buffer.append("\"}");
-        } else {
-            pprintExprList(op.getSecondaryKeyExpressions(), 0);
-        }
-        buffer.append("\n");
-        addIndent(indent).append("}");
-        if (op.isBulkload()) {
-            buffer.append(",\n");
-            buffer.append("\"bulkload\": true");
+        try {
+            jsonGenerator.writeStringField(OPERATOR_FIELD, opName);
+            List<LogicalVariable> variables = op.getVariables();
+            if (!variables.isEmpty()) {
+                writeArrayFieldOfVariables(VARIABLES_FIELD, variables);
+            }
+            writeArrayFieldOfExpression(EXPRESSIONS_FIELD, 
op.getExpressionRef(), indent);
+            writeFilterInformation(op.getMinFilterVars(), 
op.getMaxFilterVars());
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
+    }
+
+    private void writeFilterInformation(List<LogicalVariable> minFilterVars, 
List<LogicalVariable> maxFilterVars)
+            throws AlgebricksException {
+        try {
+            if (minFilterVars != null || maxFilterVars != null) {
+                jsonGenerator.writeObjectFieldStart("with-filter-on");
+                if (minFilterVars != null) {
+                    writeArrayFieldOfVariables("min", minFilterVars);
+                }
+                if (maxFilterVars != null) {
+                    writeArrayFieldOfVariables("max", maxFilterVars);
+                }
+                jsonGenerator.writeEndObject();
+            }
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
+        }
+    }
+
+    private void writeSelectLimitInformation(Mutable<ILogicalExpression> 
selectCondition, long outputLimit, Void i)
+            throws AlgebricksException, IOException {
+        if (selectCondition != null) {
+            writeStringFieldExpression(CONDITION_FIELD, selectCondition, i);
+        }
+        if (outputLimit >= 0) {
+            jsonGenerator.writeStringField("limit", 
String.valueOf(outputLimit));
         }
-        return null;
     }
 
-    public String getIndexOpString(Kind opKind) {
+    private void writeVariablesAndExpressions(List<LogicalVariable> variables,
+            List<Mutable<ILogicalExpression>> expressions, Void indent) throws 
IOException, AlgebricksException {
+        if (!variables.isEmpty()) {
+            writeArrayFieldOfVariables(VARIABLES_FIELD, variables);
+        }
+        if (!expressions.isEmpty()) {
+            writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, expressions, 
indent);
+        }
+    }
+
+    private String getIndexOpString(Kind opKind) {
         switch (opKind) {
             case DELETE:
                 return "delete-from";
@@ -653,220 +817,115 @@ public class LogicalOperatorPrettyPrintVisitorJson 
extends AbstractLogicalOperat
                 return "insert-into";
             case UPSERT:
                 return "upsert-into";
-        }
-        return null;
-    }
+            default:
+                throw new IllegalStateException();
 
-    @Override
-    public Void visitTokenizeOperator(TokenizeOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"tokenize\"");
-        variablePrintHelper(op.getTokenizeVars(), indent);
-        if (!op.getSecondaryKeyExpressions().isEmpty()) {
-            addIndent(0).append(",\n");
-            pprintExprList(op.getSecondaryKeyExpressions(), indent);
         }
-        return null;
     }
 
-    @Override
-    public Void visitForwardOperator(ForwardOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"forward\"");
-        addIndent(0).append(",\n");
-        addIndent(indent).append("\"expressions\": \""
-                + op.getSideDataExpression().getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"");
-        return null;
+    private String getOrderString(OrderOperator.IOrder order, Void indent) 
throws AlgebricksException {
+        switch (order.getKind()) {
+            case ASC:
+                return "ASC";
+            case DESC:
+                return "DESC";
+            default:
+                return order.getExpressionRef().getValue().accept(exprVisitor, 
indent);
+        }
     }
 
-    @Override
-    public Void visitSinkOperator(SinkOperator op, Integer indent) throws 
AlgebricksException {
-        addIndent(indent).append("\"operator\": \"sink\"");
-        return null;
+    /////////////// string fields ///////////////
+    /** Writes "fieldName": "expr" */
+    private void writeStringFieldExpression(String fieldName, 
Mutable<ILogicalExpression> expression, Void indent)
+            throws AlgebricksException, IOException {
+        jsonGenerator.writeStringField(fieldName, 
expression.getValue().accept(exprVisitor, indent));
     }
 
-    @Override
-    public Void visitDelegateOperator(DelegateOperator op, Integer indent) 
throws AlgebricksException {
-        addIndent(indent).append("\"operator\": \"" + op.toString() + "\"");
-        return null;
-    }
-
-    @Override
-    public Void visitWindowOperator(WindowOperator op, Integer indent) throws 
AlgebricksException {
-        Integer fldIndent = indent + 2;
-        addIndent(indent).append("\"operator\": \"window-aggregate\"");
-        variablePrintHelper(op.getVariables(), indent);
-        List<Mutable<ILogicalExpression>> expressions = op.getExpressions();
-        if (!expressions.isEmpty()) {
-            buffer.append(",\n");
-            pprintExprList(expressions, indent);
-        }
-        List<Mutable<ILogicalExpression>> partitionExpressions = 
op.getPartitionExpressions();
-        if (!partitionExpressions.isEmpty()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"partition-by\": {\n");
-            pprintExprList(partitionExpressions, fldIndent);
-            buffer.append("\n");
-            addIndent(indent).append("}");
-        }
-        List<Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>>> 
orderExpressions = op.getOrderExpressions();
-        if (!orderExpressions.isEmpty()) {
-            buffer.append(",\n");
-            addIndent(indent).append("\"order-by\": ");
-            pprintOrderExprList(orderExpressions, fldIndent);
-        }
-        if (op.hasNestedPlans()) {
-            List<Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>>> 
frameValueExpressions =
-                    op.getFrameValueExpressions();
-            if (!frameValueExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-on\": ");
-                pprintOrderExprList(frameValueExpressions, fldIndent);
-            }
-            List<Mutable<ILogicalExpression>> frameStartExpressions = 
op.getFrameStartExpressions();
-            if (!frameStartExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-start\": {\n");
-                pprintExprList(frameStartExpressions, fldIndent);
-                buffer.append("\n");
-                addIndent(indent).append("}");
-            }
-            List<Mutable<ILogicalExpression>> frameStartValidationExpressions 
= op.getFrameStartValidationExpressions();
-            if (!frameStartValidationExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-start-if\": {\n");
-                pprintExprList(frameStartValidationExpressions, fldIndent);
-                buffer.append("\n");
-                addIndent(indent).append("}");
-            }
-            List<Mutable<ILogicalExpression>> frameEndExpressions = 
op.getFrameEndExpressions();
-            if (!frameEndExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-end\": {\n");
-                pprintExprList(frameEndExpressions, fldIndent);
-                buffer.append("\n");
-                addIndent(indent).append("}");
-            }
-            List<Mutable<ILogicalExpression>> frameEndValidationExpressions = 
op.getFrameEndValidationExpressions();
-            if (!frameEndValidationExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-end-if\": {\n");
-                pprintExprList(frameEndValidationExpressions, fldIndent);
-                buffer.append("\n");
-                addIndent(indent).append("}");
-            }
-            List<Mutable<ILogicalExpression>> frameExcludeExpressions = 
op.getFrameExcludeExpressions();
-            if (!frameExcludeExpressions.isEmpty()) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-exclude\": {\n");
-                pprintExprList(frameExcludeExpressions, fldIndent);
-                buffer.append("\n");
-                addIndent(indent).append("},\n");
-                addIndent(indent).append("\"frame-exclude-negation-start\": ")
-                        
.append(String.valueOf(op.getFrameExcludeNegationStartIdx()));
-            }
-            Mutable<ILogicalExpression> frameExcludeUnaryExpression = 
op.getFrameExcludeUnaryExpression();
-            if (frameExcludeUnaryExpression.getValue() != null) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-exclude-unary\": ");
-                pprintExpr(frameExcludeUnaryExpression, fldIndent);
-            }
-            Mutable<ILogicalExpression> frameOffsetExpression = 
op.getFrameOffsetExpression();
-            if (frameOffsetExpression.getValue() != null) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-offset\": ");
-                pprintExpr(frameOffsetExpression, fldIndent);
-            }
-            int frameMaxObjects = op.getFrameMaxObjects();
-            if (frameMaxObjects != -1) {
-                buffer.append(",\n");
-                addIndent(indent).append("\"frame-max-objects\": 
").append(String.valueOf(frameMaxObjects));
-            }
-            buffer.append(",\n");
-            addIndent(indent).append("\"subplan\": ");
-            printNestedPlans(op, fldIndent);
+    /////////////// array fields ///////////////
+    /** Writes "fieldName": [ "var1", "var2", ... ] */
+    private void writeArrayFieldOfVariables(String fieldName, 
List<LogicalVariable> variables) throws IOException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        for (int i = 0, size = variables.size(); i < size; i++) {
+            jsonGenerator.writeString(String.valueOf(variables.get(i)));
         }
-        return null;
+        jsonGenerator.writeEndArray();
     }
 
-    protected void printNestedPlans(AbstractOperatorWithNestedPlans op, 
Integer indent) throws AlgebricksException {
-        idCounter.nextPrefix();
-        buffer.append("[\n");
-        boolean first = true;
-        for (ILogicalPlan p : op.getNestedPlans()) {
-            if (!first) {
-                buffer.append(",");
+    /** Writes "fieldName": [ ["var1", "var2", ...], ["var1", "var2", ...] ] */
+    private void writeArrayFieldOfNestedVariablesList(String fieldName, 
List<List<LogicalVariable>> nestedVarList)
+            throws IOException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        for (int i = 0, size = nestedVarList.size(); i < size; i++) {
+            List<LogicalVariable> nextList = nestedVarList.get(i);
+            for (int k = 0, varSize = nextList.size(); k < varSize; k++) {
+                jsonGenerator.writeString(String.valueOf(nextList.get(k)));
             }
-            printPlan(p, indent + 4);
-            first = false;
         }
-        addIndent(indent).append("]");
-        idCounter.previousPrefix();
+        jsonGenerator.writeEndArray();
     }
 
-    protected void pprintExprList(List<Mutable<ILogicalExpression>> 
expressions, Integer indent)
-            throws AlgebricksException {
-        addIndent(indent);
-        buffer.append("\"expressions\": \"");
-        boolean first = true;
-        for (Mutable<ILogicalExpression> exprRef : expressions) {
-            if (first) {
-                first = false;
-            } else {
-                buffer.append(", ");
-            }
-            pprintExpr(exprRef, indent);
-        }
-        buffer.append("\"");
+    /** Writes "fieldName" : [ "expr" ] */
+    private void writeArrayFieldOfExpression(String fieldName, 
Mutable<ILogicalExpression> expr, Void indent)
+            throws IOException, AlgebricksException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        jsonGenerator.writeString(expr.getValue().accept(exprVisitor, indent));
+        jsonGenerator.writeEndArray();
     }
 
-    protected void pprintExpr(Mutable<ILogicalExpression> exprRef, Integer 
indent) throws AlgebricksException {
-        buffer.append(exprRef.getValue().accept(exprVisitor, 
indent).replace('"', ' '));
+    /** Writes "fieldName" : [ "expr1", "expr2", ...] */
+    private void writeArrayFieldOfExpressions(String fieldName, 
List<Mutable<ILogicalExpression>> exprs, Void indent)
+            throws IOException, AlgebricksException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        for (int i = 0, size = exprs.size(); i < size; i++) {
+            
jsonGenerator.writeString(exprs.get(i).getValue().accept(exprVisitor, indent));
+        }
+        jsonGenerator.writeEndArray();
     }
 
-    protected void pprintVeList(List<Pair<LogicalVariable, 
Mutable<ILogicalExpression>>> vePairList, Integer indent)
-            throws AlgebricksException {
-        buffer.append("[");
-        boolean first = true;
-        for (Pair<LogicalVariable, Mutable<ILogicalExpression>> ve : 
vePairList) {
-            if (first) {
-                first = false;
-            } else {
-                buffer.append(",");
-            }
+    /** Writes "fieldName" : [ { "variable": "var1", "expression": "expr1" }, 
... ] */
+    private void writeArrayFieldOfVariableExpressionPairs(String fieldName,
+            List<Pair<LogicalVariable, Mutable<ILogicalExpression>>> 
varExprPairs, Void indent)
+            throws AlgebricksException, IOException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        for (Pair<LogicalVariable, Mutable<ILogicalExpression>> ve : 
varExprPairs) {
+            jsonGenerator.writeStartObject();
             if (ve.first != null) {
-                buffer.append("{\"variable\": \"" + 
ve.first.toString().replace('"', ' ') + "\"," + "\"expression\": \""
-                        + ve.second.toString().replace('"', ' ') + "\"}");
-            } else {
-                buffer.append("{\"expression\": \"" + 
ve.second.getValue().accept(exprVisitor, indent).replace('"', ' ')
-                        + "\"}");
+                jsonGenerator.writeStringField("variable", 
ve.first.toString());
             }
+            writeStringFieldExpression(EXPRESSION_FIELD, ve.second, indent);
+            jsonGenerator.writeEndObject();
         }
-        buffer.append("]");
+        jsonGenerator.writeEndArray();
     }
 
-    private void pprintOrderExprList(List<Pair<OrderOperator.IOrder, 
Mutable<ILogicalExpression>>> orderExpressions,
-            Integer indent) throws AlgebricksException {
-        buffer.append("[");
-        boolean first = true;
-        for (Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>> p : 
orderExpressions) {
-            if (first) {
-                first = false;
-            } else {
-                buffer.append(",");
-            }
-            buffer.append("{\"order\": \"" + getOrderString(p.first, indent) + 
"\"," + "\"expression\": \""
-                    + p.second.getValue().accept(exprVisitor, 
indent).replace('"', ' ') + "\"}");
+    /** Writes "fieldName" : [ { "order": "", "expression": "" }, ... ] */
+    private void writeArrayFieldOfOrderExprList(String fieldName,
+            List<Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>>> 
orderList, Void indent)
+            throws AlgebricksException, IOException {
+        jsonGenerator.writeArrayFieldStart(fieldName);
+        for (Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>> p : 
orderList) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.writeStringField("order", getOrderString(p.first, 
indent));
+            writeStringFieldExpression(EXPRESSION_FIELD, p.second, indent);
+            jsonGenerator.writeEndObject();
         }
-        buffer.append("]");
+        jsonGenerator.writeEndArray();
     }
 
-    private String getOrderString(OrderOperator.IOrder order, Integer indent) 
throws AlgebricksException {
-        switch (order.getKind()) {
-            case ASC:
-                return "ASC";
-            case DESC:
-                return "DESC";
-            default:
-                return order.getExpressionRef().getValue().accept(exprVisitor, 
indent).replace('"', ' ');
+    /////////////// object fields ///////////////
+    /** Writes "fieldName" : { "expressions": [ "expr1", "expr2", ...] } */
+    private void writeObjectFieldWithExpressions(String fieldName, 
List<Mutable<ILogicalExpression>> exprs, Void indent)
+            throws IOException, AlgebricksException {
+        jsonGenerator.writeObjectFieldStart(fieldName);
+        writeArrayFieldOfExpressions(EXPRESSIONS_FIELD, exprs, indent);
+        jsonGenerator.writeEndObject();
+    }
+
+    private void flushContentToWriter() throws AlgebricksException {
+        try {
+            jsonGenerator.flush();
+        } catch (IOException e) {
+            throw new AlgebricksException(e, ErrorCode.ERROR_PRINTING_PLAN);
         }
     }
 }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
index 67640c5..9b3a6d8 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
@@ -20,20 +20,20 @@ package 
org.apache.hyracks.algebricks.core.algebra.prettyprint;
 
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
-import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 
 public class PlanPrettyPrinter {
-    public static void printOperator(AbstractLogicalOperator op, 
AbstractLogicalOperatorPrettyPrintVisitor pvisitor,
-            int indent) throws AlgebricksException {
-        pvisitor.printOperator(op, indent);
+
+    public static final int INIT_SIZE = 256;
+
+    public static IPlanPrettyPrinter createJsonPlanPrettyPrinter() {
+        return new LogicalOperatorPrettyPrintVisitorJson();
     }
 
-    public static void printPlan(ILogicalPlan plan, 
AbstractLogicalOperatorPrettyPrintVisitor pvisitor, int indent)
-            throws AlgebricksException {
-        pvisitor.printPlan(plan, indent);
+    public static IPlanPrettyPrinter createStringPlanPrettyPrinter() {
+        return new LogicalOperatorPrettyPrintVisitor();
     }
 
-    public static void printPhysicalOps(ILogicalPlan plan, 
AlgebricksAppendable out, int indent)
+    public static void printPhysicalOps(ILogicalPlan plan, 
AlgebricksStringBuilderWriter out, int indent)
             throws AlgebricksException {
         AbstractLogicalOperatorPrettyPrintVisitor.printPhysicalOps(plan, out, 
indent);
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AbstractRuleController.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AbstractRuleController.java
index 6ed4c9e..96e15da 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AbstractRuleController.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AbstractRuleController.java
@@ -27,9 +27,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.config.AlgebricksConfig;
 import org.apache.hyracks.util.LogRedactionUtil;
 
@@ -67,10 +65,8 @@ public abstract class AbstractRuleController {
 
     private String getPlanString(Mutable<ILogicalOperator> opRef) throws 
AlgebricksException {
         if (AlgebricksConfig.ALGEBRICKS_LOGGER.isTraceEnabled() && context != 
null) {
-            AbstractLogicalOperatorPrettyPrintVisitor pvisitor = 
context.getPrettyPrintVisitor();
-            pvisitor.reset(new AlgebricksAppendable());
-            PlanPrettyPrinter.printOperator((AbstractLogicalOperator) 
opRef.getValue(), pvisitor, 0);
-            return pvisitor.get().toString();
+            IPlanPrettyPrinter prettyPrinter = context.getPrettyPrinter();
+            return 
prettyPrinter.reset().printOperator((AbstractLogicalOperator) 
opRef.getValue()).toString();
         }
         return null;
     }
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AlgebricksOptimizationContext.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AlgebricksOptimizationContext.java
index eb398e6..0fa0023 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AlgebricksOptimizationContext.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/AlgebricksOptimizationContext.java
@@ -38,7 +38,7 @@ import 
org.apache.hyracks.algebricks.core.algebra.expressions.IMissableTypeCompu
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IVariableEvalSizeEnvironment;
 import 
org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.metadata.IMetadataProvider;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
+import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.IPlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.DefaultNodeGroupDomain;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.FunctionalDependency;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.ILogicalPropertiesVector;
@@ -86,7 +86,7 @@ public class AlgebricksOptimizationContext implements 
IOptimizationContext {
     private final IExpressionTypeComputer expressionTypeComputer;
     private final IMissableTypeComputer nullableTypeComputer;
     private final INodeDomain defaultNodeDomain;
-    private final AbstractLogicalOperatorPrettyPrintVisitor prettyPrintVisitor;
+    private final IPlanPrettyPrinter prettyPrinter;
     private final IConflictingTypeResolver conflictingTypeResovler;
     private final IWarningCollector warningCollector;
 
@@ -94,8 +94,8 @@ public class AlgebricksOptimizationContext implements 
IOptimizationContext {
             IMergeAggregationExpressionFactory 
mergeAggregationExpressionFactory,
             IExpressionTypeComputer expressionTypeComputer, 
IMissableTypeComputer nullableTypeComputer,
             IConflictingTypeResolver conflictingTypeResovler, 
PhysicalOptimizationConfig physicalOptimizationConfig,
-            AlgebricksPartitionConstraint clusterLocations,
-            AbstractLogicalOperatorPrettyPrintVisitor prettyPrintVisitor, 
IWarningCollector warningCollector) {
+            AlgebricksPartitionConstraint clusterLocations, IPlanPrettyPrinter 
prettyPrinter,
+            IWarningCollector warningCollector) {
         this.varCounter = varCounter;
         this.expressionEvalSizeComputer = expressionEvalSizeComputer;
         this.mergeAggregationExpressionFactory = 
mergeAggregationExpressionFactory;
@@ -103,7 +103,7 @@ public class AlgebricksOptimizationContext implements 
IOptimizationContext {
         this.nullableTypeComputer = nullableTypeComputer;
         this.physicalOptimizationConfig = physicalOptimizationConfig;
         this.defaultNodeDomain = new DefaultNodeGroupDomain(clusterLocations);
-        this.prettyPrintVisitor = prettyPrintVisitor;
+        this.prettyPrinter = prettyPrinter;
         this.conflictingTypeResovler = conflictingTypeResovler;
         this.warningCollector = warningCollector;
     }
@@ -324,8 +324,8 @@ public class AlgebricksOptimizationContext implements 
IOptimizationContext {
     }
 
     @Override
-    public AbstractLogicalOperatorPrettyPrintVisitor getPrettyPrintVisitor() {
-        return prettyPrintVisitor;
+    public IPlanPrettyPrinter getPrettyPrinter() {
+        return prettyPrinter;
     }
 
     @Override
diff --git 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
index 426e9fb..25d53b0 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/rewriter/base/HeuristicOptimizer.java
@@ -29,9 +29,6 @@ import 
org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
 import org.apache.hyracks.algebricks.core.algebra.base.PhysicalOperatorTag;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.config.AlgebricksConfig;
 import org.apache.hyracks.util.LogRedactionUtil;
 import org.apache.logging.log4j.Level;
@@ -69,11 +66,8 @@ public class HeuristicOptimizer {
 
     private void logPlanAt(String name, Level lvl) throws AlgebricksException {
         if (AlgebricksConfig.ALGEBRICKS_LOGGER.isEnabled(lvl)) {
-            final AbstractLogicalOperatorPrettyPrintVisitor pvisitor = 
context.getPrettyPrintVisitor();
-            pvisitor.reset(new AlgebricksAppendable());
-            PlanPrettyPrinter.printPlan(plan, pvisitor, 0);
-            AlgebricksConfig.ALGEBRICKS_LOGGER.log(lvl,
-                    name + ":\n" + 
LogRedactionUtil.userData(pvisitor.get().toString()));
+            String planStr = 
context.getPrettyPrinter().reset().printPlan(plan).toString();
+            AlgebricksConfig.ALGEBRICKS_LOGGER.log(lvl, name + ":\n" + 
LogRedactionUtil.userData(planStr));
         }
     }
 
diff --git 
a/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/EnforceStructuralPropertiesRule.java
 
b/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/EnforceStructuralPropertiesRule.java
index e174ab8..9236545 100644
--- 
a/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/EnforceStructuralPropertiesRule.java
+++ 
b/hyracks-fullstack/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/EnforceStructuralPropertiesRule.java
@@ -76,8 +76,6 @@ import 
org.apache.hyracks.algebricks.core.algebra.operators.physical.SequentialM
 import 
org.apache.hyracks.algebricks.core.algebra.operators.physical.SortForwardPOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.physical.SortMergeExchangePOperator;
 import 
org.apache.hyracks.algebricks.core.algebra.operators.physical.StableSortPOperator;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
-import 
org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.FunctionalDependency;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.ILocalStructuralProperty;
 import 
org.apache.hyracks.algebricks.core.algebra.properties.ILocalStructuralProperty.PropertyType;
@@ -283,7 +281,7 @@ public class EnforceStructuralPropertiesRule implements 
IAlgebraicRewriteRule {
             if (loggerTraceEnabled) {
                 AlgebricksConfig.ALGEBRICKS_LOGGER
                         .trace(">>>> Removing redundant SORT operator " + 
op.getPhysicalOperator() + "\n");
-                printOp(op);
+                printOp(op, context);
             }
             changed = true;
             AbstractLogicalOperator nextOp = (AbstractLogicalOperator) 
op.getInputs().get(0).getValue();
@@ -528,7 +526,7 @@ public class EnforceStructuralPropertiesRule implements 
IAlgebraicRewriteRule {
         op.getInputs().set(i, topOp);
         
OperatorPropertiesUtil.computeSchemaAndPropertiesRecIfNull((AbstractLogicalOperator)
 topOp.getValue(), context);
         OperatorManipulationUtil.setOperatorMode(op);
-        printOp((AbstractLogicalOperator) topOp.getValue());
+        printOp((AbstractLogicalOperator) topOp.getValue(), context);
     }
 
     private Mutable<ILogicalOperator> 
enforceOrderProperties(List<LocalOrderProperty> oList,
@@ -616,7 +614,7 @@ public class EnforceStructuralPropertiesRule implements 
IAlgebraicRewriteRule {
             if (AlgebricksConfig.ALGEBRICKS_LOGGER.isTraceEnabled()) {
                 AlgebricksConfig.ALGEBRICKS_LOGGER
                         .trace(">>>> Added partitioning enforcer " + 
exchg.getPhysicalOperator() + ".\n");
-                printOp((AbstractLogicalOperator) op);
+                printOp((AbstractLogicalOperator) op, context);
             }
         }
     }
@@ -875,11 +873,10 @@ public class EnforceStructuralPropertiesRule implements 
IAlgebraicRewriteRule {
         return !childLocalProperties.isEmpty();
     }
 
-    private void printOp(AbstractLogicalOperator op) throws 
AlgebricksException {
-        LogicalOperatorPrettyPrintVisitor pvisitor = new 
LogicalOperatorPrettyPrintVisitor();
-        PlanPrettyPrinter.printOperator(op, pvisitor, 0);
+    private void printOp(AbstractLogicalOperator op, IOptimizationContext ctx) 
throws AlgebricksException {
         if (AlgebricksConfig.ALGEBRICKS_LOGGER.isTraceEnabled()) {
-            
AlgebricksConfig.ALGEBRICKS_LOGGER.trace(LogRedactionUtil.userData(pvisitor.get().toString()));
+            String plan = 
ctx.getPrettyPrinter().reset().printOperator(op).toString();
+            
AlgebricksConfig.ALGEBRICKS_LOGGER.trace(LogRedactionUtil.userData(plan));
         }
     }
 
diff --git 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/ErrorCode.java
 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/ErrorCode.java
index 8aebccf..5716262 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/ErrorCode.java
+++ 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/exceptions/ErrorCode.java
@@ -155,6 +155,7 @@ public class ErrorCode {
     public static final int UNSUPPORTED_WINDOW_SPEC = 119;
     public static final int EOF = 120;
     public static final int NUMERIC_PROMOTION_ERROR = 121;
+    public static final int ERROR_PRINTING_PLAN = 122;
 
     // Compilation error codes.
     public static final int RULECOLLECTION_NOT_INSTANCE_OF_LIST = 10000;
diff --git 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/resources/errormsg/en.properties
 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/resources/errormsg/en.properties
index 68a6418..fcbb6bb 100644
--- 
a/hyracks-fullstack/hyracks/hyracks-api/src/main/resources/errormsg/en.properties
+++ 
b/hyracks-fullstack/hyracks/hyracks-api/src/main/resources/errormsg/en.properties
@@ -138,6 +138,7 @@
 119 = Unsupported window specification: PARTITION BY %1$s, ORDER BY %2$s
 120 = End of file
 121 = A numeric type promotion error has occurred: %1$s
+122 = Encountered an error while printing the plan
 
 10000 = The given rule collection %1$s is not an instance of the List class.
 10001 = Cannot compose partition constraint %1$s with %2$s

Reply via email to