Skip to content

Commit 93c7015

Browse files
feat: Better type, many bug fixes
Co-authored-by: Lobo Metalurgico <LoboMetalurgico@users.noreply.github.com>
1 parent 11c5432 commit 93c7015

File tree

7 files changed

+111
-53
lines changed

7 files changed

+111
-53
lines changed

CHANGELOG.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
# V1.0.6
1+
# v1.1.0
2+
3+
- Feat: Better type on BaseModel creation
4+
5+
- Fix: Can't initialize orm when foreign keys are present due to wrong foreign key names
6+
7+
- Fix: Multiple primary keys not being created due to wrong handling of primary key fields
8+
9+
- Fix: Parsing of unique keys and foreign keys when updating tables
10+
11+
- BREAKING: Changed constructor of MariaDBConnection to accept an object instead of multiple parameters
12+
13+
# v1.0.6
214

315
- Fix: Can't create tables with foreign keys due to invalid syntax for ON DELETE and ON UPDATE clauses
416

5-
# V1.0.5
17+
# v1.0.5
618

719
- Fix: Can't connect to MySql instances due to missing allowPublicKeyRetrieval: true in connection options
820

9-
# V1.0.4
21+
# v1.0.4
1022

1123
- NOTICE: Rebranding 'promisedb' to 'promiseorm'
1224
- CI: Add CI
1325

14-
# V1.0.3
26+
# v1.0.3
1527

1628
- Fix: Register schema not awaiting before returning
1729

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { DatabaseManager, MariaDBConnection, BaseModel, EDatabaseTypes } from 'p
2424

2525
// if you're only going to have a single connection you may pass it as parameter in DatabaseManager constructor and it will automatically register it under the name 'default'
2626
const dbmgr = new DatabaseManager();
27-
const mariadb = new MariaDBConnection('localhost', 3306, 'username', 'password', 'database');
27+
const mariadb = new MariaDBConnection({ hostname: 'localhost', port: 3306, username: 'username', password: 'password', database: 'database' });
2828
await dbmgr.registerConnection('prodmaria', mariadb);
2929

