Branch data Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * pgstat_relation.c
4 : : * Implementation of relation statistics.
5 : : *
6 : : * This file contains the implementation of relation statistics. It is kept
7 : : * separate from pgstat.c to enforce the line between the statistics access /
8 : : * storage implementation and the details about individual types of
9 : : * statistics.
10 : : *
11 : : * Copyright (c) 2001-2026, PostgreSQL Global Development Group
12 : : *
13 : : * IDENTIFICATION
14 : : * src/backend/utils/activity/pgstat_relation.c
15 : : * -------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "postgres.h"
19 : :
20 : : #include "access/twophase_rmgr.h"
21 : : #include "access/xact.h"
22 : : #include "catalog/catalog.h"
23 : : #include "utils/memutils.h"
24 : : #include "utils/pgstat_internal.h"
25 : : #include "utils/rel.h"
26 : : #include "utils/timestamp.h"
27 : :
28 : :
29 : : /* Record that's written to 2PC state file when pgstat state is persisted */
30 : : typedef struct TwoPhasePgStatRecord
31 : : {
32 : : PgStat_Counter tuples_inserted; /* tuples inserted in xact */
33 : : PgStat_Counter tuples_updated; /* tuples updated in xact */
34 : : PgStat_Counter tuples_deleted; /* tuples deleted in xact */
35 : : /* tuples i/u/d prior to truncate/drop */
36 : : PgStat_Counter inserted_pre_truncdrop;
37 : : PgStat_Counter updated_pre_truncdrop;
38 : : PgStat_Counter deleted_pre_truncdrop;
39 : : Oid id; /* table's OID */
40 : : bool shared; /* is it a shared catalog? */
41 : : bool truncdropped; /* was the relation truncated/dropped? */
42 : : } TwoPhasePgStatRecord;
43 : :
44 : :
45 : : static PgStat_TableStatus *pgstat_prep_relation_pending(PgStat_Kind kind,
46 : : Oid rel_id, bool isshared);
47 : : static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
48 : : static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
49 : : static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
50 : : static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
51 : :
52 : : /*
53 : : * Determine the stats kind for a relation based on its relkind.
54 : : */
55 : : static inline PgStat_Kind
56 : 1391296 : pgstat_get_relation_kind(char relkind)
57 : : {
58 [ + + ]: 1391296 : if (relkind == RELKIND_INDEX)
59 : 764282 : return PGSTAT_KIND_INDEX;
60 : 627014 : return PGSTAT_KIND_RELATION;
61 : : }
62 : :
63 : :
64 : : /*
65 : : * Copy stats between relations. This is used for things like REINDEX
66 : : * CONCURRENTLY.
67 : : */
68 : : void
69 : 342 : pgstat_copy_relation_stats(Relation dst, Relation src)
70 : : {
71 : 342 : PgStat_Kind kind = pgstat_get_relation_kind(src->rd_rel->relkind);
72 : :
73 [ + - ]: 342 : if (kind == PGSTAT_KIND_INDEX)
74 : : {
75 : : PgStat_StatIdxEntry *srcstats;
76 : : PgStatShared_Index *dstshstats;
77 : : PgStat_EntryRef *dst_ref;
78 : :
79 : 342 : srcstats = pgstat_fetch_stat_idxentry_ext(src->rd_rel->relisshared,
80 : : RelationGetRelid(src),
81 : : NULL);
82 [ + + ]: 342 : if (!srcstats)
83 : 193 : return;
84 : :
85 : 149 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
86 : 149 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
87 [ - + ]: 149 : RelationGetRelid(dst),
88 : : false);
89 : :
90 : 149 : dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
91 : 149 : dstshstats->stats = *srcstats;
92 : :
93 : 149 : pgstat_unlock_entry(dst_ref);
94 : : }
95 : : else
96 : : {
97 : : PgStat_StatTabEntry *srcstats;
98 : : PgStatShared_Relation *dstshstats;
99 : : PgStat_EntryRef *dst_ref;
100 : :
101 : 0 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
102 : : RelationGetRelid(src),
103 : : NULL);
104 [ # # ]: 0 : if (!srcstats)
105 : 0 : return;
106 : :
107 : 0 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
108 : 0 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
109 [ # # ]: 0 : RelationGetRelid(dst),
110 : : false);
111 : :
112 : 0 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
113 : 0 : dstshstats->stats = *srcstats;
114 : :
115 : 0 : pgstat_unlock_entry(dst_ref);
116 : : }
117 : : }
118 : :
119 : : /*
120 : : * Initialize a relcache entry to count access statistics. Called whenever a
121 : : * relation is opened.
122 : : *
123 : : * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
124 : : * when the relcache entry is made; thereafter it is long-lived data.
125 : : *
126 : : * This does not create a reference to a stats entry in shared memory, nor
127 : : * allocate memory for the pending stats. That happens in
128 : : * pgstat_assoc_relation().
129 : : */
130 : : void
131 : 28729637 : pgstat_init_relation(Relation rel)
132 : : {
133 : 28729637 : char relkind = rel->rd_rel->relkind;
134 : :
135 : : /*
136 : : * We only count stats for relations with storage and partitioned tables
137 : : */
138 [ + + + + : 28729637 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
+ + + + +
+ + + ]
139 : : {
140 : 100279 : rel->pgstat_enabled = false;
141 : 100279 : rel->pgstat_info = NULL;
142 : 100279 : return;
143 : : }
144 : :
145 [ + + ]: 28629358 : if (!pgstat_track_counts)
146 : : {
147 [ + + ]: 197 : if (rel->pgstat_info)
148 : 12 : pgstat_unlink_relation(rel);
149 : :
150 : : /* We're not counting at all */
151 : 197 : rel->pgstat_enabled = false;
152 : 197 : rel->pgstat_info = NULL;
153 : 197 : return;
154 : : }
155 : :
156 : 28629161 : rel->pgstat_enabled = true;
157 : : }
158 : :
159 : : /*
160 : : * Prepare for statistics for this relation to be collected.
161 : : *
162 : : * This ensures we have a reference to the stats entry before stats can be
163 : : * generated. That is important because a relation drop in another connection
164 : : * could otherwise lead to the stats entry being dropped, which then later
165 : : * would get recreated when flushing stats.
166 : : *
167 : : * This is separate from pgstat_init_relation() as it is not uncommon for
168 : : * relcache entries to be opened without ever getting stats reported.
169 : : */
170 : : void
171 : 1248460 : pgstat_assoc_relation(Relation rel)
172 : : {
173 : : PgStat_Kind kind;
174 : :
175 : : Assert(rel->pgstat_enabled);
176 : : Assert(rel->pgstat_info == NULL);
177 : :
178 : 1248460 : kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
179 : :
180 : : /* find or make the PgStat_TableStatus entry, and update link */
181 : 2496920 : rel->pgstat_info = pgstat_prep_relation_pending(kind,
182 : : RelationGetRelid(rel),
183 : 1248460 : rel->rd_rel->relisshared);
184 : :
185 : : /* don't allow link a stats to multiple relcache entries */
186 : : Assert(rel->pgstat_info->relation == NULL);
187 : :
188 : : /* mark this relation as the owner */
189 : 1248460 : rel->pgstat_info->relation = rel;
190 : 1248460 : }
191 : :
192 : : /*
193 : : * Break the mutual link between a relcache entry and pending stats entry.
194 : : * This must be called whenever one end of the link is removed.
195 : : */
196 : : void
197 : 1966669 : pgstat_unlink_relation(Relation rel)
198 : : {
199 : : /* remove the link to stats info if any */
200 [ + + ]: 1966669 : if (rel->pgstat_info == NULL)
201 : 718209 : return;
202 : :
203 : : /* link sanity check */
204 : : Assert(rel->pgstat_info->relation == rel);
205 : 1248460 : rel->pgstat_info->relation = NULL;
206 : 1248460 : rel->pgstat_info = NULL;
207 : : }
208 : :
209 : : /*
210 : : * Ensure that stats are dropped if transaction aborts.
211 : : */
212 : : void
213 : 91474 : pgstat_create_relation(Relation rel)
214 : : {
215 : 91474 : PgStat_Kind kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
216 : :
217 : 91474 : pgstat_create_transactional(kind,
218 : 91474 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
219 [ + + ]: 91474 : RelationGetRelid(rel));
220 : 91474 : }
221 : :
222 : : /*
223 : : * Ensure that stats are dropped if transaction commits.
224 : : */
225 : : void
226 : 51020 : pgstat_drop_relation(Relation rel)
227 : : {
228 : 51020 : int nest_level = GetCurrentTransactionNestLevel();
229 : : PgStat_TableStatus *pgstat_info;
230 : 51020 : PgStat_Kind kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
231 : :
232 : 51020 : pgstat_drop_transactional(kind,
233 : 51020 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
234 [ - + ]: 51020 : RelationGetRelid(rel));
235 : :
236 [ + + + + : 51020 : if (!pgstat_should_count_relation(rel))
+ + ]
237 : 5178 : return;
238 : :
239 : : /*
240 : : * Transactionally set counters to 0. That ensures that accesses to
241 : : * pg_stat_xact_all_tables inside the transaction show 0.
242 : : */
243 : 45842 : pgstat_info = rel->pgstat_info;
244 [ + + ]: 45842 : if (pgstat_info->trans &&
245 [ + + ]: 892 : pgstat_info->trans->nest_level == nest_level)
246 : : {
247 : 888 : save_truncdrop_counters(pgstat_info->trans, true);
248 : 888 : pgstat_info->trans->tuples_inserted = 0;
249 : 888 : pgstat_info->trans->tuples_updated = 0;
250 : 888 : pgstat_info->trans->tuples_deleted = 0;
251 : : }
252 : : }
253 : :
254 : : /*
255 : : * Report that the table was just vacuumed and flush IO statistics.
256 : : */
257 : : void
258 : 128615 : pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
259 : : PgStat_Counter deadtuples, TimestampTz starttime)
260 : : {
261 : : PgStat_EntryRef *entry_ref;
262 : : PgStatShared_Relation *shtabentry;
263 : : PgStat_StatTabEntry *tabentry;
264 [ + + ]: 128615 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
265 : : TimestampTz ts;
266 : : PgStat_Counter elapsedtime;
267 : :
268 [ - + ]: 128615 : if (!pgstat_track_counts)
269 : 0 : return;
270 : :
271 : : /* Store the data in the table's hash table entry. */
272 : 128615 : ts = GetCurrentTimestamp();
273 : 128615 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
274 : :
275 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
276 : 128615 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
277 : 128615 : RelationGetRelid(rel), false);
278 : :
279 : 128615 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
280 : 128615 : tabentry = &shtabentry->stats;
281 : :
282 : 128615 : tabentry->live_tuples = livetuples;
283 : 128615 : tabentry->dead_tuples = deadtuples;
284 : :
285 : : /*
286 : : * It is quite possible that a non-aggressive VACUUM ended up skipping
287 : : * various pages, however, we'll zero the insert counter here regardless.
288 : : * It's currently used only to track when we need to perform an "insert"
289 : : * autovacuum, which are mainly intended to freeze newly inserted tuples.
290 : : * Zeroing this may just mean we'll not try to vacuum the table again
291 : : * until enough tuples have been inserted to trigger another insert
292 : : * autovacuum. An anti-wraparound autovacuum will catch any persistent
293 : : * stragglers.
294 : : */
295 : 128615 : tabentry->ins_since_vacuum = 0;
296 : :
297 [ + + ]: 128615 : if (AmAutoVacuumWorkerProcess())
298 : : {
299 : 111963 : tabentry->last_autovacuum_time = ts;
300 : 111963 : tabentry->autovacuum_count++;
301 : 111963 : tabentry->total_autovacuum_time += elapsedtime;
302 : : }
303 : : else
304 : : {
305 : 16652 : tabentry->last_vacuum_time = ts;
306 : 16652 : tabentry->vacuum_count++;
307 : 16652 : tabentry->total_vacuum_time += elapsedtime;
308 : : }
309 : :
310 : 128615 : pgstat_unlock_entry(entry_ref);
311 : :
312 : : /*
313 : : * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
314 : : * however this will not be called until after an entire autovacuum cycle
315 : : * is done -- which will likely vacuum many relations -- or until the
316 : : * VACUUM command has processed all tables and committed.
317 : : */
318 : 128615 : pgstat_flush_io(false);
319 : 128615 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
320 : : }
321 : :
322 : : /*
323 : : * Report that the table was just analyzed and flush IO statistics.
324 : : *
325 : : * Caller must provide new live- and dead-tuples estimates, as well as a
326 : : * flag indicating whether to reset the mod_since_analyze counter.
327 : : */
328 : : void
329 : 10642 : pgstat_report_analyze(Relation rel,
330 : : PgStat_Counter livetuples, PgStat_Counter deadtuples,
331 : : bool resetcounter, TimestampTz starttime)
332 : : {
333 : : PgStat_EntryRef *entry_ref;
334 : : PgStatShared_Relation *shtabentry;
335 : : PgStat_StatTabEntry *tabentry;
336 [ + + ]: 10642 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
337 : : TimestampTz ts;
338 : : PgStat_Counter elapsedtime;
339 : :
340 [ - + ]: 10642 : if (!pgstat_track_counts)
341 : 0 : return;
342 : :
343 : : /*
344 : : * Unlike VACUUM, ANALYZE might be running inside a transaction that has
345 : : * already inserted and/or deleted rows in the target table. ANALYZE will
346 : : * have counted such rows as live or dead respectively. Because we will
347 : : * report our counts of such rows at transaction end, we should subtract
348 : : * off these counts from the update we're making now, else they'll be
349 : : * double-counted after commit. (This approach also ensures that the
350 : : * shared stats entry ends up with the right numbers if we abort instead
351 : : * of committing.)
352 : : *
353 : : * Waste no time on partitioned tables, though.
354 : : */
355 [ + + + + : 10642 : if (pgstat_should_count_relation(rel) &&
+ + ]
356 [ + + ]: 10598 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
357 : : {
358 : : PgStat_TableXactStatus *trans;
359 : :
360 [ + + ]: 10219 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
361 : : {
362 : 127 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
363 : 127 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
364 : : }
365 : : /* count stuff inserted by already-aborted subxacts, too */
366 : 10092 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
367 : : /* Since ANALYZE's counts are estimates, we could have underflowed */
368 : 10092 : livetuples = Max(livetuples, 0);
369 : 10092 : deadtuples = Max(deadtuples, 0);
370 : : }
371 : :
372 : : /* Store the data in the table's hash table entry. */
373 : 10642 : ts = GetCurrentTimestamp();
374 : 10642 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
375 : :
376 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
377 : 10642 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
378 : 10642 : RelationGetRelid(rel),
379 : : false);
380 : : /* can't get dropped while accessed */
381 : : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
382 : :
383 : 10642 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
384 : 10642 : tabentry = &shtabentry->stats;
385 : :
386 : 10642 : tabentry->live_tuples = livetuples;
387 : 10642 : tabentry->dead_tuples = deadtuples;
388 : :
389 : : /*
390 : : * If commanded, reset mod_since_analyze to zero. This forgets any
391 : : * changes that were committed while the ANALYZE was in progress, but we
392 : : * have no good way to estimate how many of those there were.
393 : : */
394 [ + + ]: 10642 : if (resetcounter)
395 : 10602 : tabentry->mod_since_analyze = 0;
396 : :
397 [ + + ]: 10642 : if (AmAutoVacuumWorkerProcess())
398 : : {
399 : 470 : tabentry->last_autoanalyze_time = ts;
400 : 470 : tabentry->autoanalyze_count++;
401 : 470 : tabentry->total_autoanalyze_time += elapsedtime;
402 : : }
403 : : else
404 : : {
405 : 10172 : tabentry->last_analyze_time = ts;
406 : 10172 : tabentry->analyze_count++;
407 : 10172 : tabentry->total_analyze_time += elapsedtime;
408 : : }
409 : :
410 : 10642 : pgstat_unlock_entry(entry_ref);
411 : :
412 : : /* see pgstat_report_vacuum() */
413 : 10642 : pgstat_flush_io(false);
414 : 10642 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
415 : : }
416 : :
417 : : /*
418 : : * count a tuple insertion of n tuples
419 : : */
420 : : void
421 : 12427526 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
422 : : {
423 [ + + + + : 12427526 : if (pgstat_should_count_relation(rel))
+ + ]
424 : : {
425 : 12211779 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
426 : :
427 : 12211779 : ensure_tabstat_xact_level(pgstat_info);
428 : 12211779 : pgstat_info->trans->tuples_inserted += n;
429 : : }
430 : 12427526 : }
431 : :
432 : : /*
433 : : * count a tuple update
434 : : */
435 : : void
436 : 2392384 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
437 : : {
438 : : Assert(!(hot && newpage));
439 : :
440 [ + + - + : 2392384 : if (pgstat_should_count_relation(rel))
+ + ]
441 : : {
442 : 2392382 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
443 : :
444 : 2392382 : ensure_tabstat_xact_level(pgstat_info);
445 : 2392382 : pgstat_info->trans->tuples_updated++;
446 : :
447 : : /*
448 : : * tuples_hot_updated and tuples_newpage_updated counters are
449 : : * nontransactional, so just advance them
450 : : */
451 [ + + ]: 2392382 : if (hot)
452 : 176865 : pgstat_info->counts.tuples_hot_updated++;
453 [ + + ]: 2215517 : else if (newpage)
454 : 2198311 : pgstat_info->counts.tuples_newpage_updated++;
455 : : }
456 : 2392384 : }
457 : :
458 : : /*
459 : : * count a tuple deletion
460 : : */
461 : : void
462 : 1927653 : pgstat_count_heap_delete(Relation rel)
463 : : {
464 [ - + - - : 1927653 : if (pgstat_should_count_relation(rel))
+ - ]
465 : : {
466 : 1927653 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
467 : :
468 : 1927653 : ensure_tabstat_xact_level(pgstat_info);
469 : 1927653 : pgstat_info->trans->tuples_deleted++;
470 : : }
471 : 1927653 : }
472 : :
473 : : /*
474 : : * update tuple counters due to truncate
475 : : */
476 : : void
477 : 2385 : pgstat_count_truncate(Relation rel)
478 : : {
479 [ + + + - : 2385 : if (pgstat_should_count_relation(rel))
+ - ]
480 : : {
481 : 2385 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
482 : :
483 : 2385 : ensure_tabstat_xact_level(pgstat_info);
484 : 2385 : save_truncdrop_counters(pgstat_info->trans, false);
485 : 2385 : pgstat_info->trans->tuples_inserted = 0;
486 : 2385 : pgstat_info->trans->tuples_updated = 0;
487 : 2385 : pgstat_info->trans->tuples_deleted = 0;
488 : : }
489 : 2385 : }
490 : :
491 : : /*
492 : : * update dead-tuples count
493 : : *
494 : : * The semantics of this are that we are reporting the nontransactional
495 : : * recovery of "delta" dead tuples; so delta_dead_tuples decreases
496 : : * rather than increasing, and the change goes straight into the per-table
497 : : * counter, not into transactional state.
498 : : */
499 : : void
500 : 22240 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
501 : : {
502 [ - + - - : 22240 : if (pgstat_should_count_relation(rel))
+ - ]
503 : : {
504 : 22240 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
505 : :
506 : 22240 : pgstat_info->counts.delta_dead_tuples -= delta;
507 : : }
508 : 22240 : }
509 : :
510 : : /*
511 : : * Support function for the SQL-callable pgstat* functions. Returns
512 : : * the collected statistics for one table or NULL. NULL doesn't mean
513 : : * that the table doesn't exist, just that there are no statistics, so the
514 : : * caller is better off to report ZERO instead.
515 : : */
516 : : PgStat_StatTabEntry *
517 : 6556 : pgstat_fetch_stat_tabentry(Oid relid)
518 : : {
519 : 6556 : return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid, NULL);
520 : : }
521 : :
522 : : /*
523 : : * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
524 : : * whether the to-be-accessed table is a shared relation or not. This version
525 : : * also returns whether the caller can pfree() the result if desired.
526 : : */
527 : : PgStat_StatTabEntry *
528 : 306550 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid, bool *may_free)
529 : : {
530 [ + + ]: 306550 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
531 : :
532 : 306550 : return (PgStat_StatTabEntry *)
533 : 306550 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid, may_free);
534 : : }
535 : :
536 : : /*
537 : : * find any existing PgStat_TableStatus entry for rel
538 : : *
539 : : * Find any existing PgStat_TableStatus entry for rel_id in the current
540 : : * database. If not found, try finding from shared tables.
541 : : *
542 : : * If an entry is found, copy it and increment the copy's counters with their
543 : : * subtransaction counterparts, then return the copy. The caller may need to
544 : : * pfree() the copy.
545 : : *
546 : : * If no entry found, return NULL, don't create a new one.
547 : : */
548 : : PgStat_TableStatus *
549 : 32 : find_tabstat_entry(Oid rel_id)
550 : : {
551 : 32 : return find_tabstat_entry_kind(PGSTAT_KIND_RELATION, rel_id);
552 : : }
553 : :
554 : : /*
555 : : * Same as find_tabstat_entry but for a specific stats kind.
556 : : */
557 : : PgStat_TableStatus *
558 : 32 : find_tabstat_entry_kind(PgStat_Kind kind, Oid rel_id)
559 : : {
560 : : PgStat_EntryRef *entry_ref;
561 : : PgStat_TableXactStatus *trans;
562 : 32 : PgStat_TableStatus *tabentry = NULL;
563 : 32 : PgStat_TableStatus *tablestatus = NULL;
564 : :
565 : 32 : entry_ref = pgstat_fetch_pending_entry(kind, MyDatabaseId, rel_id);
566 [ + + ]: 32 : if (!entry_ref)
567 : : {
568 : 8 : entry_ref = pgstat_fetch_pending_entry(kind, InvalidOid, rel_id);
569 [ + - ]: 8 : if (!entry_ref)
570 : 8 : return tablestatus;
571 : : }
572 : :
573 : 24 : tabentry = (PgStat_TableStatus *) entry_ref->pending;
574 : 24 : tablestatus = palloc_object(PgStat_TableStatus);
575 : 24 : *tablestatus = *tabentry;
576 : :
577 : : /*
578 : : * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
579 : : * point to a shared memory area. Its data is saved below, so removing it
580 : : * does not matter.
581 : : */
582 : 24 : tablestatus->trans = NULL;
583 : :
584 : : /*
585 : : * Live subtransaction counts are not included yet. This is not a hot
586 : : * code path so reconcile tuples_inserted, tuples_updated and
587 : : * tuples_deleted even if the caller may not be interested in this data.
588 : : */
589 [ + + ]: 56 : for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
590 : : {
591 : 32 : tablestatus->counts.tuples_inserted += trans->tuples_inserted;
592 : 32 : tablestatus->counts.tuples_updated += trans->tuples_updated;
593 : 32 : tablestatus->counts.tuples_deleted += trans->tuples_deleted;
594 : : }
595 : :
596 : 24 : return tablestatus;
597 : : }
598 : :
599 : : /*
600 : : * Perform relation stats specific end-of-transaction work. Helper for
601 : : * AtEOXact_PgStat.
602 : : *
603 : : * Transfer transactional insert/update counts into the base tabstat entries.
604 : : * We don't bother to free any of the transactional state, since it's all in
605 : : * TopTransactionContext and will go away anyway.
606 : : */
607 : : void
608 : 161851 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
609 : : {
610 : : PgStat_TableXactStatus *trans;
611 : :
612 [ + + ]: 627682 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
613 : : {
614 : : PgStat_TableStatus *tabstat;
615 : :
616 : : Assert(trans->nest_level == 1);
617 : : Assert(trans->upper == NULL);
618 : 465831 : tabstat = trans->parent;
619 : : Assert(tabstat->trans == trans);
620 : : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
621 [ + + ]: 465831 : if (!isCommit)
622 : 16841 : restore_truncdrop_counters(trans);
623 : : /* count attempted actions regardless of commit/abort */
624 : 465831 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
625 : 465831 : tabstat->counts.tuples_updated += trans->tuples_updated;
626 : 465831 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
627 [ + + ]: 465831 : if (isCommit)
628 : : {
629 : 448990 : tabstat->counts.truncdropped = trans->truncdropped;
630 [ + + ]: 448990 : if (trans->truncdropped)
631 : : {
632 : : /* forget live/dead stats seen by backend thus far */
633 : 2963 : tabstat->counts.delta_live_tuples = 0;
634 : 2963 : tabstat->counts.delta_dead_tuples = 0;
635 : : }
636 : : /* insert adds a live tuple, delete removes one */
637 : 448990 : tabstat->counts.delta_live_tuples +=
638 : 448990 : trans->tuples_inserted - trans->tuples_deleted;
639 : : /* update and delete each create a dead tuple */
640 : 448990 : tabstat->counts.delta_dead_tuples +=
641 : 448990 : trans->tuples_updated + trans->tuples_deleted;
642 : : /* insert, update, delete each count as one change event */
643 : 448990 : tabstat->counts.changed_tuples +=
644 : 448990 : trans->tuples_inserted + trans->tuples_updated +
645 : 448990 : trans->tuples_deleted;
646 : : }
647 : : else
648 : : {
649 : : /* inserted tuples are dead, deleted tuples are unaffected */
650 : 16841 : tabstat->counts.delta_dead_tuples +=
651 : 16841 : trans->tuples_inserted + trans->tuples_updated;
652 : : /* an aborted xact generates no changed_tuple events */
653 : : }
654 : 465831 : tabstat->trans = NULL;
655 : : }
656 : 161851 : }
657 : :
658 : : /*
659 : : * Perform relation stats specific end-of-sub-transaction work. Helper for
660 : : * AtEOSubXact_PgStat.
661 : : *
662 : : * Transfer transactional insert/update counts into the next higher
663 : : * subtransaction state.
664 : : */
665 : : void
666 : 6080 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
667 : : {
668 : : PgStat_TableXactStatus *trans;
669 : : PgStat_TableXactStatus *next_trans;
670 : :
671 [ + + ]: 12668 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
672 : : {
673 : : PgStat_TableStatus *tabstat;
674 : :
675 : 6588 : next_trans = trans->next;
676 : : Assert(trans->nest_level == nestDepth);
677 : 6588 : tabstat = trans->parent;
678 : : Assert(tabstat->trans == trans);
679 : :
680 [ + + ]: 6588 : if (isCommit)
681 : : {
682 [ + + + + ]: 5499 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
683 : : {
684 [ + + ]: 4294 : if (trans->truncdropped)
685 : : {
686 : : /* propagate the truncate/drop status one level up */
687 : 16 : save_truncdrop_counters(trans->upper, false);
688 : : /* replace upper xact stats with ours */
689 : 16 : trans->upper->tuples_inserted = trans->tuples_inserted;
690 : 16 : trans->upper->tuples_updated = trans->tuples_updated;
691 : 16 : trans->upper->tuples_deleted = trans->tuples_deleted;
692 : : }
693 : : else
694 : : {
695 : 4278 : trans->upper->tuples_inserted += trans->tuples_inserted;
696 : 4278 : trans->upper->tuples_updated += trans->tuples_updated;
697 : 4278 : trans->upper->tuples_deleted += trans->tuples_deleted;
698 : : }
699 : 4294 : tabstat->trans = trans->upper;
700 : 4294 : pfree(trans);
701 : : }
702 : : else
703 : : {
704 : : /*
705 : : * When there isn't an immediate parent state, we can just
706 : : * reuse the record instead of going through a palloc/pfree
707 : : * pushup (this works since it's all in TopTransactionContext
708 : : * anyway). We have to re-link it into the parent level,
709 : : * though, and that might mean pushing a new entry into the
710 : : * pgStatXactStack.
711 : : */
712 : : PgStat_SubXactStatus *upper_xact_state;
713 : :
714 : 1205 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
715 : 1205 : trans->next = upper_xact_state->first;
716 : 1205 : upper_xact_state->first = trans;
717 : 1205 : trans->nest_level = nestDepth - 1;
718 : : }
719 : : }
720 : : else
721 : : {
722 : : /*
723 : : * On abort, update top-level tabstat counts, then forget the
724 : : * subtransaction
725 : : */
726 : :
727 : : /* first restore values obliterated by truncate/drop */
728 : 1089 : restore_truncdrop_counters(trans);
729 : : /* count attempted actions regardless of commit/abort */
730 : 1089 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
731 : 1089 : tabstat->counts.tuples_updated += trans->tuples_updated;
732 : 1089 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
733 : : /* inserted tuples are dead, deleted tuples are unaffected */
734 : 1089 : tabstat->counts.delta_dead_tuples +=
735 : 1089 : trans->tuples_inserted + trans->tuples_updated;
736 : 1089 : tabstat->trans = trans->upper;
737 : 1089 : pfree(trans);
738 : : }
739 : : }
740 : 6080 : }
741 : :
742 : : /*
743 : : * Generate 2PC records for all the pending transaction-dependent relation
744 : : * stats.
745 : : */
746 : : void
747 : 317 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
748 : : {
749 : : PgStat_TableXactStatus *trans;
750 : :
751 [ + + ]: 792 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
752 : : {
753 : : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
754 : : TwoPhasePgStatRecord record;
755 : :
756 : : Assert(trans->nest_level == 1);
757 : : Assert(trans->upper == NULL);
758 : 475 : tabstat = trans->parent;
759 : : Assert(tabstat->trans == trans);
760 : :
761 : 475 : record.tuples_inserted = trans->tuples_inserted;
762 : 475 : record.tuples_updated = trans->tuples_updated;
763 : 475 : record.tuples_deleted = trans->tuples_deleted;
764 : 475 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
765 : 475 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
766 : 475 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
767 : 475 : record.id = tabstat->id;
768 : 475 : record.shared = tabstat->shared;
769 : 475 : record.truncdropped = trans->truncdropped;
770 : :
771 : 475 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
772 : : &record, sizeof(TwoPhasePgStatRecord));
773 : : }
774 : 317 : }
775 : :
776 : : /*
777 : : * All we need do here is unlink the transaction stats state from the
778 : : * nontransactional state. The nontransactional action counts will be
779 : : * reported to the stats system immediately, while the effects on live and
780 : : * dead tuple counts are preserved in the 2PC state file.
781 : : *
782 : : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
783 : : */
784 : : void
785 : 317 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
786 : : {
787 : : PgStat_TableXactStatus *trans;
788 : :
789 [ + + ]: 792 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
790 : : {
791 : : PgStat_TableStatus *tabstat;
792 : :
793 : 475 : tabstat = trans->parent;
794 : 475 : tabstat->trans = NULL;
795 : : }
796 : 317 : }
797 : :
798 : : /*
799 : : * 2PC processing routine for COMMIT PREPARED case.
800 : : *
801 : : * Load the saved counts into our local pgstats state.
802 : : */
803 : : void
804 : 388 : pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info,
805 : : void *recdata, uint32 len)
806 : : {
807 : 388 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
808 : : PgStat_TableStatus *pgstat_info;
809 : :
810 : : /* Find or create a tabstat entry for the rel */
811 : 388 : pgstat_info = pgstat_prep_relation_pending(PGSTAT_KIND_RELATION, rec->id, rec->shared);
812 : :
813 : : /* Same math as in AtEOXact_PgStat, commit case */
814 : 388 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
815 : 388 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
816 : 388 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
817 : 388 : pgstat_info->counts.truncdropped = rec->truncdropped;
818 [ + + ]: 388 : if (rec->truncdropped)
819 : : {
820 : : /* forget live/dead stats seen by backend thus far */
821 : 14 : pgstat_info->counts.delta_live_tuples = 0;
822 : 14 : pgstat_info->counts.delta_dead_tuples = 0;
823 : : }
824 : 388 : pgstat_info->counts.delta_live_tuples +=
825 : 388 : rec->tuples_inserted - rec->tuples_deleted;
826 : 388 : pgstat_info->counts.delta_dead_tuples +=
827 : 388 : rec->tuples_updated + rec->tuples_deleted;
828 : 388 : pgstat_info->counts.changed_tuples +=
829 : 388 : rec->tuples_inserted + rec->tuples_updated +
830 : 388 : rec->tuples_deleted;
831 : 388 : }
832 : :
833 : : /*
834 : : * 2PC processing routine for ROLLBACK PREPARED case.
835 : : *
836 : : * Load the saved counts into our local pgstats state, but treat them
837 : : * as aborted.
838 : : */
839 : : void
840 : 85 : pgstat_twophase_postabort(FullTransactionId fxid, uint16 info,
841 : : void *recdata, uint32 len)
842 : : {
843 : 85 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
844 : : PgStat_TableStatus *pgstat_info;
845 : :
846 : : /* Find or create a tabstat entry for the rel */
847 : 85 : pgstat_info = pgstat_prep_relation_pending(PGSTAT_KIND_RELATION, rec->id, rec->shared);
848 : :
849 : : /* Same math as in AtEOXact_PgStat, abort case */
850 [ + + ]: 85 : if (rec->truncdropped)
851 : : {
852 : 8 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
853 : 8 : rec->tuples_updated = rec->updated_pre_truncdrop;
854 : 8 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
855 : : }
856 : 85 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
857 : 85 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
858 : 85 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
859 : 85 : pgstat_info->counts.delta_dead_tuples +=
860 : 85 : rec->tuples_inserted + rec->tuples_updated;
861 : 85 : }
862 : :
863 : : /*
864 : : * Flush out pending stats for the entry
865 : : *
866 : : * If nowait is true and the lock could not be immediately acquired, returns
867 : : * false without flushing the entry. Otherwise returns true.
868 : : *
869 : : * Some of the stats are copied to the corresponding pending database stats
870 : : * entry when successfully flushing.
871 : : */
872 : : bool
873 : 449540 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
874 : : {
875 : : Oid dboid;
876 : : PgStat_TableStatus *lstats; /* pending stats entry */
877 : : PgStatShared_Relation *shtabstats;
878 : : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
879 : : PgStat_StatDBEntry *dbentry; /* pending database entry */
880 : :
881 : 449540 : dboid = entry_ref->shared_entry->key.dboid;
882 : 449540 : lstats = (PgStat_TableStatus *) entry_ref->pending;
883 : 449540 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
884 : :
885 : : /* ignore entries that didn't accumulate any actual counts */
886 [ + + ]: 449540 : if (pg_memory_is_all_zeros(&lstats->counts,
887 : : sizeof(struct PgStat_TableCounts)))
888 : 3616 : return true;
889 : :
890 [ + + ]: 445924 : if (!pgstat_lock_entry(entry_ref, nowait))
891 : 10 : return false;
892 : :
893 : : /* add the values to the shared entry. */
894 : 445914 : tabentry = &shtabstats->stats;
895 : :
896 : 445914 : tabentry->numscans += lstats->counts.numscans;
897 [ + + ]: 445914 : if (lstats->counts.numscans)
898 : : {
899 : 98874 : TimestampTz t = GetCurrentTransactionStopTimestamp();
900 : :
901 [ + + ]: 98874 : if (t > tabentry->lastscan)
902 : 98001 : tabentry->lastscan = t;
903 : : }
904 : 445914 : tabentry->tuples_returned += lstats->counts.tuples_returned;
905 : 445914 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
906 : 445914 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
907 : 445914 : tabentry->tuples_updated += lstats->counts.tuples_updated;
908 : 445914 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
909 : 445914 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
910 : 445914 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
911 : :
912 : : /*
913 : : * If table was truncated/dropped, first reset the live/dead counters.
914 : : */
915 [ + + ]: 445914 : if (lstats->counts.truncdropped)
916 : : {
917 : 477 : tabentry->live_tuples = 0;
918 : 477 : tabentry->dead_tuples = 0;
919 : 477 : tabentry->ins_since_vacuum = 0;
920 : : }
921 : :
922 : 445914 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
923 : 445914 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
924 : 445914 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
925 : :
926 : : /*
927 : : * Using tuples_inserted to update ins_since_vacuum does mean that we'll
928 : : * track aborted inserts too. This isn't ideal, but otherwise probably
929 : : * not worth adding an extra field for. It may just amount to autovacuums
930 : : * triggering for inserts more often than they maybe should, which is
931 : : * probably not going to be common enough to be too concerned about here.
932 : : */
933 : 445914 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
934 : :
935 : 445914 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
936 : 445914 : tabentry->blocks_hit += lstats->counts.blocks_hit;
937 : :
938 : : /* Clamp live_tuples in case of negative delta_live_tuples */
939 : 445914 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
940 : : /* Likewise for dead_tuples */
941 : 445914 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
942 : :
943 : 445914 : pgstat_unlock_entry(entry_ref);
944 : :
945 : : /* The entry was successfully flushed, add the same to database stats */
946 : 445914 : dbentry = pgstat_prep_database_pending(dboid);
947 : 445914 : dbentry->tuples_returned += lstats->counts.tuples_returned;
948 : 445914 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
949 : 445914 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
950 : 445914 : dbentry->tuples_updated += lstats->counts.tuples_updated;
951 : 445914 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
952 : 445914 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
953 : 445914 : dbentry->blocks_hit += lstats->counts.blocks_hit;
954 : :
955 : 445914 : return true;
956 : : }
957 : :
958 : : void
959 : 480596 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
960 : : {
961 : 480596 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
962 : :
963 [ + + ]: 480596 : if (pending->relation)
964 : 421574 : pgstat_unlink_relation(pending->relation);
965 : 480596 : }
966 : :
967 : : void
968 : 8528 : pgstat_relation_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
969 : : {
970 : 8528 : ((PgStatShared_Relation *) header)->stats.stat_reset_time = ts;
971 : 8528 : }
972 : :
973 : : /*
974 : : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
975 : : * initialized if not exists.
976 : : */
977 : : static PgStat_TableStatus *
978 : 1248933 : pgstat_prep_relation_pending(PgStat_Kind kind, Oid rel_id, bool isshared)
979 : : {
980 : : PgStat_EntryRef *entry_ref;
981 : : PgStat_TableStatus *pending;
982 : :
983 [ + + ]: 1248933 : entry_ref = pgstat_prep_pending_entry(kind,
984 : : isshared ? InvalidOid : MyDatabaseId,
985 : : rel_id, NULL);
986 : 1248933 : pending = entry_ref->pending;
987 : 1248933 : pending->id = rel_id;
988 : 1248933 : pending->shared = isshared;
989 : :
990 : 1248933 : return pending;
991 : : }
992 : :
993 : : /*
994 : : * add a new (sub)transaction state record
995 : : */
996 : : static void
997 : 471689 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
998 : : {
999 : : PgStat_SubXactStatus *xact_state;
1000 : : PgStat_TableXactStatus *trans;
1001 : :
1002 : : /*
1003 : : * If this is the first rel to be modified at the current nest level, we
1004 : : * first have to push a transaction stack entry.
1005 : : */
1006 : 471689 : xact_state = pgstat_get_xact_stack_level(nest_level);
1007 : :
1008 : : /* Now make a per-table stack entry */
1009 : : trans = (PgStat_TableXactStatus *)
1010 : 471689 : MemoryContextAllocZero(TopTransactionContext,
1011 : : sizeof(PgStat_TableXactStatus));
1012 : 471689 : trans->nest_level = nest_level;
1013 : 471689 : trans->upper = pgstat_info->trans;
1014 : 471689 : trans->parent = pgstat_info;
1015 : 471689 : trans->next = xact_state->first;
1016 : 471689 : xact_state->first = trans;
1017 : 471689 : pgstat_info->trans = trans;
1018 : 471689 : }
1019 : :
1020 : : /*
1021 : : * Add a new (sub)transaction record if needed.
1022 : : */
1023 : : static void
1024 : 16534199 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
1025 : : {
1026 : 16534199 : int nest_level = GetCurrentTransactionNestLevel();
1027 : :
1028 [ + + ]: 16534199 : if (pgstat_info->trans == NULL ||
1029 [ + + ]: 16067624 : pgstat_info->trans->nest_level != nest_level)
1030 : 471689 : add_tabstat_xact_level(pgstat_info, nest_level);
1031 : 16534199 : }
1032 : :
1033 : : /*
1034 : : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
1035 : : * they can be cleared, and if the (sub)xact that executed the truncate/drop
1036 : : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
1037 : : * values.
1038 : : *
1039 : : * Note that for truncate we do this on the first truncate in any particular
1040 : : * subxact level only.
1041 : : */
1042 : : static void
1043 : 3289 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
1044 : : {
1045 [ + + + + ]: 3289 : if (!trans->truncdropped || is_drop)
1046 : : {
1047 : 3237 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
1048 : 3237 : trans->updated_pre_truncdrop = trans->tuples_updated;
1049 : 3237 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
1050 : 3237 : trans->truncdropped = true;
1051 : : }
1052 : 3289 : }
1053 : :
1054 : : /*
1055 : : * restore counters when a truncate aborts
1056 : : */
1057 : : static void
1058 : 17930 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
1059 : : {
1060 [ + + ]: 17930 : if (trans->truncdropped)
1061 : : {
1062 : 224 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
1063 : 224 : trans->tuples_updated = trans->updated_pre_truncdrop;
1064 : 224 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
1065 : : }
1066 : 17930 : }
|