850 lines
27 KiB
Python
850 lines
27 KiB
Python
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
|
#
|
|
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
|
#
|
|
# Use of this source code is governed by an MIT-style
|
|
# license that can be found in the LICENSE file or at
|
|
# https://opensource.org/licenses/MIT.
|
|
|
|
"""
|
|
Diese Datei enthält Funktionen für den Bau von SQL Statements, besonders
|
|
SELECT-Statements. Es gibt viel ausgefeiltere Methoden für die Erstellung von
|
|
SQL Statements. APplus benötigt jedoch die Statements als Strings, die dann an
|
|
APplus für Änderungen und erst danach an die DB geschickt werden. Dies erschwert
|
|
die Nutzung von Tools wie SqlAlchemy.
|
|
|
|
Hier werden einfache Hilfsfunktionen, die auf Strings basieren, zur Verfügung
|
|
gestellt. PyODBC erlaubt Parameter (dargestellt als '?') in SQL Statements, die
|
|
dann beim Aufruf gefüllt werden. Dies funktioniert auch im Zusammenspiel mit
|
|
APplus. Oft ist es sinnvoll, solche Parameter zu verwenden.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import datetime
|
|
from typing import Set, Sequence, Union, Optional, cast, List
|
|
|
|
|
|
def normaliseDBfield(f: str) -> str:
|
|
"""Normalisiert die Darstellung eines DB-Feldes"""
|
|
return str(f).upper()
|
|
|
|
|
|
def normaliseDBfieldSet(s: Set[str]) -> Set[str]:
|
|
"""Normalisiert eine Menge von DB-Feldern"""
|
|
return {normaliseDBfield(f) for f in s}
|
|
|
|
|
|
def normaliseDBfieldList(fields: Sequence[str]) -> Sequence[str]:
|
|
"""Normalisiert eine Menge von DB-Feldern"""
|
|
return [normaliseDBfield(f) for f in fields]
|
|
|
|
|
|
class SqlField():
|
|
"""
|
|
Wrapper um SQL Feldnamen, die die Formatierung erleichtern
|
|
|
|
:param fn: der Feldname
|
|
:type fn: str
|
|
"""
|
|
def __init__(self, fn: str):
|
|
self.field = normaliseDBfield(fn)
|
|
|
|
def __str__(self) -> str:
|
|
return self.field
|
|
|
|
|
|
class SqlFixed():
|
|
"""
|
|
Wrapper um Strings, die ohne Änderung in SQL übernommen werden
|
|
|
|
:param s: der string
|
|
:type s: str
|
|
"""
|
|
def __init__(self, s: str):
|
|
self.s = str(s)
|
|
|
|
def __str__(self) -> str:
|
|
return self.s
|
|
|
|
|
|
class SqlDateTime():
|
|
"""
|
|
Wrapper um DateTime, die die Formatierung erleichtern
|
|
|
|
:param dt: der Zeitpunkt
|
|
:type dt: Union[datetime.datetime, datetime.date]
|
|
"""
|
|
def __init__(self, dt: Union[datetime.datetime, datetime.date] = datetime.datetime.now()) -> None:
|
|
self.value = dt
|
|
|
|
def __str__(self) -> str:
|
|
# %f formatiert mit 6 Stellen, also microseconds. Es werden aber nur
|
|
# 3 Stellen unterstützt, daher werden 3 weggeworfen.
|
|
return self.value.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]
|
|
|
|
|
|
class SqlDate():
|
|
"""
|
|
Wrapper um DateTime, die die Formatierung erleichtern
|
|
|
|
:param d: das Datum
|
|
:type d: Union[datetime.datetime, datetime.date]
|
|
"""
|
|
def __init__(self, d: Union[datetime.datetime, datetime.date] = datetime.datetime.now()) -> None:
|
|
self.value = d
|
|
|
|
def __str__(self) -> str:
|
|
return self.value.strftime("%Y%m%d")
|
|
|
|
|
|
class SqlTime():
|
|
"""
|
|
Wrapper um DateTime, die die Formatierung erleichtern
|
|
|
|
:param t: die Zeit
|
|
:type t: Union[datetime.datetime, datetime.time]
|
|
"""
|
|
def __init__(self, t: Union[datetime.datetime, datetime.time] = datetime.datetime.now()) -> None:
|
|
self.value = t
|
|
|
|
def __str__(self) -> str:
|
|
return self.value.strftime("%H:%M:%S.%f")[:-3]
|
|
|
|
|
|
class SqlParam():
|
|
"""Hilfsklasse, für einen Parameter (?)"""
|
|
def __init__(self) -> None:
|
|
pass
|
|
|
|
def __str__(self) -> str:
|
|
return "?"
|
|
|
|
|
|
sqlParam = SqlParam()
|
|
"""Da SqlParam keinen Zustand hat, reicht ein einzelner statischer Wert"""
|
|
|
|
|
|
def formatSqlValueString(s: str) -> str:
|
|
"""
|
|
Formatiert einen String für ein Sql-Statement. Der String wird in "'" eingeschlossen
|
|
und Hochkomma im Text maskiert.
|
|
|
|
:param s: der String
|
|
:type s: str
|
|
:return: der formatierte String
|
|
:rtype: str
|
|
"""
|
|
|
|
if (s is None):
|
|
return "''"
|
|
|
|
return "'" + str(s).replace("'", "''") + "'"
|
|
|
|
|
|
SqlValue = Union[str, int, float, SqlParam, SqlField, SqlFixed, SqlDate, SqlDateTime, datetime.datetime, datetime.date, datetime.time]
|
|
"""Union-Type aller unterstützter SQL-Werte"""
|
|
|
|
|
|
def formatSqlValue(v: SqlValue) -> str:
|
|
"""
|
|
Formatiert einen Wert für SQL. Je nachdem um welchen Typ es sich handelt, werden andere Formatierungen verwendet.
|
|
|
|
:param v: der Wert
|
|
:type v: SqlValue
|
|
:return: der formatierte Wert
|
|
:rtype: str
|
|
"""
|
|
|
|
if v is None:
|
|
raise Exception("formatSqlValue: null not supported")
|
|
|
|
if isinstance(v, (int, float, SqlField)):
|
|
return str(v)
|
|
elif isinstance(v, str):
|
|
return formatSqlValueString(v)
|
|
elif isinstance(v, datetime.datetime):
|
|
return "'" + str(SqlDateTime(v)) + "'"
|
|
elif isinstance(v, datetime.date):
|
|
return "'" + str(SqlDate(v)) + "'"
|
|
elif isinstance(v, datetime.time):
|
|
return "'" + str(SqlTime(v)) + "'"
|
|
elif isinstance(v, (SqlDateTime, SqlDate, SqlTime)):
|
|
return "'" + str(v) + "'"
|
|
elif isinstance(v, (SqlParam, SqlFixed)):
|
|
return str(v)
|
|
else:
|
|
raise Exception("formatSqlValue: unsupported type {}".format(type(v)))
|
|
|
|
|
|
class SqlCondition():
|
|
"""Eine abstrakte Sql-Bedingung. Unterklassen erledigen die eigentliche Arbeit."""
|
|
|
|
def getCondition(self) -> str:
|
|
"""
|
|
Liefert die Bedingung als String
|
|
|
|
:return: die Bedingung
|
|
:rtype: str
|
|
"""
|
|
raise Exception("Not implemented")
|
|
|
|
def __str__(self) -> str:
|
|
return self.getCondition()
|
|
|
|
|
|
class SqlConditionPrepared(SqlCondition):
|
|
"""Eine einfache Sql-Bedingung, die immer einen festen String zurückgibt."""
|
|
|
|
def __init__(self, cond: Union[SqlCondition, str]):
|
|
self.cond = str(cond)
|
|
|
|
def getCondition(self) -> str:
|
|
return self.cond
|
|
|
|
|
|
class SqlConditionTrue(SqlConditionPrepared):
|
|
"""True-Bedingung"""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("(1=1)")
|
|
|
|
|
|
class SqlConditionFalse(SqlConditionPrepared):
|
|
"""False-Bedingung"""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("(1=0)")
|
|
|
|
|
|
class SqlConditionBool(SqlConditionPrepared):
|
|
"""Fixe True-oder-False Bedingung"""
|
|
|
|
def __init__(self, b: bool):
|
|
if b:
|
|
super().__init__(SqlConditionTrue())
|
|
else:
|
|
super().__init__(SqlConditionFalse())
|
|
|
|
|
|
class SqlConditionNot(SqlCondition):
|
|
"""
|
|
Negation einer anderen Bedingung
|
|
|
|
:param cond: die zu negierende Bedingung
|
|
:type cond: SqlCondition
|
|
"""
|
|
|
|
def __init__(self, cond: SqlCondition):
|
|
self.cond = cond
|
|
|
|
def getCondition(self) -> str:
|
|
return "(not {})".format(self.cond.getCondition())
|
|
|
|
|
|
class SqlConditionIsNull(SqlConditionPrepared):
|
|
"""
|
|
Wert soll null sein
|
|
|
|
:param v: das Feld
|
|
:type v: SqlValue
|
|
"""
|
|
|
|
def __init__(self, v: SqlValue):
|
|
super().__init__("({} is null)".format(formatSqlValue(v)))
|
|
|
|
|
|
class SqlConditionFieldIsNull(SqlConditionIsNull):
|
|
def __init__(self, field: str):
|
|
super().__init__(SqlField(field))
|
|
|
|
|
|
class SqlConditionIsNotNull(SqlConditionPrepared):
|
|
"""
|
|
Wert soll nicht null sein
|
|
|
|
:param v: der Wert
|
|
:type v: SqlValue
|
|
"""
|
|
|
|
def __init__(self, v: SqlValue):
|
|
super().__init__("({} is not null)".format(formatSqlValue(v)))
|
|
|
|
|
|
class SqlConditionFieldIsNotNull(SqlConditionIsNotNull):
|
|
def __init__(self, field: str):
|
|
super().__init__(SqlField(field))
|
|
|
|
|
|
class SqlConditionStringStartsWith(SqlConditionPrepared):
|
|
"""
|
|
Feld soll mit einem bestimmten String beginnen
|
|
|
|
:param field: das Feld
|
|
:type field: str
|
|
:param value: der Wert
|
|
:type value: str
|
|
"""
|
|
|
|
def __init__(self, field: str, value: str):
|
|
cond = ""
|
|
if value:
|
|
cond = "(left({}, {}) = {})".format(normaliseDBfield(field), len(value), formatSqlValueString(value))
|
|
else:
|
|
cond = "(1=1)"
|
|
super().__init__(cond)
|
|
|
|
|
|
class SqlConditionFieldStringNotEmpty(SqlConditionPrepared):
|
|
"""
|
|
Feld soll nicht den leeren String oder null enthalten.
|
|
Der Ausdruck wird wörtlich übernommen.
|
|
|
|
:param field: das Feld
|
|
:type field: str
|
|
"""
|
|
|
|
def __init__(self, field: str):
|
|
field = normaliseDBfield(field)
|
|
cond = "({} is not null and {} != '')".format(field, field)
|
|
super().__init__(cond)
|
|
|
|
|
|
class SqlConditionIn(SqlConditionPrepared):
|
|
"""
|
|
Bedingung der Form 'v in ...'
|
|
|
|
:param value: der Wert, kann unterschiedliche Typen besitzen
|
|
:type value: SqlValue
|
|
:param values: die erlaubten Werte
|
|
:type values: Sequence[SqlValue]
|
|
"""
|
|
def __init__(self, value: SqlValue, values: Sequence[SqlValue]):
|
|
valuesLen = len(values)
|
|
if (valuesLen == 0):
|
|
cond: Union[SqlCondition, str] = SqlConditionFalse()
|
|
elif (valuesLen == 1):
|
|
cond = SqlConditionEq(value, values[0])
|
|
else:
|
|
valuesS = formatSqlValue(values[0])
|
|
for i in range(1, valuesLen):
|
|
valuesS += ", " + formatSqlValue(values[i])
|
|
cond = "({} in ({}))".format(formatSqlValue(value), valuesS)
|
|
super().__init__(cond)
|
|
|
|
|
|
class SqlConditionFieldIn(SqlConditionIn):
|
|
def __init__(self, field: str, values: Sequence[SqlValue]):
|
|
super().__init__(SqlField(field), values)
|
|
|
|
|
|
class SqlConditionEq(SqlConditionPrepared):
|
|
"""
|
|
Bedingung der Form 'v1 is null', 'v2 is null', 'v1 = v2', '(1=1)' oder '(0=1)'
|
|
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
"""
|
|
def __init__(self, value1: Union[SqlValue, bool, None], value2: Union[SqlValue, bool, None]):
|
|
cond: Union[SqlCondition, str]
|
|
if (value1 is None) and (value2 is None):
|
|
cond = SqlConditionTrue()
|
|
elif (value1 is None) and not (value2 is None):
|
|
if (isinstance(value2, bool)):
|
|
cond = SqlConditionFalse()
|
|
else:
|
|
cond = SqlConditionIsNull(value2)
|
|
elif not (value1 is None) and (value2 is None):
|
|
if (isinstance(value1, bool)):
|
|
cond = SqlConditionFalse()
|
|
else:
|
|
cond = SqlConditionIsNull(value1)
|
|
else:
|
|
if isinstance(value1, bool) and isinstance(value2, bool):
|
|
cond = SqlConditionBool(value1 == value2)
|
|
elif isinstance(value1, bool) and not isinstance(value2, bool):
|
|
value2 = cast(SqlValue, value2)
|
|
if value1:
|
|
cond = "({} = 1)".format(formatSqlValue(value2))
|
|
else:
|
|
cond = "({} = 0 OR {} is null)".format(formatSqlValue(value2), formatSqlValue(value2))
|
|
elif not isinstance(value1, bool) and isinstance(value2, bool):
|
|
value1 = cast(SqlValue, value1)
|
|
if value2:
|
|
cond = "({} = 1)".format(formatSqlValue(value1))
|
|
else:
|
|
cond = "({} = 0 OR {} is null)".format(formatSqlValue(value1), formatSqlValue(value1))
|
|
else:
|
|
value1 = cast(SqlValue, value1)
|
|
value2 = cast(SqlValue, value2)
|
|
cond = "({} = {})".format(formatSqlValue(value1), formatSqlValue(value2))
|
|
super().__init__(cond)
|
|
|
|
|
|
class SqlConditionBinComp(SqlConditionPrepared):
|
|
"""
|
|
Bedingung der Form 'value1 op value2'
|
|
|
|
:param op: der Vergleichsoperator
|
|
:type op: str
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:type value1: SqlValue
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
:type value2: SqlValue
|
|
"""
|
|
def __init__(self, op: str, value1: SqlValue, value2: SqlValue):
|
|
if not value1 or not value2:
|
|
raise Exception("SqlConditionBinComp: value not provided")
|
|
|
|
cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2))
|
|
super().__init__(cond)
|
|
|
|
|
|
class SqlConditionLt(SqlConditionBinComp):
|
|
"""
|
|
Bedingung der Form 'value1 < value2'
|
|
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
"""
|
|
def __init__(self, value1: SqlValue, value2: SqlValue):
|
|
super().__init__("<", value1, value2)
|
|
|
|
|
|
class SqlConditionLe(SqlConditionBinComp):
|
|
"""
|
|
Bedingung der Form 'value1 <= value2'
|
|
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
"""
|
|
def __init__(self, value1: SqlValue, value2: SqlValue):
|
|
super().__init__("<=", value1, value2)
|
|
|
|
|
|
class SqlConditionGt(SqlConditionBinComp):
|
|
"""
|
|
Bedingung der Form 'value1 > value2'
|
|
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
"""
|
|
def __init__(self, value1: SqlValue, value2: SqlValue):
|
|
super().__init__(">", value1, value2)
|
|
|
|
|
|
class SqlConditionGe(SqlConditionBinComp):
|
|
"""
|
|
Bedingung der Form 'value1 >= value2'
|
|
|
|
:param value1: der Wert, kann unterschiedliche Typen besitzen
|
|
:param value2: der Wert, kann unterschiedliche Typen besitzen
|
|
"""
|
|
def __init__(self, value1: SqlValue, value2: SqlValue):
|
|
super().__init__(">=", value1, value2)
|
|
|
|
|
|
class SqlConditionFieldEq(SqlConditionEq):
|
|
def __init__(self, field: str, value: Union[SqlValue, bool, None]):
|
|
super().__init__(SqlField(field), value)
|
|
|
|
|
|
class SqlConditionFieldLt(SqlConditionLt):
|
|
def __init__(self, field: str, value: SqlValue):
|
|
super().__init__(SqlField(field), value)
|
|
|
|
|
|
class SqlConditionFieldLe(SqlConditionLe):
|
|
def __init__(self, field: str, value: SqlValue):
|
|
super().__init__(SqlField(field), value)
|
|
|
|
|
|
class SqlConditionFieldGt(SqlConditionGt):
|
|
def __init__(self, field: str, value: SqlValue):
|
|
super().__init__(SqlField(field), value)
|
|
|
|
|
|
class SqlConditionFieldGe(SqlConditionGe):
|
|
def __init__(self, field: str, value: SqlValue):
|
|
super().__init__(SqlField(field), value)
|
|
|
|
|
|
class SqlConditionList(SqlCondition):
|
|
"""
|
|
Eine SQL Bedingung, die sich aus einer Liste anderer Bedingungen zusammensetzen.
|
|
Dies kann eine "AND" oder eine "OR" Liste sein.
|
|
|
|
:param connector: wie werden Listenelemente verbunden (AND oder OR)
|
|
:type connector: str
|
|
:param emptyCond: Rückgabewert für leere Liste
|
|
:type emptyCond: str
|
|
"""
|
|
|
|
def __init__(self, connector: str, emptyCond: str):
|
|
self.connector: str = connector
|
|
self.emptyCond: str = emptyCond
|
|
self.elems: List[SqlCondition] = []
|
|
|
|
def addCondition(self, cond: Union[SqlCondition, str, None]) -> None:
|
|
if (cond is None):
|
|
return
|
|
if not (isinstance(cond, SqlCondition)):
|
|
cond = SqlConditionPrepared("("+str(cond)+")")
|
|
self.elems.append(cond)
|
|
|
|
def addConditions(self, *conds: Union[SqlCondition, str, None]) -> None:
|
|
for cond in conds:
|
|
self.addCondition(cond)
|
|
|
|
def addConditionFieldStringNotEmpty(self, field: str) -> None:
|
|
self.addCondition(SqlConditionFieldStringNotEmpty(field))
|
|
|
|
def addConditionFieldIn(self, field: str, values: Sequence[SqlValue]) -> None:
|
|
self.addCondition(SqlConditionFieldIn(field, values))
|
|
|
|
def addConditionFieldEq(self, field: str, value: Union[SqlValue, bool, None]) -> None:
|
|
self.addCondition(SqlConditionFieldEq(field, value))
|
|
|
|
def addConditionFieldsEq(self, field1: str, field2: str) -> None:
|
|
self.addCondition(SqlConditionEq(SqlField(field1), SqlField(field2)))
|
|
|
|
def addConditionEq(self, value1: Union[SqlValue, bool, None], value2: Union[SqlValue, bool, None]) -> None:
|
|
self.addCondition(SqlConditionEq(value1, value2))
|
|
|
|
def addConditionGe(self, value1: SqlValue, value2: SqlValue) -> None:
|
|
self.addCondition(SqlConditionGe(value1, value2))
|
|
|
|
def addConditionFieldGe(self, field: str, value: SqlValue) -> None:
|
|
self.addCondition(SqlConditionGe(SqlField(field), value))
|
|
|
|
def addConditionFieldsGe(self, field1: str, field2: str) -> None:
|
|
self.addCondition(SqlConditionGe(SqlField(field1), SqlField(field2)))
|
|
|
|
def addConditionLe(self, value1: SqlValue, value2: SqlValue) -> None:
|
|
self.addCondition(SqlConditionLe(value1, value2))
|
|
|
|
def addConditionFieldLe(self, field: str, value: SqlValue) -> None:
|
|
self.addCondition(SqlConditionLe(SqlField(field), value))
|
|
|
|
def addConditionFieldsLe(self, field1: str, field2: str) -> None:
|
|
self.addCondition(SqlConditionLe(SqlField(field1), SqlField(field2)))
|
|
|
|
def addConditionGt(self, value1: SqlValue, value2: SqlValue) -> None:
|
|
self.addCondition(SqlConditionGt(value1, value2))
|
|
|
|
def addConditionFieldGt(self, field: str, value: SqlValue) -> None:
|
|
self.addCondition(SqlConditionGt(SqlField(field), value))
|
|
|
|
def addConditionFieldsGt(self, field1: str, field2: str) -> None:
|
|
self.addCondition(SqlConditionGt(SqlField(field1), SqlField(field2)))
|
|
|
|
def addConditionLt(self, value1: SqlValue, value2: SqlValue) -> None:
|
|
self.addCondition(SqlConditionLt(value1, value2))
|
|
|
|
def addConditionFieldLt(self, field: str, value: SqlValue) -> None:
|
|
self.addCondition(SqlConditionLt(SqlField(field), value))
|
|
|
|
def addConditionFieldsLt(self, field1: str, field2: str) -> None:
|
|
self.addCondition(SqlConditionLt(SqlField(field1), SqlField(field2)))
|
|
|
|
def addConditionFieldIsNull(self, field: str) -> None:
|
|
self.addCondition(SqlConditionFieldIsNull(field))
|
|
|
|
def addConditionFieldIsNotNull(self, field: str) -> None:
|
|
self.addCondition(SqlConditionFieldIsNotNull(field))
|
|
|
|
def isEmpty(self) -> bool:
|
|
return not self.elems
|
|
|
|
def getCondition(self) -> str:
|
|
elemLen = len(self.elems)
|
|
if elemLen == 0:
|
|
return self.emptyCond
|
|
elif elemLen == 1:
|
|
return self.elems[0].getCondition()
|
|
else:
|
|
res = "(" + self.elems[0].getCondition()
|
|
for i in range(1, elemLen):
|
|
res += " {} {}".format(self.connector, self.elems[i].getCondition())
|
|
res += ")"
|
|
return res
|
|
|
|
|
|
class SqlConditionDateTimeFieldInRange(SqlConditionPrepared):
|
|
"""
|
|
Liegt Datetime in einem bestimmten Zeitraum?
|
|
|
|
:param field: das Feld
|
|
:type field: str
|
|
:param datetimeVon: der untere Wert (einschließlich), None erlaubt beliebige Zeiten
|
|
:param datetimeBis: der obere Wert (ausschließlich), None erlaubt beliebige Zeiten
|
|
"""
|
|
def __init__(self, field: str, datetimeVon: Optional[datetime.datetime], datetimeBis: Optional[datetime.datetime]):
|
|
cond = SqlConditionAnd()
|
|
if not (datetimeVon is None):
|
|
cond.addConditionFieldGe(field, datetimeVon)
|
|
if not (datetimeBis is None):
|
|
cond.addConditionFieldLt(field, datetimeBis)
|
|
super().__init__(str(cond))
|
|
|
|
|
|
class SqlConditionDateTimeFieldInMonth(SqlConditionPrepared):
|
|
"""
|
|
Liegt Datetime in einem bestimmten Monat?
|
|
|
|
:param field: das Feld
|
|
:type field: string
|
|
:param year: das Jahr
|
|
:param month: der Monat
|
|
"""
|
|
def __init__(self, field: str, year: int, month: int):
|
|
if month == 12:
|
|
nyear = year+1
|
|
nmonth = 1
|
|
else:
|
|
nyear = year
|
|
nmonth = month+1
|
|
cond = SqlConditionDateTimeFieldInRange(
|
|
field,
|
|
datetime.datetime(year=year, month=month, day=1),
|
|
datetime.datetime(year=nyear, month=nmonth, day=1))
|
|
super().__init__(str(cond))
|
|
|
|
|
|
class SqlConditionDateTimeFieldInYear(SqlConditionPrepared):
|
|
"""
|
|
Liegt Datetime in einem bestimmten Jahr?
|
|
|
|
:param field: das Feld
|
|
:type field: str
|
|
:param year: das Jahr
|
|
"""
|
|
def __init__(self, field: str, year: int) -> None:
|
|
nyear = year+1
|
|
cond = SqlConditionDateTimeFieldInRange(
|
|
field,
|
|
datetime.datetime(year=year, month=1, day=1),
|
|
datetime.datetime(year=nyear, month=1, day=1))
|
|
super().__init__(str(cond))
|
|
|
|
|
|
class SqlConditionDateTimeFieldInDay(SqlConditionPrepared):
|
|
"""
|
|
Liegt Datetime in einem bestimmten Monat?
|
|
|
|
:param field: das Feld
|
|
:type field: str
|
|
:param year: das Jahr
|
|
:param month: der Monat
|
|
:param day: der Tag
|
|
"""
|
|
def __init__(self, field: str, year: int, month: int, day: int) -> None:
|
|
d = datetime.datetime(year=year, month=month, day=day)
|
|
cond = SqlConditionDateTimeFieldInRange(
|
|
field,
|
|
d,
|
|
d + datetime.timedelta(days=1))
|
|
super().__init__(str(cond))
|
|
|
|
|
|
class SqlConditionAnd(SqlConditionList):
|
|
def __init__(self, *conds: Union[SqlCondition, str]) -> None:
|
|
super().__init__("AND", "(1=1)")
|
|
self.addConditions(*conds)
|
|
|
|
|
|
class SqlConditionOr(SqlConditionList):
|
|
def __init__(self, *conds: Union[SqlCondition, str]) -> None:
|
|
super().__init__("OR", "(1=0)")
|
|
self.addConditions(*conds)
|
|
|
|
|
|
class SqlJoin():
|
|
"""
|
|
Ein abstrakter Sql-Join
|
|
|
|
:param joinType: Art des Joins, wird in SQL übernommen, z.B. "LEFT JOIN".
|
|
:type joinType: str
|
|
:param table: die Tabelle, die gejoint werden soll
|
|
:type table: str
|
|
:param conds: Bedingungen, die bereits hinzugefügt werden soll. Weitere können über Attribut `on` hinzugefügt werden.
|
|
|
|
"""
|
|
|
|
def __init__(self, joinType: str, table: str, *conds: Union[SqlCondition, str]) -> None:
|
|
self.joinType = joinType
|
|
self.table = table
|
|
|
|
self.on: SqlConditionAnd = SqlConditionAnd(*conds)
|
|
"""Bedingung des Joins, kann noch nachträglich erweitert werden"""
|
|
|
|
def getJoin(self) -> str:
|
|
"""
|
|
Liefert den Join als String
|
|
"""
|
|
return self.joinType + " " + self.table + " ON " + self.on.getCondition()
|
|
|
|
def __str__(self) -> str:
|
|
return self.getJoin()
|
|
|
|
|
|
class SqlInnerJoin(SqlJoin):
|
|
"""
|
|
Ein Inner-Join.
|
|
|
|
:param table: die Tabelle, die gejoint werden soll
|
|
:type table: str
|
|
:param conds: Bedingungen, die bereits hinzugefügt werden soll. Weitere können über Attribut `on` hinzugefügt werden.
|
|
"""
|
|
|
|
def __init__(self, table: str, *conds: Union[SqlCondition, str]) -> None:
|
|
super().__init__("INNER JOIN", table, *conds)
|
|
|
|
|
|
class SqlLeftJoin(SqlJoin):
|
|
"""
|
|
Ein Left-Join.
|
|
|
|
:param table: die Tabelle, die gejoint werden soll
|
|
:type table: str
|
|
:param conds: Bedingungen, die bereits hinzugefügt werden soll. Weitere können über Attribut `on` hinzugefügt werden.
|
|
"""
|
|
def __init__(self, table: str, *conds: Union[SqlCondition, str]) -> None:
|
|
super().__init__("LEFT JOIN", table, *conds)
|
|
|
|
|
|
class SqlStatementSelect():
|
|
"""
|
|
Klasse, um einfache Select-Statements zu bauen.
|
|
|
|
:param table: die Haupt-Tabelle
|
|
:type table: str
|
|
:param fields: kein oder mehrere Felder, die selektiert werden sollen
|
|
"""
|
|
|
|
def __init__(self, table: str, *fields: str) -> None:
|
|
self.table: str = table
|
|
"""die Tabelle"""
|
|
|
|
self.top: int = 0
|
|
"""wie viele Datensätze auswählen? 0 für alle"""
|
|
|
|
self.where: SqlConditionList = SqlConditionAnd()
|
|
"""die Bedingung, Default ist True"""
|
|
|
|
self.fields: List[str] = []
|
|
"""Liste von auszuwählenden Feldern"""
|
|
self.addFields(*fields)
|
|
|
|
self.joins: List[Union[SqlJoin, str]] = []
|
|
"""Joins mit extra Tabellen"""
|
|
|
|
self.groupBy: List[str] = []
|
|
"""die Bedingung, Default ist True"""
|
|
|
|
self.having: SqlConditionList = SqlConditionAnd()
|
|
"""die Bedingung having, Default ist True"""
|
|
|
|
self.order: Optional[str] = None
|
|
"""Sortierung"""
|
|
|
|
def __str__(self) -> str:
|
|
return self.getSql()
|
|
|
|
def addFields(self, *fields: str) -> None:
|
|
"""Fügt ein oder mehrere Felder, also auszuwählende Werte zu einem SQL-Statement hinzu."""
|
|
for f in fields:
|
|
if not (f is None):
|
|
self.fields.append(f)
|
|
|
|
def addGroupBy(self, *fields: str) -> None:
|
|
"""Fügt ein oder mehrere GroupBy Felder zu einem SQL-Statement hinzu."""
|
|
for f in fields:
|
|
if not (f is None):
|
|
self.groupBy.append(f)
|
|
|
|
def setTop(self, t: int) -> None:
|
|
"""Wie viele Datensätze sollen maximal zurückgeliefert werden? 0 für alle"""
|
|
self.top = t
|
|
|
|
def addFieldsTable(self, table: str, *fields: str) -> None:
|
|
"""
|
|
Fügt ein oder mehrere Felder, die zu einer Tabelle gehören zu einem SQL-Statement hinzu.
|
|
Felder sind Strings. Vor jeden dieser Strings wird die Tabelle mit einem Punkt getrennt gesetzt.
|
|
Dies kann im Vergleich zu 'addFields' Schreibarbeit erleitern.
|
|
"""
|
|
for f in fields:
|
|
if not (f is None):
|
|
self.fields.append(table + "." + str(f))
|
|
|
|
def addJoin(self, j: Union[SqlJoin, str]) -> None:
|
|
"""Fügt ein Join zum SQL-Statement hinzu. Beispiel: 'LEFT JOIN personal p ON t.UPDUSER = p.PERSONAL'"""
|
|
self.joins.append(j)
|
|
|
|
def addLeftJoin(self, table: str, *conds: Union[SqlCondition, str]) -> SqlLeftJoin:
|
|
j = SqlLeftJoin(table, *conds)
|
|
self.addJoin(j)
|
|
return j
|
|
|
|
def addInnerJoin(self, table: str, *conds: Union[SqlCondition, str]) -> SqlInnerJoin:
|
|
j = SqlInnerJoin(table, *conds)
|
|
self.addJoin(j)
|
|
return j
|
|
|
|
def getSql(self) -> str:
|
|
"""Liefert das SQL-SELECT-Statement als String"""
|
|
def getFields() -> str:
|
|
fieldsLen = len(self.fields)
|
|
if fieldsLen == 0:
|
|
return "*"
|
|
elif fieldsLen == 1:
|
|
return str(self.fields[0])
|
|
else:
|
|
res = str(self.fields[0])
|
|
for i in range(1, fieldsLen):
|
|
res += ", " + str(self.fields[i])
|
|
return res
|
|
|
|
def getGroupBy() -> str:
|
|
groupByLen = len(self.groupBy)
|
|
if groupByLen == 0:
|
|
return ""
|
|
else:
|
|
res = " GROUP BY " + str(self.fields[0])
|
|
for i in range(1, groupByLen):
|
|
res += ", " + str(self.fields[i])
|
|
if not (self.having.isEmpty()):
|
|
res += " HAVING " + str(self.having)
|
|
return res
|
|
|
|
def getJoins() -> str:
|
|
if (len(self.joins) == 0):
|
|
return ""
|
|
else:
|
|
res = ""
|
|
for i in range(0, len(self.joins)):
|
|
res += " " + str(self.joins[i])
|
|
return res
|
|
|
|
def getWhere() -> str:
|
|
if self.where.isEmpty():
|
|
return ""
|
|
else:
|
|
return " WHERE " + str(self.where)
|
|
|
|
def getOrder() -> str:
|
|
if self.order is None:
|
|
return ""
|
|
else:
|
|
return " ORDER BY " + str(self.order)
|
|
|
|
def getTop() -> str:
|
|
if self.top <= 0:
|
|
return ""
|
|
else:
|
|
return "TOP " + str(self.top) + " "
|
|
|
|
return "SELECT " + getTop() + getFields() + " FROM " + self.table + getJoins() + getWhere() + getGroupBy() + getOrder()
|
|
|
|
|
|
SqlStatement = Union[SqlStatementSelect, str]
|