matrixone-python-sdk 0.1.0__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 (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. tests/online/test_version_management.py +291 -0
@@ -0,0 +1,285 @@
1
+ # Copyright 2021 - 2022 Matrix Origin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Online tests for Async Client functionality - tests actual database operations
17
+ """
18
+
19
+ import pytest
20
+ import pytest_asyncio
21
+ import asyncio
22
+ import os
23
+ import sys
24
+ from datetime import datetime
25
+
26
+ # Add the matrixone package to the path
27
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
28
+
29
+ from matrixone import AsyncClient
30
+ from matrixone.exceptions import ConnectionError, QueryError
31
+ from matrixone.sqlalchemy_ext import boolean_match
32
+ from .test_config import online_config
33
+
34
+
35
+ class TestAsyncClientOnline:
36
+ """Online tests for Async Client functionality"""
37
+
38
+ @pytest_asyncio.fixture(scope="function")
39
+ async def test_async_client(self):
40
+ """Create and connect AsyncClient for testing"""
41
+ host, port, user, password, database = online_config.get_connection_params()
42
+ client = AsyncClient()
43
+ await client.connect(host, port, user, password, database)
44
+ try:
45
+ yield client
46
+ finally:
47
+ try:
48
+ await client.disconnect()
49
+ except Exception as e:
50
+ print(f"Warning: Failed to disconnect async client: {e}")
51
+
52
+ @pytest_asyncio.fixture(scope="function")
53
+ async def test_database(self, test_async_client):
54
+ """Set up test database and table"""
55
+ test_db = "test_async_client_db"
56
+ test_table = "test_async_client_table"
57
+
58
+ try:
59
+ await test_async_client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
60
+ await test_async_client.execute(
61
+ f"""
62
+ CREATE TABLE IF NOT EXISTS {test_db}.{test_table} (
63
+ id INT PRIMARY KEY,
64
+ name VARCHAR(100),
65
+ value INT,
66
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
67
+ )
68
+ """
69
+ )
70
+
71
+ # Insert test data
72
+ await test_async_client.execute(f"INSERT INTO {test_db}.{test_table} VALUES (1, 'async_test1', 100, NOW())")
73
+ await test_async_client.execute(f"INSERT INTO {test_db}.{test_table} VALUES (2, 'async_test2', 200, NOW())")
74
+ await test_async_client.execute(f"INSERT INTO {test_db}.{test_table} VALUES (3, 'async_test3', 300, NOW())")
75
+
76
+ yield test_db, test_table
77
+
78
+ finally:
79
+ # Clean up
80
+ try:
81
+ await test_async_client.execute(f"DROP DATABASE IF EXISTS {test_db}")
82
+ except Exception as e:
83
+ print(f"Cleanup failed: {e}")
84
+
85
+ @pytest.mark.asyncio
86
+ async def test_basic_async_connection_and_query(self, test_async_client):
87
+ """Test basic async connection and query functionality"""
88
+ # Test simple query
89
+ result = await test_async_client.execute("SELECT 1 as test_value")
90
+ rows = result.fetchall()
91
+ assert len(rows) == 1
92
+ assert rows[0][0] == 1
93
+
94
+ # Test query with parameters
95
+ result = await test_async_client.execute("SELECT ? as param_value", (42,))
96
+ rows = result.fetchall()
97
+ assert len(rows) == 1
98
+ assert rows[0][0] == 42
99
+
100
+ @pytest.mark.asyncio
101
+ async def test_async_table_operations(self, test_async_client, test_database):
102
+ """Test async table operations"""
103
+ test_db, test_table = test_database
104
+
105
+ # Test SELECT
106
+ result = await test_async_client.execute(f"SELECT * FROM {test_db}.{test_table} ORDER BY id")
107
+ rows = result.fetchall()
108
+ assert len(rows) == 3
109
+ assert rows[0][0] == 1 # id
110
+ assert rows[0][1] == 'async_test1' # name
111
+
112
+ # Test INSERT
113
+ await test_async_client.execute(f"INSERT INTO {test_db}.{test_table} VALUES (4, 'async_test4', 400, NOW())")
114
+
115
+ # Verify INSERT
116
+ result = await test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
117
+ count = result.fetchone()[0]
118
+ assert count == 4
119
+
120
+ # Test UPDATE
121
+ await test_async_client.execute(f"UPDATE {test_db}.{test_table} SET value = 500 WHERE id = 4")
122
+
123
+ # Verify UPDATE
124
+ result = await test_async_client.execute(f"SELECT value FROM {test_db}.{test_table} WHERE id = 4")
125
+ value = result.fetchone()[0]
126
+ assert value == 500
127
+
128
+ # Test DELETE
129
+ await test_async_client.execute(f"DELETE FROM {test_db}.{test_table} WHERE id = 4")
130
+
131
+ # Verify DELETE
132
+ result = await test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
133
+ count = result.fetchone()[0]
134
+ assert count == 3
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_async_transaction_operations(self, test_async_client, test_database):
138
+ """Test async transaction operations"""
139
+ test_db, test_table = test_database
140
+
141
+ async with test_async_client.transaction() as tx:
142
+ # Insert within transaction
143
+ await tx.execute(f"INSERT INTO {test_db}.{test_table} VALUES (5, 'async_test5', 500, NOW())")
144
+
145
+ # Verify within transaction
146
+ result = await tx.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
147
+ count = result.fetchone()[0]
148
+ assert count == 4
149
+
150
+ # Verify transaction commit
151
+ result = await test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
152
+ count = result.fetchone()[0]
153
+ assert count == 4
154
+
155
+ # Test transaction rollback
156
+ try:
157
+ async with test_async_client.transaction() as tx:
158
+ await tx.execute(f"INSERT INTO {test_db}.{test_table} VALUES (6, 'async_test6', 600, NOW())")
159
+ # Force rollback by raising exception
160
+ raise Exception("Test rollback")
161
+ except Exception:
162
+ pass
163
+
164
+ # Verify rollback
165
+ result = await test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
166
+ count = result.fetchone()[0]
167
+ assert count == 4 # Should still be 4, not 5
168
+
169
+ # Clean up
170
+ await test_async_client.execute(f"DELETE FROM {test_db}.{test_table} WHERE id = 5")
171
+
172
+ @pytest.mark.asyncio
173
+ async def test_async_error_handling(self, test_async_client):
174
+ """Test async error handling"""
175
+ # Test connection error handling
176
+ with pytest.raises(QueryError):
177
+ await test_async_client.execute("SELECT * FROM non_existent_table")
178
+
179
+ # Test invalid SQL
180
+ with pytest.raises(QueryError):
181
+ await test_async_client.execute("INVALID SQL STATEMENT")
182
+
183
+ @pytest.mark.asyncio
184
+ async def test_async_result_set_operations(self, test_async_client, test_database):
185
+ """Test async result set operations"""
186
+ test_db, test_table = test_database
187
+
188
+ result = await test_async_client.execute(f"SELECT * FROM {test_db}.{test_table} ORDER BY id")
189
+
190
+ # Test fetchone
191
+ first_row = result.fetchone()
192
+ assert first_row is not None
193
+ assert first_row[0] == 1
194
+
195
+ # Test fetchmany
196
+ next_rows = result.fetchmany(2)
197
+ assert len(next_rows) == 2
198
+ assert next_rows[0][0] == 2
199
+ assert next_rows[1][0] == 3
200
+
201
+ # Test fetchall (should return remaining rows)
202
+ remaining_rows = result.fetchall()
203
+ assert len(remaining_rows) == 0 # Should be empty
204
+
205
+ # Test column names
206
+ result = await test_async_client.execute(f"SELECT id, name, value FROM {test_db}.{test_table} LIMIT 1")
207
+ columns = result.keys()
208
+ expected_columns = ['id', 'name', 'value']
209
+ assert list(columns) == expected_columns
210
+
211
+ @pytest.mark.asyncio
212
+ async def test_async_parameter_binding(self, test_async_client, test_database):
213
+ """Test async parameter binding"""
214
+ test_db, test_table = test_database
215
+
216
+ # Test string parameters
217
+ result = await test_async_client.execute(f"SELECT * FROM {test_db}.{test_table} WHERE name = ?", ('async_test1',))
218
+ rows = result.fetchall()
219
+ assert len(rows) == 1
220
+ assert rows[0][1] == 'async_test1'
221
+
222
+ # Test numeric parameters
223
+ result = await test_async_client.execute(f"SELECT * FROM {test_db}.{test_table} WHERE value = ?", (200,))
224
+ rows = result.fetchall()
225
+ assert len(rows) == 1
226
+ assert rows[0][2] == 200
227
+
228
+ # Test multiple parameters
229
+ result = await test_async_client.execute(
230
+ f"SELECT * FROM {test_db}.{test_table} WHERE name = ? AND value > ?",
231
+ ('async_test2', 150),
232
+ )
233
+ rows = result.fetchall()
234
+ assert len(rows) == 1
235
+ assert rows[0][1] == 'async_test2'
236
+ assert rows[0][2] == 200
237
+
238
+ @pytest.mark.asyncio
239
+ async def test_async_concurrent_operations(self, test_async_client, test_database):
240
+ """Test async concurrent operations"""
241
+ test_db, test_table = test_database
242
+
243
+ # Test concurrent queries
244
+ tasks = [
245
+ test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}"),
246
+ test_async_client.execute(f"SELECT MAX(value) FROM {test_db}.{test_table}"),
247
+ test_async_client.execute(f"SELECT MIN(value) FROM {test_db}.{test_table}"),
248
+ ]
249
+
250
+ results = await asyncio.gather(*tasks)
251
+
252
+ # All queries should complete successfully
253
+ assert len(results) == 3
254
+ assert results[0].fetchone()[0] == 3 # count
255
+ assert results[1].fetchone()[0] == 300 # max value
256
+ assert results[2].fetchone()[0] == 100 # min value
257
+
258
+ @pytest.mark.asyncio
259
+ async def test_async_connection_pooling(self, test_async_client, test_database):
260
+ """Test async connection pooling and reuse"""
261
+ test_db, test_table = test_database
262
+
263
+ # Execute multiple queries to test connection reuse
264
+ for i in range(5):
265
+ result = await test_async_client.execute("SELECT 1 as test")
266
+ rows = result.fetchall()
267
+ assert len(rows) == 1
268
+ assert rows[0][0] == 1
269
+
270
+ # Test concurrent operations
271
+ tasks = []
272
+ for i in range(3):
273
+ task = test_async_client.execute(f"SELECT COUNT(*) FROM {test_db}.{test_table}")
274
+ tasks.append(task)
275
+
276
+ results = await asyncio.gather(*tasks)
277
+
278
+ # All should return the same count
279
+ for result in results:
280
+ count = result.fetchone()[0]
281
+ assert count == 3
282
+
283
+
284
+ if __name__ == '__main__':
285
+ pytest.main([__file__])
@@ -0,0 +1,293 @@
1
+ """
2
+ Online tests for async model-based insert operations
3
+ """
4
+
5
+ import pytest
6
+ import pytest_asyncio
7
+ import os
8
+ import sys
9
+ import asyncio
10
+ from datetime import datetime
11
+
12
+ # Add the matrixone package to the path
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
14
+
15
+ from matrixone import AsyncClient
16
+ from matrixone.orm import declarative_base
17
+ from matrixone.exceptions import QueryError
18
+ from sqlalchemy import Column, Integer, String, Text, DECIMAL, DateTime
19
+ from .test_config import online_config
20
+
21
+ Base = declarative_base()
22
+
23
+
24
+ class AsyncUser(Base):
25
+ __tablename__ = 'test_async_insert_users'
26
+ id = Column(Integer, primary_key=True)
27
+ name = Column(String(50))
28
+ email = Column(String(100))
29
+ age = Column(Integer)
30
+ created_at = Column(DateTime)
31
+
32
+
33
+ class AsyncProduct(Base):
34
+ __tablename__ = 'test_async_insert_products'
35
+ id = Column(Integer, primary_key=True)
36
+ name = Column(String(100))
37
+ description = Column(Text)
38
+ price = Column(DECIMAL(10, 2))
39
+ category = Column(String(50))
40
+ in_stock = Column(Integer)
41
+
42
+
43
+ class AsyncOrder(Base):
44
+ __tablename__ = 'test_async_insert_orders'
45
+ id = Column(Integer, primary_key=True)
46
+ user_id = Column(Integer)
47
+ product_id = Column(Integer)
48
+ quantity = Column(Integer)
49
+ total_amount = Column(DECIMAL(10, 2))
50
+ order_date = Column(DateTime)
51
+
52
+
53
+ class TestAsyncModelInsert:
54
+ """Test async model-based insert operations"""
55
+
56
+ async def _setup_test_environment(self):
57
+ """Set up test environment for each test"""
58
+ host, port, user, password, database = online_config.get_connection_params()
59
+ client = AsyncClient()
60
+ await client.connect(host=host, port=port, user=user, password=password, database=database)
61
+
62
+ test_db = "test_async_model_insert_db"
63
+ await client.execute(f"CREATE DATABASE IF NOT EXISTS {test_db}")
64
+ await client.execute(f"USE {test_db}")
65
+
66
+ # Create test tables using models
67
+ await client.create_table(AsyncUser)
68
+ await client.create_table(AsyncProduct)
69
+ await client.create_table(AsyncOrder)
70
+
71
+ return client, test_db
72
+
73
+ async def _cleanup_test_environment(self, client, test_db):
74
+ """Clean up test environment"""
75
+ try:
76
+ await client.execute(f"DROP DATABASE IF EXISTS {test_db}")
77
+ await client.disconnect()
78
+ except Exception as e:
79
+ print(f"Cleanup failed: {e}")
80
+
81
+ @pytest.mark.asyncio
82
+ async def test_async_single_insert_with_model(self):
83
+ """Test inserting a single record using model class with async client"""
84
+ client, test_db = await self._setup_test_environment()
85
+ try:
86
+ # Insert a single user using model class
87
+ result = await client.insert(
88
+ AsyncUser,
89
+ {'id': 1, 'name': 'John Doe', 'email': 'john@example.com', 'age': 30, 'created_at': datetime.now()},
90
+ )
91
+
92
+ assert result.affected_rows == 1
93
+
94
+ # Verify the data was inserted correctly
95
+ users = await client.query(AsyncUser).all()
96
+ assert len(users) == 1
97
+
98
+ user = users[0]
99
+ assert user.name == 'John Doe'
100
+ assert user.email == 'john@example.com'
101
+ assert user.age == 30
102
+
103
+ finally:
104
+ await self._cleanup_test_environment(client, test_db)
105
+
106
+ @pytest.mark.asyncio
107
+ async def test_async_batch_insert_with_model(self):
108
+ """Test batch inserting multiple records using model class with async client"""
109
+ client, test_db = await self._setup_test_environment()
110
+ try:
111
+ # Insert multiple products using model class
112
+ products_data = [
113
+ {
114
+ 'id': 1,
115
+ 'name': 'Laptop',
116
+ 'description': 'High-performance laptop',
117
+ 'price': 999.99,
118
+ 'category': 'Electronics',
119
+ 'in_stock': 10,
120
+ },
121
+ {
122
+ 'id': 2,
123
+ 'name': 'Book',
124
+ 'description': 'Programming guide',
125
+ 'price': 29.99,
126
+ 'category': 'Education',
127
+ 'in_stock': 50,
128
+ },
129
+ {
130
+ 'id': 3,
131
+ 'name': 'Phone',
132
+ 'description': 'Smartphone with AI features',
133
+ 'price': 699.99,
134
+ 'category': 'Electronics',
135
+ 'in_stock': 25,
136
+ },
137
+ ]
138
+
139
+ result = await client.batch_insert(AsyncProduct, products_data)
140
+ assert result.affected_rows == 3
141
+
142
+ # Verify the data was inserted correctly
143
+ products = await client.query(AsyncProduct).all()
144
+ assert len(products) == 3
145
+
146
+ # Check specific products
147
+ laptop = await client.query(AsyncProduct).filter_by(name='Laptop').first()
148
+ assert laptop is not None
149
+ assert float(laptop.price) == 999.99
150
+ assert laptop.category == 'Electronics'
151
+ assert laptop.in_stock == 10
152
+
153
+ finally:
154
+ await self._cleanup_test_environment(client, test_db)
155
+
156
+ @pytest.mark.asyncio
157
+ async def test_async_insert_with_null_values(self):
158
+ """Test inserting records with NULL values using model class with async client"""
159
+ client, test_db = await self._setup_test_environment()
160
+ try:
161
+ # Insert a user with some NULL values
162
+ result = await client.insert(
163
+ AsyncUser,
164
+ {'id': 2, 'name': 'Jane Smith', 'email': None, 'age': 25, 'created_at': None}, # NULL value # NULL value
165
+ )
166
+
167
+ assert result.affected_rows == 1
168
+
169
+ # Verify the data was inserted correctly
170
+ user = await client.query(AsyncUser).filter_by(id=2).first()
171
+ assert user is not None
172
+ assert user.name == 'Jane Smith'
173
+ assert user.email is None
174
+ assert user.age == 25
175
+ assert user.created_at is None
176
+
177
+ finally:
178
+ await self._cleanup_test_environment(client, test_db)
179
+
180
+ @pytest.mark.asyncio
181
+ async def test_async_insert_with_different_data_types(self):
182
+ """Test inserting records with different data types using model class with async client"""
183
+ client, test_db = await self._setup_test_environment()
184
+ try:
185
+ # Insert an order with various data types
186
+ result = await client.insert(
187
+ AsyncOrder,
188
+ {
189
+ 'id': 1,
190
+ 'user_id': 1,
191
+ 'product_id': 1,
192
+ 'quantity': 2,
193
+ 'total_amount': 1999.98,
194
+ 'order_date': datetime(2024, 1, 15, 10, 30, 0),
195
+ },
196
+ )
197
+
198
+ assert result.affected_rows == 1
199
+
200
+ # Verify the data was inserted correctly
201
+ order = await client.query(AsyncOrder).filter_by(id=1).first()
202
+ assert order is not None
203
+ assert order.user_id == 1
204
+ assert order.product_id == 1
205
+ assert order.quantity == 2
206
+ assert float(order.total_amount) == 1999.98
207
+ assert order.order_date == datetime(2024, 1, 15, 10, 30, 0)
208
+
209
+ finally:
210
+ await self._cleanup_test_environment(client, test_db)
211
+
212
+ @pytest.mark.asyncio
213
+ async def test_async_insert_error_handling(self):
214
+ """Test error handling when inserting invalid data using model class with async client"""
215
+ client, test_db = await self._setup_test_environment()
216
+ try:
217
+ # First insert a user
218
+ await client.insert(AsyncUser, {'id': 1, 'name': 'John Doe', 'email': 'john@example.com', 'age': 30})
219
+
220
+ # Try to insert a user with duplicate primary key
221
+ with pytest.raises(QueryError):
222
+ await client.insert(
223
+ AsyncUser,
224
+ {
225
+ 'id': 1, # Duplicate primary key
226
+ 'name': 'Duplicate User',
227
+ 'email': 'duplicate@example.com',
228
+ 'age': 35,
229
+ },
230
+ )
231
+
232
+ finally:
233
+ await self._cleanup_test_environment(client, test_db)
234
+
235
+ @pytest.mark.asyncio
236
+ async def test_async_insert_vs_string_table_name(self):
237
+ """Test that both model class and string table name work the same way with async client"""
238
+ client, test_db = await self._setup_test_environment()
239
+ try:
240
+ # Insert using model class
241
+ result1 = await client.insert(
242
+ AsyncUser, {'id': 3, 'name': 'Model User', 'email': 'model@example.com', 'age': 28}
243
+ )
244
+
245
+ # Insert using string table name
246
+ result2 = await client.insert(
247
+ 'test_async_insert_users', {'id': 4, 'name': 'String User', 'email': 'string@example.com', 'age': 32}
248
+ )
249
+
250
+ assert result1.affected_rows == 1
251
+ assert result2.affected_rows == 1
252
+
253
+ # Verify both records were inserted
254
+ users = await client.query(AsyncUser).all()
255
+ assert len(users) == 2
256
+
257
+ # Check specific users
258
+ model_user = await client.query(AsyncUser).filter_by(name='Model User').first()
259
+ string_user = await client.query(AsyncUser).filter_by(name='String User').first()
260
+
261
+ assert model_user is not None
262
+ assert string_user is not None
263
+ assert model_user.email == 'model@example.com'
264
+ assert string_user.email == 'string@example.com'
265
+
266
+ finally:
267
+ await self._cleanup_test_environment(client, test_db)
268
+
269
+ @pytest.mark.asyncio
270
+ async def test_async_transaction_insert_with_model(self):
271
+ """Test inserting records within a transaction using model class with async client"""
272
+ client, test_db = await self._setup_test_environment()
273
+ try:
274
+ async with client.transaction() as tx:
275
+ # Insert a user within transaction
276
+ result = await tx.insert(
277
+ AsyncUser, {'id': 100, 'name': 'Transaction User', 'email': 'tx@example.com', 'age': 45}
278
+ )
279
+
280
+ assert result.affected_rows == 1
281
+
282
+ # Verify within transaction
283
+ user = await tx.query(AsyncUser).filter_by(id=100).first()
284
+ assert user is not None
285
+ assert user.name == 'Transaction User'
286
+
287
+ # Verify after commit
288
+ user_after_commit = await client.query(AsyncUser).filter_by(id=100).first()
289
+ assert user_after_commit is not None
290
+ assert user_after_commit.name == 'Transaction User'
291
+
292
+ finally:
293
+ await self._cleanup_test_environment(client, test_db)