2016-11-02 14 views
5

Tôi có một ứng dụng truy cập cơ sở dữ liệu PostgreSQL và cần đọc một số dữ liệu nhị phân lớn của nó tùy thuộc vào một số xử lý cần thiết. Điều này có thể là hàng trăm MB hoặc thậm chí một số GB dữ liệu. Xin vui lòng không thảo luận về việc sử dụng hệ thống tập tin thay vào đó hoặc như vậy, đó là cách nó là ngay bây giờ.Làm thế nào để phân lớp IO :: Xử lý để có được một xử lý tập tin cấp độ thấp mà không cần phải có một tập tin hoặc bộ nhớ?

Dữ liệu đó chỉ đơn giản là các tệp thuộc nhiều loại khác nhau, ví dụ: nó có thể là một thùng chứa Zip hoặc một số loại lưu trữ khác. Một số xử lý cần thiết là liệt kê nội dung của Zip, thậm chí có thể trích xuất một số thành viên để xử lý tiếp, có thể băm dữ liệu được lưu trữ ... Cuối cùng dữ liệu được đọc nhiều lần, nhưng chỉ viết một lần để lưu trữ nó.

Tất cả các thư viện Perl tôi sử dụng đều có thể làm việc với các tay cầm tệp, một số có số IO::Handle, một số khác có IO::String hoặc IO::Scalar, một số khác chỉ với trình xử lý tệp cấp thấp. Vì vậy, những gì tôi đã làm là tạo một lớp con của IO::HandleIO::Seekable hoạt động như trình bao bọc cho các phương pháp tương ứng xung quanh DBD::Pg. Trong CTOR tôi tạo một kết nối tới cơ sở dữ liệu, mở một số LOID được cung cấp để đọc và lưu trữ xử lý được cung cấp bởi Postgres trong cá thể. Đối tượng xử lý của riêng tôi sau đó được chuyển tiếp tới bất cứ ai có thể làm việc với một trình xử lý tệp như vậy và có thể trực tiếp đọc và tìm kiếm trong blob do Postgres cung cấp.

Sự cố là libs sử dụng các trình xử lý tệp cấp thấp hoặc các thao tác xử lý tệp cấp thấp trên IO::Handle. Digest::MD5 có vẻ là một, Archive::Zip một số khác. Digest::MD5croak s và nói với tôi rằng không có tay cầm nào được cung cấp, Archive::Zip mặt khác cố gắng tạo một tay cầm mới, riêng của tôi, gọi số IO::Handle::fdopen và không thành công trong trường hợp của tôi.

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; 
} 

Tôi đoán vấn đề là bản sao cấp thấp của tay cầm, loại bỏ trường hợp của riêng tôi, vì vậy không có trường hợp nào nữa có kết nối cơ sở dữ liệu của tôi và tất cả nội dung đó.

Vì vậy, thậm chí có thể trong trường hợp của tôi để cung cấp một số IO::Handle thành công nào có thể được sử dụng ở bất kỳ nơi nào có xử lý tệp cấp thấp không?

Ý tôi là, tôi không có một tập tin thực sự xử lý, tôi có một đối tượng chỉ nơi các cuộc gọi phương thức được bao bọc theo các phương thức Postgres tương ứng của chúng. Tất cả dữ liệu đó cần phải được lưu trữ ở đâu đó, gói cần phải được thực hiện vv

Tôi đã cố gắng làm những gì người khác đang làm, như IO::String, sử dụng thêm tie chẳng hạn. Nhưng cuối cùng trường hợp sử dụng là khác nhau, bởi vì Perl có thể tạo ra một tập tin cấp thấp thực sự xử lý cho một số bộ nhớ nội bộ của riêng mình. Một cái gì đó mà không được hỗ trợ ở tất cả trong trường hợp của tôi. Tôi cần phải giữ cho trường hợp của tôi xung quanh, bởi vì chỉ có biết về xử lý để cơ sở dữ liệu, vv

Sử dụng tay cầm của tôi như một phương pháp gọi read và như vậy, nhưng tôi muốn thực hiện thêm một chút và tương thích hơn với bất kỳ ai không mong đợi làm việc trên các đối tượng IO::Handle. Giống như IO::String hoặc File::Temp có thể được sử dụng như các trình xử lý tệp cấp thấp.

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

sử dụng rất ít xử lý như đối tượng. 'tie' chắc chắn là con đường để đi nếu bạn muốn sử dụng một đối tượng như một tập tin xử lý. Nó liên kết toàn bộ một đối tượng với một tay cầm, vì vậy tuyên bố rằng nó sẽ không cho phép nó giữ một cơ sở dữ liệu xử lý là khá sai. – ikegami

+0

Tôi đã thử phương pháp 'tie' giống' IO :: String' đang sử dụng và 'Digest :: MD5'' croak'ed mà bây giờ xử lý được đưa ra. Mà nó không có 'tie'. Và nguồn của nó chỉ là một 'if (fh) ... khác croak (...).' Và nhìn vào 'fdopen' được sử dụng bởi' Archive :: Zip', nó không tạo ra một tay cầm riêng mà không có tôi để 'tie' bất cứ điều gì? 'fdopen' không được gọi trên cá thể của tôi, nhưng' IO :: File' được tạo bởi 'Archive :: Zip'. –

+1

Sử dụng một quy trình hoặc một tiến trình con để nạp một đường ống, sau đó. – ikegami

Trả lời

1

