meadow-connection-sqlite 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/docs/README.md +9 -0
- package/docs/_cover.md +6 -2
- package/docs/_sidebar.md +19 -2
- package/docs/_topbar.md +4 -1
- package/docs/api/SQLite.md +55 -0
- package/docs/api/connect.md +66 -0
- package/docs/api/connectAsync.md +121 -0
- package/docs/api/createTable.md +96 -0
- package/docs/api/createTables.md +91 -0
- package/docs/api/db.md +145 -0
- package/docs/api/generateCreateTableStatement.md +109 -0
- package/docs/api/generateDropTableStatement.md +74 -0
- package/docs/api/preparedStatement.md +67 -0
- package/docs/api/reference.md +192 -0
- package/docs/api.md +13 -252
- package/docs/architecture.md +250 -0
- package/docs/quickstart.md +197 -0
- package/docs/retold-catalog.json +22 -1
- package/docs/retold-keyword-index.json +5818 -5
- package/docs/schema.md +198 -0
- package/package.json +3 -3
- package/source/Meadow-Connection-SQLite.js +79 -79
- package/source/Meadow-Schema-SQLite.js +1036 -0
- package/test/SQLite_tests.js +992 -0
package/test/SQLite_tests.js
CHANGED
|
@@ -10,6 +10,7 @@ const libFS = require('fs');
|
|
|
10
10
|
|
|
11
11
|
const libFable = require('fable');
|
|
12
12
|
const libMeadowConnectionSQLite = require('../source/Meadow-Connection-SQLite.js');
|
|
13
|
+
const libMeadowSchemaSQLite = require('../source/Meadow-Schema-SQLite.js');
|
|
13
14
|
|
|
14
15
|
const _FableConfig = (
|
|
15
16
|
{
|
|
@@ -68,6 +69,145 @@ const _AuthorTableSchema =
|
|
|
68
69
|
Description: 'A table of authors'
|
|
69
70
|
};
|
|
70
71
|
|
|
72
|
+
const _BookTableSchemaWithColumnIndexed =
|
|
73
|
+
{
|
|
74
|
+
TableName: 'BookIndexed',
|
|
75
|
+
Columns:
|
|
76
|
+
[
|
|
77
|
+
{ Column: 'IDBookIndexed', DataType: 'ID' },
|
|
78
|
+
{ Column: 'GUIDBookIndexed', DataType: 'GUID' },
|
|
79
|
+
{ Column: 'Title', DataType: 'String', Indexed: true },
|
|
80
|
+
{ Column: 'Description', DataType: 'Text' },
|
|
81
|
+
{ Column: 'ISBN', DataType: 'String', Indexed: 'unique' },
|
|
82
|
+
{ Column: 'IDPublisher', DataType: 'ForeignKey' }
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const _BookTableSchemaWithIndexName =
|
|
87
|
+
{
|
|
88
|
+
TableName: 'BookCustomIdx',
|
|
89
|
+
Columns:
|
|
90
|
+
[
|
|
91
|
+
{ Column: 'IDBookCustomIdx', DataType: 'ID' },
|
|
92
|
+
{ Column: 'GUIDBookCustomIdx', DataType: 'GUID' },
|
|
93
|
+
{ Column: 'Title', DataType: 'String', Indexed: true, IndexName: 'IX_Custom_Title' },
|
|
94
|
+
{ Column: 'ISBN', DataType: 'String', Indexed: 'unique', IndexName: 'UQ_BookCustomIdx_ISBN' },
|
|
95
|
+
{ Column: 'YearPublished', DataType: 'Numeric', Indexed: true },
|
|
96
|
+
{ Column: 'IDEditor', DataType: 'ForeignKey' }
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const _ChinookSQL = `
|
|
101
|
+
CREATE TABLE Artist (
|
|
102
|
+
ArtistId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
103
|
+
Name NVARCHAR(120)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE Album (
|
|
107
|
+
AlbumId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
108
|
+
Title NVARCHAR(160) NOT NULL,
|
|
109
|
+
ArtistId INTEGER NOT NULL,
|
|
110
|
+
FOREIGN KEY (ArtistId) REFERENCES Artist (ArtistId)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE Employee (
|
|
114
|
+
EmployeeId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
115
|
+
LastName NVARCHAR(20) NOT NULL,
|
|
116
|
+
FirstName NVARCHAR(20) NOT NULL,
|
|
117
|
+
Title NVARCHAR(30),
|
|
118
|
+
ReportsTo INTEGER,
|
|
119
|
+
BirthDate DATETIME,
|
|
120
|
+
HireDate DATETIME,
|
|
121
|
+
Address NVARCHAR(70),
|
|
122
|
+
City NVARCHAR(40),
|
|
123
|
+
State NVARCHAR(40),
|
|
124
|
+
Country NVARCHAR(40),
|
|
125
|
+
PostalCode NVARCHAR(10),
|
|
126
|
+
Phone NVARCHAR(24),
|
|
127
|
+
Fax NVARCHAR(24),
|
|
128
|
+
Email NVARCHAR(60),
|
|
129
|
+
FOREIGN KEY (ReportsTo) REFERENCES Employee (EmployeeId)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE TABLE Customer (
|
|
133
|
+
CustomerId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
134
|
+
FirstName NVARCHAR(40) NOT NULL,
|
|
135
|
+
LastName NVARCHAR(20) NOT NULL,
|
|
136
|
+
Company NVARCHAR(80),
|
|
137
|
+
Address NVARCHAR(70),
|
|
138
|
+
City NVARCHAR(40),
|
|
139
|
+
State NVARCHAR(40),
|
|
140
|
+
Country NVARCHAR(40),
|
|
141
|
+
PostalCode NVARCHAR(10),
|
|
142
|
+
Phone NVARCHAR(24),
|
|
143
|
+
Fax NVARCHAR(24),
|
|
144
|
+
Email NVARCHAR(60) NOT NULL,
|
|
145
|
+
SupportRepId INTEGER,
|
|
146
|
+
FOREIGN KEY (SupportRepId) REFERENCES Employee (EmployeeId)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE TABLE Genre (
|
|
150
|
+
GenreId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
151
|
+
Name NVARCHAR(120)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
CREATE TABLE MediaType (
|
|
155
|
+
MediaTypeId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
156
|
+
Name NVARCHAR(120)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE Playlist (
|
|
160
|
+
PlaylistId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
161
|
+
Name NVARCHAR(120)
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
CREATE TABLE Track (
|
|
165
|
+
TrackId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
166
|
+
Name NVARCHAR(200) NOT NULL,
|
|
167
|
+
AlbumId INTEGER,
|
|
168
|
+
MediaTypeId INTEGER NOT NULL,
|
|
169
|
+
GenreId INTEGER,
|
|
170
|
+
Composer NVARCHAR(220),
|
|
171
|
+
Milliseconds INTEGER NOT NULL,
|
|
172
|
+
Bytes INTEGER,
|
|
173
|
+
UnitPrice NUMERIC(10,2) NOT NULL,
|
|
174
|
+
FOREIGN KEY (AlbumId) REFERENCES Album (AlbumId),
|
|
175
|
+
FOREIGN KEY (MediaTypeId) REFERENCES MediaType (MediaTypeId),
|
|
176
|
+
FOREIGN KEY (GenreId) REFERENCES Genre (GenreId)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
CREATE TABLE Invoice (
|
|
180
|
+
InvoiceId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
181
|
+
CustomerId INTEGER NOT NULL,
|
|
182
|
+
InvoiceDate DATETIME NOT NULL,
|
|
183
|
+
BillingAddress NVARCHAR(70),
|
|
184
|
+
BillingCity NVARCHAR(40),
|
|
185
|
+
BillingState NVARCHAR(40),
|
|
186
|
+
BillingCountry NVARCHAR(40),
|
|
187
|
+
BillingPostalCode NVARCHAR(10),
|
|
188
|
+
Total NUMERIC(10,2) NOT NULL,
|
|
189
|
+
FOREIGN KEY (CustomerId) REFERENCES Customer (CustomerId)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
CREATE TABLE InvoiceLine (
|
|
193
|
+
InvoiceLineId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
194
|
+
InvoiceId INTEGER NOT NULL,
|
|
195
|
+
TrackId INTEGER NOT NULL,
|
|
196
|
+
UnitPrice NUMERIC(10,2) NOT NULL,
|
|
197
|
+
Quantity INTEGER NOT NULL,
|
|
198
|
+
FOREIGN KEY (InvoiceId) REFERENCES Invoice (InvoiceId),
|
|
199
|
+
FOREIGN KEY (TrackId) REFERENCES Track (TrackId)
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
CREATE TABLE PlaylistTrack (
|
|
203
|
+
PlaylistId INTEGER NOT NULL,
|
|
204
|
+
TrackId INTEGER NOT NULL,
|
|
205
|
+
PRIMARY KEY (PlaylistId, TrackId),
|
|
206
|
+
FOREIGN KEY (PlaylistId) REFERENCES Playlist (PlaylistId),
|
|
207
|
+
FOREIGN KEY (TrackId) REFERENCES Track (TrackId)
|
|
208
|
+
);
|
|
209
|
+
`;
|
|
210
|
+
|
|
71
211
|
suite
|
|
72
212
|
(
|
|
73
213
|
'Connection',
|
|
@@ -425,5 +565,857 @@ suite
|
|
|
425
565
|
);
|
|
426
566
|
}
|
|
427
567
|
);
|
|
568
|
+
|
|
569
|
+
suite
|
|
570
|
+
(
|
|
571
|
+
'Index Generation',
|
|
572
|
+
()=>
|
|
573
|
+
{
|
|
574
|
+
let libSchemaSQLite = null;
|
|
575
|
+
|
|
576
|
+
setup(
|
|
577
|
+
(fDone) =>
|
|
578
|
+
{
|
|
579
|
+
let _Fable = new libFable(_FableConfig);
|
|
580
|
+
libSchemaSQLite = _Fable.serviceManager.addServiceType('MeadowSchemaSQLite', libMeadowSchemaSQLite);
|
|
581
|
+
libSchemaSQLite = _Fable.serviceManager.instantiateServiceProvider('MeadowSchemaSQLite');
|
|
582
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
583
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
584
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
585
|
+
(pError) =>
|
|
586
|
+
{
|
|
587
|
+
libSchemaSQLite.setDatabase(_Fable.MeadowSQLiteProvider.db);
|
|
588
|
+
return fDone();
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test
|
|
593
|
+
(
|
|
594
|
+
'auto-detect GUID and ForeignKey indices',
|
|
595
|
+
() =>
|
|
596
|
+
{
|
|
597
|
+
let tmpIndices = libSchemaSQLite.getIndexDefinitionsFromSchema(_BookTableSchema);
|
|
598
|
+
Expect(tmpIndices).to.be.an('array');
|
|
599
|
+
Expect(tmpIndices.length).to.equal(2);
|
|
600
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDBook');
|
|
601
|
+
Expect(tmpIndices[0].Unique).to.equal(true);
|
|
602
|
+
Expect(tmpIndices[1].Name).to.equal('IX_M_IDAuthor');
|
|
603
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
test
|
|
608
|
+
(
|
|
609
|
+
'generate idempotent index script with IF NOT EXISTS',
|
|
610
|
+
() =>
|
|
611
|
+
{
|
|
612
|
+
let tmpScript = libSchemaSQLite.generateCreateIndexScript(_BookTableSchema);
|
|
613
|
+
Expect(tmpScript).to.contain('CREATE UNIQUE INDEX IF NOT EXISTS AK_M_GUIDBook ON Book(GUIDBook)');
|
|
614
|
+
Expect(tmpScript).to.contain('CREATE INDEX IF NOT EXISTS IX_M_IDAuthor ON Book(IDAuthor)');
|
|
615
|
+
}
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
test
|
|
619
|
+
(
|
|
620
|
+
'generate individual index statements with sqlite_master check',
|
|
621
|
+
() =>
|
|
622
|
+
{
|
|
623
|
+
let tmpStatements = libSchemaSQLite.generateCreateIndexStatements(_BookTableSchema);
|
|
624
|
+
Expect(tmpStatements).to.be.an('array');
|
|
625
|
+
Expect(tmpStatements.length).to.equal(2);
|
|
626
|
+
Expect(tmpStatements[0].Name).to.equal('AK_M_GUIDBook');
|
|
627
|
+
Expect(tmpStatements[0].Statement).to.contain('CREATE UNIQUE INDEX AK_M_GUIDBook');
|
|
628
|
+
Expect(tmpStatements[0].CheckStatement).to.contain("sqlite_master");
|
|
629
|
+
Expect(tmpStatements[1].Name).to.equal('IX_M_IDAuthor');
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
test
|
|
634
|
+
(
|
|
635
|
+
'create indices on a live SQLite database',
|
|
636
|
+
(fDone) =>
|
|
637
|
+
{
|
|
638
|
+
let tmpCreateTableStatement = libSchemaSQLite.generateCreateTableStatement(_BookTableSchema);
|
|
639
|
+
libSchemaSQLite._Database.exec(tmpCreateTableStatement);
|
|
640
|
+
libSchemaSQLite.createIndices(_BookTableSchema,
|
|
641
|
+
(pError) =>
|
|
642
|
+
{
|
|
643
|
+
Expect(pError).to.not.exist;
|
|
644
|
+
let tmpResult = libSchemaSQLite._Database.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='Book' ORDER BY name").all();
|
|
645
|
+
let tmpNames = tmpResult.map(r => r.name);
|
|
646
|
+
Expect(tmpNames).to.include('AK_M_GUIDBook');
|
|
647
|
+
Expect(tmpNames).to.include('IX_M_IDAuthor');
|
|
648
|
+
return fDone();
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
test
|
|
654
|
+
(
|
|
655
|
+
'create indices idempotently (run twice)',
|
|
656
|
+
(fDone) =>
|
|
657
|
+
{
|
|
658
|
+
let tmpCreateTableStatement = libSchemaSQLite.generateCreateTableStatement(_BookTableSchema);
|
|
659
|
+
libSchemaSQLite._Database.exec(tmpCreateTableStatement);
|
|
660
|
+
libSchemaSQLite.createIndices(_BookTableSchema,
|
|
661
|
+
(pError) =>
|
|
662
|
+
{
|
|
663
|
+
Expect(pError).to.not.exist;
|
|
664
|
+
libSchemaSQLite.createIndices(_BookTableSchema,
|
|
665
|
+
(pError2) =>
|
|
666
|
+
{
|
|
667
|
+
Expect(pError2).to.not.exist;
|
|
668
|
+
return fDone();
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
test
|
|
675
|
+
(
|
|
676
|
+
'createAllIndices creates indices for all tables in schema',
|
|
677
|
+
(fDone) =>
|
|
678
|
+
{
|
|
679
|
+
let tmpCreateBook = libSchemaSQLite.generateCreateTableStatement(_BookTableSchema);
|
|
680
|
+
let tmpCreateAuthor = libSchemaSQLite.generateCreateTableStatement(_AuthorTableSchema);
|
|
681
|
+
libSchemaSQLite._Database.exec(tmpCreateBook);
|
|
682
|
+
libSchemaSQLite._Database.exec(tmpCreateAuthor);
|
|
683
|
+
let tmpSchema = { Tables: [_BookTableSchema, _AuthorTableSchema] };
|
|
684
|
+
libSchemaSQLite.createAllIndices(tmpSchema,
|
|
685
|
+
(pError) =>
|
|
686
|
+
{
|
|
687
|
+
Expect(pError).to.not.exist;
|
|
688
|
+
return fDone();
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
test
|
|
694
|
+
(
|
|
695
|
+
'column-level Indexed property generates consistently named indices',
|
|
696
|
+
() =>
|
|
697
|
+
{
|
|
698
|
+
let tmpIndices = libSchemaSQLite.getIndexDefinitionsFromSchema(_BookTableSchemaWithColumnIndexed);
|
|
699
|
+
Expect(tmpIndices).to.be.an('array');
|
|
700
|
+
Expect(tmpIndices.length).to.equal(4);
|
|
701
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDBookIndexed');
|
|
702
|
+
Expect(tmpIndices[1].Name).to.equal('IX_M_T_BookIndexed_C_Title');
|
|
703
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
704
|
+
Expect(tmpIndices[2].Name).to.equal('AK_M_T_BookIndexed_C_ISBN');
|
|
705
|
+
Expect(tmpIndices[2].Unique).to.equal(true);
|
|
706
|
+
Expect(tmpIndices[3].Name).to.equal('IX_M_IDPublisher');
|
|
707
|
+
}
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
test
|
|
711
|
+
(
|
|
712
|
+
'create column-level Indexed indices on a live SQLite database',
|
|
713
|
+
(fDone) =>
|
|
714
|
+
{
|
|
715
|
+
let tmpCreateTableStatement = libSchemaSQLite.generateCreateTableStatement(_BookTableSchemaWithColumnIndexed);
|
|
716
|
+
libSchemaSQLite._Database.exec(tmpCreateTableStatement);
|
|
717
|
+
libSchemaSQLite.createIndices(_BookTableSchemaWithColumnIndexed,
|
|
718
|
+
(pError) =>
|
|
719
|
+
{
|
|
720
|
+
Expect(pError).to.not.exist;
|
|
721
|
+
let tmpResult = libSchemaSQLite._Database.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='BookIndexed' ORDER BY name").all();
|
|
722
|
+
let tmpNames = tmpResult.map(r => r.name);
|
|
723
|
+
Expect(tmpNames).to.include('AK_M_GUIDBookIndexed');
|
|
724
|
+
Expect(tmpNames).to.include('IX_M_T_BookIndexed_C_Title');
|
|
725
|
+
Expect(tmpNames).to.include('AK_M_T_BookIndexed_C_ISBN');
|
|
726
|
+
Expect(tmpNames).to.include('IX_M_IDPublisher');
|
|
727
|
+
return fDone();
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
test
|
|
733
|
+
(
|
|
734
|
+
'IndexName property overrides auto-generated index name',
|
|
735
|
+
() =>
|
|
736
|
+
{
|
|
737
|
+
let tmpIndices = libSchemaSQLite.getIndexDefinitionsFromSchema(_BookTableSchemaWithIndexName);
|
|
738
|
+
Expect(tmpIndices).to.be.an('array');
|
|
739
|
+
Expect(tmpIndices.length).to.equal(5);
|
|
740
|
+
Expect(tmpIndices[0].Name).to.equal('AK_M_GUIDBookCustomIdx');
|
|
741
|
+
Expect(tmpIndices[0].Unique).to.equal(true);
|
|
742
|
+
Expect(tmpIndices[1].Name).to.equal('IX_Custom_Title');
|
|
743
|
+
Expect(tmpIndices[1].Unique).to.equal(false);
|
|
744
|
+
Expect(tmpIndices[2].Name).to.equal('UQ_BookCustomIdx_ISBN');
|
|
745
|
+
Expect(tmpIndices[2].Unique).to.equal(true);
|
|
746
|
+
Expect(tmpIndices[3].Name).to.equal('IX_M_T_BookCustomIdx_C_YearPublished');
|
|
747
|
+
Expect(tmpIndices[3].Unique).to.equal(false);
|
|
748
|
+
Expect(tmpIndices[4].Name).to.equal('IX_M_IDEditor');
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
test
|
|
753
|
+
(
|
|
754
|
+
'create IndexName-overridden indices on a live SQLite database',
|
|
755
|
+
(fDone) =>
|
|
756
|
+
{
|
|
757
|
+
let tmpCreateTableStatement = libSchemaSQLite.generateCreateTableStatement(_BookTableSchemaWithIndexName);
|
|
758
|
+
libSchemaSQLite._Database.exec(tmpCreateTableStatement);
|
|
759
|
+
libSchemaSQLite.createIndices(_BookTableSchemaWithIndexName,
|
|
760
|
+
(pError) =>
|
|
761
|
+
{
|
|
762
|
+
Expect(pError).to.not.exist;
|
|
763
|
+
let tmpResult = libSchemaSQLite._Database.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='BookCustomIdx' ORDER BY name").all();
|
|
764
|
+
let tmpNames = tmpResult.map(r => r.name);
|
|
765
|
+
Expect(tmpNames).to.include('IX_Custom_Title');
|
|
766
|
+
Expect(tmpNames).to.include('UQ_BookCustomIdx_ISBN');
|
|
767
|
+
Expect(tmpNames).to.include('IX_M_T_BookCustomIdx_C_YearPublished');
|
|
768
|
+
Expect(tmpNames).to.include('AK_M_GUIDBookCustomIdx');
|
|
769
|
+
Expect(tmpNames).to.include('IX_M_IDEditor');
|
|
770
|
+
return fDone();
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
test
|
|
778
|
+
(
|
|
779
|
+
'schema provider is accessible from connection provider',
|
|
780
|
+
(fDone) =>
|
|
781
|
+
{
|
|
782
|
+
let _Fable = new libFable(_FableConfig);
|
|
783
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
784
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
785
|
+
Expect(_Fable.MeadowSQLiteProvider.schemaProvider).to.be.an('object');
|
|
786
|
+
return fDone();
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
suite
|
|
791
|
+
(
|
|
792
|
+
'Database Introspection',
|
|
793
|
+
()=>
|
|
794
|
+
{
|
|
795
|
+
let libSchemaSQLite = null;
|
|
796
|
+
|
|
797
|
+
setup(
|
|
798
|
+
(fDone) =>
|
|
799
|
+
{
|
|
800
|
+
let _Fable = new libFable(_FableConfig);
|
|
801
|
+
libSchemaSQLite = _Fable.serviceManager.addServiceType('MeadowSchemaSQLite', libMeadowSchemaSQLite);
|
|
802
|
+
libSchemaSQLite = _Fable.serviceManager.instantiateServiceProvider('MeadowSchemaSQLite');
|
|
803
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
804
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
805
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
806
|
+
(pError) =>
|
|
807
|
+
{
|
|
808
|
+
libSchemaSQLite.setDatabase(_Fable.MeadowSQLiteProvider.db);
|
|
809
|
+
// Create tables and indices for introspection tests
|
|
810
|
+
let tmpCreateBook = libSchemaSQLite.generateCreateTableStatement(_BookTableSchema);
|
|
811
|
+
libSchemaSQLite._Database.exec(tmpCreateBook);
|
|
812
|
+
libSchemaSQLite.createIndices(_BookTableSchema,
|
|
813
|
+
(pIndexError) =>
|
|
814
|
+
{
|
|
815
|
+
let tmpCreateIndexed = libSchemaSQLite.generateCreateTableStatement(_BookTableSchemaWithColumnIndexed);
|
|
816
|
+
libSchemaSQLite._Database.exec(tmpCreateIndexed);
|
|
817
|
+
libSchemaSQLite.createIndices(_BookTableSchemaWithColumnIndexed,
|
|
818
|
+
(pIndexError2) =>
|
|
819
|
+
{
|
|
820
|
+
let tmpCreateCustom = libSchemaSQLite.generateCreateTableStatement(_BookTableSchemaWithIndexName);
|
|
821
|
+
libSchemaSQLite._Database.exec(tmpCreateCustom);
|
|
822
|
+
libSchemaSQLite.createIndices(_BookTableSchemaWithIndexName,
|
|
823
|
+
(pIndexError3) =>
|
|
824
|
+
{
|
|
825
|
+
return fDone();
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
test
|
|
833
|
+
(
|
|
834
|
+
'listTables returns all user tables',
|
|
835
|
+
(fDone) =>
|
|
836
|
+
{
|
|
837
|
+
libSchemaSQLite.listTables(
|
|
838
|
+
(pError, pTables) =>
|
|
839
|
+
{
|
|
840
|
+
Expect(pError).to.not.exist;
|
|
841
|
+
Expect(pTables).to.be.an('array');
|
|
842
|
+
Expect(pTables.length).to.be.greaterThan(0);
|
|
843
|
+
Expect(pTables).to.include('Book');
|
|
844
|
+
Expect(pTables).to.include('BookIndexed');
|
|
845
|
+
Expect(pTables).to.include('BookCustomIdx');
|
|
846
|
+
return fDone();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
test
|
|
852
|
+
(
|
|
853
|
+
'introspectTableColumns returns column definitions for Book',
|
|
854
|
+
(fDone) =>
|
|
855
|
+
{
|
|
856
|
+
libSchemaSQLite.introspectTableColumns('Book',
|
|
857
|
+
(pError, pColumns) =>
|
|
858
|
+
{
|
|
859
|
+
Expect(pError).to.not.exist;
|
|
860
|
+
Expect(pColumns).to.be.an('array');
|
|
861
|
+
Expect(pColumns.length).to.equal(9);
|
|
862
|
+
|
|
863
|
+
// ID column
|
|
864
|
+
Expect(pColumns[0].Column).to.equal('IDBook');
|
|
865
|
+
Expect(pColumns[0].DataType).to.equal('ID');
|
|
866
|
+
|
|
867
|
+
// GUID column
|
|
868
|
+
Expect(pColumns[1].Column).to.equal('GUIDBook');
|
|
869
|
+
Expect(pColumns[1].DataType).to.equal('GUID');
|
|
870
|
+
|
|
871
|
+
// String column
|
|
872
|
+
Expect(pColumns[2].Column).to.equal('Title');
|
|
873
|
+
// Title is TEXT NOT NULL DEFAULT '' → maps to String
|
|
874
|
+
Expect(pColumns[2].DataType).to.equal('String');
|
|
875
|
+
|
|
876
|
+
// Text column
|
|
877
|
+
Expect(pColumns[3].Column).to.equal('Description');
|
|
878
|
+
Expect(pColumns[3].DataType).to.equal('Text');
|
|
879
|
+
|
|
880
|
+
// Decimal column
|
|
881
|
+
Expect(pColumns[4].Column).to.equal('Price');
|
|
882
|
+
Expect(pColumns[4].DataType).to.equal('Decimal');
|
|
883
|
+
|
|
884
|
+
// Numeric column
|
|
885
|
+
Expect(pColumns[5].Column).to.equal('PageCount');
|
|
886
|
+
Expect(pColumns[5].DataType).to.equal('Numeric');
|
|
887
|
+
|
|
888
|
+
// DateTime column (stored as TEXT in SQLite)
|
|
889
|
+
Expect(pColumns[6].Column).to.equal('PublishDate');
|
|
890
|
+
Expect(pColumns[6].DataType).to.equal('Text');
|
|
891
|
+
|
|
892
|
+
// Boolean column (stored as INTEGER, detected by "In" prefix + NOT NULL DEFAULT 0)
|
|
893
|
+
Expect(pColumns[7].Column).to.equal('InPrint');
|
|
894
|
+
Expect(pColumns[7].DataType).to.equal('Boolean');
|
|
895
|
+
|
|
896
|
+
// ForeignKey column
|
|
897
|
+
Expect(pColumns[8].Column).to.equal('IDAuthor');
|
|
898
|
+
Expect(pColumns[8].DataType).to.equal('Numeric');
|
|
899
|
+
|
|
900
|
+
return fDone();
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
test
|
|
906
|
+
(
|
|
907
|
+
'introspectTableIndices returns index definitions for Book',
|
|
908
|
+
(fDone) =>
|
|
909
|
+
{
|
|
910
|
+
libSchemaSQLite.introspectTableIndices('Book',
|
|
911
|
+
(pError, pIndices) =>
|
|
912
|
+
{
|
|
913
|
+
Expect(pError).to.not.exist;
|
|
914
|
+
Expect(pIndices).to.be.an('array');
|
|
915
|
+
Expect(pIndices.length).to.equal(2);
|
|
916
|
+
|
|
917
|
+
// Should have AK_M_GUIDBook and IX_M_IDAuthor
|
|
918
|
+
let tmpNames = pIndices.map((pIdx) => { return pIdx.Name; });
|
|
919
|
+
Expect(tmpNames).to.include('AK_M_GUIDBook');
|
|
920
|
+
Expect(tmpNames).to.include('IX_M_IDAuthor');
|
|
921
|
+
|
|
922
|
+
let tmpGUIDIndex = pIndices.find((pIdx) => { return pIdx.Name === 'AK_M_GUIDBook'; });
|
|
923
|
+
Expect(tmpGUIDIndex.Unique).to.equal(true);
|
|
924
|
+
Expect(tmpGUIDIndex.Columns).to.deep.equal(['GUIDBook']);
|
|
925
|
+
|
|
926
|
+
return fDone();
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
test
|
|
932
|
+
(
|
|
933
|
+
'introspectTableForeignKeys returns empty for table without FK constraints',
|
|
934
|
+
(fDone) =>
|
|
935
|
+
{
|
|
936
|
+
// SQLite tables created without REFERENCES have no FK constraints
|
|
937
|
+
libSchemaSQLite.introspectTableForeignKeys('Book',
|
|
938
|
+
(pError, pFKs) =>
|
|
939
|
+
{
|
|
940
|
+
Expect(pError).to.not.exist;
|
|
941
|
+
Expect(pFKs).to.be.an('array');
|
|
942
|
+
// Our Book table was created without REFERENCES clauses
|
|
943
|
+
Expect(pFKs.length).to.equal(0);
|
|
944
|
+
return fDone();
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
test
|
|
950
|
+
(
|
|
951
|
+
'introspectTableSchema combines columns and indices for BookIndexed',
|
|
952
|
+
(fDone) =>
|
|
953
|
+
{
|
|
954
|
+
libSchemaSQLite.introspectTableSchema('BookIndexed',
|
|
955
|
+
(pError, pSchema) =>
|
|
956
|
+
{
|
|
957
|
+
Expect(pError).to.not.exist;
|
|
958
|
+
Expect(pSchema).to.be.an('object');
|
|
959
|
+
Expect(pSchema.TableName).to.equal('BookIndexed');
|
|
960
|
+
Expect(pSchema.Columns).to.be.an('array');
|
|
961
|
+
|
|
962
|
+
// Check that column-level Indexed properties are folded in
|
|
963
|
+
let tmpTitleCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'Title'; });
|
|
964
|
+
Expect(tmpTitleCol.Indexed).to.equal(true);
|
|
965
|
+
Expect(tmpTitleCol).to.not.have.property('IndexName');
|
|
966
|
+
|
|
967
|
+
let tmpISBNCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'ISBN'; });
|
|
968
|
+
Expect(tmpISBNCol.Indexed).to.equal('unique');
|
|
969
|
+
Expect(tmpISBNCol).to.not.have.property('IndexName');
|
|
970
|
+
|
|
971
|
+
return fDone();
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
test
|
|
977
|
+
(
|
|
978
|
+
'introspectTableSchema preserves IndexName for custom-named indices',
|
|
979
|
+
(fDone) =>
|
|
980
|
+
{
|
|
981
|
+
libSchemaSQLite.introspectTableSchema('BookCustomIdx',
|
|
982
|
+
(pError, pSchema) =>
|
|
983
|
+
{
|
|
984
|
+
Expect(pError).to.not.exist;
|
|
985
|
+
Expect(pSchema.TableName).to.equal('BookCustomIdx');
|
|
986
|
+
|
|
987
|
+
// Title has custom IndexName IX_Custom_Title
|
|
988
|
+
let tmpTitleCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'Title'; });
|
|
989
|
+
Expect(tmpTitleCol.Indexed).to.equal(true);
|
|
990
|
+
Expect(tmpTitleCol.IndexName).to.equal('IX_Custom_Title');
|
|
991
|
+
|
|
992
|
+
// ISBN has custom IndexName UQ_BookCustomIdx_ISBN
|
|
993
|
+
let tmpISBNCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'ISBN'; });
|
|
994
|
+
Expect(tmpISBNCol.Indexed).to.equal('unique');
|
|
995
|
+
Expect(tmpISBNCol.IndexName).to.equal('UQ_BookCustomIdx_ISBN');
|
|
996
|
+
|
|
997
|
+
// YearPublished has auto-generated name → no IndexName
|
|
998
|
+
let tmpYearCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'YearPublished'; });
|
|
999
|
+
Expect(tmpYearCol.Indexed).to.equal(true);
|
|
1000
|
+
Expect(tmpYearCol).to.not.have.property('IndexName');
|
|
1001
|
+
|
|
1002
|
+
return fDone();
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
test
|
|
1008
|
+
(
|
|
1009
|
+
'introspectDatabaseSchema returns schemas for all tables',
|
|
1010
|
+
(fDone) =>
|
|
1011
|
+
{
|
|
1012
|
+
libSchemaSQLite.introspectDatabaseSchema(
|
|
1013
|
+
(pError, pSchema) =>
|
|
1014
|
+
{
|
|
1015
|
+
Expect(pError).to.not.exist;
|
|
1016
|
+
Expect(pSchema).to.be.an('object');
|
|
1017
|
+
Expect(pSchema.Tables).to.be.an('array');
|
|
1018
|
+
Expect(pSchema.Tables.length).to.be.greaterThan(0);
|
|
1019
|
+
|
|
1020
|
+
let tmpTableNames = pSchema.Tables.map((pT) => { return pT.TableName; });
|
|
1021
|
+
Expect(tmpTableNames).to.include('Book');
|
|
1022
|
+
Expect(tmpTableNames).to.include('BookIndexed');
|
|
1023
|
+
Expect(tmpTableNames).to.include('BookCustomIdx');
|
|
1024
|
+
|
|
1025
|
+
return fDone();
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
);
|
|
1029
|
+
|
|
1030
|
+
test
|
|
1031
|
+
(
|
|
1032
|
+
'generateMeadowPackageFromTable produces Meadow package JSON',
|
|
1033
|
+
(fDone) =>
|
|
1034
|
+
{
|
|
1035
|
+
libSchemaSQLite.generateMeadowPackageFromTable('Book',
|
|
1036
|
+
(pError, pPackage) =>
|
|
1037
|
+
{
|
|
1038
|
+
Expect(pError).to.not.exist;
|
|
1039
|
+
Expect(pPackage).to.be.an('object');
|
|
1040
|
+
Expect(pPackage.Scope).to.equal('Book');
|
|
1041
|
+
Expect(pPackage.DefaultIdentifier).to.equal('IDBook');
|
|
1042
|
+
Expect(pPackage.Schema).to.be.an('array');
|
|
1043
|
+
Expect(pPackage.DefaultObject).to.be.an('object');
|
|
1044
|
+
|
|
1045
|
+
// Verify schema entries
|
|
1046
|
+
let tmpIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'IDBook'; });
|
|
1047
|
+
Expect(tmpIDEntry.Type).to.equal('AutoIdentity');
|
|
1048
|
+
|
|
1049
|
+
let tmpGUIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'GUIDBook'; });
|
|
1050
|
+
Expect(tmpGUIDEntry.Type).to.equal('AutoGUID');
|
|
1051
|
+
|
|
1052
|
+
let tmpTitleEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'Title'; });
|
|
1053
|
+
Expect(tmpTitleEntry.Type).to.equal('String');
|
|
1054
|
+
|
|
1055
|
+
// Verify default object
|
|
1056
|
+
Expect(pPackage.DefaultObject.IDBook).to.equal(0);
|
|
1057
|
+
Expect(pPackage.DefaultObject.GUIDBook).to.equal('');
|
|
1058
|
+
Expect(pPackage.DefaultObject.Title).to.equal('');
|
|
1059
|
+
|
|
1060
|
+
return fDone();
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
test
|
|
1066
|
+
(
|
|
1067
|
+
'round-trip: introspect BookIndexed and regenerate matching indices',
|
|
1068
|
+
(fDone) =>
|
|
1069
|
+
{
|
|
1070
|
+
libSchemaSQLite.introspectTableSchema('BookIndexed',
|
|
1071
|
+
(pError, pSchema) =>
|
|
1072
|
+
{
|
|
1073
|
+
Expect(pError).to.not.exist;
|
|
1074
|
+
|
|
1075
|
+
// Use the introspected schema to generate index definitions
|
|
1076
|
+
let tmpIndices = libSchemaSQLite.getIndexDefinitionsFromSchema(pSchema);
|
|
1077
|
+
|
|
1078
|
+
// The original BookIndexed had:
|
|
1079
|
+
// AK_M_GUIDBookIndexed (GUID auto)
|
|
1080
|
+
// IX_M_T_BookIndexed_C_Title (Indexed: true)
|
|
1081
|
+
// AK_M_T_BookIndexed_C_ISBN (Indexed: 'unique')
|
|
1082
|
+
// IX_M_IDPublisher (FK auto)
|
|
1083
|
+
let tmpNames = tmpIndices.map((pIdx) => { return pIdx.Name; });
|
|
1084
|
+
Expect(tmpNames).to.include('AK_M_GUIDBookIndexed');
|
|
1085
|
+
Expect(tmpNames).to.include('IX_M_T_BookIndexed_C_Title');
|
|
1086
|
+
Expect(tmpNames).to.include('AK_M_T_BookIndexed_C_ISBN');
|
|
1087
|
+
Expect(tmpNames).to.include('IX_M_IDPublisher');
|
|
1088
|
+
|
|
1089
|
+
return fDone();
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
test
|
|
1095
|
+
(
|
|
1096
|
+
'round-trip: introspect BookCustomIdx and regenerate matching index names',
|
|
1097
|
+
(fDone) =>
|
|
1098
|
+
{
|
|
1099
|
+
libSchemaSQLite.introspectTableSchema('BookCustomIdx',
|
|
1100
|
+
(pError, pSchema) =>
|
|
1101
|
+
{
|
|
1102
|
+
Expect(pError).to.not.exist;
|
|
1103
|
+
|
|
1104
|
+
// Use the introspected schema to generate index definitions
|
|
1105
|
+
let tmpIndices = libSchemaSQLite.getIndexDefinitionsFromSchema(pSchema);
|
|
1106
|
+
|
|
1107
|
+
// The original BookCustomIdx had:
|
|
1108
|
+
// AK_M_GUIDBookCustomIdx (GUID auto)
|
|
1109
|
+
// IX_Custom_Title (IndexName override)
|
|
1110
|
+
// UQ_BookCustomIdx_ISBN (IndexName override, unique)
|
|
1111
|
+
// IX_M_T_BookCustomIdx_C_YearPublished (auto)
|
|
1112
|
+
// IX_M_IDEditor (FK auto)
|
|
1113
|
+
let tmpNames = tmpIndices.map((pIdx) => { return pIdx.Name; });
|
|
1114
|
+
Expect(tmpNames).to.include('AK_M_GUIDBookCustomIdx');
|
|
1115
|
+
Expect(tmpNames).to.include('IX_Custom_Title');
|
|
1116
|
+
Expect(tmpNames).to.include('UQ_BookCustomIdx_ISBN');
|
|
1117
|
+
Expect(tmpNames).to.include('IX_M_T_BookCustomIdx_C_YearPublished');
|
|
1118
|
+
Expect(tmpNames).to.include('IX_M_IDEditor');
|
|
1119
|
+
|
|
1120
|
+
return fDone();
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
|
|
1125
|
+
suite
|
|
1126
|
+
(
|
|
1127
|
+
'Chinook Database Introspection',
|
|
1128
|
+
()=>
|
|
1129
|
+
{
|
|
1130
|
+
setup(
|
|
1131
|
+
(fDone) =>
|
|
1132
|
+
{
|
|
1133
|
+
// Create Chinook tables (runs once before the suite)
|
|
1134
|
+
libSchemaSQLite._Database.exec(_ChinookSQL);
|
|
1135
|
+
return fDone();
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test
|
|
1139
|
+
(
|
|
1140
|
+
'listTables includes all 11 Chinook tables',
|
|
1141
|
+
(fDone) =>
|
|
1142
|
+
{
|
|
1143
|
+
libSchemaSQLite.listTables(
|
|
1144
|
+
(pError, pTables) =>
|
|
1145
|
+
{
|
|
1146
|
+
Expect(pError).to.not.exist;
|
|
1147
|
+
Expect(pTables).to.be.an('array');
|
|
1148
|
+
|
|
1149
|
+
let tmpChinookTables = ['Album', 'Artist', 'Customer', 'Employee',
|
|
1150
|
+
'Genre', 'Invoice', 'InvoiceLine', 'MediaType',
|
|
1151
|
+
'Playlist', 'PlaylistTrack', 'Track'];
|
|
1152
|
+
|
|
1153
|
+
tmpChinookTables.forEach(
|
|
1154
|
+
(pTableName) =>
|
|
1155
|
+
{
|
|
1156
|
+
Expect(pTables).to.include(pTableName);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
return fDone();
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
|
|
1164
|
+
test
|
|
1165
|
+
(
|
|
1166
|
+
'introspectTableColumns on Track detects all 9 columns',
|
|
1167
|
+
(fDone) =>
|
|
1168
|
+
{
|
|
1169
|
+
libSchemaSQLite.introspectTableColumns('Track',
|
|
1170
|
+
(pError, pColumns) =>
|
|
1171
|
+
{
|
|
1172
|
+
Expect(pError).to.not.exist;
|
|
1173
|
+
Expect(pColumns).to.be.an('array');
|
|
1174
|
+
Expect(pColumns.length).to.equal(9);
|
|
1175
|
+
|
|
1176
|
+
let tmpTrackId = pColumns.find((pCol) => { return pCol.Column === 'TrackId'; });
|
|
1177
|
+
Expect(tmpTrackId.DataType).to.equal('ID');
|
|
1178
|
+
|
|
1179
|
+
let tmpName = pColumns.find((pCol) => { return pCol.Column === 'Name'; });
|
|
1180
|
+
Expect(tmpName.DataType).to.equal('String');
|
|
1181
|
+
|
|
1182
|
+
let tmpUnitPrice = pColumns.find((pCol) => { return pCol.Column === 'UnitPrice'; });
|
|
1183
|
+
Expect(tmpUnitPrice.DataType).to.equal('Decimal');
|
|
1184
|
+
|
|
1185
|
+
let tmpMilliseconds = pColumns.find((pCol) => { return pCol.Column === 'Milliseconds'; });
|
|
1186
|
+
Expect(tmpMilliseconds.DataType).to.equal('Numeric');
|
|
1187
|
+
|
|
1188
|
+
return fDone();
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
test
|
|
1194
|
+
(
|
|
1195
|
+
'introspectTableColumns on Employee detects 15 columns',
|
|
1196
|
+
(fDone) =>
|
|
1197
|
+
{
|
|
1198
|
+
libSchemaSQLite.introspectTableColumns('Employee',
|
|
1199
|
+
(pError, pColumns) =>
|
|
1200
|
+
{
|
|
1201
|
+
Expect(pError).to.not.exist;
|
|
1202
|
+
Expect(pColumns.length).to.equal(15);
|
|
1203
|
+
|
|
1204
|
+
let tmpEmployeeId = pColumns.find((pCol) => { return pCol.Column === 'EmployeeId'; });
|
|
1205
|
+
Expect(tmpEmployeeId.DataType).to.equal('ID');
|
|
1206
|
+
|
|
1207
|
+
let tmpLastName = pColumns.find((pCol) => { return pCol.Column === 'LastName'; });
|
|
1208
|
+
Expect(tmpLastName.DataType).to.equal('String');
|
|
1209
|
+
|
|
1210
|
+
return fDone();
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
test
|
|
1216
|
+
(
|
|
1217
|
+
'introspectTableForeignKeys on Track detects 3 FK relationships',
|
|
1218
|
+
(fDone) =>
|
|
1219
|
+
{
|
|
1220
|
+
libSchemaSQLite.introspectTableForeignKeys('Track',
|
|
1221
|
+
(pError, pFKs) =>
|
|
1222
|
+
{
|
|
1223
|
+
Expect(pError).to.not.exist;
|
|
1224
|
+
Expect(pFKs).to.be.an('array');
|
|
1225
|
+
Expect(pFKs.length).to.equal(3);
|
|
1226
|
+
|
|
1227
|
+
let tmpAlbumFK = pFKs.find((pFK) => { return pFK.Column === 'AlbumId'; });
|
|
1228
|
+
Expect(tmpAlbumFK).to.exist;
|
|
1229
|
+
Expect(tmpAlbumFK.ReferencedTable).to.equal('Album');
|
|
1230
|
+
Expect(tmpAlbumFK.ReferencedColumn).to.equal('AlbumId');
|
|
1231
|
+
|
|
1232
|
+
let tmpMediaTypeFK = pFKs.find((pFK) => { return pFK.Column === 'MediaTypeId'; });
|
|
1233
|
+
Expect(tmpMediaTypeFK).to.exist;
|
|
1234
|
+
Expect(tmpMediaTypeFK.ReferencedTable).to.equal('MediaType');
|
|
1235
|
+
|
|
1236
|
+
let tmpGenreFK = pFKs.find((pFK) => { return pFK.Column === 'GenreId'; });
|
|
1237
|
+
Expect(tmpGenreFK).to.exist;
|
|
1238
|
+
Expect(tmpGenreFK.ReferencedTable).to.equal('Genre');
|
|
1239
|
+
|
|
1240
|
+
return fDone();
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
test
|
|
1246
|
+
(
|
|
1247
|
+
'introspectTableForeignKeys on Employee detects self-referential FK',
|
|
1248
|
+
(fDone) =>
|
|
1249
|
+
{
|
|
1250
|
+
libSchemaSQLite.introspectTableForeignKeys('Employee',
|
|
1251
|
+
(pError, pFKs) =>
|
|
1252
|
+
{
|
|
1253
|
+
Expect(pError).to.not.exist;
|
|
1254
|
+
Expect(pFKs).to.be.an('array');
|
|
1255
|
+
Expect(pFKs.length).to.equal(1);
|
|
1256
|
+
|
|
1257
|
+
Expect(pFKs[0].Column).to.equal('ReportsTo');
|
|
1258
|
+
Expect(pFKs[0].ReferencedTable).to.equal('Employee');
|
|
1259
|
+
Expect(pFKs[0].ReferencedColumn).to.equal('EmployeeId');
|
|
1260
|
+
|
|
1261
|
+
return fDone();
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
test
|
|
1267
|
+
(
|
|
1268
|
+
'introspectTableForeignKeys on PlaylistTrack detects 2 FKs',
|
|
1269
|
+
(fDone) =>
|
|
1270
|
+
{
|
|
1271
|
+
libSchemaSQLite.introspectTableForeignKeys('PlaylistTrack',
|
|
1272
|
+
(pError, pFKs) =>
|
|
1273
|
+
{
|
|
1274
|
+
Expect(pError).to.not.exist;
|
|
1275
|
+
Expect(pFKs).to.be.an('array');
|
|
1276
|
+
Expect(pFKs.length).to.equal(2);
|
|
1277
|
+
|
|
1278
|
+
let tmpPlaylistFK = pFKs.find((pFK) => { return pFK.Column === 'PlaylistId'; });
|
|
1279
|
+
Expect(tmpPlaylistFK).to.exist;
|
|
1280
|
+
Expect(tmpPlaylistFK.ReferencedTable).to.equal('Playlist');
|
|
1281
|
+
|
|
1282
|
+
let tmpTrackFK = pFKs.find((pFK) => { return pFK.Column === 'TrackId'; });
|
|
1283
|
+
Expect(tmpTrackFK).to.exist;
|
|
1284
|
+
Expect(tmpTrackFK.ReferencedTable).to.equal('Track');
|
|
1285
|
+
|
|
1286
|
+
return fDone();
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
test
|
|
1292
|
+
(
|
|
1293
|
+
'introspectTableSchema on Track combines columns with FK detection',
|
|
1294
|
+
(fDone) =>
|
|
1295
|
+
{
|
|
1296
|
+
libSchemaSQLite.introspectTableSchema('Track',
|
|
1297
|
+
(pError, pSchema) =>
|
|
1298
|
+
{
|
|
1299
|
+
Expect(pError).to.not.exist;
|
|
1300
|
+
Expect(pSchema.TableName).to.equal('Track');
|
|
1301
|
+
Expect(pSchema.Columns).to.be.an('array');
|
|
1302
|
+
Expect(pSchema.ForeignKeys).to.be.an('array');
|
|
1303
|
+
Expect(pSchema.ForeignKeys.length).to.equal(3);
|
|
1304
|
+
|
|
1305
|
+
// FK columns should be upgraded to ForeignKey DataType
|
|
1306
|
+
let tmpAlbumIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'AlbumId'; });
|
|
1307
|
+
Expect(tmpAlbumIdCol.DataType).to.equal('ForeignKey');
|
|
1308
|
+
|
|
1309
|
+
let tmpMediaTypeIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'MediaTypeId'; });
|
|
1310
|
+
Expect(tmpMediaTypeIdCol.DataType).to.equal('ForeignKey');
|
|
1311
|
+
|
|
1312
|
+
let tmpGenreIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'GenreId'; });
|
|
1313
|
+
Expect(tmpGenreIdCol.DataType).to.equal('ForeignKey');
|
|
1314
|
+
|
|
1315
|
+
return fDone();
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
test
|
|
1321
|
+
(
|
|
1322
|
+
'introspectTableSchema on Album shows FK to Artist',
|
|
1323
|
+
(fDone) =>
|
|
1324
|
+
{
|
|
1325
|
+
libSchemaSQLite.introspectTableSchema('Album',
|
|
1326
|
+
(pError, pSchema) =>
|
|
1327
|
+
{
|
|
1328
|
+
Expect(pError).to.not.exist;
|
|
1329
|
+
Expect(pSchema.TableName).to.equal('Album');
|
|
1330
|
+
Expect(pSchema.ForeignKeys.length).to.equal(1);
|
|
1331
|
+
Expect(pSchema.ForeignKeys[0].Column).to.equal('ArtistId');
|
|
1332
|
+
Expect(pSchema.ForeignKeys[0].ReferencedTable).to.equal('Artist');
|
|
1333
|
+
|
|
1334
|
+
let tmpArtistIdCol = pSchema.Columns.find((pCol) => { return pCol.Column === 'ArtistId'; });
|
|
1335
|
+
Expect(tmpArtistIdCol.DataType).to.equal('ForeignKey');
|
|
1336
|
+
|
|
1337
|
+
return fDone();
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
|
|
1342
|
+
test
|
|
1343
|
+
(
|
|
1344
|
+
'introspectDatabaseSchema includes all Chinook tables',
|
|
1345
|
+
(fDone) =>
|
|
1346
|
+
{
|
|
1347
|
+
libSchemaSQLite.introspectDatabaseSchema(
|
|
1348
|
+
(pError, pSchema) =>
|
|
1349
|
+
{
|
|
1350
|
+
Expect(pError).to.not.exist;
|
|
1351
|
+
Expect(pSchema.Tables).to.be.an('array');
|
|
1352
|
+
|
|
1353
|
+
let tmpTableNames = pSchema.Tables.map((pT) => { return pT.TableName; });
|
|
1354
|
+
Expect(tmpTableNames).to.include('Track');
|
|
1355
|
+
Expect(tmpTableNames).to.include('Album');
|
|
1356
|
+
Expect(tmpTableNames).to.include('Artist');
|
|
1357
|
+
Expect(tmpTableNames).to.include('Invoice');
|
|
1358
|
+
Expect(tmpTableNames).to.include('InvoiceLine');
|
|
1359
|
+
Expect(tmpTableNames).to.include('PlaylistTrack');
|
|
1360
|
+
Expect(tmpTableNames).to.include('Employee');
|
|
1361
|
+
Expect(tmpTableNames).to.include('Customer');
|
|
1362
|
+
|
|
1363
|
+
// Verify Track schema has FKs detected
|
|
1364
|
+
let tmpTrack = pSchema.Tables.find((pT) => { return pT.TableName === 'Track'; });
|
|
1365
|
+
Expect(tmpTrack.ForeignKeys.length).to.equal(3);
|
|
1366
|
+
|
|
1367
|
+
return fDone();
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
);
|
|
1371
|
+
|
|
1372
|
+
test
|
|
1373
|
+
(
|
|
1374
|
+
'generateMeadowPackageFromTable on Album produces valid package',
|
|
1375
|
+
(fDone) =>
|
|
1376
|
+
{
|
|
1377
|
+
libSchemaSQLite.generateMeadowPackageFromTable('Album',
|
|
1378
|
+
(pError, pPackage) =>
|
|
1379
|
+
{
|
|
1380
|
+
Expect(pError).to.not.exist;
|
|
1381
|
+
Expect(pPackage.Scope).to.equal('Album');
|
|
1382
|
+
Expect(pPackage.DefaultIdentifier).to.equal('AlbumId');
|
|
1383
|
+
Expect(pPackage.Schema).to.be.an('array');
|
|
1384
|
+
Expect(pPackage.DefaultObject).to.be.an('object');
|
|
1385
|
+
|
|
1386
|
+
let tmpIDEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'AlbumId'; });
|
|
1387
|
+
Expect(tmpIDEntry.Type).to.equal('AutoIdentity');
|
|
1388
|
+
|
|
1389
|
+
let tmpTitleEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'Title'; });
|
|
1390
|
+
Expect(tmpTitleEntry.Type).to.equal('String');
|
|
1391
|
+
|
|
1392
|
+
return fDone();
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
test
|
|
1398
|
+
(
|
|
1399
|
+
'generateMeadowPackageFromTable on Track handles FKs and Decimal',
|
|
1400
|
+
(fDone) =>
|
|
1401
|
+
{
|
|
1402
|
+
libSchemaSQLite.generateMeadowPackageFromTable('Track',
|
|
1403
|
+
(pError, pPackage) =>
|
|
1404
|
+
{
|
|
1405
|
+
Expect(pError).to.not.exist;
|
|
1406
|
+
Expect(pPackage.Scope).to.equal('Track');
|
|
1407
|
+
Expect(pPackage.DefaultIdentifier).to.equal('TrackId');
|
|
1408
|
+
|
|
1409
|
+
let tmpUnitPriceEntry = pPackage.Schema.find((pEntry) => { return pEntry.Column === 'UnitPrice'; });
|
|
1410
|
+
Expect(tmpUnitPriceEntry).to.exist;
|
|
1411
|
+
|
|
1412
|
+
return fDone();
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
);
|
|
428
1420
|
}
|
|
429
1421
|
);
|