Skip to content

Commit eb7d7ef

Browse files
author
Eugene Shershen
committed
implement UUID parameter binding
1 parent 457b244 commit eb7d7ef

1 file changed

Lines changed: 107 additions & 80 deletions

File tree

tests/test_uuid_support.py

Lines changed: 107 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,33 @@
22
Tests for UUID parameter binding support in psqlpy-sqlalchemy.
33
"""
44

5-
import asyncio
5+
import os
66
import uuid
7+
78
import pytest
8-
from sqlalchemy import Column, Integer, String, create_engine, text
9+
from sqlalchemy import Column, Integer, String, text
910
from sqlalchemy.dialects.postgresql import UUID
11+
from sqlalchemy.exc import StatementError
1012
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
1113
from sqlalchemy.orm import DeclarativeBase, sessionmaker
12-
from sqlalchemy.exc import StatementError
13-
1414

15-
import os
1615

1716
# Skip tests if database is not available (check for CI environment or explicit flag)
1817
def should_skip_db_tests():
1918
"""Check if database tests should be skipped."""
2019
# Run tests in CI environment
21-
if os.getenv('GITHUB_ACTIONS'):
20+
if os.getenv("GITHUB_ACTIONS"):
2221
return False
2322
# Run tests if explicitly enabled
24-
if os.getenv('RUN_DB_TESTS'):
23+
if os.getenv("RUN_DB_TESTS"):
2524
return False
2625
# Skip by default in local development
2726
return True
2827

28+
2929
pytestmark = pytest.mark.skipif(
30-
should_skip_db_tests(),
31-
reason="Database tests require live PostgreSQL connection. Set RUN_DB_TESTS=1 or run in CI."
30+
should_skip_db_tests(),
31+
reason="Database tests require live PostgreSQL connection. Set RUN_DB_TESTS=1 or run in CI.",
3232
)
3333

3434

@@ -38,7 +38,7 @@ class Base(DeclarativeBase):
3838

3939
class TestUUIDTable(Base):
4040
__tablename__ = "test_uuid_table"
41-
41+
4242
id = Column(Integer, primary_key=True)
4343
uid = Column(UUID(as_uuid=True), nullable=False)
4444
name = Column(String(100))
@@ -49,19 +49,19 @@ async def engine():
4949
"""Create test engine."""
5050
# Use environment variables for database connection in CI
5151
db_url = os.getenv(
52-
'DATABASE_URL',
53-
'postgresql+psqlpy://postgres:password@localhost:5432/test_db'
52+
"DATABASE_URL",
53+
"postgresql+psqlpy://postgres:password@localhost:5432/test_db",
5454
)
5555
engine = create_async_engine(db_url, echo=False)
56-
56+
5757
async with engine.begin() as conn:
5858
await conn.run_sync(Base.metadata.create_all)
59-
59+
6060
yield engine
61-
61+
6262
async with engine.begin() as conn:
6363
await conn.run_sync(Base.metadata.drop_all)
64-
64+
6565
await engine.dispose()
6666

6767

@@ -75,203 +75,230 @@ async def session(engine):
7575

7676
class TestUUIDParameterBinding:
7777
"""Test UUID parameter binding functionality."""
78-
78+
7979
async def test_uuid_object_parameter(self, engine):
8080
"""Test UUID object as parameter."""
8181
test_uuid = uuid.uuid4()
82-
82+
8383
async with engine.begin() as conn:
8484
# Insert with UUID object
8585
await conn.execute(
86-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"),
87-
{"uid": test_uuid, "name": "test_uuid_object"}
86+
text(
87+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"
88+
),
89+
{"uid": test_uuid, "name": "test_uuid_object"},
8890
)
89-
91+
9092
# Query with UUID object
9193
result = await conn.execute(
9294
text("SELECT * FROM test_uuid_table WHERE uid = :uid"),
93-
{"uid": test_uuid}
95+
{"uid": test_uuid},
9496
)
95-
97+
9698
rows = result.fetchall()
9799
assert len(rows) == 1
98100
assert rows[0].name == "test_uuid_object"
99-
101+
100102
async def test_uuid_string_parameter(self, engine):
101103
"""Test UUID string as parameter."""
102104
test_uuid = uuid.uuid4()
103105
test_uuid_str = str(test_uuid)
104-
106+
105107
async with engine.begin() as conn:
106108
# Insert with UUID string
107109
await conn.execute(
108-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"),
109-
{"uid": test_uuid_str, "name": "test_uuid_string"}
110+
text(
111+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"
112+
),
113+
{"uid": test_uuid_str, "name": "test_uuid_string"},
110114
)
111-
115+
112116
# Query with UUID string
113117
result = await conn.execute(
114118
text("SELECT * FROM test_uuid_table WHERE uid = :uid"),
115-
{"uid": test_uuid_str}
119+
{"uid": test_uuid_str},
116120
)
117-
121+
118122
rows = result.fetchall()
119123
assert len(rows) == 1
120124
assert rows[0].name == "test_uuid_string"
121-
125+
122126
async def test_uuid_with_explicit_cast(self, engine):
123127
"""Test UUID parameter with explicit PostgreSQL casting."""
124128
test_uuid = uuid.uuid4()
125-
129+
126130
async with engine.begin() as conn:
127131
# Insert test data
128132
await conn.execute(
129-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"),
130-
{"uid": test_uuid, "name": "test_cast"}
133+
text(
134+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"
135+
),
136+
{"uid": test_uuid, "name": "test_cast"},
131137
)
132-
138+
133139
# This was the original failing case - explicit UUID casting
134140
result = await conn.execute(
135-
text("SELECT * FROM test_uuid_table WHERE uid = :uid::UUID LIMIT :limit"),
136-
{"uid": str(test_uuid), "limit": 2}
141+
text(
142+
"SELECT * FROM test_uuid_table WHERE uid = :uid::UUID LIMIT :limit"
143+
),
144+
{"uid": str(test_uuid), "limit": 2},
137145
)
138-
146+
139147
rows = result.fetchall()
140148
assert len(rows) == 1
141149
assert rows[0].name == "test_cast"
142-
150+
143151
async def test_uuid_with_sqlalchemy_orm(self, session):
144152
"""Test UUID with SQLAlchemy ORM."""
145153
test_uuid = uuid.uuid4()
146-
154+
147155
# Insert with ORM
148156
test_obj = TestUUIDTable(uid=test_uuid, name="test_orm")
149157
session.add(test_obj)
150158
await session.commit()
151-
159+
152160
# Query with ORM
153161
result = await session.execute(
154-
text("SELECT * FROM test_uuid_table WHERE uid = :uid ORDER BY id LIMIT :limit"),
155-
{"uid": test_uuid, "limit": 2}
162+
text(
163+
"SELECT * FROM test_uuid_table WHERE uid = :uid ORDER BY id LIMIT :limit"
164+
),
165+
{"uid": test_uuid, "limit": 2},
156166
)
157-
167+
158168
rows = result.fetchall()
159169
assert len(rows) == 1
160170
assert rows[0].name == "test_orm"
161-
171+
162172
async def test_multiple_uuid_parameters(self, engine):
163173
"""Test multiple UUID parameters in one query."""
164174
uuid1 = uuid.uuid4()
165175
uuid2 = uuid.uuid4()
166-
176+
167177
async with engine.begin() as conn:
168178
# Insert test data
169179
await conn.execute(
170-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid1, :name1), (:uid2, :name2)"),
171-
{"uid1": uuid1, "name1": "first", "uid2": uuid2, "name2": "second"}
180+
text(
181+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid1, :name1), (:uid2, :name2)"
182+
),
183+
{
184+
"uid1": uuid1,
185+
"name1": "first",
186+
"uid2": uuid2,
187+
"name2": "second",
188+
},
172189
)
173-
190+
174191
# Query with multiple UUID parameters
175192
result = await conn.execute(
176-
text("SELECT * FROM test_uuid_table WHERE uid IN (:uid1, :uid2) ORDER BY name"),
177-
{"uid1": uuid1, "uid2": uuid2}
193+
text(
194+
"SELECT * FROM test_uuid_table WHERE uid IN (:uid1, :uid2) ORDER BY name"
195+
),
196+
{"uid1": uuid1, "uid2": uuid2},
178197
)
179-
198+
180199
rows = result.fetchall()
181200
assert len(rows) == 2
182201
assert rows[0].name == "first"
183202
assert rows[1].name == "second"
184-
203+
185204
async def test_null_uuid_parameter(self, engine):
186205
"""Test NULL UUID parameter."""
187206
async with engine.begin() as conn:
188207
# Query with NULL UUID - should return no results
189208
result = await conn.execute(
190209
text("SELECT * FROM test_uuid_table WHERE uid = :uid"),
191-
{"uid": None}
210+
{"uid": None},
192211
)
193-
212+
194213
rows = result.fetchall()
195214
assert len(rows) == 0
196-
215+
197216
async def test_invalid_uuid_string(self, engine):
198217
"""Test invalid UUID string raises proper error."""
199218
async with engine.begin() as conn:
200219
with pytest.raises((ValueError, StatementError)):
201220
await conn.execute(
202-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"),
203-
{"uid": "invalid-uuid-string", "name": "test"}
221+
text(
222+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"
223+
),
224+
{"uid": "invalid-uuid-string", "name": "test"},
204225
)
205-
226+
206227
async def test_uuid_edge_cases(self, engine):
207228
"""Test UUID edge cases."""
208229
# Test various UUID formats
209230
test_cases = [
210-
uuid.UUID('00000000-0000-0000-0000-000000000000'), # Nil UUID
211-
uuid.UUID('ffffffff-ffff-ffff-ffff-ffffffffffff'), # Max UUID
231+
uuid.UUID("00000000-0000-0000-0000-000000000000"), # Nil UUID
232+
uuid.UUID("ffffffff-ffff-ffff-ffff-ffffffffffff"), # Max UUID
212233
uuid.uuid1(), # Time-based UUID
213234
uuid.uuid4(), # Random UUID
214235
]
215-
236+
216237
async with engine.begin() as conn:
217238
for i, test_uuid in enumerate(test_cases):
218239
await conn.execute(
219-
text("INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"),
220-
{"uid": test_uuid, "name": f"edge_case_{i}"}
240+
text(
241+
"INSERT INTO test_uuid_table (uid, name) VALUES (:uid, :name)"
242+
),
243+
{"uid": test_uuid, "name": f"edge_case_{i}"},
221244
)
222-
245+
223246
# Verify all were inserted correctly
224247
result = await conn.execute(
225-
text("SELECT COUNT(*) as count FROM test_uuid_table WHERE name LIKE 'edge_case_%'")
248+
text(
249+
"SELECT COUNT(*) as count FROM test_uuid_table WHERE name LIKE 'edge_case_%'"
250+
)
226251
)
227-
252+
228253
count = result.fetchone().count
229254
assert count == len(test_cases)
230255

231256

232257
class TestUUIDTypeCompatibility:
233258
"""Test UUID type compatibility with existing functionality."""
234-
259+
235260
async def test_uuid_column_definition(self, engine):
236261
"""Test that UUID columns are properly defined."""
237262
async with engine.begin() as conn:
238263
# Check table structure
239264
result = await conn.execute(
240265
text("""
241-
SELECT column_name, data_type
242-
FROM information_schema.columns
266+
SELECT column_name, data_type
267+
FROM information_schema.columns
243268
WHERE table_name = 'test_uuid_table' AND column_name = 'uid'
244269
""")
245270
)
246-
271+
247272
row = result.fetchone()
248273
assert row is not None
249-
assert row.data_type == 'uuid'
250-
274+
assert row.data_type == "uuid"
275+
251276
async def test_uuid_index_support(self, engine):
252277
"""Test that UUID columns can be indexed."""
253278
async with engine.begin() as conn:
254279
# Create index on UUID column
255280
await conn.execute(
256-
text("CREATE INDEX IF NOT EXISTS idx_test_uuid_uid ON test_uuid_table(uid)")
281+
text(
282+
"CREATE INDEX IF NOT EXISTS idx_test_uuid_uid ON test_uuid_table(uid)"
283+
)
257284
)
258-
285+
259286
# Verify index was created
260287
result = await conn.execute(
261288
text("""
262-
SELECT indexname
263-
FROM pg_indexes
289+
SELECT indexname
290+
FROM pg_indexes
264291
WHERE tablename = 'test_uuid_table' AND indexname = 'idx_test_uuid_uid'
265292
""")
266293
)
267-
294+
268295
row = result.fetchone()
269296
assert row is not None
270-
297+
271298
# Clean up
272299
await conn.execute(text("DROP INDEX IF EXISTS idx_test_uuid_uid"))
273300

274301

275302
if __name__ == "__main__":
276303
# Run tests directly
277-
pytest.main([__file__, "-v"])
304+
pytest.main([__file__, "-v"])

0 commit comments

Comments
 (0)