PostgreSQL Source Code  git master
dropcmds.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * dropcmds.c
4  * handle various "DROP" operations
5  *
6  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  * src/backend/commands/dropcmds.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16 
17 #include "access/table.h"
18 #include "access/xact.h"
19 #include "catalog/dependency.h"
20 #include "catalog/namespace.h"
21 #include "catalog/objectaddress.h"
22 #include "catalog/pg_namespace.h"
23 #include "catalog/pg_proc.h"
24 #include "commands/defrem.h"
25 #include "miscadmin.h"
26 #include "parser/parse_type.h"
27 #include "utils/acl.h"
28 #include "utils/lsyscache.h"
29 
30 
31 static void does_not_exist_skipping(ObjectType objtype,
32  Node *object);
33 static bool owningrel_does_not_exist_skipping(List *object,
34  const char **msg, char **name);
35 static bool schema_does_not_exist_skipping(List *object,
36  const char **msg, char **name);
38  const char **msg, char **name);
39 
40 
41 /*
42  * Drop one or more objects.
43  *
44  * We don't currently handle all object types here. Relations, for example,
45  * require special handling, because (for example) indexes have additional
46  * locking requirements.
47  *
48  * We look up all the objects first, and then delete them in a single
49  * performMultipleDeletions() call. This avoids unnecessary DROP RESTRICT
50  * errors if there are dependencies between them.
51  */
52 void
54 {
55  ObjectAddresses *objects;
56  ListCell *cell1;
57 
58  objects = new_object_addresses();
59 
60  foreach(cell1, stmt->objects)
61  {
62  ObjectAddress address;
63  Node *object = lfirst(cell1);
64  Relation relation = NULL;
65  Oid namespaceId;
66 
67  /* Get an ObjectAddress for the object. */
68  address = get_object_address(stmt->removeType,
69  object,
70  &relation,
72  stmt->missing_ok);
73 
74  /*
75  * Issue NOTICE if supplied object was not found. Note this is only
76  * relevant in the missing_ok case, because otherwise
77  * get_object_address would have thrown an error.
78  */
79  if (!OidIsValid(address.objectId))
80  {
81  Assert(stmt->missing_ok);
82  does_not_exist_skipping(stmt->removeType, object);
83  continue;
84  }
85 
86  /*
87  * Although COMMENT ON FUNCTION, SECURITY LABEL ON FUNCTION, etc. are
88  * happy to operate on an aggregate as on any other function, we have
89  * historically not allowed this for DROP FUNCTION.
90  */
91  if (stmt->removeType == OBJECT_FUNCTION)
92  {
93  if (get_func_prokind(address.objectId) == PROKIND_AGGREGATE)
94  ereport(ERROR,
95  (errcode(ERRCODE_WRONG_OBJECT_TYPE),
96  errmsg("\"%s\" is an aggregate function",
97  NameListToString(castNode(ObjectWithArgs, object)->objname)),
98  errhint("Use DROP AGGREGATE to drop aggregate functions.")));
99  }
100 
101  /* Check permissions. */
102  namespaceId = get_object_namespace(&address);
103  if (!OidIsValid(namespaceId) ||
104  !object_ownercheck(NamespaceRelationId, namespaceId, GetUserId()))
105  check_object_ownership(GetUserId(), stmt->removeType, address,
106  object, relation);
107 
108  /*
109  * Make note if a temporary namespace has been accessed in this
110  * transaction.
111  */
112  if (OidIsValid(namespaceId) && isTempNamespace(namespaceId))
114 
115  /* Release any relcache reference count, but keep lock until commit. */
116  if (relation)
117  table_close(relation, NoLock);
118 
119  add_exact_object_address(&address, objects);
120  }
121 
122  /* Here we really delete them. */
123  performMultipleDeletions(objects, stmt->behavior, 0);
124 
125  free_object_addresses(objects);
126 }
127 
128 /*
129  * owningrel_does_not_exist_skipping
130  * Subroutine for RemoveObjects
131  *
132  * After determining that a specification for a rule or trigger returns that
133  * the specified object does not exist, test whether its owning relation, and
134  * its schema, exist or not; if they do, return false --- the trigger or rule
135  * itself is missing instead. If the owning relation or its schema do not
136  * exist, fill the error message format string and name, and return true.
137  */
138 static bool
139 owningrel_does_not_exist_skipping(List *object, const char **msg, char **name)
140 {
141  List *parent_object;
142  RangeVar *parent_rel;
143 
144  parent_object = list_copy_head(object, list_length(object) - 1);
145 
146  if (schema_does_not_exist_skipping(parent_object, msg, name))
147  return true;
148 
149  parent_rel = makeRangeVarFromNameList(parent_object);
150 
151  if (!OidIsValid(RangeVarGetRelid(parent_rel, NoLock, true)))
152  {
153  *msg = gettext_noop("relation \"%s\" does not exist, skipping");
154  *name = NameListToString(parent_object);
155 
156  return true;
157  }
158 
159  return false;
160 }
161 
162 /*
163  * schema_does_not_exist_skipping
164  * Subroutine for RemoveObjects
165  *
166  * After determining that a specification for a schema-qualifiable object
167  * refers to an object that does not exist, test whether the specified schema
168  * exists or not. If no schema was specified, or if the schema does exist,
169  * return false -- the object itself is missing instead. If the specified
170  * schema does not exist, fill the error message format string and the
171  * specified schema name, and return true.
172  */
173 static bool
174 schema_does_not_exist_skipping(List *object, const char **msg, char **name)
175 {
176  RangeVar *rel;
177 
178  rel = makeRangeVarFromNameList(object);
179 
180  if (rel->schemaname != NULL &&
182  {
183  *msg = gettext_noop("schema \"%s\" does not exist, skipping");
184  *name = rel->schemaname;
185 
186  return true;
187  }
188 
189  return false;
190 }
191 
192 /*
193  * type_in_list_does_not_exist_skipping
194  * Subroutine for RemoveObjects
195  *
196  * After determining that a specification for a function, cast, aggregate or
197  * operator returns that the specified object does not exist, test whether the
198  * involved datatypes, and their schemas, exist or not; if they do, return
199  * false --- the original object itself is missing instead. If the datatypes
200  * or schemas do not exist, fill the error message format string and the
201  * missing name, and return true.
202  *
203  * First parameter is a list of TypeNames.
204  */
205 static bool
207  char **name)
208 {
209  ListCell *l;
210 
211  foreach(l, typenames)
212  {
213  TypeName *typeName = lfirst_node(TypeName, l);
214 
215  if (typeName != NULL)
216  {
217  if (!OidIsValid(LookupTypeNameOid(NULL, typeName, true)))
218  {
219  /* type doesn't exist, try to find why */
220  if (schema_does_not_exist_skipping(typeName->names, msg, name))
221  return true;
222 
223  *msg = gettext_noop("type \"%s\" does not exist, skipping");
224  *name = TypeNameToString(typeName);
225 
226  return true;
227  }
228  }
229  }
230 
231  return false;
232 }
233 
234 /*
235  * does_not_exist_skipping
236  * Subroutine for RemoveObjects
237  *
238  * Generate a NOTICE stating that the named object was not found, and is
239  * being skipped. This is only relevant when "IF EXISTS" is used; otherwise,
240  * get_object_address() in RemoveObjects would have thrown an ERROR.
241  */
242 static void
244 {
245  const char *msg = NULL;
246  char *name = NULL;
247  char *args = NULL;
248 
249  switch (objtype)
250  {
252  msg = gettext_noop("access method \"%s\" does not exist, skipping");
253  name = strVal(object);
254  break;
255  case OBJECT_TYPE:
256  case OBJECT_DOMAIN:
257  {
258  TypeName *typ = castNode(TypeName, object);
259 
260  if (!schema_does_not_exist_skipping(typ->names, &msg, &name))
261  {
262  msg = gettext_noop("type \"%s\" does not exist, skipping");
263  name = TypeNameToString(typ);
264  }
265  }
266  break;
267  case OBJECT_COLLATION:
268  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
269  {
270  msg = gettext_noop("collation \"%s\" does not exist, skipping");
271  name = NameListToString(castNode(List, object));
272  }
273  break;
274  case OBJECT_CONVERSION:
275  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
276  {
277  msg = gettext_noop("conversion \"%s\" does not exist, skipping");
278  name = NameListToString(castNode(List, object));
279  }
280  break;
281  case OBJECT_SCHEMA:
282  msg = gettext_noop("schema \"%s\" does not exist, skipping");
283  name = strVal(object);
284  break;
286  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
287  {
288  msg = gettext_noop("statistics object \"%s\" does not exist, skipping");
289  name = NameListToString(castNode(List, object));
290  }
291  break;
292  case OBJECT_TSPARSER:
293  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
294  {
295  msg = gettext_noop("text search parser \"%s\" does not exist, skipping");
296  name = NameListToString(castNode(List, object));
297  }
298  break;
299  case OBJECT_TSDICTIONARY:
300  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
301  {
302  msg = gettext_noop("text search dictionary \"%s\" does not exist, skipping");
303  name = NameListToString(castNode(List, object));
304  }
305  break;
306  case OBJECT_TSTEMPLATE:
307  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
308  {
309  msg = gettext_noop("text search template \"%s\" does not exist, skipping");
310  name = NameListToString(castNode(List, object));
311  }
312  break;
314  if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
315  {
316  msg = gettext_noop("text search configuration \"%s\" does not exist, skipping");
317  name = NameListToString(castNode(List, object));
318  }
319  break;
320  case OBJECT_EXTENSION:
321  msg = gettext_noop("extension \"%s\" does not exist, skipping");
322  name = strVal(object);
323  break;
324  case OBJECT_FUNCTION:
325  {
326  ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
327 
328  if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
330  {
331  msg = gettext_noop("function %s(%s) does not exist, skipping");
332  name = NameListToString(owa->objname);
334  }
335  break;
336  }
337  case OBJECT_PROCEDURE:
338  {
339  ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
340 
341  if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
343  {
344  msg = gettext_noop("procedure %s(%s) does not exist, skipping");
345  name = NameListToString(owa->objname);
347  }
348  break;
349  }
350  case OBJECT_ROUTINE:
351  {
352  ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
353 
354  if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
356  {
357  msg = gettext_noop("routine %s(%s) does not exist, skipping");
358  name = NameListToString(owa->objname);
360  }
361  break;
362  }
363  case OBJECT_AGGREGATE:
364  {
365  ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
366 
367  if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
369  {
370  msg = gettext_noop("aggregate %s(%s) does not exist, skipping");
371  name = NameListToString(owa->objname);
373  }
374  break;
375  }
376  case OBJECT_OPERATOR:
377  {
378  ObjectWithArgs *owa = castNode(ObjectWithArgs, object);
379 
380  if (!schema_does_not_exist_skipping(owa->objname, &msg, &name) &&
382  {
383  msg = gettext_noop("operator %s does not exist, skipping");
384  name = NameListToString(owa->objname);
385  }
386  break;
387  }
388  case OBJECT_LANGUAGE:
389  msg = gettext_noop("language \"%s\" does not exist, skipping");
390  name = strVal(object);
391  break;
392  case OBJECT_CAST:
393  {
396  {
397  /* XXX quote or no quote? */
398  msg = gettext_noop("cast from type %s to type %s does not exist, skipping");
401  }
402  }
403  break;
404  case OBJECT_TRANSFORM:
406  {
407  msg = gettext_noop("transform for type %s language \"%s\" does not exist, skipping");
409  args = strVal(lsecond(castNode(List, object)));
410  }
411  break;
412  case OBJECT_TRIGGER:
413  if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
414  {
415  msg = gettext_noop("trigger \"%s\" for relation \"%s\" does not exist, skipping");
416  name = strVal(llast(castNode(List, object)));
418  list_length(castNode(List, object)) - 1));
419  }
420  break;
421  case OBJECT_POLICY:
422  if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
423  {
424  msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, skipping");
425  name = strVal(llast(castNode(List, object)));
427  list_length(castNode(List, object)) - 1));
428  }
429  break;
431  msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
432  name = strVal(object);
433  break;
434  case OBJECT_RULE:
435  if (!owningrel_does_not_exist_skipping(castNode(List, object), &msg, &name))
436  {
437  msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, skipping");
438  name = strVal(llast(castNode(List, object)));
440  list_length(castNode(List, object)) - 1));
441  }
442  break;
443  case OBJECT_FDW:
444  msg = gettext_noop("foreign-data wrapper \"%s\" does not exist, skipping");
445  name = strVal(object);
446  break;
448  msg = gettext_noop("server \"%s\" does not exist, skipping");
449  name = strVal(object);
450  break;
451  case OBJECT_OPCLASS:
452  {
453  List *opcname = list_copy_tail(castNode(List, object), 1);
454 
455  if (!schema_does_not_exist_skipping(opcname, &msg, &name))
456  {
457  msg = gettext_noop("operator class \"%s\" does not exist for access method \"%s\", skipping");
458  name = NameListToString(opcname);
459  args = strVal(linitial(castNode(List, object)));
460  }
461  }
462  break;
463  case OBJECT_OPFAMILY:
464  {
465  List *opfname = list_copy_tail(castNode(List, object), 1);
466 
467  if (!schema_does_not_exist_skipping(opfname, &msg, &name))
468  {
469  msg = gettext_noop("operator family \"%s\" does not exist for access method \"%s\", skipping");
470  name = NameListToString(opfname);
471  args = strVal(linitial(castNode(List, object)));
472  }
473  }
474  break;
475  case OBJECT_PUBLICATION:
476  msg = gettext_noop("publication \"%s\" does not exist, skipping");
477  name = strVal(object);
478  break;
479 
480  case OBJECT_COLUMN:
481  case OBJECT_DATABASE:
483  case OBJECT_INDEX:
484  case OBJECT_MATVIEW:
485  case OBJECT_ROLE:
486  case OBJECT_SEQUENCE:
487  case OBJECT_SUBSCRIPTION:
488  case OBJECT_TABLE:
489  case OBJECT_TABLESPACE:
490  case OBJECT_VIEW:
491 
492  /*
493  * These are handled elsewhere, so if someone gets here the code
494  * is probably wrong or should be revisited.
495  */
496  elog(ERROR, "unsupported object type: %d", (int) objtype);
497  break;
498 
499  case OBJECT_AMOP:
500  case OBJECT_AMPROC:
501  case OBJECT_ATTRIBUTE:
502  case OBJECT_DEFAULT:
503  case OBJECT_DEFACL:
505  case OBJECT_LARGEOBJECT:
510  case OBJECT_USER_MAPPING:
511  /* These are currently not used or needed. */
512  elog(ERROR, "unsupported object type: %d", (int) objtype);
513  break;
514 
515  /* no default, to let compiler warn about missing case */
516  }
517  if (!msg)
518  elog(ERROR, "unrecognized object type: %d", (int) objtype);
519 
520  if (!args)
521  ereport(NOTICE, (errmsg(msg, name)));
522  else
523  ereport(NOTICE, (errmsg(msg, name, args)));
524 }
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
Definition: aclchk.c:4130
#define gettext_noop(x)
Definition: c.h:1196
#define Assert(condition)
Definition: c.h:858
#define OidIsValid(objectId)
Definition: c.h:775
void performMultipleDeletions(const ObjectAddresses *objects, DropBehavior behavior, int flags)
Definition: dependency.c:332
ObjectAddresses * new_object_addresses(void)
Definition: dependency.c:2485
void add_exact_object_address(const ObjectAddress *object, ObjectAddresses *addrs)
Definition: dependency.c:2531
void free_object_addresses(ObjectAddresses *addrs)
Definition: dependency.c:2771
static bool type_in_list_does_not_exist_skipping(List *typenames, const char **msg, char **name)
Definition: dropcmds.c:206
void RemoveObjects(DropStmt *stmt)
Definition: dropcmds.c:53
static void does_not_exist_skipping(ObjectType objtype, Node *object)
Definition: dropcmds.c:243
static bool owningrel_does_not_exist_skipping(List *object, const char **msg, char **name)
Definition: dropcmds.c:139
static bool schema_does_not_exist_skipping(List *object, const char **msg, char **name)
Definition: dropcmds.c:174
int errhint(const char *fmt,...)
Definition: elog.c:1319
int errcode(int sqlerrcode)
Definition: elog.c:859
int errmsg(const char *fmt,...)
Definition: elog.c:1072
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
#define NOTICE
Definition: elog.h:35
#define ereport(elevel,...)
Definition: elog.h:149
#define stmt
Definition: indent_codes.h:59
const char ** typenames
Definition: lexi.c:115
List * list_copy_head(const List *oldlist, int len)
Definition: list.c:1593
List * list_copy_tail(const List *oldlist, int nskip)
Definition: list.c:1613
#define NoLock
Definition: lockdefs.h:34
#define AccessExclusiveLock
Definition: lockdefs.h:43
char get_func_prokind(Oid funcid)
Definition: lsyscache.c:1818
Oid GetUserId(void)
Definition: miscinit.c:514
bool isTempNamespace(Oid namespaceId)
Definition: namespace.c:3634
RangeVar * makeRangeVarFromNameList(const List *names)
Definition: namespace.c:3539
char * NameListToString(const List *names)
Definition: namespace.c:3579
Oid LookupNamespaceNoError(const char *nspname)
Definition: namespace.c:3340
#define RangeVarGetRelid(relation, lockmode, missing_ok)
Definition: namespace.h:80
#define castNode(_type_, nodeptr)
Definition: nodes.h:176
void check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, Node *object, Relation relation)
ObjectAddress get_object_address(ObjectType objtype, Node *object, Relation *relp, LOCKMODE lockmode, bool missing_ok)
Oid get_object_namespace(const ObjectAddress *address)
char * TypeNameListToString(List *typenames)
Definition: parse_type.c:492
char * TypeNameToString(const TypeName *typeName)
Definition: parse_type.c:478
Oid LookupTypeNameOid(ParseState *pstate, const TypeName *typeName, bool missing_ok)
Definition: parse_type.c:232
ObjectType
Definition: parsenodes.h:2262
@ OBJECT_EVENT_TRIGGER
Definition: parsenodes.h:2277
@ OBJECT_FDW
Definition: parsenodes.h:2279
@ OBJECT_TSPARSER
Definition: parsenodes.h:2310
@ OBJECT_COLLATION
Definition: parsenodes.h:2270
@ OBJECT_USER_MAPPING
Definition: parsenodes.h:2313
@ OBJECT_ACCESS_METHOD
Definition: parsenodes.h:2263
@ OBJECT_OPCLASS
Definition: parsenodes.h:2287
@ OBJECT_DEFACL
Definition: parsenodes.h:2274
@ OBJECT_AGGREGATE
Definition: parsenodes.h:2264
@ OBJECT_MATVIEW
Definition: parsenodes.h:2286
@ OBJECT_SCHEMA
Definition: parsenodes.h:2299
@ OBJECT_POLICY
Definition: parsenodes.h:2291
@ OBJECT_OPERATOR
Definition: parsenodes.h:2288
@ OBJECT_FOREIGN_TABLE
Definition: parsenodes.h:2281
@ OBJECT_TSCONFIGURATION
Definition: parsenodes.h:2308
@ OBJECT_OPFAMILY
Definition: parsenodes.h:2289
@ OBJECT_DOMAIN
Definition: parsenodes.h:2275
@ OBJECT_COLUMN
Definition: parsenodes.h:2269
@ OBJECT_TABLESPACE
Definition: parsenodes.h:2305
@ OBJECT_ROLE
Definition: parsenodes.h:2296
@ OBJECT_ROUTINE
Definition: parsenodes.h:2297
@ OBJECT_LARGEOBJECT
Definition: parsenodes.h:2285
@ OBJECT_PUBLICATION_NAMESPACE
Definition: parsenodes.h:2294
@ OBJECT_PROCEDURE
Definition: parsenodes.h:2292
@ OBJECT_EXTENSION
Definition: parsenodes.h:2278
@ OBJECT_INDEX
Definition: parsenodes.h:2283
@ OBJECT_DEFAULT
Definition: parsenodes.h:2273
@ OBJECT_DATABASE
Definition: parsenodes.h:2272
@ OBJECT_SEQUENCE
Definition: parsenodes.h:2300
@ OBJECT_TSTEMPLATE
Definition: parsenodes.h:2311
@ OBJECT_LANGUAGE
Definition: parsenodes.h:2284
@ OBJECT_AMOP
Definition: parsenodes.h:2265
@ OBJECT_PUBLICATION_REL
Definition: parsenodes.h:2295
@ OBJECT_FOREIGN_SERVER
Definition: parsenodes.h:2280
@ OBJECT_TSDICTIONARY
Definition: parsenodes.h:2309
@ OBJECT_ATTRIBUTE
Definition: parsenodes.h:2267
@ OBJECT_PUBLICATION
Definition: parsenodes.h:2293
@ OBJECT_RULE
Definition: parsenodes.h:2298
@ OBJECT_CONVERSION
Definition: parsenodes.h:2271
@ OBJECT_AMPROC
Definition: parsenodes.h:2266
@ OBJECT_TABLE
Definition: parsenodes.h:2304
@ OBJECT_VIEW
Definition: parsenodes.h:2314
@ OBJECT_PARAMETER_ACL
Definition: parsenodes.h:2290
@ OBJECT_TYPE
Definition: parsenodes.h:2312
@ OBJECT_FUNCTION
Definition: parsenodes.h:2282
@ OBJECT_TABCONSTRAINT
Definition: parsenodes.h:2303
@ OBJECT_DOMCONSTRAINT
Definition: parsenodes.h:2276
@ OBJECT_SUBSCRIPTION
Definition: parsenodes.h:2301
@ OBJECT_STATISTIC_EXT
Definition: parsenodes.h:2302
@ OBJECT_CAST
Definition: parsenodes.h:2268
@ OBJECT_TRIGGER
Definition: parsenodes.h:2307
@ OBJECT_TRANSFORM
Definition: parsenodes.h:2306
#define lfirst(lc)
Definition: pg_list.h:172
#define llast(l)
Definition: pg_list.h:198
#define lfirst_node(type, lc)
Definition: pg_list.h:176
static int list_length(const List *l)
Definition: pg_list.h:152
#define linitial_node(type, l)
Definition: pg_list.h:181
#define lsecond_node(type, l)
Definition: pg_list.h:186
#define list_make1(x1)
Definition: pg_list.h:212
#define linitial(l)
Definition: pg_list.h:178
#define lsecond(l)
Definition: pg_list.h:183
unsigned int Oid
Definition: postgres_ext.h:31
Definition: pg_list.h:54
Definition: nodes.h:129
char * schemaname
Definition: primnodes.h:79
List * names
Definition: parsenodes.h:268
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:126
#define strVal(v)
Definition: value.h:82
const char * name
int MyXactFlags
Definition: xact.c:134
#define XACT_FLAGS_ACCESSEDTEMPNAMESPACE
Definition: xact.h:102