From 87e0f2a4f770286c5a531e7a92b2b40fd8306b4f Mon Sep 17 00:00:00 2001 From: charlie572 Date: Wed, 14 Sep 2022 11:46:35 +0100 Subject: [PATCH 1/4] Add insert_dict() The keys of the dictionary are used as the columns in query. --- pypika/queries.py | 13 +++++++++++++ pypika/tests/test_inserts.py | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/pypika/queries.py b/pypika/queries.py index 2adc1b15..ab8e267f 100644 --- a/pypika/queries.py +++ b/pypika/queries.py @@ -879,6 +879,19 @@ def insert(self, *terms: Any) -> "QueryBuilder": self._apply_terms(*terms) self._replace = False + @builder + def insert_dict(self, terms: dict) -> "QueryBuilder": + if self._insert_table is None: + raise AttributeError("'Query' object has no attribute '%s'" % "insert") + + for term in terms: + if isinstance(term, str): + term = Field(term, table=self._insert_table) + self._columns.append(term) + + self._apply_terms(*terms.values()) + self._replace = False + @builder def replace(self, *terms: Any) -> "QueryBuilder": self._apply_terms(*terms) diff --git a/pypika/tests/test_inserts.py b/pypika/tests/test_inserts.py index f86efd7a..40ad3d83 100644 --- a/pypika/tests/test_inserts.py +++ b/pypika/tests/test_inserts.py @@ -168,6 +168,15 @@ 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) ) + def test_insert_dict(self): + d = {self.table_abc.foo: 1, self.table_abc.bar: "a", self.table_abc.buz: True} + query = ( + Query.into(self.table_abc) + .insert_dict(d) + ) + + self.assertEqual('INSERT INTO "abc" ("foo","bar","buz") VALUES (1,\'a\',true)', str(query)) + class PostgresInsertIntoOnConflictTests(unittest.TestCase): table_abc = Table("abc") From 13b3150b8356f54e48d1c6418a79417c66e5b75b Mon Sep 17 00:00:00 2001 From: charlie572 Date: Wed, 14 Sep 2022 12:12:14 +0100 Subject: [PATCH 2/4] Add on_duplicate_key_update_all() When there is a duplicate key, all fields given in the INSERT statement will be updated. --- pypika/dialects.py | 11 ++++++++++- pypika/tests/test_inserts.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pypika/dialects.py b/pypika/dialects.py index 6e151d68..edc3ab69 100644 --- a/pypika/dialects.py +++ b/pypika/dialects.py @@ -12,7 +12,8 @@ Query, QueryBuilder, ) -from pypika.terms import ArithmeticExpression, Criterion, EmptyCriterion, Field, Function, Star, Term, ValueWrapper +from pypika.terms import ArithmeticExpression, Criterion, EmptyCriterion, Field, Function, Star, Term, ValueWrapper, \ + Values from pypika.utils import QueryException, builder, format_quotes @@ -119,6 +120,14 @@ def on_duplicate_key_update(self, field: Union[Field, str], value: Any) -> "MySQ field = Field(field) if not isinstance(field, Field) else field self._duplicate_updates.append((field, ValueWrapper(value))) + @builder + def on_duplicate_key_update_all(self) -> "MySQLQueryBuilder": + if self._ignore_duplicates: + raise QueryException("Can not have two conflict handlers") + + for field in self._columns: + self._duplicate_updates.append((field, ValueWrapper(Values(field)))) + @builder def on_duplicate_key_ignore(self) -> "MySQLQueryBuilder": if self._duplicate_updates: diff --git a/pypika/tests/test_inserts.py b/pypika/tests/test_inserts.py index 40ad3d83..168e42bb 100644 --- a/pypika/tests/test_inserts.py +++ b/pypika/tests/test_inserts.py @@ -749,6 +749,20 @@ def test_insert_ignore(self): str(query), ) + def test_on_duplicate_key_update_all(self): + query = ( + MySQLQuery.into(self.table_abc) + .columns(self.table_abc.foo, self.table_abc.bar, self.table_abc.baz) + .insert(1, "a", True) + .on_duplicate_key_update_all() + ) + + self.assertEqual( + "INSERT INTO `abc` (`foo`,`bar`,`baz`) VALUES (1,'a',true) ON DUPLICATE KEY " + "UPDATE `foo`=VALUES(`foo`),`bar`=VALUES(`bar`),`baz`=VALUES(`baz`)", + str(query), + ) + class InsertSelectFromTests(unittest.TestCase): table_abc, table_efg, table_hij = Tables("abc", "efg", "hij") From d8f88c8d145cb9bb94f933db8ae70b902885671d Mon Sep 17 00:00:00 2001 From: charlie572 Date: Sun, 18 Sep 2022 20:50:11 +0100 Subject: [PATCH 3/4] Format bytes value properly --- pypika/terms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pypika/terms.py b/pypika/terms.py index c522550a..0d21b718 100644 --- a/pypika/terms.py +++ b/pypika/terms.py @@ -381,6 +381,8 @@ def get_formatted_value(cls, value: Any, **kwargs): return str.lower(str(value)) if isinstance(value, uuid.UUID): return cls.get_formatted_value(str(value), **kwargs) + if isinstance(value, bytes): + return "x'" + value.hex() + "'" if value is None: return "null" return str(value) From 3e6a248baac8e903bce91dca23c7366a11fee274 Mon Sep 17 00:00:00 2001 From: charlie572 Date: Mon, 19 Sep 2022 20:12:37 +0100 Subject: [PATCH 4/4] Fix on_duplicate_key_ignore --- pypika/dialects.py | 8 +++++--- pypika/tests/test_inserts.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pypika/dialects.py b/pypika/dialects.py index edc3ab69..b1b570f2 100644 --- a/pypika/dialects.py +++ b/pypika/dialects.py @@ -142,7 +142,7 @@ def get_sql(self, **kwargs: Any) -> str: if self._duplicate_updates: querystring += self._on_duplicate_key_update_sql(**kwargs) elif self._ignore_duplicates: - querystring += self._on_duplicate_key_ignore_sql() + querystring += self._on_duplicate_key_ignore_sql(**kwargs) return querystring def _for_update_sql(self, **kwargs) -> str: @@ -167,8 +167,10 @@ def _on_duplicate_key_update_sql(self, **kwargs: Any) -> str: ) ) - def _on_duplicate_key_ignore_sql(self) -> str: - return " ON DUPLICATE KEY IGNORE" + def _on_duplicate_key_ignore_sql(self, **kwargs: Any) -> str: + # This feature isn't available in MySQL. This is a workaround where a field is set to itself on a duplicate key. + field_sql = self._columns[0].get_sql(**kwargs) + return f" ON DUPLICATE KEY UPDATE {field_sql}={field_sql}" @builder def modifier(self, value: str) -> "MySQLQueryBuilder": diff --git a/pypika/tests/test_inserts.py b/pypika/tests/test_inserts.py index 168e42bb..7580f21d 100644 --- a/pypika/tests/test_inserts.py +++ b/pypika/tests/test_inserts.py @@ -763,6 +763,20 @@ def test_on_duplicate_key_update_all(self): str(query), ) + def test_on_duplicate_key_ignore(self): + query = ( + MySQLQuery.into(self.table_abc) + .columns(self.table_abc.foo, self.table_abc.bar, self.table_abc.baz) + .insert(1, "a", True) + .on_duplicate_key_ignore() + ) + + self.assertEqual( + "INSERT INTO `abc` (`foo`,`bar`,`baz`) VALUES (1,'a',true) ON DUPLICATE KEY " + "UPDATE `foo`=`foo`", + str(query), + ) + class InsertSelectFromTests(unittest.TestCase): table_abc, table_efg, table_hij = Tables("abc", "efg", "hij")