# project-4
**Repository Path**: yujieloong/project-4
## Basic Information
- **Project Name**: project-4
- **Description**: No description available
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 10
- **Created**: 2022-06-10
- **Last Updated**: 2022-06-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 面向对象课程-期末作业(项目4:数据库)
21215090 龙宇杰
## 1. 封装增、删、改三个功能接口
### 1.1 在数据表中插入数据
接口:
```python
def run_app():
with db.transaction(db_id='2018年各校招生计划',
user_secrets='12345678') as ctx:
学校 = ctx.学校
省份 = ctx.省份
查询结果 = ctx.SQL(
select=[学校.名称,学校.类型],
where=db.AND(
学校.是否985 == True,
省份.列_211高校数量 > 1,
)
)
ctx.INSERT(
into = "学校",
columns = ["id", "名称", "所在省份", "类型", "是否985"],
values = [["item_enterprise_2_16","中山大学","广东","综合",True],
["item_enterprise_2_17","暨南大学","广东","综合",False]]
)
ctx._commit_transaction()
```
接口实现:
```python
def INSERT(self, into=None, columns = None, values = None):
assert hasattr(self,into), '数据库里没有该表格!'
table = getattr(self, into)
table_content = self._read_table(into)
if len(table_content["primary_keys"]) != 0:
p_key = table_content["primary_keys"][0]
assert p_key in columns, '没有主键对应的值!'
for value in values:
full_value = { c.column_name:None for c in table.columns }
for c_name,v in zip(columns,value):
if c_name == p_key:
c = getattr(table, p_key)
assert v not in c.column_data, '主键值重复!'
assert c_name in full_value, '没有对应列!'
full_value[c_name] = v
new_value = [ v for _,v in full_value.items()]
table_content["rows"].append(new_value)
# print(table_content)
self._write_table(into,table_content)
return True
```
结果截图:

### 1.2 在数据表中修改条目
接口:
```python
def run_app():
with db.transaction(db_id='2018年各校招生计划',
user_secrets='12345678') as ctx:
学校 = ctx.学校
省份 = ctx.省份
查询结果 = ctx.SQL(
select=[学校.名称,学校.类型],
where=db.AND(
学校.是否985 == True,
省份.列_211高校数量 > 1,
)
)
ctx.INSERT(
into = "学校",
columns = ["id", "名称", "所在省份", "类型", "是否985"],
values = [["item_enterprise_2_16","中山大学","广东","综合",True],
["item_enterprise_2_17","暨南大学","广东","综合",False]]
)
ctx.UPDATE(
table_name = "学校",
set_data = {"是否211":True},
where=db.AND(
学校.所在省份 == "广东",
)
)
ctx._commit_transaction()
```
接口实现:
```python
def UPDATE(self, table_name="", set_data=None, where=None):
assert hasattr(self,table_name), '数据库里没有该表格!'
if where is not None:
assert isinstance(where, (AND,)), 'where类型错误!'
for op in where.ops:
if isinstance(op, (EQ,GT)):
assert isinstance(op.left, Column) ,'算子左值类型错误!'
table = getattr(self, table_name)
table._read()
table_content = self._read_table(table_name)
if where is None:
w = [True for i in range(len(table))]
else:
w = where.eval(table)
if len(table_content["primary_keys"]) != 0:
p_key = table_content["primary_keys"][0]
else: p_key = None
for i in range(len(w)):
if w[i]:
for k, v in set_data.items():
assert hasattr(table, k), '没有对应列!'
if k == p_key:
c = getattr(table, p_key)
if v != c.column_data[i]:
assert v not in c.column_data, '主键不能重复!'
for j in range(len(table.columns)):
if k == table.columns[j].column_name:
index = j
table_content["rows"][i][index] = v
print(table_content)
self._write_table(table_name,table_content)
return True
```
结果截图:

### 1.3 删除对应条目
接口:
```python
def run_app():
with db.transaction(db_id='2018年各校招生计划',
user_secrets='12345678') as ctx:
学校 = ctx.学校
省份 = ctx.省份
查询结果 = ctx.SQL(
select=[学校.名称,学校.类型],
where=db.AND(
学校.是否985 == True,
省份.列_211高校数量 > 1,
)
)
ctx.INSERT(
into = "学校",
columns = ["id", "名称", "所在省份", "类型", "是否985"],
values = [["item_enterprise_2_16","中山大学","广东","综合",True],
["item_enterprise_2_17","暨南大学","广东","综合",False]]
)
ctx.UPDATE(
table_name = "学校",
set_data = {"是否211":True},
where=db.AND(
学校.所在省份 == "广东",
)
)
ctx.DELETE(
table_name="学校",
where=db.AND(
学校.名称 == "暨南大学",
)
)
ctx._commit_transaction()
```
接口实现:
```python
def DELETE(self, table_name="", where=None):
assert hasattr(self,table_name), '数据库里没有该表格!'
if where is not None:
assert isinstance(where, (AND,)), 'where类型错误!'
for op in where.ops:
if isinstance(op, (EQ,GT)):
assert isinstance(op.left, Column) ,'算子左值类型错误!'
table = getattr(self, table_name)
table._read()
table_content = self._read_table(table_name)
if where is None:
w = [True for i in range(len(table))]
else:
w = where.eval(table)
for i in range(len(w)):
if w[i]:
table_content["rows"].pop(i)
print(table_content)
self._write_table(table_name,table_content)
return True
```
结果截图:

