From 4915e75ba574befb1a505ef83c81df41f27c1ff7 Mon Sep 17 00:00:00 2001
From: jaylin <chieh.lin@rwth-aachen.de>
Date: Fri, 1 Mar 2024 11:54:19 +0100
Subject: [PATCH] multiple database

---
 my_flask_app/__pycache__/main.cpython-39.pyc  | Bin 14632 -> 8551 bytes
 my_flask_app/main.py                          | 556 ++++++++----------
 .../models/__pycache__/models.cpython-39.pyc  | Bin 4551 -> 4622 bytes
 my_flask_app/models/models.py                 |  12 +-
 my_flask_app/templates/app.html               |   6 +
 my_flask_app/templates/select_database.html   |  19 -
 my_flask_app/templates/set_database.html      |  13 +
 7 files changed, 269 insertions(+), 337 deletions(-)
 delete mode 100644 my_flask_app/templates/select_database.html
 create mode 100644 my_flask_app/templates/set_database.html

diff --git a/my_flask_app/__pycache__/main.cpython-39.pyc b/my_flask_app/__pycache__/main.cpython-39.pyc
index 7f1701b3638f7875b2bf7d8f5cf3b86b29fd50f6..7cff0738314b51c610d7db7ec14f520833d49a47 100644
GIT binary patch
delta 5550
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
O42&H792^|{T#NwdKrny+

literal 14632
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

diff --git a/my_flask_app/main.py b/my_flask_app/main.py
index 144e885..88190b4 100644
--- a/my_flask_app/main.py
+++ b/my_flask_app/main.py
@@ -1,50 +1,68 @@
 from my_flask_app import app
-from .models.models import Table, Column, Theme
+from .models.models import CustomTable, CustomColumn, Theme
 from flask_sqlalchemy import SQLAlchemy
-from flask import jsonify, render_template, request, session, send_file
-from sqlalchemy import text
-import re, pydot, base64
-import pydot
+from flask import jsonify, redirect, render_template, request, url_for, session
+from sqlalchemy import Inspector, MetaData, create_engine, text, inspect
+import pydot, base64, os
+from sqlalchemy.orm import scoped_session, sessionmaker
 
 
-# Set up database
+# Set up database (call db.engine)
 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
-app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test"
+# app.config['SQLALCHEMY_DATABASE_URI'] = "postgresql://postgres:password@localhost:5432/test"
  
 db = SQLAlchemy() 
-db.init_app(app)
+# db.init_app(app)
 
 app.secret_key = 'my_secret_key'  # Needed for session management
 dropped_items = []
 
 @app.route('/', methods=['POST', 'GET'])
 def index():
-    # Initialize variables
-    database = db.engine.url.database
-    tables_selected = []
-    # tables_selected = ['machine_sensor', 'machine_tool', 'machine_trace']
-    schemas = getSchema()
-    themes = getThemes()
+    if request.method == 'POST':
+        # Handle the form submission
+        database_uri = request.form['database_uri']
+        session['db_uri'] = database_uri
+        engine = create_engine(database_uri)
+        session_factory = sessionmaker(bind=engine)
+        db.session = scoped_session(session_factory)
+        insp = inspect(engine)
+        metadata_obj = MetaData()
+
+        # Determine the type and naem of database 
+        database_type = engine.dialect.name                     # postgresql, sqlite
+        database = database_name_from_uri(engine, database_uri)
+        # Initialize variables
+        tables_selected = []
+        schemas = getSchema(insp)
+        themes = getThemes()
+
+        schema_Selected = request.form.get('schema', None)
+        show_all = request.form.get('show_all') == 'True'
+
+        tables1 = importMetadata(engine, schema_Selected, tables_selected, show_all)
+        # graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True)
+        # image1 = generate_erd(graph_DOT1)
+
+        if dropped_items==[]:
+            image2 = ""
+        # else:
+        #     tables2 = importMetadata(engine, None, dropped_items, False)
+        #     graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True)
+        #     image2 = generate_erd(graph_DOT2)
+
+        # return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items)
+        
 
-    schema_Selected = request.form.get('schema', None)
-    show_all = request.form.get('show_all') == 'True'
 
-    tables1 = importMetadata(database, schema_Selected, tables_selected, show_all)
-    graph_DOT1 = createGraph(tables1, themes["Blue Navy"], True, True, True)
-    image1 = generate_erd(graph_DOT1)
+        # print(insp.get_foreign_keys('machine_sensor'))
+        # print(insp.get_unique_constraints('segmentation_data', schema='segmentation'))
 
-    if dropped_items==[]:
-        image2 = ""
+        return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, dropped_items=dropped_items)
     else:
