File size: 6,986 Bytes
c567880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc69c66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c567880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68d7e91
c567880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cc69c66
c567880
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
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
    """
    # Look for a typical PK pattern: <table>.id
    for prop in node["properties"]:
        if prop["name"] == f"{node['name']}.id":
            return prop["name"]
    # Fallback
    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
    """
    # Looks for pattern: e.g. project.id when not in 'project'
    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)
            # Upgrade to list of dicts
            props = [
                {"name": prop, "description": "", "type": "string"} for prop in props
            ]
        else:
            props = []
            prop_names = set()

        # Ensure each required field is present in properties
        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)
        # Foreign Keys
        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}"
                )

    # Primary Keys
    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)
    # Build a lookup for table columns in all nodes
    table_lookup = {}
    for node in dd["nodes"]:
        table_lookup[node["name"]] = get_all_columns(node)
    # pprint.pprint(table_lookup)

    # Generate SQL
    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