From 824db2a43b347b23eb2d7b0d90cecbdac00761ff Mon Sep 17 00:00:00 2001 From: Bill Sun Date: Sat, 28 May 2022 03:38:47 +0800 Subject: [PATCH] server 2 --- Makefile | 2 + README.md | 2 +- dbconn.py | 88 +++--- out.cpp | 64 ++--- prompt.py | 145 ++++++++-- reconstruct/__init__.py | 22 +- reconstruct/ast.py | 604 +++++++++++++++++++-------------------- reconstruct/expr.py | 254 ++++++++-------- reconstruct/storage.py | 176 ++++++------ server.exe | Bin 0 -> 181760 bytes server/.claudiaideconfig | 27 -- server/libaquery.h | 15 +- server/server.cpp | 39 ++- 13 files changed, 772 insertions(+), 666 deletions(-) create mode 100644 server.exe delete mode 100644 server/.claudiaideconfig diff --git a/Makefile b/Makefile index dd4dc9b..de4a904 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,8 @@ $(info $(OS_SUPPORT)) server.bin: g++ server/server.cpp $(OS_SUPPORT) --std=c++1z -O3 -march=native -o server.bin +server.so: + g++ server/server.cpp -shared $(OS_SUPPORT) --std=c++1z -O3 -march=native -o server.so snippet: g++ -shared -fPIC --std=c++1z out.cpp -O3 -march=native -o dll.so clean: diff --git a/README.md b/README.md index e6cec8f..13077ce 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ Recent version of Linux, Windows or MacOS, with recent C++ compiler that has C++ - `exit`: quit the prompt #### Example: `f moving_avg.a`
- `exec` \ No newline at end of file + `exec` diff --git a/dbconn.py b/dbconn.py index d6a283a..443fd23 100644 --- a/dbconn.py +++ b/dbconn.py @@ -1,45 +1,45 @@ -import mariadb - -class dbconn: - def __init__(self) -> None: - self.db = None - self.cur = None - def clear(self): - drop_all = f''' - SET FOREIGN_KEY_CHECKS = 0; - - SET @tables = NULL; - - SELECT GROUP_CONCAT('`', table_schema, '`.`', table_name, '`') INTO @tables - FROM information_schema.tables - WHERE table_schema = '{self.db.database}'; - - SET @tables = CONCAT('DROP TABLE ', @tables); - PREPARE stmt FROM @tables; - EXECUTE stmt; - DEALLOCATE PREPARE stmt; - SET FOREIGN_KEY_CHECKS = 1; - ''' - if self.db: - if not self.cur: - self.cur = self.db.cursor() - self.cur.execute(drop_all) - - def connect(self, ip, password = '0508', user = 'root', db = 'db', port = 3306): - try: - self.db = mariadb.connect( - user = user, - password = password, - host = ip, - port = port, - database = db - ) - self.cur = self.db.cursor() - - except mariadb.Error as e: - print(e) - self.db = None - self.cur = None - - def exec(self, sql, params = None): +import mariadb + +class dbconn: + def __init__(self) -> None: + self.db = None + self.cur = None + def clear(self): + drop_all = f''' + SET FOREIGN_KEY_CHECKS = 0; + + SET @tables = NULL; + + SELECT GROUP_CONCAT('`', table_schema, '`.`', table_name, '`') INTO @tables + FROM information_schema.tables + WHERE table_schema = '{self.db.database}'; + + SET @tables = CONCAT('DROP TABLE ', @tables); + PREPARE stmt FROM @tables; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + SET FOREIGN_KEY_CHECKS = 1; + ''' + if self.db: + if not self.cur: + self.cur = self.db.cursor() + self.cur.execute(drop_all) + + def connect(self, ip, password = '0508', user = 'root', db = 'db', port = 3306): + try: + self.db = mariadb.connect( + user = user, + password = password, + host = ip, + port = port, + database = db + ) + self.cur = self.db.cursor() + + except mariadb.Error as e: + print(e) + self.db = None + self.cur = None + + def exec(self, sql, params = None): self.cur.execute(sql) \ No newline at end of file diff --git a/out.cpp b/out.cpp index 9eed642..768a22d 100644 --- a/out.cpp +++ b/out.cpp @@ -1,6 +1,6 @@ -#include "./server/libaquery.h" #include "./server/aggregations.h" #include "csv.h" +#include "./server/libaquery.h" #include "./server/hasher.h" #include @@ -18,40 +18,40 @@ test_a.init("a"); test_b.init("b"); test_c.init("c"); test_d.init("d"); -io::CSVReader<4> csv_reader_307VD4("test.csv"); -csv_reader_307VD4.read_header(io::ignore_extra_column, "a","b","c","d"); -int tmp_3LXIYQmp; -int tmp_1m5NCKR4; -int tmp_10LZcLgy; -int tmp_39pPZL8W; -while(csv_reader_307VD4.read_row(tmp_3LXIYQmp,tmp_1m5NCKR4,tmp_10LZcLgy,tmp_39pPZL8W)) { +io::CSVReader<4> csv_reader_1qh80y("test.csv"); +csv_reader_1qh80y.read_header(io::ignore_extra_column, "a","b","c","d"); +int tmp_6JfuovoZ; +int tmp_4B4ADRgW; +int tmp_3JHd3elW; +int tmp_1heR8kZw; +while(csv_reader_1qh80y.read_row(tmp_6JfuovoZ,tmp_4B4ADRgW,tmp_3JHd3elW,tmp_1heR8kZw)) { -test_a.emplace_back(tmp_3LXIYQmp); -test_b.emplace_back(tmp_1m5NCKR4); -test_c.emplace_back(tmp_10LZcLgy); -test_d.emplace_back(tmp_39pPZL8W); +test_a.emplace_back(tmp_6JfuovoZ); +test_b.emplace_back(tmp_4B4ADRgW); +test_c.emplace_back(tmp_3JHd3elW); +test_d.emplace_back(tmp_1heR8kZw); } -typedef record record_type3OMslKw; -unordered_map, transTypes> g7LNVAss; -for (uint32_t i1T = 0; i1T < test_a.size; ++i1T){ -g7LNVAss[forward_as_tuple(test_a[i1T],test_b[i1T],test_d[i1T])].emplace_back(i1T); +typedef record record_type3he5qd1; +unordered_map, transTypes> g59vWI2v; +for (uint32_t i3P = 0; i3P < test_a.size; ++i3P){ +g59vWI2v[forward_as_tuple(test_a[i3P],test_b[i3P],test_d[i3P])].emplace_back(i3P); } -auto out_HSfK = new TableInfo,value_type>,value_type>>("out_HSfK", 3); -cxt->tables.insert({"out_HSfK", out_HSfK}); -auto& out_HSfK_sumtestc = *(ColRef> *)(&out_HSfK->colrefs[0]); -auto& out_HSfK_b = *(ColRef>> *)(&out_HSfK->colrefs[1]); -auto& out_HSfK_d = *(ColRef>> *)(&out_HSfK->colrefs[2]); -out_HSfK_sumtestc.init("sumtestc"); -out_HSfK_b.init("b"); -out_HSfK_d.init("d"); -for(auto& i18 : g7LNVAss) { -auto &key_3s5slnK = i18.first; -auto &val_2nNLv0D = i18.second; -out_HSfK_sumtestc.emplace_back(sum(test_c[val_2nNLv0D])); -out_HSfK_b.emplace_back(get<1>(key_3s5slnK)); -out_HSfK_d.emplace_back(get<2>(key_3s5slnK)); +auto out_6JAp = new TableInfo,value_type>,value_type>>("out_6JAp", 3); +cxt->tables.insert({"out_6JAp", out_6JAp}); +auto& out_6JAp_sumtestc = *(ColRef> *)(&out_6JAp->colrefs[0]); +auto& out_6JAp_b = *(ColRef>> *)(&out_6JAp->colrefs[1]); +auto& out_6JAp_d = *(ColRef>> *)(&out_6JAp->colrefs[2]); +out_6JAp_sumtestc.init("sumtestc"); +out_6JAp_b.init("b"); +out_6JAp_d.init("d"); +for(auto& i2Y : g59vWI2v) { +auto &key_1yBYhdd = i2Y.first; +auto &val_61QXy6G = i2Y.second; +out_6JAp_sumtestc.emplace_back(sum(test_c[val_61QXy6G])); +out_6JAp_b.emplace_back(get<1>(key_1yBYhdd)); +out_6JAp_d.emplace_back(get<2>(key_1yBYhdd)); } -auto d5b7C95U = out_HSfK->order_by_view<-3,1>(); -print(d5b7C95U); +auto d1dyPtv0 = out_6JAp->order_by_view<-3,1>(); +print(d1dyPtv0); return 0; } \ No newline at end of file diff --git a/prompt.py b/prompt.py index f1a317b..c94d931 100644 --- a/prompt.py +++ b/prompt.py @@ -1,3 +1,4 @@ +import enum import re import time import dbconn @@ -12,14 +13,26 @@ import sys import os from engine.utils import base62uuid import atexit + +import threading +import ctypes + +class RunType(enum.Enum): + Threaded = 0 + IPC = 1 + +server_mode = RunType.Threaded + +server_bin = 'server.bin' if server_mode == RunType.IPC else 'server.so' + try: - os.remove('server.bin') + os.remove(server_bin) except Exception as e: print(type(e), e) nullstream = open(os.devnull, 'w') -subprocess.call(['make', 'server.bin'], stdout=nullstream) +subprocess.call(['make', server_bin], stdout=nullstream) cleanup = True def rm(): @@ -44,10 +57,8 @@ def rm(): mm.close() cleanup = False nullstream.close() - -atexit.register(rm) -def init(): +def init_ipc(): global shm, server, basecmd, mm shm = base62uuid() if sys.platform != 'win32': @@ -70,6 +81,100 @@ def init(): mm.flush() server = subprocess.Popen(["./server.bin", shm]) +import numpy as np + +c = lambda _ba: ctypes.cast((ctypes.c_char * len(_ba)).from_buffer(_ba), ctypes.c_char_p) + +class Config: + def __init__(self, nq = 0, mode = server_mode, n_bufs = 0, bf_szs = []) -> None: + self.int_size = 4 + self.n_attrib = 4 + self.buf = bytearray((self.n_attrib + n_bufs) * self.int_size) + self.np_buf = np.ndarray(shape=(self.n_attrib), buffer=self.buf, dtype=np.int32) + self.new_query = nq + self.server_mode = mode.value + self.running = 1 + self.n_buffers = n_bufs + + @property + def running(self): + return self.np_buf[0] + @running.setter + def running(self, rn): + self.np_buf[0] = rn + + @property + def new_query(self): + return self.np_buf[1] + @new_query.setter + def new_query(self, nq): + self.np_buf[1] = nq + + @property + def server_mode(self): + return self.np_buf[2] + @server_mode.setter + def server_mode(self, mode): + self.np_buf[2] = mode + + @property + def n_buffers(self): + return self.np_buf[3] + @n_buffers.setter + def n_buffers(self, n_bufs): + self.np_buf[3] = n_bufs + + def set_bufszs(self, buf_szs): + for i in range(min(len(buf_szs), self.n_buffers)): + self.np_buf[i+self.n_attrib] = buf_szs[i] + + @property + def c(self): + return c(self.buf) + +cfg = Config() +th = None + +def init_threaded(): + + if os.name == 'nt': + t = os.environ['PATH'].lower().split(';') + vars = re.compile('%.*%') + for e in t: + if(len(e) != 0): + if '%' in e: + try: + m_e = vars.findall(e) + for m in m_e: + e = e.replace(m, os.environ[m[1:-1]]) + # print(m, e) + except Exception: + continue + os.add_dll_directory(e) + + server_so = ctypes.CDLL('./'+server_bin) + global cfg, th + th = threading.Thread(target=server_so['main'], args=(-1, ctypes.POINTER(ctypes.c_char_p)(cfg.c)), daemon=True) + th.start() + +if server_mode == RunType.IPC: + atexit.register(rm) + init = init_ipc + set_ready = lambda : mm.seek(0,os.SEEK_SET) or mm.write(b'\x01\x01') + def __get_ready(): + mm.seek(0,os.SEEK_SET) + return mm.read(2)[1] + get_ready = __get_ready + server_status = lambda : server.poll() is not None +else: + init = init_threaded + rm = lambda: None + def __set_ready(): + global cfg + cfg.new_query = 1 + set_ready = __set_ready + get_ready = lambda:cfg.new_query + server_status = lambda : not th.is_alive() init() test_parser = True @@ -82,31 +187,16 @@ q = 'SELECT p.Name, v.Name FROM Production.Product p JOIN Purchasing.ProductVend res = parser.parse(q) -# else:f -# if subprocess.call(['make', 'snippet']) == 0: -# mm.seek(0) -# mm.write(b'\x01\x01') -# time.sleep(.1) -# mm.seek(0) -# print(mm.read(2)) - -# mm.close() -# handle.close() -# os.remove(shm) -# exit() keep = True cxt = engine.initialize() cxt.Info(res) while test_parser: try: - if server.poll() is not None: + if server_status(): init() - print("> ", end="") - ready = 1 - while ready == 1: - mm.seek(0,os.SEEK_SET) - ready = mm.read(2)[1] + while get_ready(): time.sleep(.00001) + print("> ", end="") q = input().lower() if q == 'exec': if not keep or cxt is None: @@ -123,8 +213,7 @@ while test_parser: with open('out.cpp', 'wb') as outfile: outfile.write((cxt.finalize()).encode('utf-8')) if subprocess.call(['make', 'snippet'], stdout = nullstream) == 0: - mm.seek(0,os.SEEK_SET) - mm.write(b'\x01\x01') + set_ready() continue if q == 'xexec': cxt = xengine.initialize() @@ -159,8 +248,10 @@ while test_parser: break elif q == 'r': if subprocess.call(['make', 'snippet']) == 0: - mm.seek(0,os.SEEK_SET) - mm.write(b'\x01\x01') + set_ready() + continue + elif q == 'rr': + set_ready() continue elif q.startswith('save'): filename = re.split(' |\t', q) diff --git a/reconstruct/__init__.py b/reconstruct/__init__.py index e99ac71..e103251 100644 --- a/reconstruct/__init__.py +++ b/reconstruct/__init__.py @@ -1,11 +1,11 @@ -from reconstruct.ast import Context, ast_node - -def initialize(): - return Context() - -def generate(ast, cxt): - for k in ast.keys(): - if k in ast_node.types.keys(): - ast_node.types[k](None, ast, cxt) - -__all__ = ["initialize", "generate"] +from reconstruct.ast import Context, ast_node + +def initialize(): + return Context() + +def generate(ast, cxt): + for k in ast.keys(): + if k in ast_node.types.keys(): + ast_node.types[k](None, ast, cxt) + +__all__ = ["initialize", "generate"] diff --git a/reconstruct/ast.py b/reconstruct/ast.py index d1dc0c2..b654710 100644 --- a/reconstruct/ast.py +++ b/reconstruct/ast.py @@ -1,303 +1,303 @@ -from engine.utils import enlist, base62uuid, base62alp -from reconstruct.storage import Context, TableInfo, ColRef - -class ast_node: - header = [] - types = dict() - first_order = False - - def __init__(self, parent:"ast_node", node, context:Context = None): - self.context = parent.context if context is None else context - self.parent = parent - self.sql = '' - self.datasource = None - self.init(node) - self.produce(node) - self.spawn(node) - self.consume(node) - - def emit(self, code): - self.context.emit(code) - def add(self, code): - self.sql += code + ' ' - - name = 'null' - - def init(self, _): - self.add(self.__class__.name.upper()) - def produce(self, _): - pass - def spawn(self, _): - pass - - def consume(self, _): - if self.parent is None: - self.emit(self.sql+';\n') - - -from reconstruct.expr import expr - - -class projection(ast_node): - name = 'projection' - first_order = 'select' - - def init(self, _): - pass - def produce(self, node): - p = node['select'] - self.projections = p if type(p) is list else [p] - self.add('SELECT') - - def spawn(self, node): - self.datasource = None # datasource is Join instead of TableInfo - if 'from' in node: - from_clause = node['from'] - self.datasource = join(self, from_clause) - if 'assumptions' in from_clause: - self.assumptions = enlist(from_clause['assumptions']) - - if self.datasource is not None: - self.datasource_changed = True - self.prev_datasource = self.context.datasource - self.context.datasource = self.datasource - - if 'where' in node: - self.where = filter(self, node['where']) - else: - self.where = None - - if 'groupby' in node: - self.group_node = groupby(self, node['groupby']) - else: - self.group_node = None - - def consume(self, node): - # deal with projections - self.out_table = TableInfo('out_'+base62uuid(4), [], self.context) - cols = [] - col_exprs = [] - for i, proj in enumerate(self.projections): - compound = False - self.datasource.rec = set() - name = '' - if type(proj) is dict: - - if 'value' in proj: - e = proj['value'] - name = expr(self, e).sql - disp_name = ''.join([a if a in base62alp else '' for a in name]) - compound = True # compound column - if 'name' in proj: # renaming column by AS keyword - name += ' ' + proj['name'] - col_exprs.append(name) - - elif type(proj) is str: - col = self.datasource.get_col(proj) - name = col.name - self.datasource.rec = None - # TODO: Type deduction in Python - cols.append(ColRef('unknown', self.out_table, None, disp_name, i, compound=compound)) - self.add(', '.join(col_exprs)) - - def finialize(astnode:ast_node): - if(astnode is not None): - self.add(astnode.sql) - self.add('FROM') - finialize(self.datasource) - finialize(self.where) - finialize(self.group_node) - if 'orderby' in node: - self.add(orderby(self, node['orderby']).sql) - if 'outfile' in node: - self.add(outfile(self, node['outfile']).sql) - if self.parent is None: - self.emit(self.sql+';\n') - else: - # TODO: subquery, name create tmp-table from subquery w/ alias as name - pass - - -class orderby(ast_node): - name = 'order by' - def produce(self, node): - if node is None: - self.sql = '' - return - elif type(node) is not list: - node = [node] - - o_list = [] - - for o in node: - o_str = expr(self, o['value']).sql - if 'sort' in o and f'{o["sort"]}'.lower() == 'desc': - o_str += ' ' + 'DESC' - o_list.append(o_str) - self.add(', '.join(o_list)) - - -class groupby(orderby): - name = 'group by' - - -class join(ast_node): - name = 'join' - def init(self, _): - self.joins:list = [] - self.tables = [] - self.tables_dir = dict() - # self.tmp_name = 'join_' + base62uuid(4) - # self.datasource = TableInfo(self.tmp_name, [], self.context) - def append(self, tbls, __alias = ''): - alias = lambda t : '(' + t + ') ' + __alias if len(__alias) else t - if type(tbls) is join: - self.joins.append(alias(tbls.__str__())) - self.tables += tbls.tables - self.tables_dir = {**self.tables_dir, **tbls.tables_dir} - - elif type(tbls) is TableInfo: - self.joins.append(alias(tbls.table_name)) - self.tables.append(tbls) - self.tables_dir[tbls.table_name] = tbls - for a in tbls.alias: - self.tables_dir[a] = tbls - - elif type(tbls) is projection: - self.joins.append(alias(tbls.finalize())) - - def produce(self, node): - if type(node) is list: - for d in node: - self.append(join(self, d).__str__()) - - elif type(node) is dict: - alias = '' - if 'value' in node: - table_name = node['value'] - tbl = None - if 'name' in node: - alias = node['name'] - if type(table_name) is dict: - if 'select' in table_name: - # TODO: subquery, create and register TableInfo in projection - tbl = projection(self, table_name).finalize() - else: - tbl = self.context.tables_byname[table_name] - if 'name' in node: - tbl.add_alias(node['name']) - - self.append(tbl, alias) - else: - keys = node.keys() - if keys[0].lower().endswith('join'): - j = join(self, node[keys[0]]) - tablename = f' {keys[0]} {j}' - if keys[1].lower() == 'on': - tablename += f' on {expr(self, node[keys[1]])}' - self.joins.append(tablename) - self.tables += j.tables - self.tables_dir = {**self.tables_dir, **j.tables_dir} - - elif type(node) is str: - self.append(self.context.tables_byname[node]) - - def get_cols(self, colExpr: str) -> ColRef: - for t in self.tables: - if colExpr in t.columns_byname: - return t.columns_byname[colExpr] - - def parse_col_names(self, colExpr:str) -> ColRef: - parsedColExpr = colExpr.split('.') - if len(parsedColExpr) <= 1: - return self.get_cols(colExpr) - else: - datasource = self.tables_dir[parsedColExpr[0]] - if datasource is None: - raise ValueError(f'Table name/alias not defined{parsedColExpr[0]}') - else: - return datasource.parse_col_names(parsedColExpr[1]) - - def consume(self, _): - self.sql = ', '.join(self.joins) - return super().consume(_) - def __str__(self): - return ', '.join(self.joins) - def __repr__(self): - return self.__str__() - - -class filter(ast_node): - name = 'where' - def produce(self, node): - self.add(expr(self, node).sql) - - -class create_table(ast_node): - name = 'create_table' - first_order = name - def init(self, node): - self.sql = 'CREATE TABLE ' - - def produce(self, node): - ct = node[self.name] - tbl = self.context.add_table(ct['name'], ct['columns']) - self.sql = f'CREATE TABLE {tbl.table_name}(' - columns = [] - for c in tbl.columns: - columns.append(f'{c.name} {c.type.upper()}') - self.sql += ', '.join(columns) - self.sql += ')' - - - -class insert(ast_node): - name = 'insert' - first_order = name - def produce(self, node): - values = node['query']['select'] - tbl = node['insert'] - self.sql = f'INSERT INTO {tbl} VALUES(' - # if len(values) != table.n_cols: - # raise ValueError("Column Mismatch") - list_values = [] - for i, s in enumerate(values): - if 'value' in s: - list_values.append(f"{s['value']}") - else: - # subquery, dispatch to select astnode - pass - self.sql += ', '.join(list_values) + ')' - - -class load(ast_node): - name="load" - first_order = name - def produce(self, node): - node = node['load'] - s1 = 'LOAD DATA INFILE ' - s2 = 'INTO TABLE ' - s3 = 'FIELDS TERMINATED BY ' - self.sql = f'{s1} \"{node["file"]["literal"]}\" {s2} {node["table"]}' - if 'term' in node: - self.sql += f' {s3} \"{node["term"]["literal"]}\"' - - -class outfile(ast_node): - name="_outfile" - def produce(self, node): - filename = node['loc']['literal'] if 'loc' in node else node['literal'] - self.sql = f'INTO OUTFILE "{filename}"' - if 'term' in node: - self.sql += f' FIELDS TERMINATED BY \"{node["term"]["literal"]}\"' - - -def include(objs): - import inspect - for _, cls in inspect.getmembers(objs): - if inspect.isclass(cls) and issubclass(cls, ast_node) and type(cls.first_order) is str: - ast_node.types[cls.first_order] = cls - - -import sys +from engine.utils import enlist, base62uuid, base62alp +from reconstruct.storage import Context, TableInfo, ColRef + +class ast_node: + header = [] + types = dict() + first_order = False + + def __init__(self, parent:"ast_node", node, context:Context = None): + self.context = parent.context if context is None else context + self.parent = parent + self.sql = '' + self.datasource = None + self.init(node) + self.produce(node) + self.spawn(node) + self.consume(node) + + def emit(self, code): + self.context.emit(code) + def add(self, code): + self.sql += code + ' ' + + name = 'null' + + def init(self, _): + self.add(self.__class__.name.upper()) + def produce(self, _): + pass + def spawn(self, _): + pass + + def consume(self, _): + if self.parent is None: + self.emit(self.sql+';\n') + + +from reconstruct.expr import expr + + +class projection(ast_node): + name = 'projection' + first_order = 'select' + + def init(self, _): + pass + def produce(self, node): + p = node['select'] + self.projections = p if type(p) is list else [p] + self.add('SELECT') + + def spawn(self, node): + self.datasource = None # datasource is Join instead of TableInfo + if 'from' in node: + from_clause = node['from'] + self.datasource = join(self, from_clause) + if 'assumptions' in from_clause: + self.assumptions = enlist(from_clause['assumptions']) + + if self.datasource is not None: + self.datasource_changed = True + self.prev_datasource = self.context.datasource + self.context.datasource = self.datasource + + if 'where' in node: + self.where = filter(self, node['where']) + else: + self.where = None + + if 'groupby' in node: + self.group_node = groupby(self, node['groupby']) + else: + self.group_node = None + + def consume(self, node): + # deal with projections + self.out_table = TableInfo('out_'+base62uuid(4), [], self.context) + cols = [] + col_exprs = [] + for i, proj in enumerate(self.projections): + compound = False + self.datasource.rec = set() + name = '' + if type(proj) is dict: + + if 'value' in proj: + e = proj['value'] + name = expr(self, e).sql + disp_name = ''.join([a if a in base62alp else '' for a in name]) + compound = True # compound column + if 'name' in proj: # renaming column by AS keyword + name += ' ' + proj['name'] + col_exprs.append(name) + + elif type(proj) is str: + col = self.datasource.get_col(proj) + name = col.name + self.datasource.rec = None + # TODO: Type deduction in Python + cols.append(ColRef('unknown', self.out_table, None, disp_name, i, compound=compound)) + self.add(', '.join(col_exprs)) + + def finialize(astnode:ast_node): + if(astnode is not None): + self.add(astnode.sql) + self.add('FROM') + finialize(self.datasource) + finialize(self.where) + finialize(self.group_node) + if 'orderby' in node: + self.add(orderby(self, node['orderby']).sql) + if 'outfile' in node: + self.add(outfile(self, node['outfile']).sql) + if self.parent is None: + self.emit(self.sql+';\n') + else: + # TODO: subquery, name create tmp-table from subquery w/ alias as name + pass + + +class orderby(ast_node): + name = 'order by' + def produce(self, node): + if node is None: + self.sql = '' + return + elif type(node) is not list: + node = [node] + + o_list = [] + + for o in node: + o_str = expr(self, o['value']).sql + if 'sort' in o and f'{o["sort"]}'.lower() == 'desc': + o_str += ' ' + 'DESC' + o_list.append(o_str) + self.add(', '.join(o_list)) + + +class groupby(orderby): + name = 'group by' + + +class join(ast_node): + name = 'join' + def init(self, _): + self.joins:list = [] + self.tables = [] + self.tables_dir = dict() + # self.tmp_name = 'join_' + base62uuid(4) + # self.datasource = TableInfo(self.tmp_name, [], self.context) + def append(self, tbls, __alias = ''): + alias = lambda t : '(' + t + ') ' + __alias if len(__alias) else t + if type(tbls) is join: + self.joins.append(alias(tbls.__str__())) + self.tables += tbls.tables + self.tables_dir = {**self.tables_dir, **tbls.tables_dir} + + elif type(tbls) is TableInfo: + self.joins.append(alias(tbls.table_name)) + self.tables.append(tbls) + self.tables_dir[tbls.table_name] = tbls + for a in tbls.alias: + self.tables_dir[a] = tbls + + elif type(tbls) is projection: + self.joins.append(alias(tbls.finalize())) + + def produce(self, node): + if type(node) is list: + for d in node: + self.append(join(self, d).__str__()) + + elif type(node) is dict: + alias = '' + if 'value' in node: + table_name = node['value'] + tbl = None + if 'name' in node: + alias = node['name'] + if type(table_name) is dict: + if 'select' in table_name: + # TODO: subquery, create and register TableInfo in projection + tbl = projection(self, table_name).finalize() + else: + tbl = self.context.tables_byname[table_name] + if 'name' in node: + tbl.add_alias(node['name']) + + self.append(tbl, alias) + else: + keys = node.keys() + if keys[0].lower().endswith('join'): + j = join(self, node[keys[0]]) + tablename = f' {keys[0]} {j}' + if keys[1].lower() == 'on': + tablename += f' on {expr(self, node[keys[1]])}' + self.joins.append(tablename) + self.tables += j.tables + self.tables_dir = {**self.tables_dir, **j.tables_dir} + + elif type(node) is str: + self.append(self.context.tables_byname[node]) + + def get_cols(self, colExpr: str) -> ColRef: + for t in self.tables: + if colExpr in t.columns_byname: + return t.columns_byname[colExpr] + + def parse_col_names(self, colExpr:str) -> ColRef: + parsedColExpr = colExpr.split('.') + if len(parsedColExpr) <= 1: + return self.get_cols(colExpr) + else: + datasource = self.tables_dir[parsedColExpr[0]] + if datasource is None: + raise ValueError(f'Table name/alias not defined{parsedColExpr[0]}') + else: + return datasource.parse_col_names(parsedColExpr[1]) + + def consume(self, _): + self.sql = ', '.join(self.joins) + return super().consume(_) + def __str__(self): + return ', '.join(self.joins) + def __repr__(self): + return self.__str__() + + +class filter(ast_node): + name = 'where' + def produce(self, node): + self.add(expr(self, node).sql) + + +class create_table(ast_node): + name = 'create_table' + first_order = name + def init(self, node): + self.sql = 'CREATE TABLE ' + + def produce(self, node): + ct = node[self.name] + tbl = self.context.add_table(ct['name'], ct['columns']) + self.sql = f'CREATE TABLE {tbl.table_name}(' + columns = [] + for c in tbl.columns: + columns.append(f'{c.name} {c.type.upper()}') + self.sql += ', '.join(columns) + self.sql += ')' + + + +class insert(ast_node): + name = 'insert' + first_order = name + def produce(self, node): + values = node['query']['select'] + tbl = node['insert'] + self.sql = f'INSERT INTO {tbl} VALUES(' + # if len(values) != table.n_cols: + # raise ValueError("Column Mismatch") + list_values = [] + for i, s in enumerate(values): + if 'value' in s: + list_values.append(f"{s['value']}") + else: + # subquery, dispatch to select astnode + pass + self.sql += ', '.join(list_values) + ')' + + +class load(ast_node): + name="load" + first_order = name + def produce(self, node): + node = node['load'] + s1 = 'LOAD DATA INFILE ' + s2 = 'INTO TABLE ' + s3 = 'FIELDS TERMINATED BY ' + self.sql = f'{s1} \"{node["file"]["literal"]}\" {s2} {node["table"]}' + if 'term' in node: + self.sql += f' {s3} \"{node["term"]["literal"]}\"' + + +class outfile(ast_node): + name="_outfile" + def produce(self, node): + filename = node['loc']['literal'] if 'loc' in node else node['literal'] + self.sql = f'INTO OUTFILE "{filename}"' + if 'term' in node: + self.sql += f' FIELDS TERMINATED BY \"{node["term"]["literal"]}\"' + + +def include(objs): + import inspect + for _, cls in inspect.getmembers(objs): + if inspect.isclass(cls) and issubclass(cls, ast_node) and type(cls.first_order) is str: + ast_node.types[cls.first_order] = cls + + +import sys include(sys.modules[__name__]) \ No newline at end of file diff --git a/reconstruct/expr.py b/reconstruct/expr.py index 9b24a64..4de2a24 100644 --- a/reconstruct/expr.py +++ b/reconstruct/expr.py @@ -1,128 +1,128 @@ -from reconstruct.ast import ast_node -from reconstruct.storage import ColRef, TableInfo - - -class expr(ast_node): - name='expr' - builtin_func_maps = { - 'max': 'MAX', - 'min': 'MIN', - 'avg': 'AVG', - 'sum': 'SUM', - 'count' : 'COUNT', - 'mins': ['mins', 'minw'], - 'maxs': ['maxs', 'maxw'], - 'avgs': ['avgs', 'avgw'], - 'sums': ['sums', 'sumw'], - } - - binary_ops = { - 'sub':'-', - 'add':'+', - 'mul':'*', - 'div':'/', - 'mod':'%', - 'and':' AND ', - 'or':' OR ', - 'xor' : ' XOR ', - 'gt':'>', - 'lt':'<', - 'le':'<=', - 'gt':'>=' - } - - compound_ops = { - } - - unary_ops = { - 'neg' : '-', - 'not' : ' NOT ' - } - - coumpound_generating_ops = ['avgs', 'mins', 'maxs', 'sums'] + \ - list(binary_ops.keys()) + list(compound_ops.keys()) + list(unary_ops.keys() ) - - def __init__(self, parent, node): - self.raw_col = None - self.inside_agg = False - if(type(parent) is expr): - self.inside_agg = parent.inside_agg - ast_node.__init__(self, parent, node, None) - - def init(self, _): - from engine.projection import projection - parent = self.parent - self.isvector = parent.isvector if type(parent) is expr else False - self.is_compound = parent.is_compound if type(parent) is expr else False - if type(parent) in [projection, expr]: - self.datasource = parent.datasource - else: - self.datasource = self.context.datasource - self.udf_map = parent.context.udf_map - self.func_maps = {**self.udf_map, **self.builtin_func_maps} - - def produce(self, node): - if type(node) is dict: - for key, val in node.items(): - if key in self.func_maps: - # TODO: distinguish between UDF agg functions and other UDF functions. - self.inside_agg = True - if type(val) is list and len(val) > 1: - cfunc = self.func_maps[key] - cfunc = cfunc[len(val) - 1] if type(cfunc) is list else cfunc - self.sql += f"{cfunc}(" - for i, p in enumerate(val): - self.sql += expr(self, p).sql + (',' if i < len(val) - 1 else '') - else: - funcname = self.func_maps[key] - funcname = funcname[0] if type(funcname) is list else funcname - self.sql += f"{funcname}(" - self.sql += expr(self, val).sql - self.sql += ')' - self.inside_agg = False - elif key in self.binary_ops: - l = expr(self, val[0]).sql - r = expr(self, val[1]).sql - self.sql += f'({l}{self.binary_ops[key]}{r})' - elif key in self.compound_ops: - x = [] - if type(val) is list: - for v in val: - x.append(expr(self, v).sql) - self.sql = self.compound_ops[key][1](x) - elif key in self.unary_ops: - self.sql += f'{self.unary_ops[key]}({expr(self, val).sql})' - else: - print(f'Undefined expr: {key}{val}') - - if key in self.coumpound_generating_ops and not self.is_compound: - self.is_compound = True - p = self.parent - while type(p) is expr and not p.is_compound: - p.is_compound = True - p = p.parent - - elif type(node) is str: - p = self.parent - while type(p) is expr and not p.isvector: - p.isvector = True - p = p.parent - - self.raw_col = self.datasource.parse_col_names(node) - self.raw_col = self.raw_col if type(self.raw_col) is ColRef else None - if self.raw_col is not None: - self.sql = self.raw_col.name - else: - self.sql = node - - elif type(node) is bool: - self.sql = '1' if node else '0' - else: - self.sql = f'{node}' - - def __str__(self): - return self.sql - def __repr__(self): - return self.__str__() - +from reconstruct.ast import ast_node +from reconstruct.storage import ColRef, TableInfo + + +class expr(ast_node): + name='expr' + builtin_func_maps = { + 'max': 'MAX', + 'min': 'MIN', + 'avg': 'AVG', + 'sum': 'SUM', + 'count' : 'COUNT', + 'mins': ['mins', 'minw'], + 'maxs': ['maxs', 'maxw'], + 'avgs': ['avgs', 'avgw'], + 'sums': ['sums', 'sumw'], + } + + binary_ops = { + 'sub':'-', + 'add':'+', + 'mul':'*', + 'div':'/', + 'mod':'%', + 'and':' AND ', + 'or':' OR ', + 'xor' : ' XOR ', + 'gt':'>', + 'lt':'<', + 'le':'<=', + 'gt':'>=' + } + + compound_ops = { + } + + unary_ops = { + 'neg' : '-', + 'not' : ' NOT ' + } + + coumpound_generating_ops = ['avgs', 'mins', 'maxs', 'sums'] + \ + list(binary_ops.keys()) + list(compound_ops.keys()) + list(unary_ops.keys() ) + + def __init__(self, parent, node): + self.raw_col = None + self.inside_agg = False + if(type(parent) is expr): + self.inside_agg = parent.inside_agg + ast_node.__init__(self, parent, node, None) + + def init(self, _): + from engine.projection import projection + parent = self.parent + self.isvector = parent.isvector if type(parent) is expr else False + self.is_compound = parent.is_compound if type(parent) is expr else False + if type(parent) in [projection, expr]: + self.datasource = parent.datasource + else: + self.datasource = self.context.datasource + self.udf_map = parent.context.udf_map + self.func_maps = {**self.udf_map, **self.builtin_func_maps} + + def produce(self, node): + if type(node) is dict: + for key, val in node.items(): + if key in self.func_maps: + # TODO: distinguish between UDF agg functions and other UDF functions. + self.inside_agg = True + if type(val) is list and len(val) > 1: + cfunc = self.func_maps[key] + cfunc = cfunc[len(val) - 1] if type(cfunc) is list else cfunc + self.sql += f"{cfunc}(" + for i, p in enumerate(val): + self.sql += expr(self, p).sql + (',' if i < len(val) - 1 else '') + else: + funcname = self.func_maps[key] + funcname = funcname[0] if type(funcname) is list else funcname + self.sql += f"{funcname}(" + self.sql += expr(self, val).sql + self.sql += ')' + self.inside_agg = False + elif key in self.binary_ops: + l = expr(self, val[0]).sql + r = expr(self, val[1]).sql + self.sql += f'({l}{self.binary_ops[key]}{r})' + elif key in self.compound_ops: + x = [] + if type(val) is list: + for v in val: + x.append(expr(self, v).sql) + self.sql = self.compound_ops[key][1](x) + elif key in self.unary_ops: + self.sql += f'{self.unary_ops[key]}({expr(self, val).sql})' + else: + print(f'Undefined expr: {key}{val}') + + if key in self.coumpound_generating_ops and not self.is_compound: + self.is_compound = True + p = self.parent + while type(p) is expr and not p.is_compound: + p.is_compound = True + p = p.parent + + elif type(node) is str: + p = self.parent + while type(p) is expr and not p.isvector: + p.isvector = True + p = p.parent + + self.raw_col = self.datasource.parse_col_names(node) + self.raw_col = self.raw_col if type(self.raw_col) is ColRef else None + if self.raw_col is not None: + self.sql = self.raw_col.name + else: + self.sql = node + + elif type(node) is bool: + self.sql = '1' if node else '0' + else: + self.sql = f'{node}' + + def __str__(self): + return self.sql + def __repr__(self): + return self.__str__() + \ No newline at end of file diff --git a/reconstruct/storage.py b/reconstruct/storage.py index 37d6ccd..44ed001 100644 --- a/reconstruct/storage.py +++ b/reconstruct/storage.py @@ -1,89 +1,89 @@ -class ColRef: - def __init__(self, _ty, cobj, table:'TableInfo', name, id, compound = False): - self.type = _ty - self.cobj = cobj - self.table = table - self.name = name - self.alias = set() - self.id = id # position in table - self.compound = compound # compound field (list as a field) - # e.g. order by, group by, filter by expressions - - self.__arr__ = (_ty, cobj, table, name, id) - def __getitem__(self, key): - if type(key) is str: - return getattr(self, key) - else: - return self.__arr__[key] - - def __setitem__(self, key, value): - self.__arr__[key] = value - - -class TableInfo: - def __init__(self, table_name, cols, cxt:'Context'): - # statics - self.table_name = table_name - self.alias = set([table_name]) - self.columns_byname = dict() # column_name, type - self.columns = [] - self.cxt = cxt - # keep track of temp vars - self.rec = None - self.add_cols(cols) - # runtime - self.order = [] # assumptions - - cxt.tables_byname[self.table_name] = self # construct reverse map - - def add_cols(self, cols, new = True): - for i, c in enumerate(cols): - self.add_col(c, new, i) - def add_col(self, c, new = True, i = 0): - _ty = c['type'] - if new: - _ty = _ty if type(c) is ColRef else list(_ty.keys())[0] - col_object = ColRef(_ty, c, self, c['name'], len(self.columns)) - else: - col_object = c - c.table = self - self.columns_byname[c['name']] = col_object - self.columns.append(col_object) - - def add_alias(self, alias): - if alias in self.cxt.tables_byname.keys(): - print("Error: table alias already exists") - return - self.cxt.tables_byname[alias] = self - self.alias.add(alias) - def parse_col_names(self, colExpr) -> ColRef: - parsedColExpr = colExpr.split('.') - if len(parsedColExpr) <= 1: - return self.columns_byname[colExpr] - else: - datasource = self.cxt.tables_byname[parsedColExpr[0]] - if datasource is None: - raise ValueError(f'Table name/alias not defined{parsedColExpr[0]}') - else: - return datasource.parse_col_names(parsedColExpr[1]) - - -class Context: - def __init__(self): - self.sql = '' - self.tables_byname = dict() - self.col_byname = dict() - self.tables = [] - self.cols = [] - self.datasource = None - self.udf_map = dict() - - def emit(self, sql:str): - self.sql += sql + ' ' - - def add_table(self, table_name, cols): - tbl = TableInfo(table_name, cols, self) - self.tables.append(tbl) - return tbl - +class ColRef: + def __init__(self, _ty, cobj, table:'TableInfo', name, id, compound = False): + self.type = _ty + self.cobj = cobj + self.table = table + self.name = name + self.alias = set() + self.id = id # position in table + self.compound = compound # compound field (list as a field) + # e.g. order by, group by, filter by expressions + + self.__arr__ = (_ty, cobj, table, name, id) + def __getitem__(self, key): + if type(key) is str: + return getattr(self, key) + else: + return self.__arr__[key] + + def __setitem__(self, key, value): + self.__arr__[key] = value + + +class TableInfo: + def __init__(self, table_name, cols, cxt:'Context'): + # statics + self.table_name = table_name + self.alias = set([table_name]) + self.columns_byname = dict() # column_name, type + self.columns = [] + self.cxt = cxt + # keep track of temp vars + self.rec = None + self.add_cols(cols) + # runtime + self.order = [] # assumptions + + cxt.tables_byname[self.table_name] = self # construct reverse map + + def add_cols(self, cols, new = True): + for i, c in enumerate(cols): + self.add_col(c, new, i) + def add_col(self, c, new = True, i = 0): + _ty = c['type'] + if new: + _ty = _ty if type(c) is ColRef else list(_ty.keys())[0] + col_object = ColRef(_ty, c, self, c['name'], len(self.columns)) + else: + col_object = c + c.table = self + self.columns_byname[c['name']] = col_object + self.columns.append(col_object) + + def add_alias(self, alias): + if alias in self.cxt.tables_byname.keys(): + print("Error: table alias already exists") + return + self.cxt.tables_byname[alias] = self + self.alias.add(alias) + def parse_col_names(self, colExpr) -> ColRef: + parsedColExpr = colExpr.split('.') + if len(parsedColExpr) <= 1: + return self.columns_byname[colExpr] + else: + datasource = self.cxt.tables_byname[parsedColExpr[0]] + if datasource is None: + raise ValueError(f'Table name/alias not defined{parsedColExpr[0]}') + else: + return datasource.parse_col_names(parsedColExpr[1]) + + +class Context: + def __init__(self): + self.sql = '' + self.tables_byname = dict() + self.col_byname = dict() + self.tables = [] + self.cols = [] + self.datasource = None + self.udf_map = dict() + + def emit(self, sql:str): + self.sql += sql + ' ' + + def add_table(self, table_name, cols): + tbl = TableInfo(table_name, cols, self) + self.tables.append(tbl) + return tbl + \ No newline at end of file diff --git a/server.exe b/server.exe new file mode 100644 index 0000000000000000000000000000000000000000..8658fd983cbd8a2980f879e73433943bceb1e355 GIT binary patch literal 181760 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 tables; std::unordered_map cols; + + Config* cfg; + + int n_buffers, *sz_bufs; + void **buffers; + Log_level log_level = LOG_SILENT; printf_type print = printf; template @@ -28,8 +39,8 @@ struct Context{ } }; -#ifdef _MSC_VER -#define __DLLEXPORT__ __declspec(dllexport) __stdcall +#ifdef _WIN32 +#define __DLLEXPORT__ __declspec(dllexport) __stdcall #else #define __DLLEXPORT__ #endif diff --git a/server/server.cpp b/server/server.cpp index 8f23e66..350035c 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -36,13 +36,43 @@ void daemon(thread_context* c) { #include "aggregations.h" typedef int (*code_snippet)(void*); int _main(); -int main(int argc, char** argv) { + +int dll_main(int argc, char** argv, Context* cxt){ + Config *cfg = reinterpret_cast(argv[0]); + + auto buf_szs = cfg->buffer_sizes; + void** buffers = (void**)malloc(sizeof(void*) * cfg->n_buffers); + for (int i = 0; i < cfg->n_buffers; i++) + buffers[i] = static_cast(argv[i + 1]); + + cxt->buffers = buffers; + cxt->cfg = cfg; + cxt->n_buffers = cfg->n_buffers; + cxt->sz_bufs = buf_szs; + + while(cfg->running){ + if (cfg->new_query) { + void* handle = dlopen("d:/gg/Aquery++/dll.so", RTLD_LAZY); + code_snippet c = reinterpret_cast(dlsym(handle, "dllmain")); + c(cxt); + dlclose(handle); + cfg->new_query = 0; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return 0; +} + +extern "C" int __DLLEXPORT__ main(int argc, char** argv) { Context* cxt = new Context(); cxt->log("%d %s\n", argc, argv[1]); const char* shmname; - if (argc <= 1) - return _main(); + if (argc < 0) + return dll_main(argc, argv, cxt); + else if (argc <= 1) + return test_main(); else shmname = argv[1]; SharedMemory shm = SharedMemory(shmname); @@ -68,7 +98,6 @@ int main(int argc, char** argv) { cxt->log("inner\n"); cxt->err("return: %d\n", c(cxt)); } - dlclose(handle); } ready = false; } @@ -77,7 +106,7 @@ int main(int argc, char** argv) { return 0; } #include "utils.h" -int _main() +int test_main() { //vector_type t; //t = 1;