datus-postgresql 0.1.2__tar.gz → 0.1.4__tar.gz

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 (19) hide show
  1. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/PKG-INFO +1 -1
  2. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/datus_postgresql/config.py +7 -2
  3. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/datus_postgresql/connector.py +105 -22
  4. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/pyproject.toml +1 -1
  5. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/scripts/init_tpch_data.py +151 -15
  6. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/conftest.py +2 -1
  7. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/integration/conftest.py +152 -15
  8. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/integration/test_integration.py +9 -4
  9. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/integration/test_tpch.py +7 -1
  10. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/unit/test_config.py +2 -1
  11. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/unit/test_connector_unit.py +11 -5
  12. datus_postgresql-0.1.2/.gitignore +0 -140
  13. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/README.md +0 -0
  14. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/datus_postgresql/__init__.py +0 -0
  15. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/datus_postgresql/handlers.py +0 -0
  16. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/docker-compose.yml +0 -0
  17. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/__init__.py +0 -0
  18. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/integration/__init__.py +0 -0
  19. {datus_postgresql-0.1.2 → datus_postgresql-0.1.4}/tests/unit/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datus-postgresql
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: PostgreSQL database adapter for Datus
5
5
  Project-URL: Homepage, https://github.com/Datus-ai/datus-db-adapters
6
6
  Project-URL: Repository, https://github.com/Datus-ai/datus-db-adapters
@@ -15,10 +15,15 @@ class PostgreSQLConfig(BaseModel):
15
15
  host: str = Field(default="127.0.0.1", description="PostgreSQL server host")
16
16
  port: int = Field(default=5432, description="PostgreSQL server port")
17
17
  username: str = Field(..., description="PostgreSQL username")
18
- password: str = Field(default="", description="PostgreSQL password", json_schema_extra={"input_type": "password"})
18
+ password: str = Field(
19
+ default="",
20
+ description="PostgreSQL password",
21
+ json_schema_extra={"input_type": "password"},
22
+ )
19
23
  database: Optional[str] = Field(default=None, description="Default database name")
20
24
  schema_name: Optional[str] = Field(default="public", alias="schema", description="Default schema name")
21
25
  sslmode: str = Field(
22
- default="prefer", description="SSL mode (disable, allow, prefer, require, verify-ca, verify-full)"
26
+ default="prefer",
27
+ description="SSL mode (disable, allow, prefer, require, verify-ca, verify-full)",
23
28
  )
24
29
  timeout_seconds: int = Field(default=30, description="Connection timeout in seconds")
@@ -5,9 +5,18 @@
5
5
  from typing import Any, Dict, List, Optional, Set, Union, override
6
6
  from urllib.parse import quote_plus
7
7
 
8
- from datus_db_core import TABLE_TYPE, DatusDbException, ErrorCode, get_logger, list_to_in_str
9
- from datus_sqlalchemy import SQLAlchemyConnector
8
+ from pandas import DataFrame
10
9
  from pydantic import BaseModel, Field
10
+ from sqlalchemy import create_engine, text
11
+
12
+ from datus_db_core import (
13
+ TABLE_TYPE,
14
+ DatusDbException,
15
+ ErrorCode,
16
+ get_logger,
17
+ list_to_in_str,
18
+ )
19
+ from datus_sqlalchemy import SQLAlchemyConnector
11
20
 
12
21
  from .config import PostgreSQLConfig
13
22
 
@@ -76,7 +85,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
76
85
  f"{database}?sslmode={config.sslmode}"
77
86
  )
78
87
 
79
- super().__init__(connection_string, dialect="postgresql", timeout_seconds=config.timeout_seconds)
88
+ super().__init__(
89
+ connection_string,
90
+ dialect="postgresql",
91
+ timeout_seconds=config.timeout_seconds,
92
+ )
80
93
  self.database_name = database
81
94
  self.schema_name = config.schema_name or "public"
82
95
 
@@ -90,7 +103,13 @@ class PostgreSQLConnector(SQLAlchemyConnector):
90
103
  @override
91
104
  def _sys_schemas(self) -> Set[str]:
92
105
  """System schemas to filter out."""
93
- return {"pg_catalog", "information_schema", "pg_toast", "pg_temp_1", "pg_toast_temp_1"}
106
+ return {
107
+ "pg_catalog",
108
+ "information_schema",
109
+ "pg_toast",
110
+ "pg_temp_1",
111
+ "pg_toast_temp_1",
112
+ }
94
113
 
95
114
  # ==================== Utility Methods ====================
96
115
 
@@ -100,6 +119,33 @@ class PostgreSQLConnector(SQLAlchemyConnector):
100
119
  escaped = identifier.replace('"', '""')
101
120
  return f'"{escaped}"'
102
121
 
122
+ def _build_connection_string(self, database_name: str) -> str:
123
+ """Build a PostgreSQL connection string for a given database."""
124
+ encoded_username = quote_plus(self.username) if self.username else ""
125
+ encoded_password = quote_plus(self.password) if self.password else ""
126
+ return (
127
+ f"postgresql+psycopg2://{encoded_username}:{encoded_password}"
128
+ f"@{self.host}:{self.port}/{database_name}?sslmode={self.config.sslmode}"
129
+ )
130
+
131
+ def _execute_on_database(self, sql: str, database_name: str) -> DataFrame:
132
+ """Execute a query on a specific database using a temporary connection.
133
+
134
+ Thread-safe: creates an isolated connection without mutating self.
135
+ """
136
+ if database_name == self.database_name:
137
+ return self._execute_pandas(sql)
138
+
139
+ conn_str = self._build_connection_string(database_name)
140
+ engine = create_engine(conn_str)
141
+ try:
142
+ with engine.connect() as conn:
143
+ result = conn.execute(text(sql))
144
+ rows = [row._asdict() for row in result.fetchall()]
145
+ return DataFrame(rows)
146
+ finally:
147
+ engine.dispose()
148
+
103
149
  # ==================== Metadata Retrieval ====================
