Cross-Database Compatibility#
RAGFlow supports three relational database backends — MySQL (default), PostgreSQL, and OceanBase (MySQL-compatible) — selected at runtime via the DB_TYPE environment variable . Most compatibility work is concentrated in api/db/services/connector_service.py and api/db/db_models.py.
Interval Expression Differences#
The most common SQL dialect friction is time arithmetic used in connector polling queries. MySQL and PostgreSQL use incompatible syntax for computing intervals from a column value:
| Backend | Expression |
|---|---|
| MySQL | NOW() - INTERVAL t2.refresh_freq MINUTE |
| PostgreSQL | NOW() AT TIME ZONE '{TIMEZONE}' - make_interval(mins => t2.refresh_freq) |
Both list_sync_tasks() and _list_due_tasks_for_freq() branch on os.getenv("DB_TYPE", "mysql") at runtime and emit the appropriate SQL() literal. PostgreSQL's path additionally injects the TIMEZONE setting (see below) for timezone-aware comparisons.
The PostgreSQL syntax was introduced in PR #11398 to fix a startup failure when running against Postgres. PR #14446 later added NOW() AT TIME ZONE '{TIMEZONE}' to the Postgres path to make the comparison timezone-aware.
Timezone Configuration#
TIMEZONE is read from the TZ environment variable and defaults to "Asia/Shanghai" . It is only injected into the PostgreSQL interval expression; the MySQL expression relies on the server's session timezone. Setting TZ is required when the Postgres server's TimeZone GUC differs from the application's intended timezone.
Enum-Based Infrastructure Routing#
api/db/db_models.py uses Python enums to dispatch database-specific implementations without inline if/else chains :
PooledDatabase— mapsMYSQL→RetryingPooledMySQLDatabase,POSTGRES→RetryingPooledPostgresqlDatabaseDatabaseMigrator— mapsMYSQL/OCEANBASE→MySQLMigrator,POSTGRES→PostgresqlMigratorDatabaseLock— mapsMYSQL/OCEANBASE→MysqlDatabaseLock,POSTGRES→PostgresDatabaseLockTextFieldType— mapsMYSQL/OCEANBASE→LONGTEXT,POSTGRES→TEXT, used by theLongTextFieldcustom field class
The active backend is resolved once from settings.DATABASE_TYPE at startup; all enum lookups key on DATABASE_TYPE.upper().
Migration Guards#
Schema migrations also require branching. PR #13582 introduced DB_TYPE guards in api/db/db_models.py around MySQL-specific DDL (e.g., primary-key migrations that use ROW_NUMBER() OVER (ORDER BY ...) on Postgres vs. a different approach on MySQL). PostgreSQL schema introspection uses the pg_indexes system catalog; MySQL uses information_schema.statistics .
Key Files and References#
| File | Relevance |
|---|---|
api/db/services/connector_service.py | Interval expression branching in list_sync_tasks and _list_due_tasks_for_freq |
api/db/db_models.py | Enum routing, LongTextField, migration guards |
common/settings.py | DB_TYPE → DATABASE_TYPE, TZ → TIMEZONE |
| PR #11398 | Initial Postgres interval fix |
| PR #14446 | Timezone-aware interval expressions |
| PR #13582 | Migration DB_TYPE guards |