0005-Implement-get_partitions_for_keys-v3.patch

text/plain

Filename: 0005-Implement-get_partitions_for_keys-v3.patch
Type: text/plain
Part: 4
Message: Re: path toward faster partition pruning

Patch

Format: format-patch
Series: patch v3-0005
Subject: Implement get_partitions_for_keys
File+
src/backend/catalog/partition.c 350 6
src/test/regress/expected/inherit.out 1 3
src/test/regress/expected/partition.out 20 44
From 5760054b91f7f115b3690432d1ee72a3db7770f3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 18 Oct 2017 17:14:53 +0900
Subject: [PATCH 5/5] Implement get_partitions_for_keys

Disable constraint_exclusion using internal partition constraints.
---
 src/backend/catalog/partition.c         | 356 +++++++++++++++++++++++++++++++-
 src/test/regress/expected/inherit.out   |   4 +-
 src/test/regress/expected/partition.out |  64 ++----
 3 files changed, 371 insertions(+), 53 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 73f4e7ab95..c5875dc064 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -178,7 +178,7 @@ typedef struct PartitionScanKeyInfo
 typedef struct PartitionSet
 {
 	/*
-	 * If either  empty or all_parts is true, values of the other fields are
+	 * If either  empty or all is true, values of the other fields are
 	 * invalid.
 	 */
 	bool	empty;				/* contains no partitions */
@@ -1121,7 +1121,7 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 {
 	List	   *new_part_constraints;
 	List	   *def_part_constraints;
-	List	   *all_parts;
+	List	   *all;
 	ListCell   *lc;
 
 	new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
@@ -1148,12 +1148,12 @@ check_default_allows_bound(Relation parent, Relation default_rel,
 	 * that do not satisfy the revised partition constraints.
 	 */
 	if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		all_parts = find_all_inheritors(RelationGetRelid(default_rel),
+		all = find_all_inheritors(RelationGetRelid(default_rel),
 										AccessExclusiveLock, NULL);
 	else
-		all_parts = list_make1_oid(RelationGetRelid(default_rel));
+		all = list_make1_oid(RelationGetRelid(default_rel));
 
-	foreach(lc, all_parts)
+	foreach(lc, all)
 	{
 		Oid			part_relid = lfirst_oid(lc);
 		Relation	part_rel;
@@ -2507,7 +2507,351 @@ partition_cmp_args(Oid partopfamily, Oid partopcintype,
 static PartitionSet *
 get_partitions_for_keys(Relation rel, PartitionScanKeyInfo *keys)
 {
-	return partset_new(false, true);
+	PartitionSet   *partset;
+	int		i,
+			eqoff = -1,
+			minoff = -1,
+			maxoff = -1;
+	PartitionKey	partkey = RelationGetPartitionKey(rel);
+	PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+	PartitionBoundInfo	boundinfo = partdesc->boundinfo;
+	PartitionBoundCmpArg	arg;
+	bool	is_equal;
+
+	/* Return an empty set if no partitions to see. */
+	if (partdesc->nparts == 0)
+		return partset_new(true, false);
+
+	/*
+	 * Initialize the set as one that's neither empty nor contains all
+	 * partitions.  The code below will set min_part_idx and max_part_idx
+	 * and/or other_parts as found out by comparing keys to the partition
+	 * bounds, as well as considering special partitions like null-accepting
+	 * and default partitions.  If it turns out that no partitions need to
+	 * be scanned, partset->empty will be set to true.
+	 */
+	partset = partset_new(false, false);
+
+	/*
+	 * Check if any of the scan keys are null.  If so, return the only
+	 * null-accepting partition if boundinfo says there is one.
+	 */
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		if (keys->keynullness[i] == IS_NULL)
+		{
+			int		other_idx = -1;
+
+			/*
+			 * Note that only one of the null-accepting partition and the
+			 * default partition can be holding null values at any given
+			 * time.
+			 */
+			if (partition_bound_accepts_nulls(boundinfo)||
+				partition_bound_has_default(boundinfo))
+				other_idx = partition_bound_accepts_nulls(boundinfo)
+								? boundinfo->null_index
+								: boundinfo->default_index;
+			if (other_idx >= 0)
+				partset->other_parts = bms_make_singleton(other_idx);
+
+			return partset;
+		}
+	}
+
+	/*
+	 * If there are no datums to compare keys with, but there exists a
+	 * partition, the latter must be a partition that accepts only nulls
+	 * or a default partition.  If it is the former and we didn't already
+	 * return it as the only scannable partition, that means the query
+	 * doesn't want null values in its outout.  So, all of what the query
+	 * wants instead must be in the default partition.
+	 */
+	if (boundinfo->ndatums == 0)
+	{
+		if (partition_bound_has_default(boundinfo))
+			partset->other_parts =
+							bms_make_singleton(boundinfo->default_index);
+		else
+			partset->empty = true;
+		return partset;
+	}
+	/* No bounding keys, so just return all partitions. */
+	else if (keys->n_eqkeys + keys->n_minkeys + keys->n_maxkeys == 0)
+	{
+		partset->all_parts = true;
+		return partset;
+	}
+
+	/* Valid keys->eqkeys must provoide all partition keys. */
+	Assert(keys->n_eqkeys == 0 || keys->n_eqkeys == partkey->partnatts);
+	eqoff = -1;
+	if (keys->n_eqkeys > 0)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->eqkeys;
+		arg.ndatums = keys->n_eqkeys;
+		eqoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal);
+
+		if (eqoff >= 0)
+		{
+			switch (partkey->strategy)
+			{
+				case PARTITION_STRATEGY_LIST:
+					/* For list partition, must exactly match the datum. */
+					if (!is_equal)
+						eqoff = -1;
+					break;
+
+				case PARTITION_STRATEGY_RANGE:
+					/*
+					 * eqoff is gives us the bound that is known to be <=
+					 * eqkeys given how partition_bound_bsearch works.  The
+					 * bound at eqoff + 1, then, would be the upper bound of
+					 * the only partition that needs to be scanned.
+					 */
+					if (partkey->strategy == PARTITION_STRATEGY_RANGE)
+						eqoff += 1;
+			}
+		}
+
+		/*
+		 * Ask later code to include the default partition, because eqkeys
+		 * didn't identify a specific partition or identified a range
+		 * of unassigned values.
+		 */
+		if (eqoff >= 0 && boundinfo->indexes[eqoff] >= 0)
+			partset->other_parts =
+							bms_make_singleton(boundinfo->indexes[eqoff]);
+		else if (partition_bound_has_default(boundinfo))
+			partset->other_parts =
+							bms_make_singleton(boundinfo->default_index);
+		else
+			partset->empty = true;
+
+		/* There are no minkeys and maxkeys when eqkeys is valid. */
+		return partset;
+	}
+
+	/*
+	 * Find the leftmost bound that satisfies the query, i.e., one that
+	 * satisfies minkeys.
+	 */
+	minoff = 0;
+	if (keys->n_minkeys > 0)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->minkeys;
+		arg.ndatums = keys->n_minkeys;
+		minoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal);
+
+		switch (partkey->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				/*
+				 * minkeys matched one of the datums (because, is_equal), but
+				 * the query may have asked to exclude that value.  If so,
+				 * move to the bound on the right, which doesn't necessarily
+				 * mean we're excluding the list partition containing that
+				 * value, because there very well might be values in the range
+				 * thus selected that belong to the partition to which the
+				 * matched value (minkeys) also belongs.
+				 */
+				if (is_equal && !keys->min_incl)
+					minoff++;
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				/*
+				 * If only a prefix of the whole partition key is provided,
+				 * there will be multiple partitions whose bound share the
+				 * same prefix.  If minkey is inclusive, we must make minoff
+				 * point to the leftmost such bound, making the result contain
+				 * all such partitions.  If it is exclusive, we must move
+				 * minoff to the right such that minoff points to the first
+				 * partition whose bound is greater than this prefix, thus
+				 * excluding all aforementioned partitions from appearing in
+				 * the result.
+				 */
+				if (is_equal && arg.ndatums < partkey->partnatts)
+				{
+					int32	cmpval;
+
+					is_equal = false;
+					do
+					{
+						if (keys->min_incl)
+							minoff -= 1;
+						else
+							minoff += 1;
+						if (minoff < 0 || minoff >= boundinfo->ndatums)
+							break;
+						cmpval = partition_bound_cmp(partkey, boundinfo,
+													 minoff, &arg);
+					} while (cmpval == 0);
+
+					/* Back up if went too far. */
+					if (!keys->min_incl)
+						minoff -= 1;
+				}
+
+				/*
+				 * At this point, minoff gives us the leftmost bound that is
+				 * known to be <= query's minkey.  The bound at minoff + 1,
+				 * then, would be the upper bound of the leftmost partition
+				 * that needs to be scanned.
+				 */
+				minoff += 1;
+				break;
+		}
+	}
+
+	/*
+	 * Find the rightmost bound that satisfies the query, i.e., one that
+	 * satisfies maxkeys.
+	 */
+	maxoff = boundinfo->ndatums - 1;
+	if (keys->n_maxkeys > 0)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = keys->maxkeys;
+		arg.ndatums = keys->n_maxkeys;
+		maxoff = partition_bound_bsearch(partkey, boundinfo, &arg, &is_equal);
+
+		switch (partkey->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				/* See the comment above for minkeys. */
+				if (is_equal && !keys->max_incl)
+					maxoff--;
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				/* See the comment above for minkeys. */
+				if (is_equal && arg.ndatums < partkey->partnatts)
+				{
+					int32	cmpval;
+
+					is_equal = false;
+					do
+					{
+						if (keys->max_incl)
+							maxoff += 1;
+						else
+							maxoff -= 1;
+						if (maxoff < 0 || maxoff >= boundinfo->ndatums)
+							break;
+						cmpval = partition_bound_cmp(partkey, boundinfo,
+													 maxoff, &arg);
+					} while (cmpval == 0);
+
+					/* Back up if went too far. */
+					if (keys->max_incl)
+						maxoff -= 1;
+				}
+
+				/*
+				 * At this point, maxoff gives us the rightmost bound that is
+				 * known to be <= query's maxkey.  The bound at maxoff + 1,
+				 * then, would be the upper bound of the rightmost partition
+				 * that needs to be scanned.  Although, if the bound is equal
+				 * to maxkeys and the latter is not inclusive, then the bound
+				 * itself is the upper bound of the rightmost partition that
+				 * needs to be scanned.
+				 */
+				if (!is_equal || keys->max_incl)
+					maxoff += 1;
+
+				break;
+		}
+	}
+
+	if (minoff >= 0 && maxoff >= 0)
+	{
+		bool	include_default = false;
+
+		/*
+		 * If the bound at minoff or maxoff looks like it's an upper bound of a
+		 * range of values unassigned to any partition, move to the adjacent
+		 * bound which instead must be the upper bound of the leftmost or
+		 * rightmost partition, respectively, that needs to be scanned.
+		 *
+		 * By doing that, we skip over a portion of values that do indeed
+		 * satisfy the query, but don't have a valid partition assigned.
+		 * Include the default partition in that case.  Although, if the
+		 * original bound in question is an infinite value, there would not
+		 * be any unassigned range, because the range is unbounded in that
+		 * direction by definition.
+		 */
+		if (boundinfo->indexes[minoff] < 0)
+		{
+			int		last_key;
+
+			Assert(partkey->strategy == PARTITION_STRATEGY_RANGE);
+			last_key = keys->n_minkeys > 0 ? keys->n_minkeys - 1
+										   : partkey->partnatts - 1;
+			if (boundinfo->kind[minoff][last_key] == PARTITION_RANGE_DATUM_VALUE)
+				include_default = true;
+			minoff += 1;
+		}
+
+		if (maxoff >= 1 && boundinfo->indexes[maxoff] < 0)
+		{
+			int		last_key;
+
+			Assert(partkey->strategy == PARTITION_STRATEGY_RANGE);
+			last_key = keys->n_maxkeys > 0 ? keys->n_maxkeys - 1
+										   : partkey->partnatts - 1;
+			maxoff -= 1;
+			if (boundinfo->kind[maxoff][last_key] == PARTITION_RANGE_DATUM_VALUE)
+				include_default = true;
+		}
+
+		switch (partkey->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				/*
+				 * Add to the other_parts, list partition indexes are not
+				 * monotonously increasing like range partitions' are.
+				 */
+				for (i = minoff; i <= maxoff; i++)
+					partset->other_parts =
+								bms_add_member(partset->other_parts,
+											   boundinfo->indexes[i]);
+				/*
+				 * If minoff != maxoff, there might be datums in that range
+				 * range that don't have a non-default partition assigned.
+				 */
+				include_default = (minoff != maxoff);
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				partset->min_part_idx = boundinfo->indexes[minoff];
+				partset->max_part_idx = boundinfo->indexes[maxoff];
+				/*
+				 * There might exist a range of values unassigned to any
+				 * non-default range partition between the datums at
+				 * minoff and maxoff.
+				 */
+				for (i = minoff; i <= maxoff; i++)
+				{
+					if (boundinfo->indexes[i] < 0)
+					{
+						include_default = true;
+						break;
+					}
+				}
+				break;
+		}
+
+		if (include_default && partition_bound_has_default(boundinfo))
+			partset->other_parts = bms_add_member(partset->other_parts,
+												  boundinfo->default_index);
+	}
+	else
+		partset->empty = true;
+
+	return partset;
 }
 
 /*
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index c698faff2f..661f137122 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1713,11 +1713,9 @@ explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'
  Append
    ->  Seq Scan on part_ab_cd
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-   ->  Seq Scan on part_ef_gh
-         Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
    ->  Seq Scan on part_null_xy
          Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from list_parted where a = 'ab';
                 QUERY PLAN                
diff --git a/src/test/regress/expected/partition.out b/src/test/regress/expected/partition.out
index 61c4596bc7..69a7819171 100644
--- a/src/test/regress/expected/partition.out
+++ b/src/test/regress/expected/partition.out
@@ -198,16 +198,14 @@ explain (costs off) select * from rlp where 1 > a;	/* commutates */
 (3 rows)
 
 explain (costs off) select * from rlp where a <= 1;
