bench_executor.mysql
MySQL is an open-source relational database management system developed by Oracle Corporation.
Website: https://www.mysql.com/
Repository: https://github.com/mysql/mysql-server
1#!/usr/bin/env python3 2 3""" 4MySQL is an open-source relational database management system developed by 5Oracle Corporation. 6 7**Website**: https://www.mysql.com/<br> 8**Repository**: https://github.com/mysql/mysql-server 9""" 10 11import os 12import pymysql 13import tempfile 14from csv import reader 15from typing import List, Tuple 16from timeout_decorator import timeout, TimeoutError # type: ignore 17from bench_executor.container import Container 18from bench_executor.logger import Logger 19 20VERSION = '8.0' 21HOST = 'localhost' 22USER = 'root' 23PASSWORD = 'root' 24DB = 'db' 25PORT = '3306' 26CLEAR_TABLES_TIMEOUT = 5 * 60 # 5 minutes 27 28 29class MySQL(Container): 30 """MySQL container for executing SQL queries.""" 31 def __init__(self, data_path: str, config_path: str, directory: str, 32 verbose: bool): 33 """Creates an instance of the MySQL class. 34 35 Parameters 36 ---------- 37 data_path : str 38 Path to the data directory of the case. 39 config_path : str 40 Path to the config directory of the case. 41 directory : str 42 Path to the directory to store logs. 43 verbose : bool 44 Enable verbose logs. 45 """ 46 self._data_path = os.path.abspath(data_path) 47 self._config_path = os.path.abspath(config_path) 48 self._logger = Logger(__name__, directory, verbose) 49 self._tables: List[str] = [] 50 tmp_dir = os.path.join(tempfile.gettempdir(), 'mysql') 51 os.umask(0) 52 os.makedirs(tmp_dir, exist_ok=True) 53 os.makedirs(os.path.join(self._data_path, 'mysql'), exist_ok=True) 54 55 super().__init__(f'blindreviewing/mysql:v{VERSION}', 'MySQL', 56 self._logger, 57 ports={PORT: PORT}, 58 environment={'MYSQL_ROOT_PASSWORD': 'root', 59 'MYSQL_DATABASE': 'db'}, 60 volumes=[f'{self._data_path}/shared/:/data/shared', 61 f'{self._config_path}/mysql/' 62 f'mysql-secure-file-prive.cnf:' 63 f'/etc/mysql/conf.d/' 64 f'mysql-secure-file-prive.cnf', 65 f'{tmp_dir}:/var/lib/mysql']) 66 67 def initialization(self) -> bool: 68 """Initialize MySQL's database. 69 70 Returns 71 ------- 72 success : bool 73 Whether the initialization was successfull or not. 74 """ 75 # MySQL should start with a initialized database, start MySQL 76 # if not initialized to avoid the pre-run start during benchmark 77 # execution 78 success = self.wait_until_ready() 79 if not success: 80 return False 81 success = self.stop() 82 83 return success 84 85 @property 86 def root_mount_directory(self) -> str: 87 """Subdirectory in the root directory of the case for MySQL. 88 89 Returns 90 ------- 91 subdirectory : str 92 Subdirectory of the root directory for MySQL. 93 """ 94 return __name__.lower() 95 96 def wait_until_ready(self, command: str = '') -> bool: 97 """Wait until MySQL is ready to execute SQL queries. 98 99 Parameters 100 ---------- 101 command : str 102 Command to execute in the MySQL container, optionally, defaults to 103 no command. 104 105 Returns 106 ------- 107 success : bool 108 Whether the MySQL was initialized successfull or not. 109 """ 110 log_line = f'port: {PORT} MySQL Community Server - GPL.' 111 return self.run_and_wait_for_log(log_line, command=command) 112 113 def load(self, csv_file: str, table: str) -> bool: 114 """Load a single CSV file into MySQL. 115 116 Parameters 117 ---------- 118 csv_file : str 119 Name of the CSV file. 120 table : str 121 Name of the table. 122 123 Returns 124 ------- 125 success : bool 126 Whether the execution was successfull or not. 127 """ 128 return self._load_csv(csv_file, table, True) 129 130 def load_multiple(self, csv_files: List[dict]) -> bool: 131 """Load multiple CSV files into MySQL. 132 133 Parameters 134 ---------- 135 csv_files : list 136 List of CSV files to load. Each entry consist of a `file` and 137 `table` key. 138 139 Returns 140 ------- 141 success : bool 142 Whether the execution was successfull or not. 143 """ 144 for entry in csv_files: 145 if not self._load_csv(entry['file'], entry['table'], True): 146 return False 147 return True 148 149 def load_sql_schema(self, schema_file: str, 150 csv_files: List[Tuple[str, str]]) -> bool: 151 """Execute SQL schema with MySQL. 152 153 Executes a .sql file with MySQL. 154 If the data is not loaded by the .sql file but only the schema is 155 provided through the .sql file, a list of CSV files can be provided to 156 load them as well. 157 158 Parameters 159 ---------- 160 schema_file : str 161 Name of the .sql file. 162 csv_files : List[Tuple[str, str]] 163 List of CSV file names to load in the tables created with the .sql 164 file, may also be an empty list. Each entry contains a Tuple with 165 the name of the CSV file and the table name. 166 167 Returns 168 ------- 169 success : bool 170 Whether the execution was successfull or not. 171 """ 172 success = True 173 174 # Load SQL schema 175 cmd = f'/bin/sh -c \'mysql --host={HOST} --port={PORT} ' + \ 176 f'--user={USER} --password={PASSWORD} --database={DB} ' + \ 177 f'< /data/shared/{schema_file}\'' 178 success, output = self.exec(cmd) 179 180 # Load CSVs 181 if success: 182 for csv_file, table in csv_files: 183 success = self._load_csv(csv_file, table, False) 184 if not success: 185 break 186 187 return success 188 189 def _load_csv(self, csv_file: str, table: str, create: bool) -> bool: 190 """Load a single CSV file into MySQL. 191 192 Parameters 193 ---------- 194 csv_file : str 195 Name of the CSV file. 196 table : str 197 Name of the table to store the data in. 198 create : bool 199 Whether to drop and create the table or re-use it 200 201 Returns 202 ------- 203 success : bool 204 Whether the execution was successfull or not. 205 """ 206 success = True 207 columns = None 208 table = table.lower() 209 path = os.path.join(self._data_path, 'shared', csv_file) 210 path2 = os.path.join(self._data_path, 'shared', f'tmp_{csv_file}') 211 212 self._tables.append(table) 213 214 # Analyze CSV for loading 215 if not os.path.exists(path): 216 self._logger.error(f'CSV file "{path}" does not exist') 217 return False 218 219 with open(path, 'r') as f: 220 csv_reader = reader(f) 221 columns = next(csv_reader) 222 columns = [x.lower() for x in columns] 223 224 # MySQL cannot set NULL as NULL keyword, use their own specific syntax 225 # for this: \N 226 with open(path, 'r') as f: 227 data = f.read() 228 data = data.replace('NULL', '\\N') 229 230 with open(path2, 'w') as f2: 231 f2.write(data) 232 233 # Load CSV 234 connection = pymysql.connect(host=HOST, user=USER, password=PASSWORD, 235 db=DB) 236 try: 237 cursor = connection.cursor() 238 239 if create: 240 cursor.execute(f'DROP TABLE IF EXISTS {table};') 241 c = ' TEXT , '.join(columns) + ' TEXT' 242 cursor.execute(f'CREATE TABLE {table} (k INT ZEROFILL ' 243 f'NOT NULL AUTO_INCREMENT, {c}, ' 244 f'PRIMARY KEY(k));') 245 c = ','.join(columns) 246 cursor.execute(f'LOAD DATA INFILE \'/data/shared/tmp_{csv_file}\' ' 247 f'INTO TABLE {table} FIELDS TERMINATED BY \',\' ' 248 f'ENCLOSED BY \'\\"\' LINES TERMINATED BY \'\\n\' ' 249 f'IGNORE 1 ROWS ({c});') 250 cursor.execute('COMMIT;') 251 252 header = '| ID | ' + ' | '.join(columns) + ' |' 253 self._logger.debug(header) 254 self._logger.debug('-' * len(header)) 255 256 cursor.execute(f'SELECT * FROM {table};') 257 number_of_records = 0 258 for record in cursor: 259 number_of_records += 1 260 self._logger.debug(record) 261 262 if number_of_records == 0: 263 success = False 264 except Exception as e: 265 self._logger.error(f'Failed to load CSV: "{e}"') 266 success = False 267 finally: 268 connection.close() 269 270 return success 271 272 @timeout(CLEAR_TABLES_TIMEOUT) 273 def _clear_tables(self): 274 """Clears all tables with a provided timeout.""" 275 connection = pymysql.connect(host=HOST, database=DB, 276 user=PASSWORD, password=PASSWORD) 277 cursor = connection.cursor() 278 for table in self._tables: 279 cursor.execute(f'DROP TABLE IF EXISTS {table};') 280 cursor.execute('COMMIT;') 281 self._tables = [] 282 connection.close() 283 284 def stop(self) -> bool: 285 """Stop MySQL 286 Clears all tables and stops the MySQL container. 287 288 Returns 289 ------- 290 success : bool 291 Whether the execution was successfull or not. 292 """ 293 try: 294 self._clear_tables() 295 except TimeoutError: 296 self._logger.warning('Clearing MySQL tables timed out after ' 297 f'{CLEAR_TABLES_TIMEOUT}s!') 298 299 return super().stop() 300 301 302if __name__ == '__main__': 303 print(f'ℹ️ Starting up MySQL v{VERSION}...') 304 m = MySQL('data', 'config', 'log', True) 305 m.wait_until_ready() 306 input('ℹ️ Press any key to stop') 307 m.stop() 308 print('ℹ️ Stopped')
30class MySQL(Container): 31 """MySQL container for executing SQL queries.""" 32 def __init__(self, data_path: str, config_path: str, directory: str, 33 verbose: bool): 34 """Creates an instance of the MySQL class. 35 36 Parameters 37 ---------- 38 data_path : str 39 Path to the data directory of the case. 40 config_path : str 41 Path to the config directory of the case. 42 directory : str 43 Path to the directory to store logs. 44 verbose : bool 45 Enable verbose logs. 46 """ 47 self._data_path = os.path.abspath(data_path) 48 self._config_path = os.path.abspath(config_path) 49 self._logger = Logger(__name__, directory, verbose) 50 self._tables: List[str] = [] 51 tmp_dir = os.path.join(tempfile.gettempdir(), 'mysql') 52 os.umask(0) 53 os.makedirs(tmp_dir, exist_ok=True) 54 os.makedirs(os.path.join(self._data_path, 'mysql'), exist_ok=True) 55 56 super().__init__(f'blindreviewing/mysql:v{VERSION}', 'MySQL', 57 self._logger, 58 ports={PORT: PORT}, 59 environment={'MYSQL_ROOT_PASSWORD': 'root', 60 'MYSQL_DATABASE': 'db'}, 61 volumes=[f'{self._data_path}/shared/:/data/shared', 62 f'{self._config_path}/mysql/' 63 f'mysql-secure-file-prive.cnf:' 64 f'/etc/mysql/conf.d/' 65 f'mysql-secure-file-prive.cnf', 66 f'{tmp_dir}:/var/lib/mysql']) 67 68 def initialization(self) -> bool: 69 """Initialize MySQL's database. 70 71 Returns 72 ------- 73 success : bool 74 Whether the initialization was successfull or not. 75 """ 76 # MySQL should start with a initialized database, start MySQL 77 # if not initialized to avoid the pre-run start during benchmark 78 # execution 79 success = self.wait_until_ready() 80 if not success: 81 return False 82 success = self.stop() 83 84 return success 85 86 @property 87 def root_mount_directory(self) -> str: 88 """Subdirectory in the root directory of the case for MySQL. 89 90 Returns 91 ------- 92 subdirectory : str 93 Subdirectory of the root directory for MySQL. 94 """ 95 return __name__.lower() 96 97 def wait_until_ready(self, command: str = '') -> bool: 98 """Wait until MySQL is ready to execute SQL queries. 99 100 Parameters 101 ---------- 102 command : str 103 Command to execute in the MySQL container, optionally, defaults to 104 no command. 105 106 Returns 107 ------- 108 success : bool 109 Whether the MySQL was initialized successfull or not. 110 """ 111 log_line = f'port: {PORT} MySQL Community Server - GPL.' 112 return self.run_and_wait_for_log(log_line, command=command) 113 114 def load(self, csv_file: str, table: str) -> bool: 115 """Load a single CSV file into MySQL. 116 117 Parameters 118 ---------- 119 csv_file : str 120 Name of the CSV file. 121 table : str 122 Name of the table. 123 124 Returns 125 ------- 126 success : bool 127 Whether the execution was successfull or not. 128 """ 129 return self._load_csv(csv_file, table, True) 130 131 def load_multiple(self, csv_files: List[dict]) -> bool: 132 """Load multiple CSV files into MySQL. 133 134 Parameters 135 ---------- 136 csv_files : list 137 List of CSV files to load. Each entry consist of a `file` and 138 `table` key. 139 140 Returns 141 ------- 142 success : bool 143 Whether the execution was successfull or not. 144 """ 145 for entry in csv_files: 146 if not self._load_csv(entry['file'], entry['table'], True): 147 return False 148 return True 149 150 def load_sql_schema(self, schema_file: str, 151 csv_files: List[Tuple[str, str]]) -> bool: 152 """Execute SQL schema with MySQL. 153 154 Executes a .sql file with MySQL. 155 If the data is not loaded by the .sql file but only the schema is 156 provided through the .sql file, a list of CSV files can be provided to 157 load them as well. 158 159 Parameters 160 ---------- 161 schema_file : str 162 Name of the .sql file. 163 csv_files : List[Tuple[str, str]] 164 List of CSV file names to load in the tables created with the .sql 165 file, may also be an empty list. Each entry contains a Tuple with 166 the name of the CSV file and the table name. 167 168 Returns 169 ------- 170 success : bool 171 Whether the execution was successfull or not. 172 """ 173 success = True 174 175 # Load SQL schema 176 cmd = f'/bin/sh -c \'mysql --host={HOST} --port={PORT} ' + \ 177 f'--user={USER} --password={PASSWORD} --database={DB} ' + \ 178 f'< /data/shared/{schema_file}\'' 179 success, output = self.exec(cmd) 180 181 # Load CSVs 182 if success: 183 for csv_file, table in csv_files: 184 success = self._load_csv(csv_file, table, False) 185 if not success: 186 break 187 188 return success 189 190 def _load_csv(self, csv_file: str, table: str, create: bool) -> bool: 191 """Load a single CSV file into MySQL. 192 193 Parameters 194 ---------- 195 csv_file : str 196 Name of the CSV file. 197 table : str 198 Name of the table to store the data in. 199 create : bool 200 Whether to drop and create the table or re-use it 201 202 Returns 203 ------- 204 success : bool 205 Whether the execution was successfull or not. 206 """ 207 success = True 208 columns = None 209 table = table.lower() 210 path = os.path.join(self._data_path, 'shared', csv_file) 211 path2 = os.path.join(self._data_path, 'shared', f'tmp_{csv_file}') 212 213 self._tables.append(table) 214 215 # Analyze CSV for loading 216 if not os.path.exists(path): 217 self._logger.error(f'CSV file "{path}" does not exist') 218 return False 219 220 with open(path, 'r') as f: 221 csv_reader = reader(f) 222 columns = next(csv_reader) 223 columns = [x.lower() for x in columns] 224 225 # MySQL cannot set NULL as NULL keyword, use their own specific syntax 226 # for this: \N 227 with open(path, 'r') as f: 228 data = f.read() 229 data = data.replace('NULL', '\\N') 230 231 with open(path2, 'w') as f2: 232 f2.write(data) 233 234 # Load CSV 235 connection = pymysql.connect(host=HOST, user=USER, password=PASSWORD, 236 db=DB) 237 try: 238 cursor = connection.cursor() 239 240 if create: 241 cursor.execute(f'DROP TABLE IF EXISTS {table};') 242 c = ' TEXT , '.join(columns) + ' TEXT' 243 cursor.execute(f'CREATE TABLE {table} (k INT ZEROFILL ' 244 f'NOT NULL AUTO_INCREMENT, {c}, ' 245 f'PRIMARY KEY(k));') 246 c = ','.join(columns) 247 cursor.execute(f'LOAD DATA INFILE \'/data/shared/tmp_{csv_file}\' ' 248 f'INTO TABLE {table} FIELDS TERMINATED BY \',\' ' 249 f'ENCLOSED BY \'\\"\' LINES TERMINATED BY \'\\n\' ' 250 f'IGNORE 1 ROWS ({c});') 251 cursor.execute('COMMIT;') 252 253 header = '| ID | ' + ' | '.join(columns) + ' |' 254 self._logger.debug(header) 255 self._logger.debug('-' * len(header)) 256 257 cursor.execute(f'SELECT * FROM {table};') 258 number_of_records = 0 259 for record in cursor: 260 number_of_records += 1 261 self._logger.debug(record) 262 263 if number_of_records == 0: 264 success = False 265 except Exception as e: 266 self._logger.error(f'Failed to load CSV: "{e}"') 267 success = False 268 finally: 269 connection.close() 270 271 return success 272 273 @timeout(CLEAR_TABLES_TIMEOUT) 274 def _clear_tables(self): 275 """Clears all tables with a provided timeout.""" 276 connection = pymysql.connect(host=HOST, database=DB, 277 user=PASSWORD, password=PASSWORD) 278 cursor = connection.cursor() 279 for table in self._tables: 280 cursor.execute(f'DROP TABLE IF EXISTS {table};') 281 cursor.execute('COMMIT;') 282 self._tables = [] 283 connection.close() 284 285 def stop(self) -> bool: 286 """Stop MySQL 287 Clears all tables and stops the MySQL container. 288 289 Returns 290 ------- 291 success : bool 292 Whether the execution was successfull or not. 293 """ 294 try: 295 self._clear_tables() 296 except TimeoutError: 297 self._logger.warning('Clearing MySQL tables timed out after ' 298 f'{CLEAR_TABLES_TIMEOUT}s!') 299 300 return super().stop()
MySQL container for executing SQL queries.
MySQL(data_path: str, config_path: str, directory: str, verbose: bool)
32 def __init__(self, data_path: str, config_path: str, directory: str, 33 verbose: bool): 34 """Creates an instance of the MySQL class. 35 36 Parameters 37 ---------- 38 data_path : str 39 Path to the data directory of the case. 40 config_path : str 41 Path to the config directory of the case. 42 directory : str 43 Path to the directory to store logs. 44 verbose : bool 45 Enable verbose logs. 46 """ 47 self._data_path = os.path.abspath(data_path) 48 self._config_path = os.path.abspath(config_path) 49 self._logger = Logger(__name__, directory, verbose) 50 self._tables: List[str] = [] 51 tmp_dir = os.path.join(tempfile.gettempdir(), 'mysql') 52 os.umask(0) 53 os.makedirs(tmp_dir, exist_ok=True) 54 os.makedirs(os.path.join(self._data_path, 'mysql'), exist_ok=True) 55 56 super().__init__(f'blindreviewing/mysql:v{VERSION}', 'MySQL', 57 self._logger, 58 ports={PORT: PORT}, 59 environment={'MYSQL_ROOT_PASSWORD': 'root', 60 'MYSQL_DATABASE': 'db'}, 61 volumes=[f'{self._data_path}/shared/:/data/shared', 62 f'{self._config_path}/mysql/' 63 f'mysql-secure-file-prive.cnf:' 64 f'/etc/mysql/conf.d/' 65 f'mysql-secure-file-prive.cnf', 66 f'{tmp_dir}:/var/lib/mysql'])
Creates an instance of the MySQL class.
Parameters
- data_path (str): Path to the data directory of the case.
- config_path (str): Path to the config directory of the case.
- directory (str): Path to the directory to store logs.
- verbose (bool): Enable verbose logs.
def
initialization(self) -> bool:
68 def initialization(self) -> bool: 69 """Initialize MySQL's database. 70 71 Returns 72 ------- 73 success : bool 74 Whether the initialization was successfull or not. 75 """ 76 # MySQL should start with a initialized database, start MySQL 77 # if not initialized to avoid the pre-run start during benchmark 78 # execution 79 success = self.wait_until_ready() 80 if not success: 81 return False 82 success = self.stop() 83 84 return success
Initialize MySQL's database.
Returns
- success (bool): Whether the initialization was successfull or not.
root_mount_directory: str
Subdirectory in the root directory of the case for MySQL.
Returns
- subdirectory (str): Subdirectory of the root directory for MySQL.
def
wait_until_ready(self, command: str = '') -> bool:
97 def wait_until_ready(self, command: str = '') -> bool: 98 """Wait until MySQL is ready to execute SQL queries. 99 100 Parameters 101 ---------- 102 command : str 103 Command to execute in the MySQL container, optionally, defaults to 104 no command. 105 106 Returns 107 ------- 108 success : bool 109 Whether the MySQL was initialized successfull or not. 110 """ 111 log_line = f'port: {PORT} MySQL Community Server - GPL.' 112 return self.run_and_wait_for_log(log_line, command=command)
Wait until MySQL is ready to execute SQL queries.
Parameters
- command (str): Command to execute in the MySQL container, optionally, defaults to no command.
Returns
- success (bool): Whether the MySQL was initialized successfull or not.
def
load(self, csv_file: str, table: str) -> bool:
114 def load(self, csv_file: str, table: str) -> bool: 115 """Load a single CSV file into MySQL. 116 117 Parameters 118 ---------- 119 csv_file : str 120 Name of the CSV file. 121 table : str 122 Name of the table. 123 124 Returns 125 ------- 126 success : bool 127 Whether the execution was successfull or not. 128 """ 129 return self._load_csv(csv_file, table, True)
Load a single CSV file into MySQL.
Parameters
- csv_file (str): Name of the CSV file.
- table (str): Name of the table.
Returns
- success (bool): Whether the execution was successfull or not.
def
load_multiple(self, csv_files: List[dict]) -> bool:
131 def load_multiple(self, csv_files: List[dict]) -> bool: 132 """Load multiple CSV files into MySQL. 133 134 Parameters 135 ---------- 136 csv_files : list 137 List of CSV files to load. Each entry consist of a `file` and 138 `table` key. 139 140 Returns 141 ------- 142 success : bool 143 Whether the execution was successfull or not. 144 """ 145 for entry in csv_files: 146 if not self._load_csv(entry['file'], entry['table'], True): 147 return False 148 return True
Load multiple CSV files into MySQL.
Parameters
- csv_files (list):
List of CSV files to load. Each entry consist of a
file
andtable
key.
Returns
- success (bool): Whether the execution was successfull or not.
def
load_sql_schema(self, schema_file: str, csv_files: List[Tuple[str, str]]) -> bool:
150 def load_sql_schema(self, schema_file: str, 151 csv_files: List[Tuple[str, str]]) -> bool: 152 """Execute SQL schema with MySQL. 153 154 Executes a .sql file with MySQL. 155 If the data is not loaded by the .sql file but only the schema is 156 provided through the .sql file, a list of CSV files can be provided to 157 load them as well. 158 159 Parameters 160 ---------- 161 schema_file : str 162 Name of the .sql file. 163 csv_files : List[Tuple[str, str]] 164 List of CSV file names to load in the tables created with the .sql 165 file, may also be an empty list. Each entry contains a Tuple with 166 the name of the CSV file and the table name. 167 168 Returns 169 ------- 170 success : bool 171 Whether the execution was successfull or not. 172 """ 173 success = True 174 175 # Load SQL schema 176 cmd = f'/bin/sh -c \'mysql --host={HOST} --port={PORT} ' + \ 177 f'--user={USER} --password={PASSWORD} --database={DB} ' + \ 178 f'< /data/shared/{schema_file}\'' 179 success, output = self.exec(cmd) 180 181 # Load CSVs 182 if success: 183 for csv_file, table in csv_files: 184 success = self._load_csv(csv_file, table, False) 185 if not success: 186 break 187 188 return success
Execute SQL schema with MySQL.
Executes a .sql file with MySQL. If the data is not loaded by the .sql file but only the schema is provided through the .sql file, a list of CSV files can be provided to load them as well.
Parameters
- schema_file (str): Name of the .sql file.
- csv_files (List[Tuple[str, str]]): List of CSV file names to load in the tables created with the .sql file, may also be an empty list. Each entry contains a Tuple with the name of the CSV file and the table name.
Returns
- success (bool): Whether the execution was successfull or not.
def
stop(self) -> bool:
285 def stop(self) -> bool: 286 """Stop MySQL 287 Clears all tables and stops the MySQL container. 288 289 Returns 290 ------- 291 success : bool 292 Whether the execution was successfull or not. 293 """ 294 try: 295 self._clear_tables() 296 except TimeoutError: 297 self._logger.warning('Clearing MySQL tables timed out after ' 298 f'{CLEAR_TABLES_TIMEOUT}s!') 299 300 return super().stop()
Stop MySQL Clears all tables and stops the MySQL container.
Returns
- success (bool): Whether the execution was successfull or not.