belongsTo('host', Host::class) ->setJoinType('LEFT'); $relations->belongsTo('service', Service::class) ->setJoinType('LEFT'); $relations->belongsTo('redundancy_group', RedundancyGroup::class) ->setJoinType('LEFT'); } public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new Binary([ 'id', 'child_id', 'host_id', 'service_id', 'redundancy_group_id' ])); $behaviors->add(new ReRoute([ 'hostgroup' => 'host.hostgroup', 'servicegroup' => 'service.servicegroup' ])); } public static function on(Connection $db, Model $root = null): Query { if ($root === null) { throw new InvalidArgumentException('Root node must not be null'); } $query = parent::on($db); $query->getSelectBase()->with( self::selectNodes($db, $root), 'unreachable_parent', true ); $query->filter(Filter::all( Filter::greaterThan('level', 0), Filter::equal('is_group_member', 0), Filter::any( Filter::any( Filter::equal('service.state.affects_children', 'y'), Filter::all( // TODO: Remove this once implicit dependencies are properly implemented Filter::equal('_has_problematic_parent', 0), Filter::equal('service.state.is_reachable', 'n') ) ), Filter::all( Filter::unlike('service_id', '*'), Filter::equal('host.state.affects_children', 'y') ), Filter::all( Filter::equal('redundancy_group.state.failed', 'y'), Filter::equal('redundancy_group.state.is_reachable', 'y') ) ) )); $query->setResultSetClass(ResultSet::class); return $query; } private static function selectNodes(Connection $db, Model $root): Select { if ($db->getAdapter() instanceof Pgsql) { $binaryCast = "CAST('00000000000000000000' AS bytea20)"; $booleanCast = "CAST('0' AS boolean)"; } else { $binaryCast = "CAST('' AS binary(20))"; $booleanCast = '0'; } $rootQuery = DependencyNode::on($db) ->columns([ 'id' => 'id', 'child_id' => 'id', 'level' => new Expression('0'), 'host_id' => 'host_id', 'service_id' => new Expression("COALESCE(%s, $binaryCast)", ['service_id']), 'redundancy_group_id' => new Expression($binaryCast), 'is_group_member' => new Expression($booleanCast), '_has_problematic_parent' => new Expression($booleanCast) ]) ->disableDefaultSort(); if ($root instanceof Host) { $rootQuery->filter(Filter::all( Filter::equal('host_id', $root->id), Filter::unlike('service_id', '*') )); } elseif ($root instanceof Service) { $rootQuery->filter(Filter::all( Filter::equal('host_id', $root->host_id), Filter::equal('service_id', $root->id) )); } elseif ($root instanceof RedundancyGroup) { $rootQuery->filter(Filter::all(Filter::equal('redundancy_group_id', $root->id))); } else { throw new InvalidArgumentException('Root node must be either a host, service or a redundancy group'); } $nodeQuery = DependencyEdge::on($db) ->columns([ 'id' => 'to_node_id', 'child_id' => 'from_node_id', 'level' => new Expression('urn.level + 1'), 'host_id' => 'to.host_id', 'service_id' => 'to.service_id', 'redundancy_group_id' => 'to.redundancy_group_id', 'is_group_member' => new Expression('urn.redundancy_group_id IS NOT NULL AND urn.level > 0'), '_has_problematic_parent' => new Expression(sprintf( 'EXISTS(SELECT 1 FROM %s immediate_parents' . ' INNER JOIN %s state ON state.id = immediate_parents.dependency_edge_state_id' . ' WHERE immediate_parents.from_node_id = %%s AND state.failed = \'y\')', (new DependencyEdge())->getTableName(), (new DependencyEdgeState())->getTableName() ), ['to_node_id']) ]); $nodeQuery->filter(Filter::equal('state.failed', 'y')); $nodeSelect = $nodeQuery->assembleSelect(); // TODO: ipl-orm doesn't preserve key order :'( $columnsProperty = (new \ReflectionClass($nodeSelect))->getProperty('columns'); $columnsProperty->setAccessible(true); $columnsProperty->setValue($nodeSelect, array_merge( [ 'id' => null, 'child_id' => null, 'level' => null, 'host_id' => null, 'service_id' => null, 'redundancy_group_id' => null, 'is_group_member' => null, '_has_problematic_parent' => null ], $nodeSelect->getColumns() )); return $rootQuery->assembleSelect()->union( $nodeSelect ->join(['urn' => 'unreachable_parent'], sprintf( 'urn.id = %s', $nodeQuery ->getResolver() ->qualifyColumn( 'from_node_id', $nodeQuery ->getResolver() ->getAlias($nodeQuery->getModel()) ) )) ); } }