OmniSciDB  a5dc49c757
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HeavyDBRelJson.java
Go to the documentation of this file.
1 /*
2  * Copyright 2022 HEAVY.AI, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.apache.calcite.rel.externalize;
18 
19 import com.fasterxml.jackson.databind.type.TypeFactory;
20 import com.google.common.collect.ImmutableList;
22 
23 import org.apache.calcite.avatica.AvaticaUtils;
24 import org.apache.calcite.avatica.util.TimeUnitRange;
25 import org.apache.calcite.plan.RelOptCluster;
26 import org.apache.calcite.plan.RelTraitSet;
27 import org.apache.calcite.rel.RelCollation;
28 import org.apache.calcite.rel.RelCollationImpl;
29 import org.apache.calcite.rel.RelCollations;
30 import org.apache.calcite.rel.RelDistribution;
31 import org.apache.calcite.rel.RelDistributions;
32 import org.apache.calcite.rel.RelFieldCollation;
33 import org.apache.calcite.rel.RelInput;
34 import org.apache.calcite.rel.RelNode;
35 import org.apache.calcite.rel.core.AggregateCall;
36 import org.apache.calcite.rel.core.CorrelationId;
37 import org.apache.calcite.rel.core.TableModify.Operation;
38 import org.apache.calcite.rel.type.RelDataType;
39 import org.apache.calcite.rel.type.RelDataTypeFactory;
40 import org.apache.calcite.rel.type.RelDataTypeField;
41 import org.apache.calcite.rex.RexBuilder;
42 import org.apache.calcite.rex.RexCall;
43 import org.apache.calcite.rex.RexCorrelVariable;
44 import org.apache.calcite.rex.RexFieldAccess;
45 import org.apache.calcite.rex.RexFieldCollation;
46 import org.apache.calcite.rex.RexInputRef;
47 import org.apache.calcite.rex.RexLiteral;
48 import org.apache.calcite.rex.RexNode;
49 import org.apache.calcite.rex.RexOver;
50 import org.apache.calcite.rex.RexSubQuery;
51 import org.apache.calcite.rex.RexWindow;
52 import org.apache.calcite.rex.RexWindowBound;
54 import org.apache.calcite.sql.SqlAggFunction;
55 import org.apache.calcite.sql.SqlFunction;
56 import org.apache.calcite.sql.SqlIdentifier;
57 import org.apache.calcite.sql.SqlKind;
59 import org.apache.calcite.sql.SqlOperatorTable;
60 import org.apache.calcite.sql.SqlSyntax;
61 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
62 import org.apache.calcite.sql.fun.SqlTrimFunction;
63 import org.apache.calcite.sql.type.SqlTypeName;
64 import org.apache.calcite.util.ImmutableBitSet;
65 import org.apache.calcite.util.JsonBuilder;
66 import org.apache.calcite.util.Util;
67 
68 import java.lang.reflect.Constructor;
69 import java.lang.reflect.InvocationTargetException;
70 import java.math.BigDecimal;
71 import java.util.ArrayList;
72 import java.util.HashMap;
73 import java.util.List;
74 import java.util.Map;
75 
80 public class HeavyDBRelJson {
81  private final Map<String, Constructor> constructorMap =
82  new HashMap<String, Constructor>();
83  private final JsonBuilder jsonBuilder;
84 
85  public static final List<String> PACKAGES = ImmutableList.of("org.apache.calcite.rel.",
86  "org.apache.calcite.rel.core.",
87  "org.apache.calcite.rel.logical.",
88  "org.apache.calcite.adapter.jdbc.",
89  "org.apache.calcite.adapter.enumerable.",
90  "org.apache.calcite.adapter.jdbc.JdbcRules$");
91 
92  public HeavyDBRelJson(JsonBuilder jsonBuilder) {
93  this.jsonBuilder = jsonBuilder;
94  }
95 
96  public RelNode create(Map<String, Object> map) {
97  String type = (String) map.get("type");
98  Constructor constructor = getConstructor(type);
99  try {
100  return (RelNode) constructor.newInstance(map);
101  } catch (InstantiationException e) {
102  throw new RuntimeException("while invoking constructor for type '" + type + "'", e);
103  } catch (IllegalAccessException e) {
104  throw new RuntimeException("while invoking constructor for type '" + type + "'", e);
105  } catch (InvocationTargetException e) {
106  throw new RuntimeException("while invoking constructor for type '" + type + "'", e);
107  } catch (ClassCastException e) {
108  throw new RuntimeException("while invoking constructor for type '" + type + "'", e);
109  }
110  }
111 
112  public Constructor getConstructor(String type) {
113  Constructor constructor = constructorMap.get(type);
114  if (constructor == null) {
115  Class clazz = typeNameToClass(type);
116  try {
117  // noinspection unchecked
118  constructor = clazz.getConstructor(RelInput.class);
119  } catch (NoSuchMethodException e) {
120  throw new RuntimeException(
121  "class does not have required constructor, " + clazz + "(RelInput)");
122  }
123  constructorMap.put(type, constructor);
124  }
125  return constructor;
126  }
127 
132  public Class typeNameToClass(String type) {
133  if (!type.contains(".")) {
134  for (String package_ : PACKAGES) {
135  try {
136  return Class.forName(package_ + type);
137  } catch (ClassNotFoundException e) {
138  // ignore
139  }
140  }
141  }
142  try {
143  return Class.forName(type);
144  } catch (ClassNotFoundException e) {
145  throw new RuntimeException("unknown type " + type);
146  }
147  }
148 
152  public String classToTypeName(Class<? extends RelNode> class_) {
153  final String canonicalName = class_.getName();
154  for (String package_ : PACKAGES) {
155  if (canonicalName.startsWith(package_)) {
156  String remaining = canonicalName.substring(package_.length());
157  if (remaining.indexOf('.') < 0 && remaining.indexOf('$') < 0) {
158  return remaining;
159  }
160  }
161  }
162  return canonicalName;
163  }
164 
165  public Object toJson(RelCollationImpl node) {
166  final List<Object> list = new ArrayList<Object>();
167  for (RelFieldCollation fieldCollation : node.getFieldCollations()) {
168  final Map<String, Object> map = jsonBuilder.map();
169  map.put("field", fieldCollation.getFieldIndex());
170  map.put("direction", fieldCollation.getDirection().name());
171  map.put("nulls", fieldCollation.nullDirection.name());
172  list.add(map);
173  }
174  return list;
175  }
176 
177  public Object toJson(RexFieldCollation node) {
178  final Map<String, Object> map = jsonBuilder.map();
179  map.put("field", toJson(node.left));
180  map.put("direction", node.getDirection().name());
181  map.put("nulls", node.getNullDirection().name());
182  return map;
183  }
184 
185  public RelCollation toCollation(List<Map<String, Object>> jsonFieldCollations) {
186  final List<RelFieldCollation> fieldCollations = new ArrayList<RelFieldCollation>();
187  for (Map<String, Object> map : jsonFieldCollations) {
188  fieldCollations.add(toFieldCollation(map));
189  }
190  return RelCollations.of(fieldCollations);
191  }
192 
193  public RelFieldCollation toFieldCollation(Map<String, Object> map) {
194  final Integer field = (Integer) map.get("field");
195  final RelFieldCollation.Direction direction = Util.enumVal(
196  RelFieldCollation.Direction.class, (String) map.get("direction"));
197  final RelFieldCollation.NullDirection nullDirection = Util.enumVal(
198  RelFieldCollation.NullDirection.class, (String) map.get("nulls"));
199  return new RelFieldCollation(field, direction, nullDirection);
200  }
201 
202  public RelDistribution toDistribution(Object o) {
203  return RelDistributions.ANY; // TODO:
204  }
205 
206  public RelDataType toType(RelDataTypeFactory typeFactory, Object o) {
207  if (o instanceof List) {
208  @SuppressWarnings("unchecked")
209  final List<Map<String, Object>> jsonList = (List<Map<String, Object>>) o;
210  final RelDataTypeFactory.Builder builder = typeFactory.builder();
211  for (Map<String, Object> jsonMap : jsonList) {
212  builder.add((String) jsonMap.get("name"), toType(typeFactory, jsonMap));
213  }
214  return builder.build();
215  } else {
216  final Map<String, Object> map = (Map<String, Object>) o;
217  final SqlTypeName sqlTypeName =
218  Util.enumVal(SqlTypeName.class, (String) map.get("type"));
219  final Integer precision = (Integer) map.get("precision");
220  final Integer scale = (Integer) map.get("scale");
221  final RelDataType type;
222  if (precision == null) {
223  type = typeFactory.createSqlType(sqlTypeName);
224  } else if (scale == null) {
225  type = typeFactory.createSqlType(sqlTypeName, precision);
226  } else {
227  type = typeFactory.createSqlType(sqlTypeName, precision, scale);
228  }
229  final boolean nullable = (Boolean) map.get("nullable");
230  return typeFactory.createTypeWithNullability(type, nullable);
231  }
232  }
233 
234  public Object toJson(AggregateCall node) {
235  final Map<String, Object> map = jsonBuilder.map();
236  map.put("agg", toJson(node.getAggregation()));
237  map.put("type", toJson(node.getType()));
238  map.put("distinct", node.isDistinct());
239  map.put("operands", node.getArgList());
240  return map;
241  }
242 
243  Object toJson(Object value) {
244  if (value == null || value instanceof Number || value instanceof String
245  || value instanceof Boolean) {
246  return value;
247  } else if (value instanceof RexNode) {
248  return toJson((RexNode) value);
249  } else if (value instanceof CorrelationId) {
250  return toJson((CorrelationId) value);
251  } else if (value instanceof List) {
252  final List<Object> list = jsonBuilder.list();
253  for (Object o : (List) value) {
254  list.add(toJson(o));
255  }
256  return list;
257  } else if (value instanceof ImmutableBitSet) {
258  final List<Object> list = jsonBuilder.list();
259  for (Integer integer : (ImmutableBitSet) value) {
260  list.add(toJson(integer));
261  }
262  return list;
263  } else if (value instanceof AggregateCall) {
264  return toJson((AggregateCall) value);
265  } else if (value instanceof RelCollationImpl) {
266  return toJson((RelCollationImpl) value);
267  } else if (value instanceof RexFieldCollation) {
268  return toJson((RexFieldCollation) value);
269  } else if (value instanceof RelDataType) {
270  return toJson((RelDataType) value);
271  } else if (value instanceof RelDataTypeField) {
272  return toJson((RelDataTypeField) value);
273  } else if (value instanceof JoinType) {
274  return value.toString();
275  } else if (value instanceof Operation) {
276  return value.toString();
277  } else {
278  if (value.toString().contains("$cor")) {
279  throw new UnsupportedOperationException(
280  "Unable to decorrelate one of the correlated subqueries.");
281  }
282  throw new UnsupportedOperationException("type not serializable: " + value
283  + " (type " + value.getClass().getCanonicalName() + ")");
284  }
285  }
286 
287  private Object toJson(RelDataType node) {
288  if (node.isStruct()) {
289  final List<Object> list = jsonBuilder.list();
290  for (RelDataTypeField field : node.getFieldList()) {
291  list.add(toJson(field));
292  }
293  return list;
294  } else {
295  final Map<String, Object> map = jsonBuilder.map();
296  map.put("type", node.getSqlTypeName().name());
297  map.put("nullable", node.isNullable());
298  if (node.getSqlTypeName().allowsPrec()) {
299  map.put("precision", node.getPrecision());
300  }
301  if (node.getSqlTypeName().allowsScale()) {
302  map.put("scale", node.getScale());
303  }
304  return map;
305  }
306  }
307 
308  private Object toJson(RelDataTypeField node) {
309  final Map<String, Object> map = (Map<String, Object>) toJson(node.getType());
310  map.put("name", node.getName());
311  return map;
312  }
313 
314  private Object toJson(CorrelationId node) {
315  return node.getId();
316  }
317 
318  private Object toJson(final RexWindowBound window_bound) {
319  final Map<String, Object> map = jsonBuilder.map();
320  map.put("unbounded", toJson(window_bound.isUnbounded()));
321  map.put("preceding", toJson(window_bound.isPreceding()));
322  map.put("following", toJson(window_bound.isFollowing()));
323  map.put("is_current_row", toJson(window_bound.isCurrentRow()));
324  map.put("offset",
325  window_bound.getOffset() != null ? toJson(window_bound.getOffset()) : null);
326  map.put("order_key", toJson(window_bound.getOrderKey()));
327  return map;
328  }
329 
330  private Object toJson(RexNode node) {
331  final Map<String, Object> map;
332  switch (node.getKind()) {
333  case FIELD_ACCESS:
334  map = jsonBuilder.map();
335  final RexFieldAccess fieldAccess = (RexFieldAccess) node;
336  map.put("field", fieldAccess.getField().getName());
337  map.put("expr", toJson(fieldAccess.getReferenceExpr()));
338  return map;
339  case LITERAL:
340  final RexLiteral literal = (RexLiteral) node;
341  final Object value2 = literal.getValue2();
342  map = jsonBuilder.map();
343  if (value2 instanceof TimeUnitRange || value2 instanceof SqlTrimFunction.Flag) {
344  map.put("literal", value2.toString());
345  } else {
346  map.put("literal", value2);
347  }
348  map.put("type", literal.getTypeName().name());
349  map.put("target_type", literal.getType().getSqlTypeName().toString());
350  final Object value = literal.getValue();
351  if (value instanceof BigDecimal) {
352  map.put("scale", ((BigDecimal) value).scale());
353  map.put("precision", ((BigDecimal) value).precision());
354  } else {
355  map.put("scale", literal.getType().getScale());
356  map.put("precision", literal.getType().getPrecision());
357  }
358  map.put("type_scale", literal.getType().getScale());
359  map.put("type_precision", literal.getType().getPrecision());
360  return map;
361  case INPUT_REF:
362  map = jsonBuilder.map();
363  map.put("input", ((RexInputRef) node).getIndex());
364  return map;
365  case CORREL_VARIABLE:
366  map = jsonBuilder.map();
367  map.put("correl", ((RexCorrelVariable) node).getName());
368  map.put("type", toJson(node.getType()));
369  return map;
370  default:
371  if (node instanceof RexCall) {
372  final RexCall call = (RexCall) node;
373  map = jsonBuilder.map();
374  map.put("op", toJson(call.getOperator()));
375  final List<Object> list = jsonBuilder.list();
376  for (RexNode operand : call.getOperands()) {
377  list.add(toJson(operand));
378  }
379  map.put("operands", list);
380  map.put("type", toJson(node.getType()));
381  if (node instanceof RexSubQuery) {
382  final HeavyDBRelJsonWriter subqueryWriter = new HeavyDBRelJsonWriter();
383  ((RexSubQuery) node).rel.explain(subqueryWriter);
384  map.put("subquery", subqueryWriter.asJsonMap());
385  }
386  if (node instanceof RexOver) {
387  final RexWindow window = ((RexOver) node).getWindow();
388  final List<Object> partitionKeyList = jsonBuilder.list();
389  for (final RexNode partitionKey : window.partitionKeys) {
390  partitionKeyList.add(toJson(partitionKey));
391  }
392  map.put("partition_keys", partitionKeyList);
393  final List<Object> orderKeyList = jsonBuilder.list();
394  for (final RexFieldCollation orderKey : window.orderKeys) {
395  orderKeyList.add(toJson(orderKey));
396  }
397  map.put("order_keys", orderKeyList);
398  RexWindowBound lower_bound = window.getLowerBound();
399  RexWindowBound upper_bound = window.getUpperBound();
400  map.put("lower_bound", toJson(lower_bound));
401  map.put("upper_bound", toJson(upper_bound));
402  map.put("is_rows", toJson(window.isRows()));
403  }
404  if (call.getOperator() instanceof SqlFunction) {
405  switch (((SqlFunction) call.getOperator()).getFunctionType()) {
406  case USER_DEFINED_CONSTRUCTOR:
407  case USER_DEFINED_FUNCTION:
408  case USER_DEFINED_PROCEDURE:
409  case USER_DEFINED_SPECIFIC_FUNCTION:
410  map.put("class", call.getOperator().getClass().getName());
411  }
412  }
413  return map;
414  }
415  throw new UnsupportedOperationException("unknown rex " + node);
416  }
417  }
418 
419  RexNode toRex(RelInput relInput, Object o) {
420  final RelOptCluster cluster = relInput.getCluster();
421  final RexBuilder rexBuilder = cluster.getRexBuilder();
422  if (o == null) {
423  return null;
424  } else if (o instanceof Map) {
425  Map map = (Map) o;
426  final String op = (String) map.get("op");
427  if (op != null) {
428  final List operands = (List) map.get("operands");
429  final Object jsonType = map.get("type");
430  final SqlOperator operator = toOp(op, map);
431  final List<RexNode> rexOperands = toRexList(relInput, operands);
432  RelDataType type;
433  if (jsonType != null) {
434  type = toType(cluster.getTypeFactory(), jsonType);
435  } else {
436  type = rexBuilder.deriveReturnType(operator, rexOperands);
437  }
438  return rexBuilder.makeCall(type, operator, rexOperands);
439  }
440  final Integer input = (Integer) map.get("input");
441  if (input != null) {
442  List<RelNode> inputNodes = relInput.getInputs();
443  int i = input;
444  for (RelNode inputNode : inputNodes) {
445  final RelDataType rowType = inputNode.getRowType();
446  if (i < rowType.getFieldCount()) {
447  final RelDataTypeField field = rowType.getFieldList().get(i);
448  return rexBuilder.makeInputRef(field.getType(), input);
449  }
450  i -= rowType.getFieldCount();
451  }
452  throw new RuntimeException("input field " + input + " is out of range");
453  }
454  final String field = (String) map.get("field");
455  if (field != null) {
456  final Object jsonExpr = map.get("expr");
457  final RexNode expr = toRex(relInput, jsonExpr);
458  return rexBuilder.makeFieldAccess(expr, field, true);
459  }
460  final String correl = (String) map.get("correl");
461  if (correl != null) {
462  final Object jsonType = map.get("type");
463  RelDataType type = toType(cluster.getTypeFactory(), jsonType);
464  return rexBuilder.makeCorrel(type, new CorrelationId(correl));
465  }
466  if (map.containsKey("literal")) {
467  final Object literal = map.get("literal");
468  final SqlTypeName sqlTypeName =
469  Util.enumVal(SqlTypeName.class, (String) map.get("type"));
470  if (literal == null) {
471  return rexBuilder.makeNullLiteral(
472  cluster.getTypeFactory().createSqlType(sqlTypeName));
473  }
474 
475  // HEAVY.AI serializes numeric literals differently, we need more data
476  // then the number. So, we need to have a special case for that.
477  if (literal instanceof Number) {
478  final SqlTypeName targetTypeName =
479  Util.enumVal(SqlTypeName.class, (String) map.get("target_type"));
480  final long scale = ((Number) map.get("scale")).longValue();
481  final long precision = ((Number) map.get("precision")).longValue();
482  final long typeScale = ((Number) map.get("type_scale")).longValue();
483  final long typePrecision = ((Number) map.get("type_precision")).longValue();
484  RelDataTypeFactory typeFactory = cluster.getTypeFactory();
485 
486  BigDecimal value =
487  BigDecimal.valueOf(((Number) literal).longValue(), (int) scale);
488 
489  if (typeScale != 0 && typeScale != -2147483648) {
490  return rexBuilder.makeLiteral(value,
491  typeFactory.createSqlType(
492  SqlTypeName.DECIMAL, (int) typePrecision, (int) typeScale),
493  false);
494  } else {
495  return rexBuilder.makeLiteral(
496  value, typeFactory.createSqlType(targetTypeName), false);
497  }
498  } else {
499  return toRex(relInput, literal);
500  }
501  }
502  throw new UnsupportedOperationException("cannot convert to rex " + o);
503  } else if (o instanceof Boolean) {
504  return rexBuilder.makeLiteral((Boolean) o);
505  } else if (o instanceof String) {
506  return rexBuilder.makeLiteral((String) o);
507  } else if (o instanceof Number) {
508  final Number number = (Number) o;
509  if (number instanceof Double || number instanceof Float) {
510  return rexBuilder.makeApproxLiteral(BigDecimal.valueOf(number.doubleValue()));
511  } else {
512  return rexBuilder.makeExactLiteral(BigDecimal.valueOf(number.longValue()));
513  }
514  } else {
515  throw new UnsupportedOperationException("cannot convert to rex " + o);
516  }
517  }
518 
519  private List<RexNode> toRexList(RelInput relInput, List operands) {
520  final List<RexNode> list = new ArrayList<RexNode>();
521  for (Object operand : operands) {
522  list.add(toRex(relInput, operand));
523  }
524  return list;
525  }
526 
527  private SqlOperator toOp(String op) {
528  return toOp(op, new HashMap<>());
529  }
530 
531  private SqlOperator toOp(String op, Map<String, Object> map) {
532  // TODO: build a map, for more efficient lookup
533  // TODO: look up based on SqlKind
534  HeavyDBSqlOperatorTable operatorTable =
535  new HeavyDBSqlOperatorTable(SqlStdOperatorTable.instance());
536  operatorTable.addUDF(null);
537  final List<SqlOperator> operatorList = operatorTable.getOperatorList();
538  for (SqlOperator operator : operatorList) {
539  if (operator.getName().equals(op)) {
540  return operator;
541  }
542  }
543  String class_ = (String) map.get("class");
544  if (class_ != null) {
545  try {
546  return (SqlOperator) Class.forName(class_).newInstance();
547  } catch (InstantiationException e) {
548  throw new RuntimeException(e);
549  } catch (IllegalAccessException e) {
550  throw new RuntimeException(e);
551  } catch (ClassNotFoundException e) {
552  throw new RuntimeException(e);
553  }
554  }
555  throw new RuntimeException("Operator " + op + " does not supported");
556  }
557 
558  SqlOperator toOp(RelInput relInput, String name) {
559  // in case different operator has the same kind, check with both name and kind.
560  String kind = name;
561  String syntax = "FUNCTION";
562  SqlKind sqlKind = SqlKind.valueOf(kind);
563  SqlSyntax sqlSyntax = SqlSyntax.valueOf(syntax);
564  HeavyDBSqlOperatorTable operatorTable =
565  new HeavyDBSqlOperatorTable(SqlStdOperatorTable.instance());
566  operatorTable.addUDF(null);
567  final List<SqlOperator> operators = operatorTable.getOperatorList();
568  List<String> names = new ArrayList<>();
569  for (SqlOperator operator : operators) {
570  names.add(operator.toString());
571  if (operator.getName().equals(name)) {
572  return operator;
573  }
574  }
575  throw new RuntimeException("Aggregation function with name " + name
576  + " not found, search in " + names.toString());
577  }
578 
579  SqlAggFunction toAggregation(String agg) {
580  return (SqlAggFunction) toOp(agg);
581  }
582 
583  SqlAggFunction toAggregation(RelInput relInput, String agg) {
584  return (SqlAggFunction) toOp(relInput, agg);
585  }
586 
587  private String toJson(SqlOperator operator) {
588  // User-defined operators are not yet handled.
589  return operator.getName();
590  }
591 }
592 
593 // End RelJson.java
DEVICE auto upper_bound(ARGS &&...args)
Definition: gpu_enabled.h:123
RelCollation toCollation(List< Map< String, Object >> jsonFieldCollations)
JoinType
Definition: sqldefs.h:238
List< RexNode > toRexList(RelInput relInput, List operands)
final Map< String, Constructor > constructorMap
SqlOperator toOp(String op, Map< String, Object > map)
SqlAggFunction toAggregation(RelInput relInput, String agg)
const rapidjson::Value & field(const rapidjson::Value &obj, const char field[]) noexcept
Definition: JsonAccessors.h:33
String classToTypeName(Class<?extends RelNode > class_)
SqlOperator toOp(RelInput relInput, String name)
RexNode toRex(RelInput relInput, Object o)
RelDataType toType(RelDataTypeFactory typeFactory, Object o)
RelFieldCollation toFieldCollation(Map< String, Object > map)
DEVICE auto lower_bound(ARGS &&...args)
Definition: gpu_enabled.h:78
RelNode create(Map< String, Object > map)
string name
Definition: setup.in.py:72
Object toJson(final RexWindowBound window_bound)