Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
AnalyticWindow.java
Go to the documentation of this file.
1 // Copyright 2014 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.cloudera.impala.analysis;
16 
17 import java.math.BigDecimal;
18 
22 import com.cloudera.impala.thrift.TAnalyticWindow;
23 import com.cloudera.impala.thrift.TAnalyticWindowBoundary;
24 import com.cloudera.impala.thrift.TAnalyticWindowBoundaryType;
25 import com.cloudera.impala.thrift.TAnalyticWindowType;
26 import com.cloudera.impala.thrift.TColumnValue;
28 import com.google.common.base.Preconditions;
29 
30 
35 public class AnalyticWindow {
36  // default window used when an analytic expr was given an order by but no window
39  new Boundary(BoundaryType.CURRENT_ROW, null));
40 
41  enum Type {
42  ROWS("ROWS"),
43  RANGE("RANGE");
44 
45  private final String description_;
46 
47  private Type(String d) {
48  description_ = d;
49  }
50 
51  @Override
52  public String toString() { return description_; }
53  public TAnalyticWindowType toThrift() {
54  return this == ROWS ? TAnalyticWindowType.ROWS : TAnalyticWindowType.RANGE;
55  }
56  }
57 
58  enum BoundaryType {
59  UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"),
60  UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING"),
61  CURRENT_ROW("CURRENT ROW"),
62  PRECEDING("PRECEDING"),
63  FOLLOWING("FOLLOWING");
64 
65  private final String description_;
66 
67  private BoundaryType(String d) {
68  description_ = d;
69  }
70 
71  @Override
72  public String toString() { return description_; }
73  public TAnalyticWindowBoundaryType toThrift() {
74  Preconditions.checkState(!isAbsolutePos());
75  if (this == CURRENT_ROW) {
76  return TAnalyticWindowBoundaryType.CURRENT_ROW;
77  } else if (this == PRECEDING) {
78  return TAnalyticWindowBoundaryType.PRECEDING;
79  } else if (this == FOLLOWING) {
80  return TAnalyticWindowBoundaryType.FOLLOWING;
81  }
82  return null;
83  }
84 
85  public boolean isAbsolutePos() {
86  return this == UNBOUNDED_PRECEDING || this == UNBOUNDED_FOLLOWING;
87  }
88 
89  public boolean isOffset() {
90  return this == PRECEDING || this == FOLLOWING;
91  }
92 
93  public boolean isPreceding() {
94  return this == UNBOUNDED_PRECEDING || this == PRECEDING;
95  }
96 
97  public boolean isFollowing() {
98  return this == UNBOUNDED_FOLLOWING || this == FOLLOWING;
99  }
100 
102  switch (this) {
103  case UNBOUNDED_PRECEDING: return UNBOUNDED_FOLLOWING;
104  case UNBOUNDED_FOLLOWING: return UNBOUNDED_PRECEDING;
105  case PRECEDING: return FOLLOWING;
106  case FOLLOWING: return PRECEDING;
107  default: return CURRENT_ROW;
108  }
109  }
110  }
111 
112  public static class Boundary {
113  private final BoundaryType type_;
114 
115  // Offset expr. Only set for PRECEDING/FOLLOWING. Needed for toSql().
116  private final Expr expr_;
117 
118  // The offset value. Set during analysis after evaluating expr_. Integral valued
119  // for ROWS windows.
120  private BigDecimal offsetValue_;
121 
122  public BoundaryType getType() { return type_; }
123  public Expr getExpr() { return expr_; }
124  public BigDecimal getOffsetValue() { return offsetValue_; }
125 
126  public Boundary(BoundaryType type, Expr e) {
127  this(type, e, null);
128  }
129 
130  // c'tor used by clone()
131  private Boundary(BoundaryType type, Expr e, BigDecimal offsetValue) {
132  Preconditions.checkState(
133  (type.isOffset() && e != null)
134  || (!type.isOffset() && e == null));
135  type_ = type;
136  expr_ = e;
137  offsetValue_ = offsetValue;
138  }
139 
140  public String toSql() {
141  StringBuilder sb = new StringBuilder();
142  if (expr_ != null) sb.append(expr_.toSql()).append(" ");
143  sb.append(type_.toString());
144  return sb.toString();
145  }
146 
147  public TAnalyticWindowBoundary toThrift(Type windowType) {
148  TAnalyticWindowBoundary result = new TAnalyticWindowBoundary(type_.toThrift());
149  if (type_.isOffset() && windowType == Type.ROWS) {
150  result.setRows_offset_value(offsetValue_.longValue());
151  }
152  // TODO: range windows need range_offset_predicate
153  return result;
154  }
155 
156  @Override
157  public boolean equals(Object obj) {
158  if (obj == null) return false;
159  if (obj.getClass() != this.getClass()) return false;
160  Boundary o = (Boundary)obj;
161  boolean exprEqual = (expr_ == null) == (o.expr_ == null);
162  if (exprEqual && expr_ != null) exprEqual = expr_.equals(o.expr_);
163  return type_ == o.type_ && exprEqual;
164  }
165 
166  public Boundary converse() {
167  Boundary result = new Boundary(type_.converse(),
168  (expr_ != null) ? expr_.clone() : null);
169  result.offsetValue_ = offsetValue_;
170  return result;
171  }
172 
173  @Override
174  public Boundary clone() {
175  return new Boundary(type_, expr_ != null ? expr_.clone() : null, offsetValue_);
176  }
177 
178  public void analyze(Analyzer analyzer) throws AnalysisException {
179  if (expr_ != null) expr_.analyze(analyzer);
180  }
181  }
182 
183  private final Type type_;
184  private final Boundary leftBoundary_;
185  private Boundary rightBoundary_; // may be null before analyze()
186  private String toSqlString_; // cached after analysis
187 
188  public Type getType() { return type_; }
192 
193  public AnalyticWindow(Type type, Boundary b) {
194  type_ = type;
195  Preconditions.checkNotNull(b);
196  leftBoundary_ = b;
197  rightBoundary_ = null;
198  }
199 
200  public AnalyticWindow(Type type, Boundary l, Boundary r) {
201  type_ = type;
202  Preconditions.checkNotNull(l);
203  leftBoundary_ = l;
204  Preconditions.checkNotNull(r);
205  rightBoundary_ = r;
206  }
207 
212  type_ = other.type_;
213  Preconditions.checkNotNull(other.leftBoundary_);
214  leftBoundary_ = other.leftBoundary_.clone();
215  if (other.rightBoundary_ != null) {
216  rightBoundary_ = other.rightBoundary_.clone();
217  }
218  toSqlString_ = other.toSqlString_; // safe to share
219  }
220 
222  Boundary newRightBoundary = leftBoundary_.converse();
223  Boundary newLeftBoundary = null;
224  if (rightBoundary_ == null) {
225  newLeftBoundary = new Boundary(leftBoundary_.getType(), null);
226  } else {
227  newLeftBoundary = rightBoundary_.converse();
228  }
229  return new AnalyticWindow(type_, newLeftBoundary, newRightBoundary);
230  }
231 
232  public String toSql() {
233  if (toSqlString_ != null) return toSqlString_;
234  StringBuilder sb = new StringBuilder();
235  sb.append(type_.toString()).append(" ");
236  if (rightBoundary_ == null) {
237  sb.append(leftBoundary_.toSql());
238  } else {
239  sb.append("BETWEEN ").append(leftBoundary_.toSql()).append(" AND ");
240  sb.append(rightBoundary_.toSql());
241  }
242  return sb.toString();
243  }
244 
245  public TAnalyticWindow toThrift() {
246  TAnalyticWindow result = new TAnalyticWindow(type_.toThrift());
248  result.setWindow_start(leftBoundary_.toThrift(type_));
249  }
250  Preconditions.checkNotNull(rightBoundary_);
252  result.setWindow_end(rightBoundary_.toThrift(type_));
253  }
254  return result;
255  }
256 
257  @Override
258  public boolean equals(Object obj) {
259  if (obj == null) return false;
260  if (obj.getClass() != this.getClass()) return false;
262  boolean rightBoundaryEqual =
263  (rightBoundary_ == null) == (o.rightBoundary_ == null);
264  if (rightBoundaryEqual && rightBoundary_ != null) {
265  rightBoundaryEqual = rightBoundary_.equals(o.rightBoundary_);
266  }
267  return type_ == o.type_
268  && leftBoundary_.equals(o.leftBoundary_)
269  && rightBoundaryEqual;
270  }
271 
272  @Override
273  public AnalyticWindow clone() { return new AnalyticWindow(this); }
274 
278  private void checkOffsetExpr(Analyzer analyzer, Boundary boundary)
279  throws AnalysisException {
280  Preconditions.checkState(boundary.getType().isOffset());
281  Expr e = boundary.getExpr();
282  Preconditions.checkNotNull(e);
283  boolean isPos = true;
284  Double val = null;
285  if (e.isConstant() && e.getType().isNumericType()) {
286  try {
287  val = TColumnValueUtil.getNumericVal(
288  FeSupport.EvalConstExpr(e, analyzer.getQueryCtx()));
289  if (val <= 0) isPos = false;
290  } catch (InternalException exc) {
291  throw new AnalysisException(
292  "Couldn't evaluate PRECEDING/FOLLOWING expression: " + exc.getMessage());
293  }
294  }
295 
296  if (type_ == Type.ROWS) {
297  if (!e.isConstant() || !e.getType().isIntegerType() || !isPos) {
298  throw new AnalysisException(
299  "For ROWS window, the value of a PRECEDING/FOLLOWING offset must be a "
300  + "constant positive integer: " + boundary.toSql());
301  }
302  Preconditions.checkNotNull(val);
303  boundary.offsetValue_ = new BigDecimal(val.longValue());
304  } else {
305  if (!e.isConstant() || !e.getType().isNumericType() || !isPos) {
306  throw new AnalysisException(
307  "For RANGE window, the value of a PRECEDING/FOLLOWING offset must be a "
308  + "constant positive number: " + boundary.toSql());
309  }
310  boundary.offsetValue_ = new BigDecimal(val);
311  }
312  }
313 
317  private void checkOffsetBoundaries(Analyzer analyzer, Boundary b1, Boundary b2)
318  throws AnalysisException {
319  Preconditions.checkState(b1.getType().isOffset());
320  Preconditions.checkState(b2.getType().isOffset());
321  Expr e1 = b1.getExpr();
322  Preconditions.checkState(
323  e1 != null && e1.isConstant() && e1.getType().isNumericType());
324  Expr e2 = b2.getExpr();
325  Preconditions.checkState(
326  e2 != null && e2.isConstant() && e2.getType().isNumericType());
327 
328  try {
329  TColumnValue val1 = FeSupport.EvalConstExpr(e1, analyzer.getQueryCtx());
330  TColumnValue val2 = FeSupport.EvalConstExpr(e2, analyzer.getQueryCtx());
331  double left = TColumnValueUtil.getNumericVal(val1);
332  double right = TColumnValueUtil.getNumericVal(val2);
333  if (left > right) {
334  throw new AnalysisException(
335  "Offset boundaries are in the wrong order: " + toSql());
336  }
337  } catch (InternalException exc) {
338  throw new AnalysisException(
339  "Couldn't evaluate PRECEDING/FOLLOWING expression: " + exc.getMessage());
340  }
341 
342  }
343 
344  public void analyze(Analyzer analyzer) throws AnalysisException {
345  leftBoundary_.analyze(analyzer);
346  if (rightBoundary_ != null) rightBoundary_.analyze(analyzer);
347 
349  throw new AnalysisException(
350  leftBoundary_.getType().toString() + " is only allowed for upper bound of "
351  + "BETWEEN");
352  }
353  if (rightBoundary_ != null
355  throw new AnalysisException(
356  rightBoundary_.getType().toString() + " is only allowed for lower bound of "
357  + "BETWEEN");
358  }
359 
360  // TODO: Remove when RANGE windows with offset boundaries are supported.
361  if (type_ == Type.RANGE) {
363  || (rightBoundary_ != null && rightBoundary_.type_.isOffset())
364  || (leftBoundary_.type_ == BoundaryType.CURRENT_ROW
365  && (rightBoundary_ == null
366  || rightBoundary_.type_ == BoundaryType.CURRENT_ROW))) {
367  throw new AnalysisException(
368  "RANGE is only supported with both the lower and upper bounds UNBOUNDED or"
369  + " one UNBOUNDED and the other CURRENT ROW.");
370  }
371  }
372 
374  throw new AnalysisException(
375  leftBoundary_.getType().toString() + " requires a BETWEEN clause");
376  }
377 
378  if (leftBoundary_.getType().isOffset()) checkOffsetExpr(analyzer, leftBoundary_);
379  if (rightBoundary_ == null) {
380  // set right boundary to implied value, but make sure to cache toSql string
381  // beforehand
382  toSqlString_ = toSql();
384  return;
385  }
386  if (rightBoundary_.getType().isOffset()) checkOffsetExpr(analyzer, rightBoundary_);
387 
389  if (rightBoundary_.getType() != BoundaryType.FOLLOWING
390  && rightBoundary_.getType() != BoundaryType.UNBOUNDED_FOLLOWING) {
391  throw new AnalysisException(
392  "A lower window bound of " + BoundaryType.FOLLOWING.toString()
393  + " requires that the upper bound also be "
394  + BoundaryType.FOLLOWING.toString());
395  }
398  }
399  }
400 
402  if (leftBoundary_.getType() != BoundaryType.PRECEDING
403  && leftBoundary_.getType() != BoundaryType.UNBOUNDED_PRECEDING) {
404  throw new AnalysisException(
405  "An upper window bound of " + BoundaryType.PRECEDING.toString()
406  + " requires that the lower bound also be "
407  + BoundaryType.PRECEDING.toString());
408  }
411  }
412  }
413  }
414 }
AnalyticWindow(Type type, Boundary l, Boundary r)
void checkOffsetBoundaries(Analyzer analyzer, Boundary b1, Boundary b2)
static final AnalyticWindow DEFAULT_WINDOW
Boundary(BoundaryType type, Expr e, BigDecimal offsetValue)
void checkOffsetExpr(Analyzer analyzer, Boundary boundary)
TAnalyticWindowBoundary toThrift(Type windowType)