mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-22 16:23:12 +08:00
fix: preserve extensionless document suffix on GaussDB (#18483)
### Summary
RAGFlow's "Create empty document" flow accepts names without a file
extension. The `POST /datasets/<dataset_id>/documents?type=empty` route
calls `_upload_empty_document()`, where `Path(name).suffix.lstrip(".")`
returns `""`.
In GaussDB's A/ORA compatibility mode, that empty string is persisted as
SQL `NULL`. Because `document.suffix` was defined as `NOT NULL`, the
insert failed with a constraint violation.
This commit is contained in:
@@ -1321,7 +1321,7 @@ class Document(DataBaseModel):
|
||||
progress_msg = TextField(null=True, help_text="process message", default="")
|
||||
process_begin_at = DateTimeField(null=True, index=True)
|
||||
process_duration = FloatField(default=0)
|
||||
suffix = CharField(max_length=32, null=False, help_text="The real file extension suffix", index=True)
|
||||
suffix = EmptyStringCharField(max_length=32, null=False, help_text="The real file extension suffix", index=True)
|
||||
|
||||
content_hash = CharField(max_length=32, null=True, help_text="xxhash128 of document content for change detection", default="", index=True)
|
||||
|
||||
@@ -1895,6 +1895,7 @@ GAUSSDB_EMPTY_STRING_COMPATIBLE_COLUMNS = (
|
||||
("dialog", ("llm_id", "rerank_id")),
|
||||
("memory", ("embd_id", "llm_id")),
|
||||
("file", ("source_type",)),
|
||||
("document", ("suffix",)),
|
||||
("system_settings", ("value",)),
|
||||
("task", ("task_type",)),
|
||||
("sync_logs", ("error_msg", "full_exception_trace")),
|
||||
@@ -2395,7 +2396,7 @@ def migrate_db():
|
||||
alter_db_add_column(migrator, "mcp_server", "variables", JSONField(null=True, help_text="MCP Server variables", default=dict))
|
||||
alter_db_rename_column(migrator, "task", "process_duation", "process_duration")
|
||||
alter_db_rename_column(migrator, "document", "process_duation", "process_duration")
|
||||
alter_db_add_column(migrator, "document", "suffix", CharField(max_length=32, null=False, default="", help_text="The real file extension suffix", index=True))
|
||||
alter_db_add_column(migrator, "document", "suffix", EmptyStringCharField(max_length=32, null=False, default="", help_text="The real file extension suffix", index=True))
|
||||
alter_db_add_column(migrator, "api_4_conversation", "errors", TextField(null=True, help_text="errors"))
|
||||
alter_db_add_column(migrator, "dialog", "meta_data_filter", JSONField(null=True, default={}))
|
||||
alter_db_column_type(migrator, "canvas_template", "title", JSONField(null=True, default=dict, help_text="Canvas title"))
|
||||
|
||||
@@ -75,6 +75,7 @@ def test_gaussdb_empty_string_compatible_migration_drops_not_null_only_for_gauss
|
||||
assert len(FakeDB.queries) == expected_query_count
|
||||
assert ('ALTER TABLE "user" ALTER COLUMN "nickname" DROP NOT NULL', None) in FakeDB.queries
|
||||
assert ('ALTER TABLE "tenant" ALTER COLUMN "llm_id" DROP NOT NULL', None) in FakeDB.queries
|
||||
assert ('ALTER TABLE "document" ALTER COLUMN "suffix" DROP NOT NULL', None) in FakeDB.queries
|
||||
assert ('ALTER TABLE "system_settings" ALTER COLUMN "value" DROP NOT NULL', None) in FakeDB.queries
|
||||
assert ('ALTER TABLE "task" ALTER COLUMN "task_type" DROP NOT NULL', None) in FakeDB.queries
|
||||
assert ('ALTER TABLE "sync_logs" ALTER COLUMN "error_msg" DROP NOT NULL', None) in FakeDB.queries
|
||||
@@ -88,15 +89,14 @@ def test_gaussdb_empty_string_compatible_migration_drops_not_null_only_for_gauss
|
||||
assert len(FakeDB.queries) == expected_query_count
|
||||
|
||||
|
||||
def test_gaussdb_migration_adds_compatible_tags_before_relaxing_columns(monkeypatch):
|
||||
def test_gaussdb_migration_adds_compatible_fields_before_relaxing_columns(monkeypatch):
|
||||
events = []
|
||||
migrated_tags_field = None
|
||||
migrated_fields = {}
|
||||
|
||||
def record_add_column(_migrator, table_name, column_name, column_type):
|
||||
nonlocal migrated_tags_field
|
||||
events.append(("add", table_name, column_name))
|
||||
if (table_name, column_name) == ("user_canvas", "tags"):
|
||||
migrated_tags_field = column_type
|
||||
if (table_name, column_name) in {("document", "suffix"), ("user_canvas", "tags")}:
|
||||
migrated_fields[(table_name, column_name)] = column_type
|
||||
|
||||
monkeypatch.setattr(settings, "DATABASE_TYPE", "gaussdb")
|
||||
monkeypatch.setattr(db_models, "alter_db_add_column", record_add_column)
|
||||
@@ -111,11 +111,22 @@ def test_gaussdb_migration_adds_compatible_tags_before_relaxing_columns(monkeypa
|
||||
|
||||
db_models.migrate_db()
|
||||
|
||||
assert isinstance(migrated_tags_field, db_models.EmptyStringCharField)
|
||||
assert migrated_tags_field.null is True
|
||||
for field in migrated_fields.values():
|
||||
assert isinstance(field, db_models.EmptyStringCharField)
|
||||
assert field.null is True
|
||||
assert set(migrated_fields) == {("document", "suffix"), ("user_canvas", "tags")}
|
||||
assert events[-1] == ("relax",)
|
||||
|
||||
|
||||
def test_empty_string_char_field_keeps_non_gaussdb_constraints(monkeypatch):
|
||||
for database_type in ("mysql", "postgres", "oceanbase"):
|
||||
monkeypatch.setattr(settings, "DATABASE_TYPE", database_type)
|
||||
field = db_models.EmptyStringCharField(null=False)
|
||||
|
||||
assert field.null is False
|
||||
assert field.db_value("") == ""
|
||||
|
||||
|
||||
def test_gaussdb_unique_email_migration_checks_unique_email_index_not_fixed_name(monkeypatch):
|
||||
class Cursor:
|
||||
def fetchone(self):
|
||||
|
||||
@@ -77,7 +77,7 @@ print("gaussdb-adapter-ok")
|
||||
def test_gaussdb_empty_string_compatible_fields_are_nullable_only_for_gaussdb():
|
||||
gaussdb_result = run_isolated_flow(
|
||||
"""
|
||||
from api.db.db_models import API4Conversation, Dialog, File, Knowledgebase, Memory, SyncLogs, SystemSettings, Task, Tenant, User, UserCanvas
|
||||
from api.db.db_models import API4Conversation, Dialog, Document, File, Knowledgebase, Memory, SyncLogs, SystemSettings, Task, Tenant, User, UserCanvas
|
||||
|
||||
fields = [
|
||||
User.nickname,
|
||||
@@ -92,6 +92,7 @@ fields = [
|
||||
Dialog.rerank_id,
|
||||
Memory.embd_id,
|
||||
Memory.llm_id,
|
||||
Document.suffix,
|
||||
SystemSettings.value,
|
||||
Task.task_type,
|
||||
Task.progress_msg,
|
||||
@@ -135,7 +136,7 @@ print("gaussdb-empty-string-compatible-ok")
|
||||
|
||||
mysql_result = run_isolated_flow(
|
||||
"""
|
||||
from api.db.db_models import API4Conversation, Dialog, File, Knowledgebase, Memory, SyncLogs, SystemSettings, Task, Tenant, User, UserCanvas
|
||||
from api.db.db_models import API4Conversation, Dialog, Document, File, Knowledgebase, Memory, SyncLogs, SystemSettings, Task, Tenant, User, UserCanvas
|
||||
|
||||
fields = [
|
||||
(User.nickname, False),
|
||||
@@ -150,6 +151,7 @@ fields = [
|
||||
(Dialog.rerank_id, False),
|
||||
(Memory.embd_id, False),
|
||||
(Memory.llm_id, False),
|
||||
(Document.suffix, False),
|
||||
(SystemSettings.value, False),
|
||||
(Task.task_type, False),
|
||||
(Task.progress_msg, True),
|
||||
|
||||
Reference in New Issue
Block a user