Skip to content

Commit 627c9ad

Browse files
authored
Add more validation to user-callable bloom filter functions (#10074)
I forgot some places. Disable-check: force-changelog-file
1 parent aad4674 commit 627c9ad

3 files changed

Lines changed: 66 additions & 33 deletions

File tree

tsl/src/compression/batch_metadata_builder_bloom1.c

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ typedef struct Bloom1MetadataBuilder
6969
Bloom1HasherInternal hasher;
7070
} Bloom1MetadataBuilder;
7171

72-
static void bloom1_hasher_init(Bloom1HasherInternal *hasher, const Oid *type_oids, int num_columns);
72+
static Bloom1HasherInternal bloom1_hasher_init(const Oid *type_oids, int num_columns);
7373

7474
/*
7575
* Low-bias invertible hash function from this article:
@@ -544,6 +544,13 @@ bloom1_contains_context_prepare(FunctionCallInfo fcinfo, bool use_element_type)
544544
num_columns)));
545545
}
546546

547+
if (num_columns < 2)
548+
{
549+
ereport(ERROR,
550+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
551+
errmsg("composite bloom filter must have at least two columns")));
552+
}
553+
547554
for (int i = 0; i < num_columns; i++)
548555
{
549556
type_oids[i] = TupleDescAttr(tupdesc, i)->atttypid;
@@ -556,7 +563,7 @@ bloom1_contains_context_prepare(FunctionCallInfo fcinfo, bool use_element_type)
556563
num_columns = 1;
557564
}
558565

559-
bloom1_hasher_init(&context->bloom_hasher, type_oids, num_columns);
566+
context->bloom_hasher = bloom1_hasher_init(type_oids, num_columns);
560567

561568
get_typlenbyvalalign(context->element_type,
562569
&context->element_typlen,
@@ -583,12 +590,6 @@ bloom1_contains_hash_internal(const char *words_buf, uint32 num_bits, uint64 has
583590
{
584591
Assert(words_buf != NULL);
585592

586-
/* Must be a power of two. */
587-
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
588-
589-
/* Must be >= 64 bits. */
590-
CheckCompressedData(num_bits >= 64);
591-
592593
const uint32 num_word_bits = sizeof(*words_buf) * 8;
593594
Assert(num_bits % num_word_bits == 0);
594595
const uint32 log2_word_bits = pg_leftmost_one_pos32(num_word_bits);
@@ -767,12 +768,12 @@ bloom1_contains_any(PG_FUNCTION_ARGS)
767768
const char *words_buf = bloom1_words_buf(bloom);
768769
const uint32 num_bits = bloom1_num_bits(bloom);
769770

770-
/* Must be a power of two. */
771-
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
772-
773771
/* Must be >= 64 bits. */
774772
CheckCompressedData(num_bits >= 64);
775773

774+
/* Must be a power of two. */
775+
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
776+
776777
const uint32 num_word_bits = sizeof(*words_buf) * 8;
777778
Assert(num_bits % num_word_bits == 0);
778779
const uint32 log2_word_bits = pg_leftmost_one_pos32(num_word_bits);
@@ -809,7 +810,7 @@ bloom1_contains_any(PG_FUNCTION_ARGS)
809810
}
810811

811812
/*
812-
* Checks whether any hashes of the given array can be present in the given
813+
* Checks whether any hashes in the given array can be present in the given
813814
* bloom filter. This is used for predicate pushdown where the values are
814815
* pre-hashed at planning time.
815816
*
@@ -864,6 +865,12 @@ bloom1_contains_any_hashes(PG_FUNCTION_ARGS)
864865
const char *words_buf = bloom1_words_buf(bloom);
865866
const uint32 num_bits = bloom1_num_bits(bloom);
866867

868+
/* Must be >= 64 bits. */
869+
CheckCompressedData(num_bits >= 64);
870+
871+
/* Must be a power of two. */
872+
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
873+
867874
for (int i = 0; i < num_hashes; i++)
868875
{
869876
if (hash_nulls[i])
@@ -912,6 +919,13 @@ bloom1_hash(PG_FUNCTION_ARGS)
912919
num_columns)));
913920
}
914921

922+
if (num_columns < 2)
923+
{
924+
ereport(ERROR,
925+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
926+
errmsg("composite bloom filter must have at least two columns")));
927+
}
928+
915929
for (int i = 0; i < num_columns; i++)
916930
{
917931
type_oids[i] = TupleDescAttr(tupdesc, i)->atttypid;
@@ -938,8 +952,8 @@ bloom1_hash(PG_FUNCTION_ARGS)
938952
num_columns = 1;
939953
}
940954

