Skip to main content

sqlfluff-plugin-conventions

CI License: MIT

Config-driven team conventions for SQLFluff: enforce the rules your team actually agreed on — column comments and comment quality, type-aware column naming, object naming, SELECT * bans, and re-runnability anti-patterns — in the same lint pass as everything else.

The plugin ships no opinions of its own. Every rule is inert until your .sqlfluff config switches it on and supplies the patterns. What a column should be called is a decision for your team, not for a linter.

Install

Requires Python 3.10+ and SQLFluff 4.3.0 or newer.

pip install sqlfluff-plugin-conventions

For development, see "Working on the plugin" below.

Quickstart

Copy an example config to your project root and lint:

cp examples/snake-case-team.sqlfluff .sqlfluff
sqlfluff lint models/

Or point at a config without renaming it:

sqlfluff lint models/ --config examples/comments-quality.sqlfluff

examples/demo.sql is a deliberately non-compliant file to see the output:

sqlfluff lint examples/demo.sql --config examples/snake-case-team.sqlfluff

The examples are starting points, not recommendations: snake-case-team.sqlfluff and hungarian-team.sqlfluff express opposite naming conventions with the same rules, and there is a test asserting they disagree.

On an existing codebase, expect a backlog the first time you enable a rule: measured over 867 published Databricks SQL files, no_select_star and require_comment each fire on roughly a third of files, while type-aware naming fires on about 5% (it only sees declared types). The governance rules are broader still: require_qualified_names fires on 273 files and require_constraints on 94, while the predicate-free DML guards (delete_without_where, update_without_where) fire on fewer than five files each — worth enabling everywhere. Roll out rule by rule with sqlfluff lint --rules, or start by enforcing them on new files only. Run scripts/corpus_check.py against your own code to see what a config will find before you commit to it.

Rules

Code Name What it checks Key config
Conventions_N001 conventions.type_naming Column name matches a regex chosen by its declared type type_patterns, type_origins
Conventions_N002 conventions.identifier_case snake / upper_snake / camel / pascal case_convention, case_columns, case_tables
Conventions_N003 conventions.identifier_length Maximum column-name length max_identifier_length
Conventions_N004 conventions.forbidden_name Names matching banned patterns, with a reason forbidden_patterns
Conventions_N005 conventions.object_name Per-kind naming for tables, views, streaming tables, materialized views object_patterns
Conventions_N006 conventions.require_qualified_names Created objects must be schema-qualified qualified_name_min_parts
Conventions_M001 conventions.require_comment Comments exist and are meaningful require_table_comments, require_column_comments, comment_min_length, comment_forbidden_patterns
Conventions_M002 conventions.require_table_properties Required TBLPROPERTIES keys and values required_property_keys, required_property_values
Conventions_M003 conventions.require_table_provider USING allowlist allowed_providers, require_explicit_provider
Conventions_M004 conventions.comment_quality Comment score from your own function clears a threshold comment_score_function, comment_score_threshold
Conventions_M005 conventions.require_table_design CLUSTER BY / PARTITIONED BY declared require_cluster_by, require_partition_by, allow_cluster_by_auto
Conventions_M006 conventions.require_constraints PRIMARY KEY / NOT NULL present require_primary_key, require_not_null
Conventions_T001 conventions.type_policy Forbidden types; explicit parameters forbidden_types, types_requiring_parameters
Conventions_A001 conventions.no_select_star SELECT * (optionally allowing t.*) force_enable, allow_qualified_star
Conventions_A002 conventions.drop_requires_if_exists DROP … IF EXISTS force_enable
Conventions_A003 conventions.insert_requires_column_list Named INSERT columns or BY NAME force_enable
Conventions_A004 conventions.delete_without_where Predicate-free DELETE force_enable
Conventions_A005 conventions.update_without_where Predicate-free UPDATE force_enable

Rules with no patterns of their own are off until force_enable = True in their config section. The rest are inert until their patterns or booleans are set. sqlfluff rules lists them all; run sqlfluff rules --verbose for full configuration docs.

sqruff compatibility

None, by design. sqruff has no plugin system and a fixed Rust rule set; its configuration files are .sqruff / sqruff.toml / pyproject.toml, and it does not read .sqlfluff. These rules cannot run there, and porting them into sqruff would duplicate work its SQLFluff replay already does.

