datus-postgresql 0.1.1__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.1 → datus_postgresql-0.1.4}/PKG-INFO +2 -2
  2. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/datus_postgresql/__init__.py +1 -1
  3. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/datus_postgresql/config.py +7 -2
  4. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/datus_postgresql/connector.py +106 -26
  5. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/pyproject.toml +3 -2
  6. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/scripts/init_tpch_data.py +151 -15
  7. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/conftest.py +2 -1
  8. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/integration/conftest.py +152 -15
  9. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/integration/test_integration.py +9 -4
  10. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/integration/test_tpch.py +7 -1
  11. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/unit/test_config.py +2 -1
  12. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/unit/test_connector_unit.py +13 -7
  13. datus_postgresql-0.1.1/.gitignore +0 -140
  14. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/README.md +0 -0
  15. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/datus_postgresql/handlers.py +0 -0
  16. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/docker-compose.yml +0 -0
  17. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/__init__.py +0 -0
  18. {datus_postgresql-0.1.1 → datus_postgresql-0.1.4}/tests/integration/__init__.py +0 -0
  19. {datus_postgresql-0.1.1 → 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.1
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
@@ -14,7 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.12
17
- Requires-Dist: datus-agent>0.2.5
17
+ Requires-Dist: datus-db-core>=0.1.0
18
18
  Requires-Dist: datus-sqlalchemy>=0.1.2
19
19
  Requires-Dist: psycopg2-binary>=2.9.11
20
20
  Requires-Dist: pydantic>=2.0.0
@@ -11,7 +11,7 @@ __all__ = ["PostgreSQLConnector", "PostgreSQLConfig", "register"]
11
11
 
12
12
  def register():
13
13
  """Register PostgreSQL connector with Datus registry."""
14
- from datus.tools.db_tools import connector_registry
14
+ from datus_db_core import connector_registry
15
15
 
16
16
  from .handlers import build_postgresql_uri, resolve_postgresql_context
17
17
 
@@ -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,12 +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.schemas.base import TABLE_TYPE
9
- from datus.tools.db_tools.base import list_to_in_str
10
- from datus.utils.exceptions import DatusException, ErrorCode
11
- from datus.utils.loggings import get_logger
12
- from datus_sqlalchemy import SQLAlchemyConnector
8
+ from pandas import DataFrame
13
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
14
20
 
15
21
  from .config import PostgreSQLConfig
16
22
 
@@ -42,7 +48,7 @@ METADATA_DICT: Dict[TABLE_TYPE, TableMetadataNames] = {
42
48
  def _get_metadata_config(table_type: TABLE_TYPE) -> TableMetadataNames:
43
49
  """Get metadata configuration for given table type."""
44
50
  if table_type not in METADATA_DICT:
45
- raise DatusException(ErrorCode.COMMON_FIELD_INVALID, f"Invalid table type '{table_type}'")
51
+ raise DatusDbException(ErrorCode.COMMON_FIELD_INVALID, f"Invalid table type '{table_type}'")
46
52
  return METADATA_DICT[table_type]
47
53
 
48
54
 
@@ -79,7 +85,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
79
85
  f"{database}?sslmode={config.sslmode}"
80
86
  )
81
87
 
82
- 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
+ )
83
93
  self.database_name = database
84
94
  self.schema_name = config.schema_name or "public"
85
95
 
@@ -93,7 +103,13 @@ class PostgreSQLConnector(SQLAlchemyConnector):
93
103
  @override
94
104
  def _sys_schemas(self) -> Set[str]:
95
105
  """System schemas to filter out."""
96
- 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
+ }
97
113
 
98
114
  # ==================== Utility Methods ====================
99
115
 
@@ -103,6 +119,33 @@ class PostgreSQLConnector(SQLAlchemyConnector):
103
119
  escaped = identifier.replace('"', '""')
104
120
  return f'"{escaped}"'
105
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
+
106
149
  # ==================== Metadata Retrieval ====================
107
150
 
