diff --git a/mindsdb_sql/parser/dialects/mindsdb/__init__.py b/mindsdb_sql/parser/dialects/mindsdb/__init__.py index 4ab0a9dd..d7e5d4ba 100644 --- a/mindsdb_sql/parser/dialects/mindsdb/__init__.py +++ b/mindsdb_sql/parser/dialects/mindsdb/__init__.py @@ -16,6 +16,7 @@ from .chatbot import CreateChatBot, UpdateChatBot, DropChatBot from .trigger import CreateTrigger, DropTrigger from .knowledge_base import CreateKnowledgeBase, DropKnowledgeBase +from .skills import CreateSkill, DropSkill, UpdateSkill # remove it in next release CreateDatasource = CreateDatabase diff --git a/mindsdb_sql/parser/dialects/mindsdb/lexer.py b/mindsdb_sql/parser/dialects/mindsdb/lexer.py index 19c200ee..74780f3b 100644 --- a/mindsdb_sql/parser/dialects/mindsdb/lexer.py +++ b/mindsdb_sql/parser/dialects/mindsdb/lexer.py @@ -31,6 +31,7 @@ class MindsDBLexer(Lexer): ENGINE, TRAIN, PREDICT, PARAMETERS, JOB, CHATBOT, EVERY,PROJECT, ANOMALY, DETECTION, KNOWLEDGE_BASE, KNOWLEDGE_BASES, + SKILL, # SHOW/DDL Keywords @@ -119,6 +120,7 @@ class MindsDBLexer(Lexer): KNOWLEDGE_BASE = r'\bKNOWLEDGE[_|\s]BASE\b' KNOWLEDGE_BASES = r'\bKNOWLEDGE[_|\s]BASES\b' + SKILL = r'\bSKILL\b' # Misc SET = r'\bSET\b' diff --git a/mindsdb_sql/parser/dialects/mindsdb/parser.py b/mindsdb_sql/parser/dialects/mindsdb/parser.py index bd9799dd..99530df6 100644 --- a/mindsdb_sql/parser/dialects/mindsdb/parser.py +++ b/mindsdb_sql/parser/dialects/mindsdb/parser.py @@ -17,6 +17,7 @@ from mindsdb_sql.parser.dialects.mindsdb.evaluate import Evaluate from mindsdb_sql.parser.dialects.mindsdb.create_file import CreateFile from mindsdb_sql.parser.dialects.mindsdb.knowledge_base import CreateKnowledgeBase, DropKnowledgeBase +from mindsdb_sql.parser.dialects.mindsdb.skills import CreateSkill, DropSkill, UpdateSkill from mindsdb_sql.exceptions import ParsingException from mindsdb_sql.parser.dialects.mindsdb.lexer import MindsDBLexer from mindsdb_sql.parser.dialects.mindsdb.retrain_predictor import RetrainPredictor @@ -85,6 +86,9 @@ class MindsDBParser(Parser): 'drop_trigger', 'create_kb', 'drop_kb', + 'create_skill', + 'drop_skill', + 'update_skill' ) def query(self, p): return p[0] @@ -130,6 +134,26 @@ def create_kb(self, p): def drop_kb(self, p): return DropKnowledgeBase(name=p.identifier, if_exists=p.if_exists_or_empty) + # -- Skills -- + @_('CREATE SKILL if_not_exists_or_empty identifier USING kw_parameter_list') + def create_skill(self, p): + params = p.kw_parameter_list + + return CreateSkill( + name=p.identifier, + type=params.pop('type'), + params=params, + if_not_exists=p.if_not_exists_or_empty + ) + + @_('DROP SKILL if_exists_or_empty identifier') + def drop_skill(self, p): + return DropSkill(name=p.identifier, if_exists=p.if_exists_or_empty) + + @_('UPDATE SKILL identifier SET kw_parameter_list') + def update_skill(self, p): + return UpdateSkill(name=p.identifier, updated_params=p.kw_parameter_list) + # -- ChatBot -- @_('CREATE CHATBOT identifier USING kw_parameter_list') def create_chat_bot(self, p): diff --git a/mindsdb_sql/parser/dialects/mindsdb/skills.py b/mindsdb_sql/parser/dialects/mindsdb/skills.py new file mode 100644 index 00000000..2a03d2bd --- /dev/null +++ b/mindsdb_sql/parser/dialects/mindsdb/skills.py @@ -0,0 +1,94 @@ +from mindsdb_sql.parser.ast.base import ASTNode +from mindsdb_sql.parser.utils import indent + + +class CreateSkill(ASTNode): + """ + Node for creating a new skill + """ + + def __init__(self, name, type, params, if_not_exists=False, *args, **kwargs): + """ + Parameters: + name (Identifier): name of the skill to create + type (str): type of the skill to create + params (dict): USING parameters to create the skill with + if_not_exists (bool): if True, do not raise an error if the skill exists + """ + super().__init__(*args, **kwargs) + self.name = name + self.type = type + self.params = params + self.if_not_exists = if_not_exists + + def to_tree(self, level=0, *args, **kwargs): + ind = indent(level) + out_str = f'{ind}CreateSkill(' \ + f'if_not_exists={self.if_not_exists}' \ + f'name={self.name.to_string()}, ' \ + f'type={self.type}, ' \ + f'params={self.params})' + return out_str + + def get_string(self, *args, **kwargs): + using_ar = [f'type={repr(self.type)}'] + using_ar += [f'{k}={repr(v)}' for k, v in self.params.items()] + using_str = ', '.join(using_ar) + + out_str = f'CREATE SKILL {"IF NOT EXISTS " if self.if_not_exists else ""}{self.name.to_string()} USING {using_str}' + return out_str + + +class UpdateSkill(ASTNode): + """ + Node for updating a skill + """ + + def __init__(self, name, updated_params, *args, **kwargs): + """ + Parameters: + name (Identifier): name of the skill to update + updated_params (dict): new SET parameters of the skill to update + """ + super().__init__(*args, **kwargs) + self.name = name + self.params = updated_params + + def to_tree(self, level=0, *args, **kwargs): + ind = indent(level) + out_str = f'{ind}UpdateSkill(' \ + f'name={self.name.to_string()}, ' \ + f'updated_params={self.params})' + return out_str + + def get_string(self, *args, **kwargs): + set_ar = [f'{k}={repr(v)}' for k, v in self.params.items()] + set_str = ', '.join(set_ar) + + out_str = f'UPDATE SKILL {self.name.to_string()} SET {set_str}' + return out_str + + +class DropSkill(ASTNode): + """ + Node for dropping a skill + """ + + def __init__(self, name, if_exists=False, *args, **kwargs): + """ + Parameters: + name (Identifier): name of the skill to drop + if_exists (bool): if True, do not raise an error if the skill does not exist + """ + super().__init__(*args, **kwargs) + self.name = name + self.if_exists = if_exists + + def to_tree(self, level=0, *args, **kwargs): + ind = indent(level) + out_str = f'{ind}DropSkill(if_exists={self.if_exists}, name={self.name.to_string()})' + return out_str + + def get_string(self, *args, **kwargs): + out_str = f'DROP SKILL {"IF EXISTS " if self.if_exists else ""}{str(self.name.to_string())}' + return out_str diff --git a/tests/test_parser/test_mindsdb/test_skills.py b/tests/test_parser/test_mindsdb/test_skills.py new file mode 100644 index 00000000..b79a4faf --- /dev/null +++ b/tests/test_parser/test_mindsdb/test_skills.py @@ -0,0 +1,62 @@ +from mindsdb_sql import parse_sql +from mindsdb_sql.parser.dialects.mindsdb import * +from mindsdb_sql.parser.ast import * + + +class TestSkills: + def test_create_skill(self): + sql = ''' + create skill if not exists my_skill + using + type = 'knowledge_base', + source ='my_knowledge_base' + ''' + ast = parse_sql(sql, dialect='mindsdb') + expected_ast = CreateSkill( + name=Identifier('my_skill'), + type='knowledge_base', + params={'source': 'my_knowledge_base'}, + if_not_exists=True + ) + assert str(ast) == str(expected_ast) + assert ast.to_tree() == expected_ast.to_tree() + + # Parse again after rendering to catch problems with rendering. + ast = parse_sql(str(ast), dialect='mindsdb') + assert str(ast) == str(expected_ast) + + def test_update_skill(self): + sql = ''' + update skill my_skill + set + source = 'new_source' + ''' + ast = parse_sql(sql, dialect='mindsdb') + expected_params = { + 'source': 'new_source' + } + expected_ast = UpdateSkill( + name=Identifier('my_skill'), + updated_params=expected_params + ) + assert str(ast) == str(expected_ast) + assert ast.to_tree() == expected_ast.to_tree() + + # Parse again after rendering to catch problems with rendering. + ast = parse_sql(str(ast), dialect='mindsdb') + assert str(ast) == str(expected_ast) + + def test_drop_skill(self): + sql = ''' + drop skill if exists my_skill + ''' + ast = parse_sql(sql, dialect='mindsdb') + expected_ast = DropSkill( + name=Identifier('my_skill'), if_exists=True + ) + assert str(ast) == str(expected_ast) + assert ast.to_tree() == expected_ast.to_tree() + + # Parse again after rendering to catch problems with rendering. + ast = parse_sql(str(ast), dialect='mindsdb') + assert str(ast) == str(expected_ast)