thomasrebele commented on code in PR #6632:
URL: https://github.com/apache/hive/pull/6632#discussion_r3911978659


##########
itests/util/src/main/java/org/apache/hadoop/hive/cli/control/CoreBeeLineDriver.java:
##########
@@ -260,13 +266,40 @@ private void runTest(QFile qFile, List<Callable<Void>> 
preCommands) throws Excep
         qFile.overwriteResults();
         System.err.println(">>> PASSED " + qFile.getName());
       }
+      resetAdditionalPartialMasks();
     } catch (Exception e) {
+      resetAdditionalPartialMasks();
       throw new Exception("Exception running or analyzing the results of the 
query file: " + qFile
           + "\n" + qFile.getDebugHint(), e);
     }
 
   }
 
+  private void setupAdditionalPartialMasks() {

Review Comment:
   This method is almost a copy of CoreCliDriver#setupAdditionalPartialMasks. 
Would it be possible to combine these two?



##########
itests/util/src/main/java/org/apache/hive/beeline/ConvertedOutputFile.java:
##########
@@ -34,10 +36,30 @@ public class ConvertedOutputFile extends OutputFile {
   private final boolean hasFetchCallback;
 
   public ConvertedOutputFile(OutputFile inner, Converter converter) throws 
Exception {
-    super(converter.getConvertedPrintStream(inner.getOut()), 
inner.getFilename());
+    this(inner, converter, null, null);
+  }
+
+  public ConvertedOutputFile(OutputFile inner, Converter converter, 
QOutProcessor qOutProcessor,
+      QOutProcessor.MaskingFoldState maskingFoldState) throws Exception {
+    super(wrapStream(inner.getOut(), converter, qOutProcessor, 
maskingFoldState), inner.getFilename());
     hasFetchCallback = (getOut() instanceof FetchCallback);
   }
 
+  private static PrintStream wrapStream(PrintStream inner, Converter converter,
+      QOutProcessor qOutProcessor, QOutProcessor.MaskingFoldState 
maskingFoldState) throws Exception {
+    if (qOutProcessor == null || maskingFoldState == null) {
+      return converter.getConvertedPrintStream(inner);
+    }
+    // Sort before mask: identical MASK lines must not be created by sorting 
already-masked rows.

Review Comment:
   I don't see the relationship with sorting here. Could you explain it, please?



##########
itests/util/src/main/java/org/apache/hadoop/hive/ql/QOutProcessor.java:
##########
@@ -185,55 +188,73 @@ private Pattern[] toPattern(String[] patternStrs) {
     return patterns;
   }
 
-  public void maskPatterns(String fname) throws Exception {
-
-    String line;
-    BufferedReader in;
-    BufferedWriter out;
-
-    File file = new File(fname);
-    File fileOrig = new File(fname + ".orig");
-    FileUtils.copyFile(file, fileOrig);
-
-    in = new BufferedReader(new InputStreamReader(new 
FileInputStream(fileOrig), "UTF-8"));
-    out = new BufferedWriter(new OutputStreamWriter(new 
FileOutputStream(file), "UTF-8"));
-
-    boolean lastWasMasked = false;
-    boolean lastWasVertexKilled = false;
+  /**
+   * Applies {@link #processLine(String)} and consecutive-line folding.
+   *
+   * @return the line to emit, or {@code null} if the line should be suppressed
+   */
+  public String maskAndFoldLine(String line, MaskingFoldState state) {
+    LineProcessingResult result = processLine(line);
+
+    if (result.line.equals(MASK_PATTERN)) {
+      // We're folding multiple masked lines into one.
+      if (state.lastWasMasked) {
+        state.lastWasVertexKilled = false;
+        return null;
+      }
+      state.lastWasMasked = true;
+      state.lastWasVertexKilled = false;
+      return result.line;
+    }
+    if (result.line.equals(MASKED_VERTEX_KILLED_PATTERN)) {
+      // Deduplicate consecutive standalone vertex-killed lines — the number 
of sibling
+      // vertices still alive when the kill propagates is non-deterministic.
+      if (state.lastWasVertexKilled) {
+        state.lastWasMasked = false;
+        return null;
+      }
+      state.lastWasVertexKilled = true;
+      state.lastWasMasked = false;
+      return result.line;
+    }
+    state.lastWasMasked = false;
+    state.lastWasVertexKilled = false;
+    return result.line;
+  }
 
-    while (null != (line = in.readLine())) {
-      LineProcessingResult result = processLine(line);
+  public List<String> maskLines(List<String> lines) {
+    MaskingFoldState state = new MaskingFoldState();
+    List<String> masked = new ArrayList<>(lines.size());
+    for (String line : lines) {
+      String folded = maskAndFoldLine(line, state);
+      if (folded != null) {
+        masked.add(folded);
+      }
+    }
+    return masked;
+  }

Review Comment:
   The logic of this method can be integrated into the function 
`maskContent(String content)`. That way a list creation can be avoided.



##########
itests/util/src/main/java/org/apache/hive/beeline/ConvertedOutputFile.java:
##########
@@ -34,10 +36,30 @@ public class ConvertedOutputFile extends OutputFile {
   private final boolean hasFetchCallback;
 
   public ConvertedOutputFile(OutputFile inner, Converter converter) throws 
Exception {
-    super(converter.getConvertedPrintStream(inner.getOut()), 
inner.getFilename());
+    this(inner, converter, null, null);
+  }
+
+  public ConvertedOutputFile(OutputFile inner, Converter converter, 
QOutProcessor qOutProcessor,
+      QOutProcessor.MaskingFoldState maskingFoldState) throws Exception {
+    super(wrapStream(inner.getOut(), converter, qOutProcessor, 
maskingFoldState), inner.getFilename());
     hasFetchCallback = (getOut() instanceof FetchCallback);
   }
 
+  private static PrintStream wrapStream(PrintStream inner, Converter converter,
+      QOutProcessor qOutProcessor, QOutProcessor.MaskingFoldState 
maskingFoldState) throws Exception {
+    if (qOutProcessor == null || maskingFoldState == null) {
+      return converter.getConvertedPrintStream(inner);
+    }
+    // Sort before mask: identical MASK lines must not be created by sorting 
already-masked rows.
+    PrintStream masked = new QTestFetchConverter(inner, false, "UTF-8", line 
-> {
+      if (line.startsWith("Reading log file:")) {

Review Comment:
   This line feels a bit out of place here. I would not expect the 
`ConvertedOutputFile` to define its own filters. I guess this corresponds to 
org.apache.hive.beeline.QFile#getStaticFilterSet. (That method defines also two 
other patterns. I'm wondering why `"INFO  : "`, the other pattern that is 
replaced with the empty string, is not necessary here.)



##########
common/src/java/org/apache/hadoop/hive/common/io/FetchConverter.java:
##########
@@ -49,8 +50,14 @@ public void println(String out) {
     }
   }
 
-  protected final void printDirect(String out) {
-    super.println(out);
+  protected final void printDirect(String line) {
+    // Delegate to a wrapped PrintStream so downstream transforms (e.g. 
QTestFetchConverter) apply
+    // when this converter is stacked above them (sort/hash then mask).
+    if (this.out instanceof PrintStream ps && ps != this) {
+      ps.println(line);
+    } else {
+      super.println(line);
+    }

Review Comment:
   See [my comment on 
CachingPrintStream](https://github.com/apache/hive/pull/6632/changes#r3913280319).



##########
common/src/java/org/apache/hadoop/hive/common/io/CachingPrintStream.java:
##########
@@ -44,7 +45,12 @@ public CachingPrintStream(OutputStream out) {
   @Override
   public void println(String out) {
     output.add(out);
-    super.println(out);
+    // Delegate to a wrapped PrintStream so downstream transforms (e.g. 
QTestFetchConverter) apply.
+    if (this.out instanceof PrintStream ps && ps != this) {
+      ps.println(out);
+    } else {
+      super.println(out);
+    }

Review Comment:
   I would prefer to remove that change, as it complicates the IO logic even 
further. Is the change actually necessary? I've commented out this change and 
the one from FetchConverter.java and ran the failed tests (see script below). 
Only cachingprintstream.q.out changed, which is the same behavior when I 
execute the tests on the base commit of the PR 
(3df58f1bef47b64f24ccf95eb75c67d261438a7f).
   
   Here the script:
   ```
   mvn test -Pitests -pl itests/qtest -Dtest=TestCliDriver 
-Dqfile=inputwherefalse.q,stats_noscan_2.q -Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest -Dtest=TestHBaseCliDriver 
-Dqfile=hbase_bulk.q,hbase_handler_bulk.q -Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest -Dtest=TestIcebergCliDriver 
-Dqfile=query_iceberg_metadata_of_unpartitioned_table.q 
-Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest 
-Dtest=TestIcebergLlapLocalCompactorCliDriver 
-Dqfile=iceberg_major_compaction_query_metadata.q -Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest -Dtest=TestMiniLlapCliDriver 
-Dqfile=newline.q,remote_script.q -Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest -Dtest=TestMiniLlapLocalCliDriver 
-Dqfile=acid_stats4.q,alter_merge_3.q,alter_partition_format_loc.q,bucket_if_with_path_filter.q,cli_print_escape_crlf.q,create_like.q,create_table_like_file.q,create_table_like_file_orc.q,database_drop.q,database_location.q,describe_database_json.q,drop_database_removes_partition_dirs.q,drop_table_removes_partition_dirs.q,escape_crlf.q,exim_10_external_managed.q,exim_12_external_location.q,exim_13_managed_location.q,exim_14_managed_location_over_existing.q,exim_15_external_part.q,exim_16_part_external.q,exim_17_part_managed.q,exim_19_00_part_external_location.q,exim_19_part_external_location.q,exim_20_part_managed_location.q,external_jdbc_auth.q,insert_overwrite_directory.q,insert_overwrite_directory2.q,insertexternal1.q,load_data_using_job.q,load_fs.q,load_orc.q,load_orc_part.q,masking_8.q,nullformatdir.q,ppd_multi_insert.q,rfc5424_parser.q,rfc5424_parser_exception.q,sfs.q,sysdb.q,table_storage.q,udf_get_json_ob
 ject.q,vectorization_escape_crlf.q,virtual_column.q 
-Dtest.output.overwrite=true
   mvn test -Pitests -pl itests/qtest -Dtest=TestNegativeLlapLocalCliDriver 
-Dqfile=authorization_uri_export.q,cachingprintstream.q,exim_20_managed_location_over_existing.q,load_orc_negative_part.q
 -Dtest.output.overwrite=true
   ```



##########
itests/util/src/test/java/org/apache/hadoop/hive/ql/TestQOutProcessor.java:
##########
@@ -104,70 +149,217 @@ public void 
testKilledVerticesCountIsMaskedInLongerLine() {
 
   /**
    * Multiple consecutive standalone MASKED_VERTEX_KILLED_PATTERN lines must be
-   * collapsed to a single line by maskPatterns().
+   * collapsed to a single line by maskLines().
    */
   @Test
-  public void testConsecutiveVertexKilledLinesDeduplicatedInFile() throws 
Exception {
-    File f = tmpFile(
+  public void testConsecutiveVertexKilledLinesDeduplicatedInMemory() {
+    List<String> input = Arrays.asList(
         "line before",
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         "line after");
-
-    qOutProcessor.maskPatterns(f.getAbsolutePath());
-
-    List<String> lines = readLines(f);
     Assert.assertEquals(
         Arrays.asList("line before", 
QOutProcessor.MASKED_VERTEX_KILLED_PATTERN, "line after"),
-        lines);
+        qOutProcessor.maskLines(input));
   }
 
   /**
    * Two separate (non-consecutive) vertex-killed blocks must each produce one 
line.
    */
   @Test
-  public void testNonConsecutiveVertexKilledLinesKeptSeparately() throws 
Exception {
-    File f = tmpFile(
+  public void testNonConsecutiveVertexKilledLinesKeptSeparatelyInMemory() {
+    List<String> input = Arrays.asList(
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         "some other line",
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN);
-
-    qOutProcessor.maskPatterns(f.getAbsolutePath());
-
-    List<String> lines = readLines(f);
     Assert.assertEquals(
         Arrays.asList(
             QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
             "some other line",
             QOutProcessor.MASKED_VERTEX_KILLED_PATTERN),
-        lines);
+        qOutProcessor.maskLines(input));
   }
 
   /**
    * Vertex-killed deduplication must reset when a normal (masked) line 
interrupts
    * the run of vertex-killed lines.
    */
   @Test
-  public void testVertexKilledRunResetByMaskedLine() throws Exception {
-    // "Deleted something" starts with "Deleted" → gets replaced by 
MASK_PATTERN
-    File f = tmpFile(
+  public void testVertexKilledRunResetByMaskedLineInMemory() {
+    List<String> input = Arrays.asList(
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
-        "Deleted /tmp/something",        // will be masked → MASK_PATTERN
+        "Deleted /tmp/something",
         QOutProcessor.MASKED_VERTEX_KILLED_PATTERN);
-
-    qOutProcessor.maskPatterns(f.getAbsolutePath());
-
-    List<String> lines = readLines(f);
-    // MASK_PATTERN lines fold duplicates; but here there is only one 
occurrence
     Assert.assertEquals(
         Arrays.asList(
             QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
             QOutProcessor.MASK_PATTERN,
             QOutProcessor.MASKED_VERTEX_KILLED_PATTERN),
-        lines);
+        qOutProcessor.maskLines(input));
+  }
+
+  @Test
+  public void testMaskContentFoldsConsecutiveVertexKilledLines() throws 
Exception {
+    String input = String.join("\n",
+        QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
+        QOutProcessor.MASKED_VERTEX_KILLED_PATTERN,
+        "line after") + "\n";
+    Assert.assertEquals(
+        QOutProcessor.MASKED_VERTEX_KILLED_PATTERN + "\nline after\n",
+        qOutProcessor.maskContent(input));
+  }
+
+  /**
+   * PREHOOK/POSTHOOK emit multiline query text in one println; stream masking 
must match
+   * {@link QOutProcessor#maskContent(String)} (including consecutive-line 
folding).
+   */
+  @Test
+  public void testMultilinePrehookStreamMaskMatchesMaskContent() throws 
Exception {
+    String multiline =
+        "PREHOOK: query: CREATE TABLE alter_table_location2 (id int, name 
string, dept string)\n"
+            + "  PARTITIONED BY (year int)\n"
+            + "  STORED AS ORC\n"
+            + "  LOCATION 
'pfile://${system:test.tmp.dir}/alter_table_location2'\n"
+            + "  TBLPROPERTIES (\"transactional\"=\"true\")";
+    String expected = qOutProcessor.maskContent(multiline + "\n");
+
+    ByteArrayOutputStream buf = new ByteArrayOutputStream();
+    QOutProcessor.MaskingFoldState state = new 
QOutProcessor.MaskingFoldState();
+    QTestFetchConverter converter = new QTestFetchConverter(buf, true, "UTF-8",
+        line -> qOutProcessor.maskAndFoldLine(line, state));
+    converter.println(multiline);
+
+    Assert.assertEquals(expected, buf.toString(StandardCharsets.UTF_8));
+  }
+
+  @Test
+  public void testBeeLineRecordMaskingAppliedWhenProcessorProvided() throws 
Exception {
+    File out = tmpFolder.newFile("test.out");
+    OutputFile inner = new OutputFile(out.getAbsolutePath());
+    QOutProcessor.MaskingFoldState state = new 
QOutProcessor.MaskingFoldState();
+
+    ConvertedOutputFile convertedOutput =
+        new ConvertedOutputFile(inner, Converter.NONE, qOutProcessor, state);
+    convertedOutput.println("hdfs://localhost:51594/tmp/other text");
+    convertedOutput.close();
+
+    String content = Files.readString(out.toPath(), StandardCharsets.UTF_8);
+    Assert.assertTrue(content.contains(QOutProcessor.HDFS_MASK));
+    Assert.assertFalse(content.contains("localhost:51594"));
+  }
+
+  /**
+   * BeeLine fetches HS2 operation logs in test mode; the "Reading log file:" 
header must be dropped
+   * (same as {@link org.apache.hive.beeline.QFile#getStaticFilterSet()}).
+   */
+  @Test
+  public void testBeeLineRecordReadingLogFileLineIsSuppressed() throws 
Exception {
+    File out = tmpFolder.newFile("test.out");
+    OutputFile inner = new OutputFile(out.getAbsolutePath());
+    QOutProcessor.MaskingFoldState state = new 
QOutProcessor.MaskingFoldState();
+    ConvertedOutputFile convertedOutput =
+        new ConvertedOutputFile(inner, Converter.NONE, qOutProcessor, state);
+
+    convertedOutput.println("Reading log file: 
/tmp/cyanzheng/operation_logs/query.test");
+    convertedOutput.println("PREHOOK: query: SELECT 1");
+    convertedOutput.close();
+
+    Assert.assertEquals("PREHOOK: query: SELECT 1\n",
+        Files.readString(out.toPath(), StandardCharsets.UTF_8));
+  }
+
+  @Test
+  public void testBeeLineRecordNoMaskingWithoutProcessor() throws Exception {
+    File out = tmpFolder.newFile("test.out");
+    OutputFile inner = new OutputFile(out.getAbsolutePath());
+
+    ConvertedOutputFile convertedOutput = new ConvertedOutputFile(inner, 
Converter.NONE);
+    convertedOutput.println("hdfs://localhost:51594/tmp/other text");
+    convertedOutput.close();
+
+    Assert.assertEquals("hdfs://localhost:51594/tmp/other text\n",
+        Files.readString(out.toPath(), StandardCharsets.UTF_8));
+  }
+
+  /**
+   * BeeLine PREHOOK/POSTHOOK emit multiline query text in one println; 
converted output must match
+   * {@link QOutProcessor#maskContent(String)} via {@link ConvertedOutputFile}.
+   */
+  @Test
+  public void testMultilinePrehookBeeLineRecordMaskMatchesMaskContent() throws 
Exception {
+    String multiline =
+        "PREHOOK: query: CREATE TABLE alter_table_location2 (id int, name 
string, dept string)\n"
+            + "  PARTITIONED BY (year int)\n"
+            + "  STORED AS ORC\n"
+            + "  LOCATION 
'pfile://${system:test.tmp.dir}/alter_table_location2'\n"
+            + "  TBLPROPERTIES (\"transactional\"=\"true\")";
+    String expected = qOutProcessor.maskContent(multiline + "\n");
+
+    File out = tmpFolder.newFile("test.out");
+    OutputFile inner = new OutputFile(out.getAbsolutePath());
+    QOutProcessor.MaskingFoldState state = new 
QOutProcessor.MaskingFoldState();
+    ConvertedOutputFile convertedOutput =
+        new ConvertedOutputFile(inner, Converter.NONE, qOutProcessor, state);
+
+    convertedOutput.println(multiline);
+    convertedOutput.close();
+
+    Assert.assertEquals(expected, Files.readString(out.toPath(), 
StandardCharsets.UTF_8));
+  }
+
+  /**
+   * With -- SORT_QUERY_RESULTS, sort unmasked rows first then apply masking 
on output so identical
+   * mask placeholders are not clustered by the sorter (same stream order as 
ConvertedOutputFile).
+   */
+  @Test
+  public void testSortQueryResultsMasksAfterSort() throws Exception {

Review Comment:
   I don't see the purpose of the test. The asserts are the same as 
testCliDriverMaskBeforeSort, so the test method does not verify what its name 
advertises. Also [HIVE-29226](https://issues.apache.org/jira/browse/HIVE-29226) 
changed SORT_QUERY_RESULTS to apply the masking before sorting.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to