agentlang 0.5.0 → 0.5.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.
Files changed (50) hide show
  1. package/out/language/generated/ast.d.ts +21 -3
  2. package/out/language/generated/ast.d.ts.map +1 -1
  3. package/out/language/generated/ast.js +26 -2
  4. package/out/language/generated/ast.js.map +1 -1
  5. package/out/language/generated/grammar.d.ts.map +1 -1
  6. package/out/language/generated/grammar.js +375 -202
  7. package/out/language/generated/grammar.js.map +1 -1
  8. package/out/language/main.cjs +392 -204
  9. package/out/language/main.cjs.map +2 -2
  10. package/out/runtime/defs.d.ts +10 -0
  11. package/out/runtime/defs.d.ts.map +1 -1
  12. package/out/runtime/interpreter.d.ts.map +1 -1
  13. package/out/runtime/interpreter.js +17 -2
  14. package/out/runtime/interpreter.js.map +1 -1
  15. package/out/runtime/module.d.ts +2 -0
  16. package/out/runtime/module.d.ts.map +1 -1
  17. package/out/runtime/module.js +33 -2
  18. package/out/runtime/module.js.map +1 -1
  19. package/out/runtime/modules/auth.js +1 -1
  20. package/out/runtime/resolvers/interface.d.ts +2 -1
  21. package/out/runtime/resolvers/interface.d.ts.map +1 -1
  22. package/out/runtime/resolvers/interface.js +2 -2
  23. package/out/runtime/resolvers/interface.js.map +1 -1
  24. package/out/runtime/resolvers/sqldb/database.d.ts +1 -0
  25. package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
  26. package/out/runtime/resolvers/sqldb/database.js +56 -8
  27. package/out/runtime/resolvers/sqldb/database.js.map +1 -1
  28. package/out/runtime/resolvers/sqldb/dbutil.d.ts +2 -0
  29. package/out/runtime/resolvers/sqldb/dbutil.d.ts.map +1 -1
  30. package/out/runtime/resolvers/sqldb/dbutil.js +26 -13
  31. package/out/runtime/resolvers/sqldb/dbutil.js.map +1 -1
  32. package/out/runtime/resolvers/sqldb/impl.d.ts +3 -1
  33. package/out/runtime/resolvers/sqldb/impl.d.ts.map +1 -1
  34. package/out/runtime/resolvers/sqldb/impl.js +34 -5
  35. package/out/runtime/resolvers/sqldb/impl.js.map +1 -1
  36. package/out/syntaxes/agentlang.monarch.js +1 -1
  37. package/out/syntaxes/agentlang.monarch.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/language/agentlang.langium +10 -3
  40. package/src/language/generated/ast.ts +54 -4
  41. package/src/language/generated/grammar.ts +375 -202
  42. package/src/runtime/defs.ts +11 -0
  43. package/src/runtime/interpreter.ts +25 -1
  44. package/src/runtime/module.ts +35 -2
  45. package/src/runtime/modules/auth.ts +1 -1
  46. package/src/runtime/resolvers/interface.ts +7 -3
  47. package/src/runtime/resolvers/sqldb/database.ts +60 -7
  48. package/src/runtime/resolvers/sqldb/dbutil.ts +114 -74
  49. package/src/runtime/resolvers/sqldb/impl.ts +53 -6
  50. package/src/syntaxes/agentlang.monarch.ts +1 -1
@@ -300,3 +300,14 @@ export class ExecGraphWalker {
300
300
  return this;
301
301
  }
302
302
  }
