(ns logseq.db-sync.index (:require [clojure.string :as string] [logseq.db-sync.common :as common] [promesa.core :as p])) (def ^:private user-upsert-cache-ttl-ms (* 60 60 1000)) (def ^:private user-upsert-cache-max 1024) (defonce ^:private *user-upsert-cache (atom {})) (def ^:private activity-touch-cache-max 8192) (defonce ^:private *activity-touch-cache (atom {})) (defn- prune-user-upsert-cache! [now-ms] (swap! *user-upsert-cache (fn [cache] (let [cache (into {} (remove (fn [[_ {:keys [cached-at]}]] (>= (- now-ms cached-at) user-upsert-cache-ttl-ms))) cache) count-cache (count cache)] (if (> count-cache user-upsert-cache-max) (into {} (drop (- count-cache user-upsert-cache-max) (sort-by (comp :cached-at val) cache))) cache))))) (defn- cache-user-upsert! [user-id email email-verified username now-ms] (swap! *user-upsert-cache assoc user-id {:email email :email-verified email-verified :username username :cached-at now-ms}) (prune-user-upsert-cache! now-ms)) (defn- graph-e2ee-sql->bool [v] (cond (nil? v) true (or (= 1 v) (= "1" v)) true (or (= 0 v) (= "0" v)) false :else (true? v))) (defn- graph-e2ee-bool->sql [v] (if (false? v) 0 1)) (defn- graph-ready-for-use-sql->bool [v] (cond (nil? v) true (or (= 1 v) (= "1" v)) true (or (= 0 v) (= "0" v)) false :else (true? v))) (defn- graph-ready-for-use-bool->sql [v] (if (false? v) 0 1)) (def ^:private graph-e2ee-migration-sql "alter table graphs add column graph_e2ee INTEGER DEFAULT 1") (def ^:private graph-ready-for-use-migration-sql "alter table graphs add column graph_ready_for_use integer default 1") (def ^:private user-created-at-migration-sql "alter table users add column created_at integer") (def ^:private daily-active-entities-create-table-sql (str "create table if not exists daily_active_entities (" "day_utc TEXT," "entity_type TEXT," "entity_id TEXT," "first_seen_at INTEGER," "primary key (day_utc, entity_type, entity_id)," "check (entity_type in ('user', 'graph'))" ");")) (def ^:private daily-active-entities-create-index-sql "create index if not exists idx_daily_active_entities_type_day on daily_active_entities (entity_type, day_utc)") (defn- duplicate-column-error? [error column-name] (let [message (-> (or (ex-message error) (some-> error .-message) (str error)) string/lower-case)] (and (string/includes? message "duplicate column") (string/includes? message (string/lower-case column-name))))) (defn- (common/ (p/let [result (common/ (common/ (p/let [result (common/ (common/ (p/let [result (common/ (.toISOString (js/Date. timestamp-ms)) (subs 0 10))) (defn- prune-activity-touch-cache! [cache] (if (<= (count cache) activity-touch-cache-max) cache (let [drop-count (- (count cache) activity-touch-cache-max)] (->> cache (sort-by val) (drop drop-count) (into {}))))) (defn- cache-activity-touch! [cache-key now-ms] (swap! *activity-touch-cache (fn [cache] (-> cache (assoc cache-key now-ms) (prune-activity-touch-cache!))))) (defn- activity-touch-cached? [cache-key] (contains? @*activity-touch-cache cache-key)) (defn- bool (aget row "graph_e2ee")) :graph-ready-for-use? (graph-ready-for-use-sql->bool (aget row "graph_ready_for_use")) :role (aget row "role") :invited-by (aget row "invited_by") :created-at (aget row "created_at") :updated-at (aget row "updated_at")}) rows)) [])) (defn decode-semantic-graph-cursor [cursor] (when (string? cursor) (try (let [value (js->clj (js/JSON.parse (js/atob cursor)))] (when (and (vector? value) (= 2 (count value)) (number? (first value)) (string? (second value))) value)) (catch :default _ nil)))) (defn- encode-semantic-graph-cursor [updated-at graph-id] (js/btoa (js/JSON.stringify (clj->js [updated-at graph-id])))) (defn [(str "(g.user_id = ? or m.user_id = ?) ") "g.graph_e2ee = 0" "g.graph_ready_for_use = 1"] (string? name) (conj "lower(g.graph_name) = lower(?)") cursor-key (conj "(g.updated_at < ? or (g.updated_at = ? and g.graph_id > ?))")) args (cond-> [user-id user-id user-id] (string? name) (conj name) cursor-key (conj cursor-updated-at cursor-updated-at cursor-graph-id) true (conj (inc limit))) sql (str "select g.graph_id, g.graph_name, g.schema_version, g.graph_ready_for_use, " "g.created_at, g.updated_at, m.role, m.invited_by " "from graphs g " "left join graph_members m on g.graph_id = m.graph_id and m.user_id = ? " "where " (string/join " and " conditions) " " "order by g.updated_at desc, g.graph_id asc limit ?")] (p/let [result (apply common/ (count rows) limit) selected (take limit rows) graphs (mapv (fn [row] {:graph-id (aget row "graph_id") :graph-name (aget row "graph_name") :schema-version (aget row "schema_version") :graph-e2ee? false :graph-ready-for-use? (graph-ready-for-use-sql->bool (aget row "graph_ready_for_use")) :role (aget row "role") :invited-by (aget row "invited_by") :created-at (aget row "created_at") :updated-at (aget row "updated_at")}) selected)] (cond-> {:graphs graphs} more? (assoc :next-cursor (let [row (nth rows (dec limit))] (encode-semantic-graph-cursor (aget row "updated_at") (aget row "graph_id")))))))) (defn sql graph-e2ee?) graph-ready-for-use? (graph-ready-for-use-bool->sql graph-ready-for-use?) result (common/bool (some-> row (aget "graph_ready_for_use")))))) (defn bool (some-> row (aget "graph_e2ee")))))) (defn sql graph-ready-for-use?) (common/now-ms) graph-id))) (defn