python-sql 1.4.3__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python-sql-1.4.3 → python_sql-1.5.0}/.gitlab-ci.yml +2 -2
- {python-sql-1.4.3 → python_sql-1.5.0}/.hgtags +2 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/CHANGELOG +7 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/PKG-INFO +1 -1
- {python-sql-1.4.3 → python_sql-1.5.0}/python_sql.egg-info/PKG-INFO +1 -1
- {python-sql-1.4.3 → python_sql-1.5.0}/python_sql.egg-info/SOURCES.txt +1 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/__init__.py +473 -12
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/functions.py +5 -2
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/operators.py +2 -2
- python_sql-1.5.0/sql/tests/test_insert.py +198 -0
- python_sql-1.5.0/sql/tests/test_merge.py +111 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_operators.py +12 -12
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_select.py +47 -1
- {python-sql-1.4.3 → python_sql-1.5.0}/tox.ini +2 -2
- python-sql-1.4.3/sql/tests/test_insert.py +0 -105
- {python-sql-1.4.3 → python_sql-1.5.0}/.flake8 +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/.isort.cfg +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/COPYRIGHT +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/MANIFEST.in +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/README.rst +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/python_sql.egg-info/dependency_links.txt +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/python_sql.egg-info/top_level.txt +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/setup.cfg +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/setup.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/aggregate.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/conditionals.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/__init__.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_aggregate.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_alias.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_as.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_cast.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_collate.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_column.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_combining_query.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_conditionals.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_delete.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_for.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_functions.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_join.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_lateral.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_literal.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_order.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_table.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_update.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_values.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_window.py +0 -0
- {python-sql-1.4.3 → python_sql-1.5.0}/sql/tests/test_with.py +0 -0
|
@@ -54,7 +54,7 @@ test-tox-python:
|
|
|
54
54
|
extends: .test-tox
|
|
55
55
|
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/python:${PYTHON_VERSION}
|
|
56
56
|
script:
|
|
57
|
-
- tox -e "py${PYTHON_VERSION/./}"
|
|
57
|
+
- tox -e "py${PYTHON_VERSION/./}" -- -v --output-file junit.xml
|
|
58
58
|
parallel:
|
|
59
59
|
matrix:
|
|
60
60
|
- PYTHON_VERSION: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
@@ -63,4 +63,4 @@ test-tox-pypy:
|
|
|
63
63
|
extends: .test-tox
|
|
64
64
|
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/pypy:3
|
|
65
65
|
script:
|
|
66
|
-
- tox -e pypy3
|
|
66
|
+
- tox -e pypy3 -- -v --output-file junit.xml
|
|
@@ -16,3 +16,5 @@ edc03ee84f0ac96d403d8f984d59fffa3274cd2f 1.3.0
|
|
|
16
16
|
a317c40a4d60089ba9e465fbd64b78df24f9e890 1.4.0
|
|
17
17
|
e71bbae3398cb6a0e72f97a0cada9fcdee2bddea 1.4.1
|
|
18
18
|
fcb64787b51db2068061eb4aa13825abc1134916 1.4.2
|
|
19
|
+
111e3e86865360f83a65c04fa48c55f3d2957ee3 1.4.3
|
|
20
|
+
6f9066b83fe3a8c4699a8555ad1bc406f18974ff 1.5.0
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
Version 1.5.0 - 2024-05-13
|
|
2
|
+
* Skip alias on INSERT without ON CONFLICT or RETURNING
|
|
3
|
+
* Add MERGE
|
|
4
|
+
* Support UPSERT
|
|
5
|
+
* Remove default escape char on LIKE and ILIKE
|
|
6
|
+
* Add GROUPING SETS, CUBE, and ROLLUP
|
|
7
|
+
|
|
1
8
|
Version 1.4.3 - 2023-12-30
|
|
2
9
|
* Render common table expression in combining query
|
|
3
10
|
* Add support for Python 3.12
|
|
@@ -7,9 +7,13 @@ from collections import defaultdict
|
|
|
7
7
|
from itertools import chain
|
|
8
8
|
from threading import current_thread, local
|
|
9
9
|
|
|
10
|
-
__version__ = '1.
|
|
11
|
-
__all__ = [
|
|
12
|
-
'
|
|
10
|
+
__version__ = '1.5.0'
|
|
11
|
+
__all__ = [
|
|
12
|
+
'Flavor', 'Table', 'Values', 'Literal', 'Column', 'Grouping', 'Conflict',
|
|
13
|
+
'Matched', 'MatchedUpdate', 'MatchedDelete',
|
|
14
|
+
'NotMatched', 'NotMatchedInsert',
|
|
15
|
+
'Rollup', 'Cube', 'Excluded', 'Join', 'Asc', 'Desc', 'NullsFirst',
|
|
16
|
+
'NullsLast', 'format2numeric']
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
def _escape_identifier(name):
|
|
@@ -663,17 +667,20 @@ class Select(FromItem, SelectQuery):
|
|
|
663
667
|
|
|
664
668
|
|
|
665
669
|
class Insert(WithQuery):
|
|
666
|
-
__slots__ = ('_table', '_columns', '_values', '_returning')
|
|
670
|
+
__slots__ = ('_table', '_columns', '_values', '_on_conflict', '_returning')
|
|
667
671
|
|
|
668
|
-
def __init__(
|
|
669
|
-
|
|
672
|
+
def __init__(
|
|
673
|
+
self, table, columns=None, values=None, returning=None,
|
|
674
|
+
on_conflict=None, **kwargs):
|
|
670
675
|
self._table = None
|
|
671
676
|
self._columns = None
|
|
672
677
|
self._values = None
|
|
678
|
+
self._on_conflict = None
|
|
673
679
|
self._returning = None
|
|
674
680
|
self.table = table
|
|
675
681
|
self.columns = columns
|
|
676
682
|
self.values = values
|
|
683
|
+
self.on_conflict = on_conflict
|
|
677
684
|
self.returning = returning
|
|
678
685
|
super(Insert, self).__init__(**kwargs)
|
|
679
686
|
|
|
@@ -709,6 +716,17 @@ class Insert(WithQuery):
|
|
|
709
716
|
value = Values(value)
|
|
710
717
|
self._values = value
|
|
711
718
|
|
|
719
|
+
@property
|
|
720
|
+
def on_conflict(self):
|
|
721
|
+
return self._on_conflict
|
|
722
|
+
|
|
723
|
+
@on_conflict.setter
|
|
724
|
+
def on_conflict(self, value):
|
|
725
|
+
if value is not None:
|
|
726
|
+
assert isinstance(value, Conflict)
|
|
727
|
+
assert value.table == self.table
|
|
728
|
+
self._on_conflict = value
|
|
729
|
+
|
|
712
730
|
@property
|
|
713
731
|
def returning(self):
|
|
714
732
|
return self._returning
|
|
@@ -743,13 +761,20 @@ class Insert(WithQuery):
|
|
|
743
761
|
# TODO manage DEFAULT
|
|
744
762
|
elif self.values is None:
|
|
745
763
|
values = ' DEFAULT VALUES'
|
|
764
|
+
on_conflict = ''
|
|
765
|
+
if self.on_conflict:
|
|
766
|
+
on_conflict = ' %s' % self.on_conflict
|
|
746
767
|
returning = ''
|
|
747
768
|
if self.returning:
|
|
748
769
|
returning = ' RETURNING ' + ', '.join(
|
|
749
770
|
map(self._format, self.returning))
|
|
771
|
+
if on_conflict or returning:
|
|
772
|
+
table = '%s AS "%s"' % (self.table, self.table.alias)
|
|
773
|
+
else:
|
|
774
|
+
table = str(self.table)
|
|
750
775
|
return (self._with_str()
|
|
751
|
-
+ 'INSERT INTO %s
|
|
752
|
-
+ columns + values + returning)
|
|
776
|
+
+ 'INSERT INTO %s' % table
|
|
777
|
+
+ columns + values + on_conflict + returning)
|
|
753
778
|
|
|
754
779
|
@property
|
|
755
780
|
def params(self):
|
|
@@ -757,12 +782,149 @@ class Insert(WithQuery):
|
|
|
757
782
|
p.extend(self._with_params())
|
|
758
783
|
if isinstance(self.values, Query):
|
|
759
784
|
p.extend(self.values.params)
|
|
785
|
+
if self.on_conflict:
|
|
786
|
+
p.extend(self.on_conflict.params)
|
|
760
787
|
if self.returning:
|
|
761
788
|
for exp in self.returning:
|
|
762
789
|
p.extend(exp.params)
|
|
763
790
|
return tuple(p)
|
|
764
791
|
|
|
765
792
|
|
|
793
|
+
class Conflict(object):
|
|
794
|
+
__slots__ = (
|
|
795
|
+
'_table', '_indexed_columns', '_index_where', '_columns', '_values',
|
|
796
|
+
'_where')
|
|
797
|
+
|
|
798
|
+
def __init__(
|
|
799
|
+
self, table, indexed_columns=None, index_where=None,
|
|
800
|
+
columns=None, values=None, where=None):
|
|
801
|
+
self._table = None
|
|
802
|
+
self._indexed_columns = None
|
|
803
|
+
self._index_where = None
|
|
804
|
+
self._columns = None
|
|
805
|
+
self._values = None
|
|
806
|
+
self._where = None
|
|
807
|
+
self.table = table
|
|
808
|
+
self.indexed_columns = indexed_columns
|
|
809
|
+
self.index_where = index_where
|
|
810
|
+
self.columns = columns
|
|
811
|
+
self.values = values
|
|
812
|
+
self.where = where
|
|
813
|
+
|
|
814
|
+
@property
|
|
815
|
+
def table(self):
|
|
816
|
+
return self._table
|
|
817
|
+
|
|
818
|
+
@table.setter
|
|
819
|
+
def table(self, value):
|
|
820
|
+
assert isinstance(value, Table)
|
|
821
|
+
self._table = value
|
|
822
|
+
|
|
823
|
+
@property
|
|
824
|
+
def indexed_columns(self):
|
|
825
|
+
return self._indexed_columns
|
|
826
|
+
|
|
827
|
+
@indexed_columns.setter
|
|
828
|
+
def indexed_columns(self, value):
|
|
829
|
+
if value is not None:
|
|
830
|
+
assert all(isinstance(col, Column) for col in value)
|
|
831
|
+
assert all(col.table == self.table for col in value)
|
|
832
|
+
self._indexed_columns = value
|
|
833
|
+
|
|
834
|
+
@property
|
|
835
|
+
def index_where(self):
|
|
836
|
+
return self._index_where
|
|
837
|
+
|
|
838
|
+
@index_where.setter
|
|
839
|
+
def index_where(self, value):
|
|
840
|
+
from sql.operators import And, Or
|
|
841
|
+
if value is not None:
|
|
842
|
+
assert isinstance(value, (Expression, And, Or))
|
|
843
|
+
self._index_where = value
|
|
844
|
+
|
|
845
|
+
@property
|
|
846
|
+
def columns(self):
|
|
847
|
+
return self._columns
|
|
848
|
+
|
|
849
|
+
@columns.setter
|
|
850
|
+
def columns(self, value):
|
|
851
|
+
if value is not None:
|
|
852
|
+
assert all(isinstance(col, Column) for col in value)
|
|
853
|
+
assert all(col.table == self.table for col in value)
|
|
854
|
+
self._columns = value
|
|
855
|
+
|
|
856
|
+
@property
|
|
857
|
+
def values(self):
|
|
858
|
+
return self._values
|
|
859
|
+
|
|
860
|
+
@values.setter
|
|
861
|
+
def values(self, value):
|
|
862
|
+
if value is not None:
|
|
863
|
+
assert isinstance(value, (list, Select))
|
|
864
|
+
if isinstance(value, list):
|
|
865
|
+
value = Values([value])
|
|
866
|
+
self._values = value
|
|
867
|
+
|
|
868
|
+
@property
|
|
869
|
+
def where(self):
|
|
870
|
+
return self._where
|
|
871
|
+
|
|
872
|
+
@where.setter
|
|
873
|
+
def where(self, value):
|
|
874
|
+
from sql.operators import And, Or
|
|
875
|
+
if value is not None:
|
|
876
|
+
assert isinstance(value, (Expression, And, Or))
|
|
877
|
+
self._where = value
|
|
878
|
+
|
|
879
|
+
def __str__(self):
|
|
880
|
+
indexed_columns = ''
|
|
881
|
+
if self.indexed_columns:
|
|
882
|
+
assert all(c.table == self.table for c in self.indexed_columns)
|
|
883
|
+
# Get columns without alias
|
|
884
|
+
indexed_columns = ', '.join(
|
|
885
|
+
c.column_name for c in self.indexed_columns)
|
|
886
|
+
indexed_columns = ' (' + indexed_columns + ')'
|
|
887
|
+
if self.index_where:
|
|
888
|
+
indexed_columns += ' WHERE ' + str(self.index_where)
|
|
889
|
+
else:
|
|
890
|
+
assert not self.index_where
|
|
891
|
+
do = ''
|
|
892
|
+
if not self.columns:
|
|
893
|
+
assert not self.values
|
|
894
|
+
assert not self.where
|
|
895
|
+
do = 'NOTHING'
|
|
896
|
+
else:
|
|
897
|
+
assert all(c.table == self.table for c in self.columns)
|
|
898
|
+
# Get columns without alias
|
|
899
|
+
do = ', '.join(c.column_name for c in self.columns)
|
|
900
|
+
# TODO manage DEFAULT
|
|
901
|
+
values = str(self.values)
|
|
902
|
+
if values.startswith('VALUES'):
|
|
903
|
+
values = values[len('VALUES'):]
|
|
904
|
+
else:
|
|
905
|
+
values = ' (' + values + ')'
|
|
906
|
+
if len(self.columns) == 1:
|
|
907
|
+
# PostgreSQL would require ROW expression
|
|
908
|
+
# with single column with parenthesis
|
|
909
|
+
do = 'UPDATE SET ' + do + ' =' + values
|
|
910
|
+
else:
|
|
911
|
+
do = 'UPDATE SET (' + do + ') =' + values
|
|
912
|
+
if self.where:
|
|
913
|
+
do += ' WHERE %s' % self.where
|
|
914
|
+
return 'ON CONFLICT' + indexed_columns + ' DO ' + do
|
|
915
|
+
|
|
916
|
+
@property
|
|
917
|
+
def params(self):
|
|
918
|
+
p = []
|
|
919
|
+
if self.index_where:
|
|
920
|
+
p.extend(self.index_where.params)
|
|
921
|
+
if self.values:
|
|
922
|
+
p.extend(self.values.params)
|
|
923
|
+
if self.where:
|
|
924
|
+
p.extend(self.where.params)
|
|
925
|
+
return p
|
|
926
|
+
|
|
927
|
+
|
|
766
928
|
class Update(Insert):
|
|
767
929
|
__slots__ = ('_where', '_values', 'from_')
|
|
768
930
|
|
|
@@ -919,6 +1081,207 @@ class Delete(WithQuery):
|
|
|
919
1081
|
return tuple(p)
|
|
920
1082
|
|
|
921
1083
|
|
|
1084
|
+
class Merge(WithQuery):
|
|
1085
|
+
__slots__ = ('_target', '_source', '_condition', '_whens')
|
|
1086
|
+
|
|
1087
|
+
def __init__(self, target, source, condition, *whens, **kwargs):
|
|
1088
|
+
self._target = None
|
|
1089
|
+
self._source = None
|
|
1090
|
+
self._condition = None
|
|
1091
|
+
self._whens = None
|
|
1092
|
+
self.target = target
|
|
1093
|
+
self.source = source
|
|
1094
|
+
self.condition = condition
|
|
1095
|
+
self.whens = whens
|
|
1096
|
+
super().__init__(**kwargs)
|
|
1097
|
+
|
|
1098
|
+
@property
|
|
1099
|
+
def target(self):
|
|
1100
|
+
return self._target
|
|
1101
|
+
|
|
1102
|
+
@target.setter
|
|
1103
|
+
def target(self, value):
|
|
1104
|
+
assert isinstance(value, Table)
|
|
1105
|
+
self._target = value
|
|
1106
|
+
|
|
1107
|
+
@property
|
|
1108
|
+
def source(self):
|
|
1109
|
+
return self._source
|
|
1110
|
+
|
|
1111
|
+
@source.setter
|
|
1112
|
+
def source(self, value):
|
|
1113
|
+
assert isinstance(value, (Table, SelectQuery, Values))
|
|
1114
|
+
self._source = value
|
|
1115
|
+
|
|
1116
|
+
@property
|
|
1117
|
+
def condition(self):
|
|
1118
|
+
return self._condition
|
|
1119
|
+
|
|
1120
|
+
@condition.setter
|
|
1121
|
+
def condition(self, value):
|
|
1122
|
+
assert isinstance(value, Expression)
|
|
1123
|
+
self._condition = value
|
|
1124
|
+
|
|
1125
|
+
@property
|
|
1126
|
+
def whens(self):
|
|
1127
|
+
return self._whens
|
|
1128
|
+
|
|
1129
|
+
@whens.setter
|
|
1130
|
+
def whens(self, value):
|
|
1131
|
+
assert all(isinstance(w, Matched) for w in value)
|
|
1132
|
+
self._whens = tuple(value)
|
|
1133
|
+
|
|
1134
|
+
def __str__(self):
|
|
1135
|
+
with AliasManager():
|
|
1136
|
+
if isinstance(self.source, (Select, Values)):
|
|
1137
|
+
source = '(%s)' % self.source
|
|
1138
|
+
else:
|
|
1139
|
+
source = self.source
|
|
1140
|
+
if self.condition:
|
|
1141
|
+
condition = 'ON %s' % self.condition
|
|
1142
|
+
else:
|
|
1143
|
+
condition = ''
|
|
1144
|
+
return (self._with_str()
|
|
1145
|
+
+ 'MERGE INTO %s AS "%s" ' % (self.target, self.target.alias)
|
|
1146
|
+
+ 'USING %s AS "%s" ' % (source, self.source.alias)
|
|
1147
|
+
+ condition + ' ' + ' '.join(map(str, self.whens)))
|
|
1148
|
+
|
|
1149
|
+
@property
|
|
1150
|
+
def params(self):
|
|
1151
|
+
p = []
|
|
1152
|
+
p.extend(self._with_params())
|
|
1153
|
+
if isinstance(self.source, (SelectQuery, Values)):
|
|
1154
|
+
p.extend(self.source.params)
|
|
1155
|
+
if self.condition:
|
|
1156
|
+
p.extend(self.condition.params)
|
|
1157
|
+
for match in self.whens:
|
|
1158
|
+
p.extend(match.params)
|
|
1159
|
+
return tuple(p)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
class Matched(object):
|
|
1163
|
+
__slots__ = ('_condition',)
|
|
1164
|
+
_when = 'MATCHED'
|
|
1165
|
+
|
|
1166
|
+
def __init__(self, condition=None):
|
|
1167
|
+
self._condition = None
|
|
1168
|
+
self.condition = condition
|
|
1169
|
+
|
|
1170
|
+
@property
|
|
1171
|
+
def condition(self):
|
|
1172
|
+
return self._condition
|
|
1173
|
+
|
|
1174
|
+
@condition.setter
|
|
1175
|
+
def condition(self, value):
|
|
1176
|
+
if value is not None:
|
|
1177
|
+
assert isinstance(value, Expression)
|
|
1178
|
+
self._condition = value
|
|
1179
|
+
|
|
1180
|
+
def _then_str(self):
|
|
1181
|
+
return 'DO NOTHING'
|
|
1182
|
+
|
|
1183
|
+
def __str__(self):
|
|
1184
|
+
if self.condition is not None:
|
|
1185
|
+
condition = ' AND ' + str(self.condition)
|
|
1186
|
+
else:
|
|
1187
|
+
condition = ''
|
|
1188
|
+
return 'WHEN ' + self._when + condition + ' THEN ' + self._then_str()
|
|
1189
|
+
|
|
1190
|
+
@property
|
|
1191
|
+
def params(self):
|
|
1192
|
+
p = []
|
|
1193
|
+
if self.condition:
|
|
1194
|
+
p.extend(self.condition.params)
|
|
1195
|
+
return tuple(p)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
class _MatchedValues(Matched):
|
|
1199
|
+
__slots__ = ('_columns', '_values')
|
|
1200
|
+
|
|
1201
|
+
def __init__(self, columns, values, **kwargs):
|
|
1202
|
+
self._columns = columns
|
|
1203
|
+
self._values = values
|
|
1204
|
+
self.columns = columns
|
|
1205
|
+
self.values = values
|
|
1206
|
+
super().__init__(**kwargs)
|
|
1207
|
+
|
|
1208
|
+
@property
|
|
1209
|
+
def columns(self):
|
|
1210
|
+
return self._columns
|
|
1211
|
+
|
|
1212
|
+
@columns.setter
|
|
1213
|
+
def columns(self, value):
|
|
1214
|
+
assert all(isinstance(col, Column) for col in value)
|
|
1215
|
+
self._columns = value
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
class MatchedUpdate(_MatchedValues, Matched):
|
|
1219
|
+
__slots__ = ()
|
|
1220
|
+
|
|
1221
|
+
@property
|
|
1222
|
+
def values(self):
|
|
1223
|
+
return self._values
|
|
1224
|
+
|
|
1225
|
+
@values.setter
|
|
1226
|
+
def values(self, value):
|
|
1227
|
+
self._values = value
|
|
1228
|
+
|
|
1229
|
+
def _then_str(self):
|
|
1230
|
+
columns = [c.column_name for c in self.columns]
|
|
1231
|
+
return 'UPDATE SET ' + ', '.join(
|
|
1232
|
+
'%s = %s' % (c, Update._format(v))
|
|
1233
|
+
for c, v in zip(columns, self.values))
|
|
1234
|
+
|
|
1235
|
+
@property
|
|
1236
|
+
def params(self):
|
|
1237
|
+
p = list(super().params)
|
|
1238
|
+
for value in self.values:
|
|
1239
|
+
if isinstance(value, (Expression, Select)):
|
|
1240
|
+
p.extend(value.params)
|
|
1241
|
+
else:
|
|
1242
|
+
p.append(value)
|
|
1243
|
+
return tuple(p)
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
class MatchedDelete(Matched):
|
|
1247
|
+
__slots__ = ()
|
|
1248
|
+
|
|
1249
|
+
def _then_str(self):
|
|
1250
|
+
return 'DELETE'
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
class NotMatched(Matched):
|
|
1254
|
+
__slots__ = ()
|
|
1255
|
+
_when = 'NOT MATCHED'
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
class NotMatchedInsert(_MatchedValues, NotMatched):
|
|
1259
|
+
__slots__ = ()
|
|
1260
|
+
|
|
1261
|
+
@property
|
|
1262
|
+
def values(self):
|
|
1263
|
+
return self._values
|
|
1264
|
+
|
|
1265
|
+
@values.setter
|
|
1266
|
+
def values(self, value):
|
|
1267
|
+
self._values = Values([value])
|
|
1268
|
+
|
|
1269
|
+
def _then_str(self):
|
|
1270
|
+
columns = ', '.join(c.column_name for c in self.columns)
|
|
1271
|
+
columns = '(' + columns + ')'
|
|
1272
|
+
if self.values is None:
|
|
1273
|
+
values = ' DEFAULT VALUES '
|
|
1274
|
+
else:
|
|
1275
|
+
values = ' ' + str(self.values)
|
|
1276
|
+
return 'INSERT ' + columns + values
|
|
1277
|
+
|
|
1278
|
+
@property
|
|
1279
|
+
def params(self):
|
|
1280
|
+
p = list(super().params)
|
|
1281
|
+
p.extend(self.values.params)
|
|
1282
|
+
return tuple(p)
|
|
1283
|
+
|
|
1284
|
+
|
|
922
1285
|
class CombiningQuery(FromItem, SelectQuery):
|
|
923
1286
|
__slots__ = ('queries', 'all_')
|
|
924
1287
|
_operator = ''
|
|
@@ -989,9 +1352,11 @@ class Table(FromItem):
|
|
|
989
1352
|
def params(self):
|
|
990
1353
|
return ()
|
|
991
1354
|
|
|
992
|
-
def insert(
|
|
1355
|
+
def insert(
|
|
1356
|
+
self, columns=None, values=None, returning=None, with_=None,
|
|
1357
|
+
on_conflict=None):
|
|
993
1358
|
return Insert(self, columns=columns, values=values,
|
|
994
|
-
returning=returning, with_=with_)
|
|
1359
|
+
on_conflict=on_conflict, returning=returning, with_=with_)
|
|
995
1360
|
|
|
996
1361
|
def update(self, columns, values, from_=None, where=None, returning=None,
|
|
997
1362
|
with_=None):
|
|
@@ -1003,6 +1368,25 @@ class Table(FromItem):
|
|
|
1003
1368
|
return Delete(self, only=only, using=using, where=where,
|
|
1004
1369
|
returning=returning, with_=with_)
|
|
1005
1370
|
|
|
1371
|
+
def merge(self, source, condition, *whens, with_=None):
|
|
1372
|
+
return Merge(self, source, condition, *whens, with_=with_)
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
class _Excluded(Table):
|
|
1376
|
+
def __init__(self):
|
|
1377
|
+
super().__init__('EXCLUDED')
|
|
1378
|
+
|
|
1379
|
+
@property
|
|
1380
|
+
def alias(self):
|
|
1381
|
+
return 'EXCLUDED'
|
|
1382
|
+
|
|
1383
|
+
@property
|
|
1384
|
+
def has_alias(self):
|
|
1385
|
+
return False
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
Excluded = _Excluded()
|
|
1389
|
+
|
|
1006
1390
|
|
|
1007
1391
|
class Join(FromItem):
|
|
1008
1392
|
__slots__ = ('_left', '_right', '_condition', '_type_')
|
|
@@ -1070,10 +1454,14 @@ class Join(FromItem):
|
|
|
1070
1454
|
def params(self):
|
|
1071
1455
|
p = []
|
|
1072
1456
|
for item in (self.left, self.right):
|
|
1073
|
-
|
|
1457
|
+
try:
|
|
1074
1458
|
p.extend(item.params)
|
|
1075
|
-
|
|
1459
|
+
except AttributeError:
|
|
1460
|
+
pass
|
|
1461
|
+
try:
|
|
1076
1462
|
p.extend(self.condition.params)
|
|
1463
|
+
except AttributeError:
|
|
1464
|
+
pass
|
|
1077
1465
|
return tuple(p)
|
|
1078
1466
|
|
|
1079
1467
|
@property
|
|
@@ -1442,6 +1830,79 @@ class Collate(Expression):
|
|
|
1442
1830
|
return (self.expression,)
|
|
1443
1831
|
|
|
1444
1832
|
|
|
1833
|
+
class Grouping(Expression):
|
|
1834
|
+
__slots__ = ('_sets',)
|
|
1835
|
+
|
|
1836
|
+
def __init__(self, *sets):
|
|
1837
|
+
super().__init__()
|
|
1838
|
+
self.sets = sets
|
|
1839
|
+
|
|
1840
|
+
@property
|
|
1841
|
+
def sets(self):
|
|
1842
|
+
return self._sets
|
|
1843
|
+
|
|
1844
|
+
@sets.setter
|
|
1845
|
+
def sets(self, value):
|
|
1846
|
+
assert all(
|
|
1847
|
+
isinstance(col, Expression) for cols in value for col in cols)
|
|
1848
|
+
self._sets = tuple(tuple(cols) for cols in value)
|
|
1849
|
+
|
|
1850
|
+
def __str__(self):
|
|
1851
|
+
return 'GROUPING SETS (%s)' % (
|
|
1852
|
+
', '.join(
|
|
1853
|
+
'(%s)' % ', '.join(str(col) for col in cols)
|
|
1854
|
+
for cols in self.sets))
|
|
1855
|
+
|
|
1856
|
+
@property
|
|
1857
|
+
def params(self):
|
|
1858
|
+
return sum((col.params for cols in self.sets for col in cols), ())
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
class Rollup(Expression):
|
|
1862
|
+
__slots__ = ('_expressions',)
|
|
1863
|
+
|
|
1864
|
+
def __init__(self, *expressions):
|
|
1865
|
+
super().__init__()
|
|
1866
|
+
self.expressions = expressions
|
|
1867
|
+
|
|
1868
|
+
@property
|
|
1869
|
+
def expressions(self):
|
|
1870
|
+
return self._expressions
|
|
1871
|
+
|
|
1872
|
+
@expressions.setter
|
|
1873
|
+
def expressions(self, value):
|
|
1874
|
+
assert all(
|
|
1875
|
+
isinstance(col, Expression)
|
|
1876
|
+
or all(isinstance(c, Expression) for c in col)
|
|
1877
|
+
for col in value)
|
|
1878
|
+
self._expressions = tuple(value)
|
|
1879
|
+
|
|
1880
|
+
def __str__(self):
|
|
1881
|
+
def format(col):
|
|
1882
|
+
if isinstance(col, Expression):
|
|
1883
|
+
return str(col)
|
|
1884
|
+
else:
|
|
1885
|
+
return '(%s)' % ', '.join(str(c) for c in col)
|
|
1886
|
+
return '%s (%s)' % (
|
|
1887
|
+
self.__class__.__name__.upper(),
|
|
1888
|
+
', '.join(format(col) for col in self.expressions))
|
|
1889
|
+
|
|
1890
|
+
@property
|
|
1891
|
+
def params(self):
|
|
1892
|
+
p = []
|
|
1893
|
+
for col in self.expressions:
|
|
1894
|
+
if isinstance(col, Expression):
|
|
1895
|
+
p.extend(col.params)
|
|
1896
|
+
else:
|
|
1897
|
+
for c in col:
|
|
1898
|
+
p.extend(c.params)
|
|
1899
|
+
return tuple(p)
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
class Cube(Rollup):
|
|
1903
|
+
pass
|
|
1904
|
+
|
|
1905
|
+
|
|
1445
1906
|
class Window(object):
|
|
1446
1907
|
__slots__ = (
|
|
1447
1908
|
'_partition', '_order_by', '_frame', '_start', '_end', '_exclude')
|
|
@@ -315,8 +315,11 @@ class Trim(Function):
|
|
|
315
315
|
for arg in (self.characters, self.string):
|
|
316
316
|
if isinstance(arg, str):
|
|
317
317
|
p.append(arg)
|
|
318
|
-
|
|
319
|
-
|
|
318
|
+
else:
|
|
319
|
+
try:
|
|
320
|
+
p.extend(arg.params)
|
|
321
|
+
except AttributeError:
|
|
322
|
+
pass
|
|
320
323
|
return tuple(p)
|
|
321
324
|
|
|
322
325
|
|
|
@@ -377,7 +377,7 @@ class Like(BinaryOperator):
|
|
|
377
377
|
__slots__ = 'escape'
|
|
378
378
|
_operator = 'LIKE'
|
|
379
379
|
|
|
380
|
-
def __init__(self, left, right, escape=
|
|
380
|
+
def __init__(self, left, right, escape=None):
|
|
381
381
|
super().__init__(left, right)
|
|
382
382
|
assert not escape or len(escape) == 1
|
|
383
383
|
self.escape = escape
|
|
@@ -386,7 +386,7 @@ class Like(BinaryOperator):
|
|
|
386
386
|
def params(self):
|
|
387
387
|
params = super().params
|
|
388
388
|
if self.escape or Flavor().get().escape_empty:
|
|
389
|
-
params += (self.escape,)
|
|
389
|
+
params += (self.escape or '',)
|
|
390
390
|
return params
|
|
391
391
|
|
|
392
392
|
def __str__(self):
|