瀏覽代碼

refactor: use nanoid for user classes and properties

This commit doesn't affect old idents because RTC is not supported yet
and all new user idents will be `:user-or-whatever-namespace/nanoid`.
Tienson Qin 1 年之前
父節點
當前提交
68ad7f4f19

+ 69 - 0
deps/db/src/logseq/db/frontend/db_ident.cljc

@@ -0,0 +1,69 @@
+(ns logseq.db.frontend.db-ident
+  "Helper fns for class and property :db/ident"
+  (:require [datascript.core :as d]
+            [clojure.string :as string]))
+
+(defn ensure-unique-db-ident
+  "Ensures the given db-ident is unique. If a db-ident conflicts, it is made
+  unique by adding a suffix with a unique number e.g. :db-ident-1 :db-ident-2"
+  [db db-ident]
+  (if (d/entity db db-ident)
+    (let [existing-idents
+          (d/q '[:find [?ident ...]
+                 :in $ ?ident-name
+                 :where
+                 [?b :db/ident ?ident]
+                 [(str ?ident) ?str-ident]
+                 [(clojure.string/starts-with? ?str-ident ?ident-name)]]
+               db
+               (str db-ident "-"))
+          new-ident (if-let [max-num (->> existing-idents
+                                          (keep #(parse-long (string/replace-first (str %) (str db-ident "-") "")))
+                                          (apply max))]
+                      (keyword (namespace db-ident) (str (name db-ident) "-" (inc max-num)))
+                      (keyword (namespace db-ident) (str (name db-ident) "-1")))]
+      new-ident)
+    db-ident))
+
+(def ^:private non-int-char-range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+(def alphabet
+  (mapv str "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"))
+
+(defn- random-bytes
+  [size]
+  #?(:org.babashka/nbb
+     nil
+     :default
+     (let [seed (js/Uint8Array. size)]
+       (.getRandomValues js/crypto seed)
+       (array-seq seed))))
+
+(defn nano-id
+  "Random id generator"
+  ([]
+   (nano-id 21))
+  ([size]
+   (let [mask' 0x3f]
+     (loop [bs (random-bytes size)
+            id ""]
+       (if bs
+         (recur (next bs)
+                (->> (first bs)
+                     (bit-and mask')
+                     alphabet
+                     (str id)))
+         id)))))
+
+;; TODO: db ident should obey clojure's rules for keywords
+(defn create-db-ident-from-name
+  "Creates a :db/ident for a class or property by sanitizing the given name.
+
+   NOTE: Only use this when creating a db-ident for a new class/property. Using
+   this in read-only contexts like querying can result in db-ident conflicts"
+  [user-namespace name-string]
+  {:pre [(or (keyword? user-namespace) (string? user-namespace)) (string? name-string)]}
+  (if (exists? js/process)
+    ;; So that we don't have to change :user.{property|class} in our tests
+    (keyword user-namespace name-string)
+    (keyword user-namespace (str (rand-nth non-int-char-range) (nano-id 20)))))

+ 0 - 77
deps/db/src/logseq/db/frontend/db_ident.cljs

@@ -1,77 +0,0 @@
-(ns logseq.db.frontend.db-ident
-  "Helper fns for class and property :db/ident"
-  (:require [datascript.core :as d]
-            [clojure.string :as string]
-            [clojure.edn :as edn]))
-
-(defn ensure-unique-db-ident
-  "Ensures the given db-ident is unique. If a db-ident conflicts, it is made
-  unique by adding a suffix with a unique number e.g. :db-ident-1 :db-ident-2"
-  [db db-ident]
-  (if (d/entity db db-ident)
-    (let [existing-idents
-          (d/q '[:find [?ident ...]
-                 :in $ ?ident-name
-                 :where
-                 [?b :db/ident ?ident]
-                 [(str ?ident) ?str-ident]
-                 [(clojure.string/starts-with? ?str-ident ?ident-name)]]
-               db
-               (str db-ident "-"))
-          new-ident (if-let [max-num (->> existing-idents
-                                          (keep #(parse-long (string/replace-first (str %) (str db-ident "-") "")))
-                                          (apply max))]
-                      (keyword (namespace db-ident) (str (name db-ident) "-" (inc max-num)))
-                      (keyword (namespace db-ident) (str (name db-ident) "-1")))]
-      new-ident)
-    db-ident))
-
-(def ^:private nano-char-range "-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-(def ^:private non-int-nano-char-range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
-
-(defn- nano-id-char []
-  (rand-nth nano-char-range))
-
-(defn- nano-id [length]
-  (assert (> length 1))
-  (str
-   (rand-nth non-int-nano-char-range)
-   (->> (repeatedly (dec length) nano-id-char)
-        (string/join))))
-
-;; TODO: db ident should obey clojure's rules for keywords
-(defn create-db-ident-from-name
-  "Creates a :db/ident for a class or property by sanitizing the given name.
-
-   NOTE: Only use this when creating a db-ident for a new class/property. Using
-   this in read-only contexts like querying can result in db-ident conflicts"
-  [user-namespace name-string]
-  {:pre [(string? name-string)]}
-  (let [n (-> name-string
-              (string/replace #"(^:\s*|\s*:$)" "")
-              (string/replace #"\s*:\s*$" "")
-              (string/replace-first #"^\d+" "")
-              (string/replace " " "-")
-              ;; '/' cannot be in name - https://clojure.org/reference/reader
-              (string/replace "/" "-")
-              (string/replace #"[#()]" "")
-              (string/trim))
-        ;; Similar check to common-util/valid-edn-keyword?. Consider merging the two use cases
-        keyword-is-valid-edn! (fn keyword-is-valid-edn! [k]
-                                (when-not (= k (edn/read-string (str k)))
-                                  (throw (ex-info "Keyword is not valid edn" {:keyword k}))))
-        k (if (seq n)
-            (keyword user-namespace n)
-            (keyword user-namespace (nano-id 8)))]
-    (try
-      (keyword-is-valid-edn! k)
-      k
-      (catch :default _e
-        (js/console.error "Generating backup db-ident for keyword" (str k))
-        (let [n (->> (filter #(re-find #"[0-9a-zA-Z-]{1}" %) (seq n))
-                     (apply str))
-              k (if (seq n)
-                  (keyword user-namespace n)
-                  (keyword user-namespace (nano-id 8)))]
-          (keyword-is-valid-edn! k)
-          k)))))

+ 0 - 17
deps/db/test/logseq/db/frontend/db_ident_test.cljs

@@ -1,17 +0,0 @@
-(ns logseq.db.frontend.db-ident-test
-  (:require [cljs.test :refer [deftest is]]
-            [logseq.db.frontend.db-ident :as db-ident]))
-
-(deftest create-db-ident-from-name
-  (is (= "Whiteboard-Object"
-         ;; Example from docs graph
-         (name (db-ident/create-db-ident-from-name "user.class" "Whiteboard/Object")))
-      "ident names must not have '/' because it is a special symbol for the reader")
-
-  ;; https://github.com/logseq/db-test/issues/4
-  (is (= "Deep-Neural-Networks-DNN"
-         (name (db-ident/create-db-ident-from-name "user.class" "Deep Neural Networks (DNN)")))
-      "ident names don't fail on special characters like parenthesis")
-
-  (is (seq (name (db-ident/create-db-ident-from-name "user.class" "123")))
-      "ident names can only have numbers"))