@aigne/afs-sqlite 1.11.0-beta.6 → 1.11.0-beta.8
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/_virtual/rolldown_runtime.mjs +7 -0
- 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/config.cjs +1 -1
- package/dist/config.d.cts +9 -36
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +9 -36
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -1
- package/dist/config.mjs.map +1 -1
- 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 +268 -26
- package/dist/sqlite-afs.d.cts +50 -48
- package/dist/sqlite-afs.d.cts.map +1 -1
- package/dist/sqlite-afs.d.mts +50 -48
- package/dist/sqlite-afs.d.mts.map +1 -1
- package/dist/sqlite-afs.mjs +270 -27
- package/dist/sqlite-afs.mjs.map +1 -1
- package/package.json +5 -4
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buildWhereClause } from "./operators.mjs";
|
|
2
|
+
import { SQLiteActionErrorCode, errorResult } from "./types.mjs";
|
|
1
3
|
import { sql } from "@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(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Table name is required");
|
|
470
|
+
const exists = await ctx.schemaService.hasTable(tableName);
|
|
471
|
+
if (!exists && !ifExists) return errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
|
|
507
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(newName)) return errorResult(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 errorResult(SQLiteActionErrorCode.NOT_FOUND, `Table '${oldName}' does not exist`);
|
|
509
|
+
if (await ctx.schemaService.hasTable(newName)) return errorResult(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 errorResult(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 errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Both oldName and newName are required");
|
|
612
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
613
|
+
if (!schema) return errorResult(SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
614
|
+
if (!schema.columns.some((c) => c.name === oldName)) return errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Column name is required");
|
|
644
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
645
|
+
if (!schema) return errorResult(SQLiteActionErrorCode.NOT_FOUND, `Table '${ctx.table}' not found`);
|
|
646
|
+
if (!schema.columns.some((c) => c.name === colName)) return errorResult(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 errorResult(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 errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
|
|
767
|
+
if (!ctx.pk) return errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level update");
|
|
768
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
769
|
+
if (!schema) return errorResult(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 errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Primary key is required for row-level delete");
|
|
800
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
801
|
+
if (!schema) return errorResult(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 = sql`SELECT * FROM ${sql.identifier(ctx.table)}`;
|
|
858
|
+
querySQL = sql`${querySQL}${buildWhereClause(where)}`;
|
|
859
|
+
if (orderBy && orderBy.length > 0) {
|
|
860
|
+
const orderClauses = orderBy.map((o) => {
|
|
861
|
+
const dir = o.direction?.toUpperCase() === "DESC" ? sql` DESC` : sql` ASC`;
|
|
862
|
+
return sql`${sql.identifier(o.column)}${dir}`;
|
|
863
|
+
});
|
|
864
|
+
querySQL = sql`${querySQL} ORDER BY ${sql.join(orderClauses, sql`, `)}`;
|
|
865
|
+
}
|
|
866
|
+
if (limit !== void 0) querySQL = sql`${querySQL} LIMIT ${limit}`;
|
|
867
|
+
if (offset !== void 0) querySQL = 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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for update_where");
|
|
902
|
+
if (!data || Object.keys(data).length === 0) return errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Update data is required");
|
|
903
|
+
const setClauses = Object.entries(data).map(([col, val]) => sql`${sql.identifier(col)} = ${val}`);
|
|
904
|
+
const updateSQL = sql`UPDATE ${sql.identifier(ctx.table)} SET ${sql.join(setClauses, sql`, `)}${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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, "Where clause is required for delete_where");
|
|
930
|
+
const deleteSQL = sql`DELETE FROM ${sql.identifier(ctx.table)}${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 errorResult(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 errorResult(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 errorResult(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 errorResult(SQLiteActionErrorCode.INVALID_INPUT, `Unsupported import format: ${format}. Supported: json, csv`);
|
|
1106
|
+
const schema = await ctx.schemaService.getSchema(ctx.table);
|
|
1107
|
+
if (!schema) return errorResult(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 errorResult(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 errorResult(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 errorResult(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 errorResult(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 errorResult(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
|
export { registerBuiltInActions };
|