【问题标题】:Inserting arrays into databases将数组插入数据库
【发布时间】:2019-06-19 17:05:44
【问题描述】:

我正在尝试编写一个通用函数,它将一行数据插入到数据库的表中,但我正在尝试编写一个长度未知的数组。我的目标是能够在任何程序中调用此函数并将一行任意长度的数据写入表(假设表和数组的长度相同。

我尝试将数组添加为单个数据。

import sqlite3
def add2Db(dbName, tableName, data):
    connection = sqlite3.connect(dbName)
    cur = connection.cursor()
    cur.execute("INSERT INTO "+ tableName +" VALUES (?)", (data))
    connection.commit()
    connection.close()

add2Db("items.db", "allItems", (1, "chair", 5, 4))

这只是崩溃并给我一个错误,说它有 4 列但只提供了一个值。

【问题讨论】:

  • 与其自己编写,不如使用现有的 SQL 构建器库或完整的 ORM(例如 SQLAlchemy)更好地解决此问题。
  • 还有一个安全问题;你写的东西很可能容易受到SQL injection attack的攻击。

标签: python arrays python-2.7 sqlite


【解决方案1】:

SQLite 不支持数组 - 您必须使用 ','.join() 将数组项转换为 TEXT 以将数组项连接成单个字符串并传递。

来源:SQLite 网站 https://www.sqlite.org/datatype3.html

【讨论】:

  • 我认为他们想将数组映射到多个列。
【解决方案2】:

我不是 Python 程序员,但我做 SQL 已经很久了。我什至写了自己的 ORM。我的建议是不要编写自己的 SQL 查询生成器。有无数微妙的问题,尤其是安全问题。我将在下面详细说明其中的一些。

改为使用成熟的 SQL 查询构建器或 ORM。他们已经处理了这些问题。这是一个使用 SQLAlchemy 的示例。

from datetime import date
from sqlalchemy import create_engine, MetaData

# Connect to the database with debugging on.
engine = create_engine('sqlite:///test.sqlite', echo=True)
conn = engine.connect()

# Read the schemas from the database
meta = MetaData()
meta.reflect(bind=engine)

# INSERT INTO users (name, birthday, state, country) VALUES (?, ?, ?, ?)
users = meta.tables['users']
conn.execute(
    users.insert().values(name="Yarrow Hock", birthday=date(1977, 1, 23), state="NY", country="US")
)

SQLAlchemy 可以执行所有 SQL 操作,并且可以使用不同的 SQL 变体。您还可以获得类型安全性。

conn.execute(
    users.insert().values(name="Yarrow Hock", birthday="in the past", state="NY", country="US")
)

sqlalchemy.exc.StatementError: (exceptions.TypeError) SQLite Date type only accepts Python date objects as input. [SQL: u'INSERT INTO users (name, birthday, state, country) VALUES (?, ?, ?, ?)']

insert into table values (...) 依赖于列定义顺序

这依赖于模式中定义的列顺序。这留下了两个问题。首先是可读性问题。

add2Db(db, 'some_table', (1, 39, 99, 45, 'papa foxtrot', 0, 42, 0, 6)

这是什么意思?读者无法判断。他们必须深入研究架构并计算列数以找出每个值的含义。

其次是维护问题。如果出于任何原因更改了架构并且列顺序不完全相同,这可能会导致一些极难发现的错误。比如……

create table users ( name text, birthday date, state text, country text );
vs
create table users ( name text, birthday date, country text, state text );

add2Db(db, 'users', ('Yarrow Hock', date(1977, 1, 23), 'NY', 'US'));

该插入将以任一列顺序静默“工作”。

您可以通过传入字典并使用列名的键来解决此问题。

add2Db(db, 'users', (name="Yarrow Hock", birthday=date(1977, 1, 23), state="NY", country="US"));

然后我们会生成如下查询:

insert into users
    (name, birthday, state, country)
values (?, ?, ?, ?)

这导致了下一个更大的问题。

SQL 注入攻击

现在这带来了一个新问题。如果我们只是将表名和列名粘贴到查询中,就会让我们面临最常见的安全漏洞之一,SQL Injection Attack。这就是人们可以制作一个值的地方,当它在 SQL 语句中天真地使用时会导致查询执行其他操作。喜欢Little Bobby Tables

虽然? 可以防止值的 SQL 注入,但仍然可以通过列名进行注入。不能保证列名是可信的。也许它们来自网络表单的参数?

Protecting table and column names is complicated 容易出错。

您编写的 SQL 越多,您就越容易受到注入攻击。

您必须为其他所有内容编写代码。

好的,你已经完成了insert。现在update? select?不要忘记子查询、分组依据、联合、连接...

如果你想写一个 SQL 查询构建器,太棒了!相反,如果您有使用 SQL 的工作要做,那么编写另一个 SQL 查询构建器不是您的工作。

其他人更难理解。

很有可能任何给定的 Python 程序员都知道 SQLAlchemy 是如何工作的,如果他们不知道的话,还有大量的教程和文档。他们不可能知道您自制的 SQL 函数,而且您必须编写所有教程和文档。

【讨论】:

    【解决方案3】:

    您不应该在没有争论的需要的情况下尝试编写自己的 ORM。你会遇到很多问题,例如这里是快速的 25 个原因not to

    改为使用任何经过验证的流行形式。我建议在 Django 之外使用 SQLAlchemy。使用它,您可以映射一个值字典以将其插入到模型中,就像 insert(schema_name).values(**dict_name) 一样(这里是插入/更新的 example)。

    【讨论】:

      【解决方案4】:

      把你的函数改成这样:

      def add2Db(dbName, tableName, data):
          num_qs = len(data)
          qm = ','.join(list('?' * num_qs))
      
          query = """
              INSERT INTO {table}
              VALUES ({qms})
                   """.format(table=tableName,
                              qms=qm)
          connection = sqlite3.connect(dbName)
          cur = connection.cursor()
          cur.execute(query, data)
          connection.commit()
          connection.close()
      

      【讨论】:

      • 这是否容易受到 SQL 注入攻击?
      • 可能取决于用例,但如果他接受来自外部来源的输入,那是适用的,他没有表示
      • tableName 需要被引用和转义。并验证num_qs > 0。否则我认为没关系。
      • 如果你想安全地注入表名,我会使用参数替换和psycopg2.extensions.AsIs,但再一次,我们不知道上下文,所以为了回答这里的问题,我认为这将够了
      • 如果您不知道要使用的上下文代码,最好假设数据是脏的。仅仅因为一个函数今天被传递干净的参数并不意味着它明天就会。至少,在答案中列出您的安全假设。
      猜你喜欢
      • 2017-07-10
      • 2014-12-03
      • 2014-12-29
      • 2011-07-05
      • 2015-03-29
      • 1970-01-01
      相关资源
      最近更新 更多