303
+
304
+ export type FkSpec = {
305
+ moduleName: string;
306
+ entityName: string;
307
+ columnName: string;
308
+ targetModuleName: string;
309
+ targetEntityName: string;
310
+ targetColumnName: string;
311
+ onDelete: 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | undefined;
312
+ onUpdate: 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | undefined;
313
+ };
@@ -14,6 +14,7 @@ import {
14
14
  isNegExpr,
15
15
  isNotExpr,
16
16
  isReturn,
17
+ JoinSpec,
17
18
  Literal,
18
19
  MapKey,
19
20
  MapLiteral,
@@ -1298,7 +1299,11 @@ async function evaluateCrudMap(crud: CrudMap, env: Environment): Promise<void> {
1298
1299
  if (qattrs === undefined && !isQueryAll) {
1299
1300
  throw new Error(`Pattern for ${entryName} with 'into' clause must be a query`);
1300
1301
  }
1301
- await evaluateJoinQuery(crud.into, inst, crud.relationships, distinct, env);
1302
+ if (crud.join) {
1303
+ await evaluateJoinQuery(crud.join, crud.into, inst, distinct, env);
1304
+ } else {
1305
+ await evaluateJoinQueryWithRelationships(crud.into, inst, crud.relationships, distinct, env);
1306
+ }
1302
1307
  return;
1303
1308
  }
1304
1309
  if (isEntityInstance(inst) || isBetweenRelationship(inst.name, inst.moduleName)) {
@@ -1576,6 +1581,25 @@ async function computeExprAttributes(
1576
1581
  }
1577
1582
 
1578
1583
  async function evaluateJoinQuery(
1584
+ joinSpec: JoinSpec,
1585
+ intoSpec: SelectIntoSpec,
1586
+ inst: Instance,
1587
+ distinct: boolean,
1588
+ env: Environment
1589
+ ): Promise<void> {
1590
+ const normIntoSpec = new Map<string, string>();
1591
+ intoSpec.entries.forEach((entry: SelectIntoEntry) => {
1592
+ normIntoSpec.set(entry.alias, entry.attribute);
1593
+ });
1594
+ const resolver = await getResolverForPath(inst.name, inst.moduleName, env);
1595
+ const result: Result = await resolver.queryByJoin(inst, [], normIntoSpec, distinct, joinSpec);
1596
+
1597
+ const transformedResult = transformDateFieldsInJoinResult(result);
1598
+
1599
+ env.setLastResult(transformedResult);
1600
+ }
1601
+
1602
+ async function evaluateJoinQueryWithRelationships(
1579
1603
  intoSpec: SelectIntoSpec,
1580
1604
  inst: Instance,
1581
1605
  relationships: RelationshipPattern[],
@@ -43,10 +43,12 @@ import {
43
43
  escapeFqName,
44
44
  encryptPassword,
45
45
  splitFqName,
46
+ splitRefs,
47
+ forceAsFqName,
46
48
  } from './util.js';
47
49
  import { parseStatement } from '../language/parser.js';
48
50
  import { ActiveSessionInfo, AdminSession } from './auth/defs.js';
49
- import { FetchModuleFn, PathAttributeName } from './defs.js';
51
+ import { FetchModuleFn, FkSpec, PathAttributeName } from './defs.js';
50
52
  import { logger } from './logger.js';
51
53
  import { CasePattern, FlowStepPattern } from '../language/syntax.js';
52
54
  import {
@@ -323,7 +325,7 @@ export class Record extends ModuleEntry {
323
325
  props = new Map();
324
326
  }
325
327
  props.set('ref', escapeFqName(fp));
326
- t = 'Path';
328
+ t = a.refSpec.type === undefined ? 'Any' : a.refSpec.type;
327
329
  }
328
330
  const enumValues: string[] | undefined = a.enumSpec?.values;
329
331
  const oneOfRef: string | undefined = a.oneOfSpec?.ref;
@@ -533,6 +535,37 @@ export class Record extends ModuleEntry {
533
535
  return undefined;
534
536
  }
535
537
 
538
+ getFkAttributeSpecs(): FkSpec[] {
539
+ const result = new Array<FkSpec>();
540
+ this.schema.forEach((attrSpec: AttributeSpec, columnName: string) => {
541
+ const refSpec = getRefSpec(attrSpec);
542
+ if (refSpec) {
543
+ const targetNames = forceAsFqName(refSpec, this.moduleName);
544
+ const parts = splitFqName(targetNames);
545
+ const targetModuleName = parts[0];
546
+ const refs = splitRefs(parts[1]);
547
+ const targetEntityName = refs[0];
548
+ let targetColumnName = '';
549
+ if (refs.length <= 1) {
550
+ targetColumnName = PathAttributeName;
551
+ } else {
552
+ targetColumnName = refs[1];
553
+ }
554
+ result.push({
555
+ moduleName: this.moduleName,
556
+ entityName: this.name,
557
+ columnName,
558
+ targetModuleName,
559
+ targetEntityName,
560
+ targetColumnName,
561
+ onDelete: 'CASCADE',
562
+ onUpdate: 'CASCADE',
563
+ });
564
+ }
565
+ });
566
+ return result;
567
+ }
568
+
536
569
  override toString(): string {
537
570
  return this.toString_();
538
571
  }
@@ -32,7 +32,7 @@ export default `module ${CoreAuthModuleName}
32
32
  import "./modules/auth.js" @as Auth
33
33
 
34
34
  entity User {
35
- id UUID @id @default(uuid()),
35
+ id UUID @id @default(uuid()) @unique,
36
36
  email Email @unique @indexed,
37
37
  firstName String,
38
38
  lastName String,
@@ -1,3 +1,4 @@
1
+ import { JoinSpec } from '../../language/generated/ast.js';
1
2
  import {
2
3
  callPostEventOnSubscription,
3
4
  Environment,
@@ -127,11 +128,14 @@ export class Resolver {
127
128
 
128
129
  public async queryByJoin(
129
130
  inst: Instance,
130
- joinsSpec: JoinInfo[],
131
+ joinInfo: JoinInfo[],
131
132
  intoSpec: Map<string, string>,
132
- distinct: boolean = false
133
+ distinct: boolean = false,
134
+ rawJoinSpec?: JoinSpec
133
135
  ): Promise<any> {
134
- return this.notImpl(`queryByJoin(${inst}, ${joinsSpec}, ${intoSpec}, ${distinct})`);
136
+ return this.notImpl(
137
+ `queryByJoin(${inst}, ${joinInfo}, ${intoSpec}, ${distinct} ${rawJoinSpec})`
138
+ );
135
139
  }
136
140
 
137
141
  /**
@@ -1,6 +1,14 @@
1
- import { DataSource, EntityManager, EntitySchema, QueryRunner, SelectQueryBuilder } from 'typeorm';
1
+ import {
2
+ DataSource,
3
+ EntityManager,
4
+ EntitySchema,
5
+ QueryRunner,
6
+ SelectQueryBuilder,
7
+ TableForeignKey,
8
+ } from 'typeorm';
2
9
  import { logger } from '../../logger.js';
3
10
  import {
11
+ asTableReference,
4
12
  DefaultVectorDimension,
5
13
  modulesAsOrmSchema,
6
14
  OwnersSuffix,
@@ -138,6 +146,7 @@ export type JoinClause = {
138
146
  tableName: string;
139
147
  queryObject?: object;
140
148
  queryValues?: object;
149
+ joinType?: string; // 'join' | 'inner join' | 'left join' | 'right join' | 'full join'
141
150
  joinOn: JoinOn | JoinOn[];
142
151
  };
143
152
 
@@ -168,6 +177,10 @@ function makePostgresDataSource(
168
177
  database: process.env.POSTGRES_DB || config?.dbname || 'postgres',
169
178
  synchronize: synchronize,
170
179
  entities: entities,
180
+ invalidWhereValuesBehavior: {
181
+ null: 'sql-null',
182
+ undefined: 'ignore',
183
+ },
171
184
  });
172
185
  }
173
186
 
@@ -190,6 +203,10 @@ function makeSqliteDataSource(
190
203
  database: config?.dbname || mkDbName(),
191
204
  synchronize: synchronize,
192
205
  entities: entities,
206
+ invalidWhereValuesBehavior: {
207
+ null: 'sql-null',
208
+ undefined: 'ignore',
209
+ },
193
210
  });
194
211
  }
195
212
 
@@ -288,6 +305,20 @@ export async function initDatabase(config: DatabaseConfig | undefined) {
288
305
  const ormScm = modulesAsOrmSchema();
289
306
  defaultDataSource = mkds(ormScm.entities, config) as DataSource;
290
307
  await defaultDataSource.initialize();
308
+ if (ormScm.fkSpecs.length > 0) {
309
+ const qr = defaultDataSource.createQueryRunner();
310
+ for (let i = 0; i < ormScm.fkSpecs.length; ++i) {
311
+ const fk = ormScm.fkSpecs[i];
312
+ const fkobj = new TableForeignKey({
313
+ columnNames: [fk.columnName],
314
+ referencedColumnNames: [fk.targetColumnName],
315
+ referencedTableName: asTableReference(fk.targetModuleName, fk.targetEntityName),
316
+ onDelete: fk.onDelete,
317
+ onUpdate: fk.onUpdate,
318
+ });
319
+ await qr.createForeignKey(asTableReference(fk.moduleName, fk.entityName), fkobj);
320
+ }
321
+ }
291
322
  const vectEnts = ormScm.vectorEntities.map((es: EntitySchema) => {
292
323
  return es.options.name;
293
324
  });
@@ -716,13 +747,25 @@ function mkBetweenClause(tableName: string | undefined, k: string, queryVals: an
716
747
  function objectToWhereClause(queryObj: object, queryVals: any, tableName?: string): string {
717
748
  const clauses: Array<string> = new Array<string>();
718
749
  Object.entries(queryObj).forEach((value: [string, any]) => {
719
- const op: string = value[1] as string;
750
+ let op: string = value[1] as string;
751
+ const k = value[0];
752
+ const isnullcheck = queryVals[k] === null;
753
+ if (isnullcheck) {
754
+ if (op === '=') {
755
+ op = 'IS';
756
+ } else if (op === '<>' || op === '!=') {
757
+ op = 'IS NOT';
758
+ } else {
759
+ throw new Error(`Operator ${op} cannot be appplied to SQL NULL`);
760
+ }
761
+ }
762
+ const v = isnullcheck ? 'NULL' : `:${k}`;
720
763
  const clause =
721
764
  op == 'between'
722
- ? mkBetweenClause(tableName, value[0], queryVals)
765
+ ? mkBetweenClause(tableName, k, queryVals)
723
766
  : tableName
724
- ? `"${tableName}"."${value[0]}" ${op} :${value[0]}`
725
- : `"${value[0]}" ${op} :${value[0]}`;
767
+ ? `"${tableName}"."${k}" ${op} ${v}`
768
+ : `"${k}" ${op} ${v}`;
726
769
  clauses.push(clause);
727
770
  });
728
771
  return clauses.join(' AND ');
@@ -731,8 +774,17 @@ function objectToWhereClause(queryObj: object, queryVals: any, tableName?: strin
731
774
  function objectToRawWhereClause(queryObj: object, queryVals: any, tableName?: string): string {
732
775
  const clauses: Array<string> = new Array<string>();
733
776
  Object.entries(queryObj).forEach((value: [string, any]) => {
734
- const op: string = value[1] as string;
777
+ let op: string = value[1] as string;
735
778
  const k: string = value[0];
779
+ if (queryVals[k] === null) {
780
+ if (op === '=') {
781
+ op = 'IS';
782
+ } else if (op === '<>' || op === '!=') {
783
+ op = 'IS NOT';
784
+ } else {
785
+ throw new Error(`Operator ${op} cannot be appplied to SQL NULL`);
786
+ }
787
+ }
736
788
  let clause = '';
737
789
  if (op == 'between') {
738
790
  clause = mkBetweenClause(tableName, k, queryVals);
@@ -843,8 +895,9 @@ export async function getManyByJoin(
843
895
  }
844
896
  const joinSql = new Array<string>();
845
897
  joinClauses.forEach((jc: JoinClause) => {
898
+ const joinType = jc.joinType ? jc.joinType : 'inner join';
846
899
  joinSql.push(
847
- `inner join ${jc.tableName} as ${jc.tableName} on ${joinOnAsSql(jc.joinOn)} AND ${jc.tableName}.${DeletedFlagAttributeName} = false`
900
+ `${joinType} ${jc.tableName} as ${jc.tableName} on ${joinOnAsSql(jc.joinOn)} AND ${jc.tableName}.${DeletedFlagAttributeName} = false`
848
901
  );
849
902
  if (jc.queryObject) {
850
903
  const q = objectToRawWhereClause(jc.queryObject, jc.queryValues, jc.tableName);
@@ -1,4 +1,12 @@
1
- import { ColumnType, EntitySchema, EntitySchemaColumnOptions, EntitySchemaOptions, TableColumnOptions, TableForeignKey, TableIndexOptions } from 'typeorm';
1
+ import {
2
+ ColumnType,
3
+ EntitySchema,
4
+ EntitySchemaColumnOptions,
5
+ EntitySchemaOptions,
6
+ TableColumnOptions,
7
+ TableForeignKey,
8
+ TableIndexOptions,
9
+ } from 'typeorm';
2
10
  import {
3
11
  AttributeSpec,
4
12
  fetchModule,
@@ -21,9 +29,9 @@ import {
21
29
  } from '../../module.js';
22
30
  import { buildGraph } from '../../relgraph.js';
23
31
  import { makeFqName } from '../../util.js';
24
- import { DeletedFlagAttributeName, ParentAttributeName, PathAttributeName } from '../../defs.js';
32
+ import { DeletedFlagAttributeName, FkSpec, ParentAttributeName, PathAttributeName } from '../../defs.js';
25
33
 
26
- export const DefaultVectorDimension = 1536
34
+ export const DefaultVectorDimension = 1536;
27
35
 
28
36
  export type TableSchema = {
29
37
  name: string;
@@ -31,14 +39,14 @@ export type TableSchema = {
31
39
  };
32
40
 
33
41
  export function asTableReference(moduleName: string, ref: string): string {
34
- const modName = moduleName.replace('.', '_')
42
+ const modName = moduleName.replace('.', '_');
35
43
  if (ref.indexOf('.') > 0) {
36
- const parts = ref.split('.')
37
- const r = `${modName}_${parts[0]}`.toLowerCase()
38
- const colref = parts.slice(1).join('.')
44
+ const parts = ref.split('.');
45
+ const r = `${modName}_${parts[0]}`.toLowerCase();
46
+ const colref = parts.slice(1).join('.');
39
47
  return `"${r}"."${colref}"`;
40
48
  } else {
41
- return `${modName}_${ref}`.toLowerCase()
49
+ return `${modName}_${ref}`.toLowerCase();
42
50
  }
43
51
  }
44
52
 
@@ -64,123 +72,148 @@ export function modulesAsDbSchema(): TableSchema[] {
64
72
  }
65
73
 
66
74
  export type OrmSchema = {
67
- entities: EntitySchema[],
68
- vectorEntities: EntitySchema[]
69
- }
75
+ entities: EntitySchema[];
76
+ vectorEntities: EntitySchema[];
77
+ fkSpecs: FkSpec[];
78
+ };
70
79
 
71
80
  export function modulesAsOrmSchema(): OrmSchema {
72
81
  const ents: EntitySchema[] = [];
73
- const vects: EntitySchema[] = []
82
+ const vects: EntitySchema[] = [];
83
+ const fkSpecs: FkSpec[] = [];
74
84
  getModuleNames().forEach((n: string) => {
75
85
  buildGraph(n);
76
86
  const mod: Module = fetchModule(n);
77
- const entities: Record[] = mod.getEntityEntries()
87
+ const entities: Record[] = mod.getEntityEntries();
78
88
  const rels: Record[] = mod.getBetweenRelationshipEntriesThatNeedStore();
79
89
  entities.concat(rels).forEach((entry: Record) => {
80
- ents.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, entry)))
81
- const ownerEntry = createOwnersEntity(entry)
82
- ents.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, ownerEntry, true)))
90
+ ents.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, entry)));
91
+ const ownerEntry = createOwnersEntity(entry);
92
+ ents.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, ownerEntry, true)));
83
93
  if (entry.getFullTextSearchAttributes()) {
84
- const vectorEntry = createVectorEntity(entry)
85
- vects.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, vectorEntry, true)))
94
+ const vectorEntry = createVectorEntity(entry);
95
+ vects.push(new EntitySchema<any>(ormSchemaFromRecordSchema(n, vectorEntry, true)));
96
+ }
97
+ });
98
+ entities.forEach((r: Record) => {
99
+ const fks = r.getFkAttributeSpecs()
100
+ if (fks.length > 0) {
101
+ fkSpecs.push(...fks)
86
102
  }
87
103
  })
88
- })
89
- return { entities: ents, vectorEntities: vects }
104
+ });
105
+ return { entities: ents, vectorEntities: vects, fkSpecs };
90
106
  }
91
107
 
92
- function ormSchemaFromRecordSchema(moduleName: string, entry: Record, hasOwnPk?: boolean): EntitySchemaOptions<any> {
93
- const entityName = entry.name
94
- const scm: RecordSchema = entry.schema
95
- const result = new EntitySchemaOptions<any>()
96
- result.tableName = asTableReference(moduleName, entityName)
97
- result.name = result.tableName
98
- const cols = new Map<string, any>()
99
- const indices = new Array<any>()
100
- const chkforpk: boolean = hasOwnPk == undefined ? false : true
101
- let needPath = true
108
+ function ormSchemaFromRecordSchema(
109
+ moduleName: string,
110
+ entry: Record,
111
+ hasOwnPk?: boolean
112
+ ): EntitySchemaOptions<any> {
113
+ const entityName = entry.name;
114
+ const scm: RecordSchema = entry.schema;
115
+ const result = new EntitySchemaOptions<any>();
116
+ result.tableName = asTableReference(moduleName, entityName);
117
+ result.name = result.tableName;
118
+ const cols = new Map<string, any>();
119
+ const indices = new Array<any>();
120
+ const chkforpk: boolean = hasOwnPk == undefined ? false : true;
121
+ let needPath = true;
102
122
  scm.forEach((attrSpec: AttributeSpec, attrName: string) => {
103
123
  let d: any = getAttributeDefaultValue(attrSpec);
104
124
  const autoUuid: boolean = d && d == 'uuid()' ? true : false;
105
125
  const autoIncr: boolean = !autoUuid && d && d == 'autoincrement()' ? true : false;
106
126
  if (autoUuid || autoIncr) d = undefined;
107
- let genStrat: 'uuid' | 'increment' | undefined = undefined
127
+ let genStrat: 'uuid' | 'increment' | undefined = undefined;
108
128
  if (autoIncr) genStrat = 'increment';
109
129
  else if (autoUuid) genStrat = 'uuid';
110
- const isuq: boolean = isUniqueAttribute(attrSpec)
111
- const ispk: boolean = chkforpk && isIdAttribute(attrSpec)
130
+ const isuq: boolean = isUniqueAttribute(attrSpec);
131
+ const ispk: boolean = chkforpk && isIdAttribute(attrSpec);
112
132
  const colDef: EntitySchemaColumnOptions = {
113
133
  type: asSqlType(attrSpec.type),
114
134
  generated: genStrat,
115
135
  default: d,
116
136
  unique: isuq,
117
137
  primary: ispk,
118
- nullable: isOptionalAttribute(attrSpec)
138
+ nullable: isOptionalAttribute(attrSpec),
119
139
  };
120
140
  if (ispk) {
121
- needPath = false
141
+ needPath = false;
122
142
  }
123
143
  if (isIndexedAttribute(attrSpec)) {
124
- indices.push(Object.fromEntries(new Map()
125
- .set('name', `${result.tableName}_${attrName}_index`)
126
- .set('columns', [attrName])
127
- .set('unique', isuq)))
144
+ indices.push(
145
+ Object.fromEntries(
146
+ new Map()
147
+ .set('name', `${result.tableName}_${attrName}_index`)
148
+ .set('columns', [attrName])
149
+ .set('unique', isuq)
150
+ )
151
+ );
128
152
  }
129
- cols.set(attrName, colDef)
153
+ cols.set(attrName, colDef);
130
154
  });
131
155
  if (needPath) {
132
- cols.set(PathAttributeName, { type: "varchar", primary: true })
133
- cols.set(ParentAttributeName, { type: "varchar", default: '', indexed: true })
156
+ cols.set(PathAttributeName, { type: 'varchar', primary: true });
157
+ cols.set(ParentAttributeName, { type: 'varchar', default: '', indexed: true });
134
158
  }
135
- cols.set(DeletedFlagAttributeName, { type: "boolean", default: false })
136
- const allBetRels = getAllBetweenRelationships()
137
- const relsSpec = new Map()
138
- const fqName = makeFqName(moduleName, entityName)
139
- getAllOneToOneRelationshipsForEntity(moduleName, entityName, allBetRels)
140
- .forEach((re: Relationship) => {
141
- const colName = re.getInverseAliasForName(fqName)
159
+ cols.set(DeletedFlagAttributeName, { type: 'boolean', default: false });
160
+ const allBetRels = getAllBetweenRelationships();
161
+ const relsSpec = new Map();
162
+ const fqName = makeFqName(moduleName, entityName);
163
+ getAllOneToOneRelationshipsForEntity(moduleName, entityName, allBetRels).forEach(
164
+ (re: Relationship) => {
165
+ const colName = re.getInverseAliasForName(fqName);
142
166
  if (cols.has(colName)) {
143
- throw new Error(`Cannot establish relationship ${re.name}, ${entityName}.${colName} already exists`)
167
+ throw new Error(
168
+ `Cannot establish relationship ${re.name}, ${entityName}.${colName} already exists`
169
+ );
144
170
  }
145
- cols.set(colName, { type: "varchar", unique: true })
146
- })
171
+ cols.set(colName, { type: 'varchar', unique: true });
172
+ }
173
+ );
147
174
  if (relsSpec.size > 0) {
148
- result.relations = Object.fromEntries(relsSpec)
175
+ result.relations = Object.fromEntries(relsSpec);
149
176
  }
150
- result.columns = Object.fromEntries(cols)
151
- const compUqs = entry.getCompositeUniqueAttributes()
177
+ result.columns = Object.fromEntries(cols);
178
+ const compUqs = entry.getCompositeUniqueAttributes();
152
179
  if (compUqs) {
153
- indices.push(Object.fromEntries(new Map()
154
- .set('name', `${result.tableName}__comp__index`)
155
- .set('columns', compUqs)
156
- .set('unique', true)))
180
+ indices.push(
181
+ Object.fromEntries(
182
+ new Map()
183
+ .set('name', `${result.tableName}__comp__index`)
184
+ .set('columns', compUqs)
185
+ .set('unique', true)
186
+ )
187
+ );
157
188
  }
158
189
  if (indices.length > 0) {
159
- result.indices = indices
190
+ result.indices = indices;
160
191
  }
161
- return result
192
+ return result;
162
193
  }
163
194
 
164
- export const OwnersSuffix = '_owners'
165
- export const VectorSuffix = '_vector'
195
+ export const OwnersSuffix = '_owners';
196
+ export const VectorSuffix = '_vector';
166
197
 
167
198
  function createOwnersEntity(entry: Record): Record {
168
- const ownersEntry = new Record(`${entry.name}${OwnersSuffix}`, entry.moduleName)
169
- const permProps = new Map().set('default', true)
170
- return ownersEntry.addAttribute('id', { type: 'UUID', properties: new Map().set('id', true) })
199
+ const ownersEntry = new Record(`${entry.name}${OwnersSuffix}`, entry.moduleName);
200
+ const permProps = new Map().set('default', true);
201
+ return ownersEntry
202
+ .addAttribute('id', { type: 'UUID', properties: new Map().set('id', true) })
171
203
  .addAttribute('user_id', { type: 'String' })
172
204
  .addAttribute('type', { type: 'String', properties: new Map().set('default', 'o') })
173
205
  .addAttribute('c', { type: 'Boolean', properties: permProps })
174
206
  .addAttribute('r', { type: 'Boolean', properties: permProps })
175
207
  .addAttribute('u', { type: 'Boolean', properties: permProps })
176
208
  .addAttribute('d', { type: 'Boolean', properties: permProps })
177
- .addAttribute('path', { type: 'String', properties: new Map().set('indexed', true) })
209
+ .addAttribute('path', { type: 'String', properties: new Map().set('indexed', true) });
178
210
  }
179
211
 
180
212
  function createVectorEntity(entry: Record): Record {
181
- const ownersEntry = new Record(`${entry.name}${VectorSuffix}`, entry.moduleName)
182
- return ownersEntry.addAttribute('id', { type: 'String', properties: new Map().set('id', true) })
183
- .addAttribute('embedding', { type: `vector(${DefaultVectorDimension})` })
213
+ const ownersEntry = new Record(`${entry.name}${VectorSuffix}`, entry.moduleName);
214
+ return ownersEntry
215
+ .addAttribute('id', { type: 'String', properties: new Map().set('id', true) })
216
+ .addAttribute('embedding', { type: `vector(${DefaultVectorDimension})` });
184
217
  }
185
218
 
186
219
  export type TableSpec = {
@@ -250,12 +283,19 @@ function entitySchemaToTable(scm: RecordSchema): TableSpec {
250
283
  }
251
284
 
252
285
  export function asSqlType(type: string): ColumnType {
253
- const t = type.toLowerCase()
254
- if (t == 'string' || t == 'datetime' || t == 'email' || t == 'url'
255
- || t == 'map' || t == 'any' || t == 'path')
286
+ const t = type.toLowerCase();
287
+ if (
288
+ t == 'string' ||
289
+ t == 'datetime' ||
290
+ t == 'email' ||
291
+ t == 'url' ||
292
+ t == 'map' ||
293
+ t == 'any' ||
294
+ t == 'path'
295
+ )
256
296
  return 'varchar';
257
297
  else if (t == 'int') return 'integer';
258
- else if (t == 'number') return 'double precision'
298
+ else if (t == 'number') return 'double precision';
259
299
  else if (!isBuiltInType(type)) return 'varchar';
260
300
  else return t as ColumnType;
261
301
  }
@@ -13,7 +13,15 @@ import {
13
13
  newInstanceAttributes,
14
14
  Relationship,
15
15
  } from '../../module.js';
16
- import { escapeFqName, makeFqName, nameToPath } from '../../util.js';
16
+ import {
17
+ escapeFqName,
18
+ escapeQueryName,
19
+ isFqName,
20
+ makeFqName,
21
+ nameToPath,
22
+ splitFqName,
23
+ splitRefs,
24
+ } from '../../util.js';
17
25
  import { JoinInfo, Resolver } from '../interface.js';
18
26
  import { asTableReference } from './dbutil.js';
19
27
  import {
@@ -41,6 +49,7 @@ import { OpenAIEmbeddings } from '@langchain/openai';
41
49
  import { Embeddings } from '@langchain/core/embeddings';
42
50
  import { DeletedFlagAttributeName, ParentAttributeName, PathAttributeName } from '../../defs.js';
43
51
  import { logger } from '../../logger.js';
52
+ import { JoinSpec } from '../../../language/generated/ast.js';
44
53
 
45
54
  function maybeFindIdAttributeName(inst: Instance): string | undefined {
46
55
  const attrEntry: AttributeEntry | undefined = findIdAttribute(inst);
@@ -251,13 +260,18 @@ export class SqlDbResolver extends Resolver {
251
260
 
252
261
  public override async queryByJoin(
253
262
  inst: Instance,
254
- joinsSpec: JoinInfo[],
263
+ joinInfo: JoinInfo[],
255
264
  intoSpec: Map<string, string>,
256
- distinct: boolean = false
265
+ distinct: boolean = false,
266
+ rawJoinSpec?: JoinSpec
257
267
  ): Promise<any> {
258
268
  const tableName = asTableReference(inst.moduleName, inst.name);
259
269
  const joinClauses: JoinClause[] = [];
260
- this.processJoinInfo(tableName, inst, joinsSpec, joinClauses);
270
+ if (rawJoinSpec) {
271
+ this.processRawJoinSpec(tableName, inst, rawJoinSpec, joinClauses);
272
+ } else {
273
+ this.processJoinInfo(tableName, inst, joinInfo, joinClauses);
274
+ }
261
275
  intoSpec.forEach((v: string, k: string) => {
262
276
  const p = nameToPath(v);
263
277
  const mn = p.hasModule() ? p.getModuleName() : inst.moduleName;
@@ -275,13 +289,46 @@ export class SqlDbResolver extends Resolver {
275
289
  return rslt;
276
290
  }
277
291
 
292
+ private processRawJoinSpec(
293
+ tableName: string,
294
+ inst: Instance,
295
+ rawJoinSpec: JoinSpec,
296
+ joinClauses: JoinClause[]
297
+ ) {
298
+ const n = rawJoinSpec.name;
299
+ let joinTableName = '';
300
+ if (isFqName(n)) {
301
+ const parts = splitFqName(n);
302
+ joinTableName = asTableReference(parts[0], parts[1]);
303
+ } else {
304
+ joinTableName = asTableReference(inst.moduleName, n);
305
+ }
306
+ const refParts = splitRefs(rawJoinSpec.rhs);
307
+ if (refParts.length != 2) {
308
+ throw new Error(`Invalid join referene - ${rawJoinSpec.rhs}`);
309
+ }
310
+ if (refParts[0] !== inst.name) {
311
+ throw new Error(`Invalid table name in join reference - ${rawJoinSpec.rhs}`);
312
+ }
313
+ const joinOn = makeJoinOn(
314
+ `"${joinTableName}"."${escapeQueryName(rawJoinSpec.lhs)}"`,
315
+ `"${tableName}"."${escapeQueryName(refParts[1])}"`,
316
+ rawJoinSpec.op
317
+ );
318
+ joinClauses.push({
319
+ tableName: joinTableName,
320
+ joinOn: joinOn,
321
+ joinType: rawJoinSpec.type.substring(1).replace('_', ' '),
322
+ });
323
+ }
324
+
278
325
  private processJoinInfo(
279
326
  joinParentTable: string,
280
327
  joinInst: Instance,
281
- joinsSpec: JoinInfo[],
328
+ joinInfo: JoinInfo[],
282
329
  joinClauses: JoinClause[]
283
330
  ) {
284
- joinsSpec.forEach((ji: JoinInfo) => {
331
+ joinInfo.forEach((ji: JoinInfo) => {
285
332
  const rel: Relationship = ji.relationship;
286
333
  const joinTableName = asTableReference(ji.queryInstance.moduleName, ji.queryInstance.name);
287
334
  let joinOn: JoinOn | JoinOn[] | undefined;
@@ -1,7 +1,7 @@
1
1
  // Monarch syntax highlighting for the agentlang language.
2
2
  export default {
3
3
  keywords: [
4
- '@actions','@after','@as','@async','@before','@catch','@distinct','@enum','@expr','@from','@into','@meta','@oneof','@public','@rbac','@ref','@then','@upsert','@with_unique','agent','agentlang/retry','allow','and','attempts','await','backoff','between','case','commitTransaction','contains','create','decision','delete','directive','else','entity','error','event','extends','false','flow','for','glossaryEntry','if','import','in','like','module','not','not_found','onSubscription','or','purge','query','read','record','relationship','resolver','return','roles','rollbackTransaction','scenario','startTransaction','subscribe','true','update','upsert','where','workflow'
4
+ '@actions','@after','@as','@async','@before','@catch','@distinct','@enum','@expr','@from','@full_join','@inner_join','@into','@join','@left_join','@meta','@oneof','@public','@rbac','@ref','@right_join','@then','@upsert','@with_unique','agent','agentlang/retry','allow','and','attempts','await','backoff','between','case','commitTransaction','contains','create','decision','delete','directive','else','entity','error','event','extends','false','flow','for','glossaryEntry','if','import','in','like','module','not','not_found','onSubscription','or','purge','query','read','record','relationship','resolver','return','roles','rollbackTransaction','scenario','startTransaction','subscribe','true','update','upsert','where','workflow'
5
5
  ],
6
6
  operators: [
7
7
  '!=','*','+',',','-','-->','.','/',':',';','<','<=','<>','=','==','>','>=','?','@'