/*------------------------------------------------------------------------
   File        : TestFollowupReader
   Purpose     : Theme 7 reader.  Answers the follow-up questions raised on
                 #11723 note #14 that the first 89 scenarios did not cover.
   Notes       : Pairs with TestFollowupWriter on harness port 8897.

                 SC01/SC02  re-confirm the forward-only vs SCROLLING difference,
                            because it is surprising (one keyword, 0 vs 20 stale)
                            and note #13 reported it imprecisely.
                 FN01/FN02  does FIND FIRST / FIND NEXT prefetch?  This decides
                            whether prefetching should apply to RAQ at all.
                 OF01       does a join written with OF behave like an explicit
                            WHERE join?
                 RI01       reposition onto a row INSERTED inside the window:
                            does it resolve, and is the window then dropped?
                 RB01       reposition BEYOND the window, then a second writer
                            phase, to see whether a NEW window forms after the
                            target row.
 ----------------------------------------------------------------------*/

using Progress.Lang.*.
using OpenEdge.Core.Assert.
using support.harness.msgbuf.* from propath.

block-level on error undo, throw.

class support.harness.msgbuf.TestFollowupReader:

    define variable sync      as MsgBufSync      no-undo.
    define variable fixture   as MsgBufFixture   no-undo.
    define variable dsFixture as MsgBufDsFixture no-undo.
    define variable log       as MsgBufLog       no-undo.
    define variable tgt       as MsgBufTargets   no-undo.

    define query qFwd    for Book.
    define query qScroll for Book scrolling.

    define buffer bProbe for Book.
    define buffer bJoinBook for Book.

    define variable rids as rowid extent 120 no-undo.

    @Before.
    method public void BeforeAll ():
        if valid-object(sync) then
            return.

        session:suppress-warnings = false no-error.

        fixture = new MsgBufFixture().
        fixture:AssertClientServer().
        dsFixture = new MsgBufDsFixture().
        tgt = new MsgBufTargets().

        log = new MsgBufLog("followup").
        log:StartRun(fixture:ConnectionInfo()).

        sync = new MsgBufSync().
        sync:Connect(8897, "TestFollowupReader").
    end method.

    @After.
    method public void AfterAll ():
        if valid-object(log) then
            log:Dump("support/harness/msgbuf/results").
    end method.

    /* ---------- SCROLLING vs forward-only, re-confirmation ---------- */

    @Test.
    method public void SC01ForwardOnlyQuery ():
        define variable iter as integer no-undo.

        Begin("SC01", "DEFINE QUERY (forward only) + GET NEXT", "blanket update Book -> V2").

        open query qFwd for each Book no-lock.
        get next qFwd.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("SC01").
            get next qFwd.
        end.
        close query qFwd.

        Finish("SC01").
    end method.

    @Test.
    method public void SC02ScrollingQuery ():
        define variable iter as integer no-undo.

        Begin("SC02", "DEFINE QUERY SCROLLING + GET NEXT", "blanket update Book -> V2").

        open query qScroll for each Book no-lock.
        get next qScroll.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("SC02").
            get next qScroll.
        end.
        close query qScroll.

        Finish("SC02").
    end method.

    /* ---------- does a FIND sequence prefetch? ---------- */

    @Test.
    method public void FN01FindNextSequence ():
        define variable iter as integer no-undo.

        /* The RAQ question.  A run of V1 after the pause means FIND NEXT is served
           from a prefetched message; all V2 means every FIND is a server round
           trip. */
        Begin("FN01", "FIND FIRST Book NO-LOCK then repeated FIND NEXT",
              "blanket update Book -> V2").

        find first Book no-lock.
        iter = 1.
        log:Row(iter, Book.book-id, Book.new_dt).

        do while iter < tgt:PauseAt:
            find next Book no-lock no-error.
            if not available Book then leave.
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
        end.

        sync:ReaderPause("FN01").

        repeat:
            find next Book no-lock no-error.
            if not available Book then leave.
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
        end.

        Finish("FN01").
    end method.

    @Test.
    method public void FN02FindNextBracketed ():
        define variable iter as integer no-undo.

        /* Same question with an index bracket, in case a bare FIND NEXT and a
           bracketed one are served differently. */
        Begin("FN02", "FIND FIRST Book WHERE book-id >= 200 NO-LOCK then FIND NEXT",
              "blanket update Book -> V2").

        find first Book where Book.book-id >= 200 no-lock.
        iter = 1.
        log:Row(iter, Book.book-id, Book.new_dt).

        do while iter < tgt:PauseAt:
            find next Book where Book.book-id >= 200 no-lock no-error.
            if not available Book then leave.
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
        end.

        sync:ReaderPause("FN02").

        repeat:
            find next Book where Book.book-id >= 200 no-lock no-error.
            if not available Book then leave.
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).
        end.

        Finish("FN02").
    end method.

    /* ---------- the OF keyword ---------- */

    @Test.
    method public void OF01JoinWithOf ():
        define variable iter as integer no-undo.

        /* OF only compiles when it resolves to exactly ONE index - the ds tables
           have two each and were rejected at compile time with error 446.  Book
           has a single index, so a self-join through a named buffer works, and it
           is the same shape as theme 1's explicit-WHERE self-join: a direct A/B on
           whether OF changes anything. */
        Begin("OF01", "FOR EACH Book NO-LOCK, EACH bJoinBook OF Book NO-LOCK",
              "blanket update Book -> V2").

        for each Book no-lock,
            each bJoinBook of Book no-lock:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt,
                    substitute("innerMark=&1", bJoinBook.new_dt)).
            if iter eq tgt:PauseAt then
                sync:ReaderPause("OF01").
        end.

        Finish("OF01").
    end method.

    /* ---------- reposition onto a row inserted inside the window ---------- */

    @Test.
    method public void RI01RepositionToInsertedRow ():
        define variable iter as integer no-undo.
        define variable rid  as rowid   no-undo.
        define variable done as logical no-undo.

        Begin("RI01", "SCROLLING scan, then REPOSITION onto a row inserted inside the window",
              substitute("insert Book id &1 (inside the window), no other change",
                         tgt:KeyCreateInside)).

        open query qScroll for each Book no-lock.
        get next qScroll.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).

            if iter eq tgt:PauseAt then
                sync:ReaderPause("RI01").
            else if iter eq tgt:PauseAt + 1 and not done then
            do:
                done = true.

                /* The row did not exist when rowids were captured, so look it up
                   now through a separate buffer. */
                find bProbe where bProbe.book-id eq tgt:KeyCreateInside no-lock no-error.
                log:Note(substitute("inserted id &1 findable by the reader: &2",
                                    tgt:KeyCreateInside, available bProbe)).

                if available bProbe then
                do:
                    rid = rowid(bProbe).
                    release bProbe.

                    reposition qScroll to rowid rid no-error.
                    log:Note(substitute("REPOSITION onto the inserted row: available=&1 error=&2",
                                        available Book, error-status:error)).

                    get next qScroll.
                    log:Note(substitute("first GET NEXT after that reposition: available=&1 id=&2 mark=&3",
                                        available Book,
                                        (if available Book then string(Book.book-id) else "n/a"),
                                        (if available Book then Book.new_dt else "n/a"))).
                end.
            end.

            get next qScroll.
        end.
        close query qScroll.

        Finish("RI01").
    end method.

    /* ---------- is there a NEW window after repositioning beyond the old one? ---------- */

    @Test.
    method public void RB01RepositionBeyondThenSecondPhase ():
        define variable iter as integer no-undo.
        define variable done as logical no-undo.

        /* Phase 1 marks V2 while the reader sits inside its first window.  The
           reader then jumps well past that window and stops again; phase 2 marks
           V3.  A run of V2 after the second pause is a window that formed AFTER
           the reposition - which is what "will it prefetch N rows after the
           target" asks. */
        Begin("RB01", "SCROLLING scan, REPOSITION to position 60, then a second writer phase",
              "phase 1: all -> V2 at pause 1; phase 2: all -> V3 at pause 2").
        CaptureRowids().

        open query qScroll for each Book no-lock.
        get next qScroll.
        do while available Book:
            iter = iter + 1.
            log:Row(iter, Book.book-id, Book.new_dt).

            if iter eq tgt:PauseAt then
                sync:ReaderPause("RB01").
            else if iter eq tgt:PauseAt + 1 and not done then
            do:
                done = true.
                reposition qScroll to rowid rids[60] no-error.
                log:Note(substitute("REPOSITION to position 60 (id &1): available=&2 error=&3",
                                    fixture:KeyAt(60), available Book, error-status:error)).
                get next qScroll.
                if available Book then
                    log:Note(substitute("landed on id &1 mark=&2", Book.book-id, Book.new_dt)).
                /* Stop here so the writer can run its second phase while this new
                   position's window - if there is one - is in flight. */
                sync:ReaderPauseSecond("RB01").
            end.

            get next qScroll.
        end.
        close query qScroll.

        Finish("RB01").
    end method.

    /* ---------- shared ---------- */

    method private void Begin (scn as character, queryText as character,
                              writerAction as character):
        fixture:Build(fixture:PROFILE_SLIM).
        log:StartScenario(scn, fixture:Profile, fixture:SampleRecordLength(),
                          tgt:PauseAt, queryText, writerAction).
        sync:ReaderReady(scn).
    end method.

    method private void BeginDs (scn as character, queryText as character,
                                writerAction as character):
        dsFixture:BuildNonunique().
        dsFixture:BuildUnique().
        log:StartScenario(scn, "dsUnique+dsNonunique",
                          dsFixture:SampleRecordLengthNonunique(),
                          tgt:PauseAt, queryText, writerAction).
        sync:ReaderReady(scn).
    end method.

    method private void CaptureRowids ():
        define variable i as integer no-undo.

        for each Book no-lock by Book.book-id:
            i = i + 1.
            if i <= extent(rids) then
                rids[i] = rowid(Book).
        end.

        release Book.
    end method.

    method private void Finish (scn as character):
        sync:ReaderDone(scn).
        log:EndScenario().
        Assert:IsTrue(log:ScenarioRows > 0).
    end method.

    method private void FinishDs (scn as character):
        sync:ReaderDone(scn).
        log:EndScenario().
        Assert:IsTrue(log:ScenarioRows > 0).
    end method.

end class.
