postgres.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. from datetime import datetime
  18. from typing import Any, List, Optional, Tuple, TYPE_CHECKING
  19. from pytz import _FixedOffset # type: ignore
  20. from sqlalchemy.dialects.postgresql.base import PGInspector
  21. from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod
  22. if TYPE_CHECKING:
  23. # prevent circular imports
  24. from superset.models.core import Database # pylint: disable=unused-import
  25. # Replace psycopg2.tz.FixedOffsetTimezone with pytz, which is serializable by PyArrow
  26. # https://github.com/stub42/pytz/blob/b70911542755aeeea7b5a9e066df5e1c87e8f2c8/src/pytz/reference.py#L25
  27. class FixedOffsetTimezone(_FixedOffset):
  28. pass
  29. class PostgresBaseEngineSpec(BaseEngineSpec):
  30. """ Abstract class for Postgres 'like' databases """
  31. engine = ""
  32. _time_grain_functions = {
  33. None: "{col}",
  34. "PT1S": "DATE_TRUNC('second', {col})",
  35. "PT1M": "DATE_TRUNC('minute', {col})",
  36. "PT1H": "DATE_TRUNC('hour', {col})",
  37. "P1D": "DATE_TRUNC('day', {col})",
  38. "P1W": "DATE_TRUNC('week', {col})",
  39. "P1M": "DATE_TRUNC('month', {col})",
  40. "P0.25Y": "DATE_TRUNC('quarter', {col})",
  41. "P1Y": "DATE_TRUNC('year', {col})",
  42. }
  43. @classmethod
  44. def fetch_data(cls, cursor: Any, limit: int) -> List[Tuple]:
  45. cursor.tzinfo_factory = FixedOffsetTimezone
  46. if not cursor.description:
  47. return []
  48. if cls.limit_method == LimitMethod.FETCH_MANY:
  49. return cursor.fetchmany(limit)
  50. return cursor.fetchall()
  51. @classmethod
  52. def epoch_to_dttm(cls) -> str:
  53. return "(timestamp 'epoch' + {col} * interval '1 second')"
  54. class PostgresEngineSpec(PostgresBaseEngineSpec):
  55. engine = "postgresql"
  56. max_column_name_length = 63
  57. try_remove_schema_from_table_name = False
  58. @classmethod
  59. def get_table_names(
  60. cls, database: "Database", inspector: PGInspector, schema: Optional[str]
  61. ) -> List[str]:
  62. """Need to consider foreign tables for PostgreSQL"""
  63. tables = inspector.get_table_names(schema)
  64. tables.extend(inspector.get_foreign_table_names(schema))
  65. return sorted(tables)
  66. @classmethod
  67. def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]:
  68. tt = target_type.upper()
  69. if tt == "DATE":
  70. return f"TO_DATE('{dttm.date().isoformat()}', 'YYYY-MM-DD')"
  71. if tt == "TIMESTAMP":
  72. return f"""TO_TIMESTAMP('{dttm.isoformat(sep=" ", timespec="microseconds")}', 'YYYY-MM-DD HH24:MI:SS.US')""" # pylint: disable=line-too-long
  73. return None