ML mostly done pre-testing, see 'TODO sprintf' though
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Sat, 15 Dec 2012 00:13:54 +0000 (19:13 -0500)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Fri, 21 Dec 2012 00:18:47 +0000 (19:18 -0500)
may need to optimize approx

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/action.pm

index 50844fe..49f0d64 100644 (file)
@@ -17,6 +17,32 @@ use OpenILS::Application::Circ::CircCommon;
 use OpenILS::Application::AppUtils;
 my $U = "OpenILS::Application::AppUtils";
 
+# used in build_hold_sort_clause()
+my %HOLD_SORT_ORDER_BY = (
+    pprox => 'p.prox',
+    hprox => 'actor.org_unit_proximity(%d, h.request_lib)',
+    aprox => 'COALESCE(hm.proximity, p.prox)',
+    approx => 'action.hold_copy_calculated_proximity(h.id, %d, %d)',
+    priority => 'pgt.hold_priority',
+    cut => 'CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END',
+    depth => 'h.selection_depth',
+    rtime => 'h.request_time',
+    htime => q!
+        CASE WHEN
+            copy_has_not_been_home.result
+        THEN actor.org_unit_proximity(%d, h.request_lib)
+        ELSE 999
+        END
+    !,
+    shtime => q!
+        CASE WHEN
+            copy_has_not_been_home_even_to_idle.result
+        THEN actor.org_unit_proximity(%d, h.request_lib)
+        ELSE 999
+        END
+    !,
+);
+
 
 sub isTrue {
        my $v = shift;
@@ -294,8 +320,8 @@ sub get_hold_sort_order {
     my $row = $dbh->selectrow_hashref(
         q!
         SELECT
-            cbho.pprox, cbho.hprox, cbho.aprox, cbho.priority,
-            cbho.cut, cbho.depth, cbho.htime, cbho.rtime
+            cbho.pprox, cbho.hprox, cbho.aprox, cbho.approx, cbho.priority,
+            cbho.cut, cbho.depth, cbho.htime, cbho.shtime, cbho.rtime
         FROM config.best_hold_order cbho
         WHERE id = (
             SELECT oils_json_to_text(value)::INT
@@ -309,26 +335,121 @@ sub get_hold_sort_order {
 
     # Return only the keys of our hash, sorted by value,
     # keys for null values omitted.
-    return grep { defined $row->{$_} } (
-        sort {$row->{$a} cmp $row->{$b}} keys %$row
-    );
+    return [
+        grep { defined $row->{$_} } (
+            sort {$row->{$a} cmp $row->{$b}} keys %$row
+        )
+    ];
 }
 
+# Returns an ORDER BY clause *and* a string with a CTE expression to precede
+# the nearest-hold SQL query
 sub build_hold_sort_clause {
-    my (@columns) = @_;
-
-    my %HOLD_SORT_EXPRESSIONS = (
-        pprox => 'p.prox',
-        hprox => '',
-        aprox => '',
-        priority => 'pgt.hold_priority',
-        cut => '',
-        depth => 'h.selection_depth',
-        rtime => 'h.request_time',
-        htime => ''
+    my ($columns, $cp, $here) = @_;
+
+    my %sprintf_args = (
+        hprox => [$cp->circ_lib],
+        approx => [$cp->id, $here->id],
+        htime => [$cp->circ_lib],
+        shtime => [$cp->circ_lib]
     );
 
+    my @clauses;
+    my $ctes_needed = 0;
+    foreach my $col (@$columns) {
+        if ($col eq 'htime' and not $ctes_needed) {
+            $ctes_needed = 1;
+        } elsif ($col eq 'shtime') {
+            $ctes_needed = 2;
+        }
+
+        my @args;
+        @args = @{$sprintf_args{$col}} if exists $sprintf_args{$col};
+
+        push @clauses, sprintf($HOLD_SORT_ORDER_BY{$col}, @args);
+
+        last if $col eq 'rtime';    # rtime is effectively unique, no need for
+                                    # more order-by clauses after that.
+    }
 
+    my ($ctes, $joins);
+    if ($ctes_needed >= 1) {
+        # For our first auxiliary query, the question we seek to answer is, "has
+        # our copy been circulating away from home too long?" Two parts to
+        # answer this question.
+        #
+        # 1: Have their been no checkouts at the copy's circ_lib since the
+        # beginning of our go-home interval?
+        # 2: Was the last transit to affect our copy before the beginning
+        # of our go-home interval an outbound transit? i.e. away from circ-lib
+
+        $ctes .= q!
+, copy_has_not_been_home AS (
+    SELECT (
+        -- part 1
+        SELECT circ.id FROM action.circulation circ
+        JOIN go_home_interval ON (true)
+        WHERE
+            circ.target_copy = %d AND -- $cp->id
+            circ.circ_lib = %d AND  -- $cp->circ_lib
+            circ.xact_start >= NOW() - go_home_interval.value
+    ) IS NULL AND (
+        -- part 2
+        SELECT atc.dest <> $cp->circ_lib FROM action.transit_copy atc
+        JOIN go_home_interval ON (true)
+        WHERE
+            atc.id = (
+                SELECT MAX(id) FROM action.transit_copy atc_inner
+                WHERE
+                    atc_inner.target_copy = $cp AND
+                    atc_inner.source_send_time < NOW() - go_home_interval.value
+            )
+    ) AS result
+) !;   # TODO sprintf
+        $joins .= " JOIN copy_has_not_been_home ON (true) ";
+    }
+    if ($ctes_needed == 2) {
+        # In this auxiliary query, we ask the question, "has our copy come home
+        # by any means that we can determine, even if it didn't circulate once
+        # it came home, in the time defined by the go-home-interval?"
+        # answer this question. Two parts to this too (besides including the
+        # previous auxiliary query).
+        #
+        # 1: there have been no homebound transits for this copy since the
+        # beginning of the go-home interval.
+        # 2: there have been no checkins at home since the beginning of
+        # the go-home interval for this copy
+
+        $ctes .= q!
+, copy_has_not_been_home_even_to_idle AS (
+    SELECT
+        copy_has_not_been_home.response AND (
+            -- part 1
+            SELECT atc.id FROM action.transit_copy atc
+            JOIN go_home_interval ON (true)
+            WHERE
+                atc.target_copy = %d AND -- $cp
+                atc.dest = %d AND  -- $cp->circ_lib
+                atc.dest_recv_time >= NOW() - go_home_interval.value
+        ) IS NULL AND (
+            -- part 2
+            SELECT circ.id FROM action.circulation circ
+            JOIN go_home_interval ON (true)
+            WHERE
+                circ.target_copy = $cp AND
+                circ.checkin_lib = $cp->circ_lib AND
+                circ.checkin_time >= NOW() - go_home_interval.value
+        ) IS NULL
+    AS result
+) !;  # TODO sprintf
+        $joins .= " JOIN copy_has_not_been_home_even_to_idle ON (true) ";
+    }
+
+    return (
+        join(", ", @clauses),
+        $ctes,
+        $joins
+    );
 }
 
 sub nearest_hold {
@@ -340,18 +461,23 @@ sub nearest_hold {
        my $age = shift() || '0 seconds';
        my $fifo = shift();
 
-    if (isTrue($fifo)) {
-        $log->info("deprecated 'fifo' param true, but ignored");
-    }
+    $log->info("deprecated 'fifo' param true, but ignored") if isTrue $fifo;
 
-#      my $holdsort = isTrue($fifo) ?
-#                      "pgt.hold_priority, CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.request_time, h.selection_depth DESC, COALESCE(hm.proximity, h.prox) " :
-#                      "COALESCE(hm.proximity, h.prox), pgt.hold_priority, CASE WHEN h.cut_in_line IS TRUE THEN 0 ELSE 1 END, h.selection_depth DESC, h.request_time ";
-    my $holdsort = build_hold_sort_clause(get_hold_sort_order($here));
+    my ($holdsort, $addl_cte, $addl_join) = build_hold_sort_clause(
+        get_hold_sort_order($here), $cp, $here
+    );
 
        local $OpenILS::Application::Storage::WRITE = 1;
 
        my $ids = action::hold_request->db_Main->selectcol_arrayref(<<" SQL", {}, $here, $cp, $age);
+        WITH go_home_interval AS (
+            SELECT OILS_JSON_TO_TEXT(
+                (SELECT value FROM actor.org_unit_ancestor_setting(
+                    'circ.hold_go_home_interval', ?
+                )
+            ))::INTERVAL AS value
+        )
+        $addl_cte
                SELECT  h.id
                  FROM  action.hold_request h
                        JOIN actor.org_unit_proximity p ON (p.from_org = ? AND p.to_org = h.pickup_lib)
@@ -362,6 +488,7 @@ sub nearest_hold {
                                ON ( au.id = ausp.usr AND ( ausp.stop_date IS NULL OR ausp.stop_date > NOW() ) )
                        LEFT JOIN config.standing_penalty csp
                                ON ( csp.id = ausp.standing_penalty AND csp.block_list LIKE '%CAPTURE%' )
+            $addl_join
                  WHERE hm.target_copy = ?
                        AND (AGE(NOW(),h.request_time) >= CAST(? AS INTERVAL) OR p.prox = 0)
                        AND h.capture_time IS NULL