Skip to content

Commit

Permalink
쿼리 최대 뎁스 분석 추가 (#19)
Browse files Browse the repository at this point in the history
* add max depth

* refactor

* nit

* doc string

* nit

* nit
  • Loading branch information
1e16miin authored Feb 12, 2025
1 parent 5e1fca5 commit 7c722ef
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 12 deletions.
60 changes: 51 additions & 9 deletions src/com/walmartlabs/lacinia/query_analyzer.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,39 @@
(some? (or (:first arguments)
(:last arguments))))

(declare summarize-selection)

(defn ^:private summarize-sub-selections
"여러 하위 selection들을 요약하는 함수입니다.
파라미터:
- fragment-map: 프래그먼트 정의들의 맵
- depth: 현재 selection의 깊이 레벨
- sub-selections: 처리할 하위 selection들의 시퀀스
동작:
1. 각 하위 selection에 현재 깊이 정보를 추가
2. 각 하위 selection을 summarize-selection을 통해 재귀적으로 처리
3. 모든 하위 selection들의 요약 정보를 하나의 시퀀스로 결합
반환값:
- 모든 하위 selection들의 요약 정보가 포함된 시퀀스"
[fragment-map depth sub-selections]
(let [sub-selections' (map #(assoc % :depth depth) sub-selections)]
(mapcat #(summarize-selection % fragment-map) sub-selections')))

(defn ^:private summarize-selection
"Recursively summarizes the selection, handling field, inline fragment, and named fragment."
[{:keys [arguments selections field-name leaf? fragment-name] :as selection} fragment-map]
"selection을 재귀적으로 요약하며, 필드, inline fragment, named fragment를 처리합니다.
파라미터:
- selection: 처리할 노드
- fragment-map: fragment 정의들의 맵
반환값:
- 필드인 경우: 필드 이름, depth, selection 정보를 포함하는 맵의 벡터
- 프래그먼트인 경우: 프래그먼트 내부 selection들의 요약 정보
- leaf나 pageInfo인 경우: nil"
[{:keys [arguments selections field-name leaf? fragment-name depth] :as selection} fragment-map]
(let [selection-kind (selection/selection-kind selection)]
(cond
;; If it's a leaf or `pageInfo`, return nil.
Expand All @@ -17,18 +47,20 @@
;; If it's a named fragment, look it up in the fragment-map and process its selections.
(= :named-fragment selection-kind)
(let [sub-selections (:selections (fragment-map fragment-name))]
(mapcat #(summarize-selection % fragment-map) sub-selections))
(summarize-sub-selections fragment-map depth sub-selections))

;; If it's an inline fragment or `edges` field, process its selections.
(or (= :inline-fragment selection-kind) (= field-name :edges))
(mapcat #(summarize-selection % fragment-map) selections)
(summarize-sub-selections fragment-map depth selections)

;; Otherwise, handle a regular field with potential nested selections.
:else
(let [n-nodes (or (-> arguments (select-keys [:first :last]) vals first) 1)]
(let [depth' (inc depth)
n-nodes (or (-> arguments (select-keys [:first :last]) vals first) 1)]
[{:field-name field-name
:selections (mapcat #(summarize-selection % fragment-map) selections)
:selections (summarize-sub-selections fragment-map depth' selections)
:list-args? (list-args? arguments)
:depth depth'
:n-nodes n-nodes}]))))

(defn ^:private calculate-complexity
Expand All @@ -38,12 +70,22 @@
(* n-nodes children-complexity)
(+ n-nodes children-complexity))))

(defn ^:private get-max-depth
"selection 요약 정보에서 최대 depth를 계산합니다."
[{:keys [selections depth] :or {depth 0}}]
(if (empty? selections)
depth
(max depth (apply max (map get-max-depth selections)))))

(defn complexity-analysis
[query]
(let [{:keys [fragments selections]} query
summarized-selections (mapcat #(summarize-selection % fragments) selections)
complexity (apply + (map calculate-complexity summarized-selections))]
{:complexity complexity}))
init-depth 0
summarized-selections (summarize-sub-selections fragments init-depth selections)
complexity (apply + (map calculate-complexity summarized-selections))
max-depth (get-max-depth summarized-selections)]
{:complexity complexity
:max-depth max-depth}))

(defn enable-query-analyzer
[context]
Expand Down
9 changes: 6 additions & 3 deletions test/com/walmartlabs/lacinia/complexity_analysis_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
(testing "It is possible to calculate the complexity of a query in the Relay connection spec
by taking into account both named fragments and inline fragments."
(is (= {:data {:node nil}
:extensions {:analysis {:complexity 32}}}
:extensions {:analysis {:complexity 32
:max-depth 4}}}
(q "query ProductDetail($productId: ID){
node(id: $productId) {
... on Product {
Expand Down Expand Up @@ -116,7 +117,8 @@
}" {:productId "id"}))))
(testing "If no arguments are passed in the query, the calculation uses the default value defined in the schema."
(is (= {:data {:node nil}
:extensions {:analysis {:complexity 22}}}
:extensions {:analysis {:complexity 22
:max-depth 4}}}
(q "query ProductDetail($productId: ID){
node(id: $productId) {
... on Product {
Expand Down Expand Up @@ -160,7 +162,8 @@
}" {:productId "id"}))))
(testing "If return type of root query is scala, then complexity is 0"
(is (= {:data {:root nil}
:extensions {:analysis {:complexity 0}}}
:extensions {:analysis {:complexity 0
:max-depth 0}}}
(q "query root{
root
}" nil)))))
Expand Down

0 comments on commit 7c722ef

Please sign in to comment.