@an5/orm 1.1.1 → 1.3.0
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 +5 -1
- package/dist/generator/src/api.d.ts +5 -0
- package/dist/generator/src/api.js +11 -1
- package/dist/generator/src/code-generator.js +12 -1
- package/dist/generator/src/code-request.d.ts +26 -0
- package/dist/generator/src/code-request.js +122 -0
- package/dist/generator/src/config.d.ts +18 -0
- package/dist/generator/src/config.js +24 -0
- package/dist/generator/src/dialect.js +4 -2
- package/dist/generator/src/dotnet-generator.js +97 -1
- package/dist/generator/src/field-types.js +3 -0
- package/dist/generator/src/golang-generator.d.ts +8 -0
- package/dist/generator/src/golang-generator.js +183 -9
- package/dist/generator/src/index.js +18 -0
- package/dist/generator/src/java-generator.d.ts +56 -0
- package/dist/generator/src/java-generator.js +1406 -0
- package/dist/generator/src/kotlin-generator.d.ts +34 -0
- package/dist/generator/src/kotlin-generator.js +753 -0
- package/dist/generator/src/metadata-generator.js +3 -1
- package/dist/generator/src/rust-generator.js +36 -1
- package/dist/generator/src/swift-generator.d.ts +42 -0
- package/dist/generator/src/swift-generator.js +768 -0
- package/dist/migration-core.d.ts +1 -1
- package/dist/migration-core.js +6 -4
- package/generator/src/api.ts +5 -0
- package/generator/src/code-generator.ts +13 -1
- package/generator/src/code-request.ts +95 -0
- package/generator/src/config.ts +48 -0
- package/generator/src/dialect.ts +5 -3
- package/generator/src/dotnet-generator.ts +97 -1
- package/generator/src/field-types.ts +3 -0
- package/generator/src/golang-generator.ts +184 -9
- package/generator/src/index.ts +21 -0
- package/generator/src/java-generator.ts +1457 -0
- package/generator/src/kotlin-generator.ts +798 -0
- package/generator/src/metadata-generator.ts +2 -1
- package/generator/src/rust-generator.ts +37 -1
- package/generator/src/swift-generator.ts +830 -0
- package/package.json +2 -2
|
@@ -186,6 +186,7 @@ func GetDefaultConnectionString() string {
|
|
|
186
186
|
content += this.generateWhereBuilder();
|
|
187
187
|
content += this.generateCrudMethods();
|
|
188
188
|
content += this.generateVectorSearch();
|
|
189
|
+
content += this.generateVectorHelpers();
|
|
189
190
|
content += this.generateHelpers();
|
|
190
191
|
// NOTE: per-model ORM types live in each `<Model>.go` file.
|
|
191
192
|
fs_1.default.writeFileSync(path_1.default.join(this.outputDir, 'client.go'), content);
|
|
@@ -401,9 +402,18 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
|
|
|
401
402
|
\t\tvar item T
|
|
402
403
|
\t\tval := reflect.ValueOf(&item).Elem()
|
|
403
404
|
\t\tscanArgs := make([]interface{}, len(cols))
|
|
405
|
+
\t\tvectorCols := make([]bool, len(cols))
|
|
404
406
|
\t\tfor i, colName := range cols {
|
|
405
407
|
\t\t\tfieldVal := findFieldByDbTag(val, colName)
|
|
406
408
|
\t\t\tif fieldVal.IsValid() && fieldVal.CanSet() {
|
|
409
|
+
\t\t\t\t// A \`VECTOR(n)\` column is stored as bytes, which database/sql cannot
|
|
410
|
+
\t\t\t\t// scan into a slice, so it is read as bytes and decoded afterwards.
|
|
411
|
+
\t\t\t\tif isVectorGoType(fieldVal.Type()) {
|
|
412
|
+
\t\t\t\t\tvar raw []byte
|
|
413
|
+
\t\t\t\t\tscanArgs[i] = &raw
|
|
414
|
+
\t\t\t\t\tvectorCols[i] = true
|
|
415
|
+
\t\t\t\t\tcontinue
|
|
416
|
+
\t\t\t\t}
|
|
407
417
|
\t\t\t\tscanArgs[i] = fieldVal.Addr().Interface()
|
|
408
418
|
\t\t\t} else {
|
|
409
419
|
\t\t\t\tvar unused interface{}
|
|
@@ -413,6 +423,20 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
|
|
|
413
423
|
\t\tif err := rows.Scan(scanArgs...); err != nil {
|
|
414
424
|
\t\t\treturn nil, err
|
|
415
425
|
\t\t}
|
|
426
|
+
\t\tfor i, isVector := range vectorCols {
|
|
427
|
+
\t\t\tif !isVector {
|
|
428
|
+
\t\t\t\tcontinue
|
|
429
|
+
\t\t\t}
|
|
430
|
+
\t\t\tdecoded, ok := decodeVectorBytes(*(scanArgs[i].(*[]byte)))
|
|
431
|
+
\t\t\tif !ok {
|
|
432
|
+
\t\t\t\tcontinue
|
|
433
|
+
\t\t\t}
|
|
434
|
+
\t\t\tfieldVal := findFieldByDbTag(val, cols[i])
|
|
435
|
+
\t\t\tif !fieldVal.IsValid() || !fieldVal.CanSet() {
|
|
436
|
+
\t\t\t\tcontinue
|
|
437
|
+
\t\t\t}
|
|
438
|
+
\t\t\tsetVectorField(fieldVal, decoded)
|
|
439
|
+
\t\t}
|
|
416
440
|
\t\tresults = append(results, item)
|
|
417
441
|
\t}
|
|
418
442
|
\tif err := rows.Err(); err != nil {
|
|
@@ -421,6 +445,160 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
|
|
|
421
445
|
\treturn results, nil
|
|
422
446
|
}
|
|
423
447
|
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* The vector codec the generated client needs on both sides of a query.
|
|
452
|
+
*
|
|
453
|
+
* A \`VECTOR(n)\` column stores float32 bytes, which is a third of the JSON text it
|
|
454
|
+
* replaces and is what the in-database distance functions read. A column written
|
|
455
|
+
* before that encoding holds JSON text, so both are still accepted on the way in.
|
|
456
|
+
*/
|
|
457
|
+
generateVectorHelpers() {
|
|
458
|
+
return `// bytesPerVectorFloat is the width of one stored vector component.
|
|
459
|
+
const bytesPerVectorFloat = 4
|
|
460
|
+
|
|
461
|
+
// isVectorGoType reports whether a model field is a vector column, which the
|
|
462
|
+
// generator declares as a float32 slice, optionally behind a pointer because the
|
|
463
|
+
// column is nullable.
|
|
464
|
+
func isVectorGoType(t reflect.Type) bool {
|
|
465
|
+
\treturn vectorSliceType(t) != nil
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// vectorSliceType returns the slice type a vector field holds, or nil for any
|
|
469
|
+
// other field. A pointer to a slice is unwrapped so both the nullable and the
|
|
470
|
+
// required spelling are recognised.
|
|
471
|
+
func vectorSliceType(t reflect.Type) reflect.Type {
|
|
472
|
+
\tif t.Kind() == reflect.Ptr {
|
|
473
|
+
\t\tt = t.Elem()
|
|
474
|
+
\t}
|
|
475
|
+
\tif t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Float32 {
|
|
476
|
+
\t\treturn t
|
|
477
|
+
\t}
|
|
478
|
+
\treturn nil
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// vectorComponents reads a vector field as float64 components.
|
|
482
|
+
//
|
|
483
|
+
// A field can arrive as JSON text, as a slice, or behind a pointer to either, and a
|
|
484
|
+
// pointer is how the generator spells a nullable column. Anything else is not a vector.
|
|
485
|
+
func vectorComponents(field reflect.Value) []float64 {
|
|
486
|
+
\tfor field.Kind() == reflect.Ptr {
|
|
487
|
+
\t\tif field.IsNil() {
|
|
488
|
+
\t\t\treturn nil
|
|
489
|
+
\t\t}
|
|
490
|
+
\t\tfield = field.Elem()
|
|
491
|
+
\t}
|
|
492
|
+
\tswitch {
|
|
493
|
+
\tcase field.Kind() == reflect.String:
|
|
494
|
+
\t\tvar parsed []float64
|
|
495
|
+
\t\tif err := json.Unmarshal([]byte(field.String()), &parsed); err != nil {
|
|
496
|
+
\t\t\treturn nil
|
|
497
|
+
\t\t}
|
|
498
|
+
\t\treturn parsed
|
|
499
|
+
\tcase field.Kind() == reflect.Slice:
|
|
500
|
+
\t\tout := make([]float64, field.Len())
|
|
501
|
+
\t\tfor i := range out {
|
|
502
|
+
\t\t\tout[i] = field.Index(i).Float()
|
|
503
|
+
\t\t}
|
|
504
|
+
\t\treturn out
|
|
505
|
+
\tdefault:
|
|
506
|
+
\t\treturn nil
|
|
507
|
+
\t}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// setVectorField stores decoded components into a vector field, matching whether
|
|
511
|
+
// the field holds the slice directly or behind a pointer.
|
|
512
|
+
func setVectorField(field reflect.Value, values []float64) {
|
|
513
|
+
\tsliceType := vectorSliceType(field.Type())
|
|
514
|
+
\tif sliceType == nil {
|
|
515
|
+
\t\treturn
|
|
516
|
+
\t}
|
|
517
|
+
\tslice := reflect.MakeSlice(sliceType, len(values), len(values))
|
|
518
|
+
\tfor i, value := range values {
|
|
519
|
+
\t\tslice.Index(i).SetFloat(value)
|
|
520
|
+
\t}
|
|
521
|
+
\tif field.Kind() == reflect.Ptr {
|
|
522
|
+
\t\tptr := reflect.New(sliceType)
|
|
523
|
+
\t\tptr.Elem().Set(slice)
|
|
524
|
+
\t\tfield.Set(ptr)
|
|
525
|
+
\t\treturn
|
|
526
|
+
\t}
|
|
527
|
+
\tfield.Set(slice)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// encodeVectorArg converts a vector field into the bytes the column stores.
|
|
531
|
+
//
|
|
532
|
+
// database/sql accepts only a fixed set of types, so a slice otherwise reaches the
|
|
533
|
+
// driver as an error. Values that already arrived as stored form pass through.
|
|
534
|
+
func encodeVectorArg(value interface{}) interface{} {
|
|
535
|
+
\tswitch v := value.(type) {
|
|
536
|
+
\tcase *[]float32:
|
|
537
|
+
\t\tif v == nil {
|
|
538
|
+
\t\t\treturn nil
|
|
539
|
+
\t\t}
|
|
540
|
+
\t\treturn encodeVectorFloats(floatsFrom32(*v))
|
|
541
|
+
\tcase []float32:
|
|
542
|
+
\t\treturn encodeVectorFloats(floatsFrom32(v))
|
|
543
|
+
\tcase *[]float64:
|
|
544
|
+
\t\tif v == nil {
|
|
545
|
+
\t\t\treturn nil
|
|
546
|
+
\t\t}
|
|
547
|
+
\t\treturn encodeVectorFloats(*v)
|
|
548
|
+
\tcase []float64:
|
|
549
|
+
\t\treturn encodeVectorFloats(v)
|
|
550
|
+
\tdefault:
|
|
551
|
+
\t\treturn value
|
|
552
|
+
\t}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
func floatsFrom32(values []float32) []float64 {
|
|
556
|
+
\tout := make([]float64, len(values))
|
|
557
|
+
\tfor i, value := range values {
|
|
558
|
+
\t\tout[i] = float64(value)
|
|
559
|
+
\t}
|
|
560
|
+
\treturn out
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// encodeVectorFloats writes float32 values little-endian, which is the order the
|
|
564
|
+
// stored bytes are read back in.
|
|
565
|
+
func encodeVectorFloats(values []float64) []byte {
|
|
566
|
+
\tout := make([]byte, len(values)*bytesPerVectorFloat)
|
|
567
|
+
\tfor i, value := range values {
|
|
568
|
+
\t\toffset := i * bytesPerVectorFloat
|
|
569
|
+
\t\tbits := math.Float32bits(float32(value))
|
|
570
|
+
\t\tout[offset] = byte(bits)
|
|
571
|
+
\t\tout[offset+1] = byte(bits >> 8)
|
|
572
|
+
\t\tout[offset+2] = byte(bits >> 16)
|
|
573
|
+
\t\tout[offset+3] = byte(bits >> 24)
|
|
574
|
+
\t}
|
|
575
|
+
\treturn out
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// decodeVectorBytes reads a stored vector: float32 bytes, legacy JSON text, or
|
|
579
|
+
// nothing at all.
|
|
580
|
+
func decodeVectorBytes(raw []byte) ([]float64, bool) {
|
|
581
|
+
\tif len(raw) == 0 {
|
|
582
|
+
\t\treturn nil, false
|
|
583
|
+
\t}
|
|
584
|
+
\t// JSON text is not a multiple of four bytes once encoded and starts with '[',
|
|
585
|
+
\t// so the two are told apart by length and first byte.
|
|
586
|
+
\tif raw[0] == '[' || len(raw)%bytesPerVectorFloat != 0 {
|
|
587
|
+
\t\tvar parsed []float64
|
|
588
|
+
\t\tif err := json.Unmarshal(raw, &parsed); err != nil || len(parsed) == 0 {
|
|
589
|
+
\t\t\treturn nil, false
|
|
590
|
+
\t\t}
|
|
591
|
+
\t\treturn parsed, true
|
|
592
|
+
\t}
|
|
593
|
+
\tout := make([]float64, len(raw)/bytesPerVectorFloat)
|
|
594
|
+
\tfor i := range out {
|
|
595
|
+
\t\toffset := i * bytesPerVectorFloat
|
|
596
|
+
\t\tbits := uint32(raw[offset]) | uint32(raw[offset+1])<<8 | uint32(raw[offset+2])<<16 | uint32(raw[offset+3])<<24
|
|
597
|
+
\t\tout[i] = float64(math.Float32frombits(bits))
|
|
598
|
+
\t}
|
|
599
|
+
\treturn out, true
|
|
600
|
+
}
|
|
601
|
+
|
|
424
602
|
`;
|
|
425
603
|
}
|
|
426
604
|
// ─── WHERE builder ─────────────────────────────────────────────────────────
|
|
@@ -637,7 +815,7 @@ func (c *TableClient[T]) Create(ctx context.Context, entity *T) (*T, error) {
|
|
|
637
815
|
\t\tfieldVal := val.Field(i)
|
|
638
816
|
\t\tif !fieldVal.IsZero() {
|
|
639
817
|
\t\t\tcols = append(cols, c.quoteName(dbTag))
|
|
640
|
-
\t\t\targs = append(args, fieldVal.Interface())
|
|
818
|
+
\t\t\targs = append(args, encodeVectorArg(fieldVal.Interface()))
|
|
641
819
|
\t\t\tphs = append(phs, c.ph(len(args)))
|
|
642
820
|
\t\t}
|
|
643
821
|
\t}
|
|
@@ -684,7 +862,7 @@ func (c *TableClient[T]) Update(ctx context.Context, entity *T) (*T, error) {
|
|
|
684
862
|
\t\t\tidVal = fieldVal.Interface()
|
|
685
863
|
\t\t\tidCol = dbTag
|
|
686
864
|
\t\t} else if !fieldVal.IsZero() {
|
|
687
|
-
\t\t\targs = append(args, fieldVal.Interface())
|
|
865
|
+
\t\t\targs = append(args, encodeVectorArg(fieldVal.Interface()))
|
|
688
866
|
\t\t\tsets = append(sets, c.quoteName(dbTag)+" = "+c.ph(len(args)))
|
|
689
867
|
\t\t}
|
|
690
868
|
\t}
|
|
@@ -1031,13 +1209,9 @@ func (c *TableClient[T]) VectorSearch(ctx context.Context, vector []float64, opt
|
|
|
1031
1209
|
\t\t\tcontinue
|
|
1032
1210
|
\t\t}
|
|
1033
1211
|
|
|
1034
|
-
\t\
|
|
1035
|
-
\t\tif
|
|
1036
|
-
\t\t\
|
|
1037
|
-
\t\t} else if fieldVal.Kind() == reflect.Slice {
|
|
1038
|
-
\t\t\tfor i := 0; i < fieldVal.Len(); i++ {
|
|
1039
|
-
\t\t\t\trowVec = append(rowVec, fieldVal.Index(i).Float())
|
|
1040
|
-
\t\t\t}
|
|
1212
|
+
\t\trowVec := vectorComponents(fieldVal)
|
|
1213
|
+
\t\tif len(rowVec) == 0 {
|
|
1214
|
+
\t\t\tcontinue
|
|
1041
1215
|
\t\t}
|
|
1042
1216
|
\t\tif len(rowVec) != len(vector) {
|
|
1043
1217
|
\t\t\tcontinue
|
|
@@ -13,6 +13,9 @@ const python_generator_1 = require("./python-generator");
|
|
|
13
13
|
const dotnet_generator_1 = require("./dotnet-generator");
|
|
14
14
|
const golang_generator_1 = require("./golang-generator");
|
|
15
15
|
const rust_generator_1 = require("./rust-generator");
|
|
16
|
+
const java_generator_1 = require("./java-generator");
|
|
17
|
+
const kotlin_generator_1 = require("./kotlin-generator");
|
|
18
|
+
const swift_generator_1 = require("./swift-generator");
|
|
16
19
|
const fs_1 = __importDefault(require("fs"));
|
|
17
20
|
function clearGeneratedFiles(outputDir, extension) {
|
|
18
21
|
if (!fs_1.default.existsSync(outputDir)) {
|
|
@@ -70,6 +73,9 @@ async function main() {
|
|
|
70
73
|
const outputDotnetDir = config.outputs.dotnetDir;
|
|
71
74
|
const outputGolangDir = config.outputs.golangDir;
|
|
72
75
|
const outputRustDir = config.outputs.rustDir;
|
|
76
|
+
const outputJavaDir = config.outputs.javaDir;
|
|
77
|
+
const outputKotlinDir = config.outputs.kotlinDir;
|
|
78
|
+
const outputSwiftDir = config.outputs.swiftDir;
|
|
73
79
|
const generateMetadata = config.config.generation.generateMetadata;
|
|
74
80
|
console.log('🚀 Starting ORM generation...');
|
|
75
81
|
// The target provider comes from the connection string: both the allowed
|
|
@@ -81,6 +87,9 @@ async function main() {
|
|
|
81
87
|
clearGeneratedFiles(outputDotnetDir, '.cs');
|
|
82
88
|
clearGeneratedFiles(outputGolangDir, '.go');
|
|
83
89
|
clearGeneratedFiles(path_1.default.join(outputRustDir, 'src'), '.rs');
|
|
90
|
+
clearGeneratedFiles(outputJavaDir, '.java');
|
|
91
|
+
clearGeneratedFiles(outputKotlinDir, '.kt');
|
|
92
|
+
clearGeneratedFiles(path_1.default.join(outputSwiftDir, 'Sources'), '.swift');
|
|
84
93
|
const pythonDirEarly = path_1.default.dirname(outputPythonMetadataPath);
|
|
85
94
|
clearGeneratedPythonFiles(pythonDirEarly);
|
|
86
95
|
if (config.config.generation.generateMetadata && fs_1.default.existsSync(outputMetadataPath)) {
|
|
@@ -120,6 +129,15 @@ async function main() {
|
|
|
120
129
|
const rustGen = new rust_generator_1.RustGenerator(outputRustDir);
|
|
121
130
|
rustGen.generate(models);
|
|
122
131
|
console.log(`✨ Generated Rust client in ${outputRustDir}`);
|
|
132
|
+
const javaGen = new java_generator_1.JavaGenerator(outputJavaDir);
|
|
133
|
+
javaGen.generate(models);
|
|
134
|
+
console.log(`✨ Generated Java client in ${outputJavaDir}`);
|
|
135
|
+
const kotlinGen = new kotlin_generator_1.KotlinGenerator(outputKotlinDir);
|
|
136
|
+
kotlinGen.generate(models);
|
|
137
|
+
console.log(`✨ Generated Kotlin client in ${outputKotlinDir}`);
|
|
138
|
+
const swiftGen = new swift_generator_1.SwiftGenerator(outputSwiftDir);
|
|
139
|
+
swiftGen.generate(models);
|
|
140
|
+
console.log(`✨ Generated Swift client in ${outputSwiftDir}`);
|
|
123
141
|
console.log('✅ ORM generation completed successfully.');
|
|
124
142
|
}
|
|
125
143
|
catch (error) {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Model } from './types';
|
|
2
|
+
export declare class JavaGenerator {
|
|
3
|
+
private outputDir;
|
|
4
|
+
/** The database being generated for; decides types like `TIMESTAMP`. */
|
|
5
|
+
private provider;
|
|
6
|
+
constructor(outputDir: string);
|
|
7
|
+
generate(models: Model[]): void;
|
|
8
|
+
private capitalize;
|
|
9
|
+
/**
|
|
10
|
+
* The Java field name for a schema column.
|
|
11
|
+
*
|
|
12
|
+
* A field named `class`, `new` or `from` would not compile as an identifier, so those are
|
|
13
|
+
* suffixed rather than left to fail at the call site.
|
|
14
|
+
*/
|
|
15
|
+
private fieldName;
|
|
16
|
+
private getterName;
|
|
17
|
+
private static toCamelCase;
|
|
18
|
+
private static toLowerCamelCase;
|
|
19
|
+
/**
|
|
20
|
+
* Every spelling of a model name the client registers it under.
|
|
21
|
+
*
|
|
22
|
+
* The metadata is keyed in camelCase while the model class is PascalCase, and the adapter
|
|
23
|
+
* resolves the difference; these are the names the context exposes so either spelling
|
|
24
|
+
* works at the call site.
|
|
25
|
+
*/
|
|
26
|
+
private propertyNames;
|
|
27
|
+
/**
|
|
28
|
+
* The Java type for a column.
|
|
29
|
+
*
|
|
30
|
+
* Driven by the declared type, so a `DECIMAL` column is a `BigDecimal` and not an `int`:
|
|
31
|
+
* both arrive from the parser as `number`, which is how the earlier clients turned
|
|
32
|
+
* decimal money into integers.
|
|
33
|
+
*/
|
|
34
|
+
private mapType;
|
|
35
|
+
/**
|
|
36
|
+
* The boxed form used in a model.
|
|
37
|
+
*
|
|
38
|
+
* Every field is boxed, primitives included: `toValues` skips the columns left null, and a
|
|
39
|
+
* column the schema does not mark optional can still read NULL until a row is written. A
|
|
40
|
+
* primitive cannot represent "unset", so the skip would have to give the column a default
|
|
41
|
+
* and quietly write it.
|
|
42
|
+
*/
|
|
43
|
+
private mapNullableType;
|
|
44
|
+
private getFilterType;
|
|
45
|
+
private generateModels;
|
|
46
|
+
private modelSource;
|
|
47
|
+
/** The expression that reads one column into its field. */
|
|
48
|
+
private readExpression;
|
|
49
|
+
private generateValues;
|
|
50
|
+
private generateOrmTypes;
|
|
51
|
+
private generateMetadata;
|
|
52
|
+
private generateConfig;
|
|
53
|
+
private modelClientSource;
|
|
54
|
+
private generateDbContext;
|
|
55
|
+
private tableName;
|
|
56
|
+
}
|