-        tables2 = importMetadata(database, None, dropped_items, False)
-        graph_DOT2 = createGraph(tables2, themes["Blue Navy"], True, True, True)
-        image2 = generate_erd(graph_DOT2)
-
-    print(getTableSchema('event_data'))
-     
-    return render_template('app.html', database=database, schemas=schemas, show_all=show_all, schema_Selected=schema_Selected, tables=tables1, image1=image1, image2=image2, dropped_items=dropped_items)
-
-
+        # Display the form
+        return render_template('app.html')
+    
 
 @app.route('/handle-drop', methods=['POST'])
 def handle_drop():
@@ -67,8 +85,6 @@ def handle_drop():
     return jsonify(image2=image2)
 
 
-
-
 @app.route('/get-table-data', methods=['POST'])
 def get_table_data():
     data = request.json
@@ -83,6 +99,15 @@ def get_table_data():
 
     return jsonify({'html_table': html_table})
 
+
+def database_name_from_uri(engine, database_uri: str):
+    if engine.dialect.name == 'postgresql':
+        return engine.url.database
+    elif engine.dialect.name == 'sqlite':
+        return os.path.splitext(os.path.basename(database_uri.split('///')[-1]))[0]
+    else:
+        return 'Unknown'
+    
 def generate_html_table(content):
     if not content:
         return "No data found."
@@ -104,7 +129,6 @@ def generate_html_table(content):
     return table_html
 
 
-
 def query_database_for_table_content(table_name, number=20):
     # Initialize content list
     content_list = []
@@ -136,6 +160,7 @@ def query_database_for_table_content(table_name, number=20):
 
     return content_list
 
+# Only postgresql needs this function (database_type = 'postgresql')
 def getTableSchema(table_name):
     sql= text(f"""
         SELECT table_schema 
@@ -144,214 +169,256 @@ def getTableSchema(table_name):
     """)
     schema = db.session.execute(sql, {'table_name': table_name}).fetchone()[0]
     return schema
+    
 
-
-
-def getSchema():
-    sql = text("""SELECT schema_name FROM information_schema.schemata;""")
-    result = db.session.execute(sql)
-    schemas = [row[0] for row in result]
+def getSchema(insp):
+    # sql = text("""SELECT schema_name FROM information_schema.schemata;""")
+    # result = db.session.execute(sql)
+    # schemas = [row[0] for row in result]
+    schemas = insp.get_schema_names()
     return schemas
 
 
-def importMetadata(database, schema=None, tables_selected=None, show_all=False):
+def importMetadata(engine, schema=None, tables_selected=None, show_all=False):
     tables = {}
-    if database == '':
+    if engine == None:
         return tables
 
     # Convert tables_selected to a list to ensure compatibility with SQL IN operation.
     tables_selected_list = list(tables_selected) if tables_selected else None
 
     # Fetch initial tables based on schema and table_names.
-    tables = fetch_initial_tables(schema, tables_selected_list)
+    tables = fetch_initial_tables(engine, schema, tables_selected_list)
 
     # If show_all is True, expand the list to include related tables.
     if show_all:
-        expand_to_include_related_tables(tables)
+        expand_to_include_related_tables(engine, tables)
 
     # Fetch columns for each table.
-    fetch_columns_for_tables(tables)
+    fetch_columns_for_tables(engine, tables)
 
     # Fetch constraints (PK, FK, Unique) for each table.
-    fetch_constraints_for_tables(tables)
+    fetch_constraints_for_tables(engine, tables)
 
     return tables
 
 
-def fetch_initial_tables(schema, tables_selected_list):
+def fetch_initial_tables(engine, schema=None, tables_selected_list=None):
+    if isinstance(engine, str):
+        engine = create_engine(engine)
     tables = {}
