-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathorg-supertag-db.el
1805 lines (1594 loc) · 65.7 KB
/
org-supertag-db.el
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; org-supertag-db.el --- Database layer for org-supertag -*- lexical-binding: t; -*-
;;; Commentary:
;; Provides fundamental data storage and query functionality
;;
;; Core Concepts:
;; - Everything is an entity, everything is a relationship
;; - Entities are nodes in relationships, relationships are connections between nodes
;; - Connect entities through relationships - using type, from, to to express relationships
;;
;; Main Features:
;; - Entity Management: Create, update, delete, query entities
;; - Relationship Management: Create, delete, query relationships
;; - Data Persistence: Save, load, backup data
;; - Cache System: Improve query performance
;;
;; Usage:
;; 1. Entity Operations
;; - Add: (org-supertag-db-add id props)
;; - Query: (org-supertag-db-get id)
;; - Delete: (org-supertag-db-remove-object id)
;;
;; 2. Relationship Operations
;; - Add: (org-supertag-db-link type from to)
;; - Query: (org-supertag-db-find-links type)
;; - Delete: (org-supertag-db-remove-link type from to)
;;
;; 3. Parser
;; - Parse: (org-supertag-db-parse-file file-path)
;;
;; 4. Data Management
;; - Save: (org-supertag-db-save)
;; - Load: (org-supertag-db-load)
;;
;; 5. Event System
;; - Add Listener: (org-supertag-db-add-listener event-type function)
;; - Trigger Event: (org-supertag-db-trigger-event event-type)
;;
;; 6. Cache System
;; - Set Cache: (org-supertag-db-cache-set key value)
;; - Get Cache: (org-supertag-db-cache-get key)
;; - Remove Cache: (org-supertag-db-cache-remove key)
;;
;; 7. Data Persistence
;; - Save: (org-supertag-db-save)
;; - Load: (org-supertag-db-load)
;;
;; 8. Backup
;; - Backup: (org-supertag-db-backup)
;; - Restore: (org-supertag-db-restore)
;; This file does not support breaking changes. If existing functions are insufficient,
;; implement new functions instead of modifying existing ones.
;;; Code:
(require 'ht)
(require 'cl-lib)
;;(require 'org-supertag-base)
(require 'org-element)
;;------------------------------------------------------------------------------
;; Type System
;;------------------------------------------------------------------------------
;; Field values are stored in Node-Field-Value relationship table
;; Node -----> Tag -----> Field Definition
;; | | |
;; | | |
;; v v v
;; Node-Tag Relations
;; |
;; v
;; Node-Field-Value Relations
;; Entity type definitions
(defconst org-supertag-db-object-type
'(:node ; Org node (headline with tags)
:tag) ; Tag (supertag with field definitions)
"Entity types supported by the system.")
;; Field value type definitions
(defconst org-supertag-db-field-type
'(:string ; Plain text
:number ; Numbers for calculation and statistics
:date ; Date using org-mode date-stamp format
:time ; Time using org-mode time-stamp format
:list ; List of values
:options ; Selection from predefined options
:ref ; Reference to fields and values from other tags
:behavior) ; Behavior definition
"Supported field value types.")
(defconst org-supertag-db-object-structure
'((:type :node
:required (;; Basic Information
:id ; Unique node identifier
:title ; Node title
:file-path ; File path
;; Position Information
:pos ; Node position
:olp ; Outline path (ancestor titles)
:level ; Level (0 for file level)
)
:optional (;; Task Information
:scheduled ; Scheduled time
:deadline ; Deadline time
:priority ; Priority
:todo ; Todo state
;; Reference Relations
:ref-to ; Referenced nodes
:ref-from ; Nodes referencing this
:ref-count ; Reference count
;; Event Information
:created-at ; Creation time
:modified-at)) ; Modification time
(:type :tag
:required (;; Basic Information
:id ; Tag identifier (using tag name)
;; Field Definitions
:fields) ; ((:name "field-name" :type field-type) ...)
:optional (;; Meta Information
:description ; Description of tag purpose
:icon ; Icon for visual identification
:color ; Color scheme (background and foreground)
:behaviors ; Behaviors associated with the tag
:created-at ; Creation time
:modified-at)) ; Modification time
"Entity structure definitions."))
;; Link type definition
(defconst org-supertag-db-link-type
'(:node-tag ; Node-tag relationship
:node-field ; Node-field relationship
:tag-ref) ; Tag reference relationship
"System supported link types.")
(defconst org-supertag-db-link-structure
'((:type :node-tag
:required (:from :to) ; node-id -> tag-id
:optional (:created-at)) ; Creation time
(:type :node-field
:required (:from ; node-id
:to ; field-name
:tag-id ; Associated tag
:value) ; Field value
:optional (:created-at
:modified-at))
(:type :tag-ref
:required (:from :to) ; tag-id -> tag-id
:optional (:ref-type)) ; Reference type
))
;; Behavior System Definitions
(defconst org-supertag-behavior-timing
'(:immediate ; Execute immediately when condition met
:deferred ; Execute at next suitable time
:scheduled ; Execute at specific time
:periodic) ; Execute periodically
"When the behavior should execute.")
(defconst org-supertag-behavior-condition
'(:node ; Node state conditions
:time ; Time-based conditions
:field ; Field value conditions
:reference ; Reference relation conditions
:custom) ; Custom predicate conditions
"What conditions trigger the behavior.")
(defconst org-supertag-behavior-operation
'(:transform ; Transform node content/properties
:create ; Create new nodes/content
:delete ; Delete nodes/content
:move ; Move nodes/content
:export ; Export to external formats
:notify ; Send notifications
:custom) ; Custom operations
"What operations the behavior can perform.")
(defconst org-supertag-behavior-method
'(:sync ; Synchronous execution
:async ; Asynchronous execution
:batch ; Batch processing
:transact) ; Transactional execution
"How the behavior should execute.")
(defconst org-supertag-db-behavior-structure
'(:required (:when ; Timing and conditions
:what ; Operation to perform
:how) ; Execution method
:optional (:description ; Behavior description
:error ; Error handling
:compose)) ; Composition rules
"Behavior structure definition.")
;; Event Types
(defconst org-supertag-db-events
'(entity:changed ; Entity update event
link:created ; Link creation event
link:removed ; Link removal event
field:changed ; Field value change event
ref:created ; Reference creation event
ref:removed ; Reference removal event
ref:updated) ; Reference update event
"Event types supported by the system.")
;;------------------------------------------------------------------------------
;; Type Validation
;;------------------------------------------------------------------------------
;; Entity Type Validation
(defun org-supertag-db-valid-object-type-p (type)
"Check if TYPE is a valid entity type.
TYPE must be :node or :tag.
Returns:
- t if valid
- nil if invalid"
(memq type org-supertag-db-object-type))
(defun org-supertag-db-valid-object-p (type props)
"Validate entity data.
TYPE is the entity type
PROPS is the property list
Returns:
- t if valid
- nil if invalid"
(let* ((struct (cl-find type org-supertag-db-object-structure
:key (lambda (x) (plist-get x :type))))
(required (plist-get struct :required)))
(and
;; Check required properties exist
(if required
(cl-every (lambda (key) (plist-member props key))
required)
t)
;; Special validation for tag type
(pcase type
(:tag
(let ((fields (plist-get props :fields)))
(or (null fields) ; Fields can be empty
(org-supertag-db--validate-fields fields))))
(_ t)))))
(defun org-supertag-db--validate-fields (fields)
"Validate field definition list.
FIELDS is a list of field definitions, each should be a plist.
Returns:
- t if valid
- Signals error if invalid"
(when fields
(unless (listp fields)
(error "Fields must be a list"))
(dolist (field fields)
;; Check required properties
(unless (plist-get field :name)
(error "Field must have a name: %S" field))
(unless (plist-get field :type)
(error "Field must have a type: %S" field))
;; Check if type is supported
(let ((type (plist-get field :type)))
(unless (alist-get type org-supertag-field-types)
(error "Unsupported field type: %s" type)))))
t)
;; Link Type Validation
(defun org-supertag-db-valid-link-type-p (type)
"Check if TYPE is a valid link type.
Valid types are :node-tag, :node-field, and :tag-ref.
Returns:
- t if valid
- nil if invalid"
(memq type '(:node-tag :node-field :tag-ref)))
(defun org-supertag-db-valid-link-p (type from to props)
"Validate link data.
TYPE is the link type
FROM is the source entity ID
TO is the target entity ID
PROPS is the link properties
Returns:
- t if valid
- nil if invalid"
(let* ((structure (org-supertag-db-get-link-type type))
(required (plist-get structure :required))
(from-obj (org-supertag-db-get from))
(to-obj (org-supertag-db-get to)))
;; 1. Validate required properties
(and (cl-every (lambda (key)
(plist-member props key))
required)
;; 2. Type-specific validation
(pcase type
(:node-field
(and (eq (plist-get from-obj :type) :node)
(stringp to)
(plist-get props :value)))
(:node-tag
(and (eq (plist-get from-obj :type) :node)
(eq (plist-get to-obj :type) :tag)))
(:tag-ref
(and (eq (plist-get from-obj :type) :tag)
(eq (plist-get to-obj :type) :tag)))
(_ t)))))
;; Entity Property Validation
(defun org-supertag-db--validate-props (type props)
"Validate entity properties against structure definition.
TYPE must be :node or :tag
PROPS is the entity property list
Returns:
- t if valid
- Signals error if invalid"
(let* ((struct (cl-find type org-supertag-db-object-structure
:key (lambda (x) (plist-get x :type))))
(required (plist-get struct :required)))
;; Validate type
(unless struct
(error "Invalid entity type: %s" type))
;; Validate required properties
(let ((missing-props
(cl-remove-if
(lambda (key) (plist-member props key))
required)))
(when missing-props
(error "Missing required properties: %s" missing-props)))))
;;; Tag Entity Structure
;;; (:type :tag :name "name" :fields (field-def ...))
;;; field-def structure:
;;; (:name "name" :type type :display-name "display" :description "desc" ...)
(defun org-supertag-db--validate-tag-def (props)
"Validate tag definition.
PROPS is the tag property list.
Returns:
- t if valid
- nil if invalid"
(let ((type (plist-get props :type))
(name (plist-get props :name))
(fields (plist-get props :fields)))
(and (eq type :tag)
(stringp name)
(not (string-empty-p name))
;; Validate field list
(or (null fields) ; Fields can be empty
(and (listp fields)
(cl-every #'org-supertag-db--validate-field-def fields))))))
(defun org-supertag-db--validate-field-def (field)
"Validate field definition.
FIELD is the field definition plist.
Returns:
- t if valid
- nil if invalid"
(let ((name (plist-get field :name))
(type (plist-get field :type)))
(and (stringp name)
(not (string-empty-p name))
(symbolp type)
(alist-get type org-supertag-field-types))))
;;------------------------------------------------------------------------------
;; Core Data Tables
;;------------------------------------------------------------------------------
(defvar org-supertag-db--object (ht-create)
"Entity storage - id -> plist.")
(defvar org-supertag-db--link (ht-create)
"Link storage - rel-id -> (type from to props).")
;; Event System
(defvar org-supertag-db--events (ht-create)
"Event system - store event handlers.")
;; Cache System
(defvar org-supertag-db--cache (ht-create)
"Cache system - store query cache.")
;;---------------------------------------------------------------------------------
;; Data Operation: Add
;;---------------------------------------------------------------------------------
(defun org-supertag-db-add (id props)
"Add or update entity.
ID is entity unique identifier
PROPS is entity properties
Returns:
- Success: entity ID
- Error: throws error"
(condition-case err
(let* ((type (plist-get props :type))
(old-props (org-supertag-db-get id))
(is-update (not (null old-props)))
;; Normalize properties
(clean-props (org-supertag-db--normalize-props props))
(new-props (if is-update
;; Update: new props take precedence, but preserve content
(org-supertag-db--normalize-props
(append
clean-props
(when (and (not (plist-get clean-props :content))
(plist-get old-props :content))
(list :content (plist-get old-props :content)))
(list :created-at (plist-get old-props :created-at)
:modified-at (current-time))))
;; New creation
(org-supertag-db--normalize-props
(append
clean-props
(list :created-at (current-time)))))))
;; 1. Validation
;; 1.1 Validate type
(unless (org-supertag-db-valid-object-type-p type)
(error "Invalid object type: %s" type))
;; 1.2 Validate properties
(unless (org-supertag-db-valid-object-p type new-props)
(error "Invalid object properties"))
;; 2. Pre-storage processing
(when is-update
;; 2.1 Handle type changes
(let ((old-type (plist-get old-props :type)))
(unless (eq old-type type)
;; Clear all related caches on type change
(org-supertag-db--cache-clear-for-type old-type id))))
;; 3. Store entity
(ht-set! org-supertag-db--object id new-props)
;; 4. Cache management
;; 4.1 Clear entity cache
(org-supertag-db--cache-remove 'entity id)
;; 4.2 Clear query cache
(org-supertag-db--cache-remove 'query (format "type:%s" type))
;; 4.3 Clear type-specific caches
(pcase type
(:node
;; Clear node-related caches
(org-supertag-db--cache-remove 'query (format "node-tags:%s" id))
(org-supertag-db--cache-remove 'query (format "node-fields:%s" id))
(org-supertag-db--cache-remove 'query (format "node-refs:%s" id)))
(:tag
;; Clear tag-related caches
(org-supertag-db--cache-remove 'query (format "tag-fields:%s" id))
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" id))))
;; 5. Trigger events
(if is-update
(progn
(org-supertag-db-emit 'entity:before-update type id old-props new-props)
(org-supertag-db-emit 'entity:updated type id old-props new-props))
(progn
(org-supertag-db-emit 'entity:before-create type id new-props)
(org-supertag-db-emit 'entity:created type id new-props)))
;; 6. Database state management
;; 6.1 Mark database as dirty
(org-supertag-db--mark-dirty)
;; 6.2 Schedule delayed save
(org-supertag-db-save)
;; 7. Return ID
id)
;; Error handling
(error
(message "[org-supertag-db-add] Error in entity add/update: %s" (error-message-string err))
(signal (car err) (cdr err)))))
(defun org-supertag-db-exists-p (id)
"Check if entity exists.
ID is the entity identifier.
Returns:
- t if exists
- nil if not exists"
(ht-contains-p org-supertag-db--object id))
;;---------------------------------------------------------------------------------
;; Data Operation: Link
;;---------------------------------------------------------------------------------
(defun org-supertag-db-link (type from to &optional props)
"Create or update a link between entities.
TYPE: Link type (:node-tag, :node-field, :tag-ref)
FROM: Source entity ID
TO: Target entity ID
PROPS: Optional link properties
Returns:
- Link ID if successful
- Signals error if validation fails"
(let* ((base-props (list :from from :to to))
(full-props (if props
(append base-props props)
base-props))
(rel-id (format "%s:%s->%s" type from to)))
;; 1. Validate
;; 1.1 Validate link type
(unless (org-supertag-db-valid-link-type-p type)
(error "Invalid link type: %s" type))
;; 1.2 Validate link data
(unless (org-supertag-db-valid-link-p type from to full-props)
(error "Invalid link data"))
;; 2. Check if link already exists
(if-let ((existing (ht-get org-supertag-db--link rel-id)))
;; If exists with same props, return ID
(if (equal existing full-props)
rel-id
;; Otherwise update link
(progn
(ht-set! org-supertag-db--link rel-id full-props)
rel-id))
;; 3. Create new link if not exists
(progn
(ht-set! org-supertag-db--link rel-id full-props)
;; 4. Clear caches
(org-supertag-db--cache-remove 'link rel-id)
(org-supertag-db--cache-remove 'query (format "links:%s:%s" type from))
(org-supertag-db--cache-remove 'query (format "links:%s:%s" type to))
;; 5. Clear type-specific caches
(pcase type
(:node-field
(org-supertag-db--cache-remove 'query (format "node-fields:%s" from)))
(:node-tag
(org-supertag-db--cache-remove 'query (format "node-tags:%s" from)))
(:tag-ref
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" from))
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" to))))
;; 6. Trigger event
(org-supertag-db-emit 'link:created type from to props)
;; 7. Mark database as dirty
(org-supertag-db--mark-dirty)
;; 8. Schedule delayed save
(org-supertag-db--schedule-save)
;; 9. Return link ID
rel-id))))
(defun org-supertag-db-unlink (type from to &optional dry-run)
"Remove a link between entities.
TYPE: Link type (:node-tag, :node-field, :tag-ref)
FROM: Source entity ID
TO: Target entity ID
DRY-RUN: If non-nil, only return data to be deleted without actual deletion
Returns:
- t if removed or exists (in dry-run mode)
- nil if link does not exist"
(let* ((rel-id (format "%s:%s->%s" type from to))
(exists (ht-contains-p org-supertag-db--link rel-id))
(link-props (and exists (ht-get org-supertag-db--link rel-id))))
(when (and exists (not dry-run))
;; 1. Remove link
(ht-remove! org-supertag-db--link rel-id)
;; 2. Clear caches
;; 2.1 Clear link cache
(org-supertag-db--cache-remove 'link rel-id)
;; 2.2 Clear query caches
(org-supertag-db--cache-remove 'query (format "links:%s:%s" type from))
(org-supertag-db--cache-remove 'query (format "links:%s:%s" type to))
;; 3. Clear type-specific caches
(pcase type
(:node-field
(org-supertag-db--cache-remove 'query (format "node-fields:%s" from)))
(:node-tag
(org-supertag-db--cache-remove 'query (format "node-tags:%s" from)))
(:tag-ref
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" from))
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" to))))
;; 4. Trigger event
(org-supertag-db-emit 'link:removed type from to link-props)
;; 5. Mark database as dirty
(org-supertag-db--mark-dirty)
;; 6. Schedule delayed save
(org-supertag-db--schedule-save))
;; Return result
exists))
(defun org-supertag-db-unlink-all (from &optional type)
"Remove all links or links of specific type from an entity.
FROM: Source entity ID
TYPE: Optional link type filter
Returns:
- Number of removed links"
(let ((count 0)
(removed-links nil)) ; Store removed links for events
;; 1. Collect links to remove
(ht-map (lambda (k v)
(when (and (equal from (plist-get v :from))
(or (null type)
(equal type (plist-get v :type))))
;; Save link info for removal
(push (cons k v) removed-links)))
org-supertag-db--link)
;; 2. Process removals
(dolist (link removed-links)
(let* ((rel-id (car link))
(props (cdr link))
(link-type (plist-get props :type))
(to (plist-get props :to)))
;; 2.1 Remove link
(ht-remove! org-supertag-db--link rel-id)
;; 2.2 Clear link caches
(org-supertag-db--cache-remove 'link rel-id)
(org-supertag-db--cache-remove 'query (format "links:%s:%s" link-type from))
(org-supertag-db--cache-remove 'query (format "links:%s:%s" link-type to))
;; 2.3 Clear type-specific caches
(pcase link-type
(:node-field
(org-supertag-db--cache-remove 'query (format "node-fields:%s" from)))
(:node-tag
(org-supertag-db--cache-remove 'query (format "node-tags:%s" from)))
(:tag-ref
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" from))
(org-supertag-db--cache-remove 'query (format "tag-refs:%s" to))))
;; 2.4 Trigger single link removal event
(org-supertag-db-emit 'link:removed link-type from to props)
(cl-incf count)))
;; 3. If any links were removed
(when (> count 0)
;; 3.1 Trigger batch removal event
(org-supertag-db-emit 'links:batch-removed from type count)
;; 3.2 Mark database as dirty
(org-supertag-db--mark-dirty)
;; 3.3 Schedule delayed save
(org-supertag-db--schedule-save))
;; 4. Return removal count
count))
;;---------------------------------------------------------------------------------
;; Data Operation: Get
;;---------------------------------------------------------------------------------
;; org-supertag-db-get (Single Entity Query)
;; ├── org-supertag-db-get-prop (Property Access)
;; │ └── org-supertag-db-get-type (Type Access Helper)
(defun org-supertag-db-get-node-tags (node-id)
"Get all tags for a node.
NODE-ID is the node identifier."
(org-supertag-db-get-link :node-tag node-id))
(defun org-supertag-db-get-node-fields (node-id)
"Get all field values for a node.
NODE-ID is the node identifier."
(org-supertag-db-get-link :node-field node-id))
(defun org-supertag-db-get-tag-refs (tag-id)
"Get all references for a tag.
TAG-ID is the tag identifier."
(org-supertag-db-get-link :tag-ref tag-id))
(defun org-supertag-db-get (id &optional default)
"Get property list for an entity.
ID is the entity identifier
DEFAULT is the value returned if entity doesn't exist
Returns:
- Property list if entity exists
- DEFAULT (nil if not provided) if entity doesn't exist
- Property list format: (:type type :prop1 value1 ...)
First tries to read from cache, falls back to entity table if cache miss."
(or (org-supertag-db--cache-get 'entity id)
(let ((value (or (ht-get org-supertag-db--object id)
default)))
(when value
(org-supertag-db--cache-set 'entity id value))
value)))
(defun org-supertag-db-get-prop (id prop &optional default)
"Get specific property value for an entity.
ID is the entity identifier
PROP is the property to get
DEFAULT is returned if entity or property doesn't exist."
(if-let ((props (org-supertag-db-get id)))
(or (plist-get props prop) default)
default))
(defun org-supertag-db-get-type (id)
"Get entity type.
ID is the entity identifier."
(org-supertag-db-get-prop id :type))
;; Get Link
(defun org-supertag-db-get-link-type (type)
"Get link type definition.
TYPE is the link type."
(pcase type
(:node-field
'(:type :node-field
:required (:from :to :tag-id :value)
:optional (:created-at :modified-at)))
(:node-tag
'(:type :node-tag
:required (:from :to)
:optional (:created-at :modified-at)))
(:tag-ref
'(:type :tag-ref
:required (:from :to :ref-type)
:optional (:created-at :modified-at)))
(_ nil)))
(defun org-supertag-db-get-link (type from)
"Get links.
TYPE is the link type
FROM is the source entity ID."
(let ((links nil)
(prefix (format "%s:%s->" type from)))
(ht-map (lambda (key value)
(when (string-prefix-p prefix key)
(push (cons key value) links)))
org-supertag-db--link)
links))
(defun org-supertag-db-get-links-type (link-type)
"Get all links of specified type.
LINK-TYPE is the link type like :node-tag, :node-field etc."
(let ((links '()))
(maphash (lambda (k v)
(when (and (stringp k)
(string-prefix-p (format ":%s:" link-type) k))
(push (cons k v) links)))
org-supertag-db--link)
links))
(defun org-supertag-db-get-link-reverse (type to)
"Get reverse links.
TYPE is the link type
TO is the target entity ID
Returns list in format ((from props) ...)"
(let (results)
(ht-map (lambda (k v)
(when (and (equal type (car v))
(equal to (nth 2 v))
(equal (plist-get v :from) (plist-get v :to)))
(push (list (cadr v) (nth 3 v)) results)))
org-supertag-db--link)
(nreverse results)))
(defun org-supertag-db-get-all-link ()
"Get all links.
Returns list in format ((type from to props) ...)"
(let (results)
(ht-map (lambda (k v)
(push v results))
org-supertag-db--link)
results))
;; Get Ref Link
(defun org-supertag-db-get-ref-context (ref-id)
"Get reference context information.
REF-ID is the reference link ID
Returns:
- Reference context if successful
- nil if failed"
(when-let* ((props (org-supertag-db-get-link ref-id))
(context (plist-get props :ref-context))
(ref-node (org-supertag-db-get ref-id))
(ref-from (plist-get ref-node :ref-from))
(ref-to (plist-get ref-node :ref-to))
(ref-count (plist-get ref-node :ref-count)))
context))
(defun org-supertag-db-get-ref-pos (ref-id)
"Get reference position information.
REF-ID is the reference link ID
Returns:
- Reference position if successful
- nil if failed"
(when-let* ((props (org-supertag-db-get-link ref-id))
(pos (plist-get props :ref-pos))
(ref-node (org-supertag-db-get ref-id))
(ref-from (plist-get ref-node :ref-from))
(ref-to (plist-get ref-node :ref-to))
(ref-count (plist-get ref-node :ref-count)))
pos))
(defun org-supertag-db-get-all ()
"Get all entities in database."
(ht-items org-supertag-db--object))
(defun org-supertag-db-get-tag-nodes (tag-id)
"Get all nodes that have TAG-ID.
Returns a list of node IDs."
(let ((nodes nil))
;; Iterate over all relationships
(maphash (lambda (_key relation)
(when (and (eq (plist-get relation :type) :node-tag)
(equal (plist-get relation :to) tag-id))
(push (plist-get relation :from) nodes)))
org-supertag-db--link)
nodes))
(defun org-supertag-get-all-files ()
"Get list of all org files in database.
Returns:
- List of absolute file paths
- nil if no files found
Notes:
1. Only returns files associated with nodes
2. Removes duplicates
3. Ensures paths exist"
(let ((files nil))
(maphash
(lambda (id props)
(when (and (eq (plist-get props :type) :node)
(plist-get props :file-path))
(let ((file-path (plist-get props :file-path)))
(when (file-exists-p file-path)
(push file-path files)))))
org-supertag-db--object)
(delete-dups (nreverse files))))
;;---------------------------------------------------------------------------------
;; Data Operation: Find
;;---------------------------------------------------------------------------------
;; org-supertag-db-find (base)
;; ├── org-supertag-db-find-by-props (Generic Property Query)
;; │ └── org-supertag-db-find-by-type (Type Query Helper)
(defun org-supertag-db-find (pred)
"Find entities that match the condition.
PRED is the predicate function.
Returns:
- List of matching entities
- nil if no matches found"
(let ((cache-key (format "%s" (sxhash pred))))
;; Try to get from cache
(or (org-supertag-db--cache-get 'query cache-key)
;; Cache miss, execute query
(let (results)
(ht-map (lambda (k v)
(when (funcall pred k v)
(push (cons k v) results)))
org-supertag-db--object)
;; Cache and return results
(let ((final-results (nreverse results)))
(org-supertag-db--cache-set 'query cache-key final-results)
final-results)))))
(defun org-supertag-db--check-match (props rules)
"Check if properties match the condition.
PROPS is the properties to check
RULES is the rules to match
Returns:
- t if all rules match
- nil if any rule fails"
(cl-loop for (key value) on rules by #'cddr
always (equal (plist-get props key) value)))
(defun org-supertag-db-find-by-props (props &optional predicate)
"Find entities that match the property condition.
PROPS is the properties to check
PREDICATE is an optional function that takes a property list and returns boolean
Returns:
- List of matching entities
- nil if no matches found"
(org-supertag-db-find
(lambda (_k v)
(and (org-supertag-db--check-match v props)
(or (null predicate)
(funcall predicate v))))))
(defun org-supertag-db-find-by-type (type &optional predicate)
"Find entities of specified type with optional predicate.
TYPE is the entity type to find (:node, :tag, etc)
PREDICATE is an optional function that takes a property list and returns boolean
Returns:
- List of matching entity IDs
- nil if no matches found"
(let ((base-pred (lambda (k v)
(eq (plist-get v :type) type))))
(if predicate
(mapcar #'car
(org-supertag-db-find
(lambda (k v)
(and (funcall base-pred k v)
(funcall predicate v)))))
(mapcar #'car
(org-supertag-db-find base-pred)))))
(defun org-supertag-db-find-nodes-by-tag (tag-id)
"Find nodes that use the specified tag.
TAG-ID is the tag ID to find
Returns:
- List of node IDs that use the tag
- nil if no matches found"
(let ((links (org-supertag-db-find-links :node-tag nil tag-id)))
(message "Found links: %S" links) ; Debug info
(mapcar (lambda (link)
(plist-get link :from))
links)))
(defun org-supertag-db-find-nodes-by-field-value (field-name value &optional tag-id)
"Find nodes with the specified field value.
FIELD-NAME is the field name to check
VALUE is the value to check
TAG-ID is the optional tag ID to filter by
Returns:
- List of matching nodes
- nil if no matches found"
(let ((links (org-supertag-db-find-links :type :node-field)))
(cl-remove-if-not
(lambda (link)
(and (equal (plist-get (nth 3 link) :value) value)
(or (null tag-id)
(equal (plist-get (nth 3 link) :tag-id) tag-id))))
links)))
(defun org-supertag-db-find-tags-by-field (field-name)
"Find tags that contain the specified field.
FIELD-NAME is the field name to check
Returns:
- List of matching tags
- nil if no matches found"
(org-supertag-db-find
(lambda (_k v)
(and (eq (plist-get v :type) :tag)
(assoc field-name (plist-get v :fields))))))
(defun org-supertag-db-find-links (type from to)
"Find links matching the specified criteria.
TYPE is the link type to find
FROM is the optional source node ID
TO is the optional target node ID
Returns:
- List of matching links
- nil if no matches found"
(let (results)
(ht-map (lambda (k v)
(when (and (or (null type) (eq (plist-get v :type) type))
(or (null from) (equal (plist-get v :from) from))
(or (null to) (equal (plist-get v :to) to)))
(push v results)))
org-supertag-db--link)
results))
;;---------------------------------------------------------------------------------
;; Data Operation: Remove
;;---------------------------------------------------------------------------------
(defun org-supertag-db-remove-object (id &optional dry-run)
"Remove an entity and all its related relationships.
ID: Entity identifier
DRY-RUN: If non-nil, only return data to be deleted without actual deletion
Returns:
A cons cell in the form (entity . links) where:
- entity is the deleted entity data
- links is the list of deleted relationships
Returns nil if entity does not exist"
(condition-case err
(when-let* ((entity (org-supertag-db-get id))
(type (plist-get entity :type))
(outgoing-links (org-supertag-db-get-link nil id))
(incoming-links (org-supertag-db-get-link-reverse nil id))
(removed-data (list :entity entity
:outgoing-links outgoing-links
:incoming-links incoming-links)))
;; 1. Trigger pre-removal event
(org-supertag-db-emit 'entity:before-remove type id entity)
;; 2. Execute actual deletion if not dry-run
(unless dry-run
;; 2.1 Remove associated relationships
(let ((link-count (org-supertag-db-unlink-all id)))
;; 2.2 Remove reverse relationships
(dolist (rev-link incoming-links)
(org-supertag-db-unlink nil (car rev-link) id))
;; 2.3 Perform type-specific cleanup
(pcase type
(:node
;; Clean node-related data