@an5/orm 1.3.2 → 1.3.3
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 +74 -11
- package/dist/cleanup.d.ts +1 -0
- package/dist/cleanup.js +177 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +147 -0
- package/dist/generator/src/attributes.d.ts +57 -0
- package/dist/generator/src/attributes.js +225 -0
- package/dist/generator/src/code-generator.js +49 -16
- package/dist/generator/src/config.d.ts +2 -2
- package/dist/generator/src/config.js +4 -3
- package/dist/generator/src/dialect.d.ts +52 -1
- package/dist/generator/src/dialect.js +136 -3
- package/dist/generator/src/dotnet-generator.js +67 -23
- package/dist/generator/src/golang-generator.d.ts +8 -0
- package/dist/generator/src/golang-generator.js +86 -21
- package/dist/generator/src/java-generator.d.ts +8 -0
- package/dist/generator/src/java-generator.js +108 -3
- package/dist/generator/src/kotlin-generator.js +51 -0
- package/dist/generator/src/parser.d.ts +1 -0
- package/dist/generator/src/parser.js +90 -35
- package/dist/generator/src/pull-schema.d.ts +54 -0
- package/dist/generator/src/pull-schema.js +116 -0
- package/dist/generator/src/push-apply.d.ts +4 -2
- package/dist/generator/src/push-apply.js +53 -4
- package/dist/generator/src/push-schema.d.ts +21 -9
- package/dist/generator/src/push-schema.js +132 -32
- package/dist/generator/src/python-generator.js +35 -22
- package/dist/generator/src/rust-generator.js +79 -74
- package/dist/generator/src/swift-generator.js +54 -3
- package/dist/generator/src/types.d.ts +10 -0
- package/dist/generator/src/view-order.d.ts +33 -0
- package/dist/generator/src/view-order.js +123 -0
- package/dist/migrate.d.ts +12 -0
- package/dist/migrate.js +442 -0
- package/dist/migration-core.d.ts +32 -3
- package/dist/migration-core.js +213 -43
- package/dist/provider-support.d.ts +0 -1
- package/dist/provider-support.js +0 -1
- package/dist/pull.d.ts +1 -0
- package/dist/pull.js +425 -0
- package/dist/push.d.ts +1 -0
- package/dist/push.js +93 -0
- package/dist/seed.d.ts +1 -0
- package/dist/seed.js +33 -0
- package/generator/src/attributes.ts +231 -0
- package/generator/src/code-generator.ts +47 -16
- package/generator/src/config.ts +5 -3
- package/generator/src/dialect.ts +188 -5
- package/generator/src/dotnet-generator.ts +68 -24
- package/generator/src/golang-generator.ts +89 -22
- package/generator/src/java-generator.ts +109 -3
- package/generator/src/kotlin-generator.ts +51 -0
- package/generator/src/parser.ts +102 -30
- package/generator/src/pull-schema.ts +127 -0
- package/generator/src/push-apply.ts +62 -5
- package/generator/src/push-schema.ts +160 -32
- package/generator/src/python-generator.ts +33 -20
- package/generator/src/rust-generator.ts +79 -73
- package/generator/src/swift-generator.ts +55 -4
- package/generator/src/types.ts +10 -0
- package/generator/src/view-order.ts +133 -0
- package/package.json +19 -14
|
@@ -143,27 +143,28 @@ namespace An5Orm
|
|
|
143
143
|
}
|
|
144
144
|
content += ` }\n\n`;
|
|
145
145
|
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
// Create / update / delete inputs describe writes; a view has none.
|
|
147
|
+
if (!model.isView) {
|
|
148
|
+
content += ` /// <summary>Typed data for creating a new ${name} record.</summary>\n`;
|
|
149
|
+
content += ` public class ${name}CreateInput\n {\n`;
|
|
150
|
+
for (const f of model.fields) {
|
|
151
|
+
if (!f.isId) {
|
|
152
|
+
const ct = this.mapType(f, f.isOptional || f.hasDefault);
|
|
153
|
+
content += ` public ${ct} ${this.capitalize(f.name)} { get; set; }\n`;
|
|
154
|
+
}
|
|
153
155
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
content += ` public ${ut} ${this.capitalize(f.name)} { get; set; }\n`;
|
|
156
|
+
content += ` }\n\n`;
|
|
157
|
+
|
|
158
|
+
content += ` /// <summary>Typed data for updating an existing ${name} record.</summary>\n`;
|
|
159
|
+
content += ` public class ${name}UpdateInput\n {\n`;
|
|
160
|
+
for (const f of model.fields) {
|
|
161
|
+
if (!f.isId) {
|
|
162
|
+
const ut = this.mapType(f, true); // always nullable for update
|
|
163
|
+
content += ` public ${ut} ${this.capitalize(f.name)} { get; set; }\n`;
|
|
164
|
+
}
|
|
164
165
|
}
|
|
166
|
+
content += ` }\n\n`;
|
|
165
167
|
}
|
|
166
|
-
content += ` }\n\n`;
|
|
167
168
|
|
|
168
169
|
// FindManyArgs
|
|
169
170
|
content += ` /// <summary>ORM-style args for ${name}.FindMany().</summary>\n`;
|
|
@@ -190,10 +191,12 @@ namespace An5Orm
|
|
|
190
191
|
content += ` }\n\n`;
|
|
191
192
|
|
|
192
193
|
// DeleteManyArgs
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
if (!model.isView) {
|
|
195
|
+
content += ` /// <summary>ORM-style args for ${name}.DeleteMany().</summary>\n`;
|
|
196
|
+
content += ` public class ${name}DeleteManyArgs\n {\n`;
|
|
197
|
+
content += ` public ${name}WhereInput Where { get; set; }\n`;
|
|
198
|
+
content += ` }\n\n`;
|
|
199
|
+
}
|
|
197
200
|
|
|
198
201
|
// CountArgs
|
|
199
202
|
content += ` /// <summary>ORM-style args for ${name}.Count().</summary>\n`;
|
|
@@ -494,8 +497,9 @@ namespace An5Orm
|
|
|
494
497
|
|
|
495
498
|
for (const model of models) {
|
|
496
499
|
const name = this.capitalize(model.name);
|
|
497
|
-
|
|
498
|
-
content += ` public
|
|
500
|
+
const client = model.isView ? 'ViewClient' : 'TableClient';
|
|
501
|
+
content += ` public ${client}<${model.name}> ${name}s => new ${client}<${model.name}>(ConnectionString, "${dottedTableName(model)}");\n`;
|
|
502
|
+
content += ` public ${client}<${model.name}> ${name} => ${name}s;\n`;
|
|
499
503
|
}
|
|
500
504
|
|
|
501
505
|
content += ` }
|
|
@@ -1190,6 +1194,46 @@ namespace An5Orm
|
|
|
1190
1194
|
return false;
|
|
1191
1195
|
}
|
|
1192
1196
|
}
|
|
1197
|
+
|
|
1198
|
+
/// <summary>
|
|
1199
|
+
/// Read-only client for a database view.
|
|
1200
|
+
///
|
|
1201
|
+
/// It exposes the same read methods as <see cref="TableClient{T}"/> and no
|
|
1202
|
+
/// write methods, so attempting to write a view fails at compile time rather
|
|
1203
|
+
/// than in the database. The view's rows still map onto the generated entity
|
|
1204
|
+
/// class, which is why the queries delegate instead of being duplicated.
|
|
1205
|
+
/// </summary>
|
|
1206
|
+
public class ViewClient<T> where T : new()
|
|
1207
|
+
{
|
|
1208
|
+
private readonly TableClient<T> _table;
|
|
1209
|
+
|
|
1210
|
+
public ViewClient(string connectionString, string viewName)
|
|
1211
|
+
{
|
|
1212
|
+
_table = new TableClient<T>(connectionString, viewName);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
public string ConnectionString => _table.ConnectionString;
|
|
1216
|
+
public string ViewName => _table.TableName;
|
|
1217
|
+
public An5Dialect Dialect => _table.Dialect;
|
|
1218
|
+
|
|
1219
|
+
public List<T> QueryRaw(string query, Dictionary<string, object> parameters = null)
|
|
1220
|
+
=> _table.QueryRaw(query, parameters);
|
|
1221
|
+
|
|
1222
|
+
public List<T> FindMany(string whereClause = null, Dictionary<string, object> parameters = null)
|
|
1223
|
+
=> _table.FindMany(whereClause, parameters);
|
|
1224
|
+
|
|
1225
|
+
public T FindFirst(string whereClause = null, Dictionary<string, object> parameters = null)
|
|
1226
|
+
=> _table.FindFirst(whereClause, parameters);
|
|
1227
|
+
|
|
1228
|
+
public T FindUnique(object id)
|
|
1229
|
+
=> _table.FindUnique(id);
|
|
1230
|
+
|
|
1231
|
+
public int Count(string whereClause = null, Dictionary<string, object> parameters = null)
|
|
1232
|
+
=> _table.Count(whereClause, parameters);
|
|
1233
|
+
|
|
1234
|
+
public List<T> VectorSearch(List<double> vector, int take = 10, string whereClause = null, Dictionary<string, object> parameters = null, string vectorField = "Embedding", string distanceMetric = "cosine")
|
|
1235
|
+
=> _table.VectorSearch(vector, take, whereClause, parameters, vectorField, distanceMetric);
|
|
1236
|
+
}
|
|
1193
1237
|
}
|
|
1194
1238
|
`;
|
|
1195
1239
|
|
|
@@ -185,6 +185,7 @@ func GetDefaultConnectionString() string {
|
|
|
185
185
|
content += this.generateBaseOrmTypes();
|
|
186
186
|
content += this.generateDbContext(models);
|
|
187
187
|
content += this.generateTableClientStruct();
|
|
188
|
+
content += this.generateViewClientStruct();
|
|
188
189
|
content += this.generateQueryAndScan();
|
|
189
190
|
content += this.generateWhereBuilder();
|
|
190
191
|
content += this.generateCrudMethods();
|
|
@@ -321,8 +322,9 @@ func SortOrderPtr(s SortOrder) *SortOrder { return &s }
|
|
|
321
322
|
|
|
322
323
|
for (const model of models) {
|
|
323
324
|
const name = this.capitalize(model.name);
|
|
324
|
-
|
|
325
|
-
content += `\t${name}
|
|
325
|
+
const client = model.isView ? 'ViewClient' : 'TableClient';
|
|
326
|
+
content += `\t${name}s *${client}[${model.name}]\n`;
|
|
327
|
+
content += `\t${name} *${client}[${model.name}]\n`;
|
|
326
328
|
}
|
|
327
329
|
|
|
328
330
|
content += `}
|
|
@@ -339,7 +341,8 @@ func NewAn5DbContextWithConnStr(db *sql.DB, connStr string) *An5DbContext {
|
|
|
339
341
|
`;
|
|
340
342
|
for (const model of models) {
|
|
341
343
|
const name = this.capitalize(model.name);
|
|
342
|
-
|
|
344
|
+
const client = model.isView ? 'NewViewClient' : 'NewTableClient';
|
|
345
|
+
content += `\tclient${name} := ${client}[${model.name}](db, "${dottedTableName(model)}", d)\n`;
|
|
343
346
|
content += `\tctx.${name}s = client${name}\n`;
|
|
344
347
|
content += `\tctx.${name} = client${name}\n`;
|
|
345
348
|
}
|
|
@@ -394,6 +397,67 @@ func (c *TableClient[T]) ph(n int) string {
|
|
|
394
397
|
\treturn "?"
|
|
395
398
|
}
|
|
396
399
|
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ─── ViewClient ────────────────────────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* The read-only client used for views.
|
|
407
|
+
*
|
|
408
|
+
* Go has no way to hide methods from an embedded value, so wrapping rather
|
|
409
|
+
* than embedding is what makes a write to a view a compile error. The read
|
|
410
|
+
* methods delegate, which keeps a view's rows scanning exactly like a table's.
|
|
411
|
+
*/
|
|
412
|
+
private generateViewClientStruct(): string {
|
|
413
|
+
return `// ViewClient provides read-only access to a database view.
|
|
414
|
+
type ViewClient[T any] struct {
|
|
415
|
+
\tDB *sql.DB
|
|
416
|
+
\tDialect Dialect
|
|
417
|
+
\tViewName string
|
|
418
|
+
\ttable *TableClient[T]
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// NewViewClient creates a typed read-only client for a view.
|
|
422
|
+
func NewViewClient[T any](db *sql.DB, viewName string, dialect Dialect) *ViewClient[T] {
|
|
423
|
+
\treturn &ViewClient[T]{
|
|
424
|
+
\t\tDB: db,
|
|
425
|
+
\t\tDialect: dialect,
|
|
426
|
+
\t\tViewName: viewName,
|
|
427
|
+
\t\ttable: NewTableClient[T](db, viewName, dialect),
|
|
428
|
+
\t}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// FindMany queries the view with ORM-style args.
|
|
432
|
+
func (c *ViewClient[T]) FindMany(ctx context.Context, args interface{}) ([]T, error) {
|
|
433
|
+
\treturn c.table.FindMany(ctx, args)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// FindFirst returns the first matching row of the view.
|
|
437
|
+
func (c *ViewClient[T]) FindFirst(ctx context.Context, args interface{}) (*T, error) {
|
|
438
|
+
\treturn c.table.FindFirst(ctx, args)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// FindUnique returns one row of the view by primary key.
|
|
442
|
+
func (c *ViewClient[T]) FindUnique(ctx context.Context, args interface{}) (*T, error) {
|
|
443
|
+
\treturn c.table.FindUnique(ctx, args)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Count returns how many rows of the view match.
|
|
447
|
+
func (c *ViewClient[T]) Count(ctx context.Context, args interface{}) (int64, error) {
|
|
448
|
+
\treturn c.table.Count(ctx, args)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// QueryRaw runs a raw SELECT against the view.
|
|
452
|
+
func (c *ViewClient[T]) QueryRaw(ctx context.Context, query string, args ...interface{}) ([]T, error) {
|
|
453
|
+
\treturn c.table.QueryRaw(ctx, query, args...)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// VectorSearch searches a vector column of the view.
|
|
457
|
+
func (c *ViewClient[T]) VectorSearch(ctx context.Context, vector []float64, opts ...VectorSearchOptions) ([]T, error) {
|
|
458
|
+
\treturn c.table.VectorSearch(ctx, vector, opts...)
|
|
459
|
+
}
|
|
460
|
+
|
|
397
461
|
`;
|
|
398
462
|
}
|
|
399
463
|
|
|
@@ -1339,25 +1403,26 @@ var _ = time.Time{}
|
|
|
1339
1403
|
}
|
|
1340
1404
|
content += `}\n\n`;
|
|
1341
1405
|
|
|
1342
|
-
// CreateInput
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1406
|
+
// CreateInput / UpdateInput / DeleteManyArgs describe writes; a view has none.
|
|
1407
|
+
if (!model.isView) {
|
|
1408
|
+
content += `// ${name}CreateInput holds data for creating a new ${name} record.\ntype ${name}CreateInput struct {\n`;
|
|
1409
|
+
for (const field of model.fields) {
|
|
1410
|
+
if (!field.isId) {
|
|
1411
|
+
const goType = this.mapGoType(field, field.isOptional || field.hasDefault);
|
|
1412
|
+
content += `\t${this.capitalize(field.name)} ${goType}\n`;
|
|
1413
|
+
}
|
|
1348
1414
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
content += `\t${this.capitalize(field.name)} ${goType}\n`;
|
|
1415
|
+
content += `}\n\n`;
|
|
1416
|
+
|
|
1417
|
+
content += `// ${name}UpdateInput holds data for updating an existing ${name} record.\ntype ${name}UpdateInput struct {\n`;
|
|
1418
|
+
for (const field of model.fields) {
|
|
1419
|
+
if (!field.isId) {
|
|
1420
|
+
const goType = this.mapGoType(field, true); // always pointer so we can detect zero
|
|
1421
|
+
content += `\t${this.capitalize(field.name)} ${goType}\n`;
|
|
1422
|
+
}
|
|
1358
1423
|
}
|
|
1424
|
+
content += `}\n\n`;
|
|
1359
1425
|
}
|
|
1360
|
-
content += `}\n\n`;
|
|
1361
1426
|
|
|
1362
1427
|
// FindManyArgs
|
|
1363
1428
|
content += `// ${name}FindManyArgs provides ORM-style args for FindMany queries on ${name}.\ntype ${name}FindManyArgs struct {\n`;
|
|
@@ -1381,9 +1446,11 @@ var _ = time.Time{}
|
|
|
1381
1446
|
content += `}\n\n`;
|
|
1382
1447
|
|
|
1383
1448
|
// DeleteManyArgs
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1449
|
+
if (!model.isView) {
|
|
1450
|
+
content += `// ${name}DeleteManyArgs provides ORM-style args for DeleteMany queries on ${name}.\ntype ${name}DeleteManyArgs struct {\n`;
|
|
1451
|
+
content += `\tWhere *${name}WhereInput\n`;
|
|
1452
|
+
content += `}\n\n`;
|
|
1453
|
+
}
|
|
1387
1454
|
|
|
1388
1455
|
// CountArgs
|
|
1389
1456
|
content += `// ${name}CountArgs provides ORM-style args for Count queries on ${name}.\ntype ${name}CountArgs struct {\n`;
|
|
@@ -1266,8 +1266,104 @@ public final class ModelClient<T> {
|
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
1268
|
|
|
1269
|
+
/**
|
|
1270
|
+
* The typed client used for views.
|
|
1271
|
+
*
|
|
1272
|
+
* <p>It repeats {@link ModelClient}'s read signatures and stops there: with no
|
|
1273
|
+
* {@code create}/{@code update}/{@code delete} to call, writing a view is a
|
|
1274
|
+
* compile error rather than a runtime refusal.
|
|
1275
|
+
*/
|
|
1276
|
+
private viewClientSource(): string {
|
|
1277
|
+
return `// This file is auto-generated. Do not edit directly.
|
|
1278
|
+
package an5.client;
|
|
1279
|
+
|
|
1280
|
+
import java.sql.SQLException;
|
|
1281
|
+
import java.util.ArrayList;
|
|
1282
|
+
import java.util.List;
|
|
1283
|
+
import java.util.Map;
|
|
1284
|
+
import java.util.function.Function;
|
|
1285
|
+
|
|
1286
|
+
import an5.adapters.An5Aggregate;
|
|
1287
|
+
import an5.adapters.An5GroupBy;
|
|
1288
|
+
import an5.adapters.An5Query;
|
|
1289
|
+
import an5.adapters.An5ViewClient;
|
|
1290
|
+
|
|
1291
|
+
/** A read-only, typed client for one database view. */
|
|
1292
|
+
public final class ViewClient<T> {
|
|
1293
|
+
|
|
1294
|
+
private final An5ViewClient view;
|
|
1295
|
+
private final Function<Map<String, Object>, T> fromRow;
|
|
1296
|
+
|
|
1297
|
+
ViewClient(An5ViewClient view, Function<Map<String, Object>, T> fromRow) {
|
|
1298
|
+
this.view = view;
|
|
1299
|
+
this.fromRow = fromRow;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/** The view name this client reads. */
|
|
1303
|
+
public String view() {
|
|
1304
|
+
return view.viewName();
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
public List<T> findMany() throws SQLException {
|
|
1308
|
+
return findMany(new An5Query());
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
public List<T> findMany(An5Query query) throws SQLException {
|
|
1312
|
+
return map(view.findMany(query));
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
public T findFirst(An5Query query) throws SQLException {
|
|
1316
|
+
return one(view.findFirst(query));
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
public T findFirst(Map<String, Object> where) throws SQLException {
|
|
1320
|
+
return one(view.findFirst(where));
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
public T findUnique(Map<String, Object> where) throws SQLException {
|
|
1324
|
+
return one(view.findUnique(where));
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
public long count(Map<String, Object> where) throws SQLException {
|
|
1328
|
+
return view.count(where);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
public Map<String, Object> aggregate(An5Aggregate aggregate) throws SQLException {
|
|
1332
|
+
return view.aggregate(aggregate);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
public List<Map<String, Object>> groupBy(An5GroupBy group) throws SQLException {
|
|
1336
|
+
return view.groupBy(group);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
public List<T> vectorSearch(
|
|
1340
|
+
double[] vector, Integer take, Map<String, Object> where, String vectorField, String metric)
|
|
1341
|
+
throws SQLException {
|
|
1342
|
+
return map(view.vectorSearch(vector, take, where, vectorField, metric));
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
public List<T> vectorSearch(double[] vector, Integer take) throws SQLException {
|
|
1346
|
+
return vectorSearch(vector, take, null, "embedding", "cosine");
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
private List<T> map(List<Map<String, Object>> rows) {
|
|
1350
|
+
List<T> mapped = new ArrayList<T>();
|
|
1351
|
+
for (Map<String, Object> row : rows) {
|
|
1352
|
+
mapped.add(fromRow.apply(row));
|
|
1353
|
+
}
|
|
1354
|
+
return mapped;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
private T one(Map<String, Object> row) {
|
|
1358
|
+
return row == null ? null : fromRow.apply(row);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
`;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1269
1364
|
private generateDbContext(models: Model[]): void {
|
|
1270
1365
|
fs.writeFileSync(path.join(this.outputDir, 'ModelClient.java'), this.modelClientSource());
|
|
1366
|
+
fs.writeFileSync(path.join(this.outputDir, 'ViewClient.java'), this.viewClientSource());
|
|
1271
1367
|
|
|
1272
1368
|
let content = `// This file is auto-generated. Do not edit directly.
|
|
1273
1369
|
package an5.client;
|
|
@@ -1299,7 +1395,7 @@ public class An5DbContext implements AutoCloseable {
|
|
|
1299
1395
|
const property = this.propertyNames(model.name)[0]!;
|
|
1300
1396
|
if (declared.has(property)) continue;
|
|
1301
1397
|
declared.add(property);
|
|
1302
|
-
content += ` private final ModelClient<${model.name}> ${property}Client;\n`;
|
|
1398
|
+
content += ` private final ${model.isView ? 'ViewClient' : 'ModelClient'}<${model.name}> ${property}Client;\n`;
|
|
1303
1399
|
}
|
|
1304
1400
|
|
|
1305
1401
|
content += `
|
|
@@ -1314,6 +1410,11 @@ public class An5DbContext implements AutoCloseable {
|
|
|
1314
1410
|
const property = this.propertyNames(model.name)[0]!;
|
|
1315
1411
|
if (assigned.has(property)) continue;
|
|
1316
1412
|
assigned.add(property);
|
|
1413
|
+
if (model.isView) {
|
|
1414
|
+
content += ` this.${property}Client = new ViewClient<${model.name}>(\n`;
|
|
1415
|
+
content += ` adapter.view("${model.name}"), ${model.name}::fromRow);\n`;
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1317
1418
|
content += ` this.${property}Client = new ModelClient<${model.name}>(\n`;
|
|
1318
1419
|
content += ` adapter.table("${model.name}"), ${model.name}::fromRow, ${model.name}::toValues);\n`;
|
|
1319
1420
|
}
|
|
@@ -1331,6 +1432,11 @@ public class An5DbContext implements AutoCloseable {
|
|
|
1331
1432
|
const property = this.propertyNames(model.name)[0]!;
|
|
1332
1433
|
if (wrapped.has(property)) continue;
|
|
1333
1434
|
wrapped.add(property);
|
|
1435
|
+
if (model.isView) {
|
|
1436
|
+
content += ` this.${property}Client = new ViewClient<${model.name}>(\n`;
|
|
1437
|
+
content += ` adapter.view("${model.name}"), ${model.name}::fromRow);\n`;
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1334
1440
|
content += ` this.${property}Client = new ModelClient<${model.name}>(\n`;
|
|
1335
1441
|
content += ` adapter.table("${model.name}"), ${model.name}::fromRow, ${model.name}::toValues);\n`;
|
|
1336
1442
|
}
|
|
@@ -1355,8 +1461,8 @@ public class An5DbContext implements AutoCloseable {
|
|
|
1355
1461
|
exposed.add(property);
|
|
1356
1462
|
const accessor = `get${this.capitalize(property)}`;
|
|
1357
1463
|
content += `
|
|
1358
|
-
/** Queries for \`${model.name}\`. */
|
|
1359
|
-
public ModelClient<${model.name}> ${accessor}() {
|
|
1464
|
+
/** ${model.isView ? 'Reads' : 'Queries'} for \`${model.name}\`. */
|
|
1465
|
+
public ${model.isView ? 'ViewClient' : 'ModelClient'}<${model.name}> ${accessor}() {
|
|
1360
1466
|
return this.${property}Client;
|
|
1361
1467
|
}
|
|
1362
1468
|
`;
|
|
@@ -704,6 +704,47 @@ class ModelClient<T : Any>(
|
|
|
704
704
|
): List<T> = delegate.vectorSearch(vector, take, filter, vectorField, metric).map(read)
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
+
/**
|
|
708
|
+
* A read-only, typed client for one view.
|
|
709
|
+
*
|
|
710
|
+
* It mirrors [ModelClient]'s read methods and stops there, so a write to a view does
|
|
711
|
+
* not compile. The runtime's read-only client does the actual work.
|
|
712
|
+
*/
|
|
713
|
+
class ViewClient<T : Any>(
|
|
714
|
+
private val delegate: an5.adapters.ViewClient,
|
|
715
|
+
private val read: (Row) -> T,
|
|
716
|
+
) {
|
|
717
|
+
|
|
718
|
+
/** The view this client reads. */
|
|
719
|
+
val name: String get() = delegate.name
|
|
720
|
+
|
|
721
|
+
fun findMany(): List<T> = delegate.findMany().map(read)
|
|
722
|
+
|
|
723
|
+
fun findMany(block: QueryBuilder.() -> Unit): List<T> = delegate.findMany(block).map(read)
|
|
724
|
+
|
|
725
|
+
fun findFirst(filter: Where): T? = delegate.findFirst(filter)?.let(read)
|
|
726
|
+
|
|
727
|
+
fun findUnique(filter: Where): T? = delegate.findUnique(filter)?.let(read)
|
|
728
|
+
|
|
729
|
+
fun count(filter: Where? = null): Long = delegate.count(filter)
|
|
730
|
+
|
|
731
|
+
/** One row of aggregate values, keyed \`_count\`, \`_sum_<field>\` and so on. */
|
|
732
|
+
fun aggregate(block: AggregateBuilder.() -> Unit): Row = delegate.aggregate(block)
|
|
733
|
+
|
|
734
|
+
/** One row per group, each carrying the group's count and aggregates. */
|
|
735
|
+
fun groupBy(vararg by: String, block: AggregateBuilder.() -> Unit = {}): List<Row> =
|
|
736
|
+
delegate.groupBy(*by, block = block)
|
|
737
|
+
|
|
738
|
+
/** The rows nearest [vector]. */
|
|
739
|
+
fun vectorSearch(
|
|
740
|
+
vector: DoubleArray,
|
|
741
|
+
take: Int = 10,
|
|
742
|
+
filter: Where? = null,
|
|
743
|
+
vectorField: String = "embedding",
|
|
744
|
+
metric: DistanceMetric = DistanceMetric.COSINE,
|
|
745
|
+
): List<T> = delegate.vectorSearch(vector, take, filter, vectorField, metric).map(read)
|
|
746
|
+
}
|
|
747
|
+
|
|
707
748
|
/**
|
|
708
749
|
* The AN5 entry point: one typed client per model, over one connection.
|
|
709
750
|
*
|
|
@@ -728,6 +769,16 @@ class An5Db internal constructor(private val an5: An5) : AutoCloseable {
|
|
|
728
769
|
const property = KotlinGenerator.toLowerCamelCase(model.name);
|
|
729
770
|
if (seen.has(property)) continue;
|
|
730
771
|
seen.add(property);
|
|
772
|
+
if (model.isView) {
|
|
773
|
+
content += ` /** Reads for \`${model.name}\`. */
|
|
774
|
+
`;
|
|
775
|
+
content += ` val ${property}: ViewClient<${model.name}> =
|
|
776
|
+
`;
|
|
777
|
+
content += ` ViewClient(an5.view("${model.name}"), ${model.name}::fromRow)
|
|
778
|
+
|
|
779
|
+
`;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
731
782
|
content += ` /** Queries for \`${model.name}\`. */
|
|
732
783
|
`;
|
|
733
784
|
content += ` val ${property}: ModelClient<${model.name}> =
|