-
-    # Construct WHERE clauses based on input parameters.
-    schema_condition = "AND table_schema = :schema" if schema else ""
-    tables_selected_condition = "AND table_name = ANY(:tables_selected)" if tables_selected_list else ""
-
-    # Fetching tables with dynamic schema and table name filtering.
-    sql_tables = text(f"""
-        SELECT table_name, table_schema
-        FROM information_schema.tables
-        WHERE table_type = 'BASE TABLE'
-        {schema_condition} {tables_selected_condition};
-    """)
-    
-    # Adjust parameters based on the conditions.
-    params = {}
-    if schema:
-        params['schema'] = schema
+    insp = inspect(engine)
+    database_type = engine.dialect.name
+
+    # Get all table names in the database (or specific schema for PostgreSQL)
+    all_tables = []
+    if schema!=None and database_type == 'postgresql':
+        all_tables = insp.get_table_names(schema=schema)
+    elif schema==None and database_type == 'postgresql':
+        for schema_of_schemas in insp.get_schema_names():
+            for table_name in insp.get_table_names(schema=schema_of_schemas):
+                all_tables.append(table_name)
+    else:       # For SQLite
+        all_tables = insp.get_table_names() 
+
+    # Filter tables if a specific list is provided
     if tables_selected_list:
-        params['tables_selected'] = tables_selected_list
-
-    result = db.session.execute(sql_tables, params)
+        table_names = [table for table in all_tables if table in tables_selected_list]
+    else:
+        table_names = all_tables
 
-    for row in result:
-        tableName, tableSchema = row
-        table = Table(tableName, tableSchema)   # adding schema to the table comment
-        tables[tableName] = table
+    for table_name in table_names:
+        # For PostgreSQL, use the provided schema, otherwise use the default schema
+        table_schema = getTableSchema(table_name) if database_type == 'postgresql' else insp.default_schema_name
+        table = CustomTable(table_name, table_schema)
+        tables[table_name] = table
         table.label = f"n{len(tables)}"
 
     return tables
 
 
-def expand_to_include_related_tables(tables):
+def expand_to_include_related_tables(engine, tables):
+    if isinstance(engine, str):
+        engine = create_engine(engine)
+    # Create an inspector object
+    insp = inspect(engine)
+
     # This dictionary will temporarily store related tables to fetch later.
     related_tables_to_fetch = {}
 
     # Iterate over initially fetched tables to find foreign key relationships.
     for tableName, table in tables.items():
-        # Fetch foreign key relationships for the current table.
-        sql_fk = text("""
-            SELECT 
-                ccu.table_name AS pk_table_name, 
-                ccu.table_schema AS pk_table_schema
-            FROM 
-                information_schema.table_constraints AS tc 
-                JOIN information_schema.key_column_usage AS kcu 
-                ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name
-                JOIN information_schema.constraint_column_usage AS ccu 
-                ON ccu.constraint_name = tc.constraint_name
-            WHERE 
-                tc.constraint_type = 'FOREIGN KEY' 
-                AND tc.table_name = :table_name
-        """)
-        fk_result = db.session.execute(sql_fk, {'table_name': tableName})
+        # Fetch foreign key relationships for the current table using the inspector.
+        fks = insp.get_foreign_keys(tableName, schema=table.schema)
+        for fk in fks:
+            referenced_table_name = fk['referred_table']
+            referenced_schema = fk['referred_schema']
 
-        
-        for referenced_table_name, referenced_schema in fk_result:
             if referenced_table_name not in tables and referenced_table_name not in related_tables_to_fetch:
                 related_tables_to_fetch[referenced_table_name] = referenced_schema
 
     # Fetch and add related tables.
     for tableName, tableSchema in related_tables_to_fetch.items():
-        # Assuming a function fetch_table_details(tableName, tableSchema) that fetches and returns
-        # a Table object with columns and constraints populated.
-        table = Table(tableName, tableSchema)
+        # Create a CustomTable object for each related table.
+        table = CustomTable(tableName, tableSchema)
         tables[tableName] = table
 
     return tables
 
 
