icsDataValidation 1.0.430__py3-none-any.whl → 1.0.439__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 (22) hide show
  1. icsDataValidation/connection_setups/sqlserver_connection_setup.py +4 -3
  2. icsDataValidation/input_parameters/testing_tool_params.py +0 -1
  3. icsDataValidation/services/database_services/snowflake_service.py +170 -67
  4. icsDataValidation/services/database_services/sqlserver_service.py +196 -88
  5. {icsdatavalidation-1.0.430.dist-info → icsdatavalidation-1.0.439.dist-info}/METADATA +1 -1
  6. {icsdatavalidation-1.0.430.dist-info → icsdatavalidation-1.0.439.dist-info}/RECORD +22 -8
  7. {icsdatavalidation-1.0.430.dist-info → icsdatavalidation-1.0.439.dist-info}/WHEEL +1 -1
  8. {icsdatavalidation-1.0.430.dist-info → icsdatavalidation-1.0.439.dist-info}/top_level.txt +1 -0
  9. tests/snowflake_service/test_create_checksums.py +146 -0
  10. tests/snowflake_service/test_create_pandas_df_from_group_by.py +485 -0
  11. tests/snowflake_service/test_create_pandas_df_from_sample.py +444 -0
  12. tests/snowflake_service/test_get_checksum_statement.py +243 -0
  13. tests/snowflake_service/test_get_column_clause.py +305 -0
  14. tests/snowflake_service/test_get_countnulls_statement.py +128 -0
  15. tests/snowflake_service/test_get_in_clause.py +66 -0
  16. tests/sqlserver_service/test_create_checksums.py +153 -0
  17. tests/sqlserver_service/test_create_pandas_df_from_group_by.py +427 -0
  18. tests/sqlserver_service/test_create_pandas_df_from_sample.py +286 -0
  19. tests/sqlserver_service/test_get_checksum_statement.py +160 -0
  20. tests/sqlserver_service/test_get_column_clause.py +182 -0
  21. tests/sqlserver_service/test_get_countnulls_statement.py +121 -0
  22. tests/sqlserver_service/test_get_in_clause.py +87 -0
