Skip to content

Commit 1986120

Browse files
committed
DBD to SQL converter script
1 parent 401a9b0 commit 1986120

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

code/Python/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
dbds.sql

code/Python/dbd_to_sql.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
2+
3+
import dbd
4+
import os
5+
from argparse import ArgumentParser
6+
from glob import glob
7+
8+
script_dir:str = os.path.dirname(os.path.abspath(__file__));
9+
10+
parser = ArgumentParser();
11+
group = parser.add_mutually_exclusive_group();
12+
group.add_argument('--layout', type=str, help="target layout, e.g. '90747013'");
13+
group.add_argument('--build', type=str, help="target build, e.g. '10.0.0.43342'");
14+
parser.add_argument('dbds', type=str, nargs='*', help='directory with / list of for dbd files to process');
15+
parser.add_argument('--output', type=str, default=os.path.join(script_dir, 'dbds.sql'), help='file or directory to dump sql to');
16+
args = parser.parse_args();
17+
18+
dbds:list[str] = args.dbds or os.path.join(
19+
os.path.dirname( # WoWDBDefs/
20+
os.path.dirname( # code/
21+
script_dir # Python/
22+
)),
23+
'definitions'
24+
);
25+
if not dbds[0].endswith(dbd.file_suffix):
26+
dbds = glob(os.path.join(dbds[0], '*.dbd'));
27+
28+
print(f"Found {len(dbds)} definitions to process");
29+
30+
outfile:str = args.output;
31+
outdir:str = '';
32+
if outfile.endswith('.sql'):
33+
with open(outfile, 'w') as file:
34+
file.write("SET SESSION FOREIGN_KEY_CHECKS=0;\n");
35+
else:
36+
if not os.path.isdir(outfile):
37+
os.makedirs(outfile);
38+
outdir = outfile;
39+
outfile = None;
40+
41+
print(f"Outputting to {outdir or outfile}");
42+
43+
def get_sql_type(type:str, int_width:int=0, is_unsigned:bool=False)->str:
44+
type = {
45+
'uint' : 'int',
46+
#'int' : 'int',
47+
'locstring' : 'text',
48+
'string' : 'text',
49+
#'float' : 'float'
50+
}.get(type, type);
51+
52+
default = {
53+
'int' : '0',
54+
'text' : "''",
55+
'float' : '0.0'
56+
}.get(type, 'NULL');
57+
58+
type = {
59+
8 : 'tinyint',
60+
16 : 'smallint',
61+
32 : 'mediumint',
62+
64 : 'bigint'
63+
}.get(int_width, type);
64+
65+
if is_unsigned:
66+
type += ' unsigned';
67+
68+
return f"{type} DEFAULT {default}";
69+
70+
file:str
71+
for file in dbds:
72+
parsed:dbd.dbd_file = dbd.parse_dbd_file(file);
73+
if not len(parsed.definitions):
74+
print(f"No definitions found in {file}! Skipping");
75+
continue;
76+
77+
types:dict[str,str] = {};
78+
foreigns:dict[str,str] = {};
79+
column:dbd.column_definition
80+
for column in parsed.columns:
81+
types[column.name] = column.type;
82+
if column.foreign:
83+
foreigns[column.name] = f"FOREIGN KEY (`{column.name}`) REFERENCES `{column.foreign.table}` (`{column.foreign.column}`) ON DELETE NO ACTION ON UPDATE NO ACTION";
84+
85+
definition:dbd.definitions = None;
86+
if args.layout:
87+
definition = next(defn for defn in parsed.definitions if args.layout in defn.layouts);
88+
if not definition:
89+
print(f"No definition found for layout {args.layout}! Skipping");
90+
continue;
91+
elif args.build:
92+
definition = next(defn for defn in parsed.definitions if args.build in defn.builds);
93+
94+
if not definition:
95+
definition = max(parsed.definitions, key =
96+
lambda defn: max(getattr(build, 'build', getattr(build[-1], 'build', 0)) for build in defn.builds)
97+
);
98+
99+
name:str = os.path.splitext(os.path.basename(file))[0];
100+
101+
# TODO: include comments in sql
102+
columns:list[str] = [];
103+
indices:list[str] = [];
104+
fkeys:list[str] = [];
105+
entry:dbd.definition_entry
106+
for entry in definition.entries:
107+
column = f"`{entry.column}` {get_sql_type(types.get(entry.column))}";
108+
if 'id' in entry.annotation:
109+
column += ' PRIMARY KEY';
110+
elif entry.column in foreigns.keys():
111+
fkeys.append(foreigns.get(entry.column));
112+
elif 'relation' in entry.annotation:
113+
indices.append(f"INDEX (`{entry.column}`)");
114+
# TODO: Get self-referencing keys to work
115+
#fkeys.append(f"FOREIGN KEY (`{entry.column}`) REFERENCES `{name}` (`{entry.column}`) ON DELETE NO ACTION ON UPDATE NO ACTION");
116+
117+
columns.append(column);
118+
119+
fields:list[str] = [','.join(columns)];
120+
if len(indices):
121+
fields.append(', '.join(indices));
122+
if len(fkeys):
123+
fields.append(', '.join(fkeys));
124+
125+
stmt:str = f"CREATE OR REPLACE TABLE `{name}` ({', '.join(fields)})";
126+
127+
if outfile:
128+
with open(outfile, 'a') as file:
129+
file.write(f"{stmt};\n");
130+
elif outdir:
131+
with open(os.path.join(outdir, f"{name}.sql"), 'w') as file:
132+
file.write(stmt);
133+
134+
if outfile:
135+
with open(outfile, 'a') as file:
136+
file.write("SET SESSION FOREIGN_KEY_CHECKS=1;\n");
137+
138+
print('Done.');

0 commit comments

Comments
 (0)