@travetto/model-sqlite 8.0.0-alpha.27 → 8.0.0-alpha.29

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.
Files changed (2) hide show
  1. package/package.json +6 -6
  2. package/src/dialect.ts +98 -21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-sqlite",
3
- "version": "8.0.0-alpha.27",
3
+ "version": "8.0.0-alpha.29",
4
4
  "type": "module",
5
5
  "description": "SQLite backing for the travetto model module, with real-time modeling support for SQL schemas.",
6
6
  "keywords": [
@@ -27,11 +27,11 @@
27
27
  "directory": "module/model-sqlite"
28
28
  },
29
29
  "dependencies": {
30
- "@travetto/config": "^8.0.0-alpha.23",
31
- "@travetto/context": "^8.0.0-alpha.21",
32
- "@travetto/model": "^8.0.0-alpha.24",
33
- "@travetto/model-query": "^8.0.0-alpha.25",
34
- "@travetto/model-sql": "^8.0.0-alpha.27"
30
+ "@travetto/config": "^8.0.0-alpha.24",
31
+ "@travetto/context": "^8.0.0-alpha.22",
32
+ "@travetto/model": "^8.0.0-alpha.25",
33
+ "@travetto/model-query": "^8.0.0-alpha.27",
34
+ "@travetto/model-sql": "^8.0.0-alpha.29"
35
35
  },
36
36
  "travetto": {
37
37
  "displayName": "SQLite Model Service"
package/src/dialect.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AbstractANSI99Dialect, type ResolvedPathContext, type TableContext, type TransactionStatements } from '@travetto/model-sql';
2
2
  import { type Class, castTo, JSONUtil } from '@travetto/runtime';
3
- import type { SchemaFieldConfig } from '@travetto/schema';
3
+ import { type SchemaFieldConfig, SchemaRegistryIndex } from '@travetto/schema';
4
4
 
5
5
  export class SqliteDialect extends AbstractANSI99Dialect {
6
6
  returningSupport = true;
@@ -41,60 +41,137 @@ export class SqliteDialect extends AbstractANSI99Dialect {
41
41
  return `json_extract(${columnName}, '$.${jsonPath.join('.')}')`;
42
42
  }
43
43
 
44
- #getSqliteArrayContext(context: ResolvedPathContext): { jsonArrayExpr: string; valueExpr: string } {
44
+ #getSqliteArrayExpression(context: ResolvedPathContext): string {
45
45
  if (!context.arrayPath || context.arrayPath.length === 0) {
46
- return { jsonArrayExpr: context.sqlPath, valueExpr: 'elem.value' };
46
+ return context.sqlPath;
47
47
  }
48
48
 
49
49
  const columnName = this.escapeIdentifier(context.arrayPath[0]);
50
- const jsonArrayExpr =
51
- context.arrayPath.length > 1 ? `json_extract(${columnName}, '$.${context.arrayPath.slice(1).join('.')}')` : columnName;
50
+ return context.arrayPath.length > 1 ? `json_extract(${columnName}, '$.${context.arrayPath.slice(1).join('.')}')` : columnName;
51
+ }
52
+
53
+ #buildSubPathCondition(context: ResolvedPathContext, parentExpression: string, onLeaf: (leafExpression: string) => string): string {
54
+ if (!context.subPath || context.subPath.length === 0) {
55
+ return onLeaf(parentExpression);
56
+ }
57
+
58
+ const arraySegmentIndices: number[] = [];
59
+ let currentClass: Class | undefined = context.arrayField?.type;
60
+
61
+ for (let index = 0; index < context.subPath.length; index++) {
62
+ const segment = context.subPath[index];
63
+ if (currentClass) {
64
+ const classConfiguration = SchemaRegistryIndex.getOptional(currentClass)?.get();
65
+ const fieldConfiguration = classConfiguration?.fields[segment];
66
+ if (fieldConfiguration) {
67
+ if (fieldConfiguration.array) {
68
+ arraySegmentIndices.push(index);
69
+ }
70
+ currentClass = fieldConfiguration.type;
71
+ }
72
+ }
73
+ }
74
+
75
+ if (arraySegmentIndices.length === 0) {
76
+ const leafExpression = `json_extract(${parentExpression}, '$.${context.subPath.join('.')}')`;
77
+ return onLeaf(leafExpression);
78
+ }
79
+
80
+ const buildLevel = (levelIndex: number, currentParent: string): string => {
81
+ const startPathIndex = levelIndex === 0 ? 0 : arraySegmentIndices[levelIndex - 1] + 1;
82
+ const endPathIndex = arraySegmentIndices[levelIndex];
83
+ const arrayPath = context.subPath!.slice(startPathIndex, endPathIndex + 1).join('.');
84
+ const alias = `ing_${levelIndex}`;
85
+
86
+ const innerCondition =
87
+ levelIndex === arraySegmentIndices.length - 1
88
+ ? (() => {
89
+ const leafPath = context.subPath!.slice(endPathIndex + 1).join('.');
90
+ const leafExpression = leafPath ? `json_extract(${alias}.value, '$.${leafPath}')` : `${alias}.value`;
91
+ return onLeaf(leafExpression);
92
+ })()
93
+ : buildLevel(levelIndex + 1, `${alias}.value`);
94
+
95
+ return `
96
+ EXISTS (
97
+ SELECT 1
98
+ FROM json_each(${currentParent}, '$.${arrayPath}') AS ${alias}
99
+ WHERE ${innerCondition}
100
+ )`;
101
+ };
52
102
 
