」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 使用 Golang 建立可維護的 SQL 查詢

使用 Golang 建立可維護的 SQL 查詢

發佈於2024-08-22
瀏覽:748

Maintainable SQL Query Building with Golang

任何使用 SQL 查询的应用程序都可以受益于使用查询生成器来提高代码的可读性、可维护性和安全性。事实上,Golang 中有许多不同的库可以做到这一点。在 Vaunt,我们尝试了许多不同的选择,最后决定自己创建一个。最终,我们想要一些安全的东西,并提供变量替换来防止 SQL 注入,同时仍然可读并且能够有条件语句。因此,我们创建了一个名为 tqla 的新库,并于去年年底发布并宣布。您可以在本文中阅读更多相关信息。

在构建 tqla 之前,我们主要使用 Squirrel 来构建 SQL 查询逻辑——我们强烈推荐它。我们仍然在某些领域使用 Squirrel,但已逐渐开始用 tqla 替换和实现新的查询构建逻辑。我们发现许多实例表明 tqla 提高了我们维护代码和修复使用其他语句生成器时遇到的问题的能力。

现实世界用例

在 Vaunt,我们最近进行了从 CockroachDB 到 TiDB 的数据库迁移。虽然 CockroachDB 高性能且可靠,但我们最终决定添加到我们的技术堆栈中以支持 OLAP 数据库。这样做的需要是支持我们对开源社区洞察产品的分析工作量。为了保持较小的技术足迹,我们决定继续使用 TiDB 并利用该数据库的 HTAP 架构。 

CockroachDB 与 PostgreSQL 很大程度上兼容,我们的许多 SQL 查询都使用 PostgreSQL 语法。要切换到 TiDB,我们必须更改一些表并更新查询以使用 MySQL 语法。在迁移过程中的一些位置,我们发现我们不正确地使用条件查询构建语句,并且缺乏适当的测试来发现语句生成不正确。

示范

在 Squirrel 的自述文件中,有一个示例说明如何使用条件查询构建来更新带有可选过滤器的语句:

if len(q) > 0 {
    users = users.Where("name LIKE ?", fmt.Sprint("%", q, "%"))
}

这是一个真实但简化的示例,说明我们如何更新其中一个查询以有条件连接表并添加可选过滤器:

psql := squirrel.StatementBuilder.PlaceholderFormat(squirrel.Question)

statementBuilder := psql.Select(`i.id`).
From("vaunt.installations i").
Where(`entity_name = ?`, name)

if len(provider) > 0 {
    statementBuilder.Where(`provider = ?`, provider)
}

if len(repo) > 0 {
    statementBuilder.Join(`repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'))`)
    statementBuilder.Where(`r.name = ?`, repo)
}

你能发现代码的问题吗?如果没有,请不要担心——在我们运行测试之前,我们自己的代码审查也会忽略这一点。 

这里的问题是我们忘记用构建器函数的结果更新语句构建器。例如,提供者条件过滤器应改为:

if len(provider) > 0 {
    statementBuilder = statementBuilder.Where(`provider = ?`, provider)
}

这是一个相对简单的错误,通过足够的测试用例很容易发现,但由于它不是技术上无效的代码,因此可能需要一些时间才能立即意识到发生了什么。

此设置的另一个可读性问题是条件连接与初始 select 语句分开。我们可以重新组织构建器,将每个部分放在它应该去的地方,但这将需要多次重复的条件语句检查,并且仍然会遇到一些可读性问题。

使用tqla

上面使用 Squirrel 的演示已被重写,tqla 中的等效项如下所示:

t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question))
if err != nil {
    return nil, err
}

query, args, err := t.Compile(`
    SELECT i.id
    FROM vaunt.installations as i
    {{ if .Repo }}
    JOIN vaunt.repositories as r on JSON_CONTAINS(i.repositories, CONCAT('["', r.id, '"]'), '$')
    {{ end }}
    WHERE entity_name = {{ .Name}}
    {{ if .Provider }}
    AND i.provider = {{ .Provider }}
    {{ end }}
    {{ if .Repo }}
    AND r.name = {{ .Repo }}
    {{ end }}
    `, data)
if err != nil {
    return nil, err
}

如您所见,tqla 的模板语法使得合并条件子句变得非常简单。 Tqla 自动用指定的占位符替换我们设置的变量,并提供我们可以与 sql 驱动程序一起使用来执行语句的参数。