-          QUERY PLAN           
--------------------------------
+        QUERY PLAN        
+--------------------------
  Append
    ->  Seq Scan on rlp1
          Filter: (a <= 1)
    ->  Seq Scan on rlp2
          Filter: (a <= 1)
-   ->  Seq Scan on rlp_default
-         Filter: (a <= 1)
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from rlp where a = 1;
        QUERY PLAN        
@@ -453,15 +451,13 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a =
                             QUERY PLAN                             
 -------------------------------------------------------------------
  Append
-   ->  Seq Scan on rlp2
-         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3abcd
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3efgh
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
    ->  Seq Scan on rlp3nullxy
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
-(9 rows)
+(7 rows)
 
 -- multi-column keys
 create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
@@ -475,16 +471,14 @@ create table mc3p5 partition of mc3p for values from (11, 1, 1) to (20, 10, 10);
 create table mc3p6 partition of mc3p for values from (20, 10, 10) to (20, 20, 20);
 create table mc3p7 partition of mc3p for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue);
 explain (costs off) select * from mc3p where a = 1;
-           QUERY PLAN           
---------------------------------
+       QUERY PLAN        
+-------------------------
  Append
    ->  Seq Scan on mc3p0
          Filter: (a = 1)
    ->  Seq Scan on mc3p1
          Filter: (a = 1)