-def fetch_columns_for_tables(tables):
-    for tableName, table in tables.items():
-        sql_columns = text("""
-            SELECT column_name, data_type, is_nullable, column_default
-            FROM information_schema.columns
-            WHERE table_name = :table_name;
-        """)
-        column_result = db.session.execute(sql_columns, {'table_name': tableName})
+def fetch_columns_for_tables(engine, tables):
+    if isinstance(engine, str):
+        engine = create_engine(engine)
+    insp = inspect(engine)
 
-        for col in column_result:
-            name, datatype, nullable, default = col
-            column = Column(table, name, '')
+    for tableName, table in tables.items():
+        # Use the inspector to get column information for each table
+        columns = insp.get_columns(tableName, schema=table.schema)
+        for col in columns:
+            name = col['name']
+            datatype = col['type']
+            nullable = col['nullable']
+            default = col['default']
+
+            # Create a CustomColumn object with the retrieved information
+            column = CustomColumn(table, name, '')
             column.setDataType({
-                "type": datatype,
-                "nullable": nullable == 'YES',
+                "type": str(datatype),
+                "nullable": nullable,
                 "default": default
             })
+
+            # Append the column to the table's columns list
             table.columns.append(column)
-    return tables  
+    return tables
+
 
+def fetch_constraints_for_tables(engine, tables):
+    if isinstance(engine, str):
+        engine = create_engine(engine)
+    insp = inspect(engine)
 
-def fetch_constraints_for_tables(tables):
     # Fetching Unique Constraints
     for tableName, table in tables.items():
-        sql_unique = text("""
-            SELECT kcu.column_name, tc.constraint_name
-            FROM information_schema.table_constraints AS tc
-            JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
-            WHERE tc.table_name = :table_name AND tc.constraint_type = 'UNIQUE';
-        """)
-        unique_result = db.session.execute(sql_unique, {'table_name': tableName})
-        for col in unique_result:
-            name, constraintName = col
-            column = table.getColumn(name)
-            if column:
-                column.isunique = True
-                if constraintName not in table.uniques:
-                    table.uniques[constraintName] = []
-                table.uniques[constraintName].append(column)
-
+        unique_constraints = insp.get_unique_constraints(tableName, schema=table.schema)
+        for uc in unique_constraints:
+            for column_name in uc['column_names']:
+                column = table.getColumn(column_name)
+                if column:
+                    column.isunique = True
+                    if uc['name'] not in table.uniques:
+                        table.uniques[uc['name']] = []
+                    table.uniques[uc['name']].append(column)
 
     # Primary Keys
     for tableName, table in tables.items():
-        sql_pk = text("""
-            SELECT kcu.column_name, tc.constraint_name, kcu.ordinal_position
-            FROM information_schema.table_constraints AS tc
-            JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
-            WHERE tc.table_name = :table_name AND tc.constraint_type = 'PRIMARY KEY';
-        """)
-        pk_result = db.session.execute(sql_pk, {'table_name': tableName})
-        for col in pk_result:
-            name, constraintName, ordinal_position = col
-            column = table.getColumn(name)
+        pk_constraint = insp.get_pk_constraint(tableName, schema=table.schema)
+        for column_name in pk_constraint['constrained_columns']:
+            column = table.getColumn(column_name)
             if column:
                 column.ispk = True
-                column.pkconstraint = constraintName
-                # Assuming you want to order PKs, though not directly used in provided class
+                column.pkconstraint = pk_constraint['name']
 
-
-    # Fetching Foreign Keys for each table
+    # Foreign Keys
     for tableName, table in tables.items():
-        sql_fk = text("""
-            SELECT 
-                tc.constraint_name, 
-                tc.table_name AS fk_table_name, 
-                kcu.column_name AS fk_column_name, 
-                ccu.table_name AS pk_table_name, 
-                ccu.column_name AS pk_column_name,
-                ccu.table_schema AS pk_table_schema
-            FROM 
-                information_schema.table_constraints AS tc 
-                JOIN information_schema.key_column_usage AS kcu 
-                ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name
-                JOIN information_schema.constraint_column_usage AS ccu 
-                ON ccu.constraint_name = tc.constraint_name
-            WHERE 
-                tc.constraint_type = 'FOREIGN KEY' 
-                AND tc.table_name = :table_name
-        """)
-
-        fk_result = db.session.execute(sql_fk, {'table_name': tableName})
-        for row in fk_result:
-            constraintName, fkTableName, fkColumnName, pkTableName, pkColumnName, pkTableSchema = row
+        fks = insp.get_foreign_keys(tableName, schema=table.schema)
+        for fk in fks:
+            fk_columns = fk['constrained_columns']
+            referred_table = fk['referred_table']
+            referred_columns = fk['referred_columns']
+            for fk_column, ref_column in zip(fk_columns, referred_columns):
+                column = table.getColumn(fk_column)
+                if column:
+                    column.fkof = f"{referred_table}.{ref_column}"
+                    if fk['name'] not in table.fks:
+                        table.fks[fk['name']] = []
+                    table.fks[fk['name']].append(column)
 
