Skip to content

Commit

Permalink
Added language_container_activation module
Browse files Browse the repository at this point in the history
  • Loading branch information
ahsimb committed Jan 8, 2024
1 parent fe612f7 commit 5547ea7
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
58 changes: 58 additions & 0 deletions exasol/language_container_activation.py
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}';"
70 changes: 70 additions & 0 deletions test/unit/test_language_container_activation.py
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)

0 comments on commit 5547ea7

Please sign in to comment.