Compare commits

...

8 Commits

Author SHA1 Message Date
Michael Paquier
9886744a36 pg_ctl: Disable autoruns for cmd.exe on Windows
On Windows, cmd.exe is used to launch the postmaster process to ease its
redirection setup.  However, cmd.exe may execute other programs at
startup due to autorun configurations, which could influence the
postmaster startup.  This patch adds /D flag to the launcher cmd.exe
command line to disable autorun settings written in the registry.

This is arguably a bug, but no backpatch is done now out of caution.

Reported-by: Hayato Kuroda
Author: Kyotaro Horiguchi
Reviewed-by: Robert Haas, Michael Paquier
Discussion: https://postgr.es/m/20230922.161551.320043332510268554.horikyota.ntt@gmail.com
2024-01-10 10:41:57 +09:00
Robert Haas
f896057e46 Document WAL summarization information functions.
Commit 174c480508ac25568561443e6d4a82d5c1103487 added two new
information functions but failed to document them anywhere.

Discussion: http://postgr.es/m/CA+Tgmobvqqj-DW9F7uUzT-cQqs6wcVb-Xhs=w=hzJnXSE-kRGw@mail.gmail.com
2024-01-09 13:04:46 -05:00
Nathan Bossart
d97ef756af Fix documentation for wal_summary_keep_time.
The documentation for this parameter lists its type as boolean, but
it is actually an integer.  Furthermore, there is no mention of how
the value is interpreted when specified without units.  This commit
fixes these oversights in commit 174c480508.

Co-authored-by: Hubert Depesz Lubaczewski
Discussion: https://postgr.es/m/ZZwkujFihO2uqKwp%40depesz.com
2024-01-09 11:35:10 -06:00
Nathan Bossart
5b1b9bce84 Cross-check lists of predefined LWLocks.
Both lwlocknames.txt and wait_event_names.txt contain a list of all
the predefined LWLocks, i.e., those with predefined positions
within MainLWLockArray.  It is easy to miss one or the other,
especially since the list in wait_event_names.txt omits the "Lock"
suffix from all the LWLock wait events.  This commit adds a cross-
check of these lists to the script that generates lwlocknames.h.
If the lists do not match exactly, building will fail.

Suggested-by: Robert Haas
Reviewed-by: Robert Haas, Michael Paquier, Bertrand Drouvot
Discussion: https://postgr.es/m/20240102173120.GA1061678%40nathanxps13
2024-01-09 11:05:19 -06:00
Joe Conway
a7be2a6c26 Add new function, PQchangePassword(), to libpq
Essentially this moves the non-interactive part of psql's "\password"
command into an exported client function. The password is not sent to the
server in cleartext because it is "encrypted" (in the case of scram and md5
it is actually hashed, but we have called these encrypted passwords for a
long time now) on the client side. This is good because it ensures the
cleartext password is never known by the server, and therefore won't end up
in logs, pg_stat displays, etc.

In other words, it exists for the same reason as PQencryptPasswordConn(), but
is more convenient as it both builds and runs the "ALTER USER" command for
you. PQchangePassword() uses PQencryptPasswordConn() to do the password
encryption. PQencryptPasswordConn() is passed a NULL for the algorithm
argument, hence encryption is done according to the server's
password_encryption setting.

Also modify the psql client to use the new function. That provides a builtin
test case. Ultimately drivers built on top of libpq should expose this
function and its use should be generally encouraged over doing ALTER USER
directly for password changes.

Author: Joe Conway
Reviewed-by: Tom Lane
Discussion: https://postgr.es/m/flat/b75955f7-e8cc-4bbd-817f-ef536bacbe93%40joeconway.com
2024-01-09 09:16:48 -05:00
Tatsuo Ishii
d596736a49 Doc: fix character_sets view.
The note regarding character encoding form in "The Information Schema"
said that LATIN1 character repertoires only use one encoding form
LATIN1. This is not correct because LATIN1 has another encoding form
ISO-2022-JP-2. To fix this, replace LATIN1 with LATIN2, which is not
supported by ISO-2022-JP-2, thus it can be said that LATIN2 only uses
one encoding form.

