forked from bkidwell/msaccess-vcs-integration
-
Notifications
You must be signed in to change notification settings - Fork 2
/
AppCodeImportExport.bas
executable file
·1456 lines (1320 loc) · 46.2 KB
/
AppCodeImportExport.bas
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Attribute VB_Name = "AppCodeImportExport"
' Access Module `AppCodeImportExport`
' -----------------------------------
'
' Version 0.12
'
' https://github.com/bkidwell/msaccess-vcs-integration
'
' Brendan Kidwell
'
' https://github.com/matonb/msaccess-vcs-integration
'
' Brett Maton
'
' https://github.com/ArminBra/msaccess-vcs-integration
'
' Armin Braun
'
' This code is licensed under BSD-style terms.
'
' This is some code for importing and exporting Access Queries, Forms,
' Reports, Macros, and Modules to and from plain text files, for the
' purpose of syncing with a version control system.
'
'
' Use:
'
' BACKUP YOUR WORK BEFORE TRYING THIS CODE!
'
' To create and/or overwrite source text files for all database objects
' in "$database-folder/source/", run
' `ExportAllSource()`.
'
' Table contents that shall be saved must be listed in the INCLUDE_TABLES variable
'
' To load and/or overwrite all database objects from source files in
' "$database-folder/source/", run `ImportProject()`.
'
' See project home page (URL above) for more information.
'
'
Option Compare Database
Option Explicit
' --------------------------------
' Configuration
' --------------------------------
' List of lookup tables that are part of the program rather than the
' data, to be exported with source code
'
' Provide a comma separated list of table names, or an empty string
' ("") if no tables are to be exported with the source code.
Private Const INCLUDE_TABLES = ""
' Do more aggressive removal of superfluous blobs from exported MS
' Access source code?
Private Const AggressiveSanitize = True
Private Const StripPublishOption = True
Private Const ArchiveMyself = True
'
' --------------------------------
' Structures
' --------------------------------
' Structure to track buffered reading or writing of binary files
Private Type BinFile
file_num As Integer
file_len As Long
file_pos As Long
buffer As String
buffer_len As Integer
buffer_pos As Integer
at_eof As Boolean
mode As String
End Type
' Structure to keep track of "on Update" and "on Delete" clauses
' Access does not in all cases execute such queries
Private Type structEnforce
foreignTable As String
foreignFields() As String
Table As String
refFields() As String
isUpdate As Boolean
End Type
' --------------------------------
' Constants
' --------------------------------
' Constants for Scripting.FileSystemObject API
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Const TristateTrue = -1, TristateFalse = 0, TristateUseDefault = -2
' --------------------------------
' Module variables
' --------------------------------
'
' Does the current database file write UCS2-little-endian when exporting
' Queries, Forms, Reports, Macros
Private UsingUcs2 As Boolean
'
' keeping "on Update" relations to be complemented after table creation
Private K() As structEnforce
' --------------------------------
' External Library Functions
' --------------------------------
Private Declare PtrSafe _
Function getTempPath Lib "kernel32" _
Alias "GetTempPathA" (ByVal nBufferLength As Long, _
ByVal lpBuffer As String) As Long
Private Declare PtrSafe _
Function getTempFileName Lib "kernel32" _
Alias "GetTempFileNameA" (ByVal lpszPath As String, _
ByVal lpPrefixString As String, _
ByVal wUnique As Long, _
ByVal lpTempFileName As String) As Long
' --------------------------------
' Basic functions missing from VB 6: buffered file read/write, string builder
' --------------------------------
' Open a binary file for reading (mode = 'r') or writing (mode = 'w').
Private Function BinOpen(file_path As String, mode As String) As BinFile
Dim f As BinFile
f.file_num = FreeFile
f.mode = LCase(mode)
If f.mode = "r" Then
Open file_path For Binary Access Read As f.file_num
f.file_len = LOF(f.file_num)
f.file_pos = 0
If f.file_len > &H4000 Then
f.buffer = String(&H4000, " ")
f.buffer_len = &H4000
Else
f.buffer = String(f.file_len, " ")
f.buffer_len = f.file_len
End If
f.buffer_pos = 0
Get f.file_num, f.file_pos + 1, f.buffer
Else
DelIfExist file_path
Open file_path For Binary Access Write As f.file_num
f.file_len = 0
f.file_pos = 0
f.buffer = String(&H4000, " ")
f.buffer_len = 0
f.buffer_pos = 0
End If
BinOpen = f
End Function
' Buffered read one byte at a time from a binary file.
Private Function BinRead(ByRef f As BinFile) As Integer
If f.at_eof = True Then
BinRead = 0
Exit Function
End If
BinRead = Asc(Mid(f.buffer, f.buffer_pos + 1, 1))
f.buffer_pos = f.buffer_pos + 1
If f.buffer_pos >= f.buffer_len Then
f.file_pos = f.file_pos + &H4000
If f.file_pos >= f.file_len Then
f.at_eof = True
Exit Function
End If
If f.file_len - f.file_pos > &H4000 Then
f.buffer_len = &H4000
Else
f.buffer_len = f.file_len - f.file_pos
f.buffer = String(f.buffer_len, " ")
End If
f.buffer_pos = 0
Get f.file_num, f.file_pos + 1, f.buffer
End If
End Function
' Buffered write one byte at a time from a binary file.
Private Sub BinWrite(ByRef f As BinFile, b As Integer)
Mid(f.buffer, f.buffer_pos + 1, 1) = Chr(b)
f.buffer_pos = f.buffer_pos + 1
If f.buffer_pos >= &H4000 Then
Put f.file_num, , f.buffer
f.buffer_pos = 0
End If
End Sub
' Close binary file.
Private Sub BinClose(ByRef f As BinFile)
If f.mode = "w" And f.buffer_pos > 0 Then
f.buffer = Left(f.buffer, f.buffer_pos)
Put f.file_num, , f.buffer
End If
Close f.file_num
End Sub
' String builder: Init
Private Function Sb_Init() As String()
Dim x(-1 To -1) As String
Sb_Init = x
End Function
' String builder: Clear
Private Sub Sb_Clear(ByRef sb() As String)
ReDim Sb_Init(-1 To -1)
End Sub
' String builder: Append
Private Sub Sb_Append(ByRef sb() As String, Value As String)
If LBound(sb) = -1 Then
ReDim sb(0 To 0)
Else
ReDim Preserve sb(0 To UBound(sb) + 1)
End If
sb(UBound(sb)) = Value
End Sub
' String builder: Get value
Private Function Sb_Get(ByRef sb() As String) As String
Sb_Get = Join(sb, "")
End Function
' --------------------------------
' Beginning of main functions of this module
' --------------------------------
' Close all open forms.
Private Function CloseFormsReports()
On Error GoTo errorHandler
Do While Forms.Count > 0
DoCmd.Close acForm, Forms(0).Name
DoEvents
Loop
Do While Reports.Count > 0
DoCmd.Close acReport, Reports(0).Name
DoEvents
Loop
Exit Function
errorHandler:
Debug.Print "AppCodeImportExport.CloseFormsReports: Error #" & Err.Number & vbCrLf & Err.Description
End Function
' Pad a string on the right to make it `count` characters long.
Public Function PadRight(Value As String, Count As Integer)
PadRight = Value
If Len(Value) < Count Then
PadRight = PadRight & Space(Count - Len(Value))
End If
End Function
' Path of the current database file.
Private Function ProjectPath() As String
ProjectPath = CurrentProject.Path
If Right(ProjectPath, 1) <> "\" Then ProjectPath = ProjectPath & "\"
End Function
'
' Generate Random / Unique tempprary file name.
Private Function TempFile(Optional sPrefix As String = "VBA") As String
Dim sTmpPath As String * 512
Dim sTmpName As String * 576
Dim nRet As Long
Dim sFileName As String
nRet = getTempPath(512, sTmpPath)
nRet = getTempFileName(sTmpPath, sPrefix, 0, sTmpName)
If nRet <> 0 Then sFileName = Left$(sTmpName, InStr(sTmpName, vbNullChar) - 1)
TempFile = sFileName
End Function
' Export a database object with optional UCS2-to-UTF-8 conversion.
Private Sub ExportObject(obj_type_num As Integer, obj_name As String, file_path As String, _
Optional Ucs2Convert As Boolean = False)
MkDirIfNotExist Left(file_path, InStrRev(file_path, "\"))
If Ucs2Convert Then
Dim tempFileName As String: tempFileName = TempFile()
Application.SaveAsText obj_type_num, obj_name, tempFileName
ConvertUcs2Utf8 tempFileName, file_path
Else
Application.SaveAsText obj_type_num, obj_name, file_path
End If
End Sub
' Import a database object with optional UTF-8-to-UCS2 conversion.
Public Sub ImportObject(obj_type_num As Integer, obj_name As String, file_path As String, _
Optional Ucs2Convert As Boolean = False)
If Ucs2Convert Then
Dim tempFileName As String: tempFileName = TempFile()
ConvertUtf8Ucs2 file_path, tempFileName
Application.LoadFromText obj_type_num, obj_name, tempFileName
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
FSO.DeleteFile tempFileName
Else
Application.LoadFromText obj_type_num, obj_name, file_path
End If
End Sub
' Binary convert a UCS2-little-endian encoded file to UTF-8.
Private Sub ConvertUcs2Utf8(Source As String, dest As String)
Dim f_in As BinFile, f_out As BinFile
Dim in_low As Integer, in_high As Integer
f_in = BinOpen(Source, "r")
f_out = BinOpen(dest, "w")
Do While Not f_in.at_eof
in_low = BinRead(f_in)
in_high = BinRead(f_in)
If in_high = 0 And in_low < &H80 Then
' U+0000 - U+007F 0LLLLLLL
BinWrite f_out, in_low
ElseIf in_high < &H8 Then
' U+0080 - U+07FF 110HHHLL 10LLLLLL
BinWrite f_out, &HC0 + ((in_high And &H7) * &H4) + ((in_low And &HC0) / &H40)
BinWrite f_out, &H80 + (in_low And &H3F)
Else
' U+0800 - U+FFFF 1110HHHH 10HHHHLL 10LLLLLL
BinWrite f_out, &HE0 + ((in_high And &HF0) / &H10)
BinWrite f_out, &H80 + ((in_high And &HF) * &H4) + ((in_low And &HC0) / &H40)
BinWrite f_out, &H80 + (in_low And &H3F)
End If
Loop
BinClose f_in
BinClose f_out
End Sub
' Binary convert a UTF-8 encoded file to UCS2-little-endian.
Private Sub ConvertUtf8Ucs2(Source As String, dest As String)
Dim f_in As BinFile, f_out As BinFile
Dim in_1 As Integer, in_2 As Integer, in_3 As Integer
f_in = BinOpen(Source, "r")
f_out = BinOpen(dest, "w")
Do While Not f_in.at_eof
in_1 = BinRead(f_in)
If (in_1 And &H80) = 0 Then
' U+0000 - U+007F 0LLLLLLL
BinWrite f_out, in_1
BinWrite f_out, 0
ElseIf (in_1 And &HE0) = &HC0 Then
' U+0080 - U+07FF 110HHHLL 10LLLLLL
in_2 = BinRead(f_in)
BinWrite f_out, ((in_1 And &H3) * &H40) + (in_2 And &H3F)
BinWrite f_out, (in_1 And &H1C) / &H4
Else
' U+0800 - U+FFFF 1110HHHH 10HHHHLL 10LLLLLL
in_2 = BinRead(f_in)
in_3 = BinRead(f_in)
BinWrite f_out, ((in_2 And &H3) * &H40) + (in_3 And &H3F)
BinWrite f_out, ((in_1 And &HF) * &H10) + ((in_2 And &H3C) / &H4)
End If
Loop
BinClose f_in
BinClose f_out
End Sub
' Determine if this database imports/exports code as UCS-2-LE. (Older file
' formats cause exported objects to use a Windows 8-bit character set.)
Private Sub InitUsingUcs2()
Dim obj_name As String, i As Integer, obj_type As Variant, fn As Integer, bytes As String
Dim obj_type_split() As String, obj_type_name As String, obj_type_num As Integer
Dim Db As Object ' DAO.Database
If CurrentDb.QueryDefs.Count > 0 Then
obj_type_num = acQuery
obj_name = CurrentDb.QueryDefs(0).Name
Else
For Each obj_type In Split( _
"Forms|" & acForm & "," & _
"Reports|" & acReport & "," & _
"Scripts|" & acMacro & "," & _
"Modules|" & acModule _
)
DoEvents
obj_type_split = Split(obj_type, "|")
obj_type_name = obj_type_split(0)
obj_type_num = Val(obj_type_split(1))
If CurrentDb.Containers(obj_type_name).Documents.Count > 0 Then
obj_name = CurrentDb.Containers(obj_type_name).Documents(0).Name
Exit For
End If
Next
End If
If obj_name = "" Then
' No objects found that can be used to test UCS2 versus UTF-8
UsingUcs2 = True
Exit Sub
End If
Dim tempFileName As String: tempFileName = TempFile()
Application.SaveAsText obj_type_num, obj_name, tempFileName
fn = FreeFile
Open tempFileName For Binary Access Read As fn
bytes = " "
Get fn, 1, bytes
If Asc(Mid(bytes, 1, 1)) = &HFF And Asc(Mid(bytes, 2, 1)) = &HFE Then
UsingUcs2 = True
Else
UsingUcs2 = False
End If
Close fn
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
FSO.DeleteFile (tempFileName)
End Sub
' Create folder `Path`. Silently do nothing if it already exists.
Private Sub MkDirIfNotExist(Path As String)
On Error GoTo MkDirIfNotexist_noop
MkDir Path
MkDirIfNotexist_noop:
On Error GoTo 0
End Sub
' Delete a file if it exists.
Private Sub DelIfExist(Path As String)
On Error GoTo DelIfNotExist_Noop
Kill Path
DelIfNotExist_Noop:
On Error GoTo 0
End Sub
' Erase all *.`ext` files in `Path`.
Private Sub ClearTextFilesFromDir(Path As String, Ext As String)
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
If Not FSO.FolderExists(Path) Then Exit Sub
On Error GoTo ClearTextFilesFromDir_noop
If Dir(Path & "*." & Ext) <> "" Then
FSO.DeleteFile Path & "*." & Ext
End If
ClearTextFilesFromDir_noop:
On Error GoTo 0
End Sub
' For each *.txt in `Path`, find and remove a number of problematic but
' unnecessary lines of VB code that are inserted automatically by the
' Access GUI and change often (we don't want these lines of code in
' version control).
Private Sub SanitizeTextFiles(Path As String, Ext As String)
Dim FSO As Object
Set FSO = CreateObject("Scripting.FileSystemObject")
'
' Setup Block matching Regex.
Dim rxBlock As Object
Set rxBlock = CreateObject("VBScript.RegExp")
rxBlock.ignoreCase = False
'
' Match PrtDevNames / Mode with or without W
Dim srchPattern As String
srchPattern = "PrtDev(?:Names|Mode)[W]?"
If (AggressiveSanitize = True) Then
' Add and group aggressive matches
srchPattern = "(?:" & srchPattern
srchPattern = srchPattern & "|GUID|NameMap|dbLongBinary ""DOL"""
srchPattern = srchPattern & ")"
End If
' Ensure that this is the begining of a block.
srchPattern = srchPattern & " = Begin"
'Debug.Print srchPattern
rxBlock.Pattern = srchPattern
'
' Setup Line Matching Regex.
Dim rxLine As Object
Set rxLine = CreateObject("VBScript.RegExp")
srchPattern = "^\s*(?:"
srchPattern = srchPattern & "Checksum ="
srchPattern = srchPattern & "|BaseInfo|NoSaveCTIWhenDisabled =1"
If (StripPublishOption = True) Then
srchPattern = srchPattern & "|dbByte ""PublishToWeb"" =""1"""
srchPattern = srchPattern & "|PublishOption =1"
End If
srchPattern = srchPattern & ")"
'Debug.Print srchPattern
rxLine.Pattern = srchPattern
Dim fileName As String
fileName = Dir(Path & "*." & Ext)
Do Until Len(fileName) = 0
DoEvents
Dim obj_name As String
obj_name = Mid(fileName, 1, InStrRev(fileName, ".") - 1)
Dim InFile As Object
Set InFile = FSO.OpenTextFile(Path & obj_name & "." & Ext, ForReading)
Dim OutFile As Object
Set OutFile = FSO.CreateTextFile(Path & obj_name & ".sanitize", True)
Dim getLine As Boolean: getLine = True
Do Until InFile.AtEndOfStream
DoEvents
Dim txt As String
'
' Check if we need to get a new line of text
If getLine = True Then
txt = InFile.ReadLine
Else
getLine = True
End If
'
' Skip lines starting with line pattern
If rxLine.Test(txt) Then
Dim rxIndent As Object
Set rxIndent = CreateObject("VBScript.RegExp")
rxIndent.Pattern = "^(\s+)\S"
'
' Get indentation level.
Dim matches As Object
Set matches = rxIndent.Execute(txt)
'
' Setup pattern to match current indent
Select Case matches.Count
Case 0
rxIndent.Pattern = "^" & vbNullString
Case Else
rxIndent.Pattern = "^" & matches(0).SubMatches(0)
End Select
rxIndent.Pattern = rxIndent.Pattern + "\S"
'
' Skip lines with deeper indentation
Do Until InFile.AtEndOfStream
txt = InFile.ReadLine
If rxIndent.Test(txt) Then Exit Do
Loop
' We've moved on at least one line so do get a new one
' when starting the loop again.
getLine = False
'
' skip blocks of code matching block pattern
ElseIf rxBlock.Test(txt) Then
Do Until InFile.AtEndOfStream
txt = InFile.ReadLine
If InStr(txt, "End") Then Exit Do
Loop
Else
OutFile.WriteLine txt
End If
Loop
OutFile.Close
InFile.Close
FSO.DeleteFile (Path & fileName)
Dim thisFile As Object
Set thisFile = FSO.GetFile(Path & obj_name & ".sanitize")
thisFile.Move (Path & fileName)
fileName = Dir()
Loop
End Sub
' Import References from a CSV, true=SUCCESS
Private Function ImportReferences(obj_path As String) As Boolean
Dim FSO, InFile
Dim line As String
Dim item() As String
Dim GUID As String
Dim Major As Long
Dim Minor As Long
Dim fileName As String
fileName = Dir(obj_path & "references.csv")
If Len(fileName) = 0 Then
ImportReferences = False
Exit Function
End If
Set FSO = CreateObject("Scripting.FileSystemObject")
Set InFile = FSO.OpenTextFile(obj_path & fileName, ForReading)
On Error GoTo failed_guid
Do Until InFile.AtEndOfStream
line = InFile.ReadLine
item = Split(line, ",")
GUID = Trim(item(0))
Major = CLng(item(1))
Minor = CLng(item(2))
Application.References.AddFromGuid GUID, Major, Minor
go_on:
Loop
On Error GoTo 0
InFile.Close
Set InFile = Nothing
Set FSO = Nothing
ImportReferences = True
Exit Function
failed_guid:
MsgBox "Failed to register " & GUID
Resume go_on
End Function
' Export References to a CSV
Private Sub ExportReferences(obj_path As String)
Dim FSO, OutFile
Dim line As String
Dim ref As Reference
Set FSO = CreateObject("Scripting.FileSystemObject")
Set OutFile = FSO.CreateTextFile(obj_path & "references.csv", True)
For Each ref In Application.References
line = ref.GUID & "," & CStr(ref.Major) & "," & CStr(ref.Minor)
OutFile.WriteLine line
Next
OutFile.Close
End Sub
' Save a Table Definition as SQL statement
Public Sub ExportTableDef(Db As Database, td As TableDef, tableName As String, fileName As String)
Dim sql() As String
Dim csql As String
Dim idx As Index
Dim fi As Field
Dim i As Integer
Dim nrSql As Integer
Dim f As Field
Dim rel As Relation
Dim FSO, OutFile
Dim ff As Object
'Debug.Print tableName
Set FSO = CreateObject("Scripting.FileSystemObject")
Set OutFile = FSO.CreateTextFile(fileName, True)
nrSql = 2
ReDim Preserve sql(nrSql)
sql(1) = "CREATE TABLE " & strName(tableName) & " ("
For Each fi In td.Fields
sql(0) = ""
sql(1) = sql(1) & strName(fi.Name) & " "
If (fi.Attributes And dbAutoIncrField) Then
sql(1) = sql(1) & "AUTOINCREMENT"
Else
sql(1) = sql(1) & strType(fi.Type) & " "
End If
Select Case fi.Type
Case dbText, dbVarBinary
sql(1) = sql(1) & "(" & fi.Size & ")"
Case Else
End Select
For Each idx In td.Indexes
If idx.Fields.Count = 1 And idx.Fields(0).Name = fi.Name Then
If idx.Primary Then sql(0) = sql(0) & " PRIMARY KEY "
If idx.Unique Then sql(0) = sql(0) & " UNIQUE "
If idx.Required Then sql(0) = sql(0) & " NOT NULL "
'
If idx.Foreign Then
Set ff = idx.Fields
sql(0) = sql(0) & formatReferences(Db, ff, tableName)
'
End If
If Len(sql(0)) > 0 Then sql(0) = " CONSTRAINT " & idx.Name & sql(0)
End If
Next
sql(1) = sql(1) + sql(0)
sql(1) = sql(1) + ", "
Next
For Each idx In td.Indexes
If idx.Fields.Count > 1 Then
If Len(sql(1)) = 0 Then sql(1) = sql(1) & " CONSTRAINT " & idx.Name
sql(1) = sql(1) & formatConstraint(idx.Primary, "PRIMARY KEY", idx)
sql(1) = sql(1) & formatConstraint(idx.Unique, "UNIQUE", idx)
sql(1) = sql(1) & formatConstraint(idx.Required, "NOT NULL", idx)
sql(0) = ""
sql(0) = formatConstraint(idx.Foreign, "FOREIGN KEY", idx)
If Len(sql(0)) > 0 Then
sql(1) = sql(1) & sql(0)
sql(1) = sql(1) & formatReferences(Db, idx.Fields, tableName)
End If
End If
Next
sql(1) = Left(sql(1), Len(sql(1)) - 2) & ")"
'Debug.Print sql
OutFile.WriteLine sql(1)
OutFile.Close
End Sub
Private Function formatReferences(Db As Database, ff As Object, tableName As String)
Dim rel As Relation
Dim sql As String
Dim f As Field
For Each rel In Db.Relations
If (rel.foreignTable = tableName) Then
If FieldsIdentical(ff, rel.Fields) Then
sql = " REFERENCES "
sql = sql & rel.Table & " ("
For Each f In rel.Fields
sql = sql & strName(f.Name) & ","
Next
sql = Left(sql, Len(sql) - 1) & ")"
If rel.Attributes And dbRelationUpdateCascade Then
sql = sql + " ON UPDATE CASCADE "
End If
If rel.Attributes And dbRelationDeleteCascade Then
sql = sql + " ON DELETE CASCADE "
End If
Exit For
End If
End If
Next
formatReferences = sql
End Function
Private Function formatConstraint(isConstraint As Boolean, keyw As String, idx As Index) As String
Dim sql As String
Dim fi As Field
sql = sql & strName(idx.Name)
If isConstraint Then
sql = sql & " " & keyw & " ("
For Each fi In idx.Fields
sql = sql & strName(fi.Name) & ","
Next
sql = Left(sql, Len(sql) - 1) & ")"
formatConstraint = sql
Else
formatConstraint = ""
End If
End Function
Private Function strName(s As String) As String
If InStr(s, " ") > 0 Then
strName = "[" & s & "]"
ElseIf UCase(s) = "UNIQUE" Then
strName = "[" & s & "]"
ElseIf UCase(s) = "VALUE" Then
strName = "[" & s & "]"
Else
strName = s
End If
End Function
Private Function strType(i As Integer) As String
Select Case i
Case dbLongBinary
strType = "LONGBINARY"
Case dbBinary
strType = "BINARY"
'Case dbBit missing enum
' strType = "BIT"
Case dbAutoIncrField
strType = "COUNTER"
Case dbCurrency
strType = "CURRENCY"
Case dbDate, dbTime
strType = "DATETIME"
Case dbGUID
strType = "GUID"
Case dbMemo
strType = "LONGTEXT"
Case dbDouble
strType = "DOUBLE"
Case dbSingle
strType = "SINGLE"
Case dbByte
strType = "UNSIGNED BYTE"
Case dbInteger
strType = "SHORT"
Case dbLong
strType = "LONG"
Case dbNumeric
strType = "NUMERIC"
Case dbText
strType = "VARCHAR"
Case Else
strType = "VARCHAR"
End Select
End Function
Private Function FieldsIdentical(ff As Object, gg As Object) As Boolean
Dim f As Field
If ff.Count <> gg.Count Then
FieldsIdentical = False
Exit Function
End If
For Each f In ff
If Not FieldInFields(f, gg) Then
FieldsIdentical = False
Exit Function
End If
Next
FieldsIdentical = True
End Function
Private Function FieldInFields(fi As Field, ff As Fields) As Boolean
Dim f As Field
For Each f In ff
If f.Name = fi.Name Then
FieldInFields = True
Exit Function
End If
Next
FieldInFields = False
End Function
' Determine if a table or exists.
' based on sample code of support.microsoftcom
' ARGUMENTS:
' TName: The name of a table or query.
'
' RETURNS: True (it exists) or False (it does not exist).
Function TableExists(TName As String) As Boolean
Dim Db As Database, Found As Boolean, Test As String
Const NAME_NOT_IN_COLLECTION = 3265
' Assume the table or query does not exist.
Found = False
Set Db = CurrentDb()
' Trap for any errors.
On Error Resume Next
' See if the name is in the Tables collection.
Test = Db.TableDefs(TName).Name
If Err <> NAME_NOT_IN_COLLECTION Then Found = True
' Reset the error variable.
Err = 0
TableExists = Found
End Function
' Main entry point for EXPORT. Export all forms, reports, queries,
' macros, modules, and lookup tables to `source` folder under the
' database's folder.
Public Sub ExportAllSource()
Dim Db As Object ' DAO.Database
Dim source_path As String
Dim obj_path As String
Dim qry As Object ' DAO.QueryDef
Dim doc As Object ' DAO.Document
Dim obj_type As Variant
Dim obj_type_split() As String
Dim obj_type_label As String
Dim obj_type_name As String
Dim obj_type_num As Integer
Dim obj_count As Integer
Dim ucs2 As Boolean
Dim tblName As Variant
Set Db = CurrentDb
CloseFormsReports
InitUsingUcs2
source_path = ProjectPath() & "source\"
MkDirIfNotExist source_path
Debug.Print
obj_path = source_path & "queries\"
ClearTextFilesFromDir obj_path, "bas"
Debug.Print PadRight("Exporting queries...", 24);
obj_count = 0
For Each qry In Db.QueryDefs
DoEvents
If Left(qry.Name, 1) <> "~" Then
ExportObject acQuery, qry.Name, obj_path & qry.Name & ".bas", UsingUcs2
obj_count = obj_count + 1
End If
Next
SanitizeTextFiles obj_path, "bas"
Debug.Print "[" & obj_count & "]"
obj_path = source_path & "tables\"
ClearTextFilesFromDir obj_path, "txt"
If (Len(Replace(INCLUDE_TABLES, " ", "")) > 0) Then
Debug.Print PadRight("Exporting tables...", 24);
obj_count = 0
For Each tblName In Split(INCLUDE_TABLES, ",")
DoEvents
ExportTable CStr(tblName), obj_path
If Len(Dir(obj_path & tblName & ".txt")) > 0 Then
obj_count = obj_count + 1
End If
Next
Debug.Print "[" & obj_count & "]"
End If
For Each obj_type In Split( _
"forms|Forms|" & acForm & "," & _
"reports|Reports|" & acReport & "," & _
"macros|Scripts|" & acMacro & "," & _
"modules|Modules|" & acModule _
, "," _
)
obj_type_split = Split(obj_type, "|")
obj_type_label = obj_type_split(0)
obj_type_name = obj_type_split(1)
obj_type_num = Val(obj_type_split(2))
obj_path = source_path & obj_type_label & "\"
obj_count = 0
ClearTextFilesFromDir obj_path, "bas"
Debug.Print PadRight("Exporting " & obj_type_label & "...", 24);
For Each doc In Db.Containers(obj_type_name).Documents
DoEvents
If (Left(doc.Name, 1) <> "~") And _
(doc.Name <> "AppCodeImportExport" Or ArchiveMyself) Then
If obj_type_label = "modules" Then
ucs2 = False
Else
ucs2 = UsingUcs2
End If
ExportObject obj_type_num, doc.Name, obj_path & doc.Name & ".bas", ucs2
obj_count = obj_count + 1
End If
Next
Debug.Print "[" & obj_count & "]"
If obj_type_label <> "modules" Then
SanitizeTextFiles obj_path, "bas"
Else
' Make sure all modules find their needed references
ExportReferences obj_path
End If
Next
Dim td As TableDef
Dim tds As TableDefs
Set tds = Db.TableDefs
obj_type_label = "tbldef"
obj_type_name = "Table_Def"
obj_type_num = acTable
obj_path = source_path & obj_type_label & "\"
obj_count = 0
MkDirIfNotExist Left(obj_path, InStrRev(obj_path, "\"))
ClearTextFilesFromDir obj_path, "def"
Debug.Print PadRight("Exporting " & obj_type_label & "...", 24);
For Each td In tds
' This is not a system table
' this is not a temporary table
' this is not an external table
If Left$(td.Name, 4) <> "MSys" And _
Left(td.Name, 1) <> "~" _
And Len(td.Connect) = 0 _
Then
'Debug.Print
ExportTableDef Db, td, td.Name, obj_path & td.Name & ".sql"
obj_count = obj_count + 1
End If
Next
Debug.Print "[" & obj_count & "]"
Debug.Print "Done."
End Sub
' Main entry point for IMPORT. Import all forms, reports, queries,
' macros, modules, and lookup tables from `source` folder under the
' database's folder.
Public Sub ImportAllSource()
Dim Db As Object ' DAO.Database
Dim FSO As Object
Dim source_path As String
Dim obj_path As String
Dim qry As Object ' DAO.QueryDef
Dim doc As Object ' DAO.Document
Dim obj_type As Variant
Dim obj_type_split() As String
Dim obj_type_label As String
Dim obj_type_name As String
Dim obj_type_num As Integer
Dim obj_count As Integer
Dim fileName As String
Dim obj_name As String
Dim ucs2 As Boolean
Set Db = CurrentDb
Set FSO = CreateObject("Scripting.FileSystemObject")
CloseFormsReports
InitUsingUcs2
source_path = ProjectPath() & "source\"
If Not FSO.FolderExists(source_path) Then
MsgBox "No source found at:" & vbCrLf & source_path, vbExclamation, "Import failed"
Exit Sub
End If
Debug.Print
obj_path = source_path & "queries\"