• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apache / iotdb / #9875

21 Aug 2023 01:06AM UTC coverage: 47.992% (-0.001%) from 47.993%
#9875

push

travis_ci

web-flow
Implement intersect with prefix pattern for PartialPath and PathPatternTree (#10909)

71 of 71 new or added lines in 3 files covered. (100.0%)

79843 of 166368 relevant lines covered (47.99%)

0.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.26
/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *      http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
package org.apache.iotdb.commons.path;
20

21
import org.apache.iotdb.commons.exception.IllegalPathException;
22
import org.apache.iotdb.commons.exception.MetadataException;
23
import org.apache.iotdb.commons.utils.PathUtils;
24
import org.apache.iotdb.commons.utils.TestOnly;
25
import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
26
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
27
import org.apache.iotdb.tsfile.read.common.Path;
28
import org.apache.iotdb.tsfile.utils.PublicBAOS;
29
import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
30
import org.apache.iotdb.tsfile.write.schema.IMeasurementSchema;
31

32
import org.apache.commons.lang3.ArrayUtils;
33
import org.apache.commons.lang3.Validate;
34
import org.slf4j.Logger;
35
import org.slf4j.LoggerFactory;
36

37
import java.io.IOException;
38
import java.io.OutputStream;
39
import java.nio.ByteBuffer;
40
import java.util.ArrayList;
41
import java.util.Arrays;
42
import java.util.Collections;
43
import java.util.List;
44
import java.util.stream.Collectors;
45

46
import static org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
47
import static org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
48

49
/**
50
 * A prefix path, suffix path or fullPath generated from SQL. Usually used in the IoTDB server
51
 * module
52
 */
53
public class PartialPath extends Path implements Comparable<Path>, Cloneable {
54

55
  private static final Logger logger = LoggerFactory.getLogger(PartialPath.class);
1✔
56
  private static final PartialPath ALL_MATCH_PATTERN = new PartialPath(new String[] {"root", "**"});
1✔
57

58
  protected String[] nodes;
59

60
  public PartialPath() {}
1✔
61

62
  /**
63
   * Construct the PartialPath using a String, will split the given String into String[] E.g., path
64
   * = "root.sg.`d.1`.`s.1`" nodes = {"root", "sg", "`d.1`", "`s.1`"}
65
   *
66
   * @param path a full String of a time series path
67
   */
68
  public PartialPath(String path) throws IllegalPathException {
1✔
69
    this.nodes = PathUtils.splitPathToDetachedNodes(path);
1✔
70
    if (nodes.length == 0) {
1✔
71
      throw new IllegalPathException(path);
×
72
    }
73
    // path is root.sg.`abc`, fullPath is root.sg.abc
74
    // path is root.sg.`select`, fullPath is root.sg.select
75
    // path is root.sg.`111`, fullPath is root.sg.`111`
76
    // path is root.sg.`a.b`, fullPath is root.sg.`a.b`
77
    // path is root.sg.`a``b`, fullPath is root.sg.`a``b`
78
    this.fullPath = getFullPath();
1✔
79
  }
1✔
80

81
  public PartialPath(String device, String measurement) throws IllegalPathException {
1✔
82
    String path = device + TsFileConstant.PATH_SEPARATOR + measurement;
1✔
83
    this.nodes = PathUtils.splitPathToDetachedNodes(path);
1✔
84
    this.fullPath = getFullPath();
1✔
85
  }
1✔
86

87
  /** @param partialNodes nodes of a time series path */
88
  public PartialPath(String[] partialNodes) {
1✔
89
    nodes = partialNodes;
1✔
90
  }
1✔
91

92
  /**
93
   * only use this method in following situations: 1. you are sure you do not want to split the
94
   * path. 2. you are sure path is correct.
95
   *
96
   * @param path path
97
   * @param needSplit whether to split path to nodes, needSplit can only be false.
98
   */
99
  public PartialPath(String path, boolean needSplit) {
1✔
100
    Validate.isTrue(!needSplit);
1✔
101
    fullPath = path;
1✔
102
    if ("".equals(path)) {
1✔
103
      this.nodes = new String[] {};
1✔
104
    } else {
105
      this.nodes = new String[] {path};
1✔
106
    }
107
  }
1✔
108

109
  public boolean hasWildcard() {
110
    for (String node : nodes) {
1✔
111
      // *, ** , d*, *d*
112
      if (PathPatternUtil.hasWildcard(node)) {
1✔
113
        return true;
×
114
      }
115
    }
116
    return false;
1✔
117
  }
118

119
  public boolean hasMultiLevelMatchWildcard() {
120
    for (String node : nodes) {
×
121
      if (PathPatternUtil.isMultiLevelMatchWildcard(node)) {
×
122
        return true;
×
123
      }
124
    }
125
    return false;
×
126
  }
127

128
  public boolean endWithMultiLevelWildcard() {
129
    return PathPatternUtil.isMultiLevelMatchWildcard(nodes[nodes.length - 1]);
1✔
130
  }
131

132
  // e.g. root.db.d.s, root.db.d.*, root.db.d.s*, not include patterns like root.db.d.**
133
  public boolean hasExplicitDevice() {
134
    if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
×
135
      return false;
×
136
    }
137
    for (int i = 0; i < nodes.length - 1; i++) {
×
138
      // *, ** , d*, *d*
139
      if (PathPatternUtil.hasWildcard(nodes[i])) {
×
140
        return false;
×
141
      }
142
    }
143
    return true;
×
144
  }
145

146
  /**
147
   * it will return a new partial path
148
   *
149
   * @param partialPath the path you want to concat
150
   * @return new partial path
151
   */
152
  public PartialPath concatPath(PartialPath partialPath) {
153
    int len = nodes.length;
1✔
154
    String[] newNodes = Arrays.copyOf(nodes, nodes.length + partialPath.nodes.length);
1✔
155
    System.arraycopy(partialPath.nodes, 0, newNodes, len, partialPath.nodes.length);
1✔
156
    return new PartialPath(newNodes);
1✔
157
  }
158

159
  /**
160
   * It will change nodes in this partial path
161
   *
162
   * @param otherNodes nodes
163
   */
164
  public void concatPath(String[] otherNodes) {
165
    int len = nodes.length;
1✔
166
    this.nodes = Arrays.copyOf(nodes, nodes.length + otherNodes.length);
1✔
167
    System.arraycopy(otherNodes, 0, nodes, len, otherNodes.length);
1✔
168
    fullPath = String.join(TsFileConstant.PATH_SEPARATOR, nodes);
1✔
169
  }
1✔
170

171
  /**
172
   * only use this method in following situations: 1. you are sure node is allowed in syntax
173
   * convention. 2. you are sure node needs not to be checked.
174
   */
175
  public PartialPath concatNode(String node) {
176
    String[] newPathNodes = Arrays.copyOf(nodes, nodes.length + 1);
1✔
177
    newPathNodes[newPathNodes.length - 1] = node;
1✔
178
    return new PartialPath(newPathNodes);
1✔
179
  }
180

181
  public String[] getNodes() {
182
    return nodes;
1✔
183
  }
184

185
  public int getNodeLength() {
186
    return nodes.length;
1✔
187
  }
188

189
  public String getTailNode() {
190
    if (nodes.length <= 0) {
1✔
191
      return "";
×
192
    }
193
    return nodes[nodes.length - 1];
1✔
194
  }
195

196
  /**
197
   * Return the intersection of paths starting with the given prefix and paths matching this path
198
   * pattern.
199
   *
200
   * <p>For example, minimizing "root.**.b.c" with prefix "root.a.b" produces "root.a.b.c",
201
   * "root.a.b.b.c", "root.a.b.**.b.c", since the multi-level wildcard can match 'a', 'a.b', and any
202
   * other sub paths start with 'a.b'.
203
   *
204
   * <p>The goal of this method is to reduce the search space when querying a database with a path
205
   * with wildcard.
206
   *
207
   * <p>If this path or path pattern doesn't start with given prefix, return empty list. For
208
   * example, "root.a.b.c".alterPrefixPath("root.b") or "root.a.**".alterPrefixPath("root.b")
209
   * returns [].
210
   *
211
   * @param prefix The prefix. Cannot be null and cannot contain any wildcard.
212
   */
213
  public List<PartialPath> alterPrefixPath(PartialPath prefix) {
214
    Validate.notNull(prefix);
1✔
215

216
    // Make sure the prefix path doesn't contain any wildcard.
217
    for (String node : prefix.getNodes()) {
1✔
218
      if (MULTI_LEVEL_PATH_WILDCARD.equals(node) || ONE_LEVEL_PATH_WILDCARD.equals(node)) {
1✔
219
        throw new IllegalArgumentException(
×
220
            "Wildcards are not allowed in the prefix path: " + prefix.getFullPath());
×
221
      }
222
    }
223

224
    List<List<String>> results = new ArrayList<>();
1✔
225
    alterPrefixPathInternal(
1✔
226
        Arrays.asList(prefix.getNodes()), Arrays.asList(nodes), new ArrayList<>(), results);
1✔
227
    return results.stream()
1✔
228
        .map(r -> new PartialPath(r.toArray(new String[0])))
1✔
229
        .collect(Collectors.toList());
1✔
230
  }
231

232
  /**
233
   * Let PRE stands for the remaining prefix nodes, PATH for the remaining path nodes, and C for the
234
   * path being processed. And assume the size of the prefix is i and that of this path is j.
235
   *
236
   * <p>If the first node of the path is not '**' and it matches the first node of the path. The
237
   * optimality equation is,
238
   *
239
   * <p>O(PRE(0, i), PATH(0, j), C) = O(PRE(1, i), PATH(1, j), [PRE(0)])
240
   *
241
   * <p>If the first node of the path is '**', it may match the first 1, 2, ... i nodes of the
242
   * prefix. '**' may match the all the remaining nodes of the prefix, as well as some additional
243
   * levels. Thus, the optimality equation is,
244
   *
245
   * <p>O(PRE(0, i), PATH(0, j), C) = { O(PRE(1, i), PATH(1, j), [PRE(0)]), ... , O([], PATH(1, j),
246
   * C + PRE(0) + ... + PRE(i-1)), O([], PATH(0, j), C + PRE(0) + ... + PRE(i-1))}
247
   *
248
   * <p>And when all the prefix nodes are matched,
249
   *
250
   * <p>O([], PATH, C) = C + PATH
251
   *
252
   * <p>The algorithm above takes all the possible matches of a multi-level wildcard into account.
253
   * Thus, it produces the correct result.
254
   *
255
   * <p>To prevent overlapping among the final results, this algorithm stops when the prefix has no
256
   * remaining node and the first remaining node of the path is '**'. This strategy works because
257
   * this algorithm will always find the path with the least levels in the search space.
258
   *
259
   * @param prefix The remaining nodes of the prefix.
260
   * @param path The remaining nodes of the path.
261
   * @param current The path being processed.
262
   * @param results The final results.
263
   * @return True if the search should be stopped, else false.
264
   */
265
  private boolean alterPrefixPathInternal(
266
      List<String> prefix, List<String> path, List<String> current, List<List<String>> results) {
267
    if (prefix.isEmpty()) {
1✔
268
      current.addAll(path);
1✔
269
      results.add(current);
1✔
270
      return !path.isEmpty() && MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0));
1✔
271
    }
