"""GDB Pretty-printers for types in mongo::optimizer.""" import os import sys from pathlib import Path import gdb import gdb.printing if not gdb: sys.path.insert(0, str(Path(os.path.abspath(__file__)).parent.parent.parent)) from buildscripts.gdb.mongo import get_boost_optional, lookup_type OPTIMIZER_NS = "mongo::optimizer" # Tracks the indentation for Op* tree types. operator_indent_level = 0 def strip_namespace(value): return str(value).split("::")[-1] def eval_print_fn(val, print_fn): """Evaluate a print function, and return the resulting string.""" # The generated output from explain contains the string "\n" (two characters) # replace them with a single EOL character so that GDB prints multi-line # explains nicely. pp_result = print_fn(val) pp_str = str(pp_result).replace("\"", "").replace("\\n", "\n") return pp_str class OptimizerTypePrinter(object): """Base class that pretty prints via a single argument C++ function.""" def __init__(self, val, print_fn_name): """Initialize base printer.""" self.val = val print_fn_symbol = gdb.lookup_symbol(print_fn_name)[0] if print_fn_symbol is None: # Couldn't find the function, this could be due to a variety of reasons but mostly # likely it is not included and/or optimized out of the executable being debugged # (e.g. there are some explain helpers which are only called from unit tests so won't # be included in a mongod binary). print("Warning: Could not find pretty print function: " + print_fn_name) self.print_fn = None else: self.print_fn = print_fn_symbol.value() @staticmethod def display_hint(): """Display hint.""" return None def to_string(self): """Return string for printing.""" if self.print_fn is None: return f"<{self.val.type}>" return eval_print_fn(self.val, self.print_fn) class IntervalPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::IntervalRequirement.""" def __init__(self, val): """Initialize IntervalPrinter.""" super().__init__(val, "ExplainGenerator::explainInterval") class CompoundIntervalPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::CompoundIntervalRequirement.""" def __init__(self, val): """Initialize CompoundIntervalPrinter.""" super().__init__(val, "ExplainGenerator::explainCompoundInterval") class CandidateIndexEntryPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::CandidateIndexEntry.""" def __init__(self, val): """Initialize CandidateIndexEntryPrinter.""" super().__init__(val, "ExplainGenerator::explainCandidateIndex") class IntervalExprPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::IntervalRequirement::Node.""" def __init__(self, val): """Initialize IntervalExprPrinter.""" super().__init__(val, "ExplainGenerator::explainIntervalExpr") class PSRExprPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::PSRExpr::Node.""" def __init__(self, val): """Initialize PSRExprPrinter.""" super().__init__(val, "ExplainGenerator::explainPartialSchemaReqExpr") class StrongStringAliasPrinter(object): """Pretty-printer for mongo::optimizer::StrongStringAlias.""" def __init__(self, val): """Initialize StrongStringAliasPrinter.""" self.val = val @staticmethod def display_hint(): """Display hint.""" return None def to_string(self): return self.val["_value"] class MemoPrinter(OptimizerTypePrinter): """Pretty-printer for mongo::optimizer::cascades::Memo.""" def __init__(self, val): """Initialize MemoPrinter.""" super().__init__(val, "ExplainGenerator::explainMemo") class FixedArityNodePrinter(object): """Pretty-printer for OpFixedArity.""" def __init__(self, val, arity, name): """Initialize FixedArityNodePrinter.""" self.val = val self.arity = arity self.name = name self.custom_children = [] @staticmethod def display_hint(): """Display hint.""" return None def children(self): """children.""" global operator_indent_level prior_indent = operator_indent_level current_indent = operator_indent_level + self.arity + len(self.custom_children) - 1 for child in self.custom_children: lhs = "\n" for _ in range(current_indent): lhs += "| " operator_indent_level = current_indent yield lhs, child current_indent -= 1 for i in range(self.arity): lhs = "\n" for _ in range(current_indent): lhs += "| " # A little weird, but most ABTs prefer to print the last child first. operator_indent_level = current_indent yield lhs, self.val["_nodes"][self.arity - i - 1] current_indent -= 1 operator_indent_level = prior_indent # Adds a custom child node which is not directly contained in the "_nodes" member variable. def add_child(self, child): self.custom_children.append(child) def to_string(self): # Default for nodes which just print their type. return self.name class Vector(object): def __init__(self, vec): self.vec = vec self.start = vec['_M_impl']['_M_start'] self.finish = vec['_M_impl']['_M_finish'] def __iter__(self): item = self.start while item != self.finish: elt = item.dereference() item = item + 1 yield elt def count(self): item_type = self.vec.type.template_argument(0) return int((int(self.finish) - int(self.start)) / item_type.sizeof) def get(self, index): if index > self.count() - 1: raise gdb.GdbError("Invalid Vector access at index {} with size {}".format( index, self.count())) item = self.start + index return item.dereference() class DynamicArityNodePrinter(FixedArityNodePrinter): """Pretty-printer for OpDynamicArity.""" def __init__(self, val, minArity, name): """Initialize DynamicArityNodePrinter.""" super().__init__(val, minArity, name) self.dynamic_nodes = Vector(self.val["_dyNodes"]) self.dynamic_count = self.dynamic_nodes.count() def children(self): """children.""" global operator_indent_level prior_indent = operator_indent_level operator_indent_level += self.dynamic_count for res in super().children(): yield res current_indent = operator_indent_level - 1 for child in self.dynamic_nodes: lhs = "\n" for _ in range(current_indent): lhs += "| " operator_indent_level = current_indent yield lhs, child current_indent -= 1 operator_indent_level = prior_indent class ExpressionBinderPrinter(DynamicArityNodePrinter): """Pretty-printer for ExpressionBinder.""" def __init__(self, val): """Initialize ExpressionBinderPrinter.""" super().__init__(val, 0, "Binder") def to_string(self): res = "Binder[{" bindings = Vector(self.val["_names"]) for name in bindings: res += str(name) + " " res += "}]" return res class FunctionCallPrinter(DynamicArityNodePrinter): """Pretty-printer for FunctionCall.""" def __init__(self, val): """Initialize FunctionCallPrinter.""" super().__init__(val, 0, "FunctionCall") def to_string(self): return "FunctionCall[{}]".format(self.val["_name"]) class UnionNodePrinter(DynamicArityNodePrinter): """Pretty-printer for UnionNode.""" def __init__(self, val): """Initialize UnionNodePrinter.""" super().__init__(val, 2, "Union") class ScanNodePrinter(object): """Pretty-printer for ScanNode.""" def __init__(self, val): """Initialize ScanNodePrinter.""" self.val = val # Use the FixedArityNodePrinter to handle access to the ExpressionBinder child. _, self.binder = next(FixedArityNodePrinter(self.val, 1, "Scan").children()) def get_bound_projection(self): pp = PolyValuePrinter(self.binder) dynamic_type = lookup_type(pp.get_dynamic_type()).strip_typedefs() binder = pp.cast_control_block(dynamic_type) bound_projections = Vector(binder["_names"]) if bound_projections.count() != 1: return "" return str(bound_projections.get(0)) def to_string(self): return "Scan[{}, {}]".format(self.val["_scanDefName"], self.get_bound_projection()) class FilterNodePrinter(FixedArityNodePrinter): """Pretty-printer for FilterNode.""" def __init__(self, val): """Initialize FilterNodePrinter.""" super().__init__(val, 2, "Filter") class EvaluationNodePrinter(FixedArityNodePrinter): """Pretty-printer for EvaluationNode.""" def __init__(self, val): """Initialize EvaluationNodePrinter.""" super().__init__(val, 2, "Evaluation") class ConstantPrinter(object): """Pretty-printer for Constant.""" def __init__(self, val): """Initialize ConstantPrinter.""" self.val = val @staticmethod def display_hint(): """Display hint.""" return None @staticmethod def print_sbe_value(tag, value): value_print_fn = "mongo::sbe::value::print" (print_fn_symbol, _) = gdb.lookup_symbol(value_print_fn) if print_fn_symbol is None: raise gdb.GdbError("Could not find pretty print function: " + value_print_fn) print_fn = print_fn_symbol.value() return print_fn(tag, value) def to_string(self): return "Constant[{}]".format( ConstantPrinter.print_sbe_value(self.val["_tag"], self.val["_val"])) class VariablePrinter(object): """Pretty-printer for Variable.""" def __init__(self, val): """Initialize VariablePrinter.""" self.val = val @staticmethod def display_hint(): """Display hint.""" return None def to_string(self): return "Variable[{}]".format(self.val["_name"]) class RootNodePrinter(FixedArityNodePrinter): """Pretty-printer for RootNode.""" def __init__(self, val): """Initialize RootNodePrinter.""" # The second child (References) of the RootNode will be printed inline, but allow the base # class to print the node child. super().__init__(val, 1, "RootNode") def to_string(self): projections = Vector(self.val["_property"]["_projections"]["_vector"]) return "\nRoot[{}]".format(projections.get(0)) class GroupByNodePrinter(FixedArityNodePrinter): """Pretty-printer for GroupByNode.""" def __init__(self, val): """Initialize GroupByNodePrinter.""" super().__init__(val, 5, "GroupBy") class PathComparePrinter(FixedArityNodePrinter): """Pretty-printer for PathCompare.""" def __init__(self, val): """Initialize PathComparePrinter.""" super().__init__(val, 1, "PathCompare") def to_string(self): return "PathCompare[{}]".format(strip_namespace(self.val["_cmp"])) class PathTraversePrinter(FixedArityNodePrinter): """Pretty-printer for PathTraverse.""" def __init__(self, val): """Initialize PathTraversePrinter.""" super().__init__(val, 1, "PathTraverse") def to_string(self): depth = self.val["_maxDepth"] return "PathTraverse[{}]".format(str(depth) if depth != 0 else "inf") class PathGetPrinter(FixedArityNodePrinter): """Pretty-printer for PathGet.""" def __init__(self, val): """Initialize PathGetPrinter.""" super().__init__(val, 1, "PathGet") def to_string(self): return "PathGet[{}]".format(self.val["_name"]) class EvalFilterPrinter(FixedArityNodePrinter): """Pretty-printer for EvalFilter.""" def __init__(self, val): """Initialize EvalFilterPrinter.""" super().__init__(val, 2, "EvalFilter") class UnaryOpPrinter(FixedArityNodePrinter): """Pretty-printer for UnaryOp.""" def __init__(self, val): """Initialize UnaryOpPrinter.""" super().__init__(val, 1, "UnaryOp") def to_string(self): return "UnaryOp[{}]".format(strip_namespace(self.val["_op"])) class BinaryOpPrinter(FixedArityNodePrinter): """Pretty-printer for BinaryOp.""" def __init__(self, val): """Initialize BinaryOpPrinter.""" super().__init__(val, 2, "BinaryOp") def to_string(self): return "BinaryOp[{}]".format(strip_namespace(self.val["_op"])) class EvalPathPrinter(FixedArityNodePrinter): """Pretty-printer for EvalPath.""" def __init__(self, val): """Initialize EvalPathPrinter.""" super().__init__(val, 2, "EvalPath") class PathComposeMPrinter(FixedArityNodePrinter): """Pretty-printer for PathComposeM.""" def __init__(self, val): """Initialize PathComposeMPrinter.""" super().__init__(val, 2, "PathComposeM") class PathComposeAPrinter(FixedArityNodePrinter): """Pretty-printer for PathComposeA.""" def __init__(self, val): """Initialize PathComposeAPrinter.""" super().__init__(val, 2, "PathComposeA") class BlackholePrinter(FixedArityNodePrinter): """Pretty-printer for Blackhole.""" def __init__(self, val): """Initialize BlackholePrinter.""" super().__init__(val, 0, "Blackhole") class IfPrinter(FixedArityNodePrinter): """Pretty-printer for If.""" def __init__(self, val): """Initialize IfPrinter.""" super().__init__(val, 3, "If") class LetPrinter(FixedArityNodePrinter): """Pretty-printer for Let.""" def __init__(self, val): """Initialize LetPrinter.""" super().__init__(val, 2, "Let") class PathFieldPrinter(FixedArityNodePrinter): """Pretty-printer for PathField.""" def __init__(self, val): """Initialize PathFieldPrinter.""" super().__init__(val, 1, "PathField") def to_string(self): return "PathField[{}]".format(self.val["_name"]) class PathConstantPrinter(FixedArityNodePrinter): """Pretty-printer for PathConstant.""" def __init__(self, val): """Initialize PathConstantPrinter.""" super().__init__(val, 1, "PathConstant") class PathLambdaPrinter(FixedArityNodePrinter): """Pretty-printer for PathLambda.""" def __init__(self, val): """Initialize PathLambdaPrinter.""" super().__init__(val, 1, "PathLambda") class PathIdentityPrinter(FixedArityNodePrinter): """Pretty-printer for PathIdentity.""" def __init__(self, val): """Initialize PathIdentityPrinter.""" super().__init__(val, 0, "PathIdentity") class PathDropPrinter(FixedArityNodePrinter): """Pretty-printer for PathDrop.""" def __init__(self, val): """Initialize PathDropPrinter.""" super().__init__(val, 0, "PathDrop") def to_string(self): return "PathDrop[{}]".format(str(self.val["_names"])) class PathKeepPrinter(FixedArityNodePrinter): """Pretty-printer for PathKeep.""" def __init__(self, val): """Initialize PathKeepPrinter.""" super().__init__(val, 0, "PathKeep") def to_string(self): return "PathKeep[{}]".format(str(self.val["_names"])) class PathObjPrinter(FixedArityNodePrinter): """Pretty-printer for PathObj.""" def __init__(self, val): """Initialize PathObjPrinter.""" super().__init__(val, 0, "PathObj") class PathArrPrinter(FixedArityNodePrinter): """Pretty-printer for PathArr.""" def __init__(self, val): """Initialize PathArrPrinter.""" super().__init__(val, 0, "PathArr") class PathDefaultPrinter(FixedArityNodePrinter): """Pretty-printer for PathDefault.""" def __init__(self, val): """Initialize PathDefaultPrinter.""" super().__init__(val, 1, "PathDefault") class LambdaAbstractionPrinter(FixedArityNodePrinter): """Pretty-printer for LambdaAbstraction.""" def __init__(self, val): """Initialize LambdaAbstractionPrinter.""" super().__init__(val, 1, "LambdaAbstraction") def to_string(self): return "LambdaAbstraction[{}]".format(self.val["_varName"]) class LambdaApplicationPrinter(FixedArityNodePrinter): """Pretty-printer for LambdaApplication.""" def __init__(self, val): """Initialize LambdaApplicationPrinter.""" super().__init__(val, 2, "LambdaApplication") class SourcePrinter(FixedArityNodePrinter): """Pretty-printer for Source.""" def __init__(self, val): """Initialize SourcePrinter.""" super().__init__(val, 0, "Source") class FieldProjectionMapPrinter(object): """Pretty-printer for FieldProjectionMap.""" def __init__(self, val): """Initialize FieldProjectionMapPrinter.""" self.val = val @staticmethod def display_hint(): """Display hint.""" return None def to_string(self): rid_proj = self.val["_ridProjection"] root_proj = self.val["_rootProjection"] res = "{" if get_boost_optional(rid_proj) is not None: res += ": " + str(rid_proj) + ", " if get_boost_optional(root_proj) is not None: res += ": " + str(root_proj) + ", " # Python reformats the string with embedded "=" characters, avoid that by replacing here. res += str(self.val["_fieldProjections"]).replace("=", ":").replace("{", "(").replace( "}", ")") res += "}" return res class PhysicalScanNodePrinter(FixedArityNodePrinter): """Pretty-printer for PhysicalScanNode.""" def __init__(self, val): """Initialize PhysicalScanNodePrinter.""" super().__init__(val, 1, "PhysicalScan") def to_string(self): return "PhysicalScan[{}, {}]".format( str(self.val["_fieldProjectionMap"]), str(self.val["_scanDefName"])) class ValueScanNodePrinter(FixedArityNodePrinter): """Pretty-printer for ValueScanNode.""" def __init__(self, val): """Initialize ValueScanNodePrinter.""" super().__init__(val, 1, "ValueScan") def to_string(self): return "ValueScan[hasRID={},arraySize={}]".format(self.val["_hasRID"], self.val["_arraySize"]) class CoScanNodePrinter(FixedArityNodePrinter): """Pretty-printer for CoScanNode.""" def __init__(self, val): """Initialize CoScanNodePrinter.""" super().__init__(val, 0, "CoScan") class IndexScanNodePrinter(FixedArityNodePrinter): """Pretty-printer for IndexScanNode.""" def __init__(self, val): """Initialize IndexScanNodePrinter.""" super().__init__(val, 1, "IndexScan") def to_string(self): return "IndexScan[{{{}}}, scanDef={}, indexDef={}, interval={}]".format( self.val["_fieldProjectionMap"], self.val["_scanDefName"], self.val["_indexDefName"], self.val["_indexInterval"]).replace("\n", "") class SeekNodePrinter(FixedArityNodePrinter): """Pretty-printer for SeekNode.""" def __init__(self, val): """Initialize SeekNodePrinter.""" super().__init__(val, 2, "Seek") def to_string(self): return "Seek[rid_projection: {}, {}, scanDef: {}]".format(self.val["_ridProjectionName"], self.val["_fieldProjectionMap"], self.val["_scanDefName"]) class MemoLogicalDelegatorNodePrinter(FixedArityNodePrinter): """Pretty-printer for MemoLogicalDelegatorNode.""" def __init__(self, val): """Initialize MemoLogicalDelegatorNodePrinter.""" super().__init__(val, 0, "MemoLogicalDelegator") def to_string(self): return "MemoLogicalDelegator[{}]".format(self.val["_groupId"]) class MemoPhysicalDelegatorNodePrinter(FixedArityNodePrinter): """Pretty-printer for MemoPhysicalDelegatorNode.""" def __init__(self, val): """Initialize MemoPhysicalDelegatorNodePrinter.""" super().__init__(val, 0, "MemoPhysicalDelegator") def to_string(self): return "MemoPhysicalDelegator[group: {}, index: {}]".format(self.val["_nodeId"]["_groupId"], self.val["_nodeId"]["_index"]) class ResidualRequirementPrinter(object): """Pretty-printer for ResidualRequirement.""" def __init__(self, val): """Initialize ResidualRequirementPrinter.""" self.val = val def to_string(self): key = self.val["_key"] req = self.val["_req"] res = "<" if get_boost_optional(key["_projectionName"]) is not None: res += "refProj: " + str(get_boost_optional(key["_projectionName"])) + ", " res += "path: '" + str(key["_path"]).replace("| ", "").replace("\n", " -> ") + "'" if get_boost_optional(req["_boundProjectionName"]) is not None: res += "boundProj: " + str(get_boost_optional(req["_boundProjectionName"])) + ", " res += ">" return res class ScanParamsPrinter(object): """Pretty-printer for ScanParams.""" def __init__(self, val): """Initialize ScanParamsPrinter.""" self.val = val @staticmethod def display_hint(): """Display hint.""" return None def to_string(self): res = "scan_params: (fields: " + str(self.val["_fieldProjectionMap"]) + ", " residual_reqs = get_boost_optional(self.val["_residualRequirements"]) if residual_reqs is not None: res += "residual: " + str(residual_reqs) res += ")" return res class SargableNodePrinter(FixedArityNodePrinter): """Pretty-printer for SargableNode.""" def __init__(self, val): """Initialize SargableNodePrinter.""" # Although Sargable technically has 3 children, avoid printing the refs (child1) and bind block (child2). super().__init__(val, 1, "Sargable") # Add children for requirements, candidateIndex, and scan_params. self.add_child(str(self.val["_reqMap"]).replace("\n", "")) self.add_child(self.print_candidate_indexes()) scan_params = get_boost_optional(self.val["_scanParams"]) if scan_params is not None: self.add_child(scan_params) def print_candidate_indexes(self): res = "candidateIndexes: [" indexes = Vector(self.val["_candidateIndexes"]) for i in range(indexes.count()): if i > 0: res += ", " res += "" res += "]" return res def to_string(self): return "Sargable [" + strip_namespace(str(self.val["_target"])) + "]" class RIDIntersectNodePrinter(FixedArityNodePrinter): """Pretty-printer for RIDIntersectNode.""" def __init__(self, val): """Initialize RIDIntersectNodePrinter.""" super().__init__(val, 2, "RIDIntersect") def to_string(self): return "RIDIntersect[" + str(self.val["_scanProjectionName"]) + "]" class RIDUnionNodePrinter(FixedArityNodePrinter): """Pretty-printer for RIDUnionNode.""" def __init__(self, val): """Initialize RIDUnionNodePrinter.""" super().__init__(val, 4, "RIDUnion") def to_string(self): return "RIDUnion[" + str(self.val["_scanProjectionName"]) + "]" def print_correlated_projections(projections): # Strip off the extra absl map prefix. return str(projections).split("elems ")[-1] class BinaryJoinNodePrinter(FixedArityNodePrinter): """Pretty-printer for BinaryJoinNode.""" def __init__(self, val): """Initialize BinaryJoinNodePrinter.""" super().__init__(val, 3, "BinaryJoin") def to_string(self): correlated = print_correlated_projections(self.val["_correlatedProjectionNames"]) return "BinaryJoin[type=" + str(strip_namespace( self.val["_joinType"])) + ", " + correlated + "]" def print_eq_join_condition(leftKeys, rightKeys): condition = "Condition[" for i in range(leftKeys.count()): condition += str(leftKeys.get(i)) + "==" + str(rightKeys.get(i)) + "," condition += "]" return condition class HashJoinNodePrinter(FixedArityNodePrinter): """Pretty-printer for HashJoinNode.""" def __init__(self, val): """Initialize HashJoinNodePrinter.""" super().__init__(val, 3, "HashJoin") # Manually add the child which prints the sets of keys. leftKeys = Vector(self.val["_leftKeys"]) rightKeys = Vector(self.val["_rightKeys"]) self.add_child(print_eq_join_condition(leftKeys, rightKeys)) def to_string(self): return "HashJoin[type=" + strip_namespace(self.val["_joinType"]) + "]" class MergeJoinNodePrinter(FixedArityNodePrinter): """Pretty-printer for MergeJoinNode.""" def __init__(self, val): """Initialize MergeJoinNodePrinter.""" super().__init__(val, 3, "MergeJoin") # Manually add the collation ops. collationOps = Vector(self.val["_collation"]) collationChild = "Collation[" + ", ".join(str(collation) for collation in collationOps) + "]" self.add_child(collationChild) # Manually add the child which prints the sets of keys. leftKeys = Vector(self.val["_leftKeys"]) rightKeys = Vector(self.val["_rightKeys"]) self.add_child(print_eq_join_condition(leftKeys, rightKeys)) def to_string(self): return "MergeJoin" def print_collation_req(req): spec = Vector(req["_spec"]) return ", ".join( str(entry["first"]) + ": " + strip_namespace(entry["second"]) for entry in spec) class SortedMergeNodePrinter(DynamicArityNodePrinter): """Pretty-printer for SortedMergeNode.""" def __init__(self, val): """Initialize SortedMergeNodePrinter.""" super().__init__(val, 2, "MergeJoin") self.add_child("collation[" + print_collation_req(self.val["_collationReq"]) + "]") def to_string(self): return "SortedMerge" class NestedLoopJoinNodePrinter(FixedArityNodePrinter): """Pretty-printer for NestedLoopJoinNode.""" def __init__(self, val): """Initialize NestedLoopJoinNodePrinter.""" super().__init__(val, 3, "NestedLoopJoin") def to_string(self): correlated = print_correlated_projections(self.val["_correlatedProjectionNames"]) return "NestedLoopJoin[type=" + strip_namespace( self.val["_joinType"]) + ", " + correlated + "]" class UnwindNodePrinter(FixedArityNodePrinter): """Pretty-printer for UnwindNode.""" def __init__(self, val): """Initialize UnwindNodePrinter.""" super().__init__(val, 3, "Unwind") class UniqueNodePrinter(FixedArityNodePrinter): """Pretty-printer for UniqueNode.""" def __init__(self, val): """Initialize UniqueNodePrinter.""" super().__init__(val, 2, "Unique") class SpoolProducerNodePrinter(FixedArityNodePrinter): """Pretty-printer for SpoolProducerNode.""" def __init__(self, val): """Initialize SpoolProducerNodePrinter.""" super().__init__(val, 4, "SpoolProducer") def to_string(self): return "SpoolProducer[" + strip_namespace(self.val["_type"]) + ", id:" + str( self.val["_spoolId"]) + "]" class SpoolConsumerNodePrinter(FixedArityNodePrinter): """Pretty-printer for SpoolConsumerNode.""" def __init__(self, val): """Initialize SpoolConsumerNodePrinter.""" super().__init__(val, 1, "SpoolConsumer") def to_string(self): return "SpoolConsumer[" + strip_namespace(self.val["_type"]) + ", id:" + str( self.val["_spoolId"]) + "]" class CollationNodePrinter(FixedArityNodePrinter): """Pretty-printer for CollationNode.""" def __init__(self, val): """Initialize CollationNodePrinter.""" # Don't print the references, will print them inline below. super().__init__(val, 1, "Collation") def to_string(self): return "Collation[" + print_collation_req(self.val["_property"]) + "]" class LimitSkipNodePrinter(FixedArityNodePrinter): """Pretty-printer for LimitSkipNode.""" def __init__(self, val): """Initialize LimitSkipNodePrinter.""" super().__init__(val, 1, "LimitSkip") def to_string(self): return "LimitSkip[limit: " + str(self.val["_property"]["_limit"]) + ", skip: " + str( self.val["_property"]["_skip"]) + "]" class ExchangeNodePrinter(FixedArityNodePrinter): """Pretty-printer for ExchangeNode.""" def __init__(self, val): """Initialize ExchangeNodePrinter.""" super().__init__(val, 2, "Exchange") def to_string(self): return "Exchange[type: " + str( self.val["_distribution"]["_distributionAndProjections"] ["_type"]) + ", projections: " + str( self.val["_distribution"]["_distributionAndProjections"]["_projectionNames"]) + "]" class ReferencesPrinter(DynamicArityNodePrinter): """Pretty-printer for References.""" def __init__(self, val): """Initialize ReferencesPrinter.""" super().__init__(val, 0, "References") class PolyValuePrinter(object): """Pretty-printer for PolyValue.""" def __init__(self, val): """Initialize PolyValuePrinter.""" self.val = val self.control_block = self.val["_object"] self.tag = self.control_block.dereference()["_tag"] if self.val.type.code == gdb.TYPE_CODE_REF: self.poly_type = self.val.type.target().strip_typedefs() else: self.poly_type = self.val.type.strip_typedefs() self.type_set = str(self.poly_type).split("<", 1)[1] if self.tag < 0: raise gdb.GdbError("Invalid PolyValue tag: {}, must be at least 0".format(self.tag)) # Check if the tag is out of range for the set of types that we know about. if self.tag > len(self.type_set.split(",")): raise gdb.GdbError("Unknown PolyValue tag: {} (max: {}), did you add a new one?".format( self.tag, str(self.type_set))) @staticmethod def display_hint(): """Display hint.""" return None def cast_control_block(self, target_type): return self.control_block.dereference().address.cast( target_type.pointer()).dereference()["_t"] def get_dynamic_type(self): # Build up the dynamic type for the particular variant of this PolyValue instance. This is # basically a reinterpret cast of the '_object' member variable to the correct instance # of ControlBlockVTable::ConcreteType where T is the template argument derived # from the _tag member variable. poly_type = self.val.type.template_argument(self.tag) dynamic_type = f"{OPTIMIZER_NS}::algebra::ControlBlockVTable<{poly_type.name}, " dynamic_type += self.type_set dynamic_type += "::ConcreteType" return dynamic_type def to_string(self): dynamic_type = self.get_dynamic_type() try: dynamic_type = lookup_type(dynamic_type).strip_typedefs() except gdb.error: return "Unknown PolyValue tag: {}, did you add a new one?".format(self.tag) # GDB automatically formats types with children, remove the extra characters to get the # output that we want. return str(self.cast_control_block(dynamic_type)).replace(" = ", "").replace("{", "").replace( "}", "") class AtomPrinter(object): """Pretty-printer for Atom.""" def __init__(self, val): """Initialize AtomPrinter.""" self.val = val def to_string(self): return self.val["_expr"] class ConjunctionPrinter(object): """Pretty-printer for Conjunction.""" def __init__(self, val, separator=" ^ "): """Initialize ConjunctionPrinter.""" self.val = val self.dynamic_nodes = Vector(self.val["_dyNodes"]) self.dynamic_count = self.dynamic_nodes.count() self.separator = separator def to_string(self): if self.dynamic_count == 0: return "" res = "" first = True for child in self.dynamic_nodes: if first: first = False else: res += self.separator res += str(child) return res class DisjunctionPrinter(ConjunctionPrinter): """Pretty-printer for Disjunction.""" def __init__(self, val): super().__init__(val, " U ") def bool_expr_type(T): return (f"{OPTIMIZER_NS}::algebra::PolyValue<" + f"{OPTIMIZER_NS}::BoolExpr<{T}>::Atom, " + f"{OPTIMIZER_NS}::BoolExpr<{T}>::Conjunction, " + f"{OPTIMIZER_NS}::BoolExpr<{T}>::Disjunction>") def register_optimizer_printers(pp): """Registers a number of pretty printers related to the CQF optimizer.""" # IntervalRequirement printer. pp.add("Interval", f"{OPTIMIZER_NS}::IntervalRequirement", False, IntervalPrinter) pp.add("CompoundInterval", f"{OPTIMIZER_NS}::CompoundIntervalRequirement", False, CompoundIntervalPrinter) # IntervalReqExpr::Node printer. pp.add( "IntervalExpr", bool_expr_type(f"{OPTIMIZER_NS}::IntervalRequirement"), False, IntervalExprPrinter, ) # PSRExpr printer. pp.add( "PSRExpr", bool_expr_type( f"std::pair<{OPTIMIZER_NS}::PartialSchemaKey, {OPTIMIZER_NS}::PartialSchemaRequirement> " ), False, PSRExprPrinter, ) # Memo printer. pp.add("Memo", f"{OPTIMIZER_NS}::cascades::Memo", False, MemoPrinter) # ResidualRequirement printer. pp.add("ResidualRequirement", f"{OPTIMIZER_NS}::ResidualRequirement", False, ResidualRequirementPrinter) # CandidateIndexEntry printer. pp.add("CandidateIndexEntry", f"{OPTIMIZER_NS}::CandidateIndexEntry", False, CandidateIndexEntryPrinter) # BoolExpr is handled by the PolyValue printer, but still need to add # printers for each of the possible bool expr types. bool_expr_types = ["Atom", "Conjunction", "Disjunction"] bool_exprs = ["ResidualRequirement"] for bool_type in bool_expr_types: for expr in bool_exprs: pp.add(bool_type, f"{OPTIMIZER_NS}::BoolExpr<{OPTIMIZER_NS}::{expr}>::{bool_type}", False, getattr(sys.modules[__name__], bool_type + "Printer")) # Utility types within the optimizer. pp.add("StrongStringAlias", f"{OPTIMIZER_NS}::StrongStringAlias", True, StrongStringAliasPrinter) pp.add("FieldProjectionMap", f"{OPTIMIZER_NS}::FieldProjectionMap", False, FieldProjectionMapPrinter) pp.add("ScanParams", f"{OPTIMIZER_NS}::ScanParams", False, ScanParamsPrinter) # Add the sub-printers for each of the possible ABT types. # This is the set of known ABT variants that GDB is aware of. When adding to this list, ensure # that a corresponding printer class exists with the name MyNewNodePrinter where "MyNewNode" # exactly matches the entry in this list. The printer class may choose to derive from one of # the arity printers to automatically print the children nodes. By default, just the name of # your node will be printed. If you want to display more information (e.g. scan def name), # then override the to_string() method. abt_type_set = [ "Blackhole", "Constant", "Variable", "UnaryOp", "BinaryOp", "If", "Let", "LambdaAbstraction", "LambdaApplication", "FunctionCall", "EvalPath", "EvalFilter", "Source", "PathConstant", "PathLambda", "PathIdentity", "PathDefault", "PathCompare", "PathDrop", "PathKeep", "PathObj", "PathArr", "PathTraverse", "PathField", "PathGet", "PathComposeM", "PathComposeA", "ScanNode", "PhysicalScanNode", "ValueScanNode", "CoScanNode", "IndexScanNode", "SeekNode", "MemoLogicalDelegatorNode", "MemoPhysicalDelegatorNode", "FilterNode", "EvaluationNode", "SargableNode", "RIDIntersectNode", "RIDUnionNode", "BinaryJoinNode", "HashJoinNode", "MergeJoinNode", "SortedMergeNode", "NestedLoopJoinNode", "UnionNode", "GroupByNode", "UnwindNode", "UniqueNode", "SpoolProducerNode", "SpoolConsumerNode", "CollationNode", "LimitSkipNode", "ExchangeNode", "RootNode", "References", "ExpressionBinder", ] for abt_type in abt_type_set: pp.add(abt_type, f"{OPTIMIZER_NS}::{abt_type}", False, getattr(sys.modules[__name__], abt_type + "Printer")) # Add the generic PolyValue printer which determines the exact type at runtime and attempts to # invoke the printer for that type. pp.add('PolyValue', OPTIMIZER_NS + "::algebra::PolyValue", True, PolyValuePrinter)