108
151
  def _get_metadata(
@@ -125,13 +168,15 @@ class PostgreSQLConnector(SQLAlchemyConnector):
125
168
  List of metadata dictionaries
126
169
  """
127
170
  self.connect()
171
+ database_name = database_name or self.database_name
128
172
  schema_name = schema_name or self.schema_name
129
173
 
130
174
  # Get metadata configuration
131
175
  metadata_config = _get_metadata_config(table_type)
132
176
 
133
177
  if table_type == "mv":
134
- # 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).
135
180
  if schema_name:
136
181
  where = f"schemaname = '{schema_name}'"
137
182
  else:
@@ -142,8 +187,9 @@ class PostgreSQLConnector(SQLAlchemyConnector):
142
187
  FROM pg_matviews
143
188
  WHERE {where}
144
189
  """
190
+ query_result = self._execute_on_database(query, database_name)
145
191
  else:
146
- # Tables and views use information_schema
192
+ # Tables and views use information_schema (supports table_catalog filter)
147
193
  if schema_name:
148
194
  where = f"table_schema = '{schema_name}'"
149
195
  else:
@@ -157,10 +203,9 @@ class PostgreSQLConnector(SQLAlchemyConnector):
157
203
  query = f"""
158
204
  SELECT table_schema, table_name
159
205
  FROM information_schema.{metadata_config.info_table}
160
- WHERE {where} {type_filter}
206
+ WHERE table_catalog = '{database_name}' AND {where} {type_filter}
161
207
  """
162
-
163
- query_result = self._execute_pandas(query)
208
+ query_result = self._execute_pandas(query)
164
209
 
165
210
  # Format results
166
211
  result = []
@@ -171,7 +216,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
171
216
  {
172
217
  "identifier": self.identifier(schema_name=schema, table_name=tb_name),
173
218
  "catalog_name": "",
174
- "database_name": self.database_name,
219
+ "database_name": database_name,
175
220
  "schema_name": schema,
176
221
  "table_name": tb_name,
177
222
  "table_type": table_type,
@@ -310,7 +355,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
310
355
 
311
356
  @override
312
357
  def get_tables_with_ddl(
313
- 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,
314
363
  ) -> List[Dict[str, str]]:
315
364
  """Get tables with DDL statements."""
316
365
  return self._get_objects_with_ddl("table", tables, catalog_name, database_name, schema_name)
@@ -324,7 +373,11 @@ class PostgreSQLConnector(SQLAlchemyConnector):
324
373
 
325
374
  @override
326
375
  def get_schema(
327
- 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 = "",
328
381
  ) -> List[Dict[str, Any]]:
329
382
  """
330
383
  Get table schema using INFORMATION_SCHEMA.
@@ -341,6 +394,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
341
394
  if not table_name:
342
395
  return []
343
396
 
397
+ database_name = database_name or self.database_name
344
398
  schema_name = schema_name or self.schema_name
345
399
 
346
400
  # Use INFORMATION_SCHEMA to get schema with comments
@@ -367,7 +421,8 @@ class PostgreSQLConnector(SQLAlchemyConnector):
367
421
  ON st.schemaname = c.table_schema AND st.relname = c.table_name
368
422
  LEFT JOIN pg_catalog.pg_description pgd
369
423
  ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
370
- WHERE c.table_schema = '{schema_name}'
424
+ WHERE c.table_catalog = '{database_name}'
425
+ AND c.table_schema = '{schema_name}'
371
426
  AND c.table_name = '{table_name}'
372
427
  ORDER BY c.ordinal_position
373
428
  """
@@ -406,7 +461,8 @@ class PostgreSQLConnector(SQLAlchemyConnector):
406
461
  @override
407
462
  def get_schemas(self, catalog_name: str = "", database_name: str = "", include_sys: bool = False) -> List[str]:
408
463
  """Get list of schemas in the current database."""
409
- 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}'"
410
466
  result = self._execute_pandas(sql)
411
467
  schemas = result["schema_name"].tolist()
412
468
 
@@ -425,11 +481,17 @@ class PostgreSQLConnector(SQLAlchemyConnector):
425
481
 
426
482
  @override
427
483
  def do_switch_context(self, catalog_name: str = "", database_name: str = "", schema_name: str = ""):
428
- """Switch schema context by updating self.schema_name.
484
+ """Switch database/schema context.
429
485
 
430
- Note: All queries use explicit schema qualification via full_name(),
431
- 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().
432
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
433
495
  if schema_name:
434
496
  self.schema_name = schema_name
435
497
 
@@ -480,7 +542,7 @@ class PostgreSQLConnector(SQLAlchemyConnector):
480
542
  return result
481
543
 
482
544
  # Otherwise get metadata and query all tables
483
- metadata = self._get_metadata(table_type, "", "", schema_name)
545
+ metadata = self._get_metadata(table_type, "", database_name, schema_name)
484
546
  for meta in metadata:
485
547
  full_name = self.full_name(schema_name=meta["schema_name"], table_name=meta["table_name"])
486
548
  sql = f"SELECT * FROM {full_name} LIMIT {top_n}"
@@ -502,28 +564,46 @@ class PostgreSQLConnector(SQLAlchemyConnector):
502
564
 
503
565
  @override
504
566
  def identifier(
505
- 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 = "",
506
572
  ) -> str:
507
573
  """Generate a unique identifier for a table."""
574
+ database_name = database_name or self.database_name
508
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}"
509
578
  if schema_name:
