/* * Report a detected deadlock, with available details. */ void DeadLockReport(void) { StringInfoData clientbuf; /* errdetail for client */ StringInfoData logbuf; /* errdetail for server log */ StringInfoData locktagbuf; int i; initStringInfo(&clientbuf); initStringInfo(&logbuf); initStringInfo(&locktagbuf); /* Generate the "waits for" lines sent to the client */ for (i = 0; i < nDeadlockDetails; i++) { DEADLOCK_INFO *info = &deadlockDetails[i]; int nextpid; /* The last proc waits for the first one... */ if (i < nDeadlockDetails - 1) nextpid = info[1].pid; else nextpid = deadlockDetails[0].pid; /* reset locktagbuf to hold next object description */ resetStringInfo(&locktagbuf); DescribeLockTag(&locktagbuf, &info->locktag); if (i > 0) appendStringInfoChar(&clientbuf, '\n'); appendStringInfo(&clientbuf, _("Process %d waits for %s on %s; blocked by process %d."), info->pid, GetLockmodeName(info->locktag.locktag_lockmethodid, info->lockmode), locktagbuf.data, nextpid); } /* Duplicate all the above for the server ... */ appendStringInfoString(&logbuf, clientbuf.data); /* ... and add info about query strings */ for (i = 0; i < nDeadlockDetails; i++) { DEADLOCK_INFO *info = &deadlockDetails[i]; appendStringInfoChar(&logbuf, '\n'); appendStringInfo(&logbuf, _("Process %d: %s"), info->pid, pgstat_get_backend_current_activity(info->pid, false)); } ereport(ERROR, (errcode(ERRCODE_T_R_DEADLOCK_DETECTED), errmsg("deadlock detected"), errdetail_internal("%s", clientbuf.data), errdetail_log("%s", logbuf.data), errhint("See server log for query details."))); }
/* * sepgsql_proc_post_create * * This routine assigns a default security label on a newly defined * procedure. */ void sepgsql_proc_post_create(Oid functionId) { Relation rel; ScanKeyData skey; SysScanDesc sscan; HeapTuple tuple; char *nsp_name; char *scontext; char *tcontext; char *ncontext; uint32 required; int i; StringInfoData audit_name; ObjectAddress object; Form_pg_proc proForm; /* * Fetch namespace of the new procedure. Because pg_proc entry is not * visible right now, we need to scan the catalog using SnapshotSelf. */ rel = heap_open(ProcedureRelationId, AccessShareLock); ScanKeyInit(&skey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(functionId)); sscan = systable_beginscan(rel, ProcedureOidIndexId, true, SnapshotSelf, 1, &skey); tuple = systable_getnext(sscan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for proc %u", functionId); proForm = (Form_pg_proc) GETSTRUCT(tuple); /* * check db_schema:{add_name} permission of the namespace */ object.classId = NamespaceRelationId; object.objectId = proForm->pronamespace; object.objectSubId = 0; sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_SCHEMA, SEPG_DB_SCHEMA__ADD_NAME, getObjectIdentity(&object), true); /* * XXX - db_language:{implement} also should be checked here */ /* * Compute a default security label when we create a new procedure object * under the specified namespace. */ scontext = sepgsql_get_client_label(); tcontext = sepgsql_get_label(NamespaceRelationId, proForm->pronamespace, 0); ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_PROCEDURE, NameStr(proForm->proname)); /* * check db_procedure:{create (install)} permission */ initStringInfo(&audit_name); nsp_name = get_namespace_name(proForm->pronamespace); appendStringInfo(&audit_name, "%s(", quote_qualified_identifier(nsp_name, NameStr(proForm->proname))); for (i = 0; i < proForm->pronargs; i++) { if (i > 0) appendStringInfoChar(&audit_name, ','); object.classId = TypeRelationId; object.objectId = proForm->proargtypes.values[i]; object.objectSubId = 0; appendStringInfoString(&audit_name, getObjectIdentity(&object)); } appendStringInfoChar(&audit_name, ')'); required = SEPG_DB_PROCEDURE__CREATE; if (proForm->proleakproof) required |= SEPG_DB_PROCEDURE__INSTALL; sepgsql_avc_check_perms_label(ncontext, SEPG_CLASS_DB_PROCEDURE, required, audit_name.data, true); /* * Assign the default security label on a new procedure */ object.classId = ProcedureRelationId; object.objectId = functionId; object.objectSubId = 0; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); /* * Cleanup */ systable_endscan(sscan); heap_close(rel, AccessShareLock); pfree(audit_name.data); pfree(tcontext); pfree(ncontext); }
void heap2_desc(StringInfo buf, uint8 xl_info, char *rec) { uint8 info = xl_info & ~XLR_INFO_MASK; info &= XLOG_HEAP_OPMASK; if (info == XLOG_HEAP2_CLEAN) { xl_heap_clean *xlrec = (xl_heap_clean *) rec; appendStringInfo(buf, "clean: rel %u/%u/%u; blk %u remxid %u", xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode, xlrec->block, xlrec->latestRemovedXid); } else if (info == XLOG_HEAP2_FREEZE_PAGE) { xl_heap_freeze_page *xlrec = (xl_heap_freeze_page *) rec; appendStringInfo(buf, "freeze_page: rel %u/%u/%u; blk %u; cutoff xid %u ntuples %u", xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode, xlrec->block, xlrec->cutoff_xid, xlrec->ntuples); } else if (info == XLOG_HEAP2_CLEANUP_INFO) { xl_heap_cleanup_info *xlrec = (xl_heap_cleanup_info *) rec; appendStringInfo(buf, "cleanup info: remxid %u", xlrec->latestRemovedXid); } else if (info == XLOG_HEAP2_VISIBLE) { xl_heap_visible *xlrec = (xl_heap_visible *) rec; appendStringInfo(buf, "visible: rel %u/%u/%u; blk %u", xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode, xlrec->block); } else if (info == XLOG_HEAP2_MULTI_INSERT) { xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec; if (xl_info & XLOG_HEAP_INIT_PAGE) appendStringInfoString(buf, "multi-insert (init): "); else appendStringInfoString(buf, "multi-insert: "); appendStringInfo(buf, "rel %u/%u/%u; blk %u; %d tuples", xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode, xlrec->blkno, xlrec->ntuples); } else if (info == XLOG_HEAP2_LOCK_UPDATED) { xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec; appendStringInfo(buf, "lock updated: xmax %u msk %04x; ", xlrec->xmax, xlrec->infobits_set); out_target(buf, &(xlrec->target)); } else if (info == XLOG_HEAP2_NEW_CID) { xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec; appendStringInfo(buf, "new_cid: "); out_target(buf, &(xlrec->target)); appendStringInfo(buf, "; cmin: %u, cmax: %u, combo: %u", xlrec->cmin, xlrec->cmax, xlrec->combocid); } else appendStringInfoString(buf, "UNKNOWN"); }
void heap_desc(StringInfo buf, uint8 xl_info, char *rec) { uint8 info = xl_info & ~XLR_INFO_MASK; info &= XLOG_HEAP_OPMASK; if (info == XLOG_HEAP_INSERT) { xl_heap_insert *xlrec = (xl_heap_insert *) rec; if (xl_info & XLOG_HEAP_INIT_PAGE) appendStringInfoString(buf, "insert(init): "); else appendStringInfoString(buf, "insert: "); out_target(buf, &(xlrec->target)); } else if (info == XLOG_HEAP_DELETE) { xl_heap_delete *xlrec = (xl_heap_delete *) rec; appendStringInfoString(buf, "delete: "); out_target(buf, &(xlrec->target)); appendStringInfoChar(buf, ' '); out_infobits(buf, xlrec->infobits_set); } else if (info == XLOG_HEAP_UPDATE) { xl_heap_update *xlrec = (xl_heap_update *) rec; if (xl_info & XLOG_HEAP_INIT_PAGE) appendStringInfoString(buf, "update(init): "); else appendStringInfoString(buf, "update: "); out_target(buf, &(xlrec->target)); appendStringInfo(buf, " xmax %u ", xlrec->old_xmax); out_infobits(buf, xlrec->old_infobits_set); appendStringInfo(buf, "; new tid %u/%u xmax %u", ItemPointerGetBlockNumber(&(xlrec->newtid)), ItemPointerGetOffsetNumber(&(xlrec->newtid)), xlrec->new_xmax); } else if (info == XLOG_HEAP_HOT_UPDATE) { xl_heap_update *xlrec = (xl_heap_update *) rec; if (xl_info & XLOG_HEAP_INIT_PAGE) /* can this case happen? */ appendStringInfoString(buf, "hot_update(init): "); else appendStringInfoString(buf, "hot_update: "); out_target(buf, &(xlrec->target)); appendStringInfo(buf, " xmax %u ", xlrec->old_xmax); out_infobits(buf, xlrec->old_infobits_set); appendStringInfo(buf, "; new tid %u/%u xmax %u", ItemPointerGetBlockNumber(&(xlrec->newtid)), ItemPointerGetOffsetNumber(&(xlrec->newtid)), xlrec->new_xmax); } else if (info == XLOG_HEAP_NEWPAGE) { xl_heap_newpage *xlrec = (xl_heap_newpage *) rec; appendStringInfo(buf, "newpage: rel %u/%u/%u; fork %u, blk %u", xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode, xlrec->forknum, xlrec->blkno); } else if (info == XLOG_HEAP_LOCK) { xl_heap_lock *xlrec = (xl_heap_lock *) rec; appendStringInfo(buf, "lock %u: ", xlrec->locking_xid); out_target(buf, &(xlrec->target)); appendStringInfoChar(buf, ' '); out_infobits(buf, xlrec->infobits_set); } else if (info == XLOG_HEAP_INPLACE) { xl_heap_inplace *xlrec = (xl_heap_inplace *) rec; appendStringInfoString(buf, "inplace: "); out_target(buf, &(xlrec->target)); } else appendStringInfoString(buf, "UNKNOWN"); }
/* * Turn a composite / record into JSON. */ static void composite_to_json(Datum composite, StringInfo result, bool use_line_feeds) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; bool needsep = false; char *sep; sep = use_line_feeds ? ",\n " : ","; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; appendStringInfoChar(result,'{'); for (i = 0; i < tupdesc->natts; i++) { Datum val, origval; bool isnull; char *attname; TYPCATEGORY tcategory; Oid typoutput; bool typisvarlena; if (tupdesc->attrs[i]->attisdropped) continue; if (needsep) appendStringInfoString(result,sep); needsep = true; attname = NameStr(tupdesc->attrs[i]->attname); escape_json(result,attname); appendStringInfoChar(result,':'); origval = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (tupdesc->attrs[i]->atttypid == RECORDARRAYOID) tcategory = TYPCATEGORY_ARRAY; else if (tupdesc->attrs[i]->atttypid == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; else if (tupdesc->attrs[i]->atttypid == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); getTypeOutputInfo(tupdesc->attrs[i]->atttypid, &typoutput, &typisvarlena); /* * If we have a toasted datum, forcibly detoast it here to avoid memory * leakage inside the type's output routine. */ if (typisvarlena && ! isnull) val = PointerGetDatum(PG_DETOAST_DATUM(origval)); else val = origval; datum_to_json(val, isnull, result, tcategory, typoutput); /* Clean up detoasted copy, if any */ if (val != origval) pfree(DatumGetPointer(val)); } appendStringInfoChar(result,'}'); ReleaseTupleDesc(tupdesc); }
static void xact_desc_commit(StringInfo buf, xl_xact_commit *xlrec) { int i; TransactionId *subxacts; subxacts = (TransactionId *) &xlrec->xnodes[xlrec->nrels]; appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time)); if (xlrec->nrels > 0) { appendStringInfoString(buf, "; rels:"); for (i = 0; i < xlrec->nrels; i++) { char *path = relpathperm(xlrec->xnodes[i], MAIN_FORKNUM); appendStringInfo(buf, " %s", path); pfree(path); } } if (xlrec->nsubxacts > 0) { appendStringInfoString(buf, "; subxacts:"); for (i = 0; i < xlrec->nsubxacts; i++) appendStringInfo(buf, " %u", subxacts[i]); } if (xlrec->nmsgs > 0) { SharedInvalidationMessage *msgs; msgs = (SharedInvalidationMessage *) &subxacts[xlrec->nsubxacts]; if (XactCompletionRelcacheInitFileInval(xlrec->xinfo)) appendStringInfo(buf, "; relcache init file inval dbid %u tsid %u", xlrec->dbId, xlrec->tsId); appendStringInfoString(buf, "; inval msgs:"); for (i = 0; i < xlrec->nmsgs; i++) { SharedInvalidationMessage *msg = &msgs[i]; if (msg->id >= 0) appendStringInfo(buf, " catcache %d", msg->id); else if (msg->id == SHAREDINVALCATALOG_ID) appendStringInfo(buf, " catalog %u", msg->cat.catId); else if (msg->id == SHAREDINVALRELCACHE_ID) appendStringInfo(buf, " relcache %u", msg->rc.relId); /* not expected, but print something anyway */ else if (msg->id == SHAREDINVALSMGR_ID) appendStringInfoString(buf, " smgr"); /* not expected, but print something anyway */ else if (msg->id == SHAREDINVALRELMAP_ID) appendStringInfoString(buf, " relmap"); else if (msg->id == SHAREDINVALSNAPSHOT_ID) appendStringInfo(buf, " snapshot %u", msg->sn.relId); else appendStringInfo(buf, " unknown id %d", msg->id); } } }
static void process_insert(PgqTriggerEvent *ev, StringInfo sql) { TriggerData *tg = ev->tgdata; HeapTuple new_row = tg->tg_trigtuple; TupleDesc tupdesc = tg->tg_relation->rd_att; int i; int need_comma = false; int attkind_idx; /* * Specify all the columns */ appendStringInfoChar(sql, '('); attkind_idx = -1; for (i = 0; i < tupdesc->natts; i++) { char *col_ident; /* Skip dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; /* Check if allowed by colstring */ attkind_idx++; if (pgqtriga_skip_col(ev, i, attkind_idx)) continue; if (need_comma) appendStringInfoChar(sql, ','); else need_comma = true; /* quote column name */ col_ident = SPI_fname(tupdesc, i + 1); pgq_encode_cstring(sql, col_ident, TBUF_QUOTE_IDENT); } /* * Append the string ") values (" */ appendStringInfoString(sql, ") values ("); /* * Append the values */ need_comma = false; attkind_idx = -1; for (i = 0; i < tupdesc->natts; i++) { char *col_value; /* Skip dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; /* Check if allowed by colstring */ attkind_idx++; if (pgqtriga_skip_col(ev, i, attkind_idx)) continue; if (need_comma) appendStringInfoChar(sql, ','); else need_comma = true; /* quote column value */ col_value = SPI_getvalue(new_row, tupdesc, i + 1); if (col_value == NULL) appendStringInfoString(sql, "null"); else pgq_encode_cstring(sql, col_value, TBUF_QUOTE_LITERAL); } /* * Terminate and done */ appendStringInfoChar(sql, ')'); }
char* fs_info(void) { char* result; FILE *fp; struct mntent *fs; struct statvfs vfs; StringInfoData resultbuf; char strbuf[30]; TimestampTz now; initStringInfo(&resultbuf); appendStringInfo(&resultbuf, "FSINFO;"); fp = setmntent("/etc/mtab", "r"); /* read only */ if (fp == NULL) { elog(LOG, "%s: could not open: %s\n", "/etc/mtab", strerror(errno)); result = palloc(2); strcpy(result, ""); return result; } while ((fs = getmntent(fp)) != NULL) { if (fs->mnt_fsname[0] == '/') { if (statvfs(fs->mnt_dir, & vfs) != 0) { //elog(LOG, "SKIPPING %s: statvfs failed: %s\n", fs->mnt_dir, strerror(errno)); } else { /* Filesystem Info */ appendStringInfoString(&resultbuf, fs->mnt_fsname); appendStringInfo(&resultbuf, FIELD_DELIMIT); appendStringInfoString(&resultbuf, fs->mnt_dir); appendStringInfo(&resultbuf, FIELD_DELIMIT); appendStringInfoString(&resultbuf, fs->mnt_type); appendStringInfo(&resultbuf, FIELD_DELIMIT); appendStringInfoString(&resultbuf, fs->mnt_opts); appendStringInfo(&resultbuf, FIELD_DELIMIT); /* Filesystem Stats */ sprintf(strbuf, "%ld", (unsigned long) vfs.f_bsize); appendStringInfoString(&resultbuf, strbuf); appendStringInfo(&resultbuf, FIELD_DELIMIT); sprintf(strbuf, "%ld", (unsigned long) vfs.f_frsize); appendStringInfoString(&resultbuf, strbuf); appendStringInfo(&resultbuf, FIELD_DELIMIT); sprintf(strbuf, "%ld", (unsigned long) vfs.f_blocks); appendStringInfoString(&resultbuf, strbuf); appendStringInfo(&resultbuf, FIELD_DELIMIT); sprintf(strbuf, "%ld", (unsigned long) vfs.f_bfree); appendStringInfoString(&resultbuf, strbuf); appendStringInfo(&resultbuf, FIELD_DELIMIT); sprintf(strbuf, "%ld", (unsigned long) vfs.f_bavail); appendStringInfoString(&resultbuf, strbuf); appendStringInfo(&resultbuf, FIELD_DELIMIT); /* add current_timestamp */ now = GetCurrentTimestamp(); appendStringInfoString(&resultbuf, (char *)timestamptz_to_str(now)); appendStringInfo(&resultbuf,REC_DELIMIT); } } } endmntent(fp); // elog(LOG, "FSINFO: %s", resultbuf.data); //print the command to send, debugging appendStringInfoString(&resultbuf, CDELIMIT); result = pstrdup(resultbuf.data); return result; }
/* * This function receives a command names and a query and it produces a protocol valid command string * which can be send to the server based on the format of the result set. It handles memory contexts * appropriately, returning a pointer to the command string in the current context when called. * */ char* exec_to_command(const char* command, char* q) { StringInfoData resultbuf; char* result; int i, j, processed, retval; SPITupleTable *coltuptable; MemoryContext pre_context; MemoryContext spi_conn_context; // elog(LOG, "%s", command); //prints the current command running pre_context = CurrentMemoryContext; SetCurrentStatementStartTimestamp(); StartTransactionCommand(); SPI_connect(); PushActiveSnapshot(GetTransactionSnapshot()); retval = SPI_execute(q, false, 0); if (retval != SPI_OK_SELECT) { elog(LOG, "Database SELECT execution failed: %d", retval); SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); result = pstrdup(""); return result; } processed = SPI_processed; coltuptable = SPI_tuptable; initStringInfo(&resultbuf); appendStringInfoString(&resultbuf, command); appendStringInfoString(&resultbuf, ";"); //artisinal semicolon if (coltuptable != NULL) { for(i = 0; i < processed; i++) { for(j = 1; j <= coltuptable->tupdesc->natts; j++) { if (SPI_getvalue(coltuptable->vals[i], coltuptable->tupdesc, j) != NULL) { appendStringInfoString(&resultbuf, SPI_getvalue(coltuptable->vals[i], coltuptable->tupdesc, j)); } appendStringInfo(&resultbuf, FIELD_DELIMIT); } appendStringInfo(&resultbuf,REC_DELIMIT); } } appendStringInfo(&resultbuf, CDELIMIT); spi_conn_context = MemoryContextSwitchTo(pre_context); result = pstrdup(resultbuf.data); MemoryContextSwitchTo(spi_conn_context); SPI_finish(); PopActiveSnapshot(); CommitTransactionCommand(); return result; }
static int process_update(PgqTriggerEvent *ev, StringInfo sql) { TriggerData *tg = ev->tgdata; HeapTuple old_row = tg->tg_trigtuple; HeapTuple new_row = tg->tg_newtuple; TupleDesc tupdesc = tg->tg_relation->rd_att; Datum old_value; Datum new_value; bool old_isnull; bool new_isnull; char *col_ident; char *col_value; int i; int need_comma = false; int need_and = false; int attkind_idx; int ignore_count = 0; attkind_idx = -1; for (i = 0; i < tupdesc->natts; i++) { /* * Ignore dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; old_value = SPI_getbinval(old_row, tupdesc, i + 1, &old_isnull); new_value = SPI_getbinval(new_row, tupdesc, i + 1, &new_isnull); /* * If old and new value are NULL, the column is unchanged */ if (old_isnull && new_isnull) continue; /* * If both are NOT NULL, we need to compare the values and skip * setting the column if equal */ if (!old_isnull && !new_isnull) { Oid opr_oid; FmgrInfo *opr_finfo_p; /* * Lookup the equal operators function call info using the * typecache if available */ TypeCacheEntry *type_cache; type_cache = lookup_type_cache(SPI_gettypeid(tupdesc, i + 1), TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO); opr_oid = type_cache->eq_opr; if (opr_oid == ARRAY_EQ_OP) opr_oid = InvalidOid; else opr_finfo_p = &(type_cache->eq_opr_finfo); /* * If we have an equal operator, use that to do binary * comparision. Else get the string representation of both * attributes and do string comparision. */ if (OidIsValid(opr_oid)) { if (DatumGetBool(FunctionCall2(opr_finfo_p, old_value, new_value))) continue; } else { char *old_strval = SPI_getvalue(old_row, tupdesc, i + 1); char *new_strval = SPI_getvalue(new_row, tupdesc, i + 1); if (strcmp(old_strval, new_strval) == 0) continue; } } if (pgqtriga_skip_col(ev, i, attkind_idx)) { /* this change should be ignored */ ignore_count++; continue; } if (need_comma) appendStringInfoChar(sql, ','); else need_comma = true; col_ident = SPI_fname(tupdesc, i + 1); col_value = SPI_getvalue(new_row, tupdesc, i + 1); append_normal_eq(sql, col_ident, col_value); } /* * It can happen that the only UPDATE an application does is to set a * column to the same value again. In that case, we'd end up here with * no columns in the SET clause yet. We add the first key column here * with it's old value to simulate the same for the replication * engine. */ if (!need_comma) { /* there was change in ignored columns, skip whole event */ if (ignore_count > 0) return 0; for (i = 0, attkind_idx = -1; i < tupdesc->natts; i++) { if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; if (pgqtriga_is_pkey(ev, i, attkind_idx)) break; } col_ident = SPI_fname(tupdesc, i + 1); col_value = SPI_getvalue(old_row, tupdesc, i + 1); append_key_eq(sql, col_ident, col_value); } appendStringInfoString(sql, " where "); for (i = 0, attkind_idx = -1; i < tupdesc->natts; i++) { /* * Ignore dropped columns */ if (tupdesc->attrs[i]->attisdropped) continue; attkind_idx++; if (!pgqtriga_is_pkey(ev, i, attkind_idx)) continue; col_ident = SPI_fname(tupdesc, i + 1); col_value = SPI_getvalue(old_row, tupdesc, i + 1); if (need_and) appendStringInfoString(sql, " and "); else need_and = true; append_key_eq(sql, col_ident, col_value); } return 1; }
/** * @fn Datum reorg_get_index_keys(PG_FUNCTION_ARGS) * @brief Get key definition of the index. * * reorg_get_index_keys(index, table) * * @param index Oid of target index. * @param table Oid of table of the index. * @retval Create index DDL for temp table. * * FIXME: this function is named get_index_keys, but actually returns * an expression for ORDER BY clause. get_order_by() might be a better name. */ Datum reorg_get_index_keys(PG_FUNCTION_ARGS) { Oid index = PG_GETARG_OID(0); Oid table = PG_GETARG_OID(1); IndexDef stmt; char *token; char *next; StringInfoData str; Relation indexRel = NULL; int nattr; parse_indexdef(&stmt, index, table); elog(DEBUG2, "indexdef.create = %s", stmt.create); elog(DEBUG2, "indexdef.index = %s", stmt.index); elog(DEBUG2, "indexdef.table = %s", stmt.table); elog(DEBUG2, "indexdef.type = %s", stmt.type); elog(DEBUG2, "indexdef.columns = %s", stmt.columns); elog(DEBUG2, "indexdef.options = %s", stmt.options); /* * FIXME: this is very unreliable implementation but I don't want to * re-implement customized versions of pg_get_indexdef_string... * * TODO: Support ASC/DESC and NULL FIRST/LAST. */ initStringInfo(&str); for (nattr = 0, next = stmt.columns; next; nattr++) { char *opcname; token = next; while (isspace((unsigned char) *token)) token++; next = skip_until(index, next, ','); opcname = skip_until(index, token, ' '); if (opcname) { /* lookup default operator name from operator class */ Oid opclass; Oid oprid; int16 strategy = BTLessStrategyNumber; #if PG_VERSION_NUM >= 80300 Oid opcintype; Oid opfamily; HeapTuple tp; Form_pg_opclass opclassTup; #endif opclass = OpclassnameGetOpcid(BTREE_AM_OID, opcname); #if PG_VERSION_NUM >= 80300 /* Retrieve operator information. */ tp = SearchSysCache(CLAOID, ObjectIdGetDatum(opclass), 0, 0, 0); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for opclass %u", opclass); opclassTup = (Form_pg_opclass) GETSTRUCT(tp); opfamily = opclassTup->opcfamily; opcintype = opclassTup->opcintype; ReleaseSysCache(tp); if (!OidIsValid(opcintype)) { if (indexRel == NULL) indexRel = index_open(index, NoLock); opcintype = RelationGetDescr(indexRel)->attrs[nattr]->atttypid; } oprid = get_opfamily_member(opfamily, opcintype, opcintype, strategy); if (!OidIsValid(oprid)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", strategy, opcintype, opcintype, opfamily); #else oprid = get_opclass_member(opclass, 0, strategy); if (!OidIsValid(oprid)) elog(ERROR, "missing operator %d for %s", strategy, opcname); #endif opcname[-1] = '\0'; appendStringInfo(&str, "%s USING %s", token, get_opname(oprid)); } else appendStringInfoString(&str, token); if (next) appendStringInfoString(&str, ", "); } if (indexRel != NULL) index_close(indexRel, NoLock); PG_RETURN_TEXT_P(cstring_to_text(str.data)); }
/* * ExportSnapshot * Export the snapshot to a file so that other backends can import it. * Returns the token (the file name) that can be used to import this * snapshot. */ static char * ExportSnapshot(Snapshot snapshot) { TransactionId topXid; TransactionId *children; int nchildren; int addTopXid; StringInfoData buf; FILE *f; int i; MemoryContext oldcxt; char path[MAXPGPATH]; char pathtmp[MAXPGPATH]; /* * It's tempting to call RequireTransactionChain here, since it's not very * useful to export a snapshot that will disappear immediately afterwards. * However, we haven't got enough information to do that, since we don't * know if we're at top level or not. For example, we could be inside a * plpgsql function that is going to fire off other transactions via * dblink. Rather than disallow perfectly legitimate usages, don't make a * check. * * Also note that we don't make any restriction on the transaction's * isolation level; however, importers must check the level if they are * serializable. */ /* * This will assign a transaction ID if we do not yet have one. */ topXid = GetTopTransactionId(); /* * We cannot export a snapshot from a subtransaction because there's no * easy way for importers to verify that the same subtransaction is still * running. */ if (IsSubTransaction()) ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), errmsg("cannot export a snapshot from a subtransaction"))); /* * We do however allow previous committed subtransactions to exist. * Importers of the snapshot must see them as still running, so get their * XIDs to add them to the snapshot. */ nchildren = xactGetCommittedChildren(&children); /* * Copy the snapshot into TopTransactionContext, add it to the * exportedSnapshots list, and mark it pseudo-registered. We do this to * ensure that the snapshot's xmin is honored for the rest of the * transaction. (Right now, because SnapshotResetXmin is so stupid, this * is overkill; but later we might make that routine smarter.) */ snapshot = CopySnapshot(snapshot); oldcxt = MemoryContextSwitchTo(TopTransactionContext); exportedSnapshots = lappend(exportedSnapshots, snapshot); MemoryContextSwitchTo(oldcxt); snapshot->regd_count++; RegisteredSnapshots++; /* * Fill buf with a text serialization of the snapshot, plus identification * data about this transaction. The format expected by ImportSnapshot is * pretty rigid: each line must be fieldname:value. */ initStringInfo(&buf); appendStringInfo(&buf, "xid:%u\n", topXid); appendStringInfo(&buf, "dbid:%u\n", MyDatabaseId); appendStringInfo(&buf, "iso:%d\n", XactIsoLevel); appendStringInfo(&buf, "ro:%d\n", XactReadOnly); appendStringInfo(&buf, "xmin:%u\n", snapshot->xmin); appendStringInfo(&buf, "xmax:%u\n", snapshot->xmax); /* * We must include our own top transaction ID in the top-xid data, since * by definition we will still be running when the importing transaction * adopts the snapshot, but GetSnapshotData never includes our own XID in * the snapshot. (There must, therefore, be enough room to add it.) * * However, it could be that our topXid is after the xmax, in which case * we shouldn't include it because xip[] members are expected to be before * xmax. (We need not make the same check for subxip[] members, see * snapshot.h.) */ addTopXid = TransactionIdPrecedes(topXid, snapshot->xmax) ? 1 : 0; appendStringInfo(&buf, "xcnt:%d\n", snapshot->xcnt + addTopXid); for (i = 0; i < snapshot->xcnt; i++) appendStringInfo(&buf, "xip:%u\n", snapshot->xip[i]); if (addTopXid) appendStringInfo(&buf, "xip:%u\n", topXid); /* * Similarly, we add our subcommitted child XIDs to the subxid data. Here, * we have to cope with possible overflow. */ if (snapshot->suboverflowed || snapshot->subxcnt + nchildren > GetMaxSnapshotSubxidCount()) appendStringInfoString(&buf, "sof:1\n"); else { appendStringInfoString(&buf, "sof:0\n"); appendStringInfo(&buf, "sxcnt:%d\n", snapshot->subxcnt + nchildren); for (i = 0; i < snapshot->subxcnt; i++) appendStringInfo(&buf, "sxp:%u\n", snapshot->subxip[i]); for (i = 0; i < nchildren; i++) appendStringInfo(&buf, "sxp:%u\n", children[i]); } appendStringInfo(&buf, "rec:%u\n", snapshot->takenDuringRecovery); /* * Now write the text representation into a file. We first write to a * ".tmp" filename, and rename to final filename if no error. This * ensures that no other backend can read an incomplete file * (ImportSnapshot won't allow it because of its valid-characters check). */ XactExportFilePath(pathtmp, topXid, list_length(exportedSnapshots), ".tmp"); if (!(f = AllocateFile(pathtmp, PG_BINARY_W))) ereport(ERROR, (errcode_for_file_access(), errmsg("could not create file \"%s\": %m", pathtmp))); if (fwrite(buf.data, buf.len, 1, f) != 1) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to file \"%s\": %m", pathtmp))); /* no fsync() since file need not survive a system crash */ if (FreeFile(f)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not write to file \"%s\": %m", pathtmp))); /* * Now that we have written everything into a .tmp file, rename the file * to remove the .tmp suffix. */ XactExportFilePath(path, topXid, list_length(exportedSnapshots), ""); if (rename(pathtmp, path) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not rename file \"%s\" to \"%s\": %m", pathtmp, path))); /* * The basename of the file is what we return from pg_export_snapshot(). * It's already in path in a textual format and we know that the path * starts with SNAPSHOT_EXPORT_DIR. Skip over the prefix and the slash * and pstrdup it so as not to return the address of a local variable. */ return pstrdup(path + strlen(SNAPSHOT_EXPORT_DIR) + 1); }
static text* plvsubst_string(text *template_in, ArrayType *vals_in, text *c_subst, FunctionCallInfo fcinfo) { ArrayType *v = vals_in; int nitems, *dims, ndims; char *p; int16 typlen; bool typbyval; char typalign; char typdelim; Oid typelem; Oid typiofunc; FmgrInfo proc; int i = 0, items = 0; StringInfo sinfo; const char *template_str; int template_len; char *sizes; int *positions; int subst_mb_len; int subst_len; const bits8 *bitmap; int bitmask; if (v != NULL && (ndims = ARR_NDIM(v)) > 0) { if (ndims != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid parameter"), errdetail("Array of arguments has wrong dimension: %d", ndims))); p = ARR_DATA_PTR(v); dims = ARR_DIMS(v); nitems = ArrayGetNItems(ndims, dims); bitmap = ARR_NULLBITMAP(v); get_type_io_data(ARR_ELEMTYPE(v), IOFunc_output, &typlen, &typbyval, &typalign, &typdelim, &typelem, &typiofunc); fmgr_info_cxt(typiofunc, &proc, fcinfo->flinfo->fn_mcxt); } else { nitems = 0; p = NULL; bitmap = NULL; } template_str = VARDATA(template_in); template_len = ora_mb_strlen(template_in, &sizes, &positions); subst_mb_len = ora_mb_strlen1(c_subst); subst_len = VARSIZE_ANY_EXHDR(c_subst); sinfo = makeStringInfo(); bitmask = 1; for (i = 0; i < template_len; i++) { if (strncmp(&template_str[positions[i]], VARDATA(c_subst), subst_len) == 0) { Datum itemvalue; char *value; if (items++ < nitems) { if (bitmap && (*bitmap & bitmask) == 0) value = pstrdup("NULL"); else { itemvalue = fetch_att(p, typbyval, typlen); value = DatumGetCString(FunctionCall3(&proc, itemvalue, ObjectIdGetDatum(typelem), Int32GetDatum(-1))); p = att_addlength_pointer(p, typlen, p); p = (char *) att_align_nominal(p, typalign); } appendStringInfoString(sinfo, value); pfree(value); if (bitmap) { bitmask <<= 1; if (bitmask == 0x100) { bitmap++; bitmask = 1; } } } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too few parameters specified for template string"))); i += subst_mb_len - 1; } else appendBinaryStringInfo(sinfo, &template_str[positions[i]], sizes[i]); } return cstring_to_text(sinfo->data); }
/* * Extract a Python traceback from the given exception data. * * The exception error message is returned in xmsg, the traceback in * tbmsg (both as palloc'd strings) and the traceback depth in * tb_depth. * * We release refcounts on all the Python objects in the traceback stack, * but not on e or v. */ static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, char **xmsg, char **tbmsg, int *tb_depth) { PyObject *e_type_o; PyObject *e_module_o; char *e_type_s = NULL; char *e_module_s = NULL; PyObject *vob = NULL; char *vstr; StringInfoData xstr; StringInfoData tbstr; /* * if no exception, return nulls */ if (e == NULL) { *xmsg = NULL; *tbmsg = NULL; *tb_depth = 0; return; } /* * Format the exception and its value and put it in xmsg. */ e_type_o = PyObject_GetAttrString(e, "__name__"); e_module_o = PyObject_GetAttrString(e, "__module__"); if (e_type_o) e_type_s = PyString_AsString(e_type_o); if (e_type_s) e_module_s = PyString_AsString(e_module_o); if (v && ((vob = PyObject_Str(v)) != NULL)) vstr = PyString_AsString(vob); else vstr = "unknown"; initStringInfo(&xstr); if (!e_type_s || !e_module_s) { if (PyString_Check(e)) /* deprecated string exceptions */ appendStringInfoString(&xstr, PyString_AsString(e)); else /* shouldn't happen */ appendStringInfoString(&xstr, "unrecognized exception"); } /* mimics behavior of traceback.format_exception_only */ else if (strcmp(e_module_s, "builtins") == 0 || strcmp(e_module_s, "__main__") == 0 || strcmp(e_module_s, "exceptions") == 0) appendStringInfo(&xstr, "%s", e_type_s); else appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); appendStringInfo(&xstr, ": %s", vstr); *xmsg = xstr.data; /* * Now format the traceback and put it in tbmsg. */ *tb_depth = 0; initStringInfo(&tbstr); /* Mimick Python traceback reporting as close as possible. */ appendStringInfoString(&tbstr, "Traceback (most recent call last):"); while (tb != NULL && tb != Py_None) { PyObject *volatile tb_prev = NULL; PyObject *volatile frame = NULL; PyObject *volatile code = NULL; PyObject *volatile name = NULL; PyObject *volatile lineno = NULL; PyObject *volatile filename = NULL; PG_TRY(); { /* * Ancient versions of Python (circa 2.3) contain a bug whereby * the fetches below can fail if the error indicator is set. */ PyErr_Clear(); lineno = PyObject_GetAttrString(tb, "tb_lineno"); if (lineno == NULL) elog(ERROR, "could not get line number from Python traceback"); frame = PyObject_GetAttrString(tb, "tb_frame"); if (frame == NULL) elog(ERROR, "could not get frame from Python traceback"); code = PyObject_GetAttrString(frame, "f_code"); if (code == NULL) elog(ERROR, "could not get code object from Python frame"); name = PyObject_GetAttrString(code, "co_name"); if (name == NULL) elog(ERROR, "could not get function name from Python code object"); filename = PyObject_GetAttrString(code, "co_filename"); if (filename == NULL) elog(ERROR, "could not get file name from Python code object"); } PG_CATCH(); { Py_XDECREF(frame); Py_XDECREF(code); Py_XDECREF(name); Py_XDECREF(lineno); Py_XDECREF(filename); PG_RE_THROW(); } PG_END_TRY(); /* The first frame always points at <module>, skip it. */ if (*tb_depth > 0) { PLyExecutionContext *exec_ctx = PLy_current_execution_context(); char *proname; char *fname; char *line; char *plain_filename; long plain_lineno; /* * The second frame points at the internal function, but to mimick * Python error reporting we want to say <module>. */ if (*tb_depth == 1) fname = "<module>"; else fname = PyString_AsString(name); proname = PLy_procedure_name(exec_ctx->curr_proc); plain_filename = PyString_AsString(filename); plain_lineno = PyInt_AsLong(lineno); if (proname == NULL) appendStringInfo( &tbstr, "\n PL/Python anonymous code block, line %ld, in %s", plain_lineno - 1, fname); else appendStringInfo( &tbstr, "\n PL/Python function \"%s\", line %ld, in %s", proname, plain_lineno - 1, fname); /* * function code object was compiled with "<string>" as the * filename */ if (exec_ctx->curr_proc && plain_filename != NULL && strcmp(plain_filename, "<string>") == 0) { /* * If we know the current procedure, append the exact line * from the source, again mimicking Python's traceback.py * module behavior. We could store the already line-split * source to avoid splitting it every time, but producing a * traceback is not the most important scenario to optimize * for. But we do not go as far as traceback.py in reading * the source of imported modules. */ line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); if (line) { appendStringInfo(&tbstr, "\n %s", line); pfree(line); } } } Py_DECREF(frame); Py_DECREF(code); Py_DECREF(name); Py_DECREF(lineno); Py_DECREF(filename); /* Release the current frame and go to the next one. */ tb_prev = tb; tb = PyObject_GetAttrString(tb, "tb_next"); Assert(tb_prev != Py_None); Py_DECREF(tb_prev); if (tb == NULL) elog(ERROR, "could not traverse Python traceback"); (*tb_depth)++; } /* Return the traceback. */ *tbmsg = tbstr.data; Py_XDECREF(e_type_o); Py_XDECREF(e_module_o); Py_XDECREF(vob); }
static void fmtLogMsg(StringInfo dst, ErrorData *edata) { { char formattedLogTime[FORMATTED_TS_LEN]; /* timestamp with milliseconds */ formatNow(formattedLogTime, sizeof formattedLogTime); /* * Always present, non-nullable; don't need to write the N/P * header. */ appendStringInfoString(dst, formattedLogTime); appendStringInfoChar(dst, '\0'); } /* username */ if (MyProcPort) appendStringInfoPtr(dst, MyProcPort->user_name); else appendStringInfoPtr(dst, NULL); /* database name */ if (MyProcPort) appendStringInfoPtr(dst, MyProcPort->database_name); else appendStringInfoPtr(dst, NULL); /* Process id */ { uint32_t nPid = htobe32(savedPid); appendBinaryStringInfo(dst, (void *) &nPid, sizeof nPid); } /* Remote host and port */ if (MyProcPort && MyProcPort->remote_host) { /* 'present' string header, since this string is nullable */ appendStringInfoChar(dst, 'P'); appendStringInfoString(dst, MyProcPort->remote_host); if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') { appendStringInfoChar(dst, ':'); appendStringInfoString(dst, MyProcPort->remote_port); } appendStringInfoChar(dst, '\0'); } else appendStringInfoPtr(dst, NULL); /* session id; non-nullable */ appendStringInfo(dst, "%lx.%x", (long) MyStartTime, MyProcPid); appendStringInfoChar(dst, '\0'); /* Line number */ { uint64_t nSeqNum = htobe64(seqNum); appendBinaryStringInfo(dst, (void *) &nSeqNum, sizeof nSeqNum); } /* PS display */ if (MyProcPort) { StringInfoData msgbuf; const char *psdisp; int displen; initStringInfo(&msgbuf); psdisp = get_ps_display(&displen); appendBinaryStringInfo(&msgbuf, psdisp, displen); appendStringInfoChar(dst, 'P'); appendStringInfoString(dst, msgbuf.data); appendStringInfoChar(dst, '\0'); pfree(msgbuf.data); } else appendStringInfoPtr(dst, NULL); /* session start timestamp */ if (cachedBackendStartTime[0] == '\0') { /* Rebuild the cache if it was blown */ reCacheBackendStartTime(); } /* backend start time; non-nullable string */ appendStringInfoString(dst, cachedBackendStartTime); appendStringInfoChar(dst, '\0'); /* * Virtual transaction id * * keep VXID format in sync with lockfuncs.c */ if (MyProc != NULL && MyProc->backendId != InvalidBackendId) { appendStringInfoChar(dst, 'P'); appendStringInfo(dst, "%d/%u", MyProc->backendId, MyProc->lxid); appendStringInfoChar(dst, '\0'); } else appendStringInfoPtr(dst, NULL); /* * Transaction id * * This seems to be a mistake both here and in elog.c; in particular, it's * not clear how the epoch would get added here. However, leave room in * the protocol to fix this later by upcasting. */ { uint64_t nTxid = htobe64((uint64) GetTopTransactionIdIfAny()); appendBinaryStringInfo(dst, (void *) &nTxid, sizeof nTxid); } /* Error severity */ { uint32_t nelevel = htobe32(edata->elevel); appendBinaryStringInfo(dst, (void *) &nelevel, sizeof nelevel); } /* SQL state code */ appendStringInfoPtr(dst, unpack_sql_state(edata->sqlerrcode)); /* errmessage */ appendStringInfoPtr(dst, edata->message); /* errdetail or errdetail_log */ if (edata->detail_log) appendStringInfoPtr(dst, edata->detail_log); else appendStringInfoPtr(dst, edata->detail); /* errhint */ appendStringInfoPtr(dst, edata->hint); /* internal query */ appendStringInfoPtr(dst, edata->internalquery); /* if printed internal query, print internal pos too */ if (edata->internalpos > 0 && edata->internalquery != NULL) { uint32_t ninternalpos = htobe32(edata->internalpos); appendBinaryStringInfo(dst, (void *) &ninternalpos, sizeof ninternalpos); } else { uint32_t ninternalpos = htobe32(-1); appendBinaryStringInfo(dst, (void *) &ninternalpos, sizeof ninternalpos); } /* errcontext */ appendStringInfoPtr(dst, edata->context); /* * user query --- only reported if not disabled by the caller. * * Also include query position. */ if (isLogLevelOutput(edata->elevel, log_min_error_statement) && debug_query_string != NULL && !edata->hide_stmt) { uint32_t nCursorPos = htobe32(edata->cursorpos); appendStringInfoPtr(dst, debug_query_string); appendBinaryStringInfo(dst, (void *) &nCursorPos, sizeof nCursorPos); } else { uint32_t nCursorPos = htobe32(-1); appendStringInfoPtr(dst, NULL); appendBinaryStringInfo(dst, (void *) &nCursorPos, sizeof nCursorPos); } /* file error location */ if (Log_error_verbosity >= PGERROR_VERBOSE) { StringInfoData msgbuf; initStringInfo(&msgbuf); if (edata->funcname && edata->filename) appendStringInfo(&msgbuf, "%s, %s:%d", edata->funcname, edata->filename, edata->lineno); else if (edata->filename) appendStringInfo(&msgbuf, "%s:%d", edata->filename, edata->lineno); appendStringInfoChar(dst, 'P'); appendStringInfoString(dst, msgbuf.data); appendStringInfoChar(dst, '\0'); pfree(msgbuf.data); } else appendStringInfoPtr(dst, NULL); /* application name */ appendStringInfoPtr(dst, application_name); }
/* * Add string fragment to query. */ bool plproxy_query_add_const(QueryBuffer *q, const char *data) { appendStringInfoString(q->sql, data); return true; }
/* * Extract all item values from a BRIN index page * * Usage: SELECT * FROM brin_page_items(get_raw_page('idx', 1), 'idx'::regclass); */ Datum brin_page_items(PG_FUNCTION_ARGS) { brin_page_state *state; FuncCallContext *fctx; if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser to use raw page functions")))); if (SRF_IS_FIRSTCALL()) { bytea *raw_page = PG_GETARG_BYTEA_P(0); Oid indexRelid = PG_GETARG_OID(1); Page page; TupleDesc tupdesc; MemoryContext mctx; Relation indexRel; AttrNumber attno; /* minimally verify the page we got */ page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular"); /* create a function context for cross-call persistence */ fctx = SRF_FIRSTCALL_INIT(); /* switch to memory context appropriate for multiple function calls */ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); indexRel = index_open(indexRelid, AccessShareLock); state = palloc(offsetof(brin_page_state, columns) + sizeof(brin_column_state) * RelationGetDescr(indexRel)->natts); state->bdesc = brin_build_desc(indexRel); state->page = page; state->offset = FirstOffsetNumber; state->unusedItem = false; state->done = false; state->dtup = NULL; /* * Initialize output functions for all indexed datatypes; simplifies * calling them later. */ for (attno = 1; attno <= state->bdesc->bd_tupdesc->natts; attno++) { Oid output; bool isVarlena; BrinOpcInfo *opcinfo; int i; brin_column_state *column; opcinfo = state->bdesc->bd_info[attno - 1]; column = palloc(offsetof(brin_column_state, outputFn) + sizeof(FmgrInfo) * opcinfo->oi_nstored); column->nstored = opcinfo->oi_nstored; for (i = 0; i < opcinfo->oi_nstored; i++) { getTypeOutputInfo(opcinfo->oi_typids[i], &output, &isVarlena); fmgr_info(output, &column->outputFn[i]); } state->columns[attno - 1] = column; } index_close(indexRel, AccessShareLock); fctx->user_fctx = state; fctx->tuple_desc = BlessTupleDesc(tupdesc); MemoryContextSwitchTo(mctx); } fctx = SRF_PERCALL_SETUP(); state = fctx->user_fctx; if (!state->done) { HeapTuple result; Datum values[7]; bool nulls[7]; /* * This loop is called once for every attribute of every tuple in the * page. At the start of a tuple, we get a NULL dtup; that's our * signal for obtaining and decoding the next one. If that's not the * case, we output the next attribute. */ if (state->dtup == NULL) { BrinTuple *tup; MemoryContext mctx; ItemId itemId; /* deformed tuple must live across calls */ mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); /* verify item status: if there's no data, we can't decode */ itemId = PageGetItemId(state->page, state->offset); if (ItemIdIsUsed(itemId)) { tup = (BrinTuple *) PageGetItem(state->page, PageGetItemId(state->page, state->offset)); state->dtup = brin_deform_tuple(state->bdesc, tup); state->attno = 1; state->unusedItem = false; } else state->unusedItem = true; MemoryContextSwitchTo(mctx); } else state->attno++; MemSet(nulls, 0, sizeof(nulls)); if (state->unusedItem) { values[0] = UInt16GetDatum(state->offset); nulls[1] = true; nulls[2] = true; nulls[3] = true; nulls[4] = true; nulls[5] = true; nulls[6] = true; } else { int att = state->attno - 1; values[0] = UInt16GetDatum(state->offset); values[1] = UInt32GetDatum(state->dtup->bt_blkno); values[2] = UInt16GetDatum(state->attno); values[3] = BoolGetDatum(state->dtup->bt_columns[att].bv_allnulls); values[4] = BoolGetDatum(state->dtup->bt_columns[att].bv_hasnulls); values[5] = BoolGetDatum(state->dtup->bt_placeholder); if (!state->dtup->bt_columns[att].bv_allnulls) { BrinValues *bvalues = &state->dtup->bt_columns[att]; StringInfoData s; bool first; int i; initStringInfo(&s); appendStringInfoChar(&s, '{'); first = true; for (i = 0; i < state->columns[att]->nstored; i++) { char *val; if (!first) appendStringInfoString(&s, " .. "); first = false; val = OutputFunctionCall(&state->columns[att]->outputFn[i], bvalues->bv_values[i]); appendStringInfoString(&s, val); pfree(val); } appendStringInfoChar(&s, '}'); values[6] = CStringGetTextDatum(s.data); pfree(s.data); } else { nulls[6] = true; } } result = heap_form_tuple(fctx->tuple_desc, values, nulls); /* * If the item was unused, jump straight to the next one; otherwise, * the only cleanup needed here is to set our signal to go to the next * tuple in the following iteration, by freeing the current one. */ if (state->unusedItem) state->offset = OffsetNumberNext(state->offset); else if (state->attno >= state->bdesc->bd_tupdesc->natts) { pfree(state->dtup); state->dtup = NULL; state->offset = OffsetNumberNext(state->offset); } /* * If we're beyond the end of the page, set flag to end the function in * the following iteration. */ if (state->offset > PageGetMaxOffsetNumber(state->page)) state->done = true; SRF_RETURN_NEXT(fctx, HeapTupleGetDatum(result)); } brin_free_desc(state->bdesc); SRF_RETURN_DONE(fctx); }