static void mkheap_heapify(MKHeap *mkheap, bool doLv0) { int i; MKEntry tmp; /* Build heap, expanded already at lv 0 */ if(doLv0) { for(i=PARENT(mkheap->maxentry); i >= 0; --i) { tmp = mkheap->p[i]; mkheap_siftdown(mkheap, i, &tmp); } } /* Populate deeper levels */ while(mke_get_lv(mkheap->p) < mkheap->mkctxt->total_lv - 1) { int nlv = mke_get_lv(mkheap->p) + 1; Assert(nlv < mkheap->mkctxt->total_lv); mkheap_save_lvtop(mkheap); mkheap_prep_siftdown_lv(mkheap, nlv, 0, mkheap->mkctxt); } #ifdef USE_ASSERT_CHECKING if(gp_mk_sort_check) mkheap_verify_heap(mkheap, 0); #endif }
/* * Heap compare. * Rule: * 1. Empty entry is always bigger, so empty cells will be at the bottom * 2. A entry is expanded at lv, means 0 - (lv-1) is the same as heaptop. * 3. From 2, deeper level is smaller. * 4. Smaller run is always smaller. Why we do this after 2. and 3.? * When a entry is inserted, we don't know this entry belongs to * current run on the next run yet. * 5. Same run, same level, then we compare the null flag. Pg 8.2 (that is, * what we are using, always use null last.) We do have a null first bit * reserved. * 6. Compare non null datum. * * These rules guarantee the heap top is minimal of current run. However, at * deeper level, mkheap_compare return 0 does not mean the two entry are really * equal. They are just equal up to a certain level. * * Step 1 - 5 are captured in compflags. */ static inline int32 mkheap_compare(MKHeap *heap, MKEntry *a, MKEntry *b) { /* Check for interrupts every COMPARES_BETWEEN_INTERRUPT_CHECKS comparisons */ if (compare_count++ % COMPARES_BETWEEN_INTERRUPT_CHECKS == 0) { CHECK_FOR_INTERRUPTS(); } int32 ret = (a->compflags & (~MKE_CF_NULLBITS)) - (b->compflags & (~MKE_CF_NULLBITS)); if (ret == 0) { int32 limitmask = heap->mkctxt->limitmask; bool needcmp = false; ret = a->compflags - b->compflags; /* need comparison if non-null values compare equal by compflags */ needcmp = (ret == 0) && (!mke_is_null(a)); if (needcmp) { /* two entries were equal according to flags. This means the first lv entries are the same * * So now compare the actual Datums for the next level (which is level at index lv) */ int32 lv = mke_get_lv(a); Assert(lv < heap->mkctxt->total_lv); Assert(lv == mke_get_lv(b)); ret = tupsort_compare_datum(a, b, heap->mkctxt->lvctxt+lv, heap->mkctxt); } /* * limitmask is either 0 or -1. If 0 then ~limitmask is all 1s, else it's all zeroes * so this is equivalanet to uselimit ? -ret : ret * * So this causes a REVERSE sort -- which is what tuplesort_limit_sort wants and because for tuplesort_limit_insert * we actually want to POP the HIGHER values, not the lower ones since we are trying to find the smallest elements * * There's no need to reverse the initial comparison of this function because it is considering only * differences in levels and runs, which already represent the reversed order. * * todo: bug when ret is the min possible int val (so negating it leaves it unchanged). (and earlier comp does not guarantee -1,0,1 as results) * todo: rename limitmask to reverseSortMask... */ ret = (ret & (~limitmask)) + ((-ret) & limitmask); } return ret; }
/* * Get the next value. On success, a non-negative value is returned and *out is populated with the value * that was on the top of the heap. * * if this is an array-backed heap then *out is inserted into the heap. If it's a reader-backed heap then * *out is ignored on input. */ int mkheap_putAndGet(MKHeap *mkheap, MKEntry *out) { int ret = 0; Assert(out); /* * fetch from appropriate source * * note that these two cases don't behave the same in terms of how *out is treated. * mkheap_putAndGet_reader should be called mkheap_get_reader -- it never puts the input value * mkheap_putAndGet_impl will put *out if it's not empty, and then do the get. */ if(mkheap->nreader > 0) ret = mkheap_putAndGet_reader(mkheap, out); else ret = mkheap_putAndGet_impl(mkheap, out); /* check: underlying call must have enforced uniquness */ AssertImply(mkheap->mkctxt->enforceUnique, ret != 0); /* free *out */ if(mkheap->mkctxt->cpfr) (mkheap->mkctxt->cpfr)(out, NULL, mkheap->mkctxt->lvctxt + mke_get_lv(out)); return ret; }
static bool mke_has_duplicates_with_root(MKHeap *mkheap) { /** since the heap is heapified, only if the level is at the max can * we have a duplicate */ if ( mke_get_lv(mkheap->p) + 1 != mkheap->mkctxt->total_lv ) return false; /* see if we have children which exactly match the root */ if(RIGHT(0) < mkheap->maxentry) { int cmp = mkheap_compare(mkheap, mkheap->p, mkheap->p+RIGHT(0)); if(cmp==0) return true; } if(LEFT(0) < mkheap->maxentry) { int cmp = mkheap_compare(mkheap, mkheap->p, mkheap->p+LEFT(0)); if(cmp==0) return true; } return false; }
static int mkqsort_comp_entry_all_lv(MKEntry *a, MKEntry *b, MKContext *mkctxt) { int i; int c; MKEntry aa = {0, }; MKEntry bb = {0, }; (*mkctxt->cpfr)(&aa, a, mk_get_ctxt(mkctxt, mke_get_lv(a))); (*mkctxt->cpfr)(&bb, b, mk_get_ctxt(mkctxt, mke_get_lv(b))); for(i=0; i<mkctxt->total_lv; ++i) { MKPrepare prep = mkctxt->prep[i]; void *ctxt = mk_get_ctxt(mkctxt, i); int32 nf1, nf2; if(prep) { (prep)(&aa, ctxt); (prep)(&bb, ctxt); } nf1 = mke_get_nullbits(&aa); nf2 = mke_get_nullbits(&bb); c = nf1 - nf2; if (c!=0) return c; if (!mke_is_null(&aa)) { Assert(!mke_is_null(&bb)); c = (comp)(&aa, &bb, ctxt); if (c!=0) return c; } } return 0; }
/** * Save the value for mkheap's current top (at the level entered in the current top) into lvtops. */ static void mkheap_save_lvtop(MKHeap *mkheap) { int lv; MKLvContext *lvctxt; Assert(mkheap->count > 0); lv = mke_get_lv(mkheap->p); lvctxt = mkheap->mkctxt->lvctxt + lv; Assert(lv < mkheap->mkctxt->total_lv); /* Free the old one and copy in the new one */ (*mkheap->mkctxt->cpfr)(mkheap->lvtops + lv, mkheap->p, lvctxt); }
static inline bool mkheap_need_heapify(MKHeap *mkheap) { return ( /* More than one entry */ mkheap_cnt(mkheap) > 1 && /* Not fully expanded */ mke_get_lv(mkheap->p) < mkheap->mkctxt->total_lv-1 && /* Cannot determine by comp flag */ ( (1 < mkheap->maxentry && mkheap->p[0].compflags == mkheap->p[1].compflags) || (2 < mkheap->maxentry && mkheap->p[0].compflags == mkheap->p[2].compflags) ) ); }
/* * Prepare heap (expand lv) at postion cur. */ static void mkheap_prep_siftdown_lv(MKHeap *mkheap, int lv, int cur, MKContext *mkctxt) { bool expchild = false; int c; Assert(cur < mkheap->maxentry); Assert(cur >= 0); /* If necessary, expand right */ if (RIGHT(cur) < mkheap->maxentry) { c = mkheap_compare(mkheap, mkheap->p + cur, mkheap->p + RIGHT(cur)); if (c == 0) { mkheap_prep_siftdown_lv(mkheap, lv, RIGHT(cur), mkctxt); expchild = true; } } /* If necessary, expand left */ if (LEFT(cur) < mkheap->maxentry) { c = mkheap_compare(mkheap, mkheap->p + cur, mkheap->p + LEFT(cur)); if (c == 0) { mkheap_prep_siftdown_lv(mkheap, lv, LEFT(cur), mkctxt); expchild = true; } } Assert(mke_get_lv(mkheap->p + cur) == lv - 1); if (mkheap->mkctxt->fetchForPrep) tupsort_prepare(mkheap->p + cur, mkctxt, lv); mke_set_lv(mkheap->p + cur, lv); if (expchild) { MKEntry tmp = mkheap->p[cur]; mkheap_siftdown(mkheap, cur, &tmp); } }
/* * Create a multi-key heap from an array of entries * * entries: the values to convert to a heap. This array will be under mkheap's ownership * alloc_sz: the allocation size of entries: that is, how much room the array has. * cnt: the number of elements in entries which should be used to build the heap * mkctxt: description of the heap to build * * If alloc_sz is zero then entries must be NULL */ MKHeap * mkheap_from_array(MKEntry *entries, int alloc_sz, int cnt, MKContext *mkctxt) { MKHeap *heap = (MKHeap *) palloc(sizeof(MKHeap)); Assert(mkctxt); Assert(alloc_sz >= cnt); AssertEquivalent(entries != NULL, cnt > 0); AssertEquivalent(!entries, cnt == 0); heap->mkctxt = mkctxt; heap->lvtops = palloc0(mkctxt->total_lv * sizeof(MKEntry)); heap->readers = NULL; heap->nreader = 0; AssertImply(alloc_sz == 0, !entries); Assert(cnt >= 0 && cnt <= alloc_sz); heap->p = entries; heap->alloc_size = alloc_sz; heap->count = cnt; heap->maxentry = cnt; #ifdef USE_ASSERT_CHECKING { int i; for (i = 0; i < cnt; ++i) { Assert(mke_get_lv(entries + i) == 0); Assert(mke_get_reader(entries + i) == 0); } } #endif /* * note: see NOTE ON UNIQUENESS CHECKING at the top of this file for * information about why we don't check uniqueness here */ mk_prepare_array(entries, 0, cnt - 1, 0, mkctxt); mkheap_heapify(heap, true); return heap; }
/* * Update all lvtops entries. The lvtops entries whose array index is not greater * than the level value stored in the current top element in the heap, update them * with the value from the top element in the heap. This function also cleans up * the remaining lvtops entries. * * This is needed after the current top entry in the heap is removed from the heap, since * some lvtops' ptr may point to the tuple that could be freed when its associated entry * is removed from the heap. */ static void mkheap_update_lvtops(MKHeap *mkheap) { int top_lv = mke_get_lv(mkheap->p); for (int lv = 0; lv < mkheap->mkctxt->total_lv; lv++) { MKEntry *lvEntry = mkheap->lvtops + lv; MKEntry *srcEntry = NULL; if (lv <= top_lv) { srcEntry = mkheap->p; if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(mkheap->p, mkheap->mkctxt, lv); } (*mkheap->mkctxt->cpfr)(lvEntry, srcEntry, mkheap->mkctxt->lvctxt + lv); /* Set the correct level */ mke_set_lv(lvEntry, lv); } }
void mkheap_verify_heap(MKHeap *heap, int top) { int empty_cnt = 0; int i; MKEntry e; if(heap->count == 0) return; Assert(heap->count > 0); Assert(heap->maxentry > 0); Assert(heap->count <= heap->maxentry); Assert(heap->maxentry <= heap->alloc_size); e = heap->p[0]; mke_set_lv(&e, 0); mke_clear_refc_copied(&e); /* Checking for lvtops */ for(i=0; i<mke_get_lv(heap->p); ++i) { int c; /* Too much trouble dealing with ref counters. Just don't */ /* if(!mke_test_flag(heap->lvtops+i, MKE_RefCnt | MKE_Copied)) { */ mke_set_lv(&e, i); if ( heap->mkctxt->fetchForPrep) tupsort_prepare(&e, heap->mkctxt, i); c = mkheap_compare(heap, &e, heap->lvtops+i); Assert(c==0); /* } */ } /* Verify Heap property */ for(i=top; i<heap->maxentry; ++i) { int left = LEFT(i); int right = RIGHT(i); int cl, cr; if(mke_is_empty(heap->p+i)) ++empty_cnt; if(left >= heap->maxentry) continue; cl = mkheap_compare(heap, heap->p+i, heap->p+left); Assert(cl<=0); if(i==0 && cl==0) Assert(mke_get_lv(heap->p) == heap->mkctxt->total_lv-1); if(right >= heap->maxentry) continue; cr = mkheap_compare(heap, heap->p+i, heap->p+right); Assert(cr<=0); if(i==0 && cr==0) Assert(mke_get_lv(heap->p) == heap->mkctxt->total_lv-1); } }
/* * Insert an entry and perhaps return the top element of the heap in *e * * Comparison happens from the specified level to the end of levels, as needed: * Return < 0 if smaller than heap top; *e is unchanged * Return = 0 if eq to heap top ; *e is unchanged (but will have value equal to the heap top) * Return > 0 if successfully inserted; *e is populated with the removed heap top * * If 0 would be returned but the heap is marked as needing uniqueness enforcement, error is generated instead */ static int mkheap_putAndGet_impl(MKHeap *mkheap, MKEntry *e) { int c = 0; int toplv; MKEntry tmp; /* can't put+get from an empty heap */ Assert(mkheap->count > 0); if ( mkheap->mkctxt->enforceUnique && mke_has_duplicates_with_root(mkheap)) { /** * See NOTE ON UNIQUENESS CHECKING in the comment at the top of the file * for information about why we check for duplicates here */ ERROR_UNIQUENESS_VIOLATED(); } if(mke_is_empty(e)) { /* adding an empty (sentinel): just remove from count and fallthrough to where top is removed */ --mkheap->count; } else if (mke_get_run(e) != mke_get_run(mkheap->p)) { /* this code assumes that the new one, with lower run, is LARGER than the top -- so it must be larger run */ Assert(mke_get_run(e) > mke_get_run(mkheap->p)); /* when the runs differ it is because we attempted once with the runs equal. * So if level is zero then: the level was zero AND validly prepared for the previous run -- and there is no need to prep again */ if ( mke_get_lv(e) != 0 ) { /* Not same run, at least prepare lv 0 */ if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(e, mkheap->mkctxt, 0); mke_set_lv(e, 0); } /* now fall through and let top be returned, new one is also inserted so no change to count */ } else { /* same run so figure out where it fits in relation to the heap top */ int lv = 0; toplv = mke_get_lv(mkheap->p); mke_set_lv(e, lv); /* populate level until we differ from the top element of the heap */ while (lv < toplv) { if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(e, mkheap->mkctxt, lv); c = mkheap_compare(mkheap, e, mkheap->lvtops+lv); if(c!=0) break; mke_set_lv(e, ++lv); } /* smaller than top */ if(c<0) return -1; /* we have not done e->lv == toplv yet since we increment at the end of the previous loop. Do it now. */ Assert(mke_get_lv(e) == lv); if(lv == toplv) { if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(e, mkheap->mkctxt, lv); c = mkheap_compare(mkheap, e, mkheap->p); if(c < 0) return -1; } if(c==0) { /* * Equal and at top level. * * This means that e is less-than/equal to all entries except the heap top. */ Assert(mke_get_lv(e) == lv); Assert(lv == mke_get_lv(mkheap->p)); /* * Expand more levels of lvtop in the current top and the new one until we detect a difference. */ while(lv < mkheap->mkctxt->total_lv - 1) { mkheap_save_lvtop(mkheap); ++lv; /* expand top */ if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(mkheap->p, mkheap->mkctxt, lv); /* expand new element */ if ( mkheap->mkctxt->fetchForPrep) tupsort_prepare(e, mkheap->mkctxt, lv); mke_set_lv(mkheap->p, lv); mke_set_lv(e, lv); c = mkheap_compare(mkheap, e, mkheap->p); if(c != 0) break; } if(c<=0) { /* if new one is less than current top then we just return that negative comparison */ /* if new one equals the current top then we could do an insert and immediate removal -- but it * won't matter so we simply return right away, leaving *e untouched */ /* enforce uniqueness first */ if(c == 0 && mkheap->mkctxt->enforceUnique) { ERROR_UNIQUENESS_VIOLATED(); } return c; } } } /* Now, I am bigger than top but not definitely smaller/equal to all other entries * * So we will: * return top as *e * do heap shuffling to restore heap ordering */ tmp = *e; *e = mkheap->p[0]; /* Sift down a hole to bottom of (current or next) run, depends on tmp.run */ mkheap_siftdown(mkheap, 0, &tmp); if(mkheap_need_heapify(mkheap)) mkheap_heapify(mkheap, false); if (mkheap->count > 0) { mkheap_update_lvtops(mkheap); } #ifdef USE_ASSERT_CHECKING if(gp_mk_sort_check) mkheap_verify_heap(mkheap, 0); #endif return 1; }