implement NOT

This commit is contained in:
Jann Stute
2024-11-28 21:31:27 +01:00
parent 1f5a4dcc7e
commit 3d4b649903
3 changed files with 49 additions and 18 deletions

View File

@@ -1,10 +1,10 @@
from sqlalchemy import and_, or_
from sqlalchemy import and_, or_, select
from sqlalchemy.sql.expression import ColumnExpressionArgument
from src.core.media_types import MediaCategories
from src.core.query_lang import BaseVisitor
from src.core.query_lang.ast import ANDList, Constraint, ConstraintType, ORList, Property
from src.core.query_lang.ast import ANDList, Constraint, ConstraintType, Not, ORList, Property
from .models import Entry, Tag, TagAlias
from .models import Entry, Tag, TagAlias, TagBoxField
class SQLBoolExpressionBuilder(BaseVisitor):
@@ -38,7 +38,18 @@ class SQLBoolExpressionBuilder(BaseVisitor):
elif node.type == ConstraintType.FileType:
return Entry.suffix.ilike(node.value)
# raise exception if Constraint stays unhandled
raise NotImplementedError("This type of constraint is not implemented yet")
def visit_property(self, node: Property) -> None:
return
def visit_not(self, node: Not) -> ColumnExpressionArgument:
return ~Entry.id.in_(
# TODO TSQLANG there is technically code duplication from Library.search_library here
select(Entry.id)
.outerjoin(Entry.tag_box_fields)
.outerjoin(TagBoxField.tags)
.outerjoin(TagAlias)
.where(self.visit(node.child))
)

View File

@@ -35,9 +35,9 @@ class AST:
class ANDList(AST):
terms: list[Union["ORList", "Constraint"]]
terms: list[Union["ORList", "Constraint", "Not"]]
def __init__(self, terms: list[Union["ORList", "Constraint"]]) -> None:
def __init__(self, terms: list[Union["ORList", "Constraint", "Not"]]) -> None:
super().__init__()
for term in terms:
term.parent = self
@@ -78,6 +78,14 @@ class Property(AST):
self.value = value
class Not(AST):
child: AST
def __init__(self, child: AST) -> None:
super().__init__()
self.child = child
T = TypeVar("T")
@@ -91,6 +99,8 @@ class BaseVisitor(ABC, Generic[T]):
return self.visit_constraint(node)
elif isinstance(node, Property):
return self.visit_property(node)
elif isinstance(node, Not):
return self.visit_not(node)
raise Exception(f"Unknown Node Type of {node}")
@abstractmethod
@@ -108,3 +118,7 @@ class BaseVisitor(ABC, Generic[T]):
@abstractmethod
def visit_property(self, node: Property) -> T:
raise NotImplementedError()
@abstractmethod
def visit_not(self, node: Not) -> T:
raise NotImplementedError()

View File

@@ -1,6 +1,6 @@
from typing import Union
from src.core.query_lang.ast import AST, ANDList, Constraint, ORList, Property
from src.core.query_lang.ast import AST, ANDList, Constraint, Not, ORList, Property
from src.core.query_lang.tokenizer import ConstraintType, Token, Tokenizer, TokenType
from src.core.query_lang.util import ParsingError
@@ -25,6 +25,18 @@ class Parser:
raise ParsingError(self.next_token.start, self.next_token.end, "Syntax Error")
return out
def __or_list(self) -> ORList:
terms = [self.__and_list()]
while self.__is_next_or():
self.__eat(TokenType.ULITERAL)
terms.append(self.__and_list())
return ORList(terms)
def __is_next_or(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "OR"
def __and_list(self) -> ANDList:
elements = [self.__term()]
while self.next_token.type != TokenType.EOF and not self.__is_next_or():
@@ -42,19 +54,10 @@ class Parser:
def __is_next_and(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "AND"
def __or_list(self) -> ORList:
terms = [self.__and_list()]
while self.__is_next_or():
def __term(self) -> Union[ORList, Constraint, Not]:
if self.__is_next_not():
self.__eat(TokenType.ULITERAL)
terms.append(self.__and_list())
return ORList(terms)
def __is_next_or(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "OR"
def __term(self) -> Union["ORList", "Constraint"]:
return Not(self.__term())
if self.next_token.type == TokenType.RBRACKETO:
self.__eat(TokenType.RBRACKETO)
out = self.__or_list()
@@ -63,6 +66,9 @@ class Parser:
else:
return self.__constraint()
def __is_next_not(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "NOT"
def __constraint(self) -> Constraint:
if self.next_token.type == TokenType.CONSTRAINTTYPE:
self.last_constraint_type = self.__eat(TokenType.CONSTRAINTTYPE).value