CSV
"; echo "CSV$gz, TAR$gz: " . Adminer\file_input(" name='csv_file[]' multiple", " " . $this->lang('If the table exists') . ": " . Adminer\html_select("csv_exists", array("insert" => $this->lang('Insert'), "truncate" => $this->lang('Truncate'), "drop" => $this->lang('Drop')), $_POST["csv_exists"]) . " ") ; echo "
\n"; } function importProcess() { if (!$_POST["csv"]) { return null; // import the sent files as SQL commands } $files = Adminer\get_files("csv_file", true); if (!is_array($files)) { echo "

" . Adminer\upload_error($files) . "\n"; return true; } foreach ($files as $file) { foreach ($this->_untar($file[0], $file[1]) as $csv) { if (!$this->_import($csv[0], $csv[1], $_POST["csv_exists"]) && $_POST["error_stops"]) { break 2; } } } return true; } /** Get files stored in a TAR archive, e.g. an export of several tables to CSV * @param string $name name of the file * @param string $tar contents of the file * @return list the file itself if it is not an archive */ protected function _untar($name, $tar) { if (!preg_match('~\.tar(\.gz)?$~i', $name)) { return array(array($name, $tar)); } $return = array(); $offset = 0; while ($offset + 512 <= strlen($tar)) { // the header of each file is 512 bytes, see tar_file() $filename = rtrim(substr($tar, $offset, 100), "\0"); if ($filename == "") { break; // the archive ends with zero blocks } $size = octdec(trim(substr($tar, $offset + 124, 12), "\0 ")); $offset += 512; if (substr($filename, -1) != "/") { // directories have no data $return[] = array($filename, substr($tar, $offset, $size)); } $offset += (int) ceil($size / 512) * 512; // the data is padded to whole blocks } return $return; } /** Create a table named after the file and import the data to it, print the result * @param string $name name of the file * @param string $csv contents of the file * @param string $exists what to do if the table exists: insert, truncate or drop * @return bool */ protected function _import($name, $csv, $exists) { $error_prefix = "

" . Adminer\h($name) . ": "; if (substr($csv, 0, 3) == "\xEF\xBB\xBF") { $csv = substr($csv, 3); // UTF-8 BOM, get_files() removes it only from the uploaded file, not from the files in an archive } if (!Adminer\is_utf8($csv)) { echo $error_prefix . $this->lang('File must be in UTF-8 encoding.') . "\n"; return false; } if (($memory_limit = Adminer\ini_bytes("memory_limit")) != "-1") { Adminer\ini_set("memory_limit", max($memory_limit, strval(10 * strlen($csv) + memory_get_usage() + 8e6))); // 10 - the parsed data take more memory than the file } $rows = Adminer\parse_csv($csv, $this->_separator($csv)); $header = array_map('Adminer\csv_value', (array) array_shift($rows)); while ($header && end($header) === "") { array_pop($header); // the first row ends with the separator } $message_prefix = "

" . Adminer\h($name) . ": "; if (!$header) { echo $message_prefix . $this->lang('%d row(s) have been imported.', 0) . "\n"; return true; } $table = preg_replace('~\.csv(\.gz)?$~i', '', basename($name)); $tables = Adminer\tables_list(); $fields = array(); if (isset($tables[$table])) { if ($exists == "drop") { if (!Adminer\drop_tables(array($table))) { echo $error_prefix . Adminer\adminer()->error() . "\n"; return false; } } else { $fields = Adminer\fields($table); foreach ($header as $column) { if (!$fields[$column]) { echo $error_prefix . $this->lang('Column %s does not exist.', "" . Adminer\h($column) . "") . "\n"; return false; } } if ($exists == "truncate" && !Adminer\truncate_tables(array($table))) { echo $error_prefix . Adminer\adminer()->error() . "\n"; return false; } } } $created = !$fields; if ($created) { $fields = $this->_fields($header, $rows); $create = array(); $primary = array(); foreach ($fields as $field) { $create[] = array("", Adminer\process_field($field, $field), ""); if ($field["primary"]) { $primary[] = " PRIMARY KEY (" . Adminer\idf_escape($field["field"]) . ")"; // the same as in recreate_table() in the SQLite driver } } $start = count(Adminer\Queries::$queries); if (!Adminer\alter_table("", $table, $create, $primary, null, "", "", "", array())) { echo $error_prefix . Adminer\adminer()->error() . "\n"; return false; } if (!$_POST["only_errors"]) { echo "

