mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 12:51:26 +08:00
feat(database): update README and column helpers for schema guidelines
- Expanded the README with detailed database schema guidelines, including naming conventions for tables, columns, and export names. - Introduced new column helper functions for UUID primary keys (v4 and v7) to streamline table definitions. - Updated existing schemas (group, message, tag, topic) to utilize the new UUID primary key helpers for improved consistency and auto-generation.
This commit is contained in:
parent
fe7358a33c
commit
c16789f697
@ -91,9 +91,7 @@
|
||||
"indexes": {
|
||||
"entity_tag_tag_id_idx": {
|
||||
"name": "entity_tag_tag_id_idx",
|
||||
"columns": [
|
||||
"tag_id"
|
||||
],
|
||||
"columns": ["tag_id"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
@ -102,23 +100,15 @@
|
||||
"name": "entity_tag_tag_id_tag_id_fk",
|
||||
"tableFrom": "entity_tag",
|
||||
"tableTo": "tag",
|
||||
"columnsFrom": [
|
||||
"tag_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["tag_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"entity_tag_entity_type_entity_id_tag_id_pk": {
|
||||
"columns": [
|
||||
"entity_type",
|
||||
"entity_id",
|
||||
"tag_id"
|
||||
],
|
||||
"columns": ["entity_type", "entity_id", "tag_id"],
|
||||
"name": "entity_tag_entity_type_entity_id_tag_id_pk"
|
||||
}
|
||||
},
|
||||
@ -175,10 +165,7 @@
|
||||
"indexes": {
|
||||
"group_entity_sort_idx": {
|
||||
"name": "group_entity_sort_idx",
|
||||
"columns": [
|
||||
"entity_type",
|
||||
"sort_order"
|
||||
],
|
||||
"columns": ["entity_type", "sort_order"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
@ -314,24 +301,17 @@
|
||||
"indexes": {
|
||||
"message_parent_id_idx": {
|
||||
"name": "message_parent_id_idx",
|
||||
"columns": [
|
||||
"parent_id"
|
||||
],
|
||||
"columns": ["parent_id"],
|
||||
"isUnique": false
|
||||
},
|
||||
"message_topic_created_idx": {
|
||||
"name": "message_topic_created_idx",
|
||||
"columns": [
|
||||
"topic_id",
|
||||
"created_at"
|
||||
],
|
||||
"columns": ["topic_id", "created_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"message_trace_id_idx": {
|
||||
"name": "message_trace_id_idx",
|
||||
"columns": [
|
||||
"trace_id"
|
||||
],
|
||||
"columns": ["trace_id"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
@ -340,12 +320,8 @@
|
||||
"name": "message_parent_id_message_id_fk",
|
||||
"tableFrom": "message",
|
||||
"tableTo": "message",
|
||||
"columnsFrom": [
|
||||
"parent_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["parent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@ -353,12 +329,8 @@
|
||||
"name": "message_topic_id_topic_id_fk",
|
||||
"tableFrom": "message",
|
||||
"tableTo": "topic",
|
||||
"columnsFrom": [
|
||||
"topic_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["topic_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@ -420,10 +392,7 @@
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"preference_scope_key_pk": {
|
||||
"columns": [
|
||||
"scope",
|
||||
"key"
|
||||
],
|
||||
"columns": ["scope", "key"],
|
||||
"name": "preference_scope_key_pk"
|
||||
}
|
||||
},
|
||||
@ -472,9 +441,7 @@
|
||||
"indexes": {
|
||||
"tag_name_unique": {
|
||||
"name": "tag_name_unique",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"columns": ["name"],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
@ -592,40 +559,27 @@
|
||||
"indexes": {
|
||||
"topic_group_updated_idx": {
|
||||
"name": "topic_group_updated_idx",
|
||||
"columns": [
|
||||
"group_id",
|
||||
"updated_at"
|
||||
],
|
||||
"columns": ["group_id", "updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"topic_group_sort_idx": {
|
||||
"name": "topic_group_sort_idx",
|
||||
"columns": [
|
||||
"group_id",
|
||||
"sort_order"
|
||||
],
|
||||
"columns": ["group_id", "sort_order"],
|
||||
"isUnique": false
|
||||
},
|
||||
"topic_updated_at_idx": {
|
||||
"name": "topic_updated_at_idx",
|
||||
"columns": [
|
||||
"updated_at"
|
||||
],
|
||||
"columns": ["updated_at"],
|
||||
"isUnique": false
|
||||
},
|
||||
"topic_is_pinned_idx": {
|
||||
"name": "topic_is_pinned_idx",
|
||||
"columns": [
|
||||
"is_pinned",
|
||||
"pinned_order"
|
||||
],
|
||||
"columns": ["is_pinned", "pinned_order"],
|
||||
"isUnique": false
|
||||
},
|
||||
"topic_assistant_id_idx": {
|
||||
"name": "topic_assistant_id_idx",
|
||||
"columns": [
|
||||
"assistant_id"
|
||||
],
|
||||
"columns": ["assistant_id"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
@ -634,12 +588,8 @@
|
||||
"name": "topic_active_node_id_message_id_fk",
|
||||
"tableFrom": "topic",
|
||||
"tableTo": "message",
|
||||
"columnsFrom": [
|
||||
"active_node_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["active_node_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@ -647,12 +597,8 @@
|
||||
"name": "topic_group_id_group_id_fk",
|
||||
"tableFrom": "topic",
|
||||
"tableTo": "group",
|
||||
"columnsFrom": [
|
||||
"group_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["group_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@ -674,4 +620,4 @@
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,4 +24,4 @@
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,105 @@
|
||||
- All the database table names use **singular** form, snake_casing
|
||||
- Export table names use `xxxxTable`
|
||||
# Database Schema Guidelines
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- **Table names**: Use **singular** form with snake_case (e.g., `topic`, `message`, `app_state`)
|
||||
- **Export names**: Use `xxxTable` pattern (e.g., `topicTable`, `messageTable`)
|
||||
- **Column names**: Drizzle auto-infers from property names, no need to specify explicitly
|
||||
|
||||
## Column Helpers
|
||||
|
||||
All helpers are exported from `./schemas/columnHelpers.ts`.
|
||||
|
||||
### Primary Keys
|
||||
|
||||
| Helper | UUID Version | Use Case |
|
||||
|--------|--------------|----------|
|
||||
| `uuidPrimaryKey()` | v4 (random) | General purpose tables |
|
||||
| `uuidPrimaryKeyOrdered()` | v7 (time-ordered) | Large tables with time-based queries |
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { uuidPrimaryKey, uuidPrimaryKeyOrdered } from './columnHelpers'
|
||||
|
||||
// General purpose table
|
||||
export const topicTable = sqliteTable('topic', {
|
||||
id: uuidPrimaryKey(),
|
||||
name: text(),
|
||||
...
|
||||
})
|
||||
|
||||
// Large table with time-ordered data
|
||||
export const messageTable = sqliteTable('message', {
|
||||
id: uuidPrimaryKeyOrdered(),
|
||||
content: text(),
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- ID is auto-generated if not provided during insert
|
||||
- Can be manually specified for migration scenarios
|
||||
- Use `.returning()` to get the generated ID after insert
|
||||
|
||||
### Timestamps
|
||||
|
||||
| Helper | Fields | Use Case |
|
||||
|--------|--------|----------|
|
||||
| `createUpdateTimestamps` | `createdAt`, `updatedAt` | Tables without soft delete |
|
||||
| `createUpdateDeleteTimestamps` | `createdAt`, `updatedAt`, `deletedAt` | Tables with soft delete |
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { createUpdateTimestamps, createUpdateDeleteTimestamps } from './columnHelpers'
|
||||
|
||||
// Without soft delete
|
||||
export const tagTable = sqliteTable('tag', {
|
||||
id: uuidPrimaryKey(),
|
||||
name: text(),
|
||||
...createUpdateTimestamps
|
||||
})
|
||||
|
||||
// With soft delete
|
||||
export const topicTable = sqliteTable('topic', {
|
||||
id: uuidPrimaryKey(),
|
||||
name: text(),
|
||||
...createUpdateDeleteTimestamps
|
||||
})
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- `createdAt`: Auto-set to `Date.now()` on insert
|
||||
- `updatedAt`: Auto-set on insert, auto-updated on update
|
||||
- `deletedAt`: `null` by default, set to timestamp for soft delete
|
||||
|
||||
## JSON Fields
|
||||
|
||||
For JSON column support, use `{ mode: 'json' }`:
|
||||
|
||||
```typescript
|
||||
data: text({ mode: 'json' }).$type<MyDataType>()
|
||||
```
|
||||
|
||||
Drizzle handles JSON serialization/deserialization automatically.
|
||||
|
||||
## Foreign Keys
|
||||
|
||||
```typescript
|
||||
// SET NULL: preserve record when referenced record is deleted
|
||||
groupId: text().references(() => groupTable.id, { onDelete: 'set null' })
|
||||
|
||||
// CASCADE: delete record when referenced record is deleted
|
||||
topicId: text().references(() => topicTable.id, { onDelete: 'cascade' })
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
Generate migrations after schema changes:
|
||||
|
||||
```bash
|
||||
yarn db:migrations:generate
|
||||
```
|
||||
|
||||
@ -1,4 +1,23 @@
|
||||
import { integer } from 'drizzle-orm/sqlite-core'
|
||||
import { integer, text } from 'drizzle-orm/sqlite-core'
|
||||
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid'
|
||||
|
||||
/**
|
||||
* UUID v4 primary key with auto-generation
|
||||
* Use for general purpose tables
|
||||
*/
|
||||
export const uuidPrimaryKey = () =>
|
||||
text()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => uuidv4())
|
||||
|
||||
/**
|
||||
* UUID v7 primary key with auto-generation (time-ordered)
|
||||
* Use for tables with large datasets that benefit from sequential inserts
|
||||
*/
|
||||
export const uuidPrimaryKeyOrdered = () =>
|
||||
text()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => uuidv7())
|
||||
|
||||
const createTimestamp = () => {
|
||||
return Date.now()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { createUpdateTimestamps } from './columnHelpers'
|
||||
import { createUpdateTimestamps, uuidPrimaryKey } from './columnHelpers'
|
||||
|
||||
/**
|
||||
* Group table - general-purpose grouping for entities
|
||||
@ -11,7 +11,7 @@ import { createUpdateTimestamps } from './columnHelpers'
|
||||
export const groupTable = sqliteTable(
|
||||
'group',
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
id: uuidPrimaryKey(),
|
||||
// Entity type this group belongs to: topic, session, assistant
|
||||
entityType: text().notNull(),
|
||||
// Display name of the group
|
||||
|
||||
@ -3,7 +3,7 @@ import type { AssistantMeta, ModelMeta } from '@shared/data/types/meta'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { check, index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { createUpdateDeleteTimestamps } from './columnHelpers'
|
||||
import { createUpdateDeleteTimestamps, uuidPrimaryKeyOrdered } from './columnHelpers'
|
||||
import { topicTable } from './topic'
|
||||
|
||||
/**
|
||||
@ -16,7 +16,7 @@ import { topicTable } from './topic'
|
||||
export const messageTable = sqliteTable(
|
||||
'message',
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
id: uuidPrimaryKeyOrdered(),
|
||||
// Adjacency list parent reference for tree structure
|
||||
// SET NULL: preserve child messages when parent is deleted
|
||||
parentId: text().references(() => messageTable.id, { onDelete: 'set null' }),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { createUpdateTimestamps } from './columnHelpers'
|
||||
import { createUpdateTimestamps, uuidPrimaryKey } from './columnHelpers'
|
||||
|
||||
/**
|
||||
* Tag table - general-purpose tags for entities
|
||||
@ -9,7 +9,7 @@ import { createUpdateTimestamps } from './columnHelpers'
|
||||
* via the entity_tag join table.
|
||||
*/
|
||||
export const tagTable = sqliteTable('tag', {
|
||||
id: text().primaryKey(),
|
||||
id: uuidPrimaryKey(),
|
||||
// Unique tag name
|
||||
name: text().notNull().unique(),
|
||||
// Display color (hex code)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { AssistantMeta } from '@shared/data/types/meta'
|
||||
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||
|
||||
import { createUpdateDeleteTimestamps } from './columnHelpers'
|
||||
import { createUpdateDeleteTimestamps, uuidPrimaryKey } from './columnHelpers'
|
||||
import { groupTable } from './group'
|
||||
import { messageTable } from './message'
|
||||
|
||||
@ -14,7 +14,7 @@ import { messageTable } from './message'
|
||||
export const topicTable = sqliteTable(
|
||||
'topic',
|
||||
{
|
||||
id: text().primaryKey(),
|
||||
id: uuidPrimaryKey(),
|
||||
name: text(),
|
||||
// Whether the name was manually edited by user
|
||||
isNameManuallyEdited: integer({ mode: 'boolean' }).default(false),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user