Back-patch to supported branches.

Author: Tatsuo Ishii
Reviewed-by: Daniel Gustafsson
Discussion: https://postgr.es/m/flat/20240102.153925.1147403616414525145.t-ishii%40sranhm.sra.co.jp
2024-01-09 19:43:19 +09:00
Alexander Korotkov
028b15405b An addition to 8c441c08279
Given that now SJE doesn't work with result relation, turn a code dealing with
that into an assert that it shouldn't happen.
2024-01-09 10:12:14 +02:00
Alexander Korotkov
8c441c0827 Forbid SJE with result relation
The target relation for INSERT/UPDATE/DELETE/MERGE has a different behavior
than other relations in EvalPlanQual() and RETURNING clause.  This is why we
forbid target relation to be either source or target relation in SJE.
It's not clear if we could ever support this.

Reported-by: Alexander Lakhin
Discussion: https://postgr.es/m/b9e8f460-f9a6-0e9b-e8ba-60d59f0bc22c%40gmail.com
2024-01-09 10:01:22 +02:00
18 changed files with 366 additions and 76 deletions

View File

@ -4179,7 +4179,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</varlistentry>
<varlistentry id="guc-wal-summary-keep-time" xreflabel="wal_summary_keep_time">
<term><varname>wal_summary_keep_time</varname> (<type>boolean</type>)
<term><varname>wal_summary_keep_time</varname> (<type>integer</type>)
<indexterm>
<primary><varname>wal_summary_keep_time</varname> configuration parameter</primary>
</indexterm>
@ -4198,6 +4198,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
know will not be required for future incremental backups.
This parameter can only be set in the
<filename>postgresql.conf</filename> file or on the server command line.
If this value is specified without units, it is taken as minutes.
The default is 10 days. If <literal>summarize_wal = off</literal>,
existing WAL summaries will not be removed regardless of the value of
this parameter, because the WAL summarizer will not run.

View File

@ -26480,6 +26480,86 @@ SELECT collation for ('foo' COLLATE "de_DE");
</sect2>
<sect2 id="functions-info-wal-summary">
<title>WAL Summarization Information Functions</title>
<para>
The functions shown in <xref linkend="functions-wal-summary"/>
print information about the status of WAL summarization.
See <xref linkend="guc-summarize-wal" />.
</para>
<table id="functions-wal-summary">
<title>WAL Summarization Information Functions</title>
<tgroup cols="1">
<thead>
<row>
<entry role="func_table_entry"><para role="func_signature">
Function
</para>
<para>
Description
</para></entry>
</row>
</thead>
<tbody>
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
<primary>pg_available_wal_summaries</primary>
</indexterm>
<function>pg_available_wal_summaries</function> ()
<returnvalue>setof record</returnvalue>
( <parameter>tli</parameter> <type>bigint</type>,
<parameter>start_lsn</parameter> <type>pg_lsn</type>,
<parameter>end_lsn</parameter> <type>pg_lsn</type> )
</para>
<para>
Returns information about the WAL summary files present in the
data directory, under <literal>pg_wal/summaries</literal>.
One row will be returned per WAL summary file. Each file summarizes
WAL on the indicated TLI within the indicated LSN range. This function
might be useful to determine whether enough WAL summaries are present
on the server to take an incremental backup based on some prior
backup whose start LSN is known.
</para></entry>
</row>
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
<primary>pg_wal_summary_contents</primary>
</indexterm>
<function>pg_wal_summary_contents</function> ( <parameter>tli</parameter> <type>bigint</type>, <parameter>start_lsn</parameter> <type>pg_lsn</type>, <parameter>end_lsn</parameter> <type>pg_lsn</type> )
<returnvalue>setof record</returnvalue>
( <parameter>relfilenode</parameter> <type>oid</type>,
<parameter>reltablespace</parameter> <type>oid</type>,
<parameter>reldatabase</parameter> <type>oid</type>,
<parameter>relforknumber</parameter> <type>smallint</type>,
<parameter>relblocknumber</parameter> <type>bigint</type>,
<parameter>is_limit_block</parameter> <type>boolean</type> )
</para>
<para>
Returns one information about the contents of a single WAL summary file
identified by TLI and starting and ending LSNs. Each row with
<literal>is_limit_block</literal> false indicates that the block
identified by the remaining output columns was modified by at least
one WAL record within the range of records summarized by this file.
Each row with <literal>is_limit_block</literal> true indicates either
that (a) the relation fork was truncated to the length given by
<literal>relblocknumber</literal> within the relevant range of WAL
records or (b) that the relation fork was created or dropped within
the relevant range of WAL records; in such cases,
<literal>relblocknumber</literal> will be zero.
</para></entry>
</row>
</tbody>
</tgroup>
</table>
</sect2>
</sect1>
<sect1 id="functions-admin">

