mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-03 00:29:14 +00:00
implement NOT
This commit is contained in:
@@ -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))
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user