mirror of https://github.com/citusdata/citus.git
Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
|
4873d771e8 | |
|
e1a641fe9b | |
|
6b5baf21fb | |
|
8c0274cba6 | |
|
907048ace8 | |
|
d8bb32bd5a | |
|
0012d70b1b | |
|
44ddef6fe8 | |
|
0ec41de26c | |
|
34c6bd4b44 | |
|
d607368a9e |
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -1,6 +1,74 @@
|
|||
### citus v7.0.0 (unreleased) ###
|
||||
### citus v7.0.1 (September 12, 2017) ###
|
||||
|
||||
* Replaces pg_dist_shard_placement metadata table with pg_dist_placement
|
||||
* Fixes a bug that could cause memory leaks in `INSERT ... SELECT` queries
|
||||
|
||||
* Fixes a bug that could cause incorrect execution of prepared statements
|
||||
|
||||
* Fixes a bug that could cause excessive memory usage during COPY
|
||||
|
||||
* Incorporates latest changes from core PostgreSQL code
|
||||
|
||||
### citus v7.0.0 (August 28, 2017) ###
|
||||
|
||||
* Adds support for PostgreSQL 10
|
||||
|
||||
* Drops support for PostgreSQL 9.5
|
||||
|
||||
* Adds support for multi-row `INSERT`
|
||||
|
||||
* Adds support for router `UPDATE` and `DELETE` queries with subqueries
|
||||
|
||||
* Adds infrastructure for distributed deadlock detection
|
||||
|
||||
* Deprecates `enable_deadlock_prevention` flag
|
||||
|
||||
* Adds support for partitioned tables
|
||||
|
||||
* Adds support for creating `UNLOGGED` tables
|
||||
|
||||
* Adds support for `SAVEPOINT`
|
||||
|
||||
* Adds UDF `citus_create_restore_point` for taking distributed snapshots
|
||||
|
||||
* Adds support for evaluating non-pushable `INSERT ... SELECT` queries
|
||||
|
||||
* Adds support for subquery pushdown on reference tables
|
||||
|
||||
* Adds shard pruning support for `IN` and `ANY`
|
||||
|
||||
* Adds support for `UPDATE` and `DELETE` commands that prune down to 0 shard
|
||||
|
||||
* Enhances transaction support by relaxing some transaction restrictions
|
||||
|
||||
* Fixes a bug causing crash if distributed table has no shards
|
||||
|
||||
* Fixes a bug causing crash when removing inactive node
|
||||
|
||||
* Fixes a bug causing failure during `COPY` on tables with dropped columns
|
||||
|
||||
* Fixes a bug causing failure during `DROP EXTENSION`
|
||||
|
||||
* Fixes a bug preventing executing `VACUUM` and `INSERT` concurrently
|
||||
|
||||
* Fixes a bug in prepared `INSERT` statements containing an implicit cast
|
||||
|
||||
* Fixes several issues related to statement cancellations and connections
|
||||
|
||||
* Fixes several 2PC related issues
|
||||
|
||||
* Removes an unnecessary dependency causing warning messages in pg_dump
|
||||
|
||||
* Adds internal infrastructure for follower clusters
|
||||
|
||||
* Adds internal infrastructure for progress tracking
|
||||
|
||||
* Implements various performance improvements
|
||||
|
||||
* Adds internal infrastructures and tests to improve development process
|
||||
|
||||
* Addresses various race conditions and deadlocks
|
||||
|
||||
* Improves and standardizes error messages
|
||||
|
||||
### citus v6.2.3 (July 13, 2017) ###
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 7.0devel.
|
||||
# Generated by GNU Autoconf 2.69 for Citus 7.0.1.
|
||||
#
|
||||
#
|
||||
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
|
||||
|
@ -579,8 +579,8 @@ MAKEFLAGS=
|
|||
# Identity of this package.
|
||||
PACKAGE_NAME='Citus'
|
||||
PACKAGE_TARNAME='citus'
|
||||
PACKAGE_VERSION='7.0devel'
|
||||
PACKAGE_STRING='Citus 7.0devel'
|
||||
PACKAGE_VERSION='7.0.1'
|
||||
PACKAGE_STRING='Citus 7.0.1'
|
||||
PACKAGE_BUGREPORT=''
|
||||
PACKAGE_URL=''
|
||||
|
||||
|
@ -621,6 +621,7 @@ infodir
|
|||
docdir
|
||||
oldincludedir
|
||||
includedir
|
||||
runstatedir
|
||||
localstatedir
|
||||
sharedstatedir
|
||||
sysconfdir
|
||||
|
@ -693,6 +694,7 @@ datadir='${datarootdir}'
|
|||
sysconfdir='${prefix}/etc'
|
||||
sharedstatedir='${prefix}/com'
|
||||
localstatedir='${prefix}/var'
|
||||
runstatedir='${localstatedir}/run'
|
||||
includedir='${prefix}/include'
|
||||
oldincludedir='/usr/include'
|
||||
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
|
||||
|
@ -945,6 +947,15 @@ do
|
|||
| -silent | --silent | --silen | --sile | --sil)
|
||||
silent=yes ;;
|
||||
|
||||
-runstatedir | --runstatedir | --runstatedi | --runstated \
|
||||
| --runstate | --runstat | --runsta | --runst | --runs \
|
||||
| --run | --ru | --r)
|
||||
ac_prev=runstatedir ;;
|
||||
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
|
||||
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
|
||||
| --run=* | --ru=* | --r=*)
|
||||
runstatedir=$ac_optarg ;;
|
||||
|
||||
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
|
||||
ac_prev=sbindir ;;
|
||||
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
|
||||
|
@ -1082,7 +1093,7 @@ fi
|
|||
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
|
||||
datadir sysconfdir sharedstatedir localstatedir includedir \
|
||||
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
|
||||
libdir localedir mandir
|
||||
libdir localedir mandir runstatedir
|
||||
do
|
||||
eval ac_val=\$$ac_var
|
||||
# Remove trailing slashes.
|
||||
|
@ -1195,7 +1206,7 @@ if test "$ac_init_help" = "long"; then
|
|||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures Citus 7.0devel to adapt to many kinds of systems.
|
||||
\`configure' configures Citus 7.0.1 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
|
@ -1235,6 +1246,7 @@ Fine tuning of the installation directories:
|
|||
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
|
||||
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
|
||||
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
|
||||
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
|
||||
--libdir=DIR object code libraries [EPREFIX/lib]
|
||||
--includedir=DIR C header files [PREFIX/include]
|
||||
--oldincludedir=DIR C header files for non-gcc [/usr/include]
|
||||
|
@ -1256,7 +1268,7 @@ fi
|
|||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of Citus 7.0devel:";;
|
||||
short | recursive ) echo "Configuration of Citus 7.0.1:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
|
@ -1344,7 +1356,7 @@ fi
|
|||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
Citus configure 7.0devel
|
||||
Citus configure 7.0.1
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
|
@ -1401,7 +1413,7 @@ cat >config.log <<_ACEOF
|
|||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by Citus $as_me 7.0devel, which was
|
||||
It was created by Citus $as_me 7.0.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
|
@ -3564,7 +3576,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by Citus $as_me 7.0devel, which was
|
||||
This file was extended by Citus $as_me 7.0.1, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
|
@ -3626,7 +3638,7 @@ _ACEOF
|
|||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
Citus config.status 7.0devel
|
||||
Citus config.status 7.0.1
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# everyone needing autoconf installed, the resulting files are checked
|
||||
# into the SCM.
|
||||
|
||||
AC_INIT([Citus], [7.0devel])
|
||||
AC_INIT([Citus], [7.0.1])
|
||||
AC_COPYRIGHT([Copyright (c) 2012-2017, Citus Data, Inc.])
|
||||
|
||||
# we'll need sed and awk for some of the version commands
|
||||
|
|
|
@ -2010,6 +2010,14 @@ CitusCopyDestReceiverReceive(TupleTableSlot *slot, DestReceiver *dest)
|
|||
|
||||
copyDest->tuplesSent++;
|
||||
|
||||
/*
|
||||
* Release per tuple memory allocated in this function. If we're writing
|
||||
* the results of an INSERT ... SELECT then the SELECT execution will use
|
||||
* its own executor state and reset the per tuple expression context
|
||||
* separately.
|
||||
*/
|
||||
ResetPerTupleExprContext(executorState);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -497,6 +497,7 @@ PutRemoteCopyData(MultiConnection *connection, const char *buffer, int nbytes)
|
|||
{
|
||||
PGconn *pgConn = connection->pgConn;
|
||||
int copyState = 0;
|
||||
bool allowInterrupts = true;
|
||||
|
||||
if (PQstatus(pgConn) != CONNECTION_OK)
|
||||
{
|
||||
|
@ -506,21 +507,22 @@ PutRemoteCopyData(MultiConnection *connection, const char *buffer, int nbytes)
|
|||
Assert(PQisnonblocking(pgConn));
|
||||
|
||||
copyState = PQputCopyData(pgConn, buffer, nbytes);
|
||||
|
||||
if (copyState == 1)
|
||||
{
|
||||
/* successful */
|
||||
return true;
|
||||
}
|
||||
else if (copyState == -1)
|
||||
if (copyState == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool allowInterrupts = true;
|
||||
|
||||
/*
|
||||
* PQputCopyData may have queued up part of the data even if it managed
|
||||
* to send some of it succesfully. We provide back pressure by waiting
|
||||
* until the socket is writable to prevent the internal libpq buffers
|
||||
* from growing excessively.
|
||||
*
|
||||
* In the future, we could reduce the frequency of these pushbacks to
|
||||
* achieve higher throughput.
|
||||
*/
|
||||
|
||||
return FinishConnectionIO(connection, allowInterrupts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -535,6 +537,7 @@ PutRemoteCopyEnd(MultiConnection *connection, const char *errormsg)
|
|||
{
|
||||
PGconn *pgConn = connection->pgConn;
|
||||
int copyState = 0;
|
||||
bool allowInterrupts = true;
|
||||
|
||||
if (PQstatus(pgConn) != CONNECTION_OK)
|
||||
{
|
||||
|
@ -544,21 +547,14 @@ PutRemoteCopyEnd(MultiConnection *connection, const char *errormsg)
|
|||
Assert(PQisnonblocking(pgConn));
|
||||
|
||||
copyState = PQputCopyEnd(pgConn, errormsg);
|
||||
|
||||
if (copyState == 1)
|
||||
{
|
||||
/* successful */
|
||||
return true;
|
||||
}
|
||||
else if (copyState == -1)
|
||||
if (copyState == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool allowInterrupts = true;
|
||||
|
||||
/* see PutRemoteCopyData() */
|
||||
|
||||
return FinishConnectionIO(connection, allowInterrupts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3111,7 +3111,9 @@ InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId,
|
|||
*
|
||||
* This code is heavily borrowed from RangeVarCallbackForDropRelation() in
|
||||
* commands/tablecmds.c in Postgres source. We need this to ensure the right
|
||||
* order of locking while dealing with DROP INDEX statments.
|
||||
* order of locking while dealing with DROP INDEX statments. Because we are
|
||||
* exclusively using this callback for INDEX processing, the PARTITION-related
|
||||
* logic from PostgreSQL's similar callback has been omitted as unneeded.
|
||||
*/
|
||||
static void
|
||||
RangeVarCallbackForDropIndex(const RangeVar *rel, Oid relOid, Oid oldRelOid, void *arg)
|
||||
|
|
|
@ -467,7 +467,14 @@ GetMultiPlan(CustomScan *customScan)
|
|||
|
||||
node = CheckNodeCopyAndSerialization(node);
|
||||
|
||||
multiPlan = (MultiPlan *) node;
|
||||
/*
|
||||
* When using prepared statements the same plan gets reused across
|
||||
* multiple statements and transactions. We make several modifications
|
||||
* to the MultiPlan during execution such as assigning task placements
|
||||
* and evaluating functions and parameters. These changes should not
|
||||
* persist, so we always work on a copy.
|
||||
*/
|
||||
multiPlan = (MultiPlan *) copyObject(node);
|
||||
|
||||
return multiPlan;
|
||||
}
|
||||
|
|
|
@ -125,8 +125,8 @@ typedef struct
|
|||
bool varprefix; /* TRUE to print prefixes on Vars */
|
||||
Oid distrelid; /* the distributed table being modified, if valid */
|
||||
int64 shardid; /* a distributed table's shardid, if positive */
|
||||
ParseExprKind special_exprkind; /* set only for exprkinds needing
|
||||
* special handling */
|
||||
ParseExprKind special_exprkind; /* set only for exprkinds needing special
|
||||
* handling */
|
||||
} deparse_context;
|
||||
|
||||
/*
|
||||
|
@ -390,6 +390,9 @@ static void get_rule_expr(Node *node, deparse_context *context,
|
|||
bool showimplicit);
|
||||
static void get_rule_expr_toplevel(Node *node, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static void get_rule_expr_funccall(Node *node, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static bool looks_like_function(Node *node);
|
||||
static void get_oper_expr(OpExpr *expr, deparse_context *context);
|
||||
static void get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
bool showimplicit);
|
||||
|
@ -3299,8 +3302,11 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
|||
/*
|
||||
* We must dig down into the expr to see if it's a PARAM_MULTIEXPR
|
||||
* Param. That could be buried under FieldStores and ArrayRefs
|
||||
* (cf processIndirection()), and underneath those there could be
|
||||
* an implicit type coercion.
|
||||
* and CoerceToDomains (cf processIndirection()), and underneath
|
||||
* those there could be an implicit type coercion. Because we
|
||||
* would ignore implicit type coercions anyway, we don't need to
|
||||
* be as careful as processIndirection() is about descending past
|
||||
* implicit CoerceToDomains.
|
||||
*/
|
||||
expr = (Node *) tle->expr;
|
||||
while (expr)
|
||||
|
@ -3319,6 +3325,14 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
|||
break;
|
||||
expr = (Node *) aref->refassgnexpr;
|
||||
}
|
||||
else if (IsA(expr, CoerceToDomain))
|
||||
{
|
||||
CoerceToDomain *cdomain = (CoerceToDomain *) expr;
|
||||
|
||||
if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
|
||||
break;
|
||||
expr = (Node *) cdomain->arg;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
@ -4423,6 +4437,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
|
|||
case T_MinMaxExpr:
|
||||
case T_SQLValueFunction:
|
||||
case T_XmlExpr:
|
||||
case T_NextValueExpr:
|
||||
case T_NullIfExpr:
|
||||
case T_Aggref:
|
||||
case T_WindowFunc:
|
||||
|
@ -5752,6 +5767,22 @@ get_rule_expr(Node *node, deparse_context *context,
|
|||
}
|
||||
break;
|
||||
|
||||
case T_NextValueExpr:
|
||||
{
|
||||
NextValueExpr *nvexpr = (NextValueExpr *) node;
|
||||
|
||||
/*
|
||||
* This isn't exactly nextval(), but that seems close enough
|
||||
* for EXPLAIN's purposes.
|
||||
*/
|
||||
appendStringInfoString(buf, "nextval(");
|
||||
simple_quote_literal(buf,
|
||||
generate_relation_name(nvexpr->seqid,
|
||||
NIL));
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
break;
|
||||
|
||||
case T_InferenceElem:
|
||||
{
|
||||
InferenceElem *iexpr = (InferenceElem *) node;
|
||||
|
@ -5810,12 +5841,11 @@ get_rule_expr(Node *node, deparse_context *context,
|
|||
case PARTITION_STRATEGY_LIST:
|
||||
Assert(spec->listdatums != NIL);
|
||||
|
||||
appendStringInfoString(buf, "FOR VALUES");
|
||||
appendStringInfoString(buf, " IN (");
|
||||
appendStringInfoString(buf, "FOR VALUES IN (");
|
||||
sep = "";
|
||||
foreach(cell, spec->listdatums)
|
||||
{
|
||||
Const *val = lfirst(cell);
|
||||
Const *val = castNode(Const, lfirst(cell));
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
get_const_expr(val, context, -1);
|
||||
|
@ -5831,50 +5861,9 @@ get_rule_expr(Node *node, deparse_context *context,
|
|||
list_length(spec->lowerdatums) ==
|
||||
list_length(spec->upperdatums));
|
||||
|
||||
appendStringInfoString(buf, "FOR VALUES");
|
||||
appendStringInfoString(buf, " FROM");
|
||||
appendStringInfoString(buf, " (");
|
||||
sep = "";
|
||||
foreach(cell, spec->lowerdatums)
|
||||
{
|
||||
PartitionRangeDatum *datum = lfirst(cell);
|
||||
Const *val;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
|
||||
appendStringInfoString(buf, "MINVALUE");
|
||||
else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
|
||||
appendStringInfoString(buf, "MAXVALUE");
|
||||
else
|
||||
{
|
||||
val = (Const *) datum->value;
|
||||
get_const_expr(val, context, -1);
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
appendStringInfoString(buf, ")");
|
||||
|
||||
appendStringInfoString(buf, " TO");
|
||||
appendStringInfoString(buf, " (");
|
||||
sep = "";
|
||||
foreach(cell, spec->upperdatums)
|
||||
{
|
||||
PartitionRangeDatum *datum = lfirst(cell);
|
||||
Const *val;
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
|
||||
appendStringInfoString(buf, "MINVALUE");
|
||||
else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
|
||||
appendStringInfoString(buf, "MAXVALUE");
|
||||
else
|
||||
{
|
||||
val = (Const *) datum->value;
|
||||
get_const_expr(val, context, -1);
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
appendStringInfoString(buf, ")");
|
||||
appendStringInfo(buf, "FOR VALUES FROM %s TO %s",
|
||||
get_range_partbound_string(spec->lowerdatums),
|
||||
get_range_partbound_string(spec->upperdatums));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -5931,6 +5920,64 @@ get_rule_expr_toplevel(Node *node, deparse_context *context,
|
|||
get_rule_expr(node, context, showimplicit);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_rule_expr_funccall - Parse back a function-call expression
|
||||
*
|
||||
* Same as get_rule_expr(), except that we guarantee that the output will
|
||||
* look like a function call, or like one of the things the grammar treats as
|
||||
* equivalent to a function call (see the func_expr_windowless production).
|
||||
* This is needed in places where the grammar uses func_expr_windowless and
|
||||
* you can't substitute a parenthesized a_expr. If what we have isn't going
|
||||
* to look like a function call, wrap it in a dummy CAST() expression, which
|
||||
* will satisfy the grammar --- and, indeed, is likely what the user wrote to
|
||||
* produce such a thing.
|
||||
*/
|
||||
static void
|
||||
get_rule_expr_funccall(Node *node, deparse_context *context,
|
||||
bool showimplicit)
|
||||
{
|
||||
if (looks_like_function(node))
|
||||
get_rule_expr(node, context, showimplicit);
|
||||
else
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
|
||||
appendStringInfoString(buf, "CAST(");
|
||||
/* no point in showing any top-level implicit cast */
|
||||
get_rule_expr(node, context, false);
|
||||
appendStringInfo(buf, " AS %s)",
|
||||
format_type_with_typemod(exprType(node),
|
||||
exprTypmod(node)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to identify node types that satisfy func_expr_windowless.
|
||||
* If in doubt, "false" is always a safe answer.
|
||||
*/
|
||||
static bool
|
||||
looks_like_function(Node *node)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false; /* probably shouldn't happen */
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_FuncExpr:
|
||||
/* OK, unless it's going to deparse as a cast */
|
||||
return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL);
|
||||
case T_NullIfExpr:
|
||||
case T_CoalesceExpr:
|
||||
case T_MinMaxExpr:
|
||||
case T_SQLValueFunction:
|
||||
case T_XmlExpr:
|
||||
/* these are all accepted by func_expr_common_subexpr */
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_oper_expr - Parse back an OpExpr node
|
||||
|
@ -6921,7 +6968,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
if (list_length(rte->functions) == 1 &&
|
||||
(rtfunc1->funccolnames == NIL || !rte->funcordinality))
|
||||
{
|
||||
get_rule_expr(rtfunc1->funcexpr, context, true);
|
||||
get_rule_expr_funccall(rtfunc1->funcexpr, context, true);
|
||||
/* we'll print the coldeflist below, if it has one */
|
||||
}
|
||||
else
|
||||
|
@ -6984,7 +7031,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
|
||||
if (funcno > 0)
|
||||
appendStringInfoString(buf, ", ");
|
||||
get_rule_expr(rtfunc->funcexpr, context, true);
|
||||
get_rule_expr_funccall(rtfunc->funcexpr, context, true);
|
||||
if (rtfunc->funccolnames != NIL)
|
||||
{
|
||||
/* Reconstruct the column definition list */
|
||||
|
@ -7177,6 +7224,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
if (!PRETTY_PAREN(context))
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
else if (j->jointype != JOIN_INNER)
|
||||
{
|
||||
/* If we didn't say CROSS JOIN above, we must provide an ON */
|
||||
appendStringInfoString(buf, " ON TRUE");
|
||||
}
|
||||
|
||||
if (!PRETTY_PAREN(context) || j->alias != NULL)
|
||||
appendStringInfoChar(buf, ')');
|
||||
|
@ -7373,13 +7425,17 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
|
|||
*
|
||||
* We strip any top-level FieldStore or assignment ArrayRef nodes that
|
||||
* appear in the input, printing them as decoration for the base column
|
||||
* name (which we assume the caller just printed). Return the subexpression
|
||||
* that's to be assigned.
|
||||
* name (which we assume the caller just printed). We might also need to
|
||||
* strip CoerceToDomain nodes, but only ones that appear above assignment
|
||||
* nodes.
|
||||
*
|
||||
* Returns the subexpression that's to be assigned.
|
||||
*/
|
||||
static Node *
|
||||
processIndirection(Node *node, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
CoerceToDomain *cdomain = NULL;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
@ -7427,10 +7483,28 @@ processIndirection(Node *node, deparse_context *context)
|
|||
*/
|
||||
node = (Node *) aref->refassgnexpr;
|
||||
}
|
||||
else if (IsA(node, CoerceToDomain))
|
||||
{
|
||||
cdomain = (CoerceToDomain *) node;
|
||||
/* If it's an explicit domain coercion, we're done */
|
||||
if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
|
||||
break;
|
||||
/* Tentatively descend past the CoerceToDomain */
|
||||
node = (Node *) cdomain->arg;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we descended past a CoerceToDomain whose argument turned out not to
|
||||
* be a FieldStore or array assignment, back up to the CoerceToDomain.
|
||||
* (This is not enough to be fully correct if there are nested implicit
|
||||
* CoerceToDomains, but such cases shouldn't ever occur.)
|
||||
*/
|
||||
if (cdomain && node == (Node *) cdomain->arg)
|
||||
node = (Node *) cdomain;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -7791,4 +7865,44 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
|
|||
return buf.data;
|
||||
}
|
||||
|
||||
/*
|
||||
* get_one_range_partition_bound_string
|
||||
* A C string representation of one range partition bound
|
||||
*/
|
||||
char *
|
||||
get_range_partbound_string(List *bound_datums)
|
||||
{
|
||||
deparse_context context;
|
||||
StringInfo buf = makeStringInfo();
|
||||
ListCell *cell;
|
||||
char *sep;
|
||||
|
||||
memset(&context, 0, sizeof(deparse_context));
|
||||
context.buf = buf;
|
||||
|
||||
appendStringInfoString(buf, "(");
|
||||
sep = "";
|
||||
foreach(cell, bound_datums)
|
||||
{
|
||||
PartitionRangeDatum *datum =
|
||||
castNode(PartitionRangeDatum, lfirst(cell));
|
||||
|
||||
appendStringInfoString(buf, sep);
|
||||
if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
|
||||
appendStringInfoString(buf, "MINVALUE");
|
||||
else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
|
||||
appendStringInfoString(buf, "MAXVALUE");
|
||||
else
|
||||
{
|
||||
Const *val = castNode(Const, datum->value);
|
||||
|
||||
get_const_expr(val, &context, -1);
|
||||
}
|
||||
sep = ", ";
|
||||
}
|
||||
appendStringInfoString(buf, ")");
|
||||
|
||||
return buf->data;
|
||||
}
|
||||
|
||||
#endif /* (PG_VERSION_NUM >= 100000) */
|
||||
|
|
|
@ -386,6 +386,9 @@ static void get_rule_expr(Node *node, deparse_context *context,
|
|||
bool showimplicit);
|
||||
static void get_rule_expr_toplevel(Node *node, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static void get_rule_expr_funccall(Node *node, deparse_context *context,
|
||||
bool showimplicit);
|
||||
static bool looks_like_function(Node *node);
|
||||
static void get_oper_expr(OpExpr *expr, deparse_context *context);
|
||||
static void get_func_expr(FuncExpr *expr, deparse_context *context,
|
||||
bool showimplicit);
|
||||
|
@ -3282,8 +3285,11 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
|||
/*
|
||||
* We must dig down into the expr to see if it's a PARAM_MULTIEXPR
|
||||
* Param. That could be buried under FieldStores and ArrayRefs
|
||||
* (cf processIndirection()), and underneath those there could be
|
||||
* an implicit type coercion.
|
||||
* and CoerceToDomains (cf processIndirection()), and underneath
|
||||
* those there could be an implicit type coercion. Because we
|
||||
* would ignore implicit type coercions anyway, we don't need to
|
||||
* be as careful as processIndirection() is about descending past
|
||||
* implicit CoerceToDomains.
|
||||
*/
|
||||
expr = (Node *) tle->expr;
|
||||
while (expr)
|
||||
|
@ -3302,6 +3308,14 @@ get_update_query_targetlist_def(Query *query, List *targetList,
|
|||
break;
|
||||
expr = (Node *) aref->refassgnexpr;
|
||||
}
|
||||
else if (IsA(expr, CoerceToDomain))
|
||||
{
|
||||
CoerceToDomain *cdomain = (CoerceToDomain *) expr;
|
||||
|
||||
if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
|
||||
break;
|
||||
expr = (Node *) cdomain->arg;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
@ -5763,6 +5777,63 @@ get_rule_expr_toplevel(Node *node, deparse_context *context,
|
|||
get_rule_expr(node, context, showimplicit);
|
||||
}
|
||||
|
||||
/*
|
||||
* get_rule_expr_funccall - Parse back a function-call expression
|
||||
*
|
||||
* Same as get_rule_expr(), except that we guarantee that the output will
|
||||
* look like a function call, or like one of the things the grammar treats as
|
||||
* equivalent to a function call (see the func_expr_windowless production).
|
||||
* This is needed in places where the grammar uses func_expr_windowless and
|
||||
* you can't substitute a parenthesized a_expr. If what we have isn't going
|
||||
* to look like a function call, wrap it in a dummy CAST() expression, which
|
||||
* will satisfy the grammar --- and, indeed, is likely what the user wrote to
|
||||
* produce such a thing.
|
||||
*/
|
||||
static void
|
||||
get_rule_expr_funccall(Node *node, deparse_context *context,
|
||||
bool showimplicit)
|
||||
{
|
||||
if (looks_like_function(node))
|
||||
get_rule_expr(node, context, showimplicit);
|
||||
else
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
|
||||
appendStringInfoString(buf, "CAST(");
|
||||
/* no point in showing any top-level implicit cast */
|
||||
get_rule_expr(node, context, false);
|
||||
appendStringInfo(buf, " AS %s)",
|
||||
format_type_with_typemod(exprType(node),
|
||||
exprTypmod(node)));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to identify node types that satisfy func_expr_windowless.
|
||||
* If in doubt, "false" is always a safe answer.
|
||||
*/
|
||||
static bool
|
||||
looks_like_function(Node *node)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false; /* probably shouldn't happen */
|
||||
switch (nodeTag(node))
|
||||
{
|
||||
case T_FuncExpr:
|
||||
/* OK, unless it's going to deparse as a cast */
|
||||
return (((FuncExpr *) node)->funcformat == COERCE_EXPLICIT_CALL);
|
||||
case T_NullIfExpr:
|
||||
case T_CoalesceExpr:
|
||||
case T_MinMaxExpr:
|
||||
case T_XmlExpr:
|
||||
/* these are all accepted by func_expr_common_subexpr */
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_oper_expr - Parse back an OpExpr node
|
||||
|
@ -6641,7 +6712,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
if (list_length(rte->functions) == 1 &&
|
||||
(rtfunc1->funccolnames == NIL || !rte->funcordinality))
|
||||
{
|
||||
get_rule_expr(rtfunc1->funcexpr, context, true);
|
||||
get_rule_expr_funccall(rtfunc1->funcexpr, context, true);
|
||||
/* we'll print the coldeflist below, if it has one */
|
||||
}
|
||||
else
|
||||
|
@ -6704,7 +6775,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
|
||||
if (funcno > 0)
|
||||
appendStringInfoString(buf, ", ");
|
||||
get_rule_expr(rtfunc->funcexpr, context, true);
|
||||
get_rule_expr_funccall(rtfunc->funcexpr, context, true);
|
||||
if (rtfunc->funccolnames != NIL)
|
||||
{
|
||||
/* Reconstruct the column definition list */
|
||||
|
@ -6894,6 +6965,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
|
|||
if (!PRETTY_PAREN(context))
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
else if (j->jointype != JOIN_INNER)
|
||||
{
|
||||
/* If we didn't say CROSS JOIN above, we must provide an ON */
|
||||
appendStringInfoString(buf, " ON TRUE");
|
||||
}
|
||||
|
||||
if (!PRETTY_PAREN(context) || j->alias != NULL)
|
||||
appendStringInfoChar(buf, ')');
|
||||
|
@ -7090,13 +7166,17 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
|
|||
*
|
||||
* We strip any top-level FieldStore or assignment ArrayRef nodes that
|
||||
* appear in the input, printing them as decoration for the base column
|
||||
* name (which we assume the caller just printed). Return the subexpression
|
||||
* that's to be assigned.
|
||||
* name (which we assume the caller just printed). We might also need to
|
||||
* strip CoerceToDomain nodes, but only ones that appear above assignment
|
||||
* nodes.
|
||||
*
|
||||
* Returns the subexpression that's to be assigned.
|
||||
*/
|
||||
static Node *
|
||||
processIndirection(Node *node, deparse_context *context)
|
||||
{
|
||||
StringInfo buf = context->buf;
|
||||
CoerceToDomain *cdomain = NULL;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
|
@ -7144,10 +7224,28 @@ processIndirection(Node *node, deparse_context *context)
|
|||
*/
|
||||
node = (Node *) aref->refassgnexpr;
|
||||
}
|
||||
else if (IsA(node, CoerceToDomain))
|
||||
{
|
||||
cdomain = (CoerceToDomain *) node;
|
||||
/* If it's an explicit domain coercion, we're done */
|
||||
if (cdomain->coercionformat != COERCE_IMPLICIT_CAST)
|
||||
break;
|
||||
/* Tentatively descend past the CoerceToDomain */
|
||||
node = (Node *) cdomain->arg;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we descended past a CoerceToDomain whose argument turned out not to
|
||||
* be a FieldStore or array assignment, back up to the CoerceToDomain.
|
||||
* (This is not enough to be fully correct if there are nested implicit
|
||||
* CoerceToDomains, but such cases shouldn't ever occur.)
|
||||
*/
|
||||
if (cdomain && node == (Node *) cdomain->arg)
|
||||
node = (Node *) cdomain;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ ALTER EXTENSION citus UPDATE TO '7.0-15';
|
|||
SHOW citus.version;
|
||||
citus.version
|
||||
---------------
|
||||
7.0devel
|
||||
7.0.1
|
||||
(1 row)
|
||||
|
||||
-- ensure no objects were created outside pg_catalog
|
||||
|
|
|
@ -998,6 +998,42 @@ SELECT key, value FROM domain_partition_column_table ORDER BY key;
|
|||
(6 rows)
|
||||
|
||||
DROP TABLE domain_partition_column_table;
|
||||
-- verify we re-evaluate volatile functions every time
|
||||
CREATE TABLE http_request (
|
||||
site_id INT,
|
||||
ingest_time TIMESTAMPTZ DEFAULT now(),
|
||||
url TEXT,
|
||||
request_country TEXT,
|
||||
ip_address TEXT,
|
||||
status_code INT,
|
||||
response_time_msec INT
|
||||
);
|
||||
SELECT create_distributed_table('http_request', 'site_id');
|
||||
create_distributed_table
|
||||
--------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
PREPARE FOO AS INSERT INTO http_request (
|
||||
site_id, ingest_time, url, request_country,
|
||||
ip_address, status_code, response_time_msec
|
||||
) VALUES (
|
||||
1, clock_timestamp(), 'http://example.com/path', 'USA',
|
||||
inet '88.250.10.123', 200, 10
|
||||
);
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
SELECT count(distinct ingest_time) FROM http_request WHERE site_id = 1;
|
||||
count
|
||||
-------
|
||||
6
|
||||
(1 row)
|
||||
|
||||
DROP TABLE http_request;
|
||||
-- verify placement state updates invalidate shard state
|
||||
--
|
||||
-- We use a immutable function to check for that. The planner will
|
||||
|
|
|
@ -533,6 +533,37 @@ SELECT key, value FROM domain_partition_column_table ORDER BY key;
|
|||
|
||||
DROP TABLE domain_partition_column_table;
|
||||
|
||||
-- verify we re-evaluate volatile functions every time
|
||||
CREATE TABLE http_request (
|
||||
site_id INT,
|
||||
ingest_time TIMESTAMPTZ DEFAULT now(),
|
||||
url TEXT,
|
||||
request_country TEXT,
|
||||
ip_address TEXT,
|
||||
status_code INT,
|
||||
response_time_msec INT
|
||||
);
|
||||
|
||||
SELECT create_distributed_table('http_request', 'site_id');
|
||||
|
||||
PREPARE FOO AS INSERT INTO http_request (
|
||||
site_id, ingest_time, url, request_country,
|
||||
ip_address, status_code, response_time_msec
|
||||
) VALUES (
|
||||
1, clock_timestamp(), 'http://example.com/path', 'USA',
|
||||
inet '88.250.10.123', 200, 10
|
||||
);
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
EXECUTE foo;
|
||||
|
||||
SELECT count(distinct ingest_time) FROM http_request WHERE site_id = 1;
|
||||
|
||||
DROP TABLE http_request;
|
||||
|
||||
-- verify placement state updates invalidate shard state
|
||||
--
|
||||
-- We use a immutable function to check for that. The planner will
|
||||
|
|
Loading…
Reference in New Issue