From 4bf38a17f6f59e6f4c2cca08ffe72a952821e6e2 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 14:16:30 +0800 Subject: [PATCH 01/14] [chore] easter egg! --- MercurySQL/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MercurySQL/__init__.py b/MercurySQL/__init__.py index ecf7ca0..bdfbf62 100644 --- a/MercurySQL/__init__.py +++ b/MercurySQL/__init__.py @@ -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 From b7d0e5a8ec92a4174e91570cc314b78ef704f46e Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:37:01 +0800 Subject: [PATCH 02/14] [fix] gitignore bug --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cfc5d5e..7701a64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vscode/* */__pycache__/* +__pycache__ *.pyc __local/* *.db @@ -11,3 +12,4 @@ MercurySQL.egg-info/* runtime runtime/* *.test.py + From 8fc8cd37c18ce77f96c1cda696492365cc0f8d45 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:47:46 +0800 Subject: [PATCH 03/14] [refactor] rename mysql to mysql_driver to avoid bugs --- MercurySQL/drivers/mysql.py | 227 ------------------------------------ 1 file changed, 227 deletions(-) delete mode 100644 MercurySQL/drivers/mysql.py diff --git a/MercurySQL/drivers/mysql.py b/MercurySQL/drivers/mysql.py deleted file mode 100644 index 6dd9b6b..0000000 --- a/MercurySQL/drivers/mysql.py +++ /dev/null @@ -1,227 +0,0 @@ -""" -Requirements: - - mysql-connector-python -""" -from .base import BaseDriver - -import mysql.connector -from typing import Any, List - - -class Driver_MySQL(BaseDriver): - pass - - -class Driver_MySQL(BaseDriver): - """ - .. note:: - Supported MySQL Version: **8.2** - """ - dependencies = ['mysql-connector-python'] - version = '0.1.0' - payload = '%s' - - Conn = mysql.connector.MySQLConnection - Cursor = mysql.connector.cursor_cext.CMySQLCursor - - class APIs: - class gensql: - @staticmethod - def drop_table(table_name: str) -> str: - return f"DROP TABLE {table_name};" - - @staticmethod - def get_all_tables() -> str: - return "SHOW TABLES;" - - @staticmethod - def get_all_columns(table_name: str) -> str: - return f"DESCRIBE `{table_name}`;" - - @staticmethod - def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, primaryKey=False, autoIncrement=False, engine='', charset='') -> str: - return f""" - CREATE TABLE IF NOT EXISTS `{table_name}` ( - `{column_name}` {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTO_INCREMENT' if autoIncrement else ''} - ) {engine} {f'DEFAULT CHARSET={charset}' if charset else ''}; - """ - - @staticmethod - def add_column(table_name: str, column_name: str, column_type: str) -> str: - return f"ALTER TABLE `{table_name}` ADD COLUMN `{column_name}` {column_type};" - - @staticmethod - def drop_column(table_name: str, column_name: str) -> str: - return f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;" - - @staticmethod - def set_primary_key(table, keyname: str, keytype: str) -> list: - return [ - f"ALTER TABLE `{table.table_name}` DROP PRIMARY KEY;", - f"ALTER TABLE `{table.table_name}` ADD PRIMARY KEY (`{keyname}`);" - ] - - @staticmethod - def insert(table_name: str, columns: str, values: str) -> str: - return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values});" - - @staticmethod - def insert_or_update(table_name: str, columns: str, values: str) -> str: - update_columns = ', '.join(f'{col}=VALUES({col})' for col in columns.split(', ')) - return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values}) ON DUPLICATE KEY UPDATE {update_columns};" - - @staticmethod - def update(table_name: str, columns: str, condition: str) -> str: - return f"UPDATE `{table_name}` SET {columns} WHERE {condition};" - - @staticmethod - def query(table_name: str, selection: str, condition: str) -> str: - return f"SELECT {selection} FROM `{table_name}` WHERE {condition};" - - @staticmethod - def delete(table_name: str, condition: str) -> str: - return f"DELETE FROM `{table_name}` WHERE {condition};" - - @classmethod - def get_all_tables(cls, conn) -> List[str]: - cursor = conn.cursor() - cursor.execute(cls.gensql.get_all_tables()) - return list(map(lambda x: x[0], cursor.fetchall())) - - @classmethod - def get_all_columns(cls, conn, table_name: str) -> List[str]: - cursor = conn.cursor() - cursor.execute(cls.gensql.get_all_columns(table_name)) - # [name, type, null, key, default, extra] - return list(map(lambda x: [x[0], x[1], x[2], x[3], x[4], x[5]], cursor.fetchall())) - - class TypeParser: - """ - Parse the type from `Python Type` -> `MySQL Type`. - """ - - @staticmethod - def parse(type_: Any) -> str: - """ - Compile the type to MySQL type. - - :param type_: The type to parse. - :type type_: Any - - :return: The MySQL type. - :rtype: str - - +-------------------+-------------+ - | Supported Types | SQLite Type | - +===================+=============+ - | bool | BOOLEAN | - +-------------------+-------------+ - | int | INTEGER | - +-------------------+-------------+ - | float | FLOAT | - +-------------------+-------------+ - | str | VARCHAR(225)| - +-------------------+-------------+ - | bytes | BLOB | - +-------------------+-------------+ - | datetime.datetime | DATETIME | - +-------------------+-------------+ - | datetime.date | DATE | - +-------------------+-------------+ - | datetime.time | TIME | - +-------------------+-------------+ - - - Example Usage: - - .. code-block:: python - - TypeParser.parse(int(10)) # INT DEFAULT 10 - TypeParser.parse(str) # VARCHAR - TypeParser.parse(float(1.3)) # FLOAT DEFAULT 1.3 - TypeParser.parse('\t DOUBLE DEFAULT 1.23') # DOUBLE DEFAULT 1.23 - ... - - """ - import datetime - - supported_types = { - bool: 'BOOLEAN', - int: 'INT', - float: 'FLOAT', - str: 'VARCHAR(225)', - bytes: 'BLOB', - - datetime.datetime: 'DATETIME', - datetime.date: 'DATE', - datetime.time: 'TIME', - } - - if not isinstance(type_, tuple): - type_ = (type_,) - - res = "" - - # round 1: Built-in Types - if type_[0] in supported_types: - res = supported_types[type_[0]] - - # round 2: Custom Types - if res == "" and isinstance(type_[0], str) and type_[0].startswith('\t'): # custom type - return type_[0].strip() - - # round 3: Built-in Types With Default Value - if type(type_[0]) != type: - if isinstance(type_[0], bytes): - type_[0] = type_[0].decode() - res = f"{supported_types[type(type_[0])]} DEFAULT {type_[0]}" - - # round 4: Not Null - for i in range(1, len(type_)): - if type_[i] == 'not null': - res += ' NOT NULL' - else: - res += f" DFAULT {type_[i]}" - - # Not Supported - if res == "": - raise TypeError(f"Type `{str(type_)}` is not supported.") - - return res - - @staticmethod - def connect(db_name: str, host: str, user: str, passwd: str = '', force=False) -> Conn: - """ - Connect to a MySQL database. - """ - if force: - return mysql.connector.connect( - host=host, - user=user, - passwd=passwd, - database=db_name - ) - else: - conn = mysql.connector.connect( - host=host, - user=user, - passwd=passwd - ) - conn.backup_cursor = conn.cursor - - def cursor(): - """ Create Database if not exists. """ - conn.cursor = conn.backup_cursor - - c = conn.cursor() - - try: - c.execute(f'USE {db_name};') - except mysql.connector.errors.ProgrammingError: - c.execute(f'CREATE DATABASE {db_name};') - c.execute(f'USE {db_name};') - - return c - - conn.cursor = cursor - return conn From b66f1b31cb4f6b8cc89d9144be43fa8f8cc6cd91 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:48:25 +0800 Subject: [PATCH 04/14] [style] change param "passwd" to "password" in connect funtion --- MercurySQL/drivers/mysql_driver.py | 228 +++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 MercurySQL/drivers/mysql_driver.py diff --git a/MercurySQL/drivers/mysql_driver.py b/MercurySQL/drivers/mysql_driver.py new file mode 100644 index 0000000..2e8ac38 --- /dev/null +++ b/MercurySQL/drivers/mysql_driver.py @@ -0,0 +1,228 @@ +""" +Requirements: + - mysql-connector-python +""" +from .base import BaseDriver + +import mysql.connector +from typing import Any, List + + +class Driver_MySQL(BaseDriver): + pass + + +class Driver_MySQL(BaseDriver): + """ + .. note:: + Supported MySQL Version: **8.2** + """ + dependencies = ['mysql-connector-python'] + version = '0.1.0' + payload = '%s' + + Conn = mysql.connector.MySQLConnection + # Cursor = mysql.connector.cursor_cext.CMySQLCursor + Cursor = mysql.connector.cursor.MySQLCursor + + class APIs: + class gensql: + @staticmethod + def drop_table(table_name: str) -> str: + return f"DROP TABLE {table_name};" + + @staticmethod + def get_all_tables() -> str: + return "SHOW TABLES;" + + @staticmethod + def get_all_columns(table_name: str) -> str: + return f"DESCRIBE `{table_name}`;" + + @staticmethod + def create_table_if_not_exists(table_name: str, column_name: str, column_type: str, primaryKey=False, autoIncrement=False, engine='', charset='') -> str: + return f""" + CREATE TABLE IF NOT EXISTS `{table_name}` ( + `{column_name}` {column_type} {'PRIMARY KEY' if primaryKey else ''} {'AUTO_INCREMENT' if autoIncrement else ''} + ) {engine} {f'DEFAULT CHARSET={charset}' if charset else ''}; + """ + + @staticmethod + def add_column(table_name: str, column_name: str, column_type: str) -> str: + return f"ALTER TABLE `{table_name}` ADD COLUMN `{column_name}` {column_type};" + + @staticmethod + def drop_column(table_name: str, column_name: str) -> str: + return f"ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`;" + + @staticmethod + def set_primary_key(table, keyname: str, keytype: str) -> list: + return [ + f"ALTER TABLE `{table.table_name}` DROP PRIMARY KEY;", + f"ALTER TABLE `{table.table_name}` ADD PRIMARY KEY (`{keyname}`);" + ] + + @staticmethod + def insert(table_name: str, columns: str, values: str) -> str: + return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values});" + + @staticmethod + def insert_or_update(table_name: str, columns: str, values: str) -> str: + update_columns = ', '.join(f'{col}=VALUES({col})' for col in columns.split(', ')) + return f"INSERT INTO `{table_name}` ({columns}) VALUES ({values}) ON DUPLICATE KEY UPDATE {update_columns};" + + @staticmethod + def update(table_name: str, columns: str, condition: str) -> str: + return f"UPDATE `{table_name}` SET {columns} WHERE {condition};" + + @staticmethod + def query(table_name: str, selection: str, condition: str) -> str: + return f"SELECT {selection} FROM `{table_name}` WHERE {condition};" + + @staticmethod + def delete(table_name: str, condition: str) -> str: + return f"DELETE FROM `{table_name}` WHERE {condition};" + + @classmethod + def get_all_tables(cls, conn) -> List[str]: + cursor = conn.cursor() + cursor.execute(cls.gensql.get_all_tables()) + return list(map(lambda x: x[0], cursor.fetchall())) + + @classmethod + def get_all_columns(cls, conn, table_name: str) -> List[str]: + cursor = conn.cursor() + cursor.execute(cls.gensql.get_all_columns(table_name)) + # [name, type, null, key, default, extra] + return list(map(lambda x: [x[0], x[1], x[2], x[3], x[4], x[5]], cursor.fetchall())) + + class TypeParser: + """ + Parse the type from `Python Type` -> `MySQL Type`. + """ + + @staticmethod + def parse(type_: Any) -> str: + """ + Compile the type to MySQL type. + + :param type_: The type to parse. + :type type_: Any + + :return: The MySQL type. + :rtype: str + + +-------------------+-------------+ + | Supported Types | SQLite Type | + +===================+=============+ + | bool | BOOLEAN | + +-------------------+-------------+ + | int | INTEGER | + +-------------------+-------------+ + | float | FLOAT | + +-------------------+-------------+ + | str | VARCHAR(225)| + +-------------------+-------------+ + | bytes | BLOB | + +-------------------+-------------+ + | datetime.datetime | DATETIME | + +-------------------+-------------+ + | datetime.date | DATE | + +-------------------+-------------+ + | datetime.time | TIME | + +-------------------+-------------+ + + + Example Usage: + + .. code-block:: python + + TypeParser.parse(int(10)) # INT DEFAULT 10 + TypeParser.parse(str) # VARCHAR + TypeParser.parse(float(1.3)) # FLOAT DEFAULT 1.3 + TypeParser.parse('\t DOUBLE DEFAULT 1.23') # DOUBLE DEFAULT 1.23 + ... + + """ + import datetime + + supported_types = { + bool: 'BOOLEAN', + int: 'INT', + float: 'FLOAT', + str: 'VARCHAR(225)', + bytes: 'BLOB', + + datetime.datetime: 'DATETIME', + datetime.date: 'DATE', + datetime.time: 'TIME', + } + + if not isinstance(type_, tuple): + type_ = (type_,) + + res = "" + + # round 1: Built-in Types + if type_[0] in supported_types: + res = supported_types[type_[0]] + + # round 2: Custom Types + if res == "" and isinstance(type_[0], str) and type_[0].startswith('\t'): # custom type + return type_[0].strip() + + # round 3: Built-in Types With Default Value + if type(type_[0]) != type: + if isinstance(type_[0], bytes): + type_[0] = type_[0].decode() + res = f"{supported_types[type(type_[0])]} DEFAULT {type_[0]}" + + # round 4: Not Null + for i in range(1, len(type_)): + if type_[i] == 'not null': + res += ' NOT NULL' + else: + res += f" DFAULT {type_[i]}" + + # Not Supported + if res == "": + raise TypeError(f"Type `{str(type_)}` is not supported.") + + return res + + @staticmethod + 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=password, + database=db_name + ) + else: + conn = mysql.connector.connect( + host=host, + user=user, + passwd=password + ) + conn.backup_cursor = conn.cursor + + def cursor(): + """ Create Database if not exists. """ + conn.cursor = conn.backup_cursor + + c = conn.cursor() + + try: + c.execute(f'USE {db_name};') + except mysql.connector.errors.ProgrammingError: + c.execute(f'CREATE DATABASE {db_name};') + c.execute(f'USE {db_name};') + + return c + + conn.cursor = cursor + return conn From 891b89cf37af4ae53123a97aeb62d5ce6f5e0676 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Sat, 20 Jul 2024 15:48:58 +0800 Subject: [PATCH 05/14] [fix] to avoid some special bugs, we set default driver to sqlite --- MercurySQL/gensql/database.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MercurySQL/gensql/database.py b/MercurySQL/gensql/database.py index c969e61..d318f22 100644 --- a/MercurySQL/gensql/database.py +++ b/MercurySQL/gensql/database.py @@ -22,6 +22,9 @@ from .table import Table +from ..drivers import sqlite + + # ========== Class Decorations ========== class DataBase: pass @@ -37,7 +40,8 @@ class DataBase: # ========= Tool Functions ========= -default_driver = None +default_driver = sqlite +# Our default driver is sqlite def set_driver(driver): From 7164d6a8b079151c0f9a4fce5f0090389d9fd8cd Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:08:40 +0800 Subject: [PATCH 06/14] [fix] edit bug caused by default driver --- MercurySQL/gensql/database.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MercurySQL/gensql/database.py b/MercurySQL/gensql/database.py index d318f22..0292917 100644 --- a/MercurySQL/gensql/database.py +++ b/MercurySQL/gensql/database.py @@ -40,8 +40,7 @@ class DataBase: # ========= Tool Functions ========= -default_driver = sqlite -# Our default driver is sqlite +default_driver = None def set_driver(driver): From 93b65f1def384d5878efc010e60500f0857d056a Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:09:53 +0800 Subject: [PATCH 07/14] [chore] a small update --- MercurySQL/drivers/mysql_driver.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MercurySQL/drivers/mysql_driver.py b/MercurySQL/drivers/mysql_driver.py index 2e8ac38..cc84eba 100644 --- a/MercurySQL/drivers/mysql_driver.py +++ b/MercurySQL/drivers/mysql_driver.py @@ -22,8 +22,7 @@ class Driver_MySQL(BaseDriver): payload = '%s' Conn = mysql.connector.MySQLConnection - # Cursor = mysql.connector.cursor_cext.CMySQLCursor - Cursor = mysql.connector.cursor.MySQLCursor + Cursor = mysql.connector.cursor_cext.CMySQLCursor class APIs: class gensql: From 29fc09c32e97d4c108f96760b1003cc3943954f8 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 21:10:37 +0800 Subject: [PATCH 08/14] [chore] update requirements --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index ef634fa..023d895 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ sphinx_rtd_theme # for release wheel + +# MySQL +mysql.connector From 9191c10e6455b51efb278f16a6e6601655da9829 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 15:10:52 -0400 Subject: [PATCH 09/14] [feat] new insert syntax support --- MercurySQL/gensql/table.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MercurySQL/gensql/table.py b/MercurySQL/gensql/table.py index 3b5d1e8..c8d2525 100644 --- a/MercurySQL/gensql/table.py +++ b/MercurySQL/gensql/table.py @@ -365,8 +365,15 @@ 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()) + + # 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") From af9809e7ea4476f1d8d24d9ecb55465983d9e629 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 17:09:06 -0400 Subject: [PATCH 10/14] [feat] add support of default values --- MercurySQL/drivers/sqlite.py | 30 ++++++++++++++++++++++++++---- MercurySQL/errors/driver_errors.py | 11 +++++++++++ MercurySQL/gensql/table.py | 22 +++++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/MercurySQL/drivers/sqlite.py b/MercurySQL/drivers/sqlite.py index 548d812..480f2fe 100644 --- a/MercurySQL/drivers/sqlite.py +++ b/MercurySQL/drivers/sqlite.py @@ -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, 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 @@ -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): + 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) diff --git a/MercurySQL/errors/driver_errors.py b/MercurySQL/errors/driver_errors.py index 3cf7a25..c9b9c5d 100644 --- a/MercurySQL/errors/driver_errors.py +++ b/MercurySQL/errors/driver_errors.py @@ -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) \ No newline at end of file diff --git a/MercurySQL/gensql/table.py b/MercurySQL/gensql/table.py index c8d2525..7ed9fd0 100644 --- a/MercurySQL/gensql/table.py +++ b/MercurySQL/gensql/table.py @@ -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. @@ -242,8 +242,15 @@ def newColumn( raise DuplicateError(f"Column `{name}` already exists.") else: return + + # TODO: 写一个函数判断类型和传入的是否一致, + 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) + + # TODO: 再写一个函数可以添加对应的标点符号 if self.isEmpty: # create it first @@ -251,13 +258,14 @@ def newColumn( 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: @@ -295,6 +303,13 @@ def struct( skipError = skipError and force for name, type_ in columns.items(): + default_value = None + + # 支持 type_ 为 [str, "默认值"] 的写法 + if isinstance(type_, list): + default_value = type_[1] + type_ = type_[0] + type_origin = type_ type_ = self.driver.TypeParser.parse(type_) isPrimaryKey = name == primaryKey @@ -306,12 +321,13 @@ def struct( ) 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, + default=default_value, primaryKey=isPrimaryKey, autoIncrement=autoIncrement, ) From 842f41aeff390c0be7c4e49cfbf64e3098b607b4 Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 19:55:52 -0400 Subject: [PATCH 11/14] [fix] finish todo, add new function --- MercurySQL/gensql/table.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/MercurySQL/gensql/table.py b/MercurySQL/gensql/table.py index 7ed9fd0..de6bfc8 100644 --- a/MercurySQL/gensql/table.py +++ b/MercurySQL/gensql/table.py @@ -243,15 +243,12 @@ def newColumn( else: return - # TODO: 写一个函数判断类型和传入的是否一致, 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) - # TODO: 再写一个函数可以添加对应的标点符号 - if self.isEmpty: # create it first cmd = self.driver.APIs.gensql.create_table_if_not_exists( @@ -275,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. @@ -286,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: @@ -304,6 +303,7 @@ def struct( for name, type_ in columns.items(): default_value = None + # 支持 type_ 为 [str, "默认值"] 的写法 if isinstance(type_, list): @@ -315,7 +315,11 @@ def struct( isPrimaryKey = name == primaryKey if name in self.columns: - if type_.lower() != self.columnsType[name].lower(): + if rebuild: + 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_}`." ) @@ -323,14 +327,15 @@ def struct( raise DuplicateError( f"Column `{name}` already exists. You can use `skipError=True` to avoid this error." ) - else: - self.newColumn( - name, - type_origin, - default=default_value, - 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: @@ -342,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: From 981b5bb4f7356dc2449e2f7d0dc8eb5e11c0878d Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 20:13:20 -0400 Subject: [PATCH 12/14] [fix] update type hint for column_default in sqlite driver --- MercurySQL/drivers/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MercurySQL/drivers/sqlite.py b/MercurySQL/drivers/sqlite.py index 480f2fe..8e61738 100644 --- a/MercurySQL/drivers/sqlite.py +++ b/MercurySQL/drivers/sqlite.py @@ -35,7 +35,7 @@ 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, column_default: 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, 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 ''} {'DEFAULT ' + column_default if column_default else ''}) """ From eaaeebff7d9b6efc5062edaf9ceb2d308ba10abd Mon Sep 17 00:00:00 2001 From: lanbinleo Date: Mon, 22 Jul 2024 20:14:41 -0400 Subject: [PATCH 13/14] [fix] update type hint for column_default in sqlite driver --- MercurySQL/drivers/sqlite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MercurySQL/drivers/sqlite.py b/MercurySQL/drivers/sqlite.py index 8e61738..8190d55 100644 --- a/MercurySQL/drivers/sqlite.py +++ b/MercurySQL/drivers/sqlite.py @@ -35,7 +35,7 @@ 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, column_default: str | None, 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 ''} {'DEFAULT ' + column_default if column_default else ''}) """ From 36b483b707d02a0510aef59c66d680d3344d8961 Mon Sep 17 00:00:00 2001 From: Jinyuan Huang Date: Tue, 23 Jul 2024 08:43:02 +0800 Subject: [PATCH 14/14] Update table.py --- MercurySQL/gensql/table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/MercurySQL/gensql/table.py b/MercurySQL/gensql/table.py index de6bfc8..9a8ab75 100644 --- a/MercurySQL/gensql/table.py +++ b/MercurySQL/gensql/table.py @@ -388,6 +388,7 @@ def insert(self, __auto=False, **kwargs) -> None: """ + # TODO: 为了兼容v1.0中的json格式传参的问题,v1.0需要修复 # if __auto is a dict if isinstance(__auto, dict): keys = list(__auto.keys())