Created Queries and Execute endpoint

This commit is contained in:
2025-02-24 12:15:01 +03:00
parent cabcf837f9
commit 836ce1dc82
14 changed files with 635 additions and 50 deletions

View File

@@ -1,8 +1,8 @@
# crud.py
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from data.models import Connection, User
from data.schemas import ConnectionCreate, ConnectionUpdate, UserCreate
from data.models import Connection, User, Query
from data.schemas import ConnectionCreate, ConnectionUpdate, UserCreate, SelectQueryIn
from core.exceptions import ObjectNotFoundInDB
async def read_user(db:AsyncSession, user_id:int):
@@ -14,7 +14,7 @@ async def read_all_users(db:AsyncSession):
return result.scalars().all()
async def create_user(db: AsyncSession, user: UserCreate):
from core.scripts import create_secret
from utils.scripts import create_secret
db_user = User(**user.model_dump(), api_key=create_secret())
db.add(db_user)
await db.commit()
@@ -60,3 +60,18 @@ async def delete_connection(db: AsyncSession, connection_id: int):
await db.delete(db_connection)
await db.commit()
return db_connection
async def create_select_query(db:AsyncSession, query:SelectQueryIn) -> Query:
db_query = Query(**query.model_dump())
db.add(db_query)
await db.commit()
await db.refresh(db_query)
return db_query
async def read_all_select_queries(db:AsyncSession) -> list[Query]:
result = await db.execute(select(Query))
return result.scalars().all()
async def read_select_query(db:AsyncSession, query_id:int) -> Query:
result = await db.execute(select(Query).filter(Query.id == query_id))
return result.scalars().first()

View File

@@ -1,5 +1,5 @@
# models.py
from sqlalchemy import Column, Integer, String, Enum, ForeignKey
from sqlalchemy import Column, Integer, String, Enum, ForeignKey, JSON
from sqlalchemy.orm import relationship
from data.db import Base
from core.enums import ConnectionTypes, UserRole
@@ -27,3 +27,19 @@ class Connection(Base):
owner_id = Column(Integer, ForeignKey("users.id"))
# owner = relationship("User", back_populates="connections")
class Query(Base):
__tablename__ = "queries"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
owner_id = Column(Integer, ForeignKey("users.id"))
table_name = Column(String, nullable=False)
columns = Column(JSON, nullable=False)
filters = Column(JSON, nullable=True)
sort_by = Column(JSON, nullable=True)
limit = Column(Integer, nullable=True)
offset = Column(Integer, nullable=True)
sql= Column(String, nullable=False)
params = Column(JSON, nullable=False)

View File

@@ -1,7 +1,9 @@
# schemas.py
from pydantic import BaseModel
from typing import Optional
from core.enums import ConnectionTypes, UserRole
import re
from typing import Union, List, Optional, Literal, Any
from typing_extensions import Annotated
from pydantic import BaseModel, Field, field_validator, ValidationInfo, UUID4
from core.enums import ConnectionTypes, UserRole, FilterOperator, SortOrder
from core.exceptions import QueryValidationError
class ConnectionBase(BaseModel):
@@ -57,3 +59,136 @@ class UserInDBBase(UserBase):
class User(UserInDBBase):
pass
class FilterClause(BaseModel):
column: str
operator: FilterOperator
value: Optional[Union[str, int, float, bool, list]] = None
@field_validator("column")
@classmethod
def validate_column(cls, v: str) -> str:
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", v):
raise QueryValidationError(
loc=["filters", "column"], msg="Invalid column name format"
)
return v
@field_validator("value")
@classmethod
def validate_value(
cls,
v: Optional[Union[str, int, float, bool, list]],
values: ValidationInfo,
) -> Optional[Union[str, int, float, bool, list]]:
operator = values.data.get("operator")
if operator in [FilterOperator.is_null, FilterOperator.is_not_null]:
if v is not None:
raise QueryValidationError(
loc=["filters", "value"],
msg="Value must be null for IS NULL/IS NOT NULL operators",
)
elif operator == FilterOperator.in_:
if not isinstance(v, list):
raise QueryValidationError(
loc=["filters", "value"],
msg="IN operator requires a list of values",
)
elif v is None:
raise QueryValidationError(
loc=["filters", "value"], msg="Value required for this operator"
)
return v
class SortClause(BaseModel):
column: str
order: SortOrder = SortOrder.asc
@field_validator("column")
@classmethod
def validate_column(cls, v: str) -> str:
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", v):
raise QueryValidationError(
loc=["sort_by", "column"], msg="Invalid column name format"
)
return v
class SelectQueryBase(BaseModel):
name: str # a short name for this query.
description: str | None = None # describing what does this query do.
table_name: str
columns: Union[Literal["*"], List[str]] = "*"
filters: Optional[List[FilterClause]] = None
sort_by: Optional[List[SortClause]] = None
limit: Annotated[int, Field(strict=True, gt=0)] = None
offset: Annotated[int, Field(strict=True, ge=0)] = None
@field_validator("table_name")
@classmethod
def validate_table_name(cls, v: str) -> str:
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", v):
raise QueryValidationError(
loc=["table_name"], msg="Invalid table name format"
)
return v
@field_validator("columns")
@classmethod
def validate_columns(
cls, v: Union[Literal["*"], List[str]]
) -> Union[Literal["*"], List[str]]:
if v == "*":
return v
for col in v:
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", col):
raise QueryValidationError(
loc=["columns"], msg=f"Invalid column name: {col}"
)
return v
class SelectQuery(SelectQueryBase):
sql: str
params: list[str | int | float]
class SelectQueryIn(SelectQuery):
owner_id: int
class SelectQueryInDB(SelectQueryIn):
id: int
class SelectResultData(BaseModel):
columns: List[str]
data: List[List[Any]]
class SelectQueryInResult(BaseModel):
id: int
sql: str
params: list[str | int | float]
class Config:
from_attributes = True
class SelectQueryMetaData(BaseModel):
cursor: Optional[UUID4] = Field(
None,
description="A UUID4 cursor for pagination. Can be None if no more data is available.",
)
total_number: int
has_more: bool = False
class SelectResult(BaseModel):
meta: SelectQueryMetaData
query: SelectQueryInResult
results: SelectResultData | None