[Developer] #567 show recent SQL cursor candidates
This commit is contained in:
@@ -18,7 +18,8 @@ public record ProbeResult(
|
||||
String vpdPredicate,
|
||||
String effectiveSql,
|
||||
SqlExecutionEvidence executionEvidence,
|
||||
String executionEvidenceMessage
|
||||
String executionEvidenceMessage,
|
||||
List<SqlExecutionCandidate> executionCandidates
|
||||
) {
|
||||
|
||||
public ProbeResult(
|
||||
@@ -49,7 +50,8 @@ public record ProbeResult(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,7 +85,8 @@ public record ProbeResult(
|
||||
vpdPredicate,
|
||||
effectiveSql,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,7 +142,8 @@ public record ProbeResult(
|
||||
predicate,
|
||||
sql,
|
||||
executionEvidence,
|
||||
executionEvidenceMessage
|
||||
executionEvidenceMessage,
|
||||
executionCandidates
|
||||
);
|
||||
}
|
||||
|
||||
@@ -151,6 +155,14 @@ public record ProbeResult(
|
||||
public ProbeResult withExecutionEvidence(
|
||||
SqlExecutionEvidence evidence,
|
||||
String unavailableMessage
|
||||
) {
|
||||
return withExecutionEvidence(evidence, unavailableMessage, List.of());
|
||||
}
|
||||
|
||||
public ProbeResult withExecutionEvidence(
|
||||
SqlExecutionEvidence evidence,
|
||||
String unavailableMessage,
|
||||
List<SqlExecutionCandidate> candidates
|
||||
) {
|
||||
return new ProbeResult(
|
||||
status,
|
||||
@@ -167,10 +179,15 @@ public record ProbeResult(
|
||||
vpdPredicate,
|
||||
effectiveSql,
|
||||
evidence,
|
||||
unavailableMessage
|
||||
unavailableMessage,
|
||||
candidates == null ? List.of() : List.copyOf(candidates)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasExecutionCandidates() {
|
||||
return executionCandidates != null && !executionCandidates.isEmpty();
|
||||
}
|
||||
|
||||
public String title() {
|
||||
return switch (status) {
|
||||
case SUCCESS -> "권한에 따라 데이터를 볼 수 있습니다.";
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.probe;
|
||||
|
||||
/**
|
||||
* One of the most recently active database cursors shown verbatim during a
|
||||
* probe. The match flag is calculated in Java, not by SQL text filtering in
|
||||
* the database.
|
||||
*/
|
||||
public record SqlExecutionCandidate(
|
||||
String sqlId,
|
||||
int childNumber,
|
||||
String parsingSchema,
|
||||
String originalSql,
|
||||
String lastActiveAt,
|
||||
boolean matchesProtectedObject
|
||||
) {
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.cloudhandson.vpdbackoffice.domain.audit.AuditEvent;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeCommand;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeResult;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.SqlExecutionCandidate;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.SqlExecutionEvidence;
|
||||
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedColumn;
|
||||
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedObject;
|
||||
@@ -49,6 +50,7 @@ public class OrdsProbeService {
|
||||
|
||||
private record ExecutionEvidenceLookup(
|
||||
SqlExecutionEvidence evidence,
|
||||
List<SqlExecutionCandidate> candidates,
|
||||
boolean sqlCatalogAccessUnavailable
|
||||
) {
|
||||
}
|
||||
@@ -56,6 +58,7 @@ public class OrdsProbeService {
|
||||
private record RecentSqlCursor(
|
||||
String sqlId,
|
||||
int childNumber,
|
||||
String parsingSchema,
|
||||
String sqlFulltext,
|
||||
String lastActiveAt,
|
||||
long executions,
|
||||
@@ -270,30 +273,33 @@ public class OrdsProbeService {
|
||||
if (lookup.sqlCatalogAccessUnavailable()) {
|
||||
return result.withExecutionEvidence(null,
|
||||
"백오피스 DB 연결이 실제 DB cursor SQL을 읽지 못했습니다. 현재 권한 검증 결과는 정상입니다. "
|
||||
+ "V$SQL과 DBMS_XPLAN은 일반 데이터 접근과 별도인 SYS 진단 권한입니다.");
|
||||
+ "V$SQL과 DBMS_XPLAN은 일반 데이터 접근과 별도인 SYS 진단 권한입니다.",
|
||||
lookup.candidates());
|
||||
}
|
||||
return result.withExecutionEvidence(null,
|
||||
"최근 15분의 최신 실행 cursor 10건에서 이 보호 대상을 찾지 못했습니다. "
|
||||
+ "ORDS 응답 지연 또는 shared pool 교체로 증적이 남지 않았을 수 있습니다.");
|
||||
+ "아래 원문 SQL을 직접 확인하세요.",
|
||||
lookup.candidates());
|
||||
}
|
||||
String message = evidence.hasPredicatePlan() ? null
|
||||
: "SQL_ID는 찾았지만 DBMS_XPLAN Predicate Information을 읽지 못했습니다. "
|
||||
+ "백오피스 DB 계정에 V$SQL/DBMS_XPLAN 조회 권한이 필요합니다.";
|
||||
return result.withExecutionEvidence(evidence, message);
|
||||
return result.withExecutionEvidence(evidence, message, lookup.candidates());
|
||||
} catch (RuntimeException exception) {
|
||||
log.debug("Recent SQL execution evidence unavailable for {}.{}: {}",
|
||||
object.owner(), object.objectName(), exception.getMessage());
|
||||
return result.withExecutionEvidence(null,
|
||||
"DB 실행 증적을 읽지 못했습니다. 백오피스 DB 계정에 V$SQL과 DBMS_XPLAN 조회 권한이 필요합니다.");
|
||||
"DB 실행 증적을 읽지 못했습니다. 백오피스 DB 계정에 V$SQL과 DBMS_XPLAN 조회 권한이 필요합니다.",
|
||||
List.of());
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutionEvidenceLookup findRecentExecutionEvidence(ProtectedObject object) {
|
||||
try {
|
||||
return new ExecutionEvidenceLookup(findRecentExecutionEvidence(jdbcTemplate, object), false);
|
||||
return findRecentExecutionEvidence(jdbcTemplate, object);
|
||||
} catch (RuntimeException exception) {
|
||||
log.debug("Backoffice cursor evidence is unavailable: {}", exception.getMessage());
|
||||
return new ExecutionEvidenceLookup(null, isSqlCatalogAccessUnavailable(exception));
|
||||
return new ExecutionEvidenceLookup(null, List.of(), isSqlCatalogAccessUnavailable(exception));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,13 +314,14 @@ public class OrdsProbeService {
|
||||
|| normalized.contains("V$SQL");
|
||||
}
|
||||
|
||||
private SqlExecutionEvidence findRecentExecutionEvidence(
|
||||
private ExecutionEvidenceLookup findRecentExecutionEvidence(
|
||||
JdbcTemplate evidenceJdbcTemplate,
|
||||
ProtectedObject object
|
||||
) {
|
||||
List<RecentSqlCursor> candidates = evidenceJdbcTemplate.query("""
|
||||
SELECT sql_id,
|
||||
child_number,
|
||||
parsing_schema_name,
|
||||
sql_fulltext,
|
||||
last_active_time,
|
||||
executions,
|
||||
@@ -324,6 +331,7 @@ public class OrdsProbeService {
|
||||
FROM (
|
||||
SELECT sql_id,
|
||||
child_number,
|
||||
parsing_schema_name,
|
||||
sql_fulltext,
|
||||
last_active_time,
|
||||
executions,
|
||||
@@ -338,6 +346,7 @@ public class OrdsProbeService {
|
||||
""", (resultSet, rowNum) -> new RecentSqlCursor(
|
||||
resultSet.getString("sql_id"),
|
||||
resultSet.getInt("child_number"),
|
||||
resultSet.getString("parsing_schema_name"),
|
||||
resultSet.getString("sql_fulltext"),
|
||||
resultSet.getTimestamp("last_active_time") == null
|
||||
? null : resultSet.getTimestamp("last_active_time").toLocalDateTime().toString(),
|
||||
@@ -347,7 +356,18 @@ public class OrdsProbeService {
|
||||
resultSet.getLong("buffer_gets")
|
||||
), EXECUTION_EVIDENCE_LOOKBACK_MINUTES);
|
||||
|
||||
return candidates.stream()
|
||||
List<SqlExecutionCandidate> candidateViews = candidates.stream()
|
||||
.map(candidate -> new SqlExecutionCandidate(
|
||||
candidate.sqlId(),
|
||||
candidate.childNumber(),
|
||||
candidate.parsingSchema(),
|
||||
candidate.sqlFulltext(),
|
||||
candidate.lastActiveAt(),
|
||||
referencesProtectedObject(candidate.sqlFulltext(), object)
|
||||
))
|
||||
.toList();
|
||||
|
||||
SqlExecutionEvidence evidence = candidates.stream()
|
||||
.filter(candidate -> referencesProtectedObject(candidate.sqlFulltext(), object))
|
||||
.findFirst()
|
||||
.map(candidate -> new SqlExecutionEvidence(
|
||||
@@ -362,6 +382,7 @@ public class OrdsProbeService {
|
||||
findPredicatePlan(evidenceJdbcTemplate, candidate.sqlId(), candidate.childNumber())
|
||||
))
|
||||
.orElse(null);
|
||||
return new ExecutionEvidenceLookup(evidence, candidateViews, false);
|
||||
}
|
||||
|
||||
static boolean referencesProtectedObject(String sqlFulltext, ProtectedObject object) {
|
||||
|
||||
Reference in New Issue
Block a user