| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * proclang.c |
| 4 | * PostgreSQL LANGUAGE support code. |
| 5 | * |
| 6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 7 | * Portions Copyright (c) 1994, Regents of the University of California |
| 8 | * |
| 9 | * IDENTIFICATION |
| 10 | * src/backend/commands/proclang.c |
| 11 | * |
| 12 | *------------------------------------------------------------------------- |
| 13 | */ |
| 14 | #include "postgres.h" |
| 15 | |
| 16 | #include "access/genam.h" |
| 17 | #include "access/htup_details.h" |
| 18 | #include "access/table.h" |
| 19 | #include "catalog/catalog.h" |
| 20 | #include "catalog/dependency.h" |
| 21 | #include "catalog/indexing.h" |
| 22 | #include "catalog/objectaccess.h" |
| 23 | #include "catalog/pg_authid.h" |
| 24 | #include "catalog/pg_language.h" |
| 25 | #include "catalog/pg_namespace.h" |
| 26 | #include "catalog/pg_pltemplate.h" |
| 27 | #include "catalog/pg_proc.h" |
| 28 | #include "catalog/pg_type.h" |
| 29 | #include "commands/dbcommands.h" |
| 30 | #include "commands/defrem.h" |
| 31 | #include "commands/proclang.h" |
| 32 | #include "miscadmin.h" |
| 33 | #include "parser/parse_func.h" |
| 34 | #include "parser/parser.h" |
| 35 | #include "utils/acl.h" |
| 36 | #include "utils/builtins.h" |
| 37 | #include "utils/fmgroids.h" |
| 38 | #include "utils/lsyscache.h" |
| 39 | #include "utils/rel.h" |
| 40 | #include "utils/syscache.h" |
| 41 | |
| 42 | |
| 43 | typedef struct |
| 44 | { |
| 45 | bool tmpltrusted; /* trusted? */ |
| 46 | bool tmpldbacreate; /* db owner allowed to create? */ |
| 47 | char *tmplhandler; /* name of handler function */ |
| 48 | char *tmplinline; /* name of anonymous-block handler, or NULL */ |
| 49 | char *tmplvalidator; /* name of validator function, or NULL */ |
| 50 | char *tmpllibrary; /* path of shared library */ |
| 51 | } PLTemplate; |
| 52 | |
| 53 | static ObjectAddress create_proc_lang(const char *languageName, bool replace, |
| 54 | Oid languageOwner, Oid handlerOid, Oid inlineOid, |
| 55 | Oid valOid, bool trusted); |
| 56 | static PLTemplate *find_language_template(const char *languageName); |
| 57 | |
| 58 | /* |
| 59 | * CREATE LANGUAGE |
| 60 | */ |
| 61 | ObjectAddress |
| 62 | CreateProceduralLanguage(CreatePLangStmt *stmt) |
| 63 | { |
| 64 | PLTemplate *pltemplate; |
| 65 | ObjectAddress tmpAddr; |
| 66 | Oid handlerOid, |
| 67 | inlineOid, |
| 68 | valOid; |
| 69 | Oid funcrettype; |
| 70 | Oid funcargtypes[1]; |
| 71 | |
| 72 | /* |
| 73 | * If we have template information for the language, ignore the supplied |
| 74 | * parameters (if any) and use the template information. |
| 75 | */ |
| 76 | if ((pltemplate = find_language_template(stmt->plname)) != NULL) |
| 77 | { |
| 78 | List *funcname; |
| 79 | |
| 80 | /* |
| 81 | * Give a notice if we are ignoring supplied parameters. |
| 82 | */ |
| 83 | if (stmt->plhandler) |
| 84 | ereport(NOTICE, |
| 85 | (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters" ))); |
| 86 | |
| 87 | /* |
| 88 | * Check permission |
| 89 | */ |
| 90 | if (!superuser()) |
| 91 | { |
| 92 | if (!pltemplate->tmpldbacreate) |
| 93 | ereport(ERROR, |
| 94 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 95 | errmsg("must be superuser to create procedural language \"%s\"" , |
| 96 | stmt->plname))); |
| 97 | if (!pg_database_ownercheck(MyDatabaseId, GetUserId())) |
| 98 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE, |
| 99 | get_database_name(MyDatabaseId)); |
| 100 | } |
| 101 | |
| 102 | /* |
| 103 | * Find or create the handler function, which we force to be in the |
| 104 | * pg_catalog schema. If already present, it must have the correct |
| 105 | * return type. |
| 106 | */ |
| 107 | funcname = SystemFuncName(pltemplate->tmplhandler); |
| 108 | handlerOid = LookupFuncName(funcname, 0, funcargtypes, true); |
| 109 | if (OidIsValid(handlerOid)) |
| 110 | { |
| 111 | funcrettype = get_func_rettype(handlerOid); |
| 112 | if (funcrettype != LANGUAGE_HANDLEROID) |
| 113 | ereport(ERROR, |
| 114 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 115 | errmsg("function %s must return type %s" , |
| 116 | NameListToString(funcname), "language_handler" ))); |
| 117 | } |
| 118 | else |
| 119 | { |
| 120 | tmpAddr = ProcedureCreate(pltemplate->tmplhandler, |
| 121 | PG_CATALOG_NAMESPACE, |
| 122 | false, /* replace */ |
| 123 | false, /* returnsSet */ |
| 124 | LANGUAGE_HANDLEROID, |
| 125 | BOOTSTRAP_SUPERUSERID, |
| 126 | ClanguageId, |
| 127 | F_FMGR_C_VALIDATOR, |
| 128 | pltemplate->tmplhandler, |
| 129 | pltemplate->tmpllibrary, |
| 130 | PROKIND_FUNCTION, |
| 131 | false, /* security_definer */ |
| 132 | false, /* isLeakProof */ |
| 133 | false, /* isStrict */ |
| 134 | PROVOLATILE_VOLATILE, |
| 135 | PROPARALLEL_UNSAFE, |
| 136 | buildoidvector(funcargtypes, 0), |
| 137 | PointerGetDatum(NULL), |
| 138 | PointerGetDatum(NULL), |
| 139 | PointerGetDatum(NULL), |
| 140 | NIL, |
| 141 | PointerGetDatum(NULL), |
| 142 | PointerGetDatum(NULL), |
| 143 | InvalidOid, |
| 144 | 1, |
| 145 | 0); |
| 146 | handlerOid = tmpAddr.objectId; |
| 147 | } |
| 148 | |
| 149 | /* |
| 150 | * Likewise for the anonymous block handler, if required; but we don't |
| 151 | * care about its return type. |
| 152 | */ |
| 153 | if (pltemplate->tmplinline) |
| 154 | { |
| 155 | funcname = SystemFuncName(pltemplate->tmplinline); |
| 156 | funcargtypes[0] = INTERNALOID; |
| 157 | inlineOid = LookupFuncName(funcname, 1, funcargtypes, true); |
| 158 | if (!OidIsValid(inlineOid)) |
| 159 | { |
| 160 | tmpAddr = ProcedureCreate(pltemplate->tmplinline, |
| 161 | PG_CATALOG_NAMESPACE, |
| 162 | false, /* replace */ |
| 163 | false, /* returnsSet */ |
| 164 | VOIDOID, |
| 165 | BOOTSTRAP_SUPERUSERID, |
| 166 | ClanguageId, |
| 167 | F_FMGR_C_VALIDATOR, |
| 168 | pltemplate->tmplinline, |
| 169 | pltemplate->tmpllibrary, |
| 170 | PROKIND_FUNCTION, |
| 171 | false, /* security_definer */ |
| 172 | false, /* isLeakProof */ |
| 173 | true, /* isStrict */ |
| 174 | PROVOLATILE_VOLATILE, |
| 175 | PROPARALLEL_UNSAFE, |
| 176 | buildoidvector(funcargtypes, 1), |
| 177 | PointerGetDatum(NULL), |
| 178 | PointerGetDatum(NULL), |
| 179 | PointerGetDatum(NULL), |
| 180 | NIL, |
| 181 | PointerGetDatum(NULL), |
| 182 | PointerGetDatum(NULL), |
| 183 | InvalidOid, |
| 184 | 1, |
| 185 | 0); |
| 186 | inlineOid = tmpAddr.objectId; |
| 187 | } |
| 188 | } |
| 189 | else |
| 190 | inlineOid = InvalidOid; |
| 191 | |
| 192 | /* |
| 193 | * Likewise for the validator, if required; but we don't care about |
| 194 | * its return type. |
| 195 | */ |
| 196 | if (pltemplate->tmplvalidator) |
| 197 | { |
| 198 | funcname = SystemFuncName(pltemplate->tmplvalidator); |
| 199 | funcargtypes[0] = OIDOID; |
| 200 | valOid = LookupFuncName(funcname, 1, funcargtypes, true); |
| 201 | if (!OidIsValid(valOid)) |
| 202 | { |
| 203 | tmpAddr = ProcedureCreate(pltemplate->tmplvalidator, |
| 204 | PG_CATALOG_NAMESPACE, |
| 205 | false, /* replace */ |
| 206 | false, /* returnsSet */ |
| 207 | VOIDOID, |
| 208 | BOOTSTRAP_SUPERUSERID, |
| 209 | ClanguageId, |
| 210 | F_FMGR_C_VALIDATOR, |
| 211 | pltemplate->tmplvalidator, |
| 212 | pltemplate->tmpllibrary, |
| 213 | PROKIND_FUNCTION, |
| 214 | false, /* security_definer */ |
| 215 | false, /* isLeakProof */ |
| 216 | true, /* isStrict */ |
| 217 | PROVOLATILE_VOLATILE, |
| 218 | PROPARALLEL_UNSAFE, |
| 219 | buildoidvector(funcargtypes, 1), |
| 220 | PointerGetDatum(NULL), |
| 221 | PointerGetDatum(NULL), |
| 222 | PointerGetDatum(NULL), |
| 223 | NIL, |
| 224 | PointerGetDatum(NULL), |
| 225 | PointerGetDatum(NULL), |
| 226 | InvalidOid, |
| 227 | 1, |
| 228 | 0); |
| 229 | valOid = tmpAddr.objectId; |
| 230 | } |
| 231 | } |
| 232 | else |
| 233 | valOid = InvalidOid; |
| 234 | |
| 235 | /* ok, create it */ |
| 236 | return create_proc_lang(stmt->plname, stmt->replace, GetUserId(), |
| 237 | handlerOid, inlineOid, |
| 238 | valOid, pltemplate->tmpltrusted); |
| 239 | } |
| 240 | else |
| 241 | { |
| 242 | /* |
| 243 | * No template, so use the provided information. If there's no |
| 244 | * handler clause, the user is trying to rely on a template that we |
| 245 | * don't have, so complain accordingly. |
| 246 | */ |
| 247 | if (!stmt->plhandler) |
| 248 | ereport(ERROR, |
| 249 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 250 | errmsg("unsupported language \"%s\"" , |
| 251 | stmt->plname), |
| 252 | errhint("The supported languages are listed in the pg_pltemplate system catalog." ))); |
| 253 | |
| 254 | /* |
| 255 | * Check permission |
| 256 | */ |
| 257 | if (!superuser()) |
| 258 | ereport(ERROR, |
| 259 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| 260 | errmsg("must be superuser to create custom procedural language" ))); |
| 261 | |
| 262 | /* |
| 263 | * Lookup the PL handler function and check that it is of the expected |
| 264 | * return type |
| 265 | */ |
| 266 | handlerOid = LookupFuncName(stmt->plhandler, 0, funcargtypes, false); |
| 267 | funcrettype = get_func_rettype(handlerOid); |
| 268 | if (funcrettype != LANGUAGE_HANDLEROID) |
| 269 | { |
| 270 | /* |
| 271 | * We allow OPAQUE just so we can load old dump files. When we |
| 272 | * see a handler function declared OPAQUE, change it to |
| 273 | * LANGUAGE_HANDLER. (This is probably obsolete and removable?) |
| 274 | */ |
| 275 | if (funcrettype == OPAQUEOID) |
| 276 | { |
| 277 | ereport(WARNING, |
| 278 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 279 | errmsg("changing return type of function %s from %s to %s" , |
| 280 | NameListToString(stmt->plhandler), |
| 281 | "opaque" , "language_handler" ))); |
| 282 | SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID); |
| 283 | } |
| 284 | else |
| 285 | ereport(ERROR, |
| 286 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 287 | errmsg("function %s must return type %s" , |
| 288 | NameListToString(stmt->plhandler), "language_handler" ))); |
| 289 | } |
| 290 | |
| 291 | /* validate the inline function */ |
| 292 | if (stmt->plinline) |
| 293 | { |
| 294 | funcargtypes[0] = INTERNALOID; |
| 295 | inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false); |
| 296 | /* return value is ignored, so we don't check the type */ |
| 297 | } |
| 298 | else |
| 299 | inlineOid = InvalidOid; |
| 300 | |
| 301 | /* validate the validator function */ |
| 302 | if (stmt->plvalidator) |
| 303 | { |
| 304 | funcargtypes[0] = OIDOID; |
| 305 | valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false); |
| 306 | /* return value is ignored, so we don't check the type */ |
| 307 | } |
| 308 | else |
| 309 | valOid = InvalidOid; |
| 310 | |
| 311 | /* ok, create it */ |
| 312 | return create_proc_lang(stmt->plname, stmt->replace, GetUserId(), |
| 313 | handlerOid, inlineOid, |
| 314 | valOid, stmt->pltrusted); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | /* |
| 319 | * Guts of language creation. |
| 320 | */ |
| 321 | static ObjectAddress |
| 322 | create_proc_lang(const char *languageName, bool replace, |
| 323 | Oid languageOwner, Oid handlerOid, Oid inlineOid, |
| 324 | Oid valOid, bool trusted) |
| 325 | { |
| 326 | Relation rel; |
| 327 | TupleDesc tupDesc; |
| 328 | Datum values[Natts_pg_language]; |
| 329 | bool nulls[Natts_pg_language]; |
| 330 | bool replaces[Natts_pg_language]; |
| 331 | NameData langname; |
| 332 | HeapTuple oldtup; |
| 333 | HeapTuple tup; |
| 334 | Oid langoid; |
| 335 | bool is_update; |
| 336 | ObjectAddress myself, |
| 337 | referenced; |
| 338 | |
| 339 | rel = table_open(LanguageRelationId, RowExclusiveLock); |
| 340 | tupDesc = RelationGetDescr(rel); |
| 341 | |
| 342 | /* Prepare data to be inserted */ |
| 343 | memset(values, 0, sizeof(values)); |
| 344 | memset(nulls, false, sizeof(nulls)); |
| 345 | memset(replaces, true, sizeof(replaces)); |
| 346 | |
| 347 | namestrcpy(&langname, languageName); |
| 348 | values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname); |
| 349 | values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner); |
| 350 | values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true); |
| 351 | values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted); |
| 352 | values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); |
| 353 | values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid); |
| 354 | values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid); |
| 355 | nulls[Anum_pg_language_lanacl - 1] = true; |
| 356 | |
| 357 | /* Check for pre-existing definition */ |
| 358 | oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(languageName)); |
| 359 | |
| 360 | if (HeapTupleIsValid(oldtup)) |
| 361 | { |
| 362 | Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup); |
| 363 | |
| 364 | /* There is one; okay to replace it? */ |
| 365 | if (!replace) |
| 366 | ereport(ERROR, |
| 367 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
| 368 | errmsg("language \"%s\" already exists" , languageName))); |
| 369 | if (!pg_language_ownercheck(oldform->oid, languageOwner)) |
| 370 | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE, |
| 371 | languageName); |
| 372 | |
| 373 | /* |
| 374 | * Do not change existing oid, ownership or permissions. Note |
| 375 | * dependency-update code below has to agree with this decision. |
| 376 | */ |
| 377 | replaces[Anum_pg_language_oid - 1] = false; |
| 378 | replaces[Anum_pg_language_lanowner - 1] = false; |
| 379 | replaces[Anum_pg_language_lanacl - 1] = false; |
| 380 | |
| 381 | /* Okay, do it... */ |
| 382 | tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); |
| 383 | CatalogTupleUpdate(rel, &tup->t_self, tup); |
| 384 | |
| 385 | langoid = oldform->oid; |
| 386 | ReleaseSysCache(oldtup); |
| 387 | is_update = true; |
| 388 | } |
| 389 | else |
| 390 | { |
| 391 | /* Creating a new language */ |
| 392 | langoid = GetNewOidWithIndex(rel, LanguageOidIndexId, |
| 393 | Anum_pg_language_oid); |
| 394 | values[Anum_pg_language_oid - 1] = ObjectIdGetDatum(langoid); |
| 395 | tup = heap_form_tuple(tupDesc, values, nulls); |
| 396 | CatalogTupleInsert(rel, tup); |
| 397 | is_update = false; |
| 398 | } |
| 399 | |
| 400 | /* |
| 401 | * Create dependencies for the new language. If we are updating an |
| 402 | * existing language, first delete any existing pg_depend entries. |
| 403 | * (However, since we are not changing ownership or permissions, the |
| 404 | * shared dependencies do *not* need to change, and we leave them alone.) |
| 405 | */ |
| 406 | myself.classId = LanguageRelationId; |
| 407 | myself.objectId = langoid; |
| 408 | myself.objectSubId = 0; |
| 409 | |
| 410 | if (is_update) |
| 411 | deleteDependencyRecordsFor(myself.classId, myself.objectId, true); |
| 412 | |
| 413 | /* dependency on owner of language */ |
| 414 | if (!is_update) |
| 415 | recordDependencyOnOwner(myself.classId, myself.objectId, |
| 416 | languageOwner); |
| 417 | |
| 418 | /* dependency on extension */ |
| 419 | recordDependencyOnCurrentExtension(&myself, is_update); |
| 420 | |
| 421 | /* dependency on the PL handler function */ |
| 422 | referenced.classId = ProcedureRelationId; |
| 423 | referenced.objectId = handlerOid; |
| 424 | referenced.objectSubId = 0; |
| 425 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| 426 | |
| 427 | /* dependency on the inline handler function, if any */ |
| 428 | if (OidIsValid(inlineOid)) |
| 429 | { |
| 430 | referenced.classId = ProcedureRelationId; |
| 431 | referenced.objectId = inlineOid; |
| 432 | referenced.objectSubId = 0; |
| 433 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| 434 | } |
| 435 | |
| 436 | /* dependency on the validator function, if any */ |
| 437 | if (OidIsValid(valOid)) |
| 438 | { |
| 439 | referenced.classId = ProcedureRelationId; |
| 440 | referenced.objectId = valOid; |
| 441 | referenced.objectSubId = 0; |
| 442 | recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| 443 | } |
| 444 | |
| 445 | /* Post creation hook for new procedural language */ |
| 446 | InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0); |
| 447 | |
| 448 | table_close(rel, RowExclusiveLock); |
| 449 | |
| 450 | return myself; |
| 451 | } |
| 452 | |
| 453 | /* |
| 454 | * Look to see if we have template information for the given language name. |
| 455 | */ |
| 456 | static PLTemplate * |
| 457 | find_language_template(const char *languageName) |
| 458 | { |
| 459 | PLTemplate *result; |
| 460 | Relation rel; |
| 461 | SysScanDesc scan; |
| 462 | ScanKeyData key; |
| 463 | HeapTuple tup; |
| 464 | |
| 465 | rel = table_open(PLTemplateRelationId, AccessShareLock); |
| 466 | |
| 467 | ScanKeyInit(&key, |
| 468 | Anum_pg_pltemplate_tmplname, |
| 469 | BTEqualStrategyNumber, F_NAMEEQ, |
| 470 | CStringGetDatum(languageName)); |
| 471 | scan = systable_beginscan(rel, PLTemplateNameIndexId, true, |
| 472 | NULL, 1, &key); |
| 473 | |
| 474 | tup = systable_getnext(scan); |
| 475 | if (HeapTupleIsValid(tup)) |
| 476 | { |
| 477 | Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup); |
| 478 | Datum datum; |
| 479 | bool isnull; |
| 480 | |
| 481 | result = (PLTemplate *) palloc0(sizeof(PLTemplate)); |
| 482 | result->tmpltrusted = tmpl->tmpltrusted; |
| 483 | result->tmpldbacreate = tmpl->tmpldbacreate; |
| 484 | |
| 485 | /* Remaining fields are variable-width so we need heap_getattr */ |
| 486 | datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler, |
| 487 | RelationGetDescr(rel), &isnull); |
| 488 | if (!isnull) |
| 489 | result->tmplhandler = TextDatumGetCString(datum); |
| 490 | |
| 491 | datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline, |
| 492 | RelationGetDescr(rel), &isnull); |
| 493 | if (!isnull) |
| 494 | result->tmplinline = TextDatumGetCString(datum); |
| 495 | |
| 496 | datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator, |
| 497 | RelationGetDescr(rel), &isnull); |
| 498 | if (!isnull) |
| 499 | result->tmplvalidator = TextDatumGetCString(datum); |
| 500 | |
| 501 | datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary, |
| 502 | RelationGetDescr(rel), &isnull); |
| 503 | if (!isnull) |
| 504 | result->tmpllibrary = TextDatumGetCString(datum); |
| 505 | |
| 506 | /* Ignore template if handler or library info is missing */ |
| 507 | if (!result->tmplhandler || !result->tmpllibrary) |
| 508 | result = NULL; |
| 509 | } |
| 510 | else |
| 511 | result = NULL; |
| 512 | |
| 513 | systable_endscan(scan); |
| 514 | |
| 515 | table_close(rel, AccessShareLock); |
| 516 | |
| 517 | return result; |
| 518 | } |
| 519 | |
| 520 | |
| 521 | /* |
| 522 | * This just returns true if we have a valid template for a given language |
| 523 | */ |
| 524 | bool |
| 525 | PLTemplateExists(const char *languageName) |
| 526 | { |
| 527 | return (find_language_template(languageName) != NULL); |
| 528 | } |
| 529 | |
| 530 | /* |
| 531 | * Guts of language dropping. |
| 532 | */ |
| 533 | void |
| 534 | DropProceduralLanguageById(Oid langOid) |
| 535 | { |
| 536 | Relation rel; |
| 537 | HeapTuple langTup; |
| 538 | |
| 539 | rel = table_open(LanguageRelationId, RowExclusiveLock); |
| 540 | |
| 541 | langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(langOid)); |
| 542 | if (!HeapTupleIsValid(langTup)) /* should not happen */ |
| 543 | elog(ERROR, "cache lookup failed for language %u" , langOid); |
| 544 | |
| 545 | CatalogTupleDelete(rel, &langTup->t_self); |
| 546 | |
| 547 | ReleaseSysCache(langTup); |
| 548 | |
| 549 | table_close(rel, RowExclusiveLock); |
| 550 | } |
| 551 | |
| 552 | /* |
| 553 | * get_language_oid - given a language name, look up the OID |
| 554 | * |
| 555 | * If missing_ok is false, throw an error if language name not found. If |
| 556 | * true, just return InvalidOid. |
| 557 | */ |
| 558 | Oid |
| 559 | get_language_oid(const char *langname, bool missing_ok) |
| 560 | { |
| 561 | Oid oid; |
| 562 | |
| 563 | oid = GetSysCacheOid1(LANGNAME, Anum_pg_language_oid, |
| 564 | CStringGetDatum(langname)); |
| 565 | if (!OidIsValid(oid) && !missing_ok) |
| 566 | ereport(ERROR, |
| 567 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 568 | errmsg("language \"%s\" does not exist" , langname))); |
| 569 | return oid; |
| 570 | } |
| 571 | |