diff --git a/evadb/executor/plan_executor.py b/evadb/executor/plan_executor.py index 8c7a8b47b..9d0bbacd0 100644 --- a/evadb/executor/plan_executor.py +++ b/evadb/executor/plan_executor.py @@ -42,6 +42,7 @@ from evadb.executor.rename_executor import RenameExecutor from evadb.executor.sample_executor import SampleExecutor from evadb.executor.seq_scan_executor import SequentialScanExecutor +from evadb.executor.set_executor import SetExecutor from evadb.executor.show_info_executor import ShowInfoExecutor from evadb.executor.storage_executor import StorageExecutor from evadb.executor.union_executor import UnionExecutor @@ -49,6 +50,7 @@ from evadb.executor.vector_index_scan_executor import VectorIndexScanExecutor from evadb.models.storage.batch import Batch from evadb.parser.create_statement import CreateDatabaseStatement +from evadb.parser.set_statement import SetStatement from evadb.parser.statement import AbstractStatement from evadb.parser.use_statement import UseStatement from evadb.plan_nodes.abstract_plan import AbstractPlan @@ -90,6 +92,8 @@ def _build_execution_tree( return CreateDatabaseExecutor(db=self._db, node=plan) elif isinstance(plan, UseStatement): return UseExecutor(db=self._db, node=plan) + elif isinstance(plan, SetStatement): + return SetExecutor(db=self._db, node=plan) # Get plan node type plan_opr_type = plan.opr_type diff --git a/evadb/executor/set_executor.py b/evadb/executor/set_executor.py new file mode 100644 index 000000000..4adfe6c05 --- /dev/null +++ b/evadb/executor/set_executor.py @@ -0,0 +1,43 @@ +# coding=utf-8 +# Copyright 2018-2023 EvaDB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from evadb.database import EvaDBDatabase +from evadb.executor.abstract_executor import AbstractExecutor +from evadb.parser.set_statement import SetStatement + + +class SetExecutor(AbstractExecutor): + def __init__(self, db: EvaDBDatabase, node: SetStatement): + super().__init__(db, node) + + def exec(self, *args, **kwargs): + # Get method implementation from the config.update_value + """ + NOTE :- Currently adding adding all configs in 'default' category. + The idea is to deprecate category to maintain the same format for + the query as DuckDB and Postgres + + Ref :- + https://www.postgresql.org/docs/7.0/sql-set.htm + https://duckdb.org/docs/sql/configuration.html + + This design change for configuation manager will be taken care of + as a separate PR for the issue #1140, where all instances of config use + will be replaced + """ + self._config.update_value( + category="default", + key=self.node.config_name, + value=self.node.config_value.value, + ) diff --git a/evadb/parser/evadb.lark b/evadb/parser/evadb.lark index d7d5b7404..b8cc2fc56 100644 --- a/evadb/parser/evadb.lark +++ b/evadb/parser/evadb.lark @@ -8,7 +8,7 @@ ddl_statement: create_database | create_table | create_index | create_function | drop_database | drop_table | drop_function | drop_index | rename_table dml_statement: select_statement | insert_statement | update_statement - | delete_statement | load_statement + | delete_statement | load_statement | set_statement utility_statement: describe_statement | show_statement | help_statement | explain_statement @@ -79,6 +79,12 @@ drop_table: DROP TABLE if_exists? uid drop_function: DROP FUNCTION if_exists? uid +// SET statements (configuration) +set_statement: SET config_name (EQUAL_SYMBOL | TO) config_value + +config_name: uid + +config_value: (string_literal | decimal_literal | boolean_literal | real_literal) // Data Manipulation Language diff --git a/evadb/parser/lark_visitor/__init__.py b/evadb/parser/lark_visitor/__init__.py index 2aa7e7837..9ed0a1b6f 100644 --- a/evadb/parser/lark_visitor/__init__.py +++ b/evadb/parser/lark_visitor/__init__.py @@ -31,6 +31,7 @@ from evadb.parser.lark_visitor._load_statement import Load from evadb.parser.lark_visitor._rename_statement import RenameTable from evadb.parser.lark_visitor._select_statement import Select +from evadb.parser.lark_visitor._set_statement import Set from evadb.parser.lark_visitor._show_statements import Show from evadb.parser.lark_visitor._table_sources import TableSources from evadb.parser.lark_visitor._use_statement import Use @@ -77,6 +78,7 @@ class LarkInterpreter( Explain, Delete, Use, + Set, ): def __init__(self, query): super().__init__() diff --git a/evadb/parser/lark_visitor/_set_statement.py b/evadb/parser/lark_visitor/_set_statement.py new file mode 100644 index 000000000..9fc8ffe48 --- /dev/null +++ b/evadb/parser/lark_visitor/_set_statement.py @@ -0,0 +1,35 @@ +# coding=utf-8 +# Copyright 2018-2023 EvaDB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from lark.tree import Tree + +from evadb.parser.set_statement import SetStatement + + +################################################################## +# DELETE STATEMENTS +################################################################## +class Set: + def set_statement(self, tree): + config_name = None + config_value = None + for child in tree.children: + if isinstance(child, Tree): + if child.data == "config_name": + config_name = self.visit(child) + elif child.data == "config_value": + config_value = self.visit(child) + + set_stmt = SetStatement(config_name, config_value) + return set_stmt diff --git a/evadb/parser/set_statement.py b/evadb/parser/set_statement.py new file mode 100644 index 000000000..eab848c27 --- /dev/null +++ b/evadb/parser/set_statement.py @@ -0,0 +1,49 @@ +# coding=utf-8 +# Copyright 2018-2023 EvaDB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations + +from typing import Any + +from evadb.parser.statement import AbstractStatement +from evadb.parser.types import StatementType + + +class SetStatement(AbstractStatement): + def __init__(self, config_name: str, config_value: Any): + super().__init__(StatementType.SET) + self._config_name = config_name + self._config_value = config_value + + @property + def config_name(self): + return self._config_name + + @property + def config_value(self): + return self._config_value + + def __str__(self): + return f"SET {str(self.config_name)} = {str(self.config_value)}" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SetStatement): + return False + return ( + self.config_name == other.config_name + and self.config_value == other.config_value + ) + + def __hash__(self) -> int: + return hash((super().__hash__(), self.config_name, self.config_value)) diff --git a/evadb/parser/types.py b/evadb/parser/types.py index a57c938db..14011f174 100644 --- a/evadb/parser/types.py +++ b/evadb/parser/types.py @@ -41,6 +41,7 @@ class StatementType(EvaDBEnum): CREATE_INDEX # noqa: F821 CREATE_DATABASE # noqa: F821 USE # noqa: F821 + SET # noqa: F821 # add other types diff --git a/evadb/parser/utils.py b/evadb/parser/utils.py index 3ad9b032f..28f13e0a9 100644 --- a/evadb/parser/utils.py +++ b/evadb/parser/utils.py @@ -21,13 +21,18 @@ from evadb.parser.parser import Parser from evadb.parser.rename_statement import RenameTableStatement from evadb.parser.select_statement import SelectStatement +from evadb.parser.set_statement import SetStatement from evadb.parser.show_statement import ShowStatement from evadb.parser.types import ObjectType from evadb.parser.use_statement import UseStatement # List of statements for which we omit binder and optimizer and pass the statement # directly to the executor. -SKIP_BINDER_AND_OPTIMIZER_STATEMENTS = (CreateDatabaseStatement, UseStatement) +SKIP_BINDER_AND_OPTIMIZER_STATEMENTS = ( + CreateDatabaseStatement, + UseStatement, + SetStatement, +) def parse_expression(expr: str): diff --git a/test/integration_tests/short/test_set_executor.py b/test/integration_tests/short/test_set_executor.py new file mode 100644 index 000000000..d73268143 --- /dev/null +++ b/test/integration_tests/short/test_set_executor.py @@ -0,0 +1,41 @@ +# coding=utf-8 +# Copyright 2018-2023 EvaDB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from test.util import get_evadb_for_testing + +import pytest + +from evadb.server.command_handler import execute_query_fetch_all + +NUM_FRAMES = 10 + + +@pytest.mark.notparallel +class SetExecutorTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.evadb = get_evadb_for_testing() + cls.evadb.catalog().reset() + + @classmethod + def tearDownClass(cls): + pass + + # integration test + def test_set_execution(self): + execute_query_fetch_all(self.evadb, "SET OPENAIKEY = 'ABCD';") + current_config_value = self.evadb.config.get_value("default", "OPENAIKEY") + + self.assertEqual("ABCD", current_config_value) diff --git a/test/unit_tests/optimizer/test_statement_to_opr_converter.py b/test/unit_tests/optimizer/test_statement_to_opr_converter.py index ef93fe3ec..d60284b13 100644 --- a/test/unit_tests/optimizer/test_statement_to_opr_converter.py +++ b/test/unit_tests/optimizer/test_statement_to_opr_converter.py @@ -317,6 +317,7 @@ def test_check_plan_equality(self): extract_object_plan = LogicalExtractObject( MagicMock(), MagicMock(), MagicMock(), MagicMock() ) + create_plan.append_child(create_function_plan) plans.append(dummy_plan) diff --git a/test/unit_tests/parser/test_parser.py b/test/unit_tests/parser/test_parser.py index 0c134d8b6..f9005b0a6 100644 --- a/test/unit_tests/parser/test_parser.py +++ b/test/unit_tests/parser/test_parser.py @@ -36,6 +36,7 @@ from evadb.parser.parser import Parser from evadb.parser.rename_statement import RenameTableStatement from evadb.parser.select_statement import SelectStatement +from evadb.parser.set_statement import SetStatement from evadb.parser.statement import AbstractStatement, StatementType from evadb.parser.table_ref import JoinNode, TableInfo, TableRef, TableValuedExpression from evadb.parser.types import ( @@ -693,6 +694,39 @@ def test_delete_statement(self): self.assertEqual(delete_stmt, expected_stmt) + def test_set_statement(self): + parser = Parser() + set_statement = """SET OPENAIKEY = 'ABCD'""" + evadb_statement_list = parser.parse(set_statement) + + self.assertIsInstance(evadb_statement_list, list) + self.assertEqual(len(evadb_statement_list), 1) + self.assertEqual(evadb_statement_list[0].stmt_type, StatementType.SET) + + set_stmt = evadb_statement_list[0] + + expected_stmt = SetStatement( + "OPENAIKEY", ConstantValueExpression("ABCD", ColumnType.TEXT) + ) + + self.assertEqual(set_stmt, expected_stmt) + + # TESTING 'TO' IN PLACE OF '=' + set_statement = """SET OPENAIKEY TO 'ABCD'""" + evadb_statement_list = parser.parse(set_statement) + + self.assertIsInstance(evadb_statement_list, list) + self.assertEqual(len(evadb_statement_list), 1) + self.assertEqual(evadb_statement_list[0].stmt_type, StatementType.SET) + + set_stmt = evadb_statement_list[0] + + expected_stmt = SetStatement( + "OPENAIKEY", ConstantValueExpression("ABCD", ColumnType.TEXT) + ) + + self.assertEqual(set_stmt, expected_stmt) + def test_create_predict_function_statement(self): parser = Parser() create_func_query = """