" . Adminer\adminer()->sqlCommandQuery(implode("\n", array_slice(Adminer\Queries::$queries, $start))) . "
\n"; } } $columns = array(); foreach ($header as $column) { $columns[] = Adminer\idf_escape($column); } $insert = "INSERT INTO " . Adminer\table($table) . " (" . implode(", ", $columns) . ") VALUES\n"; // Oracle supports a single row in INSERT, MS SQL is limited to 1000, 0 - no limit $max_rows = (Adminer\JUSH == "oracle" ? 1 : (Adminer\JUSH == "mssql" ? 1000 : 0)); Adminer\driver()->begin(); $affected = 0; $failed = false; $values = array(); $length = 0; foreach ($rows as $row) { $set = array(); foreach ($header as $key => $column) { $val = (string) $row[$key]; // a shorter row leaves the remaining values empty $set[] = ($val == "" && $fields[$column]["null"] ? "NULL" : Adminer\q(Adminer\csv_value($val))); } $value = "(" . implode(", ", $set) . ")"; if ($values && (count($values) === $max_rows || strlen($insert) + $length + strlen($value) > 1e6)) { // 1e6 - default max_allowed_packet $failed = !Adminer\connection()->query($insert . implode(",\n", $values)); if ($failed) { break; } $values = array(); $length = 0; } $values[] = $value; $length += strlen($value) + 2; $affected++; } if (!$failed && $values) { $failed = !Adminer\connection()->query($insert . implode(",\n", $values)); } if ($failed) { echo $error_prefix . Adminer\adminer()->error() . "\n"; Adminer\driver()->rollback(); return false; } Adminer\driver()->commit(); echo $message_prefix . ($created ? $this->lang('Table has been created.') . " " : "") . $this->lang('%d row(s) have been imported.', $affected) . "\n"; return true; } /** Autodetect separator of CSV data by its first row * @param string $csv * @return string */ protected function _separator($csv) { preg_match('~(?>"[^"]*"|[^"\r\n]+)+~', $csv, $match); // the first row, the same expression as in parse_csv() $return = ","; $max = 0; foreach (array(",", ";", "\t") as $separator) { $rows = Adminer\parse_csv((string) $match[0], $separator); $count = count((array) $rows[0]); if ($count > $max) { // the separator splitting the row to most values wins $max = $count; $return = $separator; } } return $return; } /** Get fields of a table created from CSV data * @param list $header column names * @param list> $rows values as they are in the file * @return Adminer\Field[] [$name => Field] */ protected function _fields($header, $rows) { $return = array(); foreach ($header as $key => $name) { $type = ""; $chars = 0; // maximal number of characters $integers = 0; // maximal number of digits before the decimal point $decimals = 0; // maximal number of digits after the decimal point $big = false; $null = false; $unique = array(); // values of the first column foreach ($rows as $row) { $val = (string) $row[$key]; if ($val == "") { // an empty value is NULL, an empty string is "" in the file $null = true; continue; } $val = Adminer\csv_value($val); if (!$key) { $unique[$val] = true; } $val_type = $this->_valueType($val); $type = $this->_commonType($type, $val_type); $chars = max($chars, $this->_strlen($val)); if ($val_type == "int") { $big = $big || $val > 2147483647 || $val < -2147483648; $integers = max($integers, strlen(ltrim($val, "-"))); } elseif ($val_type == "decimal") { preg_match('~^-?([0-9]+)\.([0-9]+)$~', $val, $match); $integers = max($integers, strlen($match[1])); $decimals = max($decimals, strlen($match[2])); } } $length = ""; if ($type == "") { // no data $type = "varchar"; $chars = 200; } if ($type == "int" && $big) { $type = "bigint"; } if ($type == "decimal") { // the precision is limited to 65 digits in MySQL, to 38 in Oracle and MS SQL $max_digits = (Adminer\JUSH == "sql" ? 65 : (preg_match('~^(oracle|mssql)$~', Adminer\JUSH) ? 38 : 1000)); if ($integers + $decimals > $max_digits) { $type = "float"; } else { $length = ($integers + $decimals) . ",$decimals"; } } if ($type == "varchar") { foreach (array(20, 50, 100, 200) as $length) { if ($chars <= $length) { break; } } } $return[$name] = array( "field" => $name, "type" => $this->_type($type), "length" => (Adminer\JUSH == "sqlite" ? "" : strval($length)), // SQLite types have no length "unsigned" => "", "null" => $null && !preg_match('~varchar|text~', $type), // an empty string is a valid value of a string column "auto_increment" => false, "collation" => "", "privileges" => array(), "comment" => "", // the first column identifies the rows if it has no empty and no repeated value; text is not usable as a key in MySQL "primary" => !$key && $rows && count($unique) == count($rows) && $type != "text", "generated" => "", "on_update" => "", "full_type" => "", ); } return $return; } /** Get type of a single value in CSV data * @param string $val * @return string int, decimal, float, date, time, datetime, varchar or text */ protected function _valueType($val) { $number = '-?([1-9][0-9]*|0)'; // leading zeros are significant, e.g. in ZIP codes if (preg_match("~^$number\$~", $val)) { return "int"; } if (preg_match("~^$number\\.[0-9]{1,3}\$~", $val)) { return "decimal"; } if (preg_match("~^$number(\\.[0-9]+)?([eE][-+]?[0-9]+)?\$~", $val)) { // more than three decimal places or exponent return "float"; } if (preg_match('~^[0-9]{4}-[0-9]{2}-[0-9]{2}$~', $val)) { return "date"; } if (preg_match('~^[0-9]{2}:[0-9]{2}(:[0-9]{2})?$~', $val)) { return "time"; } if (preg_match('~^[0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]+)?$~', $val)) { // time zone is not accepted, it would be lost return "datetime"; } return (strpos($val, "\n") !== false || $this->_strlen($val) > 200 ? "text" : "varchar"); } /** Get type usable for values of two types * @param string $type1 * @param string $type2 * @return string */ protected function _commonType($type1, $type2) { if ($type1 == "" || $type1 == $type2) { return $type2; } $numbers = array("int" => 1, "decimal" => 2, "float" => 3); if ($numbers[$type1] && $numbers[$type2]) { return ($numbers[$type1] > $numbers[$type2] ? $type1 : $type2); } return ($type1 == "text" || $type2 == "text" ? "text" : "varchar"); // e.g. a date among numbers } /** Get name of a type detected in CSV data in the current driver * @param string $type * @return string */ protected function _type($type) { if (Adminer\JUSH == "sqlite") { if (preg_match('~int~', $type)) { return "integer"; } return ($type == "float" ? "real" : ($type == "decimal" ? "numeric" : "text")); } if (Adminer\JUSH == "pgsql") { $types = array("int" => "integer", "decimal" => "numeric", "float" => "double precision", "datetime" => "timestamp", "varchar" => "character varying"); return ($types[$type] ?: $type); } if (Adminer\JUSH == "oracle") { $types = array( "int" => "number", "bigint" => "number", "decimal" => "number", "float" => "binary_double", "time" => "varchar2(8)", // Oracle has no time type, 8 characters hold HH:MM:SS "datetime" => "timestamp", "varchar" => "varchar2", "text" => "clob", ); return ($types[$type] ?: $type); } if (Adminer\JUSH == "mssql") { $types = array("datetime" => "datetime2", "varchar" => "nvarchar", "text" => "nvarchar(max)"); // the length is not used with (max) return ($types[$type] ?: $type); } $types = array("float" => "double", "text" => "mediumtext"); // MySQL return ($types[$type] ?: $type); } /** Get length of a string in characters, the string must be in UTF-8 * @param string $val * @return int */ protected function _strlen($val) { return strlen(preg_replace('~[\x80-\xBF]~', '', $val)); // continuation bytes don't start a character } protected $translations = array( 'cs' => array( '' => 'Vytvoření tabulky z nahraného CSV souboru', 'If the table exists' => 'Pokud tabulka existuje', 'Insert' => 'Vložit', 'Truncate' => 'Vyprázdnit', 'Drop' => 'Odstranit', 'Import' => 'Import', 'File must be in UTF-8 encoding.' => 'Soubor musí být v kódování UTF-8.', 'Column %s does not exist.' => 'Sloupec %s neexistuje.', 'Table has been created.' => 'Tabulka byla vytvořena.', '%d row(s) have been imported.' => array('Byl importován %d záznam.', 'Byly importovány %d záznamy.', 'Bylo importováno %d záznamů.'), ), 'de' => array( '' => 'Erstellt eine Tabelle aus einer importierten CSV-Datei', // Claude Opus 5 ), 'en' => array( '%d row(s) have been imported.' => array('%d row has been imported.', '%d rows have been imported.'), ), 'ja' => array( '' => 'インポートした CSV ファイルからテーブルを作成', // Claude Opus 5 ), 'pl' => array( '' => 'Tworzy tabelę z zaimportowanego pliku CSV', // Claude Opus 5 ), 'ro' => array( '' => 'Creează un tabel dintr-un fișier CSV importat', // Claude Opus 5 ), 'sk' => array( '' => 'Vytvorenie tabuľky z nahraného CSV súboru', // Claude Opus 5 ), 'zh' => array( '' => '从导入的 CSV 文件创建表', // Claude Opus 5 'If the table exists' => '如果表已存在', // Claude Opus 5 'Insert' => '插入', // Claude Opus 5 'Truncate' => '清空', // Claude Opus 5 'Drop' => '删除', // Claude Opus 5 'Import' => '导入', // Claude Opus 5 'File must be in UTF-8 encoding.' => '文件必须使用UTF-8编码。', // Claude Opus 5 '%d row(s) have been imported.' => '%d 行已导入。', // Claude Opus 5 'Column %s does not exist.' => '列 %s 不存在。', // Claude Opus 5 'Table has been created.' => '已创建表。', // Claude Opus 5 ), ); }