]> git.joonet.de Git - adminer.git/commitdiff
PostgreSQL: Support COPY FROM stdin in SQL query (fix #942)
authorJakub Vrana <jakub@vrana.cz>
Thu, 3 Apr 2025 09:41:43 +0000 (11:41 +0200)
committerJakub Vrana <jakub@vrana.cz>
Thu, 3 Apr 2025 09:41:43 +0000 (11:41 +0200)
CHANGELOG.md
adminer/drivers/pgsql.inc.php
adminer/sql.inc.php

index bc7ea09f0fd60bcf90f62eac5aee63ed1e89ed71..86d3c6f5b491e6e2bed922008c0899e410bc7e18 100644 (file)
@@ -1,5 +1,6 @@
 ## Adminer dev
 - Do not edit NULL values by Modify (bug #967)
+- PostgreSQL: Support COPY FROM stdin in SQL query (bug #942)
 - MySQL: Display number of found rows in group queries (regression from 5.1.1)
 - non-MySQL: Parse '--' as comment in SQL command (bug SF-842)
 
index 478519ae2857a5fce4fe364ceafc4689ece7a382..30ca0df04d1aa07d6d781415cd6ee866e2e8a119 100644 (file)
@@ -6,7 +6,7 @@ add_driver("pgsql", "PostgreSQL");
 if (isset($_GET["pgsql"])) {
        define('Adminer\DRIVER', "pgsql");
        if (extension_loaded("pgsql") && $_GET["ext"] != "pdo") {
-               class Db extends SqlDb {
+               class PgsqlDb extends SqlDb {
                        public string $extension = "PgSQL";
                        public int $timeout = 0;
                        private $link, $string, $database = true;
@@ -88,6 +88,19 @@ if (isset($_GET["pgsql"])) {
                        function warnings() {
                                return h(pg_last_notice($this->link)); // second parameter is available since PHP 7.1.0
                        }
+
+                       /** Copy from array into a table
+                       * @param list<string> $rows
+                       */
+                       function copyFrom(string $table, array $rows): bool {
+                               $this->error = '';
+                               set_error_handler(function ($errno, $error) {
+                                       $this->error = (ini_bool('html_errors') ? html_entity_decode($error) : $error);
+                               });
+                               $return = pg_copy_from($this->link, $table, $rows);
+                               restore_error_handler();
+                               return $return;
+                       }
                }
 
                class Result {
@@ -123,7 +136,7 @@ if (isset($_GET["pgsql"])) {
                }
 
        } elseif (extension_loaded("pdo_pgsql")) {
-               class Db extends PdoDb {
+               class PgsqlDb extends PdoDb {
                        public string $extension = "PDO_PgSQL";
                        public int $timeout = 0;
 
@@ -155,6 +168,12 @@ if (isset($_GET["pgsql"])) {
                                // not implemented in PDO_PgSQL as of PHP 7.2.1
                        }
 
+                       function copyFrom(string $table, array $rows): bool {
+                               $return = $this->pdo->pgsqlCopyFromArray($table, $rows);
+                               $this->error = idx($this->pdo->errorInfo(), 2) ?: '';
+                               return $return;
+                       }
+
                        function close() {
                        }
                }
@@ -163,6 +182,21 @@ if (isset($_GET["pgsql"])) {
 
 
 
+       if (class_exists('Adminer\PgsqlDb')) {
+               class Db extends PgsqlDb {
+                       function multi_query(string $query) {
+                               if (preg_match('~\bCOPY\s+(.+?)\s+FROM\s+stdin;\n?(.*)\n\\\\\.$~is', str_replace("\r\n", "\n", $query), $match)) { // no ^ to allow leading comments
+                                       $rows = explode("\n", $match[2]);
+                                       $this->affected_rows = count($rows);
+                                       return $this->copyFrom($match[1], $rows);
+                               }
+                               return parent::multi_query($query);
+                       }
+               }
+       }
+
+
+
        class Driver extends SqlDriver {
                static array $extensions = array("PgSQL", "PDO_PgSQL");
                static string $jush = "pgsql";
index f0833d7fbd43dd6eb4d5c188d8c2a7c3cb34f629..ae2ff7ee05ceecf61a3ca97bafea365462e7fe44 100644 (file)
@@ -72,10 +72,13 @@ if (!$error && $_POST) {
 
                while ($query != "") {
                        if (!$offset && preg_match("~^$space*+DELIMITER\\s+(\\S+)~i", $query, $match)) {
-                               $delimiter = $match[1];
+                               $delimiter = preg_quote($match[1]);
                                $query = substr($query, strlen($match[0]));
+                       } elseif (!$offset && JUSH == 'pgsql' && preg_match("~^($space*+COPY\\s+)[^;]+\\s+FROM\\s+stdin;~i", $query, $match)) {
+                               $delimiter = "\n\\\\\\.\r?\n";
+                               $offset = strlen($match[0]);
                        } else {
-                               preg_match('(' . preg_quote($delimiter) . "\\s*|$parse)", $query, $match, PREG_OFFSET_CAPTURE, $offset); // should always match
+                               preg_match("($delimiter\\s*|$parse)", $query, $match, PREG_OFFSET_CAPTURE, $offset); // always matches
                                list($found, $pos) = $match[0];
                                if (!$found && $fp && !feof($fp)) {
                                        $query .= fread($fp, 1e5);
@@ -85,14 +88,15 @@ if (!$error && $_POST) {
                                        }
                                        $offset = $pos + strlen($found);
 
-                                       if ($found && rtrim($found) != $delimiter) { // find matching quote or comment end
+                                       if ($found && !preg_match("(^$delimiter)", $found)) { // find matching quote or comment end
                                                $c_style_escapes = driver()->hasCStyleEscapes() || (JUSH == "pgsql" && ($pos > 0 && strtolower($query[$pos - 1]) == "e"));
 
-                                               $pattern = ($found == '/*' ? '\*/'
-                                                       : ($found == '[' ? ']'
-                                                       : (preg_match('~^-- |^#~', $found) ? "\n"
-                                                       : preg_quote($found) . ($c_style_escapes ? "|\\\\." : "")))
-                                               );
+                                               $pattern =
+                                                       ($found == '/*' ? '\*/' :
+                                                       ($found == '[' ? ']' :
+                                                       (preg_match('~^-- |^#~', $found) ? "\n" :
+                                                       preg_quote($found) . ($c_style_escapes ? '|\\\\.' : ''))))
+                                               ;
 
                                                while (preg_match("($pattern|\$)s", $query, $match, PREG_OFFSET_CAPTURE, $offset)) {
                                                        $s = $match[0][0];
@@ -108,7 +112,7 @@ if (!$error && $_POST) {
 
                                        } else { // end of a query
                                                $empty = false;
-                                               $q = substr($query, 0, $pos);
+                                               $q = substr($query, 0, $pos + ($delimiter[0] == "\n" ? 3 : 0)); // 3 - pass "\n\\." to PostgreSQL COPY
                                                $commands++;
                                                $print = "<pre id='sql-$commands'><code class='jush-" . JUSH . "'>" . adminer()->sqlCommandQuery($q) . "</code></pre>\n";
                                                if (JUSH == "sqlite" && preg_match("~^$space*+ATTACH\\b~i", $q, $match)) {