make ext_engine: duckdb to work

This commit is contained in:
2023-03-24 04:57:25 +08:00
parent 200dc71aad
commit 7c5440c4fb
29 changed files with 237 additions and 124 deletions
+11
View File
@@ -0,0 +1,11 @@
# TODO:
## 1. double scans in projections
- first for special aggrigations and singular columns
- Then in group by node decide if we have special group by aggregations
- If sp_gb_agg exists, the entire groupby aggregation is done in C plugin
- If not, group by is done in SQL
## 2. ColRef supports multiple objects
- A.a = B.b then in projection A.a B.b will refer to same projection
- Colref::ProjEq(ColRef v) => this == v or v in this.proj_eqs
+37
View File
@@ -0,0 +1,37 @@
from engine.ast import Context, ast_node
saved_cxt = None
def initialize(cxt = None, keep = False):
global saved_cxt
if cxt is None or not keep or type(cxt) is not Context:
if saved_cxt is None or not keep:
cxt = Context()
saved_cxt = cxt
else:
cxt = saved_cxt
cxt.new()
return cxt
def generate(ast, cxt):
for k in ast.keys():
if k in ast_node.types.keys():
ast_node.types[k](None, ast, cxt)
def exec(stmts, cxt = None, keep = False, parser = None):
if 'stmts' not in stmts:
return
cxt = initialize(cxt, keep)
cxt.parser = parser
stmts_stmts = stmts['stmts']
if type(stmts_stmts) is list:
for s in stmts_stmts:
generate(s, cxt)
else:
generate(stmts_stmts, cxt)
for q in cxt.queries:
if not q.startswith('O'):
cxt.print(q.strip())
return cxt
__all__ = ["initialize", "generate", "exec", "saved_cxt"]
+1747
View File
File diff suppressed because it is too large Load Diff
+484
View File
@@ -0,0 +1,484 @@
from typing import Optional, Set
from common.types import *
from engine.ast import ast_node
from engine.storage import ColRef, Context
# TODO: Decouple expr and upgrade architecture
# C_CODE : get ccode/sql code?
# projections : C/SQL/decltype string
# orderby/joins/where : SQL only
# assumption/groupby : C/sql
# is_udfexpr: C only
class expr(ast_node):
name='expr'
valid_joincond = {
0 : ('and', 'eq', 'not'),
1 : ('or', 'neq', 'not'),
2 : ('', '', '')
}
@property
def udf_decltypecall(self):
return self._udf_decltypecall if self._udf_decltypecall else self.sql
@udf_decltypecall.setter
def udf_decltypecall(self, val):
self._udf_decltypecall = val
@property
def need_decltypestr(self):
return self._udf_decltypecall is not None
def __init__(self, parent, node, *, c_code = None, supress_undefined = False):
from engine.ast import projection, udf
# gen2 expr have multi-passes
# first pass parse json into expr tree
# generate target code in later passes upon need
self.children = []
self.opname = ''
self.curr_code = ''
self.counts = {}
self.type = None
self.raw_col = None
self.udf : Optional[udf] = None
self.inside_agg = False
self.is_special = False
self.is_ColExpr = False
self.is_recursive_call_inudf = False
self.codlets : list = []
self.codebuf : Optional[str] = None
self._udf_decltypecall = None
self.node = node
self.supress_undefined = supress_undefined
if(type(parent) is expr):
self.next_valid = parent.next_valid
self.inside_agg = parent.inside_agg
self.is_udfexpr = parent.is_udfexpr
self.is_agg_func = parent.is_agg_func
self.root : expr = parent.root
self.c_code = parent.c_code
self.builtin_vars = parent.builtin_vars
else:
self.join_conditions = []
self.next_valid = 0
self.is_agg_func = False
self.is_udfexpr = type(parent) is udf
self.root : expr = self
self.c_code = self.is_udfexpr or type(parent) is projection
if self.is_udfexpr:
self.udf : udf = parent
self.builtin_vars = self.udf.builtin.keys()
else:
self.builtin_vars = []
if type(c_code) is bool:
self.c_code = c_code
self.udf_called = None
self.cols_mentioned : Optional[set[ColRef]] = None
ast_node.__init__(self, parent, node, None)
def init(self, _):
from engine.ast import _tmp_join_union, projection
parent = self.parent
self.is_compound = parent.is_compound if type(parent) is expr else False
if type(parent) in [projection, expr, _tmp_join_union]:
self.datasource = parent.datasource
else:
self.datasource = self.context.datasource
self.udf_map = parent.context.udf_map
self.func_maps = {**builtin_func, **self.udf_map, **user_module_func}
self.operators = {**builtin_operators, **self.udf_map, **user_module_func}
self.ext_aggfuncs = ['sum', 'avg', 'count', 'min', 'max',
'last', 'first', 'prev', 'next', 'var',
'stddev']
def produce(self, node):
from common.utils import enlist
from engine.ast import udf, projection
if type(node) is dict:
if 'literal' in node:
node = node['literal']
else:
if len(node) > 1:
print(f'Parser Error: {node} has more than 1 dict entry.')
is_joincond = False
for key, val in node.items():
key = key.lower()
if key not in self.valid_joincond[self.next_valid]:
self.next_valid = 2
else:
if key == self.valid_joincond[self.next_valid][2]:
self.next_valid = not self.next_valid
elif key == self.valid_joincond[self.next_valid][1]:
self.next_valid = 2
is_joincond = True
if key in self.operators:
if key in builtin_func:
if self.is_agg_func:
self.root.is_special = True # Nested Aggregation
else:
self.is_agg_func = True
op = self.operators[key]
count_distinct = False
if key == 'count' and type(val) is dict and 'distinct' in val:
count_distinct = True
val = val['distinct']
val = enlist(val)
exp_vals = []
for v in val:
if (
type(v) is str and
'*' in v and
key != 'count'
):
cols = self.datasource.get_cols(v)
if cols:
for c in cols:
exp_vals.append(expr(self, c.name, c_code=self.c_code))
else:
exp_vals.append(expr(self, v, c_code=self.c_code))
self.children = exp_vals
self.opname = key
str_vals = [e.sql for e in exp_vals]
type_vals = [e.type for e in exp_vals]
is_compound = max([e.is_compound for e in exp_vals])
if key in self.ext_aggfuncs:
self.is_compound = max(0, is_compound - 1)
else:
self.is_compound = is_compound
try:
self.type = op.return_type(*type_vals)
except AttributeError as e:
if type(self.root.parent) is not udf:
# TODO: do something when this is not an error
print(f'alert: {e}')
pass
self.type = AnyT
if count_distinct: # inject distinct col later
self.sql = f'{{{op(self.c_code, *str_vals, True)}}}'
else:
self.sql = op(self.c_code, *str_vals)
special_func = [*self.context.udf_map.keys(), *self.context.module_map.keys(),
"maxs", "mins", "avgs", "sums", "deltas", "last", "first",
"stddevs", "vars", "ratios", "pack", "truncate"]
if (
self.context.special_gb
or
(
type(self.root.parent) is projection
and
self.root.parent.force_use_spgb
)
):
special_func = [*special_func, *self.ext_aggfuncs]
if key in special_func and not self.is_special:
self.is_special = True
if key in self.context.udf_map:
self.root.udf_called = self.context.udf_map[key]
if self.is_udfexpr and key == self.root.udf.name:
self.root.is_recursive_call_inudf = True
elif key in user_module_func.keys():
udf.try_init_udf(self.context)
# TODO: make udf_called a set!
p = self.parent
while type(p) is expr and not p.udf_called:
p.udf_called = self.udf_called
p = p.parent
p = self.parent
while type(p) is expr and not p.is_special:
p.is_special = True
p = p.parent
need_decltypestr = any([e.need_decltypestr for e in exp_vals])
if need_decltypestr or (self.udf_called and type(op) is udf):
decltypestr_vals = [e.udf_decltypecall for e in exp_vals]
self.udf_decltypecall = op(self.c_code, *decltypestr_vals)
if self.udf_called and type(op) is udf:
self.udf_decltypecall = op.decltypecall(self.c_code, *decltypestr_vals)
elif self.is_udfexpr:
var_table = self.root.udf.var_table
vec = key.split('.')
_vars = [*var_table, *self.builtin_vars]
def get_vname (node):
if node in self.builtin_vars:
self.root.udf.builtin[node].enabled = True
self.builtin_var = node
return node
else:
return var_table[node]
if vec[0] not in _vars:
# print(f'Use of undefined variable {vec[0]}')
# TODO: do something when this is not an error
pass
else:
vname = get_vname(vec[0])
val = enlist(val)
if(len(val) > 2):
print('Warning: more than 2 indexes found for subvec operator.')
ex = [expr(self, v, c_code = self.c_code) for v in val]
idxs = ', '.join([e.sql for e in ex])
self.sql = f'{vname}.subvec({idxs})'
if any([e.need_decltypestr for e in ex]):
self.udf_decltypecall = f'{vname}.subvec({[", ".join([e.udf_decltypecall for e in ex])]})'
if key == 'get' and len(val) > 1:
ex_vname = expr(self, val[0], c_code=self.c_code)
self.sql = f'{ex_vname.sql}[{expr(self, val[1], c_code=self.c_code).sql}]'
if hasattr(ex_vname, 'builtin_var'):
if not hasattr(self, 'builtin_var'):
self.builtin_var = []
self.builtin_var = [*self.builtin_var, *ex_vname.builtin_var]
self.udf_decltypecall = ex_vname.sql
else:
print(f'Undefined expr: {key}{val}')
if (is_joincond and len(self.children) == 2
and all([c.is_ColExpr for c in self.children])) :
self.root.join_conditions.append(
(self.children[0].raw_col, self.children[1].raw_col)
)
if type(node) is str:
if self.is_udfexpr:
curr_udf : udf = self.root.udf
var_table = curr_udf.var_table
split = node.split('.')
if split[0] in var_table:
varname = var_table[split[0]]
if curr_udf.agg and varname in curr_udf.vecs:
if len(split) > 1:
if split[1] == 'vec':
self.sql += varname
elif split[1] == 'len':
self.sql += f'{varname}.size'
else:
print(f'no member {split[1]} in object {varname}')
else:
self.sql += f'{varname}[{curr_udf.idx_var}]'
else:
self.sql += varname
elif self.supress_undefined or split[0] in self.builtin_vars:
self.sql += node
if split[0] in self.builtin_vars:
curr_udf.builtin[split[0]].enabled = True
self.builtin_var = split[0]
else:
print(f'Undefined varname: {split[0]}')
# get the column from the datasource in SQL context
else:
if self.datasource is not None:
if (node == '*' and
not (type(self.parent) is expr
and 'count' in self.parent.node)):
self.datasource.all_cols(ordered = True)
else:
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.is_ColExpr = True
table_name = ''
if '.' in node:
table_name = self.raw_col.table.table_name
if self.raw_col.table.alias:
alias = iter(self.raw_col.table.alias)
try:
a = next(alias)
while(not a or a == table_name):
a = next(alias)
if (a and a != table_name):
table_name = a
except StopIteration:
pass
if table_name:
table_name = table_name + '.'
self.sql = table_name + self.raw_col.name
self.type = self.raw_col.type
self.is_compound = True
self.is_compound += self.raw_col.compound
self.opname = self.raw_col
else:
self.sql = '\'' + node + '\'' if node != '*' else '*'
self.type = StrT
self.opname = self.sql
if self.c_code and self.datasource is not None:
if (type(self.parent) is expr and
'distinct' in self.parent.node and
not self.is_special):
# this node is executed by monetdb
# gb condition, not special
self.sql = f'distinct({self.sql})'
self.sql = f'{{y(\"{self.sql}\")}}'
elif type(node) is bool:
self.type = BoolT
self.opname = node
if self.c_code:
self.sql = '1' if node else '0'
else:
self.sql = 'TRUE' if node else 'FALSE'
elif type(node) is not dict:
self.sql = f'{node}'
self.opname = node
if type(node) is int:
if (node >= 2**63 - 1 or node <= -2**63):
self.type = HgeT
elif (node >= 2**31 - 1 or node <= -2**31):
self.type = LongT
elif node >= 2**15 - 1 or node <= -2**15:
self.type = IntT
elif node >= 2**7 - 1 or node <= -2**7:
self.type = ShortT
else:
self.type = ByteT
elif type(node) is float:
self.type = DoubleT
self.sql = f'{{"CAST({node} AS DOUBLE)" if not c_code else "{node}f"}}'
def finalize(self, override = False):
from engine.ast import udf
if self.codebuf is None or override:
self.codebuf = ''
for c in self.codlets:
if type(c) is str:
self.codebuf += c
elif type(c) is udf:
self.codebuf += c()
elif type(c) is expr:
self.codebuf += c.finalize(override=override)
return self.codebuf
def codegen(self, delegate):
self.curr_code = ''
for c in self.children:
self.curr_code += c.codegen(delegate)
return self.curr_code
def remake_binary(self, ret_expr):
if self.root:
self.oldsql = self.sql
if (self.opname in builtin_binary_ops):
patched_opname = 'aqop_' + self.opname
self.sql = (f'{patched_opname}({self.children[0].sql}, '
f'{self.children[1].sql}, {ret_expr})')
return True
elif self.opname in builtin_vecfunc:
self.sql = self.sql[:self.sql.rindex(')')]
self.sql += ', ' + ret_expr + ')'
return True
return False
def __str__(self):
return self.sql
def __repr__(self):
return self.__str__()
# builtins is readonly, so it's okay to set default value as an object
# eval is only called at root expr.
def eval(self, c_code = None, y = lambda t: t,
materialize_builtin = False, _decltypestr = False,
count = lambda : 'count', var_inject = None,
*,
gettype = False):
assert(self.is_root)
def call(decltypestr = False) -> str:
nonlocal c_code, y, materialize_builtin, count, var_inject
if var_inject:
for k, v in var_inject.items():
locals()[k] = v
if self.udf_called is not None:
loc = locals()
builtin_vars = self.udf_called.builtin_used
for b in self.udf_called.builtin_var.all:
exec(f'loc["{b}"] = lambda: "{{{b}()}}"')
if builtin_vars:
if type(materialize_builtin) is dict:
for b in builtin_vars:
exec(f'loc["{b}"] = lambda: "{materialize_builtin[b]}"')
elif self.is_recursive_call_inudf:
for b in builtin_vars:
exec(f'loc["{b}"] = lambda : "{b}"')
x = self.c_code if c_code is None else c_code
from common.utils import escape_qoutes
if decltypestr:
return eval('f\'' + escape_qoutes(self.udf_decltypecall) + '\'')
self.sql.replace("'", "\\'")
return eval('f\'' + escape_qoutes(self.sql) + '\'')
if self.is_recursive_call_inudf or (self.need_decltypestr and self.is_udfexpr) or gettype:
return call
else:
return call(_decltypestr)
@property
def is_root(self):
return self.root == self
# For UDFs: first check if agg variable is used as vector
# if not, then check if its length is used
class fastscan(expr):
name = 'fastscan'
def init(self, _):
self.vec_vars = set()
self.requested_lens = set()
super().init(self, _)
def process(self, key : str):
segs = key.split('.')
var_table = self.root.udf.var_table
if segs[0] in var_table and len(segs) > 1:
if segs[1] == 'vec':
self.vec_vars.add(segs[0])
elif segs[1] == 'len':
self.requested_lens.add(segs[0])
def produce(self, node):
from common.utils import enlist
if type(node) is dict:
for key, val in node.items():
if key in self.operators:
val = enlist(val)
elif self.is_udfexpr:
self.process(key)
[fastscan(self, v, c_code = self.c_code) for v in val]
elif type(node) is str:
self.process(node)
class getrefs(expr):
name = 'getrefs'
def init(self, _):
self.datasource.rec = set()
self.rec = None
def produce(self, node):
from common.utils import enlist
if type(node) is dict:
for key, val in node.items():
if key in self.operators:
val = enlist(val)
[getrefs(self, v, c_code = self.c_code) for v in val]
elif type(node) is str:
self.datasource.parse_col_names(node)
def consume(self, _):
if self.root == self:
self.rec = self.datasource.rec
self.datasource.rec = None
+114
View File
@@ -0,0 +1,114 @@
import abc
from engine.ast import ast_node
from typing import Optional
from engine.storage import Context, ColRef
from common.utils import enlist
from common.types import builtin_func, user_module_func, builtin_operators
class expr_base(ast_node, metaclass = abc.ABCMeta):
def __init__(self, parent: Optional["ast_node"], node, context: Optional[Context] = None):
self.node = node
super().__init__(parent, node, context)
def init(self, node):
self.is_literal = False
self.udf_map = self.context.udf_map
self.func_maps = {**builtin_func, **self.udf_map, **user_module_func}
self.operators = {**builtin_operators, **self.udf_map, **user_module_func}
self.narrow_funcs = ['sum', 'avg', 'count', 'min', 'max', 'last', 'first']
def get_variable(self):
pass
def str_literal(self, node):
pass
def int_literal(self, node):
pass
def bool_literal(self, node):
pass
def get_literal(self, node):
if not self.get_variable(node):
self.is_literal = True
if type(node) is str:
self.str_literal(node)
elif type(node) is int:
self.int_literal(node)
elif type(node) is float:
self.float_literal(node)
elif type(node) is bool:
self.bool_literal(node)
def process_child_nodes(self):
if not hasattr(self, 'child_exprs'):
raise ValueError(f'Internal Error: process_child_nodes called without child_exprs.')
def process_non_operator(self, key, value):
pass
def produce(self, node):
from engine.ast import udf
if node and type(node) is dict:
if 'litral' in node:
self.get_literal(node['literal'])
else:
if len(node) > 1:
raise ValueError(f'Parse Error: more than 1 entry in {node}.')
key, val = next(iter(node.items()))
if key in self.operators:
self.child_exprs = [self.__class__(self, v) for v in val]
self.process_child_nodes()
else:
self.process_non_operator(key, val)
else:
self.get_literal(node['literal'])
def consume(self, _):
pass
class c_expr(expr_base):
def init(self, node):
super().init(node)
self.ccode = ''
def emit(self, snippet : str):
self.ccode += snippet
def eval(self):
return self.ccode
class sql_expr(expr_base):
def init(self):
super().init()
self.sql = ''
def emit(self, snippet):
self.sql += snippet
def eval(self):
return self.sql
class udf_expr(c_expr):
pass
class proj_expr(c_expr, sql_expr):
def init(self, node):
super(c_expr).init()
super(sql_expr).init()
class orderby_expr(c_expr, sql_expr):
def init(self, node):
super(c_expr).init()
super(sql_expr).init()
class groupby_expr(orderby_expr):
pass
class from_expr(sql_expr):
pass
class where_expr(sql_expr):
pass
+324
View File
@@ -0,0 +1,324 @@
from typing import Dict, List, Optional, Set
from common.types import *
from common.utils import CaseInsensitiveDict, base62uuid, enlist
class ColRef:
def __init__(self, _ty, cobj, table:'TableInfo', name, id, compound = False, _ty_args = None):
self.type : Types = AnyT
if type(_ty) is str:
self.type = Types.decode(_ty)
if _ty_args:
self.type = self.type(enlist(_ty_args))
elif type(_ty) is Types:
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)
self.cxt_name = ''
# e.g. order by, group by, filter by expressions
self.__arr__ = (_ty, cobj, table, name, id)
def get_name(self):
it_alias = iter(self.alias)
alias = next(it_alias, self.name)
try:
while alias == self.name:
alias = next(it_alias)
except StopIteration:
alias = self.name
return alias
def get_full_name(self):
table_name = self.table.table_name
it_alias = iter(self.table.alias)
alias = next(it_alias, table_name)
try:
while alias == table_name:
alias = next(it_alias)
except StopIteration:
alias = table_name
return f'{alias}.{self.get_name()}'
def rename(self, name):
self.alias.discard(self.name)
self.table.columns_byname.pop(self.name, None)
self.name = name
self.table.columns_byname[name] = self
return self
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'):
from engine.ast import create_trigger
# statics
self.table_name : str = table_name
self.contextname_cpp : str = ''
self.alias : Set[str] = set([table_name])
self.columns_byname : CaseInsensitiveDict[str, ColRef] = CaseInsensitiveDict() # column_name, type
self.columns : List[ColRef] = []
self.triggers : Set[create_trigger] = set()
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
cxt.tables.add(self)
def add_cols(self, cols, new = True):
for c in enlist(cols):
self.add_col(c, new)
def add_col(self, c, new = True):
_ty = c['type']
_ty_args = None
if type(_ty) is dict:
_ty_val = list(_ty.keys())[0]
_ty_args = _ty[_ty_val]
_ty = _ty_val
if new or type(c) is not ColRef:
col_object = ColRef(_ty, c, self, c['name'], len(self.columns), _ty_args = _ty_args)
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 rename(self, name):
if name in self.cxt.tables_byname.keys():
print(f"Error: table name {name} already exists")
return
self.cxt.tables_byname.pop(self.table_name, None)
self.alias.discard(self.table_name)
self.table_name = name
self.cxt.tables_byname[name] = self
self.alias.add(name)
def parse_col_names(self, colExpr) -> ColRef:
parsedColExpr = colExpr.split('.')
if len(parsedColExpr) <= 1:
col = self.columns_byname[colExpr]
if type(self.rec) is set:
self.rec.add(col)
return col
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])
def all_cols(self, ordered = False):
from ordered_set import OrderedSet
_ret_set_t = OrderedSet if ordered else set
if type(self.rec) is set:
self.rec.update(self.columns)
return _ret_set_t(self.columns)
@property
def single_table(self):
return True
class Context:
def new(self):
self.headers = set(['\"./server/monetdb_conn.h\"'])
self.ccode = ''
self.sql = ''
self.finalized = False
self.udf = None
self.module_stubs = ''
self.scans = []
self.procs = []
self.queries = []
self.module_init_loc = 0
self.special_gb = False
self.has_dll = False
self.triggers_active.clear()
def __init__(self, state = None):
from prompt import PromptState
from .ast import create_trigger
from aquery_config import compile_use_gc
self.tables_byname : Dict[str, TableInfo] = dict()
self.col_byname = dict()
self.tables : Set[TableInfo] = set()
self.cols = []
self.datasource = None
self.module_map = {}
self.udf_map = dict()
self.udf_agg_map = dict()
self.use_columnstore = False
self.print = print
self.dialect = 'MonetDB'
self.is_msvc = False
self.have_hge = False
self.Error = lambda *args: print(*args)
self.Info = lambda *_: None
self.triggers : Dict[str, create_trigger] = dict()
self.triggers_active = set()
self.stored_proceudres = dict()
self.force_compiled = False
self.use_gc = compile_use_gc
self.system_state: Optional[PromptState] = state
# self.new() called everytime new query batch is started
def get_scan_var(self):
it_var = 'i' + base62uuid(2)
scan_vars = set(s.it_var for s in self.scans)
while(it_var in scan_vars):
it_var = 'i' + base62uuid(6)
return it_var
def emit(self, sql:str):
self.sql += sql + ' '
def emitc(self, c:str):
self.ccode += c + '\n'
def add_table(self, table_name, cols):
tbl = TableInfo(table_name, cols, self)
self.tables.add(tbl)
return tbl
def remove_scan(self, scan, str_scan):
self.emitc(str_scan)
self.scans.remove(scan)
function_deco = '__AQEXPORT__(int) '
function_head = ('(Context* cxt) {\n' +
'\tusing namespace std;\n' +
'\tusing namespace types;\n' +
'\tauto server = static_cast<DataSource*>(cxt->curr_server);\n')
udf_head = ('#pragma once\n'
'#include \"./server/libaquery.h\"\n'
'#include \"./server/aggregations.h\"\n\n'
)
def get_init_func(self):
if not self.module_map:
return ''
ret = '__AQEXPORT__(void) __builtin_init_user_module(Context* cxt){\n'
for fname in self.module_map.keys():
ret += f'{fname} = (decltype({fname}))(cxt->get_module_function("{fname}"));\n'
self.queries.insert(self.module_init_loc, 'P__builtin_init_user_module')
return ret + '}\n'
def finalize_query(self):
# clear aliases
for t in self.tables:
for a in t.alias:
if a != t.table_name:
self.tables_byname.pop(a, None)
t.alias.clear()
t.alias.add(t.table_name)
def sql_begin(self):
self.sql = ''
def sql_end(self):
# eliminate empty queries
s = self.sql.strip()
while(s and s[-1] == ';'):
s = s[:-1].strip()
if s and s.lower() != 'select':
self.queries.append('Q' + self.sql)
self.sql = ''
def postproc_begin(self, proc_name: str):
self.ccode = self.function_deco + proc_name + self.function_head
def postproc_end(self, proc_name: str):
self.procs.append(self.ccode + 'return 0;\n}')
self.ccode = ''
self.queries.append('P' + proc_name)
self.finalize_query()
def abandon_query(self):
self.sql = ''
self.ccode = ''
self.finalize_query()
def direct_output(self, limit = -1, sep = ' ', end = '\n'):
if type(limit) is not int or limit > 2**32 - 1 or limit < 0:
limit = 2**32 - 1
limit = limit.to_bytes(4, 'little').decode('latin-1')
self.queries.append(
'O' + limit + sep + end)
def remove_trigger(self, name : str):
from engine.ast import create_trigger
val = self.triggers.pop(name, None)
if val.type == create_trigger.Type.Callback:
val.table.triggers.remove(val)
val.remove()
def post_exec_triggers(self):
for t in self.triggers_active:
t.execute()
self.triggers_active.clear()
def abandon_postproc(self):
self.ccode = ''
self.finalize_query()
def finalize_udf(self):
if self.udf:
self.udf += '\n'.join([
u.ccode for u in self.udf_map.values()
])
self.module_stubs = '\n'.join(
[m for m in self.module_map.values()
])
return (Context.udf_head
+ self.module_stubs
+ self.get_init_func()
+ self.udf
)
else:
return None
def finalize(self):
from aquery_config import build_driver, os_platform
if not self.finalized:
headers = ''
# if build_driver == 'MSBuild':
# headers ='#include \"./server/pch.hpp\"\n'
with open('header.cxx', 'r') as header:
headers += header.read()
for h in self.headers:
if h[0] != '"':
headers += '#include <' + h + '>\n'
else:
headers += '#include ' + h + '\n'
if os_platform == 'win':
headers += '#undef max\n'
headers += '#undef min\n'
self.ccode += headers + '\n'.join(self.procs)
self.headers = set()
return self.ccode