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 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") diff --git a/regress/expected/expr.out b/regress/expected/expr.out index b419e089a..66eec8e20 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); @@ -4313,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) @@ -4330,15 +4514,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); @@ -4392,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) @@ -4437,6 +4634,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) @@ -4480,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) @@ -4676,33 +4904,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 +4957,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 +4965,7 @@ $$) AS (results agtype); (1 row) SELECT * FROM cypher('expr', $$ - RETURN replace("", "Hello", "Mellow") + RETURN replace("Hello", "", null) $$) AS (results agtype); results --------- @@ -4743,24 +4990,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..4ca7d187e 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); @@ -1827,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) @@ -1834,10 +1895,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); @@ -1859,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) @@ -1874,6 +1945,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) @@ -1887,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) @@ -1975,31 +2066,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/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; /* diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index caace491a..82e128f64 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; @@ -8108,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 */ @@ -8116,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]; @@ -8135,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 { @@ -8152,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. */ @@ -8176,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 { @@ -8194,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"))); @@ -8225,10 +8233,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; @@ -8249,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 */ @@ -8257,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]; @@ -8276,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 { @@ -8293,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. */ @@ -8317,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 { @@ -8335,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 @@ -8366,10 +8411,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; @@ -8390,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; @@ -8401,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]; @@ -8421,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 { @@ -8438,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))); + } } /* @@ -8467,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 { @@ -8486,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; @@ -8521,24 +8605,23 @@ 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); - /* 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 +8876,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; 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(); }