diff --git a/src/Util/Database/PicoDatabaseUtilMySql.php b/src/Util/Database/PicoDatabaseUtilMySql.php index 1e036be5..0bce0c4b 100644 --- a/src/Util/Database/PicoDatabaseUtilMySql.php +++ b/src/Util/Database/PicoDatabaseUtilMySql.php @@ -186,37 +186,89 @@ public static function fixDefaultValue($defaultValue, $type) } /** - * Dumps data from various sources into SQL insert statements. + * Dumps data from various sources into SQL INSERT statements. * - * @param array $columns Columns of the target table. - * @param string $picoTableName Table name. - * @param MagicObject|PicoPageData $data Data to dump. - * @return string|null SQL insert statements or null if no data. + * This method processes data from PicoPageData, MagicObject, or an array of MagicObject instances + * and generates SQL INSERT statements. It supports batching of records and allows for a callback + * function to handle the generated SQL statements. + * + * @param array $columns Array of columns for the target table. + * @param string $picoTableName Name of the target table. + * @param MagicObject|PicoPageData|array $data Data to be dumped. Can be a PicoPageData instance, + * a MagicObject instance, or an array of MagicObject instances. + * @param int $maxRecord Maximum number of records to process in a single query (default is 100). + * @param callable|null $callbackFunction Optional callback function to process the generated SQL + * statements. The function should accept a single string parameter + * representing the SQL statement. + * @return string|null SQL INSERT statements or null if no data was processed. */ - public static function dumpData($columns, $picoTableName, $data) //NOSONAR + public static function dumpData($columns, $picoTableName, $data, $maxRecord = 100, $callbackFunction = null) //NOSONAR { - if($data instanceof PicoPageData && isset($data->getResult()[0])) + // Check if $data is an instance of PicoPageData + if($data instanceof PicoPageData) { - return self::dumpRecords($columns, $picoTableName, $data->getResult()); + // Handle case where fetching data is not required + if($data->getFindOption() & MagicObject::FIND_OPTION_NO_FETCH_DATA && $maxRecord > 0 && isset($callbackFunction) && is_callable($callbackFunction)) + { + $records = array(); + $stmt = $data->getPDOStatement(); + // Fetch records in batches + while($data = $stmt->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_NEXT)) + { + // Ensure data has all required columns + $data = self::processDataMapping($data, $columns); + if(count($records) < $maxRecord) + { + $records[] = $data; + } + else + { + if(isset($callbackFunction) && is_callable($callbackFunction)) + { + // Call the callback function with the generated SQL + $sql = self::insert($picoTableName, $records); + call_user_func($callbackFunction, $sql); + } + // Reset the records buffer + $records = array(); + } + } + // Handle any remaining records + if(!empty($records) && isset($callbackFunction) && is_callable($callbackFunction)) + { + $sql = self::insert($picoTableName, $records); + call_user_func($callbackFunction, $sql); + } + } + else if(isset($data->getResult()[0])) + { + // If data is available, dump records directly + return self::dumpRecords($columns, $picoTableName, $data->getResult()); + } } else if($data instanceof MagicObject) { + // Handle a single MagicObject instance return self::dumpRecords($columns, $picoTableName, array($data)); } else if(is_array($data) && isset($data[0]) && $data[0] instanceof MagicObject) { + // Handle an array of MagicObject instances return self::dumpRecords($columns, $picoTableName, $data); } - return null; + return null; // Return null if no valid data was processed } /** - * Dumps multiple records into SQL insert statements. + * Constructs an SQL INSERT statement for a single record. * - * @param array $columns Columns of the target table. - * @param string $picoTableName Table name. - * @param MagicObject[] $data Data records. - * @return string SQL insert statements. + * This method takes a data record and maps it to the corresponding columns of the target table, + * generating an SQL INSERT statement. It uses the PicoDatabaseQueryBuilder to build the query. + * + * @param array $columns Associative array mapping column names to their definitions in the target table. + * @param string $picoTableName Name of the target table where the record will be inserted. + * @param MagicObject $record The data record to be dumped into the SQL statement. + * @return string The generated SQL INSERT statement. */ public static function dumpRecords($columns, $picoTableName, $data) { @@ -258,11 +310,15 @@ public static function dumpRecord($columns, $picoTableName, $record) } /** - * Shows the columns of a specified table. + * Retrieves the columns of a specified table from the database. * - * @param PicoDatabase $database Database connection. - * @param string $tableName Table name. - * @return string[] An associative array of column names and their types. + * This method executes a SQL query to show the columns of the given table and returns + * an associative array where the keys are column names and the values are their respective types. + * + * @param PicoDatabase $database Database connection object. + * @param string $tableName Name of the table whose columns are to be retrieved. + * @return array An associative array mapping column names to their types. + * @throws Exception If the query fails or the table does not exist. */ public static function showColumns($database, $tableName) { @@ -278,10 +334,14 @@ public static function showColumns($database, $tableName) } /** - * Autoconfigure import data + * Automatically configures the import data settings based on the source and target databases. + * + * This method connects to the source and target databases, retrieves the list of existing + * tables, and updates the configuration for each target table by checking its presence in the + * source database. It handles exceptions and logs any errors encountered during the process. * - * @param SecretObject $config Configuration - * @return SecretObject + * @param SecretObject $config The configuration object containing database and table information. + * @return SecretObject The updated configuration object with modified table settings. */ public static function autoConfigureImportData($config) { @@ -324,12 +384,17 @@ public static function autoConfigureImportData($config) /** * Automatically configures import data settings from one database to another. * - * @param PicoDatabase $source Source database connection. - * @param PicoDatabase $target Target database connection. - * @param string $sourceTable Source table name. - * @param string $targetTable Target table name. - * @param array $options Additional options for import configuration. - * @return array Configured options for import. + * This method checks if the target table exists in the existing tables. If it does not, it creates + * a new `SecretObject` for the table, determining whether the table is present in the source + * database and configuring the mapping accordingly. + * + * @param PicoDatabase $databaseSource The source database connection. + * @param PicoDatabase $databaseTarget The target database connection. + * @param array $tables The current array of table configurations. + * @param array $sourceTables List of source table names. + * @param string $target The name of the target table to be configured. + * @param array $existingTables List of existing tables in the target database. + * @return array Updated array of table configurations with the new table info added if applicable. */ public static function updateConfigTable($databaseSource, $databaseTarget, $tables, $sourceTables, $target, $existingTables) { @@ -359,12 +424,16 @@ public static function updateConfigTable($databaseSource, $databaseTarget, $tabl } /** - * Create map template + * Creates a mapping template between source and target database tables. + * + * This method generates a mapping array that indicates which columns + * in the target table do not exist in the source table, providing a template + * for further processing or data transformation. * - * @param PicoDatabase $databaseSource Source database - * @param PicoDatabase $databaseTarget Target database - * @param string $target Target table - * @return string[] + * @param PicoDatabase $databaseSource The source database connection. + * @param PicoDatabase $databaseTarget The target database connection. + * @param string $target The name of the target table. + * @return string[] An array of mapping strings indicating missing columns in the source. */ public static function createMapTemplate($databaseSource, $databaseTarget, $target) { @@ -384,12 +453,12 @@ public static function createMapTemplate($databaseSource, $databaseTarget, $targ /** * Imports data from the source database to the target database. * - * @param PicoDatabase $source Source database connection. - * @param PicoDatabase $target Target database connection. - * @param string $sourceTable Source table name. - * @param string $targetTable Target table name. - * @param array $options Options for import operation. - * @return void + * This method connects to the source and target databases, executes any pre-import scripts, + * transfers data from the source tables to the target tables, and executes any post-import scripts. + * + * @param SecretObject $config Configuration object containing database and table details. + * @param callable $callbackFunction Callback function to execute SQL scripts. + * @return bool Returns true on successful import, false on failure. */ public static function importData($config, $callbackFunction) { @@ -452,10 +521,12 @@ public static function importData($config, $callbackFunction) } /** - * Check if array is not empty + * Checks if the provided array is not empty. * - * @param array $array Array to be checked - * @return bool + * This method verifies that the input is an array and contains at least one element. + * + * @param array $array The array to be checked. + * @return bool True if the array is not empty; otherwise, false. */ public static function isNotEmpty($array) { @@ -463,15 +534,21 @@ public static function isNotEmpty($array) } /** - * Import table - * - * @param PicoDatabase $databaseSource Source database - * @param PicoDatabase $databaseTarget Target database - * @param string $tableName Table name - * @param SecretObject $tableInfo Table information - * @param int $maxRecord Maximum record per query - * @param callable $callbackFunction Callback function - * @return bool + * Imports data from a source database table to a target database table. + * + * This method fetches records from the specified source table and processes them + * according to the provided mapping and column information. It uses a callback + * function to handle the generated SQL insert statements in batches, up to a + * specified maximum record count. + * + * @param PicoDatabase $databaseSource The source database from which to import data. + * @param PicoDatabase $databaseTarget The target database where data will be inserted. + * @param string $tableNameSource The name of the source table. + * @param string $tableNameTarget The name of the target table. + * @param SecretObject $tableInfo Information about the table, including mapping and constraints. + * @param int $maxRecord The maximum number of records to process in a single batch. + * @param callable $callbackFunction A callback function to handle the generated SQL statements. + * @return bool True on success, false on failure. */ public static function importDataTable($databaseSource, $databaseTarget, $tableNameSource, $tableNameTarget, $tableInfo, $maxRecord, $callbackFunction) { @@ -519,74 +596,103 @@ public static function importDataTable($databaseSource, $databaseTarget, $tableN } /** - * Get maximum record + * Retrieves the maximum record limit for a query. * - * @param SecretObject $tableInfo Table information - * @param int $maxRecord Maximum record per query - * @return int + * This method checks the specified table information for a maximum record value + * and ensures that the returned value is at least 1. If the table's maximum + * record limit is defined, it overrides the provided maximum record. + * + * @param SecretObject $tableInfo The table information containing maximum record settings. + * @param int $maxRecord The maximum record limit per query specified by the user. + * @return int The effective maximum record limit to be used in queries. */ public static function getMaxRecord($tableInfo, $maxRecord) { - if($tableInfo->getMaximumRecord() != null) - { - $maxRecord = $tableInfo->getMaximumRecord(); + // Check if the table information specifies a maximum record limit + if ($tableInfo->getMaximumRecord() !== null) { + $maxRecord = $tableInfo->getMaximumRecord(); // Override with table's maximum record } - if($maxRecord < 1) - { + + // Ensure the maximum record is at least 1 + if ($maxRecord < 1) { $maxRecord = 1; } - return $maxRecord; - } + return $maxRecord; // Return the final maximum record value + } + /** - * Process data mapping + * Processes data mapping according to specified column types and mappings. * - * @param array $data - * @param SecretObject[] $maps Maps - * @return array + * This method updates the input data by mapping source fields to target fields + * based on the provided mappings, then filters and fixes the data types + * according to the column definitions. + * + * @param mixed[] $data The input data to be processed. + * @param string[] $columns An associative array mapping column names to their types. + * @param string[]|null $maps Optional array of mapping definitions in the format 'target:source'. + * @return mixed[] The updated data array with fixed types and mappings applied. */ - public static function processDataMapping($data, $columns, $maps) + public static function processDataMapping($data, $columns, $maps = null) { + // Check if mappings are provided and are in array format if(isset($maps) && is_array($maps)) { foreach($maps as $map) { + // Split the mapping into target and source $arr = explode(':', $map, 2); $target = trim($arr[0]); $source = trim($arr[1]); - $data[$target] = $data[$source]; - unset($data[$source]); + // Map the source value to the target key + if (isset($data[$source])) { + $data[$target] = $data[$source]; + unset($data[$source]); // Remove the source key + } } } + // Filter the data to include only keys present in columns $data = array_intersect_key($data, array_flip(array_keys($columns))); + + // Fix data types based on column definitions $data = self::fixImportData($data, $columns); - return $data; + return $data; // Return the processed data } /** - * Fix import data + * Fixes imported data based on specified column types. + * + * This method processes the input data array and adjusts the values + * according to the expected types defined in the columns array. It + * supports boolean, integer, and float types. * - * @param mixed[] $data Data - * @param string[] $columns Columns - * @return mixed[] + * @param mixed[] $data The input data to be processed. + * @param string[] $columns An associative array mapping column names to their types. + * @return mixed[] The updated data array with fixed types. */ public static function fixImportData($data, $columns) { + // Iterate through each item in the data array foreach($data as $name=>$value) { + // Check if the column exists in the columns array if(isset($columns[$name])) { $type = $columns[$name]; + if(strtolower($type) == 'tinyint(1)' || strtolower($type) == 'boolean' || strtolower($type) == 'bool') { + // Process boolean types $data = self::fixBooleanData($data, $name, $value); } else if(stripos($type, 'integer') !== false || stripos($type, 'int(') !== false) { + // Process integer types $data = self::fixIntegerData($data, $name, $value); } else if(stripos($type, 'float') !== false || stripos($type, 'double') !== false || stripos($type, 'decimal') !== false) { + // Process float types $data = self::fixFloatData($data, $name, $value); } } @@ -595,136 +701,167 @@ public static function fixImportData($data, $columns) } /** - * Fix data + * Fixes data for safe use in SQL queries. + * + * This method processes a given value and formats it as a string suitable for SQL. + * It handles strings, booleans, nulls, and other data types appropriately. * - * @param mixed $value Value - * @return string + * @param mixed $value The value to be processed. + * @return string The formatted string representation of the value. */ public static function fixData($value) { + // Initialize the return variable $ret = null; + + // Process string values if (is_string($value)) { - $ret = "'" . addslashes($value) . "'"; + $ret = "'" . addslashes($value) . "'"; // Escape single quotes for SQL } else if(is_bool($value)) { - $ret = $value === true ? 'true' : 'false'; + $ret = $value === true ? 'true' : 'false'; // Convert boolean to string } else if ($value === null) { - $ret = "null"; + // Handle null values + $ret = "null"; // Return SQL null representation } else { - $ret = $value; + // Handle other types (e.g., integers, floats) + $ret = $value; // Use the value as-is } - return $ret; + return $ret; // Return the processed value } /** - * Fix boolean data + * Fixes boolean data in the provided array. + * + * This method updates the specified key in the input array to ensure + * that its value is a boolean. If the value is null or an empty string, + * it sets the key to null. Otherwise, it converts the value to a boolean + * based on the condition that if it equals 1, it is set to true; otherwise, false. * - * @param mixed[] $data Data - * @param string $name Name - * @param mixed $value Value - * @return mixed[] + * @param mixed[] $data The input array containing data. + * @param string $name The key in the array to update. + * @param mixed $value The value to be processed. + * @return mixed[] The updated array with the fixed boolean data. */ public static function fixBooleanData($data, $name, $value) { + // Check if the value is null or an empty string if($value === null || $value === '') { - $data[$name] = null; + $data[$name] = null; // Set to null if the value is not valid } else { + // Convert the value to a boolean (true if 1, false otherwise) $data[$name] = $data[$name] == 1 ? true : false; } - return $data; + return $data; // Return the updated array } /** - * Fix integer data + * Fixes integer data in the provided array. * - * @param mixed[] $data Data - * @param string $name Name - * @param mixed $value Value - * @return mixed[] + * This method updates the specified key in the input array to ensure + * that its value is an integer. If the value is null or an empty string, + * it sets the key to null. Otherwise, it converts the value to an integer. + * + * @param mixed[] $data The input array containing data. + * @param string $name The key in the array to update. + * @param mixed $value The value to be processed. + * @return mixed[] The updated array with the fixed integer data. */ public static function fixIntegerData($data, $name, $value) { + // Check if the value is null or an empty string if($value === null || $value === '') { - $data[$name] = null; + $data[$name] = null; // Set to null if value is not valid } else { + // Convert the value to an integer $data[$name] = intval($data[$name]); } - return $data; + return $data; // Return the updated array } /** - * Fix float data + * Fixes float data in the provided array. + * + * This method updates the specified key in the input array to ensure + * that its value is a float. If the value is null or an empty string, + * it sets the key to null. Otherwise, it converts the value to a float. * - * @param mixed[] $data Data - * @param string $name Name - * @param mixed $value Value - * @return mixed[] + * @param mixed[] $data The input array containing data. + * @param string $name The key in the array to update. + * @param mixed $value The value to be processed. + * @return mixed[] The updated array with the fixed float data. */ public static function fixFloatData($data, $name, $value) { + // Check if the value is null or an empty string if($value === null || $value === '') { - $data[$name] = null; + $data[$name] = null; // Set to null if value is not valid } else { + // Convert the value to a float $data[$name] = floatval($data[$name]); } - return $data; + return $data; // Return the updated array } /** - * Create query insert with multiple record + * Creates an SQL INSERT query for multiple records. + * + * This method generates an INSERT statement for a specified table and prepares the values + * for binding in a batch operation. It supports multiple records and ensures proper + * formatting of values. * - * @param string $tableName Table name - * @param array $data Data - * @return string + * @param string $tableName Name of the table where data will be inserted. + * @param array $data An array of associative arrays, where each associative array + * represents a record to be inserted. + * @return string The generated SQL INSERT statement with placeholders for values. */ public static function insert($tableName, $data) { - // Kumpulkan semua kolom + // Collect all unique columns from the data records $columns = array(); foreach ($data as $record) { $columns = array_merge($columns, array_keys($record)); } $columns = array_unique($columns); - // Buat placeholder untuk prepared statement + // Create placeholders for the prepared statement $placeholdersArr = array_fill(0, count($columns), '?'); $placeholders = '(' . implode(', ', $placeholdersArr) . ')'; - // Buat query INSERT + // Build the INSERT query $query = "INSERT INTO $tableName (" . implode(', ', $columns) . ") \r\nVALUES \r\n". implode(",\r\n", array_fill(0, count($data), $placeholders)); - // Siapkan nilai untuk bind + // Prepare values for binding $values = array(); foreach ($data as $record) { foreach ($columns as $column) { + // Use null if the value is not set $values[] = isset($record[$column]) && $record[$column] !== null ? $record[$column] : null; } } - // Fungsi untuk menambahkan single quote jika elemen adalah string - - // Format elemen array + // Format each value for safe SQL insertion $formattedElements = array_map(function($element){ return self::fixData($element); }, $values); - // Ganti tanda tanya dengan elemen array yang telah diformat + // Replace placeholders with formatted values return vsprintf(str_replace('?', '%s', $query), $formattedElements); }