Replace `global` keyword with `$GLOBALS`
[phpmyadmin.git] / libraries / classes / Plugins / Import / ImportCsv.php
blobdbf9d2ca593cea13ddd9f9eae3b7f58d0b3cba9e
1 <?php
2 /**
3 * CSV import plugin for phpMyAdmin
5 * @todo add an option for handling NULL values
6 */
8 declare(strict_types=1);
10 namespace PhpMyAdmin\Plugins\Import;
12 use PhpMyAdmin\File;
13 use PhpMyAdmin\Html\Generator;
14 use PhpMyAdmin\Message;
15 use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
16 use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
17 use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
18 use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
19 use PhpMyAdmin\Properties\Plugins\ImportPluginProperties;
20 use PhpMyAdmin\Util;
22 use function __;
23 use function array_shift;
24 use function array_splice;
25 use function basename;
26 use function count;
27 use function mb_strlen;
28 use function mb_strtolower;
29 use function mb_substr;
30 use function preg_grep;
31 use function preg_replace;
32 use function preg_split;
33 use function rtrim;
34 use function str_contains;
35 use function strlen;
36 use function strtr;
37 use function trim;
39 /**
40 * Handles the import for the CSV format
42 class ImportCsv extends AbstractImportCsv
44 /**
45 * Whether to analyze tables
47 * @var bool
49 private $analyze;
51 /**
52 * @psalm-return non-empty-lowercase-string
54 public function getName(): string
56 return 'csv';
59 protected function setProperties(): ImportPluginProperties
61 $this->setAnalyze(false);
63 if ($GLOBALS['plugin_param'] !== 'table') {
64 $this->setAnalyze(true);
67 $importPluginProperties = new ImportPluginProperties();
68 $importPluginProperties->setText('CSV');
69 $importPluginProperties->setExtension('csv');
70 $importPluginProperties->setOptionsText(__('Options'));
72 // create the root group that will be the options field for
73 // $importPluginProperties
74 // this will be shown as "Format specific options"
75 $importSpecificOptions = new OptionsPropertyRootGroup('Format Specific Options');
77 $generalOptions = $this->getGeneralOptions();
79 if ($GLOBALS['plugin_param'] !== 'table') {
80 $leaf = new TextPropertyItem(
81 'new_tbl_name',
82 __(
83 'Name of the new table (optional):'
86 $generalOptions->addProperty($leaf);
88 if ($GLOBALS['plugin_param'] === 'server') {
89 $leaf = new TextPropertyItem(
90 'new_db_name',
91 __(
92 'Name of the new database (optional):'
95 $generalOptions->addProperty($leaf);
98 $leaf = new NumberPropertyItem(
99 'partial_import',
101 'Import these many number of rows (optional):'
104 $generalOptions->addProperty($leaf);
106 $leaf = new BoolPropertyItem(
107 'col_names',
109 'The first line of the file contains the table column names'
110 . ' <i>(if this is unchecked, the first line will become part'
111 . ' of the data)</i>'
114 $generalOptions->addProperty($leaf);
115 } else {
116 $leaf = new NumberPropertyItem(
117 'partial_import',
119 'Import these many number of rows (optional):'
122 $generalOptions->addProperty($leaf);
124 $hint = new Message(
126 'If the data in each row of the file is not'
127 . ' in the same order as in the database, list the corresponding'
128 . ' column names here. Column names must be separated by commas'
129 . ' and not enclosed in quotations.'
132 $leaf = new TextPropertyItem(
133 'columns',
134 __('Column names:') . ' ' . Generator::showHint($hint->getMessage())
136 $generalOptions->addProperty($leaf);
139 $leaf = new BoolPropertyItem(
140 'ignore',
141 __('Do not abort on INSERT error')
143 $generalOptions->addProperty($leaf);
145 // add the main group to the root group
146 $importSpecificOptions->addProperty($generalOptions);
148 // set the options for the import plugin property item
149 $importPluginProperties->setOptions($importSpecificOptions);
151 return $importPluginProperties;
155 * Handles the whole import logic
157 * @param array $sql_data 2-element array with sql data
159 public function doImport(?File $importHandle = null, array &$sql_data = []): void
161 // $csv_replace and $csv_ignore should have been here,
162 // but we use directly from $_POST
164 $replacements = [
165 '\\n' => "\n",
166 '\\t' => "\t",
167 '\\r' => "\r",
169 $GLOBALS['csv_terminated'] = strtr($GLOBALS['csv_terminated'], $replacements);
170 $GLOBALS['csv_enclosed'] = strtr($GLOBALS['csv_enclosed'], $replacements);
171 $GLOBALS['csv_escaped'] = strtr($GLOBALS['csv_escaped'], $replacements);
172 $GLOBALS['csv_new_line'] = strtr($GLOBALS['csv_new_line'], $replacements);
174 [$GLOBALS['error'], $GLOBALS['message']] = $this->buildErrorsForParams(
175 $GLOBALS['csv_terminated'],
176 $GLOBALS['csv_enclosed'],
177 $GLOBALS['csv_escaped'],
178 $GLOBALS['csv_new_line'],
179 (string) $GLOBALS['errorUrl']
182 [$sql_template, $required_fields, $fields] = $this->getSqlTemplateAndRequiredFields(
183 $GLOBALS['db'],
184 $GLOBALS['table'],
185 $GLOBALS['csv_columns']
188 // Defaults for parser
189 $i = 0;
190 $len = 0;
191 $lastlen = null;
192 $line = 1;
193 $lasti = -1;
194 $values = [];
195 $csv_finish = false;
196 $max_lines = 0; // defaults to 0 (get all the lines)
199 * If we get a negative value, probably someone changed min value
200 * attribute in DOM or there is an integer overflow, whatever be
201 * the case, get all the lines.
203 if (isset($_REQUEST['csv_partial_import']) && $_REQUEST['csv_partial_import'] > 0) {
204 $max_lines = $_REQUEST['csv_partial_import'];
207 $max_lines_constraint = $max_lines + 1;
208 // if the first row has to be counted as column names, include one more row in the max lines
209 if (isset($_REQUEST['csv_col_names'])) {
210 $max_lines_constraint++;
213 $tempRow = [];
214 $rows = [];
215 $col_names = [];
216 $tables = [];
218 $buffer = '';
219 $col_count = 0;
220 $max_cols = 0;
221 $csv_terminated_len = mb_strlen($GLOBALS['csv_terminated']);
222 while (! ($GLOBALS['finished'] && $i >= $len) && ! $GLOBALS['error'] && ! $GLOBALS['timeout_passed']) {
223 $data = $this->import->getNextChunk($importHandle);
224 if ($data === false) {
225 // subtract data we didn't handle yet and stop processing
226 $GLOBALS['offset'] -= strlen($buffer);
227 break;
230 if ($data !== true) {
231 // Append new data to buffer
232 $buffer .= $data;
233 unset($data);
235 // Force a trailing new line at EOF to prevent parsing problems
236 if ($GLOBALS['finished'] && $buffer) {
237 $finalch = mb_substr($buffer, -1);
238 if ($GLOBALS['csv_new_line'] === 'auto' && $finalch != "\r" && $finalch != "\n") {
239 $buffer .= "\n";
240 } elseif ($GLOBALS['csv_new_line'] !== 'auto' && $finalch != $GLOBALS['csv_new_line']) {
241 $buffer .= $GLOBALS['csv_new_line'];
245 // Do not parse string when we're not at the end
246 // and don't have new line inside
247 if (
248 ($GLOBALS['csv_new_line'] === 'auto'
249 && ! str_contains($buffer, "\r")
250 && ! str_contains($buffer, "\n"))
251 || ($GLOBALS['csv_new_line'] !== 'auto'
252 && ! str_contains($buffer, $GLOBALS['csv_new_line']))
254 continue;
258 // Current length of our buffer
259 $len = mb_strlen($buffer);
260 // Currently parsed char
262 $ch = mb_substr($buffer, $i, 1);
263 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
264 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
265 $i += $csv_terminated_len - 1;
268 while ($i < $len) {
269 // Deadlock protection
270 if ($lasti == $i && $lastlen == $len) {
271 $GLOBALS['message'] = Message::error(
272 __('Invalid format of CSV input on line %d.')
274 $GLOBALS['message']->addParam($line);
275 $GLOBALS['error'] = true;
276 break;
279 $lasti = $i;
280 $lastlen = $len;
282 // This can happen with auto EOL and \r at the end of buffer
283 if (! $csv_finish) {
284 // Grab empty field
285 if ($ch == $GLOBALS['csv_terminated']) {
286 if ($i == $len - 1) {
287 break;
290 $values[] = '';
291 $i++;
292 $ch = mb_substr($buffer, $i, 1);
293 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
294 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
295 $i += $csv_terminated_len - 1;
298 continue;
301 // Grab one field
302 $fallbacki = $i;
303 if ($ch == $GLOBALS['csv_enclosed']) {
304 if ($i == $len - 1) {
305 break;
308 $need_end = true;
309 $i++;
310 $ch = mb_substr($buffer, $i, 1);
311 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
312 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
313 $i += $csv_terminated_len - 1;
315 } else {
316 $need_end = false;
319 $fail = false;
320 $value = '';
321 while (
322 ($need_end
323 && ($ch != $GLOBALS['csv_enclosed']
324 || $GLOBALS['csv_enclosed'] == $GLOBALS['csv_escaped']))
325 || (! $need_end
326 && ! ($ch == $GLOBALS['csv_terminated']
327 || $ch == $GLOBALS['csv_new_line']
328 || ($GLOBALS['csv_new_line'] === 'auto'
329 && ($ch == "\r" || $ch == "\n"))))
331 if ($ch == $GLOBALS['csv_escaped']) {
332 if ($i == $len - 1) {
333 $fail = true;
334 break;
337 $i++;
338 $ch = mb_substr($buffer, $i, 1);
339 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
340 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
341 $i += $csv_terminated_len - 1;
344 if (
345 $GLOBALS['csv_enclosed'] == $GLOBALS['csv_escaped']
346 && ($ch == $GLOBALS['csv_terminated']
347 || $ch == $GLOBALS['csv_new_line']
348 || ($GLOBALS['csv_new_line'] === 'auto'
349 && ($ch == "\r" || $ch == "\n")))
351 break;
355 $value .= $ch;
356 if ($i == $len - 1) {
357 if (! $GLOBALS['finished']) {
358 $fail = true;
361 break;
364 $i++;
365 $ch = mb_substr($buffer, $i, 1);
366 if ($csv_terminated_len <= 1 || $ch != $GLOBALS['csv_terminated'][0]) {
367 continue;
370 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
371 $i += $csv_terminated_len - 1;
374 // unquoted NULL string
375 if ($need_end === false && $value === 'NULL') {
376 $value = null;
379 if ($fail) {
380 $i = $fallbacki;
381 $ch = mb_substr($buffer, $i, 1);
382 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
383 $i += $csv_terminated_len - 1;
386 break;
389 // Need to strip trailing enclosing char?
390 if ($need_end && $ch == $GLOBALS['csv_enclosed']) {
391 if ($GLOBALS['finished'] && $i == $len - 1) {
392 $ch = null;
393 } elseif ($i == $len - 1) {
394 $i = $fallbacki;
395 $ch = mb_substr($buffer, $i, 1);
396 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
397 $i += $csv_terminated_len - 1;
400 break;
401 } else {
402 $i++;
403 $ch = mb_substr($buffer, $i, 1);
404 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
405 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
406 $i += $csv_terminated_len - 1;
411 // Are we at the end?
412 if (
413 $ch == $GLOBALS['csv_new_line']
414 || ($GLOBALS['csv_new_line'] === 'auto' && ($ch == "\r" || $ch == "\n"))
415 || ($GLOBALS['finished'] && $i == $len - 1)
417 $csv_finish = true;
420 // Go to next char
421 if ($ch == $GLOBALS['csv_terminated']) {
422 if ($i == $len - 1) {
423 $i = $fallbacki;
424 $ch = mb_substr($buffer, $i, 1);
425 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
426 $i += $csv_terminated_len - 1;
429 break;
432 $i++;
433 $ch = mb_substr($buffer, $i, 1);
434 if ($csv_terminated_len > 1 && $ch == $GLOBALS['csv_terminated'][0]) {
435 $ch = $this->readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len);
436 $i += $csv_terminated_len - 1;
440 // If everything went okay, store value
441 $values[] = $value;
444 // End of line
445 if (
446 ! $csv_finish
447 && $ch != $GLOBALS['csv_new_line']
448 && ($GLOBALS['csv_new_line'] !== 'auto' || ($ch != "\r" && $ch != "\n"))
450 continue;
453 if ($GLOBALS['csv_new_line'] === 'auto' && $ch == "\r") { // Handle "\r\n"
454 if ($i >= ($len - 2) && ! $GLOBALS['finished']) {
455 break; // We need more data to decide new line
458 if (mb_substr($buffer, $i + 1, 1) == "\n") {
459 $i++;
463 // We didn't parse value till the end of line, so there was
464 // empty one
465 if (! $csv_finish) {
466 $values[] = '';
469 if ($this->getAnalyze()) {
470 foreach ($values as $val) {
471 $tempRow[] = $val;
472 ++$col_count;
475 if ($col_count > $max_cols) {
476 $max_cols = $col_count;
479 $col_count = 0;
481 $rows[] = $tempRow;
482 $tempRow = [];
483 } else {
484 // Do we have correct count of values?
485 if (count($values) != $required_fields) {
486 // Hack for excel
487 if ($values[count($values) - 1] !== ';') {
488 $GLOBALS['message'] = Message::error(
490 'Invalid column count in CSV input on line %d.'
493 $GLOBALS['message']->addParam($line);
494 $GLOBALS['error'] = true;
495 break;
498 unset($values[count($values) - 1]);
501 $first = true;
502 $sql = $sql_template;
503 foreach ($values as $val) {
504 if (! $first) {
505 $sql .= ', ';
508 if ($val === null) {
509 $sql .= 'NULL';
510 } else {
511 $sql .= '\''
512 . $GLOBALS['dbi']->escapeString($val)
513 . '\'';
516 $first = false;
519 $sql .= ')';
520 if (isset($_POST['csv_replace'])) {
521 $sql .= ' ON DUPLICATE KEY UPDATE ';
522 foreach ($fields as $field) {
523 $fieldName = Util::backquote($field['Field']);
524 $sql .= $fieldName . ' = VALUES(' . $fieldName
525 . '), ';
528 $sql = rtrim($sql, ', ');
532 * @todo maybe we could add original line to verbose
533 * SQL in comment
535 $this->import->runQuery($sql, $sql, $sql_data);
538 $line++;
539 $csv_finish = false;
540 $values = [];
541 $buffer = mb_substr($buffer, $i + 1);
542 $len = mb_strlen($buffer);
543 $i = 0;
544 $lasti = -1;
545 $ch = mb_substr($buffer, 0, 1);
546 if ($max_lines > 0 && $line == $max_lines_constraint) {
547 $GLOBALS['finished'] = 1;
548 break;
552 if ($max_lines > 0 && $line == $max_lines_constraint) {
553 $GLOBALS['finished'] = 1;
554 break;
558 if ($this->getAnalyze()) {
559 /* Fill out all rows */
560 $num_rows = count($rows);
561 for ($i = 0; $i < $num_rows; ++$i) {
562 for ($j = count($rows[$i]); $j < $max_cols; ++$j) {
563 $rows[$i][] = 'NULL';
567 $col_names = $this->getColumnNames($col_names, $max_cols, $rows);
569 /* Remove the first row if it contains the column names */
570 if (isset($_REQUEST['csv_col_names'])) {
571 array_shift($rows);
574 $tbl_name = $this->getTableNameFromImport((string) $GLOBALS['db']);
576 $tables[] = [
577 $tbl_name,
578 $col_names,
579 $rows,
582 /* Obtain the best-fit MySQL types for each column */
583 $analyses = [];
584 $analyses[] = $this->import->analyzeTable($tables[0]);
587 * string $db_name (no backquotes)
589 * array $table = array(table_name, array() column_names, array()() rows)
590 * array $tables = array of "$table"s
592 * array $analysis = array(array() column_types, array() column_sizes)
593 * array $analyses = array of "$analysis"s
595 * array $create = array of SQL strings
597 * array $options = an associative array of options
600 /* Set database name to the currently selected one, if applicable,
601 * Otherwise, check if user provided the database name in the request,
602 * if not, set the default name
604 if (isset($_REQUEST['csv_new_db_name']) && strlen($_REQUEST['csv_new_db_name']) > 0) {
605 $newDb = $_REQUEST['csv_new_db_name'];
606 } else {
607 $result = $GLOBALS['dbi']->fetchResult('SHOW DATABASES');
609 $newDb = 'CSV_DB ' . (count($result) + 1);
612 [$db_name, $options] = $this->getDbnameAndOptions($GLOBALS['db'], $newDb);
614 /* Non-applicable parameters */
615 $create = null;
617 /* Created and execute necessary SQL statements from data */
618 $this->import->buildSql($db_name, $tables, $analyses, $create, $options, $sql_data);
620 unset($tables, $analyses);
623 // Commit any possible data in buffers
624 $this->import->runQuery('', '', $sql_data);
626 if (count($values) == 0 || $GLOBALS['error'] !== false) {
627 return;
630 $GLOBALS['message'] = Message::error(
631 __('Invalid format of CSV input on line %d.')
633 $GLOBALS['message']->addParam($line);
634 $GLOBALS['error'] = true;
637 private function buildErrorsForParams(
638 string $csvTerminated,
639 string $csvEnclosed,
640 string $csvEscaped,
641 string $csvNewLine,
642 string $errUrl
643 ): array {
644 $param_error = false;
645 if (strlen($csvTerminated) === 0) {
646 $GLOBALS['message'] = Message::error(
647 __('Invalid parameter for CSV import: %s')
649 $GLOBALS['message']->addParam(__('Columns terminated with'));
650 $GLOBALS['error'] = true;
651 $param_error = true;
652 // The default dialog of MS Excel when generating a CSV produces a
653 // semi-colon-separated file with no chance of specifying the
654 // enclosing character. Thus, users who want to import this file
655 // tend to remove the enclosing character on the Import dialog.
656 // I could not find a test case where having no enclosing characters
657 // confuses this script.
658 // But the parser won't work correctly with strings so we allow just
659 // one character.
660 } elseif (mb_strlen($csvEnclosed) > 1) {
661 $GLOBALS['message'] = Message::error(
662 __('Invalid parameter for CSV import: %s')
664 $GLOBALS['message']->addParam(__('Columns enclosed with'));
665 $GLOBALS['error'] = true;
666 $param_error = true;
667 // I could not find a test case where having no escaping characters
668 // confuses this script.
669 // But the parser won't work correctly with strings so we allow just
670 // one character.
671 } elseif (mb_strlen($csvEscaped) > 1) {
672 $GLOBALS['message'] = Message::error(
673 __('Invalid parameter for CSV import: %s')
675 $GLOBALS['message']->addParam(__('Columns escaped with'));
676 $GLOBALS['error'] = true;
677 $param_error = true;
678 } elseif (mb_strlen($csvNewLine) != 1 && $csvNewLine !== 'auto') {
679 $GLOBALS['message'] = Message::error(
680 __('Invalid parameter for CSV import: %s')
682 $GLOBALS['message']->addParam(__('Lines terminated with'));
683 $GLOBALS['error'] = true;
684 $param_error = true;
687 // If there is an error in the parameters entered,
688 // indicate that immediately.
689 if ($param_error) {
690 Generator::mysqlDie(
691 $GLOBALS['message']->getMessage(),
693 false,
694 $errUrl
698 return [$GLOBALS['error'], $GLOBALS['message']];
701 private function getTableNameFromImport(string $databaseName): string
703 $importFileName = basename($GLOBALS['import_file_name'], '.csv');
704 $importFileName = mb_strtolower($importFileName);
705 $importFileName = (string) preg_replace('/[^a-zA-Z0-9_]/', '_', $importFileName);
707 // get new table name, if user didn't provide one, set the default name
708 if (isset($_REQUEST['csv_new_tbl_name']) && strlen($_REQUEST['csv_new_tbl_name']) > 0) {
709 return $_REQUEST['csv_new_tbl_name'];
712 if (mb_strlen($databaseName)) {
713 $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
715 // logic to get table name from filename
716 // if no table then use filename as table name
717 if (count($result) === 0) {
718 return $importFileName;
721 // check to see if {filename} as table exist
722 $nameArray = preg_grep('/' . $importFileName . '/isU', $result);
723 // if no use filename as table name
724 if ($nameArray === false || count($nameArray) === 0) {
725 return $importFileName;
728 // check if {filename}_ as table exist
729 $nameArray = preg_grep('/' . $importFileName . '_/isU', $result);
730 if ($nameArray === false) {
731 return $importFileName;
734 return $importFileName . '_' . (count($nameArray) + 1);
737 return $importFileName;
740 private function getColumnNames(array $columnNames, int $maxCols, array $rows): array
742 if (isset($_REQUEST['csv_col_names'])) {
743 $columnNames = array_splice($rows, 0, 1);
744 $columnNames = $columnNames[0];
745 // MySQL column names can't end with a space character.
746 foreach ($columnNames as $key => $col_name) {
747 $columnNames[$key] = rtrim($col_name);
751 if ((isset($columnNames) && count($columnNames) != $maxCols) || ! isset($columnNames)) {
752 // Fill out column names
753 for ($i = 0; $i < $maxCols; ++$i) {
754 $columnNames[] = 'COL ' . ($i + 1);
758 return $columnNames;
761 private function getSqlTemplateAndRequiredFields(
762 ?string $db,
763 ?string $table,
764 ?string $csvColumns
765 ): array {
766 $requiredFields = 0;
767 $sqlTemplate = '';
768 $fields = [];
769 if (! $this->getAnalyze() && $db !== null && $table !== null) {
770 $sqlTemplate = 'INSERT';
771 if (isset($_POST['csv_ignore'])) {
772 $sqlTemplate .= ' IGNORE';
775 $sqlTemplate .= ' INTO ' . Util::backquote($table);
777 $tmp_fields = $GLOBALS['dbi']->getColumns($db, $table);
779 if (empty($csvColumns)) {
780 $fields = $tmp_fields;
781 } else {
782 $sqlTemplate .= ' (';
783 $fields = [];
784 $tmp = preg_split('/,( ?)/', $csvColumns);
785 if ($tmp === false) {
786 $tmp = [];
789 foreach ($tmp as $val) {
790 if (count($fields) > 0) {
791 $sqlTemplate .= ', ';
794 /* Trim also `, if user already included backquoted fields */
795 $val = trim($val, " \t\r\n\0\x0B`");
796 $found = false;
797 foreach ($tmp_fields as $field) {
798 if ($field['Field'] == $val) {
799 $found = true;
800 break;
804 if (! $found) {
805 $GLOBALS['message'] = Message::error(
807 'Invalid column (%s) specified! Ensure that columns'
808 . ' names are spelled correctly, separated by commas'
809 . ', and not enclosed in quotes.'
812 $GLOBALS['message']->addParam($val);
813 $GLOBALS['error'] = true;
814 break;
817 if (isset($field)) {
818 $fields[] = $field;
821 $sqlTemplate .= Util::backquote($val);
824 $sqlTemplate .= ') ';
827 $requiredFields = count($fields);
829 $sqlTemplate .= ' VALUES (';
832 return [$sqlTemplate, $requiredFields, $fields];
836 * Read the expected column_separated_with String of length
837 * $csv_terminated_len from the $buffer
838 * into variable $ch and return the read string $ch
840 * @param string $buffer The original string buffer read from
841 * csv file
842 * @param string $ch Partially read "column Separated with"
843 * string, also used to return after
844 * reading length equal $csv_terminated_len
845 * @param int $i Current read counter of buffer string
846 * @param int $csv_terminated_len The length of "column separated with"
847 * String
849 * @return string
851 public function readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len)
853 for ($j = 0; $j < $csv_terminated_len - 1; $j++) {
854 $i++;
855 $ch .= mb_substr($buffer, $i, 1);
858 return $ch;
861 /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
864 * Returns true if the table should be analyzed, false otherwise
866 private function getAnalyze(): bool
868 return $this->analyze;
872 * Sets to true if the table should be analyzed, false otherwise
874 * @param bool $analyze status
876 private function setAnalyze($analyze): void
878 $this->analyze = $analyze;