与 Squirrel 类似,这种语句构建方法很容易测试,因为我们可以创建不同的数据对象集以传递给模板构建器并验证输出。

您可以看到,我们可以轻松地将查询的条件部分添加到最适合的位置。例如,这里我们在 FROM 语句之后直接有一个条件 JOIN,尽管我们仍然有多个条件检查,但它并没有使模板过于复杂。

自定义功能

另一个很好的 tqla 功能有助于提高 sql 构建器的可维护性,它能够定义我们可以在模板中使用的自定义函数来抽象一些转换逻辑。

下面是我们如何使用函数将 Golang 的 time.Time 值转换为 sql.NullTime 的示例,以便我们无需事先转换即可对数据对象进行插入:

funcs := template.FuncMap{
    "time": func(t time.Time) sql.NullTime {
        if t.IsZero() {
            return sql.NullTime{Valid: false}
        }
        return sql.NullTime{Time: t, Valid: true}
    },
}

t, err := tqla.New(tqla.WithPlaceHolder(tqla.Question), tqla.WithFuncMap(funcs))
if err != nil {
    return err
}

通过在 tqla funcs 映射中定义此函数,我们现在可以通过向其提供来自数据对象(即 time.Time 字段)的参数来在查询模板中自由使用它。我们甚至可以在同一模板中使用不同字段多次调用此函数。

这是一个简化的示例:

statement, args, err := t.Compile(`
    INSERT INTO events
        (name, created_at, merged_at, closed_at)
    VALUES ( 
        {{ .Name }},
        {{ time .CreatedAt }},
        {{ time .MergedAt }},
        {{ time .ClosedAt }}
    )`, eventData)

结论

总之,我们相信使用 tqla 可以帮助提高查询构建逻辑的可维护性,同时为创建动态查询提供一些强大的实用程序。模板结构的简单性允许清晰的代码可读性,并且可以更快地调试任何潜在的错误。

我们将 tqla 开源以共享此库,希望它为希望以简单、可维护且安全的方式在许多不同类型的应用程序中构建 SQL 查询的其他用户提供一个很好的选择。

如果您有兴趣,请查看存储库,如果它对您有任何帮助,请给它一个星星。请随意提出任何功能请求或错误报告!

我们始终乐于接受反馈和贡献。

要了解未来的发展,请在 X 上关注我们或加入我们的 Discord!

