refs #702: isolate HMM backoffice branch and restore full menus
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Keeps the HMM knowledge store as the source of truth while preserving the
|
||||
* CB_VECTOR_* contracts that the existing ORDS and backoffice screens use.
|
||||
*/
|
||||
@Component
|
||||
@Order(0)
|
||||
public class HmmKnowledgeSchemaInitializer implements ApplicationRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HmmKnowledgeSchemaInitializer.class);
|
||||
|
||||
private static final List<SchemaObject> TABLES = List.of(
|
||||
new SchemaObject("HMM_KNOWLEDGE_DOCUMENTS", """
|
||||
CREATE TABLE hmm_knowledge_documents (
|
||||
document_id VARCHAR2(200) PRIMARY KEY,
|
||||
file_name VARCHAR2(500) NOT NULL,
|
||||
source_uri VARCHAR2(1000),
|
||||
abstract_text CLOB,
|
||||
original_text CLOB,
|
||||
file_content BLOB,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL
|
||||
)
|
||||
"""),
|
||||
new SchemaObject("HMM_KNOWLEDGE_CHUNKS", """
|
||||
CREATE TABLE hmm_knowledge_chunks (
|
||||
chunk_id NUMBER PRIMARY KEY,
|
||||
document_id VARCHAR2(200) NOT NULL
|
||||
REFERENCES hmm_knowledge_documents(document_id) ON DELETE CASCADE,
|
||||
chunk_no NUMBER NOT NULL,
|
||||
title VARCHAR2(500),
|
||||
chunk_text CLOB NOT NULL,
|
||||
source_uri VARCHAR2(1000),
|
||||
embedding VECTOR,
|
||||
created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL,
|
||||
CONSTRAINT hmm_knowledge_chunk_uk UNIQUE (document_id, chunk_no)
|
||||
)
|
||||
"""),
|
||||
new SchemaObject("HMM_KNOWLEDGE_TAGS", """
|
||||
CREATE TABLE hmm_knowledge_tags (
|
||||
chunk_id NUMBER NOT NULL
|
||||
REFERENCES hmm_knowledge_chunks(chunk_id) ON DELETE CASCADE,
|
||||
tech_tag VARCHAR2(100) NOT NULL,
|
||||
CONSTRAINT hmm_knowledge_tag_pk PRIMARY KEY (chunk_id, tech_tag),
|
||||
CONSTRAINT hmm_knowledge_tag_format_ck
|
||||
CHECK (REGEXP_LIKE(tech_tag, '^[A-Z0-9_-]+$'))
|
||||
)
|
||||
""")
|
||||
);
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public HmmKnowledgeSchemaInitializer(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(org.springframework.boot.ApplicationArguments args) {
|
||||
TABLES.forEach(this::ensureTable);
|
||||
ensureSequence();
|
||||
ensureIndex();
|
||||
ensureLegacyViews();
|
||||
log.info("HMM knowledge schema and CB_VECTOR compatibility views are ready");
|
||||
}
|
||||
|
||||
private void ensureTable(SchemaObject table) {
|
||||
if (!objectExists(table.name(), "TABLE")) {
|
||||
jdbcTemplate.execute(table.ddl());
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureSequence() {
|
||||
if (!objectExists("HMM_KNOWLEDGE_CHUNK_SEQ", "SEQUENCE")) {
|
||||
jdbcTemplate.execute("CREATE SEQUENCE hmm_knowledge_chunk_seq START WITH 30000 INCREMENT BY 1 NOCACHE");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureIndex() {
|
||||
if (!objectExists("HMM_KNOWLEDGE_TAG_IX", "INDEX")) {
|
||||
jdbcTemplate.execute("CREATE INDEX hmm_knowledge_tag_ix ON hmm_knowledge_tags (tech_tag, chunk_id)");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureLegacyViews() {
|
||||
replaceViewUnlessPhysicalTable("CB_VECTOR_DOCUMENT_CHUNK", """
|
||||
CREATE OR REPLACE VIEW cb_vector_document_chunk AS
|
||||
SELECT chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding, created_at
|
||||
FROM hmm_knowledge_chunks
|
||||
""");
|
||||
replaceViewUnlessPhysicalTable("CB_VECTOR_DOCUMENT_TAG", """
|
||||
CREATE OR REPLACE VIEW cb_vector_document_tag AS
|
||||
SELECT chunk_id, tech_tag
|
||||
FROM hmm_knowledge_tags
|
||||
""");
|
||||
replaceViewUnlessPhysicalTable("CB_VECTOR_SEARCH_DOCUMENTS", """
|
||||
CREATE OR REPLACE VIEW cb_vector_search_documents AS
|
||||
SELECT c.chunk_id,
|
||||
c.document_id,
|
||||
c.chunk_no,
|
||||
c.title,
|
||||
c.chunk_text,
|
||||
c.source_uri,
|
||||
c.embedding,
|
||||
(SELECT LISTAGG(t.tech_tag, ',') WITHIN GROUP (ORDER BY t.tech_tag)
|
||||
FROM hmm_knowledge_tags t
|
||||
WHERE t.chunk_id = c.chunk_id) AS tech_tag
|
||||
FROM hmm_knowledge_chunks c
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM hmm_knowledge_tags t WHERE t.chunk_id = c.chunk_id
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void replaceViewUnlessPhysicalTable(String name, String ddl) {
|
||||
if (objectExists(name, "TABLE")) {
|
||||
log.info("{} is a physical legacy table; leaving it unchanged", name);
|
||||
return;
|
||||
}
|
||||
jdbcTemplate.execute(ddl);
|
||||
}
|
||||
|
||||
private boolean objectExists(String name, String type) {
|
||||
Integer count = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM user_objects
|
||||
WHERE object_name = ?
|
||||
AND object_type = ?
|
||||
""", Integer.class, name, type);
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
private record SchemaObject(String name, String ddl) {
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,9 @@ public class VectorKnowledgeService {
|
||||
}
|
||||
|
||||
public VectorKnowledgeSummary summary() {
|
||||
int documents = count("SELECT COUNT(DISTINCT document_id) FROM cb_vector_document_chunk");
|
||||
int chunks = count("SELECT COUNT(*) FROM cb_vector_document_chunk");
|
||||
int tags = count("SELECT COUNT(*) FROM cb_vector_document_tag");
|
||||
int documents = count("SELECT COUNT(*) FROM hmm_knowledge_documents");
|
||||
int chunks = count("SELECT COUNT(*) FROM hmm_knowledge_chunks");
|
||||
int tags = count("SELECT COUNT(*) FROM hmm_knowledge_tags");
|
||||
boolean registered = protectedObjectService.findEnabled().stream()
|
||||
.anyMatch(object -> VECTOR_OBJECT.equalsIgnoreCase(object.objectName()));
|
||||
return new VectorKnowledgeSummary(
|
||||
@@ -100,6 +100,11 @@ public class VectorKnowledgeService {
|
||||
List<VectorChunk> chunks = VectorChunker.chunk(content, command.chunkSize());
|
||||
|
||||
replaceDocument(documentId);
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO hmm_knowledge_documents
|
||||
(document_id, file_name, source_uri, abstract_text, original_text, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, SYSTIMESTAMP)
|
||||
""", documentId, title, sourceUri, title, content);
|
||||
long nextChunkId = nextChunkId(chunks.size());
|
||||
int tagCount = 0;
|
||||
for (VectorChunk chunk : chunks) {
|
||||
@@ -107,13 +112,13 @@ public class VectorKnowledgeService {
|
||||
String embeddingJson = json(embedding);
|
||||
long chunkId = nextChunkId++;
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO cb_vector_document_chunk
|
||||
INSERT INTO hmm_knowledge_chunks
|
||||
(chunk_id, document_id, chunk_no, title, chunk_text, source_uri, embedding)
|
||||
VALUES (?, ?, ?, ?, ?, ?, TO_VECTOR(?))
|
||||
""", chunkId, documentId, chunk.chunkNo(), title, chunk.text(), sourceUri, embeddingJson);
|
||||
for (String tag : tags) {
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO cb_vector_document_tag (chunk_id, tech_tag)
|
||||
INSERT INTO hmm_knowledge_tags (chunk_id, tech_tag)
|
||||
VALUES (?, ?)
|
||||
""", chunkId, tag);
|
||||
tagCount++;
|
||||
@@ -143,18 +148,18 @@ public class VectorKnowledgeService {
|
||||
|
||||
private void replaceDocument(String documentId) {
|
||||
jdbcTemplate.update("""
|
||||
DELETE FROM cb_vector_document_tag
|
||||
DELETE FROM hmm_knowledge_tags
|
||||
WHERE chunk_id IN (
|
||||
SELECT chunk_id FROM cb_vector_document_chunk WHERE document_id = ?
|
||||
SELECT chunk_id FROM hmm_knowledge_chunks WHERE document_id = ?
|
||||
)
|
||||
""", documentId);
|
||||
jdbcTemplate.update("DELETE FROM cb_vector_document_chunk WHERE document_id = ?", documentId);
|
||||
jdbcTemplate.update("DELETE FROM hmm_knowledge_documents WHERE document_id = ?", documentId);
|
||||
}
|
||||
|
||||
private long nextChunkId(int chunkCount) {
|
||||
try {
|
||||
Long sequenceValue = jdbcTemplate.queryForObject(
|
||||
"SELECT cb_vector_chunk_seq.NEXTVAL FROM dual", Long.class);
|
||||
"SELECT hmm_knowledge_chunk_seq.NEXTVAL FROM dual", Long.class);
|
||||
if (sequenceValue != null) {
|
||||
return sequenceValue;
|
||||
}
|
||||
@@ -163,7 +168,7 @@ public class VectorKnowledgeService {
|
||||
// fallback keeps the demonstration usable; the setup SQL creates it.
|
||||
}
|
||||
Long max = jdbcTemplate.queryForObject(
|
||||
"SELECT NVL(MAX(chunk_id), 28000) FROM cb_vector_document_chunk", Long.class);
|
||||
"SELECT NVL(MAX(chunk_id), 28000) FROM hmm_knowledge_chunks", Long.class);
|
||||
return (max == null ? 28000 : max) + 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user