mehen-sql) introduces a new sql.* metric namespace tailored to
standalone .sql files — ad-hoc queries, analytics models, migration scripts, stored-program
bodies, DDL packages, and mixed scripts. It is backed by the
sqruff dialect-aware SQL parser.
SQL files (
.sql, .ddl, .dml) are analyzed automatically. The dialect is inferred from syntax
hints with a reported confidence (sql.dialect.confidence), falling back to ANSI. mehen compiles
ANSI, postgres, T-SQL, snowflake, bigquery, mysql, sqlite, oracle, clickhouse, redshift, sparksql,
athena, and db2.Selecting a dialect
Dialect inference is a best-effort guess. To pin a file’s dialect deterministically, add an in-file directive on its own line, using the same syntax as SQLFluff’s in-file configuration:sql.dialect.confidence is reported as 1.0,
sql.dialect.directive_present is 1, and sql.dialect.is_<dialect> reflects the pinned dialect.
mehen mirrors SQLFluff’s parsing of this directive:
- Both
-- sqlfluff:dialect:<name>and--sqlfluff:dialect:<name>(no space) are accepted. Whitespace around thedialect:<name>separator is ignored, but thesqlfluff:prefix itself must be exact. - The directive must start at the beginning of the line — an indented directive is ignored (this matches SQLFluff).
- Only
--line comments are honored; block comments (/* sqlfluff:dialect:… */) are not. - If several directives appear, the last one wins.
- An unknown dialect name (
sql.dialect.unknown) or one not compiled into this build such asdatabricks,duckdb, ortrino(sql.dialect.unsupported) emits a non-blocking warning and falls back to inference — it never aborts the analysis.
sqruff (mehen’s parser) does not itself consume SQLFluff in-file configuration — it silently
ignores the directive (and older builds panic on some inline-config forms). mehen therefore parses
the
dialect directive itself and only ever hands sqruff a resolved, validated dialect. One
intentional divergence from SQLFluff: SQLFluff matches dialect names case-sensitively, and so
does mehen — -- sqlfluff:dialect:Postgres (capital P) is reported as an unknown dialect, exactly as
SQLFluff would reject it.Why SQL gets its own family
SQL should not be squeezed into the existing function/class-centric metric model. The dominant complexity mechanism in standalone SQL is relational/dataflow structure rather than imperative control flow:- Cyclomatic complexity is meaningful for procedural PL/SQL or T-SQL, but not for ordinary declarative SELECT-heavy files.
- A SELECT with 10 joins and 5 CTEs may have no imperative branches while still being difficult to review.
- Object-touch risk (DROP, TRUNCATE, MERGE without WHERE) often dominates “review burden” in migration scripts.
Metric namespaces
Thesesql.* keys are published today (raw metrics — research foundation §15):
| Namespace | Keys |
|---|---|
sql.loc.* | physical, code, comment, blank, logical, comment_density, max_statement_lines, avg_statement_lines. |
sql.statement.* | count, kind_count.<kind>, kind_distinct, kind_entropy, unparsed_count. |
sql.query_block.* | count, max_depth, avg_select_items, max_select_items. |
sql.cte.* | count, recursive_count, dependency_edges, max_dependency_depth, max_fan_out, unused_count. |
sql.join.* | count, kind_count.<kind>, outer_count, cross_count, natural_count, non_equi_count, missing_condition_count. |
sql.subquery.*, sql.derived_table.* | count, max_depth, correlated_count, scalar_count, exists_count, in_count. |
sql.predicate.* | boolean_operator_count, max_boolean_depth, not_count, comparison_count, null_semantics_risk_count. |
sql.case.* | count, max_depth, when_count, max_when_count, missing_else_count. |
sql.aggregate.*, sql.group_by.*, sql.having.* | function_count, distinct_count, count, rollup_count, cube_count, grouping_sets_count. |
sql.window.* | function_count, frame_count, partition_expression_count, order_expression_count. |
sql.set_op.* | count, kind_count.<kind>, union_all_ratio. |
sql.expression.*, sql.function.*, sql.cast.* | max_depth, call_count, distinct_count, nested_call_depth, count. |
sql.select.* | star_count, outer_star_count, expression_without_alias_count, output_alias_coverage. |
sql.identifier.*, sql.alias.*, sql.relation.* | unqualified_column_ratio, quoted_count, table_alias_count, ref_count. |
sql.object.*, sql.dml.*, sql.ddl.*, sql.dcl.*, sql.transaction.* | Object-touch and migration-risk counts (read_count, write_count, drop_count, truncate_count, update_without_where_count, …). |
sql.dialect.* | confidence, conflict_count, requested, directive_present, is_<dialect>. |
sql.parser.* | diagnostic_count, unparsable_segment_count, unparsable_line_count, unparsable_ratio. |
sql.halstead.* | distinct_operators, distinct_operands, total_operators, total_operands, vocabulary, length, volume, difficulty, effort. |
sql.procedural.* — cyclomatic/cognitive complexity for PL/SQL and T-SQL
routines) remain on the roadmap.
Composite scores
All six explainable composite scores ship today (research foundation §8):sql.structural_complexity— CTE depth, join count, subquery depth, CASE depth, window count, set op count.sql.cognitive_complexity— SQL analogue of code cognitive complexity.sql.review_burden_index— file-level rank (0–100) for likely PR review effort.sql.change_risk_score— operational risk in migration scripts.sql.maintainability_index— composite (0–100, higher is better) with band interpretation.sql.modularity_health— CTE use ratio, fan-out, derived-table penalty (0–100; N/A without CTEs).
Prior art and scientific basis
The metric model is informed by the following work:- SonarQube PL/SQL and T-SQL — defines cyclomatic complexity for procedural blocks (anonymous
blocks, procedures, triggers, loops,
WHEN,IF/ELSIF,RAISE,AND/OR). PL/SQL docs · T-SQL docs. - SQLFluff and sqruff — dialect-aware parsing and linting; their structure rules (nested-CASE, unused-CTE, ambiguous-column-count, qualification, implicit cross-join) are reusable inspiration for metric contributors. SQLFluff docs · sqruff docs.
sqlfluff-complexityplugin — practical baseline for CPX-style metrics: CTE count, join count, nested subquery depth, CASE expressions, boolean operators, window functions, CTE dependency depth, set operations, derived tables. Repo.- Vashistha & Jain — Measuring Query Complexity in SQLShare Workload — frames query complexity as cognitive load on users authoring SQL, with operators / operands / runtime / Halstead-style measures. PDF.
- Piattini & Martínez — Measuring for Database Programs Maintainability — early SQL maintainability measures with empirical validation. DOI.
- Spider — text-to-SQL benchmark; its hardness criteria (number of components, selections,
conditions, keywords like
GROUP BY/ nested subqueries / aggregators) align well with static query complexity features. arXiv:1809.08887 · Benchmark site.
See also
- Code metrics — the existing source-code suite.
- Markdown metrics — the existing documentation suite.
- Concepts → Spaces — how SQL spaces will fit the existing model.