]> granicus.if.org Git - postgresql/blob - src/backend/catalog/namespace.c
Implement types regprocedure, regoper, regoperator, regclass, regtype
[postgresql] / src / backend / catalog / namespace.c
1 /*-------------------------------------------------------------------------
2  *
3  * namespace.c
4  *        code to support accessing and searching namespaces
5  *
6  * This is separate from pg_namespace.c, which contains the routines that
7  * directly manipulate the pg_namespace system catalog.  This module
8  * provides routines associated with defining a "namespace search path"
9  * and implementing search-path-controlled searches.
10  *
11  *
12  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
13  * Portions Copyright (c) 1994, Regents of the University of California
14  *
15  * IDENTIFICATION
16  *        $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.12 2002/04/25 02:56:55 tgl Exp $
17  *
18  *-------------------------------------------------------------------------
19  */
20 #include "postgres.h"
21
22 #include "access/heapam.h"
23 #include "access/xact.h"
24 #include "catalog/catalog.h"
25 #include "catalog/catname.h"
26 #include "catalog/heap.h"
27 #include "catalog/namespace.h"
28 #include "catalog/pg_inherits.h"
29 #include "catalog/pg_namespace.h"
30 #include "catalog/pg_opclass.h"
31 #include "catalog/pg_operator.h"
32 #include "catalog/pg_proc.h"
33 #include "catalog/pg_shadow.h"
34 #include "lib/stringinfo.h"
35 #include "miscadmin.h"
36 #include "nodes/makefuncs.h"
37 #include "storage/backendid.h"
38 #include "utils/builtins.h"
39 #include "utils/fmgroids.h"
40 #include "utils/guc.h"
41 #include "utils/catcache.h"
42 #include "utils/lsyscache.h"
43 #include "utils/syscache.h"
44
45
46 /*
47  * The namespace search path is a possibly-empty list of namespace OIDs.
48  * In addition to the explicit list, the TEMP table namespace is always
49  * implicitly searched first (if it's been initialized).  Also, the system
50  * catalog namespace is always searched.  If the system namespace is
51  * explicitly present in the path then it will be searched in the specified
52  * order; otherwise it will be searched after TEMP tables and *before* the
53  * explicit list.  (It might seem that the system namespace should be
54  * implicitly last, but this behavior appears to be required by SQL99.
55  * Also, this provides a way to search the system namespace first without
56  * thereby making it the default creation target namespace.)
57  *
58  * The default creation target namespace is kept equal to the first element
59  * of the (explicit) list.  If the list is empty, there is no default target.
60  *
61  * In bootstrap mode, the search path is set equal to 'pg_catalog', so that
62  * the system namespace is the only one searched or inserted into.
63  * The initdb script is also careful to set search_path to 'pg_catalog' for
64  * its post-bootstrap standalone backend runs.  Otherwise the default search
65  * path is determined by GUC.  The factory default path contains the PUBLIC
66  * namespace (if it exists), preceded by the user's personal namespace
67  * (if one exists).
68  */
69
70 static List *namespaceSearchPath = NIL;
71
72 /* this flag must be updated correctly when namespaceSearchPath is changed */
73 static bool pathContainsSystemNamespace = false;
74
75 /* default place to create stuff; if InvalidOid, no default */
76 static Oid      defaultCreationNamespace = InvalidOid;
77
78 /*
79  * myTempNamespace is InvalidOid until and unless a TEMP namespace is set up
80  * in a particular backend session (this happens when a CREATE TEMP TABLE
81  * command is first executed).  Thereafter it's the OID of the temp namespace.
82  */
83 static Oid      myTempNamespace = InvalidOid;
84
85 /*
86  * This is the text equivalent of the search path --- it's the value
87  * of the GUC variable 'search_path'.
88  */
89 char *namespace_search_path = NULL;
90
91
92 /*
93  * Deletion ordering constraint item.
94  */
95 typedef struct DelConstraint
96 {
97         Oid                     referencer;             /* table to delete first */
98         Oid                     referencee;             /* table to delete second */
99         int                     pred;                   /* workspace for TopoSortRels */
100         struct DelConstraint *link;     /* workspace for TopoSortRels */
101 } DelConstraint;
102
103
104 /* Local functions */
105 static Oid      GetTempTableNamespace(void);
106 static void RemoveTempRelations(Oid tempNamespaceId);
107 static List *FindTempRelations(Oid tempNamespaceId);
108 static List *FindDeletionConstraints(List *relOids);
109 static List *TopoSortRels(List *relOids, List *constraintList);
110 static void RemoveTempRelationsCallback(void);
111
112
113 /*
114  * RangeVarGetRelid
115  *              Given a RangeVar describing an existing relation,
116  *              select the proper namespace and look up the relation OID.
117  *
118  * If the relation is not found, return InvalidOid if failOK = true,
119  * otherwise raise an error.
120  */
121 Oid
122 RangeVarGetRelid(const RangeVar *relation, bool failOK)
123 {
124         Oid                     namespaceId;
125         Oid                     relId;
126
127         /*
128          * We check the catalog name and then ignore it.
129          */
130         if (relation->catalogname)
131         {
132                 if (strcmp(relation->catalogname, DatabaseName) != 0)
133                         elog(ERROR, "Cross-database references are not implemented");
134         }
135
136         if (relation->schemaname)
137         {
138                 /* use exact schema given */
139                 namespaceId = GetSysCacheOid(NAMESPACENAME,
140                                                                          CStringGetDatum(relation->schemaname),
141                                                                          0, 0, 0);
142                 if (!OidIsValid(namespaceId))
143                         elog(ERROR, "Namespace \"%s\" does not exist",
144                                  relation->schemaname);
145                 relId = get_relname_relid(relation->relname, namespaceId);
146         }
147         else
148         {
149                 /* search the namespace path */
150                 relId = RelnameGetRelid(relation->relname);
151         }
152
153         if (!OidIsValid(relId) && !failOK)
154         {
155                 if (relation->schemaname)
156                         elog(ERROR, "Relation \"%s\".\"%s\" does not exist",
157                                  relation->schemaname, relation->relname);
158                 else
159                         elog(ERROR, "Relation \"%s\" does not exist",
160                                  relation->relname);
161         }
162         return relId;
163 }
164
165 /*
166  * RangeVarGetCreationNamespace
167  *              Given a RangeVar describing a to-be-created relation,
168  *              choose which namespace to create it in.
169  *
170  * Note: calling this may result in a CommandCounterIncrement operation.
171  * That will happen on the first request for a temp table in any particular
172  * backend run; we will need to either create or clean out the temp schema.
173  */
174 Oid
175 RangeVarGetCreationNamespace(const RangeVar *newRelation)
176 {
177         Oid                     namespaceId;
178
179         /*
180          * We check the catalog name and then ignore it.
181          */
182         if (newRelation->catalogname)
183         {
184                 if (strcmp(newRelation->catalogname, DatabaseName) != 0)
185                         elog(ERROR, "Cross-database references are not implemented");
186         }
187
188         if (newRelation->istemp)
189         {
190                 /* TEMP tables are created in our backend-local temp namespace */
191                 if (newRelation->schemaname)
192                         elog(ERROR, "TEMP tables may not specify a namespace");
193                 /* Initialize temp namespace if first time through */
194                 if (!OidIsValid(myTempNamespace))
195                         myTempNamespace = GetTempTableNamespace();
196                 return myTempNamespace;
197         }
198
199         if (newRelation->schemaname)
200         {
201                 /* use exact schema given */
202                 namespaceId = GetSysCacheOid(NAMESPACENAME,
203                                                                          CStringGetDatum(newRelation->schemaname),
204                                                                          0, 0, 0);
205                 if (!OidIsValid(namespaceId))
206                         elog(ERROR, "Namespace \"%s\" does not exist",
207                                  newRelation->schemaname);
208         }
209         else
210         {
211                 /* use the default creation namespace */
212                 namespaceId = defaultCreationNamespace;
213                 if (!OidIsValid(namespaceId))
214                         elog(ERROR, "No namespace has been selected to create in");
215         }
216
217         return namespaceId;
218 }
219
220 /*
221  * RelnameGetRelid
222  *              Try to resolve an unqualified relation name.
223  *              Returns OID if relation found in search path, else InvalidOid.
224  */
225 Oid
226 RelnameGetRelid(const char *relname)
227 {
228         Oid                     relid;
229         List       *lptr;
230
231         /*
232          * If a TEMP-table namespace has been set up, it is implicitly first
233          * in the search path.
234          */
235         if (OidIsValid(myTempNamespace))
236         {
237                 relid = get_relname_relid(relname, myTempNamespace);
238                 if (OidIsValid(relid))
239                         return relid;
240         }
241
242         /*
243          * If system namespace is not in path, implicitly search it before path
244          */
245         if (!pathContainsSystemNamespace)
246         {
247                 relid = get_relname_relid(relname, PG_CATALOG_NAMESPACE);
248                 if (OidIsValid(relid))
249                         return relid;
250         }
251
252         /*
253          * Else search the path
254          */
255         foreach(lptr, namespaceSearchPath)
256         {
257                 Oid                     namespaceId = (Oid) lfirsti(lptr);
258
259                 relid = get_relname_relid(relname, namespaceId);
260                 if (OidIsValid(relid))
261                         return relid;
262         }
263
264         /* Not found in path */
265         return InvalidOid;
266 }
267
268 /*
269  * TypenameGetTypid
270  *              Try to resolve an unqualified datatype name.
271  *              Returns OID if type found in search path, else InvalidOid.
272  *
273  * This is essentially the same as RelnameGetRelid, but we never search
274  * the TEMP table namespace --- there is no reason to refer to the types
275  * of temp tables, AFAICS.
276  */
277 Oid
278 TypenameGetTypid(const char *typname)
279 {
280         Oid                     typid;
281         List       *lptr;
282
283         /*
284          * If system namespace is not in path, implicitly search it before path
285          */
286         if (!pathContainsSystemNamespace)
287         {
288                 typid = GetSysCacheOid(TYPENAMENSP,
289                                                            PointerGetDatum(typname),
290                                                            ObjectIdGetDatum(PG_CATALOG_NAMESPACE),
291                                                            0, 0);
292                 if (OidIsValid(typid))
293                         return typid;
294         }
295
296         /*
297          * Else search the path
298          */
299         foreach(lptr, namespaceSearchPath)
300         {
301                 Oid                     namespaceId = (Oid) lfirsti(lptr);
302
303                 typid = GetSysCacheOid(TYPENAMENSP,
304                                                            PointerGetDatum(typname),
305                                                            ObjectIdGetDatum(namespaceId),
306                                                            0, 0);
307                 if (OidIsValid(typid))
308                         return typid;
309         }
310
311         /* Not found in path */
312         return InvalidOid;
313 }
314
315 /*
316  * OpclassnameGetOpcid
317  *              Try to resolve an unqualified index opclass name.
318  *              Returns OID if opclass found in search path, else InvalidOid.
319  *
320  * This is essentially the same as TypenameGetTypid, but we have to have
321  * an extra argument for the index AM OID.
322  */
323 Oid
324 OpclassnameGetOpcid(Oid amid, const char *opcname)
325 {
326         Oid                     opcid;
327         List       *lptr;
328
329         /*
330          * If system namespace is not in path, implicitly search it before path
331          */
332         if (!pathContainsSystemNamespace)
333         {
334                 opcid = GetSysCacheOid(CLAAMNAMENSP,
335                                                            ObjectIdGetDatum(amid),
336                                                            PointerGetDatum(opcname),
337                                                            ObjectIdGetDatum(PG_CATALOG_NAMESPACE),
338                                                            0);
339                 if (OidIsValid(opcid))
340                         return opcid;
341         }
342
343         /*
344          * Else search the path
345          */
346         foreach(lptr, namespaceSearchPath)
347         {
348                 Oid                     namespaceId = (Oid) lfirsti(lptr);
349
350                 opcid = GetSysCacheOid(CLAAMNAMENSP,
351                                                            ObjectIdGetDatum(amid),
352                                                            PointerGetDatum(opcname),
353                                                            ObjectIdGetDatum(namespaceId),
354                                                            0);
355                 if (OidIsValid(opcid))
356                         return opcid;
357         }
358
359         /* Not found in path */
360         return InvalidOid;
361 }
362
363 /*
364  * FuncnameGetCandidates
365  *              Given a possibly-qualified function name and argument count,
366  *              retrieve a list of the possible matches.
367  *
368  * If nargs is -1, we return all functions matching the given name,
369  * regardless of argument count.
370  *
371  * We search a single namespace if the function name is qualified, else
372  * all namespaces in the search path.  The return list will never contain
373  * multiple entries with identical argument lists --- in the multiple-
374  * namespace case, we arrange for entries in earlier namespaces to mask
375  * identical entries in later namespaces.
376  */
377 FuncCandidateList
378 FuncnameGetCandidates(List *names, int nargs)
379 {
380         FuncCandidateList resultList = NULL;
381         char       *catalogname;
382         char       *schemaname = NULL;
383         char       *funcname = NULL;
384         Oid                     namespaceId;
385         CatCList   *catlist;
386         int                     i;
387
388         /* deconstruct the name list */
389         switch (length(names))
390         {
391                 case 1:
392                         funcname = strVal(lfirst(names));
393                         break;
394                 case 2:
395                         schemaname = strVal(lfirst(names));
396                         funcname = strVal(lsecond(names));
397                         break;
398                 case 3:
399                         catalogname = strVal(lfirst(names));
400                         schemaname = strVal(lsecond(names));
401                         funcname = strVal(lfirst(lnext(lnext(names))));
402                         /*
403                          * We check the catalog name and then ignore it.
404                          */
405                         if (strcmp(catalogname, DatabaseName) != 0)
406                                 elog(ERROR, "Cross-database references are not implemented");
407                         break;
408                 default:
409                         elog(ERROR, "Improper qualified name (too many dotted names)");
410                         break;
411         }
412
413         if (schemaname)
414         {
415                 /* use exact schema given */
416                 namespaceId = GetSysCacheOid(NAMESPACENAME,
417                                                                          CStringGetDatum(schemaname),
418                                                                          0, 0, 0);
419                 if (!OidIsValid(namespaceId))
420                         elog(ERROR, "Namespace \"%s\" does not exist",
421                                  schemaname);
422         }
423         else
424         {
425                 /* flag to indicate we need namespace search */
426                 namespaceId = InvalidOid;
427         }
428
429         /* Search syscache by name and (optionally) nargs only */
430         if (nargs >= 0)
431                 catlist = SearchSysCacheList(PROCNAMENSP, 2,
432                                                                          CStringGetDatum(funcname),
433                                                                          Int16GetDatum(nargs),
434                                                                          0, 0);
435         else
436                 catlist = SearchSysCacheList(PROCNAMENSP, 1,
437                                                                          CStringGetDatum(funcname),
438                                                                          0, 0, 0);
439
440         for (i = 0; i < catlist->n_members; i++)
441         {
442                 HeapTuple       proctup = &catlist->members[i]->tuple;
443                 Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
444                 int                     pathpos = 0;
445                 FuncCandidateList newResult;
446
447                 nargs = procform->pronargs;
448
449                 if (OidIsValid(namespaceId))
450                 {
451                         /* Consider only procs in specified namespace */
452                         if (procform->pronamespace != namespaceId)
453                                 continue;
454                         /* No need to check args, they must all be different */
455                 }
456                 else
457                 {
458                         /* Consider only procs that are in the search path */
459                         if (pathContainsSystemNamespace ||
460                                 !IsSystemNamespace(procform->pronamespace))
461                         {
462                                 List       *nsp;
463
464                                 foreach(nsp, namespaceSearchPath)
465                                 {
466                                         pathpos++;
467                                         if (procform->pronamespace == (Oid) lfirsti(nsp))
468                                                 break;
469                                 }
470                                 if (nsp == NIL)
471                                         continue;       /* proc is not in search path */
472                         }
473
474                         /*
475                          * Okay, it's in the search path, but does it have the same
476                          * arguments as something we already accepted?  If so, keep
477                          * only the one that appears earlier in the search path.
478                          *
479                          * If we have an ordered list from SearchSysCacheList (the
480                          * normal case), then any conflicting proc must immediately
481                          * adjoin this one in the list, so we only need to look at
482                          * the newest result item.  If we have an unordered list,
483                          * we have to scan the whole result list.
484                          */
485                         if (resultList)
486                         {
487                                 FuncCandidateList       prevResult;
488
489                                 if (catlist->ordered)
490                                 {
491                                         if (nargs == resultList->nargs &&
492                                                 memcmp(procform->proargtypes, resultList->args,
493                                                            nargs * sizeof(Oid)) == 0)
494                                                 prevResult = resultList;
495                                         else
496                                                 prevResult = NULL;
497                                 }
498                                 else
499                                 {
500                                         for (prevResult = resultList;
501                                                  prevResult;
502                                                  prevResult = prevResult->next)
503                                         {
504                                                 if (nargs == prevResult->nargs &&
505                                                         memcmp(procform->proargtypes, prevResult->args,
506                                                                    nargs * sizeof(Oid)) == 0)
507                                                         break;
508                                         }
509                                 }
510                                 if (prevResult)
511                                 {
512                                         /* We have a match with a previous result */
513                                         Assert(pathpos != prevResult->pathpos);
514                                         if (pathpos > prevResult->pathpos)
515                                                 continue; /* keep previous result */
516                                         /* replace previous result */
517                                         prevResult->pathpos = pathpos;
518                                         prevResult->oid = proctup->t_data->t_oid;
519                                         continue;       /* args are same, of course */
520                                 }
521                         }
522                 }
523
524                 /*
525                  * Okay to add it to result list
526                  */
527                 newResult = (FuncCandidateList)
528                         palloc(sizeof(struct _FuncCandidateList) - sizeof(Oid)
529                                    + nargs * sizeof(Oid));
530                 newResult->pathpos = pathpos;
531                 newResult->oid = proctup->t_data->t_oid;
532                 newResult->nargs = nargs;
533                 memcpy(newResult->args, procform->proargtypes, nargs * sizeof(Oid));
534
535                 newResult->next = resultList;
536                 resultList = newResult;
537         }
538
539         ReleaseSysCacheList(catlist);
540
541         return resultList;
542 }
543
544 /*
545  * OpernameGetCandidates
546  *              Given a possibly-qualified operator name and operator kind,
547  *              retrieve a list of the possible matches.
548  *
549  * If oprkind is '\0', we return all operators matching the given name,
550  * regardless of arguments.
551  *
552  * We search a single namespace if the operator name is qualified, else
553  * all namespaces in the search path.  The return list will never contain
554  * multiple entries with identical argument lists --- in the multiple-
555  * namespace case, we arrange for entries in earlier namespaces to mask
556  * identical entries in later namespaces.
557  *
558  * The returned items always have two args[] entries --- one or the other
559  * will be InvalidOid for a prefix or postfix oprkind.  nargs is 2, too.
560  */
561 FuncCandidateList
562 OpernameGetCandidates(List *names, char oprkind)
563 {
564         FuncCandidateList resultList = NULL;
565         char       *catalogname;
566         char       *schemaname = NULL;
567         char       *opername = NULL;
568         Oid                     namespaceId;
569         CatCList   *catlist;
570         int                     i;
571
572         /* deconstruct the name list */
573         switch (length(names))
574         {
575                 case 1:
576                         opername = strVal(lfirst(names));
577                         break;
578                 case 2:
579                         schemaname = strVal(lfirst(names));
580                         opername = strVal(lsecond(names));
581                         break;
582                 case 3:
583                         catalogname = strVal(lfirst(names));
584                         schemaname = strVal(lsecond(names));
585                         opername = strVal(lfirst(lnext(lnext(names))));
586                         /*
587                          * We check the catalog name and then ignore it.
588                          */
589                         if (strcmp(catalogname, DatabaseName) != 0)
590                                 elog(ERROR, "Cross-database references are not implemented");
591                         break;
592                 default:
593                         elog(ERROR, "Improper qualified name (too many dotted names)");
594                         break;
595         }
596
597         if (schemaname)
598         {
599                 /* use exact schema given */
600                 namespaceId = GetSysCacheOid(NAMESPACENAME,
601                                                                          CStringGetDatum(schemaname),
602                                                                          0, 0, 0);
603                 if (!OidIsValid(namespaceId))
604                         elog(ERROR, "Namespace \"%s\" does not exist",
605                                  schemaname);
606         }
607         else
608         {
609                 /* flag to indicate we need namespace search */
610                 namespaceId = InvalidOid;
611         }
612
613         /* Search syscache by name only */
614         catlist = SearchSysCacheList(OPERNAMENSP, 1,
615                                                                  CStringGetDatum(opername),
616                                                                  0, 0, 0);
617
618         for (i = 0; i < catlist->n_members; i++)
619         {
620                 HeapTuple       opertup = &catlist->members[i]->tuple;
621                 Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup);
622                 int                     pathpos = 0;
623                 FuncCandidateList newResult;
624
625                 /* Ignore operators of wrong kind, if specific kind requested */
626                 if (oprkind && operform->oprkind != oprkind)
627                         continue;
628
629                 if (OidIsValid(namespaceId))
630                 {
631                         /* Consider only opers in specified namespace */
632                         if (operform->oprnamespace != namespaceId)
633                                 continue;
634                         /* No need to check args, they must all be different */
635                 }
636                 else
637                 {
638                         /* Consider only opers that are in the search path */
639                         if (pathContainsSystemNamespace ||
640                                 !IsSystemNamespace(operform->oprnamespace))
641                         {
642                                 List       *nsp;
643
644                                 foreach(nsp, namespaceSearchPath)
645                                 {
646                                         pathpos++;
647                                         if (operform->oprnamespace == (Oid) lfirsti(nsp))
648                                                 break;
649                                 }
650                                 if (nsp == NIL)
651                                         continue;       /* oper is not in search path */
652                         }
653
654                         /*
655                          * Okay, it's in the search path, but does it have the same
656                          * arguments as something we already accepted?  If so, keep
657                          * only the one that appears earlier in the search path.
658                          *
659                          * If we have an ordered list from SearchSysCacheList (the
660                          * normal case), then any conflicting oper must immediately
661                          * adjoin this one in the list, so we only need to look at
662                          * the newest result item.  If we have an unordered list,
663                          * we have to scan the whole result list.
664                          */
665                         if (resultList)
666                         {
667                                 FuncCandidateList       prevResult;
668
669                                 if (catlist->ordered)
670                                 {
671                                         if (operform->oprleft == resultList->args[0] &&
672                                                 operform->oprright == resultList->args[1])
673                                                 prevResult = resultList;
674                                         else
675                                                 prevResult = NULL;
676                                 }
677                                 else
678                                 {
679                                         for (prevResult = resultList;
680                                                  prevResult;
681                                                  prevResult = prevResult->next)
682                                         {
683                                                 if (operform->oprleft == prevResult->args[0] &&
684                                                         operform->oprright == prevResult->args[1])
685                                                         break;
686                                         }
687                                 }
688                                 if (prevResult)
689                                 {
690                                         /* We have a match with a previous result */
691                                         Assert(pathpos != prevResult->pathpos);
692                                         if (pathpos > prevResult->pathpos)
693                                                 continue; /* keep previous result */
694                                         /* replace previous result */
695                                         prevResult->pathpos = pathpos;
696                                         prevResult->oid = opertup->t_data->t_oid;
697                                         continue;       /* args are same, of course */
698                                 }
699                         }
700                 }
701
702                 /*
703                  * Okay to add it to result list
704                  */
705                 newResult = (FuncCandidateList)
706                         palloc(sizeof(struct _FuncCandidateList) + sizeof(Oid));
707                 newResult->pathpos = pathpos;
708                 newResult->oid = opertup->t_data->t_oid;
709                 newResult->nargs = 2;
710                 newResult->args[0] = operform->oprleft;
711                 newResult->args[1] = operform->oprright;
712                 newResult->next = resultList;
713                 resultList = newResult;
714         }
715
716         ReleaseSysCacheList(catlist);
717
718         return resultList;
719 }
720
721 /*
722  * OpclassGetCandidates
723  *              Given an index access method OID, retrieve a list of all the
724  *              opclasses for that AM that are visible in the search path.
725  *
726  * NOTE: the opcname_tmp field in the returned structs should not be used
727  * by callers, because it points at syscache entries that we release at
728  * the end of this routine.  If any callers needed the name information,
729  * we could pstrdup() the names ... but at present it'd be wasteful.
730  */
731 OpclassCandidateList
732 OpclassGetCandidates(Oid amid)
733 {
734         OpclassCandidateList resultList = NULL;
735         CatCList   *catlist;
736         int                     i;
737
738         /* Search syscache by AM OID only */
739         catlist = SearchSysCacheList(CLAAMNAMENSP, 1,
740                                                                  ObjectIdGetDatum(amid),
741                                                                  0, 0, 0);
742
743         for (i = 0; i < catlist->n_members; i++)
744         {
745                 HeapTuple       opctup = &catlist->members[i]->tuple;
746                 Form_pg_opclass opcform = (Form_pg_opclass) GETSTRUCT(opctup);
747                 int                     pathpos = 0;
748                 OpclassCandidateList newResult;
749
750                 /* Consider only opclasses that are in the search path */
751                 if (pathContainsSystemNamespace ||
752                         !IsSystemNamespace(opcform->opcnamespace))
753                 {
754                         List       *nsp;
755
756                         foreach(nsp, namespaceSearchPath)
757                         {
758                                 pathpos++;
759                                 if (opcform->opcnamespace == (Oid) lfirsti(nsp))
760                                         break;
761                         }
762                         if (nsp == NIL)
763                                 continue;               /* opclass is not in search path */
764                 }
765
766                 /*
767                  * Okay, it's in the search path, but does it have the same name
768                  * as something we already accepted?  If so, keep
769                  * only the one that appears earlier in the search path.
770                  *
771                  * If we have an ordered list from SearchSysCacheList (the
772                  * normal case), then any conflicting opclass must immediately
773                  * adjoin this one in the list, so we only need to look at
774                  * the newest result item.  If we have an unordered list,
775                  * we have to scan the whole result list.
776                  */
777                 if (resultList)
778                 {
779                         OpclassCandidateList    prevResult;
780
781                         if (catlist->ordered)
782                         {
783                                 if (strcmp(NameStr(opcform->opcname),
784                                                    resultList->opcname_tmp) == 0)
785                                         prevResult = resultList;
786                                 else
787                                         prevResult = NULL;
788                         }
789                         else
790                         {
791                                 for (prevResult = resultList;
792                                          prevResult;
793                                          prevResult = prevResult->next)
794                                 {
795                                         if (strcmp(NameStr(opcform->opcname),
796                                                            prevResult->opcname_tmp) == 0)
797                                                 break;
798                                 }
799                         }
800                         if (prevResult)
801                         {
802                                 /* We have a match with a previous result */
803                                 Assert(pathpos != prevResult->pathpos);
804                                 if (pathpos > prevResult->pathpos)
805                                         continue; /* keep previous result */
806                                 /* replace previous result */
807                                 prevResult->opcname_tmp = NameStr(opcform->opcname);
808                                 prevResult->pathpos = pathpos;
809                                 prevResult->oid = opctup->t_data->t_oid;
810                                 prevResult->opcintype = opcform->opcintype;
811                                 prevResult->opcdefault = opcform->opcdefault;
812                                 prevResult->opckeytype = opcform->opckeytype;
813                                 continue;
814                         }
815                 }
816
817                 /*
818                  * Okay to add it to result list
819                  */
820                 newResult = (OpclassCandidateList)
821                         palloc(sizeof(struct _OpclassCandidateList));
822                 newResult->opcname_tmp = NameStr(opcform->opcname);
823                 newResult->pathpos = pathpos;
824                 newResult->oid = opctup->t_data->t_oid;
825                 newResult->opcintype = opcform->opcintype;
826                 newResult->opcdefault = opcform->opcdefault;
827                 newResult->opckeytype = opcform->opckeytype;
828                 newResult->next = resultList;
829                 resultList = newResult;
830         }
831
832         ReleaseSysCacheList(catlist);
833
834         return resultList;
835 }
836
837
838 /*
839  * QualifiedNameGetCreationNamespace
840  *              Given a possibly-qualified name for an object (in List-of-Values
841  *              format), determine what namespace the object should be created in.
842  *              Also extract and return the object name (last component of list).
843  *
844  * This is *not* used for tables.  Hence, the TEMP table namespace is
845  * never selected as the creation target.
846  */
847 Oid
848 QualifiedNameGetCreationNamespace(List *names, char **objname_p)
849 {
850         char       *catalogname;
851         char       *schemaname = NULL;
852         char       *objname = NULL;
853         Oid                     namespaceId;
854
855         /* deconstruct the name list */
856         switch (length(names))
857         {
858                 case 1:
859                         objname = strVal(lfirst(names));
860                         break;
861                 case 2:
862                         schemaname = strVal(lfirst(names));
863                         objname = strVal(lsecond(names));
864                         break;
865                 case 3:
866                         catalogname = strVal(lfirst(names));
867                         schemaname = strVal(lsecond(names));
868                         objname = strVal(lfirst(lnext(lnext(names))));
869                         /*
870                          * We check the catalog name and then ignore it.
871                          */
872                         if (strcmp(catalogname, DatabaseName) != 0)
873                                 elog(ERROR, "Cross-database references are not implemented");
874                         break;
875                 default:
876                         elog(ERROR, "Improper qualified name (too many dotted names)");
877                         break;
878         }
879
880         if (schemaname)
881         {
882                 /* use exact schema given */
883                 namespaceId = GetSysCacheOid(NAMESPACENAME,
884                                                                          CStringGetDatum(schemaname),
885                                                                          0, 0, 0);
886                 if (!OidIsValid(namespaceId))
887                         elog(ERROR, "Namespace \"%s\" does not exist",
888                                  schemaname);
889         }
890         else
891         {
892                 /* use the default creation namespace */
893                 namespaceId = defaultCreationNamespace;
894                 if (!OidIsValid(namespaceId))
895                         elog(ERROR, "No namespace has been selected to create in");
896         }
897
898         *objname_p = objname;
899         return namespaceId;
900 }
901
902 /*
903  * makeRangeVarFromNameList
904  *              Utility routine to convert a qualified-name list into RangeVar form.
905  */
906 RangeVar *
907 makeRangeVarFromNameList(List *names)
908 {
909         RangeVar   *rel = makeRangeVar(NULL, NULL);
910
911         switch (length(names))
912         {
913                 case 1:
914                         rel->relname = strVal(lfirst(names));
915                         break;
916                 case 2:
917                         rel->schemaname = strVal(lfirst(names));
918                         rel->relname = strVal(lsecond(names));
919                         break;
920                 case 3:
921                         rel->catalogname = strVal(lfirst(names));
922                         rel->schemaname = strVal(lsecond(names));
923                         rel->relname = strVal(lfirst(lnext(lnext(names))));
924                         break;
925                 default:
926                         elog(ERROR, "Improper relation name (too many dotted names)");
927                         break;
928         }
929
930         return rel;
931 }
932
933 /*
934  * NameListToString
935  *              Utility routine to convert a qualified-name list into a string.
936  *              Used primarily to form error messages.
937  */
938 char *
939 NameListToString(List *names)
940 {
941         StringInfoData string;
942         List            *l;
943
944         initStringInfo(&string);
945
946         foreach(l, names)
947         {
948                 if (l != names)
949                         appendStringInfoChar(&string, '.');
950                 appendStringInfo(&string, "%s", strVal(lfirst(l)));
951         }
952
953         return string.data;
954 }
955
956 /*
957  * isTempNamespace - is the given namespace my temporary-table namespace?
958  */
959 bool
960 isTempNamespace(Oid namespaceId)
961 {
962         if (OidIsValid(myTempNamespace) && myTempNamespace == namespaceId)
963                 return true;
964         return false;
965 }
966
967 /*
968  * GetTempTableNamespace
969  *              Initialize temp table namespace on first use in a particular backend
970  */
971 static Oid
972 GetTempTableNamespace(void)
973 {
974         char            namespaceName[NAMEDATALEN];
975         Oid                     namespaceId;
976
977         snprintf(namespaceName, NAMEDATALEN, "pg_temp_%d", MyBackendId);
978
979         namespaceId = GetSysCacheOid(NAMESPACENAME,
980                                                                  CStringGetDatum(namespaceName),
981                                                                  0, 0, 0);
982         if (!OidIsValid(namespaceId))
983         {
984                 /*
985                  * First use of this temp namespace in this database; create it.
986                  * The temp namespaces are always owned by the superuser.
987                  */
988                 namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_USESYSID);
989                 /* Advance command counter to make namespace visible */
990                 CommandCounterIncrement();
991         }
992         else
993         {
994                 /*
995                  * If the namespace already exists, clean it out (in case the
996                  * former owner crashed without doing so).
997                  */
998                 RemoveTempRelations(namespaceId);
999         }
1000
1001         /*
1002          * Register exit callback to clean out temp tables at backend shutdown.
1003          */
1004         on_shmem_exit(RemoveTempRelationsCallback, 0);
1005
1006         return namespaceId;
1007 }
1008
1009 /*
1010  * Remove all relations in the specified temp namespace.
1011  *
1012  * This is called at backend shutdown (if we made any temp relations).
1013  * It is also called when we begin using a pre-existing temp namespace,
1014  * in order to clean out any relations that might have been created by
1015  * a crashed backend.
1016  */
1017 static void
1018 RemoveTempRelations(Oid tempNamespaceId)
1019 {
1020         List       *tempRelList;
1021         List       *constraintList;
1022         List       *lptr;
1023
1024         /* Get a list of relations to delete */
1025         tempRelList = FindTempRelations(tempNamespaceId);
1026
1027         if (tempRelList == NIL)
1028                 return;                                 /* nothing to do */
1029
1030         /* If more than one, sort them to respect any deletion-order constraints */
1031         if (length(tempRelList) > 1)
1032         {
1033                 constraintList = FindDeletionConstraints(tempRelList);
1034                 if (constraintList != NIL)
1035                         tempRelList = TopoSortRels(tempRelList, constraintList);
1036         }
1037
1038         /* Scan the list and delete all entries */
1039         foreach(lptr, tempRelList)
1040         {
1041                 Oid                     reloid = (Oid) lfirsti(lptr);
1042
1043                 heap_drop_with_catalog(reloid, true);
1044                 /*
1045                  * Advance cmd counter to make catalog changes visible, in case
1046                  * a later entry depends on this one.
1047                  */
1048                 CommandCounterIncrement();
1049         }
1050 }
1051
1052 /*
1053  * Find all relations in the specified temp namespace.
1054  *
1055  * Returns a list of relation OIDs.
1056  */
1057 static List *
1058 FindTempRelations(Oid tempNamespaceId)
1059 {
1060         List       *tempRelList = NIL;
1061         Relation        pgclass;
1062         HeapScanDesc scan;
1063         HeapTuple       tuple;
1064         ScanKeyData key;
1065
1066         /*
1067          * Scan pg_class to find all the relations in the target namespace.
1068          * Ignore indexes, though, on the assumption that they'll go away
1069          * when their tables are deleted.
1070          */
1071         ScanKeyEntryInitialize(&key, 0x0,
1072                                                    Anum_pg_class_relnamespace,
1073                                                    F_OIDEQ,
1074                                                    ObjectIdGetDatum(tempNamespaceId));
1075
1076         pgclass = heap_openr(RelationRelationName, AccessShareLock);
1077         scan = heap_beginscan(pgclass, false, SnapshotNow, 1, &key);
1078
1079         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
1080         {
1081                 switch (((Form_pg_class) GETSTRUCT(tuple))->relkind)
1082                 {
1083                         case RELKIND_RELATION:
1084                         case RELKIND_SEQUENCE:
1085                         case RELKIND_VIEW:
1086                                 tempRelList = lconsi(tuple->t_data->t_oid, tempRelList);
1087                                 break;
1088                         default:
1089                                 break;
1090                 }
1091         }
1092
1093         heap_endscan(scan);
1094         heap_close(pgclass, AccessShareLock);
1095
1096         return tempRelList;
1097 }
1098
1099 /*
1100  * Find deletion-order constraints involving the given relation OIDs.
1101  *
1102  * Returns a list of DelConstraint objects.
1103  */
1104 static List *
1105 FindDeletionConstraints(List *relOids)
1106 {
1107         List       *constraintList = NIL;
1108         Relation        inheritsrel;
1109         HeapScanDesc scan;
1110         HeapTuple       tuple;
1111
1112         /*
1113          * Scan pg_inherits to find parents and children that are in the list.
1114          */
1115         inheritsrel = heap_openr(InheritsRelationName, AccessShareLock);
1116         scan = heap_beginscan(inheritsrel, 0, SnapshotNow, 0, NULL);
1117
1118         while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
1119         {
1120                 Oid             inhrelid = ((Form_pg_inherits) GETSTRUCT(tuple))->inhrelid;
1121                 Oid             inhparent = ((Form_pg_inherits) GETSTRUCT(tuple))->inhparent;
1122
1123                 if (intMember(inhrelid, relOids) && intMember(inhparent, relOids))
1124                 {
1125                         DelConstraint  *item;
1126
1127                         item = (DelConstraint *) palloc(sizeof(DelConstraint));
1128                         item->referencer = inhrelid;
1129                         item->referencee = inhparent;
1130                         constraintList = lcons(item, constraintList);
1131                 }
1132         }
1133
1134         heap_endscan(scan);
1135         heap_close(inheritsrel, AccessShareLock);
1136
1137         return constraintList;
1138 }
1139
1140 /*
1141  * TopoSortRels -- topological sort of a list of rels to delete
1142  *
1143  * This is a lot simpler and slower than, for example, the topological sort
1144  * algorithm shown in Knuth's Volume 1.  However, we are not likely to be
1145  * working with more than a few constraints, so the apparent slowness of the
1146  * algorithm won't really matter.
1147  */
1148 static List *
1149 TopoSortRels(List *relOids, List *constraintList)
1150 {
1151         int                     queue_size = length(relOids);
1152         Oid                *rels;
1153         int                *beforeConstraints;
1154         DelConstraint **afterConstraints;
1155         List       *resultList = NIL;
1156         List       *lptr;
1157         int                     i,
1158                                 j,
1159                                 k,
1160                                 last;
1161
1162         /* Allocate workspace */
1163         rels = (Oid *) palloc(queue_size * sizeof(Oid));
1164         beforeConstraints = (int *) palloc(queue_size * sizeof(int));
1165         afterConstraints = (DelConstraint **)
1166                 palloc(queue_size * sizeof(DelConstraint*));
1167
1168         /* Build an array of the target relation OIDs */
1169         i = 0;
1170         foreach(lptr, relOids)
1171         {
1172                 rels[i++] = (Oid) lfirsti(lptr);
1173         }
1174
1175         /*
1176          * Scan the constraints, and for each rel in the array, generate a
1177          * count of the number of constraints that say it must be before
1178          * something else, plus a list of the constraints that say it must be
1179          * after something else. The count for the j'th rel is stored in
1180          * beforeConstraints[j], and the head of its list in
1181          * afterConstraints[j].  Each constraint stores its list link in
1182          * its link field (note any constraint will be in just one list).
1183          * The array index for the before-rel of each constraint is
1184          * remembered in the constraint's pred field.
1185          */
1186         MemSet(beforeConstraints, 0, queue_size * sizeof(int));
1187         MemSet(afterConstraints, 0, queue_size * sizeof(DelConstraint*));
1188         foreach(lptr, constraintList)
1189         {
1190                 DelConstraint  *constraint = (DelConstraint *) lfirst(lptr);
1191                 Oid                     rel;
1192
1193                 /* Find the referencer rel in the array */
1194                 rel = constraint->referencer;
1195                 for (j = queue_size; --j >= 0;)
1196                 {
1197                         if (rels[j] == rel)
1198                                 break;
1199                 }
1200                 Assert(j >= 0);                 /* should have found a match */
1201                 /* Find the referencee rel in the array */
1202                 rel = constraint->referencee;
1203                 for (k = queue_size; --k >= 0;)
1204                 {
1205                         if (rels[k] == rel)
1206                                 break;
1207                 }
1208                 Assert(k >= 0);                 /* should have found a match */
1209                 beforeConstraints[j]++; /* referencer must come before */
1210                 /* add this constraint to list of after-constraints for referencee */
1211                 constraint->pred = j;
1212                 constraint->link = afterConstraints[k];
1213                 afterConstraints[k] = constraint;
1214         }
1215         /*--------------------
1216          * Now scan the rels array backwards.   At each step, output the
1217          * last rel that has no remaining before-constraints, and decrease
1218          * the beforeConstraints count of each of the rels it was constrained
1219          * against.  (This is the right order since we are building the result
1220          * list back-to-front.)
1221          * i = counter for number of rels left to output
1222          * j = search index for rels[]
1223          * dc = temp for scanning constraint list for rel j
1224          * last = last valid index in rels (avoid redundant searches)
1225          *--------------------
1226          */
1227         last = queue_size - 1;
1228         for (i = queue_size; --i >= 0;)
1229         {
1230                 DelConstraint  *dc;
1231
1232                 /* Find next candidate to output */
1233                 while (rels[last] == InvalidOid)
1234                         last--;
1235                 for (j = last; j >= 0; j--)
1236                 {
1237                         if (rels[j] != InvalidOid && beforeConstraints[j] == 0)
1238                                 break;
1239                 }
1240                 /* If no available candidate, topological sort fails */
1241                 if (j < 0)
1242                         elog(ERROR, "TopoSortRels: failed to find a workable deletion ordering");
1243                 /* Output candidate, and mark it done by zeroing rels[] entry */
1244                 resultList = lconsi(rels[j], resultList);
1245                 rels[j] = InvalidOid;
1246                 /* Update beforeConstraints counts of its predecessors */
1247                 for (dc = afterConstraints[j]; dc; dc = dc->link)
1248                         beforeConstraints[dc->pred]--;
1249         }
1250
1251         /* Done */
1252         return resultList;
1253 }
1254
1255 /*
1256  * Callback to remove temp relations at backend exit.
1257  */
1258 static void
1259 RemoveTempRelationsCallback(void)
1260 {
1261         if (OidIsValid(myTempNamespace)) /* should always be true */
1262         {
1263                 /* Need to ensure we have a usable transaction. */
1264                 AbortOutOfAnyTransaction();
1265                 StartTransactionCommand();
1266
1267                 RemoveTempRelations(myTempNamespace);
1268
1269                 CommitTransactionCommand();
1270         }
1271 }
1272
1273 /*
1274  * Routines for handling the GUC variable 'search_path'.
1275  */
1276
1277 /* parse_hook: is proposed value valid? */
1278 bool
1279 check_search_path(const char *proposed)
1280 {
1281         char       *rawname;
1282         List       *namelist;
1283         List       *l;
1284
1285         /* Need a modifiable copy of string */
1286         rawname = pstrdup(proposed);
1287
1288         /* Parse string into list of identifiers */
1289         if (!SplitIdentifierString(rawname, ',', &namelist))
1290         {
1291                 /* syntax error in name list */
1292                 pfree(rawname);
1293                 freeList(namelist);
1294                 return false;
1295         }
1296
1297         /*
1298          * If we aren't inside a transaction, we cannot do database access so
1299          * cannot verify the individual names.  Must accept the list on faith.
1300          * (This case can happen, for example, when the postmaster reads a
1301          * search_path setting from postgresql.conf.)
1302          */
1303         if (!IsTransactionState())
1304         {
1305                 pfree(rawname);
1306                 freeList(namelist);
1307                 return true;
1308         }
1309
1310         /*
1311          * Verify that all the names are either valid namespace names or "$user".
1312          * (We do not require $user to correspond to a valid namespace; should we?)
1313          */
1314         foreach(l, namelist)
1315         {
1316                 char   *curname = (char *) lfirst(l);
1317
1318                 if (strcmp(curname, "$user") == 0)
1319                         continue;
1320                 if (!SearchSysCacheExists(NAMESPACENAME,
1321                                                                   CStringGetDatum(curname),
1322                                                                   0, 0, 0))
1323                 {
1324                         pfree(rawname);
1325                         freeList(namelist);
1326                         return false;
1327                 }
1328         }
1329
1330         pfree(rawname);
1331         freeList(namelist);
1332
1333         return true;
1334 }
1335
1336 /* assign_hook: do extra actions needed when assigning to search_path */
1337 void
1338 assign_search_path(const char *newval)
1339 {
1340         char       *rawname;
1341         List       *namelist;
1342         List       *oidlist;
1343         List       *newpath;
1344         List       *l;
1345         MemoryContext oldcxt;
1346
1347         /*
1348          * If we aren't inside a transaction, we cannot do database access so
1349          * cannot look up the names.  In this case, do nothing; the internal
1350          * search path will be fixed later by InitializeSearchPath.  (We assume
1351          * this situation can only happen in the postmaster or early in backend
1352          * startup.)
1353          */
1354         if (!IsTransactionState())
1355                 return;
1356
1357         /* Need a modifiable copy of string */
1358         rawname = pstrdup(newval);
1359
1360         /* Parse string into list of identifiers */
1361         if (!SplitIdentifierString(rawname, ',', &namelist))
1362         {
1363                 /* syntax error in name list */
1364                 /* this should not happen if GUC checked check_search_path */
1365                 elog(ERROR, "assign_search_path: invalid list syntax");
1366         }
1367
1368         /*
1369          * Convert the list of names to a list of OIDs.  If any names are not
1370          * recognizable, just leave them out of the list.  (This is our only
1371          * reasonable recourse when the already-accepted default is bogus.)
1372          */
1373         oidlist = NIL;
1374         foreach(l, namelist)
1375         {
1376                 char   *curname = (char *) lfirst(l);
1377                 Oid             namespaceId;
1378
1379                 if (strcmp(curname, "$user") == 0)
1380                 {
1381                         /* $user --- substitute namespace matching user name, if any */
1382                         HeapTuple       tuple;
1383
1384                         tuple = SearchSysCache(SHADOWSYSID,
1385                                                                    ObjectIdGetDatum(GetSessionUserId()),
1386                                                                    0, 0, 0);
1387                         if (HeapTupleIsValid(tuple))
1388                         {
1389                                 char   *uname;
1390
1391                                 uname = NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename);
1392                                 namespaceId = GetSysCacheOid(NAMESPACENAME,
1393                                                                                          CStringGetDatum(uname),
1394                                                                                          0, 0, 0);
1395                                 if (OidIsValid(namespaceId))
1396                                         oidlist = lappendi(oidlist, namespaceId);
1397                                 ReleaseSysCache(tuple);
1398                         }
1399                 }
1400                 else
1401                 {
1402                         /* normal namespace reference */
1403                         namespaceId = GetSysCacheOid(NAMESPACENAME,
1404                                                                                  CStringGetDatum(curname),
1405                                                                                  0, 0, 0);
1406                         if (OidIsValid(namespaceId))
1407                                 oidlist = lappendi(oidlist, namespaceId);
1408                 }
1409         }
1410
1411         /*
1412          * Now that we've successfully built the new list of namespace OIDs,
1413          * save it in permanent storage.
1414          */
1415         oldcxt = MemoryContextSwitchTo(TopMemoryContext);
1416         newpath = listCopy(oidlist);
1417         MemoryContextSwitchTo(oldcxt);
1418
1419         /* Now safe to assign to state variable. */
1420         freeList(namespaceSearchPath);
1421         namespaceSearchPath = newpath;
1422
1423         /*
1424          * Update info derived from search path.
1425          */
1426         pathContainsSystemNamespace = intMember(PG_CATALOG_NAMESPACE,
1427                                                                                         namespaceSearchPath);
1428
1429         if (namespaceSearchPath == NIL)
1430                 defaultCreationNamespace = InvalidOid;
1431         else
1432                 defaultCreationNamespace = (Oid) lfirsti(namespaceSearchPath);
1433
1434         /* Clean up. */
1435         pfree(rawname);
1436         freeList(namelist);
1437         freeList(oidlist);
1438 }
1439
1440 /*
1441  * InitializeSearchPath: initialize module during InitPostgres.
1442  *
1443  * This is called after we are up enough to be able to do catalog lookups.
1444  */
1445 void
1446 InitializeSearchPath(void)
1447 {
1448         if (IsBootstrapProcessingMode())
1449         {
1450                 /*
1451                  * In bootstrap mode, the search path must be 'pg_catalog' so that
1452                  * tables are created in the proper namespace; ignore the GUC setting.
1453                  */
1454                 MemoryContext oldcxt;
1455
1456                 oldcxt = MemoryContextSwitchTo(TopMemoryContext);
1457                 namespaceSearchPath = makeListi1(PG_CATALOG_NAMESPACE);
1458                 MemoryContextSwitchTo(oldcxt);
1459                 pathContainsSystemNamespace = true;
1460                 defaultCreationNamespace = PG_CATALOG_NAMESPACE;
1461         }
1462         else
1463         {
1464                 /*
1465                  * If a search path setting was provided before we were able to
1466                  * execute lookups, establish the internal search path now.
1467                  */
1468                 if (namespace_search_path && *namespace_search_path &&
1469                         namespaceSearchPath == NIL)
1470                         assign_search_path(namespace_search_path);
1471         }
1472 }