From a2148827eebb175a367bd3f36077766aa2dc3a1a Mon Sep 17 00:00:00 2001 From: Matt Magoffin Date: Tue, 30 Nov 2021 16:44:07 +1300 Subject: [PATCH] Add vec_sub(l,r) to help with array-based subtraction between columns. --- aggs_for_vecs--1.3.0.sql | 37 +++++++++++++- aggs_for_vecs.c | 1 + test/vec_sub.bats | 37 ++++++++++++++ vec_sub.c | 108 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 test/vec_sub.bats create mode 100644 vec_sub.c diff --git a/aggs_for_vecs--1.3.0.sql b/aggs_for_vecs--1.3.0.sql index 9e565f9..1bba258 100644 --- a/aggs_for_vecs--1.3.0.sql +++ b/aggs_for_vecs--1.3.0.sql @@ -40,6 +40,41 @@ LANGUAGE c; +-- vec_sub + +CREATE OR REPLACE FUNCTION +vec_sub(smallint[], smallint[]) +RETURNS smallint[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; +CREATE OR REPLACE FUNCTION +vec_sub(int[], int[]) +RETURNS int[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; +CREATE OR REPLACE FUNCTION +vec_sub(bigint[], bigint[]) +RETURNS bigint[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; +CREATE OR REPLACE FUNCTION +vec_sub(real[], real[]) +RETURNS real[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; +CREATE OR REPLACE FUNCTION +vec_sub(float[], float[]) +RETURNS float[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; +CREATE OR REPLACE FUNCTION +vec_sub(numeric[], numeric[]) +RETURNS numeric[] +AS 'aggs_for_vecs', 'vec_sub' +LANGUAGE c; + + + -- vec_trim_scale CREATE OR REPLACE FUNCTION @@ -50,7 +85,7 @@ LANGUAGE c; --- vec_without_outliers +-- vec_sub CREATE OR REPLACE FUNCTION vec_without_outliers(smallint[], smallint[], smallint[]) diff --git a/aggs_for_vecs.c b/aggs_for_vecs.c index f0e4d10..cfeacab 100644 --- a/aggs_for_vecs.c +++ b/aggs_for_vecs.c @@ -21,6 +21,7 @@ PG_MODULE_MAGIC; #include "util.c" #include "pad_vec.c" +#include "vec_sub.c" #include "vec_trim_scale.c" #include "vec_without_outliers.c" #include "vec_to_count.c" diff --git a/test/vec_sub.bats b/test/vec_sub.bats new file mode 100644 index 0000000..962dc9d --- /dev/null +++ b/test/vec_sub.bats @@ -0,0 +1,37 @@ +load test_helper + +@test "int16 sub" { + result="$(query "SELECT vec_sub(ARRAY[1,2,3]::smallint[], ARRAY[3,2,1]::smallint[])")"; + echo $result; + [ "$result" = "{-2,0,2}" ] +} + +@test "int32 sub" { + result="$(query "SELECT vec_sub(ARRAY[1,2,3]::integer[], ARRAY[3,2,1]::integer[])")"; + echo $result; + [ "$result" = "{-2,0,2}" ] +} + +@test "int64 sub" { + result="$(query "SELECT vec_sub(ARRAY[1,2,3]::bigint[], ARRAY[3,2,1]::bigint[])")"; + echo $result; + [ "$result" = "{-2,0,2}" ] +} + +@test "float32 sub" { + result="$(query "SELECT vec_sub(ARRAY[1.0,2.2,3.4]::real[], ARRAY[3.4,2.2,1.0]::real[])")"; + echo $result; + [ "$result" = "{-2.4,0,2.4}" ] +} + +@test "float64 sub" { + result="$(query "SELECT vec_sub(ARRAY[1.1,2.2,3.4]::float[], ARRAY[3.4,2.2,1.1]::float[])")"; + echo $result; + [ "$result" = "{-2.3,0,2.3}" ] +} + +@test "numeric sub measurements" { + result="$(query "SELECT diff FROM (SELECT vec_sub(nums, lag(nums) OVER (ORDER BY sensor_id)) AS diff FROM measurements d WHERE sensor_id IN (3,4)) d WHERE d.diff IS NOT NULL")"; + echo $result; + [ "$result" = "{0.00,NULL,-1.11}" ] +} diff --git a/vec_sub.c b/vec_sub.c new file mode 100644 index 0000000..b1812e6 --- /dev/null +++ b/vec_sub.c @@ -0,0 +1,108 @@ + +Datum vec_sub(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(vec_sub); + +/** + * Subtract elements of two arrays. + * + * This function takes two array of n-elements and returns an array of + * n-elements where each element is difference between the input elements + * of the same index. + */ +Datum +vec_sub(PG_FUNCTION_ARGS) +{ + Oid elemTypeId; + int16 elemTypeWidth; + bool elemTypeByValue; + char elemTypeAlignmentCode; + int valsLength; + ArrayType *leftArray, *rightArray, *retArray; + Datum *leftContent, *rightContent, *retContent; + bool *leftNulls, *rightNulls, *retNulls; + int i; + int dims[1]; + int lbs[1]; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { + PG_RETURN_NULL(); + } + + leftArray = PG_GETARG_ARRAYTYPE_P(0); + rightArray = PG_GETARG_ARRAYTYPE_P(1); + + if (ARR_NDIM(leftArray) == 0 || ARR_NDIM(rightArray) == 0) { + PG_RETURN_NULL(); + } + if (ARR_NDIM(leftArray) > 1 || ARR_NDIM(rightArray) > 1) { + ereport(ERROR, (errmsg("vec_sub: one-dimensional arrays are required"))); + } + + elemTypeId = ARR_ELEMTYPE(leftArray); + + if (elemTypeId != INT2OID && + elemTypeId != INT4OID && + elemTypeId != INT8OID && + elemTypeId != FLOAT4OID && + elemTypeId != FLOAT8OID && + elemTypeId != NUMERICOID) { + ereport(ERROR, (errmsg("vec_sub input must be array of SMALLINT, INTEGER, BIGINT, REAL, DOUBLE PRECISION, or NUMERIC"))); + } + if (rightArray && elemTypeId != ARR_ELEMTYPE(rightArray)) { + ereport(ERROR, (errmsg("vec_sub mins array must be the same type as input array"))); + } + + valsLength = (ARR_DIMS(leftArray))[0]; + if (rightArray && valsLength != (ARR_DIMS(rightArray))[0]) { + ereport(ERROR, (errmsg("vec_sub right array must be the same length as left array"))); + } + + get_typlenbyvalalign(elemTypeId, &elemTypeWidth, &elemTypeByValue, &elemTypeAlignmentCode); + + deconstruct_array(leftArray, elemTypeId, elemTypeWidth, elemTypeByValue, elemTypeAlignmentCode, + &leftContent, &leftNulls, &valsLength); + if (rightArray) { + deconstruct_array(rightArray, elemTypeId, elemTypeWidth, elemTypeByValue, elemTypeAlignmentCode, + &rightContent, &rightNulls, &valsLength); + } + + retContent = palloc0(sizeof(Datum) * valsLength); + retNulls = palloc0(sizeof(bool) * valsLength); + + for (i = 0; i < valsLength; i++) { + if (leftNulls[i] || rightNulls[i]) { + retNulls[i] = true; + continue; + } + retNulls[i] = false; + switch(elemTypeId) { + case INT2OID: + retContent[i] = Int16GetDatum(DatumGetInt16(leftContent[i]) - DatumGetInt16(rightContent[i])); + break; + case INT4OID: + retContent[i] = Int32GetDatum(DatumGetInt32(leftContent[i]) - DatumGetInt32(rightContent[i])); + break; + case INT8OID: + retContent[i] = Int64GetDatum(DatumGetInt64(leftContent[i]) - DatumGetInt64(rightContent[i])); + break; + case FLOAT4OID: + retContent[i] = Float4GetDatum(DatumGetFloat4(leftContent[i]) - DatumGetFloat4(rightContent[i])); + break; + case FLOAT8OID: + retContent[i] = Float8GetDatum(DatumGetFloat8(leftContent[i]) - DatumGetFloat8(rightContent[i])); + break; + case NUMERICOID: + retContent[i] = DirectFunctionCall2(numeric_sub, leftContent[i], rightContent[i]); + break; + } + } + + dims[0] = valsLength; + lbs[0] = 1; + + retArray = construct_md_array(retContent, retNulls, 1, dims, lbs, + elemTypeId, elemTypeWidth, elemTypeByValue, elemTypeAlignmentCode); + + PG_RETURN_ARRAYTYPE_P(retArray); +} +