-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added language_container_activation module
- Loading branch information
Showing
2 changed files
with
128 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from typing import Dict | ||
import re | ||
|
||
from exasol.secret_store import Secrets | ||
|
||
# All secret store entries with language container activation commands | ||
# will have this common prefix in their keys. | ||
ACTIVATION_KEY_PREFIX = "language_container_activation_" | ||
|
||
|
||
def get_activation_sql(conf: Secrets) -> str: | ||
""" | ||
Merges multiple language container activation commands found in the secret | ||
store and returns a unified command. The resulting activation command will | ||
include the union of script languages found in different commands. | ||
For details on how an activation command may look like please refer to | ||
https://docs.exasol.com/db/latest/database_concepts/udf_scripts/adding_new_packages_script_languages.htm | ||
The secret store entries containing language activation commands should | ||
have keys with a common prefix. This prefix is defined in ACTIVATION_KEY_PREFIX. | ||
The language container activation commands are expected to have overlapping | ||
sets of script languages. The urls of script languages with the same alias | ||
found in multiple activation commands should be identical, otherwise a | ||
RuntimeException will be raised. | ||
""" | ||
|
||
merged_langs: Dict[str, str] = {} | ||
pattern = re.compile( | ||
r"\A\s*ALTER\s+SESSION\s+SET\s+SCRIPT_LANGUAGES" r"\s*=\s*'(.+?)'\s*;?\s*\Z", | ||
re.IGNORECASE, | ||
) | ||
# Iterate over all entries that look like language container activation commands. | ||
for key, value in conf.items(): | ||
if key.startswith(ACTIVATION_KEY_PREFIX): | ||
# Extract language definitions from the activation command | ||
match = pattern.match(value) | ||
if match is not None: | ||
# Iterate over script languages | ||
all_languages = match.group(1) | ||
for lang_definition in all_languages.split(): | ||
alias, lang_url = lang_definition.split("=", maxsplit=1) | ||
# If this language alias has been encountered before, the url | ||
# must be identical, otherwise the merge fails. | ||
if alias in merged_langs: | ||
if merged_langs[alias].casefold() != lang_url.casefold(): | ||
error = ( | ||
"Unable to merge multiple language container activation commands. " | ||
f"Found incompatible definitions for the language alias {alias}." | ||
) | ||
raise RuntimeError(error) | ||
else: | ||
merged_langs[alias] = lang_url | ||
|
||
# Build and return a new SQL command for merged language container activation. | ||
merged_langs_str = " ".join(f"{key}={value}" for key, value in merged_langs.items()) | ||
return f"ALTER SESSION SET SCRIPT_LANGUAGES='{merged_langs_str}';" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import re | ||
|
||
import pytest | ||
|
||
from exasol.language_container_activation import ( | ||
ACTIVATION_KEY_PREFIX, | ||
get_activation_sql, | ||
) | ||
|
||
|
||
def test_get_activation_sql(secrets): | ||
secrets.save( | ||
ACTIVATION_KEY_PREFIX + "_lang1", | ||
"ALTER SESSION SET SCRIPT_LANGUAGES='" | ||
"R=builtin_r JAVA=builtin_java PYTHON3=builtin_python3 " | ||
"FIRST_LANG=localzmq+protobuf:///bfs_path1?" | ||
"lang=python#/buckets/bfs_path1/exaudf/exaudfclient_py3';", | ||
) | ||
|
||
secrets.save("some_other_key", "some_other_value") | ||
|
||
secrets.save( | ||
ACTIVATION_KEY_PREFIX + "_lang2", | ||
"ALTER SESSION SET SCRIPT_LANGUAGES='" | ||
"R=builtin_r JAVA=builtin_java PYTHON3=builtin_python3 " | ||
"SECOND_LANG=localzmq+protobuf:///bfs_path2?" | ||
"lang=python#/buckets/bfs_path2/exaudf/exaudfclient_py3';", | ||
) | ||
|
||
sql = get_activation_sql(secrets) | ||
match = re.match( | ||
r"\A\s*ALTER\s+SESSION\s+SET\s+SCRIPT_LANGUAGES\s*=\s*'(.+?)'\s*;?\s*\Z", | ||
sql, | ||
re.IGNORECASE, | ||
) | ||
assert match is not None | ||
lang_defs = set(match.group(1).split()) | ||
expected_lang_defs = { | ||
"R=builtin_r", | ||
"JAVA=builtin_java", | ||
"PYTHON3=builtin_python3", | ||
"FIRST_LANG=localzmq+protobuf:///bfs_path1?" | ||
"lang=python#/buckets/bfs_path1/exaudf/exaudfclient_py3", | ||
"SECOND_LANG=localzmq+protobuf:///bfs_path2?" | ||
"lang=python#/buckets/bfs_path2/exaudf/exaudfclient_py3", | ||
} | ||
assert lang_defs == expected_lang_defs | ||
|
||
|
||
def test_get_activation_sql_failure(secrets): | ||
secrets.save( | ||
ACTIVATION_KEY_PREFIX + "_lang1", | ||
"ALTER SESSION SET SCRIPT_LANGUAGES='" | ||
"R=builtin_r JAVA=builtin_java PYTHON3=builtin_python3 " | ||
"LANG_ABC=localzmq+protobuf:///bfs_path1?" | ||
"lang=python#/buckets/bfs_path1/exaudf/exaudfclient_py3';", | ||
) | ||
|
||
secrets.save("some_other_key", "some_other_value") | ||
|
||
secrets.save( | ||
ACTIVATION_KEY_PREFIX + "_lang2", | ||
"ALTER SESSION SET SCRIPT_LANGUAGES='" | ||
"R=builtin_r JAVA=builtin_java PYTHON3=builtin_python3 " | ||
"LANG_ABC=localzmq+protobuf:///bfs_path2?" | ||
"lang=python#/buckets/bfs_path2/exaudf/exaudfclient_py3';", | ||
) | ||
|
||
with pytest.raises(RuntimeError): | ||
get_activation_sql(secrets) |