-   ->  Seq Scan on mc3p_default
-         Filter: (a = 1)
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from mc3p where a = 1 and abs(b) < 1;
                  QUERY PLAN                 
@@ -502,9 +496,7 @@ explain (costs off) select * from mc3p where a = 1 and abs(b) = 1;
          Filter: ((a = 1) AND (abs(b) = 1))
    ->  Seq Scan on mc3p1
          Filter: ((a = 1) AND (abs(b) = 1))
-   ->  Seq Scan on mc3p_default
-         Filter: ((a = 1) AND (abs(b) = 1))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8;
                        QUERY PLAN                       
@@ -514,9 +506,7 @@ explain (costs off) select * from mc3p where a = 1 and abs(b) = 1 and c < 8;
          Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
    ->  Seq Scan on mc3p1
          Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
-   ->  Seq Scan on mc3p_default
-         Filter: ((c < 8) AND (a = 1) AND (abs(b) = 1))
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35;
                            QUERY PLAN                            
@@ -530,9 +520,7 @@ explain (costs off) select * from mc3p where a = 10 and abs(b) between 5 and 35;
          Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
    ->  Seq Scan on mc3p4
          Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
-   ->  Seq Scan on mc3p_default
-         Filter: ((a = 10) AND (abs(b) >= 5) AND (abs(b) <= 35))
-(11 rows)
+(9 rows)
 
 explain (costs off) select * from mc3p where a > 10;
            QUERY PLAN           
