From 6990b47bc23f5cef27ee093f36c108fc469639bd Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Thu, 8 May 2025 21:31:28 +0500 Subject: [PATCH 1/6] Prevent object access hook from accesing not installed namespace (#2161) Currently we cannot install Age to shared_preload_libraries if pg_cron is installed. To prevent following error we must bail out early. postgres=# set backtrace_functions to 'get_namespace_oid'; SET postgres=# create extension pg_cron ; 2025-04-15 16:59:49.867 +05 [30402] ERROR: schema "ag_catalog" does not exist 2025-04-15 16:59:49.867 +05 [30402] BACKTRACE: 2 postgres 0x0000000102401ab0 get_namespace_oid + 204 3 age.so 0x0000000103285cd0 ag_catalog_namespace_id + 28 4 age.so 0x00000001032846fc ag_relation_id + 32 5 age.so 0x00000001032efe9c search_label_relation_cache_miss + 84 6 age.so 0x00000001032efe30 search_label_relation_cache + 100 7 age.so 0x00000001032842f4 object_access + 384 8 postgres 0x000000010240a7a0 RunObjectDropHook + 136 9 postgres 0x00000001023ee85c deleteOneObject + 108 10 postgres 0x00000001023eb860 deleteObjectsInList + 476 11 postgres 0x00000001023eba14 performMultipleDeletions + 316 12 postgres 0x0000000102560244 ATPostAlterTypeCleanup + 2144 13 postgres 0x0000000102559fb4 ATRewriteCatalogs + 516 14 postgres 0x00000001025543a8 ATController + 284 15 postgres 0x00000001025541bc AlterTable + 96 16 postgres 0x00000001028b8240 ProcessUtilitySlow + 1812 17 postgres 0x00000001028b600c standard_ProcessUtility + 3684 18 age.so 0x00000001032844f8 ag_ProcessUtility_hook + 200 19 postgres 0x00000001028b516c ProcessUtility + 392 20 postgres 0x000000010250e5b4 execute_sql_string + 812 21 postgres 0x000000010250d438 execute_extension_script + 2264 22 postgres 0x000000010250b330 ApplyExtensionUpdates + 1320 23 postgres 0x0000000102507954 CreateExtensionInternal + 1896 24 postgres 0x0000000102506ea4 CreateExtension + 1152 25 postgres 0x00000001028b8ed4 ProcessUtilitySlow + 5032 26 postgres 0x00000001028b600c standard_ProcessUtility + 3684 27 age.so 0x00000001032844f8 ag_ProcessUtility_hook + 200 28 postgres 0x00000001028b516c ProcessUtility + 392 29 postgres 0x00000001028b4768 PortalRunUtility + 232 30 postgres 0x00000001028b3660 PortalRunMulti + 756 31 postgres 0x00000001028b2abc PortalRun + 1008 32 postgres 0x00000001028ad870 exec_simple_query + 1436 33 postgres 0x00000001028ac990 PostgresMain + 2472 34 postgres 0x00000001027a49ac report_fork_failure_to_client + 0 35 postgres 0x00000001027a3e54 BackendStartup + 520 36 postgres 0x00000001027a29f0 ServerLoop + 812 37 postgres 0x000000010279fe0c PostmasterMain + 6484 38 postgres 0x000000010266acd0 startup_hacks + 0 39 dyld 0x000000018a3ab154 start + 2476 --- src/backend/catalog/ag_catalog.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/backend/catalog/ag_catalog.c b/src/backend/catalog/ag_catalog.c index 65533380d..440ad13f8 100644 --- a/src/backend/catalog/ag_catalog.c +++ b/src/backend/catalog/ag_catalog.c @@ -20,6 +20,7 @@ #include "postgres.h" #include "catalog/dependency.h" +#include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_class_d.h" #include "catalog/pg_namespace_d.h" @@ -166,6 +167,13 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, if (access != OAT_DROP) return; + /* + * Age might be installed into shared_preload_libraries before extension is + * created. In this case we must bail out from this hook. + */ + if (!OidIsValid(get_namespace_oid("ag_catalog", true))) + return; + drop_arg = arg; /* From 34bd3aa032f87e3825c040bc11860fbadac4d374 Mon Sep 17 00:00:00 2001 From: Moontasir Mahmood <53787290+Munmud@users.noreply.github.com> Date: Sun, 18 May 2025 15:56:39 +0600 Subject: [PATCH 2/6] Fix CSV import for edge with one property (#2175) - start_index marks where property fields begin. For edges, it's 4. If start_index >= total_fields, create empty properties; otherwise, parse the properties. --- src/backend/utils/load/age_load.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 3ac8eaba1..199e56139 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -177,7 +177,7 @@ agtype* create_agtype_from_list_i(char **header, char **fields, agtype_in_state result; size_t i; - if (start_index + 1 == fields_len) + if (start_index >= fields_len) { return create_empty_agtype(); } From 2b2eb71f87999573c64fffd25dac0788d13fc0eb Mon Sep 17 00:00:00 2001 From: Muhammad Taha Naveed Date: Wed, 13 Aug 2025 19:51:08 +0500 Subject: [PATCH 3/6] Add missing dependency in jdbc driver (#2206) - This was also failing CI. The issue is described here https://github.com/gradle/gradle/issues/33950 --- drivers/jdbc/lib/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/jdbc/lib/build.gradle.kts b/drivers/jdbc/lib/build.gradle.kts index 2ba529ec1..0b63bc5a6 100644 --- a/drivers/jdbc/lib/build.gradle.kts +++ b/drivers/jdbc/lib/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") testImplementation("org.testcontainers:testcontainers:1.18.0") testImplementation("org.postgresql:postgresql:42.6.0") From 48d4777bc1c3e83adbbac11e9e913fb363e050ef Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Wed, 13 Aug 2025 08:13:29 -0700 Subject: [PATCH 4/6] Fix issue 2201: unexpected empty string behavior (#2203) This PR fixes the issue of some string functions returning NULL instead of the empty string. The string functions affected and corrected are - reverse, toupper, tolower, rtrim, ltrim, trim, right, left, substring, and replace. Added additional regression tests. Corrected previous tests. modified: regress/expected/expr.out modified: regress/sql/expr.sql modified: src/backend/utils/adt/agtype.c --- regress/expected/expr.out | 245 ++++++++++++++++++++++++++++----- regress/sql/expr.sql | 80 +++++++++-- src/backend/utils/adt/agtype.c | 42 ------ 3 files changed, 281 insertions(+), 86 deletions(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index b419e089a..11183e409 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -3856,6 +3856,41 @@ SELECT * FROM age_reverse('gnirts a si siht'::cstring); "this is a string" (1 row) +-- should return empty string +SELECT * FROM age_reverse(''); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM age_reverse(''::text); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM age_reverse(''::cstring); + age_reverse +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN reverse('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN reverse("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN reverse(null) @@ -4033,6 +4068,75 @@ SELECT * FROM age_tolower('CSTRING'::cstring); "cstring" (1 row) +-- should return empty string +SELECT * FROM age_toupper(''); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM age_toupper(''::text); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM age_toupper(''::cstring); + age_toupper +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toupper('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN toupper("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM age_tolower(''); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM age_tolower(''::text); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM age_tolower(''::cstring); + age_tolower +------------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN tolower('') +$$) AS (result agtype); + result +-------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN tolower("") +$$) AS (result agtype); + result +-------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN toUpper(null) @@ -4140,6 +4244,73 @@ SELECT * FROM age_trim(' string '); "string" (1 row) +-- should return empty string +SELECT * FROM cypher('expr', $$ + RETURN lTrim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN rTrim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN trim('') +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN lTrim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN rTrim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM cypher('expr', $$ + RETURN trim("") +$$) AS (results agtype); + results +--------- + "" +(1 row) + +SELECT * FROM age_ltrim(''); + age_ltrim +----------- + "" +(1 row) + +SELECT * FROM age_rtrim(''); + age_rtrim +----------- + "" +(1 row) + +SELECT * FROM age_trim(''); + age_trim +---------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN lTrim(null) @@ -4251,15 +4422,16 @@ $$) AS (results agtype); "123" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN left("123456789", 0) $$) AS (results agtype); results --------- - + "" (1 row) +-- should return null SELECT * FROM cypher('expr', $$ RETURN left(null, 1) $$) AS (results agtype); @@ -4330,15 +4502,16 @@ $$) AS (results agtype); "789" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN right("123456789", 0) $$) AS (results agtype); results --------- - + "" (1 row) +-- should return null SELECT * FROM cypher('expr', $$ RETURN right(null, 1) $$) AS (results agtype); @@ -4437,6 +4610,13 @@ SELECT * FROM age_substring('0123456789', 1); "123456789" (1 row) +-- should return empty string +SELECT * FROM age_substring('0123456789', 0, 0); + age_substring +--------------- + "" +(1 row) + -- should return null SELECT * FROM cypher('expr', $$ RETURN substring(null, null, null) @@ -4676,33 +4856,52 @@ $$) AS (results agtype); "ababab" (1 row) --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ - RETURN replace(null, null, null) + RETURN replace("", "", "") $$) AS (results agtype); results --------- - + "" (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", null, null) + RETURN replace("Hello", "Hello", "") $$) AS (results agtype); results --------- - + "" (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "", null) + RETURN replace("", "Hello", "Mellow") $$) AS (results agtype); results --------- - + "" +(1 row) + +SELECT * FROM age_replace('', '', ''); + age_replace +------------- + "" (1 row) +SELECT * FROM age_replace('Hello', 'Hello', ''); + age_replace +------------- + "" +(1 row) + +SELECT * FROM age_replace('', 'Hello', 'Mellow'); + age_replace +------------- + "" +(1 row) + +-- should return null SELECT * FROM cypher('expr', $$ - RETURN replace("", "", "") + RETURN replace(null, null, null) $$) AS (results agtype); results --------- @@ -4710,7 +4909,7 @@ $$) AS (results agtype); (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "Hello", "") + RETURN replace("Hello", null, null) $$) AS (results agtype); results --------- @@ -4718,7 +4917,7 @@ $$) AS (results agtype); (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("", "Hello", "Mellow") + RETURN replace("Hello", "", null) $$) AS (results agtype); results --------- @@ -4743,24 +4942,6 @@ SELECT * FROM age_replace('Hello', '', null); (1 row) -SELECT * FROM age_replace('', '', ''); - age_replace -------------- - -(1 row) - -SELECT * FROM age_replace('Hello', 'Hello', ''); - age_replace -------------- - -(1 row) - -SELECT * FROM age_replace('', 'Hello', 'Mellow'); - age_replace -------------- - -(1 row) - -- should fail SELECT * FROM cypher('expr', $$ RETURN replace() diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 0cf48caf3..79c6022a9 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1642,6 +1642,16 @@ $$) AS (results agtype); SELECT * FROM age_reverse('gnirts a si siht'); SELECT * FROM age_reverse('gnirts a si siht'::text); SELECT * FROM age_reverse('gnirts a si siht'::cstring); +-- should return empty string +SELECT * FROM age_reverse(''); +SELECT * FROM age_reverse(''::text); +SELECT * FROM age_reverse(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN reverse('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN reverse("") +$$) AS (result agtype); -- should return null SELECT * FROM cypher('expr', $$ RETURN reverse(null) @@ -1715,6 +1725,25 @@ SELECT * FROM age_toupper('text'::text); SELECT * FROM age_toupper('cstring'::cstring); SELECT * FROM age_tolower('TEXT'::text); SELECT * FROM age_tolower('CSTRING'::cstring); +-- should return empty string +SELECT * FROM age_toupper(''); +SELECT * FROM age_toupper(''::text); +SELECT * FROM age_toupper(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN toupper('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN toupper("") +$$) AS (result agtype); +SELECT * FROM age_tolower(''); +SELECT * FROM age_tolower(''::text); +SELECT * FROM age_tolower(''::cstring); +SELECT * FROM cypher('expr', $$ + RETURN tolower('') +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN tolower("") +$$) AS (result agtype); -- should return null SELECT * FROM cypher('expr', $$ RETURN toUpper(null) @@ -1756,6 +1785,28 @@ $$) AS (results agtype); SELECT * FROM age_ltrim(' string '); SELECT * FROM age_rtrim(' string '); SELECT * FROM age_trim(' string '); +-- should return empty string +SELECT * FROM cypher('expr', $$ + RETURN lTrim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN rTrim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN trim('') +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN lTrim("") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN rTrim("") +$$) AS (results agtype); +SELECT * FROM cypher('expr', $$ + RETURN trim("") +$$) AS (results agtype); +SELECT * FROM age_ltrim(''); +SELECT * FROM age_rtrim(''); +SELECT * FROM age_trim(''); -- should return null SELECT * FROM cypher('expr', $$ RETURN lTrim(null) @@ -1802,10 +1853,11 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN left("123456789", 3) $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN left("123456789", 0) $$) AS (results agtype); +-- should return null SELECT * FROM cypher('expr', $$ RETURN left(null, 1) $$) AS (results agtype); @@ -1834,10 +1886,11 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN right("123456789", 3) $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ RETURN right("123456789", 0) $$) AS (results agtype); +-- should return null SELECT * FROM cypher('expr', $$ RETURN right(null, 1) $$) AS (results agtype); @@ -1874,6 +1927,8 @@ SELECT * FROM cypher('expr', $$ $$) AS (results agtype); SELECT * FROM age_substring('0123456789', 3, 2); SELECT * FROM age_substring('0123456789', 1); +-- should return empty string +SELECT * FROM age_substring('0123456789', 0, 0); -- should return null SELECT * FROM cypher('expr', $$ RETURN substring(null, null, null) @@ -1975,31 +2030,32 @@ $$) AS (results agtype); SELECT * FROM cypher('expr', $$ RETURN replace("ababab", "ab", "ab") $$) AS (results agtype); --- should return null +-- should return empty string SELECT * FROM cypher('expr', $$ - RETURN replace(null, null, null) + RETURN replace("", "", "") $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", null, null) + RETURN replace("Hello", "Hello", "") $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "", null) + RETURN replace("", "Hello", "Mellow") $$) AS (results agtype); +SELECT * FROM age_replace('', '', ''); +SELECT * FROM age_replace('Hello', 'Hello', ''); +SELECT * FROM age_replace('', 'Hello', 'Mellow'); +-- should return null SELECT * FROM cypher('expr', $$ - RETURN replace("", "", "") + RETURN replace(null, null, null) $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("Hello", "Hello", "") + RETURN replace("Hello", null, null) $$) AS (results agtype); SELECT * FROM cypher('expr', $$ - RETURN replace("", "Hello", "Mellow") + RETURN replace("Hello", "", null) $$) AS (results agtype); SELECT * FROM age_replace(null, null, null); SELECT * FROM age_replace('Hello', null, null); SELECT * FROM age_replace('Hello', '', null); -SELECT * FROM age_replace('', '', ''); -SELECT * FROM age_replace('Hello', 'Hello', ''); -SELECT * FROM age_replace('', 'Hello', 'Mellow'); -- should fail SELECT * FROM cypher('expr', $$ RETURN replace() diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index caace491a..9e61deffb 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -7629,12 +7629,6 @@ Datum age_reverse(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - { - PG_RETURN_NULL(); - } - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -7714,10 +7708,6 @@ Datum age_toupper(PG_FUNCTION_ARGS) agtv_value->type))); } - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* allocate the new string */ result = palloc0(string_len); @@ -7804,10 +7794,6 @@ Datum age_tolower(PG_FUNCTION_ARGS) agtv_value->type))); } - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* allocate the new string */ result = palloc0(string_len); @@ -7902,10 +7888,6 @@ Datum age_rtrim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -7993,10 +7975,6 @@ Datum age_ltrim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8084,10 +8062,6 @@ Datum age_trim(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8225,10 +8199,6 @@ Datum age_right(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8366,10 +8336,6 @@ Datum age_left(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8535,10 +8501,6 @@ Datum age_substring(PG_FUNCTION_ARGS) string = text_to_cstring(text_string); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; @@ -8793,10 +8755,6 @@ Datum age_replace(PG_FUNCTION_ARGS) string = text_to_cstring(text_result); string_len = strlen(string); - /* if we have an empty string, return null */ - if (string_len == 0) - PG_RETURN_NULL(); - /* build the result */ agtv_result.type = AGTV_STRING; agtv_result.val.string.val = string; From 74057bd29810e3324e5c29ab3ada135a8ee32d7b Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Mon, 18 Aug 2025 07:50:17 -0700 Subject: [PATCH 5/6] fix issue 2205: left doesn't catch overflow (#2207) Fixed issue 2205 where large int values aren't detected. The following functions were fixed - left, right, and substring modified: regress/expected/expr.out modified: regress/sql/expr.sql modified: src/backend/utils/adt/agtype.c Added regression tests. --- regress/expected/expr.out | 48 +++++++++++ regress/sql/expr.sql | 36 +++++++++ src/backend/utils/adt/agtype.c | 143 ++++++++++++++++++++++++++++++--- 3 files changed, 216 insertions(+), 11 deletions(-) diff --git a/regress/expected/expr.out b/regress/expected/expr.out index 11183e409..66eec8e20 100644 --- a/regress/expected/expr.out +++ b/regress/expected/expr.out @@ -4485,6 +4485,18 @@ ERROR: function age_left() does not exist LINE 1: SELECT * FROM age_left(); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', -2147483648) +$$) AS (result agtype); +ERROR: left() negative values are not supported for length +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', -2147483649) +$$) AS (result agtype); +ERROR: left() length value is out of INT range +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', 2147483649) +$$) AS (result agtype); +ERROR: left() length value is out of INT range --right() SELECT * FROM cypher('expr', $$ RETURN right("123456789", 1) @@ -4565,6 +4577,18 @@ ERROR: function age_right() does not exist LINE 1: SELECT * FROM age_right(); ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', -2147483648) +$$) AS (result agtype); +ERROR: right() negative values are not supported for length +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', -2147483649) +$$) AS (result agtype); +ERROR: right() length value is out of INT range +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', 2147483649) +$$) AS (result agtype); +ERROR: right() length value is out of INT range -- substring() SELECT * FROM cypher('expr', $$ RETURN substring("0123456789", 0, 1) @@ -4660,6 +4684,30 @@ SELECT * FROM age_substring(null, 1); (1 row) +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', -2147483648, 0) +$$) AS (result agtype); +ERROR: substring() negative values are not supported for offset or length +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', -2147483649, 0) +$$) AS (result agtype); +ERROR: substring() parameter value is out of INT range +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 2147483649, 0) +$$) AS (result agtype); +ERROR: substring() parameter value is out of INT range +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, -2147483648) +$$) AS (result agtype); +ERROR: substring() negative values are not supported for offset or length +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, -2147483649) +$$) AS (result agtype); +ERROR: substring() parameter value is out of INT range +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, 2147483649) +$$) AS (result agtype); +ERROR: substring() parameter value is out of INT range -- should fail SELECT * FROM cypher('expr', $$ RETURN substring("123456789", null) diff --git a/regress/sql/expr.sql b/regress/sql/expr.sql index 79c6022a9..4ca7d187e 100644 --- a/regress/sql/expr.sql +++ b/regress/sql/expr.sql @@ -1879,6 +1879,15 @@ $$) AS (results agtype); SELECT * FROM age_left('123456789', null); SELECT * FROM age_left('123456789', -1); SELECT * FROM age_left(); +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', -2147483648) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', -2147483649) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN left('abcdef', 2147483649) +$$) AS (result agtype); --right() SELECT * FROM cypher('expr', $$ RETURN right("123456789", 1) @@ -1912,6 +1921,15 @@ $$) AS (results agtype); SELECT * FROM age_right('123456789', null); SELECT * FROM age_right('123456789', -1); SELECT * FROM age_right(); +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', -2147483648) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', -2147483649) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN right('abcdef', 2147483649) +$$) AS (result agtype); -- substring() SELECT * FROM cypher('expr', $$ RETURN substring("0123456789", 0, 1) @@ -1942,6 +1960,24 @@ $$) AS (results agtype); SELECT * FROM age_substring(null, null, null); SELECT * FROM age_substring(null, null); SELECT * FROM age_substring(null, 1); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', -2147483648, 0) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', -2147483649, 0) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 2147483649, 0) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, -2147483648) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, -2147483649) +$$) AS (result agtype); +SELECT * FROM cypher('expr', $$ + RETURN substring('abcdef', 0, 2147483649) +$$) AS (result agtype); -- should fail SELECT * FROM cypher('expr', $$ RETURN substring("123456789", null) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 9e61deffb..82e128f64 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -8082,7 +8082,7 @@ Datum age_right(PG_FUNCTION_ARGS) agtype_value agtv_result; text *text_string = NULL; char *string = NULL; - int string_len; + int64 string_len; Oid type; /* extract argument values */ @@ -8090,18 +8090,21 @@ Datum age_right(PG_FUNCTION_ARGS) /* check number of args */ if (nargs != 2) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() invalid number of arguments"))); - + } /* check for a null string */ if (nargs < 0 || nulls[0]) + { PG_RETURN_NULL(); - + } /* check for a null length */ if (nulls[1]) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() length parameter cannot be null"))); - + } /* right() supports text, cstring, or the agtype string input */ arg = args[0]; type = types[0]; @@ -8109,13 +8112,19 @@ Datum age_right(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == CSTRINGOID) + { text_string = cstring_to_text(DatumGetCString(arg)); + } else if (type == TEXTOID) + { text_string = DatumGetTextPP(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() unsupported argument type %d", type))); + } } else { @@ -8126,21 +8135,29 @@ Datum age_right(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() only supports scalar arguments"))); + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* check for agtype null */ if (agtv_value->type == AGTV_NULL) + { PG_RETURN_NULL(); + } if (agtv_value->type == AGTV_STRING) + { text_string = cstring_to_text_with_len(agtv_value->val.string.val, agtv_value->val.string.len); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() unsupported argument agtype %d", agtv_value->type))); + } } /* right() only supports integer and agtype integer for the second parameter. */ @@ -8150,14 +8167,22 @@ Datum age_right(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == INT2OID) + { string_len = (int64) DatumGetInt16(arg); + } else if (type == INT4OID) + { string_len = (int64) DatumGetInt32(arg); + } else if (type == INT8OID) + { string_len = (int64) DatumGetInt64(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() unsupported argument type %d", type))); + } } else { @@ -8168,21 +8193,30 @@ Datum age_right(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() only supports scalar arguments"))); + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* no need to check for agtype null because it is an error if found */ if (agtv_value->type != AGTV_INTEGER) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() unsupported argument agtype %d", agtv_value->type))); + } string_len = agtv_value->val.int_value; } - /* negative values are not supported in the opencypher spec */ + /* out of range and negative values are not supported in the opencypher spec */ + if (string_len > INT_MAX || string_len < INT_MIN) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("right() length value is out of INT range"))); + } if (string_len < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("right() negative values are not supported for length"))); @@ -8219,7 +8253,7 @@ Datum age_left(PG_FUNCTION_ARGS) agtype_value agtv_result; text *text_string = NULL; char *string = NULL; - int string_len; + int64 string_len; Oid type; /* extract argument values */ @@ -8227,17 +8261,23 @@ Datum age_left(PG_FUNCTION_ARGS) /* check number of args */ if (nargs != 2) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() invalid number of arguments"))); + } /* check for a null string */ if (nargs < 0 || nulls[0]) + { PG_RETURN_NULL(); + } /* check for a null length */ if (nulls[1]) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() length parameter cannot be null"))); + } /* left() supports text, cstring, or the agtype string input */ arg = args[0]; @@ -8246,13 +8286,19 @@ Datum age_left(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == CSTRINGOID) + { text_string = cstring_to_text(DatumGetCString(arg)); + } else if (type == TEXTOID) + { text_string = DatumGetTextPP(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() unsupported argument type %d", type))); + } } else { @@ -8263,21 +8309,29 @@ Datum age_left(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() only supports scalar arguments"))); + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* check for agtype null */ if (agtv_value->type == AGTV_NULL) + { PG_RETURN_NULL(); + } if (agtv_value->type == AGTV_STRING) + { text_string = cstring_to_text_with_len(agtv_value->val.string.val, agtv_value->val.string.len); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() unsupported argument agtype %d", agtv_value->type))); + } } /* left() only supports integer and agtype integer for the second parameter. */ @@ -8287,14 +8341,22 @@ Datum age_left(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == INT2OID) + { string_len = (int64) DatumGetInt16(arg); + } else if (type == INT4OID) + { string_len = (int64) DatumGetInt32(arg); + } else if (type == INT8OID) + { string_len = (int64) DatumGetInt64(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() unsupported argument type %d", type))); + } } else { @@ -8305,24 +8367,37 @@ Datum age_left(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() only supports scalar arguments"))); + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* no need to check for agtype null because it is an error if found */ if (agtv_value->type != AGTV_INTEGER) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() unsupported argument agtype %d", agtv_value->type))); + } string_len = agtv_value->val.int_value; } - /* negative values are not supported in the opencypher spec */ + /* out of range and negative values are not supported in the opencypher spec */ + if (string_len > INT_MAX || string_len < INT_MIN) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("left() length value is out of INT range"))); + } + if (string_len < 0) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("left() negative values are not supported for length"))); + } + /* * We need the string as a text string so that we can let PG deal with @@ -8356,7 +8431,7 @@ Datum age_substring(PG_FUNCTION_ARGS) agtype_value agtv_result; text *text_string = NULL; char *string = NULL; - int param; + int64 param; int string_start = 0; int string_len = 0; int i; @@ -8367,19 +8442,24 @@ Datum age_substring(PG_FUNCTION_ARGS) /* check number of args */ if (nargs < 2 || nargs > 3) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() invalid number of arguments"))); + } /* check for null */ if (nargs < 0 || nulls[0]) + { PG_RETURN_NULL(); + } /* neither offset or length can be null if there is a valid string */ if ((nargs == 2 && nulls[1]) || (nargs == 3 && nulls[2])) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() offset or length cannot be null"))); - + } /* substring() supports text, cstring, or the agtype string input */ arg = args[0]; type = types[0]; @@ -8387,13 +8467,19 @@ Datum age_substring(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == CSTRINGOID) + { text_string = cstring_to_text(DatumGetCString(arg)); + } else if (type == TEXTOID) + { text_string = DatumGetTextPP(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() unsupported argument type %d", type))); + } } else { @@ -8404,21 +8490,29 @@ Datum age_substring(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() only supports scalar arguments"))); + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* check for agtype null */ if (agtv_value->type == AGTV_NULL) + { PG_RETURN_NULL(); + } if (agtv_value->type == AGTV_STRING) + { text_string = cstring_to_text_with_len(agtv_value->val.string.val, agtv_value->val.string.len); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() unsupported argument agtype %d", agtv_value->type))); + } } /* @@ -8433,15 +8527,23 @@ Datum age_substring(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == INT2OID) + { param = (int64) DatumGetInt16(arg); + } else if (type == INT4OID) + { param = (int64) DatumGetInt32(arg); + } else if (type == INT8OID) + { param = (int64) DatumGetInt64(arg); + } else + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() unsupported argument type %d", type))); + } } else { @@ -8452,30 +8554,46 @@ Datum age_substring(PG_FUNCTION_ARGS) agt_arg = DATUM_GET_AGTYPE_P(arg); if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() only supports scalar arguments"))); - + } agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); /* no need to check for agtype null because it is an error if found */ if (agtv_value->type != AGTV_INTEGER) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() unsupported argument agtype %d", agtv_value->type))); + } param = agtv_value->val.int_value; } + /* out of range values are not supported in the opencypher spec */ + if (param > INT_MAX || param < INT_MIN) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("substring() parameter value is out of INT range"))); + } + if (i == 1) + { string_start = param; + } if (i == 2) + { string_len = param; + } } /* negative values are not supported in the opencypher spec */ if (string_start < 0 || string_len < 0) + { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("substring() negative values are not supported for offset or length"))); + } /* cypher substring is 0 based while PG's is 1 based */ string_start += 1; @@ -8487,16 +8605,19 @@ Datum age_substring(PG_FUNCTION_ARGS) /* if optional length is left out */ if (nargs == 2) + { text_string = DatumGetTextPP(DirectFunctionCall2(text_substr_no_len, PointerGetDatum(text_string), Int64GetDatum(string_start))); + } /* if length is given */ else + { text_string = DatumGetTextPP(DirectFunctionCall3(text_substr, PointerGetDatum(text_string), Int64GetDatum(string_start), Int64GetDatum(string_len))); - + } /* convert it back to a cstring */ string = text_to_cstring(text_string); string_len = strlen(string); From 4f2c57a896d384ff7220f2cc83cf0cfd53f67795 Mon Sep 17 00:00:00 2001 From: John Gemignani Date: Mon, 2 Jun 2025 12:23:11 -0700 Subject: [PATCH 6/6] Fix issue with the CI build and labeler (#2183) The build now needs to use https:, instead of git: The labeler now requires a new step before running. Modified the following files - .github/workflows/installcheck.yaml .github/workflows/labeler.yml --- .github/workflows/installcheck.yaml | 14 ++++++++++---- .github/workflows/labeler.yml | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/installcheck.yaml b/.github/workflows/installcheck.yaml index 3a2d876c9..06ccd89b9 100644 --- a/.github/workflows/installcheck.yaml +++ b/.github/workflows/installcheck.yaml @@ -13,7 +13,7 @@ jobs: steps: - name: Get latest commit id of PostgreSQL 13 run: | - echo "PG_COMMIT_HASH=$(git ls-remote git://git.postgresql.org/git/postgresql.git refs/heads/REL_13_STABLE | awk '{print $1}')" >> $GITHUB_ENV + echo "PG_COMMIT_HASH=$(git ls-remote https://git.postgresql.org/git/postgresql.git refs/heads/REL_13_STABLE | awk '{print $1}')" >> $GITHUB_ENV - name: Cache PostgreSQL 13 uses: actions/cache@v3 @@ -25,7 +25,13 @@ jobs: - name: Install PostgreSQL 13 and some extensions if: steps.pg13cache.outputs.cache-hit != 'true' run: | - git clone --depth 1 --branch REL_13_STABLE git://git.postgresql.org/git/postgresql.git ~/pg13source + sudo apt-get update + sudo apt-get install -y build-essential libreadline-dev zlib1g-dev flex bison + + - name: Install PostgreSQL 13 and some extensions + if: steps.pg13cache.outputs.cache-hit != 'true' + run: | + git clone --depth 1 --branch REL_13_STABLE https://git.postgresql.org/git/postgresql.git ~/pg13source cd ~/pg13source ./configure --prefix=$HOME/pg13 CFLAGS="-std=gnu99 -ggdb -O0" --enable-cassert make install -j$(nproc) > /dev/null @@ -41,7 +47,7 @@ jobs: id: build run: | make PG_CONFIG=$HOME/pg13/bin/pg_config install -j$(nproc) - + - name: Pull and build pgvector id: pgvector run: | @@ -61,4 +67,4 @@ jobs: echo "Dump section begin." cat $HOME/work/age/age/regress/regression.diffs echo "Dump section end." - exit 1 \ No newline at end of file + exit 1 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 4234e3582..266df4ce9 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -9,6 +9,9 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Apply branch labels uses: actions/labeler@v5.0.0