Skip to content

Commit 5210884

Browse files
committed
make+tools: add custom sqlfluff plugin
1 parent 05fb8a5 commit 5210884

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ DOCKER_TOOLS = docker run \
5757
-v $(shell bash -c "mkdir -p /tmp/go-lint-cache; echo /tmp/go-lint-cache"):/root/.cache/golangci-lint \
5858
-v $$(pwd):/build taproot-assets-tools
5959

60+
DOCKER_SQLFLUFF = docker run --rm --user "$$UID:$$(id -g)" -e UID=$$UID \
61+
-v $(shell pwd):/sql sqlfluff-builder
62+
6063
GO_VERSION = 1.23.9
6164

6265
GREEN := "\\033[0;32m"
@@ -149,6 +152,10 @@ docker-tools:
149152
@$(call print, "Building tools docker image.")
150153
docker build -q -t taproot-assets-tools $(TOOLS_DIR)
151154

155+
docker-sqlfluff:
156+
@$(call print, "Building sqlfluff docker image.")
157+
docker build -q -t sqlfluff-builder tools/sqlfluff
158+
152159
scratch: build
153160

154161
# ===================
@@ -268,6 +275,12 @@ sqlc-check: sqlc
268275
exit 1; \
269276
fi
270277

278+
sql-lint: docker-sqlfluff
279+
$(DOCKER_SQLFLUFF) lint tapdb/sqlc/migrations/* tapdb/sqlc/queries/*
280+
281+
sql-fix: docker-sqlfluff
282+
$(DOCKER_SQLFLUFF) fix tapdb/sqlc/migrations/* tapdb/sqlc/queries/*
283+
271284
rpc:
272285
@$(call print, "Compiling protos.")
273286
cd ./taprpc; ./gen_protos_docker.sh

tools/sqlfluff/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM sqlfluff/sqlfluff:latest
2+
3+
COPY sqlfluff_plugin_ref_type/ /plugins/sqlfluff_plugin_ref_type/
4+
5+
ENV PYTHONPATH="/plugins/sqlfluff_plugin_ref_type"
6+
7+
USER root
8+
9+
RUN cd /plugins/sqlfluff_plugin_ref_type \
10+
chown -R 5000 . \
11+
&& chmod -R 777 /plugins /sql \
12+
&& pip install -e .
13+
14+
USER 5000
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[build-system]
2+
requires = ["setuptools>=40.8.0", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "sqlfluff_plugin_ref_type"
7+
version = "1.0.0"
8+
requires-python = ">=3.9"
9+
dependencies = [
10+
"sqlfluff>=3.1.0"
11+
]
12+
13+
[project.entry-points.sqlfluff]
14+
sqlfluff_plugin_ref_type = "sqlfluff_plugin_ref_type"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""A plugin that checks for forbidden foreign key types in SQL
2+
3+
This uses the rules API supported from 0.4.0 onwards.
4+
"""
5+
6+
from typing import Any
7+
8+
from sqlfluff.core.config import load_config_resource
9+
from sqlfluff.core.plugin import hookimpl
10+
from sqlfluff.core.rules import BaseRule, ConfigInfo
11+
12+
13+
@hookimpl
14+
def get_rules() -> list[type[BaseRule]]:
15+
"""Get plugin rules.
16+
"""
17+
# i.e. we DO recommend importing here:
18+
from .rules import Rule_LL01 # noqa: F811
19+
20+
return [Rule_LL01]
21+
22+
23+
@hookimpl
24+
def load_default_config() -> dict[str, Any]:
25+
"""Loads the default configuration for the plugin."""
26+
return {}
27+
28+
29+
@hookimpl
30+
def get_configs_info() -> dict[str, dict[str, ConfigInfo]]:
31+
"""Get rule config validations and descriptions."""
32+
return {}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""A custom rule that checks for forbidden INTEGER foreign keys.
2+
3+
This uses the rules API supported from 0.4.0 onwards.
4+
"""
5+
6+
from sqlfluff.core.rules import (
7+
BaseRule,
8+
LintResult,
9+
RuleContext,
10+
)
11+
from sqlfluff.core.rules.crawlers import SegmentSeekerCrawler
12+
from sqlfluff.utils.functional import FunctionalContext, sp
13+
14+
15+
# These two decorators allow plugins
16+
# to be displayed in the sqlfluff docs
17+
class Rule_LL01(BaseRule):
18+
"""INTEGER ... REFERENCES is forbidden! Use BIGINT instead.
19+
20+
**Anti-pattern**
21+
22+
Using ``INTEGER ... REFERENCES`` in a foreign key definition is creating
23+
a mismatch between the actual data type of the referenced primary key and
24+
the foreign key. See ``scripts/gen_sqlc_docker.sh`` for a long-form
25+
explanation.
26+
27+
.. code-block:: sql
28+
29+
transfer_id INTEGER NOT NULL REFERENCES asset_transfers (id),
30+
31+
**Best practice**
32+
33+
If the primary key of the referenced table is of type
34+
``INTEGER PRIMARY KEY``, it will be transformed to ``BIGSERIAL`` in
35+
PostgreSQL. Therefore, the foreign key should also be of type ``BIGINT``.
36+
For SQLite both INTEGER and BIGINT are equivalent, so this rule is for
37+
PostgreSQL.
38+
39+
.. code-block:: sql
40+
41+
transfer_id BIGINT NOT NULL REFERENCES asset_transfers (id),
42+
"""
43+
44+
groups = ("all",)
45+
crawl_behaviour = SegmentSeekerCrawler({"column_definition"})
46+
is_fix_compatible = True
47+
48+
def __init__(self, *args, **kwargs):
49+
"""Overwrite __init__ to set config."""
50+
super().__init__(*args, **kwargs)
51+
52+
def _eval(self, context: RuleContext):
53+
"""We should not use INTEGER ... REFERENCES."""
54+
has_integer = False
55+
has_reference = False
56+
data_type_segment = None
57+
58+
for child in context.segment.segments:
59+
if child.type == "data_type":
60+
# Check if any part of data_type is INTEGER.
61+
for part in child.segments:
62+
if part.raw.upper() == "INTEGER":
63+
has_integer = True
64+
data_type_segment = part
65+
elif child.type == "column_constraint_segment":
66+
# Look for REFERENCES keyword inside constraints.
67+
for subpart in child.segments:
68+
if "REFERENCES" in subpart.raw.upper():
69+
has_reference = True
70+
71+
if has_integer and has_reference:
72+
return LintResult(
73+
anchor=data_type_segment,
74+
description="Must use BIGINT instead of INTEGER as a data "
75+
"type in foreign key references. See "
76+
"scripts/gen_sqlc_docker.sh for explanation.",
77+
)
78+
79+
return None

0 commit comments

Comments
 (0)