Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MindsDB SQL Support for CREATE, UPDATE, and DROP Skill #317

Merged
merged 2 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mindsdb_sql/parser/dialects/mindsdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions mindsdb_sql/parser/dialects/mindsdb/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'
Expand Down
24 changes: 24 additions & 0 deletions mindsdb_sql/parser/dialects/mindsdb/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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):
Expand Down
94 changes: 94 additions & 0 deletions mindsdb_sql/parser/dialects/mindsdb/skills.py
Original file line number Diff line number Diff line change
@@ -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
62 changes: 62 additions & 0 deletions tests/test_parser/test_mindsdb/test_skills.py
Original file line number Diff line number Diff line change
@@ -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()
tmichaeldb marked this conversation as resolved.
Show resolved Hide resolved

# 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)
Loading