Có một cách để giải quyết vấn đề này, nhưng có một chút lạ.Yêu cầu của bạn về cơ bản gấp ba lần, nếu tôi đang đọc mã và nhận xét của bạn một cách chính xác:

  1. Làm việc như xử lý tệp thông thường/IO :: Xử lý càng nhiều càng tốt, thực tế là nó không phải là tệp thật cho người dùng.
  2. Làm việc với Archive::Zip, được triển khai chủ yếu trong Perl thông thường và mã này được gọi là mã IO::Handle::fdopen mà bạn đã đăng, không sao chép được tay cầm vì đó không phải là tay cầm thực sự.
  3. Làm việc với Digest::MD5, được triển khai trong XS sử dụng PerlIO. Kể từ tie thủ đoạn dựa trên và perl trong bộ nhớ "giả" filehandles không sử dụng được ở mức độ đó, nó tricker hơn 2.

Bạn có thể đạt được cả ba những bằng PerlIO layers with PerlIO::via. Mã này tương tự như những gì bạn viết với tie (triển khai một số phương thức hành vi bắt buộc). Ngoài ra, bạn có thể khai thác chức năng "biến mở dưới dạng tệp" của open và chức năng IO::Seekable + IO::Handle được cuộn trước của IO::File để đơn giản hóa yêu cầu 1 ở trên (làm cho nó có thể sử dụng được trong mã Perl giống như cách đối tượng bình thường IO::Handle).

Dưới đây là gói mẫu thực hiện những gì bạn cần. Nó có một vài cảnh báo:

  • Nó không mở rộng mã của bạn hoặc tương tác với DB; nó chỉ sử dụng mảng array lines được cung cấp dưới dạng dữ liệu tệp. Nếu điều này có vẻ như nó phù hợp với trường hợp sử dụng của bạn, bạn nên thích ứng với nó để làm việc với một DB.
  • Nó thực hiện tối thiểu tối thiểu cần thiết để làm việc cho các tập quán demo bên dưới. Bạn sẽ cần phải triển khai nhiều phương thức hơn để làm cho nó "cư xử tốt" trong hầu hết các trường hợp không phải là bản demo (ví dụ: nó không biết gì về SEEK, EOF, BINMODE, SEEK, et al). Lưu ý rằng các đối số/hành vi mong đợi của các hàm bạn sẽ triển khai không giống như những gì bạn muốn làm cho tie hoặc Tie::Handle; "giao diện" có cùng tên, nhưng các hợp đồng khác nhau.
  • Tất cả các phương pháp nhận được người invocant nên không sử dụng nó dưới dạng trực tiếp hashref/globref; họ nên theo dõi tất cả các trạng thái tùy chỉnh trong trường tổng hợp *$self->{args}. Điều này là do đối tượng may mắn được tạo hai lần (một lần được PerlIO ban phước và một lần bởi SUPER::new), vì vậy, trạng thái cần phải được chia sẻ thông qua tham chiếu được chia sẻ. Nếu bạn thay thế trường args hoặc thêm/xóa bất kỳ trường nào khác, chúng sẽ chỉ hiển thị với tập hợp các phương pháp đã tạo chúng: phương pháp PerlIO hoặc phương pháp đối tượng "bình thường". Xem nhận xét trong hàm tạo để biết thêm thông tin.
  • PerlIO nói chung không phải là siêu dễ dàng để nhìn vào. Nếu một cái gì đó không hoạt động bên dưới một hoạt động cấp thấp như sysread hoặc <$fh>, nhiều mã sẽ bị lỗi hoặc làm những điều không mong muốn, vì nó xem xét các chức năng đó không thể chết/nguyên tử-ish ở cấp độ hoạt động. Tương tự như vậy, khi gây rối với PerlIO, các chế độ thất bại sẽ dễ dàng thoát khỏi cõi "chết hoặc trả về một giá trị lỗi" và kết thúc trong lĩnh vực "phân đoạn hoặc phân đoạn lõi", đặc biệt nếu nhiều quá trình (fork()) hoặc các chủ đề liên quan (những trường hợp kỳ lạ là, ví dụ, tại sao các mô-đun dưới đây không được thực hiện xung quanh IO::File->new; theo sau là $file->open(... "via:<($class)"); nó cốt lõi bãi cho tôi, không có ý tưởng tại sao).TL, DR gỡ lỗi lý do tại sao công cụ sai ở cấp độ PerlIO có thể gây phiền nhiễu, bạn đã được cảnh báo :)
  • Bất kỳ mã XS nào xử lý tập tin thô hoặc không hoạt động thông qua các chức năng perlapi PerlIO sẽ không tôn trọng điều này. Không may là rất nhiều trong số đó, nhưng thường không có trong các mô-đun CPAN được hỗ trợ tốt. Về cơ bản, Digest::MD5 không hoạt động với các tay cầm được gắn bởi vì nó hoạt động ở mức độ "bên dưới" ma thuật của tie; PerlIO là một cấp "thấp" hơn thế, nhưng vẫn có một cấp độ khác dưới đây.
  • Mã này hơi lộn xộn và chắc chắn có thể được dọn sạch. Đặc biệt, nó có thể khá đẹp hơn một chút đối với đối tượng lớp trực tiếp, bỏ qua tất cả các công cụ giả gián tiếp đối tượng giả, và sau đó bọc nó trong một IO :: Xử lý một số cách khác, ví dụ: qua IO::Wrap.
  • PerlIO không hoạt động hoặc hoạt động khác biệt trên nhiều Perls cũ hơn nhiều.

gói:

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; 

Ví dụ sử dụng:

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

Hiện tại không có thời gian để kiểm tra cách tiếp cận của bạn trong những gì tôi đã có, nhưng nó trông khá tốt, do đó, chấp nhận nó như là câu trả lời anyway. –

Các vấn đề liên quan