272
    if (path.isEmpty()) {
1✔
273
      return false;
1✔
274
    }
275

276
    if (MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0))) {
1✔
277
      // Wildcard matches part of or the whole the prefix.
278
      for (int j = 1; j <= prefix.size(); j++) {
1✔
279
        List<String> copy = new ArrayList<>(current);
1✔
280
        copy.addAll(prefix.subList(0, j));
1✔
281
        if (alterPrefixPathInternal(
1✔
282
            prefix.subList(j, prefix.size()), path.subList(1, path.size()), copy, results)) {
1✔
283
          return true;
1✔
284
        }
285
      }
286
      // Wildcard matches the prefix and some more levels.
287
      List<String> copy = new ArrayList<>(current);
1✔
288
      copy.addAll(prefix);
1✔
289
      return alterPrefixPathInternal(
1✔
290
          Collections.emptyList(), path.subList(0, path.size()), copy, results);
1✔
291
    } else if (ONE_LEVEL_PATH_WILDCARD.equals(path.get(0)) || prefix.get(0).equals(path.get(0))) {
1✔
292
      // No need to make a copy.
293
      current.add(prefix.get(0));
1✔
294
      return alterPrefixPathInternal(
1✔
295
          prefix.subList(1, prefix.size()), path.subList(1, path.size()), current, results);
1✔
296
    }