104
150
 
105
151
  def _get_metadata(
@@ -122,13 +168,15 @@ class PostgreSQLConnector(SQLAlchemyConnector):
122
168
  List of metadata dictionaries
123
169
  """
124
170
  self.connect()
171
+ database_name = database_name or self.database_name
125
172
  schema_name = schema_name or self.schema_name
126
173
 
127
174
  # Get metadata configuration
128
175
  metadata_config = _get_metadata_config(table_type)
129
176
 
130
177
  if table_type == "mv":
131
- # Materialized views use pg_matviews
178
+ # pg_matviews is scoped to the current database connection.
179
+ # Use a temporary connection if a different database is requested (thread-safe).
132
180
  if schema_name:
133
181
  where = f"schemaname = '{schema_name}'"
134
182
  else:
@@ -139,8 +187,9 @@ class PostgreSQLConnector(SQLAlchemyConnector):
139
187
  FROM pg_matviews
140
188
  WHERE {where}
141
189
  """
190
+ query_result = self._execute_on_database(query, database_name)
142
191
  else:
143
- # Tables and views use information_schema
192
+ # Tables and views use information_schema (supports table_catalog filter)
144
193
  if schema_name:
145
194
  where = f"table_schema = '{schema_name}'"
146
195
  else:
@@ -154,10 +203,9 @@ class PostgreSQLConnector(SQLAlchemyConnector):
154
203
  query = f"""
155
204
  SELECT table_schema, table_name
156
205
  FROM information_schema.{metadata_config.info_table}
157
- WHERE {where} {type_filter}
206
+ WHERE table_catalog = '{database_name}' AND {where} {type_filter}
158
207
  """
159
-
160
- query_result = self._execute_pandas(query)
208
+ query_result = self._execute_pandas(query)
161
209
 
162
210
  # Format results
163
211
  result = []
@@ -168,7 +216,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
168
216
  {
169
217
  "identifier": self.identifier(schema_name=schema, table_name=tb_name),
170
218
  "catalog_name": "",
171
- "database_name": self.database_name,
219
+ "database_name": database_name,
172
220
  "schema_name": schema,
173
221
  "table_name": tb_name,
174
222
  "table_type": table_type,
@@ -307,7 +355,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
307
355
 
308
356
  @override
309
357
  def get_tables_with_ddl(
310
- self, catalog_name: str = "", database_name: str = "", schema_name: str = "", tables: Optional[List[str]] = None
358
+ self,
359
+ catalog_name: str = "",
360
+ database_name: str = "",
361
+ schema_name: str = "",
362
+ tables: Optional[List[str]] = None,
311
363
  ) -> List[Dict[str, str]]:
312
364
  """Get tables with DDL statements."""
313
365
  return self._get_objects_with_ddl("table", tables, catalog_name, database_name, schema_name)
@@ -321,7 +373,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
321
373
 
322
374
  @override
323
375
  def get_schema(
324
- self, catalog_name: str = "", database_name: str = "", schema_name: str = "", table_name: str = ""
376
+ self,
377
+ catalog_name: str = "",
378
+ database_name: str = "",
379
+ schema_name: str = "",
380
+ table_name: str = "",
325
381
  ) -> List[Dict[str, Any]]:
326
382
  """
327
383
  Get table schema using INFORMATION_SCHEMA.
@@ -338,6 +394,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
338
394
  if not table_name:
339
395
  return []
340
396
 
397
+ database_name = database_name or self.database_name
341
398
  schema_name = schema_name or self.schema_name
342
399
 
343
400
  # Use INFORMATION_SCHEMA to get schema with comments
@@ -364,7 +421,8 @@ class PostgreSQLConnector(SQLAlchemyConnector):
364
421
  ON st.schemaname = c.table_schema AND st.relname = c.table_name
365
422
  LEFT JOIN pg_catalog.pg_description pgd
366
423
  ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
367
- WHERE c.table_schema = '{schema_name}'
424
+ WHERE c.table_catalog = '{database_name}'
425
+ AND c.table_schema = '{schema_name}'
368
426
  AND c.table_name = '{table_name}'
369
427
  ORDER BY c.ordinal_position
370
428
  """
@@ -403,7 +461,8 @@ class PostgreSQLConnector(SQLAlchemyConnector):
403
461
  @override
404
462
  def get_schemas(self, catalog_name: str = "", database_name: str = "", include_sys: bool = False) -> List[str]:
405
463
  """Get list of schemas in the current database."""
406
- sql = "SELECT schema_name FROM information_schema.schemata"
464
+ database_name = database_name or self.database_name
465
+ sql = f"SELECT schema_name FROM information_schema.schemata WHERE catalog_name = '{database_name}'"
407
466
  result = self._execute_pandas(sql)
408
467
  schemas = result["schema_name"].tolist()
409
468
 
@@ -422,11 +481,17 @@ class PostgreSQLConnector(SQLAlchemyConnector):
422
481
 
423
482
  @override
424
483
  def do_switch_context(self, catalog_name: str = "", database_name: str = "", schema_name: str = ""):
425
- """Switch schema context by updating self.schema_name.
484
+ """Switch database/schema context.
426
485
 
427
- Note: All queries use explicit schema qualification via full_name(),
428
- so we only need to update self.schema_name here.
486
+ PostgreSQL requires reconnection to switch databases.
487
+ Schema switching only updates self.schema_name since all queries
488
+ use explicit schema qualification via full_name().
429
489
  """
490
+ if database_name and database_name != self.database_name:
491
+ self.connection_string = self._build_connection_string(database_name)
492
+ self.close()
493
+ self.connect()
494
+ self.database_name = database_name
430
495
  if schema_name:
431
496
  self.schema_name = schema_name
432
497
 
@@ -477,7 +542,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
477
542
  return result
478
543
 
479
544
  # Otherwise get metadata and query all tables
480
- metadata = self._get_metadata(table_type, "", "", schema_name)
545
+ metadata = self._get_metadata(table_type, "", database_name, schema_name)
481
546
  for meta in metadata:
482
547
  full_name = self.full_name(schema_name=meta["schema_name"], table_name=meta["table_name"])
483
548
  sql = f"SELECT * FROM {full_name} LIMIT {top_n}"
@@ -499,28 +564,46 @@ class PostgreSQLConnector(SQLAlchemyConnector):
499
564
 
500
565
  @override
501
566
  def identifier(
502
- self, catalog_name: str = "", database_name: str = "", schema_name: str = "", table_name: str = ""
567
+ self,
568
+ catalog_name: str = "",
569
+ database_name: str = "",
570
+ schema_name: str = "",
571
+ table_name: str = "",
503
572
  ) -> str:
504
573
  """Generate a unique identifier for a table."""
574
+ database_name = database_name or self.database_name
505
575
  schema_name = schema_name or self.schema_name
576
+ if database_name and schema_name:
577
+ return f"{database_name}.{schema_name}.{table_name}"
506
578
  if schema_name:
507
579
  return f"{schema_name}.{table_name}"
508
580
  return table_name
509
581
 
510
582
  @override
511
583
  def full_name(
512
- self, catalog_name: str = "", database_name: str = "", schema_name: str = "", table_name: str = ""
584
+ self,
585
+ catalog_name: str = "",
586
+ database_name: str = "",
587
+ schema_name: str = "",
588
+ table_name: str = "",
513
589
  ) -> str:
514
590
  """Build fully-qualified table name."""
591
+ database_name = database_name or self.database_name
515
592
  schema_name = schema_name or self.schema_name
593
+ if database_name and schema_name:
594
+ return f"{self._quote_identifier(database_name)}.{self._quote_identifier(schema_name)}.{self._quote_identifier(table_name)}"
516
595
  if schema_name:
517
596
  return f"{self._quote_identifier(schema_name)}.{self._quote_identifier(table_name)}"
518
597
  return self._quote_identifier(table_name)
519
598
 
520
599
  @override
521
600
  def _reset_filter_tables(
522
- self, tables: Optional[List[str]] = None, catalog_name: str = "", database_name: str = "", schema_name: str = ""
601
+ self,
602
+ tables: Optional[List[str]] = None,
603
+ catalog_name: str = "",
604
+ database_name: str = "",
605
+ schema_name: str = "",
523
606
  ) -> List[str]:
524
607
  """Reset filter tables with full names."""
525
608
  schema_name = schema_name or self.schema_name
526
- return super()._reset_filter_tables(tables, "", "", schema_name)
609
+ return super()._reset_filter_tables(tables, "", database_name, schema_name)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datus-postgresql"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "PostgreSQL database adapter for Datus"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -104,7 +104,12 @@ TPCH_DATA = {
104
104
  ],
105
105
  "tpch_nation": [
106
106
  (0, "ALGERIA", 0, " haggle. carefully final deposits detect slyly agai"),
107
- (1, "ARGENTINA", 1, "al foxes promise slyly according to the regular accounts."),
107
+ (
108
+ 1,
109
+ "ARGENTINA",
110
+ 1,
111
+ "al foxes promise slyly according to the regular accounts.",
112
+ ),
108
113
  (2, "BRAZIL", 1, "y alongside of the pending deposits."),
109
114
  (3, "CANADA", 1, "eas hang ironic, silent packages."),
110
115
  (4, "EGYPT", 4, "y above the carefully unusual theodolites."),
@@ -118,7 +123,12 @@ TPCH_DATA = {
118
123
  (12, "JAPAN", 2, "ously. final, express gifts cajole a"),
119
124
  (13, "JORDAN", 4, "ic deposits are blithely about the carefully regular pa"),
120
125
  (14, "KENYA", 0, " pending excuses haggle furiously deposits."),
121
- (15, "MOROCCO", 0, "rns. blithely bold courts among the closely regular packages"),
126
+ (
127
+ 15,
128
+ "MOROCCO",
129
+ 0,
130
+ "rns. blithely bold courts among the closely regular packages",
131
+ ),
122
132
  (16, "MOZAMBIQUE", 0, "s. ironic, unusual asymptotes wake blithely r"),
123
133
  (17, "PERU", 1, "platelets. blithely pending dependencies use fluffily"),
124
134
  (18, "CHINA", 2, "c dependencies. furiously express notornis sleep slyly"),
@@ -139,7 +149,15 @@ TPCH_DATA = {
139
149
  5755.94,
140
150
  "each slyly above the careful",
141
151
  ),
142
- (2, "Supplier#000000002", "89eJ5ksX3ImxJQBvxObC,", 5, "15-679-861-2259", 4032.68, " slyly bold instructions."),
152
+ (
153
+ 2,
154
+ "Supplier#000000002",
155
+ "89eJ5ksX3ImxJQBvxObC,",
156
+ 5,
157
+ "15-679-861-2259",
158
+ 4032.68,
159
+ " slyly bold instructions.",
160
+ ),
143
161
  (
144
162
  3,
145
163
  "Supplier#000000003",
@@ -158,7 +176,15 @@ TPCH_DATA = {
158
176
  4641.08,
159
177
  "riously even requests above the exp",
160
178
  ),
161
- (5, "Supplier#000000005", "Gcdm2rJRzl5qlTVzc", 11, "21-151-690-3663", -531.44, ". slyly regular pinto beans t"),
179
+ (
180
+ 5,
181
+ "Supplier#000000005",
182
+ "Gcdm2rJRzl5qlTVzc",
183
+ 11,
184
+ "21-151-690-3663",
185
+ -531.44,
186
+ ". slyly regular pinto beans t",
187
+ ),
162
188
  ],
163
189
  "tpch_customer": [
164
190
  (
@@ -263,7 +289,17 @@ TPCH_DATA = {
263
289
  ),
264
290
  ],
265
291
  "tpch_orders": [
266
- (1, 1, "O", 173665.47, "1996-01-02", "5-LOW", "Clerk#000000951", 0, "nstructions sleep furiously among"),
292
+ (
293
+ 1,
294
+ 1,
295
+ "O",
296
+ 173665.47,
297
+ "1996-01-02",
298
+ "5-LOW",
299
+ "Clerk#000000951",
300
+ 0,
301
+ "nstructions sleep furiously among",
302
+ ),
267
303
  (
268
304
  2,
269
305
  2,
@@ -275,9 +311,39 @@ TPCH_DATA = {
275
311
  0,
276
312
  " foxes. pending accounts at the pending",
277
313
  ),
278
- (3, 3, "F", 193846.25, "1993-10-14", "5-LOW", "Clerk#000000955", 0, "sly final accounts boost."),
279
- (4, 4, "O", 32151.78, "1995-10-11", "5-LOW", "Clerk#000000124", 0, "sits. slyly regular warthogs cajole."),
280
- (5, 5, "F", 144659.20, "1994-07-30", "5-LOW", "Clerk#000000925", 0, "quickly. bold deposits sleep slyly."),
314
+ (
315
+ 3,
316
+ 3,
317
+ "F",
318
+ 193846.25,
319
+ "1993-10-14",
320
+ "5-LOW",
321
+ "Clerk#000000955",
322
+ 0,
323
+ "sly final accounts boost.",
324
+ ),
325
+ (
326
+ 4,
327
+ 4,
328
+ "O",
329
+ 32151.78,
330
+ "1995-10-11",
331
+ "5-LOW",
332
+ "Clerk#000000124",
333
+ 0,
334
+ "sits. slyly regular warthogs cajole.",
335
+ ),
336
+ (
337
+ 5,
338
+ 5,
339
+ "F",
340
+ 144659.20,
341
+ "1994-07-30",
342
+ "5-LOW",
343
+ "Clerk#000000925",
344
+ 0,
345
+ "quickly. bold deposits sleep slyly.",
346
+ ),
281
347
  (
282
348
  6,
283
349
  6,
@@ -289,9 +355,39 @@ TPCH_DATA = {
289
355
  0,
290
356
  "ggle. special, final requests are against the furiously",
291
357
  ),
292
- (7, 7, "O", 252004.18, "1996-01-10", "2-HIGH", "Clerk#000000470", 0, "ly special requests"),
293
- (32, 8, "O", 208660.75, "1995-07-16", "2-HIGH", "Clerk#000000616", 0, "ise blithely bold, regular requests."),
294
- (33, 9, "F", 163243.98, "1993-10-27", "3-MEDIUM", "Clerk#000000409", 0, "uriously. furiously final request"),
358
+ (
359
+ 7,
360
+ 7,
361
+ "O",
362
+ 252004.18,
363
+ "1996-01-10",
364
+ "2-HIGH",
365
+ "Clerk#000000470",
366
+ 0,
367
+ "ly special requests",
368
+ ),
369
+ (
370
+ 32,
371
+ 8,
372
+ "O",
373
+ 208660.75,
374
+ "1995-07-16",
375
+ "2-HIGH",
376
+ "Clerk#000000616",
377
+ 0,
378
+ "ise blithely bold, regular requests.",
379
+ ),
380
+ (
381
+ 33,
382
+ 9,
383
+ "F",
384
+ 163243.98,
385
+ "1993-10-27",
386
+ "3-MEDIUM",
387
+ "Clerk#000000409",
388
+ 0,
389
+ "uriously. furiously final request",
390
+ ),
295
391
  (
296
392
  34,
297
393
  10,
@@ -314,10 +410,50 @@ TPCH_DATA = {
314
410
  0,
315
411
  "zzle. carefully enticing deposits nag furio",
316
412
  ),
317
- (36, 2, "O", 38988.98, "1995-11-03", "1-URGENT", "Clerk#000000358", 0, " quick packages are blithely."),
318
- (37, 3, "F", 131896.48, "1992-06-03", "3-MEDIUM", "Clerk#000000456", 0, "kly regular pinto beans."),
319
- (38, 4, "O", 46366.56, "1996-08-21", "4-NOT SPECIFIED", "Clerk#000000604", 0, "haggle blithely."),
320
- (39, 5, "O", 219707.84, "1996-09-20", "3-MEDIUM", "Clerk#000000659", 0, "ole express, ironic requests:"),
413
+ (
414
+ 36,
415
+ 2,
416
+ "O",
417
+ 38988.98,
418
+ "1995-11-03",
419
+ "1-URGENT",
420
+ "Clerk#000000358",
421
+ 0,
422
+ " quick packages are blithely.",
423
+ ),
424
+ (
425
+ 37,
426
+ 3,
427
+ "F",
428
+ 131896.48,
429
+ "1992-06-03",
430
+ "3-MEDIUM",
431
+ "Clerk#000000456",
432
+ 0,
433
+ "kly regular pinto beans.",
434
+ ),
435
+ (
436
+ 38,
437
+ 4,
438
+ "O",
439
+ 46366.56,
440
+ "1996-08-21",
441
+ "4-NOT SPECIFIED",
442
+ "Clerk#000000604",
443
+ 0,
444
+ "haggle blithely.",
445
+ ),
446
+ (
447
+ 39,
448
+ 5,
449
+ "O",
450
+ 219707.84,
451
+ "1996-09-20",
452
+ "3-MEDIUM",
453
+ "Clerk#000000659",
454
+ 0,
455
+ "ole express, ironic requests:",
456
+ ),
321
457
  ],
322
458
  }
323
459
 
@@ -6,5 +6,6 @@
6
6
  def pytest_configure(config):
7
7
  """Configure custom markers."""
8
8
  config.addinivalue_line(
9
- "markers", "integration: marks tests as integration tests (deselect with '-m \"not integration\"')"
9
+ "markers",
10
+ "integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
10
11
  )
@@ -6,6 +6,7 @@ import os
6
6
  from typing import Generator
7
7
 
8
8
  import pytest
9
+
9
10
  from datus_postgresql import PostgreSQLConfig, PostgreSQLConnector
10
11
 
11
12
  # ---------------------------------------------------------------------------
@@ -83,7 +84,12 @@ TPCH_DATA = {
83
84
  ],
84
85
  "tpch_nation": [
85
86
  (0, "ALGERIA", 0, " haggle. carefully final deposits detect slyly agai"),
86
- (1, "ARGENTINA", 1, "al foxes promise slyly according to the regular accounts."),
87
+ (
88
+ 1,
89
+ "ARGENTINA",
90
+ 1,
91
+ "al foxes promise slyly according to the regular accounts.",
92
+ ),
87
93
  (2, "BRAZIL", 1, "y alongside of the pending deposits."),
88
94
  (3, "CANADA", 1, "eas hang ironic, silent packages."),
89
95
  (4, "EGYPT", 4, "y above the carefully unusual theodolites."),
@@ -97,7 +103,12 @@ TPCH_DATA = {
97
103
  (12, "JAPAN", 2, "ously. final, express gifts cajole a"),
98
104
  (13, "JORDAN", 4, "ic deposits are blithely about the carefully regular pa"),
99
105
  (14, "KENYA", 0, " pending excuses haggle furiously deposits."),
100
- (15, "MOROCCO", 0, "rns. blithely bold courts among the closely regular packages"),
106
+ (
107
+ 15,
108
+ "MOROCCO",
109
+ 0,
110
+ "rns. blithely bold courts among the closely regular packages",
111
+ ),
101
112
  (16, "MOZAMBIQUE", 0, "s. ironic, unusual asymptotes wake blithely r"),
102
113
  (17, "PERU", 1, "platelets. blithely pending dependencies use fluffily"),
103
114
  (18, "CHINA", 2, "c dependencies. furiously express notornis sleep slyly"),
@@ -118,7 +129,15 @@ TPCH_DATA = {
118
129
  5755.94,
119
130
  "each slyly above the careful",
120
131
  ),
121
- (2, "Supplier#000000002", "89eJ5ksX3ImxJQBvxObC,", 5, "15-679-861-2259", 4032.68, " slyly bold instructions."),
132
+ (
133
+ 2,
134
+ "Supplier#000000002",
135
+ "89eJ5ksX3ImxJQBvxObC,",
136
+ 5,
137
+ "15-679-861-2259",
138
+ 4032.68,
139
+ " slyly bold instructions.",
140
+ ),
122
141
  (
123
142
  3,
124
143
  "Supplier#000000003",
@@ -137,7 +156,15 @@ TPCH_DATA = {
137
156
  4641.08,
138
157
  "riously even requests above the exp",
139
158
  ),
140
- (5, "Supplier#000000005", "Gcdm2rJRzl5qlTVzc", 11, "21-151-690-3663", -531.44, ". slyly regular pinto beans t"),
159
+ (
160
+ 5,
161
+ "Supplier#000000005",
162
+ "Gcdm2rJRzl5qlTVzc",
163
+ 11,
164
+ "21-151-690-3663",
165
+ -531.44,
166
+ ". slyly regular pinto beans t",
167
+ ),
141
168
  ],
142
169
  "tpch_customer": [
143
170
  (
@@ -242,7 +269,17 @@ TPCH_DATA = {
242
269
  ),
243
270
  ],
244
271
  "tpch_orders": [
245
- (1, 1, "O", 173665.47, "1996-01-02", "5-LOW", "Clerk#000000951", 0, "nstructions sleep furiously among"),
272
+ (
273
+ 1,
274
+ 1,
275
+ "O",
276
+ 173665.47,
277
+ "1996-01-02",
278
+ "5-LOW",
279
+ "Clerk#000000951",
280
+ 0,
281
+ "nstructions sleep furiously among",
282
+ ),
246
283
  (
247
284
  2,
248
285
  2,
@@ -254,9 +291,39 @@ TPCH_DATA = {
254
291
  0,
255
292
  " foxes. pending accounts at the pending",
256
293
  ),
257
- (3, 3, "F", 193846.25, "1993-10-14", "5-LOW", "Clerk#000000955", 0, "sly final accounts boost."),
258
- (4, 4, "O", 32151.78, "1995-10-11", "5-LOW", "Clerk#000000124", 0, "sits. slyly regular warthogs cajole."),
259
- (5, 5, "F", 144659.20, "1994-07-30", "5-LOW", "Clerk#000000925", 0, "quickly. bold deposits sleep slyly."),
294
+ (
295
+ 3,
296
+ 3,
297
+ "F",
298
+ 193846.25,
299
+ "1993-10-14",
300
+ "5-LOW",
301
+ "Clerk#000000955",
302
+ 0,
303
+ "sly final accounts boost.",
304
+ ),
305
+ (
306
+ 4,
307
+ 4,
308
+ "O",
309
+ 32151.78,
310
+ "1995-10-11",
311
+ "5-LOW",
312
+ "Clerk#000000124",
313
+ 0,
314
+ "sits. slyly regular warthogs cajole.",
315
+ ),
316
+ (
317
+ 5,
318
+ 5,
319
+ "F",
320
+ 144659.20,
321
+ "1994-07-30",
322
+ "5-LOW",
323
+ "Clerk#000000925",
324
+ 0,
325
+ "quickly. bold deposits sleep slyly.",
326
+ ),
260
327
  (
261
328
  6,
262
329
  6,
@@ -268,9 +335,39 @@ TPCH_DATA = {
268
335
  0,
269
336
  "ggle. special, final requests are against the furiously",
270
337
  ),
271
- (7, 7, "O", 252004.18, "1996-01-10", "2-HIGH", "Clerk#000000470", 0, "ly special requests"),
272
- (32, 8, "O", 208660.75, "1995-07-16", "2-HIGH", "Clerk#000000616", 0, "ise blithely bold, regular requests."),
273
- (33, 9, "F", 163243.98, "1993-10-27", "3-MEDIUM", "Clerk#000000409", 0, "uriously. furiously final request"),
338
+ (
339
+ 7,
340
+ 7,
341
+ "O",
342
+ 252004.18,
343
+ "1996-01-10",
344
+ "2-HIGH",
345
+ "Clerk#000000470",
346
+ 0,
347
+ "ly special requests",
348
+ ),
349
+ (
350
+ 32,
351
+ 8,
352
+ "O",
353
+ 208660.75,
354
+ "1995-07-16",
355
+ "2-HIGH",
356
+ "Clerk#000000616",
357
+ 0,
358
+ "ise blithely bold, regular requests.",
359
+ ),
360
+ (
361
+ 33,
362
+ 9,
363
+ "F",
364
+ 163243.98,
365
+ "1993-10-27",
366
+ "3-MEDIUM",
367
+ "Clerk#000000409",
368
+ 0,
369
+ "uriously. furiously final request",
370
+ ),
274
371
  (
275
372
  34,
276
373
  10,
@@ -293,10 +390,50 @@ TPCH_DATA = {
293
390
  0,
294
391
  "zzle. carefully enticing deposits nag furio",
295
392
  ),
296
- (36, 2, "O", 38988.98, "1995-11-03", "1-URGENT", "Clerk#000000358", 0, " quick packages are blithely."),
297
- (37, 3, "F", 131896.48, "1992-06-03", "3-MEDIUM", "Clerk#000000456", 0, "kly regular pinto beans."),
298
- (38, 4, "O", 46366.56, "1996-08-21", "4-NOT SPECIFIED", "Clerk#000000604", 0, "haggle blithely."),
299
- (39, 5, "O", 219707.84, "1996-09-20", "3-MEDIUM", "Clerk#000000659", 0, "ole express, ironic requests:"),
393
+ (
394
+ 36,
395
+ 2,
396
+ "O",
397
+ 38988.98,
398
+ "1995-11-03",
399
+ "1-URGENT",
400
+ "Clerk#000000358",
401
+ 0,
402
+ " quick packages are blithely.",
403
+ ),
404
+ (
405
+ 37,
406
+ 3,
407
+ "F",
408
+ 131896.48,
409
+ "1992-06-03",
410
+ "3-MEDIUM",
411
+ "Clerk#000000456",
412
+ 0,
413
+ "kly regular pinto beans.",
414
+ ),
415
+ (
416
+ 38,
417
+ 4,
418
+ "O",
419
+ 46366.56,
420
+ "1996-08-21",
421
+ "4-NOT SPECIFIED",
422
+ "Clerk#000000604",
423
+ 0,
424
+ "haggle blithely.",
425
+ ),
426
+ (
427
+ 39,
428
+ 5,
429
+ "O",
430
+ 219707.84,
431
+ "1996-09-20",
432
+ "3-MEDIUM",
433
+ "Clerk#000000659",
434
+ 0,
435
+ "ole express, ironic requests:",
436
+ ),
300
437
  ],
301
438
  }
302
439
 
@@ -6,6 +6,7 @@ import os
6
6
  import uuid
7
7
 
8
8
  import pytest
9
+
9
10
  from datus_postgresql import PostgreSQLConfig, PostgreSQLConnector
10
11
 
11
12
  # ==================== Connection Tests ====================
@@ -307,7 +308,8 @@ def test_execute_insert(connector: PostgreSQLConnector, config: PostgreSQLConfig
307
308
 
308
309
  # Verify
309
310
  query_result = connector.execute(
310
- {"sql_query": f"SELECT id, name FROM {table_name} ORDER BY id"}, result_format="list"
311
+ {"sql_query": f"SELECT id, name FROM {table_name} ORDER BY id"},
312
+ result_format="list",
311
313
  )
312
314
  assert len(query_result.sql_return) == 2
313
315
  assert query_result.sql_return[0]["name"] == "Alice"
@@ -342,7 +344,8 @@ def test_execute_update(connector: PostgreSQLConnector, config: PostgreSQLConfig
342
344
 
343
345
  # Verify
344
346
  query_result = connector.execute(
345
- {"sql_query": f"SELECT name FROM {table_name} WHERE id = 1"}, result_format="list"
347
+ {"sql_query": f"SELECT name FROM {table_name} WHERE id = 1"},
348
+ result_format="list",
346
349
  )
347
350
  assert query_result.sql_return == [{"name": "Alice Updated"}]
348
351
  finally:
@@ -404,8 +407,9 @@ def test_exception_on_nonexistent_table(connector: PostgreSQLConnector):
404
407
  @pytest.mark.integration
405
408
  def test_full_name_with_schema(connector: PostgreSQLConnector):
406
409
  """Test full_name with schema."""
410
+ db = connector.database_name
407
411
  full_name = connector.full_name(schema_name="myschema", table_name="mytable")
408
- assert full_name == '"myschema"."mytable"'
412
+ assert full_name == f'"{db}"."myschema"."mytable"'
409
413
 
410
414
 
411
415
  @pytest.mark.integration
@@ -419,5 +423,6 @@ def test_full_name_with_default_schema(connector: PostgreSQLConnector):
419
423
  @pytest.mark.integration
420
424
  def test_identifier(connector: PostgreSQLConnector):
421
425
  """Test identifier generation."""
426
+ db = connector.database_name
422
427
  identifier = connector.identifier(schema_name="myschema", table_name="mytable")
423
- assert identifier == "myschema.mytable"
428
+ assert identifier == f"{db}.myschema.mytable"
@@ -131,7 +131,13 @@ class TestTpchMetadata:
131
131
  """get_tables() should return TPC-H tables."""
132
132
  tables = tpch_setup.get_tables(schema_name="public")
133
133
  tpch_tables = {t for t in tables if t.startswith("tpch_")}
134
- expected = {"tpch_region", "tpch_nation", "tpch_supplier", "tpch_customer", "tpch_orders"}
134
+ expected = {
135
+ "tpch_region",
136
+ "tpch_nation",
137
+ "tpch_supplier",
138
+ "tpch_customer",
139
+ "tpch_orders",
140
+ }
135
141
  assert expected.issubset(tpch_tables)
136
142
 
137
143
  def test_get_schema_columns(self, tpch_setup):
@@ -3,9 +3,10 @@
3
3
  # See http://www.apache.org/licenses/LICENSE-2.0 for details.
4
4
 
5
5
  import pytest
6
- from datus_postgresql import PostgreSQLConfig
7
6
  from pydantic import ValidationError
8
7
 
8
+ from datus_postgresql import PostgreSQLConfig
9
+
9
10
 
10
11
  @pytest.mark.acceptance
11
12
  def test_config_with_all_required_fields():
@@ -5,6 +5,7 @@
5
5
  from unittest.mock import patch
6
6
 
7
7
  import pytest
8
+
8
9
  from datus_db_core import DatusDbException
9
10
  from datus_postgresql import PostgreSQLConfig, PostgreSQLConnector
10
11
 
@@ -223,10 +224,11 @@ def test_full_name_with_schema():
223
224
 
224
225
  with patch("datus_sqlalchemy.SQLAlchemyConnector.__init__", return_value=None):
225
226
  connector = PostgreSQLConnector(config)
227
+ connector.database_name = "postgres"
226
228
  connector.schema_name = "public"
227
229
  full_name = connector.full_name(schema_name="myschema", table_name="mytable")
228
230
 
229
- assert full_name == '"myschema"."mytable"'
231
+ assert full_name == '"postgres"."myschema"."mytable"'
230
232
 
231
233
 
232
234
  def test_full_name_with_default_schema():
@@ -235,10 +237,11 @@ def test_full_name_with_default_schema():
235
237
 
236
238
  with patch("datus_sqlalchemy.SQLAlchemyConnector.__init__", return_value=None):
237
239
  connector = PostgreSQLConnector(config)
240
+ connector.database_name = "postgres"
238
241
  connector.schema_name = "public"
239
242
  full_name = connector.full_name(table_name="mytable")
240
243
 
241
- assert full_name == '"public"."mytable"'
244
+ assert full_name == '"postgres"."public"."mytable"'
242
245
 
243
246
 
244
247
  def test_full_name_with_special_characters():
@@ -247,10 +250,11 @@ def test_full_name_with_special_characters():
247
250
 
248
251
  with patch("datus_sqlalchemy.SQLAlchemyConnector.__init__", return_value=None):
249
252
  connector = PostgreSQLConnector(config)
253
+ connector.database_name = "postgres"
250
254
  connector.schema_name = "public"
251
255
  full_name = connector.full_name(schema_name='my"schema', table_name='my"table')
252
256
 
253
- assert full_name == '"my""schema"."my""table"'
257
+ assert full_name == '"postgres"."my""schema"."my""table"'
254
258
 
255
259
 
256
260
  def test_identifier_with_schema():
@@ -259,10 +263,11 @@ def test_identifier_with_schema():
259
263
 
260
264
  with patch("datus_sqlalchemy.SQLAlchemyConnector.__init__", return_value=None):
261
265
  connector = PostgreSQLConnector(config)
266
+ connector.database_name = "postgres"
262
267
  connector.schema_name = "public"
263
268
  identifier = connector.identifier(schema_name="myschema", table_name="mytable")
264
269
 
265
- assert identifier == "myschema.mytable"
270
+ assert identifier == "postgres.myschema.mytable"
266
271
 
267
272
 
268
273
  def test_identifier_with_default_schema():
@@ -271,10 +276,11 @@ def test_identifier_with_default_schema():
271
276
 
272
277
  with patch("datus_sqlalchemy.SQLAlchemyConnector.__init__", return_value=None):
273
278
  connector = PostgreSQLConnector(config)
279
+ connector.database_name = "postgres"
274
280
  connector.schema_name = "public"
275
281
  identifier = connector.identifier(table_name="mytable")
276
282
 
277
- assert identifier == "public.mytable"
283
+ assert identifier == "postgres.public.mytable"
278
284
 
279
285
 
280
286
  @pytest.mark.acceptance
@@ -1,140 +0,0 @@
1
- # Byte-compiled / optimized / DLL files
2
- __pycache__/
3
- *.py[cod]
4
- *$py.class
5
-
6
- # C extensions
7
- *.so
8
-
9
- # Distribution / packaging
10
- .Python
11
- build/
12
- develop-eggs/
13
- dist/
14
- downloads/
15
- eggs/
16
- .eggs/
17
- lib/
18
- lib64/
19
- parts/
20
- sdist/
21
- var/
22
- wheels/
23
- pip-wheel-metadata/
24
- share/python-wheels/
25
- *.egg-info/
26
- .installed.cfg
27
- *.egg
28
- MANIFEST
29
-
30
- # PyInstaller
31
- *.manifest
32
- *.spec
33
-
34
- # Installer logs
35
- pip-log.txt
36
- pip-delete-this-directory.txt
37
-
38
- # Unit test / coverage reports
39
- htmlcov/
40
- .tox/
41
- .nox/
42
- .coverage
43
- .coverage.*
44
- .cache
45
- nosetests.xml
46
- coverage.xml
47
- *.cover
48
- *.py,cover
49
- .hypothesis/
50
- .pytest_cache/
51
-
52
- # Translations
53
- *.mo
54
- *.pot
55
-
56
- # Django stuff:
57
- *.log
58
- local_settings.py
59
- db.sqlite3
60
- db.sqlite3-journal
61
-
62
- # Flask stuff:
63
- instance/
64
- .webassets-cache
65
-
66
- # Scrapy stuff:
67
- .scrapy
68
-
69
- # Sphinx documentation
70
- docs/_build/
71
-
72
- # PyBuilder
73
- target/
74
-
75
- # Jupyter Notebook
76
- .ipynb_checkpoints
77
-
78
- # IPython
79
- profile_default/
80
- ipython_config.py
81
-
82
- # pyenv
83
- .python-version
84
-
85
- # pipenv
86
- Pipfile.lock
87
-
88
- # uv
89
- uv.lock
90
-
91
- # PEP 582
92
- __pypackages__/
93
-
94
- # Celery stuff
95
- celerybeat-schedule
96
- celerybeat.pid
97
-
98
- # SageMath parsed files
99
- *.sage.py
100
-
101
- # Environments
102
- .env
103
- .venv
104
- env/
105
- venv/
106
- ENV/
107
- env.bak/
108
- venv.bak/
109
-
110
- # Spyder project settings
111
- .spyderproject
112
- .spyproject
113
-
114
- # Rope project settings
115
- .ropeproject
116
-
117
- # mkdocs documentation
118
- /site
119
-
120
- # mypy
121
- .mypy_cache/
122
- .dmypy.json
123
- dmypy.json
124
-
125
- # Pyre type checker
126
- .pyre/
127
-
128
- # IDEs
129
- .vscode/
130
- .idea/
131
- *.swp
132
- *.swo
133
- *~
134
-
135
- # OS
136
- .DS_Store
137
- Thumbs.db
138
-
139
-
140
- .omc