tp: Create stdlib utils python lib
Bug: 255535171
Change-Id: I7c173dac52ee70d59d6495f10bf68dfad89c2567
diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py
index f91120b..051f1a9 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -21,479 +21,26 @@
from __future__ import print_function
import os
-import re
import sys
-from sql_modules_utils import *
-from typing import Union, List
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.join(ROOT_DIR))
+FILE_DIR = ROOT_DIR
-
-# Stores documentation for CREATE {TABLE|VIEW} with comment split into
-# segments.
-class TableViewDocs:
-
- def __init__(self, name: str, desc: List[str], columns: List[str], path: str):
- self.name = name
- self.desc = desc
- self.columns = columns
- self.path = path
-
- # Contructs new TableViewDocs from whole comment, by splitting it on typed
- # lines. Returns None for improperly structured schemas.
- @staticmethod
- def create_from_comment(path: str, comment: List[str], module: str,
- matches: tuple) -> tuple["TableViewDocs", List[str]]:
- obj_type, name = matches[:2]
-
- # Ignore internal tables and views.
- if re.match(r"^internal_.*", name):
- return None, []
-
- errors = validate_name(name, module)
- col_start = None
-
- # Splits code into segments by finding beginning of column segment.
- for i, line in enumerate(comment):
- # Ignore only '--' line.
- if line == "--":
- continue
-
- m = re.match(typed_comment_pattern(), line)
-
- # Ignore untyped lines
- if not m:
- continue
-
- line_type = m.group(1)
- if line_type == "column" and not col_start:
- col_start = i
- continue
-
- if not col_start:
- errors.append(f"No columns for {obj_type}.\n'{name}' in {path}'\n")
- return None, errors
-
- return (
- TableViewDocs(name, comment[:col_start], comment[col_start:], path),
- errors,
- )
-
- def check_comment(self) -> List[str]:
- errors = validate_desc(self)
- errors += validate_columns(self)
- return errors
-
-
-# Stores documentation for CREATE_FUNCTION with comment split into segments.
-class FunctionDocs:
-
- def __init__(
- self,
- path: str,
- data_from_sql: dict,
- module: str,
- name: str,
- desc: str,
- args: List[str],
- ret: List[str],
- ):
- self.path = path
- self.data_from_sql = data_from_sql
- self.module = module
- self.name = name
- self.desc = desc
- self.args = args
- self.ret = ret
-
- # Contructs new FunctionDocs from whole comment, by splitting it on typed
- # lines. Returns None for improperly structured schemas.
- @staticmethod
- def create_from_comment(path: str, comment: List[str], module: str,
- matches: tuple) -> tuple["FunctionDocs", List[str]]:
- name, args, ret, sql = matches
-
- # Ignore internal functions.
- if re.match(r"^INTERNAL_.*", name):
- return None, []
-
- errors = validate_name(name, module, upper=True)
- start_args, start_ret = None, None
-
- # Splits code into segments by finding beginning of args and ret segments.
- for i, line in enumerate(comment):
- # Ignore only '--' line.
- if line == "--":
- continue
-
- m = re.match(typed_comment_pattern(), line)
-
- # Ignore untyped lines
- if not m:
- continue
-
- line_type = m.group(1)
- if line_type == "arg" and not start_args:
- start_args = i
- continue
-
- if line_type == "ret" and not start_ret:
- start_ret = i
- continue
-
- if not start_ret or not start_args:
- errors.append(f"Function requires 'arg' and 'ret' comments.\n"
- f"'{name}' in {path}")
- return None, errors
-
- args_dict, parse_errors = parse_args(args)
- data_from_sql = {'name': name, 'args': args_dict, 'ret': ret, 'sql': sql}
- return (
- FunctionDocs(
- path,
- data_from_sql,
- module,
- name,
- comment[:start_args],
- comment[start_args:start_ret],
- comment[start_ret:],
- ),
- errors + parse_errors,
- )
-
- def check_comment(self) -> List[str]:
- errors = validate_desc(self)
- errors += validate_args(self)
- errors += validate_ret(self)
- return errors
-
-
-# Stores documentation for CREATE_VIEW_FUNCTION with comment split into
-# segments.
-class ViewFunctionDocs:
-
- def __init__(
- self,
- path: str,
- data_from_sql: str,
- module: str,
- name: str,
- desc: List[str],
- args: List[str],
- columns: List[str],
- ):
- self.path = path
- self.data_from_sql = data_from_sql
- self.module = module
- self.name = name
- self.desc = desc
- self.args = args
- self.columns = columns
-
- # Contructs new ViewFunctionDocs from whole comment, by splitting it on typed
- # lines. Returns None for improperly structured schemas.
- @staticmethod
- def create_from_comment(path: str, comment: List[str], module: str,
- matches: tuple[str]
- ) -> tuple["ViewFunctionDocs", List[str]]:
- name, args, columns, sql = matches
-
- # Ignore internal functions.
- if re.match(r"^INTERNAL_.*", name):
- return None, []
-
- errors = validate_name(name, module, upper=True)
- start_args, start_cols = None, None
-
- # Splits code into segments by finding beginning of args and cols segments.
- for i, line in enumerate(comment):
- # Ignore only '--' line.
- if line == "--":
- continue
-
- m = re.match(typed_comment_pattern(), line)
-
- # Ignore untyped lines
- if not m:
- continue
-
- line_type = m.group(1)
- if line_type == "arg" and not start_args:
- start_args = i
- continue
-
- if line_type == "column" and not start_cols:
- start_cols = i
- continue
-
- if not start_cols or not start_args:
- errors.append(f"Function requires 'arg' and 'column' comments.\n"
- f"'{name}' in {path}")
- return None, errors
-
- args_dict, parse_errors = parse_args(args)
- errors += parse_errors
-
- cols_dict, parse_errors = parse_args(columns)
- errors += parse_errors
-
- data_from_sql = dict(name=name, args=args_dict, columns=cols_dict, sql=sql)
- return (
- ViewFunctionDocs(
- path,
- data_from_sql,
- module,
- name,
- comment[:start_args],
- comment[start_args:start_cols],
- comment[start_cols:],
- ),
- errors,
- )
-
- def check_comment(self) -> List[str]:
- errors = validate_desc(self)
- errors += validate_args(self)
- errors += validate_columns(self, use_data_from_sql=True)
- return errors
-
-
-# Whether the name starts with module_name.
-def validate_name(name: str, module: str, upper: bool = False) -> List[str]:
- module_pattern = f"^{module}_.*"
- if upper:
- module_pattern = module_pattern.upper()
- starts_with_module_name = re.match(module_pattern, name)
- if module == "common":
- if starts_with_module_name:
- return [(f"Invalid name in module {name}. "
- f"In module 'common' the name shouldn't "
- f"start with '{module_pattern}'.\n")]
- else:
- if not starts_with_module_name:
- return [(f"Invalid name in module {name}. "
- f"Name has to begin with '{module_pattern}'.\n")]
- return []
-
-
-# Whether the only typed comment in provided comment segment is of type
-# `comment_type`.
-def validate_typed_comment(
- comment_segment: str,
- comment_type: str,
- docs: Union["TableViewDocs", "FunctionDocs", "ViewFunctionDocs"],
-) -> List[str]:
- for line in comment_segment:
- # Ignore only '--' line.
- if line == "--":
- continue
-
- m = re.match(typed_comment_pattern(), line)
-
- # Ignore untyped lines
- if not m:
- continue
-
- line_type = m.group(1)
-
- if line_type != comment_type:
- return [(
- f"Wrong comment type. Expected '{comment_type}', got '{line_type}'.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
- return []
-
-
-# Whether comment segment with description of the object contains content.
-def validate_desc(
- docs: Union["TableViewDocs", "FunctionDocs", "ViewFunctionDocs"]
-) -> List[str]:
- for line in docs.desc:
- if line == "--":
- continue
- return []
- return [(f"Missing documentation for {docs.name}\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
-
-
-# Whether comment segment about columns contain proper schema. Can be matched
-# against parsed SQL data by setting `use_data_from_sql`.
-def validate_columns(docs: Union["TableViewDocs", "ViewFunctionDocs"],
- use_data_from_sql=False) -> List[str]:
- errors = validate_typed_comment(docs.columns, "column", docs)
-
- if errors:
- return errors
-
- if use_data_from_sql:
- cols_from_sql = docs.data_from_sql["columns"]
-
- for line in docs.columns:
- # Ignore only '--' line.
- if line == "--" or not line.startswith("-- @column"):
- continue
-
- # Look for '-- @column' line as a column description
- m = re.match(column_pattern(), line)
- if not m:
- errors.append(f"Wrong column description.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")
- continue
-
- if not use_data_from_sql:
- return errors
-
- col_name = m.group(1)
- if col_name not in cols_from_sql:
- errors.append(f"There is no argument '{col_name}' specified in code.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")
- continue
-
- cols_from_sql.pop(col_name)
-
- if not use_data_from_sql:
- errors.append(f"Missing columns for {docs.name}\n{docs.path}\n")
- return errors
-
- if not cols_from_sql:
- return errors
-
- errors.append(
- f"Missing documentation of columns: {list(cols_from_sql.keys())}.\n"
- f"'{docs.name}' in {docs.path}:\n")
- return errors
-
-
-# Whether comment segment about columns contain proper schema. Matches against
-# parsed SQL data.
-def validate_args(docs: "FunctionDocs") -> List[str]:
- errors = validate_typed_comment(docs.args, "arg", docs)
-
- if errors:
- return errors
-
- args_from_sql = docs.data_from_sql["args"]
- for line in docs.args:
- # Ignore only '--' line.
- if line == "--" or not line.startswith("-- @"):
- continue
-
- m = re.match(args_pattern(), line)
- if m is None:
- errors.append("The arg docs formatting is wrong. It should be:\n"
- "-- @arg [a-z_]* [A-Z]* {desc}\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")
- return errors
-
- arg_name, arg_type = m.group(1), m.group(2)
- if arg_name not in args_from_sql:
- errors.append(f"There is not argument '{arg_name}' specified in code.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")
- continue
-
- arg_type_from_sql = args_from_sql.pop(arg_name)
- if arg_type != arg_type_from_sql:
- errors.append(f"In the code, the type of '{arg_name}' is "
- f"'{arg_type_from_sql}', but according to the docs "
- f"it is '{arg_type}'.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-
- if not args_from_sql:
- return errors
-
- errors.append(
- f"Missing documentation of args: {list(args_from_sql.keys())}.\n"
- f"{docs.path}\n")
- return errors
-
-
-# Whether comment segment about return contain proper schema. Matches against
-# parsed SQL data.
-def validate_ret(docs: "FunctionDocs") -> List[str]:
- errors = validate_typed_comment(docs.ret, "ret", docs)
- if errors:
- return errors
-
- ret_type_from_sql = docs.data_from_sql["ret"]
-
- for line in docs.ret:
- # Ignore only '--' line.
- if line == "--" or not line.startswith("-- @ret"):
- continue
-
- m = re.match(function_return_pattern(), line)
- if m is None:
- return [("The return docs formatting is wrong. It should be:\n"
- "-- @ret [A-Z]* {desc}\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
- docs_ret_type = m.group(1)
- if ret_type_from_sql != docs_ret_type:
- return [(f"The return type in docs is '{docs_ret_type}', "
- f"but it is {ret_type_from_sql} in code.\n"
- f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
- return []
-
-
-# Parses string with multiple arguments with type separated by comma into dict.
-def parse_args(args_str: str) -> tuple[dict, List[str]]:
- errors = []
- args = {}
- for arg_str in args_str.split(","):
- m = re.match(arg_str_pattern(), arg_str)
- if m is None:
- errors.append(f"Wrong arguments formatting for '{arg_str}'\n")
- continue
- args[m.group(1)] = m.group(2)
- return args, errors
-
-
-# After matching file to pattern, fetches and validates related documentation.
-def validate_docs_for_sql_object_type(path: str, module: str, sql: str,
- pattern: str, docs_object: type):
- errors = []
- line_id_to_match = match_pattern(pattern, sql)
- lines = sql.split("\n")
- for line_id, matches in line_id_to_match.items():
- # Fetch comment by looking at lines over beginning of match in reverse
- # order.
- comment = fetch_comment(lines[line_id - 1::-1])
- docs, obj_errors = docs_object.create_from_comment(path, comment, module,
- matches)
- errors += obj_errors
- if docs:
- errors += docs.check_comment()
-
- return errors
-
-
-def check(path: str):
- errors = []
-
- # Get module name
- module_name = path.split("/stdlib/")[-1].split("/")[0]
-
- with open(path) as f:
- sql = f.read()
-
- errors += validate_docs_for_sql_object_type(path, module_name, sql,
- create_table_view_pattern(),
- TableViewDocs)
- errors += validate_docs_for_sql_object_type(path, module_name, sql,
- create_function_pattern(),
- FunctionDocs)
- errors += validate_docs_for_sql_object_type(path, module_name, sql,
- create_view_function_pattern(),
- ViewFunctionDocs)
- return errors
+from python.generators.stdlib_docs.stdlib import *
def main():
+
errors = []
- metrics_sources = os.path.join(ROOT_DIR, "src", "trace_processor", "stdlib")
+ metrics_sources = os.path.join(FILE_DIR, "src", "trace_processor", "stdlib")
for root, _, files in os.walk(metrics_sources, topdown=True):
for f in files:
path = os.path.join(root, f)
if path.endswith(".sql"):
- errors += check(path)
+ with open(path) as f:
+ sql = f.read()
+ errors += parse_file_to_dict(path, sql)[1]
sys.stderr.write("\n\n".join(errors))
return 0 if not errors else 1