Source code for pyexasol.meta
from . import constant
from .exceptions import ExaRuntimeError
[docs]
class ExaMetaData:
"""
This class implements lock-free metadata requests using ``/*snapshot execution*/``
SQL hint, as described further in https://docs.exasol.com/db/latest/database_concepts/snapshot_mode.htm.
Note:
If you still get locks, please make sure to update Exasol server to the latest minor version.
Examples:
You may access these functions using `.meta` property of connection object.
>>> C = pyexasol.connect(...)
... print(C.meta.sql_columns('SELECT 1 AS id'))
"""
snapshot_execution_hint = "/*snapshot execution*/"
def __init__(self, connection):
self.connection = connection
self.sql_keywords = None
[docs]
def sql_columns(self, query, query_params=None):
"""
Get result set columns of SQL query without executing it
Args:
query:
SQL query text, possibly with placeholders.
query_params:
Values for placeholders.
Returns:
Columns of SQL query result without executing it.
Output format is similar to :meth:`pyexasol.ExaStatement.columns`.
"""
st = self.connection.cls_statement(
self.connection, query, query_params, prepare=True
)
columns = st.columns()
st.close()
return columns
[docs]
def schema_exists(self, schema_name):
"""
Check if schema exists.
Args:
schema_name:
Name of the schema to check.
Returns:
``True`` if the schema exists, otherwise ``False``.
"""
object_name = self.connection.format.default_format_ident_value(schema_name)
if self.connection.protocol_version() >= constant.PROTOCOL_V2:
st = self.execute_meta_nosql(
"getSchemas",
{
"schema": object_name,
},
)
else:
st = self.execute_snapshot(
"""
SELECT 1
FROM sys.exa_schemas
WHERE schema_name={object_name}
""",
{
"object_name": object_name,
},
)
return st.rowcount() > 0
[docs]
def table_exists(self, table_name):
"""
Check if table exists.
Args:
table_name:
Name of the table to check for.
If schema was not specified, ``current_schema`` is used.
Returns:
``True`` if the table exists, otherwise ``False``.
"""
if isinstance(table_name, tuple):
object_schema = self.connection.format.default_format_ident_value(
table_name[0]
)
object_name = self.connection.format.default_format_ident_value(
table_name[1]
)
else:
object_schema = self.connection.current_schema()
object_name = self.connection.format.default_format_ident_value(table_name)
if self.connection.protocol_version() >= constant.PROTOCOL_V2:
st = self.execute_meta_nosql(
"getTables",
{
"schema": object_schema,
"table": object_name,
"tableTypes": ["TABLE"],
},
)
else:
st = self.execute_snapshot(
"""
SELECT 1
FROM sys.exa_all_tables
WHERE table_schema={object_schema}
AND table_name={object_name}
""",
{
"object_schema": object_schema,
"object_name": object_name,
},
)
return st.rowcount() > 0
[docs]
def view_exists(self, view_name):
"""
Check if view exists.
Args:
view_name:
Name of the table to check for.
If schema was not specified, ``current_schema`` is used.
Returns:
``True`` if the view exists, otherwise ``False``.
"""
if isinstance(view_name, tuple):
object_schema = self.connection.format.default_format_ident_value(
view_name[0]
)
object_name = self.connection.format.default_format_ident_value(
view_name[1]
)
else:
object_schema = self.connection.current_schema()
object_name = self.connection.format.default_format_ident_value(view_name)
if self.connection.protocol_version() >= constant.PROTOCOL_V2:
st = self.execute_meta_nosql(
"getTables",
{
"schema": object_schema,
"table": object_name,
"tableTypes": ["VIEW"],
},
)
else:
st = self.execute_snapshot(
"""
SELECT 1
FROM sys.exa_all_views
WHERE view_schema={object_schema}
AND view_name={object_name}
""",
{
"object_schema": object_schema,
"object_name": object_name,
},
)
return st.rowcount() > 0
[docs]
def list_schemas(self, schema_name_pattern="%"):
"""
List Schemas.
Args:
schema_name_pattern:
Schema name or LIKE-pattern to filter on.
(default: ``'%'``)
Returns:
List of schemas from `EXA_SCHEMAS <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_schemas.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_schemas
WHERE schema_name LIKE {schema_name_pattern}
ORDER BY schema_name ASC
""",
{
"schema_name_pattern": schema_name_pattern,
},
)
return st.fetchall()
[docs]
def list_tables(self, table_schema_pattern="%", table_name_pattern="%"):
"""
List Tables.
Args:
table_schema_pattern:
Schema name or LIKE-pattern to filter on.
(default: ``'%'``)
table_name_pattern:
Table name or LIKE-pattern to filter on.
(default: ``'%'``)
Returns:
List of tables from `EXA_ALL_TABLES <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_tables.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_tables
WHERE table_schema LIKE {table_schema_pattern}
AND table_name LIKE {table_name_pattern}
ORDER BY table_schema ASC, table_name ASC
""",
{
"table_schema_pattern": table_schema_pattern,
"table_name_pattern": table_name_pattern,
},
)
return st.fetchall()
[docs]
def list_views(self, view_schema_pattern="%", view_name_pattern="%"):
"""
List Views.
Args:
view_schema_pattern:
Schema name or LIKE-pattern to filter on.
(default: ``'%'``)
view_name_pattern:
Table name or LIKE-pattern to filter on.
(default: ``'%'``)
Returns:
List of views from `EXA_ALL_VIEWS <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_views.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_views
WHERE view_schema LIKE {view_schema_pattern}
AND view_name LIKE {view_name_pattern}
ORDER BY view_schema ASC, view_name ASC
""",
{
"view_schema_pattern": view_schema_pattern,
"view_name_pattern": view_name_pattern,
},
)
return st.fetchall()
[docs]
def list_columns(
self,
column_schema_pattern="%",
column_table_pattern="%",
column_object_type_pattern="%",
column_name_pattern="%",
):
"""
List Columns.
Args:
column_schema_pattern:
Schema name or LIKE-pattern to filter on.
(default: ``'%'``)
column_table_pattern:
Table name or LIKE-pattern to filter on.
(default: ``'%'``)
column_object_type_pattern:
Object type or LIKE-pattern to filter on.
(default: ``'%'``)
column_name_pattern:
Column name or LIKE-pattern to filter on.
(default: ``'%'``)
Returns:
List of columns from `EXA_ALL_COLUMNS <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_columns.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_columns
WHERE column_schema LIKE {column_schema_pattern}
AND column_table LIKE {column_table_pattern}
AND column_object_type LIKE {column_object_type_pattern}
AND column_name LIKE {column_name_pattern}
""",
{
"column_schema_pattern": column_schema_pattern,
"column_table_pattern": column_table_pattern,
"column_object_type_pattern": column_object_type_pattern,
"column_name_pattern": column_name_pattern,
},
)
return st.fetchall()
[docs]
def list_objects(
self,
object_name_pattern="%",
object_type_pattern="%",
owner_pattern="%",
root_name_pattern="%",
):
"""
List Objects.
Args:
object_name_pattern:
Object name or LIKE-pattern to filter on.
(default: ``'%'``)
object_type_pattern:
Object type or LIKE-pattern to filter on.
(default: ``'%'``)
owner_pattern:
Owner name or LIKE-pattern to filter on.
(default: ``'%'``)
root_name_pattern:
Root name or LIKE-pattern to filter on.j
It normally refers to schema name.
(default: ``'%'``)
Returns:
List of objects from `EXA_ALL_OBJECTS <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_objects.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_objects
WHERE object_name LIKE {object_name_pattern}
AND object_type LIKE {object_type_pattern}
AND owner LIKE {owner_pattern}
AND root_name LIKE {root_name_pattern}
""",
{
"object_name_pattern": object_name_pattern,
"object_type_pattern": object_type_pattern,
"owner_pattern": owner_pattern,
"root_name_pattern": root_name_pattern,
},
)
return st.fetchall()
[docs]
def list_object_sizes(
self,
object_name_pattern="%",
object_type_pattern="%",
owner_pattern="%",
root_name_pattern="%",
):
"""
List Objects with their respective size.
Args:
object_name_pattern:
Object name or LIKE-pattern to filter on.
(default: ``'%'``)
object_type_pattern:
Object type or LIKE-pattern to filter on.
(default: ``'%'``)
owner_pattern:
Owner name or LIKE-pattern to filter on.
(default: ``'%'``)
root_name_pattern:
Root name or LIKE-pattern to filter on.j
It normally refers to schema name.
(default: ``'%'``)
Returns:
List of objects with sizes from `EXA_ALL_OBJECT_SIZES <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_object_sizes.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_object_sizes
WHERE object_name LIKE {object_name_pattern}
AND object_type LIKE {object_type_pattern}
AND owner LIKE {owner_pattern}
AND root_name LIKE {root_name_pattern}
""",
{
"object_name_pattern": object_name_pattern,
"object_type_pattern": object_type_pattern,
"owner_pattern": owner_pattern,
"root_name_pattern": root_name_pattern,
},
)
return st.fetchall()
[docs]
def list_indices(
self, index_schema_pattern="%", index_table_pattern="%", index_owner_pattern="%"
):
"""
List indicies.
Args:
index_schema_pattern:
Schema name or LIKE-pattern to filter on.
(default: ``'%'``)
index_table_pattern:
Table name or LIKE-pattern to filter on.
(default: ``'%'``)
index_owner_pattern:
Owner name or LIKE-pattern to filter on.
(default: ``'%'``)
Returns:
List of indices with sizes from `EXA_ALL_INDICES <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_all_indices.htm>`_ system view matching LIKE-pattern.
Note:
Patterns are case-sensitive. You may escape LIKE-patterns.
"""
st = self.execute_snapshot(
"""
SELECT *
FROM sys.exa_all_indices
WHERE index_schema LIKE {index_schema_pattern}
AND index_table LIKE {index_table_pattern}
AND index_owner LIKE {index_owner_pattern}
""",
{
"index_schema_pattern": index_schema_pattern,
"index_table_pattern": index_table_pattern,
"index_owner_pattern": index_owner_pattern,
},
)
return st.fetchall()
[docs]
def list_sql_keywords(self):
"""
Get reserved SQL keywords which cannot be used as identifiers without double-quote escaping
Returns:
List of SQL keywords from `EXA_SQL_KEYWORDS <https://docs.exasol.com/db/latest/sql_references/system_tables/metadata/exa_sql_keywords.htm>`_ system view.
Warning:
Never hardcode this list! It might change with next Exasol server version without warning
Note:
These keywords cannot be used as identifiers without double quotes.
"""
if not self.sql_keywords:
if self.connection.protocol_version() >= constant.PROTOCOL_V2:
st = self.execute_meta_nosql("getKeywords")
self.sql_keywords = [
r["KEYWORD"] for r in st.fetchall() if r["RESERVED"] is True
]
else:
st = self.execute_snapshot("""
SELECT keyword
FROM EXA_SQL_KEYWORDS
WHERE reserved IS TRUE
ORDER BY keyword
""")
self.sql_keywords = st.fetchcol()
return self.sql_keywords
[docs]
def execute_snapshot(self, query, query_params=None):
"""
Execute query in snapshot transaction mode using SQL hint
Args:
query:
SQL query text, possibly with placeholders.
query_params:
Values for placeholders.
Returns:
:class:`ExaStatement`
Warning:
Please do not try to query normal tables with this method. It will fail during creation of indices or statistics objects.
Note:
``fetch_dict=Tru`` is enforced to prevent users from relying on order of columns in system views
"""
options = {
"fetch_dict": True,
}
return self.connection.cls_statement(
self.connection,
f"{self.snapshot_execution_hint}{query}",
query_params,
**options,
)
[docs]
def execute_meta_nosql(self, meta_command, meta_params=None):
"""
Execute no SQL metadata command introduced in Exasol 7.0+
Args:
meta_command:
Metadata command.
meta_params:
Parameters for metadata command.
Returns:
:class:`ExaStatement`
Note:
This feature requires WebSocket protocol v2 or higher
List of available commands can be found `here <https://github.com/exasol/websocket-api/blob/master/docs/WebsocketAPIV2.md#metadata-related-commands>`_.
"""
if self.connection.protocol_version() < constant.PROTOCOL_V2:
raise ExaRuntimeError(
self.connection,
"Protocol version 2 is required to execute nosql metadata commands",
)
# Security check, prevents execution of dangerous commands if meta_command argument is dynamic
if meta_command[0:3] != "get":
raise ExaRuntimeError(
self.connection, "Meta command name should start with prefix 'get*'"
)
options = {
"fetch_dict": True,
}
return self.connection.cls_statement(
self.connection, meta_command, meta_params, meta_nosql=True, **options
)
def __repr__(self):
return f"<{self.__class__.__name__} session_id={self.connection.session_id()}>"