velocity-python 0.0.105__py3-none-any.whl → 0.0.155__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.
Files changed (120) hide show
  1. velocity/__init__.py +3 -1
  2. velocity/app/orders.py +3 -4
  3. velocity/app/tests/__init__.py +1 -0
  4. velocity/app/tests/test_email_processing.py +112 -0
  5. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  6. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  7. velocity/aws/__init__.py +3 -0
  8. velocity/aws/amplify.py +10 -6
  9. velocity/aws/handlers/__init__.py +2 -0
  10. velocity/aws/handlers/base_handler.py +248 -0
  11. velocity/aws/handlers/context.py +167 -2
  12. velocity/aws/handlers/exceptions.py +16 -0
  13. velocity/aws/handlers/lambda_handler.py +24 -85
  14. velocity/aws/handlers/mixins/__init__.py +16 -0
  15. velocity/aws/handlers/mixins/activity_tracker.py +181 -0
  16. velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
  17. velocity/aws/handlers/mixins/error_handler.py +192 -0
  18. velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
  19. velocity/aws/handlers/mixins/standard_mixin.py +73 -0
  20. velocity/aws/handlers/response.py +1 -1
  21. velocity/aws/handlers/sqs_handler.py +28 -143
  22. velocity/aws/tests/__init__.py +1 -0
  23. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  24. velocity/aws/tests/test_response.py +163 -0
  25. velocity/db/__init__.py +16 -4
  26. velocity/db/core/decorators.py +20 -4
  27. velocity/db/core/engine.py +185 -792
  28. velocity/db/core/result.py +36 -22
  29. velocity/db/core/row.py +15 -3
  30. velocity/db/core/table.py +283 -44
  31. velocity/db/core/transaction.py +19 -11
  32. velocity/db/exceptions.py +42 -18
  33. velocity/db/servers/base/__init__.py +9 -0
  34. velocity/db/servers/base/initializer.py +70 -0
  35. velocity/db/servers/base/operators.py +98 -0
  36. velocity/db/servers/base/sql.py +503 -0
  37. velocity/db/servers/base/types.py +135 -0
  38. velocity/db/servers/mysql/__init__.py +73 -0
  39. velocity/db/servers/mysql/operators.py +54 -0
  40. velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
  41. velocity/db/servers/mysql/sql.py +718 -0
  42. velocity/db/servers/mysql/types.py +107 -0
  43. velocity/db/servers/postgres/__init__.py +59 -11
  44. velocity/db/servers/postgres/operators.py +34 -0
  45. velocity/db/servers/postgres/sql.py +474 -120
  46. velocity/db/servers/postgres/types.py +88 -2
  47. velocity/db/servers/sqlite/__init__.py +61 -0
  48. velocity/db/servers/sqlite/operators.py +52 -0
  49. velocity/db/servers/sqlite/reserved.py +20 -0
  50. velocity/db/servers/sqlite/sql.py +677 -0
  51. velocity/db/servers/sqlite/types.py +92 -0
  52. velocity/db/servers/sqlserver/__init__.py +73 -0
  53. velocity/db/servers/sqlserver/operators.py +47 -0
  54. velocity/db/servers/sqlserver/reserved.py +32 -0
  55. velocity/db/servers/sqlserver/sql.py +805 -0
  56. velocity/db/servers/sqlserver/types.py +114 -0
  57. velocity/db/servers/tablehelper.py +117 -91
  58. velocity/db/tests/__init__.py +1 -0
  59. velocity/db/tests/common_db_test.py +0 -0
  60. velocity/db/tests/postgres/__init__.py +1 -0
  61. velocity/db/tests/postgres/common.py +49 -0
  62. velocity/db/tests/postgres/test_column.py +29 -0
  63. velocity/db/tests/postgres/test_connections.py +25 -0
  64. velocity/db/tests/postgres/test_database.py +21 -0
  65. velocity/db/tests/postgres/test_engine.py +205 -0
  66. velocity/db/tests/postgres/test_general_usage.py +88 -0
  67. velocity/db/tests/postgres/test_imports.py +8 -0
  68. velocity/db/tests/postgres/test_result.py +19 -0
  69. velocity/db/tests/postgres/test_row.py +137 -0
  70. velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
  71. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  72. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  73. velocity/db/tests/postgres/test_sequence.py +34 -0
  74. velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
  75. velocity/db/tests/postgres/test_table.py +101 -0
  76. velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
  77. velocity/db/tests/postgres/test_transaction.py +106 -0
  78. velocity/db/tests/sql/__init__.py +1 -0
  79. velocity/db/tests/sql/common.py +177 -0
  80. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  81. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  82. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  83. velocity/db/tests/test_db_utils.py +221 -0
  84. velocity/db/tests/test_postgres.py +448 -0
  85. velocity/db/tests/test_postgres_unchanged.py +81 -0
  86. velocity/db/tests/test_process_error_robustness.py +292 -0
  87. velocity/db/tests/test_result_caching.py +279 -0
  88. velocity/db/tests/test_result_sql_aware.py +117 -0
  89. velocity/db/tests/test_row_get_missing_column.py +72 -0
  90. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  91. velocity/db/tests/test_schema_locking_simple.py +97 -0
  92. velocity/db/tests/test_sql_builder.py +165 -0
  93. velocity/db/tests/test_tablehelper.py +486 -0
  94. velocity/db/utils.py +62 -47
  95. velocity/misc/conv/__init__.py +2 -0
  96. velocity/misc/conv/iconv.py +5 -4
  97. velocity/misc/export.py +1 -4
  98. velocity/misc/merge.py +1 -1
  99. velocity/misc/tests/__init__.py +1 -0
  100. velocity/misc/tests/test_db.py +90 -0
  101. velocity/misc/tests/test_fix.py +78 -0
  102. velocity/misc/tests/test_format.py +64 -0
  103. velocity/misc/tests/test_iconv.py +203 -0
  104. velocity/misc/tests/test_merge.py +82 -0
  105. velocity/misc/tests/test_oconv.py +144 -0
  106. velocity/misc/tests/test_original_error.py +52 -0
  107. velocity/misc/tests/test_timer.py +74 -0
  108. velocity/misc/tools.py +0 -1
  109. {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
  110. velocity_python-0.0.155.dist-info/RECORD +129 -0
  111. velocity/db/core/exceptions.py +0 -70
  112. velocity/db/servers/mysql.py +0 -641
  113. velocity/db/servers/sqlite.py +0 -968
  114. velocity/db/servers/sqlite_reserved.py +0 -208
  115. velocity/db/servers/sqlserver.py +0 -921
  116. velocity/db/servers/sqlserver_reserved.py +0 -314
  117. velocity_python-0.0.105.dist-info/RECORD +0 -56
  118. {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
  119. {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
  120. {velocity_python-0.0.105.dist-info → velocity_python-0.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,106 @@
1
+ import re
2
+ import unittest
3
+ import sys
4
+ import os
5
+ from .common import CommonPostgresTest, engine, test_db
6
+
7
+
8
+ @engine.transaction
9
+ @engine.transaction
10
+ class TestTransaction(CommonPostgresTest):
11
+
12
+ @classmethod
13
+ def create_test_tables(cls, tx):
14
+ """No special tables needed for transaction tests."""
15
+ pass
16
+
17
+ def test_init(self, tx):
18
+ t = tx.table("test_table")
19
+ assert t.exists() == False
20
+ assert tx.tables() == []
21
+ assert (
22
+ str(t)
23
+ == """Table: test_table
24
+ (table exists) False
25
+ Columns: 0
26
+ Rows: 0
27
+ """
28
+ )
29
+ self.assertTrue(re.match(r"PostGreSQL\.transaction\(.*:test_db_postgres\)", str(tx)))
30
+
31
+ def test_rollback(self, tx):
32
+ t = tx.table("test_table")
33
+ for i in range(10):
34
+
35
+ temp = t.insert(
36
+ {
37
+ "fname": "test",
38
+ "lname": "test",
39
+ "email": "test@test.com",
40
+ "age": 1,
41
+ "address": "test",
42
+ "city": "test",
43
+ "state": "test",
44
+ "zipcode": "test",
45
+ "country": "test",
46
+ }
47
+ )
48
+
49
+ assert (
50
+ str(t)
51
+ == """Table: test_table
52
+ (table exists) True
53
+ Columns: 10
54
+ Rows: 10
55
+ """
56
+ )
57
+ tx.rollback()
58
+ assert (
59
+ str(t)
60
+ == """Table: test_table
61
+ (table exists) False
62
+ Columns: 0
63
+ Rows: 0
64
+ """
65
+ )
66
+ tx.table("test_table").drop()
67
+ print("End of test_rollback")
68
+
69
+ def test_drop(self, tx):
70
+ t = tx.table("test_table")
71
+ t.drop()
72
+ for i in range(10):
73
+ t.insert(
74
+ {
75
+ "fname": "test",
76
+ "lname": "test",
77
+ "email": "test@test.com",
78
+ "age": 1,
79
+ "address": "test",
80
+ "city": "test",
81
+ "state": "test",
82
+ "zipcode": "test",
83
+ "country": "test",
84
+ }
85
+ )
86
+ assert (
87
+ str(t)
88
+ == """Table: test_table
89
+ (table exists) True
90
+ Columns: 10
91
+ Rows: 10
92
+ """
93
+ )
94
+ tx.table("test_table").drop()
95
+ assert (
96
+ str(t)
97
+ == """Table: test_table
98
+ (table exists) False
99
+ Columns: 0
100
+ Rows: 0
101
+ """
102
+ )
103
+
104
+
105
+ if __name__ == "__main__":
106
+ unittest.main()
@@ -0,0 +1 @@
1
+ # SQL tests
@@ -0,0 +1,177 @@
1
+ import unittest
2
+ from velocity.db.servers import postgres
3
+ import random
4
+
5
+ test_db = "test_foreign_key_db"
6
+ engine = postgres.initialize(
7
+ database=test_db,
8
+ )
9
+ print(engine)
10
+
11
+
12
+ @engine.transaction # Decorator to run the test in a transaction
13
+ class TestSQLModule(unittest.TestCase):
14
+
15
+ @classmethod
16
+ def setUpClass(cls, tx):
17
+ tx.switch_to_database("postgres")
18
+ tx.execute(f"drop database if exists {test_db}", single=True)
19
+ # Drop and recreate the test database to ensure a clean environment
20
+ db = tx.database(test_db)
21
+ if db.exists():
22
+ # Possibly drop if needed, or just recreate if the environment ensures cleanliness
23
+ pass
24
+ else:
25
+ db.create()
26
+ db.switch()
27
+
28
+ cls.create_tables(tx)
29
+ cls.insert_data(tx)
30
+
31
+ @classmethod
32
+ def create_tables(cls, tx):
33
+ # Table with normal columns
34
+ tx.table("normal_table").create(
35
+ columns={
36
+ "name": str,
37
+ "active": bool,
38
+ "value": float,
39
+ }
40
+ )
41
+
42
+ # Table with "aggregate-like" column names
43
+ # and a reserved keyword as a column name (e.g. "order")
44
+ tx.table("weird_names_table").create(
45
+ columns={
46
+ "Sum_info": str, # Looks like SUM but isn't
47
+ "MAX_hours": int, # Starts with MAX but not necessarily aggregate
48
+ "order": str, # reserved keyword in SQL, test quoting
49
+ }
50
+ )
51
+
52
+ # Parent and child tables to test foreign keys and multiple pointers
53
+ tx.table("fk_parent").create(
54
+ columns={
55
+ "parent_name": str,
56
+ "num_things": int,
57
+ "is_valid": bool,
58
+ }
59
+ )
60
+ tx.table("fk_middle").create(
61
+ columns={
62
+ "parent_id": int,
63
+ "title": str,
64
+ }
65
+ )
66
+ tx.table("fk_middle").create_foreign_key("parent_id", "fk_parent", "sys_id")
67
+
68
+ tx.table("fk_child").create(
69
+ columns={"parent_id": int, "middle_id": int, "description": str}
70
+ )
71
+ tx.table("fk_child").create_foreign_key("parent_id", "fk_parent", "sys_id")
72
+ tx.table("fk_child").create_foreign_key("middle_id", "fk_middle", "sys_id")
73
+
74
+ # Another table for testing multiple foreign references to the same table
75
+ tx.table("fk_self_ref").create(
76
+ columns={
77
+ "ref_id": int,
78
+ "info": str,
79
+ }
80
+ )
81
+ # A self referencing foreign key (if supported by environment)
82
+ tx.table("fk_self_ref").create_foreign_key("ref_id", "fk_self_ref", "sys_id")
83
+
84
+ # Table to test special operators and placeholders
85
+ tx.table("special_values_table").create(
86
+ columns={
87
+ "name": str,
88
+ "status": str,
89
+ "score": float,
90
+ }
91
+ )
92
+
93
+ @classmethod
94
+ def insert_data(cls, tx):
95
+ normal_table = tx.table("normal_table")
96
+ normal_table.upsert(
97
+ {"sys_id": 1, "name": "Alpha", "active": True, "value": 10.5}
98
+ )
99
+ normal_table.upsert(
100
+ {"sys_id": 2, "name": "Beta", "active": False, "value": None}
101
+ )
102
+
103
+ weird = tx.table("weird_names_table")
104
+ weird.upsert(
105
+ {
106
+ "sys_id": 10,
107
+ "Sum_info": "Not an Aggregate",
108
+ "MAX_hours": 40,
109
+ "order": "first",
110
+ }
111
+ )
112
+ weird.upsert(
113
+ {"sys_id": 20, "Sum_info": "Also Not", "MAX_hours": 50, "order": "second"}
114
+ )
115
+
116
+ parent = tx.table("fk_parent")
117
+ parent.upsert({"sys_id": 100, "parent_name": "P1"})
118
+ parent.upsert({"sys_id": 200, "parent_name": "P2"})
119
+
120
+ middle = tx.table("fk_middle")
121
+ middle.upsert({"sys_id": 300, "parent_id": 100, "title": "M1"})
122
+ middle.upsert({"sys_id": 400, "parent_id": 200, "title": "M2"})
123
+
124
+ child = tx.table("fk_child")
125
+ child.upsert(
126
+ {"sys_id": 500, "parent_id": 100, "middle_id": 300, "description": "C1"}
127
+ )
128
+ child.upsert(
129
+ {"sys_id": 600, "parent_id": 200, "middle_id": 400, "description": "C2"}
130
+ )
131
+
132
+ self_ref = tx.table("fk_self_ref")
133
+ self_ref.upsert({"sys_id": 700, "ref_id": None, "info": "root"})
134
+ self_ref.upsert({"sys_id": 800, "ref_id": 700, "info": "child_of_700"})
135
+ self_ref.upsert({"sys_id": 900, "ref_id": 800, "info": "child_of_800"})
136
+
137
+ special = tx.table("special_values_table")
138
+ special.upsert(
139
+ {
140
+ "sys_id": 1000,
141
+ "name": "Infinite Score",
142
+ "status": "open",
143
+ "score": 9999.99,
144
+ }
145
+ )
146
+ special.upsert(
147
+ {"sys_id": 1100, "name": "Unknown Value", "status": None, "score": None}
148
+ )
149
+ special.upsert(
150
+ {"sys_id": 1200, "name": "Regular Entry", "status": "closed", "score": 100}
151
+ )
152
+
153
+ emails = [
154
+ "test.user1@example.com",
155
+ "demo.account@domain.com",
156
+ "sample.email@test.com",
157
+ "mock.data@company.com",
158
+ "placeholder@example.com",
159
+ "testcase@domain.com",
160
+ "fakeuser@test.com",
161
+ "demodata@company.com",
162
+ ]
163
+ for i in range(10):
164
+ special.upsert(
165
+ {
166
+ "sys_id": 1300 + i,
167
+ "name": f"Entry {i}",
168
+ "status": "open",
169
+ "score": i * 10,
170
+ "email": random.choice(emails),
171
+ }
172
+ )
173
+
174
+ # @classmethod
175
+ # def tearDownClass(cls, tx):
176
+ # tx.switch_to_database("postgres")
177
+ # tx.execute(f"drop database if exists {test_db}", single=True)
@@ -0,0 +1,285 @@
1
+ import unittest
2
+ import velocity.db.exceptions
3
+ from common import TestSQLModule, engine
4
+ import time
5
+
6
+ DO_ALL = True
7
+
8
+ print(f"DO_ALL: {DO_ALL}")
9
+
10
+
11
+ @engine.transaction
12
+ class TestLocal(TestSQLModule):
13
+
14
+ def test_select_self_refer_wildcard(self, tx):
15
+ if not DO_ALL:
16
+ return
17
+
18
+ sql, vars = tx.table("fk_child").select(
19
+ sql_only=True, columns=["A.*", "parent_id>parent_name"]
20
+ )
21
+ expected_sql = """
22
+ SELECT A.*,
23
+ B.parent_name AS parent_id_parent_name
24
+ FROM fk_child AS A
25
+ LEFT JOIN fk_parent AS B ON A.parent_id = B.sys_id
26
+ """
27
+ expected_vars = ()
28
+ self.assertEqual(
29
+ sql.split(),
30
+ expected_sql.split(),
31
+ )
32
+ self.assertEqual(vars, expected_vars)
33
+
34
+ # for row in tx.table("fk_child").select(
35
+ # columns=["A.*", "parent_id>parent_name"]
36
+ # ):
37
+ # print(row)
38
+
39
+ sql, vars = tx.table("fk_self_ref").select(
40
+ sql_only=True, columns=["A.*", "ref_id>info"]
41
+ )
42
+ expected_sql = """
43
+ SELECT A.*,
44
+ B.info AS ref_id_info
45
+ FROM fk_self_ref AS A
46
+ LEFT JOIN fk_self_ref AS B ON A.ref_id = B.sys_id
47
+ """
48
+ expected_vars = ()
49
+ self.assertEqual(
50
+ sql.split(),
51
+ expected_sql.split(),
52
+ )
53
+ self.assertEqual(vars, expected_vars)
54
+ # for row in tx.table("fk_self_ref").select(columns=["A.*", "ref_id>info"]):
55
+ # print(row)
56
+
57
+ sql, vars = tx.table("fk_self_ref").select(
58
+ sql_only=True, columns=["A.*", "ref_id>info", "ref_id>ref_id"]
59
+ )
60
+ expected_sql = """
61
+ SELECT A.*,
62
+ B.info AS ref_id_info,
63
+ B.ref_id AS ref_id_ref_id
64
+ FROM fk_self_ref AS A
65
+ LEFT JOIN fk_self_ref AS B ON A.ref_id = B.sys_id
66
+ """
67
+ expected_vars = ()
68
+ self.assertEqual(
69
+ sql.split(),
70
+ expected_sql.split(),
71
+ )
72
+ self.assertEqual(vars, expected_vars)
73
+ # for row in tx.table("fk_self_ref").select(
74
+ # columns=["A.*", "ref_id>info", "ref_id>ref_id"]
75
+ # ):
76
+ # print(row)
77
+
78
+ def test_complex_update(self, tx):
79
+ if not DO_ALL:
80
+ return
81
+ sql, vars = tx.table("fk_child").update(
82
+ sql_only=True,
83
+ data={
84
+ "name": "@parent_id>parent_name",
85
+ "value": "@parent_id>num_things",
86
+ "active": "@parent_id>is_valid",
87
+ },
88
+ where={
89
+ ">parent_id>num_things": "10",
90
+ "<num_things": "10",
91
+ },
92
+ )
93
+
94
+ expected_sql = """
95
+ UPDATE fk_child AS A
96
+ SET name = %s,
97
+ "value" = %s,
98
+ active = %s
99
+ LEFT JOIN fk_parent AS B
100
+ WHERE B.num_things > %s
101
+ AND num_things < %s
102
+ AND A.parent_id = B.sys_id
103
+ """
104
+ expected_vars = (
105
+ "@parent_id>parent_name",
106
+ "@parent_id>num_things",
107
+ "@parent_id>is_valid",
108
+ "10",
109
+ "10",
110
+ )
111
+ self.assertEqual(
112
+ sql.split(),
113
+ expected_sql.split(),
114
+ )
115
+ self.assertEqual(vars, expected_vars)
116
+
117
+ sql, vars = tx.table("fk_child").update(
118
+ sql_only=True,
119
+ data={
120
+ "a": "a",
121
+ "b": "a",
122
+ "c": "a",
123
+ "d": "a",
124
+ "e": "a",
125
+ },
126
+ where={
127
+ "sys_id": "700",
128
+ },
129
+ )
130
+
131
+ expected_sql = """
132
+ UPDATE fk_child
133
+ SET a = %s,
134
+ b = %s,
135
+ c = %s,
136
+ d = %s,
137
+ e = %s
138
+ WHERE sys_id = %s
139
+ """
140
+ expected_vars = ("a", "a", "a", "a", "a", "700")
141
+ self.assertEqual(
142
+ sql.split(),
143
+ expected_sql.split(),
144
+ )
145
+ self.assertEqual(vars, expected_vars)
146
+
147
+ def test_complex_merge(self, tx):
148
+ if not DO_ALL:
149
+ return
150
+ for i in range(10):
151
+ tx.table("fk_you").insert(
152
+ data={
153
+ "sys_id": i + 1000,
154
+ "parent_id": 1,
155
+ "name": f"Child {i}",
156
+ "value": i,
157
+ "active": True,
158
+ },
159
+ )
160
+ self.assertRaises(
161
+ velocity.db.exceptions.DbDuplicateKeyError,
162
+ tx.table("fk_you").insert,
163
+ data={
164
+ "sys_id": i + 1000,
165
+ "parent_id": 2,
166
+ "name": f"Child {i}-2",
167
+ "value": i,
168
+ "active": False,
169
+ },
170
+ )
171
+ for i in range(10):
172
+ tx.table("fk_you").insert(
173
+ data={
174
+ "parent_id": 3,
175
+ "name": f"Child {i}-3",
176
+ "value": i,
177
+ "active": True,
178
+ },
179
+ )
180
+
181
+ for i in range(10):
182
+ tx.table("fk_you").merge(
183
+ data={
184
+ "sys_id": i + 1000,
185
+ "parent_id": 4,
186
+ "name": f"Child {i}4",
187
+ "value": i,
188
+ "active": False,
189
+ },
190
+ )
191
+ for i in range(10):
192
+ tx.table("fk_you").merge(
193
+ data={
194
+ "sys_id": i + 1100,
195
+ "parent_id": 4,
196
+ "name": f"Child {i}4",
197
+ "value": i,
198
+ "active": False,
199
+ },
200
+ )
201
+ tx.commit()
202
+
203
+ def test_duplicate_rows(self, tx):
204
+ sql, vars = tx.table("special_values_table").duplicate_rows(
205
+ sql_only=True,
206
+ columns=["email", "status"],
207
+ )
208
+ expected_sql = """
209
+ SELECT t.*
210
+ FROM special_values_table t
211
+ JOIN (SELECT email,
212
+ status
213
+ FROM special_values_table
214
+ GROUP BY email,
215
+ status
216
+ HAVING count(*) > %s) dup
217
+ ON t.email = dup.email AND t.status = dup.status
218
+ ORDER BY email, status
219
+ """
220
+ expected_vars = (1,)
221
+
222
+ self.assertEqual(
223
+ sql.split(),
224
+ expected_sql.split(),
225
+ )
226
+ self.assertEqual(vars, expected_vars)
227
+
228
+ # for row in tx.table("special_values_table").duplicate_rows(
229
+ # columns=["email", "status"],
230
+ # ):
231
+ # print(row)
232
+
233
+ sql, vars = tx.table("special_values_table").has_duplicates(
234
+ sql_only=True,
235
+ columns=["email", "status"],
236
+ )
237
+ expected_sql = """
238
+ SELECT 1
239
+ FROM special_values_table
240
+ GROUP BY email,
241
+ status
242
+ HAVING count(*) > %s FETCH NEXT 1 ROWS ONLY
243
+ """
244
+ expected_vars = (1,)
245
+ self.assertEqual(
246
+ sql.split(),
247
+ expected_sql.split(),
248
+ )
249
+ self.assertEqual(vars, expected_vars)
250
+
251
+ self.assertEqual(
252
+ tx.table("special_values_table").has_duplicates(
253
+ columns=["email", "status"],
254
+ ),
255
+ True,
256
+ )
257
+
258
+ expected_sql = """
259
+ SELECT 1
260
+ FROM special_values_table
261
+ GROUP BY email,
262
+ status
263
+ HAVING count(*) > %s FETCH NEXT 1 ROWS ONLY
264
+ """
265
+ expected_vars = (1,)
266
+
267
+ self.assertEqual(
268
+ sql.split(),
269
+ expected_sql.split(),
270
+ )
271
+ self.assertEqual(vars, expected_vars)
272
+ self.assertEqual(
273
+ tx.table("special_values_table").has_duplicates(
274
+ columns=["sys_id", "status"],
275
+ ),
276
+ False,
277
+ )
278
+ for row in tx.table("special_values_table").duplicate_rows(
279
+ columns=["sys_id", "status"],
280
+ ):
281
+ print(row)
282
+
283
+
284
+ if __name__ == "__main__":
285
+ unittest.main()