基于Bazel + SQLFluff实现SQL lint

news/2024/7/9 21:20:53 标签: sql, 数据库, hibernate, postgresql, java

e783eba99dae11fa88f8b5bea47c5f86.jpeg

背景

SQL进行版本化控制后,我们希望为SQL加入lint步骤。这样做的好处是我们可以在真正执行SQL前发现问题。

本文中,我们通过Bazel执行SQLFluff[1]以实现SQL的lint。

SQLFluff是一款使用Python语言使用的,支持SQL多方言的SQL lint工具。

它的特点是:

  1. 1. 支持多方言。如:Snowflake、PostgreSQL、ClickHouse。所有支持的方言列表:https://docs.sqlfluff.com/en/stable/dialects.html;

  2. 2. 可以输出正确的SQL,减少了我们手工修正SQL的工作;

  3. 3. 同时支持命令行方式使用和API调用方式。

集成到CI/CD流水线中

在我看来,在CICD流水线中实现SQL lint有两种方式:

  • • 方式一:在流水线中增加一个SQL lint步骤;

  • • 方式二:将SQL lint的逻辑写在测试代码,执行测试步骤,就自动执行了SQL lint。

方式二是我最爱,我会在本文最后讲原因。

工程结构

.
├── BUILD.bazel
├── WORKSPACE
├── repository-hibernate-impl
│   ├── BUILD.bazel
│   └── src
│       ├── main
│       │   └── sql
│       │       └── V1__runbook_table.sql
│       └── test
│           └── python
│               ├── BUILD.bazel
│               ├── requirements_lock.txt
│               └── sql_test.py

步骤1: 在WORKSPACE中增加Python外部依赖

本文中我们使用的是Bazel 5.4.0,所以还在使用WORKSPACE定义外部依赖

http_archive(  
    name = "rules_python",  
    sha256 = "a644da969b6824cc87f8fe7b18101a8a6c57da5db39caa6566ec6109f37d2141",  
    strip_prefix = "rules_python-0.20.0",  
    url = "https://github.com/bazelbuild/rules_python/releases/download/0.20.0/rules_python-0.20.0.tar.gz",  
)  
  
load("@rules_python//python:repositories.bzl", "py_repositories")  
  
  
py_repositories()  
  
load("@rules_python//python:repositories.bzl", "python_register_toolchains")  
  
python_register_toolchains(  
    name = "python3_11",  
    python_version = "3.11",  
)  
  
load("@python3_11//:defs.bzl", interpreter_3_11 = "interpreter")  
  
load("@rules_python//python:pip.bzl", "pip_parse")  
  
# Create a central repo that knows about the dependencies needed from  
# requirements_lock.txt.  
pip_parse(  
   name = "pip_deps",  
   python_interpreter_target = interpreter_3_11,  
   requirements_lock = "//repository-hibernate-impl/src/test/python:requirements_lock.txt",  
)  
# Load the starlark macro which will define your dependencies.  
load("@pip_deps//:requirements.bzl", "install_deps")  
# Call it to define repos for your requirements.  
install_deps()

步骤2: 定义SQLFluff依赖

requirements_lock.txt的内容如下:

sqlfluff==2.0.5  
Jinja2==3.1.2  
MarkupSafe==2.1.2  
Pygments==2.15.0  
appdirs==1.4.4  
chardet==5.1.0  
click==8.1.3  
colorama==0.4.6  
diff_cover==7.5.0  
iniconfig==2.0.0  
packaging==23.1.0  
pathspec==0.11.1  
pluggy==1.0.0  
pytest==7.3.1  
tomli==2.0.1  
toml==0.10.2  
exceptiongroup==1.1.1  
pyyaml==6.0  
regex===2023.3.23  
tblib==1.7.0  
tqdm==4.65.0  
typing_extensions==4.5.0

步骤3: 定义BUILD目标

load("@pip_deps//:requirements.bzl", "requirement")  
load("@rules_python//python:defs.bzl", "py_test")  
  
py_test(  
    name = "sql_test",  
    srcs = ["sql_test.py"],  
    # data传入是sql的label
    data = [ "//repository-hibernate-impl:sqlTest",],  
    deps = [  
       requirement("sqlfluff"),  
       requirement("Jinja2"),  
       requirement("MarkupSafe"),  
       requirement("Pygments"),  
       requirement("appdirs"),  
       requirement("chardet"),  
       requirement("click"),  
       requirement("colorama"),  
       requirement("diff_cover"),  
       requirement("iniconfig"),  
       requirement("packaging"),  
       requirement("pathspec"),  
       requirement("pluggy"),  
       requirement("pytest"),  
       requirement("tomli"),  
       requirement("toml"),  
       requirement("exceptiongroup"),  
       requirement("pyyaml"),  
       requirement("regex"),  
       requirement("tblib"),  
       requirement("tqdm"),  
       requirement("typing_extensions"),  
    ],  
)

