| Line | Hits | Source | Commit |
|---|---|---|---|
| 732 | - | eval_windowaggregates(WindowAggState *winstate) | - |
| 733 | - | { | - |
| 734 | - | WindowStatePerAgg peraggstate; | - |
| 735 | - | int wfuncno, | - |
| 736 | - | numaggs, | - |
| 737 | - | numaggs_restart, | - |
| 738 | - | i; | - |
| 739 | - | int64 aggregatedupto_nonrestarted; | - |
| 740 | - | MemoryContext oldContext; | - |
| 741 | - | ExprContext *econtext; | - |
| 742 | - | WindowObject agg_winobj; | - |
| 743 | - | TupleTableSlot *agg_row_slot; | - |
| 744 | - | TupleTableSlot *temp_slot; | - |
| 745 | - | - | |
| 746 | - | numaggs = winstate->numaggs; | - |
| 747 | - | if (numaggs == 0) | - |
| 748 | - | return; /* nothing to do */ | - |
| 749 | - | - | |
| 750 | - | /* final output execution is in ps_ExprContext */ | - |
| 751 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 752 | - | agg_winobj = winstate->agg_winobj; | - |
| 753 | - | agg_row_slot = winstate->agg_row_slot; | - |
| 754 | - | temp_slot = winstate->temp_slot_1; | - |
| 755 | - | - | |
| 756 | - | /* | - |
| 757 | - | * If the window's frame start clause is UNBOUNDED_PRECEDING and no | - |
| 758 | - | * exclusion clause is specified, then the window frame consists of a | - |
| 759 | - | * contiguous group of rows extending forward from the start of the | - |
| 760 | - | * partition, and rows only enter the frame, never exit it, as the current | - |
| 761 | - | * row advances forward. This makes it possible to use an incremental | - |
| 762 | - | * strategy for evaluating aggregates: we run the transition function for | - |
| 763 | - | * each row added to the frame, and run the final function whenever we | - |
| 764 | - | * need the current aggregate value. This is considerably more efficient | - |
| 765 | - | * than the naive approach of re-running the entire aggregate calculation | - |
| 766 | - | * for each current row. It does assume that the final function doesn't | - |
| 767 | - | * damage the running transition value, but we have the same assumption in | - |
| 768 | - | * nodeAgg.c too (when it rescans an existing hash table). | - |
| 769 | - | * | - |
| 770 | - | * If the frame start does sometimes move, we can still optimize as above | - |
| 771 | - | * whenever successive rows share the same frame head, but if the frame | - |
| 772 | - | * head moves beyond the previous head we try to remove those rows using | - |
| 773 | - | * the aggregate's inverse transition function. This function restores | - |
| 774 | - | * the aggregate's current state to what it would be if the removed row | - |
| 775 | - | * had never been aggregated in the first place. Inverse transition | - |
| 776 | - | * functions may optionally return NULL, indicating that the function was | - |
| 777 | - | * unable to remove the tuple from aggregation. If this happens, or if | - |
| 778 | - | * the aggregate doesn't have an inverse transition function at all, we | - |
| 779 | - | * must perform the aggregation all over again for all tuples within the | - |
| 780 | - | * new frame boundaries. | - |
| 781 | - | * | - |
| 782 | - | * If there's any exclusion clause, then we may have to aggregate over a | - |
| 783 | - | * non-contiguous set of rows, so we punt and recalculate for every row. | - |
| 784 | - | * (For some frame end choices, it might be that the frame is always | - |
| 785 | - | * contiguous anyway, but that's an optimization to investigate later.) | - |
| 786 | - | * | - |
| 787 | - | * In many common cases, multiple rows share the same frame and hence the | - |
| 788 | - | * same aggregate value. (In particular, if there's no ORDER BY in a RANGE | - |
| 789 | - | * window, then all rows are peers and so they all have window frame equal | - |
| 790 | - | * to the whole partition.) We optimize such cases by calculating the | - |
| 791 | - | * aggregate value once when we reach the first row of a peer group, and | - |
| 792 | - | * then returning the saved value for all subsequent rows. | - |
| 793 | - | * | - |
| 794 | - | * 'aggregatedupto' keeps track of the first row that has not yet been | - |
| 795 | - | * accumulated into the aggregate transition values. Whenever we start a | - |
| 796 | - | * new peer group, we accumulate forward to the end of the peer group. | - |
| 797 | - | */ | - |
| 798 | - | - | |
| 799 | - | /* | - |
| 800 | - | * First, update the frame head position. | - |
| 801 | - | * | - |
| 802 | - | * The frame head should never move backwards, and the code below wouldn't | - |
| 803 | - | * cope if it did, so for safety we complain if it does. | - |
| 804 | - | */ | - |
| 805 | - | update_frameheadpos(winstate); | - |
| 806 | - | if (winstate->frameheadpos < winstate->aggregatedbase) | - |
| 807 | - | elog(ERROR, "window frame head moved backward"); | - |
| 808 | - | - | |
| 809 | - | /* | - |
| 810 | - | * If the frame didn't change compared to the previous row, we can re-use | - |
| 811 | - | * the result values that were previously saved at the bottom of this | - |
| 812 | - | * function. Since we don't know the current frame's end yet, this is not | - |
| 813 | - | * possible to check for fully. But if the frame end mode is UNBOUNDED | - |
| 814 | - | * FOLLOWING or CURRENT ROW, no exclusion clause is specified, and the | - |
| 815 | - | * current row lies within the previous row's frame, then the two frames' | - |
| 816 | - | * ends must coincide. Note that on the first row aggregatedbase == | - |
| 817 | - | * aggregatedupto, meaning this test must fail, so we don't need to check | - |
| 818 | - | * the "there was no previous row" case explicitly here. | - |
| 819 | - | */ | - |
| 820 | - | if (winstate->aggregatedbase == winstate->frameheadpos && | - |
| 821 | - | (winstate->frameOptions & (FRAMEOPTION_END_UNBOUNDED_FOLLOWING | | - |
| 822 | - | FRAMEOPTION_END_CURRENT_ROW)) && | - |
| 823 | - | !(winstate->frameOptions & FRAMEOPTION_EXCLUSION) && | - |
| 824 | - | winstate->aggregatedbase <= winstate->currentpos && | - |
| 825 | - | winstate->aggregatedupto > winstate->currentpos) | - |
| 826 | - | { | - |
| 827 | - | for (i = 0; i < numaggs; i++) | - |
| 828 | - | { | - |
| 829 | - | peraggstate = &winstate->peragg[i]; | - |
| 830 | - | wfuncno = peraggstate->wfuncno; | - |
| 831 | - | econtext->ecxt_aggvalues[wfuncno] = peraggstate->resultValue; | - |
| 832 | - | econtext->ecxt_aggnulls[wfuncno] = peraggstate->resultValueIsNull; | - |
| 833 | - | } | - |
| 834 | - | return; | - |
| 835 | - | } | - |
| 836 | - | - | |
| 837 | - | /*---------- | - |
| 838 | - | * Initialize restart flags. | - |
| 839 | - | * | - |
| 840 | - | * We restart the aggregation: | - |
| 841 | - | * - if we're processing the first row in the partition, or | - |
| 842 | - | * - if the frame's head moved and we cannot use an inverse | - |
| 843 | - | * transition function, or | - |
| 844 | - | * - we have an EXCLUSION clause, or | - |
| 845 | - | * - if the new frame doesn't overlap the old one | - |
| 846 | - | * - if RPR (Row Pattern Recognition) is enabled, because the reduced | 24cfb8dRow pattern recognition patch (executor and commands). |
| 847 | - | * frame depends on pattern matching results which can differ entirely | 24cfb8dRow pattern recognition patch (executor and commands). |
| 848 | - | * from row to row, making inverse transition optimization inapplicable | 24cfb8dRow pattern recognition patch (executor and commands). |
| 849 | - | * | - |
| 850 | - | * Note that we don't strictly need to restart in the last case, but if | - |
| 851 | - | * we're going to remove all rows from the aggregation anyway, a restart | - |
| 852 | - | * surely is faster. | - |
| 853 | - | *---------- | - |
| 854 | - | */ | - |
| 855 | - | numaggs_restart = 0; | - |
| 856 | - | for (i = 0; i < numaggs; i++) | - |
| 857 | - | { | - |
| 858 | - | peraggstate = &winstate->peragg[i]; | - |
| 859 | - | if (winstate->currentpos == 0 || | - |
| 860 | - | (winstate->aggregatedbase != winstate->frameheadpos && | - |
| 861 | - | !OidIsValid(peraggstate->invtransfn_oid)) || | - |
| 862 | - | (winstate->frameOptions & FRAMEOPTION_EXCLUSION) || | - |
| 863 | 973700 | winstate->aggregatedupto <= winstate->frameheadpos || | 24cfb8dRow pattern recognition patch (executor and commands). |
| 864 | 15848 | rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 865 | - | { | - |
| 866 | - | peraggstate->restart = true; | - |
| 867 | - | numaggs_restart++; | - |
| 868 | - | } | - |
| 869 | - | else | - |
| 870 | - | peraggstate->restart = false; | - |
| 871 | - | } | - |
| 872 | - | - | |
| 873 | - | /* | - |
| 874 | - | * If we have any possibly-moving aggregates, attempt to advance | - |
| 875 | - | * aggregatedbase to match the frame's head by removing input rows that | - |
| 876 | - | * fell off the top of the frame from the aggregations. This can fail, | - |
| 877 | - | * i.e. advance_windowaggregate_base() can return false, in which case | - |
| 878 | - | * we'll restart that aggregate below. | - |
| 879 | - | */ | - |
| 880 | - | while (numaggs_restart < numaggs && | - |
| 881 | - | winstate->aggregatedbase < winstate->frameheadpos) | - |
| 882 | - | { | - |
| 883 | - | /* | - |
| 884 | - | * Fetch the next tuple of those being removed. This should never fail | - |
| 885 | - | * as we should have been here before. | - |
| 886 | - | */ | - |
| 887 | - | if (!window_gettupleslot(agg_winobj, winstate->aggregatedbase, | - |
| 888 | - | temp_slot)) | - |
| 889 | - | elog(ERROR, "could not re-fetch previously fetched frame row"); | - |
| 890 | - | - | |
| 891 | - | /* Set tuple context for evaluation of aggregate arguments */ | - |
| 892 | - | winstate->tmpcontext->ecxt_outertuple = temp_slot; | - |
| 893 | - | - | |
| 894 | - | /* | - |
| 895 | - | * Perform the inverse transition for each aggregate function in the | - |
| 896 | - | * window, unless it has already been marked as needing a restart. | - |
| 897 | - | */ | - |
| 898 | - | for (i = 0; i < numaggs; i++) | - |
| 899 | - | { | - |
| 900 | - | bool ok; | - |
| 901 | - | - | |
| 902 | - | peraggstate = &winstate->peragg[i]; | - |
| 903 | - | if (peraggstate->restart) | - |
| 904 | - | continue; | - |
| 905 | - | - | |
| 906 | - | wfuncno = peraggstate->wfuncno; | - |
| 907 | - | ok = advance_windowaggregate_base(winstate, | - |
| 908 | - | &winstate->perfunc[wfuncno], | - |
| 909 | - | peraggstate); | - |
| 910 | - | if (!ok) | - |
| 911 | - | { | - |
| 912 | - | /* Inverse transition function has failed, must restart */ | - |
| 913 | - | peraggstate->restart = true; | - |
| 914 | - | numaggs_restart++; | - |
| 915 | - | } | - |
| 916 | - | } | - |
| 917 | - | - | |
| 918 | - | /* Reset per-input-tuple context after each tuple */ | - |
| 919 | - | ResetExprContext(winstate->tmpcontext); | - |
| 920 | - | - | |
| 921 | - | /* And advance the aggregated-row state */ | - |
| 922 | - | winstate->aggregatedbase++; | - |
| 923 | - | ExecClearTuple(temp_slot); | - |
| 924 | - | } | - |
| 925 | - | - | |
| 926 | - | /* | - |
| 927 | - | * If we successfully advanced the base rows of all the aggregates, | - |
| 928 | - | * aggregatedbase now equals frameheadpos; but if we failed for any, we | - |
| 929 | - | * must forcibly update aggregatedbase. | - |
| 930 | - | */ | - |
| 931 | - | winstate->aggregatedbase = winstate->frameheadpos; | - |
| 932 | - | - | |
| 933 | - | /* | - |
| 934 | - | * If we created a mark pointer for aggregates, keep it pushed up to frame | - |
| 935 | - | * head, so that tuplestore can discard unnecessary rows. | - |
| 936 | - | */ | - |
| 937 | - | if (agg_winobj->markptr >= 0) | - |
| 938 | - | WinSetMarkPosition(agg_winobj, winstate->frameheadpos); | - |
| 939 | - | - | |
| 940 | - | /* | - |
| 941 | - | * Now restart the aggregates that require it. | - |
| 942 | - | * | - |
| 943 | - | * We assume that aggregates using the shared context always restart if | - |
| 944 | - | * *any* aggregate restarts, and we may thus clean up the shared | - |
| 945 | - | * aggcontext if that is the case. Private aggcontexts are reset by | - |
| 946 | - | * initialize_windowaggregate() if their owning aggregate restarts. If we | - |
| 947 | - | * aren't restarting an aggregate, we need to free any previously saved | - |
| 948 | - | * result for it, else we'll leak memory. | - |
| 949 | - | */ | - |
| 950 | - | if (numaggs_restart > 0) | - |
| 951 | - | MemoryContextReset(winstate->aggcontext); | - |
| 952 | - | for (i = 0; i < numaggs; i++) | - |
| 953 | - | { | - |
| 954 | - | peraggstate = &winstate->peragg[i]; | - |
| 955 | - | - | |
| 956 | - | /* Aggregates using the shared ctx must restart if *any* agg does */ | - |
| 957 | - | Assert(peraggstate->aggcontext != winstate->aggcontext || | - |
| 958 | - | numaggs_restart == 0 || | - |
| 959 | - | peraggstate->restart); | - |
| 960 | - | - | |
| 961 | - | if (peraggstate->restart) | - |
| 962 | - | { | - |
| 963 | - | wfuncno = peraggstate->wfuncno; | - |
| 964 | - | initialize_windowaggregate(winstate, | - |
| 965 | - | &winstate->perfunc[wfuncno], | - |
| 966 | - | peraggstate); | - |
| 967 | - | } | - |
| 968 | - | else if (!peraggstate->resultValueIsNull) | - |
| 969 | - | { | - |
| 970 | - | if (!peraggstate->resulttypeByVal) | - |
| 971 | - | pfree(DatumGetPointer(peraggstate->resultValue)); | - |
| 972 | - | peraggstate->resultValue = (Datum) 0; | - |
| 973 | - | peraggstate->resultValueIsNull = true; | - |
| 974 | - | } | - |
| 975 | - | } | - |
| 976 | - | - | |
| 977 | - | /* | - |
| 978 | - | * Non-restarted aggregates now contain the rows between aggregatedbase | - |
| 979 | - | * (i.e., frameheadpos) and aggregatedupto, while restarted aggregates | - |
| 980 | - | * contain no rows. If there are any restarted aggregates, we must thus | - |
| 981 | - | * begin aggregating anew at frameheadpos, otherwise we may simply | - |
| 982 | - | * continue at aggregatedupto. We must remember the old value of | - |
| 983 | - | * aggregatedupto to know how long to skip advancing non-restarted | - |
| 984 | - | * aggregates. If we modify aggregatedupto, we must also clear | - |
| 985 | - | * agg_row_slot, per the loop invariant below. | - |
| 986 | - | */ | - |
| 987 | - | aggregatedupto_nonrestarted = winstate->aggregatedupto; | - |
| 988 | - | if (numaggs_restart > 0 && | - |
| 989 | - | winstate->aggregatedupto != winstate->frameheadpos) | - |
| 990 | - | { | - |
| 991 | - | winstate->aggregatedupto = winstate->frameheadpos; | - |
| 992 | - | ExecClearTuple(agg_row_slot); | - |
| 993 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 994 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 995 | - | * If RPR is defined, we do not use aggregatedupto_nonrestarted. To | 24cfb8dRow pattern recognition patch (executor and commands). |
| 996 | - | * avoid assertion failure below, we reset aggregatedupto_nonrestarted | 24cfb8dRow pattern recognition patch (executor and commands). |
| 997 | - | * to frameheadpos. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 998 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 999 | 947530 | if (rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1000 | 946556 | aggregatedupto_nonrestarted = winstate->frameheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1001 | - | } | - |
| 1002 | - | - | |
| 1003 | - | /* | - |
| 1004 | - | * Advance until we reach a row not in frame (or end of partition). | - |
| 1005 | - | * | - |
| 1006 | - | * Note the loop invariant: agg_row_slot is either empty or holds the row | - |
| 1007 | - | * at position aggregatedupto. We advance aggregatedupto after processing | - |
| 1008 | - | * a row. | - |
| 1009 | - | */ | - |
| 1010 | - | for (;;) | - |
| 1011 | - | { | - |
| 1012 | 1991685 | int64 ret; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1013 | - | - | |
| 1014 | - | /* Fetch next row if we didn't already */ | - |
| 1015 | - | if (TupIsNull(agg_row_slot)) | - |
| 1016 | - | { | - |
| 1017 | - | if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto, | - |
| 1018 | - | agg_row_slot)) | - |
| 1019 | - | break; /* must be end of partition */ | - |
| 1020 | - | } | - |
| 1021 | - | - | |
| 1022 | - | /* | - |
| 1023 | - | * Exit loop if no more rows can be in frame. Skip aggregation if | - |
| 1024 | - | * current row is not in frame but there might be more in the frame. | - |
| 1025 | - | */ | - |
| 1026 | - | ret = row_is_in_frame(agg_winobj, winstate->aggregatedupto, | - |
| 1027 | - | agg_row_slot, false); | - |
| 1028 | - | if (ret < 0) | - |
| 1029 | - | break; | - |
| 1030 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1031 | - | if (ret == 0) | - |
| 1032 | - | goto next_tuple; | - |
| 1033 | - | - | |
| 1034 | 1982926 | if (rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1035 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1036 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1037 | - | * If currentpos is already decided but aggregatedupto is not yet | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1038 | - | * determined, we've passed the last reduced frame. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1039 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1040 | 1866344 | if (get_reduced_frame_status(winstate, winstate->currentpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1041 | 1866344 | != RF_NOT_DETERMINED && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1042 | 1866344 | get_reduced_frame_status(winstate, winstate->aggregatedupto) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1043 | - | == RF_NOT_DETERMINED) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1044 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1045 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1046 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1047 | - | * Calculate the reduced frame for aggregatedupto. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1048 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1049 | 1834752 | ret = row_is_in_reduced_frame(winstate->agg_winobj, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1050 | - | winstate->aggregatedupto); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1051 | 1834752 | if (ret == -1) /* unmatched row */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1052 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1053 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1054 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1055 | - | * Check if current row is inside a match but not the head | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1056 | - | * (skipped), and it's the base row for aggregation. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1057 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1058 | 1746500 | if (get_reduced_frame_status(winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1059 | 1713716 | winstate->aggregatedupto) == RF_SKIPPED && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1060 | 1713716 | winstate->aggregatedupto == winstate->aggregatedbase) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1061 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1062 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1063 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1064 | - | /* Set tuple context for evaluation of aggregate arguments */ | - |
| 1065 | - | winstate->tmpcontext->ecxt_outertuple = agg_row_slot; | - |
| 1066 | - | - | |
| 1067 | - | /* Accumulate row into the aggregates */ | - |
| 1068 | - | for (i = 0; i < numaggs; i++) | - |
| 1069 | - | { | - |
| 1070 | - | peraggstate = &winstate->peragg[i]; | - |
| 1071 | - | - | |
| 1072 | - | /* Non-restarted aggs skip until aggregatedupto_nonrestarted */ | - |
| 1073 | - | if (!peraggstate->restart && | - |
| 1074 | - | winstate->aggregatedupto < aggregatedupto_nonrestarted) | - |
| 1075 | - | continue; | - |
| 1076 | - | - | |
| 1077 | - | wfuncno = peraggstate->wfuncno; | - |
| 1078 | - | advance_windowaggregate(winstate, | - |
| 1079 | - | &winstate->perfunc[wfuncno], | - |
| 1080 | - | peraggstate); | - |
| 1081 | - | } | - |
| 1082 | - | - | |
| 1083 | - | next_tuple: | - |
| 1084 | - | /* Reset per-input-tuple context after each tuple */ | - |
| 1085 | - | ResetExprContext(winstate->tmpcontext); | - |
| 1086 | - | - | |
| 1087 | - | /* And advance the aggregated-row state */ | - |
| 1088 | - | winstate->aggregatedupto++; | - |
| 1089 | - | ExecClearTuple(agg_row_slot); | - |
| 1090 | - | } | - |
| 1091 | - | - | |
| 1092 | - | /* The frame's end is not supposed to move backwards, ever */ | - |
| 1093 | - | Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto); | - |
| 1094 | - | - | |
| 1095 | - | /* | - |
| 1096 | - | * finalize aggregates and fill result/isnull fields. | - |
| 1097 | - | */ | - |
| 1098 | - | for (i = 0; i < numaggs; i++) | - |
| 1099 | - | { | - |
| 1100 | - | Datum *result; | - |
| 1101 | - | bool *isnull; | - |
| 1102 | - | - | |
| 1103 | - | peraggstate = &winstate->peragg[i]; | - |
| 1104 | - | wfuncno = peraggstate->wfuncno; | - |
| 1105 | - | result = &econtext->ecxt_aggvalues[wfuncno]; | - |
| 1106 | - | isnull = &econtext->ecxt_aggnulls[wfuncno]; | - |
| 1107 | - | finalize_windowaggregate(winstate, | - |
| 1108 | - | &winstate->perfunc[wfuncno], | - |
| 1109 | - | peraggstate, | - |
| 1110 | - | result, isnull); | - |
| 1111 | - | - | |
| 1112 | - | /* | - |
| 1113 | - | * save the result in case next row shares the same frame. | - |
| 1114 | - | * | - |
| 1115 | - | * XXX in some framing modes, eg ROWS/END_CURRENT_ROW, we can know in | - |
| 1116 | - | * advance that the next row can't possibly share the same frame. Is | - |
| 1117 | - | * it worth detecting that and skipping this code? | - |
| 1118 | - | */ | - |
| 1119 | - | if (!peraggstate->resulttypeByVal && !*isnull) | - |
| 1120 | - | { | - |
| 1121 | - | oldContext = MemoryContextSwitchTo(peraggstate->aggcontext); | - |
| 1122 | - | peraggstate->resultValue = | - |
| 1123 | - | datumCopy(*result, | - |
| 1124 | - | peraggstate->resulttypeByVal, | - |
| 1125 | - | peraggstate->resulttypeLen); | - |
| 1126 | - | MemoryContextSwitchTo(oldContext); | - |
| 1127 | - | } | - |
| 1128 | - | else | - |
| 1129 | - | { | - |
| 1130 | - | peraggstate->resultValue = *result; | - |
| 1131 | - | } | - |
| 1132 | - | peraggstate->resultValueIsNull = *isnull; | - |
| 1133 | - | } | - |
| 1134 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 1198 | - | prepare_tuplestore(WindowAggState *winstate) | - |
| 1199 | - | { | - |
| 1200 | - | WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan; | - |
| 1201 | - | int frameOptions = winstate->frameOptions; | - |
| 1202 | - | int numfuncs = winstate->numfuncs; | - |
| 1203 | - | - | |
| 1204 | - | /* we shouldn't be called if this was done already */ | - |
| 1205 | - | Assert(winstate->buffer == NULL); | - |
| 1206 | - | - | |
| 1207 | - | /* Create new tuplestore */ | - |
| 1208 | - | winstate->buffer = tuplestore_begin_heap(false, false, work_mem); | - |
| 1209 | - | - | |
| 1210 | - | /* | - |
| 1211 | - | * Set up read pointers for the tuplestore. The current pointer doesn't | - |
| 1212 | - | * need BACKWARD capability, but the per-window-function read pointers do, | - |
| 1213 | - | * and the aggregate pointer does if we might need to restart aggregation. | - |
| 1214 | - | */ | - |
| 1215 | - | winstate->current_ptr = 0; /* read pointer 0 is pre-allocated */ | - |
| 1216 | - | - | |
| 1217 | - | /* reset default REWIND capability bit for current ptr */ | - |
| 1218 | - | tuplestore_set_eflags(winstate->buffer, 0); | - |
| 1219 | - | - | |
| 1220 | - | /* create read pointers for aggregates, if needed */ | - |
| 1221 | - | if (winstate->numaggs > 0) | - |
| 1222 | - | { | - |
| 1223 | - | WindowObject agg_winobj = winstate->agg_winobj; | - |
| 1224 | - | int readptr_flags = 0; | - |
| 1225 | - | - | |
| 1226 | - | /* | - |
| 1227 | - | * If the frame head is potentially movable, or we have an EXCLUSION | - |
| 1228 | - | * clause, we might need to restart aggregation ... | - |
| 1229 | - | */ | - |
| 1230 | - | if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) || | - |
| 1231 | - | (frameOptions & FRAMEOPTION_EXCLUSION)) | - |
| 1232 | - | { | - |
| 1233 | - | /* ... so create a mark pointer to track the frame head */ | - |
| 1234 | - | agg_winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, 0); | - |
| 1235 | - | /* and the read pointer will need BACKWARD capability */ | - |
| 1236 | - | readptr_flags |= EXEC_FLAG_BACKWARD; | - |
| 1237 | - | } | - |
| 1238 | - | - | |
| 1239 | - | agg_winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer, | - |
| 1240 | - | readptr_flags); | - |
| 1241 | - | } | - |
| 1242 | - | - | |
| 1243 | - | /* create mark and read pointers for each real window function */ | - |
| 1244 | - | for (int i = 0; i < numfuncs; i++) | - |
| 1245 | - | { | - |
| 1246 | - | WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]); | - |
| 1247 | - | - | |
| 1248 | - | if (!perfuncstate->plain_agg) | - |
| 1249 | - | { | - |
| 1250 | - | WindowObject winobj = perfuncstate->winobj; | - |
| 1251 | - | - | |
| 1252 | - | winobj->markptr = tuplestore_alloc_read_pointer(winstate->buffer, | - |
| 1253 | - | 0); | - |
| 1254 | - | winobj->readptr = tuplestore_alloc_read_pointer(winstate->buffer, | - |
| 1255 | - | EXEC_FLAG_BACKWARD); | - |
| 1256 | - | } | - |
| 1257 | - | } | - |
| 1258 | - | - | |
| 1259 | - | /* Create read/mark pointers for RPR navigation if needed */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1260 | 3997 | if (winstate->nav_winobj) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1261 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1262 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1263 | - | * Allocate mark and read pointers for RPR navigation. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1264 | - | * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1265 | - | * If navMaxOffsetKind == RPR_NAV_OFFSET_FIXED, we advance the mark | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1266 | - | * based on (currentpos - navMaxOffset) and optionally | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1267 | - | * (nfaContext->matchStartRow + navFirstOffset), allowing | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1268 | - | * tuplestore_trim() to free rows that are no longer reachable. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1269 | - | * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1270 | - | * RPR_NAV_OFFSET_NEEDS_EVAL is resolved at executor init; by this | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1271 | - | * point it is either FIXED or RETAIN_ALL. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1272 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1273 | 4832 | winstate->nav_winobj->markptr = | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1274 | 2416 | tuplestore_alloc_read_pointer(winstate->buffer, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1275 | 4832 | winstate->nav_winobj->readptr = | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1276 | 2416 | tuplestore_alloc_read_pointer(winstate->buffer, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1277 | - | EXEC_FLAG_BACKWARD); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1278 | 2416 | winstate->nav_winobj->markpos = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1279 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1280 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1281 | - | /* | - |
| 1282 | - | * If we are in RANGE or GROUPS mode, then determining frame boundaries | - |
| 1283 | - | * requires physical access to the frame endpoint rows, except in certain | - |
| 1284 | - | * degenerate cases. We create read pointers to point to those rows, to | - |
| 1285 | - | * simplify access and ensure that the tuplestore doesn't discard the | - |
| 1286 | - | * endpoint rows prematurely. (Must create pointers in exactly the same | - |
| 1287 | - | * cases that update_frameheadpos and update_frametailpos need them.) | - |
| 1288 | - | */ | - |
| 1289 | - | winstate->framehead_ptr = winstate->frametail_ptr = -1; /* if not used */ | - |
| 1290 | - | - | |
| 1291 | - | if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) | - |
| 1292 | - | { | - |
| 1293 | - | if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) && | - |
| 1294 | - | node->ordNumCols != 0) || | - |
| 1295 | - | (frameOptions & FRAMEOPTION_START_OFFSET)) | - |
| 1296 | - | winstate->framehead_ptr = | - |
| 1297 | - | tuplestore_alloc_read_pointer(winstate->buffer, 0); | - |
| 1298 | - | if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) && | - |
| 1299 | - | node->ordNumCols != 0) || | - |
| 1300 | - | (frameOptions & FRAMEOPTION_END_OFFSET)) | - |
| 1301 | - | winstate->frametail_ptr = | - |
| 1302 | - | tuplestore_alloc_read_pointer(winstate->buffer, 0); | - |
| 1303 | - | } | - |
| 1304 | - | - | |
| 1305 | - | /* | - |
| 1306 | - | * If we have an exclusion clause that requires knowing the boundaries of | - |
| 1307 | - | * the current row's peer group, we create a read pointer to track the | - |
| 1308 | - | * tail position of the peer group (i.e., first row of the next peer | - |
| 1309 | - | * group). The head position does not require its own pointer because we | - |
| 1310 | - | * maintain that as a side effect of advancing the current row. | - |
| 1311 | - | */ | - |
| 1312 | - | winstate->grouptail_ptr = -1; | - |
| 1313 | - | - | |
| 1314 | - | if ((frameOptions & (FRAMEOPTION_EXCLUDE_GROUP | | - |
| 1315 | - | FRAMEOPTION_EXCLUDE_TIES)) && | - |
| 1316 | - | node->ordNumCols != 0) | - |
| 1317 | - | { | - |
| 1318 | - | winstate->grouptail_ptr = | - |
| 1319 | - | tuplestore_alloc_read_pointer(winstate->buffer, 0); | - |
| 1320 | - | } | - |
| 1321 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 1328 | - | begin_partition(WindowAggState *winstate) | - |
| 1329 | - | { | - |
| 1330 | - | PlanState *outerPlan = outerPlanState(winstate); | - |
| 1331 | - | int numfuncs = winstate->numfuncs; | - |
| 1332 | - | - | |
| 1333 | - | winstate->partition_spooled = false; | - |
| 1334 | - | winstate->framehead_valid = false; | - |
| 1335 | - | winstate->frametail_valid = false; | - |
| 1336 | - | winstate->grouptail_valid = false; | - |
| 1337 | 12651 | if (rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1338 | 10192 | clear_reduced_frame(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1339 | - | winstate->spooled_rows = 0; | - |
| 1340 | - | winstate->currentpos = 0; | - |
| 1341 | - | winstate->frameheadpos = 0; | - |
| 1342 | - | winstate->frametailpos = 0; | - |
| 1343 | - | winstate->currentgroup = 0; | - |
| 1344 | - | winstate->frameheadgroup = 0; | - |
| 1345 | - | winstate->frametailgroup = 0; | - |
| 1346 | - | winstate->groupheadpos = 0; | - |
| 1347 | - | winstate->grouptailpos = -1; /* see update_grouptailpos */ | - |
| 1348 | - | ExecClearTuple(winstate->agg_row_slot); | - |
| 1349 | - | if (winstate->framehead_slot) | - |
| 1350 | - | ExecClearTuple(winstate->framehead_slot); | - |
| 1351 | - | if (winstate->frametail_slot) | - |
| 1352 | - | ExecClearTuple(winstate->frametail_slot); | - |
| 1353 | - | - | |
| 1354 | - | /* | - |
| 1355 | - | * If this is the very first partition, we need to fetch the first input | - |
| 1356 | - | * row to store in first_part_slot. | - |
| 1357 | - | */ | - |
| 1358 | - | if (TupIsNull(winstate->first_part_slot)) | - |
| 1359 | - | { | - |
| 1360 | - | TupleTableSlot *outerslot = ExecProcNode(outerPlan); | - |
| 1361 | - | - | |
| 1362 | - | if (!TupIsNull(outerslot)) | - |
| 1363 | - | ExecCopySlot(winstate->first_part_slot, outerslot); | - |
| 1364 | - | else | - |
| 1365 | - | { | - |
| 1366 | - | /* outer plan is empty, so we have nothing to do */ | - |
| 1367 | - | winstate->partition_spooled = true; | - |
| 1368 | - | winstate->more_partitions = false; | - |
| 1369 | - | return; | - |
| 1370 | - | } | - |
| 1371 | - | } | - |
| 1372 | - | - | |
| 1373 | - | /* Create new tuplestore if not done already. */ | - |
| 1374 | - | if (unlikely(winstate->buffer == NULL)) | - |
| 1375 | - | prepare_tuplestore(winstate); | - |
| 1376 | - | - | |
| 1377 | - | winstate->next_partition = false; | - |
| 1378 | - | - | |
| 1379 | - | if (winstate->numaggs > 0) | - |
| 1380 | - | { | - |
| 1381 | - | WindowObject agg_winobj = winstate->agg_winobj; | - |
| 1382 | - | - | |
| 1383 | - | /* reset mark and see positions for aggregate functions */ | - |
| 1384 | - | agg_winobj->markpos = -1; | - |
| 1385 | - | agg_winobj->seekpos = -1; | - |
| 1386 | - | - | |
| 1387 | - | /* Also reset the row counters for aggregates */ | - |
| 1388 | - | winstate->aggregatedbase = 0; | - |
| 1389 | - | winstate->aggregatedupto = 0; | - |
| 1390 | - | } | - |
| 1391 | - | - | |
| 1392 | - | /* reset mark and seek positions for RPR navigation */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1393 | 12627 | if (winstate->nav_winobj) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1394 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1395 | 10180 | winstate->nav_winobj->markpos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1396 | 10180 | winstate->nav_winobj->seekpos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1397 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1398 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1399 | - | /* reset mark and seek positions for each real window function */ | - |
| 1400 | - | for (int i = 0; i < numfuncs; i++) | - |
| 1401 | - | { | - |
| 1402 | - | WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]); | - |
| 1403 | - | - | |
| 1404 | - | if (!perfuncstate->plain_agg) | - |
| 1405 | - | { | - |
| 1406 | - | WindowObject winobj = perfuncstate->winobj; | - |
| 1407 | - | - | |
| 1408 | - | winobj->markpos = -1; | - |
| 1409 | - | winobj->seekpos = -1; | - |
| 1410 | - | - | |
| 1411 | - | /* reset null map */ | - |
| 1412 | - | if (winobj->ignore_nulls == IGNORE_NULLS || | - |
| 1413 | - | winobj->ignore_nulls == PARSER_IGNORE_NULLS) | - |
| 1414 | - | { | - |
| 1415 | - | int numargs = perfuncstate->numArguments; | - |
| 1416 | - | - | |
| 1417 | - | for (int j = 0; j < numargs; j++) | - |
| 1418 | - | { | - |
| 1419 | - | int n = winobj->num_notnull_info[j]; | - |
| 1420 | - | - | |
| 1421 | - | if (n > 0) | - |
| 1422 | - | memset(winobj->notnull_info[j], 0, | - |
| 1423 | - | NN_POS_TO_BYTES(n)); | - |
| 1424 | - | } | - |
| 1425 | - | } | - |
| 1426 | - | } | - |
| 1427 | - | } | - |
| 1428 | - | - | |
| 1429 | - | /* | - |
| 1430 | - | * Store the first tuple into the tuplestore (it's always available now; | - |
| 1431 | - | * we either read it above, or saved it at the end of previous partition) | - |
| 1432 | - | */ | - |
| 1433 | - | tuplestore_puttupleslot(winstate->buffer, winstate->first_part_slot); | - |
| 1434 | - | winstate->spooled_rows++; | - |
| 1435 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 1536 | - | release_partition(WindowAggState *winstate) | - |
| 1537 | - | { | - |
| 1538 | - | int i; | - |
| 1539 | - | - | |
| 1540 | - | for (i = 0; i < winstate->numfuncs; i++) | - |
| 1541 | - | { | - |
| 1542 | - | WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]); | - |
| 1543 | - | - | |
| 1544 | - | /* Release any partition-local state of this window function */ | - |
| 1545 | - | if (perfuncstate->winobj) | - |
| 1546 | - | perfuncstate->winobj->localmem = NULL; | - |
| 1547 | - | } | - |
| 1548 | - | - | |
| 1549 | - | /* | - |
| 1550 | - | * Release all partition-local memory (in particular, any partition-local | - |
| 1551 | - | * state that we might have trashed our pointers to in the above loop, and | - |
| 1552 | - | * any aggregate temp data). We don't rely on retail pfree because some | - |
| 1553 | - | * aggregates might have allocated data we don't have direct pointers to. | - |
| 1554 | - | */ | - |
| 1555 | - | MemoryContextReset(winstate->partcontext); | - |
| 1556 | - | MemoryContextReset(winstate->aggcontext); | - |
| 1557 | - | for (i = 0; i < winstate->numaggs; i++) | - |
| 1558 | - | { | - |
| 1559 | - | if (winstate->peragg[i].aggcontext != winstate->aggcontext) | - |
| 1560 | - | MemoryContextReset(winstate->peragg[i].aggcontext); | - |
| 1561 | - | } | - |
| 1562 | - | - | |
| 1563 | - | if (winstate->buffer) | - |
| 1564 | - | tuplestore_clear(winstate->buffer); | - |
| 1565 | - | winstate->partition_spooled = false; | - |
| 1566 | - | winstate->next_partition = true; | - |
| 1567 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1568 | - | /* Reset RPR match results */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1569 | 17249 | clear_reduced_frame(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1570 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1571 | - | /* Reset NFA state for new partition */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1572 | 17249 | winstate->nfaContext = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1573 | 17249 | winstate->nfaContextTail = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1574 | 17249 | winstate->nfaContextFree = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1575 | 17249 | winstate->nfaStateFree = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1576 | 17249 | winstate->nfaLastProcessedRow = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1577 | 17249 | winstate->nfaStatesActive = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1578 | 17249 | winstate->nfaContextsActive = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1579 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 1580 | - | /* Invalidate the nav slot position cache for the new partition. */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1581 | 17249 | winstate->nav_slot_pos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 1582 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 2311 | - | calculate_frame_offsets(PlanState *pstate) | - |
| 2312 | - | { | - |
| 2313 | - | WindowAggState *winstate = castNode(WindowAggState, pstate); | - |
| 2314 | - | ExprContext *econtext; | - |
| 2315 | - | int frameOptions = winstate->frameOptions; | - |
| 2316 | - | Datum value; | - |
| 2317 | - | bool isnull; | - |
| 2318 | - | int16 len; | - |
| 2319 | - | bool byval; | - |
| 2320 | - | - | |
| 2321 | - | /* Ensure we've not been called before for this scan */ | - |
| 2322 | - | Assert(winstate->all_first); | - |
| 2323 | - | - | |
| 2324 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 2325 | - | - | |
| 2326 | - | if (frameOptions & FRAMEOPTION_START_OFFSET) | - |
| 2327 | - | { | - |
| 2328 | - | Assert(winstate->startOffset != NULL); | - |
| 2329 | - | value = ExecEvalExprSwitchContext(winstate->startOffset, | - |
| 2330 | - | econtext, | - |
| 2331 | - | &isnull); | - |
| 2332 | - | if (isnull) | - |
| 2333 | - | ereport(ERROR, | - |
| 2334 | - | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), | - |
| 2335 | - | errmsg("frame starting offset must not be null"))); | - |
| 2336 | - | /* copy value into query-lifespan context */ | - |
| 2337 | - | get_typlenbyval(exprType((Node *) winstate->startOffset->expr), | - |
| 2338 | - | &len, | - |
| 2339 | - | &byval); | - |
| 2340 | - | winstate->startOffsetValue = datumCopy(value, byval, len); | - |
| 2341 | - | if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)) | - |
| 2342 | - | { | - |
| 2343 | - | /* value is known to be int8 */ | - |
| 2344 | - | int64 offset = DatumGetInt64(value); | - |
| 2345 | - | - | |
| 2346 | - | if (offset < 0) | - |
| 2347 | - | ereport(ERROR, | - |
| 2348 | - | (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), | - |
| 2349 | - | errmsg("frame starting offset must not be negative"))); | - |
| 2350 | - | } | - |
| 2351 | - | } | - |
| 2352 | - | - | |
| 2353 | - | if (frameOptions & FRAMEOPTION_END_OFFSET) | - |
| 2354 | - | { | - |
| 2355 | - | Assert(winstate->endOffset != NULL); | - |
| 2356 | - | value = ExecEvalExprSwitchContext(winstate->endOffset, | - |
| 2357 | - | econtext, | - |
| 2358 | - | &isnull); | - |
| 2359 | - | if (isnull) | - |
| 2360 | - | ereport(ERROR, | - |
| 2361 | - | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), | - |
| 2362 | - | errmsg("frame ending offset must not be null"))); | - |
| 2363 | - | /* copy value into query-lifespan context */ | - |
| 2364 | - | get_typlenbyval(exprType((Node *) winstate->endOffset->expr), | - |
| 2365 | - | &len, | - |
| 2366 | - | &byval); | - |
| 2367 | - | winstate->endOffsetValue = datumCopy(value, byval, len); | - |
| 2368 | - | if (frameOptions & (FRAMEOPTION_ROWS | FRAMEOPTION_GROUPS)) | - |
| 2369 | - | { | - |
| 2370 | - | /* value is known to be int8 */ | - |
| 2371 | - | int64 offset = DatumGetInt64(value); | - |
| 2372 | - | - | |
| 2373 | - | if (offset < 0) | - |
| 2374 | - | ereport(ERROR, | - |
| 2375 | - | (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), | - |
| 2376 | - | errmsg("frame ending offset must not be negative"))); | - |
| 2377 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 2378 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2379 | - | * Row pattern recognition forbids a zero-length frame end; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2380 | - | * checked here so a non-constant offset (e.g. a bind parameter) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2381 | - | * is caught, not just a literal 0. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2382 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2383 | 348 | if (winstate->rpPattern != NULL && offset == 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2384 | 8 | ereport(ERROR, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2385 | - | errcode(ERRCODE_WINDOWING_ERROR), | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2386 | - | errmsg("frame ending offset must be positive with row pattern recognition")); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2387 | - | } | - |
| 2388 | - | } | - |
| 2389 | - | winstate->all_first = false; | - |
| 2390 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 2402 | - | ExecWindowAgg(PlanState *pstate) | - |
| 2403 | - | { | - |
| 2404 | - | WindowAggState *winstate = castNode(WindowAggState, pstate); | - |
| 2405 | - | TupleTableSlot *slot; | - |
| 2406 | - | ExprContext *econtext; | - |
| 2407 | - | int i; | - |
| 2408 | - | int numfuncs; | - |
| 2409 | - | - | |
| 2410 | - | CHECK_FOR_INTERRUPTS(); | - |
| 2411 | - | - | |
| 2412 | - | if (winstate->status == WINDOWAGG_DONE) | - |
| 2413 | - | return NULL; | - |
| 2414 | - | - | |
| 2415 | - | /* | - |
| 2416 | - | * Compute frame offset values, if any, during first call (or after a | - |
| 2417 | - | * rescan). These are assumed to hold constant throughout the scan; if | - |
| 2418 | - | * user gives us a volatile expression, we'll only use its initial value. | - |
| 2419 | - | */ | - |
| 2420 | - | if (unlikely(winstate->all_first)) | - |
| 2421 | - | calculate_frame_offsets(pstate); | - |
| 2422 | - | - | |
| 2423 | - | /* We need to loop as the runCondition or qual may filter out tuples */ | - |
| 2424 | - | for (;;) | - |
| 2425 | - | { | - |
| 2426 | - | if (winstate->next_partition) | - |
| 2427 | - | { | - |
| 2428 | - | /* Initialize for first partition and set current row = 0 */ | - |
| 2429 | - | begin_partition(winstate); | - |
| 2430 | - | /* If there are no input rows, we'll detect that and exit below */ | - |
| 2431 | - | } | - |
| 2432 | - | else | - |
| 2433 | - | { | - |
| 2434 | - | /* Advance current row within partition */ | - |
| 2435 | - | winstate->currentpos++; | - |
| 2436 | - | /* This might mean that the frame moves, too */ | - |
| 2437 | - | winstate->framehead_valid = false; | - |
| 2438 | - | winstate->frametail_valid = false; | - |
| 2439 | - | /* we don't need to invalidate grouptail here; see below */ | - |
| 2440 | - | } | - |
| 2441 | - | - | |
| 2442 | - | /* | - |
| 2443 | - | * Spool all tuples up to and including the current row, if we haven't | - |
| 2444 | - | * already | - |
| 2445 | - | */ | - |
| 2446 | - | spool_tuples(winstate, winstate->currentpos); | - |
| 2447 | - | - | |
| 2448 | - | /* Move to the next partition if we reached the end of this partition */ | - |
| 2449 | - | if (winstate->partition_spooled && | - |
| 2450 | - | winstate->currentpos >= winstate->spooled_rows) | - |
| 2451 | - | { | - |
| 2452 | - | release_partition(winstate); | - |
| 2453 | - | - | |
| 2454 | - | if (winstate->more_partitions) | - |
| 2455 | - | { | - |
| 2456 | - | begin_partition(winstate); | - |
| 2457 | - | Assert(winstate->spooled_rows > 0); | - |
| 2458 | - | - | |
| 2459 | - | /* Come out of pass-through mode when changing partition */ | - |
| 2460 | - | winstate->status = WINDOWAGG_RUN; | - |
| 2461 | - | } | - |
| 2462 | - | else | - |
| 2463 | - | { | - |
| 2464 | - | /* No further partitions? We're done */ | - |
| 2465 | - | winstate->status = WINDOWAGG_DONE; | - |
| 2466 | - | return NULL; | - |
| 2467 | - | } | - |
| 2468 | - | } | - |
| 2469 | - | - | |
| 2470 | - | /* final output execution is in ps_ExprContext */ | - |
| 2471 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 2472 | - | - | |
| 2473 | - | /* Clear the per-output-tuple context for current row */ | - |
| 2474 | - | ResetExprContext(econtext); | - |
| 2475 | - | - | |
| 2476 | - | /* | - |
| 2477 | - | * Read the current row from the tuplestore, and save in | - |
| 2478 | - | * ScanTupleSlot. (We can't rely on the outerplan's output slot | - |
| 2479 | - | * because we may have to read beyond the current row. Also, we have | - |
| 2480 | - | * to actually copy the row out of the tuplestore, since window | - |
| 2481 | - | * function evaluation might cause the tuplestore to dump its state to | - |
| 2482 | - | * disk.) | - |
| 2483 | - | * | - |
| 2484 | - | * In GROUPS mode, or when tracking a group-oriented exclusion clause, | - |
| 2485 | - | * we must also detect entering a new peer group and update associated | - |
| 2486 | - | * state when that happens. We use temp_slot_2 to temporarily hold | - |
| 2487 | - | * the previous row for this purpose. | - |
| 2488 | - | * | - |
| 2489 | - | * Current row must be in the tuplestore, since we spooled it above. | - |
| 2490 | - | */ | - |
| 2491 | - | tuplestore_select_read_pointer(winstate->buffer, winstate->current_ptr); | - |
| 2492 | - | if ((winstate->frameOptions & (FRAMEOPTION_GROUPS | | - |
| 2493 | - | FRAMEOPTION_EXCLUDE_GROUP | | - |
| 2494 | - | FRAMEOPTION_EXCLUDE_TIES)) && | - |
| 2495 | - | winstate->currentpos > 0) | - |
| 2496 | - | { | - |
| 2497 | - | ExecCopySlot(winstate->temp_slot_2, winstate->ss.ss_ScanTupleSlot); | - |
| 2498 | - | if (!tuplestore_gettupleslot(winstate->buffer, true, true, | - |
| 2499 | - | winstate->ss.ss_ScanTupleSlot)) | - |
| 2500 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 2501 | - | if (!are_peers(winstate, winstate->temp_slot_2, | - |
| 2502 | - | winstate->ss.ss_ScanTupleSlot)) | - |
| 2503 | - | { | - |
| 2504 | - | winstate->currentgroup++; | - |
| 2505 | - | winstate->groupheadpos = winstate->currentpos; | - |
| 2506 | - | winstate->grouptail_valid = false; | - |
| 2507 | - | } | - |
| 2508 | - | ExecClearTuple(winstate->temp_slot_2); | - |
| 2509 | - | } | - |
| 2510 | - | else | - |
| 2511 | - | { | - |
| 2512 | - | if (!tuplestore_gettupleslot(winstate->buffer, true, true, | - |
| 2513 | - | winstate->ss.ss_ScanTupleSlot)) | - |
| 2514 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 2515 | - | } | - |
| 2516 | - | - | |
| 2517 | - | /* don't evaluate the window functions when we're in pass-through mode */ | - |
| 2518 | - | if (winstate->status == WINDOWAGG_RUN) | - |
| 2519 | - | { | - |
| 2520 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2521 | - | * If RPR is defined and skip mode is next row, clear the current | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2522 | - | * match so the next row triggers re-evaluation. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2523 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2524 | 1592544 | if (rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2525 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2526 | 988684 | if (winstate->rpSkipTo == ST_NEXT_ROW) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2527 | 7536 | clear_reduced_frame(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2528 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 2529 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 2530 | - | * Drive the row pattern match every row, so it tracks the row | 0ed285dDrive RPR row pattern matching once per row |
| 2531 | - | * scan rather than frame access: a window function that skips | 0ed285dDrive RPR row pattern matching once per row |
| 2532 | - | * the frame (e.g. nth_value() with a NULL offset) must not | 0ed285dDrive RPR row pattern matching once per row |
| 2533 | - | * leave the match state behind currentpos. | 0ed285dDrive RPR row pattern matching once per row |
| 2534 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 2535 | 988684 | Assert(winstate->nav_winobj != NULL); | 0ed285dDrive RPR row pattern matching once per row |
| 2536 | 988684 | (void) row_is_in_reduced_frame(winstate->nav_winobj, | 0ed285dDrive RPR row pattern matching once per row |
| 2537 | - | winstate->currentpos); | 0ed285dDrive RPR row pattern matching once per row |
| 2538 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2539 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 2540 | - | /* | - |
| 2541 | - | * Evaluate true window functions | - |
| 2542 | - | */ | - |
| 2543 | - | numfuncs = winstate->numfuncs; | - |
| 2544 | - | for (i = 0; i < numfuncs; i++) | - |
| 2545 | - | { | - |
| 2546 | - | WindowStatePerFunc perfuncstate = &(winstate->perfunc[i]); | - |
| 2547 | - | - | |
| 2548 | - | if (perfuncstate->plain_agg) | - |
| 2549 | - | continue; | - |
| 2550 | - | eval_windowfunction(winstate, perfuncstate, | - |
| 2551 | - | &(econtext->ecxt_aggvalues[perfuncstate->wfuncstate->wfuncno]), | - |
| 2552 | - | &(econtext->ecxt_aggnulls[perfuncstate->wfuncstate->wfuncno])); | - |
| 2553 | - | } | - |
| 2554 | - | - | |
| 2555 | - | /* | - |
| 2556 | - | * Evaluate aggregates | - |
| 2557 | - | */ | - |
| 2558 | - | if (winstate->numaggs > 0) | - |
| 2559 | - | eval_windowaggregates(winstate); | - |
| 2560 | - | } | - |
| 2561 | - | - | |
| 2562 | - | /* | - |
| 2563 | - | * If we have created auxiliary read pointers for the frame or group | - |
| 2564 | - | * boundaries, force them to be kept up-to-date, because we don't know | - |
| 2565 | - | * whether the window function(s) will do anything that requires that. | - |
| 2566 | - | * Failing to advance the pointers would result in being unable to | - |
| 2567 | - | * trim data from the tuplestore, which is bad. (If we could know in | - |
| 2568 | - | * advance whether the window functions will use frame boundary info, | - |
| 2569 | - | * we could skip creating these pointers in the first place ... but | - |
| 2570 | - | * unfortunately the window function API doesn't require that.) | - |
| 2571 | - | */ | - |
| 2572 | - | if (winstate->framehead_ptr >= 0) | - |
| 2573 | - | update_frameheadpos(winstate); | - |
| 2574 | - | if (winstate->frametail_ptr >= 0) | - |
| 2575 | - | update_frametailpos(winstate); | - |
| 2576 | - | if (winstate->grouptail_ptr >= 0) | - |
| 2577 | - | update_grouptailpos(winstate); | - |
| 2578 | - | - | |
| 2579 | - | /* | - |
| 2580 | - | * Truncate any no-longer-needed rows from the tuplestore. | - |
| 2581 | - | */ | - |
| 2582 | - | tuplestore_trim(winstate->buffer); | - |
| 2583 | - | - | |
| 2584 | - | /* | - |
| 2585 | - | * Form and return a projection tuple using the windowfunc results and | - |
| 2586 | - | * the current row. Setting ecxt_outertuple arranges that any Vars | - |
| 2587 | - | * will be evaluated with respect to that row. | - |
| 2588 | - | */ | - |
| 2589 | - | econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot; | - |
| 2590 | - | - | |
| 2591 | - | slot = ExecProject(winstate->ss.ps.ps_ProjInfo); | - |
| 2592 | - | - | |
| 2593 | - | if (winstate->status == WINDOWAGG_RUN) | - |
| 2594 | - | { | - |
| 2595 | - | econtext->ecxt_scantuple = slot; | - |
| 2596 | - | - | |
| 2597 | - | /* | - |
| 2598 | - | * Now evaluate the run condition to see if we need to go into | - |
| 2599 | - | * pass-through mode, or maybe stop completely. | - |
| 2600 | - | */ | - |
| 2601 | - | if (!ExecQual(winstate->runcondition, econtext)) | - |
| 2602 | - | { | - |
| 2603 | - | /* | - |
| 2604 | - | * Determine which mode to move into. If there is no | - |
| 2605 | - | * PARTITION BY clause and we're the top-level WindowAgg then | - |
| 2606 | - | * we're done. This tuple and any future tuples cannot | - |
| 2607 | - | * possibly match the runcondition. However, when there is a | - |
| 2608 | - | * PARTITION BY clause or we're not the top-level window we | - |
| 2609 | - | * can't just stop as we need to either process other | - |
| 2610 | - | * partitions or ensure WindowAgg nodes above us receive all | - |
| 2611 | - | * of the tuples they need to process their WindowFuncs. | - |
| 2612 | - | */ | - |
| 2613 | - | if (winstate->use_pass_through) | - |
| 2614 | - | { | - |
| 2615 | - | /* | - |
| 2616 | - | * When switching into a pass-through mode, we'd better | - |
| 2617 | - | * NULLify the aggregate results as these are no longer | - |
| 2618 | - | * updated and NULLifying them avoids the old stale | - |
| 2619 | - | * results lingering. Some of these might be byref types | - |
| 2620 | - | * so we can't have them pointing to free'd memory. The | - |
| 2621 | - | * planner insisted that quals used in the runcondition | - |
| 2622 | - | * are strict, so the top-level WindowAgg will always | - |
| 2623 | - | * filter these NULLs out in the filter clause. | - |
| 2624 | - | */ | - |
| 2625 | - | numfuncs = winstate->numfuncs; | - |
| 2626 | - | for (i = 0; i < numfuncs; i++) | - |
| 2627 | - | { | - |
| 2628 | - | econtext->ecxt_aggvalues[i] = (Datum) 0; | - |
| 2629 | - | econtext->ecxt_aggnulls[i] = true; | - |
| 2630 | - | } | - |
| 2631 | - | - | |
| 2632 | - | /* | - |
| 2633 | - | * STRICT pass-through mode is required for the top window | - |
| 2634 | - | * when there is a PARTITION BY clause. Otherwise we must | - |
| 2635 | - | * ensure we store tuples that don't match the | - |
| 2636 | - | * runcondition so they're available to WindowAggs above. | - |
| 2637 | - | */ | - |
| 2638 | - | if (winstate->top_window) | - |
| 2639 | - | { | - |
| 2640 | - | winstate->status = WINDOWAGG_PASSTHROUGH_STRICT; | - |
| 2641 | - | continue; | - |
| 2642 | - | } | - |
| 2643 | - | else | - |
| 2644 | - | { | - |
| 2645 | - | winstate->status = WINDOWAGG_PASSTHROUGH; | - |
| 2646 | - | } | - |
| 2647 | - | } | - |
| 2648 | - | else | - |
| 2649 | - | { | - |
| 2650 | - | /* | - |
| 2651 | - | * Pass-through not required. We can just return NULL. | - |
| 2652 | - | * Nothing else will match the runcondition. | - |
| 2653 | - | */ | - |
| 2654 | - | winstate->status = WINDOWAGG_DONE; | - |
| 2655 | - | return NULL; | - |
| 2656 | - | } | - |
| 2657 | - | } | - |
| 2658 | - | - | |
| 2659 | - | /* | - |
| 2660 | - | * Filter out any tuples we don't need in the top-level WindowAgg. | - |
| 2661 | - | */ | - |
| 2662 | - | if (!ExecQual(winstate->ss.ps.qual, econtext)) | - |
| 2663 | - | { | - |
| 2664 | - | InstrCountFiltered1(winstate, 1); | - |
| 2665 | - | continue; | - |
| 2666 | - | } | - |
| 2667 | - | - | |
| 2668 | - | break; | - |
| 2669 | - | } | - |
| 2670 | - | - | |
| 2671 | - | /* | - |
| 2672 | - | * When not in WINDOWAGG_RUN mode, we must still return this tuple if | - |
| 2673 | - | * we're anything apart from the top window. | - |
| 2674 | - | */ | - |
| 2675 | - | else if (!winstate->top_window) | - |
| 2676 | - | break; | - |
| 2677 | - | } | - |
| 2678 | - | - | |
| 2679 | - | return slot; | - |
| 2680 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 2690 | - | ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) | - |
| 2691 | - | { | - |
| 2692 | - | WindowAggState *winstate; | - |
| 2693 | - | Plan *outerPlan; | - |
| 2694 | - | ExprContext *econtext; | - |
| 2695 | - | ExprContext *tmpcontext; | - |
| 2696 | - | WindowStatePerFunc perfunc; | - |
| 2697 | - | WindowStatePerAgg peragg; | - |
| 2698 | - | int frameOptions = node->frameOptions; | - |
| 2699 | - | int numfuncs, | - |
| 2700 | - | wfuncno, | - |
| 2701 | - | numaggs, | - |
| 2702 | - | aggno; | - |
| 2703 | - | TupleDesc scanDesc; | - |
| 2704 | - | ListCell *l; | - |
| 2705 | - | - | |
| 2706 | - | /* check for unsupported flags */ | - |
| 2707 | - | Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); | - |
| 2708 | - | - | |
| 2709 | - | /* | - |
| 2710 | - | * create state structure | - |
| 2711 | - | */ | - |
| 2712 | - | winstate = makeNode(WindowAggState); | - |
| 2713 | - | winstate->ss.ps.plan = (Plan *) node; | - |
| 2714 | - | winstate->ss.ps.state = estate; | - |
| 2715 | - | winstate->ss.ps.ExecProcNode = ExecWindowAgg; | - |
| 2716 | - | - | |
| 2717 | - | /* copy frame options to state node for easy access */ | - |
| 2718 | - | winstate->frameOptions = frameOptions; | - |
| 2719 | - | - | |
| 2720 | - | /* | - |
| 2721 | - | * Create expression contexts. We need two, one for per-input-tuple | - |
| 2722 | - | * processing and one for per-output-tuple processing, plus an optional | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2723 | - | * third for row pattern recognition DEFINE evaluation (built just below | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2724 | - | * when a DEFINE clause is present). We cheat a little by using | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2725 | - | * ExecAssignExprContext() to build them all. Each call overwrites | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2726 | - | * ps_ExprContext, so the last call must establish the output context. | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2727 | - | */ | - |
| 2728 | - | ExecAssignExprContext(estate, &winstate->ss.ps); | - |
| 2729 | - | tmpcontext = winstate->ss.ps.ps_ExprContext; | - |
| 2730 | - | winstate->tmpcontext = tmpcontext; | - |
| 2731 | - | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation | |
| 2732 | - | /* | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2733 | - | * Row pattern recognition evaluates DEFINE clauses in a third context, | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2734 | - | * reset before each DEFINE evaluation pass. It must be distinct from | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2735 | - | * tmpcontext and ps_ExprContext so its reset frees neither input nor | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2736 | - | * output tuple memory. | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2737 | - | */ | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2738 | 4974 | if (node->defineClause != NIL) | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2739 | - | { | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2740 | 2996 | ExecAssignExprContext(estate, &winstate->ss.ps); | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2741 | 2996 | winstate->rprContext = winstate->ss.ps.ps_ExprContext; | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2742 | - | } | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 2743 | - | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation | |
| 2744 | - | ExecAssignExprContext(estate, &winstate->ss.ps); | - |
| 2745 | - | - | |
| 2746 | - | /* Create long-lived context for storage of partition-local memory etc */ | - |
| 2747 | - | winstate->partcontext = | - |
| 2748 | - | AllocSetContextCreate(CurrentMemoryContext, | - |
| 2749 | - | "WindowAgg Partition", | - |
| 2750 | - | ALLOCSET_DEFAULT_SIZES); | - |
| 2751 | - | - | |
| 2752 | - | /* | - |
| 2753 | - | * Create mid-lived context for aggregate trans values etc. | - |
| 2754 | - | * | - |
| 2755 | - | * Note that moving aggregates each use their own private context, not | - |
| 2756 | - | * this one. | - |
| 2757 | - | */ | - |
| 2758 | - | winstate->aggcontext = | - |
| 2759 | - | AllocSetContextCreate(CurrentMemoryContext, | - |
| 2760 | - | "WindowAgg Aggregates", | - |
| 2761 | - | ALLOCSET_DEFAULT_SIZES); | - |
| 2762 | - | - | |
| 2763 | - | /* Only the top-level WindowAgg may have a qual */ | - |
| 2764 | - | Assert(node->plan.qual == NIL || node->topWindow); | - |
| 2765 | - | - | |
| 2766 | - | /* Initialize the qual */ | - |
| 2767 | - | winstate->ss.ps.qual = ExecInitQual(node->plan.qual, | - |
| 2768 | - | (PlanState *) winstate); | - |
| 2769 | - | - | |
| 2770 | - | /* | - |
| 2771 | - | * Setup the run condition, if we received one from the query planner. | - |
| 2772 | - | * When set, this may allow us to move into pass-through mode so that we | - |
| 2773 | - | * don't have to perform any further evaluation of WindowFuncs in the | - |
| 2774 | - | * current partition or possibly stop returning tuples altogether when all | - |
| 2775 | - | * tuples are in the same partition. | - |
| 2776 | - | */ | - |
| 2777 | - | winstate->runcondition = ExecInitQual(node->runCondition, | - |
| 2778 | - | (PlanState *) winstate); | - |
| 2779 | - | - | |
| 2780 | - | /* | - |
| 2781 | - | * When we're not the top-level WindowAgg node or we are but have a | - |
| 2782 | - | * PARTITION BY clause we must move into one of the WINDOWAGG_PASSTHROUGH* | - |
| 2783 | - | * modes when the runCondition becomes false. | - |
| 2784 | - | */ | - |
| 2785 | - | winstate->use_pass_through = !node->topWindow || node->partNumCols > 0; | - |
| 2786 | - | - | |
| 2787 | - | /* remember if we're the top-window or we are below the top-window */ | - |
| 2788 | - | winstate->top_window = node->topWindow; | - |
| 2789 | - | - | |
| 2790 | - | /* | - |
| 2791 | - | * initialize child nodes | - |
| 2792 | - | */ | - |
| 2793 | - | outerPlan = outerPlan(node); | - |
| 2794 | - | outerPlanState(winstate) = ExecInitNode(outerPlan, estate, eflags); | - |
| 2795 | - | - | |
| 2796 | - | /* | - |
| 2797 | - | * initialize source tuple type (which is also the tuple type that we'll | - |
| 2798 | - | * store in the tuplestore and use in all our working slots). | - |
| 2799 | - | */ | - |
| 2800 | - | ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple); | - |
| 2801 | - | scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; | - |
| 2802 | - | - | |
| 2803 | - | /* the outer tuple isn't the child's tuple, but always a minimal tuple */ | - |
| 2804 | - | winstate->ss.ps.outeropsset = true; | - |
| 2805 | - | winstate->ss.ps.outerops = &TTSOpsMinimalTuple; | - |
| 2806 | - | winstate->ss.ps.outeropsfixed = true; | - |
| 2807 | - | - | |
| 2808 | - | /* | - |
| 2809 | - | * tuple table initialization | - |
| 2810 | - | */ | - |
| 2811 | - | winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2812 | - | &TTSOpsMinimalTuple); | - |
| 2813 | - | winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2814 | - | &TTSOpsMinimalTuple); | - |
| 2815 | - | winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2816 | - | &TTSOpsMinimalTuple); | - |
| 2817 | - | winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2818 | - | &TTSOpsMinimalTuple); | - |
| 2819 | - | - | |
| 2820 | 4974 | if (node->rpPattern != NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2821 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2822 | 2996 | winstate->nav_slot = ExecInitExtraTupleSlot(estate, scanDesc, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2823 | - | &TTSOpsMinimalTuple); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2824 | 2996 | winstate->nav_slot_pos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2825 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 2826 | 2996 | winstate->nav_null_slot = ExecInitExtraTupleSlot(estate, scanDesc, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2827 | - | &TTSOpsMinimalTuple); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2828 | 2996 | winstate->nav_null_slot = ExecStoreAllNullTuple(winstate->nav_null_slot); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2829 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 2830 | 2996 | winstate->nav_saved_outertuple = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2831 | 2996 | winstate->nav_match_start = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2832 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 2833 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 2834 | - | /* | - |
| 2835 | - | * create frame head and tail slots only if needed (must create slots in | - |
| 2836 | - | * exactly the same cases that update_frameheadpos and update_frametailpos | - |
| 2837 | - | * need them) | - |
| 2838 | - | */ | - |
| 2839 | - | winstate->framehead_slot = winstate->frametail_slot = NULL; | - |
| 2840 | - | - | |
| 2841 | - | if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS)) | - |
| 2842 | - | { | - |
| 2843 | - | if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) && | - |
| 2844 | - | node->ordNumCols != 0) || | - |
| 2845 | - | (frameOptions & FRAMEOPTION_START_OFFSET)) | - |
| 2846 | - | winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2847 | - | &TTSOpsMinimalTuple); | - |
| 2848 | - | if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) && | - |
| 2849 | - | node->ordNumCols != 0) || | - |
| 2850 | - | (frameOptions & FRAMEOPTION_END_OFFSET)) | - |
| 2851 | - | winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc, | - |
| 2852 | - | &TTSOpsMinimalTuple); | - |
| 2853 | - | } | - |
| 2854 | - | - | |
| 2855 | - | /* | - |
| 2856 | - | * Initialize result slot, type and projection. | - |
| 2857 | - | */ | - |
| 2858 | - | ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual); | - |
| 2859 | - | ExecAssignProjectionInfo(&winstate->ss.ps, NULL); | - |
| 2860 | - | - | |
| 2861 | - | /* Set up data for comparing tuples */ | - |
| 2862 | - | if (node->partNumCols > 0) | - |
| 2863 | - | winstate->partEqfunction = | - |
| 2864 | - | execTuplesMatchPrepare(scanDesc, | - |
| 2865 | - | node->partNumCols, | - |
| 2866 | - | node->partColIdx, | - |
| 2867 | - | node->partOperators, | - |
| 2868 | - | node->partCollations, | - |
| 2869 | - | &winstate->ss.ps); | - |
| 2870 | - | - | |
| 2871 | - | if (node->ordNumCols > 0) | - |
| 2872 | - | winstate->ordEqfunction = | - |
| 2873 | - | execTuplesMatchPrepare(scanDesc, | - |
| 2874 | - | node->ordNumCols, | - |
| 2875 | - | node->ordColIdx, | - |
| 2876 | - | node->ordOperators, | - |
| 2877 | - | node->ordCollations, | - |
| 2878 | - | &winstate->ss.ps); | - |
| 2879 | - | - | |
| 2880 | - | /* | - |
| 2881 | - | * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes. | - |
| 2882 | - | */ | - |
| 2883 | - | numfuncs = winstate->numfuncs; | - |
| 2884 | - | numaggs = winstate->numaggs; | - |
| 2885 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 2886 | - | econtext->ecxt_aggvalues = palloc0_array(Datum, numfuncs); | - |
| 2887 | - | econtext->ecxt_aggnulls = palloc0_array(bool, numfuncs); | - |
| 2888 | - | - | |
| 2889 | - | /* | - |
| 2890 | - | * allocate per-wfunc/per-agg state information. | - |
| 2891 | - | */ | - |
| 2892 | - | perfunc = palloc0_array(WindowStatePerFuncData, numfuncs); | - |
| 2893 | - | peragg = palloc0_array(WindowStatePerAggData, numaggs); | - |
| 2894 | - | winstate->perfunc = perfunc; | - |
| 2895 | - | winstate->peragg = peragg; | - |
| 2896 | - | - | |
| 2897 | - | wfuncno = -1; | - |
| 2898 | - | aggno = -1; | - |
| 2899 | - | foreach(l, winstate->funcs) | - |
| 2900 | - | { | - |
| 2901 | - | WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l); | - |
| 2902 | - | WindowFunc *wfunc = wfuncstate->wfunc; | - |
| 2903 | - | WindowStatePerFunc perfuncstate; | - |
| 2904 | - | AclResult aclresult; | - |
| 2905 | - | int i; | - |
| 2906 | - | - | |
| 2907 | - | if (wfunc->winref != node->winref) /* planner screwed up? */ | - |
| 2908 | - | elog(ERROR, "WindowFunc with winref %u assigned to WindowAgg with winref %u", | - |
| 2909 | - | wfunc->winref, node->winref); | - |
| 2910 | - | - | |
| 2911 | - | /* | - |
| 2912 | - | * Look for a previous duplicate window function, which needs the same | - |
| 2913 | - | * ignore_nulls value | - |
| 2914 | - | */ | - |
| 2915 | - | for (i = 0; i <= wfuncno; i++) | - |
| 2916 | - | { | - |
| 2917 | - | if (equal(wfunc, perfunc[i].wfunc) && | - |
| 2918 | - | !contain_volatile_functions((Node *) wfunc)) | - |
| 2919 | - | break; | - |
| 2920 | - | } | - |
| 2921 | - | if (i <= wfuncno && wfunc->ignore_nulls == perfunc[i].ignore_nulls) | - |
| 2922 | - | { | - |
| 2923 | - | /* Found a match to an existing entry, so just mark it */ | - |
| 2924 | - | wfuncstate->wfuncno = i; | - |
| 2925 | - | continue; | - |
| 2926 | - | } | - |
| 2927 | - | - | |
| 2928 | - | /* Nope, so assign a new PerAgg record */ | - |
| 2929 | - | perfuncstate = &perfunc[++wfuncno]; | - |
| 2930 | - | - | |
| 2931 | - | /* Mark WindowFunc state node with assigned index in the result array */ | - |
| 2932 | - | wfuncstate->wfuncno = wfuncno; | - |
| 2933 | - | - | |
| 2934 | - | /* Check permission to call window function */ | - |
| 2935 | - | aclresult = object_aclcheck(ProcedureRelationId, wfunc->winfnoid, GetUserId(), | - |
| 2936 | - | ACL_EXECUTE); | - |
| 2937 | - | if (aclresult != ACLCHECK_OK) | - |
| 2938 | - | aclcheck_error(aclresult, OBJECT_FUNCTION, | - |
| 2939 | - | get_func_name(wfunc->winfnoid)); | - |
| 2940 | - | InvokeFunctionExecuteHook(wfunc->winfnoid); | - |
| 2941 | - | - | |
| 2942 | - | /* Fill in the perfuncstate data */ | - |
| 2943 | - | perfuncstate->wfuncstate = wfuncstate; | - |
| 2944 | - | perfuncstate->wfunc = wfunc; | - |
| 2945 | - | perfuncstate->numArguments = list_length(wfuncstate->args); | - |
| 2946 | - | perfuncstate->winCollation = wfunc->inputcollid; | - |
| 2947 | - | - | |
| 2948 | - | get_typlenbyval(wfunc->wintype, | - |
| 2949 | - | &perfuncstate->resulttypeLen, | - |
| 2950 | - | &perfuncstate->resulttypeByVal); | - |
| 2951 | - | - | |
| 2952 | - | /* | - |
| 2953 | - | * If it's really just a plain aggregate function, we'll emulate the | - |
| 2954 | - | * Agg environment for it. | - |
| 2955 | - | */ | - |
| 2956 | - | perfuncstate->plain_agg = wfunc->winagg; | - |
| 2957 | - | if (wfunc->winagg) | - |
| 2958 | - | { | - |
| 2959 | - | WindowStatePerAgg peraggstate; | - |
| 2960 | - | - | |
| 2961 | - | perfuncstate->aggno = ++aggno; | - |
| 2962 | - | peraggstate = &winstate->peragg[aggno]; | - |
| 2963 | - | initialize_peragg(winstate, wfunc, peraggstate); | - |
| 2964 | - | peraggstate->wfuncno = wfuncno; | - |
| 2965 | - | } | - |
| 2966 | - | else | - |
| 2967 | - | { | - |
| 2968 | - | WindowObject winobj = makeNode(WindowObjectData); | - |
| 2969 | - | - | |
| 2970 | - | winobj->winstate = winstate; | - |
| 2971 | - | winobj->argstates = wfuncstate->args; | - |
| 2972 | - | winobj->localmem = NULL; | - |
| 2973 | - | perfuncstate->winobj = winobj; | - |
| 2974 | - | winobj->ignore_nulls = wfunc->ignore_nulls; | - |
| 2975 | - | init_notnull_info(winobj, perfuncstate); | - |
| 2976 | - | - | |
| 2977 | - | /* It's a real window function, so set up to call it. */ | - |
| 2978 | - | fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo, | - |
| 2979 | - | econtext->ecxt_per_query_memory); | - |
| 2980 | - | fmgr_info_set_expr((Node *) wfunc, &perfuncstate->flinfo); | - |
| 2981 | - | } | - |
| 2982 | - | } | - |
| 2983 | - | - | |
| 2984 | - | /* Update numfuncs, numaggs to match number of unique functions found */ | - |
| 2985 | - | winstate->numfuncs = wfuncno + 1; | - |
| 2986 | - | winstate->numaggs = aggno + 1; | - |
| 2987 | - | - | |
| 2988 | - | /* Set up WindowObject for aggregates, if needed */ | - |
| 2989 | - | if (winstate->numaggs > 0) | - |
| 2990 | - | { | - |
| 2991 | - | WindowObject agg_winobj = makeNode(WindowObjectData); | - |
| 2992 | - | - | |
| 2993 | - | agg_winobj->winstate = winstate; | - |
| 2994 | - | agg_winobj->argstates = NIL; | - |
| 2995 | - | agg_winobj->localmem = NULL; | - |
| 2996 | - | /* make sure markptr = -1 to invalidate. It may not get used */ | - |
| 2997 | - | agg_winobj->markptr = -1; | - |
| 2998 | - | agg_winobj->readptr = -1; | - |
| 2999 | - | winstate->agg_winobj = agg_winobj; | - |
| 3000 | - | } | - |
| 3001 | - | - | |
| 3002 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3003 | - | * Set up WindowObject for RPR navigation opcodes. This is separate from | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3004 | - | * agg_winobj because it needs its own read pointer to avoid interfering | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3005 | - | * with aggregate processing. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3006 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3007 | 4974 | if (node->rpPattern != NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3008 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3009 | 2996 | WindowObject nav_winobj = makeNode(WindowObjectData); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3010 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3011 | 2996 | nav_winobj->winstate = winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3012 | 2996 | nav_winobj->argstates = NIL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3013 | 2996 | nav_winobj->localmem = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3014 | 2996 | nav_winobj->markptr = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3015 | 2996 | nav_winobj->readptr = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3016 | 2996 | winstate->nav_winobj = nav_winobj; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3017 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3018 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3019 | - | /* Set the status to running */ | - |
| 3020 | - | winstate->status = WINDOWAGG_RUN; | - |
| 3021 | - | - | |
| 3022 | - | /* initialize frame bound offset expressions */ | - |
| 3023 | - | winstate->startOffset = ExecInitExpr((Expr *) node->startOffset, | - |
| 3024 | - | (PlanState *) winstate); | - |
| 3025 | - | winstate->endOffset = ExecInitExpr((Expr *) node->endOffset, | - |
| 3026 | - | (PlanState *) winstate); | - |
| 3027 | - | - | |
| 3028 | - | /* Lookup in_range support functions if needed */ | - |
| 3029 | - | if (OidIsValid(node->startInRangeFunc)) | - |
| 3030 | - | fmgr_info(node->startInRangeFunc, &winstate->startInRangeFunc); | - |
| 3031 | - | if (OidIsValid(node->endInRangeFunc)) | - |
| 3032 | - | fmgr_info(node->endInRangeFunc, &winstate->endInRangeFunc); | - |
| 3033 | - | winstate->inRangeColl = node->inRangeColl; | - |
| 3034 | - | winstate->inRangeAsc = node->inRangeAsc; | - |
| 3035 | - | winstate->inRangeNullsFirst = node->inRangeNullsFirst; | - |
| 3036 | - | - | |
| 3037 | - | /* Set up SKIP TO type */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3038 | 4974 | winstate->rpSkipTo = node->rpSkipTo; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3039 | - | /* Set up row pattern recognition PATTERN clause (compiled NFA) */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3040 | 4974 | winstate->rpPattern = node->rpPattern; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3041 | - | /* Set up nav offsets for tuplestore trim; resolve any NEEDS_EVAL kinds */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3042 | 4974 | winstate->navMaxOffsetKind = node->navMaxOffsetKind; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3043 | 4974 | winstate->navMaxOffset = node->navMaxOffset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3044 | 4974 | winstate->hasFirstNav = node->hasFirstNav; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3045 | 4974 | winstate->navFirstOffsetKind = node->navFirstOffsetKind; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3046 | 4974 | winstate->navFirstOffset = node->navFirstOffset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3047 | 4974 | eval_define_offsets(winstate, node->defineClause); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3048 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3049 | - | /* Copy match_start dependency bitmapset for per-context evaluation */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3050 | 4974 | winstate->defineMatchStartDependent = bms_copy(node->defineMatchStartDependent); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3051 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3052 | - | /* Calculate NFA state size and allocate cycle detection bitmap */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3053 | 4974 | if (node->rpPattern != NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3054 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3055 | 2996 | int nfaVisitedNWords; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3056 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3057 | 2996 | winstate->nfaStateSize = offsetof(RPRNFAState, counts) + | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3058 | 2996 | sizeof(int32) * node->rpPattern->maxDepth; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3059 | 2996 | nfaVisitedNWords = | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3060 | 2996 | (node->rpPattern->numElements - 1) / BITS_PER_BITMAPWORD + 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3061 | 2996 | winstate->nfaVisitedElems = palloc0(sizeof(bitmapword) * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3062 | - | nfaVisitedNWords); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3063 | - | /* High-water mark sentinels: no bits set yet. */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3064 | 2996 | winstate->nfaVisitedMinWord = PG_INT16_MAX; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3065 | 2996 | winstate->nfaVisitedMaxWord = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3066 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3067 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3068 | - | /* Set up row pattern recognition DEFINE clause */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3069 | 4974 | winstate->defineVariableList = NIL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3070 | 4974 | winstate->defineClauseExprs = NIL; | b848408Tidy up row pattern recognition plumbing |
| 3071 | - | b848408Tidy up row pattern recognition plumbing | |
| 3072 | - | /* | b848408Tidy up row pattern recognition plumbing |
| 3073 | - | * Compile DEFINE clause expressions. PREV/NEXT navigation is handled by | b848408Tidy up row pattern recognition plumbing |
| 3074 | - | * EEOP_RPR_NAV_SET/RESTORE opcodes emitted during ExecInitExpr, so no | b848408Tidy up row pattern recognition plumbing |
| 3075 | - | * varno rewriting is needed here. | b848408Tidy up row pattern recognition plumbing |
| 3076 | - | */ | b848408Tidy up row pattern recognition plumbing |
| 3077 | 11922 | foreach_node(TargetEntry, te, node->defineClause) | b848408Tidy up row pattern recognition plumbing |
| 3078 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3079 | 6948 | char *name = te->resname; | b848408Tidy up row pattern recognition plumbing |
| 3080 | 6948 | ExprState *exprstate; | b848408Tidy up row pattern recognition plumbing |
| 3081 | - | b848408Tidy up row pattern recognition plumbing | |
| 3082 | 13896 | winstate->defineVariableList = | b848408Tidy up row pattern recognition plumbing |
| 3083 | 6948 | lappend(winstate->defineVariableList, | b848408Tidy up row pattern recognition plumbing |
| 3084 | 6948 | makeString(pstrdup(name))); | b848408Tidy up row pattern recognition plumbing |
| 3085 | - | b848408Tidy up row pattern recognition plumbing | |
| 3086 | 6948 | exprstate = ExecInitExpr(te->expr, (PlanState *) winstate); | b848408Tidy up row pattern recognition plumbing |
| 3087 | - | b848408Tidy up row pattern recognition plumbing | |
| 3088 | 6948 | winstate->defineClauseExprs = | b848408Tidy up row pattern recognition plumbing |
| 3089 | 6948 | lappend(winstate->defineClauseExprs, exprstate); | b848408Tidy up row pattern recognition plumbing |
| 3090 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3091 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3092 | - | /* Initialize NFA free lists for row pattern matching */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3093 | 4974 | winstate->nfaContext = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3094 | 4974 | winstate->nfaContextTail = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3095 | 4974 | winstate->nfaContextFree = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3096 | 4974 | winstate->nfaStateFree = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3097 | 4974 | winstate->nfaLastProcessedRow = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3098 | 4974 | winstate->nfaStatesActive = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3099 | 4974 | winstate->nfaContextsActive = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3100 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3101 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3102 | - | * Allocate varMatched array for NFA evaluation. With the new varNames | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3103 | - | * ordering (DEFINE order first), varId == defineIdx for all defined | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3104 | - | * variables, so no mapping is needed. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3105 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3106 | 4974 | if (winstate->defineVariableList != NIL) | 4cf9108Further tidy up row pattern recognition plumbing |
| 3107 | 2996 | winstate->nfaVarMatched = palloc0(sizeof(bool) * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3108 | 2996 | list_length(winstate->defineVariableList)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3109 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3110 | 1978 | winstate->nfaVarMatched = NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3111 | - | winstate->all_first = true; | - |
| 3112 | - | winstate->partition_spooled = false; | - |
| 3113 | - | winstate->more_partitions = false; | - |
| 3114 | - | winstate->next_partition = true; | - |
| 3115 | - | - | |
| 3116 | - | return winstate; | - |
| 3117 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 3126 | 983092 | ExecRPRNavGetSlot(WindowAggState *winstate, int64 pos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3127 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3128 | 983092 | WindowObject winobj = winstate->nav_winobj; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3129 | 983092 | TupleTableSlot *slot = winstate->nav_slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3130 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3131 | 983092 | if (pos < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3132 | 22556 | return winstate->nav_null_slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3133 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3134 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3135 | - | * If nav_slot already holds this position, return it without re-fetching. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3136 | - | * This is critical when multiple PREV/NEXT calls in the same expression | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3137 | - | * navigate to the same row, because re-fetching would free the slot's | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3138 | - | * tuple memory and invalidate any pass-by-ref Datum pointers from earlier | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3139 | - | * navigation results. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3140 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3141 | 960536 | if (winstate->nav_slot_pos == pos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3142 | - | return slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3143 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3144 | 484480 | if (!window_gettupleslot(winobj, pos, slot)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3145 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3146 | 152 | winstate->nav_slot_pos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3147 | 152 | return winstate->nav_null_slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3148 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3149 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3150 | 484328 | winstate->nav_slot_pos = pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3151 | 484328 | return slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3152 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 3195 | - | ExecReScanWindowAgg(WindowAggState *node) | - |
| 3196 | - | { | - |
| 3197 | - | PlanState *outerPlan = outerPlanState(node); | - |
| 3198 | - | ExprContext *econtext = node->ss.ps.ps_ExprContext; | - |
| 3199 | - | - | |
| 3200 | - | node->status = WINDOWAGG_RUN; | - |
| 3201 | - | node->all_first = true; | - |
| 3202 | - | - | |
| 3203 | - | /* release tuplestore et al */ | - |
| 3204 | - | release_partition(node); | - |
| 3205 | - | - | |
| 3206 | - | /* release all temp tuples, but especially first_part_slot */ | - |
| 3207 | - | ExecClearTuple(node->ss.ss_ScanTupleSlot); | - |
| 3208 | - | ExecClearTuple(node->first_part_slot); | - |
| 3209 | - | ExecClearTuple(node->agg_row_slot); | - |
| 3210 | - | ExecClearTuple(node->temp_slot_1); | - |
| 3211 | - | ExecClearTuple(node->temp_slot_2); | - |
| 3212 | 108 | if (node->nav_slot) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3213 | 56 | ExecClearTuple(node->nav_slot); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3214 | - | if (node->framehead_slot) | - |
| 3215 | - | ExecClearTuple(node->framehead_slot); | - |
| 3216 | - | if (node->frametail_slot) | - |
| 3217 | - | ExecClearTuple(node->frametail_slot); | - |
| 3218 | - | - | |
| 3219 | - | /* Forget current wfunc values */ | - |
| 3220 | - | MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numfuncs); | - |
| 3221 | - | MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numfuncs); | - |
| 3222 | - | - | |
| 3223 | - | /* | - |
| 3224 | - | * if chgParam of subnode is not null then plan will be re-scanned by | - |
| 3225 | - | * first ExecProcNode. | - |
| 3226 | - | */ | - |
| 3227 | - | if (outerPlan->chgParam == NULL) | - |
| 3228 | - | ExecReScan(outerPlan); | - |
| 3229 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 3555 | - | window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot) | - |
| 3556 | - | { | - |
| 3557 | - | WindowAggState *winstate = winobj->winstate; | - |
| 3558 | - | MemoryContext oldcontext; | - |
| 3559 | - | - | |
| 3560 | - | /* often called repeatedly in a row */ | - |
| 3561 | - | CHECK_FOR_INTERRUPTS(); | - |
| 3562 | - | - | |
| 3563 | - | /* Don't allow passing -1 to spool_tuples here */ | - |
| 3564 | - | if (pos < 0) | - |
| 3565 | - | return false; | - |
| 3566 | - | - | |
| 3567 | - | /* If necessary, fetch the tuple into the spool */ | - |
| 3568 | - | spool_tuples(winstate, pos); | - |
| 3569 | - | - | |
| 3570 | - | if (pos >= winstate->spooled_rows) | - |
| 3571 | - | return false; | - |
| 3572 | - | - | |
| 3573 | - | if (pos < winobj->markpos) | - |
| 3574 | 0 | elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT, | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed defensive-unreachable. Line 3574 is an elog(ERROR) guarding against fetching a tuple before a WindowObject's mark position. This is a long-standing can't-happen internal-consistency check (predates RPR; the patch only changed the format string from a literal to INT64_FORMAT). The mark invariant is actively maintained by all callers: WinSetMarkPosition only ever advances the mark (line 5020 rejects backward moves), and the mark is always computed from the minimum position that will subsequently be re-fetched. For the new RPR path, advance_nav_mark (lines 4380-4419) deliberately bounds navmarkpos by Min(navmarkpos, firstreach) so that FIRST/PREV/LAST navigations can never request a row before the mark. Frame-based callers similarly mark at frameheadpos before fetching only at >= frameheadpos. Reaching line 3574 would require the executor's own mark bookkeeping to be internally inconsistent, which is not controllable by any SQL/regression input. I attempted to refute via the RPR navigation path (the only new caller surface) and could not construct an input that fetches before the mark, because the mark advancement is gated on the navigation reach bounds.Recommended fix Keep as elog(ERROR). This is standard PostgreSQL practice for tuplestore mark-position invariant violations (matches sibling "unexpected end of tuplestore" elogs in the same function). Converting to Assert would drop the safety net in production (non-cassert) builds for a memory/tuplestore corruption scenario, which is undesirable. No change needed; the RPR format-string update to INT64_FORMAT is correct. | |||
| 3575 | - | pos, winobj->markpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3576 | - | - | |
| 3577 | - | oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory); | - |
| 3578 | - | - | |
| 3579 | - | tuplestore_select_read_pointer(winstate->buffer, winobj->readptr); | - |
| 3580 | - | - | |
| 3581 | - | /* | - |
| 3582 | - | * Advance or rewind until we are within one tuple of the one we want. | - |
| 3583 | - | */ | - |
| 3584 | - | if (winobj->seekpos < pos - 1) | - |
| 3585 | - | { | - |
| 3586 | - | if (!tuplestore_skiptuples(winstate->buffer, | - |
| 3587 | - | pos - 1 - winobj->seekpos, | - |
| 3588 | - | true)) | - |
| 3589 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 3590 | - | winobj->seekpos = pos - 1; | - |
| 3591 | - | } | - |
| 3592 | - | else if (winobj->seekpos > pos + 1) | - |
| 3593 | - | { | - |
| 3594 | - | if (!tuplestore_skiptuples(winstate->buffer, | - |
| 3595 | - | winobj->seekpos - (pos + 1), | - |
| 3596 | - | false)) | - |
| 3597 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 3598 | - | winobj->seekpos = pos + 1; | - |
| 3599 | - | } | - |
| 3600 | - | else if (winobj->seekpos == pos) | - |
| 3601 | - | { | - |
| 3602 | - | /* | - |
| 3603 | - | * There's no API to refetch the tuple at the current position. We | - |
| 3604 | - | * have to move one tuple forward, and then one backward. (We don't | - |
| 3605 | - | * do it the other way because we might try to fetch the row before | - |
| 3606 | - | * our mark, which isn't allowed.) XXX this case could stand to be | - |
| 3607 | - | * optimized. | - |
| 3608 | - | */ | - |
| 3609 | - | tuplestore_advance(winstate->buffer, true); | - |
| 3610 | - | winobj->seekpos++; | - |
| 3611 | - | } | - |
| 3612 | - | - | |
| 3613 | - | /* | - |
| 3614 | - | * Now we should be on the tuple immediately before or after the one we | - |
| 3615 | - | * want, so just fetch forwards or backwards as appropriate. | - |
| 3616 | - | * | - |
| 3617 | - | * Notice that we tell tuplestore_gettupleslot to make a physical copy of | - |
| 3618 | - | * the fetched tuple. This ensures that the slot's contents remain valid | - |
| 3619 | - | * through manipulations of the tuplestore, which some callers depend on. | - |
| 3620 | - | */ | - |
| 3621 | - | if (winobj->seekpos > pos) | - |
| 3622 | - | { | - |
| 3623 | - | if (!tuplestore_gettupleslot(winstate->buffer, false, true, slot)) | - |
| 3624 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 3625 | - | winobj->seekpos--; | - |
| 3626 | - | } | - |
| 3627 | - | else | - |
| 3628 | - | { | - |
| 3629 | - | if (!tuplestore_gettupleslot(winstate->buffer, true, true, slot)) | - |
| 3630 | - | elog(ERROR, "unexpected end of tuplestore"); | - |
| 3631 | - | winobj->seekpos++; | - |
| 3632 | - | } | - |
| 3633 | - | - | |
| 3634 | - | Assert(winobj->seekpos == pos); | - |
| 3635 | - | - | |
| 3636 | - | MemoryContextSwitchTo(oldcontext); | - |
| 3637 | - | - | |
| 3638 | - | return true; | - |
| 3639 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 3680 | - | ignorenulls_getfuncarginframe(WindowObject winobj, int argno, | - |
| 3681 | - | int relpos, int seektype, bool set_mark, | - |
| 3682 | - | bool *isnull, bool *isout) | - |
| 3683 | - | { | - |
| 3684 | - | WindowAggState *winstate; | - |
| 3685 | - | ExprContext *econtext; | - |
| 3686 | - | TupleTableSlot *slot; | - |
| 3687 | - | Datum datum; | - |
| 3688 | - | int64 abs_pos; | - |
| 3689 | - | int64 mark_pos; | - |
| 3690 | - | int notnull_offset; | - |
| 3691 | - | int notnull_relpos; | - |
| 3692 | - | int forward; | - |
| 3693 | 1088 | int64 num_reduced_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3694 | - | - | |
| 3695 | - | Assert(WindowObjectIsValid(winobj)); | - |
| 3696 | - | winstate = winobj->winstate; | - |
| 3697 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 3698 | - | slot = winstate->temp_slot_1; | - |
| 3699 | - | datum = (Datum) 0; | - |
| 3700 | - | notnull_offset = 0; | - |
| 3701 | - | notnull_relpos = abs(relpos); | - |
| 3702 | - | - | |
| 3703 | - | switch (seektype) | - |
| 3704 | - | { | - |
| 3705 | - | case WINDOW_SEEK_CURRENT: | - |
| 3706 | - | elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame"); | - |
| 3707 | - | abs_pos = mark_pos = 0; /* keep compiler quiet */ | - |
| 3708 | - | break; | - |
| 3709 | - | case WINDOW_SEEK_HEAD: | - |
| 3710 | - | /* rejecting relpos < 0 is easy and simplifies code below */ | - |
| 3711 | - | if (relpos < 0) | - |
| 3712 | - | goto out_of_frame; | - |
| 3713 | - | update_frameheadpos(winstate); | - |
| 3714 | - | abs_pos = winstate->frameheadpos; | - |
| 3715 | - | mark_pos = winstate->frameheadpos; | - |
| 3716 | - | forward = 1; | - |
| 3717 | - | break; | - |
| 3718 | - | case WINDOW_SEEK_TAIL: | - |
| 3719 | - | /* rejecting relpos > 0 is easy and simplifies code below */ | - |
| 3720 | - | if (relpos > 0) | - |
| 3721 | - | goto out_of_frame; | - |
| 3722 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3723 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3724 | - | * RPR cares about frame head pos. Need to call | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3725 | - | * update_frameheadpos | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3726 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3727 | 308 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3728 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3729 | - | update_frametailpos(winstate); | - |
| 3730 | - | abs_pos = winstate->frametailpos - 1; | - |
| 3731 | - | mark_pos = 0; /* keep compiler quiet */ | - |
| 3732 | - | forward = -1; | - |
| 3733 | - | break; | - |
| 3734 | - | default: | - |
| 3735 | - | elog(ERROR, "unrecognized window seek type: %d", seektype); | - |
| 3736 | - | abs_pos = mark_pos = 0; /* keep compiler quiet */ | - |
| 3737 | - | break; | - |
| 3738 | - | } | - |
| 3739 | - | - | |
| 3740 | - | /* | - |
| 3741 | - | * Get the next nonnull value in the frame, moving forward or backward | - |
| 3742 | - | * until we find a value or reach the frame's end. | - |
| 3743 | - | */ | - |
| 3744 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3745 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3746 | - | * Check whether current row is in reduced frame. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3747 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3748 | 1088 | num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3749 | 1088 | if (num_reduced_frame < 0) /* unmatched or skipped row */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3750 | 320 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3751 | 768 | else if (num_reduced_frame > 0) /* the first row of the reduced frame */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3752 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3753 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3754 | - | * Early check if row could be out of reduced frame. When RPR is | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3755 | - | * enabled, EXCLUDE clause cannot be specified and the frame is always | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3756 | - | * contiguous. So we can safely perform the following checks. Note, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3757 | - | * however, it is possible that a row is out of reduced frame if | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3758 | - | * there's a NULL in the middle. So we need to check it in the | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3759 | - | * following do loop. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3760 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3761 | 88 | if (seektype == WINDOW_SEEK_HEAD && relpos >= num_reduced_frame) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3762 | 16 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3763 | 72 | if (seektype == WINDOW_SEEK_TAIL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3764 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3765 | 24 | if (notnull_relpos >= num_reduced_frame) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3766 | 0 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Line 3766 (goto out_of_frame inside the WINDOW_SEEK_TAIL + num_reduced_frame>0 branch) is unreachable by any in-tree SQL/regression input, and the proposed test categorically cannot cover it. The guard at 3765 is `if (notnull_relpos >= num_reduced_frame)`, where notnull_relpos = abs(relpos) (line 3701) and the code only reaches 3765 inside the `num_reduced_frame > 0` branch (line 3751), so num_reduced_frame >= 1. To take the goto we need notnull_relpos >= 1, i.e. relpos != 0. But I verified the ONLY caller of WinGetFuncArgInFrame with WINDOW_SEEK_TAIL anywhere in the tree is window_last_value (src/backend/utils/adt/windowfuncs.c:686), which hardcodes relpos=0. With relpos=0, notnull_relpos=0 and `0 >= num_reduced_frame` (>=1) is always FALSE. nth_value and all other tail-from-end needs route through WINDOW_SEEK_HEAD (nth-1, line 720), and RPR FIRST/LAST/PREV/NEXT navigation uses a separate nav_winobj/execRPR.c path, not WinGetFuncArgInFrame. Hence no SQL can make a TAIL seek with |relpos|>=1. The proposed test (last_value(val) IGNORE NULLS) uses exactly relpos=0, so it would execute lines 3763-3764 and 3769 but NEVER line 3766. The symmetric HEAD guard at 3761 (`relpos >= num_reduced_frame`) IS reachable because nth_value passes nth-1 via HEAD; line 3766 is its dead mirror for the TAIL case. Caveat: WinGetFuncArgInFrame is exported in windowapi.h, so a third-party C extension window function could call it with TAIL and a negative relpos and reach 3766 — that is why I classify it as defensive rather than strictly dead code, but it remains unreachable from SQL/regression.Recommended fix Optional, not required for correctness: since notnull_relpos is provably 0 for every reachable TAIL call (only window_last_value reaches here, with relpos=0), the runtime check at 3765-3766 can be downgraded to an assertion documenting the invariant, e.g. replace `if (notnull_relpos >= num_reduced_frame) goto out_of_frame; ` with `Assert(notnull_relpos == 0); ` (keeping line 3769's abs_pos assignment). This removes the uncovered branch while preserving defense against a hypothetical future nonzero-relpos TAIL caller in debug builds. If the maintainers prefer to keep symmetry with the HEAD guard at 3761 as future-proofing for extension authors, leave the line as-is and accept it as defensive-unreachable. | |||
| 3767 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3768 | - | /* not out of reduced frame. Set abspos as a starting point */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3769 | 24 | abs_pos = winstate->frameheadpos + num_reduced_frame - 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3770 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3771 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3772 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 3773 | - | do | - |
| 3774 | - | { | - |
| 3775 | - | int inframe; | - |
| 3776 | - | int v; | - |
| 3777 | - | - | |
| 3778 | - | /* | - |
| 3779 | - | * Check apparent out of frame case. We need to do this because we | - |
| 3780 | - | * may not call window_gettupleslot before row_is_in_frame, which | - |
| 3781 | - | * supposes abs_pos is never negative. | - |
| 3782 | - | */ | - |
| 3783 | - | if (abs_pos < 0) | - |
| 3784 | - | goto out_of_frame; | - |
| 3785 | - | - | |
| 3786 | - | /* check whether row is in frame */ | - |
| 3787 | - | inframe = row_is_in_frame(winobj, abs_pos, slot, true); | - |
| 3788 | - | if (inframe == -1) | - |
| 3789 | - | goto out_of_frame; | - |
| 3790 | - | else if (inframe == 0) | - |
| 3791 | - | goto advance; | - |
| 3792 | - | - | |
| 3793 | - | if (isout) | - |
| 3794 | - | *isout = false; | - |
| 3795 | - | - | |
| 3796 | - | v = get_notnull_info(winobj, abs_pos, argno); | - |
| 3797 | - | if (v == NN_NULL) /* this row is known to be NULL */ | - |
| 3798 | - | goto advance; | - |
| 3799 | - | - | |
| 3800 | - | else if (v == NN_UNKNOWN) /* need to check NULL or not */ | - |
| 3801 | - | { | - |
| 3802 | - | if (!window_gettupleslot(winobj, abs_pos, slot)) | - |
| 3803 | - | goto out_of_frame; | - |
| 3804 | - | - | |
| 3805 | - | econtext->ecxt_outertuple = slot; | - |
| 3806 | - | datum = ExecEvalExpr( | - |
| 3807 | - | (ExprState *) list_nth(winobj->argstates, | - |
| 3808 | - | argno), econtext, | - |
| 3809 | - | isnull); | - |
| 3810 | - | if (!*isnull) | - |
| 3811 | - | notnull_offset++; | - |
| 3812 | - | - | |
| 3813 | - | /* record the row status */ | - |
| 3814 | - | put_notnull_info(winobj, abs_pos, argno, *isnull); | - |
| 3815 | - | } | - |
| 3816 | - | else /* this row is known to be NOT NULL */ | - |
| 3817 | - | { | - |
| 3818 | - | notnull_offset++; | - |
| 3819 | - | if (notnull_offset > notnull_relpos) | - |
| 3820 | - | { | - |
| 3821 | - | /* to prepare exiting this loop, datum needs to be set */ | - |
| 3822 | - | if (!window_gettupleslot(winobj, abs_pos, slot)) | - |
| 3823 | - | goto out_of_frame; | - |
| 3824 | - | - | |
| 3825 | - | econtext->ecxt_outertuple = slot; | - |
| 3826 | - | datum = ExecEvalExpr( | - |
| 3827 | - | (ExprState *) list_nth | - |
| 3828 | - | (winobj->argstates, argno), | - |
| 3829 | - | econtext, isnull); | - |
| 3830 | - | } | - |
| 3831 | - | } | - |
| 3832 | - | advance: | - |
| 3833 | - | abs_pos += forward; | - |
| 3834 | 1664 | if (rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3835 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3836 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3837 | - | * Check whether we are still in the reduced frame. (also check | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3838 | - | * if we succeeded in getting the target row). | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3839 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3840 | 136 | num_reduced_frame--; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3841 | 136 | if (num_reduced_frame <= 0 && notnull_offset <= notnull_relpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3842 | 12 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3843 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 3844 | - | } while (notnull_offset <= notnull_relpos); | - |
| 3845 | - | - | |
| 3846 | - | if (set_mark) | - |
| 3847 | - | WinSetMarkPosition(winobj, mark_pos); | - |
| 3848 | - | - | |
| 3849 | - | return datum; | - |
| 3850 | - | - | |
| 3851 | - | out_of_frame: | - |
| 3852 | - | if (isout) | - |
| 3853 | - | *isout = true; | - |
| 3854 | - | *isnull = true; | - |
| 3855 | - | return (Datum) 0; | - |
| 3856 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 3999 | 36 | eval_nav_offset_helper(WindowAggState *winstate, Expr *offset_expr, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4000 | - | int64 defaultOffset) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4001 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4002 | 36 | ExprContext *econtext = winstate->ss.ps.ps_ExprContext; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4003 | 36 | ExprState *estate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4004 | 36 | Datum val; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4005 | 36 | bool isnull; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4006 | 36 | int64 offset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4007 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4008 | 36 | if (offset_expr == NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4009 | 36 | return defaultOffset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4010 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4011 | 36 | estate = ExecInitExpr(offset_expr, (PlanState *) winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4012 | 36 | val = ExecEvalExprSwitchContext(estate, econtext, &isnull); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4013 | - | 95b07bcSupport window functions a la SQL:2008. | |
| 4014 | 36 | if (isnull) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4015 | - | return 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4016 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4017 | 36 | offset = DatumGetInt64(val); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4018 | 36 | if (offset < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4019 | 0 | return 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test In src/test/regress/sql/rpr_integration.sql, reuse the existing PREPARE rpr_prev(int) (DEFINE B AS val > PREV(val, $1)) and add a negative EXECUTE strictly under the GENERIC plan mode so $1 stays a Param (NEEDS_EVAL). The literal-substituted custom plan folds -1 to a Const and skips eval_nav_offset_helper entirely. Example (place after the existing force_generic_plan block): SET plan_cache_mode = force_generic_plan; -- negative non-constant nav offset: eval_nav_offset_helper clamps to 0 at -- executor init (covers nodeWindowAgg.c:4019), then the per-row nav path -- raises the negative-offset error. EXECUTE rpr_prev(-1); Expected output in rpr_integration.out: ERROR: row pattern navigation offset must not be negative (Verified empirically: gcov line 4019 hit count = 1 under generic plan; = 0 under custom plan.) A volatile non-const offset such as DEFINE B AS val > PREV(val, (val - 100000)) over rows where val-100000 < 0 is an equivalent non-prepared alternative, but the prepared-statement generic-plan route is the minimal, deterministic addition.Verdict CONFIRMED reachable and CONFIRMED testable, with one critical correction the original finding under-specifies. Line 4019 (`if (offset < 0) return 0; `) at nodeWindowAgg.c:4018-4019 is only reached when a NEEDS_EVAL nav offset evaluates negative at executor-init time (eval_define_offsets -> visit_nav_exec -> eval_nav_offset_helper, called from ExecInitWindowAgg at line 3047, before any row is processed). I traced the planner side: extract_const_offset (createplan.c:2510-2525) folds any Const offset at plan time and clamps negatives there (line 2519-2520), marking it FIXED; only a NON-Const offset (Param under a generic plan, random()::int, subquery, column ref) returns false -> maxNeedsEval/firstNeedsEval -> RPR_NAV_OFFSET_NEEDS_EVAL -> eval_nav_offset_helper is invoked. So a negative literal can NEVER reach line 4019 (it is folded/clamped in the planner). I verified this empirically: built a 19beta1 coverage cluster from tmp_install, ran the exact prepared statement. Under force_generic_plan, EXECUTE rpr_prev(-1) drove gcov execution count 1 on line 4019, then errored per-row with "row pattern navigation offset must not be negative". Under force_custom_plan the SAME EXECUTE left line 4019 at ##### (not executed) because the param is substituted as a folded Const. The original finding's phrase "add EXECUTE rpr_prev(-1)" is therefore correct ONLY if the negative EXECUTE runs in the force_generic_plan section; placing it under the existing custom-plan example would NOT cover the line. The query also produces an error in the .out (expected, per MEMORY: capture errors, no DO block). | |||
| 4020 | - | - | |
| 4021 | - | return offset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4022 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4017 | 36 | offset = DatumGetInt64(val); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4018 | 36 | if (offset < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4019 | 0 | return 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test In src/test/regress/sql/rpr_integration.sql, reuse the existing PREPARE rpr_prev(int) (DEFINE B AS val > PREV(val, $1)) and add a negative EXECUTE strictly under the GENERIC plan mode so $1 stays a Param (NEEDS_EVAL). The literal-substituted custom plan folds -1 to a Const and skips eval_nav_offset_helper entirely. Example (place after the existing force_generic_plan block): SET plan_cache_mode = force_generic_plan; -- negative non-constant nav offset: eval_nav_offset_helper clamps to 0 at -- executor init (covers nodeWindowAgg.c:4019), then the per-row nav path -- raises the negative-offset error. EXECUTE rpr_prev(-1); Expected output in rpr_integration.out: ERROR: row pattern navigation offset must not be negative (Verified empirically: gcov line 4019 hit count = 1 under generic plan; = 0 under custom plan.) A volatile non-const offset such as DEFINE B AS val > PREV(val, (val - 100000)) over rows where val-100000 < 0 is an equivalent non-prepared alternative, but the prepared-statement generic-plan route is the minimal, deterministic addition.Verdict CONFIRMED reachable and CONFIRMED testable, with one critical correction the original finding under-specifies. Line 4019 (`if (offset < 0) return 0; `) at nodeWindowAgg.c:4018-4019 is only reached when a NEEDS_EVAL nav offset evaluates negative at executor-init time (eval_define_offsets -> visit_nav_exec -> eval_nav_offset_helper, called from ExecInitWindowAgg at line 3047, before any row is processed). I traced the planner side: extract_const_offset (createplan.c:2510-2525) folds any Const offset at plan time and clamps negatives there (line 2519-2520), marking it FIXED; only a NON-Const offset (Param under a generic plan, random()::int, subquery, column ref) returns false -> maxNeedsEval/firstNeedsEval -> RPR_NAV_OFFSET_NEEDS_EVAL -> eval_nav_offset_helper is invoked. So a negative literal can NEVER reach line 4019 (it is folded/clamped in the planner). I verified this empirically: built a 19beta1 coverage cluster from tmp_install, ran the exact prepared statement. Under force_generic_plan, EXECUTE rpr_prev(-1) drove gcov execution count 1 on line 4019, then errored per-row with "row pattern navigation offset must not be negative". Under force_custom_plan the SAME EXECUTE left line 4019 at ##### (not executed) because the param is substituted as a folded Const. The original finding's phrase "add EXECUTE rpr_prev(-1)" is therefore correct ONLY if the negative EXECUTE runs in the force_generic_plan section; placing it under the existing custom-plan example would NOT cover the line. The query also produces an error in the .out (expected, per MEMORY: capture errors, no DO block). | |||
| 4020 | - | - | |
| 4021 | - | return offset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4022 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4023 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4024 | - | typedef struct | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4025 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4026 | - | WindowAggState *winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4027 | - | int64 maxOffset; /* max backward-reach offset across all nav | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4028 | - | * exprs */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4029 | - | bool maxOverflow; /* true if backward-reach overflow detected */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4030 | - | int64 minFirstOffset; /* min forward-from-match_start offset; may be | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4031 | - | * negative (PREV_FIRST: inner - outer < 0) */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4032 | - | } EvalDefineOffsetsContext; | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4054 | 24 | visit_nav_exec(NavTraversal *t, RPRNavExpr *nav) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4055 | - | { | - |
| 4056 | 24 | EvalDefineOffsetsContext *context = (EvalDefineOffsetsContext *) t->data; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4057 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4058 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4059 | - | * Parser guarantee (mirrors visit_nav_plan): nav's direct children are | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4060 | - | * never RPRNavExpr -- compound nesting is flattened in place and any | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4061 | - | * other nesting is rejected. Outer-kind dispatch is sufficient. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4062 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4063 | 24 | Assert(nav->arg == NULL || !IsA(nav->arg, RPRNavExpr)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4064 | 24 | Assert(nav->offset_arg == NULL || !IsA(nav->offset_arg, RPRNavExpr)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4065 | 24 | Assert(nav->compound_offset_arg == NULL || | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4066 | - | !IsA(nav->compound_offset_arg, RPRNavExpr)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4067 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4068 | - | /* Backward reach: PREV, LAST-with-offset */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4069 | 24 | if (!context->maxOverflow) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4070 | - | { | - |
| 4071 | 24 | int64 reach = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4072 | 24 | bool gotReach = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4073 | - | - | |
| 4074 | 24 | if (nav->kind == RPR_NAV_PREV) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4075 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4076 | 12 | reach = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4077 | - | nav->offset_arg, 1); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4078 | - | gotReach = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4079 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4080 | 12 | else if (nav->kind == RPR_NAV_LAST && nav->offset_arg != NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4081 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4082 | 0 | reach = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test -- Bare LAST(v, $1) with a runtime (non-folded) offset.
The generic plan keeps
-- $1 as a Param, so the planner marks navMaxOffsetKind=NEEDS_EVAL and the
-- executor calls visit_nav_exec, hitting the RPR_NAV_LAST + offset_arg arm
-- (nodeWindowAgg.c:4082).
Verified with gcov: line 4082 hit (count 2).
PREPARE rpr_last_offset_prep(int8) AS
SELECT count(*) OVER w
FROM generate_series(1,10) s(v)
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
PATTERN (A+)
DEFINE A AS LAST(v, $1) >= 0
);
SET plan_cache_mode = force_generic_plan;
-- EXPLAIN (no execute) shows planner side:
"Nav Mark Lookback: runtime"
EXPLAIN (COSTS OFF)
EXECUTE rpr_last_offset_prep(2);
-- EXPLAIN ANALYZE actually EXECUTES the generic plan -> drives visit_nav_exec:4082
EXPLAIN (COSTS OFF, ANALYZE, TIMING OFF, SUMMARY OFF)
EXECUTE rpr_last_offset_prep(2);
-- A plain
EXECUTE under the generic plan also drives line 4082:
EXECUTE rpr_last_offset_prep(2);
RESET plan_cache_mode;
DEALLOCATE rpr_last_offset_prep;
-- NOTE: the originally proposed LAST(price, price) is WRONG: a column-ref offset
-- errors at parse time ("row pattern navigation offset must be a run-time
-- constant") and never reaches line 4082. A folded Param (plain
EXECUTE under
-- default plan_cache_mode) also misses it. force_generic_plan is mandatory.VerdictLine 4082 (the RPR_NAV_LAST + non-NULL offset_arg arm of visit_nav_exec) is genuinely reachable, and the root-cause analysis is correct: no existing test exercises a bare LAST(x, <runtime-offset>) that survives to executor-side NEEDS_EVAL evaluation. The PREV branch (4076) and compound PREV_LAST/NEXT_LAST branches are covered via force_generic_plan EXPLAIN ANALYZE tests in rpr_explain.sql, but never bare LAST. HOWEVER the PROPOSED test does NOT cover line 4082, for TWO independent reasons proven empirically: (1) LAST(price, price) uses a column reference as the offset, which is rejected at parse time with ERROR "row pattern navigation offset must be a run-time constant" (parse_rpr.c:628-632; reproduced live, matches expected rpr.out:1197 for the analogous PREV case). It never reaches the planner, let alone the executor. (2) Even replacing the offset with a Param ($1) in a normal PREPARE/EXECUTE does not work: under default plan_cache_mode=auto the custom plan const-folds $1 to a Const during eval_const_expressions, so extract_const_offset succeeds, the planner sets navMaxOffsetKind=FIXED (EXPLAIN shows "Nav Mark Lookback: 2"), eval_define_offsets returns early at nodeWindowAgg.c:4174, and visit_nav_exec is never called. I confirmed this with gcov: line 4082 showed ##### (0 hits) after running LAST(price,$1) via plain EXECUTE. Only when forcing a generic plan (SET plan_cache_mode=force_generic_plan; EXPLAIN shows "Nav Mark Lookback: runtime") does the Param stay unfolded -> navMaxOffsetKind=NEEDS_EVAL -> visit_nav_exec runs -> line 4082 hit (gcov count 2, verified). So the offset MUST be a Param kept unfolded via a generic plan; a column ref or a folded custom-plan Param will not work. | |||
| 4083 | - | nav->offset_arg, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4084 | - | gotReach = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4085 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4086 | 12 | else if (nav->kind == RPR_NAV_PREV_LAST || | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4087 | - | nav->kind == RPR_NAV_NEXT_LAST) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4088 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4089 | 8 | int64 inner = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4090 | - | nav->offset_arg, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4091 | 8 | int64 outer = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4092 | - | nav->compound_offset_arg, 1); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4093 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4094 | 8 | if (nav->kind == RPR_NAV_PREV_LAST) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4095 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4096 | 8 | if (pg_add_s64_overflow(inner, outer, &reach)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4097 | 8 | context->maxOverflow = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4098 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4099 | - | gotReach = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4100 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4101 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4102 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4103 | 0 | reach = Max(inner - outer, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test -- Compound NEXT_LAST with parameter offsets forces RPR_NAV_OFFSET_NEEDS_EVAL,
-- so the executor (visit_nav_exec) resolves the offset and runs the NEXT_LAST
-- arm reach = Max(inner - outer, 0) at nodeWindowAgg.c:4103-4104.
-- Must use a
PREPARE + force_generic_plan so $1/$2 stay as Params (a custom
-- plan would const-fold them and resolve in the planner instead).
PREPARE test_nextlast_offset(int8, int8) AS
SELECT count(*) OVER w
FROM generate_series(1,10) s(v)
WINDOW w AS (
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
PATTERN (A+)
DEFINE A AS NEXT(LAST(v, $1), $2) > 0
);
SET plan_cache_mode = force_generic_plan;
-- inner=7, outer=2 -> Max(7-2,0)=5 (positive branch result)
EXPLAIN (COSTS OFF)
EXECUTE test_nextlast_offset(7, 2);
-- inner=1, outer=3 -> Max(1-3,0)=0 (clamped-to-zero branch result)
EXECUTE test_nextlast_offset(1, 3);
DEALLOCATE test_nextlast_offset;
RESET plan_cache_mode;
-- Note:
NEXT(LAST(v, $1), $2) (parameters) is accepted;
a column reference
-- such as NEXT(LAST(price, price), 2) is rejected at parse time
-- ("row pattern navigation offset must be a run-time constant") and never
-- reaches the executor, so the originally proposed test cannot cover these lines.VerdictLines 4103-4104 (the compound NEXT_LAST arm: reach = Max(inner - outer, 0); gotReach = true; ) are genuinely reachable and testable, but the PROPOSED test does NOT cover them - it fails before reaching the executor. The proposed query NEXT(LAST(price, price), 2) uses the column reference 'price' as the navigation offset, which is rejected at parse analysis (parse_rpr.c:631) with 'ERROR: row pattern navigation offset must be a run-time constant'. I reproduced this error empirically on a live server. Navigation offsets must be run-time constants (Const or Param), so a column ref can never trigger NEEDS_EVAL. The classification 'testable' is correct, but the root-cause analysis conflates 'non-constant' (column ref, rejected) with 'NEEDS_EVAL' (parameter, accepted). NEEDS_EVAL is only set when an offset is a Param/non-foldable-but-stable expression (createplan.c:2642-2643), as the existing PREV(LAST(v,$1),$2) explain test demonstrates. The gap is real: no NEXT_LAST nav with parameter offsets exists in the suite, so the NEXT_LAST executor arm (4103-4104) is never run, while the PREV_LAST arm (4096-4097) is covered. I verified the refined parameter-based test drives these exact lines: EXPLAIN ANALYZE under force_generic_plan reported 'Nav Mark Lookback: 5' for params (7,2) = Max(7-2,0)=5 and 'Nav Mark Lookback: 0' for (1,3) = Max(1-3,0)=0, confirming the executor (not the planner const-fold) computed reach = Max(inner-outer,0) at lines 4103-4104. | |||
| 4104 | 0 | gotReach = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4105 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4106 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4107 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4108 | 8 | if (gotReach) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4109 | 12 | context->maxOffset = Max(context->maxOffset, reach); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4110 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4111 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4112 | - | /* Forward reach from match_start: FIRST, compound PREV_FIRST/NEXT_FIRST */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4113 | 24 | if (nav->kind == RPR_NAV_FIRST) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4114 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4115 | 0 | int64 reach; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableCovers How to test (one test covers this flow)@4115 Testable · @4117 Testable · @4119 TestableUse a Param offset and force a generic plan so the param is not const-folded into a Const before createplan runs:
CREATE TABLE stock(company text, price int, day int);
INSERT INTO stock
VALUES ('A',10,1),('A',20,2),('A',30,3);
SET plan_cache_mode = force_generic_plan;
PREPARE pa(bigint) AS
SELECT company, day, count(*) OVER w
FROM stock
WINDOW w AS (PARTITION BY company
ORDER BY day
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
INITIAL
PATTERN (A+)
DEFINE A AS FIRST(price, $1) > 0);
EXECUTE pa(1);
This was verified live: plan-only EXPLAIN VERBOSE prints "Nav Mark Lookahead: runtime" (navFirstOffsetKind=NEEDS_EVAL), and EXPLAIN (ANALYZE, VERBOSE)
EXECUTE pa(1) prints "Nav Mark Lookahead: 1" (executor-resolved), proving eval_define_offsets ran visit_nav_exec through lines 4113->4115->4117-4119 and set navFirstOffset=1 at line 4208. Note: a plain (non-prepared) literal query const-folds the offset to a Const at plan time and would yield FIXED, NOT covering line 4115 -- the Param + force_generic_plan combination is essential.
For a regress test that does not want to depend on plan_cache_mode, an EXPLAIN (COSTS OFF) on the generic plan asserting "Nav Mark Lookahead: runtime" plus an
EXECUTE confirms the path. | |||
| 4116 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4117 | 0 | reach = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4118 | - | nav->offset_arg, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4119 | 0 | context->minFirstOffset = Min(context->minFirstOffset, reach); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4120 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4121 | 24 | else if (nav->kind == RPR_NAV_PREV_FIRST || | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4122 | - | nav->kind == RPR_NAV_NEXT_FIRST) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4123 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4124 | 4 | int64 inner = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4125 | - | nav->offset_arg, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4126 | 4 | int64 outer = eval_nav_offset_helper(context->winstate, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4127 | - | nav->compound_offset_arg, 1); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4128 | 4 | int64 reach; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4129 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4130 | 4 | if (nav->kind == RPR_NAV_PREV_FIRST) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4131 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4132 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4133 | - | * reach = inner - outer. Both are non-negative, so the result >= | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4134 | - | * -PG_INT64_MAX, which cannot underflow int64. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4135 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4136 | 0 | reach = inner - outer; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test -- Drives nodeWindowAgg.c:4136 (visit_nav_exec PREV_FIRST branch, reach = inner - outer).
-- A Param offset is non-Const (extract_const_offset returns false) so the planner sets
-- navFirstOffsetKind = RPR_NAV_OFFSET_NEEDS_EVAL;
PREV(FIRST(...)) sets hasFirstNav, so
-- eval_define_offsets runs the walker at executor init and hits line 4136.
-- Verified live on 19beta1 RPR build: parses, plans, and returns all rows.
PREPARE rpr_prevfirst_eval(int) AS
SELECT price
FROM stock
WINDOW w AS (
PARTITION BY company
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
INITIAL
PATTERN (A+)
DEFINE A AS PREV(FIRST(price, $1), 2) > 0
);
EXECUTE rpr_prevfirst_eval(0);
DEALLOCATE rpr_prevfirst_eval;
-- (A Param in the OUTER offset also works and is equally valid:
--
DEFINE A AS PREV(FIRST(price, 1), $1) > 0)VerdictLine 4136 (reach = inner - outer; in the RPR_NAV_PREV_FIRST branch of visit_nav_exec) is genuinely reachable, so the "testable" classification is correct. HOWEVER, the proposed test is WRONG and does NOT compile/run. I executed it against a live 19beta1 RPR build: DEFINE A AS PREV(FIRST(price, price), 2) > 0 fails at PARSE time with "ERROR: row pattern navigation offset must be a run-time constant" (parse_rpr.c:629-632, ERRCODE_FEATURE_NOT_SUPPORTED). A column reference in any nav offset (inner offset_arg or outer compound_offset_arg) is rejected; so is a volatile function (random()::int -> "volatile functions are not allowed in DEFINE clause"). This is confirmed by the existing expected output rpr.out:1195-1234 where every column-ref/random offset yields these errors. Therefore a column offset can NEVER reach RPR_NAV_OFFSET_NEEDS_EVAL, and the proposed test reaches neither the executor nor line 4136. The ONLY way to make navFirstOffsetKind = NEEDS_EVAL for a PREV_FIRST is an offset expression that contains no Var and is not volatile but is also not constant-folded to a Const, i.e. a Param. I verified PREPARE p(int) AS ... DEFINE A AS PREV(FIRST(price, $1), 2) > 0; EXECUTE p(0); parses, plans (firstNeedsEval -> NEEDS_EVAL since extract_const_offset returns false for the Param), and executes -- driving eval_define_offsets -> visit_nav_exec -> the RPR_NAV_PREV_FIRST branch -> line 4136. hasFirstNav is set true by the PREV_FIRST branch (createplan.c:2656), satisfying the executor gate needsFirst at nodeWindowAgg.c:4171. Both inner-offset-Param and outer-offset-Param variants ran successfully (returned all 7 rows). Net: reachable + testable, but the proposed test must be replaced with a Param-based (PREPARE/EXECUTE) test. | |||
| 4137 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4138 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4139 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4140 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4141 | - | * NEXT_FIRST: reach = inner + outer. This can overflow, but the | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4142 | - | * result is always >= 0, so it never updates minFirstOffset | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4143 | - | * (which tracks the minimum). Clamp to PG_INT64_MAX on overflow. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4144 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4145 | 4 | if (pg_add_s64_overflow(inner, outer, &reach)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4146 | 4 | reach = PG_INT64_MAX; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4147 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4148 | 4 | context->minFirstOffset = Min(context->minFirstOffset, reach); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4149 | - | } | - |
| 4150 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4166 | 4974 | eval_define_offsets(WindowAggState *winstate, List *defineClause) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4167 | - | { | - |
| 4168 | 4974 | EvalDefineOffsetsContext ctx; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4169 | 4974 | NavTraversal trav; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4170 | 4974 | bool needsMax = (winstate->navMaxOffsetKind == RPR_NAV_OFFSET_NEEDS_EVAL); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4171 | 4974 | bool needsFirst = (winstate->hasFirstNav && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4172 | 136 | winstate->navFirstOffsetKind == RPR_NAV_OFFSET_NEEDS_EVAL); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4173 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4174 | 4974 | if (!needsMax && !needsFirst) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4175 | 4950 | return; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4176 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4177 | 24 | ctx.winstate = winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4178 | 24 | ctx.maxOffset = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4179 | 24 | ctx.maxOverflow = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4180 | 24 | ctx.minFirstOffset = PG_INT64_MAX; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4181 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4182 | 24 | trav.visit = visit_nav_exec; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4183 | 24 | trav.data = &ctx; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4184 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4185 | 48 | foreach_node(TargetEntry, te, defineClause) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4186 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4187 | 24 | nav_traversal_walker((Node *) te->expr, &trav); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4188 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4189 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4190 | 24 | if (needsMax) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4191 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4192 | 20 | if (ctx.maxOverflow) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4193 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4194 | 8 | winstate->navMaxOffsetKind = RPR_NAV_OFFSET_RETAIN_ALL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4195 | 8 | winstate->navMaxOffset = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4196 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4197 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4198 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4199 | 12 | winstate->navMaxOffsetKind = RPR_NAV_OFFSET_FIXED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4200 | 12 | winstate->navMaxOffset = ctx.maxOffset; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4201 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4202 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4203 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4204 | 24 | if (needsFirst) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4205 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4206 | 4 | winstate->navFirstOffsetKind = RPR_NAV_OFFSET_FIXED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4207 | 4 | if (ctx.minFirstOffset < PG_INT64_MAX) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4208 | 0 | winstate->navFirstOffset = ctx.minFirstOffset; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test Add to src/test/regress/sql/rpr.sql (with matching .out).
The offset MUST be a Param (column refs are rejected), and the plan MUST be generic so the Param is not const-folded to FIXED:
CREATE TEMP TABLE rpr_first_eval (company text, tdate int, price int);
INSERT INTO rpr_first_eval
VALUES ('A',1,100),('A',2,200),('A',3,150);
PREPARE first_eval_off(int8) AS
SELECT company, price, first_value(price) OVER w AS fv
FROM rpr_first_eval
WINDOW w AS (
PARTITION BY company
ORDER BY tdate
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
INITIAL
PATTERN (A+)
DEFINE A AS FIRST(price, $1) > 0
);
SET plan_cache_mode = force_generic_plan;
-- EXPLAIN shows 'Nav Mark Lookahead: runtime' (NEEDS_EVAL);
EXECUTE drives line 4208
EXPLAIN (VERBOSE, COSTS OFF)
EXECUTE first_eval_off(1);
EXECUTE first_eval_off(1);
RESET plan_cache_mode;
DEALLOCATE first_eval_off;
DROP TABLE rpr_first_eval;
This was empirically verified: line 4208 went from ##### to execution count 1, with the else branch (4210) untouched. force_generic_plan is essential;
without it the custom plan folds $1 to a Const and the line stays uncovered (the existing line-1267 test demonstrates exactly this failure mode).VerdictCONFIRMED reachable and testable, but the PROPOSED TEST IS INVALID. I empirically verified line 4208 is uncovered (gcov #####) even after a full `pg-regress rpr` run, and confirmed it CAN be driven (gcov count 1) with a corrected test. Line 4208 (`winstate->navFirstOffset = ctx.minFirstOffset; `, the true branch of `if (ctx.minFirstOffset < PG_INT64_MAX)`) executes only when needsFirst is true: hasFirstNav && navFirstOffsetKind == RPR_NAV_OFFSET_NEEDS_EVAL, and visit_nav_exec lowered minFirstOffset below the sentinel (always true once any FIRST-family nav is visited). Why the finding's PROPOSED TEST fails: `FIRST(price, price)` uses a COLUMN as the nav offset, which parse analysis REJECTS with `ERROR: row pattern navigation offset must be a run-time constant` (verified live). A nav offset must be a runtime constant; only a Const or a Param is accepted, never a column reference. So that query never plans, let alone reaches the executor. Why the finding's ROOT CAUSE is also partly wrong: the suite DOES contain a non-constant offset on a FIRST-family nav, at src/test/regress/sql/rpr.sql:1267 `DEFINE B AS PREV(FIRST(val, $1), $2)` (an RPR_NAV_PREV_FIRST, which sets hasFirst=true and firstNeedsEval=true in visit_nav_plan). The real reason it does not cover 4208 is plan caching: EXECUTE uses a CUSTOM plan for the first executions, substituting $1/$2 as Consts before create_windowagg_plan runs, so extract_const_offset succeeds and navFirstOffsetKind becomes FIXED, not NEEDS_EVAL. gcov proves this: eval_define_offsets ran 124 times and ALL 124 hit the early `return` at line 4175 (needsMax and needsFirst both false every time). To force NEEDS_EVAL you must keep the offset as an unfolded Param, which requires a GENERIC plan (SET plan_cache_mode = force_generic_plan). EXPLAIN VERBOSE then shows `Nav Mark Lookahead: runtime` (the NEEDS_EVAL display branch), and EXECUTE drives eval_define_offsets through needsFirst=true -> 4204 -> 4207 (true) -> 4208. I confirmed end-to-end: after adding the refined probe to rpr.sql and re-running, line 4208 shows execution count 1 (else-branch 4210 remains #####). | |||
| 4209 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4210 | 4 | winstate->navFirstOffset = PG_INT64_MAX; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4211 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4212 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4219 | 8909011 | rpr_is_defined(WindowAggState *winstate) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4220 | - | { | - |
| 4221 | 8909011 | return winstate->rpPattern != NULL; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4222 | - | } | - |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4243 | 4355848 | row_is_in_reduced_frame(WindowObject winobj, int64 pos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4244 | - | { | - |
| 4245 | 4355848 | WindowAggState *winstate = winobj->winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4246 | 4355848 | int state; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4247 | 4355848 | int64 rtn; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4248 | - | - | |
| 4249 | 4355848 | if (!rpr_is_defined(winstate)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4250 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4251 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4252 | - | * RPR is not defined. Assume that we are always in the reduced window | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4253 | - | * frame. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4254 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4255 | 4355788 | rtn = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4256 | - | return rtn; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4257 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4258 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4259 | 4349196 | state = get_reduced_frame_status(winstate, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4260 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4261 | 4349196 | if (state == RF_NOT_DETERMINED) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4262 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4263 | 129804 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4264 | 129804 | update_reduced_frame(winobj, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4265 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4266 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4267 | 4349136 | state = get_reduced_frame_status(winstate, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4268 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4269 | 4349136 | switch (state) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4270 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4271 | 81396 | case RF_FRAME_HEAD: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4272 | 81396 | rtn = winstate->rpr_match_length; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4273 | 81396 | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4274 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4275 | - | case RF_SKIPPED: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4276 | - | rtn = -2; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4277 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4278 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4279 | 469588 | case RF_UNMATCHED: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4280 | - | case RF_EMPTY_MATCH: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4281 | 469588 | rtn = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4282 | 469588 | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4283 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4284 | 0 | default: | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed unreachable. The switch's `default:` (elog ERROR, lines 4284-4285) is a pure can't-happen guard. `state` is a plain int but only ever receives one of the five RF_* #define constants (execnodes.h:2529-2533, values 0-4) returned by get_reduced_frame_status, which has exactly five return points and never produces an arbitrary int. The only RF_* value missing an explicit case is RF_NOT_DETERMINED (0). That value is structurally excluded at the switch: get_reduced_frame_status returns RF_NOT_DETERMINED only when !rpr_match_valid, but the code re-fetches `state` AFTER calling update_reduced_frame(), and EVERY exit path of update_reduced_frame (Case 1 line 4546, no-context line 4567, and register_result line 4597) unconditionally sets winstate->rpr_match_valid = true. So the second get_reduced_frame_status call (line 4267) can only return RF_FRAME_HEAD/RF_SKIPPED/RF_UNMATCHED/RF_EMPTY_MATCH, all of which have explicit cases. No SQL/regression input -- regardless of PATTERN/DEFINE/quantifiers/AFTER MATCH SKIP mode/empty matches -- can drive a value into the default arm. I tried to refute via empty-match (RF_EMPTY_MATCH handled), unmatched (RF_UNMATCHED handled), and SKIP-overlap completed-context (register_result still sets valid=true) paths; none reaches it.Recommended fix Keep as a defensive can't-happen guard. Converting `elog(ERROR, ...)` to `Assert(false)` (or pg_unreachable() after the existing elog) would document the invariant and is consistent with PostgreSQL practice for switch-on-internal-enum defaults, but the current elog(ERROR) is also acceptable and matches surrounding executor style. No functional change needed; the line will remain legitimately uncovered. | |||
| 4285 | 0 | elog(ERROR, "unrecognized state: %d at: " INT64_FORMAT, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4286 | - | state, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4287 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4288 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4289 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4290 | - | return rtn; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4291 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4298 | 34977 | clear_reduced_frame(WindowAggState *winstate) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4299 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4300 | 34977 | winstate->rpr_match_valid = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4301 | 34977 | winstate->rpr_match_matched = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4302 | 34977 | winstate->rpr_match_start = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4303 | 34977 | winstate->rpr_match_length = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4304 | 34977 | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4327 | 14177520 | get_reduced_frame_status(WindowAggState *winstate, int64 pos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4328 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4329 | 14177520 | int64 start = winstate->rpr_match_start; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4330 | 14177520 | int64 length = winstate->rpr_match_length; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4331 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4332 | 14177520 | if (!winstate->rpr_match_valid) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4333 | - | return RF_NOT_DETERMINED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4334 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4335 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4336 | - | * By here the record is valid and holds one of the three shapes above. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4337 | - | * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4338 | - | * The empty match (true, 0) must be classified first: it has length 0, so | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4339 | - | * the range test below would compute start + length == start and reject | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4340 | - | * its own start position as out of range. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4341 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4342 | 14160300 | if (pos == start && winstate->rpr_match_matched && length == 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4343 | - | return RF_EMPTY_MATCH; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4344 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4345 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4346 | - | * By here length >= 1 -- the only zero-length record, the empty match, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4347 | - | * has been handled -- so [start, start + length) is a well-formed range. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4348 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4349 | 14158148 | if (pos < start || pos >= start + length) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4350 | - | return RF_NOT_DETERMINED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4351 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4352 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4353 | - | * By here pos lies within [start, start + length). An unmatched record | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4354 | - | * is (false, 1), so this returns for its single in-range position. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4355 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4356 | 14013972 | if (!winstate->rpr_match_matched) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4357 | - | return RF_UNMATCHED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4358 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4359 | - | /* By here the match is real (true, >= 1) and pos is one of its rows. */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4360 | 12993484 | if (pos == start) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4361 | 1113204 | return RF_FRAME_HEAD; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4362 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4363 | - | return RF_SKIPPED; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4364 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4380 | 988588 | advance_nav_mark(WindowAggState *winstate, int64 currentPos) | 0ed285dDrive RPR row pattern matching once per row |
| 4381 | - | { | 0ed285dDrive RPR row pattern matching once per row |
| 4382 | 988588 | int64 navmarkpos; | 0ed285dDrive RPR row pattern matching once per row |
| 4383 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4384 | - | /* No RPR navigation read pointer: nothing to advance */ | 0ed285dDrive RPR row pattern matching once per row |
| 4385 | 988588 | if (winstate->nav_winobj == NULL) | 0ed285dDrive RPR row pattern matching once per row |
| 4386 | - | return; | 0ed285dDrive RPR row pattern matching once per row |
| 4387 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4388 | - | /* RETAIN_ALL disables trim for the backward (PREV/LAST) dimension */ | 0ed285dDrive RPR row pattern matching once per row |
| 4389 | 988588 | if (winstate->navMaxOffsetKind == RPR_NAV_OFFSET_RETAIN_ALL) | 0ed285dDrive RPR row pattern matching once per row |
| 4390 | - | return; | 0ed285dDrive RPR row pattern matching once per row |
| 4391 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4392 | - | /* navMax is FIXED here: NEEDS_EVAL resolved, RETAIN_ALL returned */ | 0ed285dDrive RPR row pattern matching once per row |
| 4393 | 988548 | Assert(winstate->navMaxOffsetKind == RPR_NAV_OFFSET_FIXED); | 0ed285dDrive RPR row pattern matching once per row |
| 4394 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4395 | 988548 | if (currentPos > winstate->navMaxOffset) | 0ed285dDrive RPR row pattern matching once per row |
| 4396 | 966164 | navmarkpos = currentPos - winstate->navMaxOffset; | 0ed285dDrive RPR row pattern matching once per row |
| 4397 | - | else | 0ed285dDrive RPR row pattern matching once per row |
| 4398 | - | navmarkpos = 0; | 0ed285dDrive RPR row pattern matching once per row |
| 4399 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4400 | 988548 | if (winstate->hasFirstNav && winstate->nfaContext != NULL) | 0ed285dDrive RPR row pattern matching once per row |
| 4401 | - | { | 0ed285dDrive RPR row pattern matching once per row |
| 4402 | 5848 | int64 firstreach; | 0ed285dDrive RPR row pattern matching once per row |
| 4403 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4404 | - | /* navFirst is always FIXED; it never takes RETAIN_ALL */ | 0ed285dDrive RPR row pattern matching once per row |
| 4405 | 5848 | Assert(winstate->navFirstOffsetKind == RPR_NAV_OFFSET_FIXED); | 0ed285dDrive RPR row pattern matching once per row |
| 4406 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4407 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4408 | - | * Head context has the smallest matchStartRow (contexts appended in | 0ed285dDrive RPR row pattern matching once per row |
| 4409 | - | * nondecreasing order), so bounding by it covers every FIRST reach. | 0ed285dDrive RPR row pattern matching once per row |
| 4410 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4411 | 5848 | if (!pg_add_s64_overflow(winstate->nfaContext->matchStartRow, | 0ed285dDrive RPR row pattern matching once per row |
| 4412 | - | winstate->navFirstOffset, | 0ed285dDrive RPR row pattern matching once per row |
| 4413 | - | &firstreach)) | 0ed285dDrive RPR row pattern matching once per row |
| 4414 | 5812 | navmarkpos = Min(navmarkpos, Max(firstreach, 0)); | 0ed285dDrive RPR row pattern matching once per row |
| 4415 | - | } | 0ed285dDrive RPR row pattern matching once per row |
| 4416 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4417 | 988548 | if (navmarkpos > winstate->nav_winobj->markpos) | 0ed285dDrive RPR row pattern matching once per row |
| 4418 | 970732 | WinSetMarkPosition(winstate->nav_winobj, navmarkpos); | 0ed285dDrive RPR row pattern matching once per row |
| 4419 | - | } | 0ed285dDrive RPR row pattern matching once per row |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4430 | 99156 | advance_reduced_frame_nfa(WindowObject winobj, RPRNFAContext *targetCtx, | 0ed285dDrive RPR row pattern matching once per row |
| 4431 | - | int64 pos, bool hasLimitedFrame, int64 frameOffset) | 0ed285dDrive RPR row pattern matching once per row |
| 4432 | - | { | 0ed285dDrive RPR row pattern matching once per row |
| 4433 | 99156 | WindowAggState *winstate = winobj->winstate; | 0ed285dDrive RPR row pattern matching once per row |
| 4434 | 99156 | int64 currentPos; | 0ed285dDrive RPR row pattern matching once per row |
| 4435 | 99156 | int64 startPos; | 0ed285dDrive RPR row pattern matching once per row |
| 4436 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4437 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4438 | - | * Determine where to start processing. Usually nfaLastProcessedRow+1 >= | 0ed285dDrive RPR row pattern matching once per row |
| 4439 | - | * pos since contexts are created at currentPos+1 during processing. | 0ed285dDrive RPR row pattern matching once per row |
| 4440 | - | * However, pos can exceed this when rows are skipped (e.g., unmatched | 0ed285dDrive RPR row pattern matching once per row |
| 4441 | - | * rows don't update nfaLastProcessedRow). | 0ed285dDrive RPR row pattern matching once per row |
| 4442 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4443 | 99156 | startPos = Max(pos, winstate->nfaLastProcessedRow + 1); | 0ed285dDrive RPR row pattern matching once per row |
| 4444 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4445 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4446 | - | * Process rows until target context completes or we hit boundaries. Each | 0ed285dDrive RPR row pattern matching once per row |
| 4447 | - | * row evaluation is shared across all active contexts. | 0ed285dDrive RPR row pattern matching once per row |
| 4448 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4449 | 1087744 | for (currentPos = startPos; targetCtx->states != NULL; currentPos++) | 0ed285dDrive RPR row pattern matching once per row |
| 4450 | - | { | 0ed285dDrive RPR row pattern matching once per row |
| 4451 | 994976 | bool rowExists; | 0ed285dDrive RPR row pattern matching once per row |
| 4452 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4453 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4454 | - | * Evaluate variables for this row - done only once, shared by all | 0ed285dDrive RPR row pattern matching once per row |
| 4455 | - | * contexts. | 0ed285dDrive RPR row pattern matching once per row |
| 4456 | - | * | 0ed285dDrive RPR row pattern matching once per row |
| 4457 | - | * Set nav_match_start to the head context's matchStartRow for | 0ed285dDrive RPR row pattern matching once per row |
| 4458 | - | * FIRST/LAST navigation. Match_start-dependent variables (FIRST, | 0ed285dDrive RPR row pattern matching once per row |
| 4459 | - | * LAST-with-offset) are re-evaluated per-context in ExecRPRProcessRow | 0ed285dDrive RPR row pattern matching once per row |
| 4460 | - | * when matchStartRow differs. | 0ed285dDrive RPR row pattern matching once per row |
| 4461 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4462 | 994976 | winstate->nav_match_start = targetCtx->matchStartRow; | 0ed285dDrive RPR row pattern matching once per row |
| 4463 | 994976 | rowExists = nfa_evaluate_row(winobj, currentPos, winstate->nfaVarMatched); | 0ed285dDrive RPR row pattern matching once per row |
| 4464 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4465 | - | /* No more rows in partition? Finalize all contexts */ | 0ed285dDrive RPR row pattern matching once per row |
| 4466 | 994916 | if (!rowExists) | 0ed285dDrive RPR row pattern matching once per row |
| 4467 | - | { | 0ed285dDrive RPR row pattern matching once per row |
| 4468 | 6328 | ExecRPRFinalizeAllContexts(winstate, currentPos - 1); | 0ed285dDrive RPR row pattern matching once per row |
| 4469 | - | /* Clean up dead contexts from finalization */ | 0ed285dDrive RPR row pattern matching once per row |
| 4470 | 6328 | ExecRPRCleanupDeadContexts(winstate, targetCtx); | 0ed285dDrive RPR row pattern matching once per row |
| 4471 | 6328 | break; | 0ed285dDrive RPR row pattern matching once per row |
| 4472 | - | } | 0ed285dDrive RPR row pattern matching once per row |
| 4473 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4474 | - | /* Update last processed row */ | 0ed285dDrive RPR row pattern matching once per row |
| 4475 | 988588 | winstate->nfaLastProcessedRow = currentPos; | 0ed285dDrive RPR row pattern matching once per row |
| 4476 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4477 | - | /*-------------------------- | 0ed285dDrive RPR row pattern matching once per row |
| 4478 | - | * Process all contexts for this row: | 0ed285dDrive RPR row pattern matching once per row |
| 4479 | - | * 1. Match all (convergence) | 0ed285dDrive RPR row pattern matching once per row |
| 4480 | - | * 2. Absorb redundant | 0ed285dDrive RPR row pattern matching once per row |
| 4481 | - | * 3. Advance all (divergence) | 0ed285dDrive RPR row pattern matching once per row |
| 4482 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4483 | 988588 | ExecRPRProcessRow(winstate, currentPos, hasLimitedFrame, frameOffset); | 0ed285dDrive RPR row pattern matching once per row |
| 4484 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4485 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4486 | - | * Create a new context for the next potential start position. This | 0ed285dDrive RPR row pattern matching once per row |
| 4487 | - | * enables overlapping match detection for SKIP TO NEXT ROW. | 0ed285dDrive RPR row pattern matching once per row |
| 4488 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4489 | 988588 | ExecRPRStartContext(winstate, currentPos + 1); | 0ed285dDrive RPR row pattern matching once per row |
| 4490 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4491 | - | /* | 0ed285dDrive RPR row pattern matching once per row |
| 4492 | - | * Clean up dead contexts (failed with no active states and no match). | 0ed285dDrive RPR row pattern matching once per row |
| 4493 | - | * This removes contexts that failed during processing and counts them | 0ed285dDrive RPR row pattern matching once per row |
| 4494 | - | * appropriately as pruned or mismatched. | 0ed285dDrive RPR row pattern matching once per row |
| 4495 | - | */ | 0ed285dDrive RPR row pattern matching once per row |
| 4496 | 988588 | ExecRPRCleanupDeadContexts(winstate, targetCtx); | 0ed285dDrive RPR row pattern matching once per row |
| 4497 | - | 0ed285dDrive RPR row pattern matching once per row | |
| 4498 | - | /* Advance the nav mark to the frontier so trim can free old rows. */ | 0ed285dDrive RPR row pattern matching once per row |
| 4499 | 988588 | advance_nav_mark(winstate, currentPos); | 0ed285dDrive RPR row pattern matching once per row |
| 4500 | - | } | 0ed285dDrive RPR row pattern matching once per row |
| 4501 | 99096 | } | 0ed285dDrive RPR row pattern matching once per row |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4518 | 129804 | update_reduced_frame(WindowObject winobj, int64 pos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4519 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4520 | 129804 | WindowAggState *winstate = winobj->winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4521 | 129804 | RPRNFAContext *targetCtx; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4522 | 129804 | int frameOptions = winstate->frameOptions; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4523 | 129804 | bool hasLimitedFrame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4524 | 129804 | int64 frameOffset = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4525 | 129804 | int64 matchLen; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4526 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4527 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4528 | - | * Check if we have a limited frame (ROWS ... N FOLLOWING). Each context | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4529 | - | * needs its own frame end based on matchStartRow + offset. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4530 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4531 | 129804 | hasLimitedFrame = (frameOptions & FRAMEOPTION_ROWS) && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4532 | - | !(frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4533 | 129804 | if (hasLimitedFrame) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4534 | 408 | frameOffset = DatumGetInt64(winstate->endOffsetValue); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4535 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4536 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4537 | - | * Case 1: pos is before any existing context's start position. This means | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4538 | - | * the position was already processed and determined unmatched. Head is | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4539 | - | * the oldest context (lowest matchStartRow) since contexts are added at | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4540 | - | * tail with increasing positions. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4541 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4542 | 129804 | if (winstate->nfaContext != NULL && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4543 | 113492 | pos < winstate->nfaContext->matchStartRow) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4544 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4545 | - | /* already processed, unmatched */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4546 | 23816 | winstate->rpr_match_valid = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4547 | 23816 | winstate->rpr_match_matched = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4548 | 23816 | winstate->rpr_match_start = pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4549 | 23816 | winstate->rpr_match_length = 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4550 | 23816 | return; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4551 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4552 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4553 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4554 | - | * Case 2: Find existing context for this pos, or create new one. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4555 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4556 | 105988 | targetCtx = ExecRPRGetHeadContext(winstate, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4557 | 105988 | if (targetCtx == NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4558 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4559 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4560 | - | * No context exists. If pos is already processed, it means this row | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4561 | - | * was already determined to be unmatched or skipped - no need to | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4562 | - | * reprocess. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4563 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4564 | 16312 | if (pos <= winstate->nfaLastProcessedRow) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4565 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4566 | - | /* already processed, unmatched */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4567 | 6044 | winstate->rpr_match_valid = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4568 | 6044 | winstate->rpr_match_matched = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4569 | 6044 | winstate->rpr_match_start = pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4570 | 6044 | winstate->rpr_match_length = 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4571 | 6044 | return; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4572 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4573 | - | /* Not yet processed - create new context and start fresh */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4574 | 10268 | targetCtx = ExecRPRStartContext(winstate, pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4575 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4576 | 89676 | else if (targetCtx->states == NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4577 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4578 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4579 | - | * The head context already completed in an earlier call. Reachable | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4580 | - | * under SKIP TO NEXT ROW, where overlapping contexts let one reach | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4581 | - | * FIN -- recording its result -- before the call for its own start | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4582 | - | * row arrives. Register that result. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4583 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4584 | 788 | goto register_result; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4585 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4586 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4587 | - | /* Drive the NFA forward until pos's match is resolved. */ | 0ed285dDrive RPR row pattern matching once per row |
| 4588 | 99156 | advance_reduced_frame_nfa(winobj, targetCtx, pos, hasLimitedFrame, | 0ed285dDrive RPR row pattern matching once per row |
| 4589 | - | frameOffset); | 0ed285dDrive RPR row pattern matching once per row |
| 4590 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4591 | 99884 | register_result: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4592 | 99884 | Assert(pos == targetCtx->matchStartRow); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4593 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4594 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4595 | - | * Record match result. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4596 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4597 | 99884 | winstate->rpr_match_valid = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4598 | 99884 | winstate->rpr_match_start = targetCtx->matchStartRow; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4599 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4600 | 99884 | if (targetCtx->matchEndRow < targetCtx->matchStartRow) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4601 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4602 | 63180 | matchLen = targetCtx->lastProcessedRow - targetCtx->matchStartRow + 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4603 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4604 | 63180 | if (targetCtx->matchedState != NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4605 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4606 | - | /* Empty match: FIN reached but 0 rows consumed */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4607 | 424 | winstate->rpr_match_matched = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4608 | 424 | winstate->rpr_match_length = 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4609 | 424 | ExecRPRRecordContextSuccess(winstate, 0); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4610 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4611 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4612 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4613 | - | /* No match */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4614 | 62756 | winstate->rpr_match_matched = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4615 | 62756 | winstate->rpr_match_length = 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4616 | 62756 | ExecRPRRecordContextFailure(winstate, matchLen); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4617 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4618 | 63180 | ExecRPRFreeContext(winstate, targetCtx); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4619 | 63180 | return; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4620 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4621 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4622 | - | /* Match succeeded */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4623 | 36704 | matchLen = targetCtx->matchEndRow - targetCtx->matchStartRow + 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4624 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4625 | 36704 | winstate->rpr_match_matched = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4626 | 36704 | winstate->rpr_match_length = matchLen; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4627 | 36704 | ExecRPRRecordContextSuccess(winstate, matchLen); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4628 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4629 | - | /* Remove the matched context */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4630 | 36704 | ExecRPRFreeContext(winstate, targetCtx); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4631 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4646 | 994976 | nfa_evaluate_row(WindowObject winobj, int64 pos, bool *varMatched) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4647 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4648 | 994976 | WindowAggState *winstate = winobj->winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4649 | 994976 | ExprContext *econtext = winstate->rprContext; | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 4650 | 994976 | TupleTableSlot *slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4651 | 994976 | int64 saved_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4652 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4653 | - | /* Release the previous row's DEFINE evaluation memory */ | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 4654 | 994976 | ResetExprContext(econtext); | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation |
| 4655 | - | a70f1c1Use a dedicated ExprContext for RPR DEFINE clause evaluation | |
| 4656 | - | /* Fetch current row into temp_slot_1 */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4657 | 994976 | slot = winstate->temp_slot_1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4658 | 994976 | if (!window_gettupleslot(winobj, pos, slot)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4659 | - | return false; /* No row exists */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4660 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4661 | - | /* Set up 1-slot context: only ecxt_outertuple */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4662 | 988648 | econtext->ecxt_outertuple = slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4663 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4664 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4665 | - | * Save and set currentpos so that EEOP_RPR_NAV_SET opcodes can calculate | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4666 | - | * target positions (currentpos +/- offset). | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4667 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4668 | 988648 | saved_pos = winstate->currentpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4669 | 988648 | winstate->currentpos = pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4670 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4671 | - | /* Invalidate nav_slot cache so PREV/NEXT re-fetch for new row */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4672 | 988648 | winstate->nav_slot_pos = -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4673 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4674 | 3336192 | foreach_ptr(ExprState, exprState, winstate->defineClauseExprs) | b848408Tidy up row pattern recognition plumbing |
| 4675 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4676 | 2347604 | int varIdx = foreach_current_index(exprState); | 4cf9108Further tidy up row pattern recognition plumbing |
| 4677 | 2347604 | Datum result; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4678 | 2347604 | bool isnull; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4679 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4680 | - | /* Evaluate DEFINE expression */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4681 | 2347604 | result = ExecEvalExpr(exprState, econtext, &isnull); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4682 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4683 | 3721520 | varMatched[varIdx] = (!isnull && DatumGetBool(result)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4684 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4685 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4686 | 988588 | winstate->currentpos = saved_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4687 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4688 | 988588 | return true; /* Row exists */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4689 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4706 | 1531400 | WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4707 | - | int relpos, int seektype, bool set_mark, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4708 | - | bool *isnull, bool *isout) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4709 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4710 | 1531400 | WindowAggState *winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4711 | 1531400 | int64 abs_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4712 | 1531400 | int64 mark_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4713 | 1531400 | int64 num_reduced_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4714 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4715 | 1531400 | Assert(WindowObjectIsValid(winobj)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4716 | 1531400 | winstate = winobj->winstate; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4717 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4718 | 1531400 | switch (seektype) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4719 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4720 | 0 | case WINDOW_SEEK_CURRENT: | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed defensive-unreachable. WinGetSlotInFrame has exactly one caller, WinGetFuncArgInFrame (nodeWindowAgg.c:5295). That function in turn is called from only three places (windowfuncs.c): window_first_value (WINDOW_SEEK_HEAD, line 664), window_last_value (WINDOW_SEEK_TAIL, line 686), and window_nth_value (WINDOW_SEEK_HEAD, line 720). None pass WINDOW_SEEK_CURRENT. I tried to refute via other seektype sources: the WINDOW_SEEK_CURRENT at windowfuncs.c:560 is fed to WinGetFuncArgInPartition (a DIFFERENT function used by lead/lag), not WinGetFuncArgInFrame, so it never reaches this switch. The RPR navigation functions (FIRST/LAST/PREV/NEXT) use a wholly separate path: execExprInterp.c:6210 calls ExecRPRNavGetSlot directly, bypassing WinGetSlotInFrame entirely. The function header comment at 5249 explicitly documents that WINDOW_SEEK_CURRENT is unsupported for this API. Since seektype is not derived from any SQL-controllable value (it is hardcoded at each of the three callers), no SQL or regression input can drive execution into the case WINDOW_SEEK_CURRENT arm. The only theoretical reach is a third-party C extension calling the public WinGetFuncArgInFrame (declared in windowapi.h) with seektype 0, which is not a SQL/regression input and is contractually forbidden.Recommended fix Convert to Assert(seektype != WINDOW_SEEK_CURRENT) before the switch, or leave the elog(ERROR) as a defensive guard mirroring the identical guard in ignorenulls_getfuncarginframe at line 3705-3706. The elog form is preferable to Assert here because WinGetFuncArgInFrame is part of the public window C API (windowapi.h) and the guard protects against misuse by third-party extensions even in non-assert builds. Either way, exclude these 2 lines from coverage expectations (e.g. a coverage-ignore annotation), since they are unreachable by any SQL input. | |||
| 4721 | 0 | elog(ERROR, "WINDOW_SEEK_CURRENT is not supported for WinGetFuncArgInFrame"); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4722 | - | abs_pos = mark_pos = 0; /* keep compiler quiet */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4723 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4724 | 969760 | case WINDOW_SEEK_HEAD: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4725 | - | /* rejecting relpos < 0 is easy and simplifies code below */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4726 | 969760 | if (relpos < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4727 | 0 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Claim CONFIRMED. Line 4727 (`goto out_of_frame` for `relpos < 0` under WINDOW_SEEK_HEAD) is unreachable from any SQL/regression input. WinGetSlotInFrame is called from exactly one place: WinGetFuncArgInFrame at nodeWindowAgg.c:5295, and only on the NOT-ignore_nulls branch (the IGNORE NULLS path goes to ignorenulls_getfuncarginframe at 5292, a separate function with its own duplicated relpos<0 check at line 3711). The only in-tree window functions calling WinGetFuncArgInFrame with WINDOW_SEEK_HEAD are first_value (relpos=0, windowfuncs.c:663-664) and nth_value (relpos=nth-1 with nth>0 enforced via ereport at windowfuncs.c:714-717, so relpos>=0, line 719-720). No built-in passes a negative HEAD relpos. WinGetFuncArgInFrame is exposed in windowapi.h as an extension API, so a third-party C window function could pass negative HEAD relpos and reach this line, but no SQL-only/regression input can. Classification corrected from hard-to-reach to defensive-unreachable: the guard is a deliberate cheap early-out documented at line 4725, structurally unreachable from SQL.Recommended fix Leave as a defensive early-out. It guards the arithmetic at line 4729 (frameheadpos + relpos) and the reduced-frame bounds check at 4786-4790, and mirrors the identical guard at line 3711 in ignorenulls_getfuncarginframe plus the long-standing pattern in upstream WinGetFuncArgInFrame. An Assert(relpos >= 0) would be incorrect because relpos is caller-supplied via a public extension API (windowapi.h), where rejecting an out-of-frame request must return gracefully (null result), not crash. No code change needed; optionally add a brief comment noting it is reachable only via external window functions, to explain the coverage gap. | |||
| 4728 | 969760 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4729 | 969732 | abs_pos = winstate->frameheadpos + relpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4730 | 969732 | mark_pos = abs_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4731 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4732 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4733 | - | * Account for exclusion option if one is active, but advance only | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4734 | - | * abs_pos not mark_pos. This prevents changes of the current | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4735 | - | * row's peer group from resulting in trying to fetch a row before | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4736 | - | * some previous mark position. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4737 | - | * | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4738 | - | * Note that in some corner cases such as current row being | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4739 | - | * outside frame, these calculations are theoretically too simple, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4740 | - | * but it doesn't matter because we'll end up deciding the row is | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4741 | - | * out of frame. We do not attempt to avoid fetching rows past | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4742 | - | * end of frame; that would happen in some cases anyway. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4743 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4744 | 969732 | switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4745 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4746 | - | case 0: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4747 | - | /* no adjustment needed */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4748 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4749 | 160 | case FRAMEOPTION_EXCLUDE_CURRENT_ROW: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4750 | 160 | if (abs_pos >= winstate->currentpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4751 | - | winstate->currentpos >= winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4752 | 44 | abs_pos++; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4753 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4754 | 80 | case FRAMEOPTION_EXCLUDE_GROUP: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4755 | 80 | update_grouptailpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4756 | 80 | if (abs_pos >= winstate->groupheadpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4757 | 48 | winstate->grouptailpos > winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4758 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4759 | 48 | int64 overlapstart = Max(winstate->groupheadpos, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4760 | - | winstate->frameheadpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4761 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4762 | 48 | abs_pos += winstate->grouptailpos - overlapstart; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4763 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4764 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4765 | 200 | case FRAMEOPTION_EXCLUDE_TIES: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4766 | 200 | update_grouptailpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4767 | 200 | if (abs_pos >= winstate->groupheadpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4768 | 136 | winstate->grouptailpos > winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4769 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4770 | 56 | int64 overlapstart = Max(winstate->groupheadpos, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4771 | - | winstate->frameheadpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4772 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4773 | 56 | if (abs_pos == overlapstart) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4774 | 56 | abs_pos = winstate->currentpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4775 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4776 | 0 | abs_pos += winstate->grouptailpos - overlapstart - 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test Add to src/test/regress/sql/window.sql (and matching .out): SELECT nth_value(unique1,2) over (ORDER BY four rows between current row and 3 following exclude ties), unique1, four FROM tenk1 WHERE unique1 < 10; -- VERIFIED: with tenk1's four column (= unique1%4, 4-row peer groups), nth=2 makes relpos=1 so abs_pos=frameheadpos+1 > overlapstart, taking the else-branch at line 4776 (executed 10x in an isolated coverage run; the if-branch at 4774 stayed at 0). The proposed test as written is correct and needs no change; tenk1 already has the required ties in `four`, so the synthetic data note in the finding is unnecessary but harmless.Verdict CONFIRMED testable, and the proposed test is empirically verified to cover the exact line. Line 4776 lives in the WINDOW_SEEK_HEAD + FRAMEOPTION_EXCLUDE_TIES branch (nodeWindowAgg.c:4765-4778). It is the else of `if (abs_pos == overlapstart)`, reached when abs_pos > overlapstart while EXCLUDE TIES is active and grouptailpos > frameheadpos. The only callers using WINDOW_SEEK_HEAD with a positive relpos is window_nth_value (windowfuncs.c:719-721, relpos = nth-1), so nth>=2 is required; first_value uses relpos=0 (abs_pos==frameheadpos==overlapstart, hitting the if-branch at 4774). The root-cause analysis is accurate. I built no theory only: I started the coverage-built backend (tmp_install), reset the nodeWindowAgg gcda counters, ran ONLY the proposed query against a tenk1-like table (unique1 0..99, four = g%4, giving 4-row peer groups so groupheadpos/grouptailpos span >1 row), and re-ran gcov. Result: line 4776 executed 10 times, while line 4774 showed ##### (0) in that isolated run -- so this query takes the else-branch every time and unambiguously covers the target line. The reason 4774 is not hit here: with ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING, frameheadpos==currentpos>=groupheadpos so overlapstart==frameheadpos, and abs_pos==frameheadpos+1 > overlapstart. Note: existing window.sql:266-270 already runs nth_value(unique1,2) but over a window with NO EXCLUDE clause, so it never enters the EXCLUDE_TIES switch arm -- confirming the gap is real. | |||
| 4777 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4778 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4779 | 0 | default: | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason CONFIRMED defensive-unreachable.
The switch is on (winstate->frameOptions & FRAMEOPTION_EXCLUSION).
FRAMEOPTION_EXCLUSION is the OR of three distinct non-overlapping bits:
EXCLUDE_CURRENT_ROW (0x08000), EXCLUDE_GROUP (0x10000), EXCLUDE_TIES (0x20000).
In principle the mask could yield combined values (0x18000, 0x28000, 0x30000, 0x38000) which would hit default:, so the claim "no other value is representable" is slightly imprecise.
But reachability hinges on whether more than one exclusion bit can be set at once.
It cannot: gram.y opt_window_exclusion_clause (gram.y:17573-17577) is non-recursive and returns exactly one of {EXCLUDE_CURRENT_ROW, EXCLUDE_GROUP, EXCLUDE_TIES, 0}, and frame_extent allows exactly one such clause, ORed via n->frameOptions |= $3. SQL syntax permits only a single EXCLUDE clause per frame, so at most one bit is ever set.
No planner/createplan/parse_agg path sets additional exclusion bits (planner just copies wc->frameOptions).
The codebase itself encodes this invariant: parse_rpr.c:140-150 dispatches the three bits with if/else-if and Asserts exactly one is set.
Therefore the masked value is always one of the four explicitly cased values (0, 0x08000, 0x10000, 0x20000) and default: at 4779-4780 is unreachable by any SQL/regression input.
An identical default arm exists at line 4864 (WINDOW_SEEK_TAIL branch), equally unreachable.Recommended fixKeep as-is. This is the standard PostgreSQL defensive elog(ERROR, "unrecognized ...") pattern for an exhaustive switch over masked option bits, matching the twin arm at line 4864 and the long-standing convention in this file. No change required; the line is intentionally defensive and should remain excluded from coverage expectations. | |||
| 4780 | 0 | elog(ERROR, "unrecognized frame option state: 0x%x", | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4781 | - | winstate->frameOptions); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4782 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4783 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4784 | 969732 | num_reduced_frame = row_is_in_reduced_frame(winobj, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4785 | - | winstate->frameheadpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4786 | 969732 | if (num_reduced_frame < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4787 | 960720 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4788 | 9012 | else if (num_reduced_frame > 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4789 | 6060 | if (relpos >= num_reduced_frame) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4790 | 16 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4791 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4792 | 561640 | case WINDOW_SEEK_TAIL: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4793 | - | /* rejecting relpos > 0 is easy and simplifies code below */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4794 | 561640 | if (relpos > 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4795 | 0 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason CONFIRMED. Line 4795 (`goto out_of_frame` under `if (relpos > 0)` in the WINDOW_SEEK_TAIL case) is unreachable via any SQL/regression input. WinGetSlotInFrame is reached only through the public API WinGetFuncArgInFrame (nodeWindowAgg.c:5278 -> 5295). Exhaustive grep of the whole tree (src + contrib) shows exactly three callers of WinGetFuncArgInFrame, all in src/backend/utils/adt/windowfuncs.c: first_value (HEAD, relpos=0), last_value (TAIL, relpos=0), nth_value (HEAD, relpos=nth-1). The ONLY caller that uses WINDOW_SEEK_TAIL is window_last_value (windowfuncs.c:685-686) and it hardcodes relpos=0, so `relpos > 0` is never true. RPR navigation (FIRST/LAST/PREV/NEXT) does not pass a positive TAIL relpos either: it goes through last_value for the FINAL/tail anchor (relpos=0) and computes reach positions internally via eval_nav_offset_helper; the rpPattern+TAIL branch at 4905 (gcov: covered, 8*) confirms the TAIL case itself IS exercised with relpos=0, but never with relpos>0. The branch is a symmetric defensive guard mirroring the HEAD-case `if (relpos < 0)` early-out at 4726 (per the comment at 4793-4794 'rejecting relpos > 0 is easy and simplifies code below'). It is only reachable by a third-party C window-function extension calling WinGetFuncArgInFrame with WINDOW_SEEK_TAIL and a positive offset (the API is exported in windowapi.h), which is not exercisable from SQL.Recommended fix Leave as a defensive early-out, consistent with the symmetric WINDOW_SEEK_HEAD guard at 4726 and the rationale comment at 4793-4794. No code change needed. Optionally, this could be annotated for coverage exclusion alongside the matching HEAD guard, but that is cosmetic. | |||
| 4796 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4797 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4798 | - | * RPR cares about frame head pos. Need to call | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4799 | - | * update_frameheadpos | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4800 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4801 | 561640 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4802 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4803 | 561640 | update_frametailpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4804 | 561636 | abs_pos = winstate->frametailpos - 1 + relpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4805 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4806 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4807 | - | * Account for exclusion option if one is active. If there is no | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4808 | - | * exclusion, we can safely set the mark at the accessed row. But | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4809 | - | * if there is, we can only mark the frame start, because we can't | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4810 | - | * be sure how far back in the frame the exclusion might cause us | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4811 | - | * to fetch in future. Furthermore, we have to actually check | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4812 | - | * against frameheadpos here, since it's unsafe to try to fetch a | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4813 | - | * row before frame start if the mark might be there already. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4814 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4815 | 561636 | switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4816 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4817 | - | case 0: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4818 | - | /* no adjustment needed */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4819 | - | mark_pos = abs_pos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4820 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4821 | 120 | case FRAMEOPTION_EXCLUDE_CURRENT_ROW: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4822 | 120 | if (abs_pos <= winstate->currentpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4823 | - | winstate->currentpos < winstate->frametailpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4824 | 12 | abs_pos--; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4825 | 120 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4826 | 120 | if (abs_pos < winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4827 | 8 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4828 | - | mark_pos = winstate->frameheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4829 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4830 | 160 | case FRAMEOPTION_EXCLUDE_GROUP: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4831 | 160 | update_grouptailpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4832 | 160 | if (abs_pos < winstate->grouptailpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4833 | 36 | winstate->groupheadpos < winstate->frametailpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4834 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4835 | 36 | int64 overlapend = Min(winstate->grouptailpos, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4836 | - | winstate->frametailpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4837 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4838 | 36 | abs_pos -= overlapend - winstate->groupheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4839 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4840 | 160 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4841 | 160 | if (abs_pos < winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4842 | 36 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4843 | - | mark_pos = winstate->frameheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4844 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4845 | 80 | case FRAMEOPTION_EXCLUDE_TIES: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4846 | 80 | update_grouptailpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4847 | 80 | if (abs_pos < winstate->grouptailpos && | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4848 | 24 | winstate->groupheadpos < winstate->frametailpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4849 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4850 | 24 | int64 overlapend = Min(winstate->grouptailpos, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4851 | - | winstate->frametailpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4852 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4853 | 24 | if (abs_pos == overlapend - 1) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4854 | 24 | abs_pos = winstate->currentpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4855 | - | else | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4856 | 0 | abs_pos -= overlapend - 1 - winstate->groupheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason The claimed classification "testable" and its proposed test are WRONG. Line 4856 is the EXCLUDE_TIES else-branch in the WINDOW_SEEK_TAIL case: `abs_pos -= overlapend - 1 - groupheadpos`, taken only when `abs_pos != overlapend - 1` inside the if-block guarded by `abs_pos < grouptailpos && groupheadpos < frametailpos` (line 4847-4848). I proved it is unreachable by ALL current callers. SEEK_TAIL into WinGetSlotInFrame has exactly ONE caller in the whole tree: window_last_value() in src/backend/utils/adt/windowfuncs.c:685-686, which passes a hardcoded relpos=0. With relpos=0, abs_pos = frametailpos - 1 (line 4804). overlapend = Min(grouptailpos, frametailpos). Case analysis of the entry guard: (A) if grouptailpos <= frametailpos, the guard `abs_pos < grouptailpos` i.e. frametailpos-1 < grouptailpos forces grouptailpos==frametailpos, so overlapend=frametailpos and overlapend-1 = frametailpos-1 = abs_pos -> if-branch (4853-4854). (B) if grouptailpos > frametailpos, overlapend=frametailpos so overlapend-1 = frametailpos-1 = abs_pos -> if-branch. In EVERY case abs_pos == overlapend-1, so the else at 4856 is never taken. The else only fires when relpos < 0 (then abs_pos = frametailpos-1+relpos != frametailpos-1), but no window function passes a negative relpos to a SEEK_TAIL in-frame fetch. The ignore-nulls variant (ignorenulls_getfuncarginframe) is a SEPARATE function and never reaches line 4856. RPR navigation (FIRST/LAST/PREV/NEXT) uses the reduced-frame mechanism, not this exclusion path, and per the comment at line 3755 RPR forbids EXCLUDE clauses entirely, so RPR can never reach an EXCLUDE_TIES branch. The proposed test (last_value EXCLUDE TIES, relpos=0) lands squarely on the if-branch 4853-4854, NOT 4856 -- it does not cover the line. This is symmetric "defensive" code mirroring the EXCLUDE_GROUP branch (4830-4844), written to handle a general negative relpos that no caller currently produces.Recommended fix Two acceptable options. (1) Leave as-is: the branch is intentional defensive/symmetric code that correctly handles a negative SEEK_TAIL relpos should a future window function need it (it mirrors the EXCLUDE_GROUP case). (2) If exact coverage parity is desired, replace the if/else at 4853-4856 with an Assert documenting the invariant, e.g. `Assert(abs_pos == overlapend - 1); abs_pos = winstate->currentpos; ` -- but this would lose generality for negative-relpos callers, so option (1) is preferable. Either way, do NOT add the proposed last_value EXCLUDE TIES regression test as a means to cover this line; it cannot. | |||
| 4857 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4858 | 80 | update_frameheadpos(winstate); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4859 | 80 | if (abs_pos < winstate->frameheadpos) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4860 | 0 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
ReachableHow to test Use a FOLLOWING-only frame (frame head past the current row) combined with ties on the ORDER BY column, so the current row's peer group overlaps the frame and EXCLUDE TIES rewrites abs_pos to currentpos < frameheadpos. Verified empirically to increment line 4860's gcov count on tenk1: SELECT last_value(unique1) over (ORDER BY four rows between 1 following and 2 following exclude ties), four FROM tenk1 WHERE unique1 < 12 ORDER BY four, unique1; (Equivalent minimal form on a small ties table also works: with t2(id,g) VALUES (1,1),(2,1),(3,1),(4,1),(5,2),(6,2): SELECT id, last_value(id) over (ORDER BY g rows between 1 following and 2 following exclude ties) FROM t2 ORDER BY id; -- drove 4860 +4 per run.) Note: this covers the goto at 4860 via the then-branch; line 4856 (else branch) remains separately uncovered and is in fact unreachable for relpos=0 callers.Verdict Classification "testable" is CORRECT and line 4860 is genuinely reachable - I drove execution through it empirically on the live coverage build (gcov count for ": 4860:" went from ##### to positive, with a reproducible +4/+6 delta per query).
However, the proposed test is WRONG and does NOT cover line 4860, and the stated root cause is misleading.
(1) The ONLY caller reaching WINDOW_SEEK_TAIL in WinGetSlotInFrame is window_last_value, which always passes relpos=0 (windowfuncs.c:685-686);
first_value/nth_value use WINDOW_SEEK_HEAD;
RPR navigation uses a separate nav_winobj path, not this function.
(2) With relpos=0, abs_pos=frametailpos-1, and the else branch at line 4856 (abs_pos -= overlapend-1-groupheadpos) is UNREACHABLE: entering the block needs abs_pos<grouptailpos, but reaching the else needs frametailpos>grouptailpos i.e. abs_pos>=grouptailpos - a contradiction. gcov confirms 4856 stays ##### even after targeted queries.
So the claimed mechanism ("else branch pushes abs_pos before frameheadpos") cannot happen.
(3) Line 4860 actually fires via the THEN-branch (4854 sets abs_pos=currentpos) when the frame starts AFTER the current row, so currentpos<frameheadpos.
(4) The proposed test "rows between current row and current row exclude ties" always takes the then-branch with abs_pos=currentpos=frameheadpos, so abs_pos<frameheadpos is false and the goto is never taken - verified: that query left 4860 at #####. | |||
| 4861 | - | mark_pos = winstate->frameheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4862 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4863 | 0 | default: | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason CONFIRMED defensive-unreachable.
Lines 4863-4866 are the default: arm of `switch (winstate->frameOptions & FRAMEOPTION_EXCLUSION)` in the WINDOW_SEEK_TAIL branch.
FRAMEOPTION_EXCLUSION (parsenodes.h:715-717) is the OR of exactly three single-bit flags:
EXCLUDE_CURRENT_ROW (0x08000), EXCLUDE_GROUP (0x10000), EXCLUDE_TIES (0x20000).
The grammar production opt_window_exclusion_clause (gram.y:17574-17577) is a single optional clause that assigns exactly ONE of these bits, or 0 (EXCLUDE NO OTHERS / no clause).
It is not a list, so two exclusion bits can never both be set.
No other code path (parse_clause.c, optimizer) ORs multiple exclusion bits into frameOptions.
Therefore the masked expression always evaluates to one of {0, 0x08000, 0x10000, 0x20000}, and the case 0 / CURRENT_ROW / GROUP / TIES arms (4817-4862) handle all of them.
The default: arm cannot be entered by any SQL or regression input.
It is a true defensive guard, structurally identical to the sibling switch default at 4779-4780 (WINDOW_SEEK_HEAD).
The elog plus `mark_pos = 0` also serves to keep the compiler quiet about an otherwise-uninitialized mark_pos.
I attempted to refute by checking grammar, header bit layout, and programmatic frameOptions assignment;
none allows reaching it.Recommended fixKeep as a defensive elog(ERROR). Optionally annotate with a coverage exclusion (e.g. /* GCOV_EXCL_LINE */ or a comment noting the masked value is provably one of four enumerated cases) to silence the coverage gap, consistent with how the sibling default at 4779-4780 should be treated. No behavioral change is warranted; the mark_pos = 0 assignment must stay to keep the compiler quiet. | |||
| 4864 | 0 | elog(ERROR, "unrecognized frame option state: 0x%x", | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4865 | - | winstate->frameOptions); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4866 | - | mark_pos = 0; /* keep compiler quiet */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4867 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4868 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4869 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4870 | 561592 | num_reduced_frame = row_is_in_reduced_frame(winobj, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4871 | - | winstate->frameheadpos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4872 | 561592 | if (num_reduced_frame < 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4873 | 552812 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4874 | 8780 | else if (num_reduced_frame > 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4875 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4876 | 5760 | if (-relpos >= num_reduced_frame) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4877 | 0 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Line 4877 (`if (-relpos >= num_reduced_frame) goto out_of_frame; `) is the RPR reduced-frame bound check inside the `else if (num_reduced_frame > 0)` block of the WINDOW_SEEK_TAIL case. To fire it needs: (1) num_reduced_frame >= 1, so the condition -relpos >= num_reduced_frame requires -relpos >= 1, i.e. relpos <= -1 (a NEGATIVE relpos); and (2) seektype == WINDOW_SEEK_TAIL. I exhaustively grep'd every WINDOW_SEEK_TAIL / WinGetFuncArgInFrame / WinGetSlotInFrame caller in the whole source tree (src/backend/utils/adt/windowfuncs.c is the only place). The ONLY built-in TAIL caller is window_last_value (windowfuncs.c:685-686), which passes relpos = 0. With relpos=0, -relpos=0, and since we are already inside `num_reduced_frame > 0` (>=1), `0 >= 1` is always false, so the branch is never taken. first_value and nth_value use WINDOW_SEEK_HEAD, not TAIL, and PostgreSQL's nth_value has NO `FROM LAST` variant (confirmed: no from_last support in grammar/parser). RPR's own FIRST/LAST/PREV/NEXT navigation uses dedicated EEOP_RPR_NAV opcodes and WinRPRFetchTuple, completely bypassing WinGetSlotInFrame. The sibling IGNORE NULLS path (line 3765) has the same guard `notnull_relpos >= num_reduced_frame` with notnull_relpos = abs(relpos), equally unreachable from last_value's relpos=0. The proposed test (last_value with PATTERN(A)/DEFINE) is correctly self-refuted in the finding: it reaches the function with relpos=0 but cannot make -relpos>=num_reduced_frame. No SQL or regression input exists that drives relpos<0 into the TAIL branch; only a hypothetical not-yet-existing nth-from-tail window function (or a C extension calling the API directly) could. The line is correct, intentional bound handling. Classification refined from hard-to-reach to defensive-unreachable: it is a guard with no SQL-reachable trigger given the current built-in function set.Recommended fix Leave the code as-is; it is correct, defensive RPR bound handling. To document why it is uncovered, add a brief comment near line 4874-4877 noting that all in-tree TAIL callers (last_value) use relpos==0 so -relpos>=num_reduced_frame cannot fire today, and the guard exists to protect a future nth-from-tail-style window function (and the public WinGetFuncArgInFrame C API) from reading before the reduced-frame start. Alternatively, since num_reduced_frame and relpos==0 jointly make the goto dead for current callers, an Assert documenting the invariant (e.g. Assert(relpos <= 0)) already present at 4794 covers the relpos>0 side; consider mirroring with a comment that relpos<0+TAIL is reserved for future functions. No functional change recommended. | |||
| 4878 | 5760 | abs_pos = winstate->frameheadpos + relpos + | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4879 | - | num_reduced_frame - 1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4880 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4881 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4882 | 0 | default: | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason CONFIRMED unreachable via any SQL/regression input.
WinGetSlotInFrame is static and called only from WinGetFuncArgInFrame (nodeWindowAgg.c:5295), which passes seektype through unchanged.
WinGetFuncArgInFrame (public windowapi.h API) is called only from windowfuncs.c at 3 sites: window_first_value/window_nth_value pass WINDOW_SEEK_HEAD, window_last_value passes WINDOW_SEEK_TAIL.
The seektype enum (windowapi.h:34-36) has exactly 3 compile-time-constant values:
CURRENT=0, HEAD=1, TAIL=2. All three are explicitly handled by switch cases at lines 4720, 4724, 4792 (and CURRENT itself elogs at 4721).
The default: arm at 4882-4883 fires only for a seektype outside {0,1,2}, which no built-in caller ever supplies and which cannot originate from user SQL (seektype is internal int, never derived from query data).
RPR FIRST/LAST navigation reuses the same first_value/last_value window functions, so it too only yields HEAD/TAIL.
Tried hard to refute: no exclusion-clause, RPR
PATTERN/DEFINE, or IGNORE NULLS path alters seektype before reaching this switch.
This mirrors the identical defensive default at line 3735.Recommended fixKeep as defensive elog(ERROR, ...) for consistency with the sibling switch at line 3735, OR convert to Assert(false) / pg_unreachable() since seektype is purely internal and the elog branch can never be hit by valid callers. Either way exclude lines 4882-4883 from coverage expectations as defensive-unreachable. Recommend matching whatever style is used at 3735 to keep the two parallel switches consistent. | |||
| 4883 | 0 | elog(ERROR, "unrecognized window seek type: %d", seektype); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4884 | - | abs_pos = mark_pos = 0; /* keep compiler quiet */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4885 | - | break; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4886 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4887 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4888 | 17776 | if (!window_gettupleslot(winobj, abs_pos, slot)) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4889 | 264 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4890 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4891 | - | /* The code above does not detect all out-of-frame cases, so check */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4892 | 17512 | if (row_is_in_frame(winobj, abs_pos, slot, false) <= 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4893 | 200 | goto out_of_frame; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4894 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4895 | 17292 | if (isout) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4896 | 0 | *isout = false; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed defensive-unreachable. Line 4896 (`*isout = false; `) inside the guarded block `if (isout)` at 4895 executes only when isout != NULL on the in-frame success path. Exhaustive call-graph audit: WinGetSlotInFrame (static, nodeWindowAgg.c:4706) is called from exactly one site, WinGetFuncArgInFrame:5295, which forwards the caller's isout. WinGetFuncArgInFrame has only three in-tree callers - window_first_value (windowfuncs.c:665), window_last_value (:687), window_nth_value (:721) - and ALL pass isout = NULL. ignorenulls_getfuncarginframe is a wholly separate implementation that does not call WinGetSlotInFrame. WinGetFuncArgInPartition is a distinct function (windowapi.h:59) not routed through WinGetSlotInFrame. RPR navigation uses ExecRPRNavGetSlot, also separate. grep for WinGetFuncArgInFrame across all .c files outside windowfuncs.c/nodeWindowAgg.c returns nothing, so no contrib/test-module caller exists. No SQL or regression input can make any path pass a non-NULL isout into WinGetSlotInFrame; therefore *isout = false; at 4896 is never executed. The guard line 4895 is evaluated (NULL each time) but the assignment body at 4896 is dead under all reachable inputs. I tried to refute by hunting for an alternate caller (ignore-nulls path, partition path, RPR nav path, exported-API external callers) and found none reachable from SQL.Recommended fix The isout output parameter of WinGetFuncArgInFrame/WinGetSlotInFrame has no live consumer: every in-tree call passes NULL. Two defensible options: (1) Drop the isout parameter from WinGetSlotInFrame and WinGetFuncArgInFrame, and from the windowapi.h contract, until a real consumer appears - this removes the dead assignments at 4896 and 4913 entirely. (2) Keep the parameter for API symmetry with WinGetFuncArgInPartition (which has a genuine consumer) and accept the guard as presently dead, documenting that WinGetFuncArgInFrame's isout is currently only ever called with NULL. Recommend option (2): retain for API completeness/symmetry, add a brief comment that isout is currently exercised only as NULL, and exclude 4896/4913 from the coverage target rather than writing an artificial test module. | |||
| 4897 | 17292 | if (set_mark) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4898 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4899 | - | /* | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4900 | - | * If RPR is enabled and seek type is WINDOW_SEEK_TAIL, we set the | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4901 | - | * mark position unconditionally to frameheadpos. In this case the | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4902 | - | * frame always starts at CURRENT_ROW and never goes back, thus | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4903 | - | * setting the mark at the position is safe. | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4904 | - | */ | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4905 | 17248 | if (winstate->rpPattern != NULL && seektype == WINDOW_SEEK_TAIL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4906 | 5760 | mark_pos = winstate->frameheadpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4907 | 17248 | WinSetMarkPosition(winobj, mark_pos); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4908 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4909 | - | return 0; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4910 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4911 | 1514056 | out_of_frame: | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4912 | 1514056 | if (isout) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4913 | 0 | *isout = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed unreachable from any SQL/regression input. Line 4913 `*isout = true; ` at the out_of_frame label is guarded by `if (isout)` (4912). isout reaches WinGetSlotInFrame only via its single caller WinGetFuncArgInFrame (nodeWindowAgg.c:5295), which passes its own isout straight through. WinGetFuncArgInFrame's only in-tree callers are window_first_value, window_last_value, and window_nth_value (windowfuncs.c:665, 687, 721), and ALL three pass NULL for isout. The ignorenulls_getfuncarginframe branch (5292) forwards the same isout, so it is NULL there too. Therefore isout is always NULL through every live code path, the `if (isout)` test at 4912 is always false, and 4913 never executes. The out_of_frame label itself IS reachable (e.g. last_value over an excluded/empty frame hits goto at 4889/4893), and the adjacent line 4914 `*isnull = true; ` would be covered, but the isout-guarded body is dead. WinGetFuncArgInFrame is extern (windowapi.h:63), so an out-of-tree extension could pass non-NULL isout, but no SQL query can. Symmetric to line 4896 (the *isout=false success-path counterpart), same root cause. Classification accurate; no test can cover it.Recommended fix Accept as defensive/dead. The isout out-parameter is part of the WinGetFuncArgInFrame/WinGetSlotInFrame API symmetry but has no consumer: every live caller passes NULL. Options: (1) keep lines 4895-4896 and 4912-4913 for API completeness with a /* dead: no caller passes non-NULL isout */ note, or (2) remove the isout parameter from WinGetFuncArgInFrame/WinGetSlotInFrame/ignorenulls_getfuncarginframe until a consumer exists. Either way, exclude these lines from the coverage-gap target rather than chasing them with a test. | |||
| 4914 | 1514056 | *isnull = true; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4915 | 1514056 | return -1; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4916 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4932 | 2107439 | WinCheckAndInitializeNullTreatment(WindowObject winobj, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4933 | - | bool allowNullTreatment, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4934 | - | FunctionCallInfo fcinfo) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4935 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4936 | 2107439 | Assert(WindowObjectIsValid(winobj)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4937 | 2107439 | if (winobj->ignore_nulls != NO_NULLTREATMENT && !allowNullTreatment) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4938 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4939 | 48 | const char *funcname = get_func_name(fcinfo->flinfo->fn_oid); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4940 | - | 24cfb8dRow pattern recognition patch (executor and commands). | |
| 4941 | 48 | if (!funcname) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4942 | 0 | elog(ERROR, "could not get function name"); | 24cfb8dRow pattern recognition patch (executor and commands). |
UnreachableReason Confirmed defensive-unreachable. Line 4942 `elog(ERROR, "could not get function name")` fires only when get_func_name(fcinfo->flinfo->fn_oid) returns NULL, which (per lsyscache.c:1913-1930) happens only if no pg_proc row exists for the given OID. But fn_oid here is the OID of the window function currently being executed: it was resolved at parse/plan time, the syscache tuple is held during execution, and the function cannot be dropped while in use. Therefore a valid pg_proc row always exists and funcname is never NULL on this path. The reachable, live branch is the ereport at 4943-4946 (function does not allow RESPECT/IGNORE NULLS), which IS coverable. No SQL/regression input can drive line 4942; concurrent DROP is prevented by dependency/lock semantics during query execution.Recommended fix Replace the `if (!funcname) elog(ERROR, "could not get function name");
` guard with `Assert(funcname != NULL);
` since the NULL case is structurally impossible during execution.
Alternatively fold a fallback into the ereport, e.g. errmsg("function %s does not allow RESPECT/IGNORE NULLS", funcname ? funcname :
"???"), eliminating the separate unreachable error path.
The Assert form is preferred as it documents the invariant without adding an untestable error branch. | |||
| 4943 | 48 | ereport(ERROR, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4944 | - | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4945 | - | errmsg("function %s does not allow RESPECT/IGNORE NULLS", | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4946 | - | funcname))); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4947 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4948 | 2107391 | else if (winobj->ignore_nulls == PARSER_IGNORE_NULLS) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4949 | 152 | winobj->ignore_nulls = IGNORE_NULLS; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4950 | 2107391 | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4965 | 221280 | WinGetPartitionLocalMemory(WindowObject winobj, Size sz) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4966 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4967 | 221280 | Assert(WindowObjectIsValid(winobj)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4968 | 221280 | if (winobj->localmem == NULL) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4969 | 295 | winobj->localmem = | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4970 | 295 | MemoryContextAllocZero(winobj->winstate->partcontext, sz); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4971 | 221280 | return winobj->localmem; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4972 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4980 | 501586 | WinGetCurrentPosition(WindowObject winobj) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4981 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4982 | 501586 | Assert(WindowObjectIsValid(winobj)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4983 | 501586 | return winobj->winstate->currentpos; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4984 | - | } | 24cfb8dRow pattern recognition patch (executor and commands). |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 4995 | 208 | WinGetPartitionRowCount(WindowObject winobj) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4996 | - | { | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4997 | 208 | Assert(WindowObjectIsValid(winobj)); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4998 | 208 | spool_tuples(winobj->winstate, -1); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 4999 | 208 | return winobj->winstate->spooled_rows; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5000 | - | } | 95b07bcSupport window functions a la SQL:2008. |
| Line | Hits | Source | Commit |
|---|---|---|---|
| 5278 | - | WinGetFuncArgInFrame(WindowObject winobj, int argno, | - |
| 5279 | - | int relpos, int seektype, bool set_mark, | - |
| 5280 | - | bool *isnull, bool *isout) | - |
| 5281 | - | { | - |
| 5282 | - | WindowAggState *winstate; | - |
| 5283 | - | ExprContext *econtext; | - |
| 5284 | - | TupleTableSlot *slot; | - |
| 5285 | - | - | |
| 5286 | - | Assert(WindowObjectIsValid(winobj)); | - |
| 5287 | - | winstate = winobj->winstate; | - |
| 5288 | - | econtext = winstate->ss.ps.ps_ExprContext; | - |
| 5289 | - | slot = winstate->temp_slot_1; | - |
| 5290 | - | - | |
| 5291 | - | if (winobj->ignore_nulls == IGNORE_NULLS) | - |
| 5292 | - | return ignorenulls_getfuncarginframe(winobj, argno, relpos, seektype, | - |
| 5293 | - | set_mark, isnull, isout); | - |
| 5294 | - | - | |
| 5295 | 1531400 | if (WinGetSlotInFrame(winobj, slot, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5296 | - | relpos, seektype, set_mark, | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5297 | - | isnull, isout) == 0) | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5298 | - | { | - |
| 5299 | 17292 | econtext->ecxt_outertuple = slot; | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5300 | 17292 | return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno), | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5301 | - | econtext, isnull); | 24cfb8dRow pattern recognition patch (executor and commands). |
| 5302 | - | } | - |
| 5303 | - | - | |
| 5304 | - | if (isout) | - |
| 5305 | - | *isout = true; | - |
| 5306 | - | *isnull = true; | - |
| 5307 | - | return (Datum) 0; | - |
| 5308 | - | } | - |