5

我有一个访问 PostgreSQL 数据库的应用程序,需要根据一些需要的处理从中读取一些大型二进制数据。这可能是数百 MB 甚至几 GB 的数据。请不要讨论有关使用文件系统的讨论,这就是现在的方式。

该数据只是各种类型的文件,例如它可能是一个 Zip 容器或某种其他类型的存档。一些需要的处理是列出 Zip 的内容,甚至可能提取一些成员进行进一步处理,可能对存储的数据进行哈希处理......最后数据被读取多次,但只写入一次来存储它。

我使用的所有 Perl 库都能够使用文件句柄,一些使用IO::Handle,另一些使用IO::Stringor IO::Scalar,还有一些仅使用低级文件句柄。所以我所做的是创建一个子类,IO::HandleIO::Seekable的作用类似于DBD::Pg. 在 CTOR 中,我创建了一个到数据库的连接,打开一些提供的 LOID 用于读取并将 Postgres 提供的句柄存储在实例中。然后将我自己的句柄对象转发给能够使用此类文件句柄并可以直接在 Postgres 提供的 blob 中读取和查找的任何人。

问题是使用低级文件句柄或低级文件句柄操作的库IO::HandleDigest::MD5似乎是一个,Archive::Zip另一个。Digest::MD5 croaks 并告诉我没有提供句柄,Archive::Zip另一方面尝试从我的创建一个新的自己的句柄,IO::Handle::fdopen在我的情况下调用并失败。

