Skip to content

Commit

Permalink
Fix prepared statement param handling in ChunkAppend
Browse files Browse the repository at this point in the history
This patch fixes the param handling in prepared statements for generic
plans in ChunkAppend making those params usable in chunk exclusion.
Previously those params would not be resolved and therefore not used
for chunk exclusion.

Fixes #3719
  • Loading branch information
svenklemm committed Jul 18, 2022
1 parent 93bad00 commit 1f528e2
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/nodes/chunk_append/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ do_startup_exclusion(ChunkAppendState *state)
* create skeleton plannerinfo for estimate_expression_value
*/
PlannerGlobal glob = {
.boundParams = NULL,
.boundParams = state->csstate.ss.ps.state->es_param_list_info,
};
PlannerInfo root = {
.glob = &glob,
Expand Down Expand Up @@ -339,7 +339,7 @@ initialize_runtime_exclusion(ChunkAppendState *state)
int i = 0;

PlannerGlobal glob = {
.boundParams = NULL,
.boundParams = state->csstate.ss.ps.state->es_param_list_info,
};
PlannerInfo root = {
.glob = &glob,
Expand Down
26 changes: 26 additions & 0 deletions tsl/test/shared/expected/compression_dml.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
-- test constraint exclusion with prepared statements and generic plans
CREATE TABLE i3719 (time timestamptz NOT NULL,data text);
SELECT table_name FROM create_hypertable('i3719', 'time');
table_name
i3719
(1 row)

ALTER TABLE i3719 SET (timescaledb.compress);
INSERT INTO i3719 VALUES('2021-01-01 00:00:00', 'chunk 1');
SELECT count(compress_chunk(c)) FROM show_chunks('i3719') c;
count
1
(1 row)

INSERT INTO i3719 VALUES('2021-02-22 08:00:00', 'chunk 2');
SET plan_cache_mode TO force_generic_plan;
PREPARE p1(timestamptz) AS UPDATE i3719 SET data = 'x' WHERE time=$1;
PREPARE p2(timestamptz) AS DELETE FROM i3719 WHERE time=$1;
EXECUTE p1('2021-02-22T08:00:00+00');
EXECUTE p2('2021-02-22T08:00:00+00');
DEALLOCATE p1;
DEALLOCATE p2;
DROP TABLE i3719;
324 changes: 324 additions & 0 deletions tsl/test/shared/expected/constraint_exclusion_prepared.out
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,47 @@ QUERY PLAN

DEALLOCATE prep;
RESET timescaledb.enable_chunk_append;
-- test prepared statement with params and generic plan
SET plan_cache_mode TO force_generic_plan;
PREPARE prep(timestamptz) AS SELECT device_id, time FROM :TEST_TABLE WHERE time = $1;
:PREFIX EXECUTE prep('2000-01-01 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics (actual rows=5 loops=1)
Chunks excluded during runtime: 2
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (actual rows=5 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(8 rows)

:PREFIX EXECUTE prep('2000-01-10 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics (actual rows=5 loops=1)
Chunks excluded during runtime: 2
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (actual rows=5 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(8 rows)

:PREFIX EXECUTE prep(now());
QUERY PLAN
Custom Scan (ChunkAppend) on metrics (actual rows=0 loops=1)
Chunks excluded during runtime: 3
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(8 rows)

DEALLOCATE prep;
RESET plan_cache_mode;
\set TEST_TABLE 'metrics_space'
\ir :TEST_QUERY_NAME
-- This file and its contents are licensed under the Timescale License.
Expand Down Expand Up @@ -1336,6 +1377,83 @@ QUERY PLAN

DEALLOCATE prep;
RESET timescaledb.enable_chunk_append;
-- test prepared statement with params and generic plan
SET plan_cache_mode TO force_generic_plan;
PREPARE prep(timestamptz) AS SELECT device_id, time FROM :TEST_TABLE WHERE time = $1;
:PREFIX EXECUTE prep('2000-01-01 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space (actual rows=5 loops=1)
Chunks excluded during runtime: 6
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=1 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=3 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=1 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(20 rows)

:PREFIX EXECUTE prep('2000-01-10 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space (actual rows=5 loops=1)
Chunks excluded during runtime: 6
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=1 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=3 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (actual rows=1 loops=1)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(20 rows)

:PREFIX EXECUTE prep(now());
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space (actual rows=0 loops=1)
Chunks excluded during runtime: 9
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
-> Index Scan using _hyper_X_X_chunk_metrics_space_time_idx on _hyper_X_X_chunk (never executed)
Index Cond: ("time" = $1)
(20 rows)

DEALLOCATE prep;
RESET plan_cache_mode;
\set TEST_TABLE 'metrics_compressed'
\ir :TEST_QUERY_NAME
-- This file and its contents are licensed under the Timescale License.
Expand Down Expand Up @@ -1946,6 +2064,69 @@ QUERY PLAN

DEALLOCATE prep;
RESET timescaledb.enable_chunk_append;
-- test prepared statement with params and generic plan
SET plan_cache_mode TO force_generic_plan;
PREPARE prep(timestamptz) AS SELECT device_id, time FROM :TEST_TABLE WHERE time = $1;
:PREFIX EXECUTE prep('2000-01-01 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_compressed (actual rows=5 loops=1)
Chunks excluded during runtime: 2
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=5 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 4995
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=5 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 15
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(16 rows)

:PREFIX EXECUTE prep('2000-01-10 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_compressed (actual rows=5 loops=1)
Chunks excluded during runtime: 2
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=5 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 4995
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=5 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 25
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(16 rows)

:PREFIX EXECUTE prep(now());
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_compressed (actual rows=0 loops=1)
Chunks excluded during runtime: 3
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(14 rows)

DEALLOCATE prep;
RESET plan_cache_mode;
\set TEST_TABLE 'metrics_space_compressed'
\ir :TEST_QUERY_NAME
-- This file and its contents are licensed under the Timescale License.
Expand Down Expand Up @@ -2996,6 +3177,149 @@ QUERY PLAN

DEALLOCATE prep;
RESET timescaledb.enable_chunk_append;
-- test prepared statement with params and generic plan
SET plan_cache_mode TO force_generic_plan;
PREPARE prep(timestamptz) AS SELECT device_id, time FROM :TEST_TABLE WHERE time = $1;
:PREFIX EXECUTE prep('2000-01-01 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space_compressed (actual rows=5 loops=1)
Chunks excluded during runtime: 6
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 999
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 3
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=3 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 2997
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=3 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 9
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 999
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 3
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(44 rows)

:PREFIX EXECUTE prep('2000-01-10 23:42');
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space_compressed (actual rows=5 loops=1)
Chunks excluded during runtime: 6
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 999
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 5
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=3 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 2997
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=3 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 15
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ("time" = $1)
Rows Removed by Filter: 999
-> Seq Scan on compress_hyper_X_X_chunk (actual rows=1 loops=1)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
Rows Removed by Filter: 5
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(44 rows)

:PREFIX EXECUTE prep(now());
QUERY PLAN
Custom Scan (ChunkAppend) on metrics_space_compressed (actual rows=0 loops=1)
Chunks excluded during runtime: 9
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
-> Custom Scan (DecompressChunk) on _hyper_X_X_chunk (never executed)
Filter: ("time" = $1)
-> Seq Scan on compress_hyper_X_X_chunk (never executed)
Filter: ((_ts_meta_min_1 <= $1) AND (_ts_meta_max_1 >= $1))
(38 rows)

DEALLOCATE prep;
RESET plan_cache_mode;
-- get results for all the queries
-- run queries on uncompressed hypertable and store result
\set PREFIX ''
Expand Down
Loading

0 comments on commit 1f528e2

Please sign in to comment.