@@ -0,0 +1,286 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pandas as pd
4
+ import pytest
5
+
6
+ from icsDataValidation.core.database_objects import DatabaseObject, DatabaseObjectType
7
+ from icsDataValidation.services.database_services.sqlserver_service import SQLServerService
8
+
9
+
10
+ @pytest.fixture
11
+ def sqlserver_service():
12
+ """Create a SQLServerService instance with mocked connection."""
13
+ connection_params = {
14
+ 'Driver': 'ODBC Driver 18 for SQL Server',
15
+ 'Server': 'localhost',
16
+ 'Port': '1433',
17
+ 'Database': 'testdb',
18
+ 'User': 'sa',
19
+ 'Password': 'password',
20
+ 'Encrypt': True,
21
+ 'TrustServerCertificate': True
22
+ }
23
+ service = SQLServerService(connection_params=connection_params)
24
+ service.sqlserver_connection = MagicMock()
25
+ return service
26
+
27
+
28
+ @pytest.fixture
29
+ def mock_database_object():
30
+ """Create a mock DatabaseObject."""
31
+ obj = DatabaseObject(
32
+ object_identifier="TEST_DB.TEST_SCHEMA.TEST_TABLE",
33
+ object_type=DatabaseObjectType.TABLE
34
+ )
35
+ return obj
36
+
37
+
38
+ class TestCreatePandasDfFromSampleParametrized:
39
+ """Parametrized tests for create_pandas_df_from_sample method."""
40
+
41
+ @pytest.mark.parametrize(
42
+ "column_intersections,key_columns,exclude_columns,dedicated_columns,"
43
+ "where_clause,key_filters,sample_count,numeric_scale,enclose_quotes,"
44
+ "mock_datatypes,mock_column_clause,mock_in_clause,expected_contains,expected_not_in",
45
+ [
46
+ ( # simple case with key columns
47
+ ['id', 'name', 'amount'],
48
+ ['id'],
49
+ [],
50
+ [],
51
+ "",
52
+ {},
53
+ 10,
54
+ None,
55
+ False,
56
+ [
57
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
58
+ {"COLUMN_NAME": "name", "DATA_TYPE": "varchar"},
59
+ {"COLUMN_NAME": "amount", "DATA_TYPE": "int"}
60
+ ],
61
+ ("[id] as [id], [name] AS [name], [amount] as [amount]", ['id', 'amount'], ['id', 'name', 'amount']),
62
+ None,
63
+ [
64
+ "SELECT TOP (10) [id] as [id], [name] AS [name], [amount] as [amount]",
65
+ "FROM TEST_SCHEMA.TEST_TABLE",
66
+ "WHERE 1=1",
67
+ "ORDER BY [id];"
68
+ ],
69
+ []
70
+ ),
71
+ ( # multiple key columns
72
+ ['id', 'region', 'amount'],
73
+ ['id', 'region'],
74
+ [],
75
+ [],
76
+ "",
77
+ {},
78
+ 10,
79
+ None,
80
+ False,
81
+ [
82
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
83
+ {"COLUMN_NAME": "region", "DATA_TYPE": "varchar"},
84
+ {"COLUMN_NAME": "amount", "DATA_TYPE": "int"}
85
+ ],
86
+ ("[id] as [id], [region] AS [region], [amount] as [amount]", ['id', 'amount'], ['id', 'region', 'amount']),
87
+ None,
88
+ ["ORDER BY [id], [region];"],
89
+ []
90
+ ),
91
+ ( # with where clause
92
+ ['id', 'status'],
93
+ ['id'],
94
+ [],
95
+ [],
96
+ "WHERE status = 'active'",
97
+ {},
98
+ 10,
99
+ None,
100
+ False,
101
+ [
102
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
103
+ {"COLUMN_NAME": "status", "DATA_TYPE": "varchar"}
104
+ ],
105
+ ("[id] as [id], [status] AS [status]", ['id'], ['id', 'status']),
106
+ None,
107
+ ["WHERE status = 'active'", "ORDER BY [id];"],
108
+ []
109
+ ),
110
+ ( # excluded columns
111
+ ['id', 'name', 'secret'],
112
+ ['id'],
113
+ ['secret'],
114
+ [],
115
+ "",
116
+ {},
117
+ 10,
118
+ None,
119
+ False,
120
+ [
121
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
122
+ {"COLUMN_NAME": "name", "DATA_TYPE": "varchar"}
123
+ ],
124
+ ("[id] as [id], [name] AS [name]", ['id'], ['id', 'name']),
125
+ None,
126
+ ["[id]", "[name]", "ORDER BY [id];"],
127
+ ["[secret]"]
128
+ ),
129
+ ( # with key filters
130
+ ['id', 'region', 'amount'],
131
+ ['id', 'region'],
132
+ [],
133
+ [],
134
+ "",
135
+ {'id': [1, 2], 'region': ['US', 'EU']},
136
+ 10,
137
+ None,
138
+ False,
139
+ [
140
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
141
+ {"COLUMN_NAME": "region", "DATA_TYPE": "varchar"},
142
+ {"COLUMN_NAME": "amount", "DATA_TYPE": "int"}
143
+ ],
144
+ ("[id] as [id], [region] AS [region], [amount] as [amount]", ['id', 'amount'], ['id', 'region', 'amount']),
145
+ " AND (CONCAT(cast(ROUND([id], 0) as numeric(38, 0)), '|' ,[region], '|') in ('1|US|','2|EU|'))",
146
+ [
147
+ "AND (CONCAT(cast(ROUND([id], 0) as numeric(38, 0)), '|' ,[region], '|') in ('1|US|','2|EU|'))",
148
+ "ORDER BY [id], [region];"
149
+ ],
150
+ []
151
+ ),
152
+ ( # dedicated columns
153
+ ['id', 'name', 'amount', 'description'],
154
+ ['id'],
155
+ [],
156
+ ['id', 'name'],
157
+ "",
158
+ {},
159
+ 10,
160
+ None,
161
+ False,
162
+ [
163
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
164
+ {"COLUMN_NAME": "name", "DATA_TYPE": "varchar"}
165
+ ],
166
+ ("[id] as [id], [name] AS [name]", ['id'], ['id', 'name']),
167
+ None,
168
+ ["[id]", "[name]", "ORDER BY [id];"],
169
+ ["[amount]", "[description]"]
170
+ ),
171
+ ( # custom sample count
172
+ ['id', 'name'],
173
+ ['id'],
174
+ [],
175
+ [],
176
+ "",
177
+ {},
178
+ 50,
179
+ None,
180
+ False,
181
+ [
182
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
183
+ {"COLUMN_NAME": "name", "DATA_TYPE": "varchar"}
184
+ ],
185
+ ("[id] as [id], [name] AS [name]", ['id'], ['id', 'name']),
186
+ None,
187
+ ["SELECT TOP (50)"] ,
188
+ []
189
+ ),
190
+ ( # no key columns (uses ORDER BY NEWID())
191
+ ['id', 'name', 'amount'],
192
+ [],
193
+ [],
194
+ [],
195
+ "",
196
+ {},
197
+ 10,
198
+ None,
199
+ False,
200
+ [
201
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
202
+ {"COLUMN_NAME": "name", "DATA_TYPE": "varchar"},
203
+ {"COLUMN_NAME": "amount", "DATA_TYPE": "int"}
204
+ ],
205
+ ("[id] as [id], [name] AS [name], [amount] as [amount]", ['id', 'amount'], ['id', 'name', 'amount']),
206
+ None,
207
+ ["SELECT TOP (10) [id] as [id], [name] AS [name], [amount] as [amount]", "WHERE 1=1", "ORDER BY NEWID();"],
208
+ ["ORDER BY ["]
209
+ ),
210
+ ( # with numeric scale, no double quotes
211
+ ['id', 'price'],
212
+ ['id'],
213
+ [],
214
+ [],
215
+ "",
216
+ {},
217
+ 10,
218
+ 2,
219
+ False,
220
+ [
221
+ {"COLUMN_NAME": "id", "DATA_TYPE": "int"},
222
+ {"COLUMN_NAME": "price", "DATA_TYPE": "decimal"}
223
+ ],
224
+ ("[id] as [id], CAST(ROUND([price], 2) as decimal(38,2)) as [price]", ['id', 'price'], ['id', 'price']),
225
+ None,
226
+ ["CAST(ROUND([price], 2) as decimal(38,2)) as [price]"],
227
+ []
228
+ ),
229
+ ( # special characters
230
+ ['User ID', 'Full Name', 'Email-Address'],
231
+ ['User ID'],
232
+ [],
233
+ [],
234
+ "",
235
+ {},
236
+ 10,
237
+ None,
238
+ False,
239
+ [
240
+ {"COLUMN_NAME": "User ID", "DATA_TYPE": "int"},
241
+ {"COLUMN_NAME": "Full Name", "DATA_TYPE": "varchar"},
242
+ {"COLUMN_NAME": "Email-Address", "DATA_TYPE": "varchar"}
243
+ ],
244
+ ("[User ID] as [User ID], [Full Name] AS [Full Name], [Email-Address] AS [Email-Address]", ['User ID'], ['User ID', 'Full Name', 'Email-Address']),
245
+ None,
246
+ ["[User ID]", "[Full Name]", "[Email-Address]", "ORDER BY [User ID];"],
247
+ []
248
+ ),
249
+ ],
250
+ )
251
+ def test_create_pandas_df_from_sample(
252
+ self, sqlserver_service, mock_database_object,
253
+ column_intersections, key_columns, exclude_columns, dedicated_columns,
254
+ where_clause, key_filters, sample_count, numeric_scale, enclose_quotes,
255
+ mock_datatypes, mock_column_clause, mock_in_clause,
256
+ expected_contains, expected_not_in
257
+ ):
258
+ """Test create_pandas_df_from_sample with various configurations."""
259
+ with patch.object(sqlserver_service, 'get_data_types_from_object') as mock_get_datatypes, \
260
+ patch.object(sqlserver_service, '_get_column_clause') as mock_get_column, \
261
+ patch.object(sqlserver_service, '_get_in_clause') as mock_get_in, \
262
+ patch.object(sqlserver_service, 'execute_queries') as mock_execute:
263
+
264
+ mock_get_datatypes.return_value = mock_datatypes
265
+ mock_get_column.return_value = mock_column_clause
266
+ if mock_in_clause:
267
+ mock_get_in.return_value = mock_in_clause
268
+ mock_execute.return_value = pd.DataFrame({'id': [1], 'name': ['A'], 'region': ['US'], 'User ID': [123]})
269
+
270
+ result_list, key_dict, used_columns, sample_query = sqlserver_service.create_pandas_df_from_sample(
271
+ object=mock_database_object,
272
+ column_intersections=column_intersections,
273
+ key_columns=key_columns,
274
+ exclude_columns=exclude_columns,
275
+ dedicated_columns=dedicated_columns,
276
+ where_clause=where_clause,
277
+ key_filters=key_filters,
278
+ sample_count=sample_count,
279
+ numeric_scale=numeric_scale,
280
+ enclose_column_by_double_quotes=enclose_quotes
281
+ )
282
+
283
+ for expected in expected_contains:
284
+ assert expected in sample_query
285
+ for expected in expected_not_in:
286
+ assert expected not in sample_query
@@ -0,0 +1,160 @@
1
+ from unittest.mock import MagicMock, Mock
2
+
3
+ import pytest
4
+
5
+ from icsDataValidation.core.database_objects import DatabaseObject
6
+ from icsDataValidation.services.database_services.sqlserver_service import SQLServerService
7
+
8
+
9
+ @pytest.fixture
10
+ def sqlserver_service():
11
+ """Create a SQLServerService instance with mocked connection."""
12
+ connection_params = {
13
+ 'Driver': 'ODBC Driver 18 for SQL Server',
14
+ 'Server': 'localhost',
15
+ 'Port': '1433',
16
+ 'Database': 'testdb',
17
+ 'User': 'sa',
18
+ 'Password': 'password',
19
+ 'Encrypt': True,
20
+ 'TrustServerCertificate': True
21
+ }
22
+ service = SQLServerService(connection_params=connection_params)
23
+ service.sqlserver_connection = MagicMock()
24
+ return service
25
+
26
+
27
+ @pytest.fixture
28
+ def mock_database_object():
29
+ """Create a mock DatabaseObject."""
30
+ obj = Mock(spec=DatabaseObject)
31
+ obj.database = "TestDB"
32
+ obj.schema = "dbo"
33
+ obj.name = "TestTable"
34
+ obj.type = "table"
35
+ return obj
36
+
37
+
38
+ class TestGetChecksumStatementParametrized:
39
+ """Parametrized tests for _get_checksum_statement method."""
40
+
41
+ @pytest.mark.parametrize(
42
+ "columns,mock_datatypes,exclude_columns,where_clause,numeric_scale," \
43
+ "expected_contains,expected_not_in",
44
+ [
45
+ ( # numeric column with scale
46
+ ["Amount"],
47
+ [{"COLUMN_NAME": "AMOUNT", "DATA_TYPE": "decimal"}],
48
+ [],
49
+ "",
50
+ 2,
51
+ ["SUM([AMOUNT])", "DECIMAL(38, 2)", "AS [SUM_AMOUNT]", "FROM dbo.TestTable"],
52
+ []
53
+ ),
54
+ ( # numeric column without scale
55
+ ["Amount"],
56
+ [{"COLUMN_NAME": "AMOUNT", "DATA_TYPE": "int"}],
57
+ [],
58
+ "",
59
+ None,
60
+ ["SUM([AMOUNT])", "AS [SUM_AMOUNT]"],
61
+ ["DECIMAL(38,"]
62
+ ),
63
+ ( # string column
64
+ ["Name"],
65
+ [{"COLUMN_NAME": "NAME", "DATA_TYPE": "varchar"}],
66
+ [],
67
+ "",
68
+ None,
69
+ ["COUNT(DISTINCT LOWER([NAME]))", "AS [COUNTDISTINCT_NAME]"],
70
+ []
71
+ ),
72
+ ( # boolean column
73
+ ["IsActive"],
74
+ [{"COLUMN_NAME": "ISACTIVE", "DATA_TYPE": "bit"}],
75
+ [],
76
+ "",
77
+ None,
78
+ ["COUNT(CASE WHEN [ISACTIVE] = 1", "COUNT(CASE WHEN [ISACTIVE] = 0", "AS [AGGREGATEBOOLEAN_ISACTIVE]"],
79
+ []
80
+ ),
81
+ ( # with where clause
82
+ ["Amount"],
83
+ [{"COLUMN_NAME": "AMOUNT", "DATA_TYPE": "int"}],
84
+ [],
85
+ "WHERE Amount > 100",
86
+ None,
87
+ ["WHERE Amount > 100"],
88
+ []
89
+ ),
90
+ ( # exclude columns
91
+ ["Amount", "Price"],
92
+ [{"COLUMN_NAME": "AMOUNT", "DATA_TYPE": "int"}],
93
+ ["Price"],
94
+ "",
95
+ None,
96
+ ["AMOUNT"],
97
+ ["PRICE"]
98
+ ),
99
+ ( # multiple columns mixed types
100
+ ["Amount", "Name", "IsActive"],
101
+ [
102
+ {"COLUMN_NAME": "AMOUNT", "DATA_TYPE": "decimal"},
103
+ {"COLUMN_NAME": "NAME", "DATA_TYPE": "varchar"},
104
+ {"COLUMN_NAME": "ISACTIVE", "DATA_TYPE": "bit"}
105
+ ],
106
+ [],
107
+ "",
108
+ 2,
109
+ ["SUM([AMOUNT])", "COUNT(DISTINCT LOWER([NAME]))", "AGGREGATEBOOLEAN_ISACTIVE"],
110
+ []
111
+ ),
112
+ ( # binary column
113
+ ["BinaryData"],
114
+ [{"COLUMN_NAME": "BINARYDATA", "DATA_TYPE": "varbinary"}],
115
+ [],
116
+ "",
117
+ None,
118
+ ["TRY_CONVERT(VARCHAR,[BINARYDATA])", "COUNT(DISTINCT LOWER("],
119
+ []
120
+ ),
121
+ ( # date column
122
+ ["CreatedDate"],
123
+ [{"COLUMN_NAME": "CREATEDDATE", "DATA_TYPE": "datetime"}],
124
+ [],
125
+ "",
126
+ None,
127
+ ["COUNT(DISTINCT LOWER([CREATEDDATE]))", "AS [COUNTDISTINCT_CREATEDDATE]"],
128
+ []
129
+ ),
130
+ ( # special characters in column names
131
+ ["/ISDFPS/OBJNR"],
132
+ [{"COLUMN_NAME": "/ISDFPS/OBJNR", "DATA_TYPE": "varchar"}],
133
+ [],
134
+ "",
135
+ None,
136
+ ["[/ISDFPS/OBJNR]", "AS [COUNTDISTINCT_/ISDFPS/OBJNR]"],
137
+ []
138
+ ),
139
+ ],
140
+ )
141
+ def test_get_checksum_statement(
142
+ self, sqlserver_service, mock_database_object,
143
+ columns, mock_datatypes, exclude_columns, where_clause, numeric_scale,
144
+ expected_contains, expected_not_in
145
+ ):
146
+ """Test checksum statement with various configurations."""
147
+ sqlserver_service.get_data_types_from_object = MagicMock(return_value=mock_datatypes)
148
+
149
+ result = sqlserver_service._get_checksum_statement(
150
+ object=mock_database_object,
151
+ column_intersections=columns,
152
+ exclude_columns=exclude_columns,
153
+ where_clause=where_clause,
154
+ numeric_scale=numeric_scale
155
+ )
156
+
157
+ for expected in expected_contains:
158
+ assert expected in result
159
+ for expected in expected_not_in:
160
+ assert expected not in result
@@ -0,0 +1,182 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+
5
+ from icsDataValidation.services.database_services.sqlserver_service import SQLServerService
6
+
7
+
8
+ @pytest.fixture
9
+ def sqlserver_service():
10
+ """Create a SQLServerService instance with mocked connection."""
11
+ connection_params = {
12
+ 'Driver': 'ODBC Driver 18 for SQL Server',
13
+ 'Server': 'localhost',
14
+ 'Port': '1433',
15
+ 'Database': 'testdb',
16
+ 'User': 'sa',
17
+ 'Password': 'password',
18
+ 'Encrypt': True,
19
+ 'TrustServerCertificate': True
20
+ }
21
+ service = SQLServerService(connection_params=connection_params)
22
+ service.sqlserver_connection = MagicMock()
23
+ return service
24
+
25
+
26
+ class TestGetColumnClauseParametrized:
27
+ """Parametrized tests for _get_column_clause method."""
28
+
29
+ @pytest.mark.parametrize(
30
+ "column_list,columns_datatype,numeric_scale,key_columns," \
31
+ "expected_in_clause,expected_numeric,expected_used",
32
+ [
33
+ ( # numeric with scale
34
+ ["Amount"],
35
+ [{"COLUMN_NAME": "Amount", "DATA_TYPE": "decimal"}],
36
+ 2,
37
+ [],
38
+ ["CAST(ROUND([Amount], 2) as decimal(38,2)) as [Amount]"],
39
+ ["Amount"],
40
+ ["Amount"]
41
+ ),
42
+ ( # numeric without scale
43
+ ["Price"],
44
+ [{"COLUMN_NAME": "Price", "DATA_TYPE": "int"}],
45
+ None,
46
+ [],
47
+ ["[Price] as [Price]"],
48
+ ["Price"],
49
+ ["Price"]
50
+ ),
51
+ ( # string column
52
+ ["Name"],
53
+ [{"COLUMN_NAME": "Name", "DATA_TYPE": "varchar"}],
54
+ 2,
55
+ [],
56
+ ["[Name] AS [Name]"],
57
+ [],
58
+ ["Name"]
59
+ ),
60
+ ( # date excluded by default
61
+ ["CreatedDate"],
62
+ [{"COLUMN_NAME": "CreatedDate", "DATA_TYPE": "datetime"}],
63
+ 2,
64
+ [],
65
+ [],
66
+ [],
67
+ []
68
+ ),
69
+ ( # date as key column
70
+ ["CreatedDate"],
71
+ [{"COLUMN_NAME": "CreatedDate", "DATA_TYPE": "datetime"}],
72
+ 2,
73
+ ["CreatedDate"],
74
+ ["CreatedDate"],
75
+ [],
76
+ ["CreatedDate"]
77
+ ),
78
+ ( # multiple mixed types
79
+ ["Amount", "Name", "IsActive"],
80
+ [
81
+ {"COLUMN_NAME": "Amount", "DATA_TYPE": "decimal"},
82
+ {"COLUMN_NAME": "Name", "DATA_TYPE": "varchar"},
83
+ {"COLUMN_NAME": "IsActive", "DATA_TYPE": "bit"}
84
+ ],
85
+ 2,
86
+ [],
87
+ ["CAST(ROUND([Amount], 2)", "[Name] AS [Name]", "IsActive"],
88
+ ["Amount"],
89
+ ["Amount", "Name", "IsActive"]
90
+ ),
91
+ ( # special characters in column names
92
+ ["/ISDFPS/OBJNR", "MANDT"],
93
+ [
94
+ {"COLUMN_NAME": "/ISDFPS/OBJNR", "DATA_TYPE": "varchar"},
95
+ {"COLUMN_NAME": "MANDT", "DATA_TYPE": "varchar"}
96
+ ],
97
+ 2,
98
+ [],
99
+ ["[/ISDFPS/OBJNR]", "[MANDT]"],
100
+ [],
101
+ ["/ISDFPS/OBJNR", "MANDT"]
102
+ ),
103
+ ( # binary column
104
+ ["BinaryData"],
105
+ [{"COLUMN_NAME": "BinaryData", "DATA_TYPE": "varbinary"}],
106
+ 2,
107
+ [],
108
+ ["BinaryData"],
109
+ [],
110
+ ["BinaryData"]
111
+ ),
112
+ ( # empty column list
113
+ [],
114
+ [],
115
+ 2,
116
+ [],
117
+ [],
118
+ [],
119
+ []
120
+ ),
121
+ ( # all numeric columns with scale
122
+ ["Amount", "Price", "Quantity"],
123
+ [
124
+ {"COLUMN_NAME": "Amount", "DATA_TYPE": "decimal"},
125
+ {"COLUMN_NAME": "Price", "DATA_TYPE": "money"},
126
+ {"COLUMN_NAME": "Quantity", "DATA_TYPE": "int"}
127
+ ],
128
+ 3,
129
+ [],
130
+ ["decimal(38,3)"],
131
+ ["Amount", "Price", "Quantity"],
132
+ ["Amount", "Price", "Quantity"]
133
+ ),
134
+ ( # numeric with zero scale
135
+ ["Count"],
136
+ [{"COLUMN_NAME": "Count", "DATA_TYPE": "int"}],
137
+ 0,
138
+ [],
139
+ ["[Count] as [Count]"],
140
+ ["Count"],
141
+ ["Count"]
142
+ ),
143
+ ( # text and ntext columns
144
+ ["Description", "Notes"],
145
+ [
146
+ {"COLUMN_NAME": "Description", "DATA_TYPE": "text"},
147
+ {"COLUMN_NAME": "Notes", "DATA_TYPE": "ntext"}
148
+ ],
149
+ 2,
150
+ [],
151
+ ["[Description] AS [Description]", "[Notes] AS [Notes]"],
152
+ [],
153
+ ["Description", "Notes"]
154
+ ),
155
+ ( # multiple date columns with one as key
156
+ ["CreatedDate", "ModifiedDate", "Id"],
157
+ [
158
+ {"COLUMN_NAME": "CreatedDate", "DATA_TYPE": "datetime"},
159
+ {"COLUMN_NAME": "ModifiedDate", "DATA_TYPE": "datetime2"},
160
+ {"COLUMN_NAME": "Id", "DATA_TYPE": "int"}
161
+ ],
162
+ 2,
163
+ ["CreatedDate"],
164
+ ["CreatedDate", "Id"],
165
+ ["Id"],
166
+ ["CreatedDate", "Id"]
167
+ ),
168
+ ],
169
+ )
170
+ def test_get_column_clause(
171
+ self, sqlserver_service, column_list, columns_datatype, numeric_scale,
172
+ key_columns, expected_in_clause, expected_numeric, expected_used
173
+ ):
174
+ """Test column clause with various configurations."""
175
+ column_clause, numeric_columns, used_columns = sqlserver_service._get_column_clause(
176
+ column_list, columns_datatype, numeric_scale, key_columns
177
+ )
178
+
179
+ for expected in expected_in_clause:
180
+ assert expected in column_clause
181
+ assert set(numeric_columns) == set(expected_numeric)
182
+ assert set(used_columns) == set(expected_used)