sub fdopen {
    @_ == 3 or croak 'usage: $io->fdopen(FD, MODE)';
    my ($io, $fd, $mode) = @_;
    local(*GLOB);

    if (ref($fd) && "".$fd =~ /GLOB\(/o) {
    # It's a glob reference; Alias it as we cannot get name of anon GLOBs
    my $n = qualify(*GLOB);
    *GLOB = *{*$fd};
    $fd =  $n;
    } elsif ($fd =~ m#^\d+$#) {
    # It's an FD number; prefix with "=".
    $fd = "=$fd";
    }

    open($io, _open_mode_string($mode) . '&' . $fd)
    ? $io : undef;
}

我想问题是句柄的低级副本,它删除了我自己的实例,因此不再有实例具有我的数据库连接和所有这些东西。

那么,在我的情况下,是否有可能提供一些IO::Handle可以成功用于需要低级文件句柄的地方?

我的意思是,我没有真正的文件句柄,我只有一个对象,其中方法调用被包装到它们相应的 Postgres 方法中,为此需要数据库句柄等。所有这些数据都需要存储在某个地方,需要完成包装等。

我试图做其他人正在做的事情,例如IO::String,它还使用tie了例如。但最终那个用例是不同的,因为 Perl 能够自己为某些内部存储器创建一个真正的低级文件句柄。在我的情况下根本不支持的东西。我需要保留我的实例,因为只有它知道数据库的句柄等。

IO::Handle像通过调用方法一样使用我的句柄read,这样的工作就像预期的那样,但我想更进一步,并且更兼容那些不希望在IO::Handle对象上工作的人。很像IO::StringFile::Temp可以用作低级文件句柄。

package ReadingHandle;

use strict;
use warnings;
use 5.10.1;

use base 'IO::Handle', 'IO::Seekable';

use Carp ();

sub new
{
  my $invocant  = shift || Carp::croak('No invocant given.');
  my $db        = shift || Carp::croak('No database connection given.');
  my $loid      = shift // Carp::croak('No LOID given.');
  my $dbHandle  = $db->_getHandle();
  my $self      = $invocant->SUPER::new();

    *$self->{'dbHandle'}  = $dbHandle;
    *$self->{'loid'}      = $loid;
  my $loidFd              = $dbHandle->pg_lo_open($loid, $dbHandle->{pg_INV_READ});
    *$self->{'loidFd'}    = $loidFd;

  if (!defined($loidFd))
  {
    Carp::croak("The provided LOID couldn't be opened.");
  }

  return $self;
}

sub DESTROY
{
  my $self = shift || Carp::croak('The method needs to be called with an instance.');

  $self->close();
}

sub _getDbHandle
{
  my $self = shift || Carp::croak('The method needs to be called with an instance.');

  return *$self->{'dbHandle'};
}

sub _getLoid
{
  my $self = shift || Carp::croak('The method needs to be called with an instance.');

  return *$self->{'loid'};
}

sub _getLoidFd
{
  my $self = shift || Carp::croak('The method needs to be called with an instance.');

  return *$self->{'loidFd'};
}

sub binmode
{
  my $self = shift || Carp::croak('The method needs to be called with an instance.');

  return 1;
}

sub close
{
  my $self      = shift || Carp::croak('The method needs to be called with an instance.');
  my $dbHandle  = $self->_getDbHandle();
  my $loidFd    = $self->_getLoidFd();

  return $dbHandle->pg_lo_close($loidFd);
}

sub opened
{
  my $self    = shift || Carp::croak('The method needs to be called with an instance.');
  my $loidFd  = $self->_getLoidFd();

  return defined($loidFd) ? 1 : 0;
}

sub read
{
  my $self    = shift || Carp::croak('The method needs to be called with an instance.');
  my $buffer  =\shift // Carp::croak('No buffer given.');
  my $length  = shift // Carp::croak('No amount of bytes to read given.');
  my $offset  = shift || 0;

  if ($offset > 0)
  {
    Carp::croak('Using an offset is not supported.');
  }

  my $dbHandle  = $self->_getDbHandle();
  my $loidFd    = $self->_getLoidFd();

  return $dbHandle->pg_lo_read($loidFd, $buffer, $length);
}

sub seek
{
  my $self    = shift || Carp::croak('The method needs to be called with an instance.');
  my $offset  = shift // Carp::croak('No offset given.');
  my $whence  = shift // Carp::croak('No whence given.');

  if ($offset < 0)
  {
    Carp::croak('Using a negative offset is not supported.');
  }
  if ($whence != 0)
  {
    Carp::croak('Using a whence other than 0 is not supported.');
  }

  my $dbHandle  = $self->_getDbHandle();
  my $loidFd    = $self->_getLoidFd();
  my $retVal    = $dbHandle->pg_lo_lseek($loidFd, $offset, $whence);
     $retVal    = defined($retVal) ? 1 : 0;

  return $retVal;
}

sub tell
{
  my $self      = shift || Carp::croak('The method needs to be called with an instance.');
  my $dbHandle  = $self->_getDbHandle();
  my $loidFd    = $self->_getLoidFd();
  my $retVal    = $dbHandle->pg_lo_lseek($loidFd);
     $retVal    = defined($retVal) ? $retVal : -1;

  return $retVal;
}

1;
4

1 回答 1

1

有一种方法可以解决这个问题,但这有点奇怪。如果我正确阅读了您的代码和注释,您的要求基本上是三重的:

  1. 尽可能像普通文件句柄/IO::Handle 对象一样工作,让用户看不到它不是真实文件的事实。
  2. 使用Archive::Zip,它主要在常规 Perl 中实现,它调用IO::Handle::fdopen您发布的代码,它无法复制句柄,因为它不是真正的句柄。
  3. 使用Digest::MD5,这是在 XS 中使用PerlIO实现的。由于tie基于 - 的技巧和 perl 内存中的“假”文件句柄在该级别上不可用,因此它比 2 更狡猾。

您可以通过将PerlIO 层与PerlIO::via. 该代码类似于您编写的代码tie(实现一些必需的行为方法)。此外,您可以利用 的“打开变量作为文件”功能open和预滚动的IO::Seekable+IO::Handle功能IO::File来简化上述要求 1 的实现(使其在 Perl 代码中的使用方式与普通IO::Handle对象相同)。

下面是一个示例包,可以满足您的需要。它有一些警告:

  • 它根本不会扩展您的代码或与数据库交互;它只是使用提供的linesarrayref 作为文件数据。如果这看起来适合您的用例,您应该调整它以与数据库一起使用。
  • 它实现了为以下演示用途工作所需的最低限度。SEEK您将需要实现更多方法以使其在大多数非演示情况下“表现良好”(例如,它对, EOF, BINMODE,SEEK等一无所知)。请注意,您将要实现的函数的参数/预期行为与您为tieor所做的不同Tie::Handle;“接口”具有相同的名称,但不同的合同。
  • 所有接收调用者的方法都不应直接将其用作 hashref/globref;他们应该跟踪*$self->{args}glob 字段中的所有自定义状态。这是因为被祝福的对象被创建了两次(一次被 PerlIO 祝福,一次被 祝福SUPER::new),所以需要通过共享引用来共享状态。如果您替换args字段或添加/删除任何其他字段,它们将仅对创建它们的方法集可见:PerlIO 方法或“普通”对象方法。有关更多信息,请参阅构造函数中的注释。
  • PerlIO 总的来说并不是很容易自省。sysread如果在or之类的低级操作下发生故障<$fh>,很多代码会出错或做意想不到的事情,因为它认为这些函数在操作级别无法死/原子。同样,当使用 PerlIO 时,故障模式很容易逃脱“死亡或返回错误值”的领域并最终进入“段错误或核心转储”的领域,尤其是fork()在涉及多个进程 () 或线程的情况下(这些奇怪的情况是,例如,为什么下面的模块没有在IO::File->new;后面实现$file->open(... "via:<($class)");它对我来说是核心转储,不知道为什么)。TL;DR 调试 PerlIO 级别出现问题的原因可能很烦人,你被警告了 :)
  • 任何处理原始文件句柄或不能通过 PerlIO perlapi 函数工作的 XS 代码都不会遵守这一点。不幸的是,其中有很多,但通常不常见,得到良好支持的 CPAN 模块。基本上,Digest::MD5它不适用于捆绑手柄,因为它的运行水平“低于”tie的魔力;PerlIO 比那个“低”一级,但还有一个低于这个级别。
  • 这段代码有点乱,当然可以清理。特别是,直接分层对象可能会更好一些open(),跳过所有奇怪的伪间接对象内容,然后将其包装在 IO::Handle 其他方式中,例如 via IO::Wrap
  • PerlIO 在许多更老的 Perl 上不工作,或者工作方式不同。