If you want sqruff's speed and these conventions, run both in the same CI: sqruff for formatting and core style, SQLFluff for the convention rules.

sqruff lint .
sqlfluff lint . --rules Conventions_N001,Conventions_M001

Type-aware naming, the unusual one

conventions.type_naming maps a canonical datatype to a regex the column name must match:

[sqlfluff:rules:conventions.type_naming]
type_patterns = BOOLEAN=_ind$, DATE=_date$, TIMESTAMP=_timestamp$
type_origins = declaration, cast

Types are canonical, so one entry covers every spelling: INT and INTEGER are the same, as are TIMESTAMP_NTZ/TIMESTAMP, VARCHAR/CHAR/STRING, DECIMAL/NUMERIC/DEC/parameters. Patterns are matched with search, so anchor with ^ and $ as needed.

The rule resolves a type only where the file itself states one: a CREATE TABLE column declaration, a view column list (which carries no type), or an explicit CAST(x AS T) AS name / x::T AS name. A column like total AS revenue has no locally knowable type and is skipped — the rule never guesses, because a guess is invisible in the diagnostic and a wrong report looks like a real finding.

Two ways to write structured config

INI values are strings, so mappings and lists accept either a readable spelling or JSON. The JSON spelling is the escape hatch when a regex contains , or =:

[sqlfluff:rules:conventions.type_naming]
type_patterns = BOOLEAN=_ind$, DATE=_date$

[sqlfluff:rules:conventions.require_table_properties]
required_property_values = {"quality": "gold", "owner": "data"}

[sqlfluff:rules:conventions.require_comment]
comment_forbidden_patterns = ["^(todo|tbd|n/?a)$", "the .*"]

Malformed values raise a config error naming the offending text; nothing is silently dropped.

Comment scoring: bring your own function

conventions.comment_quality has no scorer of its own. You write a function that takes a comment and returns a number between 0 and 1; the rule reports every existing comment that scores below your threshold. Requiring a comment at all is still require_comment — the two rules coexist, and enabling both means a bad comment is reported by both.

# tools/scorers.py
from sqlfluff_plugin_conventions.scoring import CommentContext, CommentScore


def decent(comment: str) -> float:
    """One-liner scorers just take the text."""
    return min(1.0, len(comment.split()) / 4)


def not_just_the_name(ctx: CommentContext) -> CommentScore:
    """Annotate the argument as CommentContext for name, datatype and more."""
    if ctx.name.lower() in ctx.comment.lower():
        return CommentScore(0.0, "comment only restates the column name")
    return CommentScore(1.0)
[sqlfluff]
rules = Conventions_M004

[sqlfluff:rules:conventions.comment_quality]
comment_score_function = tools/scorers.py:not_just_the_name
comment_score_threshold = 0.6
comment_score_columns = True
comment_score_tables = False

comment_score_function accepts three spellings:

  • path/to/scorers.py:function — a plain script in your repo
  • package.module:function — any installed module
  • an entry-point name from the group sqlfluff_conventions.comment_scorers, for scorers shipped as packages

Exactly one scorer runs per rule instance. Compose your own: a function can call other functions and return CommentScore(value, notes) to attach its reasoning to the finding.

A scorer that cannot be loaded, raises, returns a non-number, or returns a value outside 0–1 fails the lint run with the scorer's name in the message. That is deliberate: a comment scorer that silently does nothing makes a clean report a lie.

Scorers are code you install or name in config, which is the same trust model as SQLFluff plugins themselves — running SQLFluff already executes arbitrary code from installed packages. The file-path spelling means a .sqlfluff from an untrusted source can name a script to import, so treat config as code. For shared or hardened CI, set SQLFLUFF_CONVENTIONS_NO_FILE_SCORERS=1 to refuse file-path scorers entirely. Nothing is fetched or eval'd from a string.

Writing your own rules

semantics.py is a public API. It turns any SQLFluff parse tree into a small model — Table, Column, SelectStar — so you can write team rules without learning each dialect's segment names:

