File size: 2,378 Bytes
6bff5d9
 
 
 
0721bb4
 
 
 
 
 
 
 
6bff5d9
 
 
 
 
 
 
 
 
 
 
 
 
 
0721bb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bff5d9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0721bb4
6bff5d9
 
 
 
 
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
"""JSON IR (intermediate representation) Pydantic models.

See ARCHITECTURE.md §7 for the schema.

Scope: filter, group_by, agg, order_by, limit, plus a single-level `joins` to
related tables in the SAME source (KM-652 T4). having, offset, and boolean tree
filters are still deferred. Joins are supported on database (schema) sources only;
the validator rejects them on tabular sources (cross-file merge is a later step).

With joins, `select` / `group_by` / `filters` / `order_by` may reference a
`column_id` from the base table OR any joined table (column_ids are globally
unique), so a measure in one table can be grouped by a dimension in a related one.
"""

from typing import Any, Literal

from pydantic import BaseModel, Field

FilterOp = Literal[
    "=", "!=", "<", "<=", ">", ">=",
    "in", "not_in", "is_null", "is_not_null",
    "like", "between",
]
AggFn = Literal["count", "count_distinct", "sum", "avg", "min", "max"]
ValueType = Literal["int", "decimal", "string", "datetime", "date", "bool"]
SortDir = Literal["asc", "desc"]
JoinType = Literal["inner", "left"]


class Join(BaseModel):
    """A single equi-join to another table in the same source.

    `left_column_id` is a column already in the query (base table or an earlier
    join); `right_column_id` is a column in `target_table_id`. The validator
    requires the pair to match a foreign key declared in the catalog.
    """

    target_table_id: str
    left_column_id: str
    right_column_id: str
    type: JoinType = "inner"


class ColumnSelect(BaseModel):
    kind: Literal["column"] = "column"
    column_id: str
    alias: str | None = None


class AggSelect(BaseModel):
    kind: Literal["agg"] = "agg"
    fn: AggFn
    column_id: str | None = None
    alias: str | None = None


SelectItem = ColumnSelect | AggSelect


class FilterClause(BaseModel):
    column_id: str
    op: FilterOp
    value: Any
    value_type: ValueType


class OrderByClause(BaseModel):
    column_id: str
    dir: SortDir = "asc"


class QueryIR(BaseModel):
    ir_version: str = "1.0"
    source_id: str
    table_id: str
    joins: list[Join] = Field(default_factory=list)
    select: list[SelectItem]
    filters: list[FilterClause] = Field(default_factory=list)
    group_by: list[str] = Field(default_factory=list)
    order_by: list[OrderByClause] = Field(default_factory=list)
    limit: int | None = None