On Thu, 2021-03-04 at 16:51 +0000, Jacob Champion wrote:
> > I am not against fixing or improving the behavior, but given the
> > fact that we can't portably get less than second precision, it
> > seems impossible.
> 
> You could backdate the temporary file, so that any save is guaranteed
> to move the timestamp forward. That should work even if the filesystem
> has extremely poor precision.

Ah, of course, that is the way to go.
Attached is version 3 which does it like this.

Yours,
Laurenz Albe
From 7ff01271cb8ea5c9011a57ed613026ec7511d785 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <laurenz.a...@cybertec.at>
Date: Fri, 5 Mar 2021 13:34:12 +0100
Subject: [PATCH] Improve \e, \ef and \ev if the editor is quit

Before, if you edited a function or view definition or a
script file, quitting the editor without saving would cause
the *previous query* to be executed, which is certainly not
what anyone would expect.

It is better to execute nothing and clear the query buffer
in this case.

The behavior when editing the query buffer is unchanged:
quitting without saving will retain the query buffer.

Author: Laurenz Albe <laurenz.a...@cybertec.at>
Reviewed-by: Chapman Flack, Jacob Champion
Discussion: https://postgr.es/m/0ba3f2a658bac6546d9934ab6ba63a805d46a49b.camel%40cybertec.at
---
 doc/src/sgml/ref/psql-ref.sgml | 10 ++++--
 src/bin/psql/command.c         | 56 +++++++++++++++++++++++++---------
 2 files changed, 49 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 13c1edfa4d..0bf69174ff 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1970,7 +1970,9 @@ testdb=&gt;
         </para>
 
         <para>
-        The new contents of the query buffer are then re-parsed according to
+        If you edit a file or the previous query, and you quit the editor without
+        modifying the file, the query buffer is cleared.
+        Otherwise, the new contents of the query buffer are re-parsed according to
         the normal rules of <application>psql</application>, treating the
         whole buffer as a single line.  Any complete queries are immediately
         executed; that is, if the query buffer contains or ends with a
@@ -2039,7 +2041,8 @@ Tue Oct 26 21:40:57 CEST 1999
          in the form of a <command>CREATE OR REPLACE FUNCTION</command> or
          <command>CREATE OR REPLACE PROCEDURE</command> command.
          Editing is done in the same way as for <literal>\edit</literal>.
-         After the editor exits, the updated command is executed immediately
+         If you quit the editor without saving, the statement is discarded.
+         If you save and exit the editor, the updated command is executed immediately
          if you added a semicolon to it.  Otherwise it is redisplayed;
          type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal>
          to cancel.
@@ -2115,7 +2118,8 @@ Tue Oct 26 21:40:57 CEST 1999
          This command fetches and edits the definition of the named view,
          in the form of a <command>CREATE OR REPLACE VIEW</command> command.
          Editing is done in the same way as for <literal>\edit</literal>.
-         After the editor exits, the updated command is executed immediately
+         If you quit the editor without saving, the statement is discarded.
+         If you save and exit the editor, the updated command is executed immediately
          if you added a semicolon to it.  Otherwise it is redisplayed;
          type semicolon or <literal>\g</literal> to send it, or <literal>\r</literal>
          to cancel.
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index c98e3d31d0..bd01d59d96 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -10,6 +10,7 @@
 #include <ctype.h>
 #include <time.h>
 #include <pwd.h>
+#include <utime.h>
 #ifndef WIN32
 #include <sys/stat.h>			/* for stat() */
 #include <fcntl.h>				/* open() flags */
@@ -151,7 +152,7 @@ static void copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
 static bool do_connect(enum trivalue reuse_previous_specification,
 					   char *dbname, char *user, char *host, char *port);
 static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
-					int lineno, bool *edited);
+					int lineno, bool *edited, bool discard_on_quit);
 static bool do_shell(const char *command);
 static bool do_watch(PQExpBuffer query_buf, double sleep);
 static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
