6
COPY table_name ( field1, field2, field3) FROM STDIN CSV;
1,2,"q w"
3,4,"a s"
5,6,d
\.

How to execute this query by PDO ?

Update:

Problem is PDO driver executes this query as statement.
For example, if you paste it into pgAdmin, it throws an error.
I need execute it in psql:

C:\Users\User>psql -e -h localhost -U postgres db_name
psql (9.1.2)
db_name=# COPY table_name ( field1, field2, field3) FROM STDIN CSV;
COPY table_name ( field1, field2, field3) FROM STDIN CSV;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1,2,"q w"
>> 3,4,"a s"
>> 5,6,d
>> \.
Community
  • 1
  • 1
cetver
  • 11,279
  • 5
  • 36
  • 56

2 Answers2

5

Thanks to this book

Note that the function presented here effectively by-passes security restrictions, which are there for a reason. Your function should check the file path and table provided against strict white list conditions. This example is also open to SQL injection as it does not quote its input correctly.

Create function which execute COPY command

CREATE OR REPLACE FUNCTION copy_from_csv_ignoring_security(table_name text, table_fieds text, file_path text, oids boolean DEFAULT false, header boolean DEFAULT false, delimeter text DEFAULT ','::text, "null" text DEFAULT ''::text, quote text DEFAULT '"'::text, escape text DEFAULT '"'::text, force_not_null text DEFAULT ''::text)
  RETURNS void AS
$BODY$

declare statement text;
begin

