-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathsupertag-migrate-tag-ids.el
More file actions
195 lines (165 loc) · 8.02 KB
/
Copy pathsupertag-migrate-tag-ids.el
File metadata and controls
195 lines (165 loc) · 8.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
;;; supertag-migrate-tag-ids.el --- Migration script for UUID tag IDs to name-based IDs -*- lexical-binding: t; -*-
;;; Commentary:
;; This script migrates UUID-based tag IDs to name-based IDs in the supertag database.
;; It handles the complete migration process including:
;; 1. Tag entity ID updates
;; 2. Node tag list updates
;; 3. Node-tag relation updates
;; 4. Data consistency validation
;;; Code:
(require 'supertag-core-store)
(require 'supertag-core-persistence) ; For transaction support
(require 'supertag-ops-tag)
(require 'supertag-ops-node)
(require 'supertag-ops-relation)
(defun supertag-migrate--is-uuid-tag-id-p (id)
"Check if ID is a UUID-based tag ID.
Returns t if ID matches the UUID format used by old tag creation."
(and (stringp id)
(string-match-p "^id-[0-9]{8}-[0-9]{6}-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" id)))
(defun supertag-migrate--get-tag-name-from-uuid-id (uuid-id)
"Extract tag name from UUID-based tag ID.
For UUID IDs like 'id-20250827-215408-110aec2b-2b16-42b8-a53a-c804c671f038',
we need to get the actual tag name from the tag entity."
(when-let* ((tag (supertag-tag-get uuid-id))
(tag-name (plist-get tag :name)))
tag-name))
(defun supertag-migrate--generate-new-tag-id (tag-name)
"Generate new tag ID from tag name using the same sanitization logic."
(supertag-sanitize-tag-name tag-name))
(defun supertag-migrate--find-all-uuid-tags ()
"Find all UUID-based tags in the database.
Returns alist of (old-uuid-id . tag-data) pairs."
(let ((uuid-tags '()))
(maphash (lambda (id tag-data)
(when (and (eq (plist-get tag-data :type) :tag)
(supertag-migrate--is-uuid-tag-id-p id))
(push (cons id tag-data) uuid-tags)))
(supertag-store-get-collection :tags))
uuid-tags))
(defun supertag-migrate--update-node-tags-list (node-id old-tag-id new-tag-id)
"Update a node's tags list by replacing OLD-TAG-ID with NEW-TAG-ID."
(supertag-node-update node-id
(lambda (node)
(let* ((tags (plist-get node :tags))
(new-tags (mapcar (lambda (tag)
(if (equal tag old-tag-id)
new-tag-id
tag))
tags)))
(plist-put node :tags new-tags)))))
(defun supertag-migrate--update-all-node-tags (old-tag-id new-tag-id)
"Update all nodes that reference OLD-TAG-ID to use NEW-TAG-ID."
(maphash (lambda (node-id node-data)
(when (and (eq (plist-get node-data :type) :node)
(member old-tag-id (plist-get node-data :tags)))
(supertag-migrate--update-node-tags-list node-id old-tag-id new-tag-id)))
(supertag-store-get-collection :nodes)))
(defun supertag-migrate--update-node-tag-relations (old-tag-id new-tag-id)
"Update all node-tag relations from OLD-TAG-ID to NEW-TAG-ID."
(let ((relations (supertag-relation-find-by-to old-tag-id :node-tag)))
(dolist (relation relations)
(let* ((relation-id (plist-get relation :id))
(from-node-id (plist-get relation :from)))
;; Delete old relation
(supertag-relation-delete relation-id)
;; Create new relation
(supertag-relation-create
(list :type :node-tag
:from from-node-id
:to new-tag-id
:created-at (plist-get relation :created-at)))))))
(defun supertag-migrate--migrate-single-tag (old-tag-id tag-data)
"Migrate a single UUID-based tag to name-based ID.
Returns new tag ID if successful, nil otherwise."
(let* ((tag-name (plist-get tag-data :name))
(new-tag-id (supertag-migrate--generate-new-tag-id tag-name)))
;; Check if new tag ID already exists (should not happen)
(when (supertag-tag-get new-tag-id)
(error "Cannot migrate tag %s: new ID %s already exists" old-tag-id new-tag-id))
;; Update tag entity ID
(let ((updated-tag-data (plist-put (copy-sequence tag-data) :id new-tag-id)))
(supertag-store-put-entity :tags new-tag-id updated-tag-data t)
(supertag-store-remove-entity :tags old-tag-id))
;; Update all node tag lists
(supertag-migrate--update-all-node-tags old-tag-id new-tag-id)
;; Update all node-tag relations
(supertag-migrate--update-node-tag-relations old-tag-id new-tag-id)
new-tag-id))
(defun supertag-migrate-tag-ids ()
"Safely migrate tag ID format.
Uses transaction mechanism to ensure data integrity,
automatically rolling back if migration fails."
(interactive)
(message "Starting safe tag ID migration...")
(supertag--with-transaction
(let ((uuid-tags (supertag-migrate--find-all-uuid-tags))
(migrated-count 0)
(migration-errors '()))
(if (null uuid-tags)
(message "No UUID-based tags found. Migration not needed.")
(message "Found %d UUID-based tags to migrate..." (length uuid-tags))
(dolist (tag-entry uuid-tags)
(let* ((old-id (car tag-entry))
(tag-data (cdr tag-entry)))
(condition-case err
(progn
(supertag-migrate--migrate-single-tag old-id tag-data)
(cl-incf migrated-count)
(message "Migrated tag: %s -> %s" old-id (plist-get tag-data :name)))
(error
(push (format "Failed to migrate tag %s: %s" old-id (error-message-string err))
migration-errors)))))
;; 验证迁移结果
(if migration-errors
(progn
(message "Migration completed with %d errors:" (length migration-errors))
(dolist (err migration-errors)
(message " %s" err))
(error "Tag ID migration failed with errors"))
(if (supertag--validate-tag-references)
(progn
(message "Tag ID migration completed successfully!")
(message "Migrated %d UUID-based tags to name-based IDs" migrated-count)
(supertag-mark-dirty))
(error "Migration validation failed - data consistency check failed")))))))
(defun supertag-migrate--validate-migration ()
"Validate that the migration was successful.
Checks that no UUID-based tags remain and all references are updated."
(let ((remaining-uuid-tags (supertag-migrate--find-all-uuid-tags))
(validation-errors '()))
;; Check for remaining UUID tags
(when remaining-uuid-tags
(push (format "Found %d remaining UUID-based tags" (length remaining-uuid-tags))
validation-errors))
;; Check for orphaned node tag references
(maphash (lambda (node-id node-data)
(when (eq (plist-get node-data :type) :node)
(dolist (tag-id (plist-get node-data :tags))
(when (supertag-migrate--is-uuid-tag-id-p tag-id)
(push (format "Node %s has orphaned UUID tag reference: %s" node-id tag-id)
validation-errors)))))
(supertag-store-get-collection :nodes))
;; Check for orphaned relations
(maphash (lambda (_relation-id relation-data)
(when (and (eq (plist-get relation-data :type) :node-tag)
(supertag-migrate--is-uuid-tag-id-p (plist-get relation-data :to)))
(push (format "Orphaned node-tag relation: %s -> %s"
(plist-get relation-data :from) (plist-get relation-data :to))
validation-errors)))
(supertag-store-get-collection :relations))
(if validation-errors
(progn
(message "Validation failed with %d errors:" (length validation-errors))
(dolist (error validation-errors)
(message " %s" error))
nil)
(message "Migration validation passed!")
t)))
(defun supertag-migrate-tag-ids-with-validation ()
"Perform migration followed by validation."
(interactive)
(supertag-migrate-tag-ids)
(supertag-migrate--validate-migration))
(provide 'supertag-migrate-tag-ids)
;;; supertag-migrate-tag-ids.el ends here