/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ 0, 'batch_operations' => 0, 'statements_cached' => 0, 'cache_hits' => 0, 'total_execution_time' => 0, 'memory_saved' => 0 ]; // Connection information private $db_config = []; public function __construct() { $this->CI = &get_instance(); $this->CI->load->database(); // Get database configuration for optimizations $this->db_config = $this->CI->db->database; // Initialize performance monitoring $this->initializePerformanceMonitoring(); // Setup automatic cleanup register_shutdown_function([$this, 'cleanup']); } /** * Initialize performance monitoring */ private function initializePerformanceMonitoring() { $this->performance_metrics['session_start'] = microtime(true); $this->performance_metrics['memory_start'] = memory_get_usage(true); } // ================================================= // BATCH OPERATIONS // ================================================= /** * Optimized batch insert with automatic flushing * * @param string $table Table name * @param array $data Data to insert * @param array $options Options (ignore_duplicates, on_duplicate_update, etc.) * @return bool|int Success or affected rows */ public function batchInsert($table, $data, $options = []) { $table = $this->CI->db->protect_identifiers($table, true, false, false); if (!isset($this->batch_insert_buffer[$table])) { $this->batch_insert_buffer[$table] = [ 'data' => [], 'options' => $options, 'columns' => null ]; } // Ensure consistent column structure if ($this->batch_insert_buffer[$table]['columns'] === null) { $this->batch_insert_buffer[$table]['columns'] = array_keys($data); } else { // Validate columns match previous entries if (array_keys($data) !== $this->batch_insert_buffer[$table]['columns']) { throw new InvalidArgumentException('Inconsistent column structure in batch insert'); } } $this->batch_insert_buffer[$table]['data'][] = $data; // Auto-flush if threshold reached if (count($this->batch_insert_buffer[$table]['data']) >= ($this->batch_size * $this->auto_flush_threshold)) { return $this->flushBatchInserts($table); } // Memory usage check if (memory_get_usage(true) > $this->max_memory_usage) { return $this->flushAllBatches(); } return true; } /** * Flush batch inserts for specific table */ public function flushBatchInserts($table) { $table = $this->CI->db->protect_identifiers($table, true, false, false); if (!isset($this->batch_insert_buffer[$table]) || empty($this->batch_insert_buffer[$table]['data'])) { return 0; } $start_time = microtime(true); $buffer = $this->batch_insert_buffer[$table]; $this->batch_insert_buffer[$table] = ['data' => [], 'options' => $buffer['options'], 'columns' => $buffer['columns']]; try { $affected_rows = $this->executeBatchInsert($table, $buffer['data'], $buffer['columns'], $buffer['options']); // Update performance metrics $this->performance_metrics['batch_operations']++; $this->performance_metrics['total_execution_time'] += (microtime(true) - $start_time); $this->performance_metrics['queries_executed']++; return $affected_rows; } catch (Exception $e) { log_message('error', "Batch insert failed for table {$table}: " . $e->getMessage()); throw $e; } } /** * Execute optimized batch insert */ private function executeBatchInsert($table, $data, $columns, $options) { if (empty($data)) { return 0; } $escaped_columns = array_map([$this->CI->db, 'protect_identifiers'], $columns); $columns_sql = '(' . implode(', ', $escaped_columns) . ')'; // Build values for batch insert $values_array = []; foreach ($data as $row) { $escaped_values = []; foreach ($columns as $column) { $escaped_values[] = $this->CI->db->escape($row[$column]); } $values_array[] = '(' . implode(', ', $escaped_values) . ')'; } $values_sql = implode(', ', $values_array); // Build SQL with options if (isset($options['ignore_duplicates']) && $options['ignore_duplicates']) { $sql = "INSERT IGNORE INTO {$table} {$columns_sql} VALUES {$values_sql}"; } elseif (isset($options['on_duplicate_update']) && is_array($options['on_duplicate_update'])) { $sql = "INSERT INTO {$table} {$columns_sql} VALUES {$values_sql}"; $update_parts = []; foreach ($options['on_duplicate_update'] as $col => $val) { $update_parts[] = $this->CI->db->protect_identifiers($col) . ' = ' . $this->CI->db->escape($val); } $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $update_parts); } else { $sql = "INSERT INTO {$table} {$columns_sql} VALUES {$values_sql}"; } // Execute with transaction for atomicity $this->CI->db->trans_start(); $result = $this->CI->db->query($sql); $affected_rows = $this->CI->db->affected_rows(); $this->CI->db->trans_complete(); if ($this->CI->db->trans_status() === false) { throw new Exception('Batch insert transaction failed'); } return $affected_rows; } /** * Optimized batch update */ public function batchUpdate($table, $updates, $where_column, $options = []) { $table = $this->CI->db->protect_identifiers($table, true, false, false); $batch_key = $table . '_' . $where_column; if (!isset($this->batch_update_buffer[$batch_key])) { $this->batch_update_buffer[$batch_key] = []; } $this->batch_update_buffer[$batch_key] = array_merge($this->batch_update_buffer[$batch_key], $updates); // Auto-flush if threshold reached if (count($this->batch_update_buffer[$batch_key]) >= ($this->batch_size * $this->auto_flush_threshold)) { return $this->flushBatchUpdates($table, $where_column, $options); } return true; } /** * Flush batch updates */ public function flushBatchUpdates($table, $where_column, $options = []) { $table = $this->CI->db->protect_identifiers($table, true, false, false); $batch_key = $table . '_' . $where_column; if (!isset($this->batch_update_buffer[$batch_key]) || empty($this->batch_update_buffer[$batch_key])) { return 0; } $start_time = microtime(true); $updates = $this->batch_update_buffer[$batch_key]; $this->batch_update_buffer[$batch_key] = []; try { $affected_rows = $this->executeBatchUpdate($table, $updates, $where_column, $options); // Update performance metrics $this->performance_metrics['batch_operations']++; $this->performance_metrics['total_execution_time'] += (microtime(true) - $start_time); $this->performance_metrics['queries_executed']++; return $affected_rows; } catch (Exception $e) { log_message('error', "Batch update failed for table {$table}: " . $e->getMessage()); throw $e; } } /** * Execute optimized batch update using CASE WHEN */ private function executeBatchUpdate($table, $updates, $where_column, $options) { if (empty($updates)) { return 0; } // Group updates by columns being updated $update_columns = []; $where_values = []; foreach ($updates as $update) { $where_values[] = $update[$where_column]; foreach ($update as $col => $val) { if ($col !== $where_column) { $update_columns[$col][] = [ 'where_val' => $update[$where_column], 'new_val' => $val ]; } } } if (empty($update_columns)) { return 0; } // Build CASE WHEN statements for each column $case_statements = []; foreach ($update_columns as $column => $cases) { $case_sql = $this->CI->db->protect_identifiers($column) . ' = CASE '; foreach ($cases as $case) { $case_sql .= 'WHEN ' . $this->CI->db->protect_identifiers($where_column) . ' = ' . $this->CI->db->escape($case['where_val']) . ' THEN ' . $this->CI->db->escape($case['new_val']) . ' '; } $case_sql .= 'ELSE ' . $this->CI->db->protect_identifiers($column) . ' END'; $case_statements[] = $case_sql; } // Build WHERE clause $escaped_where_values = array_map([$this->CI->db, 'escape'], array_unique($where_values)); $where_clause = $this->CI->db->protect_identifiers($where_column) . ' IN (' . implode(', ', $escaped_where_values) . ')'; // Execute update $sql = "UPDATE {$table} SET " . implode(', ', $case_statements) . " WHERE {$where_clause}"; $this->CI->db->trans_start(); $result = $this->CI->db->query($sql); $affected_rows = $this->CI->db->affected_rows(); $this->CI->db->trans_complete(); if ($this->CI->db->trans_status() === false) { throw new Exception('Batch update transaction failed'); } return $affected_rows; } /** * Flush all pending batch operations */ public function flushAllBatches() { $total_affected = 0; // Flush insert batches foreach (array_keys($this->batch_insert_buffer) as $table) { $total_affected += $this->flushBatchInserts($table); } // Flush update batches foreach (array_keys($this->batch_update_buffer) as $batch_key) { [$table, $where_column] = explode('_', $batch_key, 2); $total_affected += $this->flushBatchUpdates($table, $where_column); } return $total_affected; } // ================================================= // PREPARED STATEMENT OPTIMIZATION // ================================================= /** * Execute query with prepared statement caching */ public function executeWithPreparedStatement($sql, $params = [], $cache_key = null) { $start_time = microtime(true); if ($cache_key === null) { $cache_key = md5($sql); } try { // Try to get cached statement $statement = $this->getCachedStatement($cache_key, $sql); // Bind parameters if provided if (!empty($params)) { $this->bindParameters($statement, $params); } // Execute statement $result = $statement->execute(); // Update performance metrics $this->performance_metrics['queries_executed']++; $this->performance_metrics['total_execution_time'] += (microtime(true) - $start_time); return $result; } catch (Exception $e) { log_message('error', "Prepared statement execution failed: " . $e->getMessage()); throw $e; } } /** * Get or create cached prepared statement */ private function getCachedStatement($cache_key, $sql) { if (isset(self::$prepared_statements[$cache_key])) { $this->performance_metrics['cache_hits']++; return self::$prepared_statements[$cache_key]; } // Prepare new statement $pdo = $this->getPDOConnection(); $statement = $pdo->prepare($sql); // Cache statement (with size limit) if (count(self::$prepared_statements) >= self::$statement_cache_size) { // Remove oldest statement (simple FIFO) $oldest_key = array_key_first(self::$prepared_statements); unset(self::$prepared_statements[$oldest_key]); } self::$prepared_statements[$cache_key] = $statement; $this->performance_metrics['statements_cached']++; return $statement; } /** * Get PDO connection for prepared statements */ private function getPDOConnection() { static $pdo_connection = null; if ($pdo_connection === null) { $config = $this->CI->db; $dsn = "mysql:host={$config->hostname};dbname={$config->database};charset={$config->char_set}"; $pdo_connection = new PDO($dsn, $config->username, $config->password, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false ]); } return $pdo_connection; } /** * Bind parameters to prepared statement */ private function bindParameters($statement, $params) { foreach ($params as $key => $value) { $param_key = is_numeric($key) ? ($key + 1) : $key; if (is_int($value)) { $statement->bindValue($param_key, $value, PDO::PARAM_INT); } elseif (is_bool($value)) { $statement->bindValue($param_key, $value, PDO::PARAM_BOOL); } elseif (is_null($value)) { $statement->bindValue($param_key, $value, PDO::PARAM_NULL); } else { $statement->bindValue($param_key, $value, PDO::PARAM_STR); } } } // ================================================= // QUERY OPTIMIZATION HELPERS // ================================================= /** * Optimized pagination with LIMIT/OFFSET alternative */ public function optimizedPagination($table, $conditions = [], $order_by = 'id', $page = 1, $per_page = 50) { $offset = ($page - 1) * $per_page; // Use cursor-based pagination for better performance on large datasets if ($page > 1 && isset($conditions['cursor_id'])) { return $this->cursorBasedPagination($table, $conditions, $order_by, $per_page); } // Standard LIMIT/OFFSET for first page or when cursor not available return $this->standardPagination($table, $conditions, $order_by, $offset, $per_page); } /** * Cursor-based pagination for better performance */ private function cursorBasedPagination($table, $conditions, $order_by, $per_page) { $this->CI->db->select('*'); $this->CI->db->from($table); $this->CI->db->where($order_by . ' >', $conditions['cursor_id']); // Apply additional conditions foreach ($conditions as $key => $value) { if ($key !== 'cursor_id') { $this->CI->db->where($key, $value); } } $this->CI->db->order_by($order_by, 'ASC'); $this->CI->db->limit($per_page); return $this->CI->db->get()->result_array(); } /** * Standard pagination */ private function standardPagination($table, $conditions, $order_by, $offset, $per_page) { $this->CI->db->select('*'); $this->CI->db->from($table); foreach ($conditions as $key => $value) { if ($key !== 'cursor_id') { $this->CI->db->where($key, $value); } } $this->CI->db->order_by($order_by, 'ASC'); $this->CI->db->limit($per_page, $offset); return $this->CI->db->get()->result_array(); } /** * Optimized EXISTS check */ public function existsOptimized($table, $conditions) { $this->CI->db->select('1'); $this->CI->db->from($table); foreach ($conditions as $key => $value) { $this->CI->db->where($key, $value); } $this->CI->db->limit(1); $result = $this->CI->db->get(); return $result->num_rows() > 0; } /** * Optimized COUNT with estimation for large tables */ public function countOptimized($table, $conditions = [], $estimate_threshold = 100000) { // For small counts, use exact COUNT if ($this->getTableRowEstimate($table) < $estimate_threshold) { return $this->exactCount($table, $conditions); } // For large tables, use estimated count return $this->estimateCount($table, $conditions); } /** * Exact count */ private function exactCount($table, $conditions) { $this->CI->db->select('COUNT(*) as count'); $this->CI->db->from($table); foreach ($conditions as $key => $value) { $this->CI->db->where($key, $value); } $result = $this->CI->db->get()->row_array(); return (int)$result['count']; } /** * Estimate count using table statistics */ private function estimateCount($table, $conditions) { // Use EXPLAIN to estimate count $explain_sql = "EXPLAIN SELECT COUNT(*) FROM {$table}"; if (!empty($conditions)) { $where_parts = []; foreach ($conditions as $key => $value) { $where_parts[] = $this->CI->db->protect_identifiers($key) . ' = ' . $this->CI->db->escape($value); } $explain_sql .= ' WHERE ' . implode(' AND ', $where_parts); } $explain_result = $this->CI->db->query($explain_sql)->row_array(); return isset($explain_result['rows']) ? (int)$explain_result['rows'] : $this->exactCount($table, $conditions); } /** * Get table row estimate from information_schema */ private function getTableRowEstimate($table) { $sql = "SELECT table_rows FROM information_schema.tables WHERE table_schema = ? AND table_name = ?"; $result = $this->CI->db->query($sql, [$this->CI->db->database, $table])->row_array(); return isset($result['table_rows']) ? (int)$result['table_rows'] : 0; } // ================================================= // PERFORMANCE MONITORING & CLEANUP // ================================================= /** * Get performance metrics */ public function getPerformanceMetrics() { $session_time = microtime(true) - $this->performance_metrics['session_start']; $memory_used = memory_get_usage(true) - $this->performance_metrics['memory_start']; return array_merge($this->performance_metrics, [ 'session_duration' => $session_time, 'memory_used' => $memory_used, 'queries_per_second' => $session_time > 0 ? $this->performance_metrics['queries_executed'] / $session_time : 0, 'average_query_time' => $this->performance_metrics['queries_executed'] > 0 ? $this->performance_metrics['total_execution_time'] / $this->performance_metrics['queries_executed'] : 0, 'cache_hit_rate' => $this->performance_metrics['queries_executed'] > 0 ? ($this->performance_metrics['cache_hits'] / $this->performance_metrics['queries_executed']) * 100 : 0 ]); } /** * Cleanup resources */ public function cleanup() { // Flush any remaining batches $this->flushAllBatches(); // Clear prepared statement cache self::$prepared_statements = []; // Log final performance metrics $metrics = $this->getPerformanceMetrics(); if ($metrics['queries_executed'] > 0) { log_activity('OptimizedDatabaseOperations Session Stats: ' . json_encode($metrics)); } } /** * Reset performance counters */ public function resetPerformanceCounters() { $this->performance_metrics = [ 'queries_executed' => 0, 'batch_operations' => 0, 'statements_cached' => 0, 'cache_hits' => 0, 'total_execution_time' => 0, 'memory_saved' => 0, 'session_start' => microtime(true), 'memory_start' => memory_get_usage(true) ]; } /** * Destructor */ public function __destruct() { $this->cleanup(); } }