版本聲明 本文轉載於:https://dev.to/vauntdev/maintainable-sql-query-building-with-golang-4kki?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • CSS可以根據任何屬性值來定位HTML元素嗎?
    CSS可以根據任何屬性值來定位HTML元素嗎?
    靶向html元素,在CSS 中使用任何屬性值,在CSS中,可以基於特定屬性(如下所示)基於特定屬性的基於特定屬性的emants目標元素: 字體家庭:康斯拉斯(Consolas); } 但是,出現一個常見的問題:元素可以根據任何屬性值而定位嗎?本文探討了此主題。 的目標元素有任何任何屬性值,...
    程式設計 發佈於2025-07-02
  • 在GO中構造SQL查詢時,如何安全地加入文本和值?
    在GO中構造SQL查詢時,如何安全地加入文本和值?
    在go中構造文本sql查詢時,在go sql queries 中,在使用conting and contement和contement consem per時,尤其是在使用integer per當per當per時,per per per當per. [&​​​​&&&&&&&&&&&&&&&默元組方...
    程式設計 發佈於2025-07-02
  • 為什麼使用固定定位時,為什麼具有100%網格板柱的網格超越身體?
    為什麼使用固定定位時,為什麼具有100%網格板柱的網格超越身體?
    網格超過身體,用100%grid-template-columns 為什麼在grid-template-colms中具有100%的顯示器,當位置設置為設置的位置時,grid-template-colly修復了? 問題: 考慮以下CSS和html: class =“ snippet-code”> ...
    程式設計 發佈於2025-07-02
  • Go語言如何動態發現導出包類型?
    Go語言如何動態發現導出包類型?
    與反射軟件包中的有限類型的發現能力相反,本文探討了在運行時發現所有包裝類型(尤其是struntime go import( “ FMT” “去/進口商” ) func main(){ pkg,err:= incorter.default()。導入(“ time”) ...
    程式設計 發佈於2025-07-02
  • 大批
    大批
    [2 數組是對象,因此它們在JS中也具有方法。 切片(開始):在新數組中提取部分數組,而無需突變原始數組。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    程式設計 發佈於2025-07-02
  • Java字符串非空且非null的有效檢查方法
    Java字符串非空且非null的有效檢查方法
    檢查字符串是否不是null而不是空的if (str != null && !str.isEmpty())Option 2: str.length() == 0For Java versions prior to 1.6, str.length() == 0 can be二手: if(str!= n...
    程式設計 發佈於2025-07-02
  • 如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    如何將MySQL數據庫添加到Visual Studio 2012中的數據源對話框中?
    在Visual Studio 2012 儘管已安裝了MySQL Connector v.6.5.4,但無法將MySQL數據庫添加到實體框架的“ DataSource對話框”中。為了解決這一問題,至關重要的是要了解MySQL連接器v.6.5.5及以後的6.6.x版本將提供MySQL的官方Visual...
    程式設計 發佈於2025-07-02
  • Spark DataFrame添加常量列的妙招
    Spark DataFrame添加常量列的妙招
    在Spark Dataframe ,將常數列添加到Spark DataFrame,該列具有適用於所有行的任意值的Spark DataFrame,可以通過多種方式實現。使用文字值(SPARK 1.3)在嘗試提供直接值時,用於此問題時,旨在為此目的的column方法可能會導致錯誤。 df.withCo...
    程式設計 發佈於2025-07-02
  • 找到最大計數時,如何解決mySQL中的“組函數\”錯誤的“無效使用”?
    找到最大計數時,如何解決mySQL中的“組函數\”錯誤的“無效使用”?
    如何在mySQL中使用mySql 檢索最大計數,您可能會遇到一個問題,您可能會在嘗試使用以下命令:理解錯誤正確找到由名稱列分組的值的最大計數,請使用以下修改後的查詢: 計數(*)為c 來自EMP1 按名稱組 c desc訂購 限制1 查詢說明 select語句提取名稱列和每個名稱...
    程式設計 發佈於2025-07-02
  • PHP與C++函數重載處理的區別
    PHP與C++函數重載處理的區別
    作為經驗豐富的C開發人員脫離謎題,您可能會遇到功能超載的概念。這個概念雖然在C中普遍,但在PHP中構成了獨特的挑戰。讓我們深入研究PHP功能過載的複雜性,並探索其提供的可能性。 在PHP中理解php的方法在PHP中,函數超載的概念(如C等語言)不存在。函數簽名僅由其名稱定義,而與他們的參數列表無關...
    程式設計 發佈於2025-07-02
  • 為什麼儘管有效代碼,為什麼在PHP中捕獲輸入?
    為什麼儘管有效代碼,為什麼在PHP中捕獲輸入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,輸出...
    程式設計 發佈於2025-07-02
  • 您如何在Laravel Blade模板中定義變量?
    您如何在Laravel Blade模板中定義變量?
    在Laravel Blade模板中使用Elegance 在blade模板中如何分配變量對於存儲以後使用的數據至關重要。在使用“ {{}}”分配變量的同時,它可能並不總是最優雅的解決方案。 幸運的是,Blade通過@php Directive提供了更優雅的方法: $ old_section =...
    程式設計 發佈於2025-07-02
  • Java的Map.Entry和SimpleEntry如何簡化鍵值對管理?
    Java的Map.Entry和SimpleEntry如何簡化鍵值對管理?
    A Comprehensive Collection for Value Pairs: Introducing Java's Map.Entry and SimpleEntryIn Java, when defining a collection where each element com...
    程式設計 發佈於2025-07-02
  • 如何將PANDAS DataFrame列轉換為DateTime格式並按日期過濾?
    如何將PANDAS DataFrame列轉換為DateTime格式並按日期過濾?
    Transform Pandas DataFrame Column to DateTime FormatScenario:Data within a Pandas DataFrame often exists in various formats, including strings.使用時間數據時...
    程式設計 發佈於2025-07-02
  • 如何有效地選擇熊貓數據框中的列?
    如何有效地選擇熊貓數據框中的列?
    在處理數據操作任務時,在Pandas DataFrames 中選擇列時,選擇特定列的必要條件是必要的。在Pandas中,選擇列的各種選項。 選項1:使用列名 如果已知列索引,請使用ILOC函數選擇它們。請注意,python索引基於零。 df1 = df.iloc [:,0:2]#使用索引0和1 ...
    程式設計 發佈於2025-07-02

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3