2016-11-02 12 views
5

У меня есть приложение, которое обращается к базе данных PostgreSQL и должно читать некоторые большие двоичные данные из него в зависимости от некоторой необходимой обработки. Это может быть сотни МБ или даже несколько ГБ данных. Пожалуйста, не обсуждайте использование файловых систем или таких, как сейчас.Как подкласс IO :: Обрабатывать, чтобы правильно получить дескриптор файла низкого уровня без наличия файла или памяти?

Эти данные являются просто файлами различных типов, например. это может быть Zip-контейнер или какой-то другой архив. Некоторая необходимая обработка - это список содержимого Zip, возможно, даже извлечение некоторых элементов для дальнейшей обработки, возможно, хэш хранящихся данных ... В конце данные считываются несколько раз, но записываются только один раз, чтобы сохранить их.

Все LIBS Perl я использую в состоянии работать с ручками файлов, некоторые с IO::Handle, другие с IO::String или IO::Scalar, некоторые другие только с ручками файлов низкого уровня. Итак, что я сделал, создайте подкласс IO::Handle и IO::Seekable, который действует как обертка для соответствующих методов около DBD::Pg. В CTOR я создаю подключение к базе данных, откройте некоторый предоставленный LOID для чтения и хранения дескриптора, предоставленного Postgres в экземпляре. Мой собственный объект дескриптора затем перенаправляется тому, кто может работать с таким дескриптором файла и может напрямую читать и искать в блоке, предоставленном Postgres.

Проблема заключается в том, что библиотеки libs используют дескрипторы файлов низкого уровня или операции с файлами низкого уровня на IO::Handle. Digest::MD5 похоже один, Archive::Zip другой один. Digest::MD5croak s и говорит мне, что никакой ручки не было предоставлено, 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::String или File::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; 
+0

Очень мало использует ручки в качестве объектов. 'tie' - это определенно способ пойти, если вы хотите использовать объект в качестве дескриптора файла. Он связывает весь объект с дескриптором, поэтому утверждает, что он не позволил бы ему хранить дескриптор базы данных. – ikegami

+0

Я уже пробовал один и тот же подход '' tie' 'IO :: String' и' Digest :: MD5' 'croak'ed, который теперь обрабатывается. Что это не без «галстука». И его источник - это только 'if (fh) ... else croak (...).' И глядя на 'fdopen', используемый' Archive :: Zip', не создает ли он собственный дескриптор, «связывать» что-нибудь? 'fdopen' не вызывается в моем экземпляре, а' IO :: File', созданный 'Archive :: Zip' сам по себе. –

+1

Используйте поток или дочерний процесс для подачи трубы. – ikegami

ответ

1

Существует такой способ, но это немного странно.Ваши требования в основном в три раза, если я читаю код и комментарии правильно:

  1. Работа, как обычный файл ручка/IO :: Handle объекта настолько, насколько это возможно, тот факт, что это не настоящий файл невидимого для пользователя.
  2. Работает с Archive::Zip, который реализован в основном на обычном Perl и вызывает код IO::Handle::fdopen, который вы отправили, что не позволяет дублировать дескриптор, поскольку это не настоящий дескриптор.
  3. Работает с Digest::MD5, который реализован в XS с использованием PerlIO. Поскольку tie -На уловок и Perl в памяти «поддельные» дескрипторы файлов не могут быть использованы на этом уровне, это Tricker чем 2.

Вы можете достичь все три из них с помощью PerlIO layers with PerlIO::via. Код похож на то, что вы пишете с помощью tie (реализуйте некоторые необходимые методы поведения). Кроме того, вы можете использовать функциональность «open variable as file» от open и предварительно прокатанную функциональность IO::Seekable + IO::Handle от IO::File, чтобы упростить выполнение требования 1 выше (сделать его пригодным для использования в коде Perl таким же образом, как и обычные объекты IO::Handle).

Ниже приведен пример пакета, который делает то, что вам нужно. Он имеет несколько оговорок:

  • Он не расширяет ваш код и вообще не взаимодействует с БД; он просто использует поставляемый файл lines arrayref в качестве файла. Если это похоже на то, что он подходит для вашего случая использования, вы должны адаптировать его для работы с БД.
  • Он использует голый минимум, необходимый для работы нижеприведенных демонстрационных приложений. Вам нужно будет внедрить намного больше методов, чтобы сделать его «хорошо себя вести» в большинстве случаев без демонстрации (например, он ничего не знает о SEEK, EOF, BINMODE, SEEK и др.). Имейте в виду, что аргументы/ожидаемое поведение функций, которые вы будете внедрять, не то же самое, что вы делали бы для tie или Tie::Handle; «интерфейс» имеет одинаковые имена, но разные контракты.
  • Все методы, которые получают invocant, должны не использовать его как hashref/globref напрямую; они должны отслеживать все пользовательские состояния в поле шара *$self->{args}. Это связано с тем, что благословенный объект создается дважды (один раз благословлен PerlIO и один раз SUPER::new), поэтому состояние необходимо обмениваться с помощью общей ссылки. Если вы замените поле args или добавите/удалите любые другие поля, они будут видны только для набора методов, которые их создали: либо методы PerlIO, либо методы «нормального» объекта. Дополнительную информацию см. В комментарии в конструкторе.
  • PerlIO в целом не очень легко понять. Если что-то не работает под низкоуровневой операцией, например sysread или <$fh>, много кода будет искажать или делать неожиданные вещи, поскольку оно считает, что эти функции не могут умереть/атомарно на рабочем уровне. Точно так же, когда возиться с PerlIO, для режимов отказа легко выйти из области «умирать или возвращать значение ошибки» и заканчиваться в области «segfault или core dump», особенно если задействованы несколько процессов (fork()) или потоков (эти странные случаи, например, почему нижний модуль не реализован около IO::File->new;, за которым следует $file->open(... "via:<($class)"), это ядро ​​для меня, не знаю, почему).TL; DR отладка, почему материал не соответствует требованиям PerlIO, может быть раздражающим, вы были предупреждены :)
  • Любой XS-код, который обращается к необработанному файловому дескриптору или не работает через функции PerlIO perlapi, не будет соблюдать это. К сожалению, есть много таких, но обычно не общих, хорошо поддерживаемых модулей CPAN. В принципе, Digest::MD5 не работает со связанными ручками, потому что он работает на уровне «ниже» tie 's magic; PerlIO - это один уровень «ниже», но есть еще один уровень ниже.
  • Этот код немного грязный и, безусловно, может быть очищен. В частности, было бы довольно неплохо с open() слоистым объектом, пропустить все странные объекты псевдо-косвенного объекта и затем обернуть его в ручку IO :: Handle другим способом, например. через IO::Wrap.
  • PerlIO не работает или работает по-другому, на многих более старых Perls.

Пакет:

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); 
+0

В настоящее время у вас нет времени, чтобы проверить свой подход в том, что у меня уже есть, но он выглядит неплохо, поэтому я все равно принимаю его как ответ. –