from sqlfluff.core.rules import BaseRule
from sqlfluff.core.rules.crawlers import RootOnlyCrawler
from sqlfluff_plugin_conventions.semantics import analyse


class Rule_MyTeam_X001(BaseRule):
    """Every table must have at least one column."""

    name = "myteam.at_least_one_column"
    groups = ("all",)

    crawl_behaviour = RootOnlyCrawler()

    def _eval(self, context):
        results = []
        for table in analyse(context.segment).tables:
            if not table.columns:
                results.append(self._result(anchor=table.segment))  # your wording
        return results or None

Columns carry name, canonical_type, raw_type, comment, origin (declaration / cast / alias), primary_key, not_null and the segment to anchor a violation on. Tables carry kind, provider, properties, comment, primary_key, cluster_by, cluster_by_auto and partitioned_by. Comments declared later with COMMENT ON or ALTER TABLE … COMMENT are folded in before rules run. The API is versioned with the package: it changes only with a minor release.

Working on the plugin

python -m venv .venv
.venv/bin/pip install -e ../sqlfluff -e ".[dev]"   # fork checkout, or any sqlfluff
.venv/bin/python -m pytest --cov=sqlfluff_plugin_conventions --cov-fail-under=100
.venv/bin/ruff check src/ test/ scripts/
.venv/bin/mypy
.venv/bin/mutmut run && .venv/bin/python scripts/mutation_score.py --floor 75
.venv/bin/sqlfluff rules | grep Conventions

Test line and branch coverage are held at 100% with no pragmas, and mutation testing runs weekly (and on PRs touching src/) with a 75% floor that ratchets upward — the remaining survivors are listed by the job so they can be burnt down deliberately. The dev toolchain is hash-pinned in requirements-dev.txt (pip-compile --allow-unsafe --generate-hashes --extra dev); CI installs from it, so a compromised or yanked dependency cannot silently change a build. CodeQL and OpenSSF Scorecard run on every push, releases carry an SPDX SBOM and PEP 740 provenance attestations, and the dependency tree is audited with pip-audit.

The rule tests are YAML cases under test/rules/test_cases/, one file per rule, using SQLFluff's own sqlfluff.utils.testing harness.

Measuring against a corpus

scripts/corpus_check.py lints a directory of real SQL with one of the example configs and counts findings per rule. It exists to answer "how noisy is this rule on published SQL?", and it always lints a deliberately broken control first so a harness that loads nothing cannot report success:

.venv/bin/python scripts/corpus_check.py --config examples/snake-case-team.sqlfluff

Licence

MIT.

Metadata

Release files for sqlfluff-plugin-conventions 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sqlfluff-plugin-conventions 0.2.0
File Size Uploaded Provenance Actions
sqlfluff_plugin_conventions-0.2.0.tar.gz 51.9 kB With provenance Details for sqlfluff_plugin_conventions-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sqlfluff-plugin-conventions 0.2.0
File Interpreter ABI Platform Actions
sqlfluff_plugin_conventions-0.2.0-py3-none-any.whl Python 3 none any Details for sqlfluff_plugin_conventions-0.2.0-py3-none-any.whl

Total release size: 89.0 kB

Release files / sqlfluff_plugin_conventions-0.2.0.tar.gz

Download URL sqlfluff_plugin_conventions-0.2.0.tar.gz
Size 51.9 kB
Tags Source
SHA-256 checksum
How to use checksums
5bae0b88e248079cd4e181762ef0f18b6e9b7fccd19de67a15f32ef1f196fcfd
BLAKE2b-256 checksum
How to use checksums
f1c5485f439c5d16b331d1c5c4c6d391e9b2366e5337b329a9628d3e29a9e460
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / sqlfluff_plugin_conventions-0.2.0-py3-none-any.whl

Download URL sqlfluff_plugin_conventions-0.2.0-py3-none-any.whl
Size 37.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
84db0b2df20d10d5344de5a5ee921b97a76cba0e3990ba6596c484fcc194d176
BLAKE2b-256 checksum
How to use checksums
fae093f8475e7ce3a64c1e5df9d6fe6cc27a187c3f8301e60322f6e9bfd18b6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Security for sqlfluff-plugin-conventions 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sqlfluff_plugin_conventions-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page