View File

@ -697,8 +697,8 @@
<para>
An encoding of some character repertoire. Most older character
repertoires only use one encoding form, and so there are no
separate names for them (e.g., <literal>LATIN1</literal> is an
encoding form applicable to the <literal>LATIN1</literal>
separate names for them (e.g., <literal>LATIN2</literal> is an
encoding form applicable to the <literal>LATIN2</literal>
repertoire). But for example Unicode has the encoding forms
<literal>UTF8</literal>, <literal>UTF16</literal>, etc. (not
all supported by PostgreSQL). Encoding forms are not exposed

View File

@ -7116,6 +7116,45 @@ char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
</listitem>
</varlistentry>
<varlistentry id="libpq-PQchangePassword">
<term><function>PQchangePassword</function><indexterm><primary>PQchangePassword</primary></indexterm></term>
<listitem>
<para>
Changes a <productname>PostgreSQL</productname> password.
<synopsis>
PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd);
</synopsis>
This function uses <function>PQencryptPasswordConn</function>
to build and execute the command <literal>ALTER USER ... PASSWORD
'...'</literal>, thereby changing the user's password. It exists for
the same reason as <function>PQencryptPasswordConn</function>, but
is more convenient as it both builds and runs the command for you.
<xref linkend="libpq-PQencryptPasswordConn"/> is passed a
<symbol>NULL</symbol> for the algorithm argument, hence encryption is
done according to the server's <xref linkend="guc-password-encryption"/>
setting.
</para>
<para>
The <parameter>user</parameter> and <parameter>passwd</parameter> arguments
are the SQL name of the target user, and the new cleartext password.
</para>
<para>
Returns a <structname>PGresult</structname> pointer representing
the result of the <literal>ALTER USER</literal> command, or
a null pointer if the routine failed before issuing any command.
The <xref linkend="libpq-PQresultStatus"/> function should be called
to check the return value for any errors (including the value of a null
pointer, in which case it will return
<symbol>PGRES_FATAL_ERROR</symbol>). Use
<xref linkend="libpq-PQerrorMessage"/> to get more information about
such errors.
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-PQencryptPassword">
<term><function>PQencryptPassword</function><indexterm><primary>PQencryptPassword</primary></indexterm></term>

View File

