Ticket #86: TSP_rjm.sql

File TSP_rjm.sql, 46.4 KB (added by rodj59, 3 years ago)
Line 
1--
2-- Definitions for TSP/VRP with constraints.
3--
4-- Copyright(c) Rod Millar 2008
5-- email: rodj59@yahoo.com.au , rodj59@y7.com.au
6-- This program is free software; you can redistribute it and/or modify
7-- it under the terms of the GNU General Public License as published by
8-- the Free Software Foundation; either version 2 of the License, or
9-- (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14-- GNU General Public License for more details.
15--
16-- You should have received a copy of the GNU General Public License
17-- along with this program; if not, write to the Free Software
18-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20create type total_type as (
21    total_dist float(4),
22    total_time  interval,
23    inconsistent_priority double precision,
24    consistent_priority   double precision,
25    inconsistencies     float(4),
26    consistencies       int,
27    non_dist_inconsistencies    int
28);
29
30create type time_dist_type as (
31    time interval,
32    dist float(4)
33);
34create type path_dest_type as(
35    job         int,            -- job number
36    pu          boolean,        -- t if is PU destination, f if is Drop
37    suburb      varchar,       
38    city        varchar,
39    time        timestamp,      -- scheduled reaching of the destination
40    time_bound  timestamp,      -- earliest PU or latest Drop time limit
41    dist        float(4),       -- cumulative distance along path
42    priority    double precision,
43    seq         int ,-- the sequence number in original path
44    load_pcnt   float(4),       -- cumulative load as percentage of capacity
45    job_type    varchar(1)      -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
46);
47create type inconsistent_path_dest_type as(
48    job         int,            -- job number
49    pu          boolean,        -- t if is PU destination, f if is Drop
50    suburb      varchar,       
51    city        varchar,
52    time        timestamp,      -- scheduled reaching of the destination
53    time_bound  timestamp,      -- earliest PU or latest Drop time limit
54    dist        float(4),       -- cumulative distance along path
55    priority    double precision,
56    seq         int ,-- the sequence number in original path
57    load_pcnt   float(4),       -- cumulative load as percentage of capacity
58    job_type    varchar(1),     -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
59    flag        bit(7)          -- bit pattern to indicate type(s) of inconsistency
60);
61
62create type path_links_type as(
63    prev_dest   int,            -- preceding path_dest in current path
64    next_dest   int,            -- next path_dest in current path
65    dist        float(4),       -- distance travel between prev and next for job
66    time_int    interval,       -- time to traverse the distance prev to next for the job
67    priority    double precision, -- dynamic priority
68    active      boolean         -- t if link is part of the current path
69);
70
71create type path_links_oid  as (-- NB. OID methods are not presently used !!! XXX HERE
72    prev_dest   int,            -- preceding path_dest in current path
73    next_dest   int,            -- next path_dest in current path
74    dist        float(4),       -- distance travel between prev and next for job
75    time_int    interval,       -- time to traverse the distance prev to next for the job
76    priority    double precision, -- dynamic priority
77    active      boolean,                -- t if link is part of the current path
78    oid         int             -- the oid of the DB row (NB. system bug in plpythonu necessitates this)
79);
80
81
82create table debug_table (
83    msg         varchar,        -- debug message
84    number      int8            -- sequence number
85);
86
87create table path_job (
88    job int primary key,        -- the job number
89    load_pcnt   float(4),       -- the percentage of vehicle capacity onboard
90    direct_dist float(4),       -- direct distance of the job
91    time_int    interval,       -- total/loading time for the job
92    job_type    varchar(1),     -- P/H/F/S/E  .. S = start of day, E = endofday
93    priority    double precision -- priority of the job in search
94);
95
96--
97-- leave out the pre_link/next_link if feeding into Anton's Gaul based TSP
98--
99create table path_dest (
100    job         int,            -- job number
101    pu          boolean,        -- t if is PU destination, f if is Drop
102    suburb      varchar,       
103    city        varchar,
104    time        timestamp,      -- scheduled reaching of the destination
105    time_bound  timestamp,      -- earliest PU or latest Drop time limit
106    dist        float(4),       -- cumulative distance along path
107    priority    double precision,
108    seq         int primary key,-- the sequence number in original path
109    load_pcnt   float(4),       -- cumulative load as percentage of capacity
110    job_type    varchar(1)      -- P/H/F/S/E =  point2point/Hourly/Furn/StartofDay/EndofDay
111);
112create index path_jindx on path_dest (job);
113
114--
115--
116--
117create table path_links (
118    prev_dest   int,            -- preceding path_dest in current path
119    next_dest   int,            -- next path_dest in current path
120    dist        float(4),       -- distance travel between prev and next for job
121    time_int    interval,       -- time to traverse the distance prev to next for the job
122    active      boolean,        -- t if link is part of the current path
123    priority    double precision, -- dynamic priority
124    primary key (prev_dest,next_dest)
125);
126
127
128-- priority scaled by number of distinct types of inconsistency detected
129create or replace function priority_value(tab varchar,link_tab varchar,seq int,max_dist float(4),dist float(4))
130    returns float(8) as
131$$
132    scale=plpy.execute("select inconsistency_scale('%s','%s',%s,%s,%f) * priority as icon from %s where seq = %s" % \
133                (tab,link_tab,seq,max_dist,dist,tab,seq))[0]
134    return scale * r['priority']
135$$ language 'plpythonu' volatile strict;
136
137-- number of distinct types of inconsistency detected on a destination
138create or replace function inconsistency_scale(tab varchar,link_tab varchar,seq int,max_dist float,dist float)
139    returns float(8) as
140$$
141    r=plpy.execute("select inconsistency_class('%s','%s',%s,%f) as icon " % \
142                (tab,link_tab,seq,max_dist))[0]
143    count = r['icon'].count('1')
144    if r['icon'][0] == '1':
145        count = count -1 + (dist / (max_dist * 2))
146    return count
147$$ language 'plpythonu' volatile strict;
148
149--
150-- Find the total distance travelled,total time, total inconsistencies for a particular path
151-- eg. invoke as "select * from totals('path_links')"
152create or replace function totals(tab varchar,link_tab varchar,max_dist float)
153    returns total_type as
154$$
155declare
156    record1 record;
157    record2 record;
158    record3 record;
159    dist_inconsistencies_rec record;
160    res total_type;
161    tot int;
162begin
163    -- NB. benign bugfix required HERE XXXX
164    -- ... the distance & time will be wrong for hourly jobs ... but doesn't matter w.r.t.
165    --     strategic choices since static !!!  ... so is it worth fixing ???
166    -- select only non-ceiling ie. positive priorities
167    execute 'select sum(dist) as distance,sum(time_int) as time from '||
168            quote_ident(link_tab)||' where active '
169        into record1;
170    execute 'select count(dist) as cnt ,sum(priority)' ||
171                ' as priority from '||quote_ident(tab)||
172            ' where consistent('||quote_literal(tab)||
173            ','||quote_literal(link_tab)||',seq,'||max_dist||')'
174        into record2;
175    res.total_dist := record1.distance;
176    res.total_time := record1.time;
177    res.consistencies := record2.cnt ;
178    execute 'select count(*) as tot,sum(priority*inconsistency_scale('||
179            quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
180            ','||res.total_dist||')) as priority , '
181            ||' sum(inconsistency_scale('||
182            quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
183            ','||res.total_dist||')) as ics from '||quote_ident(tab)
184        into record3;
185    -- count individual inconsistency types
186    res.inconsistencies := record3.ics;
187    res.consistent_priority := record2.priority;
188    res.inconsistent_priority := record3.priority ;
189
190    execute 'select count(seq) as cnt,sum(inconsistency_scale('||
191                quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||
192                ','||res.total_dist||')) as tot from '||quote_ident(tab)||
193                ' where inconsistency_class('||quote_literal(tab)||
194                    ','||quote_literal(link_tab)||',seq,'||max_dist||
195                    ') = B''1000000'''
196        into dist_inconsistencies_rec;
197
198    execute 'select count(seq) from '||quote_ident(tab)
199        into tot;
200    -- non_dist_inconsistencies excludes inconsistencies which are distance_only
201    res.non_dist_inconsistencies := tot - res.consistencies - dist_inconsistencies_rec.cnt;
202
203    return res;
204end;
205$$
206language 'plpgsql' volatile strict;
207
208
209--
210-- classify the type of inconsistency
211-- binary flag in the following field position ->
212--      0 = distance
213--      1 = time  (ie. delivery too late, NB. PU too early never happens !)
214--      2 = load
215--      3 = drop before pickup
216--      4 = PU & Drop on different days
217--      5 = missing predecessor link
218--      6 = missing successor link
219--
220-- eg 0010000 = load inconsistency only
221create or replace function inconsistency_class(tab varchar,link_tab varchar,seq int,max_dist float)
222    returns bit(7) as
223$$
224declare
225    row1 path_dest;
226    row2 path_dest;
227    flag boolean;
228    count int;
229    ans bit(7);
230begin
231    ans := B'0000000';
232    execute 'select * from '||quote_ident(tab)||' where seq = '||seq
233        into row1;
234    if row1.dist > max_dist
235    then
236        -- distance inconsistency
237        ans := B'1000000';
238    end if;
239    if row1.job_type in ('e','E')
240    then
241        -- the end-of-day destination
242        if row1.time <> row1.time_bound
243        then
244            ans := ans | B'0100000';
245        end if;
246    end if;
247    if row1.job_type in ('p','P','h','H','f','F')
248    then
249        -- eg. a PU or Drop of a normal job
250        if row1.load_pcnt > 1.0
251        then
252            -- load inconsistency
253            ans := ans | B'0010000';
254        end if;
255        -- find the other destination of the job
256        execute 'select * from '||quote_ident(tab)||' where seq != '||seq||
257                ' and job = '||row1.job
258            into row2;
259        if row1.pu = 't'
260        then
261            -- a pickup
262            -- .. check for drop after pickup
263            execute 'select time < '''||row1.time||'''::timestamp from '||quote_ident(tab)||
264                        ' where job = '||row1.job||' and pu is false'
265                into flag;
266            if flag
267            then
268                -- drop before pickup inconsistency
269                ans := ans | B'0001000';
270            end if;
271            -- see if there is any Home destination between the two destinations
272            execute 'select count(seq) from '||quote_ident(tab)||' where job_type in (''S'',''s'',''E'',''e'') and time > '''||
273                        row1.time||'''::timestamp and time < '''||row2.time||'''::timestamp'
274                into count;
275            if count > 0
276                then
277                    -- PU & drop on different days inconsistency
278                    ans := ans | B'0000100';
279            end if;
280        else
281            -- a drop
282            execute 'select time > '''||row1.time||'''::timestamp from '||quote_ident(tab)||
283                        ' where job = '||row1.job||' and pu is true'
284                into flag;
285            if flag
286            then
287                -- drop before pickup inconsistency
288                ans := ans | B'0001000';
289            end if;
290            execute 'select '''||row1.time||'''::timestamp > '''||row1.time_bound||'''::timestamp'
291                into flag;
292            if flag
293            then
294                -- delivery too late inconsistency
295                ans := ans | B'0100000';
296            end if;
297            -- see if there is any Home destination between the two destinations
298            execute 'select count(seq) from '||quote_ident(tab)||' where job_type in (''S'',''s'',''E'',''e'') and time > '''||
299                        row2.time||'''::timestamp and time < '''||row1.time||'''::timestamp'
300                into count;
301            if count > 0
302                then
303                    -- PU & drop on different days inconsistency
304                    ans := ans | B'0000100';
305            end if;
306        end if;
307        -- see if both prev & next destinations are linked to this destination
308        execute 'select count(*) from '||quote_ident(link_tab)||' where ( prev_dest = '||
309                    row1.seq||' ) and active'
310            into count;
311        if count  != 1
312            then
313                ans := ans | B'0000001';
314        end if;
315        execute 'select count(*) from '||quote_ident(link_tab)||' where ( '||
316                    '  next_dest = '||row1.seq||') and active'
317            into count;
318        if count != 1
319            then
320                ans := ans | B'0000010';
321        end if;
322    end if;
323    -- no inconsistencies detected
324    return ans;
325end;
326$$
327language 'plpgsql' volatile strict;
328--
329-- Test if a destination is consistent.
330-- eg.  select * from consistent('path_dest',seq_no,max_dist)
331--
332create or replace function consistent(tab varchar , link_tab varchar, seq int,max_dist float)
333    returns boolean as
334$$
335declare
336    flag int;
337begin
338    execute 'select * from inconsistency_class('||quote_literal(tab)||','||quote_literal(link_tab)||','||seq||','||max_dist||')'
339        into flag;
340    return flag = 0;
341end;
342$$
343language 'plpgsql' volatile strict;
344
345
346--
347-- Find the set of path_links records representing potential predecessor destination
348-- links for the given destination.
349-- eg. select * from prev_links('path_links',seq_no,active_flag  't'|'f')
350--
351create or replace function prev_links(tab varchar,seqno int,active varchar)
352    returns setof path_links as
353$$
354declare
355    row path_links;
356begin
357    for row in execute 'select * from '||quote_ident(tab)||' where '||
358                '  active = '||quote_literal(active)||'::bool and next_dest = '|| seqno
359    loop
360        return next row;
361    end loop;
362    return;
363end;
364$$
365language 'plpgsql' volatile strict;
366create or replace function prev_links_oid(tab varchar,oid int,active varchar)
367    returns setof path_links as
368$$
369declare
370    row path_links;
371begin
372    for row in execute 'select * from '||quote_ident(tab)||' where '||
373                '  active = '||quote_literal(active)||'::bool and oid = '|| oid
374    loop
375        return next row;
376    end loop;
377    return;
378end;
379$$
380language 'plpgsql' volatile strict;
381create or replace function prev_links_oid(tab varchar,oid int)
382    returns setof path_links as
383$$
384declare
385    row path_links;
386begin
387    for row in execute 'select * from '||quote_ident(tab)||' where '||
388                ' oid = '|| oid
389    loop
390        return next row;
391    end loop;
392    return;
393end;
394$$
395language 'plpgsql' volatile strict;
396
397
398
399-- ditto for successor destinations
400
401create or replace function next_links(tab varchar,seqno int,active varchar)
402    returns setof path_links as
403$$
404declare
405    row path_links;
406begin
407    for row in execute 'select * from '||quote_ident(tab)||
408                ' where active = '||quote_literal(active)||'::bool and prev_dest = '|| seqno
409    loop
410        return next row;
411    end loop;
412    return;
413end;
414$$
415language 'plpgsql' volatile strict;
416
417--
418
419--
420-- Find the actual travel/wait time interval for a destination from its predecessor
421-- given the current active path_links records
422-- ... takes the time for the previous destination, adds the path_links time & then takes max of
423-- that & the earliest allowable pickup time ( in the case of a pickup ) or earliest home
424-- time in the case of the pm_home destination
425--
426create or replace function dest_time(tab varchar,link_tab varchar,job_tab varchar,seq int)
427    returns time_dist_type as
428$$
429declare
430    rec1 record;
431    row1 record;
432    td   time_dist_type;
433begin
434    -- compute the timeinterval based on previous active def + path_links interval
435    execute 'select * from '||quote_ident(tab)||' where seq = '||seq
436        into row1;
437
438    if row1.job_type in ('s','S')
439        then
440        -- this is a start-of-day destination, we require a 12-hour minimum gap from previous days close XXX WARNING HERE XXX
441        execute 'select  greatest(''12:00:00''::interval + b.time,'''||row1.time_bound||'''::timestamp) - b.time as time,a.dist as dist from '||
442                quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and a.prev_dest = b.seq and a.next_dest = '||
443                row1.seq
444            into rec1;
445    else
446        if row1.pu or row1.job_type in ('e','E')
447        then
448        -- means we must not return a time less than time-bound
449        execute 'select  greatest(a.time_int + b.time,'''||row1.time_bound||'''::timestamp) - b.time as time,a.dist as dist from '||
450                quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and a.prev_dest = b.seq and a.next_dest = '||
451                row1.seq
452            into rec1;
453        elsif row1.job_type not in ('p','P','s','S') and not row1.pu
454            then
455                -- means this is the Drop destination of an hourly job (H/F) whose duration & distance is fixed
456                --    ... and whose direct predecessor is necessarily same jobs PU destination ...
457                execute 'select direct_dist as dist,time_int as time from '||quote_ident(job_tab)||'   where job = '||row1.job
458                    into rec1;
459        else
460            -- means we return whatever time we get
461            execute 'select  a.time_int  as time,a.dist as dist from '||
462                quote_ident(link_tab)||' a,'||quote_ident(tab)||' b where a.active and  a.next_dest = '||
463                row1.seq
464            into rec1;
465        end if;
466    end if;
467
468    td.time := rec1.time;
469    td.dist := rec1.dist;
470   return td;
471end;
472$$
473language 'plpgsql' volatile strict;
474
475
476--
477-- Find the actual travel/wait timestamp for each destination
478-- given the current active path_links records
479-- ... walks from given seq # forward until no changes are required or
480-- until the final destination is reached & applies changes as needed.
481-- NB. seqno should be that of the predecessor to the changed link
482-- Returns # of changes made.
483create or replace function dest_changes(tab varchar,link_tab varchar,job_tab varchar,seq_no int)
484    returns int as
485$$
486declare
487    cur_dest_rec record;
488    next_links_rec record;
489    next_dest_ts_rec record;
490    next_dest_rec record;
491    next_dest_job_rec record;
492    cur_dest_job_rec record;
493    change_cnt int;
494    final_seqno int;
495    td  time_dist_type;
496    dist float(4);
497    load float(4);
498    seq  int;
499
500begin
501    -- compute the timeinterval based on previous active def + path_links interval
502
503    -- find the final destination in the path (ie. an 'e' node)
504    execute 'select seq from '||quote_ident(tab)||' where job_type in (''s'',''S'',''e'',''E'')  and seq <> 1 order by time_bound desc limit 1'
505        into final_seqno;
506
507    -- start at the specified seq no
508    seq := seq_no;
509    change_cnt := 0;
510
511    execute 'select time,dist,load_pcnt,job,pu from '||quote_ident(tab)||' where seq = '||seq
512        into cur_dest_rec;
513
514    --  job for current dest
515    execute 'select * from '||quote_ident(job_tab)||' where job = '||cur_dest_rec.job
516        into cur_dest_job_rec;
517
518    dist := cur_dest_rec.dist;
519    load := cur_dest_rec.load_pcnt;
520    while seq <> final_seqno
521        loop
522            -- find active link to next destination
523            execute 'select * from next_links('||quote_literal(link_tab)||','||seq||',''true'')'
524                into next_links_rec;
525
526            -- fetch travel time/distance to next destination
527            execute 'select * from dest_time('||quote_literal(tab)||','||quote_literal(link_tab)||
528                    ','||quote_literal(job_tab)
529                    ||','||next_links_rec.next_dest||')'
530                into td;
531           
532            -- calculate cumulative distance/time
533            dist := dist + td.dist;
534
535            -- compute timestamp to reach next destination
536            execute 'select '''||cur_dest_rec.time||'''::timestamp + '''||td.time||'''::interval as time'
537                into next_dest_ts_rec;
538       
539            -- path_dest row for next destination
540            execute 'select * from '||quote_ident(tab)||' where seq = '||next_links_rec.next_dest
541                into next_dest_rec;
542
543            -- compute load at next dest
544
545            --  .. job for next dest
546            execute 'select * from '||quote_ident(job_tab)||' where job = '||next_dest_rec.job
547                into next_dest_job_rec; 
548
549            if not cur_dest_rec.pu
550                then
551                    -- if current dest is Drop, subtract load_pcnt
552                    load := load - cur_dest_job_rec.load_pcnt;
553            end if;
554            if next_dest_rec.pu
555                then
556                    -- if next dest is PU, add load_pcnt
557                    load := load + next_dest_job_rec.load_pcnt;
558            end if;
559                   
560
561            if  (-0.01 > (dist - next_dest_rec.dist) or ( dist - next_dest_rec.dist) > 0.01 ) or (next_dest_ts_rec.time != next_dest_rec.time) or ( -0.01 > (next_dest_rec.load_pcnt - load) or (next_dest_rec.load_pcnt - load) > 0.01 )
562                then
563                    -- we need to propagate down the path
564                    change_cnt := change_cnt + 1; -- increment # of changes tally
565                    -- apply the changes
566                    execute 'update '||quote_ident(tab)||' set time = '''||next_dest_ts_rec.time||
567                            '''::timestamp,dist = '||dist||',load_pcnt = '||load||' where seq = '||next_links_rec.next_dest ;
568
569                    seq := next_links_rec.next_dest;
570                    execute 'select time,dist,pu,job from '||quote_ident(tab)||' where seq = '||seq
571                        into cur_dest_rec;
572                    --  job for next current dest
573                    execute 'select * from '||quote_ident(job_tab)||' where job = '||cur_dest_rec.job
574                        into cur_dest_job_rec;
575            else
576                -- no change, so halt the propagation
577                return change_cnt;
578            end if;
579        end loop;
580   return change_cnt;
581end;
582$$
583language 'plpgsql' volatile strict;
584
585
586--
587-- Find the set of destinations which precede another in the current path
588--
589create or replace function preceding_destinations(tab varchar,link_tab varchar, seq int)
590    returns setof path_dest as
591$$
592declare
593    row1 record;
594    row2 path_dest;
595    sno int;
596    row_cnt int;
597begin
598    sno := seq;
599    if seq = 1
600        then
601            return;
602    end if;
603    loop
604        execute 'select * from prev_links('||quote_literal(link_tab)||','||sno||',''t'')'
605            into row1;
606        get diagnostics row_cnt = ROW_COUNT;
607        exit when row_cnt <= 0; -- this path segment must be detached !!
608        -- follow the active links backwards until first node is reached
609        execute 'select * from '||quote_ident(tab)||' where seq = ' ||row1.prev_dest
610            into row2;
611        return next row2;
612        exit when row2.seq = 1;
613        sno := row1.prev_dest;
614    end loop;
615    return;
616end;
617$$
618language 'plpgsql' volatile strict;
619create or replace function preceding_destinations_inclusive(tab varchar,link_tab varchar,seq int)
620    returns setof path_dest as
621$$
622declare
623    row path_dest;
624begin
625    -- return the seq destination first
626    execute 'select * from '||quote_ident(tab)||' where seq = '||seq
627        into row;
628    return next row;
629    for row in execute 'select * from preceding_destinations('||quote_literal(tab)||','||
630                    quote_literal(link_tab)||','||seq||')'
631    loop
632        return next row;
633    end loop;
634    return;
635
636end;
637$$
638language 'plpgsql' volatile strict;
639
640
641
642-- Find the set of destinations which follow another in the current path(inclusive)
643--
644create or replace function following_destinations(tab varchar,link_tab varchar, seq int)
645    returns setof path_dest as
646$$
647declare
648    row1 record;
649    row2 path_dest;
650    sno int;
651    max_seq int;
652    row_cnt int;
653begin
654    -- find highest seq no in destinations set
655    execute 'select max(seq) from '||quote_ident(tab)||' where job = 0'
656        into max_seq;
657    sno := seq;
658    if seq = max_seq
659        then
660            return;
661    end if;
662    loop
663        execute 'select * from next_links('||quote_literal(link_tab)||','||sno||',''t'')'
664            into row1;
665        get diagnostics row_cnt = ROW_COUNT;
666        exit when row_cnt <= 0;  -- this part of the path must be detached !
667        execute 'select * from '||quote_ident(tab)||' where seq = '||row1.next_dest
668            into row2;
669        return next row2;
670        exit when row2.seq = max_seq ;
671        sno := row1.next_dest;
672    end loop;
673    return;
674end;
675$$
676language 'plpgsql' volatile strict;
677
678create or replace function following_destinations_inclusive(tab varchar,link_tab varchar,seq int)
679    returns setof path_dest as
680$$
681declare
682    row path_dest;
683begin
684    -- return the seq destination first
685    execute 'select * from '||quote_ident(tab)||' where seq = '||seq
686        into row;
687    return next row;
688    for row in execute 'select * from following_destinations('||quote_literal(tab)||','||
689                    quote_literal(link_tab)||','||seq||')'
690    loop
691        return next row;
692    end loop;
693    return;
694
695end;
696$$
697language 'plpgsql' volatile strict;
698
699
700
701-- Find the set of destinations without predecessor in the current path (excluding first & last !)
702--
703create or replace function dests_without_pred(tab varchar,link_tab varchar)
704    returns setof path_dest as
705$$
706declare
707    seq int;
708    row path_dest;
709begin
710    for seq in execute 'select next_dest from '||quote_ident(link_tab)||' where not active except select next_dest from '||
711            quote_ident(link_tab)||' where active'
712
713        loop
714            execute 'select * from '||quote_ident(tab)||' where seq = '||seq
715                into row;
716            return next row;
717    end loop;
718    return;
719end;
720$$
721language 'plpgsql' volatile strict;
722
723-- Find the set of destinations without successor in the current path
724--
725create or replace function dests_without_succ(tab varchar,link_tab varchar)
726    returns setof path_dest as
727$$
728declare
729    seq int;
730    row path_dest;
731begin
732    for seq in execute 'select prev_dest from '||quote_ident(link_tab)||' where not active except select prev_dest from '||
733            quote_ident(link_tab)||' where active'
734
735        loop
736            execute 'select * from '||quote_ident(tab)||' where seq = '||seq
737                into row;
738            return next row;
739    end loop;
740    return;
741
742end;
743$$
744language 'plpgsql' volatile strict;
745
746
747
748--
749--
750-- Given an inconsistent load destination node, find the set of destinations where the
751-- PU destinations precede it & the Drop destinations follow it.
752-- ... returns the set of path_dest nodes
753--
754create or replace function pu_b4_drop_after(tab varchar,link_tab varchar,seq int)
755    returns setof path_dest as
756$$
757declare
758    row1 path_dest;
759    dest path_dest;
760begin
761    for row1 in execute 'select * from preceding_destinations('||quote_literal(tab)||','||quote_literal(link_tab)||
762                ','||seq||')  where pu'
763        loop
764            for dest in execute 'select * from following_destinations('||quote_literal(tab)||','||quote_literal(link_tab)||
765                ','||seq||') where not pu and job = '||row1.job
766                loop
767                    return next row1;
768                    return next dest;
769                    -- exit loop;  -- should only be one !!! .. unless multi-drops permitted
770            end loop;
771    end loop;
772    return;
773end;
774$$
775language 'plpgsql' volatile strict;
776
777--
778-- Find the set of inconsistent destinations
779--
780create or replace function inconsistent_dests(tab varchar,link_tab varchar,max_dist float)
781    returns setof inconsistent_path_dest_type as
782$$
783declare
784    row1 record;
785    row2 inconsistent_path_dest_type;
786begin
787    for row1 in execute 'select *,inconsistency_class('||quote_literal(tab)||','||
788                quote_literal(link_tab)||',seq,'||max_dist||') as flag from '||quote_ident(tab)||
789                '  where not consistent('||
790                quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||') order by priority desc'
791    loop
792        row2.job := row1.job;
793        row2.pu := row1.pu;
794        row2.suburb := row1.suburb;
795        row2.city := row1.city;
796        row2.time := row1.time;
797        row2.time_bound := row1.time_bound;
798        row2.dist := row1.dist;
799        row2.priority := row1.priority;
800        row2.seq := row1.seq;
801        row2.load_pcnt := row1.load_pcnt;
802        row2.job_type := row1.job_type;
803        row2.flag := row1.flag;
804        return next row2;
805    end loop;
806    return;
807end;
808$$
809language 'plpgsql' volatile strict;
810
811--
812-- Find the set of consistent destinations
813--
814create or replace function consistent_dests(tab varchar,link_tab varchar,max_dist float)
815    returns setof path_dest as
816$$
817declare
818    row1 path_dest;
819begin
820    for row1 in execute 'select * from '||quote_ident(tab)||' where consistent('||
821                quote_literal(tab)||','||quote_literal(link_tab)||',seq,'||max_dist||') order by priority asc'
822    loop
823        return next row1;
824    end loop;
825    return;
826end;
827$$
828language 'plpgsql' volatile strict;
829
830
831--  DON'T USE THESE FUNCTIONS ANYMORE .... PRIORITY IS DIRECTLY RECORDED ON THE path_link RECORD
832--      Priority of a path_link is the priority of the path_dest it is presently the predecessor of
833-- if it is active.
834--      If the link is inactive, its priority is negative of path_dest.
835-- To activate an inactive link, the sum of its priority & the path_dest seeking it must exceed 0 !
836--
837create or replace function path_link_priority(tab varchar,link_tab varchar,prev_dest int,next_dest int)
838    returns double precision as
839$$
840declare
841    link path_links;
842    dest path_dest;
843begin
844    execute 'select * from '||quote_ident(link_tab)||' where prev_dest = '||prev_dest||
845                ' and next_dest = '||next_dest
846        into link;
847    execute 'select * from '||quote_ident(tab)||' where seq = '||next_dest
848            into dest;
849--    if dest.job == 0 or dest.pu is None:  # eg. this is an end-of-day job
850--      return - dest.priority  # eg. end-of-day has no resistance priority
851    if link.active != true
852        then
853            return - dest.priority; -- eg. negative of the destinations existing priority
854    else
855        return  dest.priority;
856    end if;
857end;
858$$
859language 'plpgsql' volatile strict;
860
861create or replace function path_link_priority(tab varchar,next_dest int,active boolean)
862    returns double precision as
863$$
864declare
865    dest path_dest;
866begin
867    execute 'select * from '||quote_ident(tab)||' where seq = '||next_dest
868            into dest;
869--    if dest.job == 0 or dest.pu is None:  # eg. this is an end-of-day job
870--      return - dest.priority  # eg. end-of-day has no resistance priority
871    if active != true
872        then
873            return - dest.priority; -- eg. negative of the destinations existing priority
874    else
875        return  dest.priority;
876    end if;
877end;
878$$
879language 'plpgsql' volatile strict;
880
881--
882-- This variant calculates the priority of moving a destination to a different position
883-- in the active path.
884-- .. prev_dest,next_dest = seq #'s of destinations between which to insert
885--    dest = seq # of destination to be inserted
886create or replace function path_link_reloc_priority(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int)
887    returns double precision as
888$$
889declare
890    rec1 record;
891    rec2 record;
892    priority float(8);
893    priority2 float(8);
894    cnt int;
895begin
896    execute 'select count(*) from '||quote_ident(tab)||' a, '||quote_ident(tab)||
897                ' b where a.job = b.job and a.job_type not in (''p'',''P'',''e'',''E'')'
898                ||' and a.seq = '||prev_dest||' and b.seq = '||next_dest
899            into cnt;
900
901    if cnt > 0
902        then
903            -- NB. can omit this exit test iff no alternate path_links are created  for such jobs !!!
904            return 9e99; -- ie. cannot splice anything between PU/Drop of hourly job !!!
905    end if;
906
907    -- find the priority of the three active links which need to be deactivated (if active)
908    execute 'select max(priority)  as priority,count(*) as count from path_link_deactivation_triple('||
909                quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest|| ','||next_dest||','||dest||')'
910        into rec1;
911    execute 'select max(priority) as priority,count(*) as count from path_link_activation_triple('||
912                quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||')'
913        into rec2;
914   
915    if rec1.count < 3
916        then
917        return 9e99;  -- ie. infinite priority
918    else
919        priority = rec1.priority;
920    end if;
921    if rec2.count < 3
922        then
923        return 9e99;  -- ie. infinite priority
924    else
925        priority2 = rec2.priority;
926    end if;
927--    return rec1.priority + rec2.priority;
928    if priority2 > priority
929    then
930        -- ie. the priority to activate an inactive link is the greater obstacle
931        return priority2;
932    else
933        return priority;
934    end if;
935end;
936$$
937language 'plpgsql' volatile strict;
938create or replace function path_link_reloc_priority(tab varchar,link_tab varchar,next_dest int,dest int)
939    returns double precision as
940$$
941declare
942    priority float(8);
943    prev int;
944begin
945    -- find the priority of the three active links which need to be deactivated (if active)
946    execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
947        into prev;
948
949    execute 'select * from path_link_reloc_priority('||quote_literal(tab)||','
950        || quote_literal(link_tab)||','||prev|| ','||next_dest||','||dest||')'
951        into priority;
952    return priority;
953end;
954$$
955language 'plpgsql' volatile strict;
956
957-- This variant is for moving complete path segments bounded by two destinations to a new location
958-- NB. this could replace the single destination version by simply feeding it the same destination twice !!! XXX
959create or replace function path_link_reloc_priority_2(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int,dest2 int)
960    returns double precision as
961$$
962declare
963    rec1 record;
964    rec2 record;
965    priority float(8);
966    priority2 float(8);
967    cnt int;
968begin
969    execute 'select count(*) from '||quote_ident(tab)||' a, '||quote_ident(tab)||
970                ' b where a.job = b.job and a.job_type not in (''p'',''P'',''e'',''E'')'
971                ||' and a.seq = '||prev_dest||' and b.seq = '||next_dest
972            into cnt;
973
974    if cnt > 0
975        then
976            -- NB. can omit this exit test iff no alternate path_links are created  for such jobs !!!
977            return 9e99; -- ie. cannot splice anything between PU/Drop of hourly job !!!
978    end if;
979
980    -- find the priority of the three active links which need to be deactivated (if active)
981    execute 'select max(priority)  as priority,count(*) as count from path_link_deactivation_triple('||
982                quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest|| ','||next_dest||','||dest||
983                ','||dest2||')'
984        into rec1;
985    execute 'select max(priority) as priority,count(*) as count from path_link_activation_triple('||
986                quote_literal(tab)||','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||
987                ','||dest2||')'
988        into rec2;
989   
990    if rec1.count < 3
991        then
992        return 9e99;  -- ie. infinite priority
993    else
994        priority = rec1.priority;
995    end if;
996    if rec2.count < 3
997        then
998        return 9e99;  -- ie. infinite priority
999    else
1000        priority2 = rec2.priority;
1001    end if;
1002--    return rec1.priority + rec2.priority;
1003    if priority2 > priority
1004    then
1005        -- ie. the priority to activate an inactive link is the greater obstacle
1006        return priority2;
1007    else
1008        return priority;
1009    end if;
1010end;
1011$$
1012language 'plpgsql' volatile strict;
1013create or replace function path_link_reloc_priority_2(tab varchar,link_tab varchar,next_dest int,dest int,dest2 int)
1014    returns double precision as
1015$$
1016declare
1017    priority float(8);
1018    prev int;
1019begin
1020    -- find the priority of the three active links which need to be deactivated (if active)
1021    execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
1022        into prev;
1023
1024    execute 'select * from path_link_reloc_priority_2('|| quote_literal(tab)||','||
1025        quote_literal(link_tab)||','||prev|| ','||next_dest||','||dest||
1026        ','||dest2||')'
1027        into priority;
1028    return priority;
1029end;
1030$$
1031language 'plpgsql' volatile strict;
1032
1033
1034
1035--
1036-- Find the pair of active path_links records which need to be deactivated to allow the specified
1037-- path_links record to be activated.
1038-- NB. this set may contain only one or even be empty !
1039--
1040create or replace function path_link_deactivation_pair(link_tab varchar,prev_dest int, next_dest int)
1041    returns setof path_links as
1042$$
1043declare
1044    link path_links;
1045begin
1046    for link in execute 'select * from '||quote_ident(link_tab)||' where  active and  ( prev_dest = '||prev_dest||
1047            ' or next_dest = '||next_dest||')'
1048    loop
1049        return next link;
1050    end loop;
1051    return;
1052end;
1053$$
1054language 'plpgsql' volatile strict;
1055
1056--
1057-- Find the three links which need to be deactivated
1058-- to remove a destination from its current location & splice it in at a new
1059-- position in the active path indicated by the specified path_link.
1060--
1061-- dest = seq no of destination to be moved
1062-- prev_dest,next_dest = seq no's of destinations in-between which to insert dest
1063create or replace function path_link_deactivation_triple(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int)
1064    returns setof path_links as
1065$$
1066declare
1067    link path_links;
1068    rec1 record;
1069    rec2 record;
1070    row_count1 int;
1071    row_count2 int;
1072begin
1073    -- see if the job is hourly or p2p
1074    execute 'select * from '||quote_ident(tab)||' where seq = '||dest
1075        into rec1;
1076    get diagnostics row_count1 = ROW_COUNT;
1077    if row_count1 < 1
1078        then
1079            return;
1080    end if;
1081    if rec1.job_type = 'p' or rec1.job_type  = 'P'
1082    then
1083   
1084        for link in execute 'select * from '||quote_ident(link_tab)||' where active and ( prev_dest = '||
1085                dest||' or next_dest = '||dest||' or ( prev_dest = '||prev_dest||' and next_dest = '||
1086                    next_dest||' ) )'
1087        loop
1088            return next link;
1089        end loop;
1090    else
1091        if rec1.job_type != 'f' and rec1.job_type != 'F' and rec1.job_type != 'h' and rec1.job_type != 'H'
1092            then
1093                -- should not be called with any other job types
1094                return ;
1095        end if;
1096        -- the job is a hourly, so must keep link to PU/D
1097        -- .. fetch the other destination of the same job
1098        execute 'select * from '||quote_ident(tab)||' where job = '||rec1.job||' and not (seq = '||
1099                    dest||')'
1100            into rec2;
1101        for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1102                        prev_dest||' and next_dest = '||next_dest||')'
1103        loop
1104       
1105            return next link;
1106        end loop;
1107        if rec2.pu = true
1108        then
1109            -- dest is the drop, so need to get predecessor of rec2 & successor of rec1
1110            for link in execute 'select * from '||quote_ident(link_tab)||' where active and  (next_dest = '||
1111                        rec2.seq||')'
1112            loop
1113                return next link;
1114            end loop;
1115            for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1116                        rec1.seq||')'
1117            loop
1118                return next link;
1119            end loop;
1120        else
1121            -- dest is the PU, so need to get predecessor or rec1 & successor rec2
1122            -- dest is the drop, so need to get predecessor of rec2 & successor of rec1
1123            for link in execute 'select * from '||quote_ident(link_tab)||' where active and  (next_dest = '||
1124                        rec1.seq||')'
1125            loop
1126                return next link;
1127            end loop;
1128            for link in execute 'select * from '||quote_ident(link_tab)||' where active and (prev_dest = '||
1129                        rec2.seq||')'
1130            loop
1131                return next link;
1132            end loop;
1133        end if;
1134    end if;
1135    return;
1136end;
1137$$
1138language 'plpgsql' volatile strict;
1139
1140-- Variant which accepts two destinations and computes change sets to shift the whole intermediate
1141-- path segment as a contiguous unit.
1142-- NB. this function should be called with destinations belonging to the same job (ie. PU & Drop)
1143-- ... also it is assumed dest precedes dest2 in the path
1144create or replace function path_link_deactivation_triple(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int, dest2 int)
1145    returns setof path_links as
1146$$
1147declare
1148    link path_links;
1149    rec1 record;
1150    rec2 record;
1151    rec11 record;
1152    row_count11 int;
1153    row_count1 int;
1154    row_count2 int;
1155begin
1156    -- HERE XXX
1157    -- see if the job is hourly or p2p
1158    execute 'select * from '||quote_ident(tab)||' where seq = '||dest
1159        into rec1;
1160    get diagnostics row_count1 = ROW_COUNT;
1161    execute 'select * from '||quote_ident(tab)||' where seq = '||dest2
1162        into rec11;
1163    get diagnostics row_count11 = ROW_COUNT;
1164    if row_count1 < 1 or row_count11 < 1 or rec1.time > rec11.time
1165        then
1166            return;
1167    end if;
1168    if rec1.job_type = 'e' or rec1.job_type = 'E' or rec11.job_type = 's' or rec11.job_type = 'S'
1169        then
1170            -- dont shift start/end of day destinations
1171            return;
1172    end if;
1173
1174    if ( rec1.pu or (rec1.job_type = 'p' or  rec1.job_type = 'P' ))
1175            and ( (not rec11.pu) or (rec11.job_type = 'p' or rec11.job_type = 'P'))
1176    then
1177        -- only accept non-p2p job destinations if dest is PU and dest2 is Drop
1178        for link in execute 'select * from '||quote_ident(link_tab)||' where active and ( next_dest = '||
1179                dest||' or prev_dest = '||dest2||' or ( prev_dest = '||prev_dest||' and next_dest = '||
1180                    next_dest||' ) )'
1181        loop
1182            return next link;
1183        end loop;
1184    end if;
1185    return;
1186end;
1187$$
1188language 'plpgsql' volatile strict;
1189
1190create or replace function path_link_deactivation_triple_oid(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest  int)
1191    returns setof path_links_oid as
1192$$
1193declare
1194    link        path_links_oid;
1195    row1                record;
1196begin
1197    for row1 in execute 'select a.*,b.oid from path_link_deactivation_triple('||quote_literal(tab)||
1198                ','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||') a, '||
1199                quote_ident(link_tab)||' b where a.prev_dest = b.prev_dest and a.next_dest = b.next_dest'
1200    loop
1201        link.prev_dest := row1.prev_dest;
1202        link.next_dest := row1.next_dest;
1203        link.dist := row1.dist;
1204        link.time_int := row1.time_int;
1205        link.priority := row1.priority;
1206        link.active := row1.active;
1207        link.oid := row1.oid;
1208        return next link;
1209    end loop;
1210    return;
1211end;
1212$$
1213language 'plpgsql' volatile strict;
1214
1215create or replace function path_link_deactivation_triple(tab varchar,link_tab varchar,next_dest int,dest int)
1216    returns setof path_links as
1217$$
1218declare
1219    link path_links;
1220    prev int;
1221begin
1222    execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
1223        into prev;
1224
1225    for link in execute 'select * from path_link_deactivation_triple('||
1226                quote_literal(tab)||','||quote_literal(link_tab)||','||prev||','||next_dest||','||dest||')'
1227    loop
1228        return next link;
1229    end loop;
1230    return;
1231end;
1232$$
1233language 'plpgsql' volatile strict;
1234
1235-- As above, but gives the three path_links which need to be made active
1236-- NB. if this doesn't return three links , then the activation cannot be done !!!
1237--
1238create or replace function path_link_activation_triple(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int)
1239    returns setof path_links as
1240$$
1241declare
1242    link path_links;
1243    old_prev int;
1244    old_next int;
1245    rec1 record;
1246    rec2 record;
1247    rec4 record;
1248    rec5 record;
1249    row_count1 int;
1250    row_count2 int;
1251begin
1252    -- see if the job is hourly or p2p
1253    execute 'select * from '||quote_ident(tab)||' where seq = '||dest
1254        into rec1;
1255
1256    if rec1.job_type = 'p' or rec1.job_type  = 'P'
1257    then
1258        -- the link which joins the original predecessor & succesor of dest together
1259
1260        for link in execute 'select * from '||quote_ident(link_tab)||' where prev_dest in ( select prev_dest from '||
1261                quote_ident(link_tab)||' where active and next_dest = '||dest||
1262                ') and next_dest in (select next_dest from '||quote_ident(link_tab)||
1263                ' where active and prev_dest = '||dest||')'
1264        loop
1265            return next link;
1266        end loop;
1267
1268        -- the two which splice dest into the new location
1269        for link in execute 'select * from '||quote_ident(link_tab)||' where ( prev_dest = '||
1270            prev_dest||' and next_dest = '||dest||') or (prev_dest = '||dest||' and next_dest = '||
1271            next_dest||') '
1272        loop
1273            return next link;
1274        end loop;
1275    else
1276        -- the job is a hourly, so must keep link to PU/D
1277        -- .. fetch the other destination of the same job
1278        execute 'select * from '||quote_ident(tab)||' where job = '||rec1.job||' and not (seq = '||
1279                    dest||')'
1280            into rec2;
1281        if rec2.pu = true
1282        then
1283            -- dest is the drop, so need to get predecessor of rec2 & successor of rec1
1284            for link in execute 'select * from '||quote_ident(link_tab)||' where next_dest = '||
1285                        rec2.seq||' and prev_dest = '||prev_dest
1286            loop
1287                return next link;
1288            end loop;
1289            for link in execute 'select * from '||quote_ident(link_tab)||' where active and prev_dest = '||
1290                        rec1.seq||' and next_dest = '|| next_dest
1291            loop
1292                return next link;
1293            end loop;
1294            -- now for the link which joins the original predecessor & successor of the hourly job together
1295            execute 'select prev_dest from '||quote_ident(link_tab)||' where active and  next_dest = '||rec2.seq
1296                into old_prev;
1297            get diagnostics row_count1 = ROW_COUNT;
1298            execute 'select next_dest from '||quote_ident(link_tab)||' where active and  prev_dest = '||rec1.seq
1299                into old_next;
1300            get diagnostics row_count1 = ROW_COUNT;
1301            if row_count2 < 1 or row_count1 < 1
1302                then
1303                    return;
1304            end if;
1305            for link in execute 'select * from '||quote_ident(link_tab)||' where prev_dest = '||old_prev||
1306                ' and next_dest = '||old_next||' limit 1'
1307            loop
1308                return next link;
1309            end loop;
1310
1311        else
1312            -- dest is the PU, so need to get predecessor or rec1 & successor rec2
1313            for link in execute 'select * from '||quote_ident(link_tab)||' where next_dest = '||
1314                        rec1.seq||' and prev_dest = '||prev_dest
1315            loop
1316                return next link;
1317            end loop;
1318            for link in execute 'select * from '||quote_ident(link_tab)||' where active and prev_dest = '||
1319                        rec2.seq||' and next_dest = '|| next_dest
1320            loop
1321                return next link;
1322            end loop;
1323
1324            -- now for the link which joins the original predecessor & successor of the hourly job together
1325            for link in execute 'select * from '||quote_ident(link_tab)||' where prev_dest in ( select prev_dest from '||
1326                quote_ident(link_tab)||' where active and next_dest = '||rec1.seq||
1327                ') and next_dest in (select next_dest from '||quote_ident(link_tab)||
1328                ' where active and prev_dest = '||rec2.seq||')'
1329            loop
1330                return next link;
1331            end loop;
1332
1333        end if;
1334
1335    end if;
1336    return;
1337end;
1338$$
1339language 'plpgsql' volatile strict;
1340
1341
1342-- Variant takes two destinations representing a path segment to be moved as a contiguous unit.
1343-- NB. this function should be called with destinations belonging to the same job (ie. PU & Drop)
1344-- ... also it is assumed dest precedes dest2 in the path
1345create or replace function path_link_activation_triple(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest int,dest2 int)
1346    returns setof path_links as
1347$$
1348declare
1349    link path_links;
1350    old_prev int;
1351    old_next int;
1352    rec1 record;
1353    rec2 record;
1354    rec4 record;
1355    rec5 record;
1356    rec11 record;
1357    row_count1 int;
1358    row_count2 int;
1359    row_count11 int;
1360begin
1361    -- see if the job is hourly or p2p
1362    execute 'select * from '||quote_ident(tab)||' where seq = '||dest
1363        into rec1;
1364    get diagnostics row_count1 = ROW_COUNT;
1365    execute 'select * from '||quote_ident(tab)||' where seq = '||dest2
1366        into rec11;
1367    get diagnostics row_count11 = ROW_COUNT;
1368    if row_count1 < 1 or row_count11 < 1 or rec1.time > rec11.time
1369        then
1370            return;
1371    end if;
1372    if rec1.job_type = 'e' or rec1.job_type = 'E' or rec11.job_type = 's' or rec11.job_type = 'S'
1373        then
1374            -- dont shift start/end of day destinations
1375            return;
1376    end if;
1377   
1378    if ( rec1.pu or (rec1.job_type = 'p' or  rec1.job_type = 'P' ))
1379            and ( (not rec11.pu) or (rec11.job_type = 'p' or rec11.job_type = 'P'))
1380    then
1381        -- only accept non-p2p job destinations if dest is PU and dest2 is Drop
1382
1383        -- the link which joins the original predecessor & successor of dest together
1384        for link in execute 'select * from '||quote_ident(link_tab)||' where prev_dest in ( select prev_dest from '||
1385                quote_ident(link_tab)||' where active and next_dest = '||dest||
1386                ') and next_dest in (select next_dest from '||quote_ident(link_tab)||
1387                ' where active and prev_dest = '||dest2||')'
1388        loop
1389            return next link;
1390        end loop;
1391
1392        -- the two which splice dest into the new location
1393        for link in execute 'select * from '||quote_ident(link_tab)||' where ( prev_dest = '||
1394            prev_dest||' and next_dest = '||dest||') or (prev_dest = '||dest2||' and next_dest = '||
1395            next_dest||') '
1396        loop
1397            return next link;
1398        end loop;
1399      end if;
1400    return;
1401end;
1402$$
1403language 'plpgsql' volatile strict;
1404
1405create or replace function path_link_activation_triple_oid(tab varchar,link_tab varchar,prev_dest int,next_dest int,dest  int)
1406    returns setof path_links_oid as
1407$$
1408declare
1409    link        path_links_oid;
1410    row1        record;
1411begin
1412    for row1 in execute 'select a.*,b.oid from path_link_activation_triple('||quote_literal(tab)||
1413                ','||quote_literal(link_tab)||','||prev_dest||','||next_dest||','||dest||') a, '||
1414                quote_ident(link_tab)||' b where a.prev_dest = b.prev_dest and a.next_dest = b.next_dest'
1415    loop
1416        link.prev_dest := row1.prev_dest;
1417        link.next_dest := row1.next_dest;
1418        link.dist := row1.dist;
1419        link.time_int := row1.time_int;
1420        link.priority := row1.priority;
1421        link.active := row1.active;
1422        link.oid := row1.oid;
1423        return next link;
1424    end loop;
1425    return;
1426end;
1427$$
1428language 'plpgsql' volatile strict;
1429
1430create or replace function path_link_activation_triple(tab varchar,link_tab varchar,next_dest int,dest int)
1431    returns setof path_links as
1432$$
1433declare
1434    link path_links;
1435    prev int;
1436begin
1437    execute 'select prev_dest from path_links where active and next_dest = '||next_dest||' limit 1'
1438        into prev;
1439
1440    for link in execute 'select * from path_link_activation_triple('||
1441                quote_literal(tab)||','||quote_literal(link_tab)||','||prev||','||next_dest||','||dest||')'
1442    loop
1443        return next link;
1444    end loop;
1445    return;
1446end;
1447$$
1448language 'plpgsql' volatile strict;
1449