Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/commands/collationcmds.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * collationcmds.c
4
 *    collation-related commands support code
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/commands/collationcmds.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#ifdef USE_ICU
18
#include <unicode/uloc.h>
19
#endif
20
21
#include "access/htup_details.h"
22
#include "access/table.h"
23
#include "access/xact.h"
24
#include "catalog/indexing.h"
25
#include "catalog/namespace.h"
26
#include "catalog/objectaccess.h"
27
#include "catalog/pg_collation.h"
28
#include "catalog/pg_database.h"
29
#include "catalog/pg_namespace.h"
30
#include "commands/collationcmds.h"
31
#include "commands/comment.h"
32
#include "commands/dbcommands.h"
33
#include "commands/defrem.h"
34
#include "common/string.h"
35
#include "mb/pg_wchar.h"
36
#include "miscadmin.h"
37
#include "storage/fd.h"
38
#include "utils/acl.h"
39
#include "utils/builtins.h"
40
#include "utils/lsyscache.h"
41
#include "utils/pg_locale.h"
42
#include "utils/rel.h"
43
#include "utils/syscache.h"
44
45
46
typedef struct
47
{
48
  char     *localename;   /* name of locale, as per "locale -a" */
49
  char     *alias;      /* shortened alias for same */
50
  int     enc;      /* encoding */
51
} CollAliasData;
52
53
54
/*
55
 * CREATE COLLATION
56
 */
57
ObjectAddress
58
DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_exists)
59
0
{
60
0
  char     *collName;
61
0
  Oid     collNamespace;
62
0
  AclResult aclresult;
63
0
  ListCell   *pl;
64
0
  DefElem    *fromEl = NULL;
65
0
  DefElem    *localeEl = NULL;
66
0
  DefElem    *lccollateEl = NULL;
67
0
  DefElem    *lcctypeEl = NULL;
68
0
  DefElem    *providerEl = NULL;
69
0
  DefElem    *deterministicEl = NULL;
70
0
  DefElem    *rulesEl = NULL;
71
0
  DefElem    *versionEl = NULL;
72
0
  char     *collcollate;
73
0
  char     *collctype;
74
0
  const char *colllocale;
75
0
  char     *collicurules;
76
0
  bool    collisdeterministic;
77
0
  int     collencoding;
78
0
  char    collprovider;
79
0
  char     *collversion = NULL;
80
0
  Oid     newoid;
81
0
  ObjectAddress address;
82
83
0
  collNamespace = QualifiedNameGetCreationNamespace(names, &collName);
84
85
0
  aclresult = object_aclcheck(NamespaceRelationId, collNamespace, GetUserId(), ACL_CREATE);
86
0
  if (aclresult != ACLCHECK_OK)
87
0
    aclcheck_error(aclresult, OBJECT_SCHEMA,
88
0
             get_namespace_name(collNamespace));
89
90
0
  foreach(pl, parameters)
91
0
  {
92
0
    DefElem    *defel = lfirst_node(DefElem, pl);
93
0
    DefElem   **defelp;
94
95
0
    if (strcmp(defel->defname, "from") == 0)
96
0
      defelp = &fromEl;
97
0
    else if (strcmp(defel->defname, "locale") == 0)
98
0
      defelp = &localeEl;
99
0
    else if (strcmp(defel->defname, "lc_collate") == 0)
100
0
      defelp = &lccollateEl;
101
0
    else if (strcmp(defel->defname, "lc_ctype") == 0)
102
0
      defelp = &lcctypeEl;
103
0
    else if (strcmp(defel->defname, "provider") == 0)
104
0
      defelp = &providerEl;
105
0
    else if (strcmp(defel->defname, "deterministic") == 0)
106
0
      defelp = &deterministicEl;
107
0
    else if (strcmp(defel->defname, "rules") == 0)
108
0
      defelp = &rulesEl;
109
0
    else if (strcmp(defel->defname, "version") == 0)
110
0
      defelp = &versionEl;
111
0
    else
112
0
    {
113
0
      ereport(ERROR,
114
0
          (errcode(ERRCODE_SYNTAX_ERROR),
115
0
           errmsg("collation attribute \"%s\" not recognized",
116
0
              defel->defname),
117
0
           parser_errposition(pstate, defel->location)));
118
0
      break;
119
0
    }
120
0
    if (*defelp != NULL)
121
0
      errorConflictingDefElem(defel, pstate);
122
0
    *defelp = defel;
123
0
  }
124
125
0
  if (localeEl && (lccollateEl || lcctypeEl))
126
0
    ereport(ERROR,
127
0
        errcode(ERRCODE_SYNTAX_ERROR),
128
0
        errmsg("conflicting or redundant options"),
129
0
        errdetail("LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE."));
130
131
0
  if (fromEl && list_length(parameters) != 1)
132
0
    ereport(ERROR,
133
0
        errcode(ERRCODE_SYNTAX_ERROR),
134
0
        errmsg("conflicting or redundant options"),
135
0
        errdetail("FROM cannot be specified together with any other options."));
136
137
0
  if (fromEl)
138
0
  {
139
0
    Oid     collid;
140
0
    HeapTuple tp;
141
0
    Datum   datum;
142
0
    bool    isnull;
143
144
0
    collid = get_collation_oid(defGetQualifiedName(fromEl), false);
145
0
    tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
146
0
    if (!HeapTupleIsValid(tp))
147
0
      elog(ERROR, "cache lookup failed for collation %u", collid);
148
149
0
    collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
150
0
    collisdeterministic = ((Form_pg_collation) GETSTRUCT(tp))->collisdeterministic;
151
0
    collencoding = ((Form_pg_collation) GETSTRUCT(tp))->collencoding;
152
153
0
    datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collcollate, &isnull);
154
0
    if (!isnull)
155
0
      collcollate = TextDatumGetCString(datum);
156
0
    else
157
0
      collcollate = NULL;
158
159
0
    datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collctype, &isnull);
160
0
    if (!isnull)
161
0
      collctype = TextDatumGetCString(datum);
162
0
    else
163
0
      collctype = NULL;
164
165
0
    datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_colllocale, &isnull);
166
0
    if (!isnull)
167
0
      colllocale = TextDatumGetCString(datum);
168
0
    else
169
0
      colllocale = NULL;
170
171
    /*
172
     * When the ICU locale comes from an existing collation, do not
173
     * canonicalize to a language tag.
174
     */
175
176
0
    datum = SysCacheGetAttr(COLLOID, tp, Anum_pg_collation_collicurules, &isnull);
177
0
    if (!isnull)
178
0
      collicurules = TextDatumGetCString(datum);
179
0
    else
180
0
      collicurules = NULL;
181
182
0
    ReleaseSysCache(tp);
183
184
    /*
185
     * Copying the "default" collation is not allowed because most code
186
     * checks for DEFAULT_COLLATION_OID instead of COLLPROVIDER_DEFAULT,
187
     * and so having a second collation with COLLPROVIDER_DEFAULT would
188
     * not work and potentially confuse or crash some code.  This could be
189
     * fixed with some legwork.
190
     */
191
0
    if (collprovider == COLLPROVIDER_DEFAULT)
192
0
      ereport(ERROR,
193
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
194
0
           errmsg("collation \"default\" cannot be copied")));
195
0
  }
196
0
  else
197
0
  {
198
0
    char     *collproviderstr = NULL;
199
200
0
    collcollate = NULL;
201
0
    collctype = NULL;
202
0
    colllocale = NULL;
203
0
    collicurules = NULL;
204
205
0
    if (providerEl)
206
0
      collproviderstr = defGetString(providerEl);
207
208
0
    if (deterministicEl)
209
0
      collisdeterministic = defGetBoolean(deterministicEl);
210
0
    else
211
0
      collisdeterministic = true;
212
213
0
    if (rulesEl)
214
0
      collicurules = defGetString(rulesEl);
215
216
0
    if (versionEl)
217
0
      collversion = defGetString(versionEl);
218
219
0
    if (collproviderstr)
220
0
    {
221
0
      if (pg_strcasecmp(collproviderstr, "builtin") == 0)
222
0
        collprovider = COLLPROVIDER_BUILTIN;
223
0
      else if (pg_strcasecmp(collproviderstr, "icu") == 0)
224
0
        collprovider = COLLPROVIDER_ICU;
225
0
      else if (pg_strcasecmp(collproviderstr, "libc") == 0)
226
0
        collprovider = COLLPROVIDER_LIBC;
227
0
      else
228
0
        ereport(ERROR,
229
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
230
0
             errmsg("unrecognized collation provider: %s",
231
0
                collproviderstr)));
232
0
    }
233
0
    else
234
0
      collprovider = COLLPROVIDER_LIBC;
235
236
0
    if (localeEl)
237
0
    {
238
0
      if (collprovider == COLLPROVIDER_LIBC)
239
0
      {
240
0
        collcollate = defGetString(localeEl);
241
0
        collctype = defGetString(localeEl);
242
0
      }
243
0
      else
244
0
        colllocale = defGetString(localeEl);
245
0
    }
246
247
0
    if (lccollateEl)
248
0
      collcollate = defGetString(lccollateEl);
249
250
0
    if (lcctypeEl)
251
0
      collctype = defGetString(lcctypeEl);
252
253
0
    if (collprovider == COLLPROVIDER_BUILTIN)
254
0
    {
255
0
      if (!colllocale)
256
0
        ereport(ERROR,
257
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
258
0
             errmsg("parameter \"%s\" must be specified",
259
0
                "locale")));
260
261
0
      colllocale = builtin_validate_locale(GetDatabaseEncoding(),
262
0
                         colllocale);
263
0
    }
264
0
    else if (collprovider == COLLPROVIDER_LIBC)
265
0
    {
266
0
      if (!collcollate)
267
0
        ereport(ERROR,
268
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
269
0
             errmsg("parameter \"%s\" must be specified",
270
0
                "lc_collate")));
271
272
0
      if (!collctype)
273
0
        ereport(ERROR,
274
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
275
0
             errmsg("parameter \"%s\" must be specified",
276
0
                "lc_ctype")));
277
0
    }
278
0
    else if (collprovider == COLLPROVIDER_ICU)
279
0
    {
280
0
      if (!colllocale)
281
0
        ereport(ERROR,
282
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
283
0
             errmsg("parameter \"%s\" must be specified",
284
0
                "locale")));
285
286
      /*
287
       * During binary upgrade, preserve the locale string. Otherwise,
288
       * canonicalize to a language tag.
289
       */
290
0
      if (!IsBinaryUpgrade)
291
0
      {
292
0
        char     *langtag = icu_language_tag(colllocale,
293
0
                             icu_validation_level);
294
295
0
        if (langtag && strcmp(colllocale, langtag) != 0)
296
0
        {
297
0
          ereport(NOTICE,
298
0
              (errmsg("using standard form \"%s\" for ICU locale \"%s\"",
299
0
                  langtag, colllocale)));
300
301
0
          colllocale = langtag;
302
0
        }
303
0
      }
304
305
0
      icu_validate_locale(colllocale);
306
0
    }
307
308
    /*
309
     * Nondeterministic collations are currently only supported with ICU
310
     * because that's the only case where it can actually make a
311
     * difference. So we can save writing the code for the other
312
     * providers.
313
     */
314
0
    if (!collisdeterministic && collprovider != COLLPROVIDER_ICU)
315
0
      ereport(ERROR,
316
0
          (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
317
0
           errmsg("nondeterministic collations not supported with this provider")));
318
319
0
    if (collicurules && collprovider != COLLPROVIDER_ICU)
320
0
      ereport(ERROR,
321
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
322
0
           errmsg("ICU rules cannot be specified unless locale provider is ICU")));
323
324
0
    if (collprovider == COLLPROVIDER_BUILTIN)
325
0
    {
326
0
      collencoding = builtin_locale_encoding(colllocale);
327
0
    }
328
0
    else if (collprovider == COLLPROVIDER_ICU)
329
0
    {
330
0
#ifdef USE_ICU
331
      /*
332
       * We could create ICU collations with collencoding == database
333
       * encoding, but it seems better to use -1 so that it matches the
334
       * way initdb would create ICU collations.  However, only allow
335
       * one to be created when the current database's encoding is
336
       * supported.  Otherwise the collation is useless, plus we get
337
       * surprising behaviors like not being able to drop the collation.
338
       *
339
       * Skip this test when !USE_ICU, because the error we want to
340
       * throw for that isn't thrown till later.
341
       */
342
0
      if (!is_encoding_supported_by_icu(GetDatabaseEncoding()))
343
0
        ereport(ERROR,
344
0
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
345
0
             errmsg("current database's encoding is not supported with this provider")));
346
0
#endif
347
0
      collencoding = -1;
348
0
    }
349
0
    else
350
0
    {
351
0
      collencoding = GetDatabaseEncoding();
352
0
      check_encoding_locale_matches(collencoding, collcollate, collctype);
353
0
    }
354
0
  }
355
356
0
  if (!collversion)
357
0
  {
358
0
    const char *locale;
359
360
0
    if (collprovider == COLLPROVIDER_LIBC)
361
0
      locale = collcollate;
362
0
    else
363
0
      locale = colllocale;
364
365
0
    collversion = get_collation_actual_version(collprovider, locale);
366
0
  }
367
368
0
  newoid = CollationCreate(collName,
369
0
               collNamespace,
370
0
               GetUserId(),
371
0
               collprovider,
372
0
               collisdeterministic,
373
0
               collencoding,
374
0
               collcollate,
375
0
               collctype,
376
0
               colllocale,
377
0
               collicurules,
378
0
               collversion,
379
0
               if_not_exists,
380
0
               false); /* not quiet */
381
382
0
  if (!OidIsValid(newoid))
383
0
    return InvalidObjectAddress;
384
385
  /* Check that the locales can be loaded. */
386
0
  CommandCounterIncrement();
387
0
  (void) pg_newlocale_from_collation(newoid);
388
389
0
  ObjectAddressSet(address, CollationRelationId, newoid);
390
391
0
  return address;
392
0
}
393
394
/*
395
 * Subroutine for ALTER COLLATION SET SCHEMA and RENAME
396
 *
397
 * Is there a collation with the same name of the given collation already in
398
 * the given namespace?  If so, raise an appropriate error message.
399
 */
