Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.vscode/*
*/__pycache__/*
__pycache__
*.pyc
__local/*
*.db
Expand All @@ -11,3 +12,4 @@ MercurySQL.egg-info/*
runtime
runtime/*
*.test.py

7 changes: 7 additions & 0 deletions MercurySQL/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@

from .gensql import DataBase, Table, set_driver
from . import drivers

class SQL:
"""Wrap everything together"""
DataBase = DataBase
Table = Table
set_driver = set_driver
drivers = drivers
Original file line number Diff line number Diff line change
Expand Up @@ -190,22 +190,22 @@ def parse(type_: Any) -> str:
return res

@staticmethod
def connect(db_name: str, host: str, user: str, passwd: str = '', force=False) -> Conn:
def connect(db_name: str, host: str, user: str, password: str = '', force=False) -> Conn:
"""
Connect to a MySQL database.
"""
if force:
return mysql.connector.connect(
host=host,
user=user,
passwd=passwd,
passwd=password,
database=db_name
)
else:
conn = mysql.connector.connect(
host=host,
user=user,
passwd=passwd
passwd=password
)
conn.backup_cursor = conn.cursor

Expand Down
30 changes: 26 additions & 4 deletions MercurySQL/drivers/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ def get_all_columns(table_name: str) -> str:
return f"PRAGMA table_info({table_name});"

@staticmethod
def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, primaryKey=False, autoIncrement=False) -> str:
def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, column_default: str | None = None, primaryKey=False, autoIncrement=False) -> str:
return f"""
CREATE TABLE IF NOT EXISTS {table_name} ({column_name} {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTOINCREMENT' if autoIncrement else ''})
CREATE TABLE IF NOT EXISTS {table_name} ({column_name} {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTOINCREMENT' if autoIncrement else ''} {'DEFAULT ' + column_default if column_default else ''})
"""

@staticmethod
def add_column(table_name: str, column_name: str, column_type: str) -> str:
def add_column(table_name: str, column_name: str, column_type: str, column_default: str) -> str:
return f"""
ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}
ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type} {'DEFAULT ' + column_default if column_default else ''}
"""

@staticmethod
Expand Down Expand Up @@ -160,6 +160,28 @@ def parse(type_: Any) -> str:
# Not Supported
raise TypeError(f"Type `{str(type_)}` not supported.")

@staticmethod
def add_punctuation(data: any) -> str:
"""
Convert data to data that can be insert into a sql,
for example if data is str, return 'data' as a string,
if data is a number, return this number
"""
if data is None:
return None

if isinstance(data, str):
return f"'{data}'"
elif isinstance(data, bool):
return str(data).lower()
elif isinstance(data, (int, float)):
return str(data)
elif isinstance(data, bytes):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waiting to be tested

return f"x'{data.hex()}'" # Dont know if this is correct
else:
raise TypeError(f"Type `{str(type(data))}` not supported.")


@staticmethod
def connect(db_name: str, **kwargs) -> Driver_SQLite.Conn:
return sqlite3.connect(db_name, **kwargs)
11 changes: 11 additions & 0 deletions MercurySQL/errors/driver_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ def __init__(self, driver: str, dv: str, cv: str):
"""
self.message = f"Driver `{driver}`(v{dv}) is not supported by MercurySQL(v{cv}). Please update your Driver/MercurySQL to the latest version."
super().__init__(self.message)

class TypeNotMatchError(Exception):
def __init__(self, value: str, value_type: str, column: str, column_type: str) -> None:
"""
:param value: The value of the param.
:param value_type: The type of the param.
:param column: The name of the column.
:param column_type: The type of the column.
"""
self.message = f"The param `{value}` is {value_type} while the type of the column `{column}` is {column_type}."
super().__init__(self.message)
3 changes: 3 additions & 0 deletions MercurySQL/gensql/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
from .table import Table


from ..drivers import sqlite


# ========== Class Decorations ==========
class DataBase:
pass
Expand Down
60 changes: 45 additions & 15 deletions MercurySQL/gensql/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def select(self, exp: Exp = None, selection: str = "*") -> QueryResult:
return QueryResult(self, exp, selection)

def newColumn(
self, name: str, type_: Any, force=False, primaryKey=False, autoIncrement=False
self, name: str, type_: Any, default = None, force=False, primaryKey=False, autoIncrement=False
) -> None:
"""
Add a new column to the table.
Expand Down Expand Up @@ -242,22 +242,27 @@ def newColumn(
raise DuplicateError(f"Column `{name}` already exists.")
else:
return

if default and type(default) != type_:
raise TypeNotMatchError(default, type(default), name, type_)

type_ = self.driver.TypeParser.parse(type_)
default = self.driver.TypeParser.add_punctuation(default)

if self.isEmpty:
# create it first
cmd = self.driver.APIs.gensql.create_table_if_not_exists(
self.table_name,
name,
type_,
default,
primaryKey=primaryKey,
autoIncrement=autoIncrement,
)
self.db.do(cmd)
self.isEmpty = False
else:
cmd = self.driver.APIs.gensql.add_column(self.table_name, name, type_)
cmd = self.driver.APIs.gensql.add_column(self.table_name, name, type_, default)
self.db.do(cmd)

if primaryKey:
Expand All @@ -267,7 +272,7 @@ def newColumn(
self.columnsType[name] = type_

def struct(
self, columns: dict, skipError=True, primaryKey: str = None, autoIncrement=False, force=True
self, columns: dict, skipError=True, primaryKey: str = None, autoIncrement=False, force=True, rebuild=False
) -> None:
"""
Set the structure of the table.
Expand All @@ -278,6 +283,8 @@ def struct(
:type skipError: bool
:param primaryKey: The primary key of the table.
:type primaryKey: str
:param rebuild: Whether to rebuild the table.
:type rebuild: bool

Example Usage:

Expand All @@ -295,26 +302,40 @@ def struct(
skipError = skipError and force

for name, type_ in columns.items():
default_value = None


# 支持 type_ 为 [str, "默认值"] 的写法
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that to use tuple instead of list, since there's already a usage:

tb['col_name'] = str, "hello"

The code is as below
https://github.com/BernieHuang2008/MercurySQL/blob/ffc6e463bec44a94546b2438ab5c5474a5698d4b/MercurySQL/gensql/table.py#L148C1-L152C29

Python will parse the situation above as tuple, so it's recommend to have the same form as the one already implemented in table.__setitem__().

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lanbinleo 这块最好还是你来改一下吧,我不知道你在哪调用了这玩意

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
你看这一段

if isinstance(type_, list):
default_value = type_[1]
type_ = type_[0]

type_origin = type_
type_ = self.driver.TypeParser.parse(type_)
isPrimaryKey = name == primaryKey

if name in self.columns:
if type_.lower() != self.columnsType[name].lower():
if rebuild:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently deciding whether i will use this ...

print(f"Testing function, still constructing...")
# TODO: 这个Rebuild理论上会重新创建一个表,但是对应的底层代码还没写完
# self.delColumn(name)
elif type_.lower() != self.columnsType[name].lower():
raise ConfilictError(
f"Column `{name}` with different types (`{self.columnsType[name]}`) already exists. While trying to add column `{name}` with type `{type_}`."
)
elif not skipError:
raise DuplicateError(
f"Column `{name}` already exists. You can use `force=True` to avoid this error."
f"Column `{name}` already exists. You can use `skipError=True` to avoid this error."
)
else:
self.newColumn(
name,
type_origin,
primaryKey=isPrimaryKey,
autoIncrement=autoIncrement,
)

# Raise 错误后不会执行后续代码,所以这里可以不用 Else
self.newColumn(
name,
type_origin,
default=default_value,
primaryKey=isPrimaryKey,
autoIncrement=autoIncrement,
)

def delColumn(self, name: str) -> None:
if name not in self.columns:
Expand All @@ -326,7 +347,8 @@ def delColumn(self, name: str) -> None:
else:
# delete the column
self.columns.remove(name)
cmd = self.driver.APIs.gensql.drop_column(name)
# 这一行有Bug,修复了
cmd = self.driver.APIs.gensql.drop_column(table_name=self.table_name,column_name=name)
self.db.do(cmd)

def setPrimaryKey(self, keyname: str, keytype: str) -> None:
Expand Down Expand Up @@ -365,8 +387,16 @@ def insert(self, __auto=False, **kwargs) -> None:
table.insert(id=1, name='Bernie', age=15, __auto=True)

"""
# get keys and clean them
keys = list(kwargs.keys())

# TODO: 为了兼容v1.0中的json格式传参的问题,v1.0需要修复
# if __auto is a dict
if isinstance(__auto, dict):
keys = list(__auto.keys())
kwargs = __auto
else:
# get keys and clean them
keys = list(kwargs.keys())

if "__auto" in keys:
__auto = kwargs["__auto"]
keys.remove("__auto")
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ sphinx_rtd_theme

# for release
wheel

# MySQL
mysql.connector