@entity-access/entity-access 1.0.1
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/.eslintrc.cjs +234 -0
- package/.github/workflows/node.yml +44 -0
- package/.github/workflows/npm-publish.yml +33 -0
- package/.vscode/launch.json +20 -0
- package/.vscode/settings.json +39 -0
- package/LICENSE +201 -0
- package/README.md +247 -0
- package/package.json +39 -0
- package/src/TestRunner.ts +2 -0
- package/src/common/cache/InstanceCache.ts +14 -0
- package/src/common/cache/TimedCache.ts +74 -0
- package/src/common/symbols/symbols.ts +2 -0
- package/src/common/usingAsync.ts +24 -0
- package/src/compiler/ISqlHelpers.ts +30 -0
- package/src/compiler/QueryCompiler.ts +88 -0
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +83 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +83 -0
- package/src/decorators/Column.ts +46 -0
- package/src/decorators/ForeignKey.ts +42 -0
- package/src/decorators/IClassOf.ts +1 -0
- package/src/decorators/IColumn.ts +70 -0
- package/src/decorators/ISqlType.ts +70 -0
- package/src/decorators/SchemaRegistry.ts +19 -0
- package/src/decorators/Table.ts +18 -0
- package/src/decorators/parser/MemberParser.ts +8 -0
- package/src/drivers/base/BaseDriver.ts +134 -0
- package/src/drivers/postgres/PostgreSqlDriver.ts +178 -0
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +78 -0
- package/src/drivers/sql-server/SqlServerDriver.ts +215 -0
- package/src/drivers/sql-server/SqlServerLiteral.ts +12 -0
- package/src/drivers/sql-server/SqlServerQueryCompiler.ts +25 -0
- package/src/entity-query/EntityType.ts +116 -0
- package/src/migrations/Migrations.ts +17 -0
- package/src/migrations/postgres/PostgresAutomaticMigrations.ts +97 -0
- package/src/migrations/postgres/PostgresMigrations.ts +56 -0
- package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +102 -0
- package/src/migrations/sql-server/SqlServerMigrations.ts +60 -0
- package/src/model/ChangeEntry.ts +154 -0
- package/src/model/ChangeSet.ts +88 -0
- package/src/model/EntityContext.ts +48 -0
- package/src/model/EntityModel.ts +27 -0
- package/src/model/EntityQuery.ts +152 -0
- package/src/model/EntitySchema.ts +21 -0
- package/src/model/EntitySource.ts +98 -0
- package/src/model/IFilterWithParameter.ts +38 -0
- package/src/model/IdentityService.ts +23 -0
- package/src/model/SourceExpression.ts +137 -0
- package/src/query/Query.ts +158 -0
- package/src/query/ast/ExpressionToSql.ts +348 -0
- package/src/query/ast/Expressions.ts +294 -0
- package/src/query/ast/IStringTransformer.ts +74 -0
- package/src/query/ast/SqlLiteral.ts +25 -0
- package/src/query/ast/Visitor.ts +159 -0
- package/src/query/parser/ArrowToExpression.ts +160 -0
- package/src/query/parser/BabelVisitor.ts +86 -0
- package/src/sql/ISql.ts +31 -0
- package/src/sql/Sql.ts +4 -0
- package/src/tests/TestConfig.ts +6 -0
- package/src/tests/db-tests/tests/select-items.ts +37 -0
- package/src/tests/db-tests/tests/update-items.ts +17 -0
- package/src/tests/drivers/postgres/connection-test.ts +29 -0
- package/src/tests/drivers/sql-server/sql-server-test.ts +9 -0
- package/src/tests/expressions/left-joins/child-joins.ts +71 -0
- package/src/tests/expressions/simple/parse-arrow.ts +46 -0
- package/src/tests/expressions/trimInternal.ts +23 -0
- package/src/tests/model/ShoppingContext.ts +203 -0
- package/src/tests/model/createContext.ts +294 -0
- package/src/tests/query/combine.ts +28 -0
- package/test.js +107 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
3
|
+
import Migrations from "../../migrations/Migrations.js";
|
|
4
|
+
import { BaseDriver, IDbConnectionString, IDbReader, IQuery, IRecord, disposableSymbol, toQuery } from "../base/BaseDriver.js";
|
|
5
|
+
import sql from "mssql";
|
|
6
|
+
import SqlServerQueryCompiler from "./SqlServerQueryCompiler.js";
|
|
7
|
+
import SqlServerSqlMethodTransformer from "../../compiler/sql-server/SqlServerSqlMethodTransformer.js";
|
|
8
|
+
import SqlServerAutomaticMigrations from "../../migrations/sql-server/SqlServerAutomaticMigrations.js";
|
|
9
|
+
import { SqlServerLiteral } from "./SqlServerLiteral.js";
|
|
10
|
+
import usingAsync from "../../common/usingAsync.js";
|
|
11
|
+
import TimedCache from "../../common/cache/TimedCache.js";
|
|
12
|
+
|
|
13
|
+
export type ISqlServerConnectionString = sql.config;
|
|
14
|
+
|
|
15
|
+
const namedPool = new TimedCache<string, sql.ConnectionPool>();
|
|
16
|
+
|
|
17
|
+
export default class SqlServerDriver extends BaseDriver {
|
|
18
|
+
|
|
19
|
+
get compiler(): QueryCompiler {
|
|
20
|
+
return this.sqlQueryCompiler;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private sqlQueryCompiler = new SqlServerQueryCompiler();
|
|
24
|
+
private transaction: sql.Transaction;
|
|
25
|
+
|
|
26
|
+
constructor(private readonly config: ISqlServerConnectionString) {
|
|
27
|
+
super({
|
|
28
|
+
database: config.database,
|
|
29
|
+
host: config.server ??= (config as any).host,
|
|
30
|
+
port: config.port,
|
|
31
|
+
password: config.password,
|
|
32
|
+
user: config.user,
|
|
33
|
+
... config,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader> {
|
|
38
|
+
command = toQuery(command);
|
|
39
|
+
let rq = await this.newRequest();
|
|
40
|
+
|
|
41
|
+
if (command) {
|
|
42
|
+
let id = 1;
|
|
43
|
+
for (const iterator of command.values) {
|
|
44
|
+
command.text = command.text.replace(/\$/, "@p");
|
|
45
|
+
rq = rq.input("p" + id++, iterator);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new SqlReader(rq, command);
|
|
50
|
+
}
|
|
51
|
+
public async executeQuery(command: IQuery, signal?: AbortSignal): Promise<any> {
|
|
52
|
+
let rq = await this.newRequest();
|
|
53
|
+
command = toQuery(command);
|
|
54
|
+
|
|
55
|
+
if (command) {
|
|
56
|
+
let id = 1;
|
|
57
|
+
for (const iterator of command.values) {
|
|
58
|
+
command.text = command.text.replace(/\$/, "@p");
|
|
59
|
+
rq = rq.input(`p${id++}`, iterator);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
console.log(command.text);
|
|
65
|
+
const r = await rq.query(command.text);
|
|
66
|
+
return { rows: r.recordset ?? [r.output], updated: r.rowsAffected [0]};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
error = `Failed executing ${command.text}\r\n${error.stack ?? error}`;
|
|
69
|
+
console.error(error);
|
|
70
|
+
throw new Error(error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public ensureDatabase() {
|
|
75
|
+
const create = async () => {
|
|
76
|
+
const defaultDb = "master";
|
|
77
|
+
|
|
78
|
+
const db = this.config.database;
|
|
79
|
+
this.config.database = defaultDb;
|
|
80
|
+
|
|
81
|
+
const connection = await this.newRequest();
|
|
82
|
+
// @ts-expect-error readonly
|
|
83
|
+
this.config = { ... this.config };
|
|
84
|
+
this.config.database = db;
|
|
85
|
+
|
|
86
|
+
const createSql = `IF NOT EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = ${SqlServerLiteral.escapeLiteral(db)}) BEGIN
|
|
87
|
+
CREATE DATABASE ${db};
|
|
88
|
+
END`;
|
|
89
|
+
try {
|
|
90
|
+
await connection.query(createSql);
|
|
91
|
+
} catch(error) {
|
|
92
|
+
throw new Error(`Failed executing: ${createSql}\r\n${error.stack ?? error}`);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const value = create();
|
|
96
|
+
Object.defineProperty(this, "ensureDatabase", {
|
|
97
|
+
value: () => value,
|
|
98
|
+
});
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public async runInTransaction<T = any>(fx?: () => Promise<T>): Promise<T> {
|
|
103
|
+
this.transaction = new sql.Transaction(await this.newConnection());
|
|
104
|
+
let rolledBack = false;
|
|
105
|
+
try {
|
|
106
|
+
this.transaction.on("rollback", (aborted) => rolledBack = aborted);
|
|
107
|
+
await this.transaction.begin();
|
|
108
|
+
const r = await fx();
|
|
109
|
+
await this.transaction.commit();
|
|
110
|
+
return r;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!rolledBack) {
|
|
113
|
+
try {
|
|
114
|
+
await this.transaction.rollback();
|
|
115
|
+
} catch {
|
|
116
|
+
// rolledBack isn't true sometimes...
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error(error.stack ?? error);
|
|
120
|
+
} finally {
|
|
121
|
+
this.transaction = void 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public automaticMigrations(): Migrations {
|
|
126
|
+
return new SqlServerAutomaticMigrations();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected async newRequest() {
|
|
130
|
+
|
|
131
|
+
if (this.transaction) {
|
|
132
|
+
return this.transaction.request();
|
|
133
|
+
}
|
|
134
|
+
return (await this.newConnection()).request();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private newConnection() {
|
|
138
|
+
const key = this.config.server + "//" + this.config.database + "/" + this.config.user;
|
|
139
|
+
return namedPool.getOrCreateAsync(this.config.server + "://" + this.config.database,
|
|
140
|
+
() => {
|
|
141
|
+
const pool = new sql.ConnectionPool(this.config);
|
|
142
|
+
const oldClose = pool.close;
|
|
143
|
+
pool.close = ((c) => {
|
|
144
|
+
namedPool.delete(key);
|
|
145
|
+
return oldClose.call(pool, c);
|
|
146
|
+
}) as any;
|
|
147
|
+
return pool.connect();
|
|
148
|
+
}, 15000, (x) => x.close());
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class SqlReader implements IDbReader {
|
|
155
|
+
|
|
156
|
+
private pending: any[] = [];
|
|
157
|
+
private error: any = null;
|
|
158
|
+
private count: number = 0;
|
|
159
|
+
private ended = false;
|
|
160
|
+
private processPendingRows: (... a: any[]) => any;
|
|
161
|
+
|
|
162
|
+
constructor(
|
|
163
|
+
private rq: sql.Request,
|
|
164
|
+
private command: { text: string, values?: any[]}) {}
|
|
165
|
+
|
|
166
|
+
async *next(min?: number, s?: AbortSignal) {
|
|
167
|
+
const command = this.command;
|
|
168
|
+
const rq = this.rq;
|
|
169
|
+
s?.addEventListener("abort", () => {
|
|
170
|
+
rq.cancel();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
rq.stream = true;
|
|
174
|
+
|
|
175
|
+
rq.on("row", (row) => {
|
|
176
|
+
this.pending.push(row);
|
|
177
|
+
this.count++;
|
|
178
|
+
this.processPendingRows();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
rq.on("error", (e) => {
|
|
182
|
+
this.error = new Error(`Failed executing ${command.text}\r\n${e.stack ?? e}`);
|
|
183
|
+
this.processPendingRows();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
rq.on("done", () => {
|
|
187
|
+
this.ended = true;
|
|
188
|
+
this.processPendingRows();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(`Executing ${(command as any).text}`);
|
|
192
|
+
void rq.query((command as any).text);
|
|
193
|
+
|
|
194
|
+
do {
|
|
195
|
+
if (this.pending.length > 0){
|
|
196
|
+
const copy = this.pending;
|
|
197
|
+
this.pending = [];
|
|
198
|
+
yield *copy;
|
|
199
|
+
}
|
|
200
|
+
if (this.ended) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
await new Promise<any>((resolve, reject) => {
|
|
204
|
+
this.processPendingRows = resolve;
|
|
205
|
+
});
|
|
206
|
+
} while(true);
|
|
207
|
+
}
|
|
208
|
+
dispose(): Promise<any> {
|
|
209
|
+
return Promise.resolve();
|
|
210
|
+
}
|
|
211
|
+
[disposableSymbol]?(): void {
|
|
212
|
+
this.dispose()?.catch((error) => console.error(error));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const escapeLiteral = (name: string) => {
|
|
2
|
+
name = JSON.stringify(name).replace(/\'/g, "\\'");
|
|
3
|
+
return `N'${name.substring(1, name.length - 1)}'`;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const quotedLiteral = (name: string) => `[${name}]`;
|
|
7
|
+
|
|
8
|
+
export const SqlServerLiteral = {
|
|
9
|
+
|
|
10
|
+
escapeLiteral,
|
|
11
|
+
quotedLiteral
|
|
12
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
2
|
+
import SqlServerSqlMethodTransformer from "../../compiler/sql-server/SqlServerSqlMethodTransformer.js";
|
|
3
|
+
import ArrowToExpression from "../../query/parser/ArrowToExpression.js";
|
|
4
|
+
import ExpressionToSqlServer from "./ExpressionToSqlServer.js";
|
|
5
|
+
import { SqlServerLiteral } from "./SqlServerLiteral.js";
|
|
6
|
+
|
|
7
|
+
export default class SqlServerQueryCompiler extends QueryCompiler {
|
|
8
|
+
|
|
9
|
+
constructor({
|
|
10
|
+
arrowToExpression = ArrowToExpression,
|
|
11
|
+
expressionToSql = ExpressionToSqlServer,
|
|
12
|
+
quotedLiteral = SqlServerLiteral.quotedLiteral,
|
|
13
|
+
escapeLiteral = SqlServerLiteral.escapeLiteral,
|
|
14
|
+
sqlMethodTransformer = SqlServerSqlMethodTransformer
|
|
15
|
+
}: Partial<QueryCompiler> = {}) {
|
|
16
|
+
super({
|
|
17
|
+
arrowToExpression,
|
|
18
|
+
expressionToSql,
|
|
19
|
+
quotedLiteral,
|
|
20
|
+
escapeLiteral,
|
|
21
|
+
sqlMethodTransformer
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { IColumn, IEntityRelation } from "../decorators/IColumn.js";
|
|
2
|
+
import { IClassOf } from "../decorators/IClassOf.js";
|
|
3
|
+
import { Query } from "../query/Query.js";
|
|
4
|
+
import NameParser from "../decorators/parser/MemberParser.js";
|
|
5
|
+
import SchemaRegistry from "../decorators/SchemaRegistry.js";
|
|
6
|
+
import { QuotedLiteral, TableLiteral } from "../query/ast/Expressions.js";
|
|
7
|
+
import InstanceCache from "../common/cache/InstanceCache.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DbQuery represents sql equivalent table with columns...
|
|
12
|
+
*/
|
|
13
|
+
export default class EntityType {
|
|
14
|
+
|
|
15
|
+
public readonly typeClass: IClassOf<any>;
|
|
16
|
+
|
|
17
|
+
public readonly name: string;
|
|
18
|
+
public readonly schema: string;
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
public readonly columns: IColumn[] = [];
|
|
22
|
+
|
|
23
|
+
public readonly relations: IEntityRelation[] = [];
|
|
24
|
+
|
|
25
|
+
public readonly keys: IColumn[] = [];
|
|
26
|
+
|
|
27
|
+
public readonly nonKeys: IColumn[] = [];
|
|
28
|
+
|
|
29
|
+
@InstanceCache
|
|
30
|
+
public get fullyQualifiedName() {
|
|
31
|
+
return this.schema
|
|
32
|
+
? TableLiteral.create({
|
|
33
|
+
schema: QuotedLiteral.create({literal: this.schema}) ,
|
|
34
|
+
name: QuotedLiteral.create({ literal: this.name })
|
|
35
|
+
})
|
|
36
|
+
: QuotedLiteral.create({ literal: this.name });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private fieldMap: Map<string, IColumn> = new Map();
|
|
40
|
+
private columnMap: Map<string, IColumn> = new Map();
|
|
41
|
+
private relationMap: Map<string, IEntityRelation> = new Map();
|
|
42
|
+
|
|
43
|
+
public getProperty(name: string) {
|
|
44
|
+
const field = this.fieldMap.get(name);
|
|
45
|
+
const relation = this.relationMap.get(name);
|
|
46
|
+
return { field, relation };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public addColumn(c: IColumn) {
|
|
50
|
+
|
|
51
|
+
const existing = this.fieldMap.get(c.name);
|
|
52
|
+
if (existing) {
|
|
53
|
+
c.fkRelation = existing.fkRelation;
|
|
54
|
+
c.fkRelation.fkColumn = c;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.fieldMap.set(c.name, c);
|
|
58
|
+
this.columnMap.set(c.columnName, c);
|
|
59
|
+
this.columns.push(c);
|
|
60
|
+
if (c.key) {
|
|
61
|
+
this.keys.push(c);
|
|
62
|
+
} else {
|
|
63
|
+
this.nonKeys.push(c);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getColumn(name: string) {
|
|
68
|
+
return this.columnMap.get(name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public getField(name: string) {
|
|
72
|
+
return this.fieldMap.get(name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addRelation(relation: IEntityRelation) {
|
|
76
|
+
// we will also set fk to the corresponding column
|
|
77
|
+
this.relations.push(relation);
|
|
78
|
+
this.relationMap.set(relation.name, relation);
|
|
79
|
+
|
|
80
|
+
// find fk...
|
|
81
|
+
let fkColumn = this.fieldMap.get(relation.foreignKey);
|
|
82
|
+
if(!fkColumn) {
|
|
83
|
+
fkColumn = {
|
|
84
|
+
name: relation.foreignKey,
|
|
85
|
+
fkRelation: relation,
|
|
86
|
+
dataType: "BigInt"
|
|
87
|
+
};
|
|
88
|
+
this.fieldMap.set(relation.foreignKey, fkColumn);
|
|
89
|
+
}
|
|
90
|
+
fkColumn.fkRelation = relation;
|
|
91
|
+
relation.fkColumn = fkColumn;
|
|
92
|
+
if (fkColumn.dataType === "Double") {
|
|
93
|
+
fkColumn.dataType = "BigInt";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// let us set inverse relations...
|
|
97
|
+
const relatedType = SchemaRegistry.model(relation.relatedTypeClass);
|
|
98
|
+
relation.relatedEntity = relatedType;
|
|
99
|
+
const inverseRelation: IEntityRelation = {
|
|
100
|
+
name: relation.relatedName,
|
|
101
|
+
foreignKey: "",
|
|
102
|
+
relatedName: relation.name,
|
|
103
|
+
relatedTypeClass: this.typeClass,
|
|
104
|
+
dotNotCreateIndex: true,
|
|
105
|
+
fkColumn,
|
|
106
|
+
isCollection: true,
|
|
107
|
+
relatedRelation: relation,
|
|
108
|
+
relatedEntity: this
|
|
109
|
+
};
|
|
110
|
+
relatedType.relationMap.set(inverseRelation.name, inverseRelation);
|
|
111
|
+
relatedType.relations.push(inverseRelation);
|
|
112
|
+
inverseRelation.relatedRelation = relation;
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import SchemaRegistry from "../decorators/SchemaRegistry.js";
|
|
2
|
+
import EntityType from "../entity-query/EntityType.js";
|
|
3
|
+
import type EntityContext from "../model/EntityContext.js";
|
|
4
|
+
|
|
5
|
+
export default abstract class Migrations {
|
|
6
|
+
|
|
7
|
+
public async migrate(context: EntityContext) {
|
|
8
|
+
for (const iterator of context.model.entities.keys()) {
|
|
9
|
+
const type = SchemaRegistry.model(iterator);
|
|
10
|
+
await this.migrateTable(context, type);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
abstract migrateTable(context: EntityContext, type: EntityType): Promise<any>;
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
2
|
+
import { BaseDriver } from "../../drivers/base/BaseDriver.js";
|
|
3
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
4
|
+
import EntityContext from "../../model/EntityContext.js";
|
|
5
|
+
import Migrations from "../Migrations.js";
|
|
6
|
+
import PostgresMigrations from "./PostgresMigrations.js";
|
|
7
|
+
|
|
8
|
+
export default class PostgresAutomaticMigrations extends PostgresMigrations {
|
|
9
|
+
|
|
10
|
+
async migrateTable(context: EntityContext, type: EntityType) {
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// create table if not exists...
|
|
14
|
+
const nonKeyColumns = type.nonKeys;
|
|
15
|
+
const keys = type.keys;
|
|
16
|
+
|
|
17
|
+
const driver = context.driver;
|
|
18
|
+
|
|
19
|
+
await this.createTable(driver, type, keys);
|
|
20
|
+
|
|
21
|
+
await this.createColumns(driver, type, nonKeyColumns);
|
|
22
|
+
|
|
23
|
+
await this.createIndexes(driver, type, nonKeyColumns.filter((x) => x.fkRelation && !x.fkRelation?.dotNotCreateIndex));
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async createIndexes(driver: BaseDriver, type: EntityType, fkColumns: IColumn[]) {
|
|
28
|
+
|
|
29
|
+
const name = type.schema
|
|
30
|
+
? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
|
|
31
|
+
: JSON.stringify(type.name);
|
|
32
|
+
|
|
33
|
+
for (const iterator of fkColumns) {
|
|
34
|
+
const indexName = JSON.stringify(`IX_${type.name}_${iterator.columnName}`);
|
|
35
|
+
const columnName = JSON.stringify(iterator.columnName);
|
|
36
|
+
let query = `CREATE INDEX ${indexName} ON ${name} ( ${columnName})`;
|
|
37
|
+
if (iterator.nullable !== true) {
|
|
38
|
+
query += ` WHERE (${columnName} is not null)`;
|
|
39
|
+
}
|
|
40
|
+
await driver.executeQuery(query);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createColumns(driver: BaseDriver, type: EntityType, nonKeyColumns: IColumn[]) {
|
|
45
|
+
|
|
46
|
+
const name = type.schema
|
|
47
|
+
? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
|
|
48
|
+
: JSON.stringify(type.name);
|
|
49
|
+
|
|
50
|
+
if (nonKeyColumns.length > 1) {
|
|
51
|
+
nonKeyColumns.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const iterator of nonKeyColumns) {
|
|
55
|
+
const columnName = JSON.stringify(iterator.columnName);
|
|
56
|
+
let def = `ALTER TABLE ${name} ADD COLUMN IF NOT EXISTS ${columnName} `;
|
|
57
|
+
def += this.getColumnDefinition(iterator);
|
|
58
|
+
if (iterator.nullable !== true) {
|
|
59
|
+
def += " NOT NULL ";
|
|
60
|
+
}
|
|
61
|
+
if (typeof iterator.default === "string") {
|
|
62
|
+
def += " DEFAULT " + iterator.default;
|
|
63
|
+
}
|
|
64
|
+
await driver.executeQuery(def + ";");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async createTable(driver: BaseDriver, type: EntityType, keys: IColumn[]) {
|
|
70
|
+
|
|
71
|
+
const name = type.schema
|
|
72
|
+
? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
|
|
73
|
+
: JSON.stringify(type.name);
|
|
74
|
+
|
|
75
|
+
if (keys.length > 1) {
|
|
76
|
+
keys.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fields = [];
|
|
80
|
+
|
|
81
|
+
for (const iterator of keys) {
|
|
82
|
+
let def = JSON.stringify(iterator.columnName) + " ";
|
|
83
|
+
if (iterator.autoGenerate) {
|
|
84
|
+
def += iterator.dataType === "BigInt" ? "bigserial " : "serial ";
|
|
85
|
+
} else {
|
|
86
|
+
def += this.getColumnDefinition(iterator);
|
|
87
|
+
}
|
|
88
|
+
def += " not null primary key\r\n\t";
|
|
89
|
+
fields.push(def);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await driver.executeQuery(`CREATE TABLE IF NOT EXISTS ${name} (${fields.join(",")})`);
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
2
|
+
import Migrations from "../Migrations.js";
|
|
3
|
+
|
|
4
|
+
export default abstract class PostgresMigrations extends Migrations {
|
|
5
|
+
|
|
6
|
+
protected getColumnDefinition(iterator: IColumn) {
|
|
7
|
+
if (iterator.dataType === "Decimal") {
|
|
8
|
+
if (iterator.precision && iterator.scale) {
|
|
9
|
+
return `decimal (${iterator.precision}, ${iterator.scale})`;
|
|
10
|
+
}
|
|
11
|
+
return `decimal (18,2)`;
|
|
12
|
+
}
|
|
13
|
+
const type = this.getColumnType(iterator);
|
|
14
|
+
if (iterator.length) {
|
|
15
|
+
return `${type} (${iterator.length})`;
|
|
16
|
+
}
|
|
17
|
+
return type;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected getColumnType(iterator: IColumn) {
|
|
21
|
+
switch(iterator.dataType) {
|
|
22
|
+
case "BigInt":
|
|
23
|
+
return "bigint";
|
|
24
|
+
case "AsciiChar":
|
|
25
|
+
case "Char":
|
|
26
|
+
if (!iterator.length) {
|
|
27
|
+
return "text";
|
|
28
|
+
}
|
|
29
|
+
return "varchar";
|
|
30
|
+
case "DateTime":
|
|
31
|
+
return "timestamp";
|
|
32
|
+
case "DateTimeOffset":
|
|
33
|
+
return "timestamp with time zone";
|
|
34
|
+
case "Double":
|
|
35
|
+
return "float8";
|
|
36
|
+
case "Float":
|
|
37
|
+
return "real";
|
|
38
|
+
case "Int":
|
|
39
|
+
return "integer";
|
|
40
|
+
case "Boolean":
|
|
41
|
+
return "boolean";
|
|
42
|
+
case "ByteArray":
|
|
43
|
+
return "bytea";
|
|
44
|
+
case "Decimal":
|
|
45
|
+
return "decimal";
|
|
46
|
+
case "JSON":
|
|
47
|
+
return "json";
|
|
48
|
+
case "JSONB":
|
|
49
|
+
return "jsonb";
|
|
50
|
+
case "UUID":
|
|
51
|
+
return "uuid";
|
|
52
|
+
}
|
|
53
|
+
const a: never = iterator.dataType;
|
|
54
|
+
throw new Error("Not Defined");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { IColumn } from "../../decorators/IColumn.js";
|
|
2
|
+
import { BaseDriver } from "../../drivers/base/BaseDriver.js";
|
|
3
|
+
import { SqlServerLiteral } from "../../drivers/sql-server/SqlServerLiteral.js";
|
|
4
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
5
|
+
import EntityContext from "../../model/EntityContext.js";
|
|
6
|
+
import Migrations from "../Migrations.js";
|
|
7
|
+
import SqlServerMigrations from "./SqlServerMigrations.js";
|
|
8
|
+
|
|
9
|
+
export default class SqlServerAutomaticMigrations extends SqlServerMigrations {
|
|
10
|
+
|
|
11
|
+
async migrateTable(context: EntityContext, type: EntityType) {
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// create table if not exists...
|
|
15
|
+
const nonKeyColumns = type.nonKeys;
|
|
16
|
+
const keys = type.keys;
|
|
17
|
+
|
|
18
|
+
const driver = context.driver;
|
|
19
|
+
|
|
20
|
+
await this.createTable(driver, type, keys);
|
|
21
|
+
|
|
22
|
+
await this.createColumns(driver, type, nonKeyColumns);
|
|
23
|
+
|
|
24
|
+
await this.createIndexes(driver, type, nonKeyColumns.filter((x) => x.fkRelation && !x.fkRelation?.dotNotCreateIndex));
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async createIndexes(driver: BaseDriver, type: EntityType, fkColumns: IColumn[]) {
|
|
29
|
+
|
|
30
|
+
const name = type.schema
|
|
31
|
+
? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
|
|
32
|
+
: SqlServerLiteral.quotedLiteral(type.name);
|
|
33
|
+
|
|
34
|
+
for (const iterator of fkColumns) {
|
|
35
|
+
const indexName = SqlServerLiteral.quotedLiteral(`IX_${type.name}_${iterator.columnName}`);
|
|
36
|
+
const columnName = SqlServerLiteral.quotedLiteral(iterator.columnName);
|
|
37
|
+
let query = `CREATE INDEX ${indexName} ON ${name} ( ${columnName})`;
|
|
38
|
+
if (iterator.nullable !== true) {
|
|
39
|
+
query += ` WHERE (${columnName} is not null)`;
|
|
40
|
+
}
|
|
41
|
+
await driver.executeQuery(query);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async createColumns(driver: BaseDriver, type: EntityType, nonKeyColumns: IColumn[]) {
|
|
46
|
+
|
|
47
|
+
const name = type.schema
|
|
48
|
+
? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
|
|
49
|
+
: SqlServerLiteral.quotedLiteral(type.name);
|
|
50
|
+
|
|
51
|
+
if (nonKeyColumns.length > 1) {
|
|
52
|
+
nonKeyColumns.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const iterator of nonKeyColumns) {
|
|
56
|
+
const columnName = SqlServerLiteral.quotedLiteral(iterator.columnName);
|
|
57
|
+
let def = `IF COL_LENGTH(${ SqlServerLiteral.escapeLiteral(name)}, ${ SqlServerLiteral.escapeLiteral(columnName)}) IS NULL ALTER TABLE ${name} ADD ${columnName} `;
|
|
58
|
+
def += this.getColumnDefinition(iterator);
|
|
59
|
+
if (iterator.nullable === true) {
|
|
60
|
+
def += " NULL ";
|
|
61
|
+
} else {
|
|
62
|
+
def += " NOT NULL ";
|
|
63
|
+
}
|
|
64
|
+
if (typeof iterator.default === "string") {
|
|
65
|
+
def += " DEFAULT " + iterator.default;
|
|
66
|
+
}
|
|
67
|
+
await driver.executeQuery(def + ";");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async createTable(driver: BaseDriver, type: EntityType, keys: IColumn[]) {
|
|
73
|
+
|
|
74
|
+
const name = type.schema
|
|
75
|
+
? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
|
|
76
|
+
: SqlServerLiteral.quotedLiteral(type.name);
|
|
77
|
+
|
|
78
|
+
if (keys.length > 1) {
|
|
79
|
+
keys.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const fields = [];
|
|
83
|
+
|
|
84
|
+
for (const iterator of keys) {
|
|
85
|
+
let def = SqlServerLiteral.quotedLiteral(iterator.columnName) + " ";
|
|
86
|
+
if (iterator.autoGenerate) {
|
|
87
|
+
def += this.getColumnDefinition(iterator) + " IDENTITY(1,1)";
|
|
88
|
+
} else {
|
|
89
|
+
def += this.getColumnDefinition(iterator);
|
|
90
|
+
}
|
|
91
|
+
def += " NOT NULL primary key\r\n\t";
|
|
92
|
+
fields.push(def);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await driver.executeQuery(`IF OBJECT_ID(${ SqlServerLiteral.escapeLiteral(name)}) IS NULL BEGIN
|
|
96
|
+
CREATE TABLE ${name} (${fields.join(",")});
|
|
97
|
+
END`);
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
}
|