| import sqlite3 |
|
|
|
|
| def convert_type(type): |
| """ |
| Returns SQL type for given AI generated type |
| |
| This function takes AI generated type and returns SQL type. |
| For simplified Data Dictionary enums are converted to text data type, and |
| arrays are converted in text arrays |
| |
| Parameters: |
| type (str): AI generated type |
| |
| Returns: |
| sql_type (str): SQL type |
| """ |
| sql_match = { |
| "string": "TEXT", |
| "integer": "INTEGER", |
| "number": "REAL", |
| "boolean": "BOOLEAN", |
| "array": "TEXT[]", |
| "enum": "TEXT", |
| } |
| sql_type = sql_match.get(type, "TEXT") |
| return sql_type |
|
|
|
|
| def get_pk_field(node): |
| """ |
| Returns primary key field for given AI generated node |
| |
| This function takes AI generated node dictionary and returns primary key field. |
| |
| Parameters: |
| node (dict): AI generated node dictionary |
| |
| Returns: |
| pk_field (str): Primary key field |
| """ |
| |
| for prop in node["properties"]: |
| if prop["name"] == f"{node['name']}.id": |
| return prop["name"] |
| |
| return None |
|
|
|
|
| def get_all_columns(node): |
| """ |
| Returns all columns for given AI generated node |
| |
| This function takes AI generated node dictionary and returns all columns. |
| |
| Parameters: |
| node (dict): AI generated node dictionary |
| |
| Returns: |
| columns (list): List of column names |
| """ |
| return [prop["name"] for prop in node["properties"]] |
|
|
|
|
| def as_sql_col(prop_name): |
| """ |
| Returns property name as a sql column name with "." replaced with "__" |
| |
| This function takes AI generated DD node property name and replaces "." with "__". |
| Dot in the field name may cause issues during the SQL table creation. |
| |
| Parameters: |
| prop_name (str): property name |
| |
| Returns: |
| col_name (str): Column name with "." replaced with "__" |
| """ |
| return prop_name.replace(".", "__") |
|
|
|
|
| def get_foreign_table_and_field(prop_name, node_name): |
| """ |
| Returns foreign table and field for given property name and node_name |
| |
| This function takes AI generated DD node name and property name and returns foreign table and field. |
| |
| Parameters: |
| prop_name (str): property name |
| node_name (str): node name |
| |
| Returns: |
| foreign_table (str): Foreign table name |
| foreign_field (str): Foreign field name |
| """ |
| |
| if prop_name.endswith(".id") and not prop_name.startswith(node_name + "."): |
| parent = prop_name.split(".")[0] |
| return parent, prop_name |
| return None, None |
|
|
|
|
| def transform_dd(dd): |
| """ |
| Returns transformed DD |
| |
| This function takes AI generated DD and ensures all required fields are |
| present in properties and properties are dictionaries. |
| |
| Parameters: |
| dd (dict): AI generated DD |
| |
| Returns: |
| dd (dict): Transformed DD |
| """ |
| for node in dd.get("nodes", []): |
| props = node.get("properties", []) |
| if props and all(isinstance(x, dict) for x in props): |
| prop_names = {p["name"] for p in props} |
| elif props and all(isinstance(x, str) for x in props): |
| prop_names = set(props) |
| |
| props = [ |
| {"name": prop, "description": "", "type": "string"} for prop in props |
| ] |
| else: |
| props = [] |
| prop_names = set() |
|
|
| |
| for req in node.get("required", []): |
| if req not in prop_names: |
| props.append({"name": req, "description": "", "type": "string"}) |
| prop_names.add(req) |
|
|
| node["properties"] = props |
| return dd |
|
|
|
|
| def generate_create_table(node, table_lookup): |
| """ |
| Returns SQL for the given AI generated node |
| |
| This function takes AI generated node dictionary and returns SQL for the node. |
| |
| Parameters: |
| node (dict): AI generated node dictionary |
| table_lookup (dict): Dictionary of tables and their columns |
| |
| Returns: |
| sql (str): SQL for the node |
| """ |
| col_lines = [] |
| fk_constraints = [] |
| pk_fields = [] |
| pk_field = get_pk_field(node) |
| required = node.get("required", []) |
|
|
| for prop in node["properties"]: |
| col = prop["name"] |
| coltype = convert_type(prop["type"]) |
| sql_col = as_sql_col(col) |
| line = f' "{sql_col}" {coltype}' |
| if pk_field and col == pk_field: |
| pk_fields.append(sql_col) |
| if col in required or (pk_field and col == pk_field): |
| line += " NOT NULL" |
| col_lines.append(line) |
| |
| parent, parent_field = get_foreign_table_and_field(col, node["name"]) |
| if parent: |
| ref_col = as_sql_col(parent_field) |
| parent_cols = table_lookup.get(parent, {}) |
| if parent_field in parent_cols: |
| fk_constraints.append( |
| f' FOREIGN KEY ("{sql_col}") REFERENCES "{parent}"("{ref_col}")' |
| ) |
| else: |
| fk_constraints.append( |
| f" -- WARNING: {parent} does not have field {parent_field}" |
| ) |
|
|
| |
| constraints = [] |
| if pk_fields: |
| constraint_sql = ", ".join(f'"{c}"' for c in pk_fields) |
| constraints.append(f" PRIMARY KEY ({constraint_sql})") |
|
|
| lines = col_lines + constraints + fk_constraints |
| return f'CREATE TABLE "{node["name"]}" (\n' + ",\n".join(lines) + "\n);" |
|
|
|
|
| def validate_sql(sql, node_name): |
| """ |
| Returns validation result for the given SQL |
| |
| This function takes SQL and node name and returns validation result. |
| |
| Parameters: |
| sql (str): SQL |
| node_name (str): Node name |
| |
| Returns: |
| validation_result (str): Validation result |
| """ |
| conn = sqlite3.connect(":memory:") |
| try: |
| conn.execute(sql) |
| validation_result = f'Valid SQL for table "{node_name}"\n' |
| except sqlite3.Error as e: |
| validation_result = f'Invalid SQL for table "{node_name}":\n{e}\n' |
| finally: |
| conn.close() |
| return validation_result |
|
|
|
|
| def dd_to_sql(dd): |
| """ |
| Returns SQL for the given AI generated DD |
| |
| This function takes AI generated DD and returns SQL for the DD. |
| |
| Parameters: |
| dd (dict): AI generated DD |
| |
| Returns: |
| sql (str): SQL |
| validation (str): Validation result |
| """ |
| dd = transform_dd(dd) |
| |
| table_lookup = {} |
| for node in dd["nodes"]: |
| table_lookup[node["name"]] = get_all_columns(node) |
| |
|
|
| |
| combined_sql = "" |
| validation = "Validation notes:\n" |
| for node in dd["nodes"]: |
| sql = generate_create_table(node, table_lookup) + "\n\n" |
| validation = validation + validate_sql(sql, node["name"]) |
| combined_sql = combined_sql + sql |
|
|
| return combined_sql, validation |
|
|