@ -130,7 +130,7 @@ $(top_builddir)/src/port/libpgport_srv.a: | submake-libpgport
parser/gram.h: parser/gram.y
$(MAKE) -C parser gram.h
storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lwlocknames.txt
storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lwlocknames.txt utils/activity/wait_event_names.txt
$(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c
utils/activity/wait_event_types.h: utils/activity/generate-wait_event_types.pl utils/activity/wait_event_names.txt

View File

@ -1900,8 +1900,10 @@ remove_self_join_rel(PlannerInfo *root, PlanRowMark *kmark, PlanRowMark *rmark,
/* Replace varno in all the query structures */
query_tree_walker(root->parse, replace_varno_walker, &ctx,
QTW_EXAMINE_SORTGROUP);
if (root->parse->resultRelation == toRemove->relid)
root->parse->resultRelation = toKeep->relid;
/* See remove_self_joins_one_group() */
Assert(root->parse->resultRelation != toRemove->relid);
Assert(root->parse->resultRelation != toKeep->relid);
/* Replace links in the planner info */
remove_rel_from_query(root, toRemove, toKeep->relid, NULL, NULL);
@ -2086,6 +2088,14 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
{
RelOptInfo *inner = root->simple_rel_array[r];
/*
* We don't accept result relation as either source or target relation
* of SJE, because result relation has different behavior in
* EvalPlanQual() and RETURNING clause.
*/
if (root->parse->resultRelation == r)
continue;
k = r;
while ((k = bms_next_member(relids, k)) > 0)
@ -2101,6 +2111,9 @@ remove_self_joins_one_group(PlannerInfo *root, Relids relids)
PlanRowMark *imark = NULL;
List *uclauses = NIL;
if (root->parse->resultRelation == k)
continue;
/* A sanity check: the relations have the same Oid. */
Assert(root->simple_rte_array[k]->relid ==
root->simple_rte_array[r]->relid);

View File

@ -39,8 +39,8 @@ s_lock_test: s_lock.c $(top_builddir)/src/common/libpgcommon.a $(top_builddir)/s
lwlocknames.c: lwlocknames.h
touch $@
lwlocknames.h: $(top_srcdir)/src/backend/storage/lmgr/lwlocknames.txt generate-lwlocknames.pl
$(PERL) $(srcdir)/generate-lwlocknames.pl $<
lwlocknames.h: $(top_srcdir)/src/backend/storage/lmgr/lwlocknames.txt $(top_srcdir)/src/backend/utils/activity/wait_event_names.txt generate-lwlocknames.pl
$(PERL) $(srcdir)/generate-lwlocknames.pl $^
check: s_lock_test
./s_lock_test

View File

@ -15,6 +15,7 @@ my $continue = "\n";
GetOptions('outdir:s' => \$output_path);
open my $lwlocknames, '<', $ARGV[0] or die;
open my $wait_event_names, '<', $ARGV[1] or die;
# Include PID in suffix in case parallel make runs this multiple times.
my $htmp = "$output_path/lwlocknames.h.tmp$$";
@ -30,6 +31,40 @@ print $c $autogen, "\n";
print $c "const char *const IndividualLWLockNames[] = {";
#
# First, record the predefined LWLocks listed in wait_event_names.txt. We'll
# cross-check those with the ones in lwlocknames.txt.
#
my @wait_event_lwlocks;
my $record_lwlocks = 0;
while (<$wait_event_names>)
{
chomp;
# Check for end marker.
last if /^# END OF PREDEFINED LWLOCKS/;
# Skip comments and empty lines.
next if /^#/;
next if /^\s*$/;
# Start recording LWLocks when we find the WaitEventLWLock section.
if (/^Section: ClassName - WaitEventLWLock$/)
{
$record_lwlocks = 1;
next;
}
# Go to the next line if we are not yet recording LWLocks.
next if not $record_lwlocks;
# Record the LWLock.
(my $waiteventname, my $waitevendocsentence) = split(/\t/, $_);
push(@wait_event_lwlocks, $waiteventname . "Lock");
}
my $i = 0;
while (<$lwlocknames>)
{
chomp;
@ -50,6 +85,15 @@ while (<$lwlocknames>)
die "lwlocknames.txt not in order" if $lockidx < $lastlockidx;
die "lwlocknames.txt has duplicates" if $lockidx == $lastlockidx;
die "$lockname defined in lwlocknames.txt but missing from "
. "wait_event_names.txt"
if $i >= scalar @wait_event_lwlocks;
die "lists of predefined LWLocks do not match (first mismatch at "
. "$wait_event_lwlocks[$i] in wait_event_names.txt and $lockname in "
. "lwlocknames.txt)"
if $wait_event_lwlocks[$i] ne $lockname;
$i++;
while ($lastlockidx < $lockidx - 1)
{
++$lastlockidx;
@ -63,6 +107,11 @@ while (<$lwlocknames>)
print $h "#define $lockname (&MainLWLockArray[$lockidx].lock)\n";
}
die
"$wait_event_lwlocks[$i] defined in wait_event_names.txt but missing from "
. "lwlocknames.txt"
if $i < scalar @wait_event_lwlocks;
printf $c "\n};\n";
print $h "\n";
printf $h "#define NUM_INDIVIDUAL_LWLOCKS %s\n", $lastlockidx + 1;

View File

@ -276,6 +276,10 @@ Extension "Waiting in an extension."
# This class of wait events has its own set of C structure, so these are
# only used for the documentation.
#
# NB: Predefined LWLocks (i.e., those declared in lwlocknames.txt) must be
# listed in the top section of locks and must be listed in the same order as in
# lwlocknames.txt.
#
Section: ClassName - WaitEventLWLock
@ -326,6 +330,14 @@ NotifyQueueTail "Waiting to update limit on <command>NOTIFY</command> message st
WaitEventExtension "Waiting to read or update custom wait events information for extensions."
WALSummarizer "Waiting to read or update WAL summarization state."
#
# END OF PREDEFINED LWLOCKS (DO NOT CHANGE THIS LINE)
#
# Predefined LWLocks (i.e., those declared in lwlocknames.txt) must be listed
# in the section above and must be listed in the same order as in
# lwlocknames.txt. Other LWLocks must be listed in the section below.
#
XactBuffer "Waiting for I/O on a transaction status SLRU buffer."
CommitTsBuffer "Waiting for I/O on a commit timestamp SLRU buffer."
SubtransBuffer "Waiting for I/O on a sub-transaction SLRU buffer."

View File

@ -552,11 +552,11 @@ start_postmaster(void)
else
close(fd);
cmd = psprintf("\"%s\" /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
cmd = psprintf("\"%s\" /D /C \"\"%s\" %s%s < \"%s\" >> \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL, log_file);
}
else
cmd = psprintf("\"%s\" /C \"\"%s\" %s%s < \"%s\" 2>&1\"",
cmd = psprintf("\"%s\" /D /C \"\"%s\" %s%s < \"%s\" 2>&1\"",
comspec, exec_path, pgdata_opt, post_opts, DEVNULL);
if (!CreateRestrictedProcess(cmd, &pi, false))

View File

@ -2158,29 +2158,15 @@ exec_command_password(PsqlScanState scan_state, bool active_branch)
}
else
{
char *encrypted_password;
PGresult *res = PQchangePassword(pset.db, user, pw1);
encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL);
if (!encrypted_password)
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
pg_log_info("%s", PQerrorMessage(pset.db));
success = false;
}
else
{
PGresult *res;
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
fmtId(user));
appendStringLiteralConn(&buf, encrypted_password, pset.db);
res = PSQLexec(buf.data);
if (!res)
success = false;
else
PQclear(res);
PQfreemem(encrypted_password);
}
PQclear(res);
}
free(user);

View File

@ -1,7 +1,9 @@
# Copyright (c) 2022-2024, PostgreSQL Global Development Group
lwlocknames = custom_target('lwlocknames',
input: files('../../backend/storage/lmgr/lwlocknames.txt'),
input: files(
'../../backend/storage/lmgr/lwlocknames.txt',
'../../backend/utils/activity/wait_event_names.txt'),
output: ['lwlocknames.h', 'lwlocknames.c'],
command: [
perl, files('../../backend/storage/lmgr/generate-lwlocknames.pl'),

View File

@ -191,3 +191,4 @@ PQclosePrepared 188
PQclosePortal 189
PQsendClosePrepared 190
PQsendClosePortal 191
PQchangePassword 192

View File

@ -1372,3 +1372,84 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
return crypt_pwd;
}
/*
* PQchangePassword -- exported routine to change a password
*
* This is intended to be used by client applications that wish to
* change the password for a user. The password is not sent in
* cleartext because it is encrypted on the client side. This is
* good because it ensures the cleartext password is never known by
* the server, and therefore won't end up in logs, pg_stat displays,
* etc. The password encryption is performed by PQencryptPasswordConn(),
* which is passed a NULL for the algorithm argument. Hence encryption
* is done according to the server's password_encryption
* setting. We export the function so that clients won't be dependent
* on the implementation specific details with respect to how the
* server changes passwords.
*
* Arguments are a connection object, the SQL name of the target user,
* and the cleartext password.
*
* Return value is the PGresult of the executed ALTER USER statement
* or NULL if we never get there. The caller is responsible to PQclear()
* the returned PGresult.
*
* PQresultStatus() should be called to check the return value for errors,
* and PQerrorMessage() used to get more information about such errors.
*/
PGresult *
PQchangePassword(PGconn *conn, const char *user, const char *passwd)
{
char *encrypted_password = PQencryptPasswordConn(conn, passwd,
user, NULL);
if (!encrypted_password)
{
/* PQencryptPasswordConn() already registered the error */
return NULL;
}
else
{
char *fmtpw = PQescapeLiteral(conn, encrypted_password,
strlen(encrypted_password));
/* no longer needed, so clean up now */
PQfreemem(encrypted_password);
if (!fmtpw)
{
/* PQescapeLiteral() already registered the error */
return NULL;
}
else
{
char *fmtuser = PQescapeIdentifier(conn, user, strlen(user));
if (!fmtuser)
{
/* PQescapeIdentifier() already registered the error */
PQfreemem(fmtpw);
return NULL;
}
else
{
PQExpBufferData buf;
PGresult *res;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD %s",
fmtuser, fmtpw);
res = PQexec(conn, buf.data);
/* clean up */
termPQExpBuffer(&buf);
PQfreemem(fmtuser);
PQfreemem(fmtpw);
return res;
}
}
}
}

View File

@ -659,6 +659,7 @@ extern int PQenv2encoding(void);
extern char *PQencryptPassword(const char *passwd, const char *user);
extern char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);
extern PGresult *PQchangePassword(PGconn *conn, const char *user, const char *passwd);
/* === in encnames.c === */

View File

@ -6868,18 +6868,58 @@ select * from emp1 t1
-> Seq Scan on emp1 t3
(6 rows)
-- Check that SJE replaces target relation correctly
-- Check that SJE doesn't replace the target relation
explain (costs off)
WITH t1 AS (SELECT * FROM emp1)
UPDATE emp1 SET code = t1.code + 1 FROM t1
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code;
QUERY PLAN
----------------------------------
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
QUERY PLAN
-------------------------------------------------------
Update on emp1
-> Seq Scan on emp1
Filter: (id IS NOT NULL)
(3 rows)
-> Nested Loop
-> Seq Scan on emp1
-> Index Scan using emp1_pkey on emp1 emp1_1
Index Cond: (id = emp1.id)
(5 rows)
INSERT INTO emp1 VALUES (1, 1), (2, 1);
WITH t1 AS (SELECT * FROM emp1)
UPDATE emp1 SET code = t1.code + 1 FROM t1
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
id | code | code
----+------+------
1 | 2 | 1
2 | 2 | 1
(2 rows)
TRUNCATE emp1;
EXPLAIN (COSTS OFF)
UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
QUERY PLAN
-------------------------------------
Update on sj sq
-> Nested Loop
Join Filter: (sq.a = sz.a)
-> Seq Scan on sj sq
-> Materialize
-> Seq Scan on sj sz
(6 rows)
CREATE RULE sj_del_rule AS ON DELETE TO sj
DO INSTEAD
UPDATE sj SET a = 1 WHERE a = old.a;
EXPLAIN (COSTS OFF) DELETE FROM sj;
QUERY PLAN
--------------------------------------
Update on sj sj_1
-> Nested Loop
Join Filter: (sj.a = sj_1.a)
-> Seq Scan on sj sj_1
-> Materialize
-> Seq Scan on sj
(6 rows)
DROP RULE sj_del_rule ON sj CASCADE;
-- Check that SJE does not mistakenly omit qual clauses (bug #18187)
insert into emp1 values (1, 1);
explain (costs off)
@ -7076,7 +7116,6 @@ ON sj_t1.id = _t2t3t4.id;
--
-- Test RowMarks-related code
--
-- TODO: Why this select returns two copies of ctid field? Should we fix it?
EXPLAIN (COSTS OFF) -- Both sides have explicit LockRows marks
SELECT a1.a FROM sj a1,sj a2 WHERE (a1.a=a2.a) FOR UPDATE;
QUERY PLAN
@ -7086,27 +7125,6 @@ SELECT a1.a FROM sj a1,sj a2 WHERE (a1.a=a2.a) FOR UPDATE;
Filter: (a IS NOT NULL)
(3 rows)
EXPLAIN (COSTS OFF) -- A RowMark exists for the table being kept
UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
QUERY PLAN
---------------------------------
Update on sj sz
-> Seq Scan on sj sz
Filter: (a IS NOT NULL)
(3 rows)
CREATE RULE sj_del_rule AS ON DELETE TO sj
DO INSTEAD
UPDATE sj SET a = 1 WHERE a = old.a;
EXPLAIN (COSTS OFF) DELETE FROM sj; -- A RowMark exists for the table being dropped
QUERY PLAN
---------------------------------
Update on sj
-> Seq Scan on sj
Filter: (a IS NOT NULL)
(3 rows)
DROP RULE sj_del_rule ON sj CASCADE;
reset enable_hashjoin;
reset enable_mergejoin;
--

View File

@ -2499,13 +2499,16 @@ SELECT * FROM rw_view1;
(1 row)
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
QUERY PLAN
--------------------------------------------------
Update on base_tbl
-> Index Scan using base_tbl_pkey on base_tbl
Index Cond: (id = 1)
Filter: ((NOT deleted) AND snoop(data))
(4 rows)
QUERY PLAN
-------------------------------------------------------------------
Update on base_tbl base_tbl_1
-> Nested Loop
-> Index Scan using base_tbl_pkey on base_tbl base_tbl_1
Index Cond: (id = 1)
-> Index Scan using base_tbl_pkey on base_tbl
Index Cond: (id = 1)
Filter: ((NOT deleted) AND snoop(data))
(7 rows)
DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
NOTICE: snooped value: Row 1

View File

@ -2616,11 +2616,25 @@ select * from emp1 t1
inner join emp1 t2 on t1.id = t2.id
left join emp1 t3 on t1.id > 1 and t1.id < 2;
-- Check that SJE replaces target relation correctly
-- Check that SJE doesn't replace the target relation
explain (costs off)
WITH t1 AS (SELECT * FROM emp1)
UPDATE emp1 SET code = t1.code + 1 FROM t1
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code;
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
INSERT INTO emp1 VALUES (1, 1), (2, 1);
WITH t1 AS (SELECT * FROM emp1)
UPDATE emp1 SET code = t1.code + 1 FROM t1
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
TRUNCATE emp1;
EXPLAIN (COSTS OFF)
UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
CREATE RULE sj_del_rule AS ON DELETE TO sj
DO INSTEAD
UPDATE sj SET a = 1 WHERE a = old.a;
EXPLAIN (COSTS OFF) DELETE FROM sj;
DROP RULE sj_del_rule ON sj CASCADE;
-- Check that SJE does not mistakenly omit qual clauses (bug #18187)
insert into emp1 values (1, 1);
@ -2729,19 +2743,9 @@ ON sj_t1.id = _t2t3t4.id;
-- Test RowMarks-related code
--
-- TODO: Why this select returns two copies of ctid field? Should we fix it?
EXPLAIN (COSTS OFF) -- Both sides have explicit LockRows marks
SELECT a1.a FROM sj a1,sj a2 WHERE (a1.a=a2.a) FOR UPDATE;
EXPLAIN (COSTS OFF) -- A RowMark exists for the table being kept
UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
CREATE RULE sj_del_rule AS ON DELETE TO sj
DO INSTEAD
UPDATE sj SET a = 1 WHERE a = old.a;
EXPLAIN (COSTS OFF) DELETE FROM sj; -- A RowMark exists for the table being dropped
DROP RULE sj_del_rule ON sj CASCADE;
reset enable_hashjoin;
reset enable_mergejoin;