@@ -571,16 +559,14 @@ explain (costs off) select * from mc3p where a >= 10;
 (17 rows)
 
 explain (costs off) select * from mc3p where a < 10;
-           QUERY PLAN           
---------------------------------
+        QUERY PLAN        
+--------------------------
  Append
    ->  Seq Scan on mc3p0
          Filter: (a < 10)
    ->  Seq Scan on mc3p1
          Filter: (a < 10)
-   ->  Seq Scan on mc3p_default
-         Filter: (a < 10)
-(7 rows)
+(5 rows)
 
 explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10;
                   QUERY PLAN                   
@@ -592,9 +578,7 @@ explain (costs off) select * from mc3p where a <= 10 and abs(b) < 10;
          Filter: ((a <= 10) AND (abs(b) < 10))
    ->  Seq Scan on mc3p2
          Filter: ((a <= 10) AND (abs(b) < 10))
-   ->  Seq Scan on mc3p_default
-         Filter: ((a <= 10) AND (abs(b) < 10))
-(9 rows)
+(7 rows)
 
 explain (costs off) select * from mc3p where a = 11 and abs(b) = 0;	/* empty */
                  QUERY PLAN                  
@@ -621,8 +605,8 @@ explain (costs off) select * from mc3p where a > 20;
 (3 rows)
 
 explain (costs off) select * from mc3p where a >= 20;
