Download core/Db/BatchInsert.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Db/BatchInsert.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/core/Db/BatchInsert.php
-
curl -L -o BatchInsert.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/Db/BatchInsert.php
11.8 kB
| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Db; | |
| use Exception; | |
| use Piwik\Common; | |
| use Piwik\Config; | |
| use Piwik\Config\DatabaseConfig; | |
| use Piwik\Container\StaticContainer; | |
| use Piwik\Db; | |
| use Piwik\ExceptionHandler; | |
| use Piwik\Log; | |
| use Piwik\SettingsServer; | |
| use Piwik\SettingsPiwik; | |
| class BatchInsert | |
| { | |
| /** | |
| * Performs a batch insert into a specific table by iterating through the data | |
| * | |
| * NOTE: you should use tableInsertBatch() which will fallback to this function if LOAD DATA INFILE not available | |
| * | |
| * @param string $tableName PREFIXED table name! you must call Common::prefixTable() before passing the table name | |
| * @param array $fields array of unquoted field names | |
| * @param array $values array of data to be inserted | |
| * @param bool $ignoreWhenDuplicate Ignore new rows that contain unique key values that duplicate old rows | |
| */ | |
| public static function tableInsertBatchIterate($tableName, $fields, $values, $ignoreWhenDuplicate = true) | |
| { | |
| $tableName = preg_replace('/[^a-zA-Z\d_-]/', '', $tableName); | |
| $fieldList = '(' . join(',', $fields) . ')'; | |
| $ignore = $ignoreWhenDuplicate ? 'IGNORE' : ''; | |
| foreach ($values as $row) { | |
| $row = array_values($row); | |
| $query = "INSERT $ignore INTO `" . $tableName . "` | |
| $fieldList | |
| VALUES (" . Common::getSqlStringFieldsArray($row) . ")"; | |
| Db::query($query, $row); | |
| } | |
| } | |
| /** | |
| * Performs a batch insert into a specific table by sending all data in one SQL statement. | |
| * | |
| * @param string $tableName PREFIXED table name! you must call Common::prefixTable() before passing the table name | |
| * @param array $fields array of unquoted field names | |
| * @param array $values array of data to be inserted | |
| * @param bool $ignoreWhenDuplicate Ignore new rows that contain unique key values that duplicate old rows | |
| */ | |
| public static function tableInsertBatchSql($tableName, $fields, $values, $ignoreWhenDuplicate = true) | |
| { | |
| $insertLines = array(); | |
| $bind = array(); | |
| foreach ($values as $row) { | |
| $insertLines[] = "(" . Common::getSqlStringFieldsArray($row) . ")"; | |
| $bind = array_merge($bind, $row); | |
| } | |
| $fieldList = '(' . implode(',', $fields) . ')'; | |
| $insertLines = implode(',', $insertLines); | |
| $ignore = $ignoreWhenDuplicate ? 'IGNORE' : ''; | |
| $query = "INSERT $ignore INTO $tableName $fieldList VALUES $insertLines"; | |
| Db::query($query, $bind); | |
| } | |
| /** | |
| * Performs a batch insert into a specific table using either LOAD DATA INFILE or plain INSERTs, | |
| * as a fallback. On MySQL, LOAD DATA INFILE is 20x faster than a series of plain INSERTs. | |
| * | |
| * @param string $tableName PREFIXED table name! you must call Common::prefixTable() before passing the table name | |
| * @param array $fields array of unquoted field names | |
| * @param array $values array of data to be inserted | |
| * @param bool $throwException Whether to throw an exception that was caught while trying | |
| * LOAD DATA INFILE, or not. | |
| * @param string $charset The charset to use, defaults to utf8 | |
| * @throws Exception | |
| * @return bool True if the bulk LOAD was used, false if we fallback to plain INSERTs | |
| */ | |
| public static function tableInsertBatch($tableName, $fields, $values, $throwException = false, $charset = 'utf8') | |
| { | |
| $loadDataInfileEnabled = Config::getInstance()->General['enable_load_data_infile']; | |
| if ( | |
| $loadDataInfileEnabled | |
| && Db::get()->hasBulkLoader() | |
| ) { | |
| $path = self::getBestPathForLoadData(); | |
| $instanceId = SettingsPiwik::getPiwikInstanceId(); | |
| if (empty($instanceId)) { | |
| $instanceId = ''; | |
| } | |
| $filePath = $path . $tableName . '-' . $instanceId . Common::generateUniqId() . '.csv'; | |
| // always use utf8 for TiDb, as TiDb has problems with latin1 | |
| if (DatabaseConfig::isTiDb()) { | |
| $charset = 'utf8'; | |
| } | |
| try { | |
| $fileSpec = array( | |
| 'delim' => "\t", | |
| 'quote' => '"', // chr(34) | |
| 'escape' => '\\\\', // chr(92) | |
| 'escapespecial_cb' => function ($str) { | |
| return str_replace(array(chr(92), chr(34)), array(chr(92) . chr(92), chr(92) . chr(34)), $str); | |
| }, | |
| 'eol' => "\r\n", | |
| 'null' => 'NULL', | |
| 'charset' => $charset, | |
| ); | |
| self::createCSVFile($filePath, $fileSpec, $values); | |
| if (!is_readable($filePath)) { | |
| throw new Exception("File $filePath could not be read."); | |
| } | |
| $rc = self::createTableFromCSVFile($tableName, $fields, $filePath, $fileSpec); | |
| if ($rc) { | |
| unlink($filePath); | |
| return true; | |
| } | |
| } catch (Exception $e) { | |
| if ($throwException) { | |
| throw $e; | |
| } | |
| } | |
| // if all else fails, fallback to a series of INSERTs | |
| if (file_exists($filePath)) { | |
| @unlink($filePath); | |
| } | |
| } | |
| self::tableInsertBatchIterate($tableName, $fields, $values); | |
| return false; | |
| } | |
| private static function getBestPathForLoadData() | |
| { | |
| try { | |
| $path = Db::fetchOne('SELECT @@secure_file_priv'); // was introduced in 5.0.38 | |
| } catch (Exception $e) { | |
| // we do not rethrow exception as an error is expected if MySQL is < 5.0.38 | |
| // in this case tableInsertBatch might still work | |
| } | |
| if (empty($path) || !@is_dir($path) || !@is_writable($path)) { | |
| $path = StaticContainer::get('path.tmp') . '/assets/'; | |
| } elseif (!Common::stringEndsWith($path, '/')) { | |
| $path .= '/'; | |
| } | |
| return $path; | |
| } | |
| /** | |
| * Batch insert into table from CSV (or other delimited) file. | |
| * | |
| * @param string $tableName Name of table | |
| * @param array $fields Field names | |
| * @param string $filePath Path name of a file. | |
| * @param array $fileSpec File specifications (delimiter, line terminator, etc) | |
| * | |
| * @throws Exception | |
| * @return bool True if successful; false otherwise | |
| */ | |
| public static function createTableFromCSVFile($tableName, $fields, $filePath, $fileSpec) | |
| { | |
| // Chroot environment: prefix the path with the absolute chroot path | |
| $chrootPath = Config::getInstance()->General['absolute_chroot_path']; | |
| if (!empty($chrootPath)) { | |
| $filePath = $chrootPath . $filePath; | |
| } | |
| // On Windows, MySQL expects forward slashes as directory separators | |
| if (SettingsServer::isWindows()) { | |
| $filePath = str_replace('\\', '/', $filePath); | |
| } | |
| $query = " | |
| '$filePath' | |
| REPLACE | |
| INTO TABLE | |
| `" . $tableName . "`"; | |
| if (isset($fileSpec['charset'])) { | |
| $query .= ' CHARACTER SET ' . $fileSpec['charset']; | |
| } | |
| $fieldList = '(' . join(',', $fields) . ')'; | |
| $query .= " | |
| FIELDS TERMINATED BY | |
| '" . $fileSpec['delim'] . "' | |
| ENCLOSED BY | |
| '" . $fileSpec['quote'] . "' | |
| "; | |
| if (isset($fileSpec['escape'])) { | |
| $query .= " ESCAPED BY '" . $fileSpec['escape'] . "'"; | |
| } | |
| $query .= " | |
| LINES TERMINATED BY | |
| '" . $fileSpec['eol'] . "' | |
| $fieldList | |
| "; | |
| /* | |
| * First attempt: assume web server and MySQL server are on the same machine; | |
| * this requires that the db user have the FILE privilege; however, since this is | |
| * a global privilege, it may not be granted due to security concerns | |
| */ | |
| if (Config::getInstance()->General['multi_server_environment']) { | |
| $keywords = array(); // don't try 'LOAD DATA INFILE' if in a multi_server_environment | |
| } else { | |
| $keywords = array(''); | |
| } | |
| /* | |
| * Second attempt: using the LOCAL keyword means the client reads the file and sends it to the server; | |
| * the LOCAL keyword may trigger a known PHP PDO\MYSQL bug when MySQL not built with --enable-local-infile | |
| * @see https://bugs.php.net/bug.php?id=54158 | |
| */ | |
| $openBaseDir = ini_get('open_basedir'); | |
| $safeMode = ini_get('safe_mode'); | |
| if ((function_exists('mysqli_get_client_stats') || empty($openBaseDir)) && empty($safeMode)) { | |
| // php 5.x - LOAD DATA LOCAL INFILE only used if open_basedir is not set (or we're using a non-buggy version of mysqlnd) | |
| // and if safe mode is not enabled | |
| $keywords[] = 'LOCAL '; | |
| } | |
| $exceptions = array(); | |
| foreach ($keywords as $keyword) { | |
| $queryStart = 'LOAD DATA ' . $keyword . 'INFILE '; | |
| $sql = $queryStart . $query; | |
| try { | |
| $result = @Db::exec($sql); | |
| if (empty($result) || $result < 0) { | |
| continue; | |
| } | |
| return true; | |
| } catch (Exception $e) { | |
| $code = $e->getCode(); | |
| $message = $e->getMessage() . ($code ? "[$code]" : ''); | |
| if (ExceptionHandler::shouldPrintBackTraceWithMessage()) { | |
| $message .= "\n" . $e->getTraceAsString(); | |
| } | |
| $exceptions[] = "\n Try #" . (count($exceptions) + 1) . ': ' . $queryStart . ": " . $message; | |
| } | |
| } | |
| if (count($exceptions)) { | |
| $message = "LOAD DATA INFILE failed... Error was: " . implode(",", $exceptions); | |
| Log::info($message); | |
| throw new Exception($message); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Create CSV (or other delimited) files | |
| * | |
| * @param string $filePath filename to create | |
| * @param array $fileSpec File specifications (delimiter, line terminator, etc) | |
| * @param array $rows Array of array corresponding to rows of values | |
| * @throws Exception if unable to create or write to file | |
| */ | |
| protected static function createCSVFile($filePath, $fileSpec, $rows) | |
| { | |
| // Set up CSV delimiters, quotes, etc | |
| $delim = $fileSpec['delim']; | |
| $quote = $fileSpec['quote']; | |
| $eol = $fileSpec['eol']; | |
| $null = $fileSpec['null']; | |
| $escapespecial_cb = $fileSpec['escapespecial_cb']; | |
| $fp = @fopen($filePath, 'wb'); | |
| if (!$fp) { | |
| throw new Exception('Error creating the tmp file ' . $filePath . ', please check that the webserver has write permission to write this file.'); | |
| } | |
| foreach ($rows as $row) { | |
| $output = ''; | |
| foreach ($row as $value) { | |
| if (!isset($value) || is_null($value) || $value === false) { | |
| $output .= $null . $delim; | |
| } else { | |
| $output .= $quote . $escapespecial_cb($value) . $quote . $delim; | |
| } | |
| } | |
| // Replace delim with eol | |
| $output = substr_replace($output, $eol, -1); | |
| $ret = fwrite($fp, $output); | |
| if (!$ret) { | |
| fclose($fp); | |
| throw new Exception('Error writing to the tmp file ' . $filePath); | |
| } | |
| } | |
| fclose($fp); | |
| @chmod($filePath, 0777); | |
| } | |
| } | |