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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265 | # jet2sql.py - M.Keranen <mksql@yahoo.com> [07/12/2000]
# --------------------------------------------------------------------
# Creates ANSI SQL DDL from a MS Jet database file, useful for reverse
# engineering database designs in E/R tools.
#
# Requires DAO 3.6 library.
# --------------------------------------------------------------------
# Usage: python jet2sql.py infile.MDB outfile.SQL
import sys, string, pythoncom, win32com.client
const = win32com.client.constants
daoEngine = win32com.client.Dispatch('DAO.DBEngine.36')
class jetReverse:
def __init__ (self, infile):
self.jetfilename=infile
self.dtbs = daoEngine.OpenDatabase(infile)
return
def terminate(self):
return
def writeTable(self, currTabl):
self.writeLine('\ncreate table ' + chr(34) + currTabl.Name + chr(34),"",1)
self.writeLine('(',"",1)
# Write Columns
cn=0
for col in currTabl.Fields:
cn = cn +1
self.writeColumn(col.Name, col.Type, col.Size, col.Required, col.Attributes, col.DefaultValue, col.ValidationRule, currTabl.Fields.Count-cn)
# Validation Rule
tablRule = currTabl.ValidationRule
if tablRule <> "":
tablRule = " check(" + tablRule + ") "
self.writeLine("",",",1) # add a comma and CR previous line
self.writeLine(tablRule,"",0)
# Primary Key
pk=self.getPrimaryKey(currTabl)
if pk <> "":
self.writeLine("",",",1) # add a comma and CR previous line
self.writeLine(pk,"",0)
# End of table
self.writeLine("","",1) # terminate previous line
self.writeLine(');',"",1)
# Write table comment
try: sql = currTabl.Properties("Description").Value
except pythoncom.com_error: sql=""
if sql <> "":
sql = "comment on table " + chr(34) + currTabl.Name + chr(34) + " is " + chr(34) + sql + chr(34) +";"
self.writeLine(sql,"",1)
# Write column comments
for col in currTabl.Fields:
try: sql = col.Properties("Description").Value
except pythoncom.com_error: sql=""
if sql <> "":
sql = "comment on column " + chr(34) + currTabl.Name + chr(34) + "." + chr(34) + col.Name + chr(34) + " is " + chr(34) + sql + chr(34) + ";"
self.writeLine(sql,"",1)
# Write Indexes
self.writeIndexes(currTabl)
return
def writeColumn(self, colName, colType, length, requird, attributes, default, check, colRix):
# colRix: 0 based index of column from right side. 0 indicates rightmost column
if colType == const.dbByte: dataType = "Byte"
elif colType == const.dbInteger: dataType = "Integer"
elif colType == const.dbSingle: dataType = "Single"
elif colType == const.dbDouble: dataType = "Double"
elif colType == const.dbDate: dataType = "DateTime"
elif colType == const.dbLongBinary: dataType = "OLE"
elif colType == const.dbMemo: dataType = "Memo"
elif colType == const.dbCurrency: dataType = "Currency"
elif colType == const.dbLong:
if (attributes & const.dbAutoIncrField): dataType = "Counter"
else: dataType = "LongInteger"
elif colType == const.dbText:
if length == 0: dataType = "Text"
else: dataType = "char("+str(length)+")"
elif colType == const.dbBoolean:
dataType = "Bit"
if default == "Yes": default = "1"
else: default = "0"
else:
if length == 0: dataType = "Text"
else: dataType = "Text("+str(length)+")"
if default <> "":
defaultStr = "default " + default + " "
else: defaultStr = ""
if check <> "":
checkStr = "check(" + check + ") "
else:
checkStr = ""
if requird or (attributes & const.dbAutoIncrField):
mandatory = "not null "
else:
mandatory = ""
sql = " " + chr(34) + colName + chr(34) + " " + dataType + " " + defaultStr + checkStr + mandatory
if colRix > 0:
self.writeLine(sql,",",1)
else:
self.writeLine(sql,"",0)
return
def getPrimaryKey(self, currTabl):
# Get primary key fields
sql = ""
for idx in currTabl.Indexes:
if idx.Primary:
idxName = idx.Name
sql = " primary key "
cn=0
for col in idx.Fields:
cn=cn+1
sql = sql + chr(34) + col.Name + chr(34)
if idx.Fields.Count > cn : sql = sql + ","
return sql
def writeIndexes(self, currTabl):
# Write index definition
nIdx = -1
for idx in currTabl.Indexes:
nIdx = nIdx + 1
idxName = idx.Name
tablName = currTabl.Name
if idx.Primary:
idxName = tablName + "_PK"
elif idxName[:9] == "REFERENCE":
idxName = tablName + "_FK" + idxName[10:]
else:
idxName = tablName + "_IX" + str(nIdx)
sql = "create "
if idx.Unique: sql = sql + "unique "
if idx.Clustered: sql = sql + "clustered "
sql = sql + "index " + chr(34) + idxName + chr(34)
sql = sql + " on " + chr(34) + tablName + chr(34) + " ("
# Write Index Columns
cn=0
for col in idx.Fields:
cn = cn + 1
sql = sql + chr(34) + col.Name + chr(34)
if col.Attributes & const.dbDescending:
sql = sql + " desc"
else:
sql = sql + " asc"
if idx.Fields.Count > cn: sql = sql + ","
sql=sql + " );"
self.writeLine(sql,"",1)
return
def writeForeignKey(self, currRefr):
# Export foreign key
sql = "\nalter table " + chr(34) + currRefr.ForeignTable + chr(34)
self.writeLine(sql,"",1)
sql = " add foreign key ("
cn = 0
for col in currRefr.Fields:
cn = cn + 1
sql = sql + chr(34) + col.ForeignName + chr(34)
if currRefr.Fields.Count > cn: sql = sql + ","
sql = sql + ")"
self.writeLine(sql,"",1)
sql = " references " + chr(34) + currRefr.Table + chr(34) + " ("
cn = 0
for col in currRefr.Fields:
cn = cn + 1
sql = sql + chr(34) + col.Name + chr(34)
if currRefr.Fields.Count > cn: sql = sql + ","
sql = sql + ")"
if (currRefr.Attributes & const.dbRelationUpdateCascade) <> 0:
sql = sql + " on update cascade"
if (currRefr.Attributes & const.dbRelationDeleteCascade) <> 0:
sql = sql + " on delete cascade"
sql=sql+";"
self.writeLine(sql,"",1)
return
def writeQuery(self, currQry):
sql = "\ncreate view " + chr(34) + currQry.Name + chr(34) + " as"
self.writeLine(sql,"",1)
# Write Query text
sql=string.replace(currQry.SQL,chr(13),"") # Get rid of extra linefeeds
self.writeLine(sql,"",1)
# Write Query comment
try: sql = currQry.Properties("Description").Value
except pythoncom.com_error: sql=""
if sql <> "":
sql = "comment on table " + chr(34) + currQry.Name + chr(34) + " is " + chr(34) + sql + chr(34)
self.writeLine(sql,"",1)
return
def writeLine(self,strLine, delimit, newline):
# Used for controlling where lines terminate with a comma or other continuation mark
sqlfile.write(strLine)
if delimit: sqlfile.write(delimit)
if newline: sqlfile.write('\n')
return
if __name__ == '__main__':
if len(sys.argv)<2:
print "Usage: jet2sql.py infile.mdb outfile.sql"
else:
jetEng = jetReverse(sys.argv[1])
outfile = sys.argv[2]
sqlfile = open(outfile,'w')
print "\nReverse engineering %s to %s" % (jetEng.jetfilename, outfile)
# Tables
sys.stdout.write("\n Tables")
for tabl in jetEng.dtbs.TableDefs:
sys.stdout.write(".")
if tabl.Name[:4] <> "MSys" and tabl.Name[:4] <> "~TMP":
jetEng.writeTable(tabl)
# Relations / FKs
sys.stdout.write("\n Relations")
for fk in jetEng.dtbs.Relations:
sys.stdout.write(".")
jetEng.writeForeignKey(fk)
# Queries
sys.stdout.write("\n Queries")
for qry in jetEng.dtbs.QueryDefs:
sys.stdout.write(".")
jetEng.writeQuery(qry)
print "\n Done\n"
# Done
sqlfile.close()
jetEng.terminate()
|
Can you distinguish between CHAR and VARCHAR fields? Wow! What a great tool!
Is it possible to distinguish between
charandvarcharfields? In Access the former are fixed length and values are padded with spaces. Access creates variable length text fields by default and there appears to be no way to instruct it otherwise (using Access itself).It is possible to distinguish between fixed- and variable-length text fields via ODBC (ColdFusion Studio's ODBC browser can do it) so I guess you can get that information via ADO. Otherwise it is probably better to generate
varcharfields instead ofcharones. Michael StrasserErrors in "Reverse engineer MS Access/Jet databases" Carp { I tried this class on a DAO 3.51 db (yes I changed the Dispatch() method to use the correct engine) & it failed on in the writeColumn() function. The constant dbByte was not recognized. Since this is the first "if" comparison in the function, my guess is that the other constants also do not map. Using the PythonWin debugger I could not access the object "const." My guess is the "const" object is not accessing the DAO constants (of which, dbByte is one). } MG Foster
Re: Errors in "Reverse engineer MS Access/Jet databases", MG Foster, 2001/04/02. Not sure what is wrong, but I have successfully run this script under DAO 3.51 and 4.0, without errors. I repeated a test with DAO 3.51 on a Jet DB created with Access97, to be certain.
MG's fix to his Carp. I did get it to work. There has to be a Python byte code library of the DAO library. If you have PythonWin open it & make sure you don't have any scripts in the edit window. From the main menu, select Tools > "COM Makepy Utility." When the "Select Library" dialog box opens, look for the DAO library you want to make into a Python byte code library & click it (it should be like "Microsoft DAO 3.5 Library"). After this is accomplished the "Reverse Engineering" script worked.
Another (more elegant?) solution. You can also solve this problem by adding the following snippet of code:
jet2sql.py problem -- AttributeError: dbByte.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52267 and got the error --
C:\NASS\DATA>c:\python22\python jet2sql.py ag_co_co.mdb ag_co_co.sql
Reverse engineering ag_co_co.mdb to ag_co_co.sql
Tables.Traceback (most recent call last):
File "jet2sql.py", line 247, in ? jetEng.writeTable(tabl)
File "jet2sql.py", line 34, in writeTable
col.Attributes, col.DefaultValue, col.ValidationRule, currTabl.Fields.Count-cn)
File "jet2sql.py", line 76, in writeColumn
File "C:\Python22\lib\site-packages\win32com\client__init__.py", line 168, in __getattr__
AttributeError: dbByte
I had the same problem... But it was solved by using makepy to generate class information for the database engine that I was using (DAO.DBEngine.36).
UnicodeEncodeError: 'ascii' codec can't encode character. This is a fabulous utility.
One enhancement on some applications though ...
Sample Error Message:
Tables.Traceback (most recent call last):
File "jet2sql.py", line 247, in ?
File "jet2sql.py", line 66, in writeTable
File "jet2sql.py", line 225, in writeLine
UnicodeEncodeError: 'ascii' codec can't encode character '\u03bc'in position 52: ordinal not in range(128)
unicode 03bc is small greek letter mu
The Fix after much futzing around was:
changed line 66
from:
self.writeLine(sql,"",1)
to:
self.writeLine(sql.encode('UTF-8'),"",1)
Worked beautifully
Same thing DAO 3.6 COM not configured. getting DAO 3.6 working with python 2.4 on Windows XP SP2
Had to get pythonWin (pyWin32) working first
Downloaded from
https://sourceforge.net/projects/pywin32/
executed the .msi
had a complaint about mfc71.dll
download mfc71.dll from
http://starship.python.net/crew/mhammond/downloads/mfc71.dll
stuck it in Windows/System32
now for DA0 3.6
from Mark Hammonds superlative extensions
PythonWin ->tools -> COM makepy utility
-> from "Select Library" panel scroll down select 'Microsoft DAO 3.6 Object Library (5.0)
click OK
you're off ...
_QueryDefs.py errors.
MakePy necessary. I have not used this script in quite some time, as I have not used Access/Jet databases much in the part few years. After a recent conversation, I checked the comments on this recipe, and was plesantly surprised that someone found it useful within the year!
Testing the script on Python 2.5 with pywin build 209.1 on WinXPsp2, I find the script still works with out modification. The one step that is necessary is to run the COM Makepy utility (part of Mark Hammond's excellent Win32 extensions) against the DAO 3.6 library.
After noting the recent comments, I was going to update the script to use ADO, but decided if the DAO version still runs as-is, why reinvent the wheel.
For my fellow new users, who need more details.
Thanks to Matt for this tool.