注:sql的BUILD目标(repository-hibernate-impl/BUILD.bazel)为:

filegroup(  
    name = "sqlTest",  
    testonly = 1,  
    srcs = glob(["src/main/sql/*.sql"]),  
    visibility = ["//visibility:public"],  
)

步骤4: 调用SQLFluff实现SQL lint

import unittest  
import sqlfluff  
import os  
import codecs  
  
sqls_path = os.path.join(os.getcwd(), "repository-hibernate-impl/src/main/sql/")  
  
dialect = "postgres"  
  
class TestSum(unittest.TestCase):  
    def test_lint_sql(self):  
        sql_dir_files = os.listdir(sqls_path)  
        # 确保目录中有sql文件
        self.assertTrue(len(sql_dir_files) > 0)  
        for sql_filename in sql_dir_files:  
            if sql_filename.endswith(".sql"):  
                f = codecs.open(os.path.join(sqls_path, sql_filename), "r", "utf-8")  
                sql_content = f.read()  
                lint_result = sqlfluff.lint(sql_content, dialect=dialect)  
                # 如果存在lint问题
                if len(lint_result) > 0:  
                    # 通过sqlfluff修复sql的问题,并返回正确的写法。
                    fix_result = sqlfluff.fix(sql_content, dialect=dialect) 
                    # 将正确的sql写法打印出来方便查看
                    print("correct sql should be: \n" + fix_result)
                self.assertEqual(len(lint_result), 0)  
  
if __name__ == "__main__":  
    unittest.main()

执行

我们只需要在工程根目录执行bazel test //...命令,就可以对SQL进行lint了。

为什么我选择方式二

选择方式二(通过Bazel实现SQL lint)原因有二:

  1. 1. 方式一需要开发人员将代码提交后,才可以解决流水线的执行,而方式二,在本地就可以执行,有利于开发人员在本地就可以实现SQL lint。

  2. 2. 方式二可以实现构建缓存(Bazel天然支持),可以节约大量的构建成本。

引用链接

[1] SQLFluff: https://github.com/sqlfluff/sqlfluff

相关文章推荐:

  • 使用Bazel构建前端Sass

  • 比构建速度,Bazel是Gradle的10倍,不服不行!!!


http://www.niftyadmin.cn/n/236151.html

相关文章

数组篇刷题总结

二分查找: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target …

AOP除了日志记录外,还可以用于缓存管理、权限控制、性能控制、异常处理

除了日志记录外,AOP还可以用于以下情况: 缓存管理:通过AOP,可以将缓存逻辑从业务逻辑中分离出来,实现缓存的统一管理和更高效的数据读取。例如,使用AOP实现缓存切面,可以在方法执行前检查缓存中…

游戏内嵌社区服务开放,助力开发者提升玩家互动与留存

华为 HMS Core 游戏内嵌社区服务提供快速访问华为游戏中心论坛能力,支持玩家直接在游戏内浏览帖子和交流互动,助力开发者扩展内容生产和触达的场景。 一、为什么要游戏内嵌社区? 二、游戏内嵌社区的典型使用场景 1、游戏内打开论坛 您可以在…

番外12:ADS导出到AD变为PCB文件

番外12:ADS导出到AD变为PCB文件并嘉立创制板 番外12:ADS导出到AD变为PCB文件,此处的示例为功率放大器! STEP 1: 从ADS导出dxf文件 打开制作好的版图文件,在原有基础上打好散热孔和固定孔,散热孔半径0.63…

微信小程序设置锚点定位,wx.pageScrollTo

微信小程序中使用 wx.pageScrollTo({)};进行页面锚点定位,一定要将根目录设置为滑动的根据。 也就是 page 要设置样式为 overflow-y: auto;指定他的高度。 在其中,设置一个容器 home 设置id。 在添加一个内容容器 container 设置类…

docker自定义镜像

文章目录一、自定义镜像1.1 镜像结构1.2 Dockerfile1.3 dockerCompose1.3.1 dockerCompose的作用1.3.2 dockerCompose的常用命令1.4 镜像仓库一、自定义镜像 1.1 镜像结构 自定义镜像通常包含三个基本部分:基础镜像、应用程序代码和配置文件。 基础镜像&#xff…

kettle链接mysql Public Key Retrieval is not allowed

kettle 报错信息页面: 出现 Public Key Retrieval 的场景可以概括为在禁用 SSL/TLS 协议传输切当前用户在服务器端没有登录缓存的情况下,客户端没有办法拿到服务器的公钥。具体的场景如下: 新建数据库用户,首次登录;数…

MapReduce原理

MapReduce 编程规范 MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为 2 个步骤,Shuffle 阶段 4 个步骤,Reduce 阶段分为 2 个步骤Map 阶段 2 个步骤 设置 InputFormat 类, 将数据切分为 Key-Value(K1和V1) 对, 输入到第二步自定义 Map 逻辑, 将第一…