From ac678cd5ab2c17591d24c866db3236cc53efa19e Mon Sep 17 00:00:00 2001 From: Qiushi Bai Date: Thu, 20 Apr 2023 10:33:47 -0700 Subject: [PATCH 1/2] Renaming rules.sql to schema.sql; Redefining the schema (not done); --- core/data_manager.py | 6 +- querybooster.db.bak | Bin 0 -> 106496 bytes schema/{rules.sql => schema.sql} | 98 +++++++++++++++++++++---------- 3 files changed, 70 insertions(+), 34 deletions(-) create mode 100644 querybooster.db.bak rename schema/{rules.sql => schema.sql} (51%) diff --git a/core/data_manager.py b/core/data_manager.py index cf73eb7..5f6bbd2 100644 --- a/core/data_manager.py +++ b/core/data_manager.py @@ -15,9 +15,9 @@ def __init_schema(self) -> None: try: cur = self.db_conn.cursor() schema_path = Path(__file__).parent / "../schema" - with open(schema_path / 'rules.sql') as rules_sql_file: - rules_sql = rules_sql_file.read() - cur.executescript(rules_sql) + with open(schema_path / 'schema.sql') as schema_sql_file: + schema_sql = schema_sql_file.read() + cur.executescript(schema_sql) except Error as e: print(e) diff --git a/querybooster.db.bak b/querybooster.db.bak new file mode 100644 index 0000000000000000000000000000000000000000..9ed9b5971945a63316290e60d05bd4f8abff8d4c GIT binary patch literal 106496 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 aDREH2I>CVb4AP{;fjtlN|37MR4gNnEA-T)| literal 0 HcmV?d00001 diff --git a/schema/rules.sql b/schema/schema.sql similarity index 51% rename from schema/rules.sql rename to schema/schema.sql index 7f4f61f..d56dedf 100644 --- a/schema/rules.sql +++ b/schema/schema.sql @@ -5,17 +5,7 @@ CREATE TABLE IF NOT EXISTS rules( pattern TEXT, constraints TEXT, rewrite TEXT, - actions TEXT, - database VARCHAR(255) NOT NULL -); - -CREATE TABLE IF NOT EXISTS disable_rules( - rule_id INTEGER UNIQUE, - disabled BOOLEAN, - CONSTRAINT fk_rules - FOREIGN KEY (rule_id) - REFERENCES rules(id) - ON DELETE CASCADE + actions TEXT ); CREATE TABLE IF NOT EXISTS internal_rules( @@ -30,14 +20,43 @@ CREATE TABLE IF NOT EXISTS internal_rules( ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS query_logs( +CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY, + email TEXT +); + +CREATE TABLE IF NOT EXISTS applications( + id INTEGER PRIMARY KEY, + name TEXT, + guid TEXT, + user_id INTEGER, + CONSTRAINT fk_users + FOREIGN KEY (user_id) + REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS enabled( + application_id INTEGER, + rule_id INTEGER + PRIMARY KEY (application_id, rule_id), + CONSTRAINT fk_applications + FOREIGN KEY (application_id) + REFERENCES applications(id) + ON DELETE CASCADE + CONSTRAINT fk_rules + FOREIGN KEY (rule_id) + REFERENCES rules(id) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS queries( id INTEGER PRIMARY KEY, - appguid TEXT, guid TEXT, + appguid TEXT, timestamp TEXT, query_time_ms REAL, original_sql TEXT, - rewritten_sql TEXT + sql TEXT ); CREATE TABLE IF NOT EXISTS rewriting_paths( @@ -48,12 +67,10 @@ CREATE TABLE IF NOT EXISTS rewriting_paths( PRIMARY KEY (query_id, seq), CONSTRAINT fk_queries FOREIGN KEY (query_id) - REFERENCES query_logs(id) - ON DELETE CASCADE, + REFERENCES queries(id), CONSTRAINT fk_rules FOREIGN KEY (rule_id) REFERENCES rules(id) - ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS suggestions( @@ -62,7 +79,7 @@ CREATE TABLE IF NOT EXISTS suggestions( rewritten_sql TEXT, CONSTRAINT fk_queries FOREIGN KEY (query_id) - REFERENCES query_logs(id) + REFERENCES queries(id) ON DELETE CASCADE ); @@ -74,26 +91,45 @@ CREATE TABLE IF NOT EXISTS suggestion_rewriting_paths( PRIMARY KEY (query_id, seq), CONSTRAINT fk_queries FOREIGN KEY (query_id) - REFERENCES suggestions(query_id) - ON DELETE CASCADE, + REFERENCES suggestions(query_id), CONSTRAINT fk_rules FOREIGN KEY (rule_id) REFERENCES rules(id) +); + +CREATE TABLE IF NOT EXISTS tables( + id INTEGER PRIMARY KEY, + application_id INTEGER, + name TEXT, + CONSTRAINT fk_applications + FOREIGN KEY (application_id) + REFERENCES applications(id) + ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS columns( + id INTEGER PRIMARY KEY, + table_id INTEGER, + name TEXT, + type TEXT, + CONSTRAINT fk_tables + FOREIGN KEY (table_id) + REFERENCES tables(id) ON DELETE CASCADE ); -CREATE VIEW IF NOT EXISTS queries AS -SELECT ql.id AS id, - ql.timestamp AS timestamp, - (CASE WHEN ql.original_sql = ql.rewritten_sql THEN 'NO' - WHEN ql.original_sql != ql.rewritten_sql THEN 'YES' - END) AS boosted, - (SELECT AVG(ql1.query_time_ms) FROM query_logs ql1 WHERE ql1.rewritten_sql=ql.original_sql) AS before_latency, - ql.query_time_ms AS after_latency, - ql.original_sql AS sql, +CREATE VIEW IF NOT EXISTS query_log AS +SELECT q.id AS id, + q.timestamp AS timestamp, + (CASE WHEN q.sql = q.original_sql THEN 'NO' + WHEN q.sql != q.original_sql THEN 'YES' + END) AS rewritten, + (SELECT AVG(q1.query_time_ms) FROM queries q1 WHERE q1.sql=ql.original_sql) AS before_latency, + q.query_time_ms AS after_latency, + q.original_sql AS sql, (CASE WHEN s.query_id IS NOT NULL THEN 'YES' ELSE 'NO' END) AS suggestion, (CASE WHEN s.query_id IS NOT NULL THEN s.query_time_ms ELSE -1000 END) AS suggested_latency - FROM query_logs ql LEFT OUTER JOIN suggestions s ON ql.id = s.query_id - ORDER BY ql.timestamp DESC; \ No newline at end of file + FROM queries q LEFT OUTER JOIN suggestions s ON q.id = s.query_id + ORDER BY q.timestamp DESC; \ No newline at end of file From 33ae43d361c70650c476a945e23f4a50010f5b22 Mon Sep 17 00:00:00 2001 From: Qiushi Bai Date: Thu, 15 Jun 2023 09:45:43 -0700 Subject: [PATCH 2/2] [Schema Update] This commit contains major changes in both FE and BE to use the new schema that supports applications and users management. --- client/src/App.js | 10 +- client/src/dashboard/ApplicationSelect.js | 85 ++++++++++ client/src/dashboard/ApplicationTag.js | 78 +++++++++ client/src/dashboard/QueryLogs.js | 9 +- client/src/dashboard/RewritingRules.js | 16 +- client/src/mock-api/listApplications.js | 16 ++ client/src/mock-api/listQueries.js | 30 ++-- client/src/mock-api/listRules.js | 14 +- client/src/userContext.js | 5 + core/app_manager.py | 23 +++ core/data_manager.py | 183 ++++++++++++++++----- core/{query_logger.py => query_manager.py} | 11 +- core/rule_manager.py | 50 ++++-- querybooster.db.bak | Bin 106496 -> 0 bytes schema/schema.sql | 23 ++- server/server.py | 95 +++++++++-- 16 files changed, 533 insertions(+), 115 deletions(-) create mode 100644 client/src/dashboard/ApplicationSelect.js create mode 100644 client/src/dashboard/ApplicationTag.js create mode 100644 client/src/mock-api/listApplications.js create mode 100644 client/src/userContext.js create mode 100644 core/app_manager.py rename core/{query_logger.py => query_manager.py} (85%) delete mode 100644 querybooster.db.bak diff --git a/client/src/App.js b/client/src/App.js index 591b8b8..18bb37b 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,9 +1,15 @@ +import React from 'react'; import logo from './logo.svg'; import './App.css'; import Dashboard from './dashboard/Dashboard'; +// user context +import {userContext} from './userContext'; function App() { document.title = 'QueryBooster'; + + const [user, setUser] = React.useState({"id": 1, "email": "alice@ics.uci.edu"}); + return (
{/*
@@ -20,7 +26,9 @@ function App() { Learn React
*/} - + + +
); } diff --git a/client/src/dashboard/ApplicationSelect.js b/client/src/dashboard/ApplicationSelect.js new file mode 100644 index 0000000..9947231 --- /dev/null +++ b/client/src/dashboard/ApplicationSelect.js @@ -0,0 +1,85 @@ +import React, { useState, useCallback } from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogActions from '@mui/material/DialogActions'; +import NiceModal, { useModal } from '@ebay/nice-modal-react'; +import axios from 'axios'; +import defaultApplicationsData from '../mock-api/listApplications'; + +const ApplicationSelect = NiceModal.create(({ user }) => { + const modal = useModal(); + // Set up a state for list of applications + const [applications, setApplications] = React.useState([]); + // Set up a state for selected application Id + const [selectedAppId, setSelectedAppId] = useState(-1); + + const handleSelectChange = (event) => { + setSelectedAppId(event.target.value); + }; + + // initial loading applications for the current user from server + const listApplications = (user) => { + console.log('[/listApplications] -> request:'); + console.log(' user_id: ' + user.id); + // post listApplications request to server + axios.post('/listApplications', { 'user_id': user.id }) + .then(function (response) { + console.log('[/listApplications] -> response:'); + console.log(response); + // update the state for list of applications + setApplications(response.data); + }) + .catch(function (error) { + console.log('[/listApplications] -> error:'); + console.log(error); + // mock the result + console.log(defaultApplicationsData); + setApplications(defaultApplicationsData); + }); + }; + + // call listApplications() only once after initial rendering + React.useEffect(() => { listApplications(user) }, []); + + const handleSubmit = useCallback(() => { + const selectedApplication = applications.find((app) => app.id == selectedAppId); + console.log("[ApplicationSelect] selectedAppId = " + selectedAppId); + console.log("[ApplicationSelect] applications = "); + console.log(applications); + console.log("[ApplicationSelect] find selected application = "); + console.log(selectedApplication); + modal.resolve(selectedApplication); + modal.hide(); + }, [modal]); + + return ( + modal.hide()} + TransitionProps={{ + onExited: () => modal.remove(), + }} + maxWidth={'sm'} + > + Enable Rule for Application + + + + + + + + + + ); +}); + +export default ApplicationSelect; \ No newline at end of file diff --git a/client/src/dashboard/ApplicationTag.js b/client/src/dashboard/ApplicationTag.js new file mode 100644 index 0000000..48ddf69 --- /dev/null +++ b/client/src/dashboard/ApplicationTag.js @@ -0,0 +1,78 @@ +import * as React from 'react'; +import axios from 'axios'; +import { useModal } from '@ebay/nice-modal-react'; +import ApplicationSelect from './ApplicationSelect'; +import {userContext} from '../userContext'; + +function AppTagCell({ruleId: initialRuleId, tags: initialApps }) { + const [ruleId, setRule] = React.useState(initialRuleId); + const [apps, setApps] = React.useState(initialApps); + // Set up a state for providing forceUpdate function + const [, updateState] = React.useState(); + const forceUpdate = React.useCallback(() => updateState({}), []); + + const applicationSelectModal = useModal(ApplicationSelect); + + const user = React.useContext(userContext); + + function handleSelect(selectedApplication) { + if (selectedApplication) { + // post enableRule request to server + axios.post('/enableRule', {'rule': {'id': ruleId}, 'app': selectedApplication}) + .then(function (response) { + console.log('[/enableRule] -> response:'); + console.log(response); + setApps([...apps, {'app_id': selectedApplication.id, 'app_name': selectedApplication.name}]); + forceUpdate(); + }) + .catch(function (error) { + console.log('[/enableRule] -> error:'); + console.log(error); + // TODO - alter the entered application name doest not exist + }); + } + } + + const handleAddApp = React.useCallback(() => { + applicationSelectModal.show({user}).then((selectedApplication) => { + console.log("[ApplicationTag] selectedApplication = "); + console.log(selectedApplication); + handleSelect(selectedApplication); + }); + }, [applicationSelectModal]); + + function handleRemoveApp(app) { + // post disableRule request to server + axios.post('/disableRule', {'rule': {'id': ruleId}, 'app': {'id': app.app_id, 'name': app.app_name}}) + .then(function (response) { + console.log('[/disableRule] -> response:'); + console.log(response); + const updatedApps = apps.filter((a) => a !== app); + setApps(updatedApps); + forceUpdate(); + }) + .catch(function (error) { + console.log('[/disableRule] -> error:'); + console.log(error); + }); + } + + return ( +
+ {apps.map((app) => ( + + {app.app_name} + + + ))} + +
+ ); +} + +export default AppTagCell; diff --git a/client/src/dashboard/QueryLogs.js b/client/src/dashboard/QueryLogs.js index cb7a633..2942b67 100644 --- a/client/src/dashboard/QueryLogs.js +++ b/client/src/dashboard/QueryLogs.js @@ -12,6 +12,7 @@ import defaultQueriesData from '../mock-api/listQueries'; import QueryRewritingPath from './QueryRewritingPath'; import SyntaxHighlighter from 'react-syntax-highlighter'; import { vs } from 'react-syntax-highlighter/dist/esm/styles/hljs'; +import {userContext} from '../userContext'; export default function QueryLogs() { @@ -21,10 +22,12 @@ export default function QueryLogs() { const [, updateState] = React.useState(); const forceUpdate = React.useCallback(() => updateState({}), []); + const user = React.useContext(userContext); + // initial loading queries from server const listQueries = (_page) => { // post listQueries request to server - axios.post('/listQueries', {page: _page}) + axios.post('/listQueries', {page: _page, 'user_id': user.id}) .then(function (response) { console.log('[/listQueries] -> response:'); console.log(response); @@ -57,8 +60,9 @@ export default function QueryLogs() { ID + App Timestamp - Boosted + Rewritten Before Latency(s) After Latency(s) SQL @@ -70,6 +74,7 @@ export default function QueryLogs() { {queries.map((query) => ( selectQuery(query)}> {query.id} + {query.app_name} {query.timestamp} {query.boosted} {query.before_latency/1000} diff --git a/client/src/dashboard/RewritingRules.js b/client/src/dashboard/RewritingRules.js index f94fc91..03dac57 100644 --- a/client/src/dashboard/RewritingRules.js +++ b/client/src/dashboard/RewritingRules.js @@ -10,13 +10,14 @@ import TableCell from '@mui/material/TableCell'; import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; -import Switch from '@mui/material/Switch'; import Title from './Title'; import defaultRulesData from '../mock-api/listRules'; import SyntaxHighlighter from 'react-syntax-highlighter'; import { vs } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import { Box } from '@mui/material'; import AddRewritingRule from './AddRewritingRule'; +import AppTagCell from './ApplicationTag'; +import {userContext} from '../userContext'; export default function RewrittingRules() { @@ -26,10 +27,14 @@ export default function RewrittingRules() { const [, updateState] = React.useState(); const forceUpdate = React.useCallback(() => updateState({}), []); + const user = React.useContext(userContext); + // initial loading rules from server const listRules = () => { + console.log('[/listRules] -> request:'); + console.log(' user_id: ' + user.id); // post listRules request to server - axios.post('/listRules', {}) + axios.post('/listRules', {'user_id': user.id}) .then(function (response) { console.log('[/listRules] -> response:'); console.log(response); @@ -119,7 +124,7 @@ export default function RewrittingRules() { Name Pattern Rewrite - Enabled + Enabled Apps Delete @@ -139,10 +144,11 @@ export default function RewrittingRules() { - handleChange(event, rule)} - inputProps={{ 'aria-label': 'controlled' }} /> + inputProps={{ 'aria-label': 'controlled' }} /> */} + diff --git a/client/src/mock-api/listApplications.js b/client/src/mock-api/listApplications.js new file mode 100644 index 0000000..7e14f46 --- /dev/null +++ b/client/src/mock-api/listApplications.js @@ -0,0 +1,16 @@ +const defaultApplicationsData = [ + { + "id": 1, + "name": "TwitterPg" + }, + { + "id": 2, + "name": "TpchPg" + }, + { + "id": 3, + "name": "TwitterMySQL" + } +]; + +export default defaultApplicationsData; \ No newline at end of file diff --git a/client/src/mock-api/listQueries.js b/client/src/mock-api/listQueries.js index 2b1ca2f..dbfc26b 100644 --- a/client/src/mock-api/listQueries.js +++ b/client/src/mock-api/listQueries.js @@ -2,36 +2,34 @@ const defaultQueriesData = [ { "id": 1, "timestamp": "2022-10-12 16:36:03", - "latency": 3, - "original_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", + "rewritten": 'YES', + "before_latency": 35000, + "after_latency": 3200, + "sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", CAST("tweets"."state_name" AS TEXT) AS "state_name" FROM "public"."tweets" "tweets" WHERE ((CAST(DATE_TRUNC('QUARTER', CAST("tweets"."created_at" AS DATE)) AS DATE) IN ((TIMESTAMP '2016-04-01 00:00:00.000'), (TIMESTAMP '2016-07-01 00:00:00.000'), (TIMESTAMP '2016-10-01 00:00:00.000'), (TIMESTAMP '2017-01-01 00:00:00.000'))) AND (STRPOS(CAST(LOWER(CAST(CAST("tweets"."text" AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0)) GROUP BY 2`, - "rewritten_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", - tweets.state_name AS state_name - FROM public.tweets AS tweets - WHERE DATE_TRUNC('QUARTER', tweets.created_at) IN ((TIMESTAMP '2016-04-01 00:00:00.000'), (TIMESTAMP '2016-07-01 00:00:00.000'), (TIMESTAMP '2016-10-01 00:00:00.000'), (TIMESTAMP '2017-01-01 00:00:00.000')) - AND tweets.text ILIKE '%iphone%' - GROUP BY 2` + "suggestion": "NO", + "suggested_latency": -1000, + "app_name": "TwitterPg" }, { "id": 0, "timestamp": "2022-10-12 16:31:42", - "latency": 34, - "original_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", + "rewritten": 'YES', + "before_latency": 32000, + "after_latency": 2800, + "sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", CAST("tweets"."state_name" AS TEXT) AS "state_name" FROM "public"."tweets" "tweets" WHERE ((CAST(DATE_TRUNC('QUARTER', CAST("tweets"."created_at" AS DATE)) AS DATE) IN ((TIMESTAMP '2017-10-01 00:00:00.000'), (TIMESTAMP '2018-01-01 00:00:00.000'), (TIMESTAMP '2018-04-01 00:00:00.000'))) AND (STRPOS(CAST(LOWER(CAST(CAST("tweets"."text" AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0)) GROUP BY 2`, - "rewritten_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok", - CAST(tweets.state_name AS TEXT) AS state_name - FROM public.tweets AS tweets - WHERE CAST(DATE_TRUNC('QUARTER', CAST(tweets.created_at AS DATE)) AS DATE) IN ((TIMESTAMP '2017-10-01 00:00:00.000'), (TIMESTAMP '2018-01-01 00:00:00.000'), (TIMESTAMP '2018-04-01 00:00:00.000')) - AND STRPOS(CAST(LOWER(CAST(CAST(tweets.text AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0 - GROUP BY 2` + "suggestion": "NO", + "suggested_latency": -1000, + "app_name": "TwitterPg" } ]; diff --git a/client/src/mock-api/listRules.js b/client/src/mock-api/listRules.js index 209573d..132db95 100644 --- a/client/src/mock-api/listRules.js +++ b/client/src/mock-api/listRules.js @@ -7,7 +7,7 @@ const defaultRulesData = [ "constraints": "", "rewrite": "MAX()", "actions": "", - "enabled": true + "enabled_apps": [{"app_id": 1, "app_name": "TwitterPg"}] }, { "id": 10, @@ -17,7 +17,7 @@ const defaultRulesData = [ "constraints": "TYPE(x)=DATE", "rewrite": "", "actions": "", - "enabled": false + "enabled_apps": [{"app_id": 1, "app_name": "TwitterPg"}, {"app_id": 3, "app_name": "TwitterMySQL"}] }, { "id": 11, @@ -27,7 +27,7 @@ const defaultRulesData = [ "constraints": "TYPE(x)=TEXT", "rewrite": "", "actions": "", - "enabled": false + "enabled_apps": [{"app_id": 1, "app_name": "TwitterPg"}, {"app_id": 3, "app_name": "TwitterMySQL"}] }, { "id": 21, @@ -37,7 +37,7 @@ const defaultRulesData = [ "constraints": "IS(y)=CONSTANT and\nTYPE(y)=STRING", "rewrite": " ILIKE '%%'", "actions": "", - "enabled": false + "enabled_apps": [{"app_id": 1, "app_name": "TwitterPg"}, {"app_id": 3, "app_name": "TwitterMySQL"}] }, { "id": 30, @@ -47,7 +47,7 @@ const defaultRulesData = [ "constraints": "UNIQUE(tb1, a1)", "rewrite": "select <> \nfrom \nwhere 1=1 \nand <>\n", "actions": "SUBSTITUTE(s1, t2, t1) and\nSUBSTITUTE(p1, t2, t1)", - "enabled": true + "enabled_apps": [{"app_id": 1, "app_name": "TwitterPg"}, {"app_id": 3, "app_name": "TwitterMySQL"}] }, { "id": 101, @@ -57,7 +57,7 @@ const defaultRulesData = [ "constraints": "", "rewrite": "", "actions": "", - "enabled": true + "enabled_apps": [{"app_id": 3, "app_name": "TwitterMySQL"}] }, { "id": 102, @@ -67,7 +67,7 @@ const defaultRulesData = [ "constraints": "TYPE(x)=STRING", "rewrite": " = ", "actions": "", - "enabled": true + "enabled_apps": [{"app_id": 3, "app_name": "TwitterMySQL"}] } ]; diff --git a/client/src/userContext.js b/client/src/userContext.js new file mode 100644 index 0000000..fd6fd15 --- /dev/null +++ b/client/src/userContext.js @@ -0,0 +1,5 @@ +import React from 'react'; + +const userContext = React.createContext({user: {}}); + +export { userContext }; \ No newline at end of file diff --git a/core/app_manager.py b/core/app_manager.py new file mode 100644 index 0000000..cb44b38 --- /dev/null +++ b/core/app_manager.py @@ -0,0 +1,23 @@ +import sys +# append the path of the parent directory +sys.path.append("..") +from core.data_manager import DataManager + + +class AppManager: + + def __init__(self, dm: DataManager) -> None: + self.dm = dm + + def __del__(self): + del self.dm + + def list_applications(self, userid: int) -> list: + applications = self.dm.list_applications(userid) + res = [] + for app in applications: + res.append({ + 'id': app[0], + 'name': app[1] + }) + return res diff --git a/core/data_manager.py b/core/data_manager.py index 5f6bbd2..e0fa05b 100644 --- a/core/data_manager.py +++ b/core/data_manager.py @@ -1,8 +1,14 @@ +import sys +# append the path of the parent directory +sys.path.append("..") import datetime +import json import sqlite3 +import traceback from sqlite3 import Error from pathlib import Path from typing import Dict, List +from data.rules import get_rule class DataManager: @@ -10,6 +16,7 @@ def __init__(self) -> None: db_path = Path(__file__).parent / "../" self.db_conn = sqlite3.connect(db_path / 'querybooster.db') self.__init_schema() + self.__init_data() def __init_schema(self) -> None: try: @@ -20,30 +27,62 @@ def __init_schema(self) -> None: cur.executescript(schema_sql) except Error as e: print(e) + + def __init_data(self) -> None: + try: + # create two users: Alice and Bob + # + self.update_user({'id': 1, 'email': 'alice@ics.uci.edu'}) + self.update_user({'id': 2, 'email': 'bob@cs.ucla.edu'}) + # create one app for Alice + # + self.update_application({'id': 1, 'name': 'TwitterPg', 'guid': 'Alice-Tableau-Twitter-Pg', 'user_id': 1}) + # create one app for Bob + # + self.update_application({'id': 2, 'name': 'TpchPg', 'guid': 'Bob-Tableau-Tpch-Pg', 'user_id': 2}) + # create one rule for Alice + # + rule = get_rule('remove_max_distinct') + rule['owner_id'] = 1 + rule['pattern_json'] = json.dumps(rule['pattern_json']) + rule['constraints_json'] = json.dumps(rule['constraints_json']) + rule['rewrite_json'] = json.dumps(rule['rewrite_json']) + rule['actions_json'] = json.dumps(rule['actions_json']) + self.update_rule(rule) + # enable it for its app + # + self.enable_rule(rule_id=rule['id'], app_id=1, app_name='TwitterPg') + + except Error as e: + print(e) def __del__(self): if self.db_conn: self.db_conn.close() - def list_rules(self) -> List[Dict]: + def list_rules(self, userid: int) -> List[Dict]: try: cur = self.db_conn.cursor() - cur.execute('''SELECT id, - key, - name, - pattern, - constraints, - rewrite, - actions, - CASE WHEN disabled is NULL THEN 1 ELSE 0 END AS enabled, - database - FROM rules LEFT OUTER JOIN disable_rules - ON rules.id = disable_rules.rule_id''') + cur.execute('''SELECT rules.id, + rules.key, + rules.name, + rules.pattern, + rules.constraints, + rules.rewrite, + rules.actions, + enabled.application_id, + applications.name AS application_name + FROM rules LEFT OUTER JOIN enabled + ON rules.id = enabled.rule_id + LEFT OUTER JOIN applications + ON enabled.application_id = applications.id + WHERE rules.owner_id = ? + AND applications.user_id = ?''', [userid, userid]) return cur.fetchall() except Error as e: print(e) - def enabled_rules(self, database: str) -> List[Dict]: + def enabled_rules(self, appguid: str) -> List[Dict]: try: cur = self.db_conn.cursor() cur.execute('''SELECT id, @@ -53,51 +92,83 @@ def enabled_rules(self, database: str) -> List[Dict]: constraints_json, rewrite_json, actions_json - FROM rules LEFT JOIN disable_rules ON rules.id = disable_rules.rule_id + FROM rules JOIN enabled ON rules.id = enabled.rule_id + JOIN applications ON enabled.application_id = applications.id LEFT JOIN internal_rules ON rules.id = internal_rules.rule_id - WHERE disable_rules.disabled IS NULL AND rules.database = ? - ORDER BY rules.id''', [database]) + WHERE applications.guid = ? + ORDER BY rules.id''', [appguid]) return cur.fetchall() except Error as e: print(e) - def switch_rule(self, rule_id: int, enabled: bool) -> bool: + def enable_rule(self, rule_id: int, app_id: int, app_name: str) -> bool: try: cur = self.db_conn.cursor() - if enabled: - cur.execute('''DELETE FROM disable_rules WHERE rule_id = ?''', [rule_id]) - else: - cur.execute('''INSERT OR IGNORE INTO disable_rules (rule_id, disabled) VALUES (?, 1)''', [rule_id]) + if not app_id: + cur.execute('''SELECT id FROM applications WHERE name = ?''', [app_name]) + app_id = cur.fetchone()[0] + cur.execute('''INSERT OR IGNORE INTO enabled (rule_id, application_id) VALUES (?, ?)''', [rule_id, app_id]) self.db_conn.commit() return True - except Error as e: - print(e) + except Error as er: + print('[Error] in enable_rule:') + print('rule_id: ', rule_id, 'app_id: ', app_id, 'app_name: ', app_name) + print('SQLite error: %s' % (' '.join(er.args))) + print("Exception class is: ", er.__class__) + print('SQLite traceback: ') + exc_type, exc_value, exc_tb = sys.exc_info() + print(traceback.format_exception(exc_type, exc_value, exc_tb)) + return False + + def disable_rule(self, rule_id: int, app_id: int, app_name: str) -> bool: + try: + cur = self.db_conn.cursor() + if not app_id: + cur.execute('''SELECT id FROM applications WHERE name = ?''', [app_name]) + app_id = cur.fetchone()[0] + cur.execute('''DELETE FROM enabled WHERE rule_id = ? AND application_id = ?''', [rule_id, app_id]) + self.db_conn.commit() + return True + except Error as er: + print('[Error] in disable_rule:') + print('rule_id: ', rule_id, 'app_id: ', app_id, 'app_name: ', app_name) + print('SQLite error: %s' % (' '.join(er.args))) + print("Exception class is: ", er.__class__) + print('SQLite traceback: ') + exc_type, exc_value, exc_tb = sys.exc_info() + print(traceback.format_exception(exc_type, exc_value, exc_tb)) return False def update_rule(self, rule: dict) -> None: try: cur = self.db_conn.cursor() - cur.execute('''REPLACE INTO rules (id, key, name, pattern, constraints, rewrite, actions, database) + cur.execute('''REPLACE INTO rules (id, key, name, pattern, constraints, rewrite, actions, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', [rule['id'], rule['key'], rule['name'], rule['pattern'], - rule['constraints'], rule['rewrite'], rule['actions'], rule['database'] + rule['constraints'], rule['rewrite'], rule['actions'], rule['owner_id'] ]) cur.execute('''REPLACE INTO internal_rules (rule_id, pattern_json, constraints_json, rewrite_json, actions_json) VALUES (?, ?, ?, ?, ?)''', [rule['id'], rule['pattern_json'], rule['constraints_json'], rule['rewrite_json'], rule['actions_json']]) self.db_conn.commit() - except Error as e: - print(e) + except Error as er: + print('[Error] in update_rule:') + print(rule) + print('SQLite error: %s' % (' '.join(er.args))) + print("Exception class is: ", er.__class__) + print('SQLite traceback: ') + exc_type, exc_value, exc_tb = sys.exc_info() + print(traceback.format_exception(exc_type, exc_value, exc_tb)) - def add_rule(self, rule: dict) -> bool: + def add_rule(self, rule: dict, user_id: int) -> bool: try: cur = self.db_conn.cursor() cur.execute('''SELECT IFNULL(MAX(id), 0) + 1 FROM rules;''') rule['id'] = cur.fetchone()[0] - cur.execute('''INSERT INTO rules (id, key, name, pattern, constraints, rewrite, actions, database) + cur.execute('''INSERT INTO rules (id, key, name, pattern, constraints, rewrite, actions, owner_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', [rule['id'], rule['key'], rule['name'], rule['pattern'], - rule['constraints'], rule['rewrite'], rule['actions'], rule['database'] + rule['constraints'], rule['rewrite'], rule['actions'], user_id ]) cur.execute('''INSERT INTO internal_rules (rule_id, pattern_json, constraints_json, rewrite_json, actions_json) VALUES (?, ?, ?, ?, ?)''', [rule['id'], rule['pattern_json'], rule['constraints_json'], rule['rewrite_json'], rule['actions_json']]) @@ -122,10 +193,10 @@ def delete_rule(self, rule: dict) -> bool: def log_query(self, appguid: str, guid: str, original_query: str, rewritten_query: str, rewriting_path: list) -> None: try: cur = self.db_conn.cursor() - cur.execute('''SELECT IFNULL(MAX(id), 0) + 1 FROM query_logs;''') + cur.execute('''SELECT IFNULL(MAX(id), 0) + 1 FROM queries;''') query_id = cur.fetchone()[0] - cur.execute('''INSERT INTO query_logs (id, timestamp, appguid, guid, query_time_ms, original_sql, rewritten_sql) + cur.execute('''INSERT INTO queries (id, timestamp, appguid, guid, query_time_ms, original_sql, rewritten_sql) VALUES (?, ?, ?, ?, ?, ?, ?)''', [query_id, datetime.datetime.now(), appguid, guid, -1000, original_query, rewritten_query]) seq = 1 @@ -141,7 +212,7 @@ def log_query(self, appguid: str, guid: str, original_query: str, rewritten_quer def report_query(self, appguid: str, guid: str, query_time_ms: int) -> None: try: cur = self.db_conn.cursor() - cur.execute('''UPDATE query_logs + cur.execute('''UPDATE queries SET query_time_ms = ? WHERE appguid = ? AND guid = ?''', @@ -150,19 +221,20 @@ def report_query(self, appguid: str, guid: str, query_time_ms: int) -> None: except Error as e: print(e) - def list_queries(self) -> List[Dict]: + def list_queries(self, userid: int) -> List[Dict]: try: cur = self.db_conn.cursor() cur.execute('''SELECT id, timestamp, - boosted, + rewritten, before_latency, after_latency, sql, suggestion, - suggested_latency - FROM queries - ORDER BY id desc''') + suggested_latency, + app_name + FROM query_log + WHERE user_id = ?''', [userid]) return cur.fetchall() except Error as e: print(e) @@ -171,7 +243,7 @@ def get_original_sql(self, query_id: int) -> str: try: cur = self.db_conn.cursor() cur.execute('''SELECT original_sql - FROM query_logs + FROM queries WHERE id = ?''', [query_id]) return cur.fetchall()[0] except Error as e: @@ -188,6 +260,39 @@ def list_rewritings(self, query_id: int) -> List[Dict]: return cur.fetchall() except Error as e: print(e) + + def update_user(self, user: dict) -> None: + try: + cur = self.db_conn.cursor() + cur.execute('''REPLACE INTO users (id, email) + VALUES (?, ?)''', + [user['id'], user['email']]) + self.db_conn.commit() + except Error as e: + print('[Error] in update_user:') + print(e) + + def update_application(self, app: dict) -> None: + try: + cur = self.db_conn.cursor() + cur.execute('''REPLACE INTO applications (id, name, guid, user_id) + VALUES (?, ?, ?, ?)''', + [app['id'], app['name'], app['guid'], app['user_id']]) + self.db_conn.commit() + except Error as e: + print('[Error] in update_application:') + print(e) + + def list_applications(self, userid: int) -> List[Dict]: + try: + cur = self.db_conn.cursor() + cur.execute('''SELECT id, + name + FROM applications + WHERE applications.user_id = ?''', [userid]) + return cur.fetchall() + except Error as e: + print(e) if __name__ == '__main__': diff --git a/core/query_logger.py b/core/query_manager.py similarity index 85% rename from core/query_logger.py rename to core/query_manager.py index 005cd84..cecc237 100644 --- a/core/query_logger.py +++ b/core/query_manager.py @@ -5,7 +5,7 @@ import json -class QueryLogger: +class QueryManager: def __init__(self, dm: DataManager) -> None: self.dm = dm @@ -19,19 +19,20 @@ def log_query(self, appguid: str, guid: str, original_query: str, rewritten_quer def report_query(self, appguid: str, guid: str, query_time_ms: int) -> None: self.dm.report_query(appguid, guid, query_time_ms) - def list_queries(self) -> list: - queries = self.dm.list_queries() + def list_queries(self, userid: int) -> list: + queries = self.dm.list_queries(userid) res = [] for query in queries: res.append({ 'id': query[0], 'timestamp': query[1], - 'boosted': query[2], + 'rewritten': query[2], 'before_latency': query[3], 'after_latency': query[4], 'sql': query[5], 'suggestion': query[6], - 'suggested_latency': query[7] + 'suggested_latency': query[7], + 'app_name': query[8] }) return res diff --git a/core/rule_manager.py b/core/rule_manager.py index 94c43fa..1f092e2 100644 --- a/core/rule_manager.py +++ b/core/rule_manager.py @@ -20,18 +20,18 @@ def __init_rules(self) -> None: def __del__(self): del self.dm - def add_rule(self, rule: dict) -> bool: + def add_rule(self, rule: dict, user_id: int) -> bool: rule['key'] = '_'.join([word.lower() for word in str(rule['name']).split(' ')]) rule['pattern_json'], rule['rewrite_json'], rule['mapping'] = RuleParser.parse(rule['pattern'], rule['rewrite']) rule['constraints_json'] = RuleParser.parse_constraints(rule['constraints'], rule['mapping']) rule['actions_json'] = RuleParser.parse_actions(rule['actions'], rule['mapping']) - return self.dm.add_rule(rule) + return self.dm.add_rule(rule, user_id) def delete_rule(self, rule: dict) -> bool: return self.dm.delete_rule(rule) - def fetch_enabled_rules(self, database: str) -> list: - enabled_rules = self.dm.enabled_rules(database) + def fetch_enabled_rules(self, appguid: str) -> list: + enabled_rules = self.dm.enabled_rules(appguid) res = [] for enabled_rule in enabled_rules: res.append({ @@ -45,20 +45,38 @@ def fetch_enabled_rules(self, database: str) -> list: }) return res - def list_rules(self) -> list: - rules = self.dm.list_rules() + def list_rules(self, userid: int) -> list: + rules = self.dm.list_rules(userid) + # group the rule together and + # list its enabled applications + rule_applications = {} + for rule in rules: + rule_id = rule[0] + application_id = rule[7] + application_name = rule[8] + if application_id is not None: + if rule_id in rule_applications: + rule_applications[rule_id].append({"app_id": application_id, "app_name": application_name}) + else: + rule_applications[rule_id] = [{"app_id": application_id, "app_name": application_name}] + else: + rule_applications[rule_id] = None res = [] + visited_rule_ids = [] for rule in rules: - res.append({ - 'id': rule[0], - 'key': rule[1], - 'name': rule[2], - 'pattern': rule[3], - 'constraints': rule[4], - 'rewrite': rule[5], - 'actions': rule[6], - 'enabled': True if rule[7] == 1 else False - }) + if rule[0] not in visited_rule_ids: + res.append({ + 'id': rule[0], + 'key': rule[1], + 'name': rule[2], + 'pattern': rule[3], + 'constraints': rule[4], + 'rewrite': rule[5], + 'actions': rule[6], + 'enabled_apps': rule_applications[rule[0]] + }) + visited_rule_ids.append(rule[0]) + return res def transform_rule_graph(self, root_rule: dict) -> dict: diff --git a/querybooster.db.bak b/querybooster.db.bak deleted file mode 100644 index 9ed9b5971945a63316290e60d05bd4f8abff8d4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106496 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 aDREH2I>CVb4AP{;fjtlN|37MR4gNnEA-T)| diff --git a/schema/schema.sql b/schema/schema.sql index d56dedf..d8ec2d2 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -5,7 +5,11 @@ CREATE TABLE IF NOT EXISTS rules( pattern TEXT, constraints TEXT, rewrite TEXT, - actions TEXT + actions TEXT, + owner_id INTEGER, + CONSTRAINT fk_rules + FOREIGN KEY (owner_id) + REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS internal_rules( @@ -37,12 +41,12 @@ CREATE TABLE IF NOT EXISTS applications( CREATE TABLE IF NOT EXISTS enabled( application_id INTEGER, - rule_id INTEGER + rule_id INTEGER, PRIMARY KEY (application_id, rule_id), CONSTRAINT fk_applications FOREIGN KEY (application_id) REFERENCES applications(id) - ON DELETE CASCADE + ON DELETE CASCADE, CONSTRAINT fk_rules FOREIGN KEY (rule_id) REFERENCES rules(id) @@ -118,18 +122,23 @@ CREATE TABLE IF NOT EXISTS columns( ON DELETE CASCADE ); -CREATE VIEW IF NOT EXISTS query_log AS +DROP VIEW IF EXISTS query_log; +CREATE VIEW query_log AS SELECT q.id AS id, q.timestamp AS timestamp, (CASE WHEN q.sql = q.original_sql THEN 'NO' WHEN q.sql != q.original_sql THEN 'YES' END) AS rewritten, - (SELECT AVG(q1.query_time_ms) FROM queries q1 WHERE q1.sql=ql.original_sql) AS before_latency, + (SELECT AVG(q1.query_time_ms) FROM queries q1 WHERE q1.sql=q.original_sql) AS before_latency, q.query_time_ms AS after_latency, q.original_sql AS sql, (CASE WHEN s.query_id IS NOT NULL THEN 'YES' ELSE 'NO' END) AS suggestion, (CASE WHEN s.query_id IS NOT NULL THEN s.query_time_ms ELSE -1000 - END) AS suggested_latency - FROM queries q LEFT OUTER JOIN suggestions s ON q.id = s.query_id + END) AS suggested_latency, + a.user_id AS user_id, + a.name AS app_name + FROM queries q + JOIN applications a ON q.appguid = a.guid + LEFT OUTER JOIN suggestions s ON q.id = s.query_id ORDER BY q.timestamp DESC; \ No newline at end of file diff --git a/server/server.py b/server/server.py index e2c88e6..d6b22eb 100644 --- a/server/server.py +++ b/server/server.py @@ -11,7 +11,8 @@ from core.data_manager import DataManager from core.rule_generator import RuleGenerator from core.rule_manager import RuleManager -from core.query_logger import QueryLogger +from core.query_manager import QueryManager +from core.app_manager import AppManager PORT = 8000 DIRECTORY = "static" @@ -20,7 +21,8 @@ class MyHTTPRequestHandler(SimpleHTTPRequestHandler): dm = DataManager() rm = RuleManager(dm) - ql = QueryLogger(dm) + qm = QueryManager(dm) + am = AppManager(dm) def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIRECTORY, **kwargs) @@ -51,21 +53,24 @@ def post_query(self): log_text += "\n Original query" log_text += "\n--------------------------------------------------" log_text += "\n appguid: " + appguid + log_text += "\n guid: " + guid + log_text += "\n db: " + database log_text += "\n" + QueryRewriter.beautify(original_query) log_text += "\n--------------------------------------------------" logging.info(log_text) - rules = self.rm.fetch_enabled_rules(database) + rules = self.rm.fetch_enabled_rules(appguid) rewritten_query, rewriting_path = QueryRewriter.rewrite(original_query, rules) rewritten_query = QueryPatcher.patch(rewritten_query, database) for rewriting in rewriting_path: rewriting[1] = QueryPatcher.patch(rewriting[1], database) - self.ql.log_query(appguid, guid, QueryPatcher.patch(QueryRewriter.reformat(original_query), database), rewritten_query, rewriting_path) + self.qm.log_query(appguid, guid, QueryPatcher.patch(QueryRewriter.reformat(original_query), database), rewritten_query, rewriting_path) log_text = "" log_text += "\n==================================================" log_text += "\n Rewritten query" log_text += "\n--------------------------------------------------" log_text += "\n appguid: " + appguid log_text += "\n guid: " + guid + log_text += "\n db: " + database log_text += "\n" + QueryRewriter.beautify(rewritten_query) log_text += "\n--------------------------------------------------" logging.info(log_text) @@ -83,10 +88,11 @@ def post_query(self): log_text += "\n--------------------------------------------------" log_text += "\n appguid: " + appguid log_text += "\n guid: " + guid + log_text += "\n db: " + database log_text += "\n query_time_ms: " + str(query_time_ms) log_text += "\n--------------------------------------------------" logging.info(log_text) - self.ql.report_query(appguid, guid, query_time_ms) + self.qm.report_query(appguid, guid, query_time_ms) self.send_response(200) self.end_headers() response = BytesIO() @@ -102,7 +108,10 @@ def post_list_rules(self): logging.info("\n[/listRules] request:") logging.info(request) - rules_json = self.rm.list_rules() + request = json.loads(request, strict=False) + user_id = request['user_id'] + + rules_json = self.rm.list_rules(user_id) self.send_response(200) self.end_headers() @@ -110,18 +119,41 @@ def post_list_rules(self): response.write(json.dumps(rules_json).encode('utf-8')) self.wfile.write(response.getvalue()) - def post_switch_rule(self): + def post_enable_rule(self): content_length = int(self.headers['Content-Length']) body = self.rfile.read(content_length) request = body.decode('utf-8') # logging - logging.info("\n[/switcheRule] request:") + logging.info("\n[/enableRule] request:") logging.info(request) - # enable/disable rule to data manager - rule = json.loads(request) - success = self.dm.switch_rule(rule['id'], rule['enabled']) + # enable rule for the given app to data manager + request = json.loads(request, strict=False) + rule = request['rule'] + app = request['app'] + success = self.dm.enable_rule(rule['id'], app['id'], app['name']) + + self.send_response(200) + self.end_headers() + response = BytesIO() + response.write(str(success).encode('utf-8')) + self.wfile.write(response.getvalue()) + + def post_disable_rule(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + request = body.decode('utf-8') + + # logging + logging.info("\n[/disableRule] request:") + logging.info(request) + + # enable rule for the given app to data manager + request = json.loads(request, strict=False) + rule = request['rule'] + app = request['app'] + success = self.dm.disable_rule(rule['id'], app['id'], app['name']) self.send_response(200) self.end_headers() @@ -139,8 +171,10 @@ def post_add_rule(self): logging.info(request) # add rule to rule manager - rule = json.loads(request) - success = self.rm.add_rule(rule) + request = json.loads(request, strict=False) + rule = request['rule'] + user_id = request['user_id'] + success = self.rm.add_rule(rule, user_id) self.send_response(200) self.end_headers() @@ -176,7 +210,10 @@ def post_list_queries(self): logging.info("\n[/listQueries] request:") logging.info(request) - queries_json = self.ql.list_queries() + request = json.loads(request, strict=False) + user_id = request['user_id'] + + queries_json = self.qm.list_queries(user_id) self.send_response(200) self.end_headers() @@ -195,7 +232,7 @@ def post_rewriting_path(self): # fetch rewriting path from query logger query_id = json.loads(request)["queryId"] - rewriting_path_json = self.ql.rewriting_path(query_id) + rewriting_path_json = self.qm.rewriting_path(query_id) self.send_response(200) self.end_headers() @@ -353,14 +390,36 @@ def post_recommend_rules(self): response = BytesIO() response.write(json.dumps(recommend_rules_json).encode('utf-8')) self.wfile.write(response.getvalue()) + + def post_list_applications(self): + content_length = int(self.headers['Content-Length']) + body = self.rfile.read(content_length) + request = body.decode('utf-8') + + # logging + logging.info("\n[/listApplications] request:") + logging.info(request) + + request = json.loads(request, strict=False) + user_id = request['user_id'] + + applications_json = self.am.list_applications(user_id) + + self.send_response(200) + self.end_headers() + response = BytesIO() + response.write(json.dumps(applications_json).encode('utf-8')) + self.wfile.write(response.getvalue()) def do_POST(self): if self.path == "/": self.post_query() elif self.path == "/listRules": self.post_list_rules() - elif self.path == "/switchRule": - self.post_switch_rule() + elif self.path == "/enableRule": + self.post_enable_rule() + elif self.path == "/disableRule": + self.post_disable_rule() elif self.path == "/listQueries": self.post_list_queries() elif self.path == "/rewritingPath": @@ -379,6 +438,8 @@ def do_POST(self): self.post_add_rule() elif self.path == "/deleteRule": self.post_delete_rule() + elif self.path == "/listApplications": + self.post_list_applications() if __name__ == '__main__':