-
Notifications
You must be signed in to change notification settings - Fork 12
Support YDB view reflection #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
|
|
||
| import collections | ||
| import collections.abc | ||
| import re | ||
| from typing import Any, Mapping, Optional, Sequence, Tuple, Union | ||
|
|
||
| import sqlalchemy as sa | ||
|
|
@@ -73,6 +74,13 @@ def upsert(table): | |
| ydb.PrimitiveType.DyNumber: sa.TEXT, | ||
| } | ||
|
|
||
| DBAPI_COLUMN_TYPES = { | ||
| ydb_type.name: sa_type for ydb_type, sa_type in COLUMN_TYPES.items() if isinstance(ydb_type, ydb.PrimitiveType) | ||
| } | ||
|
|
||
|
|
||
| DECIMAL_DBAPI_TYPE_RE = re.compile(r"^Decimal\((\d+),\s*(\d+)\)$") | ||
|
|
||
|
|
||
| def _get_column_info(t): | ||
| nullable = False | ||
|
|
@@ -86,6 +94,28 @@ def _get_column_info(t): | |
| return COLUMN_TYPES[t], nullable | ||
|
|
||
|
|
||
| def _get_column_info_from_dbapi_description(type_name): | ||
| nullable = type_name.endswith("?") | ||
| if nullable: | ||
| type_name = type_name[:-1] | ||
|
|
||
| decimal_match = DECIMAL_DBAPI_TYPE_RE.match(type_name) | ||
| if decimal_match: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: What about Array and other container types? They could be in View columns, don't they?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the problem is same for tables, let's fix this outside of this pr |
||
| precision, scale = decimal_match.groups() | ||
| return sa.DECIMAL(precision=int(precision), scale=int(scale)), nullable | ||
|
|
||
| return DBAPI_COLUMN_TYPES.get(type_name, sa.types.NullType), nullable | ||
|
|
||
|
|
||
| def _format_reflected_column(name, col_type, nullable): | ||
| return { | ||
| "name": name, | ||
| "type": col_type, | ||
| "nullable": nullable, | ||
| "default": None, | ||
| } | ||
|
|
||
|
|
||
| class YdbRequestSettingsCharacteristic(characteristics.ConnectionCharacteristic): | ||
| def reset_characteristic(self, dialect: "YqlDialect", dbapi_connection: ydb_dbapi.Connection) -> None: | ||
| dialect.reset_ydb_request_settings(dbapi_connection) | ||
|
|
@@ -220,41 +250,58 @@ def __init__( | |
| self._add_declare_for_yql_stmt_vars = _add_declare_for_yql_stmt_vars | ||
| self._statement_prefixes = tuple(_statement_prefixes_list) if _statement_prefixes_list else () | ||
|
|
||
| def _describe_table(self, connection, table_name, schema=None) -> ydb.TableDescription: | ||
| if schema is not None: | ||
| def _ensure_schema_unsupported(self, schema): | ||
| if schema: | ||
| raise ydb_dbapi.NotSupportedError("unsupported on non empty schema") | ||
|
|
||
| def _describe_table(self, connection, table_name, schema=None) -> ydb.TableDescription: | ||
| self._ensure_schema_unsupported(schema) | ||
|
|
||
| qt = table_name if isinstance(table_name, str) else table_name.name | ||
| raw_conn = connection.connection | ||
| try: | ||
| return raw_conn.describe(qt) | ||
| except ydb_dbapi.DatabaseError as e: | ||
| raise NoSuchTableError(qt) from e | ||
|
|
||
| @reflection.cache | ||
| def get_view_names(self, connection, schema=None, **kw: Any): | ||
| return [] | ||
| self._ensure_schema_unsupported(schema) | ||
|
|
||
| raw_conn = connection.connection | ||
| return raw_conn.get_view_names() | ||
|
|
||
| @reflection.cache | ||
| def get_view_definition(self, connection, view_name, schema=None, **kw: Any): | ||
| self._ensure_schema_unsupported(schema) | ||
|
|
||
| quoted_view_name = self.identifier_preparer.quote(view_name) | ||
| result = connection.execute(sa.text(f"SHOW CREATE VIEW {quoted_view_name}")) | ||
| row = result.fetchone() | ||
| if row is None: | ||
| return None | ||
| return row._mapping.get("CreateQuery") or row[0] | ||
|
|
||
| @reflection.cache | ||
| def get_columns(self, connection, table_name, schema=None, **kw): | ||
| table = self._describe_table(connection, table_name, schema) | ||
| as_compatible = [] | ||
| for column in table.columns: | ||
| col_type, nullable = _get_column_info(column.type) | ||
| as_compatible.append( | ||
| { | ||
| "name": column.name, | ||
| "type": col_type, | ||
| "nullable": nullable, | ||
| "default": None, | ||
| } | ||
| ) | ||
| as_compatible.append(_format_reflected_column(column.name, col_type, nullable)) | ||
|
|
||
| if not as_compatible: | ||
| quoted_table_name = self.identifier_preparer.quote(table_name) | ||
| result = connection.execute(sa.text(f"SELECT * FROM {quoted_table_name} LIMIT 0")) | ||
| for column in result.cursor.description or []: | ||
| col_type, nullable = _get_column_info_from_dbapi_description(column[1]) | ||
| as_compatible.append(_format_reflected_column(column[0], col_type, nullable)) | ||
|
|
||
| return as_compatible | ||
|
|
||
| @reflection.cache | ||
| def get_table_names(self, connection, schema=None, **kw): | ||
| if schema: | ||
| raise ydb_dbapi.NotSupportedError("unsupported on non empty schema") | ||
| self._ensure_schema_unsupported(schema) | ||
|
|
||
| raw_conn = connection.connection | ||
| return raw_conn.get_table_names() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (non-blocking): Move Try and Except part with creation of the view to a yield fixture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed