1 /*------------------------------------------------------------------------- 2 * 3 * tsearchcmds.c 4 * 5 * Routines for tsearch manipulation commands 6 * 7 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group 8 * Portions Copyright (c) 1994, Regents of the University of California 9 * 10 * 11 * IDENTIFICATION 12 * src/backend/commands/tsearchcmds.c 13 * 14 *------------------------------------------------------------------------- 15 */ 16 #include "postgres.h" 17 18 #include <ctype.h> 19 20 #include "access/genam.h" 21 #include "access/htup_details.h" 22 #include "access/table.h" 23 #include "access/xact.h" 24 #include "catalog/catalog.h" 25 #include "catalog/dependency.h" 26 #include "catalog/indexing.h" 27 #include "catalog/objectaccess.h" 28 #include "catalog/pg_namespace.h" 29 #include "catalog/pg_proc.h" 30 #include "catalog/pg_ts_config.h" 31 #include "catalog/pg_ts_config_map.h" 32 #include "catalog/pg_ts_dict.h" 33 #include "catalog/pg_ts_parser.h" 34 #include "catalog/pg_ts_template.h" 35 #include "catalog/pg_type.h" 36 #include "commands/alter.h" 37 #include "commands/defrem.h" 38 #include "commands/event_trigger.h" 39 #include "common/string.h" 40 #include "miscadmin.h" 41 #include "nodes/makefuncs.h" 42 #include "parser/parse_func.h" 43 #include "tsearch/ts_cache.h" 44 #include "tsearch/ts_utils.h" 45 #include "utils/builtins.h" 46 #include "utils/fmgroids.h" 47 #include "utils/lsyscache.h" 48 #include "utils/rel.h" 49 #include "utils/syscache.h" 50 51 52 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, 53 HeapTuple tup, Relation relMap); 54 static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt, 55 HeapTuple tup, Relation relMap); 56 static DefElem *buildDefItem(const char *name, const char *val, 57 bool was_quoted); 58 59 60 /* --------------------- TS Parser commands ------------------------ */ 61 62 /* 63 * lookup a parser support function and return its OID (as a Datum) 64 * 65 * attnum is the pg_ts_parser column the function will go into 66 */ 67 static Datum 68 get_ts_parser_func(DefElem *defel, int attnum) 69 { 70 List *funcName = defGetQualifiedName(defel); 71 Oid typeId[3]; 72 Oid retTypeId; 73 int nargs; 74 Oid procOid; 75 76 retTypeId = INTERNALOID; /* correct for most */ 77 typeId[0] = INTERNALOID; 78 switch (attnum) 79 { 80 case Anum_pg_ts_parser_prsstart: 81 nargs = 2; 82 typeId[1] = INT4OID; 83 break; 84 case Anum_pg_ts_parser_prstoken: 85 nargs = 3; 86 typeId[1] = INTERNALOID; 87 typeId[2] = INTERNALOID; 88 break; 89 case Anum_pg_ts_parser_prsend: 90 nargs = 1; 91 retTypeId = VOIDOID; 92 break; 93 case Anum_pg_ts_parser_prsheadline: 94 nargs = 3; 95 typeId[1] = INTERNALOID; 96 typeId[2] = TSQUERYOID; 97 break; 98 case Anum_pg_ts_parser_prslextype: 99 nargs = 1; 100 101 /* 102 * Note: because the lextype method returns type internal, it must 103 * have an internal-type argument for security reasons. The 104 * argument is not actually used, but is just passed as a zero. 105 */ 106 break; 107 default: 108 /* should not be here */ 109 elog(ERROR, "unrecognized attribute for text search parser: %d", 110 attnum); 111 nargs = 0; /* keep compiler quiet */ 112 } 113 114 procOid = LookupFuncName(funcName, nargs, typeId, false); 115 if (get_func_rettype(procOid) != retTypeId) 116 ereport(ERROR, 117 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 118 errmsg("function %s should return type %s", 119 func_signature_string(funcName, nargs, NIL, typeId), 120 format_type_be(retTypeId)))); 121 122 return ObjectIdGetDatum(procOid); 123 } 124 125 /* 126 * make pg_depend entries for a new pg_ts_parser entry 127 * 128 * Return value is the address of said new entry. 129 */ 130 static ObjectAddress 131 makeParserDependencies(HeapTuple tuple) 132 { 133 Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple); 134 ObjectAddress myself, 135 referenced; 136 137 myself.classId = TSParserRelationId; 138 myself.objectId = prs->oid; 139 myself.objectSubId = 0; 140 141 /* dependency on namespace */ 142 referenced.classId = NamespaceRelationId; 143 referenced.objectId = prs->prsnamespace; 144 referenced.objectSubId = 0; 145 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 146 147 /* dependency on extension */ 148 recordDependencyOnCurrentExtension(&myself, false); 149 150 /* dependencies on functions */ 151 referenced.classId = ProcedureRelationId; 152 referenced.objectSubId = 0; 153 154 referenced.objectId = prs->prsstart; 155 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 156 157 referenced.objectId = prs->prstoken; 158 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 159 160 referenced.objectId = prs->prsend; 161 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 162 163 referenced.objectId = prs->prslextype; 164 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 165 166 if (OidIsValid(prs->prsheadline)) 167 { 168 referenced.objectId = prs->prsheadline; 169 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 170 } 171 172 return myself; 173 } 174 175 /* 176 * CREATE TEXT SEARCH PARSER 177 */ 178 ObjectAddress 179 DefineTSParser(List *names, List *parameters) 180 { 181 char *prsname; 182 ListCell *pl; 183 Relation prsRel; 184 HeapTuple tup; 185 Datum values[Natts_pg_ts_parser]; 186 bool nulls[Natts_pg_ts_parser]; 187 NameData pname; 188 Oid prsOid; 189 Oid namespaceoid; 190 ObjectAddress address; 191 192 if (!superuser()) 193 ereport(ERROR, 194 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 195 errmsg("must be superuser to create text search parsers"))); 196 197 prsRel = table_open(TSParserRelationId, RowExclusiveLock); 198 199 /* Convert list of names to a name and namespace */ 200 namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname); 201 202 /* initialize tuple fields with name/namespace */ 203 memset(values, 0, sizeof(values)); 204 memset(nulls, false, sizeof(nulls)); 205 206 prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId, 207 Anum_pg_ts_parser_oid); 208 values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid); 209 namestrcpy(&pname, prsname); 210 values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname); 211 values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid); 212 213 /* 214 * loop over the definition list and extract the information we need. 215 */ 216 foreach(pl, parameters) 217 { 218 DefElem *defel = (DefElem *) lfirst(pl); 219 220 if (strcmp(defel->defname, "start") == 0) 221 { 222 values[Anum_pg_ts_parser_prsstart - 1] = 223 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart); 224 } 225 else if (strcmp(defel->defname, "gettoken") == 0) 226 { 227 values[Anum_pg_ts_parser_prstoken - 1] = 228 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken); 229 } 230 else if (strcmp(defel->defname, "end") == 0) 231 { 232 values[Anum_pg_ts_parser_prsend - 1] = 233 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend); 234 } 235 else if (strcmp(defel->defname, "headline") == 0) 236 { 237 values[Anum_pg_ts_parser_prsheadline - 1] = 238 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline); 239 } 240 else if (strcmp(defel->defname, "lextypes") == 0) 241 { 242 values[Anum_pg_ts_parser_prslextype - 1] = 243 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype); 244 } 245 else 246 ereport(ERROR, 247 (errcode(ERRCODE_SYNTAX_ERROR), 248 errmsg("text search parser parameter \"%s\" not recognized", 249 defel->defname))); 250 } 251 252 /* 253 * Validation 254 */ 255 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1]))) 256 ereport(ERROR, 257 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 258 errmsg("text search parser start method is required"))); 259 260 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1]))) 261 ereport(ERROR, 262 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 263 errmsg("text search parser gettoken method is required"))); 264 265 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1]))) 266 ereport(ERROR, 267 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 268 errmsg("text search parser end method is required"))); 269 270 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1]))) 271 ereport(ERROR, 272 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 273 errmsg("text search parser lextypes method is required"))); 274 275 /* 276 * Looks good, insert 277 */ 278 tup = heap_form_tuple(prsRel->rd_att, values, nulls); 279 280 CatalogTupleInsert(prsRel, tup); 281 282 address = makeParserDependencies(tup); 283 284 /* Post creation hook for new text search parser */ 285 InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0); 286 287 heap_freetuple(tup); 288 289 table_close(prsRel, RowExclusiveLock); 290 291 return address; 292 } 293 294 /* 295 * Guts of TS parser deletion. 296 */ 297 void 298 RemoveTSParserById(Oid prsId) 299 { 300 Relation relation; 301 HeapTuple tup; 302 303 relation = table_open(TSParserRelationId, RowExclusiveLock); 304 305 tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId)); 306 307 if (!HeapTupleIsValid(tup)) 308 elog(ERROR, "cache lookup failed for text search parser %u", prsId); 309 310 CatalogTupleDelete(relation, &tup->t_self); 311 312 ReleaseSysCache(tup); 313 314 table_close(relation, RowExclusiveLock); 315 } 316 317 /* ---------------------- TS Dictionary commands -----------------------*/ 318 319 /* 320 * make pg_depend entries for a new pg_ts_dict entry 321 * 322 * Return value is address of the new entry 323 */ 324 static ObjectAddress 325 makeDictionaryDependencies(HeapTuple tuple) 326 { 327 Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple); 328 ObjectAddress myself, 329 referenced; 330 331 myself.classId = TSDictionaryRelationId; 332 myself.objectId = dict->oid; 333 myself.objectSubId = 0; 334 335 /* dependency on namespace */ 336 referenced.classId = NamespaceRelationId; 337 referenced.objectId = dict->dictnamespace; 338 referenced.objectSubId = 0; 339 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 340 341 /* dependency on owner */ 342 recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner); 343 344 /* dependency on extension */ 345 recordDependencyOnCurrentExtension(&myself, false); 346 347 /* dependency on template */ 348 referenced.classId = TSTemplateRelationId; 349 referenced.objectId = dict->dicttemplate; 350 referenced.objectSubId = 0; 351 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 352 353 return myself; 354 } 355 356 /* 357 * verify that a template's init method accepts a proposed option list 358 */ 359 static void 360 verify_dictoptions(Oid tmplId, List *dictoptions) 361 { 362 HeapTuple tup; 363 Form_pg_ts_template tform; 364 Oid initmethod; 365 366 /* 367 * Suppress this test when running in a standalone backend. This is a 368 * hack to allow initdb to create prefab dictionaries that might not 369 * actually be usable in template1's encoding (due to using external files 370 * that can't be translated into template1's encoding). We want to create 371 * them anyway, since they might be usable later in other databases. 372 */ 373 if (!IsUnderPostmaster) 374 return; 375 376 tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId)); 377 if (!HeapTupleIsValid(tup)) /* should not happen */ 378 elog(ERROR, "cache lookup failed for text search template %u", 379 tmplId); 380 tform = (Form_pg_ts_template) GETSTRUCT(tup); 381 382 initmethod = tform->tmplinit; 383 384 if (!OidIsValid(initmethod)) 385 { 386 /* If there is no init method, disallow any options */ 387 if (dictoptions) 388 ereport(ERROR, 389 (errcode(ERRCODE_SYNTAX_ERROR), 390 errmsg("text search template \"%s\" does not accept options", 391 NameStr(tform->tmplname)))); 392 } 393 else 394 { 395 /* 396 * Copy the options just in case init method thinks it can scribble on 397 * them ... 398 */ 399 dictoptions = copyObject(dictoptions); 400 401 /* 402 * Call the init method and see if it complains. We don't worry about 403 * it leaking memory, since our command will soon be over anyway. 404 */ 405 (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions)); 406 } 407 408 ReleaseSysCache(tup); 409 } 410 411 /* 412 * CREATE TEXT SEARCH DICTIONARY 413 */ 414 ObjectAddress 415 DefineTSDictionary(List *names, List *parameters) 416 { 417 ListCell *pl; 418 Relation dictRel; 419 HeapTuple tup; 420 Datum values[Natts_pg_ts_dict]; 421 bool nulls[Natts_pg_ts_dict]; 422 NameData dname; 423 Oid templId = InvalidOid; 424 List *dictoptions = NIL; 425 Oid dictOid; 426 Oid namespaceoid; 427 AclResult aclresult; 428 char *dictname; 429 ObjectAddress address; 430 431 /* Convert list of names to a name and namespace */ 432 namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname); 433 434 /* Check we have creation rights in target namespace */ 435 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE); 436 if (aclresult != ACLCHECK_OK) 437 aclcheck_error(aclresult, OBJECT_SCHEMA, 438 get_namespace_name(namespaceoid)); 439 440 /* 441 * loop over the definition list and extract the information we need. 442 */ 443 foreach(pl, parameters) 444 { 445 DefElem *defel = (DefElem *) lfirst(pl); 446 447 if (strcmp(defel->defname, "template") == 0) 448 { 449 templId = get_ts_template_oid(defGetQualifiedName(defel), false); 450 } 451 else 452 { 453 /* Assume it's an option for the dictionary itself */ 454 dictoptions = lappend(dictoptions, defel); 455 } 456 } 457 458 /* 459 * Validation 460 */ 461 if (!OidIsValid(templId)) 462 ereport(ERROR, 463 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 464 errmsg("text search template is required"))); 465 466 verify_dictoptions(templId, dictoptions); 467 468 469 dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock); 470 471 /* 472 * Looks good, insert 473 */ 474 memset(values, 0, sizeof(values)); 475 memset(nulls, false, sizeof(nulls)); 476 477 dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId, 478 Anum_pg_ts_dict_oid); 479 values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid); 480 namestrcpy(&dname, dictname); 481 values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname); 482 values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid); 483 values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId()); 484 values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId); 485 if (dictoptions) 486 values[Anum_pg_ts_dict_dictinitoption - 1] = 487 PointerGetDatum(serialize_deflist(dictoptions)); 488 else 489 nulls[Anum_pg_ts_dict_dictinitoption - 1] = true; 490 491 tup = heap_form_tuple(dictRel->rd_att, values, nulls); 492 493 CatalogTupleInsert(dictRel, tup); 494 495 address = makeDictionaryDependencies(tup); 496 497 /* Post creation hook for new text search dictionary */ 498 InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0); 499 500 heap_freetuple(tup); 501 502 table_close(dictRel, RowExclusiveLock); 503 504 return address; 505 } 506 507 /* 508 * Guts of TS dictionary deletion. 509 */ 510 void 511 RemoveTSDictionaryById(Oid dictId) 512 { 513 Relation relation; 514 HeapTuple tup; 515 516 relation = table_open(TSDictionaryRelationId, RowExclusiveLock); 517 518 tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId)); 519 520 if (!HeapTupleIsValid(tup)) 521 elog(ERROR, "cache lookup failed for text search dictionary %u", 522 dictId); 523 524 CatalogTupleDelete(relation, &tup->t_self); 525 526 ReleaseSysCache(tup); 527 528 table_close(relation, RowExclusiveLock); 529 } 530 531 /* 532 * ALTER TEXT SEARCH DICTIONARY 533 */ 534 ObjectAddress 535 AlterTSDictionary(AlterTSDictionaryStmt *stmt) 536 { 537 HeapTuple tup, 538 newtup; 539 Relation rel; 540 Oid dictId; 541 ListCell *pl; 542 List *dictoptions; 543 Datum opt; 544 bool isnull; 545 Datum repl_val[Natts_pg_ts_dict]; 546 bool repl_null[Natts_pg_ts_dict]; 547 bool repl_repl[Natts_pg_ts_dict]; 548 ObjectAddress address; 549 550 dictId = get_ts_dict_oid(stmt->dictname, false); 551 552 rel = table_open(TSDictionaryRelationId, RowExclusiveLock); 553 554 tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId)); 555 556 if (!HeapTupleIsValid(tup)) 557 elog(ERROR, "cache lookup failed for text search dictionary %u", 558 dictId); 559 560 /* must be owner */ 561 if (!pg_ts_dict_ownercheck(dictId, GetUserId())) 562 aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSDICTIONARY, 563 NameListToString(stmt->dictname)); 564 565 /* deserialize the existing set of options */ 566 opt = SysCacheGetAttr(TSDICTOID, tup, 567 Anum_pg_ts_dict_dictinitoption, 568 &isnull); 569 if (isnull) 570 dictoptions = NIL; 571 else 572 dictoptions = deserialize_deflist(opt); 573 574 /* 575 * Modify the options list as per specified changes 576 */ 577 foreach(pl, stmt->options) 578 { 579 DefElem *defel = (DefElem *) lfirst(pl); 580 ListCell *cell; 581 582 /* 583 * Remove any matches ... 584 */ 585 foreach(cell, dictoptions) 586 { 587 DefElem *oldel = (DefElem *) lfirst(cell); 588 589 if (strcmp(oldel->defname, defel->defname) == 0) 590 dictoptions = foreach_delete_current(dictoptions, cell); 591 } 592 593 /* 594 * and add new value if it's got one 595 */ 596 if (defel->arg) 597 dictoptions = lappend(dictoptions, defel); 598 } 599 600 /* 601 * Validate 602 */ 603 verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate, 604 dictoptions); 605 606 /* 607 * Looks good, update 608 */ 609 memset(repl_val, 0, sizeof(repl_val)); 610 memset(repl_null, false, sizeof(repl_null)); 611 memset(repl_repl, false, sizeof(repl_repl)); 612 613 if (dictoptions) 614 repl_val[Anum_pg_ts_dict_dictinitoption - 1] = 615 PointerGetDatum(serialize_deflist(dictoptions)); 616 else 617 repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true; 618 repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true; 619 620 newtup = heap_modify_tuple(tup, RelationGetDescr(rel), 621 repl_val, repl_null, repl_repl); 622 623 CatalogTupleUpdate(rel, &newtup->t_self, newtup); 624 625 InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0); 626 627 ObjectAddressSet(address, TSDictionaryRelationId, dictId); 628 629 /* 630 * NOTE: because we only support altering the options, not the template, 631 * there is no need to update dependencies. This might have to change if 632 * the options ever reference inside-the-database objects. 633 */ 634 635 heap_freetuple(newtup); 636 ReleaseSysCache(tup); 637 638 table_close(rel, RowExclusiveLock); 639 640 return address; 641 } 642 643 /* ---------------------- TS Template commands -----------------------*/ 644 645 /* 646 * lookup a template support function and return its OID (as a Datum) 647 * 648 * attnum is the pg_ts_template column the function will go into 649 */ 650 static Datum 651 get_ts_template_func(DefElem *defel, int attnum) 652 { 653 List *funcName = defGetQualifiedName(defel); 654 Oid typeId[4]; 655 Oid retTypeId; 656 int nargs; 657 Oid procOid; 658 659 retTypeId = INTERNALOID; 660 typeId[0] = INTERNALOID; 661 typeId[1] = INTERNALOID; 662 typeId[2] = INTERNALOID; 663 typeId[3] = INTERNALOID; 664 switch (attnum) 665 { 666 case Anum_pg_ts_template_tmplinit: 667 nargs = 1; 668 break; 669 case Anum_pg_ts_template_tmpllexize: 670 nargs = 4; 671 break; 672 default: 673 /* should not be here */ 674 elog(ERROR, "unrecognized attribute for text search template: %d", 675 attnum); 676 nargs = 0; /* keep compiler quiet */ 677 } 678 679 procOid = LookupFuncName(funcName, nargs, typeId, false); 680 if (get_func_rettype(procOid) != retTypeId) 681 ereport(ERROR, 682 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 683 errmsg("function %s should return type %s", 684 func_signature_string(funcName, nargs, NIL, typeId), 685 format_type_be(retTypeId)))); 686 687 return ObjectIdGetDatum(procOid); 688 } 689 690 /* 691 * make pg_depend entries for a new pg_ts_template entry 692 */ 693 static ObjectAddress 694 makeTSTemplateDependencies(HeapTuple tuple) 695 { 696 Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple); 697 ObjectAddress myself, 698 referenced; 699 700 myself.classId = TSTemplateRelationId; 701 myself.objectId = tmpl->oid; 702 myself.objectSubId = 0; 703 704 /* dependency on namespace */ 705 referenced.classId = NamespaceRelationId; 706 referenced.objectId = tmpl->tmplnamespace; 707 referenced.objectSubId = 0; 708 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 709 710 /* dependency on extension */ 711 recordDependencyOnCurrentExtension(&myself, false); 712 713 /* dependencies on functions */ 714 referenced.classId = ProcedureRelationId; 715 referenced.objectSubId = 0; 716 717 referenced.objectId = tmpl->tmpllexize; 718 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 719 720 if (OidIsValid(tmpl->tmplinit)) 721 { 722 referenced.objectId = tmpl->tmplinit; 723 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); 724 } 725 726 return myself; 727 } 728 729 /* 730 * CREATE TEXT SEARCH TEMPLATE 731 */ 732 ObjectAddress 733 DefineTSTemplate(List *names, List *parameters) 734 { 735 ListCell *pl; 736 Relation tmplRel; 737 HeapTuple tup; 738 Datum values[Natts_pg_ts_template]; 739 bool nulls[Natts_pg_ts_template]; 740 NameData dname; 741 int i; 742 Oid tmplOid; 743 Oid namespaceoid; 744 char *tmplname; 745 ObjectAddress address; 746 747 if (!superuser()) 748 ereport(ERROR, 749 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 750 errmsg("must be superuser to create text search templates"))); 751 752 /* Convert list of names to a name and namespace */ 753 namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname); 754 755 tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock); 756 757 for (i = 0; i < Natts_pg_ts_template; i++) 758 { 759 nulls[i] = false; 760 values[i] = ObjectIdGetDatum(InvalidOid); 761 } 762 763 tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId, 764 Anum_pg_ts_dict_oid); 765 values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid); 766 namestrcpy(&dname, tmplname); 767 values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname); 768 values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid); 769 770 /* 771 * loop over the definition list and extract the information we need. 772 */ 773 foreach(pl, parameters) 774 { 775 DefElem *defel = (DefElem *) lfirst(pl); 776 777 if (strcmp(defel->defname, "init") == 0) 778 { 779 values[Anum_pg_ts_template_tmplinit - 1] = 780 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit); 781 nulls[Anum_pg_ts_template_tmplinit - 1] = false; 782 } 783 else if (strcmp(defel->defname, "lexize") == 0) 784 { 785 values[Anum_pg_ts_template_tmpllexize - 1] = 786 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize); 787 nulls[Anum_pg_ts_template_tmpllexize - 1] = false; 788 } 789 else 790 ereport(ERROR, 791 (errcode(ERRCODE_SYNTAX_ERROR), 792 errmsg("text search template parameter \"%s\" not recognized", 793 defel->defname))); 794 } 795 796 /* 797 * Validation 798 */ 799 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1]))) 800 ereport(ERROR, 801 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 802 errmsg("text search template lexize method is required"))); 803 804 /* 805 * Looks good, insert 806 */ 807 tup = heap_form_tuple(tmplRel->rd_att, values, nulls); 808 809 CatalogTupleInsert(tmplRel, tup); 810 811 address = makeTSTemplateDependencies(tup); 812 813 /* Post creation hook for new text search template */ 814 InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0); 815 816 heap_freetuple(tup); 817 818 table_close(tmplRel, RowExclusiveLock); 819 820 return address; 821 } 822 823 /* 824 * Guts of TS template deletion. 825 */ 826 void 827 RemoveTSTemplateById(Oid tmplId) 828 { 829 Relation relation; 830 HeapTuple tup; 831 832 relation = table_open(TSTemplateRelationId, RowExclusiveLock); 833 834 tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId)); 835 836 if (!HeapTupleIsValid(tup)) 837 elog(ERROR, "cache lookup failed for text search template %u", 838 tmplId); 839 840 CatalogTupleDelete(relation, &tup->t_self); 841 842 ReleaseSysCache(tup); 843 844 table_close(relation, RowExclusiveLock); 845 } 846 847 /* ---------------------- TS Configuration commands -----------------------*/ 848 849 /* 850 * Finds syscache tuple of configuration. 851 * Returns NULL if no such cfg. 852 */ 853 static HeapTuple 854 GetTSConfigTuple(List *names) 855 { 856 HeapTuple tup; 857 Oid cfgId; 858 859 cfgId = get_ts_config_oid(names, true); 860 if (!OidIsValid(cfgId)) 861 return NULL; 862 863 tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); 864 865 if (!HeapTupleIsValid(tup)) /* should not happen */ 866 elog(ERROR, "cache lookup failed for text search configuration %u", 867 cfgId); 868 869 return tup; 870 } 871 872 /* 873 * make pg_depend entries for a new or updated pg_ts_config entry 874 * 875 * Pass opened pg_ts_config_map relation if there might be any config map 876 * entries for the config. 877 */ 878 static ObjectAddress 879 makeConfigurationDependencies(HeapTuple tuple, bool removeOld, 880 Relation mapRel) 881 { 882 Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple); 883 ObjectAddresses *addrs; 884 ObjectAddress myself, 885 referenced; 886 887 myself.classId = TSConfigRelationId; 888 myself.objectId = cfg->oid; 889 myself.objectSubId = 0; 890 891 /* for ALTER case, first flush old dependencies, except extension deps */ 892 if (removeOld) 893 { 894 deleteDependencyRecordsFor(myself.classId, myself.objectId, true); 895 deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0); 896 } 897 898 /* 899 * We use an ObjectAddresses list to remove possible duplicate 900 * dependencies from the config map info. The pg_ts_config items 901 * shouldn't be duplicates, but might as well fold them all into one call. 902 */ 903 addrs = new_object_addresses(); 904 905 /* dependency on namespace */ 906 referenced.classId = NamespaceRelationId; 907 referenced.objectId = cfg->cfgnamespace; 908 referenced.objectSubId = 0; 909 add_exact_object_address(&referenced, addrs); 910 911 /* dependency on owner */ 912 recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner); 913 914 /* dependency on extension */ 915 recordDependencyOnCurrentExtension(&myself, removeOld); 916 917 /* dependency on parser */ 918 referenced.classId = TSParserRelationId; 919 referenced.objectId = cfg->cfgparser; 920 referenced.objectSubId = 0; 921 add_exact_object_address(&referenced, addrs); 922 923 /* dependencies on dictionaries listed in config map */ 924 if (mapRel) 925 { 926 ScanKeyData skey; 927 SysScanDesc scan; 928 HeapTuple maptup; 929 930 /* CCI to ensure we can see effects of caller's changes */ 931 CommandCounterIncrement(); 932 933 ScanKeyInit(&skey, 934 Anum_pg_ts_config_map_mapcfg, 935 BTEqualStrategyNumber, F_OIDEQ, 936 ObjectIdGetDatum(myself.objectId)); 937 938 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true, 939 NULL, 1, &skey); 940 941 while (HeapTupleIsValid((maptup = systable_getnext(scan)))) 942 { 943 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); 944 945 referenced.classId = TSDictionaryRelationId; 946 referenced.objectId = cfgmap->mapdict; 947 referenced.objectSubId = 0; 948 add_exact_object_address(&referenced, addrs); 949 } 950 951 systable_endscan(scan); 952 } 953 954 /* Record 'em (this includes duplicate elimination) */ 955 record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); 956 957 free_object_addresses(addrs); 958 959 return myself; 960 } 961 962 /* 963 * CREATE TEXT SEARCH CONFIGURATION 964 */ 965 ObjectAddress 966 DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied) 967 { 968 Relation cfgRel; 969 Relation mapRel = NULL; 970 HeapTuple tup; 971 Datum values[Natts_pg_ts_config]; 972 bool nulls[Natts_pg_ts_config]; 973 AclResult aclresult; 974 Oid namespaceoid; 975 char *cfgname; 976 NameData cname; 977 Oid sourceOid = InvalidOid; 978 Oid prsOid = InvalidOid; 979 Oid cfgOid; 980 ListCell *pl; 981 ObjectAddress address; 982 983 /* Convert list of names to a name and namespace */ 984 namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname); 985 986 /* Check we have creation rights in target namespace */ 987 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE); 988 if (aclresult != ACLCHECK_OK) 989 aclcheck_error(aclresult, OBJECT_SCHEMA, 990 get_namespace_name(namespaceoid)); 991 992 /* 993 * loop over the definition list and extract the information we need. 994 */ 995 foreach(pl, parameters) 996 { 997 DefElem *defel = (DefElem *) lfirst(pl); 998 999 if (strcmp(defel->defname, "parser") == 0) 1000 prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false); 1001 else if (strcmp(defel->defname, "copy") == 0) 1002 sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false); 1003 else 1004 ereport(ERROR, 1005 (errcode(ERRCODE_SYNTAX_ERROR), 1006 errmsg("text search configuration parameter \"%s\" not recognized", 1007 defel->defname))); 1008 } 1009 1010 if (OidIsValid(sourceOid) && OidIsValid(prsOid)) 1011 ereport(ERROR, 1012 (errcode(ERRCODE_SYNTAX_ERROR), 1013 errmsg("cannot specify both PARSER and COPY options"))); 1014 1015 /* make copied tsconfig available to callers */ 1016 if (copied && OidIsValid(sourceOid)) 1017 { 1018 ObjectAddressSet(*copied, 1019 TSConfigRelationId, 1020 sourceOid); 1021 } 1022 1023 /* 1024 * Look up source config if given. 1025 */ 1026 if (OidIsValid(sourceOid)) 1027 { 1028 Form_pg_ts_config cfg; 1029 1030 tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid)); 1031 if (!HeapTupleIsValid(tup)) 1032 elog(ERROR, "cache lookup failed for text search configuration %u", 1033 sourceOid); 1034 1035 cfg = (Form_pg_ts_config) GETSTRUCT(tup); 1036 1037 /* use source's parser */ 1038 prsOid = cfg->cfgparser; 1039 1040 ReleaseSysCache(tup); 1041 } 1042 1043 /* 1044 * Validation 1045 */ 1046 if (!OidIsValid(prsOid)) 1047 ereport(ERROR, 1048 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), 1049 errmsg("text search parser is required"))); 1050 1051 cfgRel = table_open(TSConfigRelationId, RowExclusiveLock); 1052 1053 /* 1054 * Looks good, build tuple and insert 1055 */ 1056 memset(values, 0, sizeof(values)); 1057 memset(nulls, false, sizeof(nulls)); 1058 1059 cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId, 1060 Anum_pg_ts_config_oid); 1061 values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid); 1062 namestrcpy(&cname, cfgname); 1063 values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname); 1064 values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid); 1065 values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId()); 1066 values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid); 1067 1068 tup = heap_form_tuple(cfgRel->rd_att, values, nulls); 1069 1070 CatalogTupleInsert(cfgRel, tup); 1071 1072 if (OidIsValid(sourceOid)) 1073 { 1074 /* 1075 * Copy token-dicts map from source config 1076 */ 1077 ScanKeyData skey; 1078 SysScanDesc scan; 1079 HeapTuple maptup; 1080 1081 mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock); 1082 1083 ScanKeyInit(&skey, 1084 Anum_pg_ts_config_map_mapcfg, 1085 BTEqualStrategyNumber, F_OIDEQ, 1086 ObjectIdGetDatum(sourceOid)); 1087 1088 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true, 1089 NULL, 1, &skey); 1090 1091 while (HeapTupleIsValid((maptup = systable_getnext(scan)))) 1092 { 1093 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); 1094 HeapTuple newmaptup; 1095 Datum mapvalues[Natts_pg_ts_config_map]; 1096 bool mapnulls[Natts_pg_ts_config_map]; 1097 1098 memset(mapvalues, 0, sizeof(mapvalues)); 1099 memset(mapnulls, false, sizeof(mapnulls)); 1100 1101 mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid; 1102 mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype; 1103 mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno; 1104 mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict; 1105 1106 newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls); 1107 1108 CatalogTupleInsert(mapRel, newmaptup); 1109 1110 heap_freetuple(newmaptup); 1111 } 1112 1113 systable_endscan(scan); 1114 } 1115 1116 address = makeConfigurationDependencies(tup, false, mapRel); 1117 1118 /* Post creation hook for new text search configuration */ 1119 InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0); 1120 1121 heap_freetuple(tup); 1122 1123 if (mapRel) 1124 table_close(mapRel, RowExclusiveLock); 1125 table_close(cfgRel, RowExclusiveLock); 1126 1127 return address; 1128 } 1129 1130 /* 1131 * Guts of TS configuration deletion. 1132 */ 1133 void 1134 RemoveTSConfigurationById(Oid cfgId) 1135 { 1136 Relation relCfg, 1137 relMap; 1138 HeapTuple tup; 1139 ScanKeyData skey; 1140 SysScanDesc scan; 1141 1142 /* Remove the pg_ts_config entry */ 1143 relCfg = table_open(TSConfigRelationId, RowExclusiveLock); 1144 1145 tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId)); 1146 1147 if (!HeapTupleIsValid(tup)) 1148 elog(ERROR, "cache lookup failed for text search dictionary %u", 1149 cfgId); 1150 1151 CatalogTupleDelete(relCfg, &tup->t_self); 1152 1153 ReleaseSysCache(tup); 1154 1155 table_close(relCfg, RowExclusiveLock); 1156 1157 /* Remove any pg_ts_config_map entries */ 1158 relMap = table_open(TSConfigMapRelationId, RowExclusiveLock); 1159 1160 ScanKeyInit(&skey, 1161 Anum_pg_ts_config_map_mapcfg, 1162 BTEqualStrategyNumber, F_OIDEQ, 1163 ObjectIdGetDatum(cfgId)); 1164 1165 scan = systable_beginscan(relMap, TSConfigMapIndexId, true, 1166 NULL, 1, &skey); 1167 1168 while (HeapTupleIsValid((tup = systable_getnext(scan)))) 1169 { 1170 CatalogTupleDelete(relMap, &tup->t_self); 1171 } 1172 1173 systable_endscan(scan); 1174 1175 table_close(relMap, RowExclusiveLock); 1176 } 1177 1178 /* 1179 * ALTER TEXT SEARCH CONFIGURATION - main entry point 1180 */ 1181 ObjectAddress 1182 AlterTSConfiguration(AlterTSConfigurationStmt *stmt) 1183 { 1184 HeapTuple tup; 1185 Oid cfgId; 1186 Relation relMap; 1187 ObjectAddress address; 1188 1189 /* Find the configuration */ 1190 tup = GetTSConfigTuple(stmt->cfgname); 1191 if (!HeapTupleIsValid(tup)) 1192 ereport(ERROR, 1193 (errcode(ERRCODE_UNDEFINED_OBJECT), 1194 errmsg("text search configuration \"%s\" does not exist", 1195 NameListToString(stmt->cfgname)))); 1196 1197 cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid; 1198 1199 /* must be owner */ 1200 if (!pg_ts_config_ownercheck(cfgId, GetUserId())) 1201 aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION, 1202 NameListToString(stmt->cfgname)); 1203 1204 relMap = table_open(TSConfigMapRelationId, RowExclusiveLock); 1205 1206 /* Add or drop mappings */ 1207 if (stmt->dicts) 1208 MakeConfigurationMapping(stmt, tup, relMap); 1209 else if (stmt->tokentype) 1210 DropConfigurationMapping(stmt, tup, relMap); 1211 1212 /* Update dependencies */ 1213 makeConfigurationDependencies(tup, true, relMap); 1214 1215 InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0); 1216 1217 ObjectAddressSet(address, TSConfigRelationId, cfgId); 1218 1219 table_close(relMap, RowExclusiveLock); 1220 1221 ReleaseSysCache(tup); 1222 1223 return address; 1224 } 1225 1226 /* 1227 * Translate a list of token type names to an array of token type numbers 1228 */ 1229 static int * 1230 getTokenTypes(Oid prsId, List *tokennames) 1231 { 1232 TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId); 1233 LexDescr *list; 1234 int *res, 1235 i, 1236 ntoken; 1237 ListCell *tn; 1238 1239 ntoken = list_length(tokennames); 1240 if (ntoken == 0) 1241 return NULL; 1242 res = (int *) palloc(sizeof(int) * ntoken); 1243 1244 if (!OidIsValid(prs->lextypeOid)) 1245 elog(ERROR, "method lextype isn't defined for text search parser %u", 1246 prsId); 1247 1248 /* lextype takes one dummy argument */ 1249 list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid, 1250 (Datum) 0)); 1251 1252 i = 0; 1253 foreach(tn, tokennames) 1254 { 1255 Value *val = (Value *) lfirst(tn); 1256 bool found = false; 1257 int j; 1258 1259 j = 0; 1260 while (list && list[j].lexid) 1261 { 1262 if (strcmp(strVal(val), list[j].alias) == 0) 1263 { 1264 res[i] = list[j].lexid; 1265 found = true; 1266 break; 1267 } 1268 j++; 1269 } 1270 if (!found) 1271 ereport(ERROR, 1272 (errcode(ERRCODE_INVALID_PARAMETER_VALUE), 1273 errmsg("token type \"%s\" does not exist", 1274 strVal(val)))); 1275 i++; 1276 } 1277 1278 return res; 1279 } 1280 1281 /* 1282 * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING 1283 */ 1284 static void 1285 MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, 1286 HeapTuple tup, Relation relMap) 1287 { 1288 Form_pg_ts_config tsform; 1289 Oid cfgId; 1290 ScanKeyData skey[2]; 1291 SysScanDesc scan; 1292 HeapTuple maptup; 1293 int i; 1294 int j; 1295 Oid prsId; 1296 int *tokens, 1297 ntoken; 1298 Oid *dictIds; 1299 int ndict; 1300 ListCell *c; 1301 1302 tsform = (Form_pg_ts_config) GETSTRUCT(tup); 1303 cfgId = tsform->oid; 1304 prsId = tsform->cfgparser; 1305 1306 tokens = getTokenTypes(prsId, stmt->tokentype); 1307 ntoken = list_length(stmt->tokentype); 1308 1309 if (stmt->override) 1310 { 1311 /* 1312 * delete maps for tokens if they exist and command was ALTER 1313 */ 1314 for (i = 0; i < ntoken; i++) 1315 { 1316 ScanKeyInit(&skey[0], 1317 Anum_pg_ts_config_map_mapcfg, 1318 BTEqualStrategyNumber, F_OIDEQ, 1319 ObjectIdGetDatum(cfgId)); 1320 ScanKeyInit(&skey[1], 1321 Anum_pg_ts_config_map_maptokentype, 1322 BTEqualStrategyNumber, F_INT4EQ, 1323 Int32GetDatum(tokens[i])); 1324 1325 scan = systable_beginscan(relMap, TSConfigMapIndexId, true, 1326 NULL, 2, skey); 1327 1328 while (HeapTupleIsValid((maptup = systable_getnext(scan)))) 1329 { 1330 CatalogTupleDelete(relMap, &maptup->t_self); 1331 } 1332 1333 systable_endscan(scan); 1334 } 1335 } 1336 1337 /* 1338 * Convert list of dictionary names to array of dict OIDs 1339 */ 1340 ndict = list_length(stmt->dicts); 1341 dictIds = (Oid *) palloc(sizeof(Oid) * ndict); 1342 i = 0; 1343 foreach(c, stmt->dicts) 1344 { 1345 List *names = (List *) lfirst(c); 1346 1347 dictIds[i] = get_ts_dict_oid(names, false); 1348 i++; 1349 } 1350 1351 if (stmt->replace) 1352 { 1353 /* 1354 * Replace a specific dictionary in existing entries 1355 */ 1356 Oid dictOld = dictIds[0], 1357 dictNew = dictIds[1]; 1358 1359 ScanKeyInit(&skey[0], 1360 Anum_pg_ts_config_map_mapcfg, 1361 BTEqualStrategyNumber, F_OIDEQ, 1362 ObjectIdGetDatum(cfgId)); 1363 1364 scan = systable_beginscan(relMap, TSConfigMapIndexId, true, 1365 NULL, 1, skey); 1366 1367 while (HeapTupleIsValid((maptup = systable_getnext(scan)))) 1368 { 1369 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup); 1370 1371 /* 1372 * check if it's one of target token types 1373 */ 1374 if (tokens) 1375 { 1376 bool tokmatch = false; 1377 1378 for (j = 0; j < ntoken; j++) 1379 { 1380 if (cfgmap->maptokentype == tokens[j]) 1381 { 1382 tokmatch = true; 1383 break; 1384 } 1385 } 1386 if (!tokmatch) 1387 continue; 1388 } 1389 1390 /* 1391 * replace dictionary if match 1392 */ 1393 if (cfgmap->mapdict == dictOld) 1394 { 1395 Datum repl_val[Natts_pg_ts_config_map]; 1396 bool repl_null[Natts_pg_ts_config_map]; 1397 bool repl_repl[Natts_pg_ts_config_map]; 1398 HeapTuple newtup; 1399 1400 memset(repl_val, 0, sizeof(repl_val)); 1401 memset(repl_null, false, sizeof(repl_null)); 1402 memset(repl_repl, false, sizeof(repl_repl)); 1403 1404 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew); 1405 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true; 1406 1407 newtup = heap_modify_tuple(maptup, 1408 RelationGetDescr(relMap), 1409 repl_val, repl_null, repl_repl); 1410 CatalogTupleUpdate(relMap, &newtup->t_self, newtup); 1411 } 1412 } 1413 1414 systable_endscan(scan); 1415 } 1416 else 1417 { 1418 /* 1419 * Insertion of new entries 1420 */ 1421 for (i = 0; i < ntoken; i++) 1422 { 1423 for (j = 0; j < ndict; j++) 1424 { 1425 Datum values[Natts_pg_ts_config_map]; 1426 bool nulls[Natts_pg_ts_config_map]; 1427 1428 memset(nulls, false, sizeof(nulls)); 1429 values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId); 1430 values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]); 1431 values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1); 1432 values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]); 1433 1434 tup = heap_form_tuple(relMap->rd_att, values, nulls); 1435 CatalogTupleInsert(relMap, tup); 1436 1437 heap_freetuple(tup); 1438 } 1439 } 1440 } 1441 1442 EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict); 1443 } 1444 1445 /* 1446 * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING 1447 */ 1448 static void 1449 DropConfigurationMapping(AlterTSConfigurationStmt *stmt, 1450 HeapTuple tup, Relation relMap) 1451 { 1452 Form_pg_ts_config tsform; 1453 Oid cfgId; 1454 ScanKeyData skey[2]; 1455 SysScanDesc scan; 1456 HeapTuple maptup; 1457 int i; 1458 Oid prsId; 1459 int *tokens; 1460 ListCell *c; 1461 1462 tsform = (Form_pg_ts_config) GETSTRUCT(tup); 1463 cfgId = tsform->oid; 1464 prsId = tsform->cfgparser; 1465 1466 tokens = getTokenTypes(prsId, stmt->tokentype); 1467 1468 i = 0; 1469 foreach(c, stmt->tokentype) 1470 { 1471 Value *val = (Value *) lfirst(c); 1472 bool found = false; 1473 1474 ScanKeyInit(&skey[0], 1475 Anum_pg_ts_config_map_mapcfg, 1476 BTEqualStrategyNumber, F_OIDEQ, 1477 ObjectIdGetDatum(cfgId)); 1478 ScanKeyInit(&skey[1], 1479 Anum_pg_ts_config_map_maptokentype, 1480 BTEqualStrategyNumber, F_INT4EQ, 1481 Int32GetDatum(tokens[i])); 1482 1483 scan = systable_beginscan(relMap, TSConfigMapIndexId, true, 1484 NULL, 2, skey); 1485 1486 while (HeapTupleIsValid((maptup = systable_getnext(scan)))) 1487 { 1488 CatalogTupleDelete(relMap, &maptup->t_self); 1489 found = true; 1490 } 1491 1492 systable_endscan(scan); 1493 1494 if (!found) 1495 { 1496 if (!stmt->missing_ok) 1497 { 1498 ereport(ERROR, 1499 (errcode(ERRCODE_UNDEFINED_OBJECT), 1500 errmsg("mapping for token type \"%s\" does not exist", 1501 strVal(val)))); 1502 } 1503 else 1504 { 1505 ereport(NOTICE, 1506 (errmsg("mapping for token type \"%s\" does not exist, skipping", 1507 strVal(val)))); 1508 } 1509 } 1510 1511 i++; 1512 } 1513 1514 EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0); 1515 } 1516 1517 1518 /* 1519 * Serialize dictionary options, producing a TEXT datum from a List of DefElem 1520 * 1521 * This is used to form the value stored in pg_ts_dict.dictinitoption. 1522 * For the convenience of pg_dump, the output is formatted exactly as it 1523 * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the 1524 * same options. 1525 */ 1526 text * 1527 serialize_deflist(List *deflist) 1528 { 1529 text *result; 1530 StringInfoData buf; 1531 ListCell *l; 1532 1533 initStringInfo(&buf); 1534 1535 foreach(l, deflist) 1536 { 1537 DefElem *defel = (DefElem *) lfirst(l); 1538 char *val = defGetString(defel); 1539 1540 appendStringInfo(&buf, "%s = ", 1541 quote_identifier(defel->defname)); 1542 1543 /* 1544 * If the value is a T_Integer or T_Float, emit it without quotes, 1545 * otherwise with quotes. This is essential to allow correct 1546 * reconstruction of the node type as well as the value. 1547 */ 1548 if (IsA(defel->arg, Integer) || IsA(defel->arg, Float)) 1549 appendStringInfoString(&buf, val); 1550 else 1551 { 1552 /* If backslashes appear, force E syntax to quote them safely */ 1553 if (strchr(val, '\\')) 1554 appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX); 1555 appendStringInfoChar(&buf, '\''); 1556 while (*val) 1557 { 1558 char ch = *val++; 1559 1560 if (SQL_STR_DOUBLE(ch, true)) 1561 appendStringInfoChar(&buf, ch); 1562 appendStringInfoChar(&buf, ch); 1563 } 1564 appendStringInfoChar(&buf, '\''); 1565 } 1566 if (lnext(deflist, l) != NULL) 1567 appendStringInfoString(&buf, ", "); 1568 } 1569 1570 result = cstring_to_text_with_len(buf.data, buf.len); 1571 pfree(buf.data); 1572 return result; 1573 } 1574 1575 /* 1576 * Deserialize dictionary options, reconstructing a List of DefElem from TEXT 1577 * 1578 * This is also used for prsheadline options, so for backward compatibility 1579 * we need to accept a few things serialize_deflist() will never emit: 1580 * in particular, unquoted and double-quoted strings. 1581 */ 1582 List * 1583 deserialize_deflist(Datum txt) 1584 { 1585 text *in = DatumGetTextPP(txt); /* in case it's toasted */ 1586 List *result = NIL; 1587 int len = VARSIZE_ANY_EXHDR(in); 1588 char *ptr, 1589 *endptr, 1590 *workspace, 1591 *wsptr = NULL, 1592 *startvalue = NULL; 1593 typedef enum 1594 { 1595 CS_WAITKEY, 1596 CS_INKEY, 1597 CS_INQKEY, 1598 CS_WAITEQ, 1599 CS_WAITVALUE, 1600 CS_INSQVALUE, 1601 CS_INDQVALUE, 1602 CS_INWVALUE 1603 } ds_state; 1604 ds_state state = CS_WAITKEY; 1605 1606 workspace = (char *) palloc(len + 1); /* certainly enough room */ 1607 ptr = VARDATA_ANY(in); 1608 endptr = ptr + len; 1609 for (; ptr < endptr; ptr++) 1610 { 1611 switch (state) 1612 { 1613 case CS_WAITKEY: 1614 if (isspace((unsigned char) *ptr) || *ptr == ',') 1615 continue; 1616 if (*ptr == '"') 1617 { 1618 wsptr = workspace; 1619 state = CS_INQKEY; 1620 } 1621 else 1622 { 1623 wsptr = workspace; 1624 *wsptr++ = *ptr; 1625 state = CS_INKEY; 1626 } 1627 break; 1628 case CS_INKEY: 1629 if (isspace((unsigned char) *ptr)) 1630 { 1631 *wsptr++ = '\0'; 1632 state = CS_WAITEQ; 1633 } 1634 else if (*ptr == '=') 1635 { 1636 *wsptr++ = '\0'; 1637 state = CS_WAITVALUE; 1638 } 1639 else 1640 { 1641 *wsptr++ = *ptr; 1642 } 1643 break; 1644 case CS_INQKEY: 1645 if (*ptr == '"') 1646 { 1647 if (ptr + 1 < endptr && ptr[1] == '"') 1648 { 1649 /* copy only one of the two quotes */ 1650 *wsptr++ = *ptr++; 1651 } 1652 else 1653 { 1654 *wsptr++ = '\0'; 1655 state = CS_WAITEQ; 1656 } 1657 } 1658 else 1659 { 1660 *wsptr++ = *ptr; 1661 } 1662 break; 1663 case CS_WAITEQ: 1664 if (*ptr == '=') 1665 state = CS_WAITVALUE; 1666 else if (!isspace((unsigned char) *ptr)) 1667 ereport(ERROR, 1668 (errcode(ERRCODE_SYNTAX_ERROR), 1669 errmsg("invalid parameter list format: \"%s\"", 1670 text_to_cstring(in)))); 1671 break; 1672 case CS_WAITVALUE: 1673 if (*ptr == '\'') 1674 { 1675 startvalue = wsptr; 1676 state = CS_INSQVALUE; 1677 } 1678 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'') 1679 { 1680 ptr++; 1681 startvalue = wsptr; 1682 state = CS_INSQVALUE; 1683 } 1684 else if (*ptr == '"') 1685 { 1686 startvalue = wsptr; 1687 state = CS_INDQVALUE; 1688 } 1689 else if (!isspace((unsigned char) *ptr)) 1690 { 1691 startvalue = wsptr; 1692 *wsptr++ = *ptr; 1693 state = CS_INWVALUE; 1694 } 1695 break; 1696 case CS_INSQVALUE: 1697 if (*ptr == '\'') 1698 { 1699 if (ptr + 1 < endptr && ptr[1] == '\'') 1700 { 1701 /* copy only one of the two quotes */ 1702 *wsptr++ = *ptr++; 1703 } 1704 else 1705 { 1706 *wsptr++ = '\0'; 1707 result = lappend(result, 1708 buildDefItem(workspace, 1709 startvalue, 1710 true)); 1711 state = CS_WAITKEY; 1712 } 1713 } 1714 else if (*ptr == '\\') 1715 { 1716 if (ptr + 1 < endptr && ptr[1] == '\\') 1717 { 1718 /* copy only one of the two backslashes */ 1719 *wsptr++ = *ptr++; 1720 } 1721 else 1722 *wsptr++ = *ptr; 1723 } 1724 else 1725 { 1726 *wsptr++ = *ptr; 1727 } 1728 break; 1729 case CS_INDQVALUE: 1730 if (*ptr == '"') 1731 { 1732 if (ptr + 1 < endptr && ptr[1] == '"') 1733 { 1734 /* copy only one of the two quotes */ 1735 *wsptr++ = *ptr++; 1736 } 1737 else 1738 { 1739 *wsptr++ = '\0'; 1740 result = lappend(result, 1741 buildDefItem(workspace, 1742 startvalue, 1743 true)); 1744 state = CS_WAITKEY; 1745 } 1746 } 1747 else 1748 { 1749 *wsptr++ = *ptr; 1750 } 1751 break; 1752 case CS_INWVALUE: 1753 if (*ptr == ',' || isspace((unsigned char) *ptr)) 1754 { 1755 *wsptr++ = '\0'; 1756 result = lappend(result, 1757 buildDefItem(workspace, 1758 startvalue, 1759 false)); 1760 state = CS_WAITKEY; 1761 } 1762 else 1763 { 1764 *wsptr++ = *ptr; 1765 } 1766 break; 1767 default: 1768 elog(ERROR, "unrecognized deserialize_deflist state: %d", 1769 state); 1770 } 1771 } 1772 1773 if (state == CS_INWVALUE) 1774 { 1775 *wsptr++ = '\0'; 1776 result = lappend(result, 1777 buildDefItem(workspace, 1778 startvalue, 1779 false)); 1780 } 1781 else if (state != CS_WAITKEY) 1782 ereport(ERROR, 1783 (errcode(ERRCODE_SYNTAX_ERROR), 1784 errmsg("invalid parameter list format: \"%s\"", 1785 text_to_cstring(in)))); 1786 1787 pfree(workspace); 1788 1789 return result; 1790 } 1791 1792 /* 1793 * Build one DefElem for deserialize_deflist 1794 */ 1795 static DefElem * 1796 buildDefItem(const char *name, const char *val, bool was_quoted) 1797 { 1798 /* If input was quoted, always emit as string */ 1799 if (!was_quoted && val[0] != '\0') 1800 { 1801 int v; 1802 char *endptr; 1803 1804 /* Try to parse as an integer */ 1805 errno = 0; 1806 v = strtoint(val, &endptr, 10); 1807 if (errno == 0 && *endptr == '\0') 1808 return makeDefElem(pstrdup(name), 1809 (Node *) makeInteger(v), 1810 -1); 1811 /* Nope, how about as a float? */ 1812 errno = 0; 1813 (void) strtod(val, &endptr); 1814 if (errno == 0 && *endptr == '\0') 1815 return makeDefElem(pstrdup(name), 1816 (Node *) makeFloat(pstrdup(val)), 1817 -1); 1818 } 1819 /* Just make it a string */ 1820 return makeDefElem(pstrdup(name), 1821 (Node *) makeString(pstrdup(val)), 1822 -1); 1823 } 1824