decimal operator test

This commit is contained in:
wangjiaming0909 2025-02-23 17:30:09 +08:00
parent 1fad80e631
commit e42d0e118c
4 changed files with 273 additions and 90 deletions

View File

@ -122,7 +122,7 @@ SSdbRaw *mndStbActionEncode(SStbObj *pStb) {
int32_t size = sizeof(SStbObj) + (pStb->numOfColumns + pStb->numOfTags) * sizeof(SSchema) + pStb->commentLen +
pStb->ast1Len + pStb->ast2Len + pStb->numOfColumns * sizeof(SColCmpr) + STB_RESERVE_SIZE +
taosArrayGetSize(pStb->pFuncs) * TSDB_FUNC_NAME_LEN;
taosArrayGetSize(pStb->pFuncs) * TSDB_FUNC_NAME_LEN + sizeof(int32_t) * pStb->numOfColumns;
SSdbRaw *pRaw = sdbAllocRaw(SDB_STB, STB_VER_NUMBER, size);
if (pRaw == NULL) goto _OVER;

View File

@ -1755,7 +1755,7 @@ static int32_t sclGetMathOperatorResType(SOperatorNode *pOp) {
pOp->node.resType.bytes = tDataTypes[TSDB_DATA_TYPE_TIMESTAMP].bytes;
} else {
if (hasDecimalType) {
decimalGetRetType(&ldt, &rdt, pOp->opType, &pOp->node.resType);
return decimalGetRetType(&ldt, &rdt, pOp->opType, &pOp->node.resType);
} else {
pOp->node.resType.type = TSDB_DATA_TYPE_DOUBLE;
pOp->node.resType.bytes = tDataTypes[TSDB_DATA_TYPE_DOUBLE].bytes;

View File

@ -1048,7 +1048,7 @@ int8_t gConvertTypes[TSDB_DATA_TYPE_MAX][TSDB_DATA_TYPE_MAX] = {
/*UINT*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, -1, 17, 0, 0, -1, 17,
/*UBIG*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 17, 0, 0, -1, 17,
/*JSON*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, -1, -1,
/*VARB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 7,
/*VARB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1,
/*DECI*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, -1, -1, 17,
/*BLOB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, -1, -1,
/*MEDB*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, -1, -1,
@ -1251,7 +1251,8 @@ static int32_t vectorConvertVarToDouble(SScalarParam *pInput, int32_t *converted
SColumnInfoData *pCol = pInput->columnData;
int32_t code = TSDB_CODE_SUCCESS;
*pOutputCol = NULL;
if (IS_VAR_DATA_TYPE(pCol->info.type) && pCol->info.type != TSDB_DATA_TYPE_JSON && pCol->info.type != TSDB_DATA_TYPE_VARBINARY) {
bool isVarChar = IS_VAR_DATA_TYPE(pCol->info.type) && pCol->info.type != TSDB_DATA_TYPE_JSON && pCol->info.type != TSDB_DATA_TYPE_VARBINARY;
if (isVarChar || IS_DECIMAL_TYPE(pCol->info.type)) {
SCL_ERR_RET(vectorConvertSingleCol(pInput, &output, TSDB_DATA_TYPE_DOUBLE, 0, -1, -1));
*converted = VECTOR_DO_CONVERT;
*pOutputCol = output.columnData;

View File

@ -1,13 +1,14 @@
from ast import Tuple
from pydoc import doc
from random import randrange
from re import A
import time
import threading
import secrets
from tkinter.tix import COLUMN
from regex import D
from sympy import true
from regex import D, F
from sympy import Dict, true
from torch import is_conj
import query
from tag_lite import column
from util.log import *
@ -23,7 +24,8 @@ syntax_error = -2147473920
invalid_column = -2147473918
invalid_compress_level = -2147483084
invalid_encode_param = -2147483087
invalid_operation = -2147483136
scalar_convert_err = -2147470768
class DecimalTypeGeneratorConfig:
def __init__(self):
@ -71,7 +73,7 @@ class DecimalStringRandomGenerator:
## 写入大整数的例子, 如10000000000, scale解析时可能为负数
## Generate decimal with E/e
def generate_(self, config: DecimalTypeGeneratorConfig) -> str:
def generate(self, config: DecimalTypeGeneratorConfig) -> str:
ret: str = ""
sign = self.generate_sign(config.positive_ratio)
if config.with_corner_case and self.current_should_generate_corner_case(
@ -161,21 +163,76 @@ class TaosShell:
if len(self.queryResult) == 0:
self.queryResult = [[] for i in range(len(vals))]
for val in vals:
self.queryResult[col].append(val)
self.queryResult[col].append(val.strip())
col += 1
def query(self, sql: str):
with open(self.tmp_file_path, "r+") as f:
f.truncate(0)
try:
command = f'taos -s "{sql} >> {self.tmp_file_path}"'
result = subprocess.run(
command, shell=True, check=True, stderr=subprocess.PIPE
)
self.read_result()
except subprocess.CalledProcessError as e:
except Exception as e:
tdLog.exit(f"Command '{sql}' failed with error: {e.stderr.decode('utf-8')}")
self.queryResult = []
return self.queryResult
class DecimalColumnExpr:
def __init__(self, format: str, executor):
self.format_: str = format
self.executor_ = executor
self.params_: Tuple = List
def __str__(self):
return self.format_ % (self.params_)
def execute(self, params):
return self.executor_(params)
def get_val(self, tbname: str, idx: int):
params: Tuple = ()
for p in self.params_:
params = params + (p.get_val(tbname, idx),)
return self.execute(params)
def check(self, query_col_res: List, tbname: str):
for i in range(len(query_col_res)):
v_from_query = query_col_res[i]
params: Tuple = ()
for p in self.params_:
if isinstance(p, Column) or isinstance(p, DecimalColumnExpr):
p = p.get_val(tbname, i)
params = params + (p,)
v_from_calc_in_py = self.execute(params)
if v_from_calc_in_py == 'NULL' or v_from_query == 'NULL':
if v_from_calc_in_py != v_from_query:
tdLog.exit(f"query with expr: {self} calc in py got: {v_from_calc_in_py}, query got: {v_from_query}")
tdLog.debug(f"query with expr: {self} calc got same result: NULL")
continue
if isinstance(v_from_calc_in_py, float):
dec_from_query = float(v_from_query)
dec_from_insert = float(v_from_calc_in_py)
else:
dec_from_query = Decimal(v_from_query)
dec_from_insert = Decimal(v_from_calc_in_py)
if dec_from_query != dec_from_insert:
tdLog.exit(
f"check decimal column failed for expr: {self} params: {params}, query: {v_from_query}, expect {dec_from_insert}, but get {dec_from_query}")
else:
tdLog.debug(
f"check decimal succ for expr: {self}, params: {params}, insert:{v_from_calc_in_py} query:{v_from_query}, py dec: {dec_from_insert}"
)
def generate(self, format_params) -> str:
self.params_ = format_params
return f"({self.format_})".format(*format_params)
class TypeEnum:
BOOL = 1
@ -222,13 +279,13 @@ class TypeEnum:
elif type == TypeEnum.NCHAR:
return "NCHAR"
elif type == TypeEnum.UTINYINT:
return "UTINYINT"
return "TINYINT UNSIGNED"
elif type == TypeEnum.USMALLINT:
return "USMALLINT"
return "SMALLINT UNSIGNED"
elif type == TypeEnum.UINT:
return "UINT"
return "INT UNSIGNED"
elif type == TypeEnum.UBIGINT:
return "UBIGINT"
return "BIGINT UNSIGNED"
elif type == TypeEnum.JSON:
return "JSON"
elif type == TypeEnum.VARBINARY:
@ -239,6 +296,8 @@ class TypeEnum:
return "BINARY"
elif type == TypeEnum.GEOMETRY:
return "GEOMETRY"
elif type == TypeEnum.DECIMAL64:
return "DECIMAL"
else:
raise Exception("unknow type")
@ -309,11 +368,17 @@ class DataType:
return str(secrets.randbelow(9223372036854775808))
if self.type == TypeEnum.JSON:
return f'{{"key": "{secrets.token_urlsafe(10)}"}}'
if self.type == TypeEnum.GEOMETRY:
return "'POINT(1.0 1.0)'"
raise Exception(f"unsupport type {self.type}")
def check(self, values, offset: int):
return True
def get_typed_val(self, val):
if self.type == TypeEnum.FLOAT or self.type == TypeEnum.DOUBLE:
return float(val)
return val
class DecimalType(DataType):
def __init__(self, type, precision: int, scale: int):
@ -324,7 +389,7 @@ class DecimalType(DataType):
else:
bytes = 16
super().__init__(type, bytes, self.get_decimal_type_mod())
self.generator: DecimalStringRandomGenerator = DecimalStringRandomGenerator()
self.decimal_generator: DecimalStringRandomGenerator = DecimalStringRandomGenerator()
self.generator_config: DecimalTypeGeneratorConfig = DecimalTypeGeneratorConfig()
self.generator_config.prec = precision
self.generator_config.scale = scale
@ -356,11 +421,16 @@ class DecimalType(DataType):
return f"DecimalType({self.precision_}, {self.scale()})"
def generate_value(self) -> str:
val = self.generator.generate_(self.generator_config)
self.aggregator.add_value(val)
self.values.append(val) ## save it into files maybe
val = self.decimal_generator.generate(self.generator_config)
self.aggregator.add_value(val) ## convert to Decimal first
# self.values.append(val) ## save it into files maybe
return val
def get_typed_val(self, val):
if val == "NULL":
return None
return Decimal(val).quantize(Decimal("1." + "0" * self.scale()), ROUND_HALF_UP)
@staticmethod
def default_compression() -> str:
return "zstd"
@ -372,30 +442,30 @@ class DecimalType(DataType):
def check(self, values, offset: int):
val_from_query = values
val_insert = self.values[offset:]
for v1, v2 in zip(val_from_query, val_insert):
if v2 == "NULL":
if v1.strip() != "NULL":
for v_from_query, v_from_insert in zip(val_from_query, val_insert):
if v_from_insert == "NULL":
if v_from_query.strip() != "NULL":
tdLog.debug(
f"val_insert: {val_insert} val_from_query: {val_from_query}"
)
tdLog.exit(f"insert NULL, query not NULL: {v1}")
tdLog.exit(f"insert NULL, query not NULL: {v_from_query}")
else:
continue
try:
dec1: Decimal = Decimal(v1)
dec2: Decimal = Decimal(v2)
dec2 = dec2.quantize(Decimal("1." + "0" * self.scale()), ROUND_HALF_UP)
dec_query: Decimal = Decimal(v_from_query)
dec_insert: Decimal = Decimal(v_from_insert)
dec_insert = dec_insert.quantize(Decimal("1." + "0" * self.scale()), ROUND_HALF_UP)
except Exception as e:
tdLog.exit(f"failed to convert {v1} or {v2} to decimal, {e}")
tdLog.exit(f"failed to convert {v_from_query} or {v_from_insert} to decimal, {e}")
return False
if dec1 != dec2:
if dec_query != dec_insert:
tdLog.exit(
f"check decimal column failed for insert: {v2}, query: {v1}, expect {dec2}, but get {dec1}"
f"check decimal column failed for insert: {v_from_insert}, query: {v_from_query}, expect {dec_insert}, but get {dec_query}"
)
return False
else:
tdLog.debug(
f"check decimal succ, insert:{v2} query:{v1}, py dec: {dec2}"
f"check decimal succ, insert:{v_from_insert} query:{v_from_query}, py dec: {dec_insert}"
)
@ -403,9 +473,35 @@ class Column:
def __init__(self, type: DataType):
self.type_: DataType = type
self.name_: str = ""
self.saved_vals:dict[str:[]] = {}
def generate_value(self):
return self.type_.generate_value()
def is_constant_col(self):
return '' in self.saved_vals.keys()
def get_typed_val(self, val):
return self.type_.get_typed_val(val)
def get_constant_val(self):
return self.get_typed_val(self.saved_vals[''][0])
def __str__(self):
if self.is_constant_col():
return self.get_constant_val()
return self.name_
def get_val(self, tbname: str, idx: int):
if self.is_constant_col():
return self.get_constant_val()
return self.get_typed_val(self.saved_vals[tbname][idx])
## tbName: for normal table, pass the tbname, for child table, pass the child table name
def generate_value(self, tbName: str = '', save: bool = True):
val = self.type_.generate_value()
if save:
if tbName not in self.saved_vals:
self.saved_vals[tbName] = []
self.saved_vals[tbName].append(val)
return val
def get_type_str(self) -> str:
return str(self.type_)
@ -422,14 +518,15 @@ class Column:
or self.type_.type == TypeEnum.VARCHAR
or self.type_.type == TypeEnum.NCHAR
or self.type_.type == TypeEnum.VARBINARY
or self.type_.type == TypeEnum.JSON
):
return f"'{val}'"
else:
return val
@staticmethod
def get_all_type_columns() -> List:
other_types = [
def get_all_type_columns(types_to_exclude: List[TypeEnum] = []) -> List:
all_types = [
Column(DataType(TypeEnum.BOOL)),
Column(DataType(TypeEnum.TINYINT)),
Column(DataType(TypeEnum.SMALLINT)),
@ -439,19 +536,23 @@ class Column:
Column(DataType(TypeEnum.DOUBLE)),
Column(DataType(TypeEnum.VARCHAR, 255)),
Column(DataType(TypeEnum.TIMESTAMP)),
Column(DataType(TypeEnum.NCHAR)),
Column(DataType(TypeEnum.NCHAR, 255)),
Column(DataType(TypeEnum.UTINYINT)),
Column(DataType(TypeEnum.USMALLINT)),
Column(DataType(TypeEnum.UINT)),
Column(DataType(TypeEnum.UBIGINT)),
Column(DataType(TypeEnum.JSON)),
Column(DataType(TypeEnum.VARBINARY)),
Column(DataType(TypeEnum.DECIMAL)),
Column(DataType(TypeEnum.BINARY)),
Column(DataType(TypeEnum.GEOMETRY)),
Column(DataType(TypeEnum.DECIMAL64)),
Column(DataType(TypeEnum.VARBINARY, 255)),
Column(DecimalType(TypeEnum.DECIMAL, 38, 10)),
Column(DataType(TypeEnum.BINARY, 255)),
Column(DataType(TypeEnum.GEOMETRY, 10240)),
Column(DecimalType(TypeEnum.DECIMAL64, 18, 4)),
]
return other_types
for c in all_types:
for type in types_to_exclude:
if c.type_.type == type:
all_types.remove(c)
return all_types
class DecimalColumnTableCreater:
@ -521,7 +622,7 @@ class TableInserter:
columns: List[Column],
tags_cols: List[Column] = [],
):
self.conn = conn
self.conn: TDSql = conn
self.dbName = dbName
self.tbName = tbName
self.tag_cols = tags_cols
@ -533,7 +634,7 @@ class TableInserter:
for i in range(rows):
sql += f"({start_ts + i * step}"
for column in self.columns:
sql += f", {column.generate_value()}"
sql += f", {column.generate_value(self.tbName)}"
sql += ")"
if i != rows - 1:
sql += ", "
@ -572,18 +673,6 @@ class TableDataValidator:
colIdx += 1
class DecimalColumnExpr:
def __init__(self, format: str, executor):
self.format_: str = ""
self.executor_ = None
def execute(self, params: List):
return self.executor_(params)
def generate(self, format_params) -> str:
return f"({self.format_ % format_params})"
class DecimalBinaryOperator(DecimalColumnExpr):
def __init__(self, op: str):
super().__init__()
@ -597,7 +686,67 @@ class DecimalBinaryOperator(DecimalColumnExpr):
@staticmethod
def execute_plus(params):
return params[0] + params[1]
if params[0] is None or params[1] is None:
return 'NULL'
if isinstance(params[0], float) or isinstance(params[1], float):
return float(params[0]) + float(params[1])
return Decimal(params[0]) + Decimal(params[1])
@staticmethod
def execute_minus(params):
return params[0] - params[1]
@staticmethod
def execute_mul(params):
return params[0] * params[1]
@staticmethod
def execute_div(params):
return params[0] / params[1]
@staticmethod
def execute_mod(params):
return params[0] % params[1]
@staticmethod
def execute_eq(params):
return params[0] == params[1]
@staticmethod
def execute_ne(params):
return params[0] != params[1]
@staticmethod
def execute_gt(params):
return params[0] > params[1]
@staticmethod
def execute_lt(params):
return params[0] < params[1]
@staticmethod
def execute_ge(params):
return params[0] >= params[1]
@staticmethod
def execute_le(params):
return params[0] <= params[1]
@staticmethod
def get_all_binary_ops() -> List[DecimalColumnExpr]:
return [
DecimalColumnExpr(" {0} + {1} ", DecimalBinaryOperator.execute_plus),
DecimalColumnExpr(" {0} - {1} ", DecimalBinaryOperator.execute_minus),
DecimalColumnExpr(" {0} * {1} ", DecimalBinaryOperator.execute_mul),
DecimalColumnExpr(" {0} / {1} ", DecimalBinaryOperator.execute_div),
DecimalColumnExpr(" {0} % {1} ", DecimalBinaryOperator.execute_mod),
DecimalColumnExpr(" {0} == {1} ", DecimalBinaryOperator.execute_eq),
DecimalColumnExpr(" {0} != {1} ", DecimalBinaryOperator.execute_ne),
DecimalColumnExpr(" {0} > {1} ", DecimalBinaryOperator.execute_gt),
DecimalColumnExpr(" {0} < {1} ", DecimalBinaryOperator.execute_lt),
DecimalColumnExpr(" {0} >= {1} ", DecimalBinaryOperator.execute_ge),
DecimalColumnExpr(" {0} <= {1} ", DecimalBinaryOperator.execute_le),
]
def execute(self, left, right):
if self.op_ == "+":
@ -812,12 +961,12 @@ class TDTestCase:
## TODO add more values for all rows
tag_values = ["1", "t1"]
DecimalColumnTableCreater(
tdSql, self.db_name, self.stable_name, self.norm_tb_columns
tdSql, self.db_name, self.stable_name, self.stb_columns
).create_child_table(
self.c_table_prefix, self.c_table_num, self.tags, tag_values
)
self.check_desc("meters", self.stb_columns, self.tags)
self.check_desc("t1", self.norm_tb_columns, self.tags)
self.check_desc("t1", self.stb_columns, self.tags)
## invalid precision/scale
invalid_precision_scale = [
@ -872,7 +1021,7 @@ class TDTestCase:
f"{self.c_table_prefix}{i}",
self.stb_columns,
self.tags,
).insert(10000, 1537146000000, 500)
).insert(1000, 1537146000000, 500)
for i in range(self.c_table_num):
TableDataValidator(
@ -881,7 +1030,7 @@ class TDTestCase:
TableInserter(
tdSql, self.db_name, self.norm_table_name, self.norm_tb_columns
).insert(100000, 1537146000000, 500, flush_database=True)
).insert(1000, 1537146000000, 500, flush_database=True)
TableDataValidator(
self.norm_tb_columns, self.norm_table_name, self.db_name
).validate()
@ -995,13 +1144,15 @@ class TDTestCase:
for expr in exprs:
for col in tb_cols:
left_is_decimal = col.type_.is_decimal_type()
for const_val in constant_cols:
right_is_decimal = const_val.type_.is_decimal_type()
for const_col in constant_cols:
right_is_decimal = const_col.type_.is_decimal_type()
if not left_is_decimal and not right_is_decimal:
continue
select_expr = expr.generate((col.name_, const_val.generate_value()))
sql = f"select {select_expr} from {dbname}.{tbname}"
res = TaosShell().query(sql)
const_col.generate_value()
select_expr = expr.generate((col, const_col))
sql = f"select {select_expr} from {dbname}.{tbname}"
res = TaosShell().query(sql)
expr.check(res[0], tbname)
## query
## build expr, expr.generate(column) to generate sql expr
## pass this expr into DataValidator.
@ -1011,38 +1162,69 @@ class TDTestCase:
## test others unsupported types operator with decimal
def test_decimal_unsupported_types(self):
unsupported_type_cols = [
Column(DataType(TypeEnum.JSON)),
Column(DataType(TypeEnum.GEOMETRY)),
Column(DataType(TypeEnum.VARBINARY)),
unsupported_type = [
TypeEnum.JSON,
TypeEnum.GEOMETRY,
TypeEnum.VARBINARY,
]
all_type_columns = Column.get_all_type_columns()
all_type_columns: List[Column] = Column.get_all_type_columns([TypeEnum.JSON])
tbname = "test_decimal_unsupported_types"
DecimalColumnTableCreater(
tdSql, self.db_name, tbname, all_type_columns
).create()
tdSql.error(
f"select c17 + c15 from {self.db_name}{tbname}", queryTimes=1, show=True
)
tag_cols = [Column(DataType(TypeEnum.JSON))]
tb_creater = DecimalColumnTableCreater( tdSql, self.db_name, tbname, all_type_columns, tag_cols)
tb_creater.create()
tb_creater.create_child_table(tbname, 10, tag_cols, ['{"k1": "v1"}'])
for i in range(10):
TableInserter(tdSql, self.db_name, f'{tbname}{i}', all_type_columns, tag_cols).insert(100, 1537146000000, 500, flush_database=True)
for col in all_type_columns:
## only test decimal cols
if not col.type_.is_decimal_type():
continue
for unsupported_col in all_type_columns:
## only test unsupported cols
if not unsupported_col.type_.type in unsupported_type:
continue
for binary_op in DecimalBinaryOperator.get_all_binary_ops():
select_expr = binary_op.generate((col.name_, unsupported_col.name_))
sql = f"select {select_expr} from {self.db_name}.{tbname}"
select_expr_reverse = binary_op.generate(
(unsupported_col.name_, col.name_)
)
sql_reverse = (
f"select {select_expr_reverse} from {self.db_name}.{tbname}"
)
tdLog.info(
f"select expr: {select_expr} with type: {col.type_} and {unsupported_col.type_} should err"
)
err = tdSql.error(sql)
if tdSql.errno != invalid_operation and tdSql.errno != scalar_convert_err:
tdLog.exit(f"expected err not occured for sql: {sql}, expect: {invalid_operation} or {scalar_convert_err}, but got {tdSql.errno}")
tdLog.info(
f"select expr: {select_expr} with type: {unsupported_col.type_} and {col.type_} should err"
)
err = tdSql.error(sql_reverse)
if tdSql.errno != invalid_operation and tdSql.errno != scalar_convert_err:
tdLog.exit(f"expected err not occured for sql: {sql}, expect: {invalid_operation} or {scalar_convert_err}, but got {tdSql.errno}")
def test_decimal_operators(self):
tdLog.debug("start to test decimal operators")
self.test_decimal_unsupported_types()
## tables: meters, nt
## columns: c1, c2, c3, c4, c5, c7, c8, c9, c10, c99, c100
binary_operators = [
DecimalColumnExpr("%s + %s", DecimalBinaryOperator.execute_plus),
DecimalColumnExpr("-"),
DecimalColumnExpr("*"),
DecimalColumnExpr("/"),
DecimalColumnExpr("%"),
DecimalColumnExpr(">"),
DecimalColumnExpr("<"),
DecimalColumnExpr(">="),
DecimalColumnExpr("<="),
DecimalColumnExpr("=="),
DecimalColumnExpr("!="),
DecimalBinaryOperatorIn("in"),
DecimalBinaryOperatorIn("not in"),
# DecimalColumnExpr("-"),
# DecimalColumnExpr("*"),
# DecimalColumnExpr("/"),
# DecimalColumnExpr("%"),
# DecimalColumnExpr(">"),
# DecimalColumnExpr("<"),
# DecimalColumnExpr(">="),
# DecimalColumnExpr("<="),
# DecimalColumnExpr("=="),
# DecimalColumnExpr("!="),
# DecimalBinaryOperatorIn("in"),
# DecimalBinaryOperatorIn("not in"),
]
all_type_columns = Column.get_all_type_columns()