400
void
401
IsThereCollationInNamespace(const char *collname, Oid nspOid)
402
0
{
403
  /* make sure the name doesn't already exist in new schema */
404
0
  if (SearchSysCacheExists3(COLLNAMEENCNSP,
405
0
                CStringGetDatum(collname),
406
0
                Int32GetDatum(GetDatabaseEncoding()),
407
0
                ObjectIdGetDatum(nspOid)))
408
0
    ereport(ERROR,
409
0
        (errcode(ERRCODE_DUPLICATE_OBJECT),
410
0
         errmsg("collation \"%s\" for encoding \"%s\" already exists in schema \"%s\"",
411
0
            collname, GetDatabaseEncodingName(),
412
0
            get_namespace_name(nspOid))));
413
414
  /* mustn't match an any-encoding entry, either */
415
0
  if (SearchSysCacheExists3(COLLNAMEENCNSP,
416
0
                CStringGetDatum(collname),
417
0
                Int32GetDatum(-1),
418
0
                ObjectIdGetDatum(nspOid)))
419
0
    ereport(ERROR,
420
0
        (errcode(ERRCODE_DUPLICATE_OBJECT),
421
0
         errmsg("collation \"%s\" already exists in schema \"%s\"",
422
0
            collname, get_namespace_name(nspOid))));
423
0
}
424
425
/*
426
 * ALTER COLLATION
427
 */
428
ObjectAddress
429
AlterCollation(AlterCollationStmt *stmt)
430
0
{
431
0
  Relation  rel;
432
0
  Oid     collOid;
433
0
  HeapTuple tup;
434
0
  Form_pg_collation collForm;
435
0
  Datum   datum;
436
0
  bool    isnull;
437
0
  char     *oldversion;
438
0
  char     *newversion;
439
0
  ObjectAddress address;
440
441
0
  rel = table_open(CollationRelationId, RowExclusiveLock);
442
0
  collOid = get_collation_oid(stmt->collname, false);
443
444
0
  if (collOid == DEFAULT_COLLATION_OID)
445
0
    ereport(ERROR,
446
0
        (errmsg("cannot refresh version of default collation"),
447
    /* translator: %s is an SQL command */
448
0
         errhint("Use %s instead.",
449
0
             "ALTER DATABASE ... REFRESH COLLATION VERSION")));
450
451
0
  if (!object_ownercheck(CollationRelationId, collOid, GetUserId()))
452
0
    aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_COLLATION,
453
0
             NameListToString(stmt->collname));
454
455
0
  tup = SearchSysCacheCopy1(COLLOID, ObjectIdGetDatum(collOid));
456
0
  if (!HeapTupleIsValid(tup))
457
0
    elog(ERROR, "cache lookup failed for collation %u", collOid);
458
459
0
  collForm = (Form_pg_collation) GETSTRUCT(tup);
460
0
  datum = SysCacheGetAttr(COLLOID, tup, Anum_pg_collation_collversion, &isnull);
461
0
  oldversion = isnull ? NULL : TextDatumGetCString(datum);
462
463
0
  if (collForm->collprovider == COLLPROVIDER_LIBC)
464
0
    datum = SysCacheGetAttrNotNull(COLLOID, tup, Anum_pg_collation_collcollate);
465
0
  else
466
0
    datum = SysCacheGetAttrNotNull(COLLOID, tup, Anum_pg_collation_colllocale);
467
468
0
  newversion = get_collation_actual_version(collForm->collprovider,
469
0
                        TextDatumGetCString(datum));
470
471
  /* cannot change from NULL to non-NULL or vice versa */
472
0
  if ((!oldversion && newversion) || (oldversion && !newversion))
473
0
    elog(ERROR, "invalid collation version change");
474
0
  else if (oldversion && newversion && strcmp(newversion, oldversion) != 0)
475
0
  {
476
0
    bool    nulls[Natts_pg_collation];
477
0
    bool    replaces[Natts_pg_collation];
478
0
    Datum   values[Natts_pg_collation];
479
480
0
    ereport(NOTICE,
481
0
        (errmsg("changing version from %s to %s",
482
0
            oldversion, newversion)));
483
484
0
    memset(values, 0, sizeof(values));
485
0
    memset(nulls, false, sizeof(nulls));
486
0
    memset(replaces, false, sizeof(replaces));
487
488
0
    values[Anum_pg_collation_collversion - 1] = CStringGetTextDatum(newversion);
489
0
    replaces[Anum_pg_collation_collversion - 1] = true;
490
491
0
    tup = heap_modify_tuple(tup, RelationGetDescr(rel),
492
0
                values, nulls, replaces);
493
0
  }
494
0
  else
495
0
    ereport(NOTICE,
496
0
        (errmsg("version has not changed")));
497
498
0
  CatalogTupleUpdate(rel, &tup->t_self, tup);
499
500
0
  InvokeObjectPostAlterHook(CollationRelationId, collOid, 0);
501
502
0
  ObjectAddressSet(address, CollationRelationId, collOid);
503
504
0
  heap_freetuple(tup);
505
0
  table_close(rel, NoLock);
506
507
0
  return address;
