Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
573 changes: 533 additions & 40 deletions datajunction-server/datajunction_server/api/semantic_layer.py

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions datajunction-server/datajunction_server/database/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,28 @@ async def find_names(
result = await session.execute(statement)
return list(result.scalars().all())

@classmethod
async def find_names_with_display_names(
cls,
session: AsyncSession,
prefix: str | None = None,
node_type: NodeType | None = None,
) -> list[tuple[str, str | None]]:
"""Find active node names and display names without ORM hydration."""
statement = (
select(Node.name, NodeRevision.display_name)
.join(
NodeRevision,
and_(
Node.id == NodeRevision.node_id,
Node.current_version == NodeRevision.version,
),
)
.where(*cls._find_filters(prefix, node_type))
)
result = await session.execute(statement)
return list(result.tuples().all())

@classmethod
async def main_branch_names(
cls,
Expand Down
38 changes: 38 additions & 0 deletions datajunction-server/datajunction_server/internal/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,53 @@

import logging

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from datajunction_server.database.catalog import Catalog
from datajunction_server.database.custom_metadata_schema import CustomMetadataSchema
from datajunction_server.database.engine import Engine
from datajunction_server.models.dialect import Dialect
from datajunction_server.models.semantic_layer_metadata import (
SEMANTIC_LAYER_METADATA_DESCRIPTION,
SEMANTIC_LAYER_METADATA_KEY,
SEMANTIC_LAYER_METADATA_SCHEMA,
)
from datajunction_server.utils import get_settings

logger = logging.getLogger(__name__)
settings = get_settings()


async def seed_default_custom_metadata_schemas(session: AsyncSession) -> None:
"""Register global schemas owned by the DJ server."""
schema = (
await session.execute(
select(CustomMetadataSchema).where(
CustomMetadataSchema.key == SEMANTIC_LAYER_METADATA_KEY,
CustomMetadataSchema.namespace.is_(None),
CustomMetadataSchema.node_type.is_(None),
CustomMetadataSchema.deactivated_at.is_(None),
),
)
).scalar_one_or_none()
if schema is None:
schema = CustomMetadataSchema(
key=SEMANTIC_LAYER_METADATA_KEY,
namespace=None,
node_type=None,
)
session.add(schema)

schema.json_schema = SEMANTIC_LAYER_METADATA_SCHEMA
schema.value_kind = "object"
schema.filterable = False
schema.description = SEMANTIC_LAYER_METADATA_DESCRIPTION
schema.reserved = True
schema.deactivated_at = None
await session.commit()


async def seed_default_catalogs(session: AsyncSession):
"""
Seeds two default catalogs:
Expand Down Expand Up @@ -59,3 +95,5 @@ async def seed_default_catalogs(session: AsyncSession):
await session.commit()

logger.info("Added system catalog and engines")

await seed_default_custom_metadata_schemas(session)
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"""Built-in JSON Schema for semantic-layer custom metadata."""

SEMANTIC_LAYER_METADATA_KEY = "semantic_layer"
SEMANTIC_LAYER_METADATA_DESCRIPTION = (
"Portable semantic-layer column metadata and client-specific extensions."
)

_SEMANTIC_TYPES = [
"currency",
"percentage",
"proportion",
"count",
"duration",
"data_size",
"date",
"timestamp",
"identifier",
"category",
"url",
"boolean",
"number",
"string",
]

_FORMAT_PRESETS = [
"smart_number",
"number",
"currency",
"percentage",
"duration",
"data_size",
]

_FILTER_KINDS = [
"text",
"number",
"range",
"date",
"datetime",
"boolean",
"select",
]

_FILTER_OPERATORS = [
"=",
"!=",
">",
">=",
"<",
"<=",
"IN",
"NOT IN",
"IS NULL",
"IS NOT NULL",
"between",
"contains",
"starts_with",
"ends_with",
]

_COLUMN_METADATA_PROPERTIES = {
"display_name": {"type": "string"},
"semantic_type": {"type": "string", "enum": _SEMANTIC_TYPES},
"unit": {"$ref": "#/$defs/Unit"},
"attributes": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"format": {"$ref": "#/$defs/FormatMetadata"},
"filter": {"$ref": "#/$defs/FilterMetadata"},
"extensions": {"$ref": "#/$defs/Extensions"},
}

SEMANTIC_LAYER_METADATA_SCHEMA = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"ColumnMetadata": {
"type": "object",
"additionalProperties": False,
"properties": _COLUMN_METADATA_PROPERTIES,
},
"Unit": {
"oneOf": [
{"$ref": "#/$defs/AtomicUnit"},
{"$ref": "#/$defs/CompoundUnit"},
],
},
"AtomicUnit": {
"type": "object",
"additionalProperties": False,
"required": ["kind"],
"properties": {
"kind": {
"type": "string",
"enum": [
"currency",
"time",
"data_size",
"percentage",
"proportion",
"count",
"unitless",
],
},
"code": {"type": "string", "minLength": 1},
},
},
"CompoundUnit": {
"type": "object",
"additionalProperties": False,
"required": ["numerator", "denominator"],
"properties": {
"numerator": {"$ref": "#/$defs/AtomicUnit"},
"denominator": {"$ref": "#/$defs/AtomicUnit"},
},
},
"FormatMetadata": {
"type": "object",
"additionalProperties": False,
"properties": {
"preset": {"type": "string", "enum": _FORMAT_PRESETS},
"precision": {"type": "integer", "minimum": 0},
"scale": {"type": "number"},
},
},
"FilterMetadata": {
"type": "object",
"additionalProperties": False,
"properties": {
"kind": {"type": "string", "enum": _FILTER_KINDS},
"operators": {
"type": "array",
"items": {"type": "string", "enum": _FILTER_OPERATORS},
"uniqueItems": True,
},
"default_operator": {
"type": "string",
"enum": _FILTER_OPERATORS,
},
"multi": {"type": "boolean"},
},
},
"Extensions": {
"type": "object",
"additionalProperties": {"type": "object"},
},
},
"type": "object",
"additionalProperties": False,
"properties": {
**_COLUMN_METADATA_PROPERTIES,
"columns": {
"type": "object",
"additionalProperties": {"$ref": "#/$defs/ColumnMetadata"},
},
},
}
Loading
Loading