297

298
    return false;
1✔
299
  }
300

301
  /**
302
   * Test if current PartialPath matches a full path. Current partialPath acts as a full path
303
   * pattern. rPath is supposed to be a full timeseries path without wildcards. e.g.
304
   * "root.sg.device.*" matches path "root.sg.device.s1" whereas it does not match "root.sg.device"
305
   * and "root.sg.vehicle.s1"
306
   *
307
   * @param rPath a plain full path of a timeseries
308
   * @return true if a successful match, otherwise return false
309
   */
310
  public boolean matchFullPath(PartialPath rPath) {
311
    return matchPath(rPath.getNodes(), 0, 0, false, false);
1✔
312
  }
313

314
  /**
315
   * Check if current pattern PartialPath can match 1 prefix path.
316
   *
317
   * <p>1) Current partialPath acts as a full path pattern.
318
   *
319
   * <p>2) Input parameter prefixPath is 1 prefix of time-series path.
320
   *
321
   * <p>For example:
322
   *
323
   * <p>1) Pattern "root.sg1.d1.*" can match prefix path "root.sg1.d1.s1", "root.sg1.d1",
324
   * "root.sg1", "root" etc.
325
   *
326
   * <p>1) Pattern "root.sg1.d1.*" does not match prefix path "root.sg2", "root.sg1.d2".
327
   *
328
   * @param prefixPath
329
   * @return true if a successful match, otherwise return false
330
   */
331
  public boolean matchPrefixPath(PartialPath prefixPath) {
332
    return matchPath(prefixPath.getNodes(), 0, 0, false, true);
1✔
333
  }
334

335
  private boolean matchPath(
336
      String[] pathNodes,
337
      int pathIndex,
338
      int patternIndex,
339
      boolean multiLevelWild,
340
      boolean pathIsPrefix) {
341
    if (pathIndex == pathNodes.length && patternIndex == nodes.length) {
1✔
342
      return true;
1✔
343
    } else if (patternIndex == nodes.length && multiLevelWild) {
1✔
344
      return matchPath(pathNodes, pathIndex + 1, patternIndex, true, pathIsPrefix);
1✔
345
    } else if (pathIndex >= pathNodes.length) {
1✔
346
      return pathIsPrefix;
1✔
347
    } else if (patternIndex >= nodes.length) {
1✔
348
      return false;
1✔
349
    }
350

351
    String pathNode = pathNodes[pathIndex];
1✔
352
    String patternNode = nodes[patternIndex];
1✔
353
    boolean isMatch = false;
1✔
354
    if (patternNode.equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
355
      isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, true, pathIsPrefix);
1✔
356
    } else {
357
      if (PathPatternUtil.hasWildcard(patternNode)) {
1✔
358
        if (PathPatternUtil.isNodeMatch(patternNode, pathNode)) {
1✔
359
          isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, false, pathIsPrefix);
1✔
360
        }
361
      } else {
362
        if (patternNode.equals(pathNode)) {
1✔
363
          isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, false, pathIsPrefix);
1✔
364
        }
