File Coverage

blib/lib/FIAS/SQL.pm
Criterion Covered Total %
statement 29 259 11.2
branch 0 108 0.0
condition 0 43 0.0
subroutine 10 39 25.6
pod 18 23 78.2
total 57 472 12.0


line stmt bran cond sub pod time code
1             package FIAS::SQL;
2             $FIAS::SQL::VERSION = '0.08';
3             # ABSTRACT: Модуль для минимальной работы с данными из базы ФИАC https://fias.nalog.ru/FiasInfo.aspx
4              
5 1     1   707 use strict;
  1         1  
  1         24  
6 1     1   3 use warnings;
  1         2  
  1         19  
7 1     1   506 use utf8;
  1         12  
  1         4  
8              
9 1     1   1363 use DBI;
  1         14406  
  1         58  
10 1     1   585 use XBase;
  1         18093  
  1         31  
11 1     1   630 use LWP::UserAgent;
  1         38754  
  1         37  
12 1     1   7 use Carp qw ( confess );
  1         2  
  1         44  
13 1     1   574 use Encode qw ( decode );
  1         8537  
  1         62  
14 1     1   472 use Readonly;
  1         3144  
  1         42  
15 1     1   10 use v5.10;
  1         4  
16              
17              
18             # Описываем уровни адресных объектов в человекочитаемом виде.
19             # Уровень объекта может принимать только нижеперечисленные значения
20             # Условно выделены следующие уровни адресных объектов:
21             Readonly our $LEVELS => {
22             # 1 – уровень региона
23             region => '1',
24             # 3 – уровень района
25             district => '3',
26             # 35 – уровень городских и сельских поселений
27             settlement => '35',
28             # 4 – уровень города
29             town => '4',
30             # 6 – уровень населенного пункта
31             inhabitet_locality => '6',
32             # 65 – планировочная структура
33             planning_structure => '65',
34             # 7 – уровень улицы
35             street => '7',
36             # 75 – земельный участок
37             stead => '75',
38             # 8 – здания, сооружения, объекта незавершенного строительства
39             structure => '8',
40             # 9 – уровень помещения в пределах здания, сооружения
41             premises => '9'
42             };
43              
44              
45             sub new {
46 0     0 1   my ( $class, %params ) = @_;
47              
48             # параметры подключения к базе
49 0           my $db_connection = delete $params{db_connection};
50              
51             my $dsn = delete $db_connection->{dsn}
52 0 0         or confess 'no dsn!';
53              
54 0           my $login = delete $db_connection->{login};
55 0           my $password = delete $db_connection->{password};
56              
57 0           my $self;
58              
59             $self->{dbh} = DBI->connect( $dsn, $login, $password, $params{additional_connection_params} )
60 0 0         or confess $DBI::errstr;
61              
62 0           bless $self, $class;
63              
64 0           return $self;
65             }
66              
67             sub load_files {
68 0     0 1   my ( $self, %params ) = @_;
69              
70 0           my $directory = delete $params{directory};
71 0           my $update_flag = delete $params{update};
72              
73 0 0         opendir( my $dh, $directory ) or confess $!;
74              
75             # Получаем все файлы из папки $directory
76 0           my @files;
77 0           while ( my $file = readdir( $dh ) ) {
78 0 0         push @files, $file
79             if $file =~ /\.[Dd][Bb][Ff]$/;
80             }
81 0           closedir( $dh );
82              
83             # хендлер базы
84 0           my $dbh = $self->{dbh};
85              
86             # TODO надо будет в следующей версии продумать этот момент, когда обновление будем делать
87             # перед заполнением базы хорошо бы её дропнуть
88             #$dbh->do("DROP DATABASE IF EXISTS $basename");
89             #$dbh->do("CREATE DATABASE $basename");
90             #$dbh->do("USE $basename");
91              
92             # Хеш полей базы( используем при обновлении )
93 0           my %sql_tables_names;
94             # перебираем все файлы
95 0           for my $dbf_file_name ( @files ) {
96              
97             # Получаем имя таблицы для SQL без номера региона и расширения
98             # HOUSE89.DBF превращается в HOUSE
99 0           ( my $sql_table_name = $dbf_file_name ) =~ s/\d*\.[Dd][Bb][Ff]$//;
100              
101             # Информация в данных таблицах не нужна
102 0 0 0       next if $dbf_file_name =~ /^D/
      0        
      0        
103             || $sql_table_name eq 'NORDOC'
104             || $sql_table_name eq 'STEAD'
105             || $sql_table_name eq 'NDOCTYPE';
106              
107             # Получаем доступ к таблице dbf
108 0 0         my $table = XBase->new( "$directory/$dbf_file_name" ) or confess XBase->errstr;
109              
110             # индекс последней записи в dbf
111 0           my $index_of_last_record = $table->last_record;
112             # массив типов записей в таблице dbf
113 0           my @type = $table->field_types;
114             # массив имён полей в таблице dbf
115 0           my @name = $table->field_names;
116             # массив длин полей в таблице dbf
117 0           my @len = $table->field_lengths;
118             # массив количества знаков после запятой полей 'N' в таблице dbf
119 0           my @dec = $table->field_decimals;
120              
121             # сохраняем имена таблиц для последующей обработки при обновлении
122 0 0         if ( $update_flag ) {
123 0           $sql_tables_names{ lc $sql_table_name} = 1;
124 0           $sql_table_name .= '_';
125             }
126 0           $dbh->do(_create_table( $sql_table_name, $table ) );
127              
128             # счётчик записей
129 0           my $all_records = -1;
130             # буфер записей
131 0           my @sqldata;
132              
133 0           my $fields_for_insert = '(' . lc( join( ',', _get_table_fields( $sql_table_name, $table ) ) ) . ')';
134              
135             # выдёргиваем из файла по одной записи
136 0           my $cursor = $table->prepare_select();
137 0           while ( my $record = $cursor->fetch_hashref() ) {
138 0           $all_records++;
139             # Забираем только актуальные записи
140 0 0         push @sqldata, '('. _convert_data( $record, $table, $sql_table_name ) . ')'
141             unless _check_record_actuality( $record );
142              
143             # Если есть актуальные записи и их накопилось 5000 или считали последнюю запись файла,
144             # необходимо закоммитить значения в таблицу, если конечно есть что коммитить
145 0 0 0       if ( scalar @sqldata && !( scalar @sqldata % 5000) || $all_records == $index_of_last_record ) {
      0        
146 0           my $sql_table = lc $sql_table_name;
147 0 0         $dbh->do( "REPLACE INTO $sql_table $fields_for_insert VALUES " . join(',', @sqldata ) )
148             or confess $DBI::errstr;
149 0           undef @sqldata;
150             }
151             }
152 0           print "Table $dbf_file_name copied\n";
153 0           $table->close;
154             }
155              
156             # после обработки файлов, при обновлении заменяем файлы
157 0 0         if ( $update_flag ){
158 0           for my $table ( keys %sql_tables_names ) {
159 0           my $tmp_table = $table . '_';
160 0 0         $dbh->do( "DROP TABLE $table" ) or confess $DBI::errstr;
161 0 0         $dbh->do( "ALTER TABLE $tmp_table RENAME $table") or confess $DBI::errstr;
162             }
163             }
164             # Доработка таблиц для уменьшения и убыстрания
165             # TODO перенести в функции создания таблиц
166 0           $dbh->do('ALTER TABLE addrob DROP COLUMN nextid');
167 0           $dbh->do('ALTER TABLE house DROP COLUMN enddate');
168 0           $dbh->do('ALTER TABLE room DROP COLUMN nextid');
169             }
170              
171             sub get_address_objects {
172 0     0 1   my ( $self, %params ) = @_;
173              
174 0           my $parentguid = $params{parentguid};
175              
176             # Разрешаются только вышеперечисленные уровни
177             confess 'level is incorrect'
178 0 0 0       if $params{aolevel} && !$LEVELS->{ $params{aolevel} };
179              
180             # Забираем числовое значения уровня для выборкиж
181 0           my $aolevel;
182             $aolevel = $LEVELS->{ $params{aolevel} }
183 0 0         if $params{aolevel};
184              
185 0           my $sqlquery = 'SELECT * FROM addrob';
186 0           my ( @where, @binds );
187              
188 0 0 0       $sqlquery .= ' WHERE '
189             if ( $aolevel || $parentguid);
190              
191             # Добавляем WHERE в зависимости от пришедших параметров
192 0 0         if ( $aolevel ) {
193 0           push @where, 'aolevel = ?';
194 0           push @binds, $aolevel;
195             }
196              
197 0 0         if ( $parentguid ) {
198             # если приходит мультизначение
199 0 0         if ( ref $parentguid eq 'ARRAY' ) {
200 0           push @where, 'parentguid in (' . join (',', ('?') x @$parentguid) . ')';
201 0           push @binds, @$parentguid;
202             }
203             else {
204 0           push @where, 'parentguid = ?';
205 0           push @binds, $parentguid;
206             }
207             }
208              
209 0           $sqlquery .= join( ' AND ', @where );
210              
211             return ref $parentguid eq 'ARRAY'
212             ? $self->{dbh}->selectall_arrayref( $sqlquery, undef, @binds )
213 0 0         : $self->{dbh}->selectall_arrayref( $sqlquery, { Slice => {} }, @binds )
214             }
215              
216             sub get_address_objects_guids_only {
217 0     0 1   my ( $self, %params ) = @_;
218              
219 0           return [map{ $_->{aoguid} } @{ $self->get_address_objects( %params ) } ];
  0            
  0            
220             }
221              
222              
223             sub get_sublevels_for_objects {
224 0     0 1   my ( $self, $parentguid ) = @_;
225              
226             # Должен быть id родительского объекта
227 0 0         confess 'parentguid is empty!'
228             unless $parentguid;
229              
230             my $sublevels = $self->{dbh}->selectcol_arrayref(
231 0           'SELECT DISTINCT aolevel FROM addrob WHERE parentguid = ? ORDER BY aolevel',
232             undef, $parentguid);
233              
234 0           my %reversed_LEVELS = reverse %$LEVELS;
235              
236 0           return [ map { $reversed_LEVELS{ $_ } } @$sublevels ];
  0            
237             }
238             sub get_data_for_object_by_aoguid {
239 0     0 1   my ( $self, $aoguid ) = @_;
240              
241             # Должен быть id объекта
242 0 0         confess 'aoguid is empty!'
243             unless $aoguid;
244              
245             return $self->{dbh}->selectrow_hashref(
246 0           'SELECT aoguid, aolevel, offname, shortname, parentguid, postalcode FROM addrob WHERE aoguid = ?',
247             undef, $aoguid );
248             }
249             sub get_data_for_object_by_aoguid_array{
250 0     0 1   my ( $self, $aoguid ) = @_;
251              
252             # Должен быть id объекта
253 0 0         confess 'aoguid is empty!'
254             unless $aoguid;
255              
256 0           return $self->{dbh}->selectrow_array("SELECT * FROM addrob where aoguid = '$aoguid'");
257             }
258             sub add_object_to_base {
259 0     0 1   my ( $self, $table, @values ) = @_;
260              
261             # эскейпим данные
262 0           $_ = "\'$_\'" for @values;
263              
264             # собираем данные региона в строку
265 0           my $values_string = join(',', @values );
266              
267 0 0         $self->{dbh}->do( "INSERT INTO $table VALUES($values_string)")
268             or confess $DBI::errstr; ;
269              
270             }
271              
272             sub get_houses_of_address_objects {
273 0     0 1   my ( $self, $aoguid ) = @_;
274              
275 0           my $sqlquery = 'SELECT * FROM house ';
276              
277 0 0         $sqlquery .= ' WHERE aoguid '
278             if $aoguid;
279              
280 0           my @binds;
281 0 0         if ( $aoguid ) {
282 0 0         if ( ref $aoguid eq 'ARRAY' ){
283 0           $sqlquery .= ' in (' . join (',', ('?') x @$aoguid) . ')';
284 0           push @binds, @$aoguid;
285             }
286             else {
287              
288 0           $sqlquery .= ' = ?';
289 0           push @binds, $aoguid;
290             }
291             }
292              
293             return ref $aoguid eq 'ARRAY'
294             ? $self->{dbh}->selectall_arrayref( $sqlquery, undef, @binds )
295 0 0         : $self->{dbh}->selectall_arrayref( $sqlquery, { Slice => {} }, @binds )
296              
297             }
298              
299             sub get_data_for_house_by_houseguid {
300 0     0 1   my ( $self, $houseguid ) = @_;
301              
302             # Должен быть id дома
303 0 0         confess 'houseguid is empty!'
304             unless $houseguid;
305              
306             return $self->{dbh}->selectrow_hashref(
307 0           'SELECT housenum, buildnum, strucnum, aoguid, houseguid, postalcode FROM house WHERE houseguid = ?',
308             undef, $houseguid );
309             }
310              
311             sub get_houses_guids_only {
312 0     0 0   my ( $self, %params ) = @_;
313              
314 0           return [map{ $_->{houseguid} } @{ $self->get_houses_of_address_objects() } ];
  0            
  0            
315             }
316              
317             sub get_rooms_of_address_objects {
318 0     0 1   my ( $self, $houseguid ) = @_;
319              
320             # ID дома не может быть пустым
321 0 0         confess 'houseguid is empty!' unless $houseguid;
322              
323 0           my $sqlquery = 'SELECT * FROM room WHERE houseguid ';
324              
325 0           my @binds;
326              
327 0 0         if ( ref $houseguid eq 'ARRAY' ){
328 0           $sqlquery .= ' in (' . join (',', ('?') x @$houseguid) . ')';
329 0           push @binds, @$houseguid;
330             }
331             else {
332              
333 0           $sqlquery .= ' = ?';
334 0           push @binds, $houseguid;
335             }
336              
337             return ref $houseguid eq 'ARRAY'
338             ? $self->{dbh}->selectall_arrayref( $sqlquery, undef, @binds )
339 0 0         : $self->{dbh}->selectall_arrayref( $sqlquery, { Slice => {} }, @binds );
340             }
341              
342             sub get_data_for_room_by_roomguid {
343 0     0 1   my ( $self, $roomguid ) = @_;
344              
345             # Должен быть id дома
346 0 0         confess 'roomguid is empty!'
347             unless $roomguid;
348              
349             return $self->{dbh}->selectrow_hashref(
350 0           'SELECT flatnumber, flattype.shortname AS flattype, houseguid, roomguid, postalcode, roomnumber, roomtype.shortname AS roomtype FROM room
351             JOIN flattype ON flattype.fltypeid = room.flattype
352             JOIN roomtype ON roomtype.rmtypeid = room.roomtype
353             WHERE roomguid = ?',
354             undef, $roomguid );
355             }
356              
357             sub get_data_for_table {
358 0     0 0   my ( $self, $table_name ) = @_;
359              
360 0           return $self->{dbh}->selectall_arrayref( "select * from $table_name" );
361             }
362              
363             sub get_parent_record_chain_by_aoguid {
364 0     0 0   my ( $self, $aoguid ) = @_;
365              
366             # Должен быть id
367 0 0         confess 'aoguid is empty!'
368             unless $aoguid;
369              
370 0           my $levels_chain;
371 0           my %reversed_LEVELS = reverse %$LEVELS;
372              
373 0           my $ao_data = $self->get_data_for_object_by_aoguid( $aoguid );
374 0 0         confess 'wrong aoguid!' unless $ao_data;
375 0           push @$levels_chain, { guid => $ao_data->{aoguid}, level => $reversed_LEVELS{ $ao_data->{aolevel} } };
376              
377 0           while( $reversed_LEVELS{ $ao_data->{aolevel} } ne 'region' ) {
378 0           $ao_data = $self->get_data_for_object_by_aoguid( $ao_data->{parentguid} );
379 0           push @$levels_chain, { guid => $ao_data->{aoguid}, level => $reversed_LEVELS{ $ao_data->{aolevel} } };
380             }
381              
382 0           return $levels_chain;
383             }
384              
385              
386             sub get_parent_record_chain_by_houseguid {
387 0     0 1   my ( $self, $houseguid ) = @_;
388              
389             # Должен быть id
390 0 0         confess 'houseguid is empty!'
391             unless $houseguid;
392              
393 0           my $levels_chain;
394              
395 0           my $house_data = $self->get_data_for_house_by_houseguid( $houseguid );
396 0 0         confess 'wrong houseguid' unless $house_data;
397 0           push @$levels_chain, { guid => $house_data->{houseguid}, level => 'house' };
398 0           push @$levels_chain, @{ $self->get_parent_record_chain_by_aoguid( $house_data->{aoguid} ) };
  0            
399              
400 0           return $levels_chain;
401             }
402              
403              
404             sub get_parent_record_chain_by_roomguid {
405 0     0 1   my ( $self, $roomguid ) = @_;
406              
407             # Должен быть id
408 0 0         confess 'roomguid is empty!'
409             unless $roomguid;
410              
411 0           my $levels_chain;
412              
413 0           my $room_data = $self->get_data_for_room_by_roomguid( $roomguid );
414 0 0         confess 'wrong roomguid' unless $room_data;
415 0           push @$levels_chain, { guid => $room_data->{roomguid}, level => 'room' };
416 0           push @$levels_chain, @{ $self->get_parent_record_chain_by_houseguid( $room_data->{houseguid} ) };
  0            
417              
418 0           return $levels_chain;
419             }
420             sub set_database_version {
421 0     0 1   my ( $self, $version ) = @_;
422              
423             # хендлер базы
424 0           my $dbh = $self->{dbh};
425              
426             # создаём индексы
427 0 0         $dbh->do( 'CREATE TABLE IF NOT EXISTS version ( database_version INT (8), restriction ENUM(\'\') PRIMARY KEY )' )
428             or confess $DBI::errstr;
429 0 0         $dbh->do( "REPLACE INTO version (database_version) VALUES ( $version )" )
430             or confess $DBI::errstr;
431             }
432              
433             sub get_database_version {
434 0     0 1   my ( $self ) = @_;
435              
436             # хендлер базы
437 0           my $dbh = $self->{dbh};
438              
439 0           return $dbh->selectrow_arrayref('SELECT database_version FROM version')->[0];
440             }
441             sub get_link_for_full_database_dbf{
442 0     0 0   return 'http://fias.nalog.ru/Public/Downloads/Actual/fias_dbf.rar';
443             }
444             sub get_actual_base_version {
445 0     0 0   my $ua = LWP::UserAgent->new;
446              
447 0           my $get = $ua->get( 'http://fias.nalog.ru/Public/Downloads/Actual/VerDate.txt', ':content_file'=>'version.txt' );
448              
449 0           open my $file, '<', "version.txt";
450 0           my $firstline = <$file>;
451 0           $firstline =~ s/\.//g;
452 0           close $file;
453 0           unlink 'version.txt';
454              
455 0           return $firstline;
456             }
457              
458             sub clone_base_structure {
459 0     0 1   my ( $self, $destination_object ) = @_;
460              
461             # Забираем список таблиц из ФИАС
462 0           my @fias_tables = grep { $_ !~ /\_$/} @{ $self->show_tables() };
  0            
  0            
463             # Создаём таблицы в тестовой базе
464 0           for my $table_name ( @fias_tables ) {
465 0           my $cr_statement = $self->{dbh}->selectcol_arrayref( "SHOW CREATE TABLE $table_name", { Columns => [2] } )->[0];
466 0           $destination_object->{dbh}->do( $cr_statement );
467             }
468             }
469              
470             sub show_tables {
471 0     0 1   my ( $self ) = @_;
472              
473 0           return $self->{dbh}->selectcol_arrayref("show tables");
474             }
475              
476             sub _check_record_actuality {
477 0     0     my ( $record ) = @_;
478              
479             # если есть поля свидетельствующие о неактуальности, то возвращаем 1.
480             return 1
481             if exists $record->{ACTSTATUS} && !$record->{ACTSTATUS}
482             || exists $record->{LIVESTATUS} && !$record->{LIVESTATUS}
483             || $record->{NEXTID}
484 0 0 0       || exists $record->{ENDDATE} && $record->{ENDDATE} ne '20790606';
      0        
      0        
      0        
      0        
      0        
485              
486 0           return 0;
487             }
488              
489             sub _get_table_fields {
490 0     0     my ( $sql_table_name, $dbf_table ) = @_;
491              
492             # Изначально FIAS содержит много подробной, но не всегда необходимой информации
493             # для того, чтобы не перегружать базу, записываем только необходимые поля, которые перечислены в этом хеше
494 0           state $table_config = {
495             ADDROB => {
496             fields => [ qw/AOGUID AOLEVEL OFFNAME SHORTNAME PARENTGUID POSTALCODE NEXTID/ ],
497             #where => 'WHERE ACTSTATUS = 1 AND LIVESTATUS = 1 AND CURRSTATUS = 0'
498             },
499             HOUSE =>{
500             fields => [ qw/HOUSENUM BUILDNUM STRUCNUM AOGUID HOUSEGUID POSTALCODE STATSTATUS STRSTATUS ENDDATE/ ]
501             },
502             ROOM => {
503             fields => [ qw/FLATNUMBER FLATTYPE HOUSEGUID ROOMGUID POSTALCODE ROOMNUMBER ROOMTYPE NEXTID/ ],
504             #where => 'WHERE LIVESTATUS = 1',
505             },
506             };
507              
508             # При апдейте приходят подчёркивания, это лишннее
509 0           $sql_table_name =~ s/\_$//a;
510              
511             # Возвращаем только необходимые поля
512 0           return @{ $table_config->{ $sql_table_name }{fields} }
513 0 0         if $table_config->{ $sql_table_name };
514              
515             # Возвращаем все поля
516 0           return $dbf_table->field_names;
517             }
518              
519             sub _create_table {
520 0     0     my ( $sql_table_name, $dbf_table ) = @_;
521              
522 0           my @sqlcommand;
523              
524             # Получаем имена полей в таблице
525 0           my @field_names = _get_table_fields( $sql_table_name, $dbf_table );
526              
527             # Выясняем для каждого поля дополнительные параметры.
528 0           for my $name ( @field_names ) {
529             # Длина поля
530 0           my $len = $dbf_table->field_length( $name );
531             # Количество знаков после запятой для полей 'NUMERIC'
532 0           my $dec = $dbf_table->field_decimal( $name );
533             # Тип поля
534 0           my $type = $dbf_table->field_type( $name );
535             # Переводим имя поля в нижний регистр
536 0           $name = lc $name;
537              
538             # DBF 'C' переходит в 'CHAR'
539 0 0         if ( $type eq 'C' ) {
    0          
    0          
540 0           push @sqlcommand, "\`$name\` CHAR($len)";
541             }
542             # DBF 'D' переходит в 'DATE'
543             elsif ( $type eq 'D' ) {
544 0           push @sqlcommand, "\`$name\` DATE";
545             }
546             # DBF 'N' переходит в 'NUMERIC'
547             elsif ( $type eq 'N' ) {
548 0           push @sqlcommand, "\`$name\` NUMERIC($len, $dec)";
549             }
550             # Пришло что-то неизведанное!
551 0           else { confess "unknown $type type" }
552             }
553              
554             # имя таблицы тоже переводим в нижний регистр
555 0           $sql_table_name = lc $sql_table_name;
556              
557 0           my $create_string = "CREATE TABLE IF NOT EXISTS $sql_table_name (" . join( ', ', @sqlcommand );
558              
559 0 0         if ( $sql_table_name =~ /^house\_?$/ ) {
    0          
    0          
560 0           $create_string .= ", PRIMARY KEY \`houseguid\` (\`houseguid\`), KEY \`aoguid\`(\`aoguid\`)";
561             }
562             elsif ( $sql_table_name =~ /^room\_?$/ ) {
563 0           $create_string .= ", PRIMARY KEY \`roomguid\` (\`roomguid\`), KEY \`houseguid\`(\`houseguid\`)";
564             }
565             elsif ( $sql_table_name =~ /^addrob\_?$/ ) {
566 0           $create_string .= ", PRIMARY KEY \`aoguid\` (\`aoguid\`), KEY \`aolevel\`(\`aolevel\`), KEY \`aoguid\`(\`aoguid\`)";
567             }
568 0           $create_string .= ') MAX_ROWS=1000000000';
569 0           return $create_string;
570             }
571              
572             sub _convert_data {
573 0     0     my ( $record, $dbf_table, $sql_table_name ) = @_;
574              
575 0           my @sqlcommand;
576              
577             # Получаем имена полей в таблице
578 0           my @field_names = _get_table_fields( $sql_table_name, $dbf_table );
579              
580             # Выясняем для каждого поля дополнительные параметры.
581 0           for my $name ( @field_names ) {
582             # Тип поля
583 0           my $type = $dbf_table->field_type( $name );
584 0           my $cell;
585             # Экранируем текст
586 0 0         if ( $type eq 'C' ) {
    0          
    0          
587 0   0       $cell = "'" . _get_quoted_text( $record->{ $name } ) . "'" // '\N';
588             }
589             # Превращаем дату DBF в дату SQL
590             elsif ( $type eq 'D' ) {
591             $cell =
592             ( $record->{ $name } )
593 0 0         ? _get_formatted_date( $record->{ $name } )
594             : '\N';
595             }
596             # Получаем численное значение из DBF
597             elsif ( $type eq 'N' ) {
598 0   0       $cell = $record->{ $name } // 0;
599             }
600              
601 0           push @sqlcommand, $cell;
602             }
603             # Склеиваем данные запятыми для инсёрта
604 0           return join( ',', @sqlcommand );
605             }
606              
607             sub _get_formatted_date {
608 0     0     my ( $date ) = @_;
609              
610 0 0         $date = sprintf( '%08d', $date ) if ( length($date) < 8 );
611 0           $date =~ s/(\d{4})(\d{2})(\d{2})/\'$1-$2-$3\'/;
612              
613 0           return $date;
614             }
615              
616             sub _get_quoted_text {
617 0     0     my ( $text ) = @_;
618              
619             # Экранируем
620 0           $text =~ s/\\/\\\\/g;
621 0           $text =~ s/\'/\\\'/g;
622             # Декодируем
623 0           $text = decode( 'cp866', $text );
624              
625 0           return $text;
626             }
627             1;
628              
629             __END__