@@ -261,6 +261,121 @@ LANGUAGE sql VOLATILE AS $$
261261 SELECT * FROM column_tetris .compute_layout (relation_name::regclass::oid );
262262$$;
263263
264+ -- ---------------------------------------------------------------------------
265+ -- padding_wasted(text) — Returns bytes of avoidable padding per row
266+ -- ---------------------------------------------------------------------------
267+
268+ CREATE FUNCTION column_tetris .padding_wasted(relation_name text , report_mode text DEFAULT ' row' )
269+ RETURNS bigint
270+ LANGUAGE plpgsql AS $$
271+ DECLARE
272+ v_rel_oid oid ;
273+ v_current_waste int := 0 ;
274+ v_optimal_waste int := 0 ;
275+ v_offset int ;
276+ v_align int ;
277+ v_padding int ;
278+ v_num_cols int ;
279+ v_any_nullable bool;
280+ v_row_count bigint ;
281+ col record;
282+ BEGIN
283+ IF report_mode NOT IN (' row' , ' total' ) THEN
284+ RAISE EXCEPTION ' invalid report_mode: %. Must be row or total' , report_mode;
285+ END IF;
286+
287+ v_rel_oid := relation_name::regclass::oid ;
288+
289+ SELECT count (* )::int , bool_or(NOT a .attnotnull )
290+ INTO v_num_cols, v_any_nullable
291+ FROM pg_attribute a
292+ WHERE a .attrelid = v_rel_oid
293+ AND a .attnum > 0
294+ AND NOT a .attisdropped ;
295+
296+ IF v_num_cols = 0 THEN
297+ RETURN 0 ;
298+ END IF;
299+
300+ -- Current layout waste
301+ v_offset := 23 ;
302+ IF v_any_nullable THEN
303+ v_offset := v_offset + ((v_num_cols + 7 ) / 8 );
304+ END IF;
305+ v_offset := ((v_offset + 7 ) / 8 ) * 8 ;
306+
307+ FOR col IN
308+ SELECT t .typalign , t .typlen
309+ FROM pg_attribute a
310+ JOIN pg_type t ON t .oid = a .atttypid
311+ WHERE a .attrelid = v_rel_oid
312+ AND a .attnum > 0
313+ AND NOT a .attisdropped
314+ ORDER BY a .attnum
315+ LOOP
316+ IF col .typlen = - 1 THEN
317+ v_align := 4 ;
318+ v_offset := ((v_offset + v_align - 1 ) / v_align) * v_align + 4 ;
319+ ELSE
320+ v_align := CASE col .typalign
321+ WHEN ' d' THEN 8 WHEN ' i' THEN 4
322+ WHEN ' s' THEN 2 WHEN ' c' THEN 1 ELSE 4
323+ END;
324+ v_padding := ((v_offset + v_align - 1 ) / v_align) * v_align - v_offset;
325+ v_current_waste := v_current_waste + v_padding;
326+ v_offset := ((v_offset + v_align - 1 ) / v_align) * v_align + col .typlen ;
327+ END IF;
328+ END LOOP;
329+
330+ -- Optimal layout waste
331+ v_offset := 23 ;
332+ IF v_any_nullable THEN
333+ v_offset := v_offset + ((v_num_cols + 7 ) / 8 );
334+ END IF;
335+ v_offset := ((v_offset + 7 ) / 8 ) * 8 ;
336+
337+ FOR col IN
338+ SELECT t .typalign , t .typlen
339+ FROM pg_attribute a
340+ JOIN pg_type t ON t .oid = a .atttypid
341+ WHERE a .attrelid = v_rel_oid
342+ AND a .attnum > 0
343+ AND NOT a .attisdropped
344+ ORDER BY
345+ CASE
346+ WHEN t .typlen = - 1 THEN 5
347+ WHEN t .typalign = ' d' THEN 1
348+ WHEN t .typalign = ' i' THEN 2
349+ WHEN t .typalign = ' s' THEN 3
350+ WHEN t .typalign = ' c' THEN 4
351+ ELSE 5
352+ END,
353+ CASE WHEN a .attnotnull THEN 0 ELSE 1 END,
354+ a .attnum
355+ LOOP
356+ IF col .typlen = - 1 THEN
357+ v_align := 4 ;
358+ v_offset := ((v_offset + v_align - 1 ) / v_align) * v_align + 4 ;
359+ ELSE
360+ v_align := CASE col .typalign
361+ WHEN ' d' THEN 8 WHEN ' i' THEN 4
362+ WHEN ' s' THEN 2 WHEN ' c' THEN 1 ELSE 4
363+ END;
364+ v_padding := ((v_offset + v_align - 1 ) / v_align) * v_align - v_offset;
365+ v_optimal_waste := v_optimal_waste + v_padding;
366+ v_offset := ((v_offset + v_align - 1 ) / v_align) * v_align + col .typlen ;
367+ END IF;
368+ END LOOP;
369+
370+ IF report_mode = ' total' THEN
371+ EXECUTE format(' SELECT count(*) FROM %s' , v_rel_oid::regclass) INTO v_row_count;
372+ RETURN (v_current_waste - v_optimal_waste)::bigint * v_row_count;
373+ END IF;
374+
375+ RETURN (v_current_waste - v_optimal_waste)::bigint ;
376+ END;
377+ $$;
378+
264379-- ---------------------------------------------------------------------------
265380-- validate(oid) — Raises exception if layout is suboptimal
266381-- ---------------------------------------------------------------------------
0 commit comments