365
      }
366

367
      if (!isMatch && multiLevelWild) {
1✔
368
        isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex, true, pathIsPrefix);
1✔
369
      }
370
    }
371
    return isMatch;
1✔
372
  }
373

374
  /**
375
   * Test if current PartialPath matches a full path's prefix. Current partialPath acts as a prefix
376
   * path pattern. rPath is supposed to be a full time-series path without wildcards. e.g. Current
377
   * PartialPath "root.sg" or "root.*" can match rPath "root.sg.device.s1", "root.sg.device" or
378
   * "root.sg.vehicle.s1".
379
   *
380
   * @param rPath a plain full path of a time-series
381
   * @return true if a successful match, otherwise return false.
382
   */
383
  public boolean prefixMatchFullPath(PartialPath rPath) {
384
    String[] rNodes = rPath.getNodes();
×
385
    if (this.nodes.length > rNodes.length) {
×
386
      return false;
×
387
    }
388
    for (int i = 0; i < this.nodes.length; i++) {
×
389
      if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
×
390
        return true;
×
391
      }
392
      if (nodes[i].equals(ONE_LEVEL_PATH_WILDCARD)) {
×
393
        continue;
×
394
      }
395
      if (PathPatternUtil.hasWildcard(nodes[i])) {
×
396
        if (PathPatternUtil.isNodeMatch(nodes[i], rNodes[i])) {
×
397
          continue;
×
398
        } else {
399
          return false;
×
400
        }
401
      }
402
      if (!nodes[i].equals(rNodes[i])) {
×
403
        return false;
×
404
      }
405
    }
406

407
    return true;
×
408
  }
409

410
  /**
411
   * Test if this path pattern includes input path pattern. e.g. "root.**" includes "root.sg.**",
412
   * "root.*.d.s" includes "root.sg.d.s", "root.sg.**" does not include "root.**.s", "root.*.d.s"
413
   * does not include "root.sg.d1.*"
414
   *
415
   * @param rPath a pattern path of a timeseries
416
   * @return true if this path pattern includes input path pattern, otherwise return false
417
   */
418
  public boolean include(PartialPath rPath) {
419
    String[] rNodes = rPath.getNodes();
1✔
420
    String[] lNodes = nodes.clone();
1✔
421
    // Replace * with ** if they are adjacent to each other
422
    for (int i = 1; i < lNodes.length; i++) {
1✔
423
      if (MULTI_LEVEL_PATH_WILDCARD.equals(lNodes[i - 1])
1✔
424
          && ONE_LEVEL_PATH_WILDCARD.equals(lNodes[i])) {
1✔
425
        lNodes[i] = MULTI_LEVEL_PATH_WILDCARD;
1✔
426
      }
427
      if (MULTI_LEVEL_PATH_WILDCARD.equals(lNodes[lNodes.length - i])
1✔
428
          && ONE_LEVEL_PATH_WILDCARD.equals(lNodes[lNodes.length - 1 - i])) {
1✔
429
        lNodes[lNodes.length - 1 - i] = MULTI_LEVEL_PATH_WILDCARD;
1✔
430
      }
431
    }
432
    // dp[i][j] means if nodes1[0:i) includes nodes[0:j)
433
    // for example: "root.sg.**" includes "root.sg.d1.*"
434
    // 1 0 0 0 0 |→| 1 0 0 0 0 |→| 1 0 0 0 0 |→| 1 0 0 0 0
435
    // 0 0 0 0 0 |↓| 0 1 0 0 0 |→| 0 1 0 0 0 |→| 0 1 0 0 0
436
    // 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 1 0 0 |→| 0 0 1 0 0
437
    // 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 0 1 1
438
    // Since the derivation of the next row depends only on the previous row, the calculation can
439
    // be performed using a one-dimensional array
440
    boolean[] dp = new boolean[rNodes.length + 1];
1✔
441
    dp[0] = true;
1✔
442
    for (int i = 1; i <= lNodes.length; i++) {
1✔
443
      boolean[] newDp = new boolean[rNodes.length + 1];
1✔
444
      for (int j = i; j <= rNodes.length; j++) {
1✔
445
        if (lNodes[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
446
          // if encounter MULTI_LEVEL_PATH_WILDCARD
447
          if (dp[j - 1]) {
1✔
448
            for (int k = j; k <= rNodes.length; k++) {
1✔
449
              newDp[k] = true;
1✔
450
            }
451
            break;
1✔
452
          }
453
        } else {
454
          // if without MULTI_LEVEL_PATH_WILDCARD, scan and check
455
          if (!rNodes[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)
1✔
456
              && ((PathPatternUtil.hasWildcard(lNodes[i - 1])
1✔
457
                      && PathPatternUtil.isNodeMatch(lNodes[i - 1], rNodes[j - 1]))
1✔
458
                  || lNodes[i - 1].equals(rNodes[j - 1]))) {
1✔
459
            // if nodes1[i-1] includes rNodes[j-1], dp[i][j] = dp[i-1][j-1]
460
            newDp[j] |= dp[j - 1];
1✔
461
          }
462
        }
463
      }
464
      dp = newDp;
1✔
465
    }
466
    return dp[rNodes.length];
1✔
467
  }
468

469
  /**
470
   * Test if this path pattern overlaps with input path pattern. Overlap means the result sets
471
   * generated by two path pattern share some common elements. e.g. "root.sg.**" overlaps with
472
   * "root.**", "root.*.d.s" overlaps with "root.sg.d.s", "root.sg.**" overlaps with "root.**.s",
473
   * "root.*.d.s" doesn't overlap with "root.sg.d1.*"
474
   *
475
   * @param rPath a pattern path of a timeseries
476
   * @return true if overlapping otherwise return false
477
   */
478
  public boolean overlapWith(PartialPath rPath) {
479
    String[] rNodes = rPath.getNodes();
1✔
480
    for (int i = 0; i < this.nodes.length && i < rNodes.length; i++) {
1✔
481
      // if encounter MULTI_LEVEL_PATH_WILDCARD
482
      if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)
1✔
483
          || rNodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
484
        return checkOverlapWithMultiLevelWildcard(nodes, rNodes);
1✔
485
      }
486
      // if without MULTI_LEVEL_PATH_WILDCARD, scan and check
487
      if ((PathPatternUtil.hasWildcard(nodes[i])
1✔
488
              && PathPatternUtil.isNodeMatch(nodes[i], rNodes[i]))
1✔
489
          || (PathPatternUtil.hasWildcard(rNodes[i])
1✔
490
              && PathPatternUtil.isNodeMatch(rNodes[i], nodes[i]))) {
1✔
491
        continue;
1✔
492
      }
493
      if (!nodes[i].equals(rNodes[i])) {
1✔
494
        return false;
1✔
495
      }
496
    }