### 1.4 bug
可能是js环境下python库不支持__exit__这个钩子函数的功能。不手动commit的话,在with...as...的上下文环境中,无法触发后台的commit函数。

使用异常机制也照样无法执行else(即finally)部分的语句。
```python
def run_app():
try:
ctx = db.transaction(db_id='2018年各校招生计划', user_secrets='12345678')
学校 = ctx.学校
省份 = ctx.省份
查询结果 = ctx.SQL(
select=[学校.名称,学校.类型],
where=db.AND(
学校.是否985 == True,
省份.列_211高校数量 > 1,
)
)
ctx.INSERT(
into = "学校",
columns = ["id", "名称", "所在省份", "类型", "是否985"],
values = [["item_enterprise_2_16","中山大学","广东","综合",True],
["item_enterprise_2_17","暨南大学","广东","综合",False]]
)
except:
ctx._abort_transaction()
print("transaction execute ERROR!")
else:
print("commit tansaction!")
print("ctx:",ctx)
if not (ctx.transaction_id is None):
ctx._commit_transaction()
......
class transaction:
def __init__(self, db_id, user_secrets, table_users=None):
self.transaction_id = ajax.rpc.create_transaction(user_secrets, db_id, table_users)
self.db_id = db_id
self.user_secrets = user_secrets
self.table_users = table_users
self.tables = []
# 使用test_run()使用异常机制时,需要在这里执行__enter__()函数
self.__enter__()
......
```
## 2. 乐观事务处理
老师实现的原始版本乐观事务处理是有bug的。每次写的结果都保存到_transaction_writes这个数据结构里面。而每次读都是从本地文件进行读。事务可能有很多各种各样的SQL语句组成,它们有时是前后因果依赖的。因此同一事务中,必须读到最新写入的值。所以每个事务,需要对它操作的表维护一个内存中的副本_transaction_cache。
代码改动如下:
```python
_transaction_cache = {}
def read_table(user_secrets, transaction_id, db_id, table_name):
assert user_secrets==_user_secrets
_transaction_global_lock.acquire()
table_writes = _transaction_table_writes.get((db_id, table_name), [])
timestamp = 0 if len(table_writes)==0 else table_writes[-1]
reads = _transaction_reads.get(transaction_id, [])
reads.append((db_id, table_name, timestamp))
_transaction_reads[transaction_id] = reads
# read
total_cache = _transaction_cache.get(transaction_id,{})
table_cache = total_cache.get((db_id, table_name), {})
if table_cache is None:
with open(os.path.join('db_files', db_id, f'{table_name}.json'), encoding="utf-8") as fp:
data = json.load(fp)
total_cache[(db_id, table_name)] = data
_transaction_cache[transaction_id] = total_cache
else:
data = table_cache
_transaction_global_lock.release()
return data
def write_table(user_secrets, transaction_id, db_id, table_name, table_data):
print("Enter write_table()")
print("db_id:",db_id,", table_name",table_name,", table_data:\n",table_data)
assert user_secrets==_user_secrets
_transaction_global_lock.acquire()
# 保存时间戳就好,更改保存到_transaction_cache里
table_writes = _transaction_table_writes.get((db_id, table_name), [])
timestamp = 0 if len(table_writes)==0 else table_writes[-1]
writes = _transaction_writes.get(transaction_id, [])
writes.append((db_id, table_name, timestamp))
_transaction_writes[transaction_id] = writes
# 更新保存到缓存里
total_cache = _transaction_cache.get(transaction_id,{})
total_cache[(db_id, table_name)] = table_data
_transaction_cache[transaction_id] = total_cache
_transaction_global_lock.release()
def commit_transaction(user_secrets, transaction_id):
print("Enter commit_transaction()")
assert user_secrets==_user_secrets
succ = True
_transaction_global_lock.acquire()
reads = _transaction_reads.get(transaction_id, [])
for (db_id, table_name, timestamp) in reads:
table_writes = _transaction_table_writes.get((db_id, table_name), [])
if len(table_writes)>0 and table_writes[-1]>timestamp:
succ = False
if succ:
for (db_id, table_name, timestamp) in _transaction_writes[transaction_id]:
table_writes = _transaction_table_writes.get((db_id, table_name), [])
print("table_writes for ",(db_id, table_name)," is:",table_writes)
if len(table_writes)>0 and table_writes[-1]>timestamp:
succ = False
_transaction_global_lock.release()
return succ
new_timestamp = 1 if len(table_writes)==0 else table_writes[-1] + 1
table_writes.append(new_timestamp)
print("After update,table_writes is :",table_writes)
# 更新写时间戳
_transaction_table_writes[(db_id, table_name)] = table_writes
# 永久保存到本地
total_cache = _transaction_cache.get(transaction_id,{})
for k, table_data in total_cache.items():
with open(os.path.join('db_files', k[0], f'{k[1]}.json'), 'w', encoding="utf-8") as fp:
json.dump(table_data, fp, indent=4, sort_keys=True, ensure_ascii=False)
_transaction_global_lock.release()
return succ
```