memgraph/tools/gdb-plugins/operator_tree.py
Teon Banek 2fbf2c7ff4 Handle dependent branches in basic Cartesian
Summary:
This change should correctly plan Cartesian which have dependent Filter
or Expand operators. Tests have been added for those cases. Other cases
are not yet supported and should throw an exception.

Reviewers: msantl, mtomic, buda

Reviewed By: mtomic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1426
2018-06-19 13:06:34 +02:00

115 lines
4.0 KiB
Python

import re
import gdb
def _logical_operator_type():
'''Returns the LogicalOperator gdb.Type'''
# This is a function, because the type may appear during gdb runtime.
# Therefore, we cannot assign it on import.
return gdb.lookup_type('query::plan::LogicalOperator')
def _iter_fields_and_base_classes(value):
'''Iterate all fields of value.type'''
types_to_process = [value.type]
while types_to_process:
for field in types_to_process.pop().fields():
if field.is_base_class:
types_to_process.append(field.type)
yield field
def _fields(value):
'''Return a list of value.type fields.'''
return [f for f in _iter_fields_and_base_classes(value)
if not f.is_base_class]
def _has_field(value, field_name):
'''Return True if value.type has a field named field_name.'''
return field_name in [f.name for f in _fields(value)]
def _base_classes(value):
'''Return a list of base classes for value.type.'''
return [f for f in _iter_fields_and_base_classes(value)
if f.is_base_class]
def _is_instance(value, type_):
'''Return True if value is an instance of type.'''
return value.type.unqualified() == type_ or \
type_ in [base.type for base in _base_classes(value)]
# Pattern for matching std::unique_ptr<T, Deleter> and std::shared_ptr<T>
_SMART_PTR_TYPE_PATTERN = \
re.compile('^std::(unique|shared)_ptr<(?P<pointee_type>[\w:]*)')
def _is_smart_ptr(maybe_smart_ptr, type_name=None):
type_ = maybe_smart_ptr.type.unqualified()
if type_.name is None:
return False
match = _SMART_PTR_TYPE_PATTERN.match(type_.name)
if match is None or type_name is None:
return bool(match)
return type_name == match.group('pointee_type')
def _smart_ptr_pointee(smart_ptr):
'''Returns the pointer to object in shared_ptr/unique_ptr.'''
# This function may not be needed when gdb adds dereferencing
# shared_ptr/unique_ptr via Python API.
if _has_field(smart_ptr, '_M_ptr'):
# shared_ptr
return smart_ptr['_M_ptr']
if _has_field(smart_ptr, '_M_t'):
# unique_ptr
smart_ptr = smart_ptr['_M_t']
if _has_field(smart_ptr, '_M_t'):
# Check for one more level of _M_t
smart_ptr = smart_ptr['_M_t']
if _has_field(smart_ptr, '_M_head_impl'):
return smart_ptr['_M_head_impl']
def _get_operator_input(operator):
'''Returns the input operator of given operator, if it has any.'''
if not _has_field(operator, 'input_'):
return None
input_op = _smart_ptr_pointee(operator['input_']).dereference()
return input_op.cast(input_op.dynamic_type)
class PrintOperatorTree(gdb.Command):
'''Print the tree of logical operators from the expression.'''
def __init__(self):
super(PrintOperatorTree, self).__init__("print-operator-tree",
gdb.COMMAND_USER,
gdb.COMPLETE_EXPRESSION)
def invoke(self, argument, from_tty):
try:
operator = gdb.parse_and_eval(argument)
except gdb.error as e:
raise gdb.GdbError(*e.args)
logical_operator_type = _logical_operator_type()
if operator.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_REF):
operator = operator.referenced_value()
if _is_smart_ptr(operator, 'query::plan::LogicalOperator'):
operator = _smart_ptr_pointee(operator).dereference()
if not _is_instance(operator, logical_operator_type):
raise gdb.GdbError("Expected a '%s', but got '%s'" %
(logical_operator_type, operator.type))
next_op = operator.cast(operator.dynamic_type)
tree = []
while next_op is not None:
tree.append('* %s <%s>' % (next_op.type.name, next_op.address))
next_op = _get_operator_input(next_op)
print('\n'.join(tree))
PrintOperatorTree()