-
Notifications
You must be signed in to change notification settings - Fork 2
feature/padding-waste #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -261,6 +261,121 @@ LANGUAGE sql VOLATILE AS $$ | |||||||||||||||||||||
| SELECT * FROM column_tetris.compute_layout(relation_name::regclass::oid); | ||||||||||||||||||||||
| $$; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| -- --------------------------------------------------------------------------- | ||||||||||||||||||||||
| -- padding_wasted(text) — Returns bytes of avoidable padding per row | ||||||||||||||||||||||
| -- --------------------------------------------------------------------------- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| CREATE FUNCTION column_tetris.padding_wasted(relation_name text, report_mode text DEFAULT 'row') | ||||||||||||||||||||||
| RETURNS bigint | ||||||||||||||||||||||
|
Comment on lines
+264
to
+269
|
||||||||||||||||||||||
| LANGUAGE plpgsql AS $$ | ||||||||||||||||||||||
| DECLARE | ||||||||||||||||||||||
| v_rel_oid oid; | ||||||||||||||||||||||
| v_current_waste int := 0; | ||||||||||||||||||||||
| v_optimal_waste int := 0; | ||||||||||||||||||||||
| v_offset int; | ||||||||||||||||||||||
| v_align int; | ||||||||||||||||||||||
| v_padding int; | ||||||||||||||||||||||
| v_num_cols int; | ||||||||||||||||||||||
| v_any_nullable bool; | ||||||||||||||||||||||
| v_row_count bigint; | ||||||||||||||||||||||
| col record; | ||||||||||||||||||||||
| BEGIN | ||||||||||||||||||||||
| IF report_mode NOT IN ('row', 'total') THEN | ||||||||||||||||||||||
| RAISE EXCEPTION 'invalid report_mode: %. Must be row or total', report_mode; | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| v_rel_oid := relation_name::regclass::oid; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| SELECT count(*)::int, bool_or(NOT a.attnotnull) | ||||||||||||||||||||||
| INTO v_num_cols, v_any_nullable | ||||||||||||||||||||||
| FROM pg_attribute a | ||||||||||||||||||||||
| WHERE a.attrelid = v_rel_oid | ||||||||||||||||||||||
| AND a.attnum > 0 | ||||||||||||||||||||||
| AND NOT a.attisdropped; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+268
to
+295
|
||||||||||||||||||||||
| IF v_num_cols = 0 THEN | ||||||||||||||||||||||
| RETURN 0; | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| -- Current layout waste | ||||||||||||||||||||||
| v_offset := 23; | ||||||||||||||||||||||
| IF v_any_nullable THEN | ||||||||||||||||||||||
| v_offset := v_offset + ((v_num_cols + 7) / 8); | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
| v_offset := ((v_offset + 7) / 8) * 8; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| FOR col IN | ||||||||||||||||||||||
| SELECT t.typalign, t.typlen | ||||||||||||||||||||||
| FROM pg_attribute a | ||||||||||||||||||||||
| JOIN pg_type t ON t.oid = a.atttypid | ||||||||||||||||||||||
| WHERE a.attrelid = v_rel_oid | ||||||||||||||||||||||
| AND a.attnum > 0 | ||||||||||||||||||||||
| AND NOT a.attisdropped | ||||||||||||||||||||||
| ORDER BY a.attnum | ||||||||||||||||||||||
| LOOP | ||||||||||||||||||||||
| IF col.typlen = -1 THEN | ||||||||||||||||||||||
| v_align := 4; | ||||||||||||||||||||||
| v_offset := ((v_offset + v_align - 1) / v_align) * v_align + 4; | ||||||||||||||||||||||
| ELSE | ||||||||||||||||||||||
| v_align := CASE col.typalign | ||||||||||||||||||||||
| WHEN 'd' THEN 8 WHEN 'i' THEN 4 | ||||||||||||||||||||||
| WHEN 's' THEN 2 WHEN 'c' THEN 1 ELSE 4 | ||||||||||||||||||||||
| END; | ||||||||||||||||||||||
| v_padding := ((v_offset + v_align - 1) / v_align) * v_align - v_offset; | ||||||||||||||||||||||
| v_current_waste := v_current_waste + v_padding; | ||||||||||||||||||||||
| v_offset := ((v_offset + v_align - 1) / v_align) * v_align + col.typlen; | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
| END LOOP; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| -- Optimal layout waste | ||||||||||||||||||||||
| v_offset := 23; | ||||||||||||||||||||||
| IF v_any_nullable THEN | ||||||||||||||||||||||
| v_offset := v_offset + ((v_num_cols + 7) / 8); | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
| v_offset := ((v_offset + 7) / 8) * 8; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| FOR col IN | ||||||||||||||||||||||
| SELECT t.typalign, t.typlen | ||||||||||||||||||||||
| FROM pg_attribute a | ||||||||||||||||||||||
| JOIN pg_type t ON t.oid = a.atttypid | ||||||||||||||||||||||
| WHERE a.attrelid = v_rel_oid | ||||||||||||||||||||||
| AND a.attnum > 0 | ||||||||||||||||||||||
| AND NOT a.attisdropped | ||||||||||||||||||||||
| ORDER BY | ||||||||||||||||||||||
| CASE | ||||||||||||||||||||||
| WHEN t.typlen = -1 THEN 5 | ||||||||||||||||||||||
| WHEN t.typalign = 'd' THEN 1 | ||||||||||||||||||||||
| WHEN t.typalign = 'i' THEN 2 | ||||||||||||||||||||||
| WHEN t.typalign = 's' THEN 3 | ||||||||||||||||||||||
| WHEN t.typalign = 'c' THEN 4 | ||||||||||||||||||||||
| ELSE 5 | ||||||||||||||||||||||
| END, | ||||||||||||||||||||||
| CASE WHEN a.attnotnull THEN 0 ELSE 1 END, | ||||||||||||||||||||||
| a.attnum | ||||||||||||||||||||||
| LOOP | ||||||||||||||||||||||
| IF col.typlen = -1 THEN | ||||||||||||||||||||||
| v_align := 4; | ||||||||||||||||||||||
| v_offset := ((v_offset + v_align - 1) / v_align) * v_align + 4; | ||||||||||||||||||||||
| ELSE | ||||||||||||||||||||||
| v_align := CASE col.typalign | ||||||||||||||||||||||
| WHEN 'd' THEN 8 WHEN 'i' THEN 4 | ||||||||||||||||||||||
| WHEN 's' THEN 2 WHEN 'c' THEN 1 ELSE 4 | ||||||||||||||||||||||
| END; | ||||||||||||||||||||||
| v_padding := ((v_offset + v_align - 1) / v_align) * v_align - v_offset; | ||||||||||||||||||||||
| v_optimal_waste := v_optimal_waste + v_padding; | ||||||||||||||||||||||
| v_offset := ((v_offset + v_align - 1) / v_align) * v_align + col.typlen; | ||||||||||||||||||||||
| END IF; | ||||||||||||||||||||||
| END LOOP; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| IF report_mode = 'total' THEN | ||||||||||||||||||||||
|
||||||||||||||||||||||
| IF report_mode = 'total' THEN | |
| IF report_mode = 'estimated' THEN | |
| SELECT GREATEST(COALESCE(c.reltuples, 0), 0)::bigint | |
| INTO v_row_count | |
| FROM pg_class c | |
| WHERE c.oid = v_rel_oid; | |
| RETURN (v_current_waste - v_optimal_waste)::bigint * COALESCE(v_row_count, 0); | |
| ELSIF report_mode = 'total' THEN | |
| -- Exact row count; may be expensive on large tables. |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
padding_wasted can return a negative number when v_current_waste <= v_optimal_waste (the same situation validate() treats as non-suboptimal). Since this function is documented as returning wasted/avoidable bytes, consider clamping the result to 0 when current is not worse than optimal (and do the same for total mode).
| RETURN (v_current_waste - v_optimal_waste)::bigint * v_row_count; | |
| END IF; | |
| RETURN (v_current_waste - v_optimal_waste)::bigint; | |
| RETURN GREATEST((v_current_waste - v_optimal_waste)::bigint, 0::bigint) * v_row_count; | |
| END IF; | |
| RETURN GREATEST((v_current_waste - v_optimal_waste)::bigint, 0::bigint); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| -- Test: padding_wasted() function | ||
| -- Expected: correct per-row and total waste calculations | ||
|
|
||
| -- Setup: need warn mode so test tables can be created with suboptimal order | ||
| SELECT column_tetris.set_mode('warn'); | ||
|
|
||
| -- Test 1: Suboptimal table should have padding waste > 0 | ||
| CREATE TABLE test_pw_suboptimal ( | ||
| flag boolean, | ||
| big_id bigint | ||
| ); | ||
|
|
||
| DO $$ | ||
| DECLARE | ||
| v_waste int; | ||
| BEGIN | ||
| SELECT column_tetris.padding_wasted('test_pw_suboptimal') INTO v_waste; | ||
| IF v_waste <= 0 THEN | ||
| RAISE EXCEPTION 'TEST FAILED: padding_wasted should be > 0 for suboptimal table, got %', v_waste; | ||
| END IF; | ||
| RAISE NOTICE 'TEST PASSED: suboptimal table has % bytes waste per row', v_waste; | ||
| END; | ||
| $$; | ||
|
|
||
| -- Test 2: Optimal table should have 0 waste | ||
| CREATE TABLE test_pw_optimal ( | ||
| big_id bigint, | ||
| count integer, | ||
| flag boolean | ||
| ); | ||
|
|
||
| DO $$ | ||
| DECLARE | ||
| v_waste int; | ||
| BEGIN | ||
| SELECT column_tetris.padding_wasted('test_pw_optimal') INTO v_waste; | ||
| IF v_waste <> 0 THEN | ||
|
Comment on lines
+14
to
+37
|
||
| RAISE EXCEPTION 'TEST FAILED: padding_wasted should be 0 for optimal table, got %', v_waste; | ||
| END IF; | ||
| RAISE NOTICE 'TEST PASSED: optimal table has 0 waste'; | ||
| END; | ||
| $$; | ||
|
|
||
| -- Test 3: Non-optimal order but zero actual waste (int4, int4, float8, text) | ||
| CREATE TABLE test_pw_zero_waste ( | ||
| a integer, | ||
| b integer, | ||
| c float8, | ||
| d text | ||
| ); | ||
|
|
||
| DO $$ | ||
| DECLARE | ||
| v_waste int; | ||
| BEGIN | ||
| SELECT column_tetris.padding_wasted('test_pw_zero_waste') INTO v_waste; | ||
| IF v_waste <> 0 THEN | ||
| RAISE EXCEPTION 'TEST FAILED: int4+int4+float8+text should have 0 waste, got %', v_waste; | ||
|
Comment on lines
+53
to
+58
|
||
| END IF; | ||
| RAISE NOTICE 'TEST PASSED: zero waste despite non-optimal order'; | ||
| END; | ||
| $$; | ||
|
|
||
| -- Test 4: Total mode = per_row * row_count | ||
| INSERT INTO test_pw_suboptimal (flag, big_id) | ||
| SELECT true, generate_series(1, 100); | ||
|
|
||
| DO $$ | ||
| DECLARE | ||
| v_per_row bigint; | ||
| v_total bigint; | ||
| BEGIN | ||
| SELECT column_tetris.padding_wasted('test_pw_suboptimal') INTO v_per_row; | ||
| SELECT column_tetris.padding_wasted('test_pw_suboptimal', 'total') INTO v_total; | ||
| IF v_total <> v_per_row * 100 THEN | ||
| RAISE EXCEPTION 'TEST FAILED: total should be % * 100 = %, got %', v_per_row, v_per_row * 100, v_total; | ||
| END IF; | ||
| RAISE NOTICE 'TEST PASSED: total mode = % (% per row * 100 rows)', v_total, v_per_row; | ||
| END; | ||
| $$; | ||
|
|
||
| -- Test 5: Invalid mode raises exception | ||
| DO $$ | ||
| DECLARE | ||
| v_caught bool := false; | ||
| BEGIN | ||
| BEGIN | ||
| PERFORM column_tetris.padding_wasted('test_pw_suboptimal', 'bad_mode'); | ||
| EXCEPTION WHEN raise_exception THEN | ||
| v_caught := true; | ||
| IF SQLERRM NOT LIKE '%invalid report_mode%' THEN | ||
| RAISE EXCEPTION 'TEST FAILED: unexpected error message: %', SQLERRM; | ||
| END IF; | ||
| RAISE NOTICE 'TEST PASSED: invalid mode rejected'; | ||
| END; | ||
| IF NOT v_caught THEN | ||
| RAISE EXCEPTION 'TEST FAILED: invalid mode should raise exception'; | ||
| END IF; | ||
| END; | ||
| $$; | ||
|
|
||
| -- Cleanup | ||
| DROP TABLE IF EXISTS test_pw_suboptimal; | ||
| DROP TABLE IF EXISTS test_pw_optimal; | ||
| DROP TABLE IF EXISTS test_pw_zero_waste; | ||
| DO $$ BEGIN RAISE NOTICE 'All 06_padding_wasted tests passed'; END; $$; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README describes
padding_wasted()as "bytes a table wastes per row" and shows an example return value, but the implementation is based on the extension’s fixed-width alignment heuristic (current vs suggested order). Consider clarifying that it reports avoidable fixed-width alignment padding (not total row overhead), and note that'total'will compute row count and may be slow on large tables.