53
- const valueExpr =
54
- context.subPath && context.subPath.length > 0 ? `json_extract(elem.value, '$.${context.subPath.join('.')}')` : 'elem.value';
103
+ return buildLevel(0, parentExpression);
104
+ }
55
105
 
56
- return { jsonArrayExpr, valueExpr };
106
+ #buildArrayElementExists(context: ResolvedPathContext, onLeaf: (leafExpression: string) => string): string {
107
+ const jsonArrayExpression = this.#getSqliteArrayExpression(context);
108
+ const subPathCondition = this.#buildSubPathCondition(context, 'elem.value', onLeaf);
109
+ return `EXISTS (
110
+ SELECT 1
111
+ FROM json_each(${jsonArrayExpression}) AS elem
112
+ WHERE ${subPathCondition}
113
+ )`;
57
114
  }
58
115
 
59
116
  compileArrayAll(context: ResolvedPathContext, identifier: string, value: unknown[]): { sql: string; formatted: unknown } {
60
- const { jsonArrayExpr, valueExpr } = this.#getSqliteArrayContext(context);
117
+ const elementExists = this.#buildArrayElementExists(context, leafExpression => `${leafExpression} = req.value`);
61
118
  return {
62
- sql: `NOT EXISTS (SELECT 1 FROM json_each(${identifier}) AS req WHERE req.value NOT IN (SELECT ${valueExpr} FROM json_each(${jsonArrayExpr}) AS elem))`,
119
+ sql: `NOT EXISTS (
120
+ SELECT 1
121
+ FROM json_each(${identifier}) AS req
122
+ WHERE NOT ${elementExists}
123
+ )`,
63
124
  formatted: JSONUtil.toUTF8(value)
64
125
  };
65
126
  }
66
127
 
67
128
  compileArrayEquals(context: ResolvedPathContext, identifier: string, values: unknown): { sql: string; formatted: unknown } {
68
- const { jsonArrayExpr, valueExpr } = this.#getSqliteArrayContext(context);
69
129
  if (Array.isArray(values)) {
70
130
  return {
71
- sql: `NOT EXISTS (SELECT 1 FROM json_each(${identifier}) AS req WHERE req.value NOT IN (SELECT ${valueExpr} FROM json_each(${jsonArrayExpr}) AS elem))`,
131
+ sql: this.#buildArrayElementExists(context, leafExpression => `${leafExpression} IN (SELECT value FROM json_each(${identifier}))`),
72
132
  formatted: JSONUtil.toUTF8(values)
73
133
  };
74
134
  }
135
+
75
136
  if (typeof values === 'object' && values !== null) {
76
137
  return {
77
- sql: `EXISTS (SELECT 1 FROM json_each(${jsonArrayExpr}) AS elem WHERE NOT EXISTS (SELECT 1 FROM json_each(${identifier}) AS req WHERE json_extract(elem.value, '$.' || req.key) IS NOT req.value))`,
138
+ sql: this.#buildArrayElementExists(
139
+ context,
140
+ leafExpression => `
141
+ NOT EXISTS (
142
+ SELECT 1
143
+ FROM json_each(${identifier}) AS req
144
+ WHERE json_extract(${leafExpression}, '$.' || req.key) IS NOT req.value
145
+ )`
146
+ ),
78
147
  formatted: JSONUtil.toUTF8(values)
79
148
  };
80
149
  }
150
+
81
151
  return {
82
- sql: `EXISTS (SELECT 1 FROM json_each(${jsonArrayExpr}) AS elem WHERE ${valueExpr} = ${identifier})`,
152
+ sql: this.#buildArrayElementExists(context, leafExpression => `${leafExpression} = ${identifier}`),
83
153
  formatted: values
84
154
  };
85
155
  }
86
156
 
87
157
  compileArrayAny(context: ResolvedPathContext, identifier: string, values: unknown[]): { sql: string; formatted: unknown } {
88
- const { jsonArrayExpr, valueExpr } = this.#getSqliteArrayContext(context);
89
- return {
90
- sql: `EXISTS (SELECT 1 FROM json_each(${jsonArrayExpr}) AS elem WHERE ${valueExpr} IN (SELECT value FROM json_each(${identifier})))`,
91
- formatted: JSONUtil.toUTF8(values)
92
- };
158
+ return this.compileArrayEquals(context, identifier, values);
93
159
  }
94
160
 
95
161
  compileArrayExists(context: ResolvedPathContext, identifier?: string): { sql: string } {
96
- const { jsonArrayExpr } = this.#getSqliteArrayContext(context);
97
- return { sql: `(${jsonArrayExpr} IS NOT NULL AND json_array_length(${jsonArrayExpr}) > 0)` };
162
+ const jsonArrayExpression = this.#getSqliteArrayExpression(context);
163
+ return { sql: `(${jsonArrayExpression} IS NOT NULL AND json_array_length(${jsonArrayExpression}) > 0)` };
164
+ }
165
+
166
+ compileArrayRegex(context: ResolvedPathContext, identifier: string, value: RegExp | string): { sql: string; formatted: unknown } {
167
+ const regex = value instanceof RegExp ? value : new RegExp(String(value));
168
+ const caseInsensitive = regex.flags.includes('i');
169
+ const regexOp = this.getRegexOperator(caseInsensitive);
170
+ const regexSource = this.formatRegex(regex.source, caseInsensitive);
171
+ return {
172
+ sql: this.#buildArrayElementExists(context, leafExpression => `${leafExpression} ${regexOp} ${identifier}`),
173
+ formatted: regexSource
174
+ };
98
175
  }
99
176
 
100
177
  getRegexOperator(caseInsensitive: boolean): string {