1package App::Netdisco::DB::ResultSet::Node;
2use base 'App::Netdisco::DB::ResultSet';
3
4use strict;
5use warnings;
6
7__PACKAGE__->load_components(qw/
8  +App::Netdisco::DB::ExplicitLocking
9/);
10
11=head1 ADDITIONAL METHODS
12
13=head2 search_by_mac( \%cond, \%attrs? )
14
15 my $set = $rs->search_by_mac({mac => '00:11:22:33:44:55', active => 1});
16
17Like C<search()>, this returns a ResultSet of matching rows from the Node
18table.
19
20=over 4
21
22=item *
23
24The C<cond> parameter must be a hashref containing a key C<mac> with
25the value to search for.
26
27=item *
28
29Results are ordered by time last seen.
30
31=item *
32
33Additional columns C<time_first_stamp> and C<time_last_stamp> provide
34preformatted timestamps of the C<time_first> and C<time_last> fields.
35
36=item *
37
38A JOIN is performed on the Device table and the Device C<dns> column
39prefetched.
40
41=back
42
43To limit results only to active nodes, set C<< {active => 1} >> in C<cond>.
44
45=cut
46
47sub search_by_mac {
48    my ($rs, $cond, $attrs) = @_;
49
50    die "mac address required for search_by_mac\n"
51      if ref {} ne ref $cond or !exists $cond->{mac};
52
53    $cond->{'me.mac'} = delete $cond->{mac};
54
55    return $rs
56      ->search_rs({}, {
57        order_by => {'-desc' => 'time_last'},
58        '+columns' => [
59          'device.dns',
60          { time_first_stamp => \"to_char(time_first, 'YYYY-MM-DD HH24:MI')" },
61          { time_last_stamp =>  \"to_char(time_last, 'YYYY-MM-DD HH24:MI')" },
62        ],
63        join => 'device',
64      })
65      ->search($cond, $attrs);
66}
67
68=head1 SPECIAL METHODS
69
70=head2 delete( \%options? )
71
72Overrides the built-in L<DBIx::Class> delete method to more efficiently
73handle the removal or archiving of nodes.
74
75=cut
76
77sub delete {
78  my $self = shift;
79  my ($opts) = @_;
80  $opts = {} if (ref {} ne ref $opts);
81
82  my $schema = $self->result_source->schema;
83  my $nodes = $self->search(undef, { columns => 'mac' });
84
85  if (exists $opts->{archive_nodes} and $opts->{archive_nodes}) {
86      foreach my $set (qw/
87        NodeIp
88        NodeNbt
89        NodeMonitor
90        Node
91      /) {
92          $schema->resultset($set)->search(
93            { mac => { '-in' => $nodes->as_query }},
94          )->update({ active => \'false' });
95      }
96
97      $schema->resultset('NodeWireless')
98        ->search({ mac => { '-in' => $nodes->as_query }})->delete;
99
100      # avoid letting DBIC delete nodes
101      return 0E0;
102  }
103  elsif (exists $opts->{only_nodes} and $opts->{only_nodes}) {
104      # now let DBIC do its thing
105      return $self->next::method();
106  }
107  elsif (exists $opts->{keep_nodes} and $opts->{keep_nodes}) {
108      # avoid letting DBIC delete nodes
109      return 0E0;
110  }
111  else {
112      foreach my $set (qw/
113        NodeMonitor
114        NodeWireless
115        NodeNbt
116      /) {
117          $schema->resultset($set)->search(
118            { mac => { '-in' => $nodes->as_query }},
119          )->delete;
120      }
121
122      # for node_ip only delete if there are no longer
123      # any nodes referencing the IP.
124
125      my @mac_restrict_aq     = @{${ $nodes->as_query }};
126      my @macport_restrict_aq = @{${ $nodes->search(undef, { '+columns' => 'port' })->as_query }};
127      my $mac_restrict     = shift @mac_restrict_aq;
128      my $macport_restrict = shift @macport_restrict_aq;
129
130      my $deleted = $schema->storage->dbh_do(sub {
131        my ($storage, $dbh, @extra) = @_;
132        local $dbh->{TraceLevel} =
133          ($ENV{DBIC_TRACE} ? '1|SQL' : $dbh->{TraceLevel});
134        $dbh->do(<<SQL
135DELETE FROM node_ip WHERE mac IN (
136  SELECT mac FROM node_ip
137    LEFT OUTER JOIN (
138      SELECT mac, port FROM node
139      EXCEPT ($macport_restrict)) exceptions
140    USING (mac)
141   WHERE node_ip.mac IN ($mac_restrict)
142     AND exceptions.port IS NULL)
143SQL
144        , undef, (map {$_->[1]} (@macport_restrict_aq, @mac_restrict_aq)));
145      });
146
147      # now let DBIC do its thing
148      return $self->next::method();
149  }
150}
151
1521;
153