508
0
}
509
510
511
Datum
512
pg_collation_actual_version(PG_FUNCTION_ARGS)
513
0
{
514
0
  Oid     collid = PG_GETARG_OID(0);
515
0
  char    provider;
516
0
  char     *locale;
517
0
  char     *version;
518
0
  Datum   datum;
519
520
0
  if (collid == DEFAULT_COLLATION_OID)
521
0
  {
522
    /* retrieve from pg_database */
523
524
0
    HeapTuple dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
525
526
0
    if (!HeapTupleIsValid(dbtup))
527
0
      ereport(ERROR,
528
0
          (errcode(ERRCODE_UNDEFINED_OBJECT),
529
0
           errmsg("database with OID %u does not exist", MyDatabaseId)));
530
531
0
    provider = ((Form_pg_database) GETSTRUCT(dbtup))->datlocprovider;
532
533
0
    if (provider == COLLPROVIDER_LIBC)
534
0
    {
535
0
      datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_datcollate);
536
0
      locale = TextDatumGetCString(datum);
537
0
    }
538
0
    else
539
0
    {
540
0
      datum = SysCacheGetAttrNotNull(DATABASEOID, dbtup, Anum_pg_database_datlocale);
541
0
      locale = TextDatumGetCString(datum);
542
0
    }
543
544
0
    ReleaseSysCache(dbtup);
545
0
  }
546
0
  else
547
0
  {
548
    /* retrieve from pg_collation */
549
550
0
    HeapTuple colltp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
551
552
0
    if (!HeapTupleIsValid(colltp))
553
0
      ereport(ERROR,
554
0
          (errcode(ERRCODE_UNDEFINED_OBJECT),
555
0
           errmsg("collation with OID %u does not exist", collid)));
556
557
0
    provider = ((Form_pg_collation) GETSTRUCT(colltp))->collprovider;
558
0
    Assert(provider != COLLPROVIDER_DEFAULT);
559
560
0
    if (provider == COLLPROVIDER_LIBC)
561
0
    {
562
0
      datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_collcollate);
563
0
      locale = TextDatumGetCString(datum);
564
0
    }
565
0
    else
566
0
    {
567
0
      datum = SysCacheGetAttrNotNull(COLLOID, colltp, Anum_pg_collation_colllocale);
568
0
      locale = TextDatumGetCString(datum);
569
0
    }
570
571
0
    ReleaseSysCache(colltp);
572
0
  }
573
574
0
  version = get_collation_actual_version(provider, locale);
575
0
  if (version)
576
0
    PG_RETURN_TEXT_P(cstring_to_text(version));
577
0
  else
578
0
    PG_RETURN_NULL();
579
0
}
580
581
582
/* will we use "locale -a" in pg_import_system_collations? */
583
#if !defined(WIN32)
584
#define READ_LOCALE_A_OUTPUT
585
#endif
586
587
/* will we use EnumSystemLocalesEx in pg_import_system_collations? */
588
#ifdef WIN32
589
#define ENUM_SYSTEM_LOCALE
590
#endif
591
592
593
#ifdef READ_LOCALE_A_OUTPUT
594
/*
595
 * "Normalize" a libc locale name, stripping off encoding tags such as
596
 * ".utf8" (e.g., "en_US.utf8" -> "en_US", but "br_FR.iso885915@euro"
597
 * -> "br_FR@euro").  Return true if a new, different name was
598
 * generated.
599
 */
600
static bool
601
normalize_libc_locale_name(char *new, const char *old)
602
0
{
603
0
  char     *n = new;
604
0
  const char *o = old;
605
0
  bool    changed = false;
606
607
0
  while (*o)
608
0
  {
609
0
    if (*o == '.')
610
0
    {
611
      /* skip over encoding tag such as ".utf8" or ".UTF-8" */
612
0
      o++;
613
0
      while ((*o >= 'A' && *o <= 'Z')
614
0
           || (*o >= 'a' && *o <= 'z')
615
0
           || (*o >= '0' && *o <= '9')
616
0
           || (*o == '-'))
617
0
        o++;
618
0
      changed = true;
619
0
    }
620
0
    else
621
0
      *n++ = *o++;
622
0
  }
623
0
  *n = '\0';
624
625
0
  return changed;
626
0
}
627
628
/*
629
 * qsort comparator for CollAliasData items
630
 */
631
static int
632
cmpaliases(const void *a, const void *b)
633
0
{
634
0
  const CollAliasData *ca = (const CollAliasData *) a;
635
0
  const CollAliasData *cb = (const CollAliasData *) b;
636
637
  /* comparing localename is enough because other fields are derived */
638
0
  return strcmp(ca->localename, cb->localename);
639
0
}
640
#endif              /* READ_LOCALE_A_OUTPUT */
641
642
643
#ifdef USE_ICU
644
/*
645
 * Get a comment (specifically, the display name) for an ICU locale.
646
 * The result is a palloc'd string, or NULL if we can't get a comment
647
 * or find that it's not all ASCII.  (We can *not* accept non-ASCII
648
 * comments, because the contents of template0 must be encoding-agnostic.)
649
 */
