Repository: modin-project/modin

Issue:
Make OmniSci engine configurable
Currently, there is no way to pass custom launch parameters to the OmniSci backend. All of the Modin engines (Ray and Dask so far) have an option of [explicit initialization](https://modin.readthedocs.io/en/latest/using_modin.html#advanced-usage-experimental), this option is not supported for OmniSci engine though.

Ideally, we want to implement something like this:
```python
import modin.pandas as pd
from dbe import PyDbEngine

# Explicit engine initialization with custom parameters
server = PyDbEngine(enable_watchdog=1)

# Modin is supposed to use the existing instance of the DbEngine
print(pd.DataFrame({"a":[1, 2, 3]}).sum())
```
This approach would work fine for now, because there is always a single one partition, containing a single one OmniSci server, but if we want to scale (for example running on a cluster and so different machines) we *may* need more than one instance of DBEngine, and so the presented approach won't work.

@ienkovich @Garra1980 
so, I have a question, does the future "scaling" suppose to have multiple OmniSci servers, which will cause us to initialize all of them with the custom parameters, or the whole scaling will be handled inside a single OmniSci server and so we can use the above approach?


Discussion:
We want to have distributed execution with OmniSci eventually. We still don't have any particular plans on how we will do it but we will definitely have multiple OmniSci engine instances.



Resolving patch:
diff --git a/modin/config/envvars.py b/modin/config/envvars.py
--- a/modin/config/envvars.py
+++ b/modin/config/envvars.py
@@ -340,6 +340,43 @@ class PersistentPickle(EnvironmentVariable, type=bool):
     default = False
 
 
+class OmnisciLaunchParameters(EnvironmentVariable, type=dict):
+    """
+    Additional command line options for the OmniSci engine.
+
+    Please visit OmniSci documentation for the description of available parameters:
+    https://docs.omnisci.com/installation-and-configuration/config-parameters#configuration-parameters-for-omniscidb
+    """
+
+    varname = "MODIN_OMNISCI_LAUNCH_PARAMETERS"
+    default = {
+        "enable_union": 1,
+        "enable_columnar_output": 1,
+        "enable_lazy_fetch": 0,
+        "null_div_by_zero": 1,
+        "enable_watchdog": 0,
+    }
+
+    @classmethod
+    def get(self):
+        """
+        Get the resulted command-line options.
+
+        Decode and merge specified command-line options with the default one.
+
+        Returns
+        -------
+        dict
+            Decoded and verified config value.
+        """
+        custom_parameters = super().get()
+        result = self.default.copy()
+        result.update(
+            {key.replace("-", "_"): value for key, value in custom_parameters.items()}
+        )
+        return result
+
+
 def _check_vars():
     """
     Check validity of environment variables.
diff --git a/modin/config/pubsub.py b/modin/config/pubsub.py
--- a/modin/config/pubsub.py
+++ b/modin/config/pubsub.py
@@ -75,6 +75,29 @@ class ExactStr(str):
         or (isinstance(value, str) and value.strip().isdigit()),
         help="an integer value",
     ),
+    dict: TypeDescriptor(
+        decode=lambda value: {
+            key: int(val) if val.isdigit() else val
+            for key_value in value.split(",")
+            for key, val in [[v.strip() for v in key_value.split("=", maxsplit=1)]]
+        },
+        normalize=lambda value: value
+        if isinstance(value, dict)
+        else {
+            key: int(val) if val.isdigit() else val
+            for key_value in value.split(",")
+            for key, val in [[v.strip() for v in key_value.split("=", maxsplit=1)]]
+        },
+        verify=lambda value: isinstance(value, dict)
+        or (
+            isinstance(value, str)
+            and all(
+                key_value.find("=") not in (-1, len(key_value) - 1)
+                for key_value in value.split(",")
+            )
+        ),
+        help="a sequence of KEY=VALUE values separated by comma (Example: 'KEY1=VALUE1,KEY2=VALUE2,KEY3=VALUE3')",
+    ),
 }
 
 # special marker to distinguish unset value from None value
diff --git a/modin/experimental/engines/omnisci_on_ray/frame/omnisci_worker.py b/modin/experimental/engines/omnisci_on_ray/frame/omnisci_worker.py
--- a/modin/experimental/engines/omnisci_on_ray/frame/omnisci_worker.py
+++ b/modin/experimental/engines/omnisci_on_ray/frame/omnisci_worker.py
@@ -30,7 +30,7 @@
 if sys.platform == "linux":
     sys.setdlopenflags(prev)
 
-from modin.config import OmnisciFragmentSize
+from modin.config import OmnisciFragmentSize, OmnisciLaunchParameters
 
 
 class OmnisciServer:
@@ -39,13 +39,7 @@ class OmnisciServer:
     @classmethod
     def start_server(cls):
         if cls._server is None:
-            cls._server = PyDbEngine(
-                enable_union=1,
-                enable_columnar_output=1,
-                enable_lazy_fetch=0,
-                null_div_by_zero=1,
-                enable_watchdog=0,
-            )
+            cls._server = PyDbEngine(**OmnisciLaunchParameters.get())
 
     @classmethod
     def stop_server(cls):


Test patch:
diff --git a/modin/config/test/test_parameter.py b/modin/config/test/test_parameter.py
--- a/modin/config/test/test_parameter.py
+++ b/modin/config/test/test_parameter.py
@@ -72,6 +72,18 @@ def callback(p: Parameter):
     [
         (make_prefilled(bool, "false"), {"1": True, False: False}, ["nope", 2]),
         (make_prefilled(int, "10"), {" 15\t": 15, 25: 25}, ["-10", 1.0, "foo"]),
+        (
+            make_prefilled(dict, "key = value"),
+            {
+                "KEY1 = VALUE1, KEY2=VALUE2=VALUE3,KEY3=0": {
+                    "KEY1": "VALUE1",
+                    "KEY2": "VALUE2=VALUE3",
+                    "KEY3": 0,
+                },
+                "KEY=1": {"KEY": 1},
+            },
+            ["key1=some,string", "key1=value1,key2=", "random string"],
+        ),
     ],
 )
 def test_validation(parameter, good, bad):
@@ -83,7 +95,7 @@ def test_validation(parameter, good, bad):
             parameter.put(inval)
 
 
-@pytest.mark.parametrize("vartype", [bool, int])
+@pytest.mark.parametrize("vartype", [bool, int, dict])
 def test_init_validation(vartype):
     parameter = make_prefilled(vartype, "bad value")
     with pytest.raises(ValueError):