From 1cc68117d8e37ae8ed83f4c565302180cfadb749 Mon Sep 17 00:00:00 2001
From: David Geier <geidav.pg@gmail.com>
Date: Fri, 6 Nov 2026 15:01:09 +0100
Subject: [PATCH v12 4/4] Use in-place American Flag Sort to extract trigrams

American Flag Sort is an in-place MSD radix sort variant that
recursively partitions trigrams by character position. It's
slower though than out-of-place LSD radix sort for big input
arrays. Insertion sort handles small partitions where the
overhead of frequency and prefix sum computations outweigh
the gains. qunique() still removes duplicates afterwards.
---
 contrib/pg_trgm/trgm_op.c | 75 +++++++++++++++++++--------------------
 1 file changed, 36 insertions(+), 39 deletions(-)

diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index 5c1b1c14cd4..49688bbc984 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -272,8 +272,8 @@ radix_key(char x, bool char_is_signed)
 	return char_is_signed ? x ^ 0x80 : x;
 }
 
-static inline size_t
-trigram_radix_sort_and_unique(trgm *trg, size_t count, bool char_is_signed)
+static inline void
+trigram_radix_sort_recurse(trgm *trg, size_t count, int base, bool char_is_signed)
 {
 	if (count < 64)
 	{
@@ -292,55 +292,50 @@ trigram_radix_sort_and_unique(trgm *trg, size_t count, bool char_is_signed)
 
 			memcpy(trg[j], &tmp, sizeof(trgm));
 		}
-
-		return qunique(trg, count, sizeof(trgm), CMPTRGM_EQ);
 	}
 	else
 	{
-		trgm *buffer = palloc_array(trgm, count);
-		trgm *starts[256];
-		trgm *from = trg;
-		trgm *to = buffer;
-		uint32_t freqs[3][256];
+		uint32_t freqs[256];
+		uint32_t starts[256];
+		uint32_t cur[256];
 
-		/*
-		* Compute frequencies to partition the buffer.
-		*/
 		memset(freqs, 0, sizeof(freqs));
+		for (size_t i = 0; i < count; i++)
+			freqs[radix_key(trg[i][base], char_is_signed)]++;
 
-		for (size_t j = 0; j < count; j++)
-		{
-			freqs[0][radix_key(trg[j][0], char_is_signed)]++;
-			freqs[1][radix_key(trg[j][1], char_is_signed)]++;
-			freqs[2][radix_key(trg[j][2], char_is_signed)]++;
-		}
+		starts[0] = 0;
+		for (int k = 1; k < 256; k++)
+			starts[k] = starts[k - 1] + freqs[k - 1];
 
-		/*
-		* Do the sorting. Start with last character because that's the "LSB"
-		* in a trigram. Avoid unnecessary copies by ping-ponging between the buffers.
-		*/
-		for (int i = 2; i >= 0; i--)
+		memcpy(cur, starts, sizeof(cur));
+
+		for (size_t i = 0; i < count; i++)
 		{
-			trgm *old_from = from;
-			trgm *next = to;
+			unsigned char b = radix_key(trg[i][base], char_is_signed);
 
-			for (size_t j = 0; j < 256; j++)
+			while (i < starts[b] || i >= starts[b] + freqs[b])
 			{
-				starts[j] = next;
-				next += freqs[i][j];
-			}
+				const char c = trg[i][base];
+				size_t j = cur[b];
+				trgm tmp;
+
+				while (trg[j][base] == c)
+					j++;
+
+				Assert(j < starts[b] + freqs[b]);
 
-			for (size_t j = 0; j < count; j++)
-				memcpy(starts[radix_key(from[j][i], char_is_signed)]++, from[j], sizeof(trgm));
+				memcpy(tmp, trg[i], sizeof(trgm));
+				memcpy(trg[i], trg[j], sizeof(trgm));
+				memcpy(trg[j], tmp, sizeof(trgm));
 
-			from = to;
-			to = old_from;
+				cur[b] = j + 1;
+				b = radix_key(trg[i][base], char_is_signed);
+			}
 		}
 
-		count = qunique(buffer, count, sizeof(trgm), CMPTRGM_EQ);
-		memcpy(trg, buffer, sizeof(trgm) * count);
-		pfree(buffer);
-		return count;
+		for (int k = 0; k < 256; k++)
+			if (freqs[k] > 1 && base < 2)
+				trigram_radix_sort_recurse(trg + starts[k], freqs[k], base + 1, char_is_signed);
 	}
 }
 
@@ -348,9 +343,11 @@ static size_t
 trigram_sort_and_unique(trgm *array, size_t n)
 {
 	if (GetDefaultCharSignedness())
-		return trigram_radix_sort_and_unique(array, n, true);
+		trigram_radix_sort_recurse(array, n, 0, true);
 	else
-		return trigram_radix_sort_and_unique(array, n, false);
+		trigram_radix_sort_recurse(array, n, 0, false);
+
+	return qunique(array, n, sizeof(trgm), CMPTRGM_EQ);
 }
 
 /*
-- 
2.53.0

