Created Queries and Execute endpoint
This commit is contained in:
21
data/crud.py
21
data/crud.py
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
143
data/schemas.py
143
data/schemas.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user