From 488562e207257418ea365105c8ff8e44a4184083 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Tue, 6 Jun 2023 10:53:20 +0800 Subject: [PATCH 01/17] typo --- README.md | 8 ++++---- sql_control/main.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c6e1efe..234caa8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -~~目前只需要改commands/DB.py就行了~~ +~~目前只需要改command/DB.py就行了~~ commands中的执行功能都差不多写完了,tokenize已完成,AST写了一点 @@ -201,11 +201,11 @@ WHERE level:AST_KEYWORDS. -#### 1.3 在AST基础上,实现对`sql_commands`的调用,完成语句 +#### 1.3 在AST基础上,实现对`sql_command`的调用,完成语句 -文件:`sql_commands/main.py`(示例用,之后可能会改) +文件:`sql_control/main.py`(示例用,之后可能会改) -- [x] 完成示例文件,演示如何结合AST与实现的`sql_commands`,通过FROM语句获得databse中的表 +- [x] 完成示例文件,演示如何结合AST与实现的`sql_command`,通过FROM语句获得databse中的表 - [ ] 完成AST与执行代码关于WHERE的结合 diff --git a/sql_control/main.py b/sql_control/main.py index 6fea041..e38ff7c 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -19,7 +19,7 @@ def ast_clear(self): def extract(self,text:str): """ - 获取不同clause(子句)的内容 + 从text中解析出AST,并且获取不同clause(子句)的内容 """ # 根据text,生成AST(这里AST只实现了SELECT查询) self.ast = AST(sql).content @@ -36,7 +36,7 @@ def execute(self,text:str): self.ast_clear() # 获取当前语句的AST self.extract(text) - # 示例用,演示怎么从AST解析后的树中获取table + # 示例用,演示怎么从AST的解析中获取table tables = self.get_tables() print(tables) From 1f101a2eab8df385e814a8f20efa31a271b8a5e6 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Tue, 6 Jun 2023 10:56:48 +0800 Subject: [PATCH 02/17] more info on main.py --- sql_control/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sql_control/main.py b/sql_control/main.py index e38ff7c..edddc23 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -42,6 +42,7 @@ def execute(self,text:str): def get_tables(self): #从FROM clause中获得content + #想要获得完整结构,建议于AST_Builder.py的代码最后,在print(show)语句那里打个断点,观察一下show的结构 table_names = self.clause["FROM"].content ret = [] for table_name_i in table_names: From 3d720c8246eef0382a0b07645be10c306520ee2f Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Tue, 6 Jun 2023 11:33:39 +0800 Subject: [PATCH 03/17] more hints on main.py --- sql_control/main.py | 16 +++++++++++++++- sql_parse/ast_def.py | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/sql_control/main.py b/sql_control/main.py index edddc23..f4a2da0 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -1,5 +1,6 @@ from sql_parse.AST_builder import AST from sql_command.DB import DB +import pandas as pd class blabla: def __init__(self): @@ -8,6 +9,10 @@ def __init__(self): """ self.db = DB() self.db.create("test",["索引","姓名"], [int,str]) + test_table = self.db.database['test']['tabledata'] + flag, test_table = self.db.insert(table=test_table,attributes=["索引"],values=[[23],[24]]) + flag, test_table = self.db.update(table=test_table,update_rows=None,attributes=["姓名"], values=[["张三"],["王五"]]) + self.db.database['test']['tabledata'] = test_table def ast_clear(self): """ @@ -36,8 +41,17 @@ def execute(self,text:str): self.ast_clear() # 获取当前语句的AST self.extract(text) + # 确定当前是什么类型的语句 + # 如果以SELECT开头,那么就是查询语句 + # 正确写法是:if "SELECT" == self.ast.content[0].value + # 下面的写法对于简单语句成立,但对于复杂语句例如 + # UPDATE (SELECT * FROM test) SET id = 1 不成立 + if "SELECT" in self.clause.keys(): + self.execute_query_statement() + + def execute_query_statement(self): # 示例用,演示怎么从AST的解析中获取table - tables = self.get_tables() + tables : list[pd.DataFrame] = self.get_tables() print(tables) def get_tables(self): diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index 647e2f5..db2d17c 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -23,7 +23,7 @@ def __init__(self): def deal(self, cls, value): """ - 对于一个非关键词的token,根据自己clause的类型,将其加入到自己的内容中 + 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 """ # 当前columns的类型决定其会记录columns if self.value in ["SELECT"]: From 16917b71609941286a4022e87142d719762a875a Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 02:54:39 +0800 Subject: [PATCH 04/17] implement BASIC CREATE --- README.md | 42 ++++++++++++++++-- sql_parse/AST_builder.py | 96 +++++++++++++++++++++++++++++++++++++--- sql_parse/ast_def.py | 21 ++++++++- sql_parse/keywords.py | 4 +- sql_parse/tokenizer.py | 23 +++++++++- 5 files changed, 174 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 234caa8..c22c3b0 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ WHERE id = 1 AND this < 2.3; - [ ] 对WHERE中AND、OR的正确顺序判断 -- [ ] 对CREATE的实现 +- [x] 对CREATE的实现 -- [ ] 对主键`PRIMARY`的实现 +- [x] 对主键`PRIMARY`的实现 - [ ] 对非空`NOT NULL`的实现 @@ -63,7 +63,7 @@ WHERE id = 1 AND this < 2.3; -**目前已部分完成,只到能够解析查询命令(SELECT)、更新命令(UPDATE)与删除命令(DELETE)的地方** +**目前已部分完成,只到能够解析查询命令(SELECT)、更新命令(UPDATE)、删除命令(DELETE)与基础创建命令(CREATE)(不包含NOT NULL)的地方** 输入1: ``` @@ -189,6 +189,36 @@ WHERE level:AST_KEYWORDS. ---- {'left': 'name', 'op': '>', 'right': 1} ``` +输入4: +``` +CREATE TABLE Persons +( + PersonID SERIAL int, + LastName PRIMARY varchar(255), + FirstName char(255), + Address float, + City varchar(255) +); +``` + + +**实际输出4**: +``` +CREATE level:AST_KEYWORDS.CLAUSE +-- Persons +COLUMNS level:AST_KEYWORDS.CLAUSE +--COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION +---- {'PRIMARY': False, 'name': 'PERSONID', 'type': 'int'} +--COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION +---- {'PRIMARY': True, 'name': 'LASTNAME', 'type': 'varchar', 'length': 255} +--COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION +---- {'PRIMARY': False, 'name': 'FIRSTNAME', 'type': 'char', 'length': 255} +--COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION +---- {'PRIMARY': False, 'name': 'ADDRESS', 'type': 'float'} +--COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION +---- {'PRIMARY': False, 'name': 'CITY', 'type': 'varchar', 'length': 255} +``` + **用语解释**: @@ -215,6 +245,12 @@ WHERE level:AST_KEYWORDS. - [ ] 完成AST与执行代码关于UPDATE的结合 +- [ ] 完成AST与执行代码关于CREATE的基础结合,确定类型 + +- [ ] 完成AST与执行代码关于CREATE的主键设置 + +- [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,主键的自动更新并插入 + - [ ] 更多…… ### 2. 目标总览 diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index 77a4eb5..0a501df 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -1,12 +1,12 @@ from sql_parse.tokenizer import tokenizer from sql_parse import tokens from sql_parse.ast_def import AST_KEYWORDS -from sql_parse.ast_def import _statement,_clause,_expression +from sql_parse.ast_def import _statement,_clause,_expression,_coldef class AST: def __init__(self, text = None): - self.get_tokens(text) + self.get_tokens(text=text) new_statement = _statement() new_statement.attribute = AST_KEYWORDS.STATEMENT self.content , _ = self.build_AST(start_idx=0, cur_node=new_statement) @@ -51,7 +51,78 @@ def create_node(self, level): return _clause() if(level == AST_KEYWORDS.EXPRESSION): return _expression() + if(level == AST_KEYWORDS.COLUMN_DEFINITION): + return _coldef() + def build_AST_CREATE(self, start_idx = 0, statement_node = None): + stream = self.tokens + total_idx = len(stream) + + # 第一个必定会存在的clause: value="CREATE",content包含table的名称 + create_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + create_clause_node.value = "CREATE" + statement_node.content.append(create_clause_node) + + # 第二个必定会存在的clause:value="COLUMNS",content包含每一列的定义 + columns_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + columns_clause_node.value = "COLUMNS" + statement_node.content.append(columns_clause_node) + + # 在找到CREATE关键词后,我们希望找的是:CREATE TABLE 表名 + requireValue = "TABLE" + cur_node = create_clause_node + idx = start_idx + 1 + + pair_level = 0 + + while idx < total_idx: + cls, value = stream[idx] + + # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 + if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): + cur_node.deal(cls, value) + + # 如果当前token为标点 + elif cls in tokens.Punctuation: + # ()的处理 + if value in ["(", ")"]: + if value == "(": + pair_level = pair_level + 1 + if pair_level == 1 and value == "(": + # 遇到这里,说明读到了CREATE TABLE 表名 ( 的情况,接下来该是新的clause了 + if cur_node.value == "CREATE": + cur_coldef_node = self.create_node(AST_KEYWORDS.COLUMN_DEFINITION) + cur_coldef_node.value = "COLUMN_DEFINITION" + columns_clause_node.content.append(cur_coldef_node) + cur_node = cur_coldef_node + requireValue = "," + idx = idx + 1 + continue + if pair_level == 1 and value == ")": + # 遇到这里,说明是在希望读取下一个列的时候,发现已经没有更多列了 + # 那可真是个天大的喜事,说明读取完成了 + return statement_node, idx + if value == ")": + pair_level = pair_level - 1 + # 逗号的处理 + if value in [","]: + # 一个列的结束,另一个列的开始 + cur_coldef_node = self.create_node(AST_KEYWORDS.COLUMN_DEFINITION) + cur_coldef_node.value = "COLUMN_DEFINITION" + columns_clause_node.content.append(cur_coldef_node) + cur_node = cur_coldef_node + idx = idx + 1 + continue + + # 如果当前token特殊,为关键字……(但其实唯一要看的关键字就是TABLE和PRIMARY(目前的话)) + else: + val = value.upper() + if val == "TABLE": pass + if val == "PRIMARY": + cur_node.content[0]["PRIMARY"] = True + idx = idx + 1 + return statement_node, idx + def build_AST(self, start_idx = 0, cur_node = None): stream = self.tokens idx = start_idx @@ -85,6 +156,10 @@ def build_AST(self, start_idx = 0, cur_node = None): # 如果当前token特殊,为关键字 else: + # CREATE的语法区别和其他的差别太大了,放弃兼容性,直接单独处理 + if value.upper() == "CREATE": + node_create, _ = self.build_AST_CREATE(idx, cur_node) + return node_create, None # 判断当前token的层级 par_cls_level = cur_node.attribute cur_cls_level = self.get_level(value=value.upper(),par_value=cur_node.value) @@ -127,7 +202,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): pprint的单层实现 """ for idx, cur_list_i in enumerate(cur_list): - if isinstance(cur_list_i, _clause) or isinstance(cur_list_i, _expression): + if isinstance(cur_list_i, _clause) or isinstance(cur_list_i, _expression) or isinstance(cur_list_i,_coldef): level = "level:" + str(cur_list_i.attribute) item = _pre + str(cur_list_i.value) print(f"{item:<60} {level:<50}") @@ -153,8 +228,17 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): DELETE table1 WHERE id = 1 AND this < 2.3 OR name>1; """ - - a = AST(sql3) + sql4 = """ + CREATE TABLE Persons + ( + PersonID SERIAL int, + LastName PRIMARY varchar(255), + FirstName char(255), + Address float, + City varchar(255) + ); + """ + a = AST(sql4) # TODO: 由于自己的实现是从左往右读TOKEN,而没有提前读等操作,因而不可能先读 # AND再读WHERE。自己的一个暂时的解决方法是将AND和WHERE一样看作一个 # expression,这样能保证一个CLAUSE中只有一个表达式(例如a=3),读到AND时执行 @@ -164,4 +248,4 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): # 现在来看这部分有点困难,先不要动为好 a.pprint() show = a.content - print("\n\n",show) \ No newline at end of file + print("\n\n",show) diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index db2d17c..ec6c599 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -5,6 +5,7 @@ class AST_KEYWORDS(enum.IntEnum): STATEMENT = 0 CLAUSE = 1 EXPRESSION = 2 + COLUMN_DEFINITION = 10 class _statement: def __init__(self): @@ -26,7 +27,7 @@ def deal(self, cls, value): 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 """ # 当前columns的类型决定其会记录columns - if self.value in ["SELECT"]: + if self.value in ["SELECT", "CREATE"]: if cls in tokens.Name: self.content.append(value) # 当前clause的类型决定其会记录tables @@ -61,6 +62,24 @@ def deal(self, cls, value): if cls in tokens.Operator: # 中间的Operator self.content[0]["op"] = value +class _coldef: + def __init__(self): + self.attribute = AST_KEYWORDS.COLUMN_DEFINITION + self.content = [{"PRIMARY":False}] + + def deal(self, cls, value): + """ + 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 + """ + # 说明这是一个column的类型提示 + if cls in tokens.Name.Builtin: + self.content[0]["type"] = value + + elif cls in tokens.Name: + self.content[0]["name"] = value.upper() + + elif cls in tokens.Literal: + self.content[0]["length"] = Numerize(cls,value) def Numerize(cls, text: str): """ diff --git a/sql_parse/keywords.py b/sql_parse/keywords.py index 5d3a405..d851f33 100644 --- a/sql_parse/keywords.py +++ b/sql_parse/keywords.py @@ -711,4 +711,6 @@ 'MIN': tokens.Keyword, 'MAX': tokens.Keyword, 'DISTINCT': tokens.Keyword, -} \ No newline at end of file +} + + diff --git a/sql_parse/tokenizer.py b/sql_parse/tokenizer.py index d694953..d0bde05 100644 --- a/sql_parse/tokenizer.py +++ b/sql_parse/tokenizer.py @@ -26,6 +26,13 @@ def default_initialization(self): self._keywords.append(keywords.KEYWORDS_COMMON) self._keywords.append(keywords.KEYWORDS) + # reverse to find the built-in keywords + self.KEYWORDS_BUILTIN_LIST = [] + for k, v in keywords.KEYWORDS.items(): + if v != tokens.Name.Builtin: + continue + self.KEYWORDS_BUILTIN_LIST.append(k) + def is_keyword(self, value): """ 判断当前的内容是一个NAME还是关键字 @@ -40,6 +47,20 @@ def is_keyword(self, value): else: return tokens.Name, value + def builtin_check(self, action, value): + """ + 一个特殊的check,保证当前的token是一个内置的类型,而非一个自定义的类型 + 这里多余的处理是因为最开始的正则不太对 + """ + if action != tokens.Token.Name: + return action, value + val = value.upper() + if val in self.KEYWORDS_BUILTIN_LIST: + return tokens.Token.Name.Builtin, value + else: + return action, value + + def consume(self,iterator, n): """ 将迭代器向后推动n个位置,即跳过n个元素,这里是为了在匹配到字符为token后,跳过已匹配部分 @@ -64,7 +85,7 @@ def tokenize(self,text: str): if not m: # 从这个字符往后看,并不能匹配出任何的关键字或者token continue elif isinstance(action, tokens._TokenType): # 如果这是一个普通Token - yield action, m.group() + yield self.builtin_check(action,m.group()) elif action is keywords.PROCESS_AS_KEYWORD: # 如果这是一个关键字 yield self.is_keyword(m.group()) From 0abe533693078faaf2ca640d7920d9cb92fc3a1c Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 10:56:14 +0800 Subject: [PATCH 05/17] updates --- README.md | 89 ++++++++++++++++++++++++++-------------- sql_parse/AST_builder.py | 16 ++++++-- sql_parse/ast_def.py | 22 +++++++++- 3 files changed, 91 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index c22c3b0..ca510bf 100644 --- a/README.md +++ b/README.md @@ -43,24 +43,33 @@ WHERE id = 1 AND this < 2.3; 完成情况: -- [x] 对SELECT的实现 +- [x] 对SELECT的解析 -- [X] 对FROM的实现 +- [X] 对FROM的解析 -- [x] 对WHERE的基础实现 +- [x] 对WHERE的基础解析 - [ ] 对WHERE中AND、OR的正确顺序判断 -- [x] 对CREATE的实现 +- [x] 对CREATE的解析 -- [x] 对主键`PRIMARY`的实现 +- [x] 对SET的解析 -- [ ] 对非空`NOT NULL`的实现 +- [x] 对UPDATE的解析 -- [x] 对UPDATE的实现 +- [x] 对DELETE的解析 -- [x] 对DELETE的实现 +##### 星期五添加 +- [x] 对SELECT时`WILDCARD`的解析(就是选中所有列,详见输入输出5) + +- [x] 对CREATE时主键`PRIMARY`的解析 + +- [x] 对CREATE时非空`NOT NULL`的解析 + +- [x] 对SET赋值式右边的Binary Expression的解析(抱歉的是SET时的expression结构也换了,换成assignment了,详见输出2) + +- [ ] 对INSERT INTO的解析 **目前已部分完成,只到能够解析查询命令(SELECT)、更新命令(UPDATE)、删除命令(DELETE)与基础创建命令(CREATE)(不包含NOT NULL)的地方** @@ -133,36 +142,27 @@ WHERE level:AST_KEYWORDS.CLAUSE 输入2: ``` UPDATE table1 -SET alexa = 50000, country='USA', salary = 14.5 +SET alexa = 50000, country='USA', salary = salary * 14.5 WHERE id = 1 AND this < 2.3 OR name>1; ``` **实际输出2**: ``` -UPDATE level:AST_KEYWORDS.CLAUSE - +UPDATE level:AST_KEYWORDS.CLAUSE -- table1 SET level:AST_KEYWORDS.CLAUSE - --SET level:AST_KEYWORDS.EXPRESSION - ----- {'left': 'alexa', 'op': '=', 'right': 50000} +---- {'assignment': 'alexa', 'expression': {'left': 50000}} --SET level:AST_KEYWORDS.EXPRESSION - ----- {'left': 'country', 'op': '=', 'right': 'USA'} +---- {'assignment': 'country', 'expression': {'left': 'USA'}} --SET level:AST_KEYWORDS.EXPRESSION - ----- {'left': 'salary', 'op': '=', 'right': 14.5} +---- {'assignment': 'salary', 'expression': {'left': 'salary', 'op': '*', 'right': 14.5}} WHERE level:AST_KEYWORDS.CLAUSE - --WHERE level:AST_KEYWORDS.EXPRESSION - ---- {'left': 'id', 'op': '=', 'right': 1} --AND level:AST_KEYWORDS.EXPRESSION - ---- {'left': 'this', 'op': '<', 'right': 2.3} --OR level:AST_KEYWORDS.EXPRESSION - ---- {'left': 'name', 'op': '>', 'right': 1} ``` @@ -195,7 +195,7 @@ CREATE TABLE Persons ( PersonID SERIAL int, LastName PRIMARY varchar(255), - FirstName char(255), + FirstName char(255) NOT NULL, Address float, City varchar(255) ); @@ -208,15 +208,36 @@ CREATE level:AST_KEYWORDS. -- Persons COLUMNS level:AST_KEYWORDS.CLAUSE --COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION ----- {'PRIMARY': False, 'name': 'PERSONID', 'type': 'int'} +---- {'PRIMARY': False, 'NOT NULL': False, 'name': 'PERSONID', 'type': 'int'} --COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION ----- {'PRIMARY': True, 'name': 'LASTNAME', 'type': 'varchar', 'length': 255} +---- {'PRIMARY': True, 'NOT NULL': False, 'name': 'LASTNAME', 'type': 'varchar', 'length': 255} --COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION ----- {'PRIMARY': False, 'name': 'FIRSTNAME', 'type': 'char', 'length': 255} +---- {'PRIMARY': False, 'NOT NULL': True, 'name': 'FIRSTNAME', 'type': 'char', 'length': 255} --COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION ----- {'PRIMARY': False, 'name': 'ADDRESS', 'type': 'float'} +---- {'PRIMARY': False, 'NOT NULL': False, 'name': 'ADDRESS', 'type': 'float'} --COLUMN_DEFINITION level:AST_KEYWORDS.COLUMN_DEFINITION ----- {'PRIMARY': False, 'name': 'CITY', 'type': 'varchar', 'length': 255} +---- {'PRIMARY': False, 'NOT NULL': False, 'name': 'CITY', 'type': 'varchar', 'length': 255} +``` + +输入5:(与输入1相比,SELECT所有列): +``` +SELECT * +FROM table1, table2 +WHERE id = 1 AND "this" < 2.3; +``` + +**实际输出5**: +``` +SELECT level:AST_KEYWORDS.CLAUSE +-- Token.Wildcard +FROM level:AST_KEYWORDS.CLAUSE +-- table1 +-- table2 +WHERE level:AST_KEYWORDS.CLAUSE +--WHERE level:AST_KEYWORDS.EXPRESSION +---- {'left': 'id', 'op': '=', 'right': 1} +--AND level:AST_KEYWORDS.EXPRESSION +---- {'left': 'this', 'op': '<', 'right': 2.3} ``` @@ -245,11 +266,17 @@ COLUMNS level:AST_KEYWORDS. - [ ] 完成AST与执行代码关于UPDATE的结合 -- [ ] 完成AST与执行代码关于CREATE的基础结合,确定类型 +##### 星期五添加的 + +- [ ] 完成AST与执行代码关于SELECT的WILDCARD设置(也就是选中所有列,详见输入5,输出5) + +- [ ] 完成AST与执行代码关于SET的右侧赋值式结合 + +- [ ] 完成AST与执行代码关于CREATE的基础结合,限制每一列的类型 -- [ ] 完成AST与执行代码关于CREATE的主键设置 +- [ ] 完成AST与执行代码关于CREATE的主键设置,NOT NULL设置 -- [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,主键的自动更新并插入 +- [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,在INSERT时主键的自动更新并插入(当然还没做INSERT) - [ ] 更多…… diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index 0a501df..9652a4f 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -120,6 +120,8 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): if val == "TABLE": pass if val == "PRIMARY": cur_node.content[0]["PRIMARY"] = True + if val == "NOT NULL": + cur_node.content[0]["NOT NULL"] = True idx = idx + 1 return statement_node, idx @@ -130,6 +132,9 @@ def build_AST(self, start_idx = 0, cur_node = None): while idx < total_idx: cls, value = stream[idx] + if value == "*": + pass + # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): cur_node.deal(cls, value) @@ -221,7 +226,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): """ sql2 = """ UPDATE table1 - SET alexa = 50000, country='USA', salary = 14.5 + SET alexa = 50000, country='USA', salary = salary * 14.5 WHERE id = 1 AND this < 2.3 OR name>1; """ sql3 = """ @@ -233,12 +238,17 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): ( PersonID SERIAL int, LastName PRIMARY varchar(255), - FirstName char(255), + FirstName char(255) NOT NULL, Address float, City varchar(255) ); """ - a = AST(sql4) + sql5 = """ + SELECT * + FROM table1, table2 + WHERE id = 1 AND "this" < 2.3; + """ + a = AST(sql5) # TODO: 由于自己的实现是从左往右读TOKEN,而没有提前读等操作,因而不可能先读 # AND再读WHERE。自己的一个暂时的解决方法是将AND和WHERE一样看作一个 # expression,这样能保证一个CLAUSE中只有一个表达式(例如a=3),读到AND时执行 diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index ec6c599..bc16194 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -30,6 +30,8 @@ def deal(self, cls, value): if self.value in ["SELECT", "CREATE"]: if cls in tokens.Name: self.content.append(value) + elif cls in tokens.Wildcard: + self.content.append(tokens.Wildcard) # 当前clause的类型决定其会记录tables if self.value in ["FROM", "UPDATE"]: if cls in tokens.Name: @@ -52,7 +54,7 @@ def deal(self, cls, value): """ 对于一个非关键词的token,根据自己expression的类型,将其加入到自己的内容中 """ - if self.value in ["WHERE", "AND", "OR", "SET"]: + if self.value in ["WHERE", "AND", "OR"]: if cls in tokens.Name or cls in tokens.Literal: if self.content == []: # 左边 sub_expr_left = {"left": Numerize(cls,value), "op": None, "right": None} @@ -61,11 +63,27 @@ def deal(self, cls, value): self.content[0]["right"] = Numerize(cls,value) if cls in tokens.Operator: # 中间的Operator self.content[0]["op"] = value + if self.value in ["SET"]: + if cls in tokens.Name or cls in tokens.Literal: + if self.content == []: # 左边 + sub_expr_left = {"assignment": Numerize(cls,value), "expression": None} + self.content.append(sub_expr_left) + else: # 右边 + if self.content[0]["expression"] == None: + self.content[0]["expression"] = dict() + self.content[0]["expression"]["left"] = Numerize(cls,value) + else: + self.content[0]["expression"]["right"] = Numerize(cls,value) + if cls in tokens.Operator or cls in tokens.Wildcard: # 中间的Operator,Wildcard是指* + if self.content[0]["expression"] == None: + pass + else: + self.content[0]["expression"]["op"] = value class _coldef: def __init__(self): self.attribute = AST_KEYWORDS.COLUMN_DEFINITION - self.content = [{"PRIMARY":False}] + self.content = [{"PRIMARY":False,"NOT NULL":False}] def deal(self, cls, value): """ From 702dd3d43b27452b525289e0fb47615d4cbd2902 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 10:58:01 +0800 Subject: [PATCH 06/17] fix --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca510bf..07b3bba 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,6 @@ WHERE id = 1 AND this < 2.3; - [ ] 对WHERE中AND、OR的正确顺序判断 -- [x] 对CREATE的解析 - - [x] 对SET的解析 - [x] 对UPDATE的解析 @@ -69,6 +67,8 @@ WHERE id = 1 AND this < 2.3; - [x] 对SET赋值式右边的Binary Expression的解析(抱歉的是SET时的expression结构也换了,换成assignment了,详见输出2) +- [x] 对CREATE的解析 + - [ ] 对INSERT INTO的解析 @@ -268,6 +268,8 @@ WHERE level:AST_KEYWORDS. ##### 星期五添加的 +- [ ] 一个小问题:所有什么时候在硬盘写入文件,读入文件? + - [ ] 完成AST与执行代码关于SELECT的WILDCARD设置(也就是选中所有列,详见输入5,输出5) - [ ] 完成AST与执行代码关于SET的右侧赋值式结合 From b7c519c77703ec0b702f4bb153b442f8277bb838 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 10:59:07 +0800 Subject: [PATCH 07/17] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07b3bba..11e61d2 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ WHERE level:AST_KEYWORDS. ##### 星期五添加的 -- [ ] 一个小问题:所有什么时候在硬盘写入文件,读入文件? +- [ ] 一个小问题:所以什么时候在硬盘写入文件,读入文件? - [ ] 完成AST与执行代码关于SELECT的WILDCARD设置(也就是选中所有列,详见输入5,输出5) From 3b3ec866bf526761f0185a6dcbde2441128bcf98 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 11:33:36 +0800 Subject: [PATCH 08/17] fix --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 11e61d2..d1bd95f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ WHERE id = 1 AND this < 2.3; - [x] 对CREATE时非空`NOT NULL`的解析 +- [ ] 对CREATE各种约束的解析 + - [x] 对SET赋值式右边的Binary Expression的解析(抱歉的是SET时的expression结构也换了,换成assignment了,详见输出2) - [x] 对CREATE的解析 From f84402598cb87482c061f91826f9f2ac12015e1f Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 16:56:35 +0800 Subject: [PATCH 09/17] initial for INSERT INTO --- sql_parse/AST_builder.py | 17 ++++++++++------- sql_parse/ast_def.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index 9652a4f..4b3006f 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -77,6 +77,9 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): while idx < total_idx: cls, value = stream[idx] + + if value == "PRIMARY": + pass # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): @@ -119,7 +122,7 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): val = value.upper() if val == "TABLE": pass if val == "PRIMARY": - cur_node.content[0]["PRIMARY"] = True + self.search_constraint(idx, val) if val == "NOT NULL": cur_node.content[0]["NOT NULL"] = True idx = idx + 1 @@ -234,13 +237,13 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): WHERE id = 1 AND this < 2.3 OR name>1; """ sql4 = """ - CREATE TABLE Persons - ( - PersonID SERIAL int, - LastName PRIMARY varchar(255), + CREATE TABLE Persons ( + PersonID int, + LastName varchar(255), FirstName char(255) NOT NULL, Address float, - City varchar(255) + City varchar(255), + PRIMARY KEY (PersonID) ); """ sql5 = """ @@ -248,7 +251,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): FROM table1, table2 WHERE id = 1 AND "this" < 2.3; """ - a = AST(sql5) + a = AST(sql4) # TODO: 由于自己的实现是从左往右读TOKEN,而没有提前读等操作,因而不可能先读 # AND再读WHERE。自己的一个暂时的解决方法是将AND和WHERE一样看作一个 # expression,这样能保证一个CLAUSE中只有一个表达式(例如a=3),读到AND时执行 diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index bc16194..2fe47e6 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -6,6 +6,7 @@ class AST_KEYWORDS(enum.IntEnum): CLAUSE = 1 EXPRESSION = 2 COLUMN_DEFINITION = 10 + CONSTRAINT = 11 class _statement: def __init__(self): @@ -21,6 +22,7 @@ class _clause: def __init__(self): self.attribute = AST_KEYWORDS.CLAUSE self.content = [] + self.value = None def deal(self, cls, value): """ @@ -49,6 +51,7 @@ class _expression: def __init__(self): self.attribute = AST_KEYWORDS.EXPRESSION self.content = [] + self.value = None def deal(self, cls, value): """ @@ -99,6 +102,25 @@ def deal(self, cls, value): elif cls in tokens.Literal: self.content[0]["length"] = Numerize(cls,value) +class _constraint: + def __init__(self): + self.attribute = AST_KEYWORDS.CONSTRAINT + self.content = [] + + def deal(self, cls, value): + """ + 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 + """ + # 说明这是一个column的类型提示 + if cls in tokens.Name.Builtin: + self.content[0]["type"] = value + + elif cls in tokens.Name: + self.content[0]["name"] = value.upper() + + elif cls in tokens.Literal: + self.content[0]["length"] = Numerize(cls,value) + def Numerize(cls, text: str): """ 将token的text转换为对应的数字(如果需要的话) From 7a4c613dbe954173dd3fea8d0e356a2ea66b91f0 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 16:56:43 +0800 Subject: [PATCH 10/17] fix --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dca4913..3da298b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ sql_parse/clauses/__pycache__ sql_parse/clauses sql_parse/test.py sql_command/__pycache__/DB.cpython-310.pyc +pyrightconfig.json From c3cca12063c966c88810bb0d9156faa1be1f257c Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 19:52:49 +0800 Subject: [PATCH 11/17] implement of INSERT statement --- README.md | 49 +++++++++++++++++++-- sql_parse/AST_builder.py | 93 +++++++++++++++++++++++++++++++++------- sql_parse/ast_def.py | 29 +++---------- 3 files changed, 130 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index d1bd95f..6741af9 100644 --- a/README.md +++ b/README.md @@ -65,13 +65,13 @@ WHERE id = 1 AND this < 2.3; - [x] 对CREATE时非空`NOT NULL`的解析 -- [ ] 对CREATE各种约束的解析 +- [ ] 对CREATE各种约束的解析(摆,不想做,因为外键有点麻烦) - [x] 对SET赋值式右边的Binary Expression的解析(抱歉的是SET时的expression结构也换了,换成assignment了,详见输出2) - [x] 对CREATE的解析 -- [ ] 对INSERT INTO的解析 +- [x] 对INSERT INTO的解析 **目前已部分完成,只到能够解析查询命令(SELECT)、更新命令(UPDATE)、删除命令(DELETE)与基础创建命令(CREATE)(不包含NOT NULL)的地方** @@ -242,6 +242,43 @@ WHERE level:AST_KEYWORDS. ---- {'left': 'this', 'op': '<', 'right': 2.3} ``` +输入6: +``` +INSERT INTO table1 (id, name, this) +VALUES (1, 'alex', 2.3); +``` + +**实际输出6**: +``` +INSERT level:AST_KEYWORDS.CLAUSE +-- table1 +COLUMNS level:AST_KEYWORDS.CLAUSE +-- id +-- name +-- this +VALUES level:AST_KEYWORDS.CLAUSE +-- 1 +-- alex +-- 2.3 +``` + +输入7:(与输入6的区别在于,INSERT INTO未指定表名) +``` +INSERT INTO table1 +VALUES (1, 'alex', 2.3); +``` + +**实际输出7** +``` +INSERT level:AST_KEYWORDS.CLAUSE +-- table1 +COLUMNS level:AST_KEYWORDS.CLAUSE +VALUES level:AST_KEYWORDS.CLAUSE +-- 1 +-- alex +-- 2.3 +``` + **用语解释**: @@ -280,7 +317,13 @@ WHERE level:AST_KEYWORDS. - [ ] 完成AST与执行代码关于CREATE的主键设置,NOT NULL设置 -- [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,在INSERT时主键的自动更新并插入(当然还没做INSERT) +- [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,在INSERT时主键的自动更新并插入 + + + +- [ ] 完成AST与执行代码关于INSERT的基础结合 + +- [ ] 完成AST与执行代码关于INSERT在表名未指定列时的正确处理(详见输入输出7) - [ ] 更多…… diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index 4b3006f..d8a718a 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -69,7 +69,6 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): statement_node.content.append(columns_clause_node) # 在找到CREATE关键词后,我们希望找的是:CREATE TABLE 表名 - requireValue = "TABLE" cur_node = create_clause_node idx = start_idx + 1 @@ -98,7 +97,6 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): cur_coldef_node.value = "COLUMN_DEFINITION" columns_clause_node.content.append(cur_coldef_node) cur_node = cur_coldef_node - requireValue = "," idx = idx + 1 continue if pair_level == 1 and value == ")": @@ -127,6 +125,61 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): cur_node.content[0]["NOT NULL"] = True idx = idx + 1 return statement_node, idx + + def build_AST_INSERT(self, start_idx = 0, statement_node = None): + stream = self.tokens + total_idx = len(stream) + + # 第一个必定会存在的clause: value="INSERT",content包含table的名称 + insert_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + insert_clause_node.value = "INSERT" + statement_node.content.append(insert_clause_node) + + # 第二个必定会存在的clause:value="COLUMNS",content包含每一列的名称 + columns_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + columns_clause_node.value = "COLUMNS" + statement_node.content.append(columns_clause_node) + + # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 + values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + values_clause_node.value = "VALUES" + statement_node.content.append(values_clause_node) + + # 在找到INSERT关键词后,我们希望找的是:INSERT INTO 表名 + requireValue = "INTO" + cur_node = insert_clause_node + idx = start_idx + 1 + + pair_entry_count = 0 + + while idx < total_idx: + cls, value = stream[idx] + + # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 + if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): + cur_node.deal(cls, value) + + # 如果当前token为标点 + elif cls in tokens.Punctuation: + # ()的处理 + if value in ["(", ")"]: + if cur_node.value == "INSERT" and value == "(": + # 遇到这里,说明读到了INSERT INTO 表名 ( 的情况,接下来该是第二个clause了 + cur_node = columns_clause_node + requireValue = "VALUES" + idx = idx + 1 + continue + + # 如果当前token特殊,为关键字 + else: + val = value.upper() + if val == "INTO": pass + # 读到这里,说明columns的名字已经读完了,在读VALUES了,进入下一个clause + if val == "VALUES": + cur_node = values_clause_node + idx = idx + 1 + return statement_node, idx + def build_AST(self, start_idx = 0, cur_node = None): stream = self.tokens @@ -135,9 +188,6 @@ def build_AST(self, start_idx = 0, cur_node = None): while idx < total_idx: cls, value = stream[idx] - if value == "*": - pass - # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): cur_node.deal(cls, value) @@ -164,10 +214,16 @@ def build_AST(self, start_idx = 0, cur_node = None): # 如果当前token特殊,为关键字 else: + # 特殊语法检查 # CREATE的语法区别和其他的差别太大了,放弃兼容性,直接单独处理 if value.upper() == "CREATE": node_create, _ = self.build_AST_CREATE(idx, cur_node) return node_create, None + # INSERT同理 + if value.upper() == "INSERT": + node_insert, _ = self.build_AST_INSERT(idx, cur_node) + return node_insert, None + # 判断当前token的层级 par_cls_level = cur_node.attribute cur_cls_level = self.get_level(value=value.upper(),par_value=cur_node.value) @@ -222,43 +278,50 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): if __name__ == "__main__": + # 基础SELECT sql1 = """ SELECT id, name, this FROM table1, table2 WHERE id = 1 AND "this" < 2.3; """ + # 基础UPDATE sql2 = """ UPDATE table1 SET alexa = 50000, country='USA', salary = salary * 14.5 WHERE id = 1 AND this < 2.3 OR name>1; """ + # 基础DELETE sql3 = """ DELETE table1 WHERE id = 1 AND this < 2.3 OR name>1; """ + # 基础CREATE sql4 = """ CREATE TABLE Persons ( - PersonID int, + PersonID PRIMARY int, LastName varchar(255), FirstName char(255) NOT NULL, Address float, City varchar(255), - PRIMARY KEY (PersonID) ); """ + # SELECT的特殊情况:Wildcard sql5 = """ SELECT * FROM table1, table2 WHERE id = 1 AND "this" < 2.3; """ - a = AST(sql4) - # TODO: 由于自己的实现是从左往右读TOKEN,而没有提前读等操作,因而不可能先读 - # AND再读WHERE。自己的一个暂时的解决方法是将AND和WHERE一样看作一个 - # expression,这样能保证一个CLAUSE中只有一个表达式(例如a=3),读到AND时执行 - # WHERE查询,再将AND查询的结果,与WHERE查询的结果取交集。如果后面还有AND,就 - # 再与左边的取交集……这样对于多个AND没有问题,但是如果有OR,那么优先级就被打 - # 乱了,必须要先完成OR两边的再对两边的结果取并集 - # 现在来看这部分有点困难,先不要动为好 + # 基础INSERT + sql6 = """ + INSERT INTO table1 (id, name, this) + VALUES (1, 'alex', 2.3); + """ + # INSERT的特殊情况,不指定列名 + sql7 = """ + INSERT INTO table1 + VALUES (1, 'alex', 2.3); + """ + a = AST(sql7) a.pprint() show = a.content print("\n\n",show) diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index 2fe47e6..8ad7528 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -6,7 +6,6 @@ class AST_KEYWORDS(enum.IntEnum): CLAUSE = 1 EXPRESSION = 2 COLUMN_DEFINITION = 10 - CONSTRAINT = 11 class _statement: def __init__(self): @@ -29,17 +28,19 @@ def deal(self, cls, value): 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 """ # 当前columns的类型决定其会记录columns - if self.value in ["SELECT", "CREATE"]: + if self.value in ["SELECT", "CREATE", "COLUMNS"]: if cls in tokens.Name: self.content.append(value) elif cls in tokens.Wildcard: self.content.append(tokens.Wildcard) # 当前clause的类型决定其会记录tables - if self.value in ["FROM", "UPDATE"]: + if self.value in ["FROM", "UPDATE", "INSERT"]: if cls in tokens.Name: self.content.append(value) - # 当前clause的类型决定其会记录一个表达式 - # TODO: 完成一些例如CREATE,PRIMARY之类的处理 + # 当前clause的类型决定其会记录values,但并不是表达式 + if self.value in ["VALUES"]: + if cls in tokens.Name or cls in tokens.Literal: + self.content.append(Numerize(cls,value)) # WHERE很特殊,之后WHERE会被重复利用一次,以完成多个表达式的连接 # 因此理论上WHERE clause不可能接受到非关键词token(忽略),WHERE expression才会接受到 @@ -102,24 +103,6 @@ def deal(self, cls, value): elif cls in tokens.Literal: self.content[0]["length"] = Numerize(cls,value) -class _constraint: - def __init__(self): - self.attribute = AST_KEYWORDS.CONSTRAINT - self.content = [] - - def deal(self, cls, value): - """ - 对于一个非关键词的token,根据自己的类型,将其加入到自己的内容中 - """ - # 说明这是一个column的类型提示 - if cls in tokens.Name.Builtin: - self.content[0]["type"] = value - - elif cls in tokens.Name: - self.content[0]["name"] = value.upper() - - elif cls in tokens.Literal: - self.content[0]["length"] = Numerize(cls,value) def Numerize(cls, text: str): """ From 48852eefe409826e7602571c4ffe155d8d048a5b Mon Sep 17 00:00:00 2001 From: SkyXJW <135524222+SkyXJW@users.noreply.github.com> Date: Fri, 9 Jun 2023 20:48:16 +0800 Subject: [PATCH 12/17] Implement the execution of CREATE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * xiugai-XJW 实现AST与CREATE的基础结合,新增not_null_flag、primary_key、attri_len来记录表中各属性是否not null、主码 、(这里仅考虑cahr与varchar类型的)属性长度 * commit 2 --- sql_command/DB.py | 15 ++++++++++--- sql_control/main.py | 46 +++++++++++++++++++++++++++++++++++---- sql_minibuilder-main.zip | Bin 0 -> 50137 bytes 3 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 sql_minibuilder-main.zip diff --git a/sql_command/DB.py b/sql_command/DB.py index 0cc1b42..7f0fc8e 100644 --- a/sql_command/DB.py +++ b/sql_command/DB.py @@ -2,16 +2,22 @@ class DB: def __init__(self): - self.dbtypes=dict[dict['tablename':str,'tabledata':pd.DataFrame,'datatypes':dict]] + self.dbtypes=dict[dict['tablename':str,'tabledata':pd.DataFrame,'datatypes':dict,'not_null_flag':pd.Series,'primary_key':str,'attri_len':pd.Series]] self.database = {} #访问某个表用 database['表名'] #访问某个表中的数据用 database['表名']['tabledata'] 这是一个pd.DataFrame型的数据 #访问某个表中某个属性的数据类型用 database['表名']['datatypes']['属性名'] + #查询某个表中某个属性是否是not null :database['表名']['not_null_flag']['属性名'] + #访问某个表中某个(这里仅考虑char varchar类型)属性设定的长度 :database['表名']['attri_len']['属性名'] def create(self, table: str, attributes: list[str], - types: list[str]) -> bool: + types: list[str], + not_null: list[bool], + primary_key: list[str], + char_attri: list[str], + char_attri_len: list[int]) -> bool: #检查表是否已经存在 if table in self.database: print(f"Table '{table}' already exists.") @@ -21,7 +27,10 @@ def create(self, newtable = { 'tablename': table, 'tabledata': pd.DataFrame(columns=attributes), - 'datatypes': {attr: data_type for attr, data_type in zip(attributes, types)} + 'datatypes': {attr: data_type for attr, data_type in zip(attributes, types)}, + 'not_null_flag': pd.Series(data=not_null,index=attributes,dtype=bool), + 'primary_key': primary_key, + 'attri_len': pd.Series(data=char_attri_len,index=char_attri,dtype=int) } #数据库添加新表 self.database[table] = newtable diff --git a/sql_control/main.py b/sql_control/main.py index f4a2da0..f0cc9da 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -8,7 +8,7 @@ def __init__(self): 示例用 """ self.db = DB() - self.db.create("test",["索引","姓名"], [int,str]) + self.db.create("test",["索引","姓名"], ["int","varchar"],[True,False],["索引"],["姓名"],[20]) test_table = self.db.database['test']['tabledata'] flag, test_table = self.db.insert(table=test_table,attributes=["索引"],values=[[23],[24]]) flag, test_table = self.db.update(table=test_table,update_rows=None,attributes=["姓名"], values=[["张三"],["王五"]]) @@ -48,11 +48,36 @@ def execute(self,text:str): # UPDATE (SELECT * FROM test) SET id = 1 不成立 if "SELECT" in self.clause.keys(): self.execute_query_statement() + elif "CREATE" in self.clause.keys(): + self.excute_create_statement() def execute_query_statement(self): # 示例用,演示怎么从AST的解析中获取table tables : list[pd.DataFrame] = self.get_tables() print(tables) + + def excute_create_statement(self): + table = self.clause["CREATE"].content[0] + attribute_ls = [] + types_ls = [] + not_null_ls = [] + primary_key_ls = [] + char_attri_ls = [] + char_attri_len_ls = [] + for i in range(len(self.clause["COLUMNS"].content)): + attribute_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) + types_ls.append(self.clause["COLUMNS"].content[i].content[0]["type"]) + if (self.clause["COLUMNS"].content[i].content[0]["type"]=="char") or (self.clause["COLUMNS"].content[i].content[0]["type"]=="varchar"): + char_attri_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) + char_attri_len_ls.append(self.clause["COLUMNS"].content[i].content[0]["length"]) + not_null_ls.append(self.clause["COLUMNS"].content[i].content[0]["PRIMARY"]) + if self.clause["COLUMNS"].content[i].content[0]["PRIMARY"]: + primary_key_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) + if self.db.create(table=table, attributes=attribute_ls, types=types_ls, not_null=not_null_ls, primary_key=primary_key_ls,char_attri=char_attri_ls,char_attri_len=char_attri_len_ls): + print("创建成功!") + print(self.db.database[table]["not_null_flag"]) + else: + print("创建失败!") def get_tables(self): #从FROM clause中获得content @@ -68,10 +93,23 @@ def get_tables(self): if __name__ == "__main__": + # sql = """ + # SELECT id, name, this + # FROM test + # WHERE id = 1 AND this < 2.3; + # """ + # a = blabla() + # a.execute(sql) + #测试create sql = """ - SELECT id, name, this - FROM test - WHERE id = 1 AND this < 2.3; + CREATE TABLE Persons + ( + PersonID SERIAL int, + LastName PRIMARY varchar(255), + FirstName char(255) NOT NULL, + Address float, + City varchar(255) + ); """ a = blabla() a.execute(sql) \ No newline at end of file diff --git a/sql_minibuilder-main.zip b/sql_minibuilder-main.zip new file mode 100644 index 0000000000000000000000000000000000000000..c1749de3e67c5e1ae7b790a2afc68c9aafb1bbf2 GIT binary patch literal 50137 zcmb@ubCBlT+bw*zt!dk~ZQI7(wl!_rn6_=(Hl}S(+uhUl_q;miJNW1K)=5>eQ>n_z zwX;*%Ypv@_lw`rc(E$IpEap#W{QJ&-ZfO2#U7T!zc2@RQ#%@-&re@Ccc1BkAjQ`hz zO8*+#hbmP|Mi{%`h0!HJPmd5K9)4_X z4R@oN_PjADcuhd0kYm4kc8-QY3Z+cw0sbpsz`qClpMC!KhUH%i*xkj%!PJcLzwrL! z2B7PouL=Isd2&zyfd9XRXKUnUZ(_+{?c!i>mpGs>!~`F9;~iE0hpPpZmq}d&T0V0W zB;61X`AHkP>k$&YS?*?`GKzFsK27{d^fm2oqYV73xYfp90k8NB>EP2I~mnf|>|i zd@&P;110AcbSwQ_Gxib$47oTOX*lb~bHlmo#ww|$Qq6z6vCY3_k--oez>2G_Rq}Tgpa%b>&OT_z4*Dp0{|0r008U1 zG%_mU!ea8`40fh7IG?Jh6MvsSX-jXRX3-pxegT!L|45Y|Fa*U9i{zPQNNGs-B;0aL z<#f|ld-yu_v$(sFaJ|fQSqPk z_rq3So^PXqYg?L^Dv zM*vSFC>2j<{^QN{^UcDQ8#%W^+VR;Op1y^?z-7!tfq#_ncb|CYC#0dZ>kmT&2F?V4 zfwV-UU>SJe=noll>|0kaK!Q+VF$Vlz)8#NEAmtDoe@nxiaNmQ;hvPm%K>|V;*H65S zGZDEzqAXY$KUbP|LX(ZBSb=gT>PPunEG zLdp#tq^JCMxUyx7e*)qsF*NjhJ*y%Yc2N}tTVmlse99s2Tb@37Q?UY4)!ix)bRG|W z76v$gI)nc7$%yitnT;gSnV-;I!tgDXIY0`TW+K*`GZJT_-_^*^JN|T!Mbv|jprE>1 z&XGGiPJ;99oknVIlUxJ2W04Reb3=y_b={knNCWO_muCY{Na$Re-O94T1=cxnNiCVD|oCQjz^)4+zXxtGdmsxuLuE-dAw zBILE@d3`$RXbK#t63B z)>ir<8uFzP5p;31j((2^uW+=s)O=>@>+J&N7F}Z`h)e5^VD=c;CYNcSAlr(NM-hLV z7|lB3S2V#{`v^-bR&3Uz^9!(zD8Y*g7puQ)fAnV{qmfxY^ zy~8ko;%5z z@gOZ4XVvhx)mA0ah0RgtaEk*Sm?bkkiv@54Ooj3N179J10yaHBygAp%AF;Xt4sjdAcmhr~CoN)X2|knx^29(sE#&LM$T2fAM(3+(7;ea*bu0w{5 zk742JiJom^d}=tCtr{kxDLFmzGiVR*!NLjBa3OUNMLd(ZJl=J>oC! zAeO!xM?w4%GMGtR->80@?Iv~m4xNQ6*Kw2yI0_u=Rx3{a3YTk zBeEWMVlZ&;iSrL2M)VleN6O|B9(nmU`y9C0WltxNPRg2_wzm3BIpKtpjd$V5r+j=Y z9?h72i>D6j73zNZx+*}%#(P~BP(tM9lVcPoA#ZZ4fCJs`w1duK;sP5fs0dD?o%_L2wRcWSbmSTDpvEO#px^Q`fNp39FhzGBi-cPt095zh3X{;; zY|qoH>{YVFU)tlTA9wC_FP@3NHg;m+xKnLA*t#Oc!ZiEP(-@7JwHt%alRW|0c0zMn z0e8Rk>9)h;Fk*ODEs)`JUa&fx!P_fsJ#BFK2tydS3$-?ZIF-&dRQf)J`6)K!(oQNb zmDM3a&Knv@WFPj^zYZ5bV$;)nf~QVm#zV&5{%z?<1&7c>ZO3Z3PY!h!@yhp=lps| z2!z=~^V+nkzV)eTIzM3aQ5u`o(%PPvFO*UHE11Hvep8#SG3qzw!--P{3AnwjJCJb_ zNH@@kQ57xYEYi0(B_LfHNzLVW@pGcX=1(HnPk)XJHz33p(1nY+KP#d;y6+=GGfNg) z!71o>i-`$AD#6Al2ryjpZ_Y7Zp3O-1X>47lVoHq9px)t>l(@{aSWFKaZ%ZZlNnAQ)R4;$K$nJBCp5pMZN@f>St>s!-(_h3D8WTUT9O+$%AgCFr?0GkS*r>! zXUZKkmjkg@B;`uko@Ca!lMWWT)d6pRExN}@FA0wDfM;#{7kpXR+gP9yOm01yC^^M< z>O9;VC?70gT3F;rl=Y>n2)`RTiMf(ntRv!iElG}GzG)q0 z{X+-glyN`zsIr65jpmG3I!P9WA_dT1I;Sp>{uD)9x>MI&@(Xn#Tah)G(qRB&DL(aAuMZ3Cx{u9Jhv38 zth2oG+}L$tdoM?irm9_I)^}f3B@RC+QQXirxf7)gN-th#p7A3M!rd&Fom09<*P3K> zP|7uo+K*KQ-sQ;k_ zhas;tH+c&!+Df4~{_+R7saSuKMYbsjk5#;bWxT|6fffa(90g>+a#YgugP*qej$zXS zhHsFtBEffiah_z;Vd6F0#8}Ml9*ETnoOui>lb=(NVj+-LP@L0PQ@ChZG zZfU~%NMYhnc!$o1bdhL7mxHe2w4eX8li%>V7;~l1cE?>;zvaf zki#JsyT{j=>Sn@5jUH{sF+07FW|fp~BY(OU?Qa97Ul0j4JRvnDJAKvNp*!S%(7L*S zC+sGP2!>rq^%JH#^mG3$bxc(=AZZgV5X#AjW&&$$=qK4qjo+QmIWa_ch0v%X+~P6p z5DgOUD;(zG>YfSfG&@-ADd*uWn7y{TIa#Cl^3(Vo^b*C?Z3y2=vzFyyUBJkGUjUVy z-eZ|jV6q4w7yZjY`6C4?I0K1aK&8V#JN2W@+jL&ZK`Yh%+;`8^Bosr1r*p>$mGq-7 z;SZG5KD;9_%-lSZY`|?HxzOKWzo+?y{UFG*zoDXz^MfE0^!F&kAp3)oHHfshi}IdK z++>L181%6faXeCwmudJv}QkGs8a@uHmw?#T`p&0+OZa$rMsW7MflRMVTloTynL#L|u%h&{4-NsM<+e zkx6ie9oLdgvXxvinCKSh0B6Hl1b{=$G$L#)LTFH$9WQeqr$|M&_?biadxZ4$Xsyr< zTwtKJ_C|XG9*%I}Uv3WOuMNfIK(A}Z*W;OKZwK?8?&ssHui-0q5-Qy5z8~J7(4V}G zXp#+nx3V||@d*)PG+{w#u?7+(z7Ubq2&dn_7!skZB2m^|igQX2HLb|zIRW3zbWGzi zwyC0+ajjiZ~&gvYwX*?33))kuN6FufR6eIaQa_*P- zd>7T}{V_D~Tp>KQ>WJ<*sj!Mh<9HY(D!5s;}E_pC7~ z{#Nx@sc0=k{Bl*9qDAM=hj@!$2E9hdZM%B3UxlNs_$LajNPX96`DK??8RQD-zesGm zWB3OsRS{vvpL=~&wpTy2I-xn}RWy|nP^==+u_KeUq8}UB?GQIOWH&Wf?dz;~^X=N2 zJtsI;iFoSjg`uX$7*lJsU6&nU;~jO%Pw)zHP1~(am+V-xN z5pij_?cUc(Ejk(cTQbLemNXEqt{xW_AZW;`#f+njAfap)Y=D8_hb0y>sa0>Ml2k-A z$&kx###8^&WF!u&1X(53zzuQHb+IpYxOQn1MMm57$%x1E_HkKXoUNLcYLU`rQgA=- z@nB1WgG&Coe!p+&b22-=@Or=B&c(~;YWSM84;>mHg%GNtH>YfEI72NBNSog_|9&iE zx%nE}pZ>|Q^Er0nK)8z7>ofLvQDJqp)XdRzdvVImPh-RC3=)@xQ~Vt;hs-aMj8P$R z#@g-@lw0T@U&T4CpyJjO_`YGmZ0nh<(6AcfV}q-9jrKvX7tF*}=sqOv>d8?M9z{=imSxM{Gp;X811ykVn8@5R#8S5aRZp7oNRnTuRKo7C zs_Lys-a_s8bT)iIovfEkHIBS@DxncGboo2@EsQ%@Ia>Qv3I_aHwM2suRpu`QJfNJXl-dCU z2N}&r;zC1rq%F6dU<%|1-vGw#cG~?8=2iGW+fr=YTCD~7Bam;7w zh8s^o46)O`SIqiNDU18ZqRupT^6wEt$4GW2Il-*>L80sXo*17?KgB^m(I#_01=#^A znFeJSKn?=WnRKufc85^YtQy!s@RNyl-mC+}UYF(9ph!ITiP25T>XtSv>n z)g(X@0fRxG=(z6ocxOh0&2Vj+zV7yD`=Es?FDVBDCvi_2sqVAm_&n^Sc5fah-c$mR zuBgCO2`9!a`81kSgJAU!&!09o467vx)Dfr@W+Y1by(Vl{h}#+rF8y+*%nlG+|0^` z04F*MP9;@}~;Vk*lg3A!$Tu^v~g}9ZF#z2K4%Otrb9MR>6i}KS<%#>oT z5lmN!ygZnf<-UZu&bnb}gvH>>cr6ff3NhW3rcp!t(sJ3(5zLyeUrQk)HfR}wYpOvY ze1MowVk#=A=eL(*y&!dAQZVa$S%I!6dSY;usEDf0wrh(`tvgq?T-Wt>6^wVA-cGZR z8xgzX>Snk5$53Dki2NzTno`cQ4%5Nh_BJ)oO|4>AZ787Px!6Os0kNT2R+fqMOUe}+ z{8s;*^jG0A5r5AgsfCt(ixg=V7QL0^Q(v0sR$A%ydg5ty2xe9~$o9LdA;8WJlF^1I z9`sffvhHpfeM8z?1+BEN6tF6b4ia4EdIX*HSxDTm@YqGPANh0F; zLO$Jv*`J2|!>4S9vg^5Nm{H>ENd4+}FN|UstBpqliDa3hZS-iG$v$*Ps<@{WcfU^D z2LvDtIkpcqPF%du8RDkxqULWy797c#ec`7EK4aDe6_@e_c4c>FtRH}T*J}GG54mlJ z4}{pA-=8X7#Am44s##ZmcbcPV?l)J-yyMfV7nR{86m`V685OCk0sM6}aU3Kj7v6jJ~6L*#TF&8v}=yFd~1 zjje$3=CLH+cKM$Nt@M4pALoaP8_|#sv4u14KI{Jc6EV$%(36n5jay1hVND1f&BUNL zxo;CinpTNeyTM7nr+0!icW_xkw|7d@F)>Njp@78d*#>Gxpqxj_I|rq6viK#a8;HKC z`5Xj|A#NHkO79mH)5q&e5?$zcOUJ^C4BmnodnejWCY&U(mB7j)3_lMdapMm*yTlR? zvTB@<@sWt;7^@?J)9|j=V|TvDC{AK0n&2d~?&QiWCYe&wT(d&extb4h5|HJyJ>HwQ z51Wb0Qwx*K8?o7+BF{NeIi0IZjZZQZiTZOqh&w-8N|QTElf4!vmc~<8)2tNy8V z14FobPdKRZeHl8@O5%+9^|y*LTcVm@Rz%1~vd`LCM>{q_U6GX`@+f|{uwr|7@+usY zd_Hjg2kv~*rD3`2kpdk(A2^vho`ycDN~&xEQ*i@QgbqH_9HUUg#w9yyM(g8;%3{u3 zkjA|}u(y)(=eXn6nt!nZB(EmWWG^Sg?oN=71W_6&3vzF!Ih!p?IdD5$!mbfcEQYdr zK6M=YdtDqdbq`p#4>_g$_z72ze6KQA7jCQ(y#MCvV>>N2N#0*hJQLK<#hK+!uwOR) zFuu#RpIOX7!XpcLZ!-Fr;>)r*twFZu-v#37FKy&bvEx<3Uzv8jHwFXtCSZCx=c-_O zcsH*F*6zG>g1zVBeF1)P11GVMSl3R7che`o*$^A11)K2pM$+^@f;7ygW52(F4lWQ`*U~U5Xb}nJcT=HQ`6PHM@}zj)vChQG6v=YD zV_ZoNz8Q0T=o?}Miw(S_5crDY>gK>0Yv#s)K{n}n^Bit4lt6XrzgB$xiSP6EjP$b3 zTnsDblUtz>(y1DFd*Kw2Ig|cGiwKE(@{*1cF-?cZ)8yNPYaVus-`&^z)!x95!S_e` zn^l(`;%(ky7*S8n1JYsAtS`|{%+vS2+uXH>+;LARx0`$l_^VdVUe%?liO{5WP$aq2-s@+SetJkh%FqMp(oxR6YF$IH59D-mUDjU zO3ONEyptM2uJe8iDt3TfiyJb-thmPRf=0awym~MP=c?Wzh;7w6a4(-B-+FHUnCJcl z0#Vh?K9GW9)l+UACMoMPqqLudPq(X|@lJ~B>&zGGm!XFPiihhoapKXVzQ@(hF*9W{f zJ1>>@Ldg&APn>N#oDe=Y4u1Fd>>fy)$M$`SDOi5qpWe>`UoQfP{?_GrY z#M~DStb(66n}SJ{x7CkfcG!i=TFjrJ>7VdBVCWp%5nO0bEcZ?Es1D;kpF{+v^)I?i z6a4P3rQvZ0rEo~&aL|;zd&&e_Mu7!XH1XX!XVmpYY@v@GE{}rdkMHLVFc=LY)@I3f zCs^O9Lm7p;7Ih72Q1^7t+p0G?GQPC@IoflXPj(NsVjLgbPxZ3|D{DiZiU&Hn=4#Im zrod(X-}(eo?>d5=FQ3utBf5)iqs@z`Kh>^UR$D60cuy8EU3&Ac;f~izr-mwe`KEvb z(vL@L<~epNYvxQMhWamGA{-tzFS8Fh%=CE6J333omF^4$p*zxyhdKV?qXaOA-HPHNT%GzZJlcWsD)^bS}!Xt^V%oq>oo9H;*(_5AzZ7t@Ai{qH33PKf1 zgscksEze3@O2Ar^Fy73Za~>k=>RoO+TI=vB?bNL7Q%YA)e&6KY^PUiFW?C=d$FsR^ z&gp~pA(+@!t1RlLWq&tvN$jyq)6BC?werbNb(q&=j=8xv_`{7Bb8a(jCmE@CvwV+x zy_wE5KT@;my7Eg7d?RcxHjRWSb0Gm4xEwVn8rf@e=vm~+ZC3eb>{qOHT1tU`7Irh^ z%sORS_q9|zS!+HG72v8mPq42~;7X-CWwa5$@im8NAiJzP0^g;GrY&U^LVr7Gu-3}^q_8F zAH(-8ZkK&cSB>9Y3oH`u3Bb~Q1ZPI_zuH{%c)B>mFgVzDJMUC2`8U&N;_MyKt2VX_Sq*4uIU2FfrDQw`jw|(UHHZ=R!9qu1o9M z7wdK?=z#zj@^1CS-WSRllbh&)MxmvCZs0`e;Bravs^K>4Dxtc24XIq}8CR#!IBY3z zU{|T(&1Q{ihHQA7us$S-hW$(KPjAFM4=KI2>pZPR-Cm>BYGjazOas}ZY5Xb|6#J-{ zVF1HjQAM#mDovIx89m5N#y45Iy5*YpRW?tE>6)IK(OXH5L*Z4@RI{o@JILH}#VRgUfP%n*_OAouiI<=HIjAnT&(ceLhCn%Z!Pz20+P z?VG>gv!;cxcG&w_{<3GE1n@>Aum}J(x4B;Uyz&|T?Zab8@|7k34VkYC*)zMN4@0Wo z9&Nqd$8joB)zt@`3n*G+Jgli+=-{y1E{0o}>wZSOSxhPWx`ZbQ z$yxOFhec9htFgwCQlUw7IOi~BtpVPOrIi#{eC~TRliH#Cx@~X{xSb5mtmP8y_S-f1 z41@gqwwyAR?x(F9VY6*m`LP@hkIZ9dgWWBZC2oj}DpB3y{>0t{A14mP5f*iU8!Gk0m292VRC zlA)9EEZl@lJoanQLql#DUNq4#Eb~&Pt|S-RqGTs~y=1Y`NO>TJ<^R1MbYdN$$Q6np zj&il9=9m;1Q3Qe|T0~GRwmR|+Y7nd`GIEKC6WsALe*fF zACy1OCMUlS^d}WVjI`_(4tO)d2XvwQsKzXWZx6R~c4mJ#>?G+O^WyrmG`7~ms3O&g zc&G%OM5c!r^qUB6i1eo-CxY^G=6xgm7e)9#3+?_p2EM(kvxDvb&tP}?hjyGs@oQ#7 z0st8(|7y+Ie7UR^V7EldL*SFjmQPq@>1$3 zx@4^SRIZAvX3<458C_Te5VCywW+B~twHzrSGhGppSC(FPk3Y1Rdcg0FNU#fIVsh^9 z-yMQ^E(|6z9KlQPRsxh%l~`xk1pAWrDm>&gQj>>`QDFgSST4m$>v9qas)@Y@;oGaY z^?gSH#O2Um&J{vL85nBlv8Df**(gG{#ly_Q_epeCe1vKv4_Xlm6GKkf@7r74D}89w zDe-n4V$4Z6;>x#p@GVKC(&A`r%;Q#tl}`Iy76d(GkLnE5`#omeYKK3J&`!v(J+{Dv zr4^uI6aJ9O7{EWnS*go;@ElfEq2Kgo-gcv0bVF7|B8MOFkORb(Mqbbddy@hbNrcBW zf7G69>0lwXaS}~bmRkx(<2{lM29;7TSQ1bvQ?x^g@F-HJaYW3G013|G-W`0I6#j>*srPtb@C+Es$@_BC!$;|%-4fN zFZ%mnYbRO9w@Rz(b);I<9gq2QW|_T5g=sYp`H&~ct0f=}S% z$)l&1PlA)Fn$7u0~FHzqsV-g%p@hhZmQ*c&_$ zk=fEVVI)GlAOu*jyvl|?`Rti$>^x23^X^`TSmL;ieI>SY-o}?@aDMbr=?QAaF4Hkh;O zK4J2yGLP^pRgFG3m>7w_6NtjHl4SV3u? zHp3A4h^D?$ndm77ARc;fcQ@|bubazY7TFjt-qqu1xBQM zl{mmhQln6LR;Nbx3VRh#2njfHvDGHrm(=JBTFXcqNaOXOZwy!$NbdN?u9h|zig`ck@XSj&V*gM)&m zaD4-3?3C^bgE~>8VU(8*0ScdR>;?~&6c53i9)kGNhyFV2kS;MBd8x`7 z7fibM=ih=OVaBt$%dBe_6NQAzKMyyMS|cp90Mu{a0B=u#%CP?2zPoVbC@@sQGlc^r zdI66}A8t-c@kR)K8y%gUcJVaGe&v@}6_N^e=Q1ncq&igp7(iDMoPgPgi`O_NRsa6> zm_WJ0NZpz}B^LZsVs5V|5Ja((GDSs9J@nmO!_a5zSMvl#7Y3!6>>si}w305&Ul9Lu zD*fMc;s08#`M*tjjz-Qd|Fd}e|IDrn{mc6OKM?=#dGLRW7YDd=*nkQEXi@?Iy8jQ* zg;mvn|5=~^zo)}zbi5r=B{TEprT(6pO=$Vpq_L+Js)Q1;1n10C-PoNOfn0xHY!@V@&e4UZ2~4x2wgfCI7utQx;zy zNl?>Yex*t4;yM3nzb3F^OPWcl>FMlLe!s}QuG{T9yII}#`)$l@y!YDmEuh%O!+e>? zyj&#zw(A2k%pNmpxfLI~Ps)WjgwtmpgGH>Aerq%4O3H(_5>)ReCYZ{a#3~V&U=oG` z7H3tDk5$tZF#qGktY%}ZtHM964{&C%PMj>En@B@*k z)9nsXMg!3VJ>Wv)Tl{R7>sK4!3;l*S38J*v{DJwL<1x$!!qj7|=g(oFF{Xc+EGV=o zkZnB?|m#18VoG$H}=uJJTELx)ymhIVbrc6b;=8eUu%@9I;cxDjB8Hf@rxe6Jd~0Vb@vO{vyZ_@Tz(*0=l4qHdF4@9!aqn}Ko->^ zuvi4qskvE_fnhmuoZih_kU6srdyP_BjwX^~$Fm5>^C{BNg@m%ph865z8?6$gd26wF z1PibHD2 zbPdO3ySJArKVwG^<(<(Vxi@?`&@bI*G0k}`D$Z(qkiN?K^)N2Yh--)_!bF~&ooqqA zZ~)RN9B%10N25m!{Gk?5 z@AH>iUM}U0jUTU{xU^Q-vz@M79#yizieB=;IGl$Ft<-JNO94}D4?tAfPZQEm6;$d9 z8|(5$ou8ro$1{6zRPA*UsqElEl5Jfn@9eM{5CEHCg%Ci9;vATtC?gQvWN1F;eNE>N*sGF>~VZ&|MhcMf_K;16q5yD_-h+1VR81b2sRnmZ)`L7HySg)Hy zGFF9PXV`|rOPO{}mO>u_V1f~OkXcX?0~R0egjA=hfqw+dp&?8Cz))(xj|NexGcjNh zsKyWhr#DIj-bc5!JE6M7&{> z##Cd({{!=Vs8%W~-J+|a&*=Dm1@t+;JgF?V3y50gJ{bytJEo&>x=@x8$oOEAjDG2PthQ14j$*zOgTY-iYft% z7FPLG3Cki?*UwpWiKhj~1GzMlegbL1!}Fhy@Um~d-rfV96Scw8ZwAQRm!PtY z^HHro}2gGFr82kDI&u34z&=8iTvGG2#!>>xtyzvDCr%rV&jG?b?UI` zUtpq!gPOUcWKzz-G^%MGu!v?S>O@tC?8T~iiJBGS6E@Oi0;CQKG{uKAie=^};;tg7 zUWmGgvu1Nar*J^v$K~P((6{mB?DEf5p6@HCpqg_BJ|s9M#tH@Q2?dp*%Nl}`5Gv)- zGyKEN=E2iLXYTIV4mUj0$e@vvC2~F)KByF+l@Ee+k!lKn8%x!%MgX2fQwU-SzE?&r zO8q_d8B`-+RCyx*hy*)X=Q{0vdn^+GjRj0&TD~j&r+X!u8y1_MP5jt>0!BK&vsMJD z%G(#XHz%?xOnqbrmj<(R7T%D>Ylt8VpqW!*+@y^RKXoz!@imi5Vc5t1M9?)s%PmYo zx|%Jfr|3e-70nh2g)QP~I**_+l@ltY*7={5kZ)RqmTZ%@%XBH_d!6)6oR&$dD$BI+42%3E!QO?4Yp$daP&#S0hWB%<&TdGG-lm z7_~d_Du|j3ZuKX%#m(L<|90?uc$w65Khx39>h4YbIA8KD>%k=jT}MF@roH8+HZB_4 zWKR~iw^vXJ`QpypizO4;y*mAj$7eZ*G$!cuFSo;qX+V-RwdT5h!HCWRRFL5rLGb%^ z{%XAY&#I*8j_#_|W!Qdm9yShw@d&o2>4}7RGlLMZ;2F`HFbFKl9s!MG^dHRUNaNXL zNcK2K{2kdj0B~}Ei*iQ1tx5Hy!8~dF{VxQZ%*!?ReB3=Y%j8rbmoCPNU_H9zqo(`s zYed}C2QBes5l@rC?mN-Gz00^X2h7Q~d(#T79s;SrVzEXkB3g{`OmOX?Rr|=~E70}X z&c+CfBbbdZ>2m}-J_8rBF%Yn@RP zs@IwNj;}NeB^o^BFGH*9#0|1+vTk0@#`MO98qaa{T^K1RH(nY!=GPndE@im7uAK&5 z!vD-_f}haNgE$?;ody^>U|ivXzaK+*YYy(XJ-4*u4YJ+HuagF8@smhPxDo0wMr1A`PalJv16SzgURzOiNP$6ty zgIfNS)>Mj_Y^~7L(!~7HJRjtjCBU1~C0E>E0Pdx?kl^gy?>*VPhCwsXM|gZp!43xx zm9_Z)@;U8~(@N8GWxFI|YTBS$AFh1G;^H8fdb9?Pb6%pE+LZp6I&D)F}8+>DqBHe3I3YrDZ^2gBk~ZSfdyRPo%JoerC@{diAFsHb&0?IWKwpu)>X2cu1=|ppsH#p%J#p+$VU6#7PuIFws)(flD zDBlNHu;5d)g{(Fuh&Jp7gA$n4Z|1KB708p5FwZ!*Gn6o^FQFQ1_sdX8Ay5tDQvm}q zX{_=Q!-Ldru|$O{lNFq*Mff7O5yg?vdX)N2L_4C?wW4?nYV1+bA`}O_BiW*j^TLgh zqcEswjHc9LVD4~Qk)rsOE<=oo)T2!UDd__a$j%V4q45=xNM8doQg@vJjDPX|p*1kQ zUZ!Cq7)3vj8D&y#)kH!v>GL+I@J{g}{$UHrg#BEDAyFWaw9awio~=|)k#H`B%TImN zg0|#D%dI-Cm&Q&={>|_mZnU#3H&Ghtnchj>jaTE- z>p0(87_e|u{qWb?A>+kS-VVZ(9fYl$XwEB?%%@>loTB5k`L+3Q8JT;^g7>jM@?av1 zLM-ad>mVK4^)-rXE59Gd?5=`pa@ty=OKePkKsHJoC9@#oHK!%-H=-lhcXH^*vCV0u+#ZcT0{m0jZ$7o1rOTz0^Rfi*`h;6<|Q+Z z<}RL7XLHSjEpsEiSKRo51|o*W+^lk5sR*K%zAS2X?ux@){TVJTKMRCzSwx(?yrLuQi=F&HpsNHJZwtAG{~-%@9WE;&dSEhFs7 zuCZ9Pp2`#jlEZtf7Y-KcB8f^X8!emaQ)6Mhklr(nA=e#*?@mP! zP4jsw%qusyWz`4h)YYJt|JZJasg)Xt=K8Rv(UEFzG95j#T67b+=%RYyl+Q;Zzhut3 zuUz4t*V^QR8uX>{!zCpLdR2NBHQ60S8TB}~EcFvv`)Rr~f@~&I?m|}L-j@BkLmE&1 zJx%4RAbn-F)<4|NCvfp=L^5uB9j=qJy{Rh?bk`v`Y3&AL6bVh1{5O4ts)74MdYb;~ z!pgB!75|M%vX=}=3eT>bjoTvIhXww5g#TbwWziTWUzRe1j1$>=gCzg_qyN*Eb&Nz? zkra*67w2%r*3;BCx}lQFp~?D3GX)kL(hOeWGuEj)y-Q6>ACJc^sg-c8Ihk&Pt++F3 zlHDcF(NN38@=rK$#NM8iqdwx-n{$sog2l`sRTyJ-#cIf@=nTHqE>k%#@V`=Ee{CnE zrVSBM_jjVhu!lOX$!SX$`*wcz-n@4)T=KNZvSnCo{CfHN^HA6M^8OEE*;fm81T_jvd6;l8`#QcC?eoS%}G5ShAOv z%w@t)Qf7@rJsBci{Qp>c%b+--s9O}*!QCOa5AGJ+Avgqg55e6X0tENq?(Qyu!QF$q zyWjcb-l}(B)q8)wUtPPVt7rGwYp>m>`<%6@hROuy(mzW5bMPl5oH;-q#VMwKlB69X z8OL!KYl%v<=>OQykFOmZx7J!Z9Jjh3-JWY3=VPvJTl{+_-zxxl{^T2`piVNs$eFbW zabg?^N_7!Qv2@hgch1-x@vQAHJNP-mwxisgVe+|FhVOuFb`zF4E_?w<{aZs9qSFdZHBYF-Q1<|@*Dxi{T=ZbYrT&ms#s z)U-6L6(o^dUYrOVUpI9K@C#*kwVv>MAq>3bT|Ujs`F>%ID2XFpb^q~s>}Eim(qB+A zv=r2xAWC<2qD?+}{q;RU1YNi7=R9k~n{^M}Gq2x|sUuj(lyq{q&qywOMXR2|F1vgV zop$4VN*zt%>s_;bi}aP!Ae9t*E$y<*RJ!{wP)#N=IMFy`MktZL4R@rU;yL5(VbSQY zV-Af463CBbEV;T7Zmf|-U<@o%6}v+9=6RT;4f67@rYVS;>fmjh*p;|3Pkh~ zqWCm7gfOjOEVv96Do2;`R#4y*(cysJ+*@KyPKTt?N(DtZZgDXAqf>;}VQzwm;jDKl z!e~-ho zs#Ike$%>XdzIqiGAojxo<0|SkVUvHZv9FacmeZ^_w#mAYxNJvslwYxFln4DTF4D(X zKqzFR1240}{8S{iuwkS6VZyvu(Y}D$Sh8xFTDlf8?OucgV<&#T4z<^I3zN12NOTt`#8D z+tn_XTxkO|IHrB+iaQmWil% zT?F#_FXUW&6>No@tx4@pw-@V{y0k31Y@QqX7rW>IUhBO0+l0jhC9j1hC*FA^-Sk5q zCm|gpLViJ=+6yPz*9sm?qjh<;gGilZNO>>DAN|S$P$R#u(^oE3uAnNocc$WyI_cp0 zDN>Mkk7S4e*Ah@33JahX%B#APAMu1&$XVZZghhJeWYJkoKUW|>)0&%+%xbpkY0JZjr?)GJq(vm5R)hjeCWUW{ZDM(^ zUKhY$jmE!u`D+c0>I~lOHBbPT2w~U(^&cBdNtGGXoJPGlZCEERa%S`R*$*EzU>!Z( zTCg=`y(ShZdHIEEsnj@$u_A6D85UZBkF-oM+PuoO)Gk7@RA?Oo7FE?8k*-f$@_Uch zAc~cJ+|fLW)NRzOO1IW}N3j+RnP`}>Io%PCf6I*l9uHHV^-rgc)LN>E9zdRPKP{(B z3bB7ke|OlTWj%4MKe$v(0bUl_uo$i+0ON1x%?m}R=06pZhyM~}BKmKBkE*8@#Pbd@ zsH=)>T(GS=BF@MUu|J(((}KNkwp8rGnVLSG*EJhzOFkNNv7oa5maZ9Dz1^eSSd+XT z@N2P+`-Lq{Ov!5LQ*@S)vD+?G?k8v7b)DDfNp#rlGQ&fGqqrU7Q-eRt__Al?2VJ@| z`e{rmTpp>|@AU&~t)mOlxbZ(gF+Y`WM^~jL*iho&?+E1BzaXHNVxUX=PA`U>7S2Y5 z&_c;QH9>6&2#UQ(ezWEVn0U$g)d7RZAAd5+@VtkA%#z|Ca^%%}W6u@V1|;7022h=e z4H|yMWj%v{=}-FRbf&R`?+aAIaG5*^SMAtz{7NSjVLQYt`PJHqCW(6#H=y$g=hkoW zx=gy7FN)&0G=h8NVM@TpOnTz$d*4c6TZV_PZGQJXH)XDuk2SzGHZebacYT}5AfWks$@{^BuS zK{Gy9%Muj@sj}86F1Ld*ne&fWMdy)En;0H9Dnfz1+`I9Pt0My)b1Qd-W(mr==A$Xo z1c{+QK8x;rooXus-dUeHmvPPskhUr7js|VJ&kS`pIrXUzcZuG zddX@#*(TS`>h6h=H7_jtu4iKLYw%~QgO`L|#<^7Pm3t;QS2*>%6>aDKI`?MZO(zLA z;y&>ln;z#o9YsTZ#KZ0teWyd?bWtyX`itInS;w!+jo-9E46;MrOsmn0N({|}dQM2H z_!aD8v;2ujK7&Z`e11UIyy;1XsXrs~bi7EPM+9CGx*6K7fE?6Ll4ygYW5Jch+Z&I% zl8)5-J6BipL=+-jft6ZIxH4+Ou3p$z`a~;Ynn)?vjY_?0K_r!#JAA(IY~6Dt^2_h4 zNvb*g7nu;{dn5GFHn(ul5neV<)0 zf{yuTs{L?}gpoIdD^T(4?|Wvi`_;$w;~R4%PFb~Bt!g$ss~ElpEE~Td8+>@*Hm}H} zHpz4n7AR9M25VJX+!xu_@M>)3=DDmvv^$H0>7uIv-0G&?8r&CkOgH&ZOJ5r62p;U< zaP;#2gZ6)wAstM~L$|Bsa!eAHJ5|>{PpU$ZmFcPRf-la^!C-eYJlC&O4Y+CPUs!(mH>m zBcRYrVf0AjU`<*W?86Q%V*p;Mhj?WSB(o_Z$@&+{39-fM0T37 zvAGqL`cg{K5v^Bmg-&N!pm6iet}oL)?)#vL%qnqWidn+**uQ-hsLS#%5Z|BIu6agE zYx%}674pMX?Z}2RMvfU)^c8_$47JZ zt`#}!ypGmM%&{SY8@HPBjgrmv$QI#ec2Sv`SO*sAs_yyY`AJrIcj?G_!kpY+8+V6c zxs>=I6UHGTnol0MjP}dN0?fzSBFUmM7yQ2pQucCjEJw6r?KTGHmr|`V9ExM=(2TN9 z0OwOUb$=D3N@S9WZmU*r>MAT0&-}cDm@fF9?aTqIg&ukh9Yl6~8h7@Gyn`-=4E;^U za7!#%b-9jN8Vy2dY~j3Fm3c0Vv%dxhV`lsReqjHE6kX4vfq*!7gMiTfA9!H5HTQIP z{AmVWGxz`8=3c1lo<+QnU~sRrS{NXTfPo{*(NQ@nOP-RGp0gq)v>_g@U*(pPV2&6g z!~GSPw1#c?Fbj8pU~Oc7V0n0@eu@tv{FcwsP=JVba)pb3G^Vtyo64 z8ts{q`|~9O=!IaR-HrF9hx56I8s7M0cKCWB`laORnG4;z8WH1Uf8q+zjW4~v+cRkr z3%ou>-(DYpDh(>F4~7H6g02q`M4$u|H!cOfAcfSEbXK5SSuOpw*iBM+5} z>2&t;Tz1lQc6vV1)>h4`{p&M5ALD*iL+VMyitml9@9U%ve|Lx0nLz6HExvH>HZp#9 z{B|^cciOf*{^sOW5x{Wx3N?(?(j#b_5q~iepOl_f^DK$F8Rx+v**JKcaKJT;mVld} z`}cM>Ko7u!-%Q|!@4)VZGvI)^rM!8a7CZH$_X>Mp4sdDW5w^Vnm||Zy@%Xh}LEymA z8NN*m>`dLJW!X*i1zCs;ECGF(2b4{o_`}eEK0*ULKp)Bh9yTA20cDFPdcaoyl~|ag z{xEHL+JqW`FzCu2AQHQcI-qRn;YTn0;{ajD=1=y3p6qQ(Ku_>C*r7{6k1fGvXXZ+$<>zkz{8N|0r*PQT z)D=Ab2l)V*$0QWOr-xxn&1O}fXwnq1pi}@UHDnq;#M|HWaz4d-~%v_YyRX6 z@Xy(11-?m!^$cF65_}*H7@9xX0zUBuw!vrfH$1})Y?(dj0sJ$!FM)6RVLc;P0|5Wr zZ8G4Sc$oj-RUCm|$o8kj6D`>P63~Gq;4^F65cnn^=09{*L*SRRy#;*J3-cej0^d9I zfWO(3GT;+!AdY!AQG_G22l%!`WuRSGqYPw(DL^bX1QPU_!6srU7yx1W00s3h6U^V+eir)L_h9z zu5Rp}VZ@DOk!Zw?Y!Pjwga;xBT46}bHaznPz1Ty!5 z5D#qn8etoxfy(HBil{*Y@BG>|+O+qeHYwl|(t&JHn-UNn#<|PL0UZ<@)Fun$K|c5! zjb4vvG;@jx6f&K*WM^l1Vxw@iKO^l6sJ zVGC*|HA2$MLR!EnEYL0NEmU8p3Xl~O6b*Ze+1IHKWW@pn8C_t4#$laXj4r^JZv}f3 zTHB!otic9Vz}(vOQI)gGpE@GvV>Xc6up#FYHjtly4`09*J90h)*a9Ee!4?N{K0ns$ zC8Y7AZeOPoum%ql9OM~nv`Lr7gLp6zk>+_KTlHr0>`vj@d;t&oBf}Ycigjw)4mm<3!5RFqy&ucjz zWR2hH`+RhPxfmcz*jv6n9|NEvItUN;mb%YJ2WW@^(t^F^>Z>JGIqOkb_SFY&p@Vo} zZ<+gi^nhC!Aa~eXp*}&Tv`gdz_@L)JBR{G%GUNk{zFG#=v!+~xj$EVz%b@2rBR`5X zL!<+(pywtdQXG&Y%&lJEn-Z`G4@3rYE8X`d3#|Nw?S0N>{Ak(tCJF4!DU9^`O>-if zQA%r_|NmuNd;Px27`Bx&@(_~6x~Ec{Pvlfpo|X9jF_?9f`G4qs9bu5K`&7d4y&C&X z!yung%4A($-w4|3quV&Z-blgT*nP8)oy|_jU(3j6EIyK>Z=b>5(2lwQVQ+XuIjrp- z@3;yu?r$`xxf~WAO)Ac0r{)WYNV4 z@?85@UgFSSp3t}djH>>fUr~G%gRRdjsii!l$g)5`tWej@F4@8%X<2kcNY5`I3BGhj zTu*riPr0k&JQCJ(OG+g^JNTb&)m+4)92g{dvh(HDiSRy2za3kAbLmiX>BKFI)Geg3 zNysRbw8{TZW7syqM*jhc(I9eOg8Ca)vW%ko7cSNRW4v|Ye&S9`n1g?jRaB?pQca)# zFC$iq=2F=#>647Wck8DiYKbXUVX%SWwPsb#CB|%$W=NZ!W4GU@BD?@m)b8$WrZQvG zaC58@wq8r4Lx$r9d)#5lH_LmSs+q-QyHlg_OrmV7*qj9&GCldV0ru7V~hfdTI*?+4J7L6>DUXZs;~?DJ8=&J3k=T!4TV%-#%TxKtl*G*+s; zooCTv%Ff`!O0#hHYo5R`S-WGkt4AZ}%<8w3_XEBow|ovCuR0Ao5>ne_IV^aAiuv(y z`3Aja<$tktO3e%T2aP6@k1?KeIk~e{y;ld&Q;ny$TuauV)2FJg@T0iOXf2TAreIGS zXUCrO2uqynnF!3CEW<6l=zM`ni&{w*8( z(6yHSQ$2wS9vmKfs0Kt34KVYcBzk7;UV~PPu5+f`GRKcoyVD=G)tp)k%@%!AO%{j4 z0Rjy}`R4OJm5-gai?qoq$V|xg*7;Ve&D)s!Fn&}X-4!ElMUl@YL#~2x9(s?^T5$%9 zirzzX=Fg>%wslY);mmt4MRdZ*ETV+mtn!Ot=X7Z_Wyl z*{%^F+RfGVosQXw4{^C{{qZ9q{B6t0+=c1tVYh%dmIB#)Oo%gA@OZ`Hx`dcPKF$$B z+){p!KV2@nQLT%#G`+E@RArU-XvLsb%%;e}K-iIyqEl;)8bk#tE9(N1sRIHnn1?z?ZQFe!#EmEEHCd7`cMr;7)GnN^n{un^a?2gQz&R7!E!J+NGRco}-;lKn=;b$h=9G~5FK@Lemn4nQb)RRxb06uP^LVwOzFP17-e% z>dawz4;Z$|l;zbJ3|`l2NK3&+qf%Hcr-j>4dcQe9kE~F}@x{VT+IHYB!sI2G&n z(HEi;*%%0`L^yXC-=G<+f1lDazA7cT20fLjvc{cphFYIzz=z|SqaiOr3prNm_LsFRF;RGpmzMt75g0pfMytY;EhdrZ;Wcl?-kQg! zc-7`A;lui+P7E5comD^MXVwTX#>xMq!@R3O4a*y>^6hCjS}ffMmk&xQGQ=%;u{k`0 z|3MwC?N^h8^fn5XP7pdg(S#)Qy)z#<8V6z2>cw$d*`Er4p8^{49TTN?$m!8Y871*g zJK^=$nXw$YXM<9-*NPVfUTrR1arK4XVYg(JIcVfx5wLM*HgI2YE|7=54quRqhdkv3 zfX`vV@`m3rQoieFV!Q!TdfvauuYy5MFK9p+c3VbgGCm@ijKtKdgLVtqaVyhV~~jr9K( z?@u31CV)<00THsdhj7ML4!I1Z@%Mbr_4lup6^ZxL9H}eebF3xX7}P3_DHQcVu%MTdfb_^m1A{{xnkJv5T*m@h zyqbHO$Pi;0osXA>Q2AaA#|KR~A{j8Ic2}}O52+P{IW0?7Dgad+7(1=+jG_>o)7rlD!*b1W#-4Ha!KR-`%Prn}~ zi>2S1{5KuQajdQ31;--9b|cmh6>PGo;KlXZX=xAW=9*A8PfiTPSgbXDlL7cNxiBgs zSz%x1<9{c&)1*7w4m+#v+c8h)Ixnw_#y{i9=B7@|XhUXQO>E7v!l@PU#kvLK&6(%j zjEYiy2evR|o^u93!GC}8XbeHd0$fU=&|Gdz*Gbr1*sqg;q>`Pkh(MRpI7nlJ$!&CiB+umm>-gg=&UN z{2}?TX|}O%^4a|*<#Qi!jp=YCw@^8aRsYPWjjst?rD*KDX$ja*$FvUp#h)&V$wycV)JSq_WAZce#fqT5R?Et=x=A(cR7|Ib&y_RW%(&xE z)RE25GTnv!4&PJrAkgkCb4J$~6szbSF$zWFYcG%NXGzb`(QgoQH^vPT?{=D?C%)%z zrZX!sQD4|3J{r^BrRn}Ws(^gPc8K!R8WAo6kpgnGu>oS{1JLO4VJd?sCDA_9Mc5Ep zWc*|&W1K{t7(?ShwklTr$b3iYJK+>WUf3ZGoHn^Jf-tV_!QYWE93*BeW0fyGlS2Ls zm3^flXp(Argq)_t^!8MYd#J9TrVss$h=P~Nm)`g$F{E;YOmbUHToq6vNhYNtj+9Y5 zqFJQh!+`5WWZEiUx>9wP?}2>Rfyy_R{&00V=^`-3#Ni++8&#rAaQ>7S^Z=hVuV_)g zJ^2tn#+m#JE_m_JYrO7Q*FcbT^jAxUT>?#JV(m`NKkW>E_7TJD1mC^vCcH_lvSX;q&V4a?nRiVM#^w#k2&PDN}7?gNO0D$ zg1Jr?y{s!_$xGOyI2j?_UZ|-28SpPylUffJ0Rd`xXn^XzzVNG~Bs>KLo2q=q?T#YO z`5#Dg4BzSa2ns+sw;Yji=$vvngJgbZc!cDV5zD3;UT!<_z%8{4hk*6b^m``yas-+-SAU29r#Da?b*@G2kaCu}_VjyNJ zjZgur^lzXp@l7BdGY$8y@)tEC?M`!PnZg}eJ#&Q$FJSj4=U9(Vc=aokubhs}ThgVc zDwQGLUxu>V)o8NdQ!K%ASz>lq-9@JE4H_X07VOZsI*dofe7rduesTQ-KaSJ6WW0mz zTCa^is?z;CPN$eNPN(@#>t+E&cZ06KWGN^$3_0Tk2|cBl&HNz_(O@5TV(MEJu1)`W zSqnNb9Od^BAl(LbVYxGK8ybxqqtX#dB-_~qV_Ij~V=ehj+s(?sC3*i_LRaIku~+_M zDe$5F?XR|@?rEW%;*YMq-7*UeAs&$8aWcd!Q#3K$97Czp`ETv~Xx?&&#MxL5g{+Ty z26dO72oCj@;LtIuV+CcR;mKN{P$afr)D0TLc&Mk>tmj_^0hXI4>p_y`N%AkD1gqbT zH8tu>t<+0e!!&)nG?A72e6p}xY|Cw_e#NWql#C_*nW(q^G@uYSq<-Xa{h_~({)zOmD-Cj986 zh-59<&K2222&46HlU2L~#raSXFjk2UN=>B*LGFAA8~yZ-{&vJK8V> z>6J06jsQ{%yO}#Fbj;OOmEAah3Dg=wlAm&9x2?t&qX2KVkD>4AXzW*2%`d~_?uYsj zq0Ts5SfO&13?wr=2qxyJTSsuoJi)~;1dUNsBd#We?O`vxPh0%w1#MNPiqOD*NEvi; z(QlZ{j4CrpX4K)fe4~_X0Y-2fX`=Du5WmU4B**|?G$+0yGjnQux5^8})%7UvOdEPb zXR5ujDtd;O=IZFk5Pr`$%xxCb>KG*v_>2I1>&a z(K^dF>Xg8{AB;VYzuw~$34%w#2(R*25+o!-YjLY96Z}D#Fj{2mb4{UHsygS;nT0cj zC4a*eFuL~7=c+F|u6oD7ONfja8w<8$$mID5WPd?pQWU3uQ!Qh6{qB6a6DZ5{(7?c+`|SJ4u`*vXhfD=|sdLR=y%d25!@A{vI(V|}o0tUshv9_X!R*e!9^R?*pDgS; z3laW9Ex3ZA->qAd(HQb={Uh9LF{9%3bWqNb(Z}sR)-1CIz@XY!+97khX^+F{(cyLJ z+?UkOFAA`q8#f}uOZJ}#C?A*x=+8`H{$3B~Xj`H0ktXFyffj)jZ|(cHiZU5xL=o9~Uq zz6Dc(heR4nCrjoY$p7O19s2XHf3|D7f5%1cb&@n3Ou%v4o4L#rSv-k@k8!CC8VKsa zwtRMak4o!T^F`^rRwjmZ)J;e_y=OHqHu+83`;m*5^D$!@a@~*v zc__Ky-6QaMwb)2@FQnaPqf0BPU6#OJ!{$YxiLv-z{F9}AtKxD1l~6po=@oQTk0h+V zQuXMgH`hHE*E@2Mpk!2HArO4O0#vSpxIBI5wua`exLs2I(uA>L?EHNezK=5hWMB*0 zALcaf#dbA77>N{X{k8jMyV6hjhS#G%Y_8d$hebp%@t;ngeiO1G0ahmT#M++Osi7bc zcN7XpReP&=`8?;fRFLtYZ3x4fck!HFcavK5elSbf?3X(7P;%gh<3%!aF14L_;B|j? zkGx@w?NTzd?Y%4bc*0!2Y~vI$b#PjF@q36nxm$lLBYhWcc)}3vi;zxdO};OAv|&X7n)!7CgK)|aEpGU!#^ovP zi<-`x6!p2D&YRN2owv?g=8)61;*ntfCwcS~6KdP+9hvzxEzbE={ZmE8c+cn#^1`jW z&RIiw^=lO-TqOfXFhq|=6FwA7CBvO@1wGjl;{#QA9TwbB7?p9%dn|M@-wwbiMezXQ zme>iByqQdTDbww6Tk!) zfgDd1oC9Hp7@tb%OH@nQ`?M~3M74MW8wmiIHo2a+#%O)HeRuSKEoC&~ z0T7u0=t0y)?au;;I(NRk3tBg3M3W^Orgd4->Eas!b6dGZPWZGwvAPbfl)uEHXZEo! zED&E~yfA79AyXh3xm}^OAdP?3!$p*;o@{2m@Y@BeMMH7hpHK}#IAxA`Le&l>v_J|F z(;+UKlhKQ=7N;1(#hb?xlTRUDavfF}sma$x1XDj~!Kr(EeSm~Nt3l#5gNlO~3Bl^6 zH|2-ETWNFIYZ4=?tythjxLR9y;DSL|UeG0ki&|sDR)6660}@_5M6wstn4s1~Yef$>M8^~by{-bb9d)_3;!O%NGfN6kJ7jGK z?UoirWuz70g#3F!Eub-A?K0QbBhL~-r=uNW55m*J5lzL&Au0ef4^aAJ`Tf9*q_&3v zm9+i=<6qi)HiQzQWq`1vZ^#Q5B6P-y3;MsM)&U48T}mF;DMep=?tG^rh~ec4UZbyS zl*&*+m29wSi}PMtkRY2A&I!na6*EY5lalBF>P79vjGLx@axEudwFuVtJU;u`{0J0!ovTT<9 zXq7tELWL2o1Qu$+0L&E%%U1kL=zNp4`!7TdmJbo(=OBGAr!gE*J`Fk~>m;}b><9wb z5!4PO1ZP69C%fA4cIf#5t!f+e)DU{a=0vpvG+-|%U}c<_0pwGJ-V=WrYTYp4<%#P6 zrNHM`Okpx0jz%v9JTDY+!yFh{+kok%{N_!+=esA*Ka%?D@Q@$4InJVgBxBhD_}1H$ z2u3lA;6Y)wDN}?BVm={Oge+b;1}C#GsOinQUYND1bf_z4m64eNry}4-^w=h$Q7~34 z6t@l~TtrLhIu(Ro))Kc-I2$!&M+i3ggr_-#&O-w99v?VNqI6JlX9a-L+Bn}{$V_#} zaL}fzbNw&9d0k<#bG6Aguw!F-qne1&Rh^+QPkNq?5cswlmPTNZ6D9^2F|xh}9BXo& z0gRe8V7cMb95tpKkONd09g!gAecI{(+7y*g{V`yeZ`Ym%poEdy+siG|SdxmL^Jdb5 z2g(UNP#B2;`VR?Eg@^>ADT+S>pz;vtoR-0a;oSJ&gz*C$E(v9bl9*k<^x?PMaX&nz zPWf1NgsYBu9pax{)Qr8s1EoSc-wtiDfd}6xe?ilHXD9}f#n8C0Q3eGF!b0EY3B^W%;D{yDPS~KT*H$G9Mra1sR^k_X8 zp;I*80Z^IPkknrZRGiAPjle)k*MbZ`yF>T^Dh2x`SOreFZt*k6MBsGs3qH)#+B}_j zfGE)=0wkq<`J8I6cBXFiuD#-4r21!Un<@4XzFwcqj3cl+khqZY_BP%+aFytqkj9WV zjOe~7=oFRE+%WitP!XA8y~P2UmmMN^M{J^52{aITpb2mgxqb)ee?^L?F@w;VMJxXkTAN z&dw#(088q^Rx#pq`N3%8j)US}p@NIR0+Uofx6#NzGSss9JiTbf?HEM8-EZi<^e!RM zzyw@K>YP9@-e|xBH(3N`dt&JT(?wourA2z%+&ni!0Y~xuRJC0ARnK(Dr?uxDNOW=l zwA8X0HgIur9AK2MUu1%bZl`sd0j1D#`;KkYoQ+4;D=4i45rY5n#VE3X30jkP3|v>i zJR#GeYjb+8_;>YRgzEgJ`fYj3*0gDDm7D7-xFXpl!<_w$0cYnOII`S-A#bu)|C>Yy zQXp~M+R=Eew8%EW1vDGXqbnnb|B@1NAYVE=5wGR&uWp)>TIdb^SaWq%1$rI%!yzdR zs5KZF($F)W`4m@Zz za+cV&#~LlgV94cxQxss=!KWjTrG?q#-sN+MB7+=nxO++&(N#3!1gdI^bi)`&n? zCj~}c|05)POgj?qo{k_CvGW0#U~68qvmsx6P8g*Npwbu zw7f?Lb}t^n4x<@TAnQA3?f7vCgmrE^I9?(8pJYkKRWoZ;+cbMXqWf8keTyK^^@b;%ee3V9ZAlG?ccCGdjIWaJOG?9ZodFjN8#{{ak+G4(CgSZ$PNfbVjkr-{aLUfU+d z+4A}p;@SN7OTglidHK@Ty_T-O?E$$`>)KxXfcBc2SBK+sxYCMhC)#6<-cDOr!fEr9-5^LjMMV~ZA3FAGhJ;0oHuCK@ zN!4YhOR?YL_}vERHq=|VTViJIKQ6}UHZ*v+3?qL#u$5|Ybz3G~R3M?n%r2GRP16Z| z(--2F+bT8PA}qR87u_N%>M5H=lR8c(+?Fo5OXPoO!02Z^S$5uCMqW6S2kYBaS2%gw zaE%R5wG+0G)Sp>;%A~}Sx#m6BGG+RYm?w=aZ?$SD8s^sOcH$XI3wyR3z<(zBuV>d7 z{!^BIY3RORc%9H1U1m!8{6FG6|FwnXHiS--6a@l;4j%$S=YL>DoQtEa zxr4Qr`Op7*NnDY(uN{^&Zm#|%zwJn5_Mi_H%n1}qYlx`)GgFZwmbf)V+wY_YCNE)A z$KFAwelHZx!=u?0v~d2x^&)gS6>&Af?&I+4keWBA&)l=oZ0c=Vf0X zN4i#;mfR5BX*a;{2QD&s!0le%HZyrX%1dRU|GnJy2>;yVA;05Ea-KOpEbxT+N}GVi zhsa|?SqT%kCCg(?yMP5cLbKZ-T@TQo5?Oxpu7D27|9N zs%4;t{FjtA1VgPPhpJFJW9{(bwwrwfn4gk!f9qDGfjf*62Wz^W$U66SPC{NiKjH7} zxwPfAeOp~to8g)ZLqU$pfBt>PwcpIC+)YILq9~ibA|7qCD8Scb(?Bua>%qgw6r*!&>c)q_49tVr8qx^bwiOW4sKO&P` z7p8%O8&UqAXaT&wC%ImyC1>|LM`ruM;OR%_`PuE!qO5wN0*8`Hc){LE#s%@5#2e|> z_h-`|m(>ApM)HQQe&z2=B&milaf8}JBJ-XTJ8uu7K6u3t)d&*ouZL#T)J&WsZh^~X z`(l`S3%+?`beSByqgXu~9xhLME9Br)zc+`Cc9KgEoyiWDtAQ{EPU91>v$pE{vYC}O7&d`6MD6LH)1 z9#(!5Eu2MTSpFd>^G3z3Upi37}-25}q;#aSPzqlJ1*tFb7E) zJ5hUpyB(<4I4z%tfMpQeC%wi7)3RjWK!1MMf8?|N6|66P^OsirJ;U|&vZ?Evz3z*+YGWTn+&0VUkH?=bw!94OcwbBbNvoayt*3i@Ckui3I+*W5Cz zuw8F&suMF}2N;KayMGbLY~gftsI^~NpJ$q8$+5rviBAf(VB{D)dc*%Dba|7ev2F*V z)`c}OToqBM_XoMuGrQ$xwumMD(|2pBn49ca70NEIEKpz#)bqZChOAl2=L2hx?e07O zQzf=^F;y6%Q*&}tw40ozQ^PW~`J#YQ8$4tRT35BWSlOuatjGtQ;5YF1SLS@HV;Wk+@!Bc{vEJ(q0Mc+AFDY=`>^~+hc-L z%nF^4(sdEIjiLO;plw{{)cR?dGD%Vsiy<&E^0x!_c(Y5<+Ml zbfW=_f8)XjdyOG(_oNM}!k~91Y7!~mcPtN+P(>9s6&grFS$3E;FcTAh6UK<2p?7W3 z)S%HgY$*N88Y6RY|8aBH=JFWzWf|I<>Com z>-gtYIthEMZPj?DQ=O72?zqmy*Z3Q5O2nQa8OJkQq1Dwna6jJC9S+79A$kexaw^Pa z{H4?|T`2v)>Zi&!=wBaOq09+72N4wd-x$o&UKDt&oOCvQ&0$k&Oh_(;UBo-HhkE# z6CM(nKCMG(X+WKTdaG}1B)s`f7T>Q0^--l)tuaR&m_HfccT||l&M>Z{5Ncj4lG%5M9CN7ydCRqcmE}lt&#=jh8PJBtU!o&w20XZ)NS3x50VY@7eu<4Pi)&IX5KIZ8L`l+k2x; zgW0TuYT_nGt@!-hes(f~{Cj~K3hSq#TuK7Zk6Sd-zl^Fqj&>t2`#t)Kl9SM#1e{)L zJuGAZsLG@#IM6&!=xtsYProI_=y6jyBewi0<^QV65Wt6zAeNB1X1)hx-gEX&hFLOL z8C~u^J-D@KsdRKYqy|r%c>=2IZqKtsJ#JcrM>~WJ42jlse(O(jo^q8wno!BC{3&i- z!ePwe`C(K_rQ1oR*|}$79};+Os}ZAv^B3FN6DG%sY*8A9j)w5R{^+$uOI4{Q?@448g{~4>}!B77`Y?jq4$iIWZ5_s(y^wLHew0CDoN9xj{ zEBH4|)+)NH%!TXzS7b^^>*)_Gkv{IEB2H#+AdPFcTzp>o4nrk3M#pX5Rtu8p`P32p761ygAfz2%c*0sb5QL`m75w@&Wk^B%Cm8~o{ z!H7-7ED5yn0=Zv4DS~8Ko$;uzKdofP*9Adq?lhPBB$r(f;7x%Wq-a}19n)8b)`6z( zG|DJrK1H9`w!u695GyUg0?JB{KewYi&JrU#Qm>92T%pO30iUvq0V8YPkZI_7W_~-G zJOZmmy2y#dA)cG|b4IdF8?j&@@iq_GyZ8Rx9Cxlp6`T00jB*-C4t-#@ zKL;@syzygf=f=acW2A3PAa#*u1)eBb?-#)xOZCPQ1qp7*tf9SaWdkSIicO~7@m*v^ z7?J~5wZ=9jjQw`2+hk<|6h-*wr>yiqw!>&P|7sW1{<8fcOqeN}jK9#2@mjzP4SFY@ zw}FN3all`*9WzU7ZWD`5?x2uIt~5_fzglR*_`c;skrT2~G}efd3X_*|HsAZwZvOI` z$Ya~#J4Wey#)6jxw)Yc=`#+UG|2#icyw|{`c|14J7cQky&o=O>;IR+mv5?v+F$_y+cPK9Do)w^s*I+|n8 z4Hkf_+BkOJbL01oYHQQIId&guU5r<-Jx$#X0h8mTt!1_s{P{gh%(NNJeqF6)@4*~Uz!S| zc&f;{Sm~jI2_y>rD&4U*At#%oHHO!U)2bYMqnzf`AO9A0u&jRudCW?;q8VGy_t*~G z&yv|raSCW|MuWi_xq+H6B}-8oZTp>rI=R1HqW#I%U|=wcvi^z?{v*p4O0f(fD59V( z;SEzdOr}^+LL=c=jyPG0om#R>8r^91_&5&poj$cEP9lkAny;K8u5?U|N@LVzN$jGW z9>vNFDL8ba$tjAofI+%!jE;*{Y9dP0IdXo0MFWCTxkMv3o6hmKduRo2f=n%P2Eo@&iaR z28-yaW$QMu3CCe8U}B6^8?h|St&7g8f7q*QMWNj-){PqoDv3IuZbAyipV0oN5X~8=f*wqhIEVJ z>PiZ>$1yn&=?ES?z=oiPsO@XyNT+RuFF@+??#oMo|kuKfn$mE7Qp6Z%^ANNCvNHr)YxU1 zbJbo@xfeA5ybA`NSdkqCocjzZqhwWur)W((vm_owsPrpk(B_BB$3ZbZnhhi9dwpZH`ob06lCbv*9xdju1$VAYTj0dJNu>mSvjGJ z%&^GYdHQjdez4fagE56lIvsUW9gLtd5^=d!; zFuoSPwCWt)L;I1YXH%ALO1QdGHcpYzzRkJNHnNUe88&whd)ZKaY`yTjZLoX9;#hw< z|BSOYYaoZrV$YNBu{`zKEpHY)9bAGt1b26LcY+0HaCdii3GVLh5G1$+5AN;+cL|!u zx#!(u_q_G3@7&k3dS>_jqkdJpYpT1eOCsdX$kM}$uTF0iPb_#`oF&|faN4%38##On ziBJnv(rIK5+tE zZo4@dh+}#pe!bpBObAWs%6YYa9BNrnIu8*lLbpgLo@34P|K`wNo^*f%dumRndz963 zqqZ|$?r>Q1wTSN-HyBd5t5@rt*nu6n)7H)5fnkpXFfJka3q4M3J!OAeSU4raT3|tg z@wxI1SlSJmqU=mLTDR12jfzamy)zCka>EbD95cimK?;vKFA!9+Ko04q!0^upt>`I| zqpzqZD0`^wUli@>h)v9-s1P?HICP1xBF>k`apG7SuSg~%y)+$Redx|t&_hc{RTQpz zBG`4QM9i1**Sw+mw1xudAY4WjszskBuTR)0v}x|^0)cIlJYzf<$Qa4^e(C_G9H0|} z!np>u{4An}tKDEnu*Xj!d9WpP&gf%{u$e$JwP<@*7SqwSUEQtc`_17MGt$liZ#-*J zureAFm5p^+(U!CjY%@H%sd;*EX^FY_ae^Le8GWH{88;*ulmz1lB-=+5L8Kdj0?d6p&f)`ysd9su~?_c{Kxy7M1vF59{9;aDoKsNV5(ZAY_Y zEmn;RPM<)ruVb^fG^C*n0hVki#>{Y$qD-RX$i7G|55u_w$gyPKgTipyLR+CSnoomg zEclu`8$T((_GUZQIFT*Y`xif%v%wLrUAKPEZe}ed4-LDs*2al0OJA*|_(?~Rcy63T zDbgpULNN|D&3fQ}D`=(?ImRTH0=X;nzOY%uoTz!_caCKgEQy3+_kQ5CVACQhq0EY_f%uj$mlqTOr zg6e&@klq#N94><|`qqa57A7{xYY7>Ql9Qu{TX2jCsFoB-1%$M_x5@B)T7Jqm%?Enx zfIKF^;Dv{VgJYdc^(Ueb!~(dWjZ}b2kkz7^y@dir(&4*)xR;D@Y~jGRyX}*t6=Gs%f;i#wDAL6@F@VI%>|i6igbzO~ta*wwQ+xmL z_F^E0^Ex&(7I(pjV468~;G#AY3mS4QAQacg%!3qERJ8bgg>pu|ZAfplwo+jnd2FTd5xmxCydht2#CZxz9yfCwO?kTKmQY*B{1Mdsp5Ey+ z`OwNcH{~2h&Fe3$&-u@`dLjsyNxSKby_CcG2lgY6MyJZLv1g|jNE}NtOji+)VVc<| zR|~MZP#iCc#4-u4J%#qgo||})SOX2TZJjAEckPjwqo3F= zT92J{y~}wMG|>8`!f6S!j=}?~D|b++Knz6Rwe49LNE{{XI&oeQ@ieG&E$hrCc+lER zMT~|MLNzhN>PSc)?#UQBeqDS{JBt!#Wo*JcSmtAd#-|Xhv0fD=0iic0v?60o$1Y-F zxLNFP`DDVvqN2HkTP^$do5E;~DQ79ELI=UEoy_Nm1 z^m4X#@R5~5ro}|0sfPqnRVhv^hWi*Ga7~u$qk%4*jE9y#Jxvo6b1DOvpwr8rt)&(B ze3wC42qm=J_Wg^N4IOx6HEMUTb7EB`j&q8RVhGIT6wRb*UvtD{AYi0{xi)#%>daruq1@Nj@e z0bQauOT~d?@B6}-x#Z+Gi-GPdSbXtVoB%?6DN<*}C$ll#AvFeyIe(;ZGRWuoiDHx& zkBB)vl{#n!ttExV{%s-CYkJh~QPY?KlPND=gvMYfh3LRU%AguR<8w@`9<*zlQw1xF zYQvFSh>&b>-jS?2SR}TI2B|RIGw27)E*ieI!=4lYL7Dn_M|$#Cx!*_0S>sq z-7RtFYbw@vw!0DGPsl@k`oP9@&bV6vmJ<0PmkBS2P#tfjz49()G@3`JJ;ZEoigs1q z0>AkrYz`XbKA%+Sb@iTYWO6GCrBBf&Y0!an@&p$vTLq?8)?~b^h~&Nw>D#M%`g-oU z50$m?m~M9J7Z}G&c%UYrR-7Hi%a*rb$&2b9+Kp(b=;ve+`pwTTnTnY=El@W(l`7Vv zljX(jF!Z06*91gal@vOVF3Tq3dWyyO&F|KP3e;P406fC0ts$LbUKe49k6)g+*6{Oa z5<0#4vAjg-h-hcH0hS3hT}S9OYU|>@rLHyub3N}ev0l4_6ZvgkTXPK z*dxCzw35s|)CemPFdo0lg&5l|_cwJilKhg#RMn0@fS zTW!F6+J+pg%v?~l*87@14?P}V9xWdxYml|_ko@jD#0W%eiTrK2GW`{B&Aaee+w{Bs z!MnHWm#>2>goKP(@(Ftzz%SoU7`;_g_^7iNj+;+z5vim)VC|urW4zJEWc37;e|D8? zGqvu&6SrUX>d?gQih7>|q^lATl7}iWaZ6nx*A@2q>AlO1coWum8F4lamh4WWm6d~_ z3v%!5>eP%3O&F`p#SkYW3I9W1+CaC&)Vw&;XPA0~!>!ax&EPsIgC{mWs>;~$RO4`% zI|Z!gjG;#rr1>**f8@-DxXD%t$FeCJdER?)rwn~J z$r@Z@AqZ&o+{Tv$RqF(2Bw_@v_;`7>KQwqJDsU7$E_A=n2tM`p5NBo&y$1MUT`h<; z4#Xog<rQSqGN|$MntfB00rIMkZQ`Whh2<*KuI$>T+qqZzDtl6Zn6AV8eSWpP%3V=k*SEVyBk52WnE~;E z2Adynb3jF_BO}`GU*b}s)3%AquE?5vtvs;rc!r)ed~w)T#{)xo-i&!PU5}*h zvCkLCSM^P=Em>>NI_Kc%NBP`G+74ux^~E(B>!Pv75^YX|eM8b7>LT%W>T9WGw+Atf zMPYZ_WO{zITQJ8McJn52A(Ouok$m1lm2k?dq8ig@V<#U zHOI^1h&W$ou?(Znci(vht04*Mpzbp?*g`Ns%p{%FROheL=hph3{5`$G^iwZMm-B)t zz{(#pYp;Z1q$@y(L8V%L4jJqUp*2=MvF{x*aBhFK=D}^gwHtP}+-&oSz1%0B&&^?l zq4D86cX8Yo~ zvwJcK`b;&K=*Xc~NQtAzKkXU6<>2FFG`|-$Frk(3~4JjaJo-JGEpG-vjn1)X@ zN%MAdu!uoBmIv?N#o-qa3?;WZvkVTteAT9+>4iWCO*fvr5uJQLprxVRZadz!N0bna zNffMNa-MSSP5?ja7=Bd40|{25R35RhUTa1PyH8*->TjuUN1k<%ViW+hK(AETX!KjC zd_PWRaXa9%_0hmhU4_s*H)r0R3GoFz)zjQNoBsR)237OHWj?b-q=Y?4oDS-StS2Av zos>$X&mQAxCxVp<6eB*sU-Ls@h!Hw-+M=Y&E7*hrD;bCs*VH*cN_z2Clu$I*0S{^L z3CB+(P%`JMrG9A1cH6YyS#!6eB8KKo>vP<6((J$JZA=rJ+#ug%%R~a>~qR zTMG-Ni;PZDE6HTid0)wC1z9P{_&I;GjR7siFUd(44EEiUz2Nq}n=P;eKAR=nLEeb~ z9s}Q)y6?1uV4yuI6sJHuQa?%%hj?5|{#P|f15_xTLGAV*gZvnBkv!u}yELh4A3k@J zgDrl zo0ZR&d}5?L`EzlWko||}!$X8cpNYe}4;PYrv=}^w>FW?yVQds3>58XaXv%?uA2Ol2 z^m1s*At*53dY?MyE<-_1q7LivVmn@P79FVZIW0+-n&?tk!0+t{jIv)0Eiqy*+P&V1 z*04UoQX?U!R)G+(!fMUjtU0#w2shM>KG5CS6BV~1S6ui>gH4lF3OcZ!%U;H?9Bvx| zy3x|cQf$vAr&s~%CxU|9A<(iX8xl6q*{Y_W?Z8l|qTyAur|K4B*}pBg)&ZBXL}qiC z;Slxe5Ju$cr@=01uRdUXj=$P*CG@Ym&&})_2XPo#&Mn)@}h8G|Di(P@)*qz!_X@O@g>@)pk?m;wK=sMI2i%Kie4{P`sl)6rBmn(I9$ z-_0E#CrPPw?Um;vX#D5?S<3cpYl5}+Zygc?$RRg>RE(eD9~jYU=pA=2b)vTw1UD1r zibZhT?gtukveL5HR!`qxg@K5116NCFPl!{dSh3(8zH~%f7w_TOG^#E(w{n~+T`~8> z*ssljK|y;5+@VU>8vC!L65aFsqu*$Uc6EqLT}y zGnt2n=lHPK7fZ%IR~zslKkOC>;JRm1l0|EM%?OcB_nw|Xe7|ZQ9?@!jvJfTY3^z8= z2-^XV5Es)RohsGpQ_43=gTFgOhgR&bW9hckHP-Xp4ztgT>X-E#!p@u%kdM0gW^tRGImCA;XS)`L2IPZGpL2mi2BmaE8 z+v#%5v$X^K`o=7I{8N0gRG*?xoWhQ|H~7J)lcLuhX#sURkGe&Xpj^KM9(2h}C?5zXgV3(8 z7%>RWwvKO-r4-6SV^a265*B%Ybd{OHJ8u&(Iy9+w-jxy8h?O#DNs^U!R{wn z%W)vW+EzJH!&H_)5qwS7`m>>*e0EW9NMI7LRe6X3gtMY*O`^Y1BL&{z2Rp&`;e(ew zQH?345QNd;as<~HuiNF|o_5HY$-(n^|80t%sP9XrRL4fcx9K&yHb=d_mc@CsyAyr> zr71+0p6WP0s$C2@Gb1tJknzD$-sLE^+2owuBIKQ%k;$SQ!+6AdPdvTZ*=I`+9Qopp zmFD@x$M3N1>ho74)RZ3#4?jX6M??59hbV)8YvuAplFU$;R>B>I6{5yijC}6r<8{_m z+`%{jVnZ04k4UqZx5jM4s+~H{J!IUq9vxnVKLObaGh921GkZ-=5pL=w7-#W&Bo&WM zp2PxFgdCW4n>3~HMzdm6Zri+fc+X=^MPy>qjKs+@^@e@A>oCVdK@Uk)`w8kY_Lihs z+^Px}QE;m-sv~w#3vV83ncM=$l?-e9@;)H8~*MraUO_G23F5Ybv2dZ`!E#kIPXb>;{N zjz%4Gbk#x#Bo$zfYV0+%wG82>l^fpEa5#ts1nv}8Tm=QLPV$?oGZuohR);cTJ=p9#B_OGFOes{cMJ?Ai2B+g%b z)*UH~mo2gjG#L@!E9fa|ztv3-rwx=uU&;tsFI;x;B zB{80mH9RC>onH!B()v**1l6j4?3?T7sC}sMfzy3!xBTZkv*yb}q(LSo4P8R#F#Oru zq~N2Lbi`rQ0NfPD)sDR9xOm3vaFoeH>#8v&mlyayn`H5ep^?)!bIkcx-%a2jn&jWg zy4krWY5!E+;Y@zbDX|k7BlRc#cAjNi65@hOHkC+%JxZ>Sia?GPPedx4$qF=WaJ&<` zV>*Jd(6OqvqOkA2=Uyp6M0?;LR-2xCA>;ByY&dK{{%N@C3u9_;^eJ1nxZw*^4!>A} z+xh0$#zoJ~+4+>7P8Igd@Q(^)1|3%X_fG|G8_*Q@1C;C-_BXXy@Aa*M1{^s(BTOa{ z_*wTw1ZU)PB}jb2AqzrlF6^P@vBU><4!)Zie9Ip^z%*oKn*Flos*wQ{zIMZk9vlD~ zwSOxPq>L``+|g$W6rY^2V+ZmgZ0YYD-k4wJXB@5bH&kRhWsI~C!0#6vl4eHbyG_^^ z^(P)>l;O<<7 z+*&%nfJA~jFp4~8r*s`#Zoe_60^vhI-{t{#7?`u88wPH6yX`#i)Axc)vwE9{{Usg& z_v5@aKIWgF$5J$&U)u=7;kVsg{rOt;__A_uEBN#Z z@3zrJ^i{gi<1U2&ROKE2$B7R$Py3fCzAxvOU9aclZhVzl5Cj&-;m@byiC_sWZURsnrE`M}(xhsZ6Z0_ci$92T=^ zdi|%iQ-G?$G2)HNJH4O1i}hmUFn6A$jH~aL+|VZU6M7&c>&flWu=^gK$@uxW+;PW3 zBikPJ9=-CPpa)$W%osCs{D`DZhK5HCc=;b@2tE5&w=xp3K)Kk9}75jM7v(D&so60 zeV;Eme%594jefKm?q|l^n^x2QoI*C z$cD2WsNc>{KF^@Fa@5|gVBpp@xPf0`Wiij1aIRQTyOBdEhDxazkd{vKD zV3~6K^Kb(`6r#T1jzjguVDHi61TBBs7|-_rUB$Vdp`3>WyS_7O4^Sdy|M7z!Ct9yP zoXnRAKi{r%XqHmtyFB4^a<16boHEFizsfra^gdjisXnm9Yh^=5IEt9K%bW7GDAjZo z(?Qd&hdu5VjuRg;7sWTd__|bhdaq1m&~ug!8IBj9sFf#;Fhf)8Vbkbg(Y$Xf z-DkNVSsC9YoN7YCV8wlJFAM;SQx`b3mdk6on=he3zOQ(CntbBF=|t=}S6jb*y?wAr z&j5~XKw-MRC?l<&ESSCeN2_ff4&Q}G32{!5lSV*oy;F}BeOI&NOMBDQnh_jWVursl z8)bjPsQZ%T3`Td`5?@M;BYI!wU>cjtt(py9nsaI)`D0uEBz@wyOn0yCE7s&KUX)NKQfGYU5MvxsZ7K3dOTbuZBa z*Uw0QnmshZcUhXM(1SbPN=)BR`%+G@vFM!Au5k`j)>mp)f0RZII((>3YpTh5k#kVA z`dL57GF1th(wuxTlRQ1u<%ws*auNPfgs2ftI8u7rFL$rJsimU5$eJ3FX-ET1*N!UJ zUt_v%k9o#W)1{1Jz&P4kvtM{2L(X%img*&BEH`|IIg;k(bK7Zu83zUlbOw0_yS3UQ z0;{=p9J5eiTBQ<9m0?727LpbrPk-ey z#riI%R*se1&i)gwmrMZo;%K=>*3x3fpLJLZVc@UGn}v287j!rtO@|0)i3OicIGu_u zU*?9y7qsIBs|vq5Lpokl&*U+575jBcXg&50q(?Gdi!>*od7+a*`&Lai9IIW&mlImY z_Oq5}j;&RQ!;5aooTeS#su$c5hdOKf_Um3v&g+B7x>j6+leg2=PEU=`>sz#TVF_P9@goV80rLA?GogjIEii?GEZ=ypJg?k`(jckgsbyn4*{w zU+}SH0=if7N5QMLj>=bzvqv}HH5hDK^DU7rKgdOM+;&7eJzypzuHPHwytAX;g{EJI zP>5jb{(2{isj>$NW*GCqHn7oN%$Bic@FLjO?z~r&12orrGM!!B`4bTgT)R~R(g(XC zMpz6n-&fwK???jtVmmeFa?q~}L^Ez^#UV`wX{u0Jh>?r_IEDwjTD6e4?AZ5{S>Q|g z#_Zvy6pWYP!4}Z!kTc0*n(?7ey(il-=cz5!fP-|5MZRiYo4h+qIU0>eczLE8UGis) zFGkQ@#R1M|`SZGtuCXZGz4|8J@*_AQYwb@BKLkq1@KhOrlczz*Iq#jF`r2dw!l26-hh|#Hvl~e_?ZLvaoL87L?pqYbE}UmUhEg2<|;chLT@~ zT=MieJ7s@RWs+tgcyaJB7Fi@&yj5y?D|(>%6nc?%!os(;Lh3nVxf?opC6+@W`NRu+ zOn$Pr(uwGNbKQjGlSH#1H)33xRNzeJ{B^uiup3{i3pfRx_9_6 zZy2P2%exV5o)B`)=Lwwd$;f?NH8L&(Cir&^8vY=+OzVs~`UqKY1j`wSR^x9eJ<>hK zyQ!Vn^Hzy0ZLBBiM369KvmOH>**GtK%fV}=xJ3j@R?n@PG`Z#5HaqsGno%(nodQyu zreIRA3v&7Al*8?MJF#juH8LMGEIkj}**n&Uen?I9VAWJntzr5&AJ&>~dL-0@-lDB3 zE&QfYWptV$?~dtt^0R4KA^rLB9JpdTeezNi98@yn`>c%cT*1%ozyl7^}?X zJSWoz2<2E*(y{_bRymoNatfksVVS7iVa!Brv!0P2?3+Idf!yqQIVUJh45UB7t5KCd z&S}=_l;f89QQRFe?SR=LEiqa!d+O#!u_w$G8?Wu@V0)Jq{__UZ^=I>)K z>eCO(v4G3r54s9NI(l83!nFf>f^Wj}c+k{6xBZ17UhC9LNr(vk?v=odtS&%JUoNCg6b7evd$Lm2lR+HflIOr2uPKU^8e} z#2rXtlWo(~ZGcr2sC`!kURwbpB}kpzp+%G2ET%Q|(?!WuKKCXL_?#%_QgX}uQ6{Eu zF=W}u*L1;TviSCNg)ma%!8Dk&4#+A_SK3bYjN%$S_I- z9Dt^l!%>}q)Tp7cgd0sYRQwih4T&P1UuFvQGFt>9rYt6x_I8=5GZZ5p@@C8&r-fgu z64H!V5G*kKDXem$0dB=03gJkkV-BW5>GkU3PU0=FQQF0Ii7DS5O73>JE-Sth2+x{^Bk ztGFmx8x~f0@~u&1fK@&(D5HHjjXUZbAu&W;fLCF6fz09ND=K4(lO7ewyhybfQEfORWURnCN9%^k(mlK9FMzhph+w0dmfg(!@12JjYMIKbJ$)F1PCe@=dq->_ zc&q7CCC`%~Vp-u<>Pr^4cx9Z82N@V6`WP+T6(`{Jx&1c8Rc~>aq^!BjoDv(6J&L0R zNBp&4(nS?Tbl1r~LtmMoQD>Nx@e)_i@iBpduJL}kk=aDBH!6G~b-eWJvv*J76aipy zZ~=4#uVx1f<9F9B5@2~$;j{(2^f9xc3TrJL+Ov<+&5X8DG@1q`iFz!Qn*9NH!17wT zOx-V!H&HO}awgCN6^v5!${5`$N0!3`X&#IqL>x79Da5vL08*d&jKIg}!r`enR+>u@-9Kz@Cs$ZZuPix^qINm6lnLLlQGzX& zRoTWoOqCIemvTFUBR}u;6ONBZ}c{G1hd0^MTAj8-ymMp7!b%)-lh)N5u;5Vr|Z?;zqAhdRSG ztgFBvVP4(9*`8Q(B+F98EB?r<^dDtBexjh|gmmNkCAn3SviD8;g4(P18cO zK3ITSjCge|0r%Uoy&c$-0^@_}6oc8M*zE|j8cv(N8ngq5D1$quy;zG3zun@H*QpB; z!&x$?sKBGmLwOIC$fLYKT{}v?hDq?3E5jV_Ih}-qM=qa}?^4@X(iwJQB+q@hY#Gzd7@Iw@5cbdi!!bQ9MHWOU#u zx*ffgye>6{@TXGw1#W|N#cSIbZsmOrbj$aI_L^{?KyV?uQEsOaLcs5(?R|*M9^ua~{R3i4nS*Bb~b>wI}E4 z-bvK$3PZ(B-C>E)xzzLpb!tXzx9939HS;F-g{#+40H3E~ZMKDV>v}UOA-d3VZpk+N z<9?2WB<8eJcbLag81=F`@g5{8G}YK*DoM94a;MM{OPV)$eNyUP;(ri>9$rZ%we(~_ zL%ve;h_qiHONn^L`G|3np~WXgtCfE9HK_`I)0dv4)@ArpR0mJNcTt zmdT{_cxSICRUlKabrCEC?x5-mFeC>*Q+raTk$9>vur?xFOzrL}xiwWk173AGYN#y` zR9aJW*zWVBL{S#1xSzKy83mj_0)3H8uk?5TBxP7j$V0m=nOUm`9F~8?S<7|Io8JvrXv?O z(}?|yr}5PnR#i{RfVSC-N|p6$!16gyUVBYY2W)sC6e9qee^@896;~@axx%?`Lo|OKt#^U- z!Gg`n3ggnoDh3Pml1>VU%4cmuZfk&!q1$72`tttIo}^^gRw>B02;5F206_2`dXj!m zhkSD-scAXRi2vf=UtC6P#4hl^i(*G4!b&%%x5ky`Nt~j*3o#>>5sxQg^tSUSMcAEZ zhu;O=Ga%I77StzBTUIGm$&#k;_KUm^J+Q9z3Oh1SjgTYj~bM z&1?76)Y_Ii$-Bg%tJCA<<*brV`SXJ5k<9z~abBvV(>=K+%NLcX)eacqyL}NQP5U!j z&Zj3M*Su3mil%`11kjQ#7HLjz^mHeKX6~BI}V4CB^k*V`0^46_EU=i`I60``$kOGaCLUF zw9+JwD`ZMiNcCG^cO=1~9M6v@0)e#^j#VDQyW8T>3`E&OJo)@cN~5m}sDv{P;=H1` zcvqBRzEf|lzZePkYV9()i7UneoQzn}_laEl60)+c^Xy_ih)A>Z7Oon#Q&K^!GNqgO z#C3EHeZwupKqP*;OKt>yZpwjox-ZELT)DviDxdrKqukrILuJl!mY(j3pCAAY3cGf> zrAbIy87U>I0

