Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*.pyc
t.xhtml
t.py
.idea
.venv
29 changes: 29 additions & 0 deletions EGE/SQL/Aggregate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class Aggregate:
@staticmethod
def call(self): # abstract
pass

class Count(Aggregate):
@staticmethod
def call(self):
raise NotImplemented()

class Sum(Aggregate):
@staticmethod
def call(self):
raise NotImplemented()

class Avg(Aggregate):
@staticmethod
def call(self):
raise NotImplemented()

class Min(Aggregate):
@staticmethod
def call(self):
raise NotImplemented()

class Max(Aggregate):
@staticmethod
def call(self):
raise NotImplemented()
81 changes: 81 additions & 0 deletions EGE/SQL/Queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from abc import ABC, abstractmethod

import EGE.Html as html
from EGE.GenBase import EGEError
from EGE.SQL.Table import Table, Field
from EGE.Prog import SynElement, Block

class Query(ABC):
def __init__(self, table, *,
where: SynElement = None,
having: SynElement = None,
group_by: list[SynElement] = None):
"""table: Union[Table, str]"""
if table is None:
raise EGEError('Table is none!')

self.table = table if isinstance(table, Table) else None
self.table_name = table.name if isinstance(table, Table) else table

self.where: SynElement = where
self.having: SynElement = having
self.group_by: list[SynElement] = group_by

@abstractmethod
def text(self, opts: dict):
"""opts: dict[str, Any]"""
pass

@abstractmethod
def run(self):
pass

def text_html(self):
return self.text({ 'html': 1 })

def text_html_tt(self):
return html.tag('tt', self.text({ 'html': 1 }))

def _field_list_sql(self, fields: list, opts: dict):
"""
fields: list[SynElement | str]
opts: dict[str, Any]
"""
return ', '.join([ f.to_lang_named('SQL', opts)
if issubclass(type(f), SynElement) else f
for f in fields ])

def where_sql(self, opts: dict):
return f"WHERE {self.where.to_lang_named('SQL', opts)}" if self.where else ''

def having_sql(self, opts: dict):
return f"HAVING {self.having.to_lang_named('SQL', opts)}" if self.having else ''

def group_by_sql(self, opts: dict):
return f"GROUP BY {self._field_list_sql(self.group_by, opts)}" if self.group_by else ''

def _maybe_run(self):
with getattr(self, 'run', None) as run:
if callable(run):
return run()
return self

class Update(Query):
def __init__(self, table, assigns: Block, *, where: SynElement = None):
if assigns is None:
raise EGEError('Assigns is none!')
super(Update, self).__init__(table, where=where)
self.assigns: Block = assigns

def run(self):
self.table.update(self.assigns, self.where)
# TODO check if need return table

def text(self, opts: dict):
"""opts: dict[str, Any]"""
assigns = self.assigns.to_lang_named('SQL')
where_sql = self.where_sql(opts)
if where_sql:
return f"UPDATE {self.table_name} SET {assigns} {where_sql}"
else:
return f"UPDATE {self.table_name} SET {assigns}"
69 changes: 69 additions & 0 deletions EGE/SQL/RandomTable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from EGE.Random import Random

class BaseTable:
columns: list
def __init__(self, rnd: Random, col_count, row_count, name):
self.rnd = rnd
self.columns = self.get_columns()
self.fields = [
self.columns[0],
rnd.pick_n(
col_count - 1,
self.columns[1:len(self.columns)]
)
]
row_sources = [ row for row in self.get_rows_array()
if len(row) >= row_count ]
self.rows = rnd.pick_n(row_count, row_sources)


def get_columns(self) -> list:
return []

def get_rows_array(self):
pass

class Products(BaseTable):
pass

class Jobs(BaseTable):
pass

class SalesMonth(BaseTable):
pass

class Cities(BaseTable):
pass

class People(BaseTable):
pass

class Subjects(BaseTable):
pass

class Marks(BaseTable):
pass

class ParticipantsMonth(BaseTable):
pass

def ok_table(table: BaseTable, rows, cols):
t_cols = table.get_columns()
t_rows = table.get_rows_array()
return t_cols >= cols and any([len(t_rows) > rows])

def pick(rnd: Random, *args):
return rnd.pick([
lambda *args2: Products(args2),
lambda *args2: Jobs(args2),
lambda *args2: SalesMonth(args2),
lambda *args2: Cities(args2),
lambda *args2: People(args2),
lambda *args2: Subjects(args2),
lambda *args2: Marks(args2),
lambda *args2: ParticipantsMonth(args2),
])(args)

def create_table(rnd: Random, rows: int, columns: int):
table = pick(rnd, rows, columns)
return table
206 changes: 206 additions & 0 deletions EGE/SQL/Table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
from EGE.GenBase import EGEError
from EGE.Prog import CallFuncAggregate, make_expr, Op, SynElement, Block
from EGE.Random import Random
from EGE.Utils import aggregate_function

class Field:
def __init__(self, attr):
self.name: str = ''
self.name_alias: str = ''
if isinstance(attr, dict):
self.name = attr['name']
self.name_alias = attr['name_alias']
else:
self.name = attr

