select('ruledef.*') ->join('testrule', 'testrule.RuleID = ruledef.RuleID', 'inner') ->where('ruledef.EventCode', $eventCode) ->where('ruledef.EndDate IS NULL') ->where('testrule.TestSiteID', $testSiteID) ->where('testrule.EndDate IS NULL') ->orderBy('ruledef.RuleID', 'ASC') ->findAll(); } /** * Get all tests linked to a rule * * @param int $ruleID The rule ID * @return array Array of test site IDs */ public function getLinkedTests(int $ruleID): array { $db = \Config\Database::connect(); $result = $db->table('testrule') ->where('RuleID', $ruleID) ->where('EndDate IS NULL') ->select('TestSiteID') ->get() ->getResultArray(); return array_map('intval', array_column($result, 'TestSiteID')); } /** * Link a rule to a test * * @param int $ruleID The rule ID * @param int $testSiteID The test site ID * @return bool Success status */ public function linkTest(int $ruleID, int $testSiteID): bool { $db = \Config\Database::connect(); // Check if already linked (and not soft deleted) $existing = $db->table('testrule') ->where('RuleID', $ruleID) ->where('TestSiteID', $testSiteID) ->where('EndDate IS NULL') ->get() ->getRowArray(); if ($existing) { return true; // Already linked } // Check if soft deleted - restore it $softDeleted = $db->table('testrule') ->where('RuleID', $ruleID) ->where('TestSiteID', $testSiteID) ->where('EndDate IS NOT NULL') ->get() ->getRowArray(); if ($softDeleted) { return $db->table('testrule') ->where('TestRuleID', $softDeleted['TestRuleID']) ->update(['EndDate' => null]); } // Create new link return $db->table('testrule')->insert([ 'RuleID' => $ruleID, 'TestSiteID' => $testSiteID, 'CreateDate' => date('Y-m-d H:i:s'), ]); } /** * Unlink a rule from a test (soft delete) * * @param int $ruleID The rule ID * @param int $testSiteID The test site ID * @return bool Success status */ public function unlinkTest(int $ruleID, int $testSiteID): bool { $db = \Config\Database::connect(); return $db->table('testrule') ->where('RuleID', $ruleID) ->where('TestSiteID', $testSiteID) ->where('EndDate IS NULL') ->update(['EndDate' => date('Y-m-d H:i:s')]); } /** * Validate that a DSL expression compiles successfully * * @param string $expr The DSL expression to validate * @return array ['valid' => bool, 'error' => string|null, 'compiled' => array|null] */ public function validateExpression(string $expr): array { $service = new \App\Services\RuleExpressionService(); try { $compiled = $service->compile($expr); return [ 'valid' => true, 'error' => null, 'compiled' => $compiled, ]; } catch (\Throwable $e) { return [ 'valid' => false, 'error' => $e->getMessage(), 'compiled' => null, ]; } } /** * Compile a DSL expression to JSON * * @param string $expr The DSL expression to compile * @return array The compiled structure * @throws \InvalidArgumentException If compilation fails */ public function compileExpression(string $expr): array { $service = new \App\Services\RuleExpressionService(); return $service->compile($expr); } /** * Check if a rule's expression needs recompilation * Returns true if the raw expression differs from the compiled version * * @param int $ruleID The rule ID to check * @return bool True if recompilation is needed */ public function needsRecompilation(int $ruleID): bool { $rule = $this->find($ruleID); if (!$rule) { return false; } $rawExpr = $rule['ConditionExpr'] ?? ''; $compiledJson = $rule['ConditionExprCompiled'] ?? ''; if (empty($rawExpr)) { return false; } if (empty($compiledJson)) { return true; } // Try to compile current expression and compare with stored version try { $compiled = $this->compileExpression($rawExpr); $stored = json_decode($compiledJson, true); return json_encode($compiled) !== json_encode($stored); } catch (\Throwable $e) { return true; } } /** * Automatically compile expression if needed and update the rule * * @param int $ruleID The rule ID * @return bool Success status */ public function autoCompile(int $ruleID): bool { if (!$this->needsRecompilation($ruleID)) { return true; } $rule = $this->find($ruleID); if (!$rule) { return false; } $rawExpr = $rule['ConditionExpr'] ?? ''; if (empty($rawExpr)) { return false; } try { $compiled = $this->compileExpression($rawExpr); return $this->update($ruleID, [ 'ConditionExprCompiled' => json_encode($compiled), ]); } catch (\Throwable $e) { log_message('error', 'Auto-compile failed for RuleID=' . $ruleID . ': ' . $e->getMessage()); return false; } } /** * Compile all rules that need recompilation * * @return array ['success' => int, 'failed' => int] */ public function compileAllPending(): array { $rules = $this->where('EndDate IS NULL')->findAll(); $success = 0; $failed = 0; foreach ($rules as $rule) { $ruleID = (int) ($rule['RuleID'] ?? 0); if ($ruleID === 0) { continue; } if ($this->needsRecompilation($ruleID)) { if ($this->autoCompile($ruleID)) { $success++; } else { $failed++; } } } return [ 'success' => $success, 'failed' => $failed, ]; } }