>yxnULmo~1k?G=)6fQ4_-?2>8Bl^g>!Zs2a!vP7w=$u%=D$)qH* zA&SR!?Q{hnR}jPmn^hP@aCS$ig(;rFcn@xLYk$*n4+CyWPw%QK=xCAlVEsx1iT^vw zwv0i4_n1fZhl5Ieyqp7Md{F}vIMV@OHJJ;dSEeI^vUjnCnhjuC7O(p=!WpMl*OOQM znzw@g+M`bYcgU|?U%lqie8j0MRGo{Hc=d{z_+&;Z=GS4P;;(KWx6$BB8$G)-I&?_e z#7a$lG@6^fOFm#Pl{Y!#UF=i9k~5qb*4qVFR}KQ(t0S-O)-Yre7~Sg+%)mm z-I1Q%lLc5&I)uo(g-YdTvgYYE9!r*fc3*=MXbY$KW;n!nJ5JG@QgV8WX=Li3Xk#xg za!+1E$$HgJDG;M_1`=$fKU&V`kOwAeF`1qi!p2*TpgS%i{3=T2(Zz#`CK?^zq5bwQ z!Rw~`x%QYXs7b8mJN5ktm%8KG1Ur5USC59BvCfCI>9B_T-fT~R>ao1*iDw(DSjEp4 z(Cfwl>9mQbr;Cnwwj) zDHbJvg?RLWQjHGlXLGY@Ck$m2UH%wJMl@uvM;l2={{$1uC5b9DP_WXEWZB zMmxx@#GGs*&wl4%tV%|xS$^3tF8Py~4|q&H*9fNk*dbnpYQx=YamjV{$CmJ{Z6lWO zt*NHNTre8uQwpOwMl!aYFPS&6SwqAb#UcSh6F31EDpoZgar)=BqTlN#*TwHmvg?|h ziF0kWSECc!XzCkPyWe05G2Hb{>d8}QVYJQUYqhMj%`YRl+R@H_2wpXxGN{)vzXL^y zNEKXZ0Oyts1bM}GR`wettK!?r^->D#g)5w2Sf9i>M%OxLFWx>*^tgwvnvxj|i!Qn> zUYb7+LGTez8A+_3X_aB2MhORe&M)nSAde`mcqb;tL;=EKkf`O4C<$#)16Qc*U6!p+ zYMN)&pxTd{;BM&cU{*_zh6a7-vEMeU0J|%APkDsT*#kb+d_c2xY@6z5swfQX2`%bB<0OM5zCpp&A4 z$`7AMf2kMd4UqX10`8Seawv%s)t%r^@eYwcV9~UN2HmwE;d04JNC{fU&v`fx)OHJ^4b?Y-#d&#IbQ`V}laB12otHX3g!TDt3H zJbDdBR44xF;dO%xRs9rM^)28g7Fbg&avGcxA`10>s}|z%ET+v)f$KtX4lipE0YTLx zCS6X?F-VKm1!|*evT8f%ZvFmxbkyjGGfRir#oivdfa_836Pq+A)Nz>VAbyEYF6j{$ zn1_e{FbYO3D~^UJosE8n4ICL&qgG7`!ki0ae7%(TR^0(q1qr;d*-pW`+*v|Ra2)xl zsPmLB!g%sW0=4Rb&(tJmv8~&uRJj;<7$9u;lw1{L#VD+!M1s`rNe>U+4rm{ z3z3ju6ulEcbeFk>2o$D6s74&~(}E2Rl!~)hJ?Fup^SLugCE1h(u8ENP%izJbX8I9c zgIhMY$M}UIgKdN9dAcACbWgL9;NXp^SGCwT$s*0+QpWtCcbo?Uh&yv*ov;J=9@BSXp3Hh55v((v zrWlodicd7kn~=rWu)$|+wC64fhfgvb9ZELvvJ}m>=qx_hQ?-^vy=uBJS{gl(4#sZb zGt?tyL!ScG>*}n9`mm20VMH7SIjCPDHO=cTCr`>iTaEOZhWXN9$;8@m)-sEnWj%2> z!n5|{_v6IVO5%%!!k;?{*qtEu*ps;KPtsj1LCi5ZrA{6nNxo>Vp#>Gn5BVy{fPkU_ z(B3o~0GP=g{w0~~w~hL@(c2B?zcxpED_v`I8*>9^b1Nfb2Rdtga~p=go&23HG~ky& z6s?jDZrwK;P#^#R>;I(&0w8=FNxtoWOX6=Q=}pa@%uQ`<9gKfR0Q?K#{}TPz5A$ya zzi|HWw*M`$zn!FaaWu3wGG_Rl{;v|C1CJI3-nb`wvj)Fdf?u{@C6IcX{aaFhJNa+= zR{G91hGz5@jJ76 z0PwHAz`xOa8?gU&@XK12BK~gxenB;~wYJu`G5W)L0RRb^f7T@7KVdxoV+>tgJ9k5U zLo;Jt-9O+#{|k@6do;uCEi14@~^ErQz>P2+05UO#BuJ^FMI$*D_MSbD`++ z-*fT17v+CoX%$cfqV`{u$Wsdi0-Ye|-}9U9`!j d{{roo=Mn`Oh&M|Q0ARj-AYk7dieG*?`ajhLmLUKD literal 0 HcmV?d00001 From 9efb2d4c0c8beaa9cd28d374b2ecb2eb10ad6218 Mon Sep 17 00:00:00 2001 From: n19htfall <108577213+outs1derPlato@users.noreply.github.com> Date: Fri, 9 Jun 2023 20:59:32 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86SET=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=85=B6=E8=A1=A8=E8=BE=BE=E5=BC=8F=E7=9A=84=E5=88=A4?= =?UTF-8?q?=E6=96=AD=EF=BC=8C=E7=8E=B0=E5=9C=A8=E5=8F=AF=E4=BB=A5=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=A4=96=E9=83=A8=E7=9A=84csv=20(#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fuann-Kinoko <838832306@qq.com> Co-authored-by: 不安きのこ <56078314+Fuann-Kinoko@users.noreply.github.com> --- README.md | 18 +-- sql_control/main.py | 238 ++++++++++++++++++++++++++++++--------- sql_parse/AST_builder.py | 1 + 3 files changed, 195 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 6741af9..e34b28b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ WHERE id = 1 AND this < 2.3; - [x] 对WHERE的基础解析 -- [ ] 对WHERE中AND、OR的正确顺序判断 +- [x] 对WHERE中AND、OR的正确顺序判断 - [x] 对SET的解析 @@ -297,25 +297,25 @@ VALUES level:AST_KEYWORDS. - [x] 完成示例文件,演示如何结合AST与实现的`sql_command`,通过FROM语句获得databse中的表 -- [ ] 完成AST与执行代码关于WHERE的结合 +- [x] 完成AST与执行代码关于WHERE的结合 -- [ ] 完成AST与执行代码关于SELECT的结合 +- [x] 完成AST与执行代码关于SELECT的结合 -- [ ] 完成AST与执行代码关于DELETE的结合 +- [x] 完成AST与执行代码关于DELETE的结合 -- [ ] 完成AST与执行代码关于UPDATE的结合 +- [x] 完成AST与执行代码关于UPDATE的结合 ##### 星期五添加的 - [ ] 一个小问题:所以什么时候在硬盘写入文件,读入文件? -- [ ] 完成AST与执行代码关于SELECT的WILDCARD设置(也就是选中所有列,详见输入5,输出5) +- [x] 完成AST与执行代码关于SELECT的WILDCARD设置(也就是选中所有列,详见输入5,输出5) -- [ ] 完成AST与执行代码关于SET的右侧赋值式结合 +- [x] 完成AST与执行代码关于SET的右侧赋值式结合 -- [ ] 完成AST与执行代码关于CREATE的基础结合,限制每一列的类型 +- [x] 完成AST与执行代码关于CREATE的基础结合,限制每一列的类型 -- [ ] 完成AST与执行代码关于CREATE的主键设置,NOT NULL设置 +- [x] 完成AST与执行代码关于CREATE的主键设置,NOT NULL设置 - [ ] 完成AST与执行代码关于CREATE中类型有SERIAL时,在INSERT时主键的自动更新并插入 diff --git a/sql_control/main.py b/sql_control/main.py index f0cc9da..8f853c8 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -1,19 +1,44 @@ from sql_parse.AST_builder import AST from sql_command.DB import DB +import os import pandas as pd +from sql_parse.tokens import Token + +data_folder = '../data' +file_names = os.listdir(data_folder) + + class blabla: def __init__(self): """ 示例用 """ self.db = DB() - self.db.create("test",["索引","姓名"], ["int","varchar"],[True,False],["索引"],["姓名"],[20]) - test_table = self.db.database['test']['tabledata'] - flag, test_table = self.db.insert(table=test_table,attributes=["索引"],values=[[23],[24]]) - flag, test_table = self.db.update(table=test_table,update_rows=None,attributes=["姓名"], values=[["张三"],["王五"]]) - self.db.database['test']['tabledata'] = test_table - + + self.dict ={} + + # 外部数据读取 test + for file_name in file_names: + if file_name.endswith('.csv'): + var_name = os.path.splitext(file_name)[0] + file_path = data_folder + '/' + file_name + data_table = pd.read_csv(file_path) + self.dict[var_name] = data_table + + # 内置 test2 + self.db.create("test2", ["id", "name", "this", "this2"], [int, str, float, float]) + self.test2 = self.db.database['test2']['tabledata'] + flag, self.test2 = self.db.insert(table=self.test2, attributes=["id"], values=[[1], [2], [3]]) + flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["name"], values=["张三", "李四", "王五"]) + flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["this"], values=[2.1, 3, 3.6]) + flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["this2"], values=[11, 12, 13]) + self.dict['test2'] = self.test2 + + print('原表test:\n', self.dict['test'], '\n') + print('原表test2:\n', self.dict['test2'], '\n') + + def ast_clear(self): """ 清除上一个语句留下的AST @@ -24,16 +49,16 @@ def ast_clear(self): def extract(self,text:str): """ - 从text中解析出AST,并且获取不同clause(子句)的内容 + 获取不同clause(子句)的内容 """ # 根据text,生成AST(这里AST只实现了SELECT查询) - self.ast = AST(sql).content + self.ast = AST(text).content # 将AST中的不同clause保存过来 self.clause = {} for clause in self.ast.content: self.clause[clause.value] = clause - def execute(self,text:str): + def execute(self, text: str): """ 执行ast中的语句 """ @@ -41,20 +66,114 @@ def execute(self,text:str): self.ast_clear() # 获取当前语句的AST self.extract(text) - # 确定当前是什么类型的语句 - # 如果以SELECT开头,那么就是查询语句 - # 正确写法是:if "SELECT" == self.ast.content[0].value - # 下面的写法对于简单语句成立,但对于复杂语句例如 - # UPDATE (SELECT * FROM test) SET id = 1 不成立 - if "SELECT" in self.clause.keys(): - self.execute_query_statement() - elif "CREATE" in self.clause.keys(): - self.excute_create_statement() - - def execute_query_statement(self): - # 示例用,演示怎么从AST的解析中获取table - tables : list[pd.DataFrame] = self.get_tables() - print(tables) + function = self.funct() + + # 查询 + if function == 'SELECT': + tables = self.get_tables(function) + cols = self.get_col() + for table in tables: + rows = self.get_row(table) if "WHERE" in self.clause.keys() else None + result = self.db.select(self.dict[table], cols, rows) if Token.Wildcard not in cols else self.db.select(self.dict[table], self.dict[table].columns, rows) + print(result) + + # 更新 + elif function == 'UPDATE': + tables = self.get_tables(function) + for table in tables: + rows = self.get_row(table) if "WHERE" in self.clause.keys() else None + for up in self.clause["SET"].content: + v = up.content[0]['expression'] + if len(v) == 1: + if isinstance(v['left'], int) or isinstance(v['left'], float) or (isinstance(v['left'], str) and f"'{v['left']}'" in text): + value = v['left'] + else: + value = self.db.select(self.dict[table], v['left'], rows).values.tolist() + else: + value = self.get_val(table, up.content[0]['expression'], rows) + self.db.update(self.dict[table], update_rows=rows, attributes=[up.content[0]['assignment']], values=value) + + # 添加 + elif function == 'INSERT': + print('INSERT') + + # 删除 + elif function == 'DELETE': + tables = self.get_tables(function) + for table in tables: + rows = self.get_row(table) if "WHERE" in self.clause.keys() else None + self.db.delete(self.dict[table], del_rows=rows) + + # 创建新表 + elif function == 'CREATE': + print("CREATE") + + # 删除表 + elif function == 'DROP': + print("DROP") + + else: + print("ERROR FUNCTION") + + # 判断功能 + def funct(self): + return self.ast.content[0].value + + # SELECT功能的筛选列 + def get_col(self): + num_col = len(self.clause["SELECT"].content) + cols = [] + for i in range(0, num_col): + col = self.clause["SELECT"].content[i] + cols.append(col) + return cols + + # WHERE语句的筛选行和对AND、OR运算结果 + def get_row(self, table): + num_condition = len(self.clause["WHERE"].content) + rows_ = [] + for i in range(0, num_condition): + condition = self.clause["WHERE"].content[i].content[0] + row = self.db.where(self.dict[table], condition['left'], condition['right'], condition['op']) + rows_.append(set(row)) + operators = [expression_i.value for expression_i in self.clause["WHERE"].content[1:]] + rows = rows_[0] + i = 1 + while i < len(rows_): + if operators[i - 1] == "AND": + rows = rows.intersection(rows_[i]) + elif operators[i - 1] == "OR": + temp_result = rows_[i] + j = i + 1 + while j < len(rows_) and operators[j - 1] == "AND": + temp_result = temp_result.intersection(rows_[j]) + j += 1 + rows = rows.union(temp_result) + i = j - 1 + i += 1 + return list(rows) + + # SET 右边为表达式的情况 + def get_val(self, table, expression, rows): + result = self.db.select(self.dict[table], expression['left'], rows) + if expression['op'] == '+': + result += expression['right'] + elif expression['op'] == '-': + result -= expression['right'] + elif expression['op'] == '*': + result *= expression['right'] + elif expression['op'] == '/': + result /= expression['right'] + result = result.values.tolist() + return result + + def get_tables(self, function): + if function == "SELECT" or function == "DELETE": + table_names = self.clause["FROM"].content + elif function == "UPDATE": + table_names = self.clause["UPDATE"].content + # ret = [] + # for table_name_i in table_names: def excute_create_statement(self): table = self.clause["CREATE"].content[0] @@ -79,37 +198,50 @@ def excute_create_statement(self): else: print("创建失败!") - def get_tables(self): - #从FROM clause中获得content - #想要获得完整结构,建议于AST_Builder.py的代码最后,在print(show)语句那里打个断点,观察一下show的结构 - table_names = self.clause["FROM"].content - ret = [] - for table_name_i in table_names: - # 调用sql_commands中实现的方法,访问得到每个table的DataFrame - table = self.db.database[table_name_i]['tabledata'] - ret.append(table) - return ret - if __name__ == "__main__": - # sql = """ - # SELECT id, name, this - # FROM test - # WHERE id = 1 AND this < 2.3; - # """ - # a = blabla() - # a.execute(sql) - #测试create - sql = """ - CREATE TABLE Persons - ( - PersonID SERIAL int, - LastName PRIMARY varchar(255), - FirstName char(255) NOT NULL, - Address float, - City varchar(255) - ); - """ a = blabla() - a.execute(sql) \ No newline at end of file + sql1 = """ + SELECT id, name + FROM test2 + WHERE id >= 2 AND this < 3.1 OR this2 = 13 AND id = 1; + """ + a.execute(sql1) + + sql2 = """ + UPDATE test2 + SET this2 = 100, name = 'Li Si', this = this * 1.1 + WHERE id >= 2 AND this2 < 13; + """ + a.execute(sql2) + + sql3 = """ + SELECT id, name, this, this2 + FROM test2 + """ + a.execute(sql3) + + sql4 = """ + DELETE FROM test2 + WHERE id = 1 + """ + a.execute(sql4) + a.execute(sql3) + + sql5 = """ + SELECT * + FROM test + WHERE gender = Female; + """ + a.execute(sql5) + + sql6 = """ + UPDATE test + SET salary = salary * 1.2 + WHERE id = 1 OR id = 2 OR id = 5; + """ + a.execute(sql6) + + sql7 = """SELECT * FROM test""" + a.execute(sql7) \ No newline at end of file diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index d8a718a..ab71494 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -311,6 +311,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): FROM table1, table2 WHERE id = 1 AND "this" < 2.3; """ + # 基础INSERT sql6 = """ INSERT INTO table1 (id, name, this) From 12e8294352a4b344d0dcca3a589e5534a0768686 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 21:15:28 +0800 Subject: [PATCH 14/17] implement multi VALUES for INSERT --- README.md | 16 ++++++++++++++-- sql_parse/AST_builder.py | 26 ++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6741af9..4ac501c 100644 --- a/README.md +++ b/README.md @@ -262,10 +262,14 @@ VALUES level:AST_KEYWORDS. -- 2.3 ``` -输入7:(与输入6的区别在于,INSERT INTO未指定表名) +输入7:(与输入6的区别在于,INSERT INTO未指定表名,同时是多列) ``` INSERT INTO table1 -VALUES (1, 'alex', 2.3); +VALUES( + (1, 'alex', 2.3), + (2, 'bob', 2.4), + (5, 'jjj', 2.5) +); ``` **实际输出7** @@ -277,6 +281,14 @@ VALUES level:AST_KEYWORDS. -- 1 -- alex -- 2.3 +VALUES level:AST_KEYWORDS.CLAUSE +-- 2 +-- bob +-- 2.4 +VALUES level:AST_KEYWORDS.CLAUSE +-- 5 +-- jjj +-- 2.5 ``` diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index d8a718a..dc0ee88 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -140,17 +140,13 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): columns_clause_node.value = "COLUMNS" statement_node.content.append(columns_clause_node) - # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 - values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) - values_clause_node.value = "VALUES" - statement_node.content.append(values_clause_node) # 在找到INSERT关键词后,我们希望找的是:INSERT INTO 表名 requireValue = "INTO" cur_node = insert_clause_node idx = start_idx + 1 - pair_entry_count = 0 + pair_level = 0 while idx < total_idx: cls, value = stream[idx] @@ -162,6 +158,8 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): # 如果当前token为标点 elif cls in tokens.Punctuation: # ()的处理 + if value == "(": + pair_level = pair_level + 1 if value in ["(", ")"]: if cur_node.value == "INSERT" and value == "(": # 遇到这里,说明读到了INSERT INTO 表名 ( 的情况,接下来该是第二个clause了 @@ -169,6 +167,14 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): requireValue = "VALUES" idx = idx + 1 continue + if pair_level == 2 and value == "(": + # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 + values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + values_clause_node.value = "VALUES" + statement_node.content.append(values_clause_node) + cur_node = values_clause_node + if value == ")": + pair_level = pair_level - 1 # 如果当前token特殊,为关键字 else: @@ -176,7 +182,7 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): if val == "INTO": pass # 读到这里,说明columns的名字已经读完了,在读VALUES了,进入下一个clause if val == "VALUES": - cur_node = values_clause_node + pass idx = idx + 1 return statement_node, idx @@ -298,7 +304,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): # 基础CREATE sql4 = """ CREATE TABLE Persons ( - PersonID PRIMARY int, + PersonID SERIAL int, LastName varchar(255), FirstName char(255) NOT NULL, Address float, @@ -319,7 +325,11 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): # INSERT的特殊情况,不指定列名 sql7 = """ INSERT INTO table1 - VALUES (1, 'alex', 2.3); + VALUES( + (1, 'alex', 2.3), + (2, 'bob', 2.4), + (5, 'jjj', 2.5) + ); """ a = AST(sql7) a.pprint() From eaeeb921bdf85c0be04f895d56ed93163120e930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=AE=89=E3=81=8D=E3=81=AE=E3=81=93?= <56078314+Fuann-Kinoko@users.noreply.github.com> Date: Fri, 9 Jun 2023 21:16:51 +0800 Subject: [PATCH 15/17] implement multi VALUES for INSERT (#7) --- README.md | 16 ++++++++++++++-- sql_parse/AST_builder.py | 26 ++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e34b28b..47d347c 100644 --- a/README.md +++ b/README.md @@ -262,10 +262,14 @@ VALUES level:AST_KEYWORDS. -- 2.3 ``` -输入7:(与输入6的区别在于,INSERT INTO未指定表名) +输入7:(与输入6的区别在于,INSERT INTO未指定表名,同时是多列) ``` INSERT INTO table1 -VALUES (1, 'alex', 2.3); +VALUES( + (1, 'alex', 2.3), + (2, 'bob', 2.4), + (5, 'jjj', 2.5) +); ``` **实际输出7** @@ -277,6 +281,14 @@ VALUES level:AST_KEYWORDS. -- 1 -- alex -- 2.3 +VALUES level:AST_KEYWORDS.CLAUSE +-- 2 +-- bob +-- 2.4 +VALUES level:AST_KEYWORDS.CLAUSE +-- 5 +-- jjj +-- 2.5 ``` diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index ab71494..8ce412e 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -140,17 +140,13 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): columns_clause_node.value = "COLUMNS" statement_node.content.append(columns_clause_node) - # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 - values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) - values_clause_node.value = "VALUES" - statement_node.content.append(values_clause_node) # 在找到INSERT关键词后,我们希望找的是:INSERT INTO 表名 requireValue = "INTO" cur_node = insert_clause_node idx = start_idx + 1 - pair_entry_count = 0 + pair_level = 0 while idx < total_idx: cls, value = stream[idx] @@ -162,6 +158,8 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): # 如果当前token为标点 elif cls in tokens.Punctuation: # ()的处理 + if value == "(": + pair_level = pair_level + 1 if value in ["(", ")"]: if cur_node.value == "INSERT" and value == "(": # 遇到这里,说明读到了INSERT INTO 表名 ( 的情况,接下来该是第二个clause了 @@ -169,6 +167,14 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): requireValue = "VALUES" idx = idx + 1 continue + if pair_level == 2 and value == "(": + # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 + values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) + values_clause_node.value = "VALUES" + statement_node.content.append(values_clause_node) + cur_node = values_clause_node + if value == ")": + pair_level = pair_level - 1 # 如果当前token特殊,为关键字 else: @@ -176,7 +182,7 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): if val == "INTO": pass # 读到这里,说明columns的名字已经读完了,在读VALUES了,进入下一个clause if val == "VALUES": - cur_node = values_clause_node + pass idx = idx + 1 return statement_node, idx @@ -298,7 +304,7 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): # 基础CREATE sql4 = """ CREATE TABLE Persons ( - PersonID PRIMARY int, + PersonID SERIAL int, LastName varchar(255), FirstName char(255) NOT NULL, Address float, @@ -320,7 +326,11 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): # INSERT的特殊情况,不指定列名 sql7 = """ INSERT INTO table1 - VALUES (1, 'alex', 2.3); + VALUES( + (1, 'alex', 2.3), + (2, 'bob', 2.4), + (5, 'jjj', 2.5) + ); """ a = AST(sql7) a.pprint() From 54974b039f55eb47f5d1c6751b36ea18935c0af9 Mon Sep 17 00:00:00 2001 From: Fuann-Kinoko <838832306@qq.com> Date: Fri, 9 Jun 2023 23:14:28 +0800 Subject: [PATCH 16/17] implement INSERT execution --- sql_command/DB.py | 7 +- sql_control/main.py | 225 +++++++++++++++++++++++++++------------ sql_parse/AST_builder.py | 30 ++++-- sql_parse/ast_def.py | 2 +- 4 files changed, 183 insertions(+), 81 deletions(-) diff --git a/sql_command/DB.py b/sql_command/DB.py index 7f0fc8e..4b000b0 100644 --- a/sql_command/DB.py +++ b/sql_command/DB.py @@ -2,13 +2,14 @@ class DB: def __init__(self): - self.dbtypes=dict[dict['tablename':str,'tabledata':pd.DataFrame,'datatypes':dict,'not_null_flag':pd.Series,'primary_key':str,'attri_len':pd.Series]] + self.dbtypes=dict[dict['tablename':str,'tabledata':pd.DataFrame,'datatypes':dict,'not_null_flag':dict,'primary_key':str,'attri_len':dict]] self.database = {} #访问某个表用 database['表名'] #访问某个表中的数据用 database['表名']['tabledata'] 这是一个pd.DataFrame型的数据 #访问某个表中某个属性的数据类型用 database['表名']['datatypes']['属性名'] #查询某个表中某个属性是否是not null :database['表名']['not_null_flag']['属性名'] #访问某个表中某个(这里仅考虑char varchar类型)属性设定的长度 :database['表名']['attri_len']['属性名'] + #primary: database['表名']['primary_key']['属性名'] def create(self, table: str, @@ -28,9 +29,9 @@ def create(self, 'tablename': table, 'tabledata': pd.DataFrame(columns=attributes), 'datatypes': {attr: data_type for attr, data_type in zip(attributes, types)}, - 'not_null_flag': pd.Series(data=not_null,index=attributes,dtype=bool), + 'not_null_flag': {attr: not_null for attr, not_null in zip(attributes, not_null)}, 'primary_key': primary_key, - 'attri_len': pd.Series(data=char_attri_len,index=char_attri,dtype=int) + 'attri_len': {ch: chlen for ch, chlen in zip(char_attri,char_attri_len)} } #数据库添加新表 self.database[table] = newtable diff --git a/sql_control/main.py b/sql_control/main.py index 8f853c8..3145e31 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -15,9 +15,7 @@ def __init__(self): 示例用 """ self.db = DB() - - self.dict ={} - + self.dict = self.db.database # 外部数据读取 test for file_name in file_names: if file_name.endswith('.csv'): @@ -26,19 +24,6 @@ def __init__(self): data_table = pd.read_csv(file_path) self.dict[var_name] = data_table - # 内置 test2 - self.db.create("test2", ["id", "name", "this", "this2"], [int, str, float, float]) - self.test2 = self.db.database['test2']['tabledata'] - flag, self.test2 = self.db.insert(table=self.test2, attributes=["id"], values=[[1], [2], [3]]) - flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["name"], values=["张三", "李四", "王五"]) - flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["this"], values=[2.1, 3, 3.6]) - flag, self.test2 = self.db.update(table=self.test2, update_rows=None, attributes=["this2"], values=[11, 12, 13]) - self.dict['test2'] = self.test2 - - print('原表test:\n', self.dict['test'], '\n') - print('原表test2:\n', self.dict['test2'], '\n') - - def ast_clear(self): """ 清除上一个语句留下的AST @@ -66,6 +51,7 @@ def execute(self, text: str): self.ast_clear() # 获取当前语句的AST self.extract(text) + function = self.funct() # 查询 @@ -95,7 +81,19 @@ def execute(self, text: str): # 添加 elif function == 'INSERT': - print('INSERT') + tables = self.get_tables(function) + for table in tables: + cols = self.clause["COLUMNS"].content + values_clauses = self.ast.content[2:] + cur_table = self.db.database[table] + values_for_insert = [] + for values_clause_i in values_clauses: + values = values_clause_i.content + self.check_types(cur_table['datatypes'], cols, values) + self.check_null(cur_table['not_null_flag'], cols, values) + self.check_primary(cur_table['tabledata'],cur_table['primary_key'],cols,values) + values_for_insert.append(values) + _, cur_table['tabledata'] = self.db.insert(cur_table['tabledata'], cols, values_for_insert) # 删除 elif function == 'DELETE': @@ -106,7 +104,7 @@ def execute(self, text: str): # 创建新表 elif function == 'CREATE': - print("CREATE") + self.excute_create_statement() # 删除表 elif function == 'DROP': @@ -115,6 +113,45 @@ def execute(self, text: str): else: print("ERROR FUNCTION") + if function != "SELECT": + self.save_tables() + + def check_types(self, requires, cols, values): + for i in range(len(cols)): + if requires[cols[i]].upper() in ["VARCHAR", "CHAR"]: + if not isinstance(values[i], str): + raise Exception(f"Type of {cols[i]} is {requires[cols[i]]}, but {type(values[i])} is given.") + if requires[cols[i]].upper() in ["INT"]: + if not isinstance(values[i], int): + raise Exception(f"Type of {cols[i]} is {requires[cols[i]]}, but {type(values[i])} is given.") + if requires[cols[i]].upper() in ["FLOAT"]: + if not isinstance(values[i],float): + pass + raise Exception(f"Type of {cols[i]} is {requires[cols[i]]}, but {type(values[i])} is given.") + + def check_null(self, requires, cols, values): + for k in requires: + v = requires[k] + if v is True: + if k not in cols: + raise Exception(f"{k} is not null, but null is given.") + else: + if values[cols.index(k)] is None: + raise Exception(f"{k} is not null, but null is given.") + + def check_primary(self, table: pd.DataFrame, primary_keys, cols, values): + for k in primary_keys: + if k in cols: + if values[cols.index(k)] in table[k].values: + raise Exception(f"{k} is primary key, but {values[cols.index(k)]} is already in table.") + else: + raise Exception(f"{k} is primary key, but not given in insertion values.") + + + def save_tables(self): + # 待实现 + pass + # 判断功能 def funct(self): return self.ast.content[0].value @@ -172,8 +209,9 @@ def get_tables(self, function): table_names = self.clause["FROM"].content elif function == "UPDATE": table_names = self.clause["UPDATE"].content - # ret = [] - # for table_name_i in table_names: + elif function == "INSERT": + table_names = self.clause["INSERT"].content + return table_names def excute_create_statement(self): table = self.clause["CREATE"].content[0] @@ -183,18 +221,27 @@ def excute_create_statement(self): primary_key_ls = [] char_attri_ls = [] char_attri_len_ls = [] - for i in range(len(self.clause["COLUMNS"].content)): - attribute_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) - types_ls.append(self.clause["COLUMNS"].content[i].content[0]["type"]) - if (self.clause["COLUMNS"].content[i].content[0]["type"]=="char") or (self.clause["COLUMNS"].content[i].content[0]["type"]=="varchar"): - char_attri_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) - char_attri_len_ls.append(self.clause["COLUMNS"].content[i].content[0]["length"]) - not_null_ls.append(self.clause["COLUMNS"].content[i].content[0]["PRIMARY"]) - if self.clause["COLUMNS"].content[i].content[0]["PRIMARY"]: - primary_key_ls.append(self.clause["COLUMNS"].content[i].content[0]["name"]) - if self.db.create(table=table, attributes=attribute_ls, types=types_ls, not_null=not_null_ls, primary_key=primary_key_ls,char_attri=char_attri_ls,char_attri_len=char_attri_len_ls): + + for col_def in self.clause["COLUMNS"].content: + t = col_def.content[0] + attribute_ls.append(t["name"]) + types_ls.append(t["type"]) + if t["type"] in ["char","varchar"]: + char_attri_ls.append(t["name"]) + char_attri_len_ls.append(t["length"]) + # attention: if it is a primary key, then it should always be not null + not_null_ls.append(t["NOT NULL"] or t["PRIMARY"]) + if t["PRIMARY"]: + primary_key_ls.append(t["name"]) + if self.db.create(table=table, + attributes=attribute_ls, + types=types_ls, + not_null=not_null_ls, + primary_key=primary_key_ls, + char_attri=char_attri_ls, + char_attri_len=char_attri_len_ls): print("创建成功!") - print(self.db.database[table]["not_null_flag"]) + # print(self.db.database[table]) else: print("创建失败!") @@ -202,46 +249,92 @@ def excute_create_statement(self): if __name__ == "__main__": a = blabla() + + # 创建 sql1 = """ - SELECT id, name - FROM test2 - WHERE id >= 2 AND this < 3.1 OR this2 = 13 AND id = 1; - """ + CREATE TABLE Persons + ( + PersonID PRIMARY int, + LastName varchar(255) NOT NULL, + FirstName char(255), + Address float, + City varchar(255) + ); + """ a.execute(sql1) + print(a.db.database["Persons"]['tabledata']) + print("="*20) + print("="*20) + # 插入 sql2 = """ - UPDATE test2 - SET this2 = 100, name = 'Li Si', this = this * 1.1 - WHERE id >= 2 AND this2 < 13; - """ + INSERT INTO Persons (PersonID,LastName, Address, City) + VALUES ( + (3,'my', 2.3, "this"), + (4,'she', 5.6, "7"), + (5,'thistsdas',1.1,"9") + ); + """ a.execute(sql2) + print(a.db.database["Persons"]['tabledata']) + print("="*20) + print("="*20) + # 更新 sql3 = """ - SELECT id, name, this, this2 - FROM test2 - """ + UPDATE Persons + SET Address = Address * 1.1 + WHERE PersonID >= 2 + """ a.execute(sql3) - - sql4 = """ - DELETE FROM test2 - WHERE id = 1 - """ - a.execute(sql4) - a.execute(sql3) - - sql5 = """ - SELECT * - FROM test - WHERE gender = Female; - """ - a.execute(sql5) - - sql6 = """ - UPDATE test - SET salary = salary * 1.2 - WHERE id = 1 OR id = 2 OR id = 5; - """ - a.execute(sql6) - - sql7 = """SELECT * FROM test""" - a.execute(sql7) \ No newline at end of file + print(a.db.database["Persons"]['tabledata']) + print("="*20) + print("="*20) + + # print(a.db.database["Persons"]['datatypes']) + # print(a.db.database["Persons"]['not_null_flag']) + # print(a.db.database["Persons"]['primary_key']) + # a = blabla() + # sql1 = """ + # SELECT id, name + # FROM test2 + # WHERE id >= 2 AND this < 3.1 OR this2 = 13 AND id = 1; + # """ + # a.execute(sql1) + + # sql2 = """ + # UPDATE test2 + # SET this2 = 100, name = 'Li Si', this = this * 1.1 + # WHERE id >= 2 AND this2 < 13; + # """ + # a.execute(sql2) + + # sql3 = """ + # SELECT id, name, this, this2 + # FROM test2 + # """ + # a.execute(sql3) + + # sql4 = """ + # DELETE FROM test2 + # WHERE id = 1 + # """ + # a.execute(sql4) + # a.execute(sql3) + + # sql5 = """ + # SELECT * + # FROM test + # WHERE gender = Female; + # """ + # a.execute(sql5) + + # sql6 = """ + # UPDATE test + # SET salary = salary * 1.2 + # WHERE id = 1 OR id = 2 OR id = 5; + # """ + # a.execute(sql6) + + # sql7 = """SELECT * FROM test""" + # a.execute(sql7) \ No newline at end of file diff --git a/sql_parse/AST_builder.py b/sql_parse/AST_builder.py index 8ce412e..34c87d6 100644 --- a/sql_parse/AST_builder.py +++ b/sql_parse/AST_builder.py @@ -77,9 +77,6 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): while idx < total_idx: cls, value = stream[idx] - if value == "PRIMARY": - pass - # 如果当前token并不特殊,非关键字,那么就是当前node需要接受的内容 if (cls not in tokens.Keyword) and (cls not in tokens.Punctuation): cur_node.deal(cls, value) @@ -120,7 +117,7 @@ def build_AST_CREATE(self, start_idx = 0, statement_node = None): val = value.upper() if val == "TABLE": pass if val == "PRIMARY": - self.search_constraint(idx, val) + cur_node.content[0]["PRIMARY"] = True if val == "NOT NULL": cur_node.content[0]["NOT NULL"] = True idx = idx + 1 @@ -164,10 +161,9 @@ def build_AST_INSERT(self, start_idx = 0, statement_node = None): if cur_node.value == "INSERT" and value == "(": # 遇到这里,说明读到了INSERT INTO 表名 ( 的情况,接下来该是第二个clause了 cur_node = columns_clause_node - requireValue = "VALUES" idx = idx + 1 continue - if pair_level == 2 and value == "(": + elif (cur_node.value == "COLUMNS" and pair_level == 1 and value == "(" and self.next_token_value(idx+1) != "(") or (pair_level == 2 and value == "("): # 第三个必定会存在的clause:value="VALUES",content包含每一列的值 values_clause_node = self.create_node(AST_KEYWORDS.CLAUSE) values_clause_node.value = "VALUES" @@ -260,7 +256,19 @@ def build_AST(self, start_idx = 0, cur_node = None): raise Exception(f"Current node level is {cur_node.attribute}, but the word level is {cur_cls_level}") idx = idx + 1 return cur_node, idx - + + def next_token_value(self, start_idx): + stream = self.tokens + idx = start_idx + total_idx = len(stream) + while idx < total_idx: + cls, value = stream[idx] + if value == " ": + idx = idx+1 + continue + return value + return None + def pprint(self): """ 用还算好看的方式打印自己的content @@ -325,11 +333,11 @@ def pprint_impl(self,depth = 0, cur_list = None,_pre = ''): """ # INSERT的特殊情况,不指定列名 sql7 = """ - INSERT INTO table1 + INSERT INTO table1(id,name) VALUES( - (1, 'alex', 2.3), - (2, 'bob', 2.4), - (5, 'jjj', 2.5) + (1, 'alex'), + (2, 'bob'), + (5, 'jjj') ); """ a = AST(sql7) diff --git a/sql_parse/ast_def.py b/sql_parse/ast_def.py index 8ad7528..9e47d69 100644 --- a/sql_parse/ast_def.py +++ b/sql_parse/ast_def.py @@ -98,7 +98,7 @@ def deal(self, cls, value): self.content[0]["type"] = value elif cls in tokens.Name: - self.content[0]["name"] = value.upper() + self.content[0]["name"] = value elif cls in tokens.Literal: self.content[0]["length"] = Numerize(cls,value) From e634e03a3d2a90b744919c679ffbb5d0cc9ef6d7 Mon Sep 17 00:00:00 2001 From: n19htfall Date: Fri, 9 Jun 2023 23:50:59 +0800 Subject: [PATCH 17/17] NO DICT! --- .idea/.gitignore | 3 ++ .idea/inspectionProfiles/Project_Default.xml | 19 ++++++++ .../inspectionProfiles/profiles_settings.xml | 6 +++ .idea/modules.xml | 8 ++++ .idea/sql_minibuilder.iml | 14 ++++++ .idea/vcs.xml | 6 +++ sql_control/main.py | 45 ++++++++++++------- 7 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/sql_minibuilder.iml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..717e9bd --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,19 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..58e8fc9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sql_minibuilder.iml b/.idea/sql_minibuilder.iml new file mode 100644 index 0000000..8e5446a --- /dev/null +++ b/.idea/sql_minibuilder.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sql_control/main.py b/sql_control/main.py index 3145e31..c4c699d 100644 --- a/sql_control/main.py +++ b/sql_control/main.py @@ -5,8 +5,8 @@ from sql_parse.tokens import Token -data_folder = '../data' -file_names = os.listdir(data_folder) +# data_folder = '../data' +# file_names = os.listdir(data_folder) class blabla: @@ -15,14 +15,13 @@ def __init__(self): 示例用 """ self.db = DB() - self.dict = self.db.database # 外部数据读取 test - for file_name in file_names: - if file_name.endswith('.csv'): - var_name = os.path.splitext(file_name)[0] - file_path = data_folder + '/' + file_name - data_table = pd.read_csv(file_path) - self.dict[var_name] = data_table + # for file_name in file_names: + # if file_name.endswith('.csv'): + # var_name = os.path.splitext(file_name)[0] + # file_path = data_folder + '/' + file_name + # data_table = pd.read_csv(file_path) + # self.dict[var_name] = data_table def ast_clear(self): """ @@ -59,14 +58,16 @@ def execute(self, text: str): tables = self.get_tables(function) cols = self.get_col() for table in tables: + cur_table = self.db.database[table] rows = self.get_row(table) if "WHERE" in self.clause.keys() else None - result = self.db.select(self.dict[table], cols, rows) if Token.Wildcard not in cols else self.db.select(self.dict[table], self.dict[table].columns, rows) + result = self.db.select(cur_table['tabledata'], cols, rows) if Token.Wildcard not in cols else self.db.select(cur_table['tabledata'], cur_table['tabledata'].columns, rows) print(result) # 更新 elif function == 'UPDATE': tables = self.get_tables(function) for table in tables: + cur_table = self.db.database[table] rows = self.get_row(table) if "WHERE" in self.clause.keys() else None for up in self.clause["SET"].content: v = up.content[0]['expression'] @@ -74,10 +75,10 @@ def execute(self, text: str): if isinstance(v['left'], int) or isinstance(v['left'], float) or (isinstance(v['left'], str) and f"'{v['left']}'" in text): value = v['left'] else: - value = self.db.select(self.dict[table], v['left'], rows).values.tolist() + value = self.db.select(cur_table['tabledata'], v['left'], rows).values.tolist() else: value = self.get_val(table, up.content[0]['expression'], rows) - self.db.update(self.dict[table], update_rows=rows, attributes=[up.content[0]['assignment']], values=value) + self.db.update(cur_table['tabledata'], update_rows=rows, attributes=[up.content[0]['assignment']], values=value) # 添加 elif function == 'INSERT': @@ -99,8 +100,9 @@ def execute(self, text: str): elif function == 'DELETE': tables = self.get_tables(function) for table in tables: + cur_table = self.db.database[table] rows = self.get_row(table) if "WHERE" in self.clause.keys() else None - self.db.delete(self.dict[table], del_rows=rows) + self.db.delete(cur_table['tabledata'], del_rows=rows) # 创建新表 elif function == 'CREATE': @@ -167,11 +169,12 @@ def get_col(self): # WHERE语句的筛选行和对AND、OR运算结果 def get_row(self, table): + cur_table = self.db.database[table] num_condition = len(self.clause["WHERE"].content) rows_ = [] for i in range(0, num_condition): condition = self.clause["WHERE"].content[i].content[0] - row = self.db.where(self.dict[table], condition['left'], condition['right'], condition['op']) + row = self.db.where(cur_table['tabledata'], condition['left'], condition['right'], condition['op']) rows_.append(set(row)) operators = [expression_i.value for expression_i in self.clause["WHERE"].content[1:]] rows = rows_[0] @@ -192,7 +195,8 @@ def get_row(self, table): # SET 右边为表达式的情况 def get_val(self, table, expression, rows): - result = self.db.select(self.dict[table], expression['left'], rows) + cur_table = self.db.database[table] + result = self.db.select(cur_table['tabledata'], expression['left'], rows) if expression['op'] == '+': result += expression['right'] elif expression['op'] == '-': @@ -246,7 +250,6 @@ def excute_create_statement(self): print("创建失败!") - if __name__ == "__main__": a = blabla() @@ -291,6 +294,16 @@ def excute_create_statement(self): print("="*20) print("="*20) + # 查询 + sql4 = """ + SELECT * + FROM Persons + WHERE PersonID >= 4 + """ + a.execute(sql4) + print("=" * 20) + print("=" * 20) + # print(a.db.database["Persons"]['datatypes']) # print(a.db.database["Persons"]['not_null_flag']) # print(a.db.database["Persons"]['primary_key'])