497
    return this.nodes.length == rNodes.length;
1✔
498
  }
499

500
  /**
501
   * Try to check overlap between nodes1 and nodes2 with MULTI_LEVEL_PATH_WILDCARD. Time complexity
502
   * O(n^2).
503
   *
504
   * @return true if overlapping, otherwise return false
505
   */
506
  private boolean checkOverlapWithMultiLevelWildcard(String[] nodes1, String[] nodes2) {
507
    // dp[i][j] means if nodes1[0:i) and nodes[0:j) overlapping
508
    boolean[][] dp = new boolean[nodes1.length + 1][nodes2.length + 1];
1✔
509
    dp[0][0] = true;
1✔
510
    for (int i = 1; i <= nodes1.length; i++) {
1✔
511
      boolean prune = false; //
1✔
512
      // prune if we've already found that these two path are never overlapped in the beginning
513
      // steps.
514
      // e.g. root.db1.**.s1 and root.db2.**.s1
515
      for (int j = 1; j <= nodes2.length; j++) {
1✔
516
        if (nodes1[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)
1✔
517
            || nodes2[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
518
          // if encounter MULTI_LEVEL_PATH_WILDCARD
519
          if (nodes1[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
520
            // if nodes1[i-1] is MULTI_LEVEL_PATH_WILDCARD, dp[i][k(k>=j)]=dp[i-1][j-1]
521
            if (dp[i - 1][j - 1]) {
1✔
522
              for (int k = j; k <= nodes2.length; k++) {
1✔
523
                dp[i][k] = true;
1✔
524
              }
525
            }
526
          }
527
          if (nodes2[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
528
            // if nodes2[j-1] is MULTI_LEVEL_PATH_WILDCARD, dp[k(k>=i)][j]=dp[i-1][j-1]
529
            if (dp[i - 1][j - 1]) {
1✔
530
              for (int k = i; k <= nodes1.length; k++) {
1✔
531
                dp[k][j] = true;
1✔
532
              }
533
            }
534
          }
535
        } else {
536
          // if without MULTI_LEVEL_PATH_WILDCARD, scan and check
537
          if ((PathPatternUtil.hasWildcard(nodes1[i - 1])
1✔
538
                  && PathPatternUtil.isNodeMatch(nodes1[i - 1], nodes2[j - 1]))
1✔
539
              || (PathPatternUtil.hasWildcard(nodes2[j - 1])
1✔
540
                  && PathPatternUtil.isNodeMatch(nodes2[j - 1], nodes1[i - 1]))
1✔
541
              || nodes1[i - 1].equals(nodes2[j - 1])) {
1✔
542
            // if nodes1[i-1] and nodes[j-1] is matched, dp[i][j] = dp[i-1][j-1]
543
            dp[i][j] |= dp[i - 1][j - 1];
1✔
544
          }
545
        }
546
        prune |= dp[i][j];
1✔
547
      }
548
      if (!prune) {
1✔
549
        break;
1✔
550
      }
551
    }
552

553
    return dp[nodes1.length][nodes2.length];
1✔
554
  }
555

556
  /**
557
   * Test if this path pattern overlaps with input prefix full path. Overlap means the result sets
558
   * generated by two path pattern share some common elements. e.g.
559
   *
560
   * <ul>
561
   *   <li>"root.sg.**" overlaps with prefix full path "root" because "root.sg.**" share some common
562
   *       element "root.sg.d" with "root.**"
563
   *   <li>"root.*.d*.s" overlaps with prefix full path "root.sg" because "root.*.d*.s" share some
564
   *       common element "root.sg.d1.s" with "root.sg.**"
565
   *   <li>"root.*.d.s" doesn't overlap with prefix full path "root.sg.d1" because there is no
566
   *       common element between "root.*.d.s" and "root.sg.d1.**"
567
   * </ul>
568
   *
569
   * @param prefixFullPath prefix full path
570
   * @return true if overlapping otherwise return false
571
   */
572
  public boolean overlapWithFullPathPrefix(PartialPath prefixFullPath) {
573
    String[] rNodes = prefixFullPath.getNodes();
×
574
    int rNodesIndex = 0;
×
575
    for (int i = 0; i < this.nodes.length && rNodesIndex < rNodes.length; i++) {
×
576
      // if encounter MULTI_LEVEL_PATH_WILDCARD
577
      if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
×
578
        return true;
×
579
      } else if (nodes[i].equals(ONE_LEVEL_PATH_WILDCARD)) {
×
580
        rNodesIndex++;
×
581
      } else if (PathPatternUtil.hasWildcard(nodes[i])) {
×
582
        if (!PathPatternUtil.isNodeMatch(nodes[i], rNodes[rNodesIndex])) {
×
583
          return false;
×
584
        } else {
585
          rNodesIndex++;
×
586
        }
587
      } else if (nodes[i].equals(rNodes[rNodesIndex])) {
×
588
        rNodesIndex++;
×
589
      } else {
590
        return false;
×
591
      }
592
    }
593
    return true;
×
594
  }
595

596
  /**
597
   * Generate a list of partial paths which are the intersection of this path pattern and input
598
   * prefix pattern.
599
   *
600
   * @param prefixPattern must be a prefix full path ending with one "**", like "root.sg.**"
601
   * @return a list of partial paths which are the intersection of this path pattern and input
602
   *     prefix pattern.
603
   */
604
  public List<PartialPath> intersectWithPrefixPattern(PartialPath prefixPattern) {
605
    if (prefixPattern.equals(ALL_MATCH_PATTERN)) {
1✔
606
      return Collections.singletonList(this);
×
607
    }
608
    String[] prefixFullPath =
1✔
609
        Arrays.copyOf(prefixPattern.getNodes(), prefixPattern.getNodeLength() - 1);
1✔
610
    // init dp array
611
    int thisLength = this.getNodeLength();
1✔
612
    boolean[] matchIndex = new boolean[thisLength];
1✔
613
    matchIndex[0] = true; // "root" must match "root"
1✔
614

615
    // dp[i][j] means if nodes[0:i] matches prefixFullPath[0:j]
616
    // for example: "root.**.d1.**" intersect "root.sg1.d1(.**)"
617
    // dp[i][j] = (nodes[i]=="**"&&dp[i][j-1]) || (nodes[i] matches prefixFullPath[j]&&dp[i-1][j-1])
618
    // 1 0 0 0 |→| 1 0 0 0 |→| 1 0 0 0
619
    // 0 0 0 0 |↓| 0 1 0 0 |→| 0 1 0 0
620
    // 0 0 0 0 |↓| 0 0 0 0 |↓| 0 1 1 0
621
    // Since the derivation of the next row depends only on the previous row, the calculation can
622
    // be performed using a one-dimensional array named "matchIndex"
623
    for (int i = 1; i < prefixFullPath.length; i++) {
1✔
624
      for (int j = thisLength - 1; j >= 1; j--) {
1✔
625
        if (nodes[j].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
626
          matchIndex[j] = matchIndex[j] || matchIndex[j - 1];
1✔
627
        } else if (PathPatternUtil.isNodeMatch(nodes[j], prefixFullPath[i])) {
1✔
628
          matchIndex[j] = matchIndex[j - 1];
1✔
629
        } else {
630
          matchIndex[j] = false;
1✔
631
        }
632
      }
633
    }
634
    // Scan in reverse order to construct the result set.
635
    // The structure of the result set is prefixFullPath+remaining nodes. 【E.g.root.sg1.d1 + **】
636
    // It can be pruned if the remaining nodes start with **.
637
    List<PartialPath> res = new ArrayList<>();
1✔
638
    if (matchIndex[thisLength - 1] && nodes[thisLength - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
639
      res.add(new PartialPath(ArrayUtils.addAll(prefixFullPath, nodes[thisLength - 1])));
1✔
640
    } else {
641
      for (int j = thisLength - 2; j > 0; j--) {
1✔
642
        if (matchIndex[j]) {
1✔
643
          res.add(
1✔
644
              new PartialPath(
645
                  ArrayUtils.addAll(prefixFullPath, Arrays.copyOfRange(nodes, j + 1, thisLength))));
1✔
646
          if (nodes[j + 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
647
            break;
1✔
648
          }
649
          if (nodes[j].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
650
            res.add(
1✔
651
                new PartialPath(
652
                    ArrayUtils.addAll(prefixFullPath, Arrays.copyOfRange(nodes, j, thisLength))));
1✔
653
            break;
1✔
654
          }
655
        }
656
      }
657
    }
658
    return res;
1✔
659
  }
660

661
  @Override
662
  public String getFullPath() {
663
    if (fullPath == null) {
1✔
664
      StringBuilder s = new StringBuilder(nodes[0]);
1✔
665
      for (int i = 1; i < nodes.length; i++) {
1✔
666
        s.append(TsFileConstant.PATH_SEPARATOR).append(nodes[i]);
1✔
667
      }
668
      fullPath = s.toString();
1✔
669
    }
670
    return fullPath;
1✔
671
  }
672

673
  public PartialPath copy() {
674
    PartialPath result = new PartialPath();
×
675
    result.nodes = nodes;
×
676
    result.fullPath = fullPath;
×
677
    result.device = device;
×
678
    return result;
×
679
  }
680

681
  @Override
682
  public boolean equals(Object obj) {
683
    if (!(obj instanceof PartialPath)) {
1✔
684
      return false;
×
685
    }
686
    String[] otherNodes = ((PartialPath) obj).getNodes();
1✔
687
    if (this.nodes.length != otherNodes.length) {
1✔
688
      return false;
1✔
689
    } else {
690
      for (int i = 0; i < this.nodes.length; i++) {
1✔
691
        if (!nodes[i].equals(otherNodes[i])) {
1✔
692
          return false;
1✔
693
        }
694
      }
695
    }
696
    return true;
1✔
697
  }
698

699
  @Override
700
  public boolean equals(String obj) {
701
    return this.getFullPath().equals(obj);
1✔
702
  }
703

704
  @Override
705
  public int hashCode() {
706
    int h = 0;
1✔
707
    for (String node : nodes) {
1✔
708
      h += 31 * h + node.hashCode();
1✔
709
    }
710
    return h;
1✔
711
  }
712

713
  @Override
714
  public String getMeasurement() {
715
    return nodes[nodes.length - 1];
1✔
716
  }
717

718
  public String getFirstNode() {
719
    return nodes[0];
1✔
720
  }
721

722
  @Override
723
  public String getDevice() {
724
    if (device != null) {
1✔
725
      return device;
1✔
726
    } else {
727
      if (nodes.length == 1) {
1✔
728
        return "";
×
729
      }
730
      StringBuilder s = new StringBuilder(nodes[0]);
1✔
731
      for (int i = 1; i < nodes.length - 1; i++) {
1✔
732
        s.append(TsFileConstant.PATH_SEPARATOR);
1✔
733
        s.append(nodes[i]);
1✔
734
      }
735
      device = s.toString();
1✔
736
    }
737
    return device;
1✔
738
  }
739

740
  // todo remove measurement related interface after invoker using MeasurementPath explicitly
741
  public String getMeasurementAlias() {
742
    throw new RuntimeException("Only MeasurementPath support alias");
×
743
  }
744

745
  public void setMeasurementAlias(String measurementAlias) {
746
    throw new RuntimeException("Only MeasurementPath support alias");
×
747
  }
748

749
  public boolean isMeasurementAliasExists() {
750
    return false;
1✔
751
  }
752

753
  @Override
754
  public String getFullPathWithAlias() {
755
    throw new RuntimeException("Only MeasurementPath support alias");
×
756
  }
757

758
  public IMeasurementSchema getMeasurementSchema() throws MetadataException {
759
    throw new MetadataException("This path doesn't represent a measurement");
×
760
  }
761

762
  public TSDataType getSeriesType() throws UnsupportedOperationException {
763
    throw new UnsupportedOperationException("This path doesn't represent a measurement");
×
764
  }
765

766
  @Override
767
  public int compareTo(Path path) {
768
    PartialPath partialPath = (PartialPath) path;
1✔
769
    return this.getFullPath().compareTo(partialPath.getFullPath());
1✔
770
  }
771

772
  public boolean startsWithOrPrefixOf(String[] otherNodes) {
773
    for (int i = 0; i < otherNodes.length && i < nodes.length; i++) {
1✔
774
      if (!nodes[i].equals(otherNodes[i])) {
1✔
775
        return false;
×
776
      }
777
    }
778
    return true;
1✔
779
  }
780

781
  public boolean startsWith(String prefix) {
782
    return getFullPath().startsWith(prefix);
×
783
  }
784

785
  public boolean containNode(String otherNode) {
786
    for (String node : nodes) {
1✔
787
      if (node.equals(otherNode)) {
1✔
788
        return true;
1✔
789
      }
790
    }
791
    return false;
1✔
792
  }
793

794
  @Override
795
  public String toString() {
796
    return getFullPath();
1✔
797
  }
798

799
  public PartialPath getDevicePath() {
800
    return new PartialPath(Arrays.copyOf(nodes, nodes.length - 1));
1✔
801
  }
802

803
  public List<PartialPath> getDevicePathPattern() {
804
    List<PartialPath> result = new ArrayList<>();
1✔
805
    result.add(getDevicePath());
1✔
806
    if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
1✔
807
      result.add(new PartialPath(nodes));
×
808
    }
809
    return result;
1✔
810
  }
811

812
  @TestOnly
813
  public Path toTSFilePath() {
814
    return new Path(getDevice(), getMeasurement(), true);
×
815
  }
816

817
  public static List<String> toStringList(List<PartialPath> pathList) {
818
    List<String> ret = new ArrayList<>();
1✔
819
    for (PartialPath path : pathList) {
1✔
820
      ret.add(path.getFullPath());
1✔
821
    }
1✔
822
    return ret;
1✔
823
  }
824

825
  /**
826
   * Convert a list of Strings to a list of PartialPaths, ignoring all illegal paths
827
   *
828
   * @param pathList
829
   * @return
830
   */
831
  public static List<PartialPath> fromStringList(List<String> pathList) {
832
    if (pathList == null || pathList.isEmpty()) {
1✔
833
      return Collections.emptyList();
×
834
    }
835

836
    List<PartialPath> ret = new ArrayList<>();
1✔
837
    for (String s : pathList) {
1✔
838
      try {
839
        ret.add(new PartialPath(s));
1✔
840
      } catch (IllegalPathException e) {
×
841
        logger.warn("Encountered an illegal path {}", s);
×
842
      }
1✔
843
    }
1✔
844
    return ret;
1✔
845
  }
846

847
  @Override
848
  public PartialPath clone() {
849
    return new PartialPath(this.getNodes().clone());
×
850
  }
851

852
  public ByteBuffer serialize() throws IOException {
853
    PublicBAOS publicBAOS = new PublicBAOS();
×
854
    serialize((OutputStream) publicBAOS);
×
855
    return ByteBuffer.wrap(publicBAOS.getBuf(), 0, publicBAOS.size());
×
856
  }
857

858
  @Override
859
  public void serialize(OutputStream stream) throws IOException {
860
    PathType.Partial.serialize(stream);
1✔
861
    serializeWithoutType(stream);
1✔
862
  }
1✔
863

864
  @Override
865
  public void serialize(ByteBuffer byteBuffer) {
866
    PathType.Partial.serialize(byteBuffer);
1✔
867
    serializeWithoutType(byteBuffer);
1✔
868
  }
1✔
869

870
  @Override
871
  public void serialize(PublicBAOS stream) throws IOException {
872
    PathType.Partial.serialize(stream);
×
873
    serializeWithoutType(stream);
×
874
  }
×
875

876
  @Override
877
  protected void serializeWithoutType(ByteBuffer byteBuffer) {
878
    super.serializeWithoutType(byteBuffer);
1✔
879
    ReadWriteIOUtils.write(nodes.length, byteBuffer);
1✔
880
    for (String node : nodes) {
1✔
881
      ReadWriteIOUtils.write(node, byteBuffer);
1✔
882
    }
883
  }
1✔
884

885
  @Override
886
  protected void serializeWithoutType(OutputStream stream) throws IOException {
887
    super.serializeWithoutType(stream);
1✔
888
    ReadWriteIOUtils.write(nodes.length, stream);
1✔
889
    for (String node : nodes) {
1✔
890
      ReadWriteIOUtils.write(node, stream);
1✔
891
    }
892
  }
1✔
893

894
  // Attention!!! If you want to use serialize and deserialize of partialPath, must invoke
895
  // PathDeserializeUtil.deserialize
896
  public static PartialPath deserialize(ByteBuffer byteBuffer) {
897
    Path path = Path.deserialize(byteBuffer);
1✔
898
    PartialPath partialPath = new PartialPath();
1✔
899
    int nodeSize = ReadWriteIOUtils.readInt(byteBuffer);
1✔
900
    String[] nodes = new String[nodeSize];
1✔
901
    for (int i = 0; i < nodeSize; i++) {
1✔
902
      nodes[i] = ReadWriteIOUtils.readString(byteBuffer);
1✔
903
    }
904
    partialPath.nodes = nodes;
1✔
905
    partialPath.setMeasurement(path.getMeasurement());
1✔
906
    partialPath.device = path.getDevice();
1✔
907
    partialPath.fullPath = path.getFullPath();
1✔
908
    return partialPath;
1✔
909
  }
910

911
  public PartialPath transformToPartialPath() {
912
    return this;
×
913
  }
914

915
  /**
916
   * PartialPath basic total, 52B
917
   *
918
   * <ul>
919
   *   <li>Object header, 8B
920
   *   <li>String[] reference + header + length, 8 + 4 + 8= 20B
921
   *   <li>Path attributes' references, 8 * 3 = 24B
922
   * </ul>
923
   */
924
  public static int estimateSize(PartialPath partialPath) {
925
    int size = 52;
1✔
926
    for (String node : partialPath.getNodes()) {
1✔
927
      size += estimateStringSize(node);
1✔
928
    }
929
    size += estimateStringSize(partialPath.getFullPath());
1✔
930
    return size;
1✔
931
  }
932

933
  /**
934
   * String basic total, 32B
935
   *
936
   * <ul>
937
   *   <li>Object header, 8B
938
   *   <li>char[] reference + header + length, 8 + 4 + 8= 20B
939
   *   <li>hash code, 4B
940
   * </ul>
941
   */
942
  private static int estimateStringSize(String string) {
943
    // each char takes 2B in Java
944
    return string == null ? 0 : 32 + 2 * string.length();
1✔
945
  }
946
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc