ObjectOS
참조

ObjectQL

/api/v1/data/*, 뷰, 리포트, AI 도구에서 사용하는 구조화된 쿼리 형식입니다.

ObjectQL

ObjectQL은 데이터 엔진이 사용하는 JSON 쿼리 형식입니다. /api/v1/data/:object가 받아들이는 형식이며, 뷰가 컴파일되는 대상이고, 리포트가 직렬화되는 형식이며, AI query_data 도구가 생성하는 형식입니다.

두 가지 형태가 있습니다.

  • 단순 목록(Simple list)where, orderBy, limit, expand 등을 쿼리 문자열 파라미터로 GET /api/v1/data/:object에 전달합니다.
  • 고급 쿼리(Advanced query) — 전체 쿼리 본문을 /api/v1/data/:object/query에 POST합니다 (groupByaggregations에 필요).

쿼리 형태

{
  object:        string,                  // required — target object name
  fields?:       string[],                // projection (default: all visible fields)
  where?:        FilterCondition,         // see Filters
  orderBy?:      SortNode[],              // [{ field, order: 'asc' | 'desc' }]
  limit?:        number,                  // page size
  offset?:       number,                  // offset pagination
  cursor?:       string,                  // cursor pagination (preferred)
  expand?:       string[],                // batch-resolve relation fields
  joins?:        JoinNode[],              // explicit joins (inner | left | right | full)
  groupBy?:      (string | GroupByNode)[],
  aggregations?: AggregationNode[],       // sum/avg/count/min/max/...
  having?:       FilterCondition,         // filter after aggregation
  distinct?:     boolean
}

스키마 소스: packages/spec/src/data/query.zod.ts.

필터(Filters)

where는 조건들의 트리입니다. 필드 연산자는 $로 접두됩니다. 논리 결합자($and, $or, $not)는 최상위 레벨에 위치합니다.

비교

연산자예시비고
$eq{ status: { $eq: "open" } }같음
$ne{ priority: { $ne: "low" } }같지 않음
$gt, $gte, $lt, $lte{ amount: { $gte: 1000 } }비교

모든 비교 연산자는 **필드 참조(field reference)**도 받아들여 필드 간 비교를 할 수 있습니다: { end_at: { $gt: { $field: "start_at" } } }.

집합 및 범위

연산자예시
$in{ status: { $in: ["new","open"] } }
$nin{ owner_id: { $nin: ["u_1","u_2"] } }
$between{ created_at: { $between: ["2026-01-01","2026-02-01"] } }

문자열

연산자예시비고
$contains{ subject: { $contains: "refund" } }대소문자 구분 안 함
$notContains{ notes: { $notContains: "test" } }
$startsWith{ email: { $startsWith: "@" } }
$endsWith{ email: { $endsWith: "@acme.com" } }

Null 및 존재 여부

연산자예시
$null{ closed_at: { $null: true } }
$exists{ external_id: { $exists: false } }

논리 결합자

{
  "$and": [
    { "status": { "$eq": "open" } },
    {
      "$or": [
        { "priority": { "$in": ["high", "urgent"] } },
        { "due_at":   { "$lt": { "$cel": "now()" } } }
      ]
    }
  ]
}

CEL 표현식은 { "$cel": "..." }로 삽입할 수 있습니다 — 서버가 쿼리 시점에 평가하는 "현재 기준(now-relative)" 필터에 유용합니다.

정렬

"orderBy": [
  { "field": "priority", "order": "desc" },
  { "field": "created_at", "order": "asc" }
]

쿼리 문자열 약식 표기: ?orderBy=-priority,created_at.

페이지네이션

커서(권장). 응답에 nextCursor가 포함됩니다. 이를 다음 요청의 cursor로 다시 전달하세요.

GET /api/v1/data/ticket?limit=50&orderBy=-created_at
→ { items: [...], nextCursor: "eyJ..." }

GET /api/v1/data/ticket?limit=50&cursor=eyJ...

오프셋. 더 단순하지만 대용량 테이블에서는 성능이 저하됩니다.

GET /api/v1/data/ticket?limit=50&offset=200

런타임은 ObjectSpec.maxPageSize를 통해 객체별로 limit을 제한합니다 (기본값 200).

관계 — expand

expand는 외래 키를 일괄 해석하여 N+1 문제를 방지합니다.

{
  "object": "support_ticket",
  "expand": ["assignee", "customer.account"],
  "limit": 20
}

각 티켓을 단순 ID가 아니라 assigneecustomer.account가 중첩 객체로 구체화된 형태로 반환합니다.

조인(Joins)

메타데이터 그래프 외부에서 임시 조인을 수행하려면 다음과 같이 합니다.

"joins": [
  { "type": "left", "object": "user", "as": "u", "on": "assignee_id = u.id" }
]

type: inner | left | right | full. 조인된 테이블은 as 별칭을 통해 where, orderBy, aggregations에서 접근할 수 있습니다.

집계(Aggregation)

POST /api/v1/data/:object/query에서만 가능합니다.

{
  "object": "order",
  "where": { "status": { "$ne": "cancelled" } },
  "groupBy": [
    "customer_id",
    { "field": "created_at", "dateGranularity": "month" }
  ],
  "aggregations": [
    { "function": "sum",   "field": "amount", "alias": "total_sales" },
    { "function": "count", "alias": "order_count" }
  ],
  "having": { "total_sales": { "$gt": 10000 } },
  "orderBy": [{ "field": "total_sales", "order": "desc" }],
  "limit": 25
}

함수: count, sum, avg, min, max, count_distinct, array_agg, string_agg.

날짜 단위(시간 버킷 기준 group-by용): day | week | month | quarter | year.

Distinct

{ "object": "ticket", "fields": ["status"], "distinct": true }

검색

searchable: true 필드 전반에 대한 전체 텍스트 검색은 GET /api/v1/search?q=...&object=ticket에서 제공됩니다. 객체별 점수 산정 규칙은 객체 스펙에서 구성합니다.

ObjectQL이 사용되는 곳

  • GET /api/v1/data/:object — 쿼리 문자열 형태
  • POST /api/v1/data/:object/query — 전체 본문, 집계 지원
  • 뷰 정의(filter, sort) — ObjectQL로 컴파일됨
  • 리포트 — ObjectQL로 직렬화됨
  • AI query_data 도구 — 승인 큐를 위한 ObjectQL 본문을 생성함

함께 보기

On this page