1

我正在使用旧系统,需要找到一种方法将文件插入到使用 Perl 的预先存在的 Postgres 8.2 bytea 列中。

到目前为止,我的搜索使我相信以下几点:

  1. 对此没有共识的最佳方法。
  2. lo_import 看起来很有希望,但我显然太迟钝了,无法让它工作。

我希望做类似以下的事情

my $bind1 = "foo"
my $bind2 = "123"
my $file = "/path/to/file.ext"

my $q = q{
      INSERT INTO generic_file_table
        (column_1,
         column_2,
         bytea_column
        ) 
      VALUES
      (?, ?, lo_import(?))
};
my $sth = $dbh->prepare($q);
$sth->execute($bind1, $bind2, $file);
$sth->finish();`

我的脚本没有 lo_import/bytea 部分。但是有了它,我得到了这个错误:

DBD::Pg::st 执行失败:错误:列“内容”的类型为 bytea,但表达式在字符 176 处 > 为 oid 类型 提示:您将需要重写或强制转换表达式。

我认为我做错的是我没有正确地将实际的二进制文件传递给数据库。我想我正在传递文件路径,而不是文件本身。如果这是真的,那么我需要弄清楚的是如何将文件打开/读入 tmp 缓冲区,然后使用缓冲区进行导入。

还是我离这里很远?只要它们与 Perl 5.8/DBI/PG 8.2 一起使用,我愿意接受任何指针或替代解决方案。

4

2 回答 2

3

Pg 提供了两种存储二进制文件的方法:

  • 表中的大对象pg_largeobjectoid. 通常通过lo扩展使用。可装载lo_import.

  • 常规表中的bytea 列。在 PostgreSQL 9.0 及更低版本中表示为八进制转义\000\001\002fred\004,或在 Pg 9.1 及更高版本中默认为十六进制转义,例如\x0102. 该设置允许您在具有格式的版本中bytea_output选择escape(八进制)和格式。hexhex

您正在尝试使用lo_import将数据加载到bytea列中。那是行不通的。

您需要做的是发送 PostgreSQL 正确转义的 bytea 数据。在受支持的当前 PostgreSQL 版本中,您只需将其格式化为十六进制,\x在前面敲一个,就可以了。在您的版本中,您必须将其作为八进制反斜杠序列转义,并且(因为您使用的是不使用的旧 PostgreSQL standard_conforming_strings)可能也必须将反斜杠加倍。

这个邮件列表帖子提供了一个很好的示例,可以在您的版本上运行,后续消息甚至解释了如何修复它以使其也适用于史前较少的 PostgreSQL 版本。它展示了如何使用参数绑定来强制 bytea 引用。

基本上,您需要读取文件数据。您不能只将文件名作为参数传递 - 数据库服务器将如何访问本地文件并读取它?它会在服务器上寻找路径。

读入数据后,您需要将其作为 bytea 转义并将其作为参数发送到服务器。

更新:像这样:

use strict;
use warnings;
use 5.16.3;

use DBI;
use DBD::Pg;
use DBD::Pg qw(:pg_types);
use File::Slurp;

die("Usage: $0 filename") unless defined($ARGV[0]);
die("File $ARGV[0] doesn't exist") unless (-e $ARGV[0]);
my $filename = $ARGV[0];


my $dbh = DBI->connect("dbi:Pg:dbname=regress","","", {AutoCommit=>0});
$dbh->do(q{
        DROP TABLE IF EXISTS byteatest;
        CREATE TABLE byteatest( blah bytea not null );
});
$dbh->commit();

my $filedata = read_file($filename);
my $sth = $dbh->prepare("INSERT INTO byteatest(blah) VALUES (?)");
# Note the need to specify bytea type. Otherwise the text won't be escaped,
# it'll be sent assuming it's text in client_encoding, so NULLs will cause the
# string to be truncated.  If it isn't valid utf-8 you'll get an error. If it
# is, it might not be stored how you want.
#
# So specify {pg_type => DBD::Pg::PG_BYTEA} .
#

$sth->bind_param(1, $filedata, { pg_type => DBD::Pg::PG_BYTEA });
$sth->execute();
undef $filedata;

$dbh->commit();
于 2013-07-03T23:43:50.907 回答
0

感谢那些帮助我的人。花了一段时间才确定这个。解决方案是打开文件并存储它。然后专门调出类型为 bytea 的绑定变量。这是详细的解决方案:

.....
##some variables
my datum1 = "foo";
my datum2 = "123";
my file = "/path/to/file.dat";
my $contents;

##open the file and store it
open my $FH, $file or die "Could not open file: $!";
 {
 local $/ = undef;
 $contents = <$FH>;
 };
close $FH;
print "$contents\n";

##preparte SQL
my $q = q{
  INSERT INTO generic_file_table
    (column_1, 
    column_2, 
    bytea_column
    ) 
  VALUES
  (?, ?, ?)
  };
my $sth = $dbh->prepare($q);
##bind variables and specifically set #3 to bytea; then execute.
$sth->bind_param(1,$datum1);
$sth->bind_param(2,$datum2);
$sth->bind_param(3,$contents, { pg_type => DBD::Pg::PG_BYTEA });
$sth->execute();
$sth->finish();
于 2013-07-04T20:17:18.463 回答