diff --git a/src/backend/regex/regc_locale.c b/src/backend/regex/regc_locale.c
index 6cf27958b1545a61fba01e76dc4d37aca32789dc..c0414a24912f2f8e1d73be9cbe6469546873c57b 100644
--- a/src/backend/regex/regc_locale.c
+++ b/src/backend/regex/regc_locale.c
@@ -350,6 +350,16 @@ static const struct cname
 };
 
 
+/*
+ * We do not use the hard-wired Unicode classification tables that Tcl does.
+ * This is because (a) we need to deal with other encodings besides Unicode,
+ * and (b) we want to track the behavior of the libc locale routines as
+ * closely as possible.  For example, it wouldn't be unreasonable for a
+ * locale to not consider every Unicode letter as a letter.  So we build
+ * character classification cvecs by asking libc, even for Unicode.
+ */
+
+
 /*
  * element - map collating-element name to celt
  */
@@ -489,7 +499,11 @@ eclass(struct vars * v,			/* context */
 /*
  * cclass - supply cvec for a character class
  *
- * Must include case counterparts on request.
+ * Must include case counterparts if "cases" is true.
+ *
+ * The returned cvec might be either a transient cvec gotten from getcvec(),
+ * or a permanently cached one from pg_ctype_get_cache().  This is okay
+ * because callers are not supposed to explicitly free the result either way.
  */
 static struct cvec *
 cclass(struct vars * v,			/* context */
@@ -548,79 +562,54 @@ cclass(struct vars * v,			/* context */
 		index = (int) CC_ALPHA;
 
 	/*
-	 * Now compute the character class contents.
-	 *
-	 * For the moment, assume that only char codes < 256 can be in these
-	 * classes.
+	 * Now compute the character class contents.  For classes that are
+	 * based on the behavior of a <wctype.h> or <ctype.h> function, we use
+	 * pg_ctype_get_cache so that we can cache the results.  Other classes
+	 * have definitions that are hard-wired here, and for those we just
+	 * construct a transient cvec on the fly.
 	 */
 
 	switch ((enum classes) index)
 	{
 		case CC_PRINT:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isprint((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isprint);
 			break;
 		case CC_ALNUM:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isalnum((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isalnum);
 			break;
 		case CC_ALPHA:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isalpha((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isalpha);
 			break;
 		case CC_ASCII:
+			/* hard-wired meaning */
 			cv = getcvec(v, 0, 1);
 			if (cv)
 				addrange(cv, 0, 0x7f);
 			break;
 		case CC_BLANK:
+			/* hard-wired meaning */
 			cv = getcvec(v, 2, 0);
 			addchr(cv, '\t');
 			addchr(cv, ' ');
 			break;
 		case CC_CNTRL:
+			/* hard-wired meaning */
 			cv = getcvec(v, 0, 2);
 			addrange(cv, 0x0, 0x1f);
 			addrange(cv, 0x7f, 0x9f);
 			break;
 		case CC_DIGIT:
-			cv = getcvec(v, 0, 1);
-			if (cv)
-				addrange(cv, (chr) '0', (chr) '9');
+			cv = pg_ctype_get_cache(pg_wc_isdigit);
 			break;
 		case CC_PUNCT:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_ispunct((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_ispunct);
 			break;
 		case CC_XDIGIT:
+			/*
+			 * It's not clear how to define this in non-western locales, and
+			 * even less clear that there's any particular use in trying.
+			 * So just hard-wire the meaning.
+			 */
 			cv = getcvec(v, 0, 3);
 			if (cv)
 			{
@@ -630,50 +619,20 @@ cclass(struct vars * v,			/* context */
 			}
 			break;
 		case CC_SPACE:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isspace((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isspace);
 			break;
 		case CC_LOWER:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_islower((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_islower);
 			break;
 		case CC_UPPER:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isupper((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isupper);
 			break;
 		case CC_GRAPH:
-			cv = getcvec(v, UCHAR_MAX, 0);
-			if (cv)
-			{
-				for (i = 0; i <= UCHAR_MAX; i++)
-				{
-					if (pg_wc_isgraph((chr) i))
-						addchr(cv, (chr) i);
-				}
-			}
+			cv = pg_ctype_get_cache(pg_wc_isgraph);
 			break;
 	}
+
+	/* If cv is NULL now, the reason must be "out of memory" */
 	if (cv == NULL)
 		ERR(REG_ESPACE);
 	return cv;
diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index 7c010e372858065bedbc47382a84cb3650e03efa..eac951f200065bada7be27020d10d09ab960ded1 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -1,7 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * regc_pg_locale.c
- *	  ctype functions adapted to work on pg_wchar (a/k/a chr)
+ *	  ctype functions adapted to work on pg_wchar (a/k/a chr),
+ *	  and functions to cache the results of wholesale ctype probing.
  *
  * This file is #included by regcomp.c; it's not meant to compile standalone.
  *
@@ -72,6 +73,7 @@ typedef enum
 
 static PG_Locale_Strategy pg_regex_strategy;
 static pg_locale_t pg_regex_locale;
+static Oid	pg_regex_collation;
 
 /*
  * Hard-wired character properties for C locale
@@ -233,6 +235,7 @@ pg_set_regex_collation(Oid collation)
 		/* C/POSIX collations use this path regardless of database encoding */
 		pg_regex_strategy = PG_REGEX_LOCALE_C;
 		pg_regex_locale = 0;
+		pg_regex_collation = C_COLLATION_OID;
 	}
 	else
 	{
@@ -275,6 +278,8 @@ pg_set_regex_collation(Oid collation)
 			else
 				pg_regex_strategy = PG_REGEX_LOCALE_1BYTE;
 		}
+
+		pg_regex_collation = collation;
 	}
 }
 
@@ -656,3 +661,218 @@ pg_wc_tolower(pg_wchar c)
 	}
 	return 0;					/* can't get here, but keep compiler quiet */
 }
+
+
+/*
+ * These functions cache the results of probing libc's ctype behavior for
+ * all character codes of interest in a given encoding/collation.  The
+ * result is provided as a "struct cvec", but notice that the representation
+ * is a touch different from a cvec created by regc_cvec.c: we allocate the
+ * chrs[] and ranges[] arrays separately from the struct so that we can
+ * realloc them larger at need.  This is okay since the cvecs made here
+ * should never be freed by freecvec().
+ *
+ * We use malloc not palloc since we mustn't lose control on out-of-memory;
+ * the main regex code expects us to return a failure indication instead.
+ */
+
+typedef int (*pg_wc_probefunc) (pg_wchar c);
+
+typedef struct pg_ctype_cache
+{
+	pg_wc_probefunc probefunc;		/* pg_wc_isalpha or a sibling */
+	Oid			collation;			/* collation this entry is for */
+	struct cvec cv;					/* cache entry contents */
+	struct pg_ctype_cache *next;	/* chain link */
+} pg_ctype_cache;
+
+static pg_ctype_cache *pg_ctype_cache_list = NULL;
+
+/*
+ * Add a chr or range to pcc->cv; return false if run out of memory
+ */
+static bool
+store_match(pg_ctype_cache *pcc, pg_wchar chr1, int nchrs)
+{
+	chr		   *newchrs;
+
+	if (nchrs > 1)
+	{
+		if (pcc->cv.nranges >= pcc->cv.rangespace)
+		{
+			pcc->cv.rangespace *= 2;
+			newchrs = (chr *) realloc(pcc->cv.ranges,
+									  pcc->cv.rangespace * sizeof(chr) * 2);
+			if (newchrs == NULL)
+				return false;
+			pcc->cv.ranges = newchrs;
+		}
+		pcc->cv.ranges[pcc->cv.nranges * 2] = chr1;
+		pcc->cv.ranges[pcc->cv.nranges * 2 + 1] = chr1 + nchrs - 1;
+		pcc->cv.nranges++;
+	}
+	else
+	{
+		assert(nchrs == 1);
+		if (pcc->cv.nchrs >= pcc->cv.chrspace)
+		{
+			pcc->cv.chrspace *= 2;
+			newchrs = (chr *) realloc(pcc->cv.chrs,
+									  pcc->cv.chrspace * sizeof(chr));
+			if (newchrs == NULL)
+				return false;
+			pcc->cv.chrs = newchrs;
+		}
+		pcc->cv.chrs[pcc->cv.nchrs++] = chr1;
+	}
+	return true;
+}
+
+/*
+ * Given a probe function (e.g., pg_wc_isalpha) get a struct cvec for all
+ * chrs satisfying the probe function.  The active collation is the one
+ * previously set by pg_set_regex_collation.  Return NULL if out of memory.
+ *
+ * Note that the result must not be freed or modified by caller.
+ */
+static struct cvec *
+pg_ctype_get_cache(pg_wc_probefunc probefunc)
+{
+	pg_ctype_cache *pcc;
+	pg_wchar	max_chr;
+	pg_wchar	cur_chr;
+	int			nmatches;
+	chr		   *newchrs;
+
+	/*
+	 * Do we already have the answer cached?
+	 */
+	for (pcc = pg_ctype_cache_list; pcc != NULL; pcc = pcc->next)
+	{
+		if (pcc->probefunc == probefunc &&
+			pcc->collation == pg_regex_collation)
+			return &pcc->cv;
+	}
+
+	/*
+	 * Nope, so initialize some workspace ...
+	 */
+	pcc = (pg_ctype_cache *) malloc(sizeof(pg_ctype_cache));
+	if (pcc == NULL)
+		return NULL;
+	pcc->probefunc = probefunc;
+	pcc->collation = pg_regex_collation;
+	pcc->cv.nchrs = 0;
+	pcc->cv.chrspace = 128;
+	pcc->cv.chrs = (chr *) malloc(pcc->cv.chrspace * sizeof(chr));
+	pcc->cv.nranges = 0;
+	pcc->cv.rangespace = 64;
+	pcc->cv.ranges = (chr *) malloc(pcc->cv.rangespace * sizeof(chr) * 2);
+	if (pcc->cv.chrs == NULL || pcc->cv.ranges == NULL)
+		goto out_of_memory;
+
+	/*
+	 * Decide how many character codes we ought to look through.  For C locale
+	 * there's no need to go further than 127.  Otherwise, if the encoding is
+	 * UTF8 go up to 0x7FF, which is a pretty arbitrary cutoff but we cannot
+	 * extend it as far as we'd like (say, 0xFFFF, the end of the Basic
+	 * Multilingual Plane) without creating significant performance issues due
+	 * to too many characters being fed through the colormap code.  This will
+	 * need redesign to fix reasonably, but at least for the moment we have
+	 * all common European languages covered.  Otherwise (not C, not UTF8) go
+	 * up to 255.  These limits are interrelated with restrictions discussed
+	 * at the head of this file.
+	 */
+	switch (pg_regex_strategy)
+	{
+		case PG_REGEX_LOCALE_C:
+			max_chr = (pg_wchar) 127;
+			break;
+		case PG_REGEX_LOCALE_WIDE:
+		case PG_REGEX_LOCALE_WIDE_L:
+			max_chr = (pg_wchar) 0x7FF;
+			break;
+		case PG_REGEX_LOCALE_1BYTE:
+		case PG_REGEX_LOCALE_1BYTE_L:
+			max_chr = (pg_wchar) UCHAR_MAX;
+			break;
+		default:
+			max_chr = 0;		/* can't get here, but keep compiler quiet */
+			break;
+	}
+
+	/*
+	 * And scan 'em ...
+	 */
+	nmatches = 0;				/* number of consecutive matches */
+
+	for (cur_chr = 0; cur_chr <= max_chr; cur_chr++)
+	{
+		if ((*probefunc) (cur_chr))
+			nmatches++;
+		else if (nmatches > 0)
+		{
+			if (!store_match(pcc, cur_chr - nmatches, nmatches))
+				goto out_of_memory;
+			nmatches = 0;
+		}
+	}
+
+	if (nmatches > 0)
+		if (!store_match(pcc, cur_chr - nmatches, nmatches))
+			goto out_of_memory;
+
+	/*
+	 * We might have allocated more memory than needed, if so free it
+	 */
+	if (pcc->cv.nchrs == 0)
+	{
+		free(pcc->cv.chrs);
+		pcc->cv.chrs = NULL;
+		pcc->cv.chrspace = 0;
+	}
+	else if (pcc->cv.nchrs < pcc->cv.chrspace)
+	{
+		newchrs = (chr *) realloc(pcc->cv.chrs,
+								  pcc->cv.nchrs * sizeof(chr));
+		if (newchrs == NULL)
+			goto out_of_memory;
+		pcc->cv.chrs = newchrs;
+		pcc->cv.chrspace = pcc->cv.nchrs;
+	}
+	if (pcc->cv.nranges == 0)
+	{
+		free(pcc->cv.ranges);
+		pcc->cv.ranges = NULL;
+		pcc->cv.rangespace = 0;
+	}
+	else if (pcc->cv.nranges < pcc->cv.rangespace)
+	{
+		newchrs = (chr *) realloc(pcc->cv.ranges,
+								  pcc->cv.nranges * sizeof(chr) * 2);
+		if (newchrs == NULL)
+			goto out_of_memory;
+		pcc->cv.ranges = newchrs;
+		pcc->cv.rangespace = pcc->cv.nranges;
+	}
+
+	/*
+	 * Success, link it into cache chain
+	 */
+	pcc->next = pg_ctype_cache_list;
+	pg_ctype_cache_list = pcc;
+
+	return &pcc->cv;
+
+	/*
+	 * Failure, clean up
+	 */
+out_of_memory:
+	if (pcc->cv.chrs)
+		free(pcc->cv.chrs);
+	if (pcc->cv.ranges)
+		free(pcc->cv.ranges);
+	free(pcc);
+
+	return NULL;
+}