@an5/orm 1.0.8 → 1.0.9
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 +167 -175
- package/dist/an5Metadata.js +64 -0
- package/dist/an5Orm.d.ts +92 -3
- package/dist/an5Orm.js +697 -206
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/generator/src/code-generator.d.ts +14 -13
- package/dist/generator/src/code-generator.js +249 -225
- package/dist/generator/src/dotnet-generator.d.ts +13 -11
- package/dist/generator/src/dotnet-generator.js +872 -546
- package/dist/generator/src/index.d.ts +1 -1
- package/dist/generator/src/index.js +104 -88
- package/dist/generator/src/metadata-generator.d.ts +11 -8
- package/dist/generator/src/metadata-generator.js +104 -90
- package/dist/generator/src/parser.d.ts +11 -11
- package/dist/generator/src/parser.js +206 -206
- package/dist/generator/src/python-generator.d.ts +19 -9
- package/dist/generator/src/python-generator.js +345 -105
- package/dist/generator/src/types.d.ts +27 -27
- package/dist/generator/src/types.js +2 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +19 -8
- package/dist/logger.js +1 -1
- package/dist/migration-core.d.ts +87 -0
- package/dist/migration-core.js +692 -0
- package/dist/scripts/release-cli.d.ts +2 -2
- package/dist/scripts/release-cli.js +115 -115
- package/dist/sql-utils.d.ts +3 -1
- package/dist/sql-utils.js +94 -24
- package/generator/src/code-generator.ts +259 -230
- package/generator/src/dotnet-generator.ts +882 -544
- package/generator/src/golang-generator.ts +1242 -0
- package/generator/src/index.ts +111 -116
- package/generator/src/metadata-generator.ts +116 -101
- package/generator/src/parser.ts +202 -202
- package/generator/src/python-generator.ts +424 -114
- package/generator/src/types.ts +29 -29
- package/package.json +12 -6
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
export {};
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
export {};
|
|
3
3
|
//# sourceMappingURL=release-cli.d.ts.map
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
#!/usr/bin/env tsx
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
function run(cmd, allowFail = false) {
|
|
11
|
-
try {
|
|
12
|
-
return (0, child_process_1.execSync)(cmd, { encoding: 'utf8' }).trim();
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
if (allowFail)
|
|
16
|
-
return error.stdout?.toString()?.trim() || '';
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function inferComponent(file) {
|
|
21
|
-
const normalized = file.replace('\\', '/').toLowerCase();
|
|
22
|
-
if (normalized.includes('/generator/'))
|
|
23
|
-
return 'generator';
|
|
24
|
-
if (normalized.includes('/an5client/'))
|
|
25
|
-
return 'client';
|
|
26
|
-
if (normalized.includes('/an5schema/'))
|
|
27
|
-
return 'schema';
|
|
28
|
-
if (normalized.includes('/.github/'))
|
|
29
|
-
return 'ci';
|
|
30
|
-
if (normalized.includes('package.json') || normalized.includes('tsconfig'))
|
|
31
|
-
return 'build';
|
|
32
|
-
if (normalized.includes('.md'))
|
|
33
|
-
return 'docs';
|
|
34
|
-
return 'misc';
|
|
35
|
-
}
|
|
36
|
-
function collectChanges() {
|
|
37
|
-
const status = run('git status --porcelain');
|
|
38
|
-
const files = status
|
|
39
|
-
.split('\n')
|
|
40
|
-
.map(l => l.trim())
|
|
41
|
-
.filter(Boolean)
|
|
42
|
-
.map(l => l.slice(3).trim())
|
|
43
|
-
.filter(Boolean);
|
|
44
|
-
const groups = new Map();
|
|
45
|
-
for (const file of files) {
|
|
46
|
-
const component = inferComponent(file);
|
|
47
|
-
if (!groups.has(component)) {
|
|
48
|
-
groups.set(component, { component, files: [], summary: '' });
|
|
49
|
-
}
|
|
50
|
-
groups.get(component).files.push(file);
|
|
51
|
-
}
|
|
52
|
-
return Array.from(groups.values()).map(group => ({
|
|
53
|
-
...group,
|
|
54
|
-
summary: `${group.component} updates`
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
function generateChangelog(groups) {
|
|
58
|
-
const lines = ['# Changelog', ''];
|
|
59
|
-
for (const group of groups) {
|
|
60
|
-
lines.push(`## ${group.component}`);
|
|
61
|
-
lines.push(`- ${group.summary}`);
|
|
62
|
-
for (const file of group.files) {
|
|
63
|
-
lines.push(` - ${file}`);
|
|
64
|
-
}
|
|
65
|
-
lines.push('');
|
|
66
|
-
}
|
|
67
|
-
return lines.join('\n').trim();
|
|
68
|
-
}
|
|
69
|
-
async function maybeUseLLM(summary) {
|
|
70
|
-
let apiKey = '';
|
|
71
|
-
try {
|
|
72
|
-
const configPath = require('path').join(__dirname, '..', '..', 'an5Adapters', 'typescript', 'config');
|
|
73
|
-
const mod = require(configPath);
|
|
74
|
-
if (mod?.getLlmConfig) {
|
|
75
|
-
const db = await mod.getLlmConfig();
|
|
76
|
-
if (db)
|
|
77
|
-
apiKey = db.apiKey;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
catch { }
|
|
81
|
-
if (!apiKey)
|
|
82
|
-
return summary;
|
|
83
|
-
try {
|
|
84
|
-
const prompt = `Summarize these changes into one concise release note in English:\n${summary}`;
|
|
85
|
-
const payload = JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a helpful release note writer.' }, { role: 'user', content: prompt }] });
|
|
86
|
-
const response = run(`curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer ${apiKey}" -H "Content-Type: application/json" -d '${payload}'`, true);
|
|
87
|
-
const parsed = JSON.parse(response);
|
|
88
|
-
return parsed.choices?.[0]?.message?.content?.trim() || summary;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return summary;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async function main() {
|
|
95
|
-
const groups = collectChanges();
|
|
96
|
-
if (groups.length === 0) {
|
|
97
|
-
console.log('No modified files detected.');
|
|
98
|
-
process.exit(0);
|
|
99
|
-
}
|
|
100
|
-
const changelog = generateChangelog(groups);
|
|
101
|
-
const outputPath = path_1.default.resolve(process.cwd(), 'CHANGELOG.md');
|
|
102
|
-
fs_1.default.writeFileSync(outputPath, changelog + '\n');
|
|
103
|
-
const summary = groups.map(g => `${g.component}: ${g.files.join(', ')}`).join('\n');
|
|
104
|
-
const llmSummary = await maybeUseLLM(summary);
|
|
105
|
-
console.log('\nGenerated changelog:\n');
|
|
106
|
-
console.log(changelog);
|
|
107
|
-
console.log('\nLLM summary:\n');
|
|
108
|
-
console.log(llmSummary);
|
|
109
|
-
const message = groups.map(g => g.component).join(', ');
|
|
110
|
-
const commitMessage = `chore: update ${message}`;
|
|
111
|
-
run(`git add -A`);
|
|
112
|
-
run(`git commit -m "${commitMessage}"`, true);
|
|
113
|
-
console.log(`\nCommitted with: ${commitMessage}`);
|
|
114
|
-
}
|
|
115
|
-
main().catch(console.error);
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
function run(cmd, allowFail = false) {
|
|
11
|
+
try {
|
|
12
|
+
return (0, child_process_1.execSync)(cmd, { encoding: 'utf8' }).trim();
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (allowFail)
|
|
16
|
+
return error.stdout?.toString()?.trim() || '';
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function inferComponent(file) {
|
|
21
|
+
const normalized = file.replace('\\', '/').toLowerCase();
|
|
22
|
+
if (normalized.includes('/generator/'))
|
|
23
|
+
return 'generator';
|
|
24
|
+
if (normalized.includes('/an5client/'))
|
|
25
|
+
return 'client';
|
|
26
|
+
if (normalized.includes('/an5schema/'))
|
|
27
|
+
return 'schema';
|
|
28
|
+
if (normalized.includes('/.github/'))
|
|
29
|
+
return 'ci';
|
|
30
|
+
if (normalized.includes('package.json') || normalized.includes('tsconfig'))
|
|
31
|
+
return 'build';
|
|
32
|
+
if (normalized.includes('.md'))
|
|
33
|
+
return 'docs';
|
|
34
|
+
return 'misc';
|
|
35
|
+
}
|
|
36
|
+
function collectChanges() {
|
|
37
|
+
const status = run('git status --porcelain');
|
|
38
|
+
const files = status
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map(l => l.trim())
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.map(l => l.slice(3).trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
const groups = new Map();
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const component = inferComponent(file);
|
|
47
|
+
if (!groups.has(component)) {
|
|
48
|
+
groups.set(component, { component, files: [], summary: '' });
|
|
49
|
+
}
|
|
50
|
+
groups.get(component).files.push(file);
|
|
51
|
+
}
|
|
52
|
+
return Array.from(groups.values()).map(group => ({
|
|
53
|
+
...group,
|
|
54
|
+
summary: `${group.component} updates`
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
function generateChangelog(groups) {
|
|
58
|
+
const lines = ['# Changelog', ''];
|
|
59
|
+
for (const group of groups) {
|
|
60
|
+
lines.push(`## ${group.component}`);
|
|
61
|
+
lines.push(`- ${group.summary}`);
|
|
62
|
+
for (const file of group.files) {
|
|
63
|
+
lines.push(` - ${file}`);
|
|
64
|
+
}
|
|
65
|
+
lines.push('');
|
|
66
|
+
}
|
|
67
|
+
return lines.join('\n').trim();
|
|
68
|
+
}
|
|
69
|
+
async function maybeUseLLM(summary) {
|
|
70
|
+
let apiKey = '';
|
|
71
|
+
try {
|
|
72
|
+
const configPath = require('path').join(__dirname, '..', '..', 'an5Adapters', 'typescript', 'config');
|
|
73
|
+
const mod = require(configPath);
|
|
74
|
+
if (mod?.getLlmConfig) {
|
|
75
|
+
const db = await mod.getLlmConfig();
|
|
76
|
+
if (db)
|
|
77
|
+
apiKey = db.apiKey;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
if (!apiKey)
|
|
82
|
+
return summary;
|
|
83
|
+
try {
|
|
84
|
+
const prompt = `Summarize these changes into one concise release note in English:\n${summary}`;
|
|
85
|
+
const payload = JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a helpful release note writer.' }, { role: 'user', content: prompt }] });
|
|
86
|
+
const response = run(`curl -s https://api.openai.com/v1/chat/completions -H "Authorization: Bearer ${apiKey}" -H "Content-Type: application/json" -d '${payload}'`, true);
|
|
87
|
+
const parsed = JSON.parse(response);
|
|
88
|
+
return parsed.choices?.[0]?.message?.content?.trim() || summary;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return summary;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function main() {
|
|
95
|
+
const groups = collectChanges();
|
|
96
|
+
if (groups.length === 0) {
|
|
97
|
+
console.log('No modified files detected.');
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
const changelog = generateChangelog(groups);
|
|
101
|
+
const outputPath = path_1.default.resolve(process.cwd(), 'CHANGELOG.md');
|
|
102
|
+
fs_1.default.writeFileSync(outputPath, changelog + '\n');
|
|
103
|
+
const summary = groups.map(g => `${g.component}: ${g.files.join(', ')}`).join('\n');
|
|
104
|
+
const llmSummary = await maybeUseLLM(summary);
|
|
105
|
+
console.log('\nGenerated changelog:\n');
|
|
106
|
+
console.log(changelog);
|
|
107
|
+
console.log('\nLLM summary:\n');
|
|
108
|
+
console.log(llmSummary);
|
|
109
|
+
const message = groups.map(g => g.component).join(', ');
|
|
110
|
+
const commitMessage = `chore: update ${message}`;
|
|
111
|
+
run(`git add -A`);
|
|
112
|
+
run(`git commit -m "${commitMessage}"`, true);
|
|
113
|
+
console.log(`\nCommitted with: ${commitMessage}`);
|
|
114
|
+
}
|
|
115
|
+
main().catch(console.error);
|
|
116
116
|
//# sourceMappingURL=release-cli.js.map
|
package/dist/sql-utils.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { RelationDef } from "./metadata";
|
|
|
8
8
|
* user-supplied column / alias names.
|
|
9
9
|
*/
|
|
10
10
|
export declare function quoteIdentifier(name: string): string;
|
|
11
|
+
/** Quotes one-part or multipart SQL Server table identifiers safely. */
|
|
12
|
+
export declare function quoteTableIdentifier(name: string): string;
|
|
11
13
|
/** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
|
|
12
14
|
export declare function sanitizeParamName(name: string): string;
|
|
13
15
|
/** Validates a direction token, defaulting to ASC. */
|
|
@@ -20,7 +22,7 @@ export interface QueryContext {
|
|
|
20
22
|
}
|
|
21
23
|
export declare function buildOrderBy(orderBy: any): string;
|
|
22
24
|
/**
|
|
23
|
-
* Recursively builds a WHERE clause from a
|
|
25
|
+
* Recursively builds a WHERE clause from a structured filter object.
|
|
24
26
|
*
|
|
25
27
|
* Values are always bound as parameters (`@name`); only identifiers are
|
|
26
28
|
* interpolated directly, and they are run through {@link quoteIdentifier}.
|
package/dist/sql-utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.quoteIdentifier = quoteIdentifier;
|
|
4
|
+
exports.quoteTableIdentifier = quoteTableIdentifier;
|
|
4
5
|
exports.sanitizeParamName = sanitizeParamName;
|
|
5
6
|
exports.normalizeSortDirection = normalizeSortDirection;
|
|
6
7
|
exports.toNonNegativeInt = toNonNegativeInt;
|
|
@@ -18,6 +19,48 @@ function quoteIdentifier(name) {
|
|
|
18
19
|
const s = String(name);
|
|
19
20
|
return `[${s.replace(/\]/g, "]]")}]`;
|
|
20
21
|
}
|
|
22
|
+
function unquoteBracketIdentifier(part) {
|
|
23
|
+
const trimmed = String(part).trim();
|
|
24
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
25
|
+
return trimmed.slice(1, -1).replace(/\]\]/g, "]");
|
|
26
|
+
}
|
|
27
|
+
return trimmed;
|
|
28
|
+
}
|
|
29
|
+
function splitMultipartIdentifier(name) {
|
|
30
|
+
const parts = [];
|
|
31
|
+
let current = "";
|
|
32
|
+
let inBracket = false;
|
|
33
|
+
const input = String(name).trim();
|
|
34
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
35
|
+
const ch = input[i];
|
|
36
|
+
if (ch === "[")
|
|
37
|
+
inBracket = true;
|
|
38
|
+
if (ch === "]") {
|
|
39
|
+
if (input[i + 1] === "]") {
|
|
40
|
+
current += "]]";
|
|
41
|
+
i += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
inBracket = false;
|
|
45
|
+
}
|
|
46
|
+
if (ch === "." && !inBracket) {
|
|
47
|
+
if (current.trim())
|
|
48
|
+
parts.push(current.trim());
|
|
49
|
+
current = "";
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
current += ch;
|
|
53
|
+
}
|
|
54
|
+
if (current.trim())
|
|
55
|
+
parts.push(current.trim());
|
|
56
|
+
return parts.length > 0 ? parts : [input];
|
|
57
|
+
}
|
|
58
|
+
/** Quotes one-part or multipart SQL Server table identifiers safely. */
|
|
59
|
+
function quoteTableIdentifier(name) {
|
|
60
|
+
return splitMultipartIdentifier(name)
|
|
61
|
+
.map(part => quoteIdentifier(unquoteBracketIdentifier(part)))
|
|
62
|
+
.join(".");
|
|
63
|
+
}
|
|
21
64
|
/** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
|
|
22
65
|
function sanitizeParamName(name) {
|
|
23
66
|
const s = String(name);
|
|
@@ -34,7 +77,7 @@ function toNonNegativeInt(value, fallback = 0) {
|
|
|
34
77
|
const n = Number.parseInt(String(value), 10);
|
|
35
78
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
36
79
|
}
|
|
37
|
-
const OPERATOR_KEYS = ["in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"];
|
|
80
|
+
const OPERATOR_KEYS = ["equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"];
|
|
38
81
|
function isOperatorValue(value) {
|
|
39
82
|
if (!value || typeof value !== "object" || value instanceof Date)
|
|
40
83
|
return false;
|
|
@@ -55,7 +98,7 @@ function buildOrderBy(orderBy) {
|
|
|
55
98
|
return orderClauses.length > 0 ? ` ORDER BY ${orderClauses.join(", ")}` : "";
|
|
56
99
|
}
|
|
57
100
|
/**
|
|
58
|
-
* Recursively builds a WHERE clause from a
|
|
101
|
+
* Recursively builds a WHERE clause from a structured filter object.
|
|
59
102
|
*
|
|
60
103
|
* Values are always bound as parameters (`@name`); only identifiers are
|
|
61
104
|
* interpolated directly, and they are run through {@link quoteIdentifier}.
|
|
@@ -83,18 +126,27 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
|
|
|
83
126
|
conditions.push(`(${filtered.join(" OR ")})`);
|
|
84
127
|
}
|
|
85
128
|
}
|
|
86
|
-
else if (key === "AND"
|
|
87
|
-
const
|
|
129
|
+
else if (key === "AND") {
|
|
130
|
+
const andItems = Array.isArray(value) ? value : [value];
|
|
131
|
+
const andConditions = andItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}and_${idx}_`, ctx));
|
|
88
132
|
const filtered = andConditions.filter(Boolean);
|
|
89
133
|
if (filtered.length > 0) {
|
|
90
134
|
conditions.push(`(${filtered.join(" AND ")})`);
|
|
91
135
|
}
|
|
92
136
|
}
|
|
137
|
+
else if (key === "NOT") {
|
|
138
|
+
const notItems = Array.isArray(value) ? value : [value];
|
|
139
|
+
const notConditions = notItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}not_${idx}_`, ctx));
|
|
140
|
+
const filtered = notConditions.filter(Boolean);
|
|
141
|
+
if (filtered.length > 0) {
|
|
142
|
+
conditions.push(`NOT (${filtered.join(" AND ")})`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
93
145
|
else {
|
|
94
146
|
const modelRelations = relationMap[modelName];
|
|
95
147
|
const relation = modelRelations?.[key];
|
|
96
148
|
if (relation) {
|
|
97
|
-
const relationTable = modelToTable[relation.modelName] || relation.modelName;
|
|
149
|
+
const relationTable = quoteTableIdentifier(modelToTable[relation.modelName] || relation.modelName);
|
|
98
150
|
const subParams = {};
|
|
99
151
|
let subWhere = value;
|
|
100
152
|
let op = "some";
|
|
@@ -114,26 +166,23 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
|
|
|
114
166
|
}
|
|
115
167
|
const subWhereSql = parseWhere(relation.modelName, subWhere, subParams, `${prefix}${key}_`, ctx);
|
|
116
168
|
Object.assign(params, subParams);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
conditions.push(`${fk} IN (SELECT ${lk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
126
|
-
}
|
|
169
|
+
const fk = quoteIdentifier(relation.foreignKey);
|
|
170
|
+
const lk = quoteIdentifier(relation.localKey);
|
|
171
|
+
const outerKey = relation.relationType === "one" ? fk : lk;
|
|
172
|
+
const innerKey = relation.relationType === "one" ? lk : fk;
|
|
173
|
+
if (op === "every") {
|
|
174
|
+
if (subWhereSql) {
|
|
175
|
+
conditions.push(`${outerKey} NOT IN (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK) WHERE NOT (${subWhereSql}))`);
|
|
127
176
|
}
|
|
128
177
|
else {
|
|
129
|
-
|
|
130
|
-
conditions.push(`${lk} NOT IN (SELECT ${fk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
conditions.push(`${lk} IN (SELECT ${fk} FROM ${relationTable} WITH (NOLOCK) WHERE ${subWhereSql})`);
|
|
134
|
-
}
|
|
178
|
+
conditions.push("1 = 1");
|
|
135
179
|
}
|
|
136
180
|
}
|
|
181
|
+
else {
|
|
182
|
+
const relationPredicate = subWhereSql ? ` WHERE ${subWhereSql}` : "";
|
|
183
|
+
const operator = op === "none" ? "NOT IN" : "IN";
|
|
184
|
+
conditions.push(`${outerKey} ${operator} (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK)${relationPredicate})`);
|
|
185
|
+
}
|
|
137
186
|
}
|
|
138
187
|
else {
|
|
139
188
|
const col = quoteIdentifier(key);
|
|
@@ -141,7 +190,16 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
|
|
|
141
190
|
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
142
191
|
const ops = Object.entries(value);
|
|
143
192
|
for (const [op, opVal] of ops) {
|
|
144
|
-
if (op === "
|
|
193
|
+
if (op === "equals") {
|
|
194
|
+
if (opVal === null) {
|
|
195
|
+
conditions.push(`${col} IS NULL`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
conditions.push(`${col} = @${paramName}_equals`);
|
|
199
|
+
params[`${paramName}_equals`] = opVal;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else if (op === "in" && Array.isArray(opVal)) {
|
|
145
203
|
if (opVal.length === 0) {
|
|
146
204
|
conditions.push("1 = 0");
|
|
147
205
|
}
|
|
@@ -182,8 +240,20 @@ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
|
|
|
182
240
|
params[`${paramName}_endsWith`] = `%${opVal}`;
|
|
183
241
|
}
|
|
184
242
|
else if (op === "not") {
|
|
185
|
-
|
|
186
|
-
|
|
243
|
+
if (opVal === null) {
|
|
244
|
+
conditions.push(`${col} IS NOT NULL`);
|
|
245
|
+
}
|
|
246
|
+
else if (opVal && typeof opVal === "object" && !(opVal instanceof Date) && !Array.isArray(opVal)) {
|
|
247
|
+
const nestedParams = {};
|
|
248
|
+
const nestedSql = parseWhere(modelName, { [key]: opVal }, nestedParams, `${prefix}${key}_not_`, ctx);
|
|
249
|
+
Object.assign(params, nestedParams);
|
|
250
|
+
if (nestedSql)
|
|
251
|
+
conditions.push(`NOT (${nestedSql})`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
conditions.push(`${col} <> @${paramName}_not`);
|
|
255
|
+
params[`${paramName}_not`] = opVal;
|
|
256
|
+
}
|
|
187
257
|
}
|
|
188
258
|
else if (op === "gte") {
|
|
189
259
|
conditions.push(`${col} >= @${paramName}_gte`);
|