Pre-#67 builds wrote Conversation.endpoint as the Kotlin enum `.name` (e.g. "OPENAI"). #67 added a read-side shim in ConversationMapper that converted those legacy rows back to the wire-format SerialName on read. Replace the shim with a one-time data migration so the conversion runs once per user on db upgrade and the read path is plain. Bump database to v4 and register MIGRATION_3_4 on both Android and iOS builders. The migration runs nine UPDATE statements per affected column (conversations.endpoint, conversations.endpointType, presets.endpoint). The presets table is migrated defensively — PresetDao currently has no production callers, so the column is empty in the field, but symmetry is enforced if a future change starts persisting presets. Schema v4.json is structurally identical to v3.json; identityHash is unchanged (b4117797915eafc13b5bd956677f83bf). Only the version field differs. Pre-#67 ChatPayloadBuilder coerced any unknown endpoint name to EModelEndpoint.AGENTS, so a legacy "AGENTS" row may have originated from any custom endpoint. The migration rewrites "AGENTS" -> "agents", preserving the same lossy outcome the read-side shim already produced. A dedicated test pins this contract so a future "smart" migration can't silently change cache/fresh consistency. Verified via :core:data:connectedDebugAndroidTest (5 tests, all green: existing v1->v3 helper test, v1->v4 Room API test, v3->v4 endpoint normalization, v3->v4 lossy AGENTS, v3->v4 preset normalization).
620 lines
No EOL
18 KiB
JSON
620 lines
No EOL
18 KiB
JSON
{
|
|
"formatVersion": 1,
|
|
"database": {
|
|
"version": 4,
|
|
"identityHash": "b4117797915eafc13b5bd956677f83bf",
|
|
"entities": [
|
|
{
|
|
"tableName": "conversations",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversationId` TEXT NOT NULL, `title` TEXT NOT NULL, `user` TEXT NOT NULL, `endpoint` TEXT, `endpointType` TEXT, `model` TEXT, `agentId` TEXT, `isArchived` INTEGER NOT NULL, `tags` TEXT NOT NULL, `iconURL` TEXT, `greeting` TEXT, `modelParams` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `lastSyncedAt` INTEGER NOT NULL, PRIMARY KEY(`conversationId`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "conversationId",
|
|
"columnName": "conversationId",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "title",
|
|
"columnName": "title",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "user",
|
|
"columnName": "user",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "endpoint",
|
|
"columnName": "endpoint",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "endpointType",
|
|
"columnName": "endpointType",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "model",
|
|
"columnName": "model",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "agentId",
|
|
"columnName": "agentId",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isArchived",
|
|
"columnName": "isArchived",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "tags",
|
|
"columnName": "tags",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "iconURL",
|
|
"columnName": "iconURL",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "greeting",
|
|
"columnName": "greeting",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "modelParams",
|
|
"columnName": "modelParams",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "createdAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "lastSyncedAt",
|
|
"columnName": "lastSyncedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"conversationId"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_conversations_isArchived_updatedAt",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"isArchived",
|
|
"updatedAt"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_conversations_isArchived_updatedAt` ON `${TABLE_NAME}` (`isArchived`, `updatedAt`)"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "messages",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `conversationId` TEXT NOT NULL, `parentMessageId` TEXT, `sender` TEXT, `text` TEXT, `content` TEXT, `isCreatedByUser` INTEGER NOT NULL, `model` TEXT, `endpoint` TEXT, `iconURL` TEXT, `unfinished` INTEGER NOT NULL, `error` INTEGER NOT NULL, `finishReason` TEXT, `tokenCount` INTEGER, `feedback` TEXT, `files` TEXT, `attachments` TEXT, `metadata` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`messageId`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "messageId",
|
|
"columnName": "messageId",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "conversationId",
|
|
"columnName": "conversationId",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "parentMessageId",
|
|
"columnName": "parentMessageId",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "sender",
|
|
"columnName": "sender",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "text",
|
|
"columnName": "text",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "content",
|
|
"columnName": "content",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isCreatedByUser",
|
|
"columnName": "isCreatedByUser",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "model",
|
|
"columnName": "model",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "endpoint",
|
|
"columnName": "endpoint",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "iconURL",
|
|
"columnName": "iconURL",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "unfinished",
|
|
"columnName": "unfinished",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "error",
|
|
"columnName": "error",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "finishReason",
|
|
"columnName": "finishReason",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "tokenCount",
|
|
"columnName": "tokenCount",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "feedback",
|
|
"columnName": "feedback",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "files",
|
|
"columnName": "files",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "attachments",
|
|
"columnName": "attachments",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "metadata",
|
|
"columnName": "metadata",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "createdAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"messageId"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_messages_conversationId",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"conversationId"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversationId` ON `${TABLE_NAME}` (`conversationId`)"
|
|
},
|
|
{
|
|
"name": "index_messages_parentMessageId",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"parentMessageId"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_parentMessageId` ON `${TABLE_NAME}` (`parentMessageId`)"
|
|
},
|
|
{
|
|
"name": "index_messages_conversationId_createdAt",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"conversationId",
|
|
"createdAt"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversationId_createdAt` ON `${TABLE_NAME}` (`conversationId`, `createdAt`)"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "files",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`fileId` TEXT NOT NULL, `user` TEXT NOT NULL, `conversationId` TEXT, `messageId` TEXT, `filename` TEXT NOT NULL, `filepath` TEXT NOT NULL, `type` TEXT NOT NULL, `bytes` INTEGER NOT NULL, `source` TEXT NOT NULL, `width` INTEGER, `height` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `localPath` TEXT, PRIMARY KEY(`fileId`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "fileId",
|
|
"columnName": "fileId",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "user",
|
|
"columnName": "user",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "conversationId",
|
|
"columnName": "conversationId",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "messageId",
|
|
"columnName": "messageId",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "filename",
|
|
"columnName": "filename",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "filepath",
|
|
"columnName": "filepath",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "type",
|
|
"columnName": "type",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "bytes",
|
|
"columnName": "bytes",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "source",
|
|
"columnName": "source",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "width",
|
|
"columnName": "width",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "height",
|
|
"columnName": "height",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "createdAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "localPath",
|
|
"columnName": "localPath",
|
|
"affinity": "TEXT"
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"fileId"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_files_conversationId",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"conversationId"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_files_conversationId` ON `${TABLE_NAME}` (`conversationId`)"
|
|
},
|
|
{
|
|
"name": "index_files_messageId",
|
|
"unique": false,
|
|
"columnNames": [
|
|
"messageId"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_files_messageId` ON `${TABLE_NAME}` (`messageId`)"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "agents",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT, `description` TEXT, `avatar` TEXT, `provider` TEXT NOT NULL, `model` TEXT NOT NULL, `category` TEXT, `authorName` TEXT, `isPromoted` INTEGER NOT NULL, `conversationStarters` TEXT, `tools` TEXT, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "name",
|
|
"columnName": "name",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "description",
|
|
"columnName": "description",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "avatar",
|
|
"columnName": "avatar",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "provider",
|
|
"columnName": "provider",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "model",
|
|
"columnName": "model",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "category",
|
|
"columnName": "category",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "authorName",
|
|
"columnName": "authorName",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isPromoted",
|
|
"columnName": "isPromoted",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "conversationStarters",
|
|
"columnName": "conversationStarters",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "tools",
|
|
"columnName": "tools",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"tableName": "presets",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`presetId` TEXT NOT NULL, `title` TEXT NOT NULL, `endpoint` TEXT, `model` TEXT, `isDefault` INTEGER NOT NULL, `order` INTEGER, `params` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, PRIMARY KEY(`presetId`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "presetId",
|
|
"columnName": "presetId",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "title",
|
|
"columnName": "title",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "endpoint",
|
|
"columnName": "endpoint",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "model",
|
|
"columnName": "model",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "isDefault",
|
|
"columnName": "isDefault",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "order",
|
|
"columnName": "order",
|
|
"affinity": "INTEGER"
|
|
},
|
|
{
|
|
"fieldPath": "params",
|
|
"columnName": "params",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "createdAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"presetId"
|
|
]
|
|
}
|
|
},
|
|
{
|
|
"tableName": "conversation_tags",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tag` TEXT NOT NULL, `user` TEXT NOT NULL, `description` TEXT, `count` INTEGER NOT NULL, `position` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL)",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "id",
|
|
"columnName": "id",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "tag",
|
|
"columnName": "tag",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "user",
|
|
"columnName": "user",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "description",
|
|
"columnName": "description",
|
|
"affinity": "TEXT"
|
|
},
|
|
{
|
|
"fieldPath": "count",
|
|
"columnName": "count",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "position",
|
|
"columnName": "position",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "createdAt",
|
|
"columnName": "createdAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updatedAt",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": true,
|
|
"columnNames": [
|
|
"id"
|
|
]
|
|
},
|
|
"indices": [
|
|
{
|
|
"name": "index_conversation_tags_tag_user",
|
|
"unique": true,
|
|
"columnNames": [
|
|
"tag",
|
|
"user"
|
|
],
|
|
"orders": [],
|
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversation_tags_tag_user` ON `${TABLE_NAME}` (`tag`, `user`)"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"tableName": "drafts",
|
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`conversation_id` TEXT NOT NULL, `text` TEXT NOT NULL, `updated_at` INTEGER NOT NULL, PRIMARY KEY(`conversation_id`))",
|
|
"fields": [
|
|
{
|
|
"fieldPath": "conversationId",
|
|
"columnName": "conversation_id",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "text",
|
|
"columnName": "text",
|
|
"affinity": "TEXT",
|
|
"notNull": true
|
|
},
|
|
{
|
|
"fieldPath": "updatedAt",
|
|
"columnName": "updated_at",
|
|
"affinity": "INTEGER",
|
|
"notNull": true
|
|
}
|
|
],
|
|
"primaryKey": {
|
|
"autoGenerate": false,
|
|
"columnNames": [
|
|
"conversation_id"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"setupQueries": [
|
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b4117797915eafc13b5bd956677f83bf')"
|
|
]
|
|
}
|
|
} |