Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simple insert_dict() builder #725

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,20 @@ This produces:
WHERE "date">NOW()-7
GROUP BY "col1","col2"

Inserting dictionaries
""""""""""""""""

There's a simple convenience function to insert dicts with ``pypika.Query.into("mytable").insert_dict()``.

.. code-block:: python
Query.into(Table("foo")).insert_dict({"value": 42, "created_at": datetime(2024, 3, 15)})

This produces:

.. code-block:: sql
INSERT INTO "foo" ("value", "created_at")
VALUES (42, '2024-03-15T00:00:00')

.. _tutorial_end:

.. _contributing_start:
Expand Down
16 changes: 14 additions & 2 deletions pypika/queries.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from copy import copy
from functools import reduce
from typing import Any, List, Optional, Sequence, Tuple as TypedTuple, Type, Union
from typing import Any, List, Optional, Sequence, Tuple as TypedTuple, Type, Union, Set, Dict

from pypika.enums import Dialects, JoinType, ReferenceOption, SetOperation
from pypika.terms import (
Expand Down Expand Up @@ -750,7 +750,7 @@ def __init__(
self._select_star_tables = set()
self._mysql_rollup = False
self._select_into = False

self._using_insert_dict = False
self._subquery_count = 0
self._foreign_table = False

Expand Down Expand Up @@ -890,6 +890,9 @@ def columns(self, *terms: Any) -> "QueryBuilder":
if self._insert_table is None:
raise AttributeError("'Query' object has no attribute '%s'" % "insert")

if self._using_insert_dict:
raise QueryException("Cannot mix use of columns() and insert_dict()")

if terms and isinstance(terms[0], (list, tuple)):
terms = terms[0]

Expand All @@ -903,6 +906,15 @@ def insert(self, *terms: Any) -> "QueryBuilder":
self._apply_terms(*terms)
self._replace = False

def insert_dict(self, data: Dict[str, Any]) -> "QueryBuilder":
if self._columns:
raise QueryException("Cannot mix use of columns() and insert_dict()")

cols = data.keys()
builder = self.columns(*cols).insert(*data.values())
builder._using_insert_dict = True
return builder

@builder
def replace(self, *terms: Any) -> "QueryBuilder":
self._apply_terms(*terms)
Expand Down
23 changes: 23 additions & 0 deletions pypika/tests/test_inserts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from pypika.terms import Values
from pypika.utils import QueryException
from datetime import datetime

__author__ = "Timothy Heys"
__email__ = "theys@kayak.com"
Expand Down Expand Up @@ -168,6 +169,28 @@ def test_insert_with_statement(self):
'WITH sub_qs AS (SELECT "id" FROM "abc") INSERT INTO "abc" SELECT "sub_qs"."id" FROM sub_qs', str(q)
)

class InsertIntoWithDict(unittest.TestCase):
table_abc = Table("abc")

def test_inserting_simple_dictionary(self):
q = Query.into(self.table_abc).insert_dict({"c1": "value", "c2": 1})
self.assertEqual("INSERT INTO \"abc\" (\"c1\",\"c2\") VALUES ('value',1)", str(q))

def test_inserting_dictionary_goes_through_value_quoting_logic(self):
q = Query.into(self.table_abc).insert_dict({"num": 1, "timestamp": datetime(2023, 4, 18)})
self.assertEqual("INSERT INTO \"abc\" (\"num\",\"timestamp\") VALUES (1,'2023-04-18T00:00:00')", str(q))

def test_inserting_dictionary_produces_builder(self):
q = Query.into(self.table_abc).insert_dict({"num": 1, "timestamp": datetime(2023, 4, 18)})
q = q.insert(2, datetime(2023, 4, 19))
self.assertEqual("INSERT INTO \"abc\" (\"num\",\"timestamp\") VALUES (1,'2023-04-18T00:00:00'),(2,'2023-04-19T00:00:00')", str(q))

def test_columns_is_not_allowed_with_insert_dict_even_with_matching_columns(self):
with self.assertRaisesRegex(QueryException, "Cannot mix use of columns.*and insert_dict"):
Query.into(self.table_abc).columns("num", "key").insert_dict({"num": 1, "key": "foo"})

with self.assertRaisesRegex(QueryException, "Cannot mix use of columns.*and insert_dict"):
Query.into(self.table_abc).insert_dict({"num": 1, "key": "foo"}).columns("num", "key")

class PostgresInsertIntoOnConflictTests(unittest.TestCase):
table_abc = Table("abc")
Expand Down