1 /*------------------------------------------------------------------------- 2 * 3 * rel.h 4 * POSTGRES relation descriptor (a/k/a relcache entry) definitions. 5 * 6 * 7 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group 8 * Portions Copyright (c) 1994, Regents of the University of California 9 * 10 * src/include/utils/rel.h 11 * 12 *------------------------------------------------------------------------- 13 */ 14 #ifndef REL_H 15 #define REL_H 16 17 #include "access/tupdesc.h" 18 #include "access/xlog.h" 19 #include "catalog/pg_class.h" 20 #include "catalog/pg_index.h" 21 #include "catalog/pg_publication.h" 22 #include "fmgr.h" 23 #include "nodes/bitmapset.h" 24 #include "rewrite/prs2lock.h" 25 #include "storage/block.h" 26 #include "storage/relfilenode.h" 27 #include "utils/relcache.h" 28 #include "utils/reltrigger.h" 29 30 31 /* 32 * LockRelId and LockInfo really belong to lmgr.h, but it's more convenient 33 * to declare them here so we can have a LockInfoData field in a Relation. 34 */ 35 36 typedef struct LockRelId 37 { 38 Oid relId; /* a relation identifier */ 39 Oid dbId; /* a database identifier */ 40 } LockRelId; 41 42 typedef struct LockInfoData 43 { 44 LockRelId lockRelId; 45 } LockInfoData; 46 47 typedef LockInfoData *LockInfo; 48 49 /* 50 * Here are the contents of a relation cache entry. 51 */ 52 53 typedef struct RelationData 54 { 55 RelFileNode rd_node; /* relation physical identifier */ 56 /* use "struct" here to avoid needing to include smgr.h: */ 57 struct SMgrRelationData *rd_smgr; /* cached file handle, or NULL */ 58 int rd_refcnt; /* reference count */ 59 BackendId rd_backend; /* owning backend id, if temporary relation */ 60 bool rd_islocaltemp; /* rel is a temp rel of this session */ 61 bool rd_isnailed; /* rel is nailed in cache */ 62 bool rd_isvalid; /* relcache entry is valid */ 63 bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and 64 * rd_replidindex) */ 65 bool rd_statvalid; /* is rd_statlist valid? */ 66 67 /*---------- 68 * rd_createSubid is the ID of the highest subtransaction the rel has 69 * survived into; or zero if the rel was not created in the current top 70 * transaction. This can be now be relied on, whereas previously it could 71 * be "forgotten" in earlier releases. Likewise, rd_newRelfilenodeSubid is 72 * the ID of the highest subtransaction the relfilenode change has 73 * survived into, or zero if not changed in the current transaction (or we 74 * have forgotten changing it). rd_newRelfilenodeSubid can be forgotten 75 * when a relation has multiple new relfilenodes within a single 76 * transaction, with one of them occurring in a subsequently aborted 77 * subtransaction, e.g. 78 * BEGIN; 79 * TRUNCATE t; 80 * SAVEPOINT save; 81 * TRUNCATE t; 82 * ROLLBACK TO save; 83 * -- rd_newRelfilenodeSubid is now forgotten 84 */ 85 SubTransactionId rd_createSubid; /* rel was created in current xact */ 86 SubTransactionId rd_newRelfilenodeSubid; /* new relfilenode assigned in 87 * current xact */ 88 89 Form_pg_class rd_rel; /* RELATION tuple */ 90 TupleDesc rd_att; /* tuple descriptor */ 91 Oid rd_id; /* relation's object id */ 92 LockInfoData rd_lockInfo; /* lock mgr's info for locking relation */ 93 RuleLock *rd_rules; /* rewrite rules */ 94 MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */ 95 TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */ 96 /* use "struct" here to avoid needing to include rowsecurity.h: */ 97 struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */ 98 99 /* data managed by RelationGetFKeyList: */ 100 List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ 101 bool rd_fkeyvalid; /* true if list has been computed */ 102 103 struct PartitionKeyData *rd_partkey; /* partition key, or NULL */ 104 MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */ 105 struct PartitionDescData *rd_partdesc; /* partitions, or NULL */ 106 MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */ 107 List *rd_partcheck; /* partition CHECK quals */ 108 bool rd_partcheckvalid; /* true if list has been computed */ 109 MemoryContext rd_partcheckcxt; /* private cxt for rd_partcheck, if any */ 110 111 /* data managed by RelationGetIndexList: */ 112 List *rd_indexlist; /* list of OIDs of indexes on relation */ 113 Oid rd_pkindex; /* OID of primary key, if any */ 114 Oid rd_replidindex; /* OID of replica identity index, if any */ 115 116 /* data managed by RelationGetStatExtList: */ 117 List *rd_statlist; /* list of OIDs of extended stats */ 118 119 /* data managed by RelationGetIndexAttrBitmap: */ 120 Bitmapset *rd_indexattr; /* identifies columns used in indexes */ 121 Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ 122 Bitmapset *rd_pkattr; /* cols included in primary key */ 123 Bitmapset *rd_idattr; /* included in replica identity index */ 124 125 PublicationActions *rd_pubactions; /* publication actions */ 126 127 /* 128 * rd_options is set whenever rd_rel is loaded into the relcache entry. 129 * Note that you can NOT look into rd_rel for this data. NULL means "use 130 * defaults". 131 */ 132 bytea *rd_options; /* parsed pg_class.reloptions */ 133 134 /* 135 * Oid of the handler for this relation. For an index this is a function 136 * returning IndexAmRoutine, for table like relations a function returning 137 * TableAmRoutine. This is stored separately from rd_indam, rd_tableam as 138 * its lookup requires syscache access, but during relcache bootstrap we 139 * need to be able to initialize rd_tableam without syscache lookups. 140 */ 141 Oid rd_amhandler; /* OID of index AM's handler function */ 142 143 /* 144 * Table access method. 145 */ 146 const struct TableAmRoutine *rd_tableam; 147 148 /* These are non-NULL only for an index relation: */ 149 Form_pg_index rd_index; /* pg_index tuple describing this index */ 150 /* use "struct" here to avoid needing to include htup.h: */ 151 struct HeapTupleData *rd_indextuple; /* all of pg_index tuple */ 152 153 /* 154 * index access support info (used only for an index relation) 155 * 156 * Note: only default support procs for each opclass are cached, namely 157 * those with lefttype and righttype equal to the opclass's opcintype. The 158 * arrays are indexed by support function number, which is a sufficient 159 * identifier given that restriction. 160 */ 161 MemoryContext rd_indexcxt; /* private memory cxt for this stuff */ 162 /* use "struct" here to avoid needing to include amapi.h: */ 163 struct IndexAmRoutine *rd_indam; /* index AM's API struct */ 164 Oid *rd_opfamily; /* OIDs of op families for each index col */ 165 Oid *rd_opcintype; /* OIDs of opclass declared input data types */ 166 RegProcedure *rd_support; /* OIDs of support procedures */ 167 FmgrInfo *rd_supportinfo; /* lookup info for support procedures */ 168 int16 *rd_indoption; /* per-column AM-specific flags */ 169 List *rd_indexprs; /* index expression trees, if any */ 170 List *rd_indpred; /* index predicate tree, if any */ 171 Oid *rd_exclops; /* OIDs of exclusion operators, if any */ 172 Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */ 173 uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ 174 Oid *rd_indcollation; /* OIDs of index collations */ 175 176 /* 177 * rd_amcache is available for index and table AMs to cache private data 178 * about the relation. This must be just a cache since it may get reset 179 * at any time (in particular, it will get reset by a relcache inval 180 * message for the relation). If used, it must point to a single memory 181 * chunk palloc'd in CacheMemoryContext, or in rd_indexcxt for an index 182 * relation. A relcache reset will include freeing that chunk and setting 183 * rd_amcache = NULL. 184 */ 185 void *rd_amcache; /* available for use by index/table AM */ 186 187 /* 188 * foreign-table support 189 * 190 * rd_fdwroutine must point to a single memory chunk palloc'd in 191 * CacheMemoryContext. It will be freed and reset to NULL on a relcache 192 * reset. 193 */ 194 195 /* use "struct" here to avoid needing to include fdwapi.h: */ 196 struct FdwRoutine *rd_fdwroutine; /* cached function pointers, or NULL */ 197 198 /* 199 * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new 200 * version of a table, we need to make any toast pointers inserted into it 201 * have the existing toast table's OID, not the OID of the transient toast 202 * table. If rd_toastoid isn't InvalidOid, it is the OID to place in 203 * toast pointers inserted into this rel. (Note it's set on the new 204 * version of the main heap, not the toast table itself.) This also 205 * causes toast_save_datum() to try to preserve toast value OIDs. 206 */ 207 Oid rd_toastoid; /* Real TOAST table's OID, or InvalidOid */ 208 209 /* use "struct" here to avoid needing to include pgstat.h: */ 210 struct PgStat_TableStatus *pgstat_info; /* statistics collection area */ 211 } RelationData; 212 213 214 /* 215 * ForeignKeyCacheInfo 216 * Information the relcache can cache about foreign key constraints 217 * 218 * This is basically just an image of relevant columns from pg_constraint. 219 * We make it a subclass of Node so that copyObject() can be used on a list 220 * of these, but we also ensure it is a "flat" object without substructure, 221 * so that list_free_deep() is sufficient to free such a list. 222 * The per-FK-column arrays can be fixed-size because we allow at most 223 * INDEX_MAX_KEYS columns in a foreign key constraint. 224 * 225 * Currently, we mostly cache fields of interest to the planner, but the set 226 * of fields has already grown the constraint OID for other uses. 227 */ 228 typedef struct ForeignKeyCacheInfo 229 { 230 NodeTag type; 231 Oid conoid; /* oid of the constraint itself */ 232 Oid conrelid; /* relation constrained by the foreign key */ 233 Oid confrelid; /* relation referenced by the foreign key */ 234 int nkeys; /* number of columns in the foreign key */ 235 /* these arrays each have nkeys valid entries: */ 236 AttrNumber conkey[INDEX_MAX_KEYS]; /* cols in referencing table */ 237 AttrNumber confkey[INDEX_MAX_KEYS]; /* cols in referenced table */ 238 Oid conpfeqop[INDEX_MAX_KEYS]; /* PK = FK operator OIDs */ 239 } ForeignKeyCacheInfo; 240 241 242 /* 243 * StdRdOptions 244 * Standard contents of rd_options for heaps and generic indexes. 245 * 246 * RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only 247 * be applied to relations that use this format or a superset for 248 * private options data. 249 */ 250 /* autovacuum-related reloptions. */ 251 typedef struct AutoVacOpts 252 { 253 bool enabled; 254 int vacuum_threshold; 255 int analyze_threshold; 256 int vacuum_cost_limit; 257 int freeze_min_age; 258 int freeze_max_age; 259 int freeze_table_age; 260 int multixact_freeze_min_age; 261 int multixact_freeze_max_age; 262 int multixact_freeze_table_age; 263 int log_min_duration; 264 float8 vacuum_cost_delay; 265 float8 vacuum_scale_factor; 266 float8 analyze_scale_factor; 267 } AutoVacOpts; 268 269 typedef struct StdRdOptions 270 { 271 int32 vl_len_; /* varlena header (do not touch directly!) */ 272 int fillfactor; /* page fill factor in percent (0..100) */ 273 /* fraction of newly inserted tuples prior to trigger index cleanup */ 274 float8 vacuum_cleanup_index_scale_factor; 275 int toast_tuple_target; /* target for tuple toasting */ 276 AutoVacOpts autovacuum; /* autovacuum-related options */ 277 bool user_catalog_table; /* use as an additional catalog relation */ 278 int parallel_workers; /* max number of parallel workers */ 279 bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */ 280 bool vacuum_truncate; /* enables vacuum to truncate a relation */ 281 } StdRdOptions; 282 283 #define HEAP_MIN_FILLFACTOR 10 284 #define HEAP_DEFAULT_FILLFACTOR 100 285 286 /* 287 * RelationGetToastTupleTarget 288 * Returns the relation's toast_tuple_target. Note multiple eval of argument! 289 */ 290 #define RelationGetToastTupleTarget(relation, defaulttarg) \ 291 ((relation)->rd_options ? \ 292 ((StdRdOptions *) (relation)->rd_options)->toast_tuple_target : (defaulttarg)) 293 294 /* 295 * RelationGetFillFactor 296 * Returns the relation's fillfactor. Note multiple eval of argument! 297 */ 298 #define RelationGetFillFactor(relation, defaultff) \ 299 ((relation)->rd_options ? \ 300 ((StdRdOptions *) (relation)->rd_options)->fillfactor : (defaultff)) 301 302 /* 303 * RelationGetTargetPageUsage 304 * Returns the relation's desired space usage per page in bytes. 305 */ 306 #define RelationGetTargetPageUsage(relation, defaultff) \ 307 (BLCKSZ * RelationGetFillFactor(relation, defaultff) / 100) 308 309 /* 310 * RelationGetTargetPageFreeSpace 311 * Returns the relation's desired freespace per page in bytes. 312 */ 313 #define RelationGetTargetPageFreeSpace(relation, defaultff) \ 314 (BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100) 315 316 /* 317 * RelationIsUsedAsCatalogTable 318 * Returns whether the relation should be treated as a catalog table 319 * from the pov of logical decoding. Note multiple eval of argument! 320 */ 321 #define RelationIsUsedAsCatalogTable(relation) \ 322 ((relation)->rd_options && \ 323 ((relation)->rd_rel->relkind == RELKIND_RELATION || \ 324 (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \ 325 ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false) 326 327 /* 328 * RelationGetParallelWorkers 329 * Returns the relation's parallel_workers reloption setting. 330 * Note multiple eval of argument! 331 */ 332 #define RelationGetParallelWorkers(relation, defaultpw) \ 333 ((relation)->rd_options ? \ 334 ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) 335 336 337 /* 338 * ViewOptions 339 * Contents of rd_options for views 340 */ 341 typedef struct ViewOptions 342 { 343 int32 vl_len_; /* varlena header (do not touch directly!) */ 344 bool security_barrier; 345 int check_option_offset; 346 } ViewOptions; 347 348 /* 349 * RelationIsSecurityView 350 * Returns whether the relation is security view, or not. Note multiple 351 * eval of argument! 352 */ 353 #define RelationIsSecurityView(relation) \ 354 ((relation)->rd_options ? \ 355 ((ViewOptions *) (relation)->rd_options)->security_barrier : false) 356 357 /* 358 * RelationHasCheckOption 359 * Returns true if the relation is a view defined with either the local 360 * or the cascaded check option. Note multiple eval of argument! 361 */ 362 #define RelationHasCheckOption(relation) \ 363 ((relation)->rd_options && \ 364 ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0) 365 366 /* 367 * RelationHasLocalCheckOption 368 * Returns true if the relation is a view defined with the local check 369 * option. Note multiple eval of argument! 370 */ 371 #define RelationHasLocalCheckOption(relation) \ 372 ((relation)->rd_options && \ 373 ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0 ? \ 374 strcmp((char *) (relation)->rd_options + \ 375 ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ 376 "local") == 0 : false) 377 378 /* 379 * RelationHasCascadedCheckOption 380 * Returns true if the relation is a view defined with the cascaded check 381 * option. Note multiple eval of argument! 382 */ 383 #define RelationHasCascadedCheckOption(relation) \ 384 ((relation)->rd_options && \ 385 ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0 ? \ 386 strcmp((char *) (relation)->rd_options + \ 387 ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ 388 "cascaded") == 0 : false) 389 390 391 /* 392 * RelationIsValid 393 * True iff relation descriptor is valid. 394 */ 395 #define RelationIsValid(relation) PointerIsValid(relation) 396 397 #define InvalidRelation ((Relation) NULL) 398 399 /* 400 * RelationHasReferenceCountZero 401 * True iff relation reference count is zero. 402 * 403 * Note: 404 * Assumes relation descriptor is valid. 405 */ 406 #define RelationHasReferenceCountZero(relation) \ 407 ((bool)((relation)->rd_refcnt == 0)) 408 409 /* 410 * RelationGetForm 411 * Returns pg_class tuple for a relation. 412 * 413 * Note: 414 * Assumes relation descriptor is valid. 415 */ 416 #define RelationGetForm(relation) ((relation)->rd_rel) 417 418 /* 419 * RelationGetRelid 420 * Returns the OID of the relation 421 */ 422 #define RelationGetRelid(relation) ((relation)->rd_id) 423 424 /* 425 * RelationGetNumberOfAttributes 426 * Returns the total number of attributes in a relation. 427 */ 428 #define RelationGetNumberOfAttributes(relation) ((relation)->rd_rel->relnatts) 429 430 /* 431 * IndexRelationGetNumberOfAttributes 432 * Returns the number of attributes in an index. 433 */ 434 #define IndexRelationGetNumberOfAttributes(relation) \ 435 ((relation)->rd_index->indnatts) 436 437 /* 438 * IndexRelationGetNumberOfKeyAttributes 439 * Returns the number of key attributes in an index. 440 */ 441 #define IndexRelationGetNumberOfKeyAttributes(relation) \ 442 ((relation)->rd_index->indnkeyatts) 443 444 /* 445 * RelationGetDescr 446 * Returns tuple descriptor for a relation. 447 */ 448 #define RelationGetDescr(relation) ((relation)->rd_att) 449 450 /* 451 * RelationGetRelationName 452 * Returns the rel's name. 453 * 454 * Note that the name is only unique within the containing namespace. 455 */ 456 #define RelationGetRelationName(relation) \ 457 (NameStr((relation)->rd_rel->relname)) 458 459 /* 460 * RelationGetNamespace 461 * Returns the rel's namespace OID. 462 */ 463 #define RelationGetNamespace(relation) \ 464 ((relation)->rd_rel->relnamespace) 465 466 /* 467 * RelationIsMapped 468 * True if the relation uses the relfilenode map. Note multiple eval 469 * of argument! 470 */ 471 #define RelationIsMapped(relation) \ 472 (RELKIND_HAS_STORAGE((relation)->rd_rel->relkind) && \ 473 ((relation)->rd_rel->relfilenode == InvalidOid)) 474 475 /* 476 * RelationOpenSmgr 477 * Open the relation at the smgr level, if not already done. 478 */ 479 #define RelationOpenSmgr(relation) \ 480 do { \ 481 if ((relation)->rd_smgr == NULL) \ 482 smgrsetowner(&((relation)->rd_smgr), smgropen((relation)->rd_node, (relation)->rd_backend)); \ 483 } while (0) 484 485 /* 486 * RelationCloseSmgr 487 * Close the relation at the smgr level, if not already done. 488 * 489 * Note: smgrclose should unhook from owner pointer, hence the Assert. 490 */ 491 #define RelationCloseSmgr(relation) \ 492 do { \ 493 if ((relation)->rd_smgr != NULL) \ 494 { \ 495 smgrclose((relation)->rd_smgr); \ 496 Assert((relation)->rd_smgr == NULL); \ 497 } \ 498 } while (0) 499 500 /* 501 * RelationGetTargetBlock 502 * Fetch relation's current insertion target block. 503 * 504 * Returns InvalidBlockNumber if there is no current target block. Note 505 * that the target block status is discarded on any smgr-level invalidation. 506 */ 507 #define RelationGetTargetBlock(relation) \ 508 ( (relation)->rd_smgr != NULL ? (relation)->rd_smgr->smgr_targblock : InvalidBlockNumber ) 509 510 /* 511 * RelationSetTargetBlock 512 * Set relation's current insertion target block. 513 */ 514 #define RelationSetTargetBlock(relation, targblock) \ 515 do { \ 516 RelationOpenSmgr(relation); \ 517 (relation)->rd_smgr->smgr_targblock = (targblock); \ 518 } while (0) 519 520 /* 521 * RelationNeedsWAL 522 * True if relation needs WAL. 523 */ 524 #define RelationNeedsWAL(relation) \ 525 ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) 526 527 /* 528 * RelationUsesLocalBuffers 529 * True if relation's pages are stored in local buffers. 530 */ 531 #define RelationUsesLocalBuffers(relation) \ 532 ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) 533 534 /* 535 * RELATION_IS_LOCAL 536 * If a rel is either temp or newly created in the current transaction, 537 * it can be assumed to be accessible only to the current backend. 538 * This is typically used to decide that we can skip acquiring locks. 539 * 540 * Beware of multiple eval of argument 541 */ 542 #define RELATION_IS_LOCAL(relation) \ 543 ((relation)->rd_islocaltemp || \ 544 (relation)->rd_createSubid != InvalidSubTransactionId) 545 546 /* 547 * RELATION_IS_OTHER_TEMP 548 * Test for a temporary relation that belongs to some other session. 549 * 550 * Beware of multiple eval of argument 551 */ 552 #define RELATION_IS_OTHER_TEMP(relation) \ 553 ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ 554 !(relation)->rd_islocaltemp) 555 556 557 /* 558 * RelationIsScannable 559 * Currently can only be false for a materialized view which has not been 560 * populated by its query. This is likely to get more complicated later, 561 * so use a macro which looks like a function. 562 */ 563 #define RelationIsScannable(relation) ((relation)->rd_rel->relispopulated) 564 565 /* 566 * RelationIsPopulated 567 * Currently, we don't physically distinguish the "populated" and 568 * "scannable" properties of matviews, but that may change later. 569 * Hence, use the appropriate one of these macros in code tests. 570 */ 571 #define RelationIsPopulated(relation) ((relation)->rd_rel->relispopulated) 572 573 /* 574 * RelationIsAccessibleInLogicalDecoding 575 * True if we need to log enough information to have access via 576 * decoding snapshot. 577 */ 578 #define RelationIsAccessibleInLogicalDecoding(relation) \ 579 (XLogLogicalInfoActive() && \ 580 RelationNeedsWAL(relation) && \ 581 (IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation))) 582 583 /* 584 * RelationIsLogicallyLogged 585 * True if we need to log enough information to extract the data from the 586 * WAL stream. 587 * 588 * We don't log information for unlogged tables (since they don't WAL log 589 * anyway) and for system tables (their content is hard to make sense of, and 590 * it would complicate decoding slightly for little gain). Note that we *do* 591 * log information for user defined catalog tables since they presumably are 592 * interesting to the user... 593 */ 594 #define RelationIsLogicallyLogged(relation) \ 595 (XLogLogicalInfoActive() && \ 596 RelationNeedsWAL(relation) && \ 597 !IsCatalogRelation(relation)) 598 599 /* 600 * RelationGetPartitionKey 601 * Returns the PartitionKey of a relation 602 */ 603 #define RelationGetPartitionKey(relation) ((relation)->rd_partkey) 604 605 /* 606 * RelationGetPartitionDesc 607 * Returns partition descriptor for a relation. 608 */ 609 #define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc) 610 611 /* routines in utils/cache/relcache.c */ 612 extern void RelationIncrementReferenceCount(Relation rel); 613 extern void RelationDecrementReferenceCount(Relation rel); 614 extern List *RelationGetRepsetList(Relation rel); 615 616 #endif /* REL_H */ 617