@@ -102,6 +102,7 @@ PG_FUNCTION_INFO_V1(aqo_queries_update);
102102PG_FUNCTION_INFO_V1 (aqo_reset );
103103PG_FUNCTION_INFO_V1 (aqo_cleanup );
104104PG_FUNCTION_INFO_V1 (aqo_drop_class );
105+ PG_FUNCTION_INFO_V1 (aqo_cardinality_error );
105106
106107
107108bool
@@ -2203,3 +2204,95 @@ aqo_drop_class(PG_FUNCTION_ARGS)
22032204
22042205PG_RETURN_INT32 (cnt );
22052206}
2207+
2208+ typedef enum {
2209+ AQE_NN = 0 ,AQE_QUERYID ,AQE_FS ,AQE_CERROR ,AQE_NEXECS ,AQE_TOTAL_NCOLS
2210+ }ce_output_order ;
2211+
2212+ /*
2213+ * Show cardinality error gathered on last execution.
2214+ * Skip entries with empty stat slots. XXX: is it possible?
2215+ */
2216+ Datum
2217+ aqo_cardinality_error (PG_FUNCTION_ARGS )
2218+ {
2219+ bool controlled = PG_GETARG_BOOL (0 );
2220+ ReturnSetInfo * rsinfo = (ReturnSetInfo * )fcinfo -> resultinfo ;
2221+ TupleDesc tupDesc ;
2222+ MemoryContext per_query_ctx ;
2223+ MemoryContext oldcontext ;
2224+ Tuplestorestate * tupstore ;
2225+ Datum values [AQE_TOTAL_NCOLS ];
2226+ bool nulls [AQE_TOTAL_NCOLS ];
2227+ HASH_SEQ_STATUS hash_seq ;
2228+ QueriesEntry * qentry ;
2229+ StatEntry * sentry ;
2230+ int counter = 0 ;
2231+
2232+ /* check to see if caller supports us returning a tuplestore */
2233+ if (rsinfo == NULL || !IsA (rsinfo ,ReturnSetInfo ))
2234+ ereport (ERROR ,
2235+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
2236+ errmsg ("set-valued function called in context that cannot accept a set" )));
2237+ if (!(rsinfo -> allowedModes & SFRM_Materialize ))
2238+ ereport (ERROR ,
2239+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
2240+ errmsg ("materialize mode required, but it is not allowed in this context" )));
2241+
2242+ /* Switch into long-lived context to construct returned data structures */
2243+ per_query_ctx = rsinfo -> econtext -> ecxt_per_query_memory ;
2244+ oldcontext = MemoryContextSwitchTo (per_query_ctx );
2245+
2246+ /* Build a tuple descriptor for our result type */
2247+ if (get_call_result_type (fcinfo ,NULL ,& tupDesc )!= TYPEFUNC_COMPOSITE )
2248+ elog (ERROR ,"return type must be a row type" );
2249+ Assert (tupDesc -> natts == AQE_TOTAL_NCOLS );
2250+
2251+ tupstore = tuplestore_begin_heap (true, false,work_mem );
2252+ rsinfo -> returnMode = SFRM_Materialize ;
2253+ rsinfo -> setResult = tupstore ;
2254+ rsinfo -> setDesc = tupDesc ;
2255+
2256+ MemoryContextSwitchTo (oldcontext );
2257+
2258+ LWLockAcquire (& aqo_state -> queries_lock ,LW_SHARED );
2259+ LWLockAcquire (& aqo_state -> stat_lock ,LW_SHARED );
2260+
2261+ hash_seq_init (& hash_seq ,queries_htab );
2262+ while ((qentry = hash_seq_search (& hash_seq ))!= NULL )
2263+ {
2264+ bool found ;
2265+ double * ce ;
2266+ int64 nexecs ;
2267+ int nvals ;
2268+
2269+ memset (nulls ,0 ,AQE_TOTAL_NCOLS * sizeof (nulls [0 ]));
2270+
2271+ sentry = (StatEntry * )hash_search (stat_htab ,& qentry -> queryid ,
2272+ HASH_FIND ,& found );
2273+ if (!found )
2274+ /* Statistics not found by some reason. Just go further */
2275+ continue ;
2276+
2277+ nvals = controlled ?sentry -> cur_stat_slot_aqo :sentry -> cur_stat_slot ;
2278+ if (nvals == 0 )
2279+ /* No one stat slot filled */
2280+ continue ;
2281+
2282+ nexecs = controlled ?sentry -> execs_with_aqo :sentry -> execs_without_aqo ;
2283+ ce = controlled ?sentry -> est_error_aqo :sentry -> est_error ;
2284+
2285+ values [AQE_NN ]= Int32GetDatum (counter ++ );
2286+ values [AQE_QUERYID ]= Int64GetDatum (qentry -> queryid );
2287+ values [AQE_FS ]= Int64GetDatum (qentry -> fs );
2288+ values [AQE_NEXECS ]= Int64GetDatum (nexecs );
2289+ values [AQE_CERROR ]= Float8GetDatum (ce [nvals - 1 ]);
2290+ tuplestore_putvalues (tupstore ,tupDesc ,values ,nulls );
2291+ }
2292+
2293+ LWLockRelease (& aqo_state -> stat_lock );
2294+ LWLockRelease (& aqo_state -> queries_lock );
2295+
2296+ tuplestore_donestoring (tupstore );
2297+ return (Datum )0 ;
2298+ }