941-
Bloom1Hasher *hasher = bloom1_hasher_create(type_oids, num_columns);
942-
uint64 hash = hasher->hash_values(hasher, values);
955+
Bloom1HasherInternal hasher = bloom1_hasher_init(type_oids, num_columns);
956+
uint64 hash = hasher.functions.hash_values(&hasher, values);
943957
PG_RETURN_INT64((int64) hash);
944958
}
945959

@@ -981,36 +995,39 @@ batch_metadata_builder_bloom1_varlena_size(void)
981995
return bloom1_varlena_alloc_size(desired_bits);
982996
}
983997

984-
static void
985-
bloom1_hasher_init(Bloom1HasherInternal *hasher, const Oid *type_oids, int num_columns)
998+
static Bloom1HasherInternal
999+
bloom1_hasher_init(const Oid *type_oids, int num_columns)
9861000
{
987-
*hasher = (Bloom1HasherInternal){
1001+
Bloom1HasherInternal hasher = (Bloom1HasherInternal){
9881002
.functions =
9891003
(Bloom1Hasher){
9901004
.hash_values = bloom1_hash_values,
9911005
.num_columns = num_columns,
9921006
},
9931007
};
9941008

1009+
Assert(num_columns != 0);
9951010
for (int i = 0; i < num_columns; i++)
9961011
{
997-
hasher->hash_functions[i] =
998-
bloom1_get_hash_function(type_oids[i], &hasher->hash_function_finfos[i]);
999-
if (hasher->hash_functions[i] == NULL)
1012+
hasher.hash_functions[i] =
1013+
bloom1_get_hash_function(type_oids[i], &hasher.hash_function_finfos[i]);
1014+
if (hasher.hash_functions[i] == NULL)
10001015
{
10011016
ereport(ERROR,
10021017
(errcode(ERRCODE_UNDEFINED_FUNCTION),
10031018
errmsg("the argument type %s lacks an extended hash function",
10041019
format_type_be(type_oids[i]))));
10051020
}
10061021
}
1022+
1023+
return hasher;
10071024
}
10081025

10091026
Bloom1Hasher *
10101027
bloom1_hasher_create(const Oid *type_oids, int num_columns)
10111028
{
10121029
Bloom1HasherInternal *hasher = palloc(sizeof(*hasher));
1013-
bloom1_hasher_init(hasher, type_oids, num_columns);
1030+
*hasher = bloom1_hasher_init(type_oids, num_columns);
10141031
return &hasher->functions;
10151032
}
10161033

@@ -1038,7 +1055,7 @@ batch_metadata_builder_bloom1_create(int num_columns, const Oid *type_oids,
10381055
memcpy(builder->input_columns, attnums, num_columns * sizeof(AttrNumber));
10391056

10401057
/* Initialize the embedded hasher */
1041-
bloom1_hasher_init(&builder->hasher, type_oids, num_columns);
1058+
builder->hasher = bloom1_hasher_init(type_oids, num_columns);
10421059

10431060
/*
10441061
* Initialize the bloom filter.
@@ -1197,15 +1214,15 @@ ts_bloom1_composite_debug_hash(PG_FUNCTION_ARGS)
11971214
}
11981215
ReleaseTupleDesc(tupdesc);
11991216

1200-
Bloom1Hasher *hasher = bloom1_hasher_create(type_oids, num_fields);
1217+
Bloom1HasherInternal hasher = bloom1_hasher_init(type_oids, num_fields);
12011218

12021219
NullableDatum values[MAX_BLOOM_FILTER_COLUMNS];
12031220
for (int i = 0; i < num_fields; i++)
12041221
{
12051222
values[i].value = GetAttributeByNum(tuple, i + 1, &values[i].isnull);
12061223
}
12071224

1208-
uint64 hash = hasher->hash_values(hasher, values);
1225+
uint64 hash = hasher.functions.hash_values(&hasher, values);
12091226
PG_RETURN_INT64((int64) hash);
12101227
}
12111228

@@ -1228,9 +1245,8 @@ bloom1_contains_hash(Datum bloom_datum, uint64 hash)
12281245
const uint32 num_bits = 8 * VARSIZE_ANY_EXHDR(bloom);
12291246

12301247
/* Validate bloom structure */
1231-
CheckCompressedData(num_bits != 0);
1232-
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
12331248
CheckCompressedData(num_bits >= 64);
1249+
CheckCompressedData(num_bits == (1ULL << pg_leftmost_one_pos32(num_bits)));
12341250

12351251
return bloom1_contains_hash_internal(words_buf, num_bits, hash);
12361252
}

tsl/test/expected/compress_bloom_sparse_debug.out

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,25 @@ select ts_bloom1_debug_hash('2025-05-05'::timestamptz);
156156
----------------------
157157
2448302731963240730
158158

159-
-- The "contains" functions should error out when called with wrong arguments.
160159
\set ON_ERROR_STOP 0
160+
-- The "contains" functions should error out when called with wrong arguments.
161161
select _timescaledb_functions.bloom1_contains('\xffffffffffffffff'::_timescaledb_internal.bloom1, 1::bit) ;
162162
ERROR: the argument type bit lacks an extended hash function
163163
select _timescaledb_functions.bloom1_contains_any('\xffffffffffffffff'::_timescaledb_internal.bloom1, array[1::bit]) ;
164164
ERROR: the argument type bit lacks an extended hash function
165165
select _timescaledb_functions.bloom1_contains(_timescaledb_functions.bloom1in('\x'::cstring), 1);
166166
ERROR: the compressed data is corrupt
167+
select _timescaledb_functions.bloom1_contains_any(_timescaledb_functions.bloom1in('\x'::cstring), 1);
168+
ERROR: function _timescaledb_functions.bloom1_contains_any(_timescaledb_internal.bloom1, integer) does not exist at character 8
169+
select _timescaledb_functions.bloom1_contains('\x'::_timescaledb_internal.bloom1, ROW(1));
170+
ERROR: composite bloom filter must have at least two columns
171+
select _timescaledb_functions.bloom1_contains('\xffffffffffffffff'::_timescaledb_internal.bloom1, ROW());
172+
ERROR: composite bloom filter must have at least two columns
173+
-- The hash function is callable by user, so must return proper error
174+
select _timescaledb_functions.bloom1_hash(ROW(1, 2, 3, 4, 5, 6, 7, 8, 9));
175+
ERROR: composite bloom filter supports at most 8 columns, got 9
176+
select _timescaledb_functions.bloom1_hash(ROW());
177+
ERROR: composite bloom filter must have at least two columns
167178
\set ON_ERROR_STOP 1
168179
-- Test that the "contains" function cope with different source chunks.
169180
create table detoaster(ts int, tag text) with (tsdb.hypertable,
@@ -234,6 +245,3 @@ SELECT _timescaledb_functions.bloom1_contains(NULL, pg_catalog.record_in(null::c
234245
-----------------
235246
f
236247

237-
-- The hash function is callable by user, so must return proper error
238-
SELECT _timescaledb_functions.bloom1_hash(ROW(1, 2, 3, 4, 5, 6, 7, 8, 9));
239-
ERROR: composite bloom filter supports at most 8 columns, got 9

tsl/test/sql/compress_bloom_sparse_debug.sql

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,15 +109,26 @@ select ts_bloom1_debug_hash('2025-05-05'::timestamp);
109109
select ts_bloom1_debug_hash('2025-05-05'::timestamptz);
110110

111111

112-
-- The "contains" functions should error out when called with wrong arguments.
113112
\set ON_ERROR_STOP 0
114113

114+
-- The "contains" functions should error out when called with wrong arguments.
115115
select _timescaledb_functions.bloom1_contains('\xffffffffffffffff'::_timescaledb_internal.bloom1, 1::bit) ;
116116

117117
select _timescaledb_functions.bloom1_contains_any('\xffffffffffffffff'::_timescaledb_internal.bloom1, array[1::bit]) ;
118118

119119
select _timescaledb_functions.bloom1_contains(_timescaledb_functions.bloom1in('\x'::cstring), 1);
120120

121+
select _timescaledb_functions.bloom1_contains_any(_timescaledb_functions.bloom1in('\x'::cstring), 1);
122+
123+
select _timescaledb_functions.bloom1_contains('\x'::_timescaledb_internal.bloom1, ROW(1));
124+
125+
select _timescaledb_functions.bloom1_contains('\xffffffffffffffff'::_timescaledb_internal.bloom1, ROW());
126+
127+
-- The hash function is callable by user, so must return proper error
128+
select _timescaledb_functions.bloom1_hash(ROW(1, 2, 3, 4, 5, 6, 7, 8, 9));
129+
130+
select _timescaledb_functions.bloom1_hash(ROW());
131+
121132
\set ON_ERROR_STOP 1
122133

123134

@@ -171,7 +182,5 @@ SELECT _timescaledb_functions.bloom1_contains('\xd098c885f08468eb8916751d947f248
171182
SELECT _timescaledb_functions.bloom1_contains(NULL, pg_catalog.record_in(null::cstring, 23::oid, 12::int4));
172183

173184

174-
-- The hash function is callable by user, so must return proper error
175-
SELECT _timescaledb_functions.bloom1_hash(ROW(1, 2, 3, 4, 5, 6, 7, 8, 9));
176185

177186

0 commit comments

Comments
 (0)