首页 后端开发 Python教程 用于安全编写原始 SQL 的库

用于安全编写原始 SQL 的库

Sep 14, 2024 am 06:21 AM

我在 PropelAuth 的职责之一是用各种语言/框架编写示例应用程序/指南。这确实是我工作中最有趣的部分之一。我开始尝试不同的堆栈,无论是新的还是旧的,并找出支持客户的最佳方法。

因此,我最终从头开始创建了很多项目。每当我开始一个新项目时,我都必须做出一些重要的选择,而我倾向于花费大量时间的一个决定是:

我应该使用什么数据库库?

对我来说,我喜欢的库是那些我正在编写的代码和 SQL 查询本身之间具有很少抽象层的库

这样做的一个原因是实用性——因为我经常切换语言,所以我没有太多时间来熟练掌握任何特定的 ORM。我过去也从事过包含大量数据科学成分的工作,所以 SQL 是我非常熟悉的东西。

但我也是一个不喜欢“魔法”的开发人员 - 所以我避免使用那些我无法轻易判断生成的 SQL 是什么样子的库,或者我觉得我花了所有时间的库谷歌搜索“如何加入 X”,然后搜索“如何在两个条件下加入 X”。

在这篇文章中,我想强调一些我经常使用的库,以及我很高兴尝试的库,它们都试图最小化我编写的代码和 SQL 之间的差异被处决。

我个人最喜欢的:SQLx

我最喜欢的 Rust 箱子之一是 SQLx。

用他们自己的话说:

SQLx 支持 编译时检查查询。然而,它并没有通过提供 Rust API 或 DSL(特定于域的语言)来构建查询来实现这一点。相反,它提供了将常规 SQL 作为输入的宏,并确保它对您的数据库有效。其工作方式是 SQLx 在编译时连接到您的开发数据库,​​让数据库本身验证您的 SQL 查询(并返回一些信息)。

换句话来说,SQLx 让您可以编写如下查询:

let row = sqlx::query!(r#"
    SELECT enail
    FROM user
    WHERE user_id = ?
"#, user_id)
  .fetch_one(&pool)
  .await?;
登录后复制

这似乎是标准的,但是当你编译代码时,你会得到这样的错误:

error returned from database: column "enail" of relation "user" does not exist
登录后复制

同样的编译时检查也适用于复杂查询:

SELECT job_id, job_data 
FROM job_queue
WHERE job_status = 'Queued' AND run_at >= NOW()
ORDER BY run_at ASC
FOR UPDATE SKIP LOCKE -- oops
LIMIT 1
登录后复制
error returned from database: syntax error at or near "LOCKE"
登录后复制

由于查询是根据数据库进行检查的,因此这也适用于您安装的任何扩展。

为什么这么酷?

令人难以置信的是我们实际上只是在编写 SQL。但与像 postgres 这样的包不同,它还允许您编写原始 SQL,SQLx 可以防止我们犯愚蠢的错误。

这确实需要付出很小的代价——我们现在对数据库有编译时依赖,但 SQLx 通过“离线模式”解决了这个问题。当您的数据库可用时,您可以生成一个包含所有经过验证的查询的文件,然后在您的构建中,SQLx 将检查该文件而不是数据库。

在我寻求最小化我编写的代码和执行的 SQL 之间的差异时,使用 SQLx 既没有差异,而且我不必牺牲安全性来获得它。

使用 PgTyped 为您的 SQL 生成 TS 接口

正如 JavaScript/TypeScript 生态系统中经常出现的那样,这里有很多选项。

像 Kysely 这样的选项可以从数据库生成 TS 类型,然后提供查询生成器和编写原始 SQL 的方法。 Drizzle 是一个查询生成器,但它的既定目标是减少您编写的 TS 代码与生成的 SQL 之间的差异。甚至还有一个 SQLx 端口我还没有机会尝试。

但是最符合我在这里寻找的库是 PgTyped。使用 PgTyped,您可以在单独的文件中定义查询,如下所示:

/* @name FindEmailById */
SELECT email FROM user WHERE user_id = :userId;
登录后复制

然后运行命令 npx pgtyped -c config.json,它会根据您的架构生成具有正确类型的函数:

export interface IFindEmailByIdParams {
    userId?: string | null;
}
登录后复制
export interface IFindEmailByIdResult {
    email: string
}export const findEmailById = new PreparedQuery< // ...
登录后复制

您可以调用该函数从数据库获取结果。重要的是,如果您的查询错误(假设它引用了不存在的列),您会收到如下错误:

Error in query. Details: {
  errorCode: 'errorMissingColumn',
  hint: 'Perhaps you meant to reference the column "user.email".',
  message: 'column "enail" does not exist',
  position: '7'
}
登录后复制

这意味着您不仅可以安全地编写原始 SQL — 您的应用程序代码还可以获得一个很好的 TS 抽象来调用(或在测试中模拟)。

PgTyped 的最大缺点是 Github 问题——类型的可为空性不受尊重,这可能非常令人沮丧,因为这意味着您可能会合理地为必填字段传入 null。另一个缺点是它特定于 Postgres……稍后将在“可移植性”部分详细介绍。

Prisma recently released TypedSQL — a “a new way to write raw SQL queries in a type-safe way.” They mention that part of the inspiration was both SQLx and PgTyped, so I am excited to try it out!

Something for the Python world: PugSQL

A library I enjoy when I switch to Python is PugSQL (Python). Similar to PgTyped, you create separate SQL files for your queries like this:

-- :name find_email :one
select email from users where user_id = :user_id
登录后复制

which will create a function that you can call:

email = queries.find_email(user_id=42)
登录后复制

The downside (relative to the previous libraries) is these queries aren’t automatically checked for issues. That being said, some tests can surface most (all?) the issues with the query itself — you just need to write them.

If you are feeling fancy, it’s possible to add your own automation which will check the queries. There are ways to verify a query against a DB without running the query — it’s just some additional work. Each query being in its own file makes it a bit easier to automate since you don’t need to go parse out the queries in the first place.

Mark up your interfaces with JDBI

Whenever I talk about how much I liked Dropwizard, I usually get met with blank stares. It’s a bit of a deeper cut in the Java world relative to Spring (either that or normal people don’t discuss Dropwizard at parties).

One of the reasons I liked Dropwizard so much was just because it came with JDBI. That library allowed you to annotate the functions on an interface with SQL queries and it would generate the implementation for you.

public interface UserDAO {
  @SqlQuery("select email from user where user_id = :user_id")
  String fetchEmail(@Bind("user_id") String userId);
}
登录后复制
final UserDAO userDao = database.onDemand(UserDAO.class);
登录后复制

Again though, this would require additional testing to find issues in the queries.

I should also mention that Spring Data JPA does also have the same concept with it’s @Query annotation. It’s been a very long time, but back when I was comparing JDBI and Spring Data JPA - I always felt like Spring was trying to get me to use it’s more magical “function name to sql query” methods. Upon re-reading the docs recently though, I was wrong, and it does mention that you can fallback to @Query pretty frequently.

Other considerations

“Use it sparingly”

If you followed some of the links in this post, you’ll find that some of these libraries don’t advocate for this approach as the primary way to query the database.

TypedSQL describes it as an escape hatch for when querying via their ORM isn’t sufficient. Same for Spring Data JPA which describes it as “fine for a small number of queries”.

This isn’t an unfounded claim — if you go down the path of writing raw SQL for every query, it can be pretty verbose. There are absolutely times where I am making a simple, boring table that’s basically just a key-value store, and the exercise in writing INSERT INTO boring_table VALUES (...) and SELECT * FROM boring_table WHERE ... etc is just a typing exercise.

A library that provides the best of both worlds seems great! The devil is really in the details, as it depends on what you consider to be complex enough to warrant writing raw SQL and how frequently those queries come up.

Portability

One issue with the raw SQL approach is it’s less portable. If you are using an ORM, that ORM often will be compatible with more than just the database you are currently working with.

This can mean small things like running sqlite locally and a different DB in production — or big things like making it easier to migrate your database to something else.

Again, your mileage may vary here — it’s really dependent on how much you care about this.

Use a query builder instead

Going back to the java ecosystem, a popular library is jOOQ. With jOOQ, you aren’t writing raw SQL, but it’s very close:

Libraries for writing raw SQL safely

To me, this is great! My stated goal was just keeping the delta between my code and the generated SQL as little as possible, so query builders like jOOQ or Drizzle do a good job of keeping that delta small.

Not all query builders are made equal here, as I tend to dislike ones like Knex which have a larger delta.

Summary

  • Raw SQL libraries like SQLx, PgTyped, and JDBI allow writing SQL directly while providing safety and type checking.

  • These libraries aim to minimize the gap between code and executed SQL, with some offering benefits like compile-time checking and generated type interfaces.

  • 替代方案包括查询构建器,例如 jOOQ 和 Drizzle,您可以直接编写 SQL,但差距仍然很小。

  • 选择数据库库时的考虑因素包括可移植性、冗长性以及复杂查询与简单 CRUD 操作的需求。

以上是用于安全编写原始 SQL 的库的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1671
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1276
29
C# 教程
1256
24
Python与C:学习曲线和易用性 Python与C:学习曲线和易用性 Apr 19, 2025 am 12:20 AM

Python更易学且易用,C 则更强大但复杂。1.Python语法简洁,适合初学者,动态类型和自动内存管理使其易用,但可能导致运行时错误。2.C 提供低级控制和高级特性,适合高性能应用,但学习门槛高,需手动管理内存和类型安全。

Python和时间:充分利用您的学习时间 Python和时间:充分利用您的学习时间 Apr 14, 2025 am 12:02 AM

要在有限的时间内最大化学习Python的效率,可以使用Python的datetime、time和schedule模块。1.datetime模块用于记录和规划学习时间。2.time模块帮助设置学习和休息时间。3.schedule模块自动化安排每周学习任务。

Python vs.C:探索性能和效率 Python vs.C:探索性能和效率 Apr 18, 2025 am 12:20 AM

Python在开发效率上优于C ,但C 在执行性能上更高。1.Python的简洁语法和丰富库提高开发效率。2.C 的编译型特性和硬件控制提升执行性能。选择时需根据项目需求权衡开发速度与执行效率。

学习Python:2小时的每日学习是否足够? 学习Python:2小时的每日学习是否足够? Apr 18, 2025 am 12:22 AM

每天学习Python两个小时是否足够?这取决于你的目标和学习方法。1)制定清晰的学习计划,2)选择合适的学习资源和方法,3)动手实践和复习巩固,可以在这段时间内逐步掌握Python的基本知识和高级功能。