包裹:

package TiedThing;

use strict;
use warnings;
use parent "IO::File";

our @pushargs;
sub new {
    my ( $class, $args ) = @_;
    # Build a glob to be used by the PerlIO methods. This does two things:
    # 1. Gets us a place to stick a shared hashref so PerlIO methods and user-
    # -defined object methods can manipulate the same data. They must use the
    # {args} glob field to do that; new fields written will .
    # 2. Unifies the ways of addressing that across custom functions and PerlIO
    # functions. We could just pass a hashref { args => $args } into PUSHED, but
    # then we'd have to remember "PerlIO functions receive a blessed hashref,
    # custom functions receive a blessed glob" which is lame.
    my $glob = Symbol::gensym();
    *$glob->{args} = $args;
    local @pushargs = ($glob, $class);
    my $self = $class->SUPER::new(\my $unused, "<:via($class)");
    *$self->{args} = $args;
    return $self;
}

sub custom {
    my $self = shift;
    return *$self->{args}->{customvalue};
}

sub PUSHED { return bless($pushargs[0], $pushargs[1]); }

sub FILL { return shift(@{*$_[0]->{args}->{lines}}); }

1;

示例用法:

my $object = TiedThing->new({
    lines => [join("\n", 1..9, 1..9)],
    customvalue => "custom!",
});
say "can call custom method: " . $object->custom;
say "raw read with <>: " . <$object>;
my $buf;
read($object, $buf, 10);
say "raw read with read(): " . $buf;
undef $buf;
$object->read($buf, 10);
say "OO read via IO::File::read (end): " . $buf;
my $checksummer = Digest::MD5->new;;
$checksummer->addfile($object);
say "Md5 read: " . $checksummer->hexdigest;
my $dupto = IO::Handle->new;
# Doesn't break/return undef; still not usable without implementing
# more state sharing inside the object.
say "Can dup handle: " . $dupto->fdopen($object, "r");

my $archiver = Archive::Zip->new;
# Dies, but long after the fdopen() call. Can be fixed by implementing more
# PerlIO methods.
$archiver->readFromFileHandle($object);
于 2017-08-13T19:52:56.167 回答