650
static char *
651
get_icu_locale_comment(const char *localename)
652
0
{
653
0
  UErrorCode  status;
654
0
  UChar   displayname[128];
655
0
  int32   len_uchar;
656
0
  int32   i;
657
0
  char     *result;
658
659
0
  status = U_ZERO_ERROR;
660
0
  len_uchar = uloc_getDisplayName(localename, "en",
661
0
                  displayname, lengthof(displayname),
662
0
                  &status);
663
0
  if (U_FAILURE(status))
664
0
    return NULL;     /* no good reason to raise an error */
665
666
  /* Check for non-ASCII comment (can't use pg_is_ascii for this) */
667
0
  for (i = 0; i < len_uchar; i++)
668
0
  {
669
0
    if (displayname[i] > 127)
670
0
      return NULL;
671
0
  }
672
673
  /* OK, transcribe */
674
0
  result = palloc(len_uchar + 1);
675
0
  for (i = 0; i < len_uchar; i++)
676
0
    result[i] = displayname[i];
677
0
  result[len_uchar] = '\0';
678
679
0
  return result;
680
0
}
681
#endif              /* USE_ICU */
682
683
684
/*
685
 * Create a new collation using the input locale 'locale'. (subroutine for
686
 * pg_import_system_collations())
687
 *
688
 * 'nspid' is the namespace id where the collation will be created.
689
 *
690
 * 'nvalidp' is incremented if the locale has a valid encoding.
691
 *
692
 * 'ncreatedp' is incremented if the collation is actually created.  If the
693
 * collation already exists it will quietly do nothing.
694
 *
695
 * The returned value is the encoding of the locale, -1 if the locale is not
696
 * valid for creating a collation.
697
 *
698
 */
699
pg_attribute_unused()
700
static int
701
create_collation_from_locale(const char *locale, int nspid,
702
               int *nvalidp, int *ncreatedp)
703
0
{
704
0
  int     enc;
705
0
  Oid     collid;
706
707
  /*
708
   * Some systems have locale names that don't consist entirely of ASCII
709
   * letters (such as "bokm&aring;l" or "fran&ccedil;ais"). This is pretty
710
   * silly, since we need the locale itself to interpret the non-ASCII
711
   * characters. We can't do much with those, so we filter them out.
712
   */
713
0
  if (!pg_is_ascii(locale))
714
0
  {
715
0
    elog(DEBUG1, "skipping locale with non-ASCII name: \"%s\"", locale);
716
0
    return -1;
717
0
  }
718
719
0
  enc = pg_get_encoding_from_locale(locale, false);
720
0
  if (enc < 0)
721
0
  {
722
0
    elog(DEBUG1, "skipping locale with unrecognized encoding: \"%s\"", locale);
723
0
    return -1;
724
0
  }
725
0
  if (!PG_VALID_BE_ENCODING(enc))
726
0
  {
727
0
    elog(DEBUG1, "skipping locale with client-only encoding: \"%s\"", locale);
728
0
    return -1;
729
0
  }
730
0
  if (enc == PG_SQL_ASCII)
731
0
    return -1;       /* C/POSIX are already in the catalog */
732
733
  /* count valid locales found in operating system */
734
0
  (*nvalidp)++;
735
736
  /*
737
   * Create a collation named the same as the locale, but quietly doing
738
   * nothing if it already exists.  This is the behavior we need even at
739
   * initdb time, because some versions of "locale -a" can report the same
740
   * locale name more than once.  And it's convenient for later import runs,
741
   * too, since you just about always want to add on new locales without a
742
   * lot of chatter about existing ones.
743
   */
744
0
  collid = CollationCreate(locale, nspid, GetUserId(),
745
0
               COLLPROVIDER_LIBC, true, enc,
746
0
               locale, locale, NULL, NULL,
747
0
               get_collation_actual_version(COLLPROVIDER_LIBC, locale),
748
0
               true, true);
749
0
  if (OidIsValid(collid))
750
0
  {
751
0
    (*ncreatedp)++;
752
753
    /* Must do CCI between inserts to handle duplicates correctly */
754
0
    CommandCounterIncrement();
755
0
  }
756
757
0
  return enc;
758
0
}
759
760
761
#ifdef ENUM_SYSTEM_LOCALE
762
/* parameter to be passed to the callback function win32_read_locale() */
763
typedef struct
764
{
765
  Oid     nspid;
766
  int      *ncreatedp;
767
  int      *nvalidp;
768
} CollParam;
769
770
/*
771
 * Callback function for EnumSystemLocalesEx() in
772
 * pg_import_system_collations().  Creates a collation for every valid locale
773
 * and a POSIX alias collation.
774
 *
775
 * The callback contract is to return TRUE to continue enumerating and FALSE
776
 * to stop enumerating.  We always want to continue.
777
 */