statement := 'COPY ' || table_name || ' (' || table_fieds || ') ' || 'FROM ''' || file_path || ''' WITH ';
IF oids THEN
 statement := statement || 'OIDS ';
end if;
statement := statement || 'DELIMITER ''' || delimeter || ''' ';
statement := statement || 'NULL ''' || "null" || ''' CSV ';
IF header THEN
 statement := statement || 'HEADER ';
end if;
statement := statement || 'QUOTE ''' || "quote" || ''' ';
statement := statement || 'ESCAPE ''' || "escape" || ''' ';
IF force_not_null <> '' THEN
statement := statement || 'FORCE NOT NULL ''' || force_not_null || ''' ';
end if;
execute statement;
end;
$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER
  COST 100;

Give rights on function

revoke all on function copy_from_csv_ignoring_security(text, text, text, boolean, boolean, text, text, text, text, text) from public;
grant execute on function copy_from_csv_ignoring_security(text, text, text, boolean, boolean, text, text, text, text, text) to db_user;

Execute from PHP

$dbh->exec('SELECT copy_from_csv_ignoring_security(...)');

===== If version >= 9.1.7 trick above doesn't works. =====

Solution:

create file .pgpass (avoid password prompt) in home directory of user which run this script.

#.pgpass contents (chmod 600 - requred)    
host:port:db_name:user_name:password

create php function, which executes meta-command

function executeMetaCommand($dbUser, $dbName, $dbPort, $command)
{    
    $command = sprintf(
        "psql -U %s -p %s -d %s -f - <<EOT\n%s\nEOT\n",
        $dbUser, $dbPort, $dbName, $command
    );
    $streams = array(
        array('pipe', 'r'),// stdin
        array('pipe', 'w'),// stdout
        array('pipe', 'w') // stderr
    );
    $process = proc_open($command, $streams, $pipes);
    if (!is_resource($process)) {
        throw new Exception("Cannot open process:\n$command");
    } else {
        list(, $stdout, $stderr) = $pipes;
        $error = stream_get_contents($stderr);
        fclose($stderr);
        if (strlen($error) > 0) {
            throw new Exception("Process error:\n$error");
        } else {
            $output = stream_get_contents($stdout);
            fclose($stdout);
            $returnCode = proc_close($process);
            if ($returnCode === -1) {
                throw new Exception("Process was completed incorrectly:\n$output");
            } else {
                return array(
                    $returnCode,
                    $output
                );
            }
        }
    }
}

//usage:
$command = sprintf("\\copy table(field1, field2) FROM '%s' WITH CSV", $filePath);
executeMetaCommand('postgres', 'test_db', '5432', $command);
Community
  • 1
  • 1
cetver
  • 11,279
  • 5
  • 36
  • 56
  • Note that the pl/pgSQL function written here is an extremely bad idea from a security point of view - it allows any user to run any `COPY FROM` command. Postgres deliberately restricts this so that you can't gain access to random bits of server configuration, and the idea is to write a privileged function **for a particular import**. The function is also wide open to SQL injection, as it is not using `quote_literal` and `quote_ident` when it constructs the dynamic SQL from its arguments. – IMSoP Feb 11 '14 at 11:39
  • agree, but 2 option is safe – cetver Feb 11 '14 at 15:30
  • I've taken the liberty of editing your answer to make clear just how unsafe that function is, because I fear people will copy-and-paste it without realising. For discussion of how to do it properly, with a slightly safer example, see my blog: http://rwec.co.uk/q/pg-copy – IMSoP Feb 16 '14 at 21:41
0

Just put the input lines into a csv file, say input.csv and then try the following:

<?php
$dbh = new PDO($dsn,$username,$pw);

$num_inserted=$dbh->exec("\copy table_name(field1,field2,field3) from /path/to/input.csv");

if($num_inserted===false)
{
  die("Unable to insert rows into table_name from input.csv\n");
}

echo("Inserted $num_inserted rows.\n");
?>
  • ERROR: must be superuser to COPY to or from a file HINT: Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone – cetver Feb 13 '12 at 14:20
  • That's strange...I can use `psql` to copy data in and out without superuser access. –  Feb 13 '12 at 14:21
  • version: 9.1 User and DB created by: CREATE ROLE db_name NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN; CREATE ROLE db_user NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT LOGIN ENCRYPTED PASSWORD 'password'; GRANT db_name TO db_user; CREATE DATABASE db_name WITH OWNER=db_user; REVOKE ALL ON DATABASE db_name FROM public; – cetver Feb 13 '12 at 14:24
  • @cetver - Edit made. You should be able to use the `exec` method to pass the `\copy` command. –  Feb 13 '12 at 14:27
  • same error: exception 'PDOException' with message 'SQLSTATE[42501]: Insufficient privilege: 7 ERROR: must be superuser to COPY to or from a file HINT: Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone. – cetver Feb 13 '12 at 14:49
  • Hmmm, in that case, try running `psql` via a `system` call. –  Feb 13 '12 at 14:50
  • If you can't run a `\copy` command via PDO and if you can't run a `copy` command from `psql`, then you'll have to contact your DBA and obtain permission to import data from a csv file. –  Feb 13 '12 at 14:52
  • I can run `copy` with `stdin` option by current user, also I can run `copy` with `file` option by POSTGRES ( superuser ) – cetver Feb 13 '12 at 14:55
  • But you should be able to use `copy` with `stdin` and then pipe in the input file... I'd talk to your DBA. –  Feb 13 '12 at 15:09
  • 2
    For anyone wondering why this doesn't work, `\copy` is a command specific to the command-line client `psql`, and cannot be executed on some other connection to the server like this. I'm not sure why it's not a syntax error, but from the message reported, I'd guess it's being interpreted as an SQL `COPY` statement, which is not the same thing. See http://www.postgresql.org/docs/current/interactive/sql-copy.html – IMSoP Feb 11 '14 at 20:22
  • This solution doesn't work for me because I cannot use the exec() call in this circumstance due to security restrictions. The OP asked a question about "copy from stdin" which has laxer security requirements, so I'm pretty sure there's a way to do it without requiring exec or psql. Some systems allow "COPY ... from STDIN" as a query on the usual connection, and pretty sure this is a more generalizable way of implementing this. – cazort Mar 04 '21 at 19:42