def to_lang(self):
return self.name_alias + '.' + self.name if self.name_alias else self.name

def __str__(self):
return self.name

def __repr__(self):
if self.name_alias:
return f"Field({{'name': '{self.name}', 'name_alias': '{self.name_alias}'}})"
return f"Field('{self.name}')"

def __eq__(self, other):
if isinstance(other, str):
return other == self.name or other == self.name_alias
elif isinstance(other, Field):
return other.name == self.name and other.name_alias == self.name_alias
raise EGEError("Imvalid type to compare with Field!")



class Table:
def __init__(self, fields: list, **kwargs):
if fields is None:
raise EGEError('No fields')

self.fields: list[Field] = [ self._make_field(i) for i in fields ]
self.name: str = kwargs['name'] if 'name' in kwargs.keys() else ''
self.field_index: dict = {}
self._update_field_index()
for i in self.fields:
i.table = self
self.data: list = []

def _make_field(self, field):
return field if isinstance(field, Field) else Field(field)

def _update_field_index(self):
for i, key in enumerate(self.fields):
self.field_index[key.name] = i

def assign_field_alias(self, alias: str):
raise NotImplemented()

def insert_row(self, *fields):
if len(fields) != len(self.fields):
EGEError(f'Wrong column count {len(fields)} != {len(self.fields)}')
self.data.append(list(fields))
return self

def insert_rows(self, *rows):
for fields in rows:
self.insert_row(*fields)
return self

def insert_column(self):
raise NotImplemented()

def print_row(self, row):
raise NotImplemented()

def print(self):
raise NotImplemented()

def count(self):
return len(self.data)

def _row_hash(self, row, env=None):
if env is None:
env = {}
for f in self.fields:
env[f.name] = row[self.field_index[f.name]]
return env

def _hash(self):
env = { '&columns': { str(f): self.column_array(str(f)) for f in self.fields },
'&': aggregate_function(),
'&count': self.count() }
return env

def select(self, fields, where=None, p=None):
if not isinstance(fields, list):
fields = [ fields ]
for f in fields:
if not isinstance(f, CallFuncAggregate) and f not in self.fields:
raise EGEError(f"Table '{self.name}' does not contain field '{f}'!")
ref, aggr, group, having = 0, 0, 0, 0
if isinstance(ref, dict):
ref = p[ref]
group = p[group]
having = p[having]

aggr = list(filter(lambda x: isinstance(x, CallFuncAggregate), fields))
k = 0
args = []
exprs = [ f'expr_{i}' for i in range(1, 4) ]
for f in fields:
# TODO this seems to be wrong. Check later
if not isinstance(f, Field) and hasattr(f, '__call__') and f.__name__ in exprs:
k += 1
args.append(f'expr_{k}')
else:
args.append(f)
result = Table(args)
values = [ f if hasattr(f, '__call__') else make_expr(f) for f in fields ]
calc_row = lambda x: [ i.run(x) for i in values ]

tab_where = self.where(where, ref)
if group:
raise NotImplemented()
else:
ans = []
env = tab_where._hash()
for data in tab_where.data:
ans.append(calc_row(tab_where._row_hash(data, env)))
result.data = [ ans[0] ] if aggr else ans #TODO check correctness in case if aggr == True

return result

def group_by(self):
raise NotImplemented()

def where(self, where: Op = None, ref: bool = None):
"""where: Union[callable, None], ref: Union[bool, None]"""
if where is None:
return self
table = Table(self.fields)
table.data = [ data if ref else data[:] for data in self.data if where.run(self._row_hash(data)) ]
return table

def count_where(self, where: Op = None):
if where is None:
return self.count()
res = list(filter(lambda x: where.run(self._row_hash(x)), self.data))
return len(res)

def update(self, assigns: Block, where: Op = None):
data = self.data if where is None else self.where(where, True)
for row in data:
row_hash = self._row_hash(row)
assigns.run(row_hash)
for f in self.fields:
row[self._field_index(f.name)] = row_hash[f.name]
return self

def delete(self):
raise NotImplemented()

def natural_join(self):
raise NotImplemented()

def inner_join(self):
raise NotImplemented()

def inner_join_expr(self):
raise NotImplemented()

def table_html(self):
raise NotImplemented()

def fetch_val(self):
raise NotImplemented()

#TODO how best to implement getting the random number generator in table methods
def random_row(self, rnd: Random):
return rnd.pick(self.data)

def _field_index(self, field: str):
"""field: Union[int, str]"""
if isinstance(field, int):
if 1 <= field <= len(self.fields):
return field - 1
else:
raise EGEError(f"Unknown field {field}")
if field not in self.field_index.keys():
raise EGEError(f"Unknown field {field}")
return self.field_index[field]

def column_array(self, field):
"""field: Union[int, str]"""
column_idx = self._field_index(field)
return [ data[column_idx] for data in self.data ]

def column_hash(self, field):
"""field: Union[int, str]"""
column_idx = self._field_index(field)
r = {}
for row in self.data:
if row[column_idx] not in r.keys():
r[row[column_idx]] = 0
r[row[column_idx]] += 1
return r
Loading