-           QUERY PLAN           
---------------------------------
+        QUERY PLAN         
+---------------------------
  Append
    ->  Seq Scan on mc3p5
          Filter: (a >= 20)
@@ -630,9 +614,7 @@ explain (costs off) select * from mc3p where a >= 20;
          Filter: (a >= 20)
    ->  Seq Scan on mc3p7
          Filter: (a >= 20)
-   ->  Seq Scan on mc3p_default
-         Filter: (a >= 20)
-(9 rows)
+(7 rows)
 
 explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or (a = 10 and abs(b) = 5 and c = 10) or (a > 11 and a < 20);
                                                            QUERY PLAN                                                            
@@ -672,9 +654,7 @@ explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1 and c = 1) or
          Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1))
    ->  Seq Scan on mc3p5
          Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1))
-   ->  Seq Scan on mc3p_default
-         Filter: (((a = 1) AND (abs(b) = 1) AND (c = 1)) OR ((a = 10) AND (abs(b) = 5) AND (c = 10)) OR ((a > 11) AND (a < 20)) OR (a < 1) OR (a = 1))
-(11 rows)
+(9 rows)
 
 explain (costs off) select * from mc3p where a = 1 or abs(b) = 1 or c = 1;
                       QUERY PLAN                      
@@ -712,9 +692,7 @@ explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 a
          Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
    ->  Seq Scan on mc3p4
          Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
-   ->  Seq Scan on mc3p_default
-         Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 10)))
-(13 rows)
+(11 rows)
 
 explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 and abs(b) = 9);
                                  QUERY PLAN                                  
@@ -726,8 +704,6 @@ explain (costs off) select * from mc3p where (a = 1 and abs(b) = 1) or (a = 10 a
          Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
    ->  Seq Scan on mc3p2
          Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
-   ->  Seq Scan on mc3p_default
-         Filter: (((a = 1) AND (abs(b) = 1)) OR ((a = 10) AND (abs(b) = 9)))
-(9 rows)
+(7 rows)
 
 drop table lp, coll_pruning, rlp, mc3p;
-- 
2.11.0