@aigne/afs-sqlite 1.11.0-beta.6 → 1.11.0-beta.7
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/dist/actions/built-in.cjs +870 -16
- package/dist/actions/built-in.d.cts.map +1 -1
- package/dist/actions/built-in.d.mts.map +1 -1
- package/dist/actions/built-in.mjs +870 -16
- package/dist/actions/built-in.mjs.map +1 -1
- package/dist/actions/operators.cjs +156 -0
- package/dist/actions/operators.mjs +157 -0
- package/dist/actions/operators.mjs.map +1 -0
- package/dist/actions/types.cjs +33 -0
- package/dist/actions/types.d.cts +22 -0
- package/dist/actions/types.d.cts.map +1 -1
- package/dist/actions/types.d.mts +22 -0
- package/dist/actions/types.d.mts.map +1 -1
- package/dist/actions/types.mjs +32 -0
- package/dist/actions/types.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/node/builder.cjs +11 -8
- package/dist/node/builder.d.cts.map +1 -1
- package/dist/node/builder.d.mts.map +1 -1
- package/dist/node/builder.mjs +11 -8
- package/dist/node/builder.mjs.map +1 -1
- package/dist/operations/query-builder.cjs +2 -2
- package/dist/operations/query-builder.d.cts.map +1 -1
- package/dist/operations/query-builder.d.mts.map +1 -1
- package/dist/operations/query-builder.mjs +2 -2
- package/dist/operations/query-builder.mjs.map +1 -1
- package/dist/operations/search.cjs +1 -1
- package/dist/operations/search.mjs +1 -1
- package/dist/operations/search.mjs.map +1 -1
- package/dist/sqlite-afs.cjs +247 -26
- package/dist/sqlite-afs.d.cts +29 -4
- package/dist/sqlite-afs.d.cts.map +1 -1
- package/dist/sqlite-afs.d.mts +29 -4
- package/dist/sqlite-afs.d.mts.map +1 -1
- package/dist/sqlite-afs.mjs +248 -27
- package/dist/sqlite-afs.mjs.map +1 -1
- package/package.json +4 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const require_operators = require('./operators.cjs');
|
|
2
|
+
const require_types = require('./types.cjs');
|
|
1
3
|
let _aigne_sqlite = require("@aigne/sqlite");
|
|
2
4
|
|
|
3
5
|
//#region src/actions/built-in.ts
|
|
@@ -14,6 +16,18 @@ async function execRun(db, query) {
|
|
|
14
16
|
await db.run(_aigne_sqlite.sql.raw(query)).execute();
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
19
|
+
* Executes a parameterized SQL query and returns all rows
|
|
20
|
+
*/
|
|
21
|
+
async function execSql(db, sqlQuery) {
|
|
22
|
+
return db.all(sqlQuery).execute();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Executes a parameterized SQL statement (for INSERT, UPDATE, DELETE)
|
|
26
|
+
*/
|
|
27
|
+
async function runSql(db, sqlQuery) {
|
|
28
|
+
return { changes: (await db.run(sqlQuery).execute()).rowsAffected };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
17
31
|
* Registers built-in actions to the registry
|
|
18
32
|
*/
|
|
19
33
|
function registerBuiltInActions(registry) {
|
|
@@ -129,7 +143,49 @@ function registerBuiltInActions(registry) {
|
|
|
129
143
|
}
|
|
130
144
|
});
|
|
131
145
|
registry.register({
|
|
132
|
-
name: "
|
|
146
|
+
name: "insert",
|
|
147
|
+
description: "Insert a new row into the table",
|
|
148
|
+
rootLevel: false,
|
|
149
|
+
tableLevel: true,
|
|
150
|
+
rowLevel: false,
|
|
151
|
+
inputSchemaGenerator: (schemaCtx) => {
|
|
152
|
+
if (!schemaCtx.tableSchema) return {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: { data: {
|
|
155
|
+
type: "object",
|
|
156
|
+
description: "Row data to insert (column names as keys)",
|
|
157
|
+
additionalProperties: true
|
|
158
|
+
} },
|
|
159
|
+
required: ["data"]
|
|
160
|
+
};
|
|
161
|
+
return generateInsertSchema(schemaCtx.tableSchema);
|
|
162
|
+
},
|
|
163
|
+
handler: async (ctx, params) => {
|
|
164
|
+
const data = params.data;
|
|
165
|
+
if (!data || Object.keys(data).length === 0) throw new Error("Insert data is required");
|
|
166
|
+
if (!await ctx.schemaService.getSchema(ctx.table)) throw new Error(`Table '${ctx.table}' not found`);
|
|
167
|
+
const columns = Object.keys(data);
|
|
168
|
+
const values = columns.map((col) => formatValueForSQL(data[col]));
|
|
169
|
+
const insertSQL = `INSERT INTO "${ctx.table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`;
|
|
170
|
+
try {
|
|
171
|
+
await execRun(ctx.db, insertSQL);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
174
|
+
throw new Error(`Insert failed: ${message}`);
|
|
175
|
+
}
|
|
176
|
+
const newId = (await execAll(ctx.db, "SELECT last_insert_rowid() as id"))[0]?.id;
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
data: {
|
|
180
|
+
id: newId,
|
|
181
|
+
...data
|
|
182
|
+
},
|
|
183
|
+
message: `Row inserted successfully with id ${newId}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
registry.register({
|
|
188
|
+
name: "create_table",
|
|
133
189
|
description: "Create a new table in the database",
|
|
134
190
|
rootLevel: true,
|
|
135
191
|
tableLevel: false,
|
|
@@ -387,44 +443,758 @@ function registerBuiltInActions(registry) {
|
|
|
387
443
|
}
|
|
388
444
|
});
|
|
389
445
|
registry.register({
|
|
390
|
-
name: "
|
|
391
|
-
description: "
|
|
446
|
+
name: "drop_table",
|
|
447
|
+
description: "Drop a table from the database",
|
|
448
|
+
rootLevel: true,
|
|
449
|
+
tableLevel: false,
|
|
450
|
+
rowLevel: false,
|
|
451
|
+
inputSchema: {
|
|
452
|
+
type: "object",
|
|
453
|
+
properties: {
|
|
454
|
+
name: {
|
|
455
|
+
type: "string",
|
|
456
|
+
description: "Table name to drop"
|
|
457
|
+
},
|
|
458
|
+
ifExists: {
|
|
459
|
+
type: "boolean",
|
|
460
|
+
default: false,
|
|
461
|
+
description: "Don't error if table doesn't exist"
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
required: ["name"]
|
|
465
|
+
},
|
|
466
|
+
handler: async (ctx, params) => {
|
|
467
|
+
const tableName = params.name;
|
|
468
|
+
const ifExists = params.ifExists;
|
|
469
|
+
if (!tableName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Table name is required");
|
|
470
|
+
const exists = await ctx.schemaService.hasTable(tableName);
|
|
471
|
+
if (!exists && !ifExists) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${tableName}' does not exist`);
|
|
472
|
+
if (exists) {
|
|
473
|
+
const ifExistsClause = ifExists ? "IF EXISTS " : "";
|
|
474
|
+
await execRun(ctx.db, `DROP TABLE ${ifExistsClause}"${tableName}"`);
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
data: { tableName },
|
|
479
|
+
message: `Table '${tableName}' dropped successfully`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
registry.register({
|
|
484
|
+
name: "rename_table",
|
|
485
|
+
description: "Rename a table in the database",
|
|
486
|
+
rootLevel: true,
|
|
487
|
+
tableLevel: false,
|
|
488
|
+
rowLevel: false,
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: "object",
|
|
491
|
+
properties: {
|
|
492
|
+
oldName: {
|
|
493
|
+
type: "string",
|
|
494
|
+
description: "Current table name"
|
|
495
|
+
},
|
|
496
|
+
newName: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "New table name"
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
required: ["oldName", "newName"]
|
|
502
|
+
},
|
|
503
|
+
handler: async (ctx, params) => {
|
|
504
|
+
const oldName = params.oldName;
|
|
505
|
+
const newName = params.newName;
|
|
506
|
+
if (!oldName || !newName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
|
|
507
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(newName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "New table name must start with a letter and contain only alphanumeric characters and underscores");
|
|
508
|
+
if (!await ctx.schemaService.hasTable(oldName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${oldName}' does not exist`);
|
|
509
|
+
if (await ctx.schemaService.hasTable(newName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Table '${newName}' already exists`);
|
|
510
|
+
await execRun(ctx.db, `ALTER TABLE "${oldName}" RENAME TO "${newName}"`);
|
|
511
|
+
return {
|
|
512
|
+
success: true,
|
|
513
|
+
data: {
|
|
514
|
+
oldName,
|
|
515
|
+
newName
|
|
516
|
+
},
|
|
517
|
+
message: `Table renamed from '${oldName}' to '${newName}'`
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
registry.register({
|
|
522
|
+
name: "add_column",
|
|
523
|
+
description: "Add a new column to the table",
|
|
524
|
+
rootLevel: false,
|
|
525
|
+
tableLevel: true,
|
|
526
|
+
rowLevel: false,
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: "object",
|
|
529
|
+
properties: {
|
|
530
|
+
name: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Column name"
|
|
533
|
+
},
|
|
534
|
+
type: {
|
|
535
|
+
type: "string",
|
|
536
|
+
enum: [
|
|
537
|
+
"INTEGER",
|
|
538
|
+
"TEXT",
|
|
539
|
+
"REAL",
|
|
540
|
+
"BLOB",
|
|
541
|
+
"NUMERIC"
|
|
542
|
+
],
|
|
543
|
+
description: "Column type"
|
|
544
|
+
},
|
|
545
|
+
nullable: {
|
|
546
|
+
type: "boolean",
|
|
547
|
+
default: true,
|
|
548
|
+
description: "Whether the column allows NULL values"
|
|
549
|
+
},
|
|
550
|
+
defaultValue: {
|
|
551
|
+
oneOf: [
|
|
552
|
+
{ type: "string" },
|
|
553
|
+
{ type: "number" },
|
|
554
|
+
{ type: "boolean" },
|
|
555
|
+
{ type: "null" }
|
|
556
|
+
],
|
|
557
|
+
description: "Default value for the column"
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
required: ["name", "type"]
|
|
561
|
+
},
|
|
562
|
+
handler: async (ctx, params) => {
|
|
563
|
+
const colName = params.name;
|
|
564
|
+
const colType = params.type;
|
|
565
|
+
const nullable = params.nullable !== false;
|
|
566
|
+
const defaultValue = params.defaultValue;
|
|
567
|
+
if (!colName || !colType) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Column name and type are required");
|
|
568
|
+
let columnDef = `"${colName}" ${colType.toUpperCase()}`;
|
|
569
|
+
if (!nullable) columnDef += " NOT NULL";
|
|
570
|
+
if (defaultValue !== void 0) columnDef += ` DEFAULT ${formatValueForSQL(defaultValue)}`;
|
|
571
|
+
try {
|
|
572
|
+
await execRun(ctx.db, `ALTER TABLE "${ctx.table}" ADD COLUMN ${columnDef}`);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if ((error instanceof Error ? error.message : String(error)).includes("duplicate column name")) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Column '${colName}' already exists`);
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
data: {
|
|
580
|
+
table: ctx.table,
|
|
581
|
+
column: colName,
|
|
582
|
+
type: colType
|
|
583
|
+
},
|
|
584
|
+
message: `Column '${colName}' added to table '${ctx.table}'`
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
registry.register({
|
|
589
|
+
name: "rename_column",
|
|
590
|
+
description: "Rename a column in the table",
|
|
591
|
+
rootLevel: false,
|
|
592
|
+
tableLevel: true,
|
|
593
|
+
rowLevel: false,
|
|
594
|
+
inputSchema: {
|
|
595
|
+
type: "object",
|
|
596
|
+
properties: {
|
|
597
|
+
oldName: {
|
|
598
|
+
type: "string",
|
|
599
|
+
description: "Current column name"
|
|
600
|
+
},
|
|
601
|
+
newName: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "New column name"
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
required: ["oldName", "newName"]
|
|
607
|
+
},
|
|
608
|
+
handler: async (ctx, params) => {
|
|
609
|
+
const oldName = params.oldName;
|
|
610
|
+
const newName = params.newName;
|
|
611
|
+
if (!oldName || !newName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
|
|
612
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
613
|
+
if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
614
|
+
if (!schema.columns.some((c) => c.name === oldName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Column '${oldName}' not found in table '${ctx.table}'`);
|
|
615
|
+
await execRun(ctx.db, `ALTER TABLE "${ctx.table}" RENAME COLUMN "${oldName}" TO "${newName}"`);
|
|
616
|
+
return {
|
|
617
|
+
success: true,
|
|
618
|
+
data: {
|
|
619
|
+
table: ctx.table,
|
|
620
|
+
oldName,
|
|
621
|
+
newName
|
|
622
|
+
},
|
|
623
|
+
message: `Column renamed from '${oldName}' to '${newName}'`
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
registry.register({
|
|
628
|
+
name: "drop_column",
|
|
629
|
+
description: "Drop a column from the table",
|
|
392
630
|
rootLevel: false,
|
|
393
631
|
tableLevel: true,
|
|
394
632
|
rowLevel: false,
|
|
633
|
+
inputSchema: {
|
|
634
|
+
type: "object",
|
|
635
|
+
properties: { name: {
|
|
636
|
+
type: "string",
|
|
637
|
+
description: "Column name to drop"
|
|
638
|
+
} },
|
|
639
|
+
required: ["name"]
|
|
640
|
+
},
|
|
641
|
+
handler: async (ctx, params) => {
|
|
642
|
+
const colName = params.name;
|
|
643
|
+
if (!colName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Column name is required");
|
|
644
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
645
|
+
if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
646
|
+
if (!schema.columns.some((c) => c.name === colName)) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Column '${colName}' not found in table '${ctx.table}'`);
|
|
647
|
+
await execRun(ctx.db, `ALTER TABLE "${ctx.table}" DROP COLUMN "${colName}"`);
|
|
648
|
+
return {
|
|
649
|
+
success: true,
|
|
650
|
+
data: {
|
|
651
|
+
table: ctx.table,
|
|
652
|
+
column: colName
|
|
653
|
+
},
|
|
654
|
+
message: `Column '${colName}' dropped from table '${ctx.table}'`
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
registry.register({
|
|
659
|
+
name: "create_index",
|
|
660
|
+
description: "Create an index on the table",
|
|
661
|
+
rootLevel: false,
|
|
662
|
+
tableLevel: true,
|
|
663
|
+
rowLevel: false,
|
|
664
|
+
inputSchema: {
|
|
665
|
+
type: "object",
|
|
666
|
+
properties: {
|
|
667
|
+
name: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "Index name"
|
|
670
|
+
},
|
|
671
|
+
columns: {
|
|
672
|
+
type: "array",
|
|
673
|
+
items: { type: "string" },
|
|
674
|
+
minItems: 1,
|
|
675
|
+
description: "Columns to index (supports compound indexes)"
|
|
676
|
+
},
|
|
677
|
+
unique: {
|
|
678
|
+
type: "boolean",
|
|
679
|
+
default: false,
|
|
680
|
+
description: "Whether the index is unique"
|
|
681
|
+
},
|
|
682
|
+
ifNotExists: {
|
|
683
|
+
type: "boolean",
|
|
684
|
+
default: false,
|
|
685
|
+
description: "Don't error if index already exists"
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
required: ["name", "columns"]
|
|
689
|
+
},
|
|
690
|
+
handler: async (ctx, params) => {
|
|
691
|
+
const indexName = params.name;
|
|
692
|
+
const columns = params.columns;
|
|
693
|
+
const unique = params.unique;
|
|
694
|
+
const ifNotExists = params.ifNotExists;
|
|
695
|
+
if (!indexName || !columns || columns.length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Index name and columns are required");
|
|
696
|
+
const uniqueClause = unique ? "UNIQUE " : "";
|
|
697
|
+
const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
|
|
698
|
+
const columnsClause = columns.map((c) => `"${c}"`).join(", ");
|
|
699
|
+
await execRun(ctx.db, `CREATE ${uniqueClause}INDEX ${ifNotExistsClause}"${indexName}" ON "${ctx.table}" (${columnsClause})`);
|
|
700
|
+
return {
|
|
701
|
+
success: true,
|
|
702
|
+
data: {
|
|
703
|
+
indexName,
|
|
704
|
+
table: ctx.table,
|
|
705
|
+
columns,
|
|
706
|
+
unique: !!unique
|
|
707
|
+
},
|
|
708
|
+
message: `Index '${indexName}' created on table '${ctx.table}'`
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
registry.register({
|
|
713
|
+
name: "drop_index",
|
|
714
|
+
description: "Drop an index from the table",
|
|
715
|
+
rootLevel: false,
|
|
716
|
+
tableLevel: true,
|
|
717
|
+
rowLevel: false,
|
|
718
|
+
inputSchema: {
|
|
719
|
+
type: "object",
|
|
720
|
+
properties: {
|
|
721
|
+
name: {
|
|
722
|
+
type: "string",
|
|
723
|
+
description: "Index name to drop"
|
|
724
|
+
},
|
|
725
|
+
ifExists: {
|
|
726
|
+
type: "boolean",
|
|
727
|
+
default: false,
|
|
728
|
+
description: "Don't error if index doesn't exist"
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
required: ["name"]
|
|
732
|
+
},
|
|
733
|
+
handler: async (ctx, params) => {
|
|
734
|
+
const indexName = params.name;
|
|
735
|
+
const ifExists = params.ifExists;
|
|
736
|
+
if (!indexName) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Index name is required");
|
|
737
|
+
const ifExistsClause = ifExists ? "IF EXISTS " : "";
|
|
738
|
+
await execRun(ctx.db, `DROP INDEX ${ifExistsClause}"${indexName}"`);
|
|
739
|
+
return {
|
|
740
|
+
success: true,
|
|
741
|
+
data: { indexName },
|
|
742
|
+
message: `Index '${indexName}' dropped`
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
registry.register({
|
|
747
|
+
name: "update",
|
|
748
|
+
description: "Update this row",
|
|
749
|
+
rootLevel: false,
|
|
750
|
+
tableLevel: false,
|
|
751
|
+
rowLevel: true,
|
|
395
752
|
inputSchemaGenerator: (schemaCtx) => {
|
|
396
753
|
if (!schemaCtx.tableSchema) return {
|
|
397
754
|
type: "object",
|
|
398
755
|
properties: { data: {
|
|
399
756
|
type: "object",
|
|
400
|
-
description: "
|
|
757
|
+
description: "Fields to update",
|
|
401
758
|
additionalProperties: true
|
|
402
759
|
} },
|
|
403
760
|
required: ["data"]
|
|
404
761
|
};
|
|
405
|
-
return
|
|
762
|
+
return generateUpdateSchema(schemaCtx.tableSchema);
|
|
406
763
|
},
|
|
407
764
|
handler: async (ctx, params) => {
|
|
408
765
|
const data = params.data;
|
|
409
|
-
if (!data || Object.keys(data).length === 0)
|
|
410
|
-
if (!
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
const
|
|
766
|
+
if (!data || Object.keys(data).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
|
|
767
|
+
if (!ctx.pk) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level update");
|
|
768
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
769
|
+
if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
770
|
+
const pkColumn = schema.primaryKey[0] ?? "rowid";
|
|
771
|
+
const setClauses = Object.entries(data).map(([col, val]) => `"${col}" = ${formatValueForSQL(val)}`).join(", ");
|
|
772
|
+
const updateSQL = `UPDATE "${ctx.table}" SET ${setClauses} WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`;
|
|
414
773
|
try {
|
|
415
|
-
await execRun(ctx.db,
|
|
774
|
+
await execRun(ctx.db, updateSQL);
|
|
416
775
|
} catch (error) {
|
|
417
776
|
const message = error instanceof Error ? error.message : String(error);
|
|
418
|
-
|
|
777
|
+
if (message.includes("UNIQUE constraint failed")) return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Unique constraint violation: ${message}`);
|
|
778
|
+
throw error;
|
|
419
779
|
}
|
|
420
|
-
|
|
780
|
+
return {
|
|
781
|
+
success: true,
|
|
782
|
+
data: (await execAll(ctx.db, `SELECT * FROM "${ctx.table}" WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`))[0],
|
|
783
|
+
message: "Row updated successfully"
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
registry.register({
|
|
788
|
+
name: "delete",
|
|
789
|
+
description: "Delete this row",
|
|
790
|
+
rootLevel: false,
|
|
791
|
+
tableLevel: false,
|
|
792
|
+
rowLevel: true,
|
|
793
|
+
inputSchema: {
|
|
794
|
+
type: "object",
|
|
795
|
+
properties: {},
|
|
796
|
+
description: "Delete the current row"
|
|
797
|
+
},
|
|
798
|
+
handler: async (ctx) => {
|
|
799
|
+
if (!ctx.pk) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level delete");
|
|
800
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
801
|
+
if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
802
|
+
const pkColumn = schema.primaryKey[0] ?? "rowid";
|
|
803
|
+
await execRun(ctx.db, `DELETE FROM "${ctx.table}" WHERE "${pkColumn}" = ${formatValueForSQL(ctx.pk)}`);
|
|
804
|
+
return {
|
|
805
|
+
success: true,
|
|
806
|
+
data: { pk: ctx.pk },
|
|
807
|
+
message: "Row deleted successfully"
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
registry.register({
|
|
812
|
+
name: "query",
|
|
813
|
+
description: "Query rows with conditions, ordering, and pagination",
|
|
814
|
+
rootLevel: false,
|
|
815
|
+
tableLevel: true,
|
|
816
|
+
rowLevel: false,
|
|
817
|
+
inputSchema: {
|
|
818
|
+
type: "object",
|
|
819
|
+
properties: {
|
|
820
|
+
where: {
|
|
821
|
+
type: "object",
|
|
822
|
+
description: "Query conditions (MongoDB-style operators)"
|
|
823
|
+
},
|
|
824
|
+
orderBy: {
|
|
825
|
+
type: "array",
|
|
826
|
+
items: {
|
|
827
|
+
type: "object",
|
|
828
|
+
properties: {
|
|
829
|
+
column: { type: "string" },
|
|
830
|
+
direction: {
|
|
831
|
+
type: "string",
|
|
832
|
+
enum: ["asc", "desc"],
|
|
833
|
+
default: "asc"
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
required: ["column"]
|
|
837
|
+
},
|
|
838
|
+
description: "Sort order"
|
|
839
|
+
},
|
|
840
|
+
limit: {
|
|
841
|
+
type: "integer",
|
|
842
|
+
minimum: 1,
|
|
843
|
+
description: "Maximum rows to return"
|
|
844
|
+
},
|
|
845
|
+
offset: {
|
|
846
|
+
type: "integer",
|
|
847
|
+
minimum: 0,
|
|
848
|
+
description: "Number of rows to skip"
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
handler: async (ctx, params) => {
|
|
853
|
+
const where = params.where;
|
|
854
|
+
const orderBy = params.orderBy;
|
|
855
|
+
const limit = params.limit;
|
|
856
|
+
const offset = params.offset;
|
|
857
|
+
let querySQL = _aigne_sqlite.sql`SELECT * FROM ${_aigne_sqlite.sql.identifier(ctx.table)}`;
|
|
858
|
+
querySQL = _aigne_sqlite.sql`${querySQL}${require_operators.buildWhereClause(where)}`;
|
|
859
|
+
if (orderBy && orderBy.length > 0) {
|
|
860
|
+
const orderClauses = orderBy.map((o) => {
|
|
861
|
+
const dir = o.direction?.toUpperCase() === "DESC" ? _aigne_sqlite.sql` DESC` : _aigne_sqlite.sql` ASC`;
|
|
862
|
+
return _aigne_sqlite.sql`${_aigne_sqlite.sql.identifier(o.column)}${dir}`;
|
|
863
|
+
});
|
|
864
|
+
querySQL = _aigne_sqlite.sql`${querySQL} ORDER BY ${_aigne_sqlite.sql.join(orderClauses, _aigne_sqlite.sql`, `)}`;
|
|
865
|
+
}
|
|
866
|
+
if (limit !== void 0) querySQL = _aigne_sqlite.sql`${querySQL} LIMIT ${limit}`;
|
|
867
|
+
if (offset !== void 0) querySQL = _aigne_sqlite.sql`${querySQL} OFFSET ${offset}`;
|
|
868
|
+
const rows = await execSql(ctx.db, querySQL);
|
|
421
869
|
return {
|
|
422
870
|
success: true,
|
|
423
871
|
data: {
|
|
424
|
-
|
|
425
|
-
|
|
872
|
+
rows,
|
|
873
|
+
count: rows.length
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
registry.register({
|
|
879
|
+
name: "update_where",
|
|
880
|
+
description: "Update rows matching conditions",
|
|
881
|
+
rootLevel: false,
|
|
882
|
+
tableLevel: true,
|
|
883
|
+
rowLevel: false,
|
|
884
|
+
inputSchema: {
|
|
885
|
+
type: "object",
|
|
886
|
+
properties: {
|
|
887
|
+
where: {
|
|
888
|
+
type: "object",
|
|
889
|
+
description: "Update conditions (MongoDB-style operators)"
|
|
426
890
|
},
|
|
427
|
-
|
|
891
|
+
data: {
|
|
892
|
+
type: "object",
|
|
893
|
+
description: "Fields to update"
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
required: ["where", "data"]
|
|
897
|
+
},
|
|
898
|
+
handler: async (ctx, params) => {
|
|
899
|
+
const where = params.where;
|
|
900
|
+
const data = params.data;
|
|
901
|
+
if (!where || Object.keys(where).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for update_where");
|
|
902
|
+
if (!data || Object.keys(data).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
|
|
903
|
+
const setClauses = Object.entries(data).map(([col, val]) => _aigne_sqlite.sql`${_aigne_sqlite.sql.identifier(col)} = ${val}`);
|
|
904
|
+
const updateSQL = _aigne_sqlite.sql`UPDATE ${_aigne_sqlite.sql.identifier(ctx.table)} SET ${_aigne_sqlite.sql.join(setClauses, _aigne_sqlite.sql`, `)}${require_operators.buildWhereClause(where)}`;
|
|
905
|
+
const result = await runSql(ctx.db, updateSQL);
|
|
906
|
+
return {
|
|
907
|
+
success: true,
|
|
908
|
+
data: { affectedRows: result.changes },
|
|
909
|
+
message: `${result.changes} row(s) updated`
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
registry.register({
|
|
914
|
+
name: "delete_where",
|
|
915
|
+
description: "Delete rows matching conditions",
|
|
916
|
+
rootLevel: false,
|
|
917
|
+
tableLevel: true,
|
|
918
|
+
rowLevel: false,
|
|
919
|
+
inputSchema: {
|
|
920
|
+
type: "object",
|
|
921
|
+
properties: { where: {
|
|
922
|
+
type: "object",
|
|
923
|
+
description: "Delete conditions (MongoDB-style operators)"
|
|
924
|
+
} },
|
|
925
|
+
required: ["where"]
|
|
926
|
+
},
|
|
927
|
+
handler: async (ctx, params) => {
|
|
928
|
+
const where = params.where;
|
|
929
|
+
if (!where || Object.keys(where).length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for delete_where");
|
|
930
|
+
const deleteSQL = _aigne_sqlite.sql`DELETE FROM ${_aigne_sqlite.sql.identifier(ctx.table)}${require_operators.buildWhereClause(where)}`;
|
|
931
|
+
const result = await runSql(ctx.db, deleteSQL);
|
|
932
|
+
return {
|
|
933
|
+
success: true,
|
|
934
|
+
data: { affectedRows: result.changes },
|
|
935
|
+
message: `${result.changes} row(s) deleted`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
registry.register({
|
|
940
|
+
name: "bulk_insert",
|
|
941
|
+
description: "Insert multiple rows at once",
|
|
942
|
+
rootLevel: false,
|
|
943
|
+
tableLevel: true,
|
|
944
|
+
rowLevel: false,
|
|
945
|
+
inputSchema: {
|
|
946
|
+
type: "object",
|
|
947
|
+
properties: { rows: {
|
|
948
|
+
type: "array",
|
|
949
|
+
items: { type: "object" },
|
|
950
|
+
minItems: 1,
|
|
951
|
+
description: "Array of row data to insert"
|
|
952
|
+
} },
|
|
953
|
+
required: ["rows"]
|
|
954
|
+
},
|
|
955
|
+
handler: async (ctx, params) => {
|
|
956
|
+
const rows = params.rows;
|
|
957
|
+
if (!rows || rows.length === 0) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "At least one row is required");
|
|
958
|
+
const columns = Object.keys(rows[0]);
|
|
959
|
+
const columnsList = columns.map((c) => `"${c}"`).join(", ");
|
|
960
|
+
const insertedIds = [];
|
|
961
|
+
for (const row of rows) {
|
|
962
|
+
const values = columns.map((col) => formatValueForSQL(row[col]));
|
|
963
|
+
const insertSQL = `INSERT INTO "${ctx.table}" (${columnsList}) VALUES (${values.join(", ")})`;
|
|
964
|
+
try {
|
|
965
|
+
await execRun(ctx.db, insertSQL);
|
|
966
|
+
const lastIdResult = await execAll(ctx.db, "SELECT last_insert_rowid() as id");
|
|
967
|
+
insertedIds.push(lastIdResult[0]?.id ?? 0);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
970
|
+
return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Insert failed at row ${insertedIds.length}: ${message}`, {
|
|
971
|
+
insertedCount: insertedIds.length,
|
|
972
|
+
insertedIds
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
success: true,
|
|
978
|
+
data: {
|
|
979
|
+
insertedCount: rows.length,
|
|
980
|
+
insertedIds
|
|
981
|
+
},
|
|
982
|
+
message: `${rows.length} row(s) inserted`
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
registry.register({
|
|
987
|
+
name: "pragma",
|
|
988
|
+
description: "Execute a PRAGMA command (whitelist-restricted)",
|
|
989
|
+
rootLevel: true,
|
|
990
|
+
tableLevel: false,
|
|
991
|
+
rowLevel: false,
|
|
992
|
+
inputSchema: {
|
|
993
|
+
type: "object",
|
|
994
|
+
properties: {
|
|
995
|
+
command: {
|
|
996
|
+
type: "string",
|
|
997
|
+
enum: [
|
|
998
|
+
"table_info",
|
|
999
|
+
"table_list",
|
|
1000
|
+
"index_list",
|
|
1001
|
+
"index_info",
|
|
1002
|
+
"foreign_key_list",
|
|
1003
|
+
"database_list",
|
|
1004
|
+
"collation_list",
|
|
1005
|
+
"journal_mode",
|
|
1006
|
+
"synchronous",
|
|
1007
|
+
"cache_size",
|
|
1008
|
+
"page_size",
|
|
1009
|
+
"encoding",
|
|
1010
|
+
"auto_vacuum",
|
|
1011
|
+
"integrity_check",
|
|
1012
|
+
"quick_check"
|
|
1013
|
+
],
|
|
1014
|
+
description: "PRAGMA command to execute"
|
|
1015
|
+
},
|
|
1016
|
+
argument: {
|
|
1017
|
+
type: "string",
|
|
1018
|
+
description: "Argument for the command (e.g., table name)"
|
|
1019
|
+
},
|
|
1020
|
+
value: {
|
|
1021
|
+
oneOf: [{ type: "string" }, { type: "number" }],
|
|
1022
|
+
description: "Value for writable PRAGMAs"
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
required: ["command"]
|
|
1026
|
+
},
|
|
1027
|
+
handler: async (ctx, params) => {
|
|
1028
|
+
const command = params.command;
|
|
1029
|
+
const argument = params.argument;
|
|
1030
|
+
const value = params.value;
|
|
1031
|
+
const allowedCommands = [
|
|
1032
|
+
"table_info",
|
|
1033
|
+
"table_list",
|
|
1034
|
+
"index_list",
|
|
1035
|
+
"index_info",
|
|
1036
|
+
"foreign_key_list",
|
|
1037
|
+
"database_list",
|
|
1038
|
+
"collation_list",
|
|
1039
|
+
"journal_mode",
|
|
1040
|
+
"synchronous",
|
|
1041
|
+
"cache_size",
|
|
1042
|
+
"page_size",
|
|
1043
|
+
"encoding",
|
|
1044
|
+
"auto_vacuum",
|
|
1045
|
+
"integrity_check",
|
|
1046
|
+
"quick_check"
|
|
1047
|
+
];
|
|
1048
|
+
if (!allowedCommands.includes(command)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `PRAGMA command '${command}' is not allowed. Allowed: ${allowedCommands.join(", ")}`);
|
|
1049
|
+
let pragmaSQL = `PRAGMA ${command}`;
|
|
1050
|
+
if (argument) pragmaSQL += `("${argument}")`;
|
|
1051
|
+
if (value !== void 0) pragmaSQL += ` = ${typeof value === "string" ? `'${value}'` : value}`;
|
|
1052
|
+
const result = await execAll(ctx.db, pragmaSQL);
|
|
1053
|
+
return {
|
|
1054
|
+
success: true,
|
|
1055
|
+
data: result.length === 1 && Object.keys(result[0]).length === 1 ? Object.values(result[0])[0] : result
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
registry.register({
|
|
1060
|
+
name: "import",
|
|
1061
|
+
description: "Import data into table from JSON or CSV format",
|
|
1062
|
+
tableLevel: true,
|
|
1063
|
+
rowLevel: false,
|
|
1064
|
+
inputSchema: {
|
|
1065
|
+
type: "object",
|
|
1066
|
+
properties: {
|
|
1067
|
+
format: {
|
|
1068
|
+
type: "string",
|
|
1069
|
+
enum: ["json", "csv"],
|
|
1070
|
+
description: "Data format (json or csv)"
|
|
1071
|
+
},
|
|
1072
|
+
data: {
|
|
1073
|
+
type: "string",
|
|
1074
|
+
description: "The data to import (JSON array string or CSV string)"
|
|
1075
|
+
},
|
|
1076
|
+
onConflict: {
|
|
1077
|
+
type: "string",
|
|
1078
|
+
enum: [
|
|
1079
|
+
"abort",
|
|
1080
|
+
"ignore",
|
|
1081
|
+
"replace"
|
|
1082
|
+
],
|
|
1083
|
+
default: "abort",
|
|
1084
|
+
description: "Conflict resolution strategy"
|
|
1085
|
+
},
|
|
1086
|
+
delimiter: {
|
|
1087
|
+
type: "string",
|
|
1088
|
+
default: ",",
|
|
1089
|
+
description: "CSV delimiter character"
|
|
1090
|
+
},
|
|
1091
|
+
header: {
|
|
1092
|
+
type: "boolean",
|
|
1093
|
+
default: true,
|
|
1094
|
+
description: "Whether CSV has a header row"
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
required: ["format", "data"]
|
|
1098
|
+
},
|
|
1099
|
+
handler: async (ctx, params) => {
|
|
1100
|
+
const format = params.format;
|
|
1101
|
+
const data = params.data;
|
|
1102
|
+
const onConflict = params.onConflict ?? "abort";
|
|
1103
|
+
const delimiter = params.delimiter ?? ",";
|
|
1104
|
+
const hasHeader = params.header !== false;
|
|
1105
|
+
if (!["json", "csv"].includes(format)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Unsupported import format: ${format}. Supported: json, csv`);
|
|
1106
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
1107
|
+
if (!schema) return require_types.errorResult(require_types.SQLiteActionErrorCode.NOT_FOUND, `Table not found: ${ctx.table}`);
|
|
1108
|
+
let rows;
|
|
1109
|
+
try {
|
|
1110
|
+
if (format === "json") {
|
|
1111
|
+
const parsed = JSON.parse(data);
|
|
1112
|
+
if (!Array.isArray(parsed)) return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, "JSON data must be an array of objects");
|
|
1113
|
+
rows = parsed;
|
|
1114
|
+
} else rows = parseCSV(data, delimiter, hasHeader, schema);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Failed to parse ${format} data: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
1117
|
+
}
|
|
1118
|
+
if (rows.length === 0) return {
|
|
1119
|
+
success: true,
|
|
1120
|
+
data: {
|
|
1121
|
+
imported: 0,
|
|
1122
|
+
skipped: 0
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
let conflictClause = "";
|
|
1126
|
+
if (onConflict === "ignore") conflictClause = " OR IGNORE";
|
|
1127
|
+
else if (onConflict === "replace") conflictClause = " OR REPLACE";
|
|
1128
|
+
const columnNames = schema.columns.map((c) => c.name);
|
|
1129
|
+
let imported = 0;
|
|
1130
|
+
let skipped = 0;
|
|
1131
|
+
try {
|
|
1132
|
+
await execRun(ctx.db, "BEGIN TRANSACTION");
|
|
1133
|
+
for (const row of rows) {
|
|
1134
|
+
const usedColumns = [];
|
|
1135
|
+
const values = [];
|
|
1136
|
+
for (const colName of columnNames) if (colName in row) {
|
|
1137
|
+
usedColumns.push(colName);
|
|
1138
|
+
values.push(formatValueForSQL(row[colName]));
|
|
1139
|
+
}
|
|
1140
|
+
if (usedColumns.length === 0) continue;
|
|
1141
|
+
const insertSQL = `INSERT${conflictClause} INTO "${ctx.table}" (${usedColumns.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`;
|
|
1142
|
+
try {
|
|
1143
|
+
await execAll(ctx.db, insertSQL);
|
|
1144
|
+
if (((await execAll(ctx.db, "SELECT changes() as changes"))[0]?.changes ?? 0) > 0) imported++;
|
|
1145
|
+
else skipped++;
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
if (onConflict === "abort") {
|
|
1148
|
+
await execRun(ctx.db, "ROLLBACK");
|
|
1149
|
+
return require_types.errorResult(require_types.SQLiteActionErrorCode.CONSTRAINT_VIOLATION, `Import aborted: ${error instanceof Error ? error.message : "constraint violation"}`);
|
|
1150
|
+
}
|
|
1151
|
+
skipped++;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
await execRun(ctx.db, "COMMIT");
|
|
1155
|
+
return {
|
|
1156
|
+
success: true,
|
|
1157
|
+
data: {
|
|
1158
|
+
imported,
|
|
1159
|
+
skipped
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
try {
|
|
1164
|
+
await execRun(ctx.db, "ROLLBACK");
|
|
1165
|
+
} catch {}
|
|
1166
|
+
return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Import failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
registry.register({
|
|
1171
|
+
name: "vacuum",
|
|
1172
|
+
description: "Compact the database file and reclaim unused space",
|
|
1173
|
+
rootLevel: true,
|
|
1174
|
+
tableLevel: false,
|
|
1175
|
+
rowLevel: false,
|
|
1176
|
+
inputSchema: {
|
|
1177
|
+
type: "object",
|
|
1178
|
+
properties: {},
|
|
1179
|
+
additionalProperties: false
|
|
1180
|
+
},
|
|
1181
|
+
handler: async (ctx) => {
|
|
1182
|
+
const pageCountBefore = await execAll(ctx.db, "PRAGMA page_count");
|
|
1183
|
+
const pageSize = (await execAll(ctx.db, "PRAGMA page_size"))[0]?.page_size ?? 4096;
|
|
1184
|
+
const sizeBefore = (pageCountBefore[0]?.page_count ?? 0) * pageSize;
|
|
1185
|
+
try {
|
|
1186
|
+
await execRun(ctx.db, "VACUUM");
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
return require_types.errorResult(require_types.SQLiteActionErrorCode.INVALID_INPUT, `Vacuum failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
1189
|
+
}
|
|
1190
|
+
const sizeAfter = ((await execAll(ctx.db, "PRAGMA page_count"))[0]?.page_count ?? 0) * pageSize;
|
|
1191
|
+
return {
|
|
1192
|
+
success: true,
|
|
1193
|
+
data: {
|
|
1194
|
+
sizeBefore,
|
|
1195
|
+
sizeAfter,
|
|
1196
|
+
freedBytes: sizeBefore - sizeAfter
|
|
1197
|
+
}
|
|
428
1198
|
};
|
|
429
1199
|
}
|
|
430
1200
|
});
|
|
@@ -441,6 +1211,67 @@ function formatValueForSQL(value) {
|
|
|
441
1211
|
return `'${String(value).replace(/'/g, "''")}'`;
|
|
442
1212
|
}
|
|
443
1213
|
/**
|
|
1214
|
+
* Parse a CSV string into an array of row objects.
|
|
1215
|
+
*/
|
|
1216
|
+
function parseCSV(data, delimiter, hasHeader, schema) {
|
|
1217
|
+
const lines = data.split("\n").filter((line) => line.trim() !== "");
|
|
1218
|
+
if (lines.length === 0) return [];
|
|
1219
|
+
let headers;
|
|
1220
|
+
let startIndex;
|
|
1221
|
+
if (hasHeader) {
|
|
1222
|
+
if (lines.length < 1) return [];
|
|
1223
|
+
headers = parseCsvLine(lines[0], delimiter);
|
|
1224
|
+
startIndex = 1;
|
|
1225
|
+
} else {
|
|
1226
|
+
headers = schema.columns.map((c) => c.name);
|
|
1227
|
+
startIndex = 0;
|
|
1228
|
+
}
|
|
1229
|
+
const rows = [];
|
|
1230
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
1231
|
+
const values = parseCsvLine(lines[i], delimiter);
|
|
1232
|
+
const row = {};
|
|
1233
|
+
for (let j = 0; j < headers.length && j < values.length; j++) row[headers[j]] = values[j];
|
|
1234
|
+
rows.push(row);
|
|
1235
|
+
}
|
|
1236
|
+
return rows;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Parse a single CSV line, handling quoted fields.
|
|
1240
|
+
*/
|
|
1241
|
+
function parseCsvLine(line, delimiter) {
|
|
1242
|
+
const fields = [];
|
|
1243
|
+
let current = "";
|
|
1244
|
+
let inQuotes = false;
|
|
1245
|
+
let i = 0;
|
|
1246
|
+
while (i < line.length) {
|
|
1247
|
+
const char = line[i];
|
|
1248
|
+
if (inQuotes) if (char === "\"") if (i + 1 < line.length && line[i + 1] === "\"") {
|
|
1249
|
+
current += "\"";
|
|
1250
|
+
i += 2;
|
|
1251
|
+
} else {
|
|
1252
|
+
inQuotes = false;
|
|
1253
|
+
i++;
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
current += char;
|
|
1257
|
+
i++;
|
|
1258
|
+
}
|
|
1259
|
+
else if (char === "\"") {
|
|
1260
|
+
inQuotes = true;
|
|
1261
|
+
i++;
|
|
1262
|
+
} else if (line.substring(i, i + delimiter.length) === delimiter) {
|
|
1263
|
+
fields.push(current);
|
|
1264
|
+
current = "";
|
|
1265
|
+
i += delimiter.length;
|
|
1266
|
+
} else {
|
|
1267
|
+
current += char;
|
|
1268
|
+
i++;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
fields.push(current);
|
|
1272
|
+
return fields;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
444
1275
|
* Maps SQLite column type to JSON Schema type
|
|
445
1276
|
*/
|
|
446
1277
|
function sqliteTypeToJsonSchema(sqliteType) {
|
|
@@ -530,6 +1361,29 @@ function generateDuplicateSchema(tableSchema) {
|
|
|
530
1361
|
"x-copied-columns": copiedColumns
|
|
531
1362
|
};
|
|
532
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Generates JSON Schema for update action based on table schema
|
|
1366
|
+
*/
|
|
1367
|
+
function generateUpdateSchema(tableSchema) {
|
|
1368
|
+
const properties = {};
|
|
1369
|
+
const pkColumn = tableSchema.primaryKey[0] ?? "rowid";
|
|
1370
|
+
for (const col of tableSchema.columns) {
|
|
1371
|
+
if (col.name === pkColumn) continue;
|
|
1372
|
+
const typeInfo = sqliteTypeToJsonSchema(col.type);
|
|
1373
|
+
properties[col.name] = typeInfo;
|
|
1374
|
+
}
|
|
1375
|
+
return {
|
|
1376
|
+
type: "object",
|
|
1377
|
+
description: `Update fields in "${tableSchema.name}" table`,
|
|
1378
|
+
properties: { data: {
|
|
1379
|
+
type: "object",
|
|
1380
|
+
description: "Fields to update",
|
|
1381
|
+
properties,
|
|
1382
|
+
additionalProperties: false
|
|
1383
|
+
} },
|
|
1384
|
+
required: ["data"]
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
533
1387
|
|
|
534
1388
|
//#endregion
|
|
535
1389
|
exports.registerBuiltInActions = registerBuiltInActions;
|