python-sql 1.5.1__py3-none-any.whl → 1.5.2__py3-none-any.whl
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.5.1.dist-info → python_sql-1.5.2.dist-info}/METADATA +1 -1
- python_sql-1.5.2.dist-info/RECORD +41 -0
- {python_sql-1.5.1.dist-info → python_sql-1.5.2.dist-info}/WHEEL +1 -1
- sql/__init__.py +170 -98
- sql/aggregate.py +21 -6
- sql/functions.py +9 -8
- sql/operators.py +6 -3
- sql/tests/test_aggregate.py +25 -1
- sql/tests/test_alias.py +15 -0
- sql/tests/test_collate.py +0 -5
- sql/tests/test_combining_query.py +5 -1
- sql/tests/test_delete.py +21 -1
- sql/tests/test_excluded.py +15 -0
- sql/tests/test_expression.py +17 -0
- sql/tests/test_flavor.py +46 -0
- sql/tests/test_for.py +4 -0
- sql/tests/test_from.py +25 -0
- sql/tests/test_from_item.py +46 -0
- sql/tests/test_functions.py +17 -1
- sql/tests/test_grouping.py +13 -0
- sql/tests/test_insert.py +57 -1
- sql/tests/test_join.py +23 -0
- sql/tests/test_lateral.py +1 -1
- sql/tests/test_merge.py +43 -5
- sql/tests/test_operators.py +20 -1
- sql/tests/test_order.py +16 -7
- sql/tests/test_rollup.py +13 -0
- sql/tests/test_select.py +56 -0
- sql/tests/test_update.py +8 -0
- sql/tests/test_window.py +24 -0
- sql/tests/test_with.py +4 -0
- python_sql-1.5.1.dist-info/RECORD +0 -34
- {python_sql-1.5.1.dist-info → python_sql-1.5.2.dist-info}/top_level.txt +0 -0
sql/aggregate.py
CHANGED
|
@@ -8,7 +8,7 @@ _sentinel = object()
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Aggregate(Expression):
|
|
11
|
-
__slots__ = ('
|
|
11
|
+
__slots__ = ('_expression', '_distinct', '_order_by', '_within',
|
|
12
12
|
'_filter', '_window')
|
|
13
13
|
_sql = ''
|
|
14
14
|
|
|
@@ -22,13 +22,24 @@ class Aggregate(Expression):
|
|
|
22
22
|
self.filter_ = filter_
|
|
23
23
|
self.window = window
|
|
24
24
|
|
|
25
|
+
@property
|
|
26
|
+
def expression(self):
|
|
27
|
+
return self._expression
|
|
28
|
+
|
|
29
|
+
@expression.setter
|
|
30
|
+
def expression(self, value):
|
|
31
|
+
if not isinstance(value, Expression):
|
|
32
|
+
raise ValueError("invalid expression: %r" % value)
|
|
33
|
+
self._expression = value
|
|
34
|
+
|
|
25
35
|
@property
|
|
26
36
|
def distinct(self):
|
|
27
37
|
return self._distinct
|
|
28
38
|
|
|
29
39
|
@distinct.setter
|
|
30
40
|
def distinct(self, value):
|
|
31
|
-
|
|
41
|
+
if not isinstance(value, bool):
|
|
42
|
+
raise ValueError("invalid distinct: %r" % value)
|
|
32
43
|
self._distinct = value
|
|
33
44
|
|
|
34
45
|
@property
|
|
@@ -40,7 +51,8 @@ class Aggregate(Expression):
|
|
|
40
51
|
if value is not None:
|
|
41
52
|
if isinstance(value, Expression):
|
|
42
53
|
value = [value]
|
|
43
|
-
|
|
54
|
+
if any(not isinstance(col, Expression) for col in value):
|
|
55
|
+
raise ValueError("invalid order by: %r" % value)
|
|
44
56
|
self._order_by = value
|
|
45
57
|
|
|
46
58
|
@property
|
|
@@ -52,7 +64,8 @@ class Aggregate(Expression):
|
|
|
52
64
|
if value is not None:
|
|
53
65
|
if isinstance(value, Expression):
|
|
54
66
|
value = [value]
|
|
55
|
-
|
|
67
|
+
if any(not isinstance(col, Expression) for col in value):
|
|
68
|
+
raise ValueError("invalid within: %r" % value)
|
|
56
69
|
self._within = value
|
|
57
70
|
|
|
58
71
|
@property
|
|
@@ -63,7 +76,8 @@ class Aggregate(Expression):
|
|
|
63
76
|
def filter_(self, value):
|
|
64
77
|
from sql.operators import And, Or
|
|
65
78
|
if value is not None:
|
|
66
|
-
|
|
79
|
+
if not isinstance(value, (Expression, And, Or)):
|
|
80
|
+
raise ValueError("invalid filter: %r" % value)
|
|
67
81
|
self._filter = value
|
|
68
82
|
|
|
69
83
|
@property
|
|
@@ -73,7 +87,8 @@ class Aggregate(Expression):
|
|
|
73
87
|
@window.setter
|
|
74
88
|
def window(self, value):
|
|
75
89
|
if value:
|
|
76
|
-
|
|
90
|
+
if not isinstance(value, Window):
|
|
91
|
+
raise ValueError("invalid window: %r" % value)
|
|
77
92
|
self._window = value
|
|
78
93
|
|
|
79
94
|
@property
|
sql/functions.py
CHANGED
|
@@ -39,7 +39,8 @@ class Function(Expression, FromItem):
|
|
|
39
39
|
|
|
40
40
|
@columns_definitions.setter
|
|
41
41
|
def columns_definitions(self, value):
|
|
42
|
-
|
|
42
|
+
if not isinstance(value, list):
|
|
43
|
+
raise ValueError("invalid columns definitions: %r" % value)
|
|
43
44
|
self._columns_definitions = value
|
|
44
45
|
|
|
45
46
|
@staticmethod
|
|
@@ -286,7 +287,8 @@ class Trim(Function):
|
|
|
286
287
|
_function = 'TRIM'
|
|
287
288
|
|
|
288
289
|
def __init__(self, string, position='BOTH', characters=' '):
|
|
289
|
-
|
|
290
|
+
if position.upper() not in {'LEADING', 'TRAILING', 'BOTH'}:
|
|
291
|
+
raise ValueError("invalid position: %r" % position)
|
|
290
292
|
self.position = position.upper()
|
|
291
293
|
self.characters = characters
|
|
292
294
|
self.string = string
|
|
@@ -316,10 +318,7 @@ class Trim(Function):
|
|
|
316
318
|
if isinstance(arg, str):
|
|
317
319
|
p.append(arg)
|
|
318
320
|
else:
|
|
319
|
-
|
|
320
|
-
p.extend(arg.params)
|
|
321
|
-
except AttributeError:
|
|
322
|
-
pass
|
|
321
|
+
p.extend(arg.params)
|
|
323
322
|
return tuple(p)
|
|
324
323
|
|
|
325
324
|
|
|
@@ -486,7 +485,8 @@ class WindowFunction(Function):
|
|
|
486
485
|
def filter_(self, value):
|
|
487
486
|
from sql.operators import And, Or
|
|
488
487
|
if value is not None:
|
|
489
|
-
|
|
488
|
+
if not isinstance(value, (Expression, And, Or)):
|
|
489
|
+
raise ValueError("invalid filter: %r" % value)
|
|
490
490
|
self._filter = value
|
|
491
491
|
|
|
492
492
|
@property
|
|
@@ -496,7 +496,8 @@ class WindowFunction(Function):
|
|
|
496
496
|
@window.setter
|
|
497
497
|
def window(self, value):
|
|
498
498
|
if value:
|
|
499
|
-
|
|
499
|
+
if not isinstance(value, Window):
|
|
500
|
+
raise ValueError("invalid window: %r" % value)
|
|
500
501
|
self._window = value
|
|
501
502
|
|
|
502
503
|
def __str__(self):
|
sql/operators.py
CHANGED
|
@@ -121,7 +121,8 @@ class NaryOperator(list, Operator):
|
|
|
121
121
|
return self
|
|
122
122
|
|
|
123
123
|
def __str__(self):
|
|
124
|
-
return '(' + (' %s ' % self._operator).join(
|
|
124
|
+
return '(' + (' %s ' % self._operator).join(
|
|
125
|
+
map(self._format, self)) + ')'
|
|
125
126
|
|
|
126
127
|
|
|
127
128
|
class And(NaryOperator):
|
|
@@ -248,7 +249,8 @@ class Is(BinaryOperator):
|
|
|
248
249
|
_operator = 'IS'
|
|
249
250
|
|
|
250
251
|
def __init__(self, left, right):
|
|
251
|
-
|
|
252
|
+
if right not in {None, True, False}:
|
|
253
|
+
raise ValueError("invalid right: %r" % right)
|
|
252
254
|
super(Is, self).__init__(left, right)
|
|
253
255
|
|
|
254
256
|
@property
|
|
@@ -379,7 +381,8 @@ class Like(BinaryOperator):
|
|
|
379
381
|
|
|
380
382
|
def __init__(self, left, right, escape=None):
|
|
381
383
|
super().__init__(left, right)
|
|
382
|
-
|
|
384
|
+
if escape and len(escape) != 1:
|
|
385
|
+
raise ValueError("invalid escape: %r" % escape)
|
|
383
386
|
self.escape = escape
|
|
384
387
|
|
|
385
388
|
@property
|
sql/tests/test_aggregate.py
CHANGED
|
@@ -3,12 +3,36 @@
|
|
|
3
3
|
import unittest
|
|
4
4
|
|
|
5
5
|
from sql import AliasManager, Flavor, Literal, Table, Window
|
|
6
|
-
from sql.aggregate import Avg, Count
|
|
6
|
+
from sql.aggregate import Aggregate, Avg, Count
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TestAggregate(unittest.TestCase):
|
|
10
10
|
table = Table('t')
|
|
11
11
|
|
|
12
|
+
def test_invalid_expression(self):
|
|
13
|
+
with self.assertRaises(ValueError):
|
|
14
|
+
Aggregate('foo')
|
|
15
|
+
|
|
16
|
+
def test_invalid_distinct(self):
|
|
17
|
+
with self.assertRaises(ValueError):
|
|
18
|
+
Aggregate(self.table.c, distinct='foo')
|
|
19
|
+
|
|
20
|
+
def test_invalid_order(self):
|
|
21
|
+
with self.assertRaises(ValueError):
|
|
22
|
+
Aggregate(self.table.c, order_by=['foo'])
|
|
23
|
+
|
|
24
|
+
def test_invalid_within(self):
|
|
25
|
+
with self.assertRaises(ValueError):
|
|
26
|
+
Aggregate(self.table.c, within=['foo'])
|
|
27
|
+
|
|
28
|
+
def test_invalid_filter(self):
|
|
29
|
+
with self.assertRaises(ValueError):
|
|
30
|
+
Aggregate(self.table.c, filter_='foo')
|
|
31
|
+
|
|
32
|
+
def test_invalid_window(self):
|
|
33
|
+
with self.assertRaises(ValueError):
|
|
34
|
+
Aggregate(self.table.c, window='foo')
|
|
35
|
+
|
|
12
36
|
def test_avg(self):
|
|
13
37
|
avg = Avg(self.table.c)
|
|
14
38
|
self.assertEqual(str(avg), 'AVG("c")')
|
sql/tests/test_alias.py
CHANGED
|
@@ -61,3 +61,18 @@ class TestAliasManager(unittest.TestCase):
|
|
|
61
61
|
self.finish2.wait()
|
|
62
62
|
if not self.succeed1.is_set() or not self.succeed2.is_set():
|
|
63
63
|
self.fail()
|
|
64
|
+
|
|
65
|
+
def test_contains(self):
|
|
66
|
+
with AliasManager():
|
|
67
|
+
AliasManager.get(self.t1)
|
|
68
|
+
self.assertTrue(AliasManager.contains(self.t1))
|
|
69
|
+
|
|
70
|
+
def test_contains_exclude(self):
|
|
71
|
+
with AliasManager(exclude=[self.t1]):
|
|
72
|
+
self.assertEqual(AliasManager.get(self.t1), '')
|
|
73
|
+
self.assertFalse(AliasManager.contains(self.t1))
|
|
74
|
+
|
|
75
|
+
def test_set(self):
|
|
76
|
+
with AliasManager():
|
|
77
|
+
AliasManager.set(self.t1, 'foo')
|
|
78
|
+
self.assertEqual(AliasManager.get(self.t1), 'foo')
|
sql/tests/test_collate.py
CHANGED
|
@@ -17,8 +17,3 @@ class TestCollate(unittest.TestCase):
|
|
|
17
17
|
collate = Collate("foo", 'C')
|
|
18
18
|
self.assertEqual(str(collate), '%s COLLATE "C"')
|
|
19
19
|
self.assertEqual(collate.params, ("foo",))
|
|
20
|
-
|
|
21
|
-
def test_collate_injection(self):
|
|
22
|
-
collate = Collate(self.column, 'C";')
|
|
23
|
-
with self.assertRaises(ValueError):
|
|
24
|
-
str(collate)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
import unittest
|
|
4
4
|
|
|
5
|
-
from sql import Table, Union, With
|
|
5
|
+
from sql import CombiningQuery, Table, Union, With
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestUnion(unittest.TestCase):
|
|
@@ -10,6 +10,10 @@ class TestUnion(unittest.TestCase):
|
|
|
10
10
|
q2 = Table('t2').select()
|
|
11
11
|
q3 = Table('t3').select()
|
|
12
12
|
|
|
13
|
+
def test_invalid_queries(self):
|
|
14
|
+
with self.assertRaises(ValueError):
|
|
15
|
+
CombiningQuery('foo', 'bar')
|
|
16
|
+
|
|
13
17
|
def test_union2(self):
|
|
14
18
|
query = Union(self.q1, self.q2)
|
|
15
19
|
self.assertEqual(str(query),
|
sql/tests/test_delete.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
import unittest
|
|
4
4
|
|
|
5
|
-
from sql import Table, With
|
|
5
|
+
from sql import Delete, Table, With
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestDelete(unittest.TestCase):
|
|
@@ -27,11 +27,31 @@ class TestDelete(unittest.TestCase):
|
|
|
27
27
|
'SELECT "a"."c" FROM "t2" AS "a"))')
|
|
28
28
|
self.assertEqual(query.params, ())
|
|
29
29
|
|
|
30
|
+
def test_delete_invalid_table(self):
|
|
31
|
+
with self.assertRaises(ValueError):
|
|
32
|
+
Delete('foo')
|
|
33
|
+
|
|
34
|
+
def test_delete_invalid_where(self):
|
|
35
|
+
with self.assertRaises(ValueError):
|
|
36
|
+
self.table.delete(where='foo')
|
|
37
|
+
|
|
30
38
|
def test_delete_returning(self):
|
|
31
39
|
query = self.table.delete(returning=[self.table.c])
|
|
32
40
|
self.assertEqual(str(query), 'DELETE FROM "t" RETURNING "c"')
|
|
33
41
|
self.assertEqual(query.params, ())
|
|
34
42
|
|
|
43
|
+
def test_delet_returning_select(self):
|
|
44
|
+
query = self.table.delete(returning=[self.table.select()])
|
|
45
|
+
|
|
46
|
+
self.assertEqual(
|
|
47
|
+
str(query),
|
|
48
|
+
'DELETE FROM "t" RETURNING (SELECT * FROM "t")')
|
|
49
|
+
self.assertEqual(query.params, ())
|
|
50
|
+
|
|
51
|
+
def test_delete_invalid_returning(self):
|
|
52
|
+
with self.assertRaises(ValueError):
|
|
53
|
+
self.table.delete(returning='foo')
|
|
54
|
+
|
|
35
55
|
def test_with(self):
|
|
36
56
|
t1 = Table('t1')
|
|
37
57
|
w = With(query=t1.select(t1.c1))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import Excluded
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestExcluded(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_alias(self):
|
|
12
|
+
self.assertEqual(Excluded.alias, 'EXCLUDED')
|
|
13
|
+
|
|
14
|
+
def test_has_alias(self):
|
|
15
|
+
self.assertFalse(Excluded.has_alias)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import Expression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestExpression(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_str(self):
|
|
12
|
+
with self.assertRaises(NotImplementedError):
|
|
13
|
+
str(Expression())
|
|
14
|
+
|
|
15
|
+
def test_params(self):
|
|
16
|
+
with self.assertRaises(NotImplementedError):
|
|
17
|
+
Expression().params
|
sql/tests/test_flavor.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import Flavor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFlavor(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test(self):
|
|
12
|
+
Flavor()
|
|
13
|
+
|
|
14
|
+
def test_limitstyle(self):
|
|
15
|
+
flavor = Flavor(limitstyle='rownum')
|
|
16
|
+
|
|
17
|
+
self.assertEqual(flavor.limitstyle, 'rownum')
|
|
18
|
+
|
|
19
|
+
def test_invalid_limitstyle(self):
|
|
20
|
+
with self.assertRaises(ValueError):
|
|
21
|
+
Flavor(limitstyle='foo')
|
|
22
|
+
|
|
23
|
+
def test_max_limit(self):
|
|
24
|
+
flavor = Flavor(max_limit=42)
|
|
25
|
+
|
|
26
|
+
self.assertEqual(flavor.max_limit, 42)
|
|
27
|
+
|
|
28
|
+
def test_invalid_max_limit(self):
|
|
29
|
+
with self.assertRaises(ValueError):
|
|
30
|
+
Flavor(max_limit='foo')
|
|
31
|
+
|
|
32
|
+
def test_paramstyle_format(self):
|
|
33
|
+
flavor = Flavor(paramstyle='format')
|
|
34
|
+
|
|
35
|
+
self.assertEqual(flavor.paramstyle, 'format')
|
|
36
|
+
self.assertEqual(flavor.param, '%s')
|
|
37
|
+
|
|
38
|
+
def test_paramstyle_qmark(self):
|
|
39
|
+
flavor = Flavor(paramstyle='qmark')
|
|
40
|
+
|
|
41
|
+
self.assertEqual(flavor.paramstyle, 'qmark')
|
|
42
|
+
self.assertEqual(flavor.param, '?')
|
|
43
|
+
|
|
44
|
+
def test_invalid_paramstyle(self):
|
|
45
|
+
with self.assertRaises(ValueError):
|
|
46
|
+
Flavor(paramstyle='foo')
|
sql/tests/test_for.py
CHANGED
sql/tests/test_from.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import CombiningQuery, From, Table
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFrom(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_add(self):
|
|
12
|
+
t1 = Table('t1')
|
|
13
|
+
t2 = Table('t2')
|
|
14
|
+
from_ = From([t1]) + t2
|
|
15
|
+
|
|
16
|
+
self.assertEqual(from_, [t1, t2])
|
|
17
|
+
|
|
18
|
+
def test_invalid_add(self):
|
|
19
|
+
with self.assertRaises(TypeError):
|
|
20
|
+
From([Table('t')]) + 'foo'
|
|
21
|
+
|
|
22
|
+
def test_invalid_add_combining_query(self):
|
|
23
|
+
with self.assertRaises(TypeError):
|
|
24
|
+
From([Table('t')]) + CombiningQuery(
|
|
25
|
+
Table('t1').select(), Table('t2').select())
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import AliasManager, Column, From, FromItem
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFromItem(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_from_item(self):
|
|
12
|
+
from_item = FromItem()
|
|
13
|
+
|
|
14
|
+
with AliasManager():
|
|
15
|
+
self.assertFalse(from_item.has_alias)
|
|
16
|
+
from_item.alias
|
|
17
|
+
self.assertTrue(from_item.has_alias)
|
|
18
|
+
|
|
19
|
+
def test_get_column(self):
|
|
20
|
+
from_item = FromItem()
|
|
21
|
+
|
|
22
|
+
foo = from_item.foo
|
|
23
|
+
|
|
24
|
+
self.assertIsInstance(foo, Column)
|
|
25
|
+
self.assertEqual(foo.name, 'foo')
|
|
26
|
+
|
|
27
|
+
def test_get_invalid_column(self):
|
|
28
|
+
from_item = FromItem()
|
|
29
|
+
|
|
30
|
+
with self.assertRaises(AttributeError):
|
|
31
|
+
from_item.__foo__
|
|
32
|
+
|
|
33
|
+
def test_add(self):
|
|
34
|
+
from_item1 = FromItem()
|
|
35
|
+
from_item2 = FromItem()
|
|
36
|
+
|
|
37
|
+
from_ = from_item1 + from_item2
|
|
38
|
+
|
|
39
|
+
self.assertIsInstance(from_, From)
|
|
40
|
+
self.assertEqual(from_, [from_item1, from_item2])
|
|
41
|
+
|
|
42
|
+
def test_invalid_add(self):
|
|
43
|
+
from_item = FromItem()
|
|
44
|
+
|
|
45
|
+
with self.assertRaises(TypeError):
|
|
46
|
+
from_item + 'foo'
|
sql/tests/test_functions.py
CHANGED
|
@@ -5,12 +5,16 @@ import unittest
|
|
|
5
5
|
from sql import AliasManager, Flavor, Table, Window
|
|
6
6
|
from sql.functions import (
|
|
7
7
|
Abs, AtTimeZone, CurrentTime, Div, Function, FunctionKeyword,
|
|
8
|
-
FunctionNotCallable, Overlay, Rank, Trim)
|
|
8
|
+
FunctionNotCallable, Overlay, Rank, Trim, WindowFunction)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class TestFunctions(unittest.TestCase):
|
|
12
12
|
table = Table('t')
|
|
13
13
|
|
|
14
|
+
def test_invalid_columns_definitions(self):
|
|
15
|
+
with self.assertRaises(ValueError):
|
|
16
|
+
Function(columns_definitions='foo')
|
|
17
|
+
|
|
14
18
|
def test_abs(self):
|
|
15
19
|
abs_ = Abs(self.table.c1)
|
|
16
20
|
self.assertEqual(str(abs_), 'ABS("c1")')
|
|
@@ -87,6 +91,10 @@ class TestFunctions(unittest.TestCase):
|
|
|
87
91
|
self.assertEqual(str(trim), 'TRIM(BOTH %s FROM "c1")')
|
|
88
92
|
self.assertEqual(trim.params, (' ',))
|
|
89
93
|
|
|
94
|
+
def test_trim_invalid_position(self):
|
|
95
|
+
with self.assertRaises(ValueError):
|
|
96
|
+
Trim('test', 'foo')
|
|
97
|
+
|
|
90
98
|
def test_at_time_zone(self):
|
|
91
99
|
time_zone = AtTimeZone(self.table.c1, 'UTC')
|
|
92
100
|
self.assertEqual(str(time_zone), '"c1" AT TIME ZONE %s')
|
|
@@ -142,6 +150,10 @@ class TestWindowFunction(unittest.TestCase):
|
|
|
142
150
|
self.assertEqual(str(function), 'RANK("a"."c") OVER ()')
|
|
143
151
|
self.assertEqual(function.params, ())
|
|
144
152
|
|
|
153
|
+
def test_invalid_window(self):
|
|
154
|
+
with self.assertRaises(ValueError):
|
|
155
|
+
WindowFunction(window='foo')
|
|
156
|
+
|
|
145
157
|
def test_filter(self):
|
|
146
158
|
t = Table('t')
|
|
147
159
|
function = Rank(t.c, filter_=t.c > 0, window=Window([]))
|
|
@@ -150,3 +162,7 @@ class TestWindowFunction(unittest.TestCase):
|
|
|
150
162
|
self.assertEqual(str(function),
|
|
151
163
|
'RANK("a"."c") FILTER (WHERE ("a"."c" > %s)) OVER ()')
|
|
152
164
|
self.assertEqual(function.params, (0,))
|
|
165
|
+
|
|
166
|
+
def test_invalid_filter(self):
|
|
167
|
+
with self.assertRaises(ValueError):
|
|
168
|
+
WindowFunction(filter_='foo', window=Window([]))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# This file is part of python-sql. The COPYRIGHT file at the top level of
|
|
2
|
+
# this repository contains the full copyright notices and license terms.
|
|
3
|
+
|
|
4
|
+
import unittest
|
|
5
|
+
|
|
6
|
+
from sql import Grouping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestGrouping(unittest.TestCase):
|
|
10
|
+
|
|
11
|
+
def test_invalid_sets(self):
|
|
12
|
+
with self.assertRaises(ValueError):
|
|
13
|
+
Grouping('foo')
|
sql/tests/test_insert.py
CHANGED
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
# this repository contains the full copyright notices and license terms.
|
|
3
3
|
import unittest
|
|
4
4
|
|
|
5
|
-
from sql import Conflict, Excluded, Table, With
|
|
5
|
+
from sql import Conflict, Excluded, Insert, Table, With
|
|
6
6
|
from sql.functions import Abs
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TestInsert(unittest.TestCase):
|
|
10
10
|
table = Table('t')
|
|
11
11
|
|
|
12
|
+
def test_insert_invalid_table(self):
|
|
13
|
+
with self.assertRaises(ValueError):
|
|
14
|
+
Insert('foo')
|
|
15
|
+
|
|
16
|
+
def test_insert_invalid_columns(self):
|
|
17
|
+
with self.assertRaises(ValueError):
|
|
18
|
+
self.table.insert(['foo'], [['foo']])
|
|
19
|
+
|
|
20
|
+
def test_insert_invalid_values(self):
|
|
21
|
+
with self.assertRaises(ValueError):
|
|
22
|
+
self.table.insert([self.table.c], 'foo')
|
|
23
|
+
|
|
12
24
|
def test_insert_default(self):
|
|
13
25
|
query = self.table.insert()
|
|
14
26
|
self.assertEqual(str(query), 'INSERT INTO "t" DEFAULT VALUES')
|
|
@@ -64,6 +76,10 @@ class TestInsert(unittest.TestCase):
|
|
|
64
76
|
'WHERE (("a"."c1" = "b"."c") AND ("a"."c2" = %s)))')
|
|
65
77
|
self.assertEqual(tuple(query.params), ('foo', 'bar'))
|
|
66
78
|
|
|
79
|
+
def test_insert_invalid_returning(self):
|
|
80
|
+
with self.assertRaises(ValueError):
|
|
81
|
+
self.table.insert(returning='foo')
|
|
82
|
+
|
|
67
83
|
def test_with(self):
|
|
68
84
|
t1 = Table('t1')
|
|
69
85
|
w = With(query=t1.select())
|
|
@@ -104,6 +120,14 @@ class TestInsert(unittest.TestCase):
|
|
|
104
120
|
'INSERT INTO "default"."t1" ("c1") VALUES (%s)')
|
|
105
121
|
self.assertEqual(tuple(query.params), ('foo',))
|
|
106
122
|
|
|
123
|
+
def test_upsert_invalid_on_conflict(self):
|
|
124
|
+
with self.assertRaises(ValueError):
|
|
125
|
+
self.table.insert(on_conflict='foo')
|
|
126
|
+
|
|
127
|
+
def test_upsert_invalid_table_on_conflict(self):
|
|
128
|
+
with self.assertRaises(ValueError):
|
|
129
|
+
self.table.insert(on_conflict=Conflict(Table('t1')))
|
|
130
|
+
|
|
107
131
|
def test_upsert_nothing(self):
|
|
108
132
|
query = self.table.insert(
|
|
109
133
|
[self.table.c1], [['foo']],
|
|
@@ -196,3 +220,35 @@ class TestInsert(unittest.TestCase):
|
|
|
196
220
|
'INSERT INTO "t" AS "a" ("c1") VALUES (%s) '
|
|
197
221
|
'ON CONFLICT DO UPDATE SET "c1" = (("EXCLUDED"."c1" + %s))')
|
|
198
222
|
self.assertEqual(tuple(query.params), (1, 2))
|
|
223
|
+
|
|
224
|
+
def test_conflict_invalid_table(self):
|
|
225
|
+
with self.assertRaises(ValueError):
|
|
226
|
+
Conflict('foo')
|
|
227
|
+
|
|
228
|
+
def test_conflict_invalid_indexed_columns(self):
|
|
229
|
+
with self.assertRaises(ValueError):
|
|
230
|
+
Conflict(self.table, indexed_columns=['foo'])
|
|
231
|
+
|
|
232
|
+
def test_conflict_indexed_columns_invalid_table(self):
|
|
233
|
+
with self.assertRaises(ValueError):
|
|
234
|
+
Conflict(self.table, indexed_columns=[Table('t').c])
|
|
235
|
+
|
|
236
|
+
def test_conflict_invalid_index_where(self):
|
|
237
|
+
with self.assertRaises(ValueError):
|
|
238
|
+
Conflict(self.table, index_where='foo')
|
|
239
|
+
|
|
240
|
+
def test_conflict_invalid_columns(self):
|
|
241
|
+
with self.assertRaises(ValueError):
|
|
242
|
+
Conflict(self.table, columns=['foo'])
|
|
243
|
+
|
|
244
|
+
def test_conflict_columns_invalid_table(self):
|
|
245
|
+
with self.assertRaises(ValueError):
|
|
246
|
+
Conflict(self.table, columns=[Table('t').c])
|
|
247
|
+
|
|
248
|
+
def test_conflict_invalid_values(self):
|
|
249
|
+
with self.assertRaises(ValueError):
|
|
250
|
+
Conflict(self.table, values='foo')
|
|
251
|
+
|
|
252
|
+
def test_conflict_invalid_where(self):
|
|
253
|
+
with self.assertRaises(ValueError):
|
|
254
|
+
Conflict(self.table, where='foo')
|
sql/tests/test_join.py
CHANGED
|
@@ -20,6 +20,22 @@ class TestJoin(unittest.TestCase):
|
|
|
20
20
|
self.assertEqual(str(join),
|
|
21
21
|
'"t1" AS "a" INNER JOIN "t2" AS "b" ON ("a"."c" = "b"."c")')
|
|
22
22
|
|
|
23
|
+
def test_join_invalid_left(self):
|
|
24
|
+
with self.assertRaises(ValueError):
|
|
25
|
+
Join('foo', Table('t1'))
|
|
26
|
+
|
|
27
|
+
def test_join_invalid_right(self):
|
|
28
|
+
with self.assertRaises(ValueError):
|
|
29
|
+
Join(Table('t1'), 'foo')
|
|
30
|
+
|
|
31
|
+
def test_join_invalid_condition(self):
|
|
32
|
+
with self.assertRaises(ValueError):
|
|
33
|
+
Join(Table('t1'), Table('t2'), condition='foo')
|
|
34
|
+
|
|
35
|
+
def test_join_invalid_type(self):
|
|
36
|
+
with self.assertRaises(ValueError):
|
|
37
|
+
Join(Table('t1'), Table('t2'), type_='foo')
|
|
38
|
+
|
|
23
39
|
def test_join_subselect(self):
|
|
24
40
|
t1 = Table('t1')
|
|
25
41
|
t2 = Table('t2')
|
|
@@ -50,3 +66,10 @@ class TestJoin(unittest.TestCase):
|
|
|
50
66
|
join = getattr(t1, method)(t2)
|
|
51
67
|
type_ = method[:-len('_join')].replace('_', ' ').upper()
|
|
52
68
|
self.assertEqual(join.type_, type_)
|
|
69
|
+
|
|
70
|
+
def test_join_alias(self):
|
|
71
|
+
join = Join(Table('t1'), Table('t2'))
|
|
72
|
+
with self.assertRaises(AttributeError):
|
|
73
|
+
join.alias
|
|
74
|
+
with self.assertRaises(AttributeError):
|
|
75
|
+
join.has_alias
|
sql/tests/test_lateral.py
CHANGED
|
@@ -11,7 +11,7 @@ class TestLateral(unittest.TestCase):
|
|
|
11
11
|
def test_lateral_select(self):
|
|
12
12
|
t1 = Table('t1')
|
|
13
13
|
t2 = Table('t2')
|
|
14
|
-
lateral =
|
|
14
|
+
lateral = t2.select(where=t2.id == t1.t2).lateral()
|
|
15
15
|
query = From([t1, lateral]).select()
|
|
16
16
|
|
|
17
17
|
self.assertEqual(str(query),
|