Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
-> Memoize
Cache Key: t1.c2
+ Cache Mode: binary
-> Subquery Scan on q
-> HashAggregate
Output: t2.c1, t3.c1
Output: t2.c1, t3.c1
Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3)
Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer))))
-(16 rows)
+(17 rows)
SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10;
C 1
if (es->format != EXPLAIN_FORMAT_TEXT)
{
ExplainPropertyText("Cache Key", keystr.data, es);
+ ExplainPropertyText("Cache Mode", mstate->binary_mode ? "binary" : "logical", es);
}
else
{
ExplainIndentText(es);
appendStringInfo(es->str, "Cache Key: %s\n", keystr.data);
+ ExplainIndentText(es);
+ appendStringInfo(es->str, "Cache Mode: %s\n", mstate->binary_mode ? "binary" : "logical");
}
pfree(keystr.data);
#include "executor/nodeMemoize.h"
#include "lib/ilist.h"
#include "miscadmin.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
/* States of the ExecMemoize state machine */
static uint32 MemoizeHash_hash(struct memoize_hash *tb,
const MemoizeKey *key);
-static int MemoizeHash_equal(struct memoize_hash *tb,
+static bool MemoizeHash_equal(struct memoize_hash *tb,
const MemoizeKey *params1,
const MemoizeKey *params2);
#define SH_KEY_TYPE MemoizeKey *
#define SH_KEY key
#define SH_HASH_KEY(tb, key) MemoizeHash_hash(tb, key)
-#define SH_EQUAL(tb, a, b) (MemoizeHash_equal(tb, a, b) == 0)
+#define SH_EQUAL(tb, a, b) MemoizeHash_equal(tb, a, b)
#define SH_SCOPE static inline
#define SH_STORE_HASH
#define SH_GET_HASH(tb, a) a->hash
TupleTableSlot *pslot = mstate->probeslot;
uint32 hashkey = 0;
int numkeys = mstate->nkeys;
- FmgrInfo *hashfunctions = mstate->hashfunctions;
- Oid *collations = mstate->collations;
- for (int i = 0; i < numkeys; i++)
+ if (mstate->binary_mode)
{
- /* rotate hashkey left 1 bit at each step */
- hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
+ for (int i = 0; i < numkeys; i++)
+ {
+ /* rotate hashkey left 1 bit at each step */
+ hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
+
+ if (!pslot->tts_isnull[i]) /* treat nulls as having hash key 0 */
+ {
+ FormData_pg_attribute *attr;
+ uint32 hkey;
+
+ attr = &pslot->tts_tupleDescriptor->attrs[i];
+
+ hkey = datum_image_hash(pslot->tts_values[i], attr->attbyval, attr->attlen);
+
+ hashkey ^= hkey;
+ }
+ }
+ }
+ else
+ {
+ FmgrInfo *hashfunctions = mstate->hashfunctions;
+ Oid *collations = mstate->collations;
- if (!pslot->tts_isnull[i]) /* treat nulls as having hash key 0 */
+ for (int i = 0; i < numkeys; i++)
{
- uint32 hkey;
+ /* rotate hashkey left 1 bit at each step */
+ hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
+
+ if (!pslot->tts_isnull[i]) /* treat nulls as having hash key 0 */
+ {
+ uint32 hkey;
- hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i],
- collations[i], pslot->tts_values[i]));
- hashkey ^= hkey;
+ hkey = DatumGetUInt32(FunctionCall1Coll(&hashfunctions[i],
+ collations[i], pslot->tts_values[i]));
+ hashkey ^= hkey;
+ }
}
}
* table lookup. 'key2' is never used. Instead the MemoizeState's
* probeslot is always populated with details of what's being looked up.
*/
-static int
+static bool
MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
const MemoizeKey *key2)
{
/* probeslot should have already been prepared by prepare_probe_slot() */
ExecStoreMinimalTuple(key1->params, tslot, false);
- econtext->ecxt_innertuple = tslot;
- econtext->ecxt_outertuple = pslot;
- return !ExecQualAndReset(mstate->cache_eq_expr, econtext);
+ if (mstate->binary_mode)
+ {
+ int numkeys = mstate->nkeys;
+
+ slot_getallattrs(tslot);
+ slot_getallattrs(pslot);
+
+ for (int i = 0; i < numkeys; i++)
+ {
+ FormData_pg_attribute *attr;
+
+ if (tslot->tts_isnull[i] != pslot->tts_isnull[i])
+ return false;
+
+ /* both NULL? they're equal */
+ if (tslot->tts_isnull[i])
+ continue;
+
+ /* perform binary comparison on the two datums */
+ attr = &tslot->tts_tupleDescriptor->attrs[i];
+ if (!datum_image_eq(tslot->tts_values[i], pslot->tts_values[i],
+ attr->attbyval, attr->attlen))
+ return false;
+ }
+ return true;
+ }
+ else
+ {
+ econtext->ecxt_innertuple = tslot;
+ econtext->ecxt_outertuple = pslot;
+ return ExecQualAndReset(mstate->cache_eq_expr, econtext);
+ }
}
/*
*/
mstate->singlerow = node->singlerow;
+ /*
+ * Record if the cache keys should be compared bit by bit, or logically
+ * using the type's hash equality operator
+ */
+ mstate->binary_mode = node->binary_mode;
+
/* Zero the statistics counters */
memset(&mstate->stats, 0, sizeof(MemoizeInstrumentation));
COPY_POINTER_FIELD(collations, sizeof(Oid) * from->numKeys);
COPY_NODE_FIELD(param_exprs);
COPY_SCALAR_FIELD(singlerow);
+ COPY_SCALAR_FIELD(binary_mode);
COPY_SCALAR_FIELD(est_entries);
return newnode;
WRITE_OID_ARRAY(collations, node->numKeys);
WRITE_NODE_FIELD(param_exprs);
WRITE_BOOL_FIELD(singlerow);
+ WRITE_BOOL_FIELD(binary_mode);
WRITE_UINT_FIELD(est_entries);
}
WRITE_NODE_FIELD(hash_operators);
WRITE_NODE_FIELD(param_exprs);
WRITE_BOOL_FIELD(singlerow);
+ WRITE_BOOL_FIELD(binary_mode);
WRITE_FLOAT_FIELD(calls, "%.0f");
WRITE_UINT_FIELD(est_entries);
}
READ_OID_ARRAY(collations, local_node->numKeys);
READ_NODE_FIELD(param_exprs);
READ_BOOL_FIELD(singlerow);
+ READ_BOOL_FIELD(binary_mode);
READ_UINT_FIELD(est_entries);
READ_DONE();
* Returns true the hashing is possible, otherwise return false.
*
* Additionally we also collect the outer exprs and the hash operators for
- * each parameter to innerrel. These set in 'param_exprs' and 'operators'
- * when we return true.
+ * each parameter to innerrel. These set in 'param_exprs', 'operators' and
+ * 'binary_mode' when we return true.
*/
static bool
paraminfo_get_equal_hashops(PlannerInfo *root, ParamPathInfo *param_info,
RelOptInfo *outerrel, RelOptInfo *innerrel,
- List **param_exprs, List **operators)
+ List **param_exprs, List **operators,
+ bool *binary_mode)
{
ListCell *lc;
*param_exprs = NIL;
*operators = NIL;
+ *binary_mode = false;
if (param_info != NULL)
{
*operators = lappend_oid(*operators, hasheqoperator);
*param_exprs = lappend(*param_exprs, expr);
+
+ /*
+ * When the join operator is not hashable then it's possible that
+ * the operator will be able to distinguish something that the
+ * hash equality operator could not. For example with floating
+ * point types -0.0 and +0.0 are classed as equal by the hash
+ * function and equality function, but some other operator may be
+ * able to tell those values apart. This means that we must put
+ * memoize into binary comparison mode so that it does bit-by-bit
+ * comparisons rather than a "logical" comparison as it would
+ * using the hash equality operator.
+ */
+ if (!OidIsValid(rinfo->hashjoinoperator))
+ *binary_mode = true;
}
}
*operators = lappend_oid(*operators, typentry->eq_opr);
*param_exprs = lappend(*param_exprs, expr);
+
+ /*
+ * We must go into binary mode as we don't have too much of an idea of
+ * how these lateral Vars are being used. See comment above when we
+ * set *binary_mode for the non-lateral Var case. This could be
+ * relaxed a bit if we had the RestrictInfos and knew the operators
+ * being used, however for cases like Vars that are arguments to
+ * functions we must operate in binary mode as we don't have
+ * visibility into what the function is doing with the Vars.
+ */
+ *binary_mode = true;
}
/* We're okay to use memoize */
List *param_exprs;
List *hash_operators;
ListCell *lc;
+ bool binary_mode;
/* Obviously not if it's disabled */
if (!enable_memoize)
outerrel,
innerrel,
¶m_exprs,
- &hash_operators))
+ &hash_operators,
+ &binary_mode))
{
return (Path *) create_memoize_path(root,
innerrel,
param_exprs,
hash_operators,
extra->inner_unique,
+ binary_mode,
outer_path->parent->rows);
}
static Material *make_material(Plan *lefttree);
static Memoize *make_memoize(Plan *lefttree, Oid *hashoperators,
Oid *collations, List *param_exprs,
- bool singlerow, uint32 est_entries);
+ bool singlerow, bool binary_mode,
+ uint32 est_entries);
static WindowAgg *make_windowagg(List *tlist, Index winref,
int partNumCols, AttrNumber *partColIdx, Oid *partOperators, Oid *partCollations,
int ordNumCols, AttrNumber *ordColIdx, Oid *ordOperators, Oid *ordCollations,
}
plan = make_memoize(subplan, operators, collations, param_exprs,
- best_path->singlerow, best_path->est_entries);
+ best_path->singlerow, best_path->binary_mode,
+ best_path->est_entries);
copy_generic_path_info(&plan->plan, (Path *) best_path);
static Memoize *
make_memoize(Plan *lefttree, Oid *hashoperators, Oid *collations,
- List *param_exprs, bool singlerow, uint32 est_entries)
+ List *param_exprs, bool singlerow, bool binary_mode,
+ uint32 est_entries)
{
Memoize *node = makeNode(Memoize);
Plan *plan = &node->plan;
node->collations = collations;
node->param_exprs = param_exprs;
node->singlerow = singlerow;
+ node->binary_mode = binary_mode;
node->est_entries = est_entries;
return node;
MemoizePath *
create_memoize_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
List *param_exprs, List *hash_operators,
- bool singlerow, double calls)
+ bool singlerow, bool binary_mode, double calls)
{
MemoizePath *pathnode = makeNode(MemoizePath);
pathnode->hash_operators = hash_operators;
pathnode->param_exprs = param_exprs;
pathnode->singlerow = singlerow;
+ pathnode->binary_mode = binary_mode;
pathnode->calls = calls;
/*
mpath->param_exprs,
mpath->hash_operators,
mpath->singlerow,
+ mpath->binary_mode,
mpath->calls);
}
default:
#include "postgres.h"
#include "access/detoast.h"
+#include "common/hashfn.h"
#include "fmgr.h"
#include "utils/builtins.h"
#include "utils/datum.h"
return result;
}
+/*-------------------------------------------------------------------------
+ * datum_image_hash
+ *
+ * Generate a hash value based on the binary representation of 'value'. Most
+ * use cases will want to use the hash function specific to the Datum's type,
+ * however, some corner cases require generating a hash value based on the
+ * actual bits rather than the logical value.
+ *-------------------------------------------------------------------------
+ */
+uint32
+datum_image_hash(Datum value, bool typByVal, int typLen)
+{
+ Size len;
+ uint32 result;
+
+ if (typByVal)
+ result = hash_bytes((unsigned char *) &value, sizeof(Datum));
+ else if (typLen > 0)
+ result = hash_bytes((unsigned char *) DatumGetPointer(value), typLen);
+ else if (typLen == -1)
+ {
+ struct varlena *val;
+
+ len = toast_raw_datum_size(value);
+
+ val = PG_DETOAST_DATUM_PACKED(value);
+
+ result = hash_bytes((unsigned char *) VARDATA_ANY(val), len - VARHDRSZ);
+
+ /* Only free memory if it's a copy made here. */
+ if ((Pointer) val != (Pointer) value)
+ pfree(val);
+ }
+ else if (typLen == -2)
+ {
+ char *s;
+
+ s = DatumGetCString(value);
+ len = strlen(s) + 1;
+
+ result = hash_bytes((unsigned char *) s, len);
+ }
+ else
+ {
+ elog(ERROR, "unexpected typLen: %d", typLen);
+ result = 0; /* keep compiler quiet */
+ }
+
+ return result;
+}
+
/*-------------------------------------------------------------------------
* btequalimage
*
* NULL if 'last_tuple' is NULL. */
bool singlerow; /* true if the cache entry is to be marked as
* complete after caching the first tuple. */
+ bool binary_mode; /* true when cache key should be compared bit
+ * by bit, false when using hash equality ops */
MemoizeInstrumentation stats; /* execution statistics */
SharedMemoizeInfo *shared_info; /* statistics for parallel workers */
} MemoizeState;
List *param_exprs; /* cache keys */
bool singlerow; /* true if the cache entry is to be marked as
* complete after caching the first record. */
+ bool binary_mode; /* true when cache key should be compared bit
+ * by bit, false when using hash equality ops */
Cardinality calls; /* expected number of rescans */
uint32 est_entries; /* The maximum number of entries that the
* planner expects will fit in the cache, or 0
bool singlerow; /* true if the cache entry should be marked as
* complete after we store the first tuple in
* it. */
+ bool binary_mode; /* true when cache key should be compared bit
+ * by bit, false when using hash equality ops */
uint32 est_entries; /* The maximum number of entries that the
* planner expects will fit in the cache, or 0
* if unknown */
List *param_exprs,
List *hash_operators,
bool singlerow,
+ bool binary_mode,
double calls);
extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, SpecialJoinInfo *sjinfo);
extern bool datum_image_eq(Datum value1, Datum value2,
bool typByVal, int typLen);
+/*
+ * datum_image_hash
+ *
+ * Generates hash value for 'value' based on its bits rather than logical
+ * value.
+ */
+extern uint32 datum_image_hash(Datum value, bool typByVal, int typLen);
+
/*
* Serialize and restore datums so that we can transfer them to parallel
* workers.
Index Cond: (hundred = t1.hundred)
-> Memoize
Cache Key: t2.thousand
+ Cache Mode: logical
-> Index Scan using tenk1_unique2 on tenk1 t3
Index Cond: (unique2 = t2.thousand)
-(13 rows)
+(14 rows)
explain (costs off)
select * from tenk1 t1 left join
Index Cond: (hundred = t1.hundred)
-> Memoize
Cache Key: t2.thousand
+ Cache Mode: logical
-> Index Scan using tenk1_unique2 on tenk1 t3
Index Cond: (unique2 = t2.thousand)
-(13 rows)
+(14 rows)
explain (costs off)
select count(*) from
-> Memoize
Output: (i8.q1), t2.f1
Cache Key: i8.q1
+ Cache Mode: binary
-> Limit
Output: (i8.q1), t2.f1
-> Seq Scan on public.text_tbl t2
Output: i8.q1, t2.f1
-(19 rows)
+(20 rows)
select * from
text_tbl t1
-> Memoize
Output: (i8.q1), t2.f1
Cache Key: i8.q1
+ Cache Mode: binary
-> Limit
Output: (i8.q1), t2.f1
-> Seq Scan on public.text_tbl t2
-> Memoize
Output: ((i8.q1)), (t2.f1)
Cache Key: (i8.q1), t2.f1
+ Cache Mode: binary
-> Limit
Output: ((i8.q1)), (t2.f1)
-> Seq Scan on public.text_tbl t3
Output: (i8.q1), t2.f1
-(28 rows)
+(30 rows)
select * from
text_tbl t1
-> Memoize
Output: ss1.c0
Cache Key: tt4.f1
+ Cache Mode: binary
-> Subquery Scan on ss1
Output: ss1.c0
Filter: (ss1.c0 = 'foo'::text)
Output: (tt4.f1)
-> Seq Scan on public.text_tbl tt5
Output: tt4.f1
-(32 rows)
+(33 rows)
select 1 from
text_tbl as tt1
-> Seq Scan on tenk1 a
-> Memoize
Cache Key: a.two
+ Cache Mode: binary
-> Function Scan on generate_series g
-(6 rows)
+(7 rows)
explain (costs off)
select count(*) from tenk1 a cross join lateral generate_series(1,two) g;
-> Seq Scan on tenk1 a
-> Memoize
Cache Key: a.two
+ Cache Mode: binary
-> Function Scan on generate_series g
-(6 rows)
+(7 rows)
-- don't need the explicit LATERAL keyword for functions
explain (costs off)
-> Seq Scan on tenk1 a
-> Memoize
Cache Key: a.two
+ Cache Mode: binary
-> Function Scan on generate_series g
-(6 rows)
+(7 rows)
-- lateral with UNION ALL subselect
explain (costs off)
-> Values Scan on "*VALUES*"
-> Memoize
Cache Key: "*VALUES*".column1
+ Cache Mode: logical
-> Index Only Scan using tenk1_unique2 on tenk1 b
Index Cond: (unique2 = "*VALUES*".column1)
-(9 rows)
+(10 rows)
select count(*) from tenk1 a,
tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x;
Rows Removed by Filter: 9000
-> Memoize (actual rows=1 loops=N)
Cache Key: t2.twenty
+ Cache Mode: logical
Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
-> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
Index Cond: (unique1 = t2.twenty)
Heap Fetches: N
-(11 rows)
+(12 rows)
-- And check we get the expected results.
SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
Rows Removed by Filter: 9000
-> Memoize (actual rows=1 loops=N)
Cache Key: t1.twenty
+ Cache Mode: logical
Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
-> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N)
Index Cond: (unique1 = t1.twenty)
Heap Fetches: N
-(11 rows)
+(12 rows)
-- And check we get the expected results.
SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
Rows Removed by Filter: 8800
-> Memoize (actual rows=1 loops=N)
Cache Key: t2.thousand
+ Cache Mode: logical
Hits: N Misses: N Evictions: N Overflows: 0 Memory Usage: NkB
-> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
Index Cond: (unique1 = t2.thousand)
Heap Fetches: N
-(11 rows)
+(12 rows)
+CREATE TABLE flt (f float);
+CREATE INDEX flt_f_idx ON flt (f);
+INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float);
+ANALYZE flt;
+SET enable_seqscan TO off;
+-- Ensure memoize operates in logical mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
+ explain_memoize
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=4 loops=N)
+ -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
+ Heap Fetches: N
+ -> Memoize (actual rows=2 loops=N)
+ Cache Key: f1.f
+ Cache Mode: logical
+ Hits: 1 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
+ Index Cond: (f = f1.f)
+ Heap Fetches: N
+(10 rows)
+
+-- Ensure memoize operates in binary mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
+ explain_memoize
+-------------------------------------------------------------------------------
+ Nested Loop (actual rows=4 loops=N)
+ -> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
+ Heap Fetches: N
+ -> Memoize (actual rows=2 loops=N)
+ Cache Key: f1.f
+ Cache Mode: binary
+ Hits: 0 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
+ Index Cond: (f <= f1.f)
+ Heap Fetches: N
+(10 rows)
+
+DROP TABLE flt;
+-- Exercise Memoize in binary mode with a large fixed width type and a
+-- varlena type.
+CREATE TABLE strtest (n name, t text);
+CREATE INDEX strtest_n_idx ON strtest (n);
+CREATE INDEX strtest_t_idx ON strtest (t);
+INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(md5('three'),100));
+-- duplicate rows so we get some cache hits
+INSERT INTO strtest SELECT * FROM strtest;
+ANALYZE strtest;
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
+ explain_memoize
+----------------------------------------------------------------------------------
+ Nested Loop (actual rows=24 loops=N)
+ -> Seq Scan on strtest s1 (actual rows=6 loops=N)
+ -> Memoize (actual rows=4 loops=N)
+ Cache Key: s1.n
+ Cache Mode: binary
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N)
+ Index Cond: (n <= s1.n)
+(8 rows)
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
+ explain_memoize
+----------------------------------------------------------------------------------
+ Nested Loop (actual rows=24 loops=N)
+ -> Seq Scan on strtest s1 (actual rows=6 loops=N)
+ -> Memoize (actual rows=4 loops=N)
+ Cache Key: s1.t
+ Cache Mode: binary
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ -> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N)
+ Index Cond: (t <= s1.t)
+(8 rows)
+
+DROP TABLE strtest;
+RESET enable_seqscan;
RESET enable_mergejoin;
RESET work_mem;
RESET enable_bitmapscan;
Index Cond: (unique1 < 1000)
-> Memoize
Cache Key: t1.twenty
+ Cache Mode: logical
-> Index Only Scan using tenk1_unique1 on tenk1 t2
Index Cond: (unique1 = t1.twenty)
-(13 rows)
+(14 rows)
-- And ensure the parallel plan gives us the correct results.
SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
Filter: (ten = 1)
-> Memoize
Cache Key: o.four
+ Cache Mode: binary
-> CTE Scan on x
CTE x
-> Recursive Union
-> Result
-> WorkTable Scan on x x_1
Filter: (a < 10)
-(12 rows)
+(13 rows)
select sum(o.four), sum(ss.a) from
onek o cross join lateral (
SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
INNER JOIN tenk1 t2 ON t1.unique1 = t2.thousand
WHERE t2.unique1 < 1200;', true);
+
+CREATE TABLE flt (f float);
+CREATE INDEX flt_f_idx ON flt (f);
+INSERT INTO flt VALUES('-0.0'::float),('+0.0'::float);
+ANALYZE flt;
+
+SET enable_seqscan TO off;
+
+-- Ensure memoize operates in logical mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
+
+-- Ensure memoize operates in binary mode
+SELECT explain_memoize('
+SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
+
+DROP TABLE flt;
+
+-- Exercise Memoize in binary mode with a large fixed width type and a
+-- varlena type.
+CREATE TABLE strtest (n name, t text);
+CREATE INDEX strtest_n_idx ON strtest (n);
+CREATE INDEX strtest_t_idx ON strtest (t);
+INSERT INTO strtest VALUES('one','one'),('two','two'),('three',repeat(md5('three'),100));
+-- duplicate rows so we get some cache hits
+INSERT INTO strtest SELECT * FROM strtest;
+ANALYZE strtest;
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
+
+-- Ensure we get 3 hits and 3 misses
+SELECT explain_memoize('
+SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
+
+DROP TABLE strtest;
+
+RESET enable_seqscan;
RESET enable_mergejoin;
RESET work_mem;
RESET enable_bitmapscan;