From a25da2c56135fb0846044dd3438a77630c1c58c9 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 16:20:54 -0500 Subject: [PATCH 1/2] Return default_version to stable now that 1.0.0 has shipped 1.0.0 is tagged and dist'd (RELEASE.md step 6, done outside this PR - make tag pushes to origin, which is a personal fork in this checkout, so the tag was created and pushed to upstream manually instead). This is step 7: flip default_version back to stable so ordinary source edits regenerate sql/count_nulls--stable.sql instead of silently corrupting the just-released sql/count_nulls--1.0.0.sql, add the update script from 1.0.0 to stable, and open a fresh stable section in HISTORY.md for the next cycle. Verified: make verify-results green, and the full update chain (CREATE EXTENSION VERSION '0.9.6' -> ALTER EXTENSION UPDATE -> stable) still works end to end. Also keeps sql/count_nulls--1.0.0.sql installable going forward (a manual DATA += in the Makefile) since it drops out of base.mk's install-script wildcard once it's no longer the "current version" file - the same gap already tracked as Postgres-Extensions/pgxntool#48. --- HISTORY.md | 5 ++ Makefile | 9 ++++ count_nulls.control | 2 +- sql/count_nulls--1.0.0--stable.sql | 1 + sql/count_nulls--stable.sql | 87 ++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 sql/count_nulls--1.0.0--stable.sql create mode 100644 sql/count_nulls--stable.sql diff --git a/HISTORY.md b/HISTORY.md index 985b923..f96bf99 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # count_nulls history +stable +------ + +No user-facing changes yet. + 1.0.0 ----- diff --git a/Makefile b/Makefile index e57011a..3f08c82 100644 --- a/Makefile +++ b/Makefile @@ -2,3 +2,12 @@ include pgxntool/base.mk # Temporary hack testdeps: $(wildcard test/*/*.sql) $(wildcard test/*.sql) # Be careful not to include directories in this + +# Install the outgoing "current" version's full install script now that +# default_version is 'stable' (see count_nulls.control and RELEASE.md), so +# CREATE EXTENSION count_nulls VERSION '1.0.0' keeps working. Not covered by +# base.mk's DATA wildcard, which only picks up upgrade scripts +# (sql/*--*--*.sql) and the CURRENT version file (now +# sql/count_nulls--stable.sql) - a pgxntool bug, filed as +# Postgres-Extensions/pgxntool#48. +DATA += sql/count_nulls--1.0.0.sql diff --git a/count_nulls.control b/count_nulls.control index c5c5c28..bf8f662 100644 --- a/count_nulls.control +++ b/count_nulls.control @@ -1,4 +1,4 @@ # count_nulls extension comment = 'Count the number of null arguments' -default_version = '1.0.0' +default_version = 'stable' relocatable = false diff --git a/sql/count_nulls--1.0.0--stable.sql b/sql/count_nulls--1.0.0--stable.sql new file mode 100644 index 0000000..dc73d7b --- /dev/null +++ b/sql/count_nulls--1.0.0--stable.sql @@ -0,0 +1 @@ +-- No actual SQL changes to this version bump diff --git a/sql/count_nulls--stable.sql b/sql/count_nulls--stable.sql new file mode 100644 index 0000000..ef7c3b5 --- /dev/null +++ b/sql/count_nulls--stable.sql @@ -0,0 +1,87 @@ +/* DO NOT EDIT - AUTO-GENERATED FILE */ +CREATE OR REPLACE FUNCTION null_count( + VARIADIC argument anyarray +) RETURNS int LANGUAGE sql IMMUTABLE AS $body$ + SELECT sum( CASE WHEN a IS NULL THEN 1 ELSE 0 END )::int + FROM unnest( $1 ) a +$body$; + +CREATE OR REPLACE FUNCTION null_count( + argument jsonb +) RETURNS int LANGUAGE sql IMMUTABLE AS $body$ +SELECT count(*)::int + FROM jsonb_each_text( $1 ) a + WHERE value IS NULL +$body$; +CREATE OR REPLACE FUNCTION null_count( + argument json +) RETURNS int LANGUAGE sql IMMUTABLE AS 'SELECT @extschema@.null_count($1::jsonb)'; + +CREATE OR REPLACE FUNCTION null_count_trigger( +) RETURNS trigger LANGUAGE plpgsql IMMUTABLE AS $body$ +DECLARE + c_msg CONSTANT text := coalesce( + nullif( TG_ARGV[1], 'null' ) + , format( '%s must contain %s NULL fields', TG_RELID::regclass, TG_ARGV[0] ) + ); +BEGIN + IF TG_NARGS NOT BETWEEN 1 AND 2 THEN + RAISE '% usage: number of NULL columns[, error message]', TG_NAME; + END IF; + -- Casing intentional for cut/paste/replace/ + IF nullif(TG_ARGV[0], 'null') IS null THEN + RAISE '%: first argument must not be null', TG_NAME; + END IF; + + IF @extschema@.null_count( row_to_json(NEW) ) <> TG_ARGV[0]::int THEN + RAISE '%', c_msg; + END IF; + + RETURN NEW; +END +$body$; + + +CREATE OR REPLACE FUNCTION not_null_count( + VARIADIC argument anyarray +) RETURNS int LANGUAGE sql IMMUTABLE AS $body$ + SELECT sum( CASE WHEN a IS NOT NULL THEN 1 ELSE 0 END )::int + FROM unnest( $1 ) a +$body$; + +CREATE OR REPLACE FUNCTION not_null_count( + argument jsonb +) RETURNS int LANGUAGE sql IMMUTABLE AS $body$ +SELECT count(*)::int + FROM jsonb_each_text( $1 ) a + WHERE value IS NOT NULL +$body$; +CREATE OR REPLACE FUNCTION not_null_count( + argument json +) RETURNS int LANGUAGE sql IMMUTABLE AS 'SELECT @extschema@.not_null_count($1::jsonb)'; + +CREATE OR REPLACE FUNCTION not_null_count_trigger( +) RETURNS trigger LANGUAGE plpgsql IMMUTABLE AS $body$ +DECLARE + c_msg CONSTANT text := coalesce( + nullif( TG_ARGV[1], 'null' ) + , format( '%s must contain %s NOT NULL fields', TG_RELID::regclass, TG_ARGV[0] ) + ); +BEGIN + IF TG_NARGS NOT BETWEEN 1 AND 2 THEN + RAISE '% usage: number of NOT NULL columns[, error message]', TG_NAME; + END IF; + -- Casing intentional for cut/paste/replace/ + IF nullif(TG_ARGV[0], 'null') IS null THEN + RAISE '%: first argument must not be null', TG_NAME; + END IF; + + IF @extschema@.not_null_count( row_to_json(NEW) ) <> TG_ARGV[0]::int THEN + RAISE '%', c_msg; + END IF; + + RETURN NEW; +END +$body$; + +-- vi: expandtab sw=2 ts=2 From 0e5817ea62a49983505e15b6bb1c86f2bfa068c5 Mon Sep 17 00:00:00 2001 From: jnasbyupgrade Date: Fri, 24 Jul 2026 18:01:19 -0500 Subject: [PATCH 2/2] RELEASE.md: note that step 7's PR description should stay small --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index 78cbac9..bc73c65 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -134,3 +134,6 @@ it's worth a direct look before relying on it. is always available) for the next cycle. Leaving master stamped at the real version means the next source edit regenerates and corrupts the released version's install file. +- [ ] Keep this PR's description small — something like "Reset version back to + `stable` after release." is enough. It's a mechanical, low-risk step; it + doesn't need the detailed rationale a real content change would.