Python vs. C:了解关键差异 Python vs. C:了解关键差异 Apr 21, 2025 am 12:18 AM

Python和C 各有优势,选择应基于项目需求。1)Python适合快速开发和数据处理,因其简洁语法和动态类型。2)C 适用于高性能和系统编程,因其静态类型和手动内存管理。

Python标准库的哪一部分是:列表或数组? Python标准库的哪一部分是:列表或数组? Apr 27, 2025 am 12:03 AM

pythonlistsarepartofthestAndArdLibrary,herilearRaysarenot.listsarebuilt-In,多功能,和Rused ForStoringCollections,而EasaraySaraySaraySaraysaraySaraySaraysaraySaraysarrayModuleandleandleandlesscommonlyusedDduetolimitedFunctionalityFunctionalityFunctionality。

Python:自动化,脚本和任务管理 Python:自动化,脚本和任务管理 Apr 16, 2025 am 12:14 AM

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

科学计算的Python:详细的外观 科学计算的Python:详细的外观 Apr 19, 2025 am 12:15 AM

Python在科学计算中的应用包括数据分析、机器学习、数值模拟和可视化。1.Numpy提供高效的多维数组和数学函数。2.SciPy扩展Numpy功能,提供优化和线性代数工具。3.Pandas用于数据处理和分析。4.Matplotlib用于生成各种图表和可视化结果。

See all articles