-            # Ensure the foreign key table is the current table being processed
-            if fkTableName != tableName:
-                continue
-
-
-            fkTable = tables.get(fkTableName)
-            pkTable = tables.get(pkTableName)
+    return tables
 
-            if fkTable and pkTable:
-                fkColumn = fkTable.getColumn(fkColumnName)
-                pkColumn = pkTable.getColumn(pkColumnName)
 
-                if fkColumn and pkColumn:
-                    # Here, instead of assigning pkColumn directly, store relevant info
-                    fkColumn.fkof = pkColumn  # Adjust based on your application's needs
-                    if constraintName not in fkTable.fks:
-                        fkTable.fks[constraintName] = []
-                    fkTable.fks[constraintName].append(fkColumn)
-    return tables
+# def fetch_constraints_for_tables(engine, tables):
+#     # Fetching Unique Constraints
+#     for tableName, table in tables.items():
+#         sql_unique = text("""
+#             SELECT kcu.column_name, tc.constraint_name
+#             FROM information_schema.table_constraints AS tc
+#             JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+#             WHERE tc.table_name = :table_name AND tc.constraint_type = 'UNIQUE';
+#         """)
+#         unique_result = db.session.execute(sql_unique, {'table_name': tableName})
+#         for col in unique_result:
+#             name, constraintName = col
+#             column = table.getColumn(name)
+#             if column:
+#                 column.isunique = True
+#                 if constraintName not in table.uniques:
+#                     table.uniques[constraintName] = []
+#                 table.uniques[constraintName].append(column)
+
+
+#     # Primary Keys
+#     for tableName, table in tables.items():
+#         sql_pk = text("""
+#             SELECT kcu.column_name, tc.constraint_name, kcu.ordinal_position
+#             FROM information_schema.table_constraints AS tc
+#             JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
+#             WHERE tc.table_name = :table_name AND tc.constraint_type = 'PRIMARY KEY';
+#         """)
+#         pk_result = db.session.execute(sql_pk, {'table_name': tableName})
+#         for col in pk_result:
+#             name, constraintName, ordinal_position = col
+#             column = table.getColumn(name)
+#             if column:
+#                 column.ispk = True
+#                 column.pkconstraint = constraintName
+#                 # Assuming you want to order PKs, though not directly used in provided class
+
+
+#     # Fetching Foreign Keys for each table
+#     for tableName, table in tables.items():
+#         sql_fk = text("""
+#             SELECT 
+#                 tc.constraint_name, 
+#                 tc.table_name AS fk_table_name, 
+#                 kcu.column_name AS fk_column_name, 
+#                 ccu.table_name AS pk_table_name, 
+#                 ccu.column_name AS pk_column_name,
+#                 ccu.table_schema AS pk_table_schema
+#             FROM 
+#                 information_schema.table_constraints AS tc 
+#                 JOIN information_schema.key_column_usage AS kcu 
+#                 ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name
+#                 JOIN information_schema.constraint_column_usage AS ccu 
+#                 ON ccu.constraint_name = tc.constraint_name
+#             WHERE 
+#                 tc.constraint_type = 'FOREIGN KEY' 
+#                 AND tc.table_name = :table_name
+#         """)
+
+#         fk_result = db.session.execute(sql_fk, {'table_name': tableName})
+#         for row in fk_result:
+#             constraintName, fkTableName, fkColumnName, pkTableName, pkColumnName, pkTableSchema = row
+
+#             # Ensure the foreign key table is the current table being processed
+#             if fkTableName != tableName:
+#                 continue
+
+
+#             fkTable = tables.get(fkTableName)
+#             pkTable = tables.get(pkTableName)
+
+#             if fkTable and pkTable:
+#                 fkColumn = fkTable.getColumn(fkColumnName)
+#                 pkColumn = pkTable.getColumn(pkColumnName)
+
+#                 if fkColumn and pkColumn:
+#                     # Here, instead of assigning pkColumn directly, store relevant info
+#                     fkColumn.fkof = pkColumn  # Adjust based on your application's needs
+#                     if constraintName not in fkTable.fks:
+#                         fkTable.fks[constraintName] = []
+#                     fkTable.fks[constraintName].append(fkColumn)
+#     return tables
 
 
 def createGraph(tables, theme, showColumns, showTypes, useUpperCase):