3030
// You can also extends BaseModel in a class and pass the params to super() if you wish to instantiate your models all a once somewhere else.
@@ -36,6 +36,13 @@ const iceCreamModel = new BaseModel({
3636
unique: true,
3737
primaryKey: true,
3838
},
39+
calories: {
40+
type: EDatabaseTypes.UINT, // Unsigned integer
41+
maxSize: 30_000, // 30,000 calories max (max integer value, not byte size!)
42+
minSize: 0,
43+
primaryKey: false,
44+
autoIncrement: false,
45+
},
3946
price: {
4047
type: EDatabaseTypes.DECIMAL,
4148
maxSize: 999.99, // $999.99
@@ -48,6 +55,7 @@ const iceCreamModel = new BaseModel({
4855
const iceModel = await dbmgr.getConnection('prodmaria').registerModel('icecream', iceCreamModel);
4956
iceModel.create({
5057
flavor: 'chocolate',
58+
calories: 250,
5159
price: 9.99,
5260
}).then(async () => {
5361
console.log('Successfully created item on database!');
@@ -57,7 +65,7 @@ iceModel.create({
5765
/* Output:
5866
5967
> Successfully created item on database!
60-
{ flavor: 'chocolate', price: '9.99' }
68+
{ flavor: 'chocolate', calories: 250, price: '9.99' }
6169
6270
*/
6371
```

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "promiseorm",
3-
"version": "1.0.6",
3+
"version": "1.1.0",
44
"description": "A Typescript ORM for automatic creation and management of models and entries from simple objects",
55
"main": "build/index.js",
66
"files": [

src/main/connection/MariaDBConnection.ts

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class MariaDBConnection extends DatabaseConnection {
2020
private pool?: mariaDB.Pool;
2121
private isConnecting: boolean;
2222

23-
constructor(hostname: string, port: number, username: string, password: string, database: string) {
23+
constructor({ hostname, port, username, password, database }: { hostname: string, port: number, username: string, password: string, database: string }) {
2424
super();
2525

2626
this.hostname = hostname;
@@ -181,15 +181,16 @@ export class MariaDBConnection extends DatabaseConnection {
181181
* @private
182182
*/
183183
private convertTypes(field: IDatabaseField): IMariaDBField {
184-
if (!field.maxSize && field.type !== EDatabaseTypes.BOOLEAN) throw new DatabaseException('Any field (other than boolean) must have a maxSize!');
184+
if (!(field.type === EDatabaseTypes.BOOLEAN || field.type === EDatabaseTypes.TIMESTAMP) && !field.maxSize)
185+
throw new DatabaseException('Any field (other than boolean and timestamp) must have a maxSize!');
185186
const finalObject: IMariaDBField = {
186187
type: EMariaDBFieldTypes.string,
187188
attributes: '',
188-
typeSize: field.maxSize ?? 1,
189+
typeSize: (field.type === EDatabaseTypes.BOOLEAN ? 1 : field.maxSize) ?? 1,
189190
nullable: field.nullable,
190191
primaryKey: (field.primaryKey && (field.type === EDatabaseTypes.UINT || field.type === EDatabaseTypes.SINT)) ?? false,
191192
autoIncrement: field.autoIncrement ?? false,
192-
unique: field.unique ?? false,
193+
unique: (field.type === EDatabaseTypes.BOOLEAN ? false : field.unique) ?? false,
193194
default: field.default,
194195
};
195196
switch (field.type) {
@@ -251,14 +252,14 @@ export class MariaDBConnection extends DatabaseConnection {
251252
const uniqueKeys: string[] = [];
252253
fieldsKeys.forEach((fieldKey) => {
253254
if (fields[fieldKey].primaryKey) primaryKeys.push(conn.escapeId(fieldKey));
254-
if (fields[fieldKey].unique) uniqueKeys.push(conn.escapeId(fieldKey));
255+
if (fields[fieldKey].type !== EDatabaseTypes.BOOLEAN && fields[fieldKey].unique) uniqueKeys.push(conn.escapeId(fieldKey));
255256
});
256257

257258
// Add the constraints
258259
if (primaryKeys.length > 0) tableFields.push(`PRIMARY KEY (${primaryKeys.join(', ')})`);
259260
uniqueKeys.filter((key) => !primaryKeys.includes(key)).forEach((key) => tableFields.push(`UNIQUE INDEX ${key} (${key})`));
260261
fieldsKeys.filter(key => fields[key].foreignKey).forEach((key) => {
261-
let query = `FOREIGN KEY (${conn.escapeId(key)}) REFERENCES ${conn.escapeId(fields[key].foreignKey!.table.getName())}(${conn.escapeId(fields[key].foreignKey!.field)})`;
262+
let query = `CONSTRAINT FOREIGN KEY ${key + '_fk'} (${conn.escapeId(key)}) REFERENCES ${conn.escapeId(fields[key].foreignKey!.table.getName())}(${conn.escapeId(fields[key].foreignKey!.field)})`;
262263
if (fields[key].foreignKey!.onDelete) query += ` ON DELETE ${fields[key].foreignKey!.onDelete!}`;
263264
if (fields[key].foreignKey!.onUpdate) query += ` ON UPDATE ${fields[key].foreignKey!.onUpdate!}`;
264265
tableFields.push(query);
@@ -277,23 +278,22 @@ export class MariaDBConnection extends DatabaseConnection {
277278
const currentFields: IMariaDBDescribeField[] = await conn.query(`DESCRIBE ${tableName}`);
278279
const fieldsKeys = Object.keys(fields);
279280

281+
const tableData: { Table: string, 'Create Table': string } = (await conn.query(`SHOW CREATE TABLE ${tableName}`))[0];
282+
280283
const fieldsToAdd = fieldsKeys.filter((key) => !currentFields.find((field) => field.Field === key));
281284
const fieldsToRemove = currentFields.filter((field) => !fields[field.Field]);
282285
const fieldsToUpdate: string[] = [];
283-
const existingPrimaryKeys = currentFields.filter((field) => field.Key === 'PRI');
286+
284287
const newPrimaryKeys = fieldsKeys.filter((key) => fields[key].primaryKey);
285-
const didPrimaryKeysChange = existingPrimaryKeys.length !== newPrimaryKeys.length ||
286-
existingPrimaryKeys.some((key) => !newPrimaryKeys.includes(key.Field)) ||
287-
newPrimaryKeys.some((key) => !existingPrimaryKeys.find((field) => field.Field === key));
288-
const existingUniqueKeys = currentFields.filter((field) => field.Key === 'UNI');
289-
const newUniqueKeys = fieldsKeys.filter((key) => fields[key].unique && !fields[key].primaryKey);
290-
const uniqueKeysToRemove = existingUniqueKeys.filter((key) => !newUniqueKeys.includes(key.Field));
291-
const uniqueKeysToAdd = newUniqueKeys.filter((key) => !existingUniqueKeys.find((field) => field.Field === key));
292-
const existingForeignKeys = currentFields.filter((field) => field.Key === 'MUL');
293-
const newForeignKeys = fieldsKeys.filter((key) => fields[key].foreignKey);
294-
const foreignKeysToRemove = existingForeignKeys.filter((key) => !newForeignKeys.includes(key.Field)).map((item) => item.Field);
295-
const foreignKeysToAdd = newForeignKeys.filter((key) => !existingForeignKeys.find((field) => field.Field === key));
296-
const foreignKeysToUpdate = newForeignKeys.filter((key) => existingForeignKeys.find((field) => field.Field === key));
288+
const newUniqueKeys = fieldsKeys.filter((key) => fields[key].type !== EDatabaseTypes.BOOLEAN && fields[key].unique && !fields[key].primaryKey);
289+
const newForeignKeys = fieldsKeys.filter((key) => fields[key].foreignKey).map((key) => key + '_fk');
290+
291+
const currentPrimaryKey = (tableData['Create Table'].match(/PRIMARY KEY \((.*?)\)/)?.[1] ?? '').split(',').map(key => key.replace(/`/g, '').trim()).filter(key => key.length > 0);
292+
const currentUniqueKeys = Array.from(tableData['Create Table'].matchAll(/UNIQUE KEY `(.*?)`/g)).map(match => match[1]).filter(key => !currentPrimaryKey.includes(key));
293+
const currentForeignKeys = Array.from(
294+
tableData['Create Table']
295+
.matchAll(/CONSTRAINT `(.*?)` FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)( ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?( ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?/g),
296+
).map(match => match[1]);
297297

298298
// Find all fields that should be updated
299299
for (const field of currentFields) {
@@ -309,20 +309,35 @@ export class MariaDBConnection extends DatabaseConnection {
309309
}
310310

311311
const operations: string[] = [];
312-
if (didPrimaryKeysChange) {
312+
if (currentPrimaryKey.join(',') !== newPrimaryKeys.join(',')) {
313313
operations.push('DROP PRIMARY KEY');
314314
operations.push(`ADD PRIMARY KEY (${fieldsKeys.filter((fieldKey) => fields[fieldKey].primaryKey).map((key) => conn.escapeId(key)).join(', ')})`);
315315
}
316+
316317
fieldsToRemove.forEach((field) => operations.push(`DROP ${field.Field}`));
317318
fieldsToAdd.forEach((field) => operations.push(`ADD ${this.createSQLField(conn, field, fields[field])}`));
318319
fieldsToUpdate.forEach((field) => operations.push(`CHANGE ${conn.escapeId(field)} ${this.createSQLField(conn, field, fields[field])}`));
319-
uniqueKeysToRemove.forEach((field) => operations.push(`DROP INDEX ${field.Field}`));
320+
321+
const uniqueKeysToRemove = currentUniqueKeys.filter((key) => !newUniqueKeys.includes(key));
322+
const uniqueKeysToAdd = newUniqueKeys.filter((key) => !currentUniqueKeys.includes(key));
323+
324+
uniqueKeysToRemove.forEach((field) => operations.push(`DROP INDEX ${field}`));
320325
uniqueKeysToAdd.forEach((field) => operations.push(`ADD UNIQUE INDEX ${field} (${field})`));
326+
327+
const foreignKeysToRemove = currentForeignKeys.filter((key) => !newForeignKeys.includes(key));
328+
const foreignKeysToAdd = newForeignKeys.filter((key) => !currentForeignKeys.includes(key));
329+
const foreignKeysToUpdate = newForeignKeys.filter((key) => currentForeignKeys.includes(key) && (key !== (key.replace(/_fk$/, '')) + '_fk'));
330+
321331
[...foreignKeysToRemove, ...foreignKeysToUpdate].forEach((key) => operations.push(`DROP FOREIGN KEY ${conn.escapeId(key)}`));
322332
[...foreignKeysToAdd, ...foreignKeysToUpdate].forEach((key) => {
323-
let query = `ADD FOREIGN KEY (${conn.escapeId(key)}) REFERENCES ${conn.escapeId(fields[key].foreignKey!.table.getName())}(${conn.escapeId(fields[key].foreignKey!.field)})`;
324-
if (fields[key].foreignKey!.onDelete) query += ` ON DELETE ${fields[key].foreignKey!.onDelete!}`;
325-
if (fields[key].foreignKey!.onUpdate) query += ` ON UPDATE ${fields[key].foreignKey!.onUpdate!}`;
333+
const fieldKey = key.substring(0, key.length - 3);
334+
335+
let query = `ADD FOREIGN KEY ${key} (${conn.escapeId(fieldKey)}) REFERENCES ${
336+
conn.escapeId(fields[fieldKey].foreignKey!.table.getName())
337+
}(${conn.escapeId(fields[fieldKey].foreignKey!.field)})`;
338+
339+
if (fields[fieldKey].foreignKey!.onDelete) query += ` ON DELETE ${fields[fieldKey].foreignKey!.onDelete!}`;
340+
if (fields[fieldKey].foreignKey!.onUpdate) query += ` ON UPDATE ${fields[fieldKey].foreignKey!.onUpdate!}`;
326341
operations.push(query);
327342
});
328343
if (operations.length > 0) await conn.query(`ALTER TABLE ${conn.escapeId(tableName)} ${operations.join(', ')}`);
Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,41 @@
11
import { EDatabaseTypes } from './EDatabaseTypes';
22
import { BaseModel } from '../../models';
33

4-
export interface IDatabaseField {
5-
type: EDatabaseTypes;
6-
maxSize?: number;
7-
nullable: boolean;
8-
minSize?: number;
9-
unique?: boolean;
10-
default?: any;
11-
autoIncrement?: boolean;
12-
primaryKey?: boolean;
13-
foreignKey?: {
14-
table: BaseModel,
15-
field: string,
16-
onDelete?: 'CASCADE' | 'RESTRICT' | 'NO ACTION' | 'SET NULL',
17-
onUpdate?: 'CASCADE' | 'RESTRICT' | 'NO ACTION' | 'SET NULL',
18-
};
19-
}
4+
type ForeignKeyOptions = {
5+
table: BaseModel,
6+
field: string,
7+
onDelete?: 'CASCADE' | 'RESTRICT' | 'NO ACTION' | 'SET NULL',
8+
onUpdate?: 'CASCADE' | 'RESTRICT' | 'NO ACTION' | 'SET NULL',
9+
};
10+
11+
export type IDatabaseField =
12+
| {
13+
type: EDatabaseTypes.BOOLEAN;
14+
nullable: boolean;
15+
default?: any;
16+
autoIncrement?: boolean;
17+
primaryKey?: boolean;
18+
foreignKey?: ForeignKeyOptions;
19+
}
20+
| {
21+
type: EDatabaseTypes.TIMESTAMP;
22+
maxSize?: number;
23+
minSize?: number;
24+
nullable: boolean;
25+
unique?: boolean;
26+
default?: any;
27+
autoIncrement?: boolean;
28+
primaryKey?: boolean;
29+
foreignKey?: ForeignKeyOptions;
30+
}
31+
| {
32+
type: Exclude<Exclude<EDatabaseTypes, EDatabaseTypes.BOOLEAN>, EDatabaseTypes.TIMESTAMP>;
33+
maxSize: number;
34+
minSize?: number;
35+
nullable: boolean;
36+
unique?: boolean;
37+
default?: any;
38+
autoIncrement?: boolean;
39+
primaryKey?: boolean;
40+
foreignKey?: ForeignKeyOptions;
41+
};

src/main/models/BaseModel.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ export class BaseModel {
2626
Object.keys(fields).forEach((fieldKey) => {
2727
if (fields[fieldKey].foreignKey) {
2828
if (fields[fieldKey].foreignKey!.table === this) throw new DatabaseException(`${fieldKey} foreign key's table references the model itself (Circular Model dependence).`);
29-
if (!fields[fieldKey].foreignKey!.table.fields[fields[fieldKey].foreignKey!.field])
29+
const field = fields[fieldKey].foreignKey!.table.fields[fields[fieldKey].foreignKey!.field];
30+
if (!field)
3031
throw new DatabaseException(`Field ${fieldKey} has a foreign key with a field that doesn't exists on the table.`);
31-
if (fields[fieldKey].foreignKey!.table.fields[fields[fieldKey].foreignKey!.field].type !== fields[fieldKey].type)
32+
if (field.type !== fields[fieldKey].type)
3233
throw new DatabaseException(`Foreign key field ${fieldKey} has a different type than the one referenced.`);
33-
if (fields[fieldKey].foreignKey!.table.fields[fields[fieldKey].foreignKey!.field].maxSize !== fields[fieldKey].maxSize)
34+
if ((field.type !== EDatabaseTypes.BOOLEAN && field.maxSize) !== (fields[fieldKey].type !== EDatabaseTypes.BOOLEAN && fields[fieldKey].maxSize))
3435
throw new DatabaseException(`Foreign key field ${fieldKey} has a different maxSize than the one referenced.`);
35-
if (fields[fieldKey].foreignKey!.table.fields[fields[fieldKey].foreignKey!.field].minSize !== fields[fieldKey].minSize)
36+
if ((field.type !== EDatabaseTypes.BOOLEAN && field.minSize) !== (fields[fieldKey].type !== EDatabaseTypes.BOOLEAN && fields[fieldKey].minSize))
3637
throw new DatabaseException(`Foreign key field ${fieldKey} has a different minSize than the one referenced.`);
3738
}
3839
});

0 commit comments

Comments
 (0)