778
static BOOL CALLBACK
779
win32_read_locale(LPWSTR pStr, DWORD dwFlags, LPARAM lparam)
780
{
781
  CollParam  *param = (CollParam *) lparam;
782
  char    localebuf[NAMEDATALEN];
783
  int     result;
784
  int     enc;
785
786
  (void) dwFlags;
787
788
  result = WideCharToMultiByte(CP_ACP, 0, pStr, -1, localebuf, NAMEDATALEN,
789
                 NULL, NULL);
790
791
  if (result == 0)
792
  {
793
    if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
794
      elog(DEBUG1, "skipping locale with too-long name: \"%s\"", localebuf);
795
    return TRUE;
796
  }
797
  if (localebuf[0] == '\0')
798
    return TRUE;
799
800
  enc = create_collation_from_locale(localebuf, param->nspid,
801
                     param->nvalidp, param->ncreatedp);
802
  if (enc < 0)
803
    return TRUE;
804
805
  /*
806
   * Windows will use hyphens between language and territory, where POSIX
807
   * uses an underscore. Simply create a POSIX alias.
808
   */
809
  if (strchr(localebuf, '-'))
810
  {
811
    char    alias[NAMEDATALEN];
812
    Oid     collid;
813
814
    strcpy(alias, localebuf);
815
    for (char *p = alias; *p; p++)
816
      if (*p == '-')
817
        *p = '_';
818
819
    collid = CollationCreate(alias, param->nspid, GetUserId(),
820
                 COLLPROVIDER_LIBC, true, enc,
821
                 localebuf, localebuf, NULL, NULL,
822
                 get_collation_actual_version(COLLPROVIDER_LIBC, localebuf),
823
                 true, true);
824
    if (OidIsValid(collid))
825
    {
826
      (*param->ncreatedp)++;
827
828
      CommandCounterIncrement();
829
    }
830
  }
831
832
  return TRUE;
833
}
834
#endif              /* ENUM_SYSTEM_LOCALE */
835
836
837
/*
838
 * pg_import_system_collations: add known system collations to pg_collation
839
 */
840
Datum
841
pg_import_system_collations(PG_FUNCTION_ARGS)
842
0
{
843
0
  Oid     nspid = PG_GETARG_OID(0);
844
0
  int     ncreated = 0;
845
846
0
  if (!superuser())
847
0
    ereport(ERROR,
848
0
        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
849
0
         errmsg("must be superuser to import system collations")));
850
851
0
  if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(nspid)))
852
0
    ereport(ERROR,
853
0
        (errcode(ERRCODE_UNDEFINED_SCHEMA),
854
0
         errmsg("schema with OID %u does not exist", nspid)));
855
856
  /* Load collations known to libc, using "locale -a" to enumerate them */
857
0
#ifdef READ_LOCALE_A_OUTPUT
858
0
  {
859
0
    FILE     *locale_a_handle;
860
0
    char    localebuf[LOCALE_NAME_BUFLEN];
861
0
    int     nvalid = 0;
862
0
    Oid     collid;
863
0
    CollAliasData *aliases;
864
0
    int     naliases,
865
0
          maxaliases,
866
0
          i;
867
868
    /* expansible array of aliases */
869
0
    maxaliases = 100;
870
0
    aliases = palloc_array(CollAliasData, maxaliases);
871
0
    naliases = 0;
872
873
0
    locale_a_handle = OpenPipeStream("locale -a", "r");
874
0
    if (locale_a_handle == NULL)
875
0
      ereport(ERROR,
876
0
          (errcode_for_file_access(),
877
0
           errmsg("could not execute command \"%s\": %m",
878
0
              "locale -a")));
879
880
0
    while (fgets(localebuf, sizeof(localebuf), locale_a_handle))
881
0
    {
882
0
      size_t    len;
883
0
      int     enc;
884
0
      char    alias[LOCALE_NAME_BUFLEN];
885
886
0
      len = strlen(localebuf);
887
888
0
      if (len == 0 || localebuf[len - 1] != '\n')
889
0
      {
890
0
        elog(DEBUG1, "skipping locale with too-long name: \"%s\"", localebuf);
891
0
        continue;
892
0
      }
893
0
      localebuf[len - 1] = '\0';
894
895
0
      enc = create_collation_from_locale(localebuf, nspid, &nvalid, &ncreated);
896
0
      if (enc < 0)
897
0
        continue;
898
899
      /*
900
       * Generate aliases such as "en_US" in addition to "en_US.utf8"
901
       * for ease of use.  Note that collation names are unique per
902
       * encoding only, so this doesn't clash with "en_US" for LATIN1,
903
       * say.
904
       *
905
       * However, it might conflict with a name we'll see later in the
906
       * "locale -a" output.  So save up the aliases and try to add them
907
       * after we've read all the output.
908
       */
909
0
      if (normalize_libc_locale_name(alias, localebuf))
910
0
      {
911
0
        if (naliases >= maxaliases)
912
0
        {
913
0
          maxaliases *= 2;
914
0
          aliases = repalloc_array(aliases, CollAliasData, maxaliases);
915
0
        }
916
0
        aliases[naliases].localename = pstrdup(localebuf);
917
0
        aliases[naliases].alias = pstrdup(alias);
918
0
        aliases[naliases].enc = enc;
919
0
        naliases++;
920
0
      }
921
0
    }
922
923
    /*
924
     * We don't check the return value of this, because we want to support
925
     * the case where there "locale" command does not exist.  (This is
926
     * unusual but can happen on minimalized Linux distributions, for
927
     * example.)  We will warn below if no locales could be found.
928
     */
929
0
    ClosePipeStream(locale_a_handle);
930
931
    /*
932
     * Before processing the aliases, sort them by locale name.  The point
933
     * here is that if "locale -a" gives us multiple locale names with the
934
     * same encoding and base name, say "en_US.utf8" and "en_US.utf-8", we
935
     * want to pick a deterministic one of them.  First in ASCII sort
936
     * order is a good enough rule.  (Before PG 10, the code corresponding
937
     * to this logic in initdb.c had an additional ordering rule, to
938
     * prefer the locale name exactly matching the alias, if any.  We
939
     * don't need to consider that here, because we would have already
940
     * created such a pg_collation entry above, and that one will win.)
941
     */
942
0
    if (naliases > 1)
943
0
      qsort(aliases, naliases, sizeof(CollAliasData), cmpaliases);
944
945
    /* Now add aliases, ignoring any that match pre-existing entries */
946
0
    for (i = 0; i < naliases; i++)
947
0
    {
948
0
      char     *locale = aliases[i].localename;
949
0
      char     *alias = aliases[i].alias;
950
0
      int     enc = aliases[i].enc;
951
952
0
      collid = CollationCreate(alias, nspid, GetUserId(),
953
0
                   COLLPROVIDER_LIBC, true, enc,
954
0
                   locale, locale, NULL, NULL,
955
0
                   get_collation_actual_version(COLLPROVIDER_LIBC, locale),
956
0
                   true, true);
957
0
      if (OidIsValid(collid))
958
0
      {
959
0
        ncreated++;
960
961
0
        CommandCounterIncrement();
962
0
      }
963
0
    }
964
965
    /* Give a warning if "locale -a" seems to be malfunctioning */
966
0
    if (nvalid == 0)
967
0
      ereport(WARNING,
968
0
          (errmsg("no usable system locales were found")));
969
0
  }