@@ -393,139 +460,4 @@ def getThemes():
 
 
 if __name__ == "__main__":
-    app.run(port=5001)
-
-
-class Theme:
-    def __init__(self, color, fillcolor, fillcolorC,
-            bgcolor, icolor, tcolor, style, shape, pencolor, penwidth):
-        self.color = color
-        self.fillcolor = fillcolor
-        self.fillcolorC = fillcolorC
-        self.bgcolor = bgcolor
-        self.icolor = icolor
-        self.tcolor = tcolor
-        self.style = style
-        self.shape = shape
-        self.pencolor = pencolor
-        self.penwidth = penwidth
-
-
-class Table:
-    def __init__(self, name, comment):
-        self.name = name
-        self.comment = comment if comment is not None and comment != 'None' else ''
-        self.label = None
-
-        self.columns = []           # list of all columns
-        self.uniques = {}           # dictionary with UNIQUE constraints, by name + list of columns
-        self.pks = []               # list of PK columns (if any)
-        self.fks = {}               # dictionary with FK constraints, by name + list of FK columns
-
-
-    @classmethod
-    def getClassName(cls, name, useUpperCase, withQuotes=True):
-        if re.match("^[A-Z_0-9]*$", name) == None:
-            return f'"{name}"' if withQuotes else name
-        return name.upper() if useUpperCase else name.lower()
-
-    def getName(self, useUpperCase, withQuotes=True):
-        return Table.getClassName(self.name, useUpperCase, withQuotes)
-
-    def getColumn(self, name):
-        for column in self.columns:
-            if column.name == name:
-                return column
-        return None
-
-    def getDotShape(self, theme, showColumns, showTypes, useUpperCase):
-        fillcolor = theme.fillcolorC if showColumns else theme.fillcolor
-        colspan = "2" if showTypes else "1"
-        tableName = self.getName(useUpperCase, False)
-
-        s = (f'  {self.label} [\n'
-            + f'    fillcolor="{fillcolor}" color="{theme.color}" penwidth="1"\n'
-            + f'    label=<<table style="{theme.style}" border="0" cellborder="0" cellspacing="0" cellpadding="1">\n'
-            + f'      <tr><td bgcolor="{theme.bgcolor}" align="center"'
-            + f' colspan="{colspan}"><font color="{theme.tcolor}"><b>{tableName}</b></font></td></tr>\n')
-
-        if showColumns:
-            for column in self.columns:
-                name = column.getName(useUpperCase, False)
-                if column.ispk: name = f"<u>{name}</u>"
-                if column.fkof != None: name = f"<i>{name}</i>"
-                if column.nullable: name = f"{name}*"
-                if column.identity: name = f"{name} I"
-                if column.isunique: name = f"{name} U"
-                datatype = column.datatype
-                if useUpperCase: datatype = datatype.upper()
-
-                if showTypes:
-                    s += (f'      <tr><td align="left"><font color="{theme.icolor}">{name}&nbsp;</font></td>\n'
-                        + f'        <td align="left"><font color="{theme.icolor}">{datatype}</font></td></tr>\n')
-                else:
-                    s += f'      <tr><td align="left"><font color="{theme.icolor}">{name}</font></td></tr>\n'
-
-        return s + '    </table>>\n  ]\n'
-
-
-    def getDotLinks(self, theme):
-        s = ""
-        for constraint in self.fks:
-            fks = self.fks[constraint]
-            fk1 = fks[0]
-            dashed = "" if not fk1.nullable else ' style="dashed"'
-            arrow = "" if fk1.ispk and len(self.pks) == len(fk1.fkof.table.pks) else ' arrowtail="crow"'
-            s += (f'  {self.label} -> {fk1.fkof.table.label}'
-                + f' [ penwidth="{theme.penwidth}" color="{theme.pencolor}"{dashed}{arrow} ]\n')
-        return s
-    
-
-class Column:
-    def __init__(self, table, name, comment):
-        self.table = table
-        self.name = name
-        self.comment = comment if comment is not None and comment != 'None' else ''
-        self.nullable = True
-        self.datatype = None        # with (length, or precision/scale)
-        self.identity = False
-
-        self.isunique = False
-        self.ispk = False
-        self.pkconstraint = None
-        self.fkof = None            # points to the PK column on the other side
-
-
-    def getName(self, useUpperCase, withQuotes=True):
-        return Table.getClassName(self.name, useUpperCase, withQuotes)
-
-
-    def setDataType(self, datatype):
-        self.datatype = datatype["type"]
-        self.nullable = bool(datatype["nullable"])
-
-        if self.datatype == "FIXED":
-            self.datatype = "NUMBER"
-        elif "fixed" in datatype:
-            fixed = bool(datatype["fixed"])
-            if self.datatype == "TEXT":
-                self.datatype = "CHAR" if fixed else "VARCHAR"
-
-        if "length" in datatype:
-            self.datatype += f"({str(datatype['length'])})"
-        elif "scale" in datatype:
-            if int(datatype['precision']) == 0:
-                self.datatype += f"({str(datatype['scale'])})"
-                if self.datatype == "TIMESTAMP_NTZ(9)":
-                    self.datatype = "TIMESTAMP"
-            elif "scale" in datatype and int(datatype['scale']) == 0:
-                self.datatype += f"({str(datatype['precision'])})"
-                if self.datatype == "NUMBER(38)":
-                    self.datatype = "INT"
-                elif self.datatype.startswith("NUMBER("):
-                    self.datatype = f"INT({str(datatype['precision'])})"
-            elif "scale" in datatype:
-                self.datatype += f"({str(datatype['precision'])},{str(datatype['scale'])})"
-                #if column.datatype.startswith("NUMBER("):
-                #    column.datatype = f"FLOAT({str(datatype['precision'])},{str(datatype['scale'])})"
-        self.datatype = self.datatype.lower()
+    app.run(debug=True)
\ No newline at end of file
diff --git a/my_flask_app/models/__pycache__/models.cpython-39.pyc b/my_flask_app/models/__pycache__/models.cpython-39.pyc
index 52c1845341f74497b1ac419ea9d8e8fb62401df8..922505e8e007a16202bcb47fb6b19cc560709207 100644
GIT binary patch
delta 432
zcmX@E+^52u$ji&cz`(#zz3E}9?MB`ij4F=$p~b01#rnw^nW-82E~&-YCHV#VxrwPM
z@u?LhMTyBJ`9+(LGx9UCb32z7m*nS8zRpz1&Q_e9k(!%0If6NXT?8t=lleKj1eCpy
zrJG$C%GPC_$1VzCZ$86%mzgnY@+!_|c5$eX9#;~hrVRrFLljqXeqM1&QDSCZNs$~#
zfH^JOFp4cDu{a|&C5kn%s3^aD@_A12$rrelVcI8u;r3>;Wnf?^a+vJOQ_s!=F?I4$
zo-QGKkgx;;1H&!$<ow*+)Vz|(vAibSaN#27$#Zy3VQ!mzi8mMKm(31*dl;3YSlv7$
zTwQLl`GxvAxdz>0P0OrEO^IR&ag7LxVsZ9x42qg8FCfMkHQ7u+gE4Ayyg)jm`{d&S
zk?>$p6g1Kc0=a@8l@I`NofsGxA{ZDLirE+#7&sU?__$b#oF+F4iZVJ)o-Swr0DujC
A!2kdN

delta 352
zcmeBEIj+o`$ji&cz`(#z(7!HKawG2yMkRaw(BjmhV*TWd%+w5hm(=3ylKcYw+{*a0
zoW$bn_{4&O%_kZ885vn8-)E}iU{B7^%}vcKnH<lYz{ofG81r*Rp~+iVx*2&VyRpt=
z<lp?7^)55xt;r`jn;8W+2XiGcD%&zJFx=uw&d)0@DN4-DD=Cr#2{5N+8{T3|Ni5Dt
zO}RDs2DbyF;AD9oZ#Fvy28JTn$pt+1jBJy?@pSPyfCMB!gd_t4!{kZ4CfrCulP~g`
zGV)L6<;!K{+uX>vhf(<!tD9$ptII7mzffN%*PvUhX_*zNDYsZcTq8nmu{e7;2Hl#R
zARxwgYjU}O2IH;C3k1>`eI|1VMluRb&JZ+WjG0`(Bcbiiz`zi}z`#(<#=yY9!N|eK
e#Zm+k(_|@90J((;M2Lb2_sIo3;*-A$N&*0#JYpmO

diff --git a/my_flask_app/models/models.py b/my_flask_app/models/models.py
index 5092e14..118e0c5 100644
--- a/my_flask_app/models/models.py
+++ b/my_flask_app/models/models.py
@@ -15,10 +15,10 @@ class Theme:
         self.penwidth = penwidth
 
 
-class Table():
-    def __init__(self, name, comment):
+class CustomTable():
+    def __init__(self, name, schema):
         self.name = name
-        self.comment = comment if comment is not None and comment != 'None' else ''
+        self.schema = schema if schema is not None and schema != 'None' else ''   # table schema
         self.label = None
 
         self.columns = []           # list of all columns
@@ -34,7 +34,7 @@ class Table():
         return name.upper() if useUpperCase else name.lower()
 
     def getName(self, useUpperCase, withQuotes=True):
-        return Table.getClassName(self.name, useUpperCase, withQuotes)
+        return CustomTable.getClassName(self.name, useUpperCase, withQuotes)
 
     def getColumn(self, name):
         for column in self.columns:
@@ -85,7 +85,7 @@ class Table():
         return s
 
 
-class Column:
+class CustomColumn:
     def __init__(self, table, name, comment):
         self.table = table
         self.name = name
@@ -101,7 +101,7 @@ class Column:
 
 
     def getName(self, useUpperCase, withQuotes=True):
-        return Table.getClassName(self.name, useUpperCase, withQuotes)
+        return CustomTable.getClassName(self.name, useUpperCase, withQuotes)
 
 
     def setDataType(self, datatype):
diff --git a/my_flask_app/templates/app.html b/my_flask_app/templates/app.html
index b7f13c2..99e8ff9 100644
--- a/my_flask_app/templates/app.html
+++ b/my_flask_app/templates/app.html
@@ -383,6 +383,12 @@
             </li>
             <!-- Add more items as needed -->
         </ul>
+
+        <!-- set_database.html -->
+        <form method="post" action="/">
+            <input type="text" name="database_uri" placeholder="Enter Database URI">
+            <input type="submit" value="Set Database">
+        </form>
         
         <div id="resize-handle-left" class="resize-handle-left"></div>
     </div> 
diff --git a/my_flask_app/templates/select_database.html b/my_flask_app/templates/select_database.html
deleted file mode 100644
index 98135dc..0000000
--- a/my_flask_app/templates/select_database.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <title>Select Database</title>
-    <!-- Add some CSS for styling -->
-</head>
-<body>
-    <div>
-        <!-- <h3>Current Database: {{ current_db }}</h3> -->
-        <h3>Select a Database</h3>
-        <ul>
-            {% for db in databases %}
-                <li>{{ db }}</li>
-            {% endfor %}
-        </ul>
-    </div>
-</body>
-</html>
diff --git a/my_flask_app/templates/set_database.html b/my_flask_app/templates/set_database.html
new file mode 100644
index 0000000..b3337a2
--- /dev/null
+++ b/my_flask_app/templates/set_database.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Set Database</title>
+</head>
+<body>
+    <form method="POST" action="/set-database">
+        <label for="database_uri">Database URI:</label>
+        <input type="text" name="database_uri" id="database_uri">
+        <input type="submit" value="Set Database">
+    </form>
+</body>
+</html>
\ No newline at end of file
-- 
GitLab