实现SQL基本语法
#!/usr/bin/env python
2 # writen by Lisp at 2017/5/15
3
4 """
5 对员工信息文件,实现增删改查操作
6
7 1. 可进行模糊查询,语法至少支持下面3种:
8
9 a. select user_name,user_age from db.emp where user_age > 22
10 b. select * from db.emp where user_dept = "IT"
11 c. select * from db.emp where user_date like "2013"
12 d. select * from db.emp limit 3
13 e. select * from db.emp where user_id > 12 and user_id < 15 or user_name like 李
14 f. select * from db.emp where not user_id > 3
15
16 查到的信息,打印后,最后面还要显示查到的条数
17
18 2. 可创建新员工纪录,以phone做唯一键,staff_id需自增
19
20 a. insert into db.emp values 张光棍,31,13111111111,CEO,2011-11-11
21
22 3. 可删除指定员工信息纪录,输入员工id,即可删除
23
24 a. delete from db.emp where id >25
25
26 4. 可修改员工信息,语法如下:
27
28 a. UPDATE db.emp SET dept="Market" WHERE user_dept = "IT"
29
30 注意:以上需求,要充分使用函数,请尽你的最大限度来减少重复代码!
31
32
33 思路:
34 1. 先从文件读出所有数据到列表,每行数据按字典的格式存储
35 2. 更新,插入,删除操作均操作列表,操作完以后,执行文件写入操作
36
37 """
38 # 记录 input 输入的历史命令, 可以使用上下左右箭头; 不同环境可能不一样.
39 import readline
40 import os
41
42
43 def get_all_info(file_name):
44 """
45 通过db file 得到所有用户信息; 此处无需判断异常, 因为在调用此函数之前, 均已做好判断
46 :param file_name: str db_filename
47 :return: list 返回所有用户信息的列表, 用户信息以dict形式保存
48 """
49 user_info_list = []
50 with open(file_name, ‘r‘, encoding=‘utf-8‘) as r_f:
51 for each_line in r_f.readlines():
52 each_list = each_line.strip().split(‘,‘)
53 user_info_list.append(dict(zip(tk_temp, each_list)))
54 return user_info_list
55
56
57 def write_to_file(db_name, write_list):
58 """
59 将内容写入文件; 此处同样无需判断异常
60 :param db_name get_filename()函数可得到
61 :param write_list 要往文件中写入的数据 [{},{},{}...] 形式
62 :return: 无返回,只写文件
63 """
64 # 将字段名字组成字符串 先列表生成,再join
65 temp_str = ‘,‘.join([‘{‘ + x + ‘}‘ for x in tk_temp])
66 with open(db_name, ‘w+‘, encoding=‘utf-8‘) as w_f:
67 for each_dic in write_list: # 遍历要写的数据列表
68 if write_list[0] == each_dic: # 判断首行
69 temp_data = ‘{0}‘.format(temp_str).format_map(each_dic)
70 else:
71 temp_data = ‘\n{0}‘.format(temp_str).format_map(each_dic)
72 w_f.writelines(temp_data)
73
74
75 def get_where_clause(para_str, para_item):
76 """
77 处理where后面的字符串
78 id -> 每行记录的ID, user_name -> 每行记录的user_name ...
79 and 和 or 和 not 可以不做处理
80 like -> like前后值翻转, like变为in
81 limit 最后处理
82 :param para_item: dict 用户详细信息 -- 文件中的每行记录
83 :param para_str: content -- where后的字符串
84 :return: union
85 """
86 # 遍历字段名,把where后面的字段名全部替换为字段值
87 # temp_str = para_str
88 for ITEM in tk_temp:
89 # 如: where user_id > 10 --> where 1 > 10
90 para_str = para_str.replace(ITEM, para_item[ITEM].lower())
91
92 if ‘"‘ in para_str:
93 para_str = para_str.replace(‘"‘, ‘‘)
94 if "‘" in para_str:
95 para_str = para_str.replace("‘", ‘‘)
96 # 处理like关键字
97 temp_list = para_str.split() # 定义临时列表, 用空格分开替换后的where子句
98 if ‘like‘ in para_str: # 判断是否有like语句
99 if ‘ like ‘ in para_str: # 如果有like, 则判断是否被空格分开;因为上面的列表是按照空格分割的list
100 for temp in temp_list: # 遍历temp_list
101 if temp == ‘like‘: # 如果有like的列表元素
102 like_flag = temp_list.index(temp) # 找出like的下标
103 temp_list[like_flag] = ‘in‘ # 把like替换为in
104 if temp_list[like_flag - 1] != ‘not‘: # 找出like的下标后,判断like的前面是否有not关键字
105 # 没有,则前后两个元素互换; 操作空间是temp_list
106 temp_list[like_flag - 1], temp_list[like_flag + 1] = "‘" + str(
107 temp_list[like_flag + 1]) + "‘", "‘" + str(temp_list[like_flag - 1]) + "‘"
108 else:
109 # 有,则not前一个元素与like后一个元素互换
110 temp_list[like_flag - 2], temp_list[like_flag + 1] = "‘" + str(
111 temp_list[like_flag + 1]) + "‘", "‘" + str(temp_list[like_flag - 2]) + "‘"
112 else:
113 return 901, ‘like关键字附近‘ # 返回语法错误和提示
114 para_str = ‘ ‘.join(temp_list) # 再用空格将处理过like的列表组成字符串
115 # 处理 类似 user_dept=it 有一个等号的情况; 需要把一个等号变为两个等号 user_dept==it 才可以被eval
116 for temp in temp_list: # 遍历临时列表
117 if ‘=‘ in temp and ‘==‘ not in temp: # 如果列表元素中有=, 而且还没被替换; 匹配[‘user_id=2‘, ‘=‘, ‘user_dept‘, ‘=it‘]
118 if ‘<=‘ not in temp and ‘>=‘ not in temp: # 如果是 >= , <= 则不处理
119 if temp != ‘=‘: # 匹配user_id=2, user_id=, =2的情况
120 temp_para = temp.split(‘=‘) # 则,将item用=分割, 获取=两边的内容
121 if temp.startswith(‘=‘): # =2
122 bf_temp_index = temp_list.index(temp) - 1
123 bf_temp = temp_list[bf_temp_index]
124 para_str = para_str.replace(temp, ‘=="‘ + temp_para[-1] + ‘"‘)
125 para_str = para_str.replace(bf_temp + ‘ ‘, ‘"{}"‘.format(bf_temp))
126 elif temp.endswith(‘=‘): # user_id=
127 bh_temp_index = temp_list.index(temp) + 1
128 bh_temp = temp_list[bh_temp_index]
129 para_str = para_str.replace(temp, ‘\"‘ + temp_para[0] + ‘\"==‘)
130 para_str = para_str.replace(bh_temp + ‘ ‘, ‘"{}"‘.format(bh_temp))
131 else: # user_id=2
132 # 替换原字符串中的temp, 替换为 ‘user_dept‘==‘IT‘
133 para_str = para_str.replace(temp, "‘" + temp_para[0] + "‘==‘" + temp_para[1] + "‘")
134 else: # 匹配‘=‘的情况,则说明=两边是空格, 那直接替换原字符串 = 为 ==
135 temp_index = temp_list.index(temp)
136 old_str = temp_list[temp_index - 1] + ‘ = ‘ + temp_list[temp_index + 1]
137 new_str = ‘"{}" == "{}"‘.format(temp_list[temp_index - 1], temp_list[temp_index + 1])
138 para_str = para_str.replace(old_str, new_str)
139 return para_str
140
141
142 def get_ok_dic_id(para_str, user_info_list):
143 """
144 遍历整个用户信息表, 返回符合where后面条件的dic在user_info_list中下标的列表, 使用与delete,select,update
145 :param user_info_list: 用户信息列表
146 :param para_str 传给get_where_clause函数, 返回能被eval的字符串
147 :return: union
148 """
149 res_list = []
150 if not para_str:
151 return list(range(len(user_info_list)))
152 for dic_item in user_info_list: # 遍历user_info_list,
153 # 调用函数,处理where子句,返回可以eval的字符串
154 eval_str = get_where_clause(para_str, dic_item)
155 if eval_str[0] == 901:
156 return eval_str
157 try:
158 if eval(eval_str): # 判断eval的字符串是否成立; 成立则追加成立的字典下标到返回列表
159 res_list.append(user_info_list.index(dic_item))
160 except (NameError, SyntaxError):
161 return 901, ‘Where子句‘
162 return res_list
163
164
165 def get_filename(para_str):
166 """
167 这个函数用来解决 sql 语句当中 db.emp 对应文件的问题
168 :param para_str: 小写字符串 db.emp
169 :return: union
170 """
171 # 获取当前文件的绝对路径(不包含文件名)
172 real_path = os.path.dirname(os.path.realpath(__file__))
173 # 替换参数中字符串的.为/
174 db_path = para_str.replace(‘.‘, ‘/‘)
175 # 字符串拼接,组成数据库文件的绝对路径
176 file_path = real_path + ‘/‘ + db_path
177 # 判断文件是否存在
178 if os.path.isfile(file_path):
179 return 950, db_path # 返回数据文件的相对路径
180 else:
181 return 951, ‘{}不存在‘.format(db_path) # 数据文件不存在
182
183
184 def get_res_list(content, user_info_list, cl_name=‘‘):
185 """
186 select函数专用; 简化 select * from ... 和 select column1, column2 from ... 的情况
187 :param cl_name: 查询字段,默认为空代表*
188 :param user_info_list: 用户信息列表
189 :param content: where子句
190 :return: res_list
191 """
192 t_flag = False
193 res_list = []
194 cont_list = []
195 # 获取字符串形式的字段名{user_name},{user_age}...
196 # 因查询的字段,显示不同
197 if not cl_name:
198 temp_str = ‘,‘.join([‘{‘ + x + ‘}‘ for x in tk_temp])
199 else:
200 new_cl_list = cl_name.split(‘,‘)
201 for each_cl in new_cl_list:
202 if each_cl not in tk_temp:
203 return 921, ‘字段名 {} 不存在‘.format(each_cl)
204 temp_str = ‘,‘.join([‘{‘ + x + ‘}‘ for x in new_cl_list])
205
206 if content == ‘‘:
207 for item in user_info_list:
208 temp = ‘[{0}]‘.format(temp_str).format_map(item)
209 res_list.append(temp)
210 # select * from db.emp [ where... | limit... ]
211 else:
212 # 如果有limit,把limit后面那个值取出来,content变为limit前的str;更改limit标志位
213 if ‘limit‘ in content:
214 cont_list = content.split(‘limit‘)
215 if cont_list[0]:
216 content = cont_list[0]
217 else:
218 content = ‘‘
219 t_flag = True
220 # 根据content 取出匹配到的dic的下标
221 se_id = get_ok_dic_id(content, user_info_list)
222 if se_id:
223 if se_id[0] == 901:
224 return se_id
225 else:
226 # 遍历下标,将内容先追加到一个列表
227 for each_id in se_id:
228 temp = ‘[{0}]‘.format(temp_str).format_map(dict(user_info_list[each_id]))
229 res_list.append(temp)
230 else:
231 res_list = se_id
232
233 if t_flag:
234 return res_list[:int(cont_list[-1])]
235 else:
236 return res_list
237
238
239 def my_sql_select(para_str):
240 """
241 查询
242 :param: para_str: 接收用户输入的字符串 小写
243 :return:
244 """
245 para_list = para_str.split()
246
247 try:
248 # 找到from关键字的下标
249 from_id = para_list.index(ky_list[5])
250 except ValueError:
251 # 无关键字
252 return 901, ‘关键字{}缺失‘.format(ky_list[5])
253
254 # select 到 from 之间 判断长度
255 if len(para_list[1:from_id]) >= 1:
256 cl_name = ‘‘.join(para_list[1:from_id])
257 else:
258 return 901, ‘{}与{}之间‘.format(ky_list[0], ky_list[5])
259
260 if len(para_list[from_id + 1:]) >= 2:
261 try:
262 # 查找where的下标
263 where_id = para_list.index(ky_list[6])
264 if len(para_list[from_id + 1:where_id]) != 1:
265 return 901, ‘数据文件部分有语法错误‘
266 if len(para_list[where_id + 1:]) > 0:
267 content = para_str.split(ky_list[6])[-1].strip()
268 else:
269 return 901, ‘关键字{}后缺少条件‘.format(ky_list[6])
270
271 except ValueError:
272 # 没有where
273 try:
274 limit_id = para_list.index(ky_list[-2])
275 if len(para_list[from_id + 1:limit_id]) != 1:
276 return 901, ‘关键字{}位置不对‘.format(ky_list[-2])
277 if len(para_list[limit_id:]) == 2:
278 content = ‘ ‘.join(para_list[limit_id:])
279 else:
280 return 901, ‘{} 后参数异常‘.format(ky_list[-2])
281 except ValueError:
282 return 901, ‘缺少 {} 或者 {} 关键字‘.format(ky_list[-3], ky_list[-2])
283 elif len(para_list[from_id + 1:]) == 1:
284 content = ‘‘
285 else:
286 return 901, ‘缺少数据文件‘
287
288 db_name = para_list[from_id + 1]
289 # 分割关键字之间的str结束, 得到:
290 # select-->from = cl_name
291 # from-->where = db_name
292 # where--> = content(maybe include limit)
293
294 # 通过get_filename函数将db.emp类型转文件路径db/emp
295 file_tup = get_filename(db_name)
296 file_name = file_tup[1]
297 # 将文件内容读取出来,保存到user_info_list列表
298 if file_tup[0] == 951:
299 return file_tup
300 user_info_list = get_all_info(file_name)
301
302 # select * from ...
303 if cl_name == ‘*‘:
304 # select * from db.emp + content
305 # 这里这种情况直接调用函数, 因为就cl_name不一样,其他都基本一致.
306 # 把cl_name传进去,进行匹配判断.
307 res_list = get_res_list(content, user_info_list)
308 if res_list:
309 if res_list[0] == 901 or res_list[0] == 921:
310 return res_list
311 else:
312 # select user_id ,name , xx from db.emp + content
313 res_list = get_res_list(content, user_info_list, cl_name)
314 if res_list:
315 if res_list[0] == 901 or res_list[0] == 921:
316 return res_list
317
318 for each_res in res_list:
319 print(each_res)
320 return 920, ‘总共{}条记录‘.format(len(res_list))
321
322
323 def my_sql_insert(para_str):
324 """
325 往数据文件插入数据.
326 1. 可以直接将数据插入到文件;
327 2. 先append到列表,然后再将列表覆盖写入文件的方式;
328 这里为了练习函数的使用,采用方法2。
329 :param para_str: 小写字符串 insert into [db_file] values xxx
330 :return:
331 """
332 # 将接收的参数通过空格来分割,转为列表
333 para_list = para_str.split()
334
335 try:
336 values_id = para_list.index(ky_list[4]) # 找到values关键字的位置;ky_list[4]=values
337 except ValueError:
338 return 901, ‘关键字{}缺失‘.format(ky_list[4]) # 无values关键字
339
340 if len(para_list[2:values_id]) != 1: # 判断into之后到values关键字之间是否只有一个元素
341 return 901, ‘数据文件部分有语法错误‘ # 否,则返回选择的数据库和表有误
342 else:
343 db_name = para_list[2] # 是,数据文件暂定
344
345 if len(para_list[values_id + 1:]) < 1: # 判断values关键字之后是否再有无空格
346 return 901, ‘未发现要插入的数据‘ # 如果有,插入的数据格式有误
347 else:
348 # 如果有values,则以values分割,取后面的字符串,以,分割得到一个列表
349 temp_list = para_str.split(ky_list[4])[1].split(‘,‘)
350 # 匹配列表长度,与用户信息dict长度是否一致
351 if len(temp_list) == 5:
352 for temp_item in temp_list:
353 # 去掉空格
354 temp_index = temp_list.index(temp_item)
355 temp_list[temp_index] = temp_item.strip()
356 else:
357 return 921, ‘要插入的数据 格式有误‘
358 content = ‘,‘.join(temp_list) # 没有,则确定插入的数据
359
360 # 通过上述判断,可得出要插入的内容content和要插入的文件名db_name
361 # 通过get_filename函数将db.emp类型转文件路径db/emp
362 file_tup = get_filename(db_name)
363 file_name = file_tup[1]
364 # 将文件内容读取出来,保存到user_info_list列表
365 if file_tup[0] == 951:
366 return file_tup
367 user_info_list = get_all_info(file_name)
368
369 # 遍历user_info_list,判断手机号是否唯一
370 for item in user_info_list:
371 if content.split(‘,‘)[2] in item.values():
372 return 911, ‘手机号{}已经存在‘.format(content.split(‘,‘)[2]) # 返回手机号已经存在
373
374 # 上述判断通过,则这里表明输入的参数中,数据文件以及要插入的数据都是合法的
375 # 得到主键ID
376 insert_id = int(user_info_list[-1][tk_temp[0]]) + 1
377
378 # 得到要插入的完整内容 id,name,age,phone,dept,date
379 insert_str = str(insert_id) + ‘,‘ + content
380
381 # 先将得到的内容通过,分割得到列表;再与字段名的列表tk_temp组成dict,追加到user_info_list
382 user_info_list.append(dict(zip(tk_temp, insert_str.split(‘,‘))))
383
384 # 将user_info_list写入文件
385 write_to_file(file_name, user_info_list)
386 # 打印插入成功提示
387 print(‘{2} func: insert(), db_file: {0}, values: {1} {3}‘.format(file_name, content, ‘{‘, ‘}‘))
388 return 910, ‘当前数据库中最大主键 ID = {0}‘.format(str(insert_id)) # 返回插入成功
389
390
391 def my_sql_delete(para_str):
392 """
393 删除
394 delete from db.emp where id >25
395 delete * from db.emp
396 删除成功提示
397 :param: para_str: str
398 :return:
399 """
400 # 清空文件标识
401 tc_flag = False
402
403 # 将接收的参数通过空格来分割,转为列表
404 para_list = para_str.split()
405