@@ -1003,6 +1004,13 @@ exec_command_edit(PsqlScanState scan_state, bool active_branch,
 			}
 			if (status != PSQL_CMD_ERROR)
 			{
+				/*
+				 * If the query buffer is empty, we'll edit the previous
+				 * query. But in that case, we don't want to keep that if the
+				 * editor is quit.
+				 */
+				bool		discard_on_quit = (query_buf->len == 0);
+
 				expand_tilde(&fname);
 				if (fname)
 					canonicalize_path(fname);
@@ -1010,7 +1018,7 @@ exec_command_edit(PsqlScanState scan_state, bool active_branch,
 				/* If query_buf is empty, recall previous query for editing */
 				copy_previous_query(query_buf, previous_buf);
 
-				if (do_edit(fname, query_buf, lineno, NULL))
+				if (do_edit(fname, query_buf, lineno, NULL, discard_on_quit))
 					status = PSQL_CMD_NEWEDIT;
 				else
 					status = PSQL_CMD_ERROR;
@@ -1133,11 +1141,9 @@ exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
 		{
 			bool		edited = false;
 
-			if (!do_edit(NULL, query_buf, lineno, &edited))
+			if (!do_edit(NULL, query_buf, lineno, &edited, true))
 				status = PSQL_CMD_ERROR;
-			else if (!edited)
-				puts(_("No changes"));
-			else
+			else if (edited)
 				status = PSQL_CMD_NEWEDIT;
 		}
 
@@ -3626,12 +3632,7 @@ UnsyncVariables(void)
 }
 
 
-/*
- * do_edit -- handler for \e
- *
- * If you do not specify a filename, the current query buffer will be copied
- * into a temporary one.
- */
+/* helper for do_edit() */
 static bool
 editFile(const char *fname, int lineno)
 {
@@ -3699,10 +3700,18 @@ editFile(const char *fname, int lineno)
 }
 
 
-/* call this one */
+/*
+ * do_edit -- handler for \e
+ *
+ * If you do not specify a filename, the current query buffer will be copied
+ * into a temporary one.
+ * "edited" will be set to true if the file is modified.
+ * If "discard_on_quit" is true, discard the query buffer if the file was
+ * not modified.
+ */
 static bool
 do_edit(const char *filename_arg, PQExpBuffer query_buf,
-		int lineno, bool *edited)
+		int lineno, bool *edited, bool discard_on_quit)
 {
 	char		fnametmp[MAXPGPATH];
 	FILE	   *stream = NULL;
@@ -3763,6 +3772,8 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 		else
 		{
 			unsigned int ql = query_buf->len;
+			/* old modification timestamp for the temporary file */
+			struct utimbuf epoch = { 0, 0 };
 
 			/* force newline-termination of what we send to editor */
 			if (ql > 0 && query_buf->data[ql - 1] != '\n')
@@ -3790,6 +3801,14 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 					pg_log_error("%s: %m", fname);
 				error = true;
 			}
+			/* set the modification timestamp to the epoch */
+			else if (utime(fname, &epoch) != 0)
+			{
+				pg_log_error("%s: %m", fname);
+				if (remove(fname) != 0)
+					pg_log_error("%s: %m", fname);
+				error = true;
+			}
 		}
 	}
 
@@ -3809,6 +3828,7 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 		error = true;
 	}
 
+	/* file was edited if the modification time is changed */
 	if (!error && before.st_mtime != after.st_mtime)
 	{
 		stream = fopen(fname, PG_BINARY_R);
@@ -3839,6 +3859,14 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf,
 			fclose(stream);
 		}
 	}
+	else
+	{
+		/* if we were editing anything else than the query buffer, discard */
+		if (discard_on_quit)
+			resetPQExpBuffer(query_buf);
+		else
+			puts(_("No changes"));
+	}
 
 	/* remove temp file */
 	if (!filename_arg)
-- 
2.26.2

Reply via email to