970
0
#endif              /* READ_LOCALE_A_OUTPUT */
971
972
  /*
973
   * Load collations known to ICU
974
   *
975
   * We use uloc_countAvailable()/uloc_getAvailable() rather than
976
   * ucol_countAvailable()/ucol_getAvailable().  The former returns a full
977
   * set of language+region combinations, whereas the latter only returns
978
   * language+region combinations if they are distinct from the language's
979
   * base collation.  So there might not be a de-DE or en-GB, which would be
980
   * confusing.
981
   */
982
0
#ifdef USE_ICU
983
0
  {
984
0
    int     i;
985
986
    /*
987
     * Start the loop at -1 to sneak in the root locale without too much
988
     * code duplication.
989
     */
990
0
    for (i = -1; i < uloc_countAvailable(); i++)
991
0
    {
992
0
      const char *name;
993
0
      char     *langtag;
994
0
      char     *icucomment;
995
0
      Oid     collid;
996
997
0
      if (i == -1)
998
0
        name = "";   /* ICU root locale */
999
0
      else
1000
0
        name = uloc_getAvailable(i);
1001
1002
0
      langtag = icu_language_tag(name, ERROR);
1003
1004
      /*
1005
       * Be paranoid about not allowing any non-ASCII strings into
1006
       * pg_collation
1007
       */
1008
0
      if (!pg_is_ascii(langtag))
1009
0
        continue;
1010
1011
0
      collid = CollationCreate(psprintf("%s-x-icu", langtag),
1012
0
                   nspid, GetUserId(),
1013
0
                   COLLPROVIDER_ICU, true, -1,
1014
0
                   NULL, NULL, langtag, NULL,
1015
0
                   get_collation_actual_version(COLLPROVIDER_ICU, langtag),
1016
0
                   true, true);
1017
0
      if (OidIsValid(collid))
1018
0
      {
1019
0
        ncreated++;
1020
1021
0
        CommandCounterIncrement();
1022
1023
0
        icucomment = get_icu_locale_comment(name);
1024
0
        if (icucomment)
1025
0
          CreateComments(collid, CollationRelationId, 0,
1026
0
                   icucomment);
1027
0
      }
1028
0
    }
1029
0
  }
1030
0
#endif              /* USE_ICU */
1031
1032
  /* Load collations known to WIN32 */
1033
#ifdef ENUM_SYSTEM_LOCALE
1034
  {
1035
    int     nvalid = 0;
1036
    CollParam param;
1037
1038
    param.nspid = nspid;
1039
    param.ncreatedp = &ncreated;
1040
    param.nvalidp = &nvalid;
1041
1042
    /*
1043
     * Enumerate the locales that are either installed on or supported by
1044
     * the OS.
1045
     */
1046
    if (!EnumSystemLocalesEx(win32_read_locale, LOCALE_ALL,
1047
                 (LPARAM) &param, NULL))
1048
      _dosmaperr(GetLastError());
1049
1050
    /* Give a warning if EnumSystemLocalesEx seems to be malfunctioning */
1051
    if (nvalid == 0)
1052
      ereport(WARNING,
1053
          (errmsg("no usable system locales were found")));
1054
  }
1055
#endif              /* ENUM_SYSTEM_LOCALE */
1056
1057
0
  PG_RETURN_INT32(ncreated);
1058
0
}