sqlglot.executor.python
1import collections 2import itertools 3import math 4 5from sqlglot import exp, planner, tokens 6from sqlglot.dialects.dialect import Dialect 7from sqlglot.errors import ExecuteError 8from sqlglot.executor.context import Context 9from sqlglot.executor.env import ENV 10from sqlglot.executor.table import RowReader, Table 11from sqlglot.generators.python import PythonGenerator 12from sqlglot.optimizer.scope import build_scope 13 14SUBQUERY_NODES = (exp.Subquery, exp.Exists, exp.All, exp.Any) 15 16 17class PythonExecutor: 18 def __init__(self, env=None, tables=None): 19 self.generator = Python().generator(identify=True, comments=False) 20 self.env = {**ENV, **(env or {})} 21 self.tables = tables or {} 22 self._subquery_plans = {} 23 self._plan_names_by_sql = {} 24 self._ctes = None 25 self._outer_scope = None 26 self.env.update( 27 SUBQUERY_COMPARISON=self._subquery_comparison, 28 SUBQUERY_EXISTS=self._subquery_exists, 29 SUBQUERY_SCALAR=self._subquery_scalar, 30 ) 31 32 def execute(self, plan, outer_scope=None): 33 ctes, scope = self._ctes, self._outer_scope 34 self._ctes, self._outer_scope = plan.ctes, outer_scope 35 try: 36 return self._execute(plan) 37 finally: 38 self._ctes, self._outer_scope = ctes, scope 39 40 def _execute(self, plan): 41 finished = set() 42 queue = set(plan.leaves) 43 contexts = {} 44 45 while queue: 46 node = queue.pop() 47 try: 48 context = self.context( 49 { 50 name: table 51 for dep in node.dependencies 52 for name, table in contexts[dep].tables.items() 53 } 54 ) 55 56 if isinstance(node, planner.Scan): 57 contexts[node] = self.scan(node, context) 58 elif isinstance(node, planner.Aggregate): 59 contexts[node] = self.aggregate(node, context) 60 elif isinstance(node, planner.Join): 61 contexts[node] = self.join(node, context) 62 elif isinstance(node, planner.Sort): 63 contexts[node] = self.sort(node, context) 64 elif isinstance(node, planner.SetOperation): 65 contexts[node] = self.set_operation(node, context) 66 else: 67 raise NotImplementedError 68 69 if node.offset: 70 table = contexts[node].tables[node.name] 71 table.rows = table.rows[node.offset :] 72 73 finished.add(node) 74 75 for dep in node.dependents: 76 if all(d in contexts for d in dep.dependencies): 77 queue.add(dep) 78 79 for dep in node.dependencies: 80 if all(d in finished for d in dep.dependents): 81 contexts.pop(dep) 82 except Exception as e: 83 raise ExecuteError(f"Step '{node.id}' failed: {e}") from e 84 85 root = plan.root 86 return contexts[root].tables[root.name] 87 88 def generate(self, expression): 89 """Convert a SQL expression into literal Python code and compile it into bytecode.""" 90 if not expression: 91 return None 92 93 expression = self._replace_subqueries(expression) 94 sql = self.generator.generate(expression) 95 return compile(sql, sql, "eval", optimize=2) 96 97 def _replace_subqueries(self, expression): 98 if not expression.find(*SUBQUERY_NODES): 99 return expression 100 101 expression = expression.copy() 102 103 while True: 104 subquery = expression.find(*SUBQUERY_NODES) 105 106 if subquery is None: 107 return expression 108 109 target, replacement = self._compile_subquery(subquery) 110 111 if target is expression: 112 expression = replacement 113 else: 114 target.replace(replacement) 115 116 def _compile_subquery(self, subquery): 117 query = subquery.this.unnest() 118 scope = build_scope(query) 119 120 outer_columns = list(scope.external_columns if scope else []) 121 122 plan = self._register_subquery(query) 123 parent = subquery.parent 124 125 if isinstance(subquery, exp.Exists): 126 return subquery, exp.func( 127 "SUBQUERY_EXISTS", plan, exp.var("scope"), *outer_columns, copy=False 128 ) 129 130 if len(query.selects) != 1: 131 raise ExecuteError( 132 f"Subquery used as an expression returned {len(query.selects)} columns" 133 ) 134 135 if isinstance(subquery, (exp.All, exp.Any)): 136 return self._compile_quantified(parent, subquery.key.upper(), plan, outer_columns) 137 138 if isinstance(parent, exp.In) and subquery is parent.args.get("query"): 139 return self._compile_quantified(parent, "ANY", plan, outer_columns, op="EQ") 140 141 return subquery, exp.func( 142 "SUBQUERY_SCALAR", plan, exp.var("scope"), *outer_columns, copy=False 143 ) 144 145 def _compile_quantified(self, comparison, quantifier, plan, outer_columns, op=None): 146 if not isinstance(comparison, (exp.Binary, exp.In)): 147 raise ExecuteError(f"Unsupported {quantifier} subquery: expected a comparison") 148 149 return comparison, exp.func( 150 "SUBQUERY_COMPARISON", 151 comparison.this, 152 plan, 153 exp.var("scope"), 154 exp.Literal.string(op or comparison.key.upper()), 155 exp.Literal.string(quantifier), 156 *outer_columns, 157 copy=False, 158 ) 159 160 def _register_subquery(self, query): 161 if self._ctes is not None and not query.args.get("with_"): 162 query.set("with_", self._ctes) 163 164 sql = query.sql() 165 name = self._plan_names_by_sql.get(sql) 166 167 if name is None: 168 name = self._plan_names_by_sql[sql] = f"_sq_{len(self._subquery_plans)}" 169 self._subquery_plans[name] = (planner.Plan(query), {}) 170 171 return exp.Literal.string(name) 172 173 def _subquery_table(self, plan_name, scope, args): 174 plan, cache = self._subquery_plans[plan_name] 175 176 try: 177 return cache[args] 178 except KeyError: 179 pass 180 except TypeError: # an unhashable correlated value can't be memoized 181 cache = None 182 183 table = self.execute(plan, scope) 184 185 if cache is not None: 186 cache[args] = table 187 188 return table 189 190 def _subquery_exists(self, plan_name, scope, *args): 191 return bool(self._subquery_table(plan_name, scope, args).rows) 192 193 def _subquery_scalar(self, plan_name, scope, *args): 194 rows = self._subquery_table(plan_name, scope, args).rows 195 196 if len(rows) > 1: 197 raise ExecuteError("More than one row returned by a subquery used as an expression") 198 199 return rows[0][0] if rows else None 200 201 def _subquery_comparison(self, value, plan_name, scope, op, quantifier, *args): 202 compare = self.env[op] 203 is_any = quantifier == "ANY" 204 saw_null = False 205 206 for row in self._subquery_table(plan_name, scope, args).rows: 207 result = compare(value, row[0]) 208 209 if result is None: 210 saw_null = True 211 elif bool(result) is is_any: 212 return is_any 213 214 return None if saw_null else not is_any 215 216 def generate_tuple(self, expressions): 217 """Convert an array of SQL expressions into tuple of Python byte code.""" 218 if not expressions: 219 return tuple() 220 return tuple(self.generate(expression) for expression in expressions) 221 222 def context(self, tables): 223 return Context(tables, env=self.env, outer=self._outer_scope) 224 225 def table(self, expressions): 226 return Table( 227 expression.alias_or_name if isinstance(expression, exp.Expr) else expression 228 for expression in expressions 229 ) 230 231 def scan(self, step, context): 232 source = step.source 233 234 if source and isinstance(source, exp.Expr): 235 source = source.name or source.alias 236 237 if source is None: 238 context, table_iter = self.static() 239 elif source in context: 240 if not step.projections and not step.condition: 241 return self.context({step.name: context.tables[source]}) 242 table_iter = context.table_iter(source) 243 else: 244 context, table_iter = self.scan_table(step) 245 246 return self.context({step.name: self._project_and_filter(context, step, table_iter)}) 247 248 def _project_and_filter(self, context, step, table_iter): 249 sink = self.table(step.projections if step.projections else context.columns) 250 condition = self.generate(step.condition) 251 projections = self.generate_tuple(step.projections) 252 253 for reader in table_iter: 254 if len(sink) >= step.offset + step.limit: 255 break 256 257 if condition and not context.eval(condition): 258 continue 259 260 if projections: 261 sink.append(context.eval_tuple(projections)) 262 else: 263 sink.append(reader.row) 264 265 return sink 266 267 def static(self): 268 return self.context({}), [RowReader(())] 269 270 def scan_table(self, step): 271 table = self.tables.find(step.source) 272 context = self.context({step.source.alias_or_name: table}) 273 return context, iter(table) 274 275 def join(self, step, context): 276 source = step.source_name 277 278 source_table = context.tables[source] 279 source_context = self.context({source: source_table}) 280 column_ranges = {source: range(0, len(source_table.columns))} 281 282 for name, join in step.joins.items(): 283 table = context.tables[name] 284 start = max(r.stop for r in column_ranges.values()) 285 column_ranges[name] = range(start, len(table.columns) + start) 286 join_context = self.context({name: table}) 287 condition = self.generate(join["condition"]) 288 condition_context = ( 289 self.context( 290 { 291 name: Table( 292 source_context.columns + join_context.columns, 293 column_range=column_range, 294 ) 295 for name, column_range in column_ranges.items() 296 } 297 ) 298 if condition 299 else None 300 ) 301 302 if join.get("source_key"): 303 table = self.hash_join( 304 join, source_context, join_context, condition, condition_context 305 ) 306 else: 307 table = self.nested_loop_join( 308 join, source_context, join_context, condition, condition_context 309 ) 310 311 source_context = self.context( 312 { 313 name: Table(table.columns, table.rows, column_range) 314 for name, column_range in column_ranges.items() 315 } 316 ) 317 if not step.condition and not step.projections: 318 return source_context 319 320 sink = self._project_and_filter( 321 source_context, 322 step, 323 (reader for reader, _ in iter(source_context)), 324 ) 325 326 if step.projections: 327 return self.context({step.name: sink}) 328 else: 329 return self.context( 330 { 331 name: Table(table.columns, sink.rows, table.column_range) 332 for name, table in source_context.tables.items() 333 } 334 ) 335 336 @staticmethod 337 def _join_matches(row, condition, condition_context): 338 if not condition: 339 return True 340 341 condition_context.set_row(row) 342 return condition_context.eval(condition) is True 343 344 def nested_loop_join(self, join, source_context, join_context, condition, condition_context): 345 table = Table(source_context.columns + join_context.columns) 346 source_rows = source_context.table.rows 347 join_rows = join_context.table.rows 348 matched_source = set() 349 matched_join = set() 350 351 for source_index, source_row in enumerate(source_rows): 352 for join_index, join_row in enumerate(join_rows): 353 row = source_row + join_row 354 if self._join_matches(row, condition, condition_context): 355 table.append(row) 356 matched_source.add(source_index) 357 matched_join.add(join_index) 358 359 self._append_unmatched_join_rows( 360 table, join, source_rows, join_rows, matched_source, matched_join 361 ) 362 363 return table 364 365 def hash_join(self, join, source_context, join_context, condition, condition_context): 366 source_key = self.generate_tuple(join["source_key"]) 367 join_key = self.generate_tuple(join["join_key"]) 368 results = collections.defaultdict(lambda: ([], [])) 369 370 for index, (reader, ctx) in enumerate(source_context): 371 key = ctx.eval_tuple(source_key) 372 if all(value is not None for value in key): 373 results[key][0].append((index, reader.row)) 374 for index, (reader, ctx) in enumerate(join_context): 375 key = ctx.eval_tuple(join_key) 376 if all(value is not None for value in key): 377 results[key][1].append((index, reader.row)) 378 379 table = Table(source_context.columns + join_context.columns) 380 matched_source = set() 381 matched_join = set() 382 383 for source_group, join_group in results.values(): 384 for (source_index, source_row), (join_index, join_row) in itertools.product( 385 source_group, join_group 386 ): 387 row = source_row + join_row 388 if self._join_matches(row, condition, condition_context): 389 table.append(row) 390 matched_source.add(source_index) 391 matched_join.add(join_index) 392 393 self._append_unmatched_join_rows( 394 table, 395 join, 396 source_context.table.rows, 397 join_context.table.rows, 398 matched_source, 399 matched_join, 400 ) 401 402 return table 403 404 @staticmethod 405 def _append_unmatched_join_rows( 406 table, join, source_rows, join_rows, matched_source, matched_join 407 ): 408 side = join.get("side") 409 if side in ("LEFT", "FULL"): 410 join_nulls = (None,) * (len(table.columns) - len(source_rows[0]) if source_rows else 0) 411 for index, row in enumerate(source_rows): 412 if index not in matched_source: 413 table.append(row + join_nulls) 414 415 if side in ("RIGHT", "FULL"): 416 source_width = len(table.columns) - (len(join_rows[0]) if join_rows else 0) 417 source_nulls = (None,) * source_width 418 for index, row in enumerate(join_rows): 419 if index not in matched_join: 420 table.append(source_nulls + row) 421 422 def aggregate(self, step, context): 423 group_by = self.generate_tuple(step.group.values()) 424 aggregations = self.generate_tuple(step.aggregations) 425 operands = self.generate_tuple(step.operands) 426 427 if operands: 428 operand_table = Table(self.table(step.operands).columns) 429 430 for reader, ctx in context: 431 operand_table.append(ctx.eval_tuple(operands)) 432 433 for i, (a, b) in enumerate(zip(context.table.rows, operand_table.rows)): 434 context.table.rows[i] = a + b 435 436 width = len(context.columns) 437 context.add_columns(*operand_table.columns) 438 439 operand_table = Table( 440 context.columns, 441 context.table.rows, 442 range(width, width + len(operand_table.columns)), 443 ) 444 445 context = self.context( 446 { 447 None: operand_table, 448 **context.tables, 449 } 450 ) 451 452 context.sort(group_by) 453 454 group = None 455 start = 0 456 end = 1 457 length = len(context.table) 458 table = self.table(list(step.group) + step.aggregations) 459 460 def add_row(): 461 table.append(group + context.eval_tuple(aggregations)) 462 463 if length: 464 for i in range(length): 465 context.set_index(i) 466 key = context.eval_tuple(group_by) 467 group = key if group is None else group 468 end += 1 469 if key != group: 470 context.set_range(start, end - 2) 471 add_row() 472 group = key 473 start = end - 2 474 if not step.condition and len(table.rows) >= step.offset + step.limit: 475 break 476 if i == length - 1: 477 context.set_range(start, end - 1) 478 add_row() 479 elif step.limit > 0 and not group_by: 480 context.set_range(0, 0) 481 table.append(context.eval_tuple(aggregations)) 482 483 context = self.context({step.name: table, **{name: table for name in context.tables}}) 484 485 if step.projections or step.condition: 486 return self.context( 487 {step.name: self._project_and_filter(context, step, context.table_iter(step.name))} 488 ) 489 return context 490 491 def sort(self, step, context): 492 projections = self.generate_tuple(step.projections) 493 projection_columns = [p.alias_or_name for p in step.projections] 494 all_columns = list(context.columns) + projection_columns 495 sink = self.table(all_columns) 496 for reader, ctx in context: 497 sink.append(reader.row + ctx.eval_tuple(projections)) 498 499 sort_ctx = self.context( 500 { 501 None: sink, 502 **{table: sink for table in context.tables}, 503 } 504 ) 505 sort_ctx.sort(self.generate_tuple(step.key)) 506 507 if not math.isinf(step.limit): 508 sort_ctx.table.rows = sort_ctx.table.rows[0 : step.offset + step.limit] 509 510 rows = sort_ctx.table.rows 511 512 if projection_columns: 513 rows = [row[len(context.columns) : len(all_columns)] for row in rows] 514 515 output = Table(projection_columns or context.columns, rows=rows) 516 return self.context({step.name: output}) 517 518 def set_operation(self, step, context): 519 left = context.tables[step.left] 520 right = context.tables[step.right] 521 522 sink = self.table(left.columns) 523 524 if issubclass(step.op, exp.Intersect): 525 right_counts = collections.Counter(right.rows) 526 seen = set() 527 for row in left.rows: 528 if right_counts[row] and (not step.distinct or row not in seen): 529 sink.append(row) 530 seen.add(row) 531 if not step.distinct: 532 right_counts[row] -= 1 533 elif issubclass(step.op, exp.Except): 534 right_counts = collections.Counter(right.rows) 535 seen = set() 536 for row in left.rows: 537 if right_counts[row] and not step.distinct: 538 right_counts[row] -= 1 539 elif not right_counts[row] and (not step.distinct or row not in seen): 540 sink.append(row) 541 seen.add(row) 542 elif issubclass(step.op, exp.Union) and step.distinct: 543 sink.rows = list(set(left.rows).union(set(right.rows))) 544 else: 545 sink.rows = left.rows + right.rows 546 547 if not math.isinf(step.limit): 548 sink.rows = sink.rows[0 : step.offset + step.limit] 549 550 return self.context({step.name: sink}) 551 552 553class Python(Dialect): 554 class Tokenizer(tokens.Tokenizer): 555 STRING_ESCAPES = ["\\"] 556 557 Generator = PythonGenerator
SUBQUERY_NODES =
(<class 'sqlglot.expressions.query.Subquery'>, <class 'sqlglot.expressions.functions.Exists'>, <class 'sqlglot.expressions.core.All'>, <class 'sqlglot.expressions.core.Any'>)
class
PythonExecutor:
18class PythonExecutor: 19 def __init__(self, env=None, tables=None): 20 self.generator = Python().generator(identify=True, comments=False) 21 self.env = {**ENV, **(env or {})} 22 self.tables = tables or {} 23 self._subquery_plans = {} 24 self._plan_names_by_sql = {} 25 self._ctes = None 26 self._outer_scope = None 27 self.env.update( 28 SUBQUERY_COMPARISON=self._subquery_comparison, 29 SUBQUERY_EXISTS=self._subquery_exists, 30 SUBQUERY_SCALAR=self._subquery_scalar, 31 ) 32 33 def execute(self, plan, outer_scope=None): 34 ctes, scope = self._ctes, self._outer_scope 35 self._ctes, self._outer_scope = plan.ctes, outer_scope 36 try: 37 return self._execute(plan) 38 finally: 39 self._ctes, self._outer_scope = ctes, scope 40 41 def _execute(self, plan): 42 finished = set() 43 queue = set(plan.leaves) 44 contexts = {} 45 46 while queue: 47 node = queue.pop() 48 try: 49 context = self.context( 50 { 51 name: table 52 for dep in node.dependencies 53 for name, table in contexts[dep].tables.items() 54 } 55 ) 56 57 if isinstance(node, planner.Scan): 58 contexts[node] = self.scan(node, context) 59 elif isinstance(node, planner.Aggregate): 60 contexts[node] = self.aggregate(node, context) 61 elif isinstance(node, planner.Join): 62 contexts[node] = self.join(node, context) 63 elif isinstance(node, planner.Sort): 64 contexts[node] = self.sort(node, context) 65 elif isinstance(node, planner.SetOperation): 66 contexts[node] = self.set_operation(node, context) 67 else: 68 raise NotImplementedError 69 70 if node.offset: 71 table = contexts[node].tables[node.name] 72 table.rows = table.rows[node.offset :] 73 74 finished.add(node) 75 76 for dep in node.dependents: 77 if all(d in contexts for d in dep.dependencies): 78 queue.add(dep) 79 80 for dep in node.dependencies: 81 if all(d in finished for d in dep.dependents): 82 contexts.pop(dep) 83 except Exception as e: 84 raise ExecuteError(f"Step '{node.id}' failed: {e}") from e 85 86 root = plan.root 87 return contexts[root].tables[root.name] 88 89 def generate(self, expression): 90 """Convert a SQL expression into literal Python code and compile it into bytecode.""" 91 if not expression: 92 return None 93 94 expression = self._replace_subqueries(expression) 95 sql = self.generator.generate(expression) 96 return compile(sql, sql, "eval", optimize=2) 97 98 def _replace_subqueries(self, expression): 99 if not expression.find(*SUBQUERY_NODES): 100 return expression 101 102 expression = expression.copy() 103 104 while True: 105 subquery = expression.find(*SUBQUERY_NODES) 106 107 if subquery is None: 108 return expression 109 110 target, replacement = self._compile_subquery(subquery) 111 112 if target is expression: 113 expression = replacement 114 else: 115 target.replace(replacement) 116 117 def _compile_subquery(self, subquery): 118 query = subquery.this.unnest() 119 scope = build_scope(query) 120 121 outer_columns = list(scope.external_columns if scope else []) 122 123 plan = self._register_subquery(query) 124 parent = subquery.parent 125 126 if isinstance(subquery, exp.Exists): 127 return subquery, exp.func( 128 "SUBQUERY_EXISTS", plan, exp.var("scope"), *outer_columns, copy=False 129 ) 130 131 if len(query.selects) != 1: 132 raise ExecuteError( 133 f"Subquery used as an expression returned {len(query.selects)} columns" 134 ) 135 136 if isinstance(subquery, (exp.All, exp.Any)): 137 return self._compile_quantified(parent, subquery.key.upper(), plan, outer_columns) 138 139 if isinstance(parent, exp.In) and subquery is parent.args.get("query"): 140 return self._compile_quantified(parent, "ANY", plan, outer_columns, op="EQ") 141 142 return subquery, exp.func( 143 "SUBQUERY_SCALAR", plan, exp.var("scope"), *outer_columns, copy=False 144 ) 145 146 def _compile_quantified(self, comparison, quantifier, plan, outer_columns, op=None): 147 if not isinstance(comparison, (exp.Binary, exp.In)): 148 raise ExecuteError(f"Unsupported {quantifier} subquery: expected a comparison") 149 150 return comparison, exp.func( 151 "SUBQUERY_COMPARISON", 152 comparison.this, 153 plan, 154 exp.var("scope"), 155 exp.Literal.string(op or comparison.key.upper()), 156 exp.Literal.string(quantifier), 157 *outer_columns, 158 copy=False, 159 ) 160 161 def _register_subquery(self, query): 162 if self._ctes is not None and not query.args.get("with_"): 163 query.set("with_", self._ctes) 164 165 sql = query.sql() 166 name = self._plan_names_by_sql.get(sql) 167 168 if name is None: 169 name = self._plan_names_by_sql[sql] = f"_sq_{len(self._subquery_plans)}" 170 self._subquery_plans[name] = (planner.Plan(query), {}) 171 172 return exp.Literal.string(name) 173 174 def _subquery_table(self, plan_name, scope, args): 175 plan, cache = self._subquery_plans[plan_name] 176 177 try: 178 return cache[args] 179 except KeyError: 180 pass 181 except TypeError: # an unhashable correlated value can't be memoized 182 cache = None 183 184 table = self.execute(plan, scope) 185 186 if cache is not None: 187 cache[args] = table 188 189 return table 190 191 def _subquery_exists(self, plan_name, scope, *args): 192 return bool(self._subquery_table(plan_name, scope, args).rows) 193 194 def _subquery_scalar(self, plan_name, scope, *args): 195 rows = self._subquery_table(plan_name, scope, args).rows 196 197 if len(rows) > 1: 198 raise ExecuteError("More than one row returned by a subquery used as an expression") 199 200 return rows[0][0] if rows else None 201 202 def _subquery_comparison(self, value, plan_name, scope, op, quantifier, *args): 203 compare = self.env[op] 204 is_any = quantifier == "ANY" 205 saw_null = False 206 207 for row in self._subquery_table(plan_name, scope, args).rows: 208 result = compare(value, row[0]) 209 210 if result is None: 211 saw_null = True 212 elif bool(result) is is_any: 213 return is_any 214 215 return None if saw_null else not is_any 216 217 def generate_tuple(self, expressions): 218 """Convert an array of SQL expressions into tuple of Python byte code.""" 219 if not expressions: 220 return tuple() 221 return tuple(self.generate(expression) for expression in expressions) 222 223 def context(self, tables): 224 return Context(tables, env=self.env, outer=self._outer_scope) 225 226 def table(self, expressions): 227 return Table( 228 expression.alias_or_name if isinstance(expression, exp.Expr) else expression 229 for expression in expressions 230 ) 231 232 def scan(self, step, context): 233 source = step.source 234 235 if source and isinstance(source, exp.Expr): 236 source = source.name or source.alias 237 238 if source is None: 239 context, table_iter = self.static() 240 elif source in context: 241 if not step.projections and not step.condition: 242 return self.context({step.name: context.tables[source]}) 243 table_iter = context.table_iter(source) 244 else: 245 context, table_iter = self.scan_table(step) 246 247 return self.context({step.name: self._project_and_filter(context, step, table_iter)}) 248 249 def _project_and_filter(self, context, step, table_iter): 250 sink = self.table(step.projections if step.projections else context.columns) 251 condition = self.generate(step.condition) 252 projections = self.generate_tuple(step.projections) 253 254 for reader in table_iter: 255 if len(sink) >= step.offset + step.limit: 256 break 257 258 if condition and not context.eval(condition): 259 continue 260 261 if projections: 262 sink.append(context.eval_tuple(projections)) 263 else: 264 sink.append(reader.row) 265 266 return sink 267 268 def static(self): 269 return self.context({}), [RowReader(())] 270 271 def scan_table(self, step): 272 table = self.tables.find(step.source) 273 context = self.context({step.source.alias_or_name: table}) 274 return context, iter(table) 275 276 def join(self, step, context): 277 source = step.source_name 278 279 source_table = context.tables[source] 280 source_context = self.context({source: source_table}) 281 column_ranges = {source: range(0, len(source_table.columns))} 282 283 for name, join in step.joins.items(): 284 table = context.tables[name] 285 start = max(r.stop for r in column_ranges.values()) 286 column_ranges[name] = range(start, len(table.columns) + start) 287 join_context = self.context({name: table}) 288 condition = self.generate(join["condition"]) 289 condition_context = ( 290 self.context( 291 { 292 name: Table( 293 source_context.columns + join_context.columns, 294 column_range=column_range, 295 ) 296 for name, column_range in column_ranges.items() 297 } 298 ) 299 if condition 300 else None 301 ) 302 303 if join.get("source_key"): 304 table = self.hash_join( 305 join, source_context, join_context, condition, condition_context 306 ) 307 else: 308 table = self.nested_loop_join( 309 join, source_context, join_context, condition, condition_context 310 ) 311 312 source_context = self.context( 313 { 314 name: Table(table.columns, table.rows, column_range) 315 for name, column_range in column_ranges.items() 316 } 317 ) 318 if not step.condition and not step.projections: 319 return source_context 320 321 sink = self._project_and_filter( 322 source_context, 323 step, 324 (reader for reader, _ in iter(source_context)), 325 ) 326 327 if step.projections: 328 return self.context({step.name: sink}) 329 else: 330 return self.context( 331 { 332 name: Table(table.columns, sink.rows, table.column_range) 333 for name, table in source_context.tables.items() 334 } 335 ) 336 337 @staticmethod 338 def _join_matches(row, condition, condition_context): 339 if not condition: 340 return True 341 342 condition_context.set_row(row) 343 return condition_context.eval(condition) is True 344 345 def nested_loop_join(self, join, source_context, join_context, condition, condition_context): 346 table = Table(source_context.columns + join_context.columns) 347 source_rows = source_context.table.rows 348 join_rows = join_context.table.rows 349 matched_source = set() 350 matched_join = set() 351 352 for source_index, source_row in enumerate(source_rows): 353 for join_index, join_row in enumerate(join_rows): 354 row = source_row + join_row 355 if self._join_matches(row, condition, condition_context): 356 table.append(row) 357 matched_source.add(source_index) 358 matched_join.add(join_index) 359 360 self._append_unmatched_join_rows( 361 table, join, source_rows, join_rows, matched_source, matched_join 362 ) 363 364 return table 365 366 def hash_join(self, join, source_context, join_context, condition, condition_context): 367 source_key = self.generate_tuple(join["source_key"]) 368 join_key = self.generate_tuple(join["join_key"]) 369 results = collections.defaultdict(lambda: ([], [])) 370 371 for index, (reader, ctx) in enumerate(source_context): 372 key = ctx.eval_tuple(source_key) 373 if all(value is not None for value in key): 374 results[key][0].append((index, reader.row)) 375 for index, (reader, ctx) in enumerate(join_context): 376 key = ctx.eval_tuple(join_key) 377 if all(value is not None for value in key): 378 results[key][1].append((index, reader.row)) 379 380 table = Table(source_context.columns + join_context.columns) 381 matched_source = set() 382 matched_join = set() 383 384 for source_group, join_group in results.values(): 385 for (source_index, source_row), (join_index, join_row) in itertools.product( 386 source_group, join_group 387 ): 388 row = source_row + join_row 389 if self._join_matches(row, condition, condition_context): 390 table.append(row) 391 matched_source.add(source_index) 392 matched_join.add(join_index) 393 394 self._append_unmatched_join_rows( 395 table, 396 join, 397 source_context.table.rows, 398 join_context.table.rows, 399 matched_source, 400 matched_join, 401 ) 402 403 return table 404 405 @staticmethod 406 def _append_unmatched_join_rows( 407 table, join, source_rows, join_rows, matched_source, matched_join 408 ): 409 side = join.get("side") 410 if side in ("LEFT", "FULL"): 411 join_nulls = (None,) * (len(table.columns) - len(source_rows[0]) if source_rows else 0) 412 for index, row in enumerate(source_rows): 413 if index not in matched_source: 414 table.append(row + join_nulls) 415 416 if side in ("RIGHT", "FULL"): 417 source_width = len(table.columns) - (len(join_rows[0]) if join_rows else 0) 418 source_nulls = (None,) * source_width 419 for index, row in enumerate(join_rows): 420 if index not in matched_join: 421 table.append(source_nulls + row) 422 423 def aggregate(self, step, context): 424 group_by = self.generate_tuple(step.group.values()) 425 aggregations = self.generate_tuple(step.aggregations) 426 operands = self.generate_tuple(step.operands) 427 428 if operands: 429 operand_table = Table(self.table(step.operands).columns) 430 431 for reader, ctx in context: 432 operand_table.append(ctx.eval_tuple(operands)) 433 434 for i, (a, b) in enumerate(zip(context.table.rows, operand_table.rows)): 435 context.table.rows[i] = a + b 436 437 width = len(context.columns) 438 context.add_columns(*operand_table.columns) 439 440 operand_table = Table( 441 context.columns, 442 context.table.rows, 443 range(width, width + len(operand_table.columns)), 444 ) 445 446 context = self.context( 447 { 448 None: operand_table, 449 **context.tables, 450 } 451 ) 452 453 context.sort(group_by) 454 455 group = None 456 start = 0 457 end = 1 458 length = len(context.table) 459 table = self.table(list(step.group) + step.aggregations) 460 461 def add_row(): 462 table.append(group + context.eval_tuple(aggregations)) 463 464 if length: 465 for i in range(length): 466 context.set_index(i) 467 key = context.eval_tuple(group_by) 468 group = key if group is None else group 469 end += 1 470 if key != group: 471 context.set_range(start, end - 2) 472 add_row() 473 group = key 474 start = end - 2 475 if not step.condition and len(table.rows) >= step.offset + step.limit: 476 break 477 if i == length - 1: 478 context.set_range(start, end - 1) 479 add_row() 480 elif step.limit > 0 and not group_by: 481 context.set_range(0, 0) 482 table.append(context.eval_tuple(aggregations)) 483 484 context = self.context({step.name: table, **{name: table for name in context.tables}}) 485 486 if step.projections or step.condition: 487 return self.context( 488 {step.name: self._project_and_filter(context, step, context.table_iter(step.name))} 489 ) 490 return context 491 492 def sort(self, step, context): 493 projections = self.generate_tuple(step.projections) 494 projection_columns = [p.alias_or_name for p in step.projections] 495 all_columns = list(context.columns) + projection_columns 496 sink = self.table(all_columns) 497 for reader, ctx in context: 498 sink.append(reader.row + ctx.eval_tuple(projections)) 499 500 sort_ctx = self.context( 501 { 502 None: sink, 503 **{table: sink for table in context.tables}, 504 } 505 ) 506 sort_ctx.sort(self.generate_tuple(step.key)) 507 508 if not math.isinf(step.limit): 509 sort_ctx.table.rows = sort_ctx.table.rows[0 : step.offset + step.limit] 510 511 rows = sort_ctx.table.rows 512 513 if projection_columns: 514 rows = [row[len(context.columns) : len(all_columns)] for row in rows] 515 516 output = Table(projection_columns or context.columns, rows=rows) 517 return self.context({step.name: output}) 518 519 def set_operation(self, step, context): 520 left = context.tables[step.left] 521 right = context.tables[step.right] 522 523 sink = self.table(left.columns) 524 525 if issubclass(step.op, exp.Intersect): 526 right_counts = collections.Counter(right.rows) 527 seen = set() 528 for row in left.rows: 529 if right_counts[row] and (not step.distinct or row not in seen): 530 sink.append(row) 531 seen.add(row) 532 if not step.distinct: 533 right_counts[row] -= 1 534 elif issubclass(step.op, exp.Except): 535 right_counts = collections.Counter(right.rows) 536 seen = set() 537 for row in left.rows: 538 if right_counts[row] and not step.distinct: 539 right_counts[row] -= 1 540 elif not right_counts[row] and (not step.distinct or row not in seen): 541 sink.append(row) 542 seen.add(row) 543 elif issubclass(step.op, exp.Union) and step.distinct: 544 sink.rows = list(set(left.rows).union(set(right.rows))) 545 else: 546 sink.rows = left.rows + right.rows 547 548 if not math.isinf(step.limit): 549 sink.rows = sink.rows[0 : step.offset + step.limit] 550 551 return self.context({step.name: sink})
PythonExecutor(env=None, tables=None)
19 def __init__(self, env=None, tables=None): 20 self.generator = Python().generator(identify=True, comments=False) 21 self.env = {**ENV, **(env or {})} 22 self.tables = tables or {} 23 self._subquery_plans = {} 24 self._plan_names_by_sql = {} 25 self._ctes = None 26 self._outer_scope = None 27 self.env.update( 28 SUBQUERY_COMPARISON=self._subquery_comparison, 29 SUBQUERY_EXISTS=self._subquery_exists, 30 SUBQUERY_SCALAR=self._subquery_scalar, 31 )
def
generate(self, expression):
89 def generate(self, expression): 90 """Convert a SQL expression into literal Python code and compile it into bytecode.""" 91 if not expression: 92 return None 93 94 expression = self._replace_subqueries(expression) 95 sql = self.generator.generate(expression) 96 return compile(sql, sql, "eval", optimize=2)
Convert a SQL expression into literal Python code and compile it into bytecode.
def
generate_tuple(self, expressions):
217 def generate_tuple(self, expressions): 218 """Convert an array of SQL expressions into tuple of Python byte code.""" 219 if not expressions: 220 return tuple() 221 return tuple(self.generate(expression) for expression in expressions)
Convert an array of SQL expressions into tuple of Python byte code.
def
scan(self, step, context):
232 def scan(self, step, context): 233 source = step.source 234 235 if source and isinstance(source, exp.Expr): 236 source = source.name or source.alias 237 238 if source is None: 239 context, table_iter = self.static() 240 elif source in context: 241 if not step.projections and not step.condition: 242 return self.context({step.name: context.tables[source]}) 243 table_iter = context.table_iter(source) 244 else: 245 context, table_iter = self.scan_table(step) 246 247 return self.context({step.name: self._project_and_filter(context, step, table_iter)})
def
join(self, step, context):
276 def join(self, step, context): 277 source = step.source_name 278 279 source_table = context.tables[source] 280 source_context = self.context({source: source_table}) 281 column_ranges = {source: range(0, len(source_table.columns))} 282 283 for name, join in step.joins.items(): 284 table = context.tables[name] 285 start = max(r.stop for r in column_ranges.values()) 286 column_ranges[name] = range(start, len(table.columns) + start) 287 join_context = self.context({name: table}) 288 condition = self.generate(join["condition"]) 289 condition_context = ( 290 self.context( 291 { 292 name: Table( 293 source_context.columns + join_context.columns, 294 column_range=column_range, 295 ) 296 for name, column_range in column_ranges.items() 297 } 298 ) 299 if condition 300 else None 301 ) 302 303 if join.get("source_key"): 304 table = self.hash_join( 305 join, source_context, join_context, condition, condition_context 306 ) 307 else: 308 table = self.nested_loop_join( 309 join, source_context, join_context, condition, condition_context 310 ) 311 312 source_context = self.context( 313 { 314 name: Table(table.columns, table.rows, column_range) 315 for name, column_range in column_ranges.items() 316 } 317 ) 318 if not step.condition and not step.projections: 319 return source_context 320 321 sink = self._project_and_filter( 322 source_context, 323 step, 324 (reader for reader, _ in iter(source_context)), 325 ) 326 327 if step.projections: 328 return self.context({step.name: sink}) 329 else: 330 return self.context( 331 { 332 name: Table(table.columns, sink.rows, table.column_range) 333 for name, table in source_context.tables.items() 334 } 335 )
def
nested_loop_join( self, join, source_context, join_context, condition, condition_context):
345 def nested_loop_join(self, join, source_context, join_context, condition, condition_context): 346 table = Table(source_context.columns + join_context.columns) 347 source_rows = source_context.table.rows 348 join_rows = join_context.table.rows 349 matched_source = set() 350 matched_join = set() 351 352 for source_index, source_row in enumerate(source_rows): 353 for join_index, join_row in enumerate(join_rows): 354 row = source_row + join_row 355 if self._join_matches(row, condition, condition_context): 356 table.append(row) 357 matched_source.add(source_index) 358 matched_join.add(join_index) 359 360 self._append_unmatched_join_rows( 361 table, join, source_rows, join_rows, matched_source, matched_join 362 ) 363 364 return table
def
hash_join( self, join, source_context, join_context, condition, condition_context):
366 def hash_join(self, join, source_context, join_context, condition, condition_context): 367 source_key = self.generate_tuple(join["source_key"]) 368 join_key = self.generate_tuple(join["join_key"]) 369 results = collections.defaultdict(lambda: ([], [])) 370 371 for index, (reader, ctx) in enumerate(source_context): 372 key = ctx.eval_tuple(source_key) 373 if all(value is not None for value in key): 374 results[key][0].append((index, reader.row)) 375 for index, (reader, ctx) in enumerate(join_context): 376 key = ctx.eval_tuple(join_key) 377 if all(value is not None for value in key): 378 results[key][1].append((index, reader.row)) 379 380 table = Table(source_context.columns + join_context.columns) 381 matched_source = set() 382 matched_join = set() 383 384 for source_group, join_group in results.values(): 385 for (source_index, source_row), (join_index, join_row) in itertools.product( 386 source_group, join_group 387 ): 388 row = source_row + join_row 389 if self._join_matches(row, condition, condition_context): 390 table.append(row) 391 matched_source.add(source_index) 392 matched_join.add(join_index) 393 394 self._append_unmatched_join_rows( 395 table, 396 join, 397 source_context.table.rows, 398 join_context.table.rows, 399 matched_source, 400 matched_join, 401 ) 402 403 return table
def
aggregate(self, step, context):
423 def aggregate(self, step, context): 424 group_by = self.generate_tuple(step.group.values()) 425 aggregations = self.generate_tuple(step.aggregations) 426 operands = self.generate_tuple(step.operands) 427 428 if operands: 429 operand_table = Table(self.table(step.operands).columns) 430 431 for reader, ctx in context: 432 operand_table.append(ctx.eval_tuple(operands)) 433 434 for i, (a, b) in enumerate(zip(context.table.rows, operand_table.rows)): 435 context.table.rows[i] = a + b 436 437 width = len(context.columns) 438 context.add_columns(*operand_table.columns) 439 440 operand_table = Table( 441 context.columns, 442 context.table.rows, 443 range(width, width + len(operand_table.columns)), 444 ) 445 446 context = self.context( 447 { 448 None: operand_table, 449 **context.tables, 450 } 451 ) 452 453 context.sort(group_by) 454 455 group = None 456 start = 0 457 end = 1 458 length = len(context.table) 459 table = self.table(list(step.group) + step.aggregations) 460 461 def add_row(): 462 table.append(group + context.eval_tuple(aggregations)) 463 464 if length: 465 for i in range(length): 466 context.set_index(i) 467 key = context.eval_tuple(group_by) 468 group = key if group is None else group 469 end += 1 470 if key != group: 471 context.set_range(start, end - 2) 472 add_row() 473 group = key 474 start = end - 2 475 if not step.condition and len(table.rows) >= step.offset + step.limit: 476 break 477 if i == length - 1: 478 context.set_range(start, end - 1) 479 add_row() 480 elif step.limit > 0 and not group_by: 481 context.set_range(0, 0) 482 table.append(context.eval_tuple(aggregations)) 483 484 context = self.context({step.name: table, **{name: table for name in context.tables}}) 485 486 if step.projections or step.condition: 487 return self.context( 488 {step.name: self._project_and_filter(context, step, context.table_iter(step.name))} 489 ) 490 return context
def
sort(self, step, context):
492 def sort(self, step, context): 493 projections = self.generate_tuple(step.projections) 494 projection_columns = [p.alias_or_name for p in step.projections] 495 all_columns = list(context.columns) + projection_columns 496 sink = self.table(all_columns) 497 for reader, ctx in context: 498 sink.append(reader.row + ctx.eval_tuple(projections)) 499 500 sort_ctx = self.context( 501 { 502 None: sink, 503 **{table: sink for table in context.tables}, 504 } 505 ) 506 sort_ctx.sort(self.generate_tuple(step.key)) 507 508 if not math.isinf(step.limit): 509 sort_ctx.table.rows = sort_ctx.table.rows[0 : step.offset + step.limit] 510 511 rows = sort_ctx.table.rows 512 513 if projection_columns: 514 rows = [row[len(context.columns) : len(all_columns)] for row in rows] 515 516 output = Table(projection_columns or context.columns, rows=rows) 517 return self.context({step.name: output})
def
set_operation(self, step, context):
519 def set_operation(self, step, context): 520 left = context.tables[step.left] 521 right = context.tables[step.right] 522 523 sink = self.table(left.columns) 524 525 if issubclass(step.op, exp.Intersect): 526 right_counts = collections.Counter(right.rows) 527 seen = set() 528 for row in left.rows: 529 if right_counts[row] and (not step.distinct or row not in seen): 530 sink.append(row) 531 seen.add(row) 532 if not step.distinct: 533 right_counts[row] -= 1 534 elif issubclass(step.op, exp.Except): 535 right_counts = collections.Counter(right.rows) 536 seen = set() 537 for row in left.rows: 538 if right_counts[row] and not step.distinct: 539 right_counts[row] -= 1 540 elif not right_counts[row] and (not step.distinct or row not in seen): 541 sink.append(row) 542 seen.add(row) 543 elif issubclass(step.op, exp.Union) and step.distinct: 544 sink.rows = list(set(left.rows).union(set(right.rows))) 545 else: 546 sink.rows = left.rows + right.rows 547 548 if not math.isinf(step.limit): 549 sink.rows = sink.rows[0 : step.offset + step.limit] 550 551 return self.context({step.name: sink})
class
Python(sqlglot.dialects.dialect.Dialect):
554class Python(Dialect): 555 class Tokenizer(tokens.Tokenizer): 556 STRING_ESCAPES = ["\\"] 557 558 Generator = PythonGenerator
Generator =
<class 'sqlglot.generators.python.PythonGenerator'>
UNESCAPED_SEQUENCES: dict[str, str] =
{'\\a': '\x07', '\\b': '\x08', '\\f': '\x0c', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\v': '\x0b', '\\\\': '\\'}
Mapping of an escaped sequence (\n) to its unescaped version (
).
STRINGS_SUPPORT_ESCAPED_SEQUENCES: bool =
True
Whether string literals support escape sequences (e.g. \n). Set by the metaclass based on the tokenizer's STRING_ESCAPES.
BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES: bool =
True
Whether byte string literals support escape sequences. Set by the metaclass based on the tokenizer's BYTE_STRING_ESCAPES.
tokenizer_class =
<class 'Python.Tokenizer'>
parser_class =
<class 'sqlglot.parsers.base.BaseParser'>
generator_class =
<class 'sqlglot.generators.python.PythonGenerator'>
INVERSE_TIME_MAPPING: dict[str, str] =
{'%mstrict': '%m', '%dstrict': '%d', '%Hstrict': '%H', '%Istrict': '%I', '%Mstrict': '%M', '%Sstrict': '%S'}
INVERSE_TIME_TRIE: dict =
{'%': {'m': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'd': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'H': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'I': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'M': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'S': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}}}
INVERSE_FORMAT_MAPPING: dict[str, str] =
{'%mstrict': '%m', '%dstrict': '%d', '%Hstrict': '%H', '%Istrict': '%I', '%Mstrict': '%M', '%Sstrict': '%S'}
INVERSE_FORMAT_TRIE: dict =
{'%': {'m': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'd': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'H': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'I': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'M': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}, 'S': {'s': {'t': {'r': {'i': {'c': {'t': {0: True}}}}}}}}}
ESCAPED_SEQUENCES: dict[str, str] =
{'\x07': '\\a', '\x08': '\\b', '\x0c': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', '\x0b': '\\v', '\\': '\\\\'}
VALID_INTERVAL_UNITS: set[str] =
{'MICROSECS', 'MINUTES', 'YEAR', 'WY', 'DAYOFMONTH', 'DOW', 'MONTH', 'MICROSECOND', 'TIMEZONE_MINUTE', 'EPOCH_MICROSECONDS', 'MINUTE', 'DAYOFWEEK_ISO', 'TZH', 'Y', 'DW', 'WEEK', 'MM', 'DAYOFWEEK', 'QUARTER', 'MSEC', 'NSEC', 'M', 'USECS', 'NANOSECS', 'WEEKDAY', 'DW_ISO', 'MONTHS', 'D', 'MINS', 'DY', 'DAYOFYEAR', 'WK', 'HH', 'MS', 'TIMEZONE_HOUR', 'MIL', 'S', 'YYYY', 'DECADES', 'MON', 'NANOSECOND', 'WEEKOFYEAR', 'HOUR', 'TZM', 'MILLENNIUM', 'SECONDS', 'DOW_ISO', 'DAY OF WEEK', 'USECOND', 'H', 'MILLISECON', 'QTRS', 'MILLISECS', 'DOY', 'SECOND', 'EPOCH_MILLISECOND', 'HRS', 'DEC', 'EPOCH_SECONDS', 'YYY', 'YY', 'YRS', 'MSECONDS', 'SEC', 'USEC', 'MILLISECOND', 'HOURS', 'DAY OF YEAR', 'MILLISEC', 'C', 'MSECOND', 'WEEKDAY_ISO', 'EPOCH_NANOSECOND', 'NANOSEC', 'SECS', 'CENTURY', 'MILS', 'US', 'WEEKOFYEAR_ISO', 'WEEKOFYEARISO', 'QUARTERS', 'YR', 'EPOCH', 'EPOCH_MILLISECONDS', 'MONS', 'MILLISECONDS', 'MICROSECONDS', 'MI', 'WEEK_ISO', 'MIN', 'MILLENIA', 'WOY', 'CENTS', 'EPOCH_MICROSECOND', 'DAYS', 'MSECS', 'CENT', 'W', 'MICROSEC', 'HR', 'NS', 'DD', 'EPOCH_NANOSECONDS', 'DECADE', 'NSECONDS', 'DAY', 'DECS', 'DAYOFWEEKISO', 'USECONDS', 'YEARS', 'NSECOND', 'CENTURIES', 'WEEKISO', 'QTR', 'Q', 'EPOCH_SECOND'}
Inherited Members
- sqlglot.tokens.Tokenizer
- Tokenizer
- SINGLE_TOKENS
- BIT_STRINGS
- BYTE_STRINGS
- HEX_STRINGS
- RAW_STRINGS
- HEREDOC_STRINGS
- UNICODE_STRINGS
- IDENTIFIERS
- QUOTES
- VAR_SINGLE_TOKENS
- ESCAPE_FOLLOW_CHARS
- IDENTIFIER_ESCAPES
- HEREDOC_TAG_IS_IDENTIFIER
- HEREDOC_STRING_ALTERNATIVE
- STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS
- NESTED_COMMENTS
- HINT_START
- TOKENS_PRECEDING_HINT
- KEYWORDS
- COMMANDS
- COMMAND_PREFIX_TOKENS
- NUMERIC_LITERALS
- NUMBERS_CAN_HAVE_DECIMALS
- COMMENTS
- dialect
- tokenize
- sql
- size
- tokens