510
579
  return f"{schema_name}.{table_name}"
511
580
  return table_name
512
581
 
513
582
  @override
514
583
  def full_name(
515
- 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 = "",
516
589
  ) -> str:
517
590
  """Build fully-qualified table name."""
591
+ database_name = database_name or self.database_name
518
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)}"
519
595
  if schema_name:
520
596
  return f"{self._quote_identifier(schema_name)}.{self._quote_identifier(table_name)}"
521
597
  return self._quote_identifier(table_name)
522
598
 
523
599
  @override
524
600
  def _reset_filter_tables(
525
- 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 = "",
526
606
  ) -> List[str]:
527
607
  """Reset filter tables with full names."""
528
608
  schema_name = schema_name or self.schema_name
529
- 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.1"
3
+ version = "0.1.4"
4
4
  description = "PostgreSQL database adapter for Datus"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -18,7 +18,7 @@ classifiers = [
18
18
  ]
19
19
 
20
20
  dependencies = [
21
- "datus-agent>0.2.5",
21
+ "datus-db-core>=0.1.0",
22
22
  "datus-sqlalchemy>=0.1.2",
23
23
  "psycopg2-binary>=2.9.11",
24
24
  "pydantic>=2.0.0",
@@ -33,6 +33,7 @@ Issues = "https://github.com/Datus-ai/datus-db-adapters/issues"
33
33
  postgresql = "datus_postgresql:register"
34
34
 
35
35
  [tool.uv.sources]
36
+ datus-db-core = { workspace = true }
36
37
  datus-sqlalchemy = { workspace = true }
37
38
 
38
39
  [build-system]
@@ -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,7 +5,8 @@
5
5
  from unittest.mock import patch
6
6
 
7
7
  import pytest
8
- from datus.utils.exceptions import DatusException
8
+
9
+ from datus_db_core import DatusDbException
9
10
  from datus_postgresql import PostgreSQLConfig, PostgreSQLConnector
10
11
 
11
12
 
@@ -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
@@ -308,7 +314,7 @@ def test_get_metadata_config_invalid_type():
308
314
  """Test _get_metadata_config with invalid table type."""
309
315
  from datus_postgresql.connector import _get_metadata_config
310
316
 
311
- with pytest.raises(DatusException, match="Invalid table type"):
317
+ with pytest.raises(DatusDbException, match="Invalid table type"):
312
318
  _get_metadata_config("invalid_type")
313
319
 
314
320
 
@@ -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