velocity-python 0.0.132__py3-none-any.whl → 0.0.134__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

Files changed (64) hide show
  1. velocity/__init__.py +1 -1
  2. velocity/app/tests/__init__.py +1 -0
  3. velocity/app/tests/test_email_processing.py +112 -0
  4. velocity/app/tests/test_payment_profile_sorting.py +191 -0
  5. velocity/app/tests/test_spreadsheet_functions.py +124 -0
  6. velocity/aws/tests/__init__.py +1 -0
  7. velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
  8. velocity/aws/tests/test_response.py +163 -0
  9. velocity/db/core/decorators.py +20 -3
  10. velocity/db/core/engine.py +33 -7
  11. velocity/db/exceptions.py +7 -0
  12. velocity/db/servers/base/initializer.py +2 -1
  13. velocity/db/servers/mysql/__init__.py +13 -4
  14. velocity/db/servers/postgres/__init__.py +14 -4
  15. velocity/db/servers/sqlite/__init__.py +13 -4
  16. velocity/db/servers/sqlserver/__init__.py +13 -4
  17. velocity/db/tests/__init__.py +1 -0
  18. velocity/db/tests/common_db_test.py +0 -0
  19. velocity/db/tests/postgres/__init__.py +1 -0
  20. velocity/db/tests/postgres/common.py +49 -0
  21. velocity/db/tests/postgres/test_column.py +29 -0
  22. velocity/db/tests/postgres/test_connections.py +25 -0
  23. velocity/db/tests/postgres/test_database.py +21 -0
  24. velocity/db/tests/postgres/test_engine.py +205 -0
  25. velocity/db/tests/postgres/test_general_usage.py +88 -0
  26. velocity/db/tests/postgres/test_imports.py +8 -0
  27. velocity/db/tests/postgres/test_result.py +19 -0
  28. velocity/db/tests/postgres/test_row.py +137 -0
  29. velocity/db/tests/postgres/test_schema_locking.py +335 -0
  30. velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
  31. velocity/db/tests/postgres/test_sequence.py +34 -0
  32. velocity/db/tests/postgres/test_table.py +101 -0
  33. velocity/db/tests/postgres/test_transaction.py +106 -0
  34. velocity/db/tests/sql/__init__.py +1 -0
  35. velocity/db/tests/sql/common.py +177 -0
  36. velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
  37. velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
  38. velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
  39. velocity/db/tests/test_db_utils.py +221 -0
  40. velocity/db/tests/test_postgres.py +212 -0
  41. velocity/db/tests/test_postgres_unchanged.py +81 -0
  42. velocity/db/tests/test_process_error_robustness.py +292 -0
  43. velocity/db/tests/test_result_caching.py +279 -0
  44. velocity/db/tests/test_result_sql_aware.py +117 -0
  45. velocity/db/tests/test_row_get_missing_column.py +72 -0
  46. velocity/db/tests/test_schema_locking_initializers.py +226 -0
  47. velocity/db/tests/test_schema_locking_simple.py +97 -0
  48. velocity/db/tests/test_sql_builder.py +165 -0
  49. velocity/db/tests/test_tablehelper.py +486 -0
  50. velocity/misc/tests/__init__.py +1 -0
  51. velocity/misc/tests/test_db.py +90 -0
  52. velocity/misc/tests/test_fix.py +78 -0
  53. velocity/misc/tests/test_format.py +64 -0
  54. velocity/misc/tests/test_iconv.py +203 -0
  55. velocity/misc/tests/test_merge.py +82 -0
  56. velocity/misc/tests/test_oconv.py +144 -0
  57. velocity/misc/tests/test_original_error.py +52 -0
  58. velocity/misc/tests/test_timer.py +74 -0
  59. {velocity_python-0.0.132.dist-info → velocity_python-0.0.134.dist-info}/METADATA +1 -1
  60. velocity_python-0.0.134.dist-info/RECORD +125 -0
  61. velocity_python-0.0.132.dist-info/RECORD +0 -76
  62. {velocity_python-0.0.132.dist-info → velocity_python-0.0.134.dist-info}/WHEEL +0 -0
  63. {velocity_python-0.0.132.dist-info → velocity_python-0.0.134.dist-info}/licenses/LICENSE +0 -0
  64. {velocity_python-0.0.132.dist-info → velocity_python-0.0.134.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test to verify that Row.get() handles missing columns gracefully.
4
+ """
5
+
6
+ import unittest
7
+ from unittest.mock import Mock, patch
8
+ from velocity.db.core.row import Row
9
+ from velocity.db.exceptions import DbColumnMissingError
10
+
11
+
12
+ class TestRowGetMissingColumn(unittest.TestCase):
13
+
14
+ def setUp(self):
15
+ """Set up a mock row for testing."""
16
+ # Create a mock table
17
+ self.mock_table = Mock()
18
+
19
+ # Create a row instance
20
+ self.row = Row(self.mock_table, {"id": 1})
21
+
22
+ def test_get_existing_column(self):
23
+ """Test that get() works normally for existing columns."""
24
+ # Mock the table.get_value to return a normal value
25
+ self.mock_table.get_value.return_value = "test_value"
26
+
27
+ result = self.row.get("existing_column")
28
+ self.assertEqual(result, "test_value")
29
+
30
+ def test_get_missing_column_with_db_column_missing_error(self):
31
+ """Test that get() returns default when DbColumnMissingError is raised."""
32
+ # Mock the table.get_value to raise DbColumnMissingError
33
+ self.mock_table.get_value.side_effect = DbColumnMissingError(
34
+ 'Column "nonexistent" does not exist'
35
+ )
36
+
37
+ result = self.row.get("nonexistent", "default_value")
38
+ self.assertEqual(result, "default_value")
39
+
40
+ def test_get_missing_column_with_generic_error(self):
41
+ """Test that get() returns default when a generic column error is raised."""
42
+ # Mock the table.get_value to raise a generic exception with column error message
43
+ generic_error = Exception('column "descriptor" does not exist')
44
+ self.mock_table.get_value.side_effect = generic_error
45
+
46
+ result = self.row.get("descriptor", "default_value")
47
+ self.assertEqual(result, "default_value")
48
+
49
+ def test_get_missing_column_no_default(self):
50
+ """Test that get() returns None when no default is provided."""
51
+ # Mock the table.get_value to raise DbColumnMissingError
52
+ self.mock_table.get_value.side_effect = DbColumnMissingError(
53
+ 'Column "nonexistent" does not exist'
54
+ )
55
+
56
+ result = self.row.get("nonexistent")
57
+ self.assertIsNone(result)
58
+
59
+ def test_get_other_exception_reraises(self):
60
+ """Test that get() re-raises non-column-related exceptions."""
61
+ # Mock the table.get_value to raise a different type of exception
62
+ other_error = Exception("Some other database error")
63
+ self.mock_table.get_value.side_effect = other_error
64
+
65
+ with self.assertRaises(Exception) as context:
66
+ self.row.get("some_column")
67
+
68
+ self.assertEqual(str(context.exception), "Some other database error")
69
+
70
+
71
+ if __name__ == "__main__":
72
+ unittest.main()
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test schema locking support in all database initializers.
4
+ """
5
+
6
+ import unittest
7
+ import os
8
+ from unittest.mock import patch, MagicMock
9
+
10
+ from velocity.db.servers.mysql import MySQLInitializer
11
+ from velocity.db.servers.sqlite import SQLiteInitializer
12
+ from velocity.db.servers.sqlserver import SQLServerInitializer
13
+ from velocity.db.servers.postgres import PostgreSQLInitializer
14
+
15
+
16
+ class TestSchemaLockingInitializers(unittest.TestCase):
17
+ """Test that all database initializers support schema locking parameters."""
18
+
19
+ def setUp(self):
20
+ """Set up test environment."""
21
+ # Mock the database drivers
22
+ self.mock_mysql = MagicMock()
23
+ self.mock_sqlite = MagicMock()
24
+ self.mock_pytds = MagicMock()
25
+ self.mock_psycopg2 = MagicMock()
26
+
27
+ @patch('mysql.connector')
28
+ def test_mysql_schema_locked_parameter(self, mock_mysql_connector):
29
+ """Test MySQL initializer accepts schema_locked parameter."""
30
+ mock_mysql_connector.return_value = self.mock_mysql
31
+
32
+ config = {
33
+ "database": "test_db",
34
+ "host": "localhost",
35
+ "user": "test_user",
36
+ "password": "test_pass"
37
+ }
38
+
39
+ # Test with schema_locked=True
40
+ engine = MySQLInitializer.initialize(config, schema_locked=True)
41
+ self.assertTrue(engine.schema_locked)
42
+
43
+ # Test with schema_locked=False (default)
44
+ engine = MySQLInitializer.initialize(config, schema_locked=False)
45
+ self.assertFalse(engine.schema_locked)
46
+
47
+ # Test default behavior
48
+ engine = MySQLInitializer.initialize(config)
49
+ self.assertFalse(engine.schema_locked)
50
+
51
+ def test_sqlite_schema_locked_parameter(self):
52
+ """Test SQLite initializer accepts schema_locked parameter."""
53
+ config = {"database": ":memory:"}
54
+
55
+ # Test with schema_locked=True
56
+ engine = SQLiteInitializer.initialize(config, schema_locked=True)
57
+ self.assertTrue(engine.schema_locked)
58
+
59
+ # Test with schema_locked=False (default)
60
+ engine = SQLiteInitializer.initialize(config, schema_locked=False)
61
+ self.assertFalse(engine.schema_locked)
62
+
63
+ # Test default behavior
64
+ engine = SQLiteInitializer.initialize(config)
65
+ self.assertFalse(engine.schema_locked)
66
+
67
+ @patch('pytds')
68
+ def test_sqlserver_schema_locked_parameter(self, mock_pytds):
69
+ """Test SQL Server initializer accepts schema_locked parameter."""
70
+ mock_pytds.return_value = self.mock_pytds
71
+
72
+ config = {
73
+ "database": "test_db",
74
+ "server": "localhost",
75
+ "user": "test_user",
76
+ "password": "test_pass"
77
+ }
78
+
79
+ # Test with schema_locked=True
80
+ engine = SQLServerInitializer.initialize(config, schema_locked=True)
81
+ self.assertTrue(engine.schema_locked)
82
+
83
+ # Test with schema_locked=False (default)
84
+ engine = SQLServerInitializer.initialize(config, schema_locked=False)
85
+ self.assertFalse(engine.schema_locked)
86
+
87
+ # Test default behavior
88
+ engine = SQLServerInitializer.initialize(config)
89
+ self.assertFalse(engine.schema_locked)
90
+
91
+ @patch('psycopg2')
92
+ def test_postgres_schema_locked_parameter(self, mock_psycopg2):
93
+ """Test PostgreSQL initializer accepts schema_locked parameter."""
94
+ mock_psycopg2.return_value = self.mock_psycopg2
95
+
96
+ config = {
97
+ "database": "test_db",
98
+ "host": "localhost",
99
+ "user": "test_user",
100
+ "password": "test_pass"
101
+ }
102
+
103
+ # Test with schema_locked=True
104
+ engine = PostgreSQLInitializer.initialize(config, schema_locked=True)
105
+ self.assertTrue(engine.schema_locked)
106
+
107
+ # Test with schema_locked=False (default)
108
+ engine = PostgreSQLInitializer.initialize(config, schema_locked=False)
109
+ self.assertFalse(engine.schema_locked)
110
+
111
+ # Test default behavior
112
+ engine = PostgreSQLInitializer.initialize(config)
113
+ self.assertFalse(engine.schema_locked)
114
+
115
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "true"})
116
+ @patch('mysql.connector')
117
+ def test_mysql_environment_variable_override(self, mock_mysql_connector):
118
+ """Test MySQL respects VELOCITY_SCHEMA_LOCKED environment variable."""
119
+ mock_mysql_connector.return_value = self.mock_mysql
120
+
121
+ config = {
122
+ "database": "test_db",
123
+ "host": "localhost",
124
+ "user": "test_user",
125
+ "password": "test_pass"
126
+ }
127
+
128
+ # Environment variable should override default
129
+ engine = MySQLInitializer.initialize(config)
130
+ self.assertTrue(engine.schema_locked)
131
+
132
+ # Environment variable should override explicit False
133
+ engine = MySQLInitializer.initialize(config, schema_locked=False)
134
+ self.assertTrue(engine.schema_locked)
135
+
136
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "true"})
137
+ def test_sqlite_environment_variable_override(self):
138
+ """Test SQLite respects VELOCITY_SCHEMA_LOCKED environment variable."""
139
+ config = {"database": ":memory:"}
140
+
141
+ # Environment variable should override default
142
+ engine = SQLiteInitializer.initialize(config)
143
+ self.assertTrue(engine.schema_locked)
144
+
145
+ # Environment variable should override explicit False
146
+ engine = SQLiteInitializer.initialize(config, schema_locked=False)
147
+ self.assertTrue(engine.schema_locked)
148
+
149
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "true"})
150
+ @patch('pytds')
151
+ def test_sqlserver_environment_variable_override(self, mock_pytds):
152
+ """Test SQL Server respects VELOCITY_SCHEMA_LOCKED environment variable."""
153
+ mock_pytds.return_value = self.mock_pytds
154
+
155
+ config = {
156
+ "database": "test_db",
157
+ "server": "localhost",
158
+ "user": "test_user",
159
+ "password": "test_pass"
160
+ }
161
+
162
+ # Environment variable should override default
163
+ engine = SQLServerInitializer.initialize(config)
164
+ self.assertTrue(engine.schema_locked)
165
+
166
+ # Environment variable should override explicit False
167
+ engine = SQLServerInitializer.initialize(config, schema_locked=False)
168
+ self.assertTrue(engine.schema_locked)
169
+
170
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "true"})
171
+ @patch('psycopg2')
172
+ def test_postgres_environment_variable_override(self, mock_psycopg2):
173
+ """Test PostgreSQL respects VELOCITY_SCHEMA_LOCKED environment variable."""
174
+ mock_psycopg2.return_value = self.mock_psycopg2
175
+
176
+ config = {
177
+ "database": "test_db",
178
+ "host": "localhost",
179
+ "user": "test_user",
180
+ "password": "test_pass"
181
+ }
182
+
183
+ # Environment variable should override default
184
+ engine = PostgreSQLInitializer.initialize(config)
185
+ self.assertTrue(engine.schema_locked)
186
+
187
+ # Environment variable should override explicit False
188
+ engine = PostgreSQLInitializer.initialize(config, schema_locked=False)
189
+ self.assertTrue(engine.schema_locked)
190
+
191
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "1"})
192
+ @patch('mysql.connector')
193
+ def test_environment_variable_various_true_values(self, mock_mysql_connector):
194
+ """Test that various 'true' values in environment variable work."""
195
+ mock_mysql_connector.return_value = self.mock_mysql
196
+
197
+ config = {
198
+ "database": "test_db",
199
+ "host": "localhost",
200
+ "user": "test_user",
201
+ "password": "test_pass"
202
+ }
203
+
204
+ # Test "1"
205
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "1"}):
206
+ engine = MySQLInitializer.initialize(config)
207
+ self.assertTrue(engine.schema_locked)
208
+
209
+ # Test "yes"
210
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "yes"}):
211
+ engine = MySQLInitializer.initialize(config)
212
+ self.assertTrue(engine.schema_locked)
213
+
214
+ # Test "TRUE" (case insensitive)
215
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "TRUE"}):
216
+ engine = MySQLInitializer.initialize(config)
217
+ self.assertTrue(engine.schema_locked)
218
+
219
+ # Test "false" (should not lock)
220
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "false"}):
221
+ engine = MySQLInitializer.initialize(config)
222
+ self.assertFalse(engine.schema_locked)
223
+
224
+
225
+ if __name__ == "__main__":
226
+ unittest.main()
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple test to verify schema locking parameters in initializers.
4
+ """
5
+
6
+ import unittest
7
+ import os
8
+ from unittest.mock import patch
9
+
10
+ from velocity.db.servers.sqlite import SQLiteInitializer
11
+
12
+
13
+ class TestSchemaLockingSimple(unittest.TestCase):
14
+ """Test schema locking with SQLite (no external dependencies)."""
15
+
16
+ def test_sqlite_schema_locked_parameter(self):
17
+ """Test SQLite initializer accepts schema_locked parameter."""
18
+ config = {"database": ":memory:"}
19
+
20
+ # Test with schema_locked=True
21
+ engine = SQLiteInitializer.initialize(config, schema_locked=True)
22
+ self.assertTrue(engine.schema_locked)
23
+
24
+ # Test with schema_locked=False (default)
25
+ engine = SQLiteInitializer.initialize(config, schema_locked=False)
26
+ self.assertFalse(engine.schema_locked)
27
+
28
+ # Test default behavior
29
+ engine = SQLiteInitializer.initialize(config)
30
+ self.assertFalse(engine.schema_locked)
31
+
32
+ @patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "true"})
33
+ def test_sqlite_environment_variable_override(self):
34
+ """Test SQLite respects VELOCITY_SCHEMA_LOCKED environment variable."""
35
+ config = {"database": ":memory:"}
36
+
37
+ # Environment variable should override default
38
+ engine = SQLiteInitializer.initialize(config)
39
+ self.assertTrue(engine.schema_locked)
40
+
41
+ # Environment variable should override explicit False
42
+ engine = SQLiteInitializer.initialize(config, schema_locked=False)
43
+ self.assertTrue(engine.schema_locked)
44
+
45
+ def test_environment_variable_various_true_values(self):
46
+ """Test that various 'true' values in environment variable work."""
47
+ config = {"database": ":memory:"}
48
+
49
+ # Test "1"
50
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "1"}):
51
+ engine = SQLiteInitializer.initialize(config)
52
+ self.assertTrue(engine.schema_locked)
53
+
54
+ # Test "yes"
55
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "yes"}):
56
+ engine = SQLiteInitializer.initialize(config)
57
+ self.assertTrue(engine.schema_locked)
58
+
59
+ # Test "TRUE" (case insensitive)
60
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "TRUE"}):
61
+ engine = SQLiteInitializer.initialize(config)
62
+ self.assertTrue(engine.schema_locked)
63
+
64
+ # Test "false" (should not lock)
65
+ with patch.dict(os.environ, {"VELOCITY_SCHEMA_LOCKED": "false"}):
66
+ engine = SQLiteInitializer.initialize(config)
67
+ self.assertFalse(engine.schema_locked)
68
+
69
+ def test_schema_locking_methods(self):
70
+ """Test schema locking runtime methods work correctly."""
71
+ config = {"database": ":memory:"}
72
+
73
+ # Start unlocked
74
+ engine = SQLiteInitializer.initialize(config, schema_locked=False)
75
+ self.assertFalse(engine.schema_locked)
76
+
77
+ # Lock at runtime
78
+ engine.lock_schema()
79
+ self.assertTrue(engine.schema_locked)
80
+
81
+ # Unlock at runtime
82
+ engine.unlock_schema()
83
+ self.assertFalse(engine.schema_locked)
84
+
85
+ # Test context manager
86
+ engine.lock_schema()
87
+ self.assertTrue(engine.schema_locked)
88
+
89
+ with engine.unlocked_schema():
90
+ self.assertFalse(engine.schema_locked)
91
+
92
+ # Should be locked again after context
93
+ self.assertTrue(engine.schema_locked)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ unittest.main()
@@ -0,0 +1,165 @@
1
+ import unittest
2
+ from velocity.db.servers import postgres
3
+ from velocity.db.core.table import Query
4
+ import sqlparse
5
+
6
+ test_db = "test_foreign_key_db"
7
+ engine = postgres.initialize(database=test_db)
8
+
9
+
10
+ @engine.transaction
11
+ class TestSqlBuilder(unittest.TestCase):
12
+
13
+ def test_basic_select(self, tx):
14
+ sql, vals = tx.table("child_table").select(
15
+ columns=[
16
+ "parent_id>name",
17
+ "middle_id>title",
18
+ "sys_id",
19
+ "description",
20
+ ],
21
+ where={
22
+ "sys_id": 1,
23
+ "parent_id>name": None,
24
+ "in_list": [1, 2, 3, 4, 5],
25
+ "in_tuple": (1, 2, 3, 4, 5),
26
+ "!something": Query("Select * from table where sys_id = %s", (1000,)),
27
+ "!=notequal": 2,
28
+ "><between": [1, 10],
29
+ },
30
+ groupby="parent_id>name, middle_id>title",
31
+ having={"<>parent_id>name": "test"},
32
+ orderby="parent_id>name desc, middle_id>title asc, sys_id desc",
33
+ sql_only=True,
34
+ )
35
+
36
+ expected_sql = """SELECT B.name,
37
+ C.title,
38
+ A.sys_id,
39
+ A.description
40
+ FROM child_table AS A
41
+ LEFT JOIN parent_table AS B ON A.parent_id = parent_table.sys_id
42
+ LEFT JOIN middle_table AS C ON A.middle_id = middle_table.sys_id
43
+ WHERE A.sys_id = %s
44
+ AND B.name IS NULL
45
+ AND A.in_list IN %s
46
+ AND A.in_tuple IN %s
47
+ AND A.something NOT IN
48
+ (SELECT *
49
+ FROM TABLE
50
+ WHERE sys_id = %s)
51
+ AND A.notequal <> %s
52
+ AND A.between BETWEEN %s AND %s
53
+ GROUP BY B.name,
54
+ C.title
55
+ HAVING B.name <> %s
56
+ ORDER BY B.name DESC,
57
+ C.title ASC,
58
+ sys_id DESC"""
59
+ self.assertEqual(sql, expected_sql)
60
+ self.assertEqual(
61
+ vals, (1, [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], 1000, 2, 1, 10, "test")
62
+ )
63
+
64
+ def test_select_with_wildcard(self, tx):
65
+ sql, vals = tx.table("users").select(sql_only=True)
66
+ expected_sql = "SELECT * FROM users"
67
+ expected_sql = sqlparse.format(
68
+ expected_sql, reindent=True, keyword_case="upper"
69
+ )
70
+ self.assertEqual(sql, expected_sql)
71
+ self.assertEqual(vals, ())
72
+
73
+ def test_select_with_columns(self, tx):
74
+ sql, vals = tx.table("users").select(columns=["id", "name"], sql_only=True)
75
+ expected_sql = "SELECT id, name FROM users"
76
+ expected_sql = sqlparse.format(
77
+ expected_sql, reindent=True, keyword_case="upper"
78
+ )
79
+ self.assertEqual(sql, expected_sql)
80
+ self.assertEqual(vals, ())
81
+
82
+ def test_select_with_distinct(self, tx):
83
+ sql, vals = tx.table("users").select(columns="DISTINCT id, name", sql_only=True)
84
+ print(sql)
85
+ expected_sql = "SELECT DISTINCT id, name FROM users"
86
+ expected_sql = sqlparse.format(
87
+ expected_sql, reindent=True, keyword_case="upper"
88
+ )
89
+ self.assertEqual(sql, expected_sql)
90
+ self.assertEqual(vals, ())
91
+
92
+ def test_select_with_join(self, tx):
93
+ sql, vals = tx.table("users").select(
94
+ columns=["id", "name", "profile>email"],
95
+ where={"profile>email": "test@example.com"},
96
+ sql_only=True,
97
+ )
98
+ print(sql)
99
+ expected_sql = (
100
+ 'SELECT "A"."id", "A"."name", "B"."email" AS "profile_email" '
101
+ 'FROM "users" AS "A" '
102
+ 'LEFT OUTER JOIN "profile" AS "B" '
103
+ 'ON "A"."profile_id" = "B"."id" '
104
+ 'WHERE "B"."email" = %s'
105
+ )
106
+ self.assertEqual(sql, expected_sql)
107
+ self.assertEqual(vals, ("test@example.com",))
108
+
109
+ def xtest_group_by(self):
110
+ sql, vals = SqlBuilder.select(
111
+ columns="id, COUNT(*)", table="users", groupby="id"
112
+ )
113
+ expected_sql = 'SELECT "id", COUNT(*) FROM "users" GROUP BY id'
114
+ self.assertEqual(sql, expected_sql)
115
+ self.assertEqual(vals, [])
116
+
117
+ def xtest_order_by(self):
118
+ sql, vals = SqlBuilder.select(
119
+ columns="id, name", table="users", orderby="name DESC"
120
+ )
121
+ expected_sql = 'SELECT "id", "name" FROM "users" ORDER BY name DESC'
122
+ self.assertEqual(sql, expected_sql)
123
+ self.assertEqual(vals, [])
124
+
125
+ def xtest_limit_offset(self):
126
+ sql, vals = SqlBuilder.select(
127
+ columns="id, name", table="users", start=10, qty=20
128
+ )
129
+ expected_sql = (
130
+ 'SELECT "id", "name" FROM "users" OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY'
131
+ )
132
+ self.assertEqual(sql, expected_sql)
133
+ self.assertEqual(vals, [])
134
+
135
+ def xtest_for_update(self):
136
+ sql, vals = SqlBuilder.select(columns="id, name", table="users", lock=True)
137
+ expected_sql = 'SELECT "id", "name" FROM "users" FOR UPDATE'
138
+ self.assertEqual(sql, expected_sql)
139
+ self.assertEqual(vals, [])
140
+
141
+ def xtest_skip_locked(self):
142
+ sql, vals = SqlBuilder.select(
143
+ columns="id, name", table="users", skip_locked=True
144
+ )
145
+ expected_sql = 'SELECT "id", "name" FROM "users" FOR UPDATE SKIP LOCKED'
146
+ self.assertEqual(sql, expected_sql)
147
+ self.assertEqual(vals, [])
148
+
149
+ def xtest_invalid_table(self):
150
+ with self.assertRaises(Exception) as context:
151
+ SqlBuilder.select(columns="id, name", table=None)
152
+ self.assertTrue("Table name required" in str(context.exception))
153
+
154
+ def xtest_missing_foreign_key(self):
155
+ with self.assertRaises(exceptions.DbApplicationError) as context:
156
+ SqlBuilder.select(
157
+ columns=["profile>email"],
158
+ table="users",
159
+ tx=lambda table: None, # Simulate missing foreign key
160
+ )
161
+ self.assertTrue("Foreign key not defined" in str(context.exception))
162
+
163
+
164
+ if __name__ == "__main__":
165
+ unittest.main()