简体   繁体   English

SQLAlchemy:如何在 Postgres 数据库中执行原始 INSERT sql 查询?

[英]SQLAlchemy : how can I execute a raw INSERT sql query in a Postgres database?

I'm building an app using Python and the clean architecture principles, with TDD.我正在使用 Python 和简洁的架构原则以及 TDD 构建一个应用程序。

Some unit tests require executing some raw SQL queries against an in-memory database.一些单元测试需要对内存数据库执行一些原始 SQL 查询。

I am trying to switch from sqlite to postgresql inmemory data, using pytest-postgres.我正在尝试使用 pytest-postgres 从 sqlite 切换到 postgresql 内存数据。

Problem问题

  • When using sqlite inmemory database, I can both insert and select data.使用 sqlite 内存数据库时,我可以插入和选择数据。
  • When using Postgresql inmemory database, I can only SELECT (raw INSERT fails).使用 Postgresql 内存数据库时,我只能选择(原始插入失败)。

Insert work in sqlite…在 sqlite 中插入工作…

    s_tb_name = "tb_customer"
    ls_cols = ["first_name", "last_name", "email"]
    ls_vals = ['("John", "Doe", "john.doe@mail.net")',
               '("Jane", "Doe", "jane.doe@mail.net")',
               '("Eric", "Dal", "eric.d@home.com")']
    s_cols = ', '.join(ls_cols)
    s_vals = ', '.join(ls_vals)
    session.execute(f"INSERT INTO {s_tb_name} ({s_cols}) VALUES ({s_vals})")

…but fail in Postgres: ...但在 Postgres 中失败:

E       sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedColumn) column "John" does not exist
E       LINE 1: ..., email) VALUES (("John"....

From this psycopg documentation page , I understand this is due to pyscopg2.从这个 psycopg 文档页面,我了解到这是由于 pyscopg2。
It prevents injecting raw dynamic SQL, and it seems I should add this :它可以防止注入原始动态 SQL,看来我应该添加以下内容:

tb_sql_id = sql.Identifier(s_tb_name)
cols_sql_id = sql.SQL(' ,').join(map(sql.Identifier, ls_cols))
vals_sql_id = sql.SQL(' ,').join(map(sql.Literal, ls_vals))
psycopg2_query = sql.SQL(f"INSERT INTO {tb_sql_id} ({cols_sql_id}) VALUES ({vals_sql_id})")

but logically, sqlalchemy refuses to execute the psycopg2_query :但从逻辑上讲,sqlalchemy 拒绝执行psycopg2_query

sqlalchemy.exc.ArgumentError: SQL expression object expected, got object of type <class 'psycopg2.sql.SQL'> instead

Question

Is there a way to execute raw dynamic insert queries in Postgres using SQL Alchemy?有没有办法使用 SQL Alchemy 在 Postgres 中执行原始动态插入查询?

I am compelled to warn you about SQL injection, but since this is for your tests that should not be a concern.我不得不警告您有关 SQL 注入的信息,但由于这是针对您的测试,因此不必担心。

Two changes are needed:需要做两个改动:

  1. The values in ls_vals need to be enclosed in single, rather than double quotes ls_vals的值需要用单引号而不是双引号括起来
  2. The extra parens after VALUES need to be removed需要删除VALUES之后的额外括号
    s_tb_name = "tb_customer"
    ls_cols = ["first_name", "last_name", "email"]
    ls_vals = ["('John', 'Doe', 'john.doe@mail.net')",
               "('Jane', 'Doe', 'jane.doe@mail.net')",
               "('Eric', 'Dal', 'eric.d@home.com')"]
    s_cols = ', '.join(ls_cols)
    s_vals = ', '.join(ls_vals)
    session.execute(f"INSERT INTO {s_tb_name} ({s_cols}) VALUES {s_vals}")

As pointed by others, injecting SQL like this is to be avoided in most cases.正如其他人所指出的,在大多数情况下应该避免像这样注入 SQL。

Here, the SQL is written in the unit test itself.在这里,SQL 是在单元测试本身中编写的。 There is no external input leaking to the SQL injection, which alleviates the security risk. SQL注入没有外部输入泄露,降低了安全风险。

Mike Organek's solution did not fully work for me, but it pointed me to the right direction : I just had to also remove the parens from ls_vals. Mike Organek 的解决方案并不完全适合我,但它为我指明了正确的方向:我只需要从 ls_vals 中删除括号。

    s_tb_name = "tb_customer"
    ls_cols = ["first_name", "last_name", "email"]
    ls_vals = ["'John', 'Doe', 'john.doe@mail.net'",
               "'Jane', 'Doe', 'jane.doe@mail.net'",
               "'Eric', 'Dal', 'eric.d@home.com'"]
    s_cols = ', '.join(ls_cols)
    s_vals = '(' + '), ('.join(ls_vals) + ')'
    session.execute(f"INSERT INTO {s_tb_name} ({s_cols}) VALUES {s_vals}")

This made the insert test pass, both when using the sqlite engine and the postgres engine.这使得插入测试通过,无论是在使用 sqlite 引擎还是 postgres 引擎时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM