@nshiab/simple-data-analysis-core 2.0.5 → 2.0.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/README.md +118 -12
- package/esm/class/Simple.d.ts +8 -2
- package/esm/class/Simple.d.ts.map +1 -1
- package/esm/class/Simple.js +9 -3
- package/esm/class/SimpleDB.d.ts +72 -1
- package/esm/class/SimpleDB.d.ts.map +1 -1
- package/esm/class/SimpleDB.js +81 -1
- package/esm/class/SimpleTable.d.ts +271 -288
- package/esm/class/SimpleTable.d.ts.map +1 -1
- package/esm/class/SimpleTable.js +269 -340
- package/esm/helpers/LogValue.d.ts +8 -0
- package/esm/helpers/LogValue.d.ts.map +1 -0
- package/esm/helpers/LogValue.js +23 -0
- package/esm/helpers/cacheTableDependencies.d.ts +9 -0
- package/esm/helpers/cacheTableDependencies.d.ts.map +1 -1
- package/esm/helpers/cacheTableDependencies.js +131 -21
- package/esm/helpers/cleanSQL.d.ts +1 -1
- package/esm/helpers/cleanSQL.d.ts.map +1 -1
- package/esm/helpers/cleanSQL.js +268 -23
- package/esm/helpers/flushAllTables.d.ts.map +1 -1
- package/esm/helpers/flushAllTables.js +23 -28
- package/esm/helpers/foldIdentifier.d.ts +3 -0
- package/esm/helpers/foldIdentifier.d.ts.map +1 -0
- package/esm/helpers/foldIdentifier.js +4 -0
- package/esm/helpers/geometryFromJSON.d.ts +3 -0
- package/esm/helpers/geometryFromJSON.d.ts.map +1 -0
- package/esm/helpers/geometryFromJSON.js +4 -0
- package/esm/helpers/index.d.ts +1 -0
- package/esm/helpers/index.d.ts.map +1 -1
- package/esm/helpers/index.js +1 -0
- package/esm/helpers/logData.d.ts.map +1 -1
- package/esm/helpers/logData.js +45 -44
- package/esm/helpers/makeLogConverter.d.ts +5 -0
- package/esm/helpers/makeLogConverter.d.ts.map +1 -0
- package/esm/helpers/makeLogConverter.js +64 -0
- package/esm/helpers/osmFiles.d.ts.map +1 -1
- package/esm/helpers/parseTypes.d.ts +1 -1
- package/esm/helpers/parseTypes.d.ts.map +1 -1
- package/esm/helpers/parseTypes.js +4 -0
- package/esm/helpers/pendingOps.d.ts +4 -2
- package/esm/helpers/pendingOps.d.ts.map +1 -1
- package/esm/helpers/prepareGeometry.d.ts +3 -0
- package/esm/helpers/prepareGeometry.d.ts.map +1 -0
- package/esm/helpers/prepareGeometry.js +99 -0
- package/esm/helpers/prepareJSON.d.ts +3 -0
- package/esm/helpers/prepareJSON.d.ts.map +1 -0
- package/esm/helpers/prepareJSON.js +50 -0
- package/esm/helpers/printTable.d.ts +5 -4
- package/esm/helpers/printTable.d.ts.map +1 -1
- package/esm/helpers/printTable.js +32 -36
- package/esm/helpers/queryDB.js +1 -1
- package/esm/helpers/queueAsyncBarrier.d.ts +4 -0
- package/esm/helpers/queueAsyncBarrier.d.ts.map +1 -1
- package/esm/helpers/queueAsyncBarrier.js +4 -0
- package/esm/helpers/queueOp.d.ts.map +1 -1
- package/esm/helpers/queueOp.js +5 -3
- package/esm/helpers/readMutationRows.d.ts +9 -0
- package/esm/helpers/readMutationRows.d.ts.map +1 -0
- package/esm/helpers/readMutationRows.js +94 -0
- package/esm/helpers/referencedTables.d.ts +9 -0
- package/esm/helpers/referencedTables.d.ts.map +1 -0
- package/esm/helpers/referencedTables.js +22 -0
- package/esm/helpers/runQuery.d.ts.map +1 -1
- package/esm/helpers/runQuery.js +11 -13
- package/esm/helpers/scanSQL.d.ts +7 -0
- package/esm/helpers/scanSQL.d.ts.map +1 -0
- package/esm/helpers/scanSQL.js +71 -0
- package/esm/helpers/tableRegistry.d.ts +2 -0
- package/esm/helpers/tableRegistry.d.ts.map +1 -1
- package/esm/helpers/tableRegistry.js +12 -4
- package/esm/helpers/updateColumnsWithJS.d.ts +52 -0
- package/esm/helpers/updateColumnsWithJS.d.ts.map +1 -0
- package/esm/helpers/updateColumnsWithJS.js +139 -0
- package/esm/methods/addColumn.d.ts +1 -1
- package/esm/methods/addColumn.d.ts.map +1 -1
- package/esm/methods/bins.d.ts +1 -1
- package/esm/methods/bins.d.ts.map +1 -1
- package/esm/methods/bins.js +45 -29
- package/esm/methods/bm25.d.ts.map +1 -1
- package/esm/methods/bm25.js +1 -2
- package/esm/methods/cache.d.ts.map +1 -1
- package/esm/methods/cache.js +4 -6
- package/esm/methods/convert.d.ts +2 -2
- package/esm/methods/convert.d.ts.map +1 -1
- package/esm/methods/convert.js +5 -0
- package/esm/methods/getBottom.d.ts +1 -1
- package/esm/methods/getBottom.d.ts.map +1 -1
- package/esm/methods/getBottom.js +2 -2
- package/esm/methods/getData.d.ts +1 -1
- package/esm/methods/getData.d.ts.map +1 -1
- package/esm/methods/getData.js +2 -2
- package/esm/methods/join.d.ts.map +1 -1
- package/esm/methods/join.js +8 -16
- package/esm/methods/joinGeo.d.ts.map +1 -1
- package/esm/methods/joinGeo.js +10 -19
- package/esm/methods/loadArray.d.ts +1 -1
- package/esm/methods/loadArray.d.ts.map +1 -1
- package/esm/methods/loadArray.js +74 -19
- package/esm/methods/loadData.js +3 -0
- package/esm/methods/log.d.ts +7 -0
- package/esm/methods/log.d.ts.map +1 -0
- package/esm/methods/log.js +63 -0
- package/esm/methods/logBottom.d.ts +5 -0
- package/esm/methods/logBottom.d.ts.map +1 -0
- package/esm/methods/logBottom.js +9 -0
- package/esm/methods/removeTable.d.ts.map +1 -1
- package/esm/methods/removeTable.js +2 -0
- package/esm/methods/removeTables.d.ts.map +1 -1
- package/esm/methods/removeTables.js +7 -0
- package/esm/methods/renameTable.d.ts.map +1 -1
- package/esm/methods/renameTable.js +4 -0
- package/esm/methods/rowToText.js +2 -2
- package/esm/methods/selectTables.d.ts.map +1 -1
- package/esm/methods/selectTables.js +6 -0
- package/esm/methods/setTypes.d.ts +1 -1
- package/esm/methods/setTypes.d.ts.map +1 -1
- package/esm/methods/stream.d.ts.map +1 -1
- package/esm/methods/stream.js +2 -1
- package/esm/methods/updateWithJS.d.ts +1 -3
- package/esm/methods/updateWithJS.d.ts.map +1 -1
- package/esm/methods/updateWithJS.js +123 -37
- package/llm.md +365 -461
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/script/class/Simple.d.ts +8 -2
- package/script/class/Simple.d.ts.map +1 -1
- package/script/class/Simple.js +9 -3
- package/script/class/SimpleDB.d.ts +72 -1
- package/script/class/SimpleDB.d.ts.map +1 -1
- package/script/class/SimpleDB.js +81 -1
- package/script/class/SimpleTable.d.ts +271 -288
- package/script/class/SimpleTable.d.ts.map +1 -1
- package/script/class/SimpleTable.js +269 -340
- package/script/helpers/LogValue.d.ts +8 -0
- package/script/helpers/LogValue.d.ts.map +1 -0
- package/script/helpers/LogValue.js +26 -0
- package/script/helpers/cacheTableDependencies.d.ts +9 -0
- package/script/helpers/cacheTableDependencies.d.ts.map +1 -1
- package/script/helpers/cacheTableDependencies.js +170 -20
- package/script/helpers/cleanSQL.d.ts +1 -1
- package/script/helpers/cleanSQL.d.ts.map +1 -1
- package/script/helpers/cleanSQL.js +271 -23
- package/script/helpers/flushAllTables.d.ts.map +1 -1
- package/script/helpers/flushAllTables.js +24 -29
- package/script/helpers/foldIdentifier.d.ts +3 -0
- package/script/helpers/foldIdentifier.d.ts.map +1 -0
- package/script/helpers/foldIdentifier.js +7 -0
- package/script/helpers/geometryFromJSON.d.ts +3 -0
- package/script/helpers/geometryFromJSON.d.ts.map +1 -0
- package/script/helpers/geometryFromJSON.js +7 -0
- package/script/helpers/index.d.ts +1 -0
- package/script/helpers/index.d.ts.map +1 -1
- package/script/helpers/index.js +3 -1
- package/script/helpers/logData.d.ts.map +1 -1
- package/script/helpers/logData.js +45 -44
- package/script/helpers/makeLogConverter.d.ts +5 -0
- package/script/helpers/makeLogConverter.d.ts.map +1 -0
- package/script/helpers/makeLogConverter.js +70 -0
- package/script/helpers/osmFiles.d.ts.map +1 -1
- package/script/helpers/parseTypes.d.ts +1 -1
- package/script/helpers/parseTypes.d.ts.map +1 -1
- package/script/helpers/parseTypes.js +4 -0
- package/script/helpers/pendingOps.d.ts +4 -2
- package/script/helpers/pendingOps.d.ts.map +1 -1
- package/script/helpers/prepareGeometry.d.ts +3 -0
- package/script/helpers/prepareGeometry.d.ts.map +1 -0
- package/script/helpers/prepareGeometry.js +102 -0
- package/script/helpers/prepareJSON.d.ts +3 -0
- package/script/helpers/prepareJSON.d.ts.map +1 -0
- package/script/helpers/prepareJSON.js +53 -0
- package/script/helpers/printTable.d.ts +5 -4
- package/script/helpers/printTable.d.ts.map +1 -1
- package/script/helpers/printTable.js +32 -36
- package/script/helpers/queryDB.js +1 -1
- package/script/helpers/queueAsyncBarrier.d.ts +4 -0
- package/script/helpers/queueAsyncBarrier.d.ts.map +1 -1
- package/script/helpers/queueAsyncBarrier.js +4 -0
- package/script/helpers/queueOp.d.ts.map +1 -1
- package/script/helpers/queueOp.js +4 -2
- package/script/helpers/readMutationRows.d.ts +9 -0
- package/script/helpers/readMutationRows.d.ts.map +1 -0
- package/script/helpers/readMutationRows.js +97 -0
- package/script/helpers/referencedTables.d.ts +9 -0
- package/script/helpers/referencedTables.d.ts.map +1 -0
- package/script/helpers/referencedTables.js +28 -0
- package/script/helpers/runQuery.d.ts.map +1 -1
- package/script/helpers/runQuery.js +11 -13
- package/script/helpers/scanSQL.d.ts +7 -0
- package/script/helpers/scanSQL.d.ts.map +1 -0
- package/script/helpers/scanSQL.js +74 -0
- package/script/helpers/tableRegistry.d.ts +2 -0
- package/script/helpers/tableRegistry.d.ts.map +1 -1
- package/script/helpers/tableRegistry.js +16 -4
- package/script/helpers/updateColumnsWithJS.d.ts +52 -0
- package/script/helpers/updateColumnsWithJS.d.ts.map +1 -0
- package/script/helpers/updateColumnsWithJS.js +145 -0
- package/script/methods/addColumn.d.ts +1 -1
- package/script/methods/addColumn.d.ts.map +1 -1
- package/script/methods/bins.d.ts +1 -1
- package/script/methods/bins.d.ts.map +1 -1
- package/script/methods/bins.js +45 -29
- package/script/methods/bm25.d.ts.map +1 -1
- package/script/methods/bm25.js +1 -2
- package/script/methods/cache.d.ts.map +1 -1
- package/script/methods/cache.js +3 -5
- package/script/methods/convert.d.ts +2 -2
- package/script/methods/convert.d.ts.map +1 -1
- package/script/methods/convert.js +5 -0
- package/script/methods/getBottom.d.ts +1 -1
- package/script/methods/getBottom.d.ts.map +1 -1
- package/script/methods/getBottom.js +2 -2
- package/script/methods/getData.d.ts +1 -1
- package/script/methods/getData.d.ts.map +1 -1
- package/script/methods/getData.js +2 -2
- package/script/methods/join.d.ts.map +1 -1
- package/script/methods/join.js +8 -16
- package/script/methods/joinGeo.d.ts.map +1 -1
- package/script/methods/joinGeo.js +10 -19
- package/script/methods/loadArray.d.ts +1 -1
- package/script/methods/loadArray.d.ts.map +1 -1
- package/script/methods/loadArray.js +74 -19
- package/script/methods/loadData.js +3 -0
- package/script/methods/log.d.ts +7 -0
- package/script/methods/log.d.ts.map +1 -0
- package/script/methods/log.js +69 -0
- package/script/methods/logBottom.d.ts +5 -0
- package/script/methods/logBottom.d.ts.map +1 -0
- package/script/methods/logBottom.js +15 -0
- package/script/methods/removeTable.d.ts.map +1 -1
- package/script/methods/removeTable.js +2 -0
- package/script/methods/removeTables.d.ts.map +1 -1
- package/script/methods/removeTables.js +7 -0
- package/script/methods/renameTable.d.ts.map +1 -1
- package/script/methods/renameTable.js +4 -0
- package/script/methods/rowToText.js +2 -2
- package/script/methods/selectTables.d.ts.map +1 -1
- package/script/methods/selectTables.js +6 -0
- package/script/methods/setTypes.d.ts +1 -1
- package/script/methods/setTypes.d.ts.map +1 -1
- package/script/methods/stream.d.ts.map +1 -1
- package/script/methods/stream.js +2 -1
- package/script/methods/updateWithJS.d.ts +1 -3
- package/script/methods/updateWithJS.d.ts.map +1 -1
- package/script/methods/updateWithJS.js +122 -36
|
@@ -13,7 +13,7 @@ export declare function prepareArray(rows: {
|
|
|
13
13
|
[key: string]: unknown;
|
|
14
14
|
}[], columnTypes?: {
|
|
15
15
|
[key: string]: string;
|
|
16
|
-
}): PreparedArray;
|
|
16
|
+
}, requireExplicitNestedTypes?: boolean): PreparedArray;
|
|
17
17
|
export declare function executePreparedArray(simpleTable: SimpleTable, prepared: PreparedArray): Promise<void>;
|
|
18
18
|
export {};
|
|
19
19
|
//# sourceMappingURL=loadArray.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadArray.d.ts","sourceRoot":"","sources":["../../src/methods/loadArray.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"loadArray.d.ts","sourceRoot":"","sources":["../../src/methods/loadArray.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAK1B,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAKvD,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,EAClC,OAAO,GAAE,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAM,QAiBnE;AAED,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,WAAW,EAAE,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,YAAY,CAC1B,IAAI,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,EAClC,WAAW,GAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAO,EAC3C,0BAA0B,UAAQ,GACjC,aAAa,CAsHf;AAED,wBAAsB,oBAAoB,CACxC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,aAAa,iBA4ExB"}
|
package/esm/methods/loadArray.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { arrayValue, DuckDBDataChunk, DuckDBDateValue, DuckDBTimestampTZValue, DuckDBTimestampValue, DuckDBTimeValue, } from "@duckdb/node-api";
|
|
2
|
+
import prepareGeometry from "../helpers/prepareGeometry.js";
|
|
3
|
+
import prepareJSON from "../helpers/prepareJSON.js";
|
|
4
|
+
import geometryFromJSON from "../helpers/geometryFromJSON.js";
|
|
2
5
|
import queueOp from "../helpers/queueOp.js";
|
|
3
6
|
import parseType from "../helpers/parseTypes.js";
|
|
4
7
|
import parseDuckDBType from "../helpers/parseDuckDBType.js";
|
|
@@ -8,7 +11,7 @@ export default function loadArray(simpleTable, rows, options = {}) {
|
|
|
8
11
|
if (rows.length === 0) {
|
|
9
12
|
throw new Error("The array is empty. loadArray needs at least one object to infer the column types.");
|
|
10
13
|
}
|
|
11
|
-
const prepared = prepareArray(rows, options.columnTypes);
|
|
14
|
+
const prepared = prepareArray(rows, options.columnTypes, true);
|
|
12
15
|
queueOp(simpleTable, {
|
|
13
16
|
kind: "barrier",
|
|
14
17
|
method: "loadArray()",
|
|
@@ -16,17 +19,26 @@ export default function loadArray(simpleTable, rows, options = {}) {
|
|
|
16
19
|
execute: () => executePreparedArray(simpleTable, prepared),
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
|
-
export function prepareArray(rows, columnTypes = {}) {
|
|
22
|
+
export function prepareArray(rows, columnTypes = {}, requireExplicitNestedTypes = false) {
|
|
20
23
|
const keys = Object.keys(rows[0]);
|
|
21
24
|
const overrides = new Map();
|
|
22
25
|
for (const [key, type] of Object.entries(columnTypes)) {
|
|
23
26
|
if (!keys.includes(key)) {
|
|
24
27
|
throw new Error(`Unknown column ${JSON.stringify(key)} in loadArray columnTypes.`);
|
|
25
28
|
}
|
|
29
|
+
if (type.toUpperCase() === "JSON") {
|
|
30
|
+
overrides.set(key, "JSON");
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (/^geometry/i.test(type)) {
|
|
34
|
+
if (!/^geometry\('EPSG:4326'\)$/i.test(type)) {
|
|
35
|
+
throw new Error(`Unsupported type ${JSON.stringify(type)} for column ${JSON.stringify(key)}. GeoJSON ingestion requires GEOMETRY('EPSG:4326'); reproject input to WGS84 first.`);
|
|
36
|
+
}
|
|
37
|
+
overrides.set(key, "GEOMETRY('EPSG:4326')");
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
26
40
|
try {
|
|
27
|
-
const normalized =
|
|
28
|
-
? type.toUpperCase()
|
|
29
|
-
: parseType(type);
|
|
41
|
+
const normalized = parseType(type);
|
|
30
42
|
parseDuckDBType(normalized);
|
|
31
43
|
overrides.set(key, normalized);
|
|
32
44
|
}
|
|
@@ -48,6 +60,14 @@ export function prepareArray(rows, columnTypes = {}) {
|
|
|
48
60
|
}
|
|
49
61
|
continue;
|
|
50
62
|
}
|
|
63
|
+
if (requireExplicitNestedTypes) {
|
|
64
|
+
for (let j = 0; j < rows.length; j++) {
|
|
65
|
+
const cell = rows[j][key];
|
|
66
|
+
if (cell !== null && typeof cell === "object" && !(cell instanceof Date)) {
|
|
67
|
+
throw new Error(`Column ${JSON.stringify(key)}, row ${j + 1}: array and object cells require an explicit columnTypes entry. Use JSON for nested data, FLOAT[n] for fixed-size vectors, or GEOMETRY('EPSG:4326') for GeoJSON geometries.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
const value = firstNonNullValue[i];
|
|
52
72
|
const type = typeof value;
|
|
53
73
|
if (type === "symbol" || type === "function") {
|
|
@@ -104,24 +124,59 @@ export async function executePreparedArray(simpleTable, prepared) {
|
|
|
104
124
|
simpleTable.connection = simpleTable.sdb.connection;
|
|
105
125
|
}
|
|
106
126
|
const { keys, types, columnsData, rowCount } = prepared;
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
appender.
|
|
127
|
+
const hasGeometry = types.includes("GEOMETRY('EPSG:4326')");
|
|
128
|
+
const needsConversion = hasGeometry || types.includes("JSON");
|
|
129
|
+
const staged = needsConversion
|
|
130
|
+
? `__sda_array_${crypto.randomUUID().replaceAll("-", "")}`
|
|
131
|
+
: simpleTable.name;
|
|
132
|
+
const storageTypes = types.map((type) => type.startsWith("GEOMETRY") || type === "JSON" ? "VARCHAR" : type);
|
|
133
|
+
if (hasGeometry && !simpleTable.sdb.spatialLoaded) {
|
|
134
|
+
await simpleTable.sdb.customQuery("INSTALL spatial; LOAD spatial; SET geometry_always_xy = true;");
|
|
135
|
+
simpleTable.sdb.spatialLoaded = true;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await simpleTable.sdb.customQuery(`CREATE ${needsConversion ? "TEMP" : "OR REPLACE"} TABLE ${quoteIdentifier(staged)}(${keys.map((key, i) => `${quoteIdentifier(key)} ${storageTypes[i]}`).join(", ")})`);
|
|
139
|
+
const appender = await simpleTable.connection
|
|
140
|
+
.createAppender(staged);
|
|
141
|
+
try {
|
|
142
|
+
const duckDBTypes = storageTypes.map((d) => parseDuckDBType(d));
|
|
143
|
+
// The maximum capacity of a DuckDB data chunk is 2048 rows.
|
|
144
|
+
const chunkSize = 2000;
|
|
145
|
+
for (let start = 0; start < rowCount; start += chunkSize) {
|
|
146
|
+
const end = Math.min(start + chunkSize, rowCount);
|
|
147
|
+
const dataChunk = DuckDBDataChunk.create(duckDBTypes, end - start);
|
|
148
|
+
for (let i = 0; i < keys.length; i++) {
|
|
149
|
+
dataChunk.setColumnValues(i, columnsData[i].slice(start, end));
|
|
150
|
+
}
|
|
151
|
+
appender.appendDataChunk(dataChunk);
|
|
152
|
+
}
|
|
153
|
+
appender.flushSync();
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
appender.closeSync();
|
|
157
|
+
}
|
|
158
|
+
if (!needsConversion)
|
|
159
|
+
return;
|
|
160
|
+
// CTAS is atomic: conversion must finish before the destination is replaced.
|
|
161
|
+
await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${quoteIdentifier(simpleTable.name)} AS SELECT ${keys.map((key, i) => types[i].startsWith("GEOMETRY")
|
|
162
|
+
? `${geometryFromJSON(quoteIdentifier(key))} AS ${quoteIdentifier(key)}`
|
|
163
|
+
: types[i] === "JSON"
|
|
164
|
+
? `${quoteIdentifier(key)}::JSON AS ${quoteIdentifier(key)}`
|
|
165
|
+
: quoteIdentifier(key)).join(", ")} FROM ${quoteIdentifier(staged)}`);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
if (needsConversion) {
|
|
169
|
+
await simpleTable.sdb.customQuery(`DROP TABLE IF EXISTS ${quoteIdentifier(staged)}`);
|
|
170
|
+
}
|
|
120
171
|
}
|
|
121
|
-
appender.flushSync();
|
|
122
172
|
}
|
|
123
173
|
/** Validates and snapshots a value for an explicitly declared destination type. */
|
|
124
174
|
function prepareTypedValue(value, type, column, row) {
|
|
175
|
+
if (type === "JSON")
|
|
176
|
+
return prepareJSON(value, column, row);
|
|
177
|
+
if (type === "GEOMETRY('EPSG:4326')") {
|
|
178
|
+
return prepareGeometry(value, column, row);
|
|
179
|
+
}
|
|
125
180
|
if (value === null || value === undefined || Number.isNaN(value))
|
|
126
181
|
return null;
|
|
127
182
|
let compatible = false;
|
package/esm/methods/loadData.js
CHANGED
|
@@ -47,6 +47,9 @@ export function loadDataQuery(table, files, options = {}) {
|
|
|
47
47
|
return `CREATE OR REPLACE TABLE ${quoteIdentifier(table)} AS ${loadDataSelect(files, options)};`;
|
|
48
48
|
}
|
|
49
49
|
function loadDataSelect(files, options = {}) {
|
|
50
|
+
if (Object.values(options.columnTypes ?? {}).some((type) => /^\s*geometry\b/i.test(type))) {
|
|
51
|
+
throw new Error("Geometry column types are not supported by loadData(). Use loadGeoData() instead.");
|
|
52
|
+
}
|
|
50
53
|
const fileExtension = getExtension(files[0]);
|
|
51
54
|
if (options.fileType !== undefined &&
|
|
52
55
|
!["csv", "dsv", "json", "parquet", "excel"].includes(options.fileType)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/methods/log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAMvD,wBAA8B,GAAG,CAC/B,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE,KAAK,GAAG,MAAM,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,iBA4EP"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logData from "../helpers/logData.js";
|
|
2
|
+
import getData from "./getData.js";
|
|
3
|
+
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
4
|
+
import formatNumber from "../helpers/formatNumber.js";
|
|
5
|
+
export default async function log(simpleTable, options = {}) {
|
|
6
|
+
if (simpleTable.connection === undefined) {
|
|
7
|
+
await simpleTable.sdb.start();
|
|
8
|
+
simpleTable.db = simpleTable.sdb.db;
|
|
9
|
+
simpleTable.connection = simpleTable.sdb.connection;
|
|
10
|
+
}
|
|
11
|
+
if (simpleTable.connection === undefined) {
|
|
12
|
+
throw new Error("simpleTable.connection is undefined");
|
|
13
|
+
}
|
|
14
|
+
let count;
|
|
15
|
+
if (typeof options === "number") {
|
|
16
|
+
count = options;
|
|
17
|
+
}
|
|
18
|
+
else if (options === "all") {
|
|
19
|
+
count = await simpleTable.getRowCount();
|
|
20
|
+
}
|
|
21
|
+
else if (typeof options === "object") {
|
|
22
|
+
if (options.count === "all") {
|
|
23
|
+
count = await simpleTable.getRowCount();
|
|
24
|
+
}
|
|
25
|
+
else if (typeof options.count === "number") {
|
|
26
|
+
count = options.count;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
count = simpleTable.rowsToLog;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
count = simpleTable.rowsToLog;
|
|
34
|
+
}
|
|
35
|
+
const types = typeof options === "object"
|
|
36
|
+
? options.types ?? simpleTable.typesToLog
|
|
37
|
+
: simpleTable.typesToLog;
|
|
38
|
+
const conditions = typeof options === "object"
|
|
39
|
+
? options.conditions ?? undefined
|
|
40
|
+
: undefined;
|
|
41
|
+
if (simpleTable.connection === undefined ||
|
|
42
|
+
!(await simpleTable.sdb.hasTable(simpleTable.name))) {
|
|
43
|
+
console.log(`\nTable ${simpleTable.name}: no data`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(`\nTable ${simpleTable.name}:`);
|
|
47
|
+
conditions && console.log(`Conditions: ${conditions}`);
|
|
48
|
+
const data = await getData(simpleTable, { limit: count, conditions }, "log()");
|
|
49
|
+
logData(types ? await simpleTable.getTypes() : null, data, simpleTable.charsToLog);
|
|
50
|
+
const rowCount = conditions
|
|
51
|
+
? parseInt((await simpleTable.sdb.customQuery(`select count(*) as count from ${quoteIdentifier(simpleTable.name)} where ${conditions}`, { returnData: true }))[0].count)
|
|
52
|
+
: await simpleTable.getRowCount();
|
|
53
|
+
const displayedRows = data.length;
|
|
54
|
+
const showing = displayedRows < rowCount
|
|
55
|
+
? ` / showing ${formatNumber(displayedRows)} rows`
|
|
56
|
+
: "";
|
|
57
|
+
const charsToLog = typeof simpleTable.charsToLog === "number"
|
|
58
|
+
? ` (charsToLog: ${simpleTable.charsToLog})`
|
|
59
|
+
: "";
|
|
60
|
+
console.log(`${formatNumber(rowCount)} rows in total${showing}${charsToLog}`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logBottom.d.ts","sourceRoot":"","sources":["../../src/methods/logBottom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAIvD,wBAA8B,SAAS,CACrC,WAAW,EAAE,WAAW,EACxB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,iBAW1C"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import logData from "../helpers/logData.js";
|
|
2
|
+
import getBottom from "./getBottom.js";
|
|
3
|
+
export default async function logBottom(simpleTable, count, options = {}) {
|
|
4
|
+
const rows = count ?? simpleTable.rowsToLog;
|
|
5
|
+
console.log(`\nTable ${simpleTable.name} (${rows} bottom rows):`);
|
|
6
|
+
const data = await getBottom(simpleTable, rows, options, "logBottom()");
|
|
7
|
+
logData(null, data, simpleTable.charsToLog);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeTable.d.ts","sourceRoot":"","sources":["../../src/methods/removeTable.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"removeTable.d.ts","sourceRoot":"","sources":["../../src/methods/removeTable.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAGvD,wBAA8B,WAAW,CACvC,WAAW,EAAE,WAAW,iBAiBzB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { assertCacheTableMutation } from "../helpers/cacheTableDependencies.js";
|
|
1
2
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
3
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
3
4
|
import queryDB from "../helpers/queryDB.js";
|
|
4
5
|
import { retainRegisteredTables } from "../helpers/tableRegistry.js";
|
|
5
6
|
export default async function removeTable(simpleTable) {
|
|
7
|
+
assertCacheTableMutation(simpleTable);
|
|
6
8
|
await queryDB(simpleTable, `DROP TABLE ${quoteIdentifier(simpleTable.name)};`, mergeOptions(simpleTable, {
|
|
7
9
|
table: null,
|
|
8
10
|
method: "removeTable()",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"removeTables.d.ts","sourceRoot":"","sources":["../../src/methods/removeTables.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"removeTables.d.ts","sourceRoot":"","sources":["../../src/methods/removeTables.ts"],"names":[],"mappings":"AAIA,OAAO,WAAW,MAAM,yBAAyB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AAIjD,wBAA8B,YAAY,CACxC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,EAAE,iBA2CxD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertCacheTableMutation } from "../helpers/cacheTableDependencies.js";
|
|
1
2
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
3
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
3
4
|
import queryDB from "../helpers/queryDB.js";
|
|
@@ -11,6 +12,12 @@ export default async function removeTables(simpleDB, tables) {
|
|
|
11
12
|
? tables
|
|
12
13
|
: [tables];
|
|
13
14
|
assertSameDatabase(simpleDB, tablesToBeRemoved.filter((table) => table instanceof SimpleTable), "removeTables()");
|
|
15
|
+
const names = tablesToBeRemoved.map((table) => (table instanceof SimpleTable ? table.name : table).toLowerCase());
|
|
16
|
+
for (const table of simpleDB.getTables()) {
|
|
17
|
+
if (names.includes(table.name.toLowerCase())) {
|
|
18
|
+
assertCacheTableMutation(table);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
14
21
|
await queryDB(simpleDB, tablesToBeRemoved.map((d) => `DROP TABLE ${quoteIdentifier(d instanceof SimpleTable ? d.name : d)};`).join("\n"), mergeOptions(simpleDB, {
|
|
15
22
|
table: null,
|
|
16
23
|
method: "removeTable()",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renameTable.d.ts","sourceRoot":"","sources":["../../src/methods/renameTable.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"renameTable.d.ts","sourceRoot":"","sources":["../../src/methods/renameTable.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAIvD,wBAA8B,WAAW,CACvC,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,MAAM,iBAgBb"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { assertCacheTableMutation } from "../helpers/cacheTableDependencies.js";
|
|
1
2
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
3
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
3
4
|
import queryDB from "../helpers/queryDB.js";
|
|
4
5
|
import { markTableChanged } from "../helpers/tableGeneration.js";
|
|
6
|
+
import { assertTableNameAvailable } from "../helpers/tableRegistry.js";
|
|
5
7
|
export default async function renameTable(simpleTable, name) {
|
|
8
|
+
assertCacheTableMutation(simpleTable);
|
|
9
|
+
assertTableNameAvailable(simpleTable.sdb, name, simpleTable);
|
|
6
10
|
await queryDB(simpleTable, `ALTER TABLE ${quoteIdentifier(simpleTable.name)} RENAME TO ${quoteIdentifier(name)};`, mergeOptions(simpleTable, {
|
|
7
11
|
table: simpleTable.name,
|
|
8
12
|
method: "renameTable()",
|
package/esm/methods/rowToText.js
CHANGED
|
@@ -19,8 +19,8 @@ export default function rowToText(simpleTable, columns, newColumn) {
|
|
|
19
19
|
throw new Error(`The column ${col} is of type ${allTypes[col]}. The rowToText() method only works with string columns. Please convert the column to string first with the .convert() method.`);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
const parts = columns.map((col) =>
|
|
23
|
-
const concatenatedExpression = parts.join("
|
|
22
|
+
const parts = columns.map((col) => `concat(?, COALESCE(${quoteIdentifier(col)}, ?))`);
|
|
23
|
+
const concatenatedExpression = `concat(${parts.join(", ")})`;
|
|
24
24
|
return `SELECT *, CAST(${concatenatedExpression} AS VARCHAR) AS ${quoteIdentifier(newColumn)} FROM ${input}`;
|
|
25
25
|
},
|
|
26
26
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectTables.d.ts","sourceRoot":"","sources":["../../src/methods/selectTables.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"selectTables.d.ts","sourceRoot":"","sources":["../../src/methods/selectTables.ts"],"names":[],"mappings":"AAIA,OAAO,WAAW,MAAM,yBAAyB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,sBAAsB,CAAC;AAKjD,wBAA8B,YAAY,CACxC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,EAAE,iBAqDxD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertCacheTableMutation } from "../helpers/cacheTableDependencies.js";
|
|
1
2
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
3
|
import mergeOptions from "../helpers/mergeOptions.js";
|
|
3
4
|
import queryDB from "../helpers/queryDB.js";
|
|
@@ -9,6 +10,11 @@ export default async function selectTables(simpleDB, tables) {
|
|
|
9
10
|
const selected = Array.isArray(tables) ? tables : [tables];
|
|
10
11
|
assertSameDatabase(simpleDB, selected.filter((table) => table instanceof SimpleTable), "selectTables()");
|
|
11
12
|
const tablesToBeSelected = selected.map((t) => t instanceof SimpleTable ? t.name : t);
|
|
13
|
+
for (const table of simpleDB.getTables()) {
|
|
14
|
+
if (!tablesToBeSelected.includes(table.name)) {
|
|
15
|
+
assertCacheTableMutation(table);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
12
18
|
const existingTables = await simpleDB.getTableNames();
|
|
13
19
|
const missingTables = tablesToBeSelected.filter((table) => !existingTables.includes(table));
|
|
14
20
|
if (missingTables.length > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type SimpleTable from "../class/SimpleTable.js";
|
|
2
2
|
export default function setTypes(simpleTable: SimpleTable, types: {
|
|
3
|
-
[key: string]: "integer" | "float" | "number" | "string" | "date" | "time" | "datetime" | "datetimeTz" | "bigint" | "double" | "varchar" | "timestamp" | "timestamp with time zone" | "boolean" | `geometry('${string}')` | `GEOMETRY('${string}')`;
|
|
3
|
+
[key: string]: "integer" | "float" | "number" | "string" | "date" | "time" | "datetime" | "datetimeTz" | "bigint" | "double" | "varchar" | "timestamp" | "timestamp with time zone" | "boolean" | "json" | "JSON" | `float[${number}]` | `FLOAT[${number}]` | `geometry('${string}')` | `GEOMETRY('${string}')`;
|
|
4
4
|
}): void;
|
|
5
5
|
//# sourceMappingURL=setTypes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setTypes.d.ts","sourceRoot":"","sources":["../../src/methods/setTypes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE;IACL,CAAC,GAAG,EAAE,MAAM,GACR,SAAS,GACT,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,WAAW,GACX,0BAA0B,GAC1B,SAAS,GACT,aAAa,MAAM,IAAI,GACvB,aAAa,MAAM,IAAI,CAAC;CAC7B,QAkCF"}
|
|
1
|
+
{"version":3,"file":"setTypes.d.ts","sourceRoot":"","sources":["../../src/methods/setTypes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE;IACL,CAAC,GAAG,EAAE,MAAM,GACR,SAAS,GACT,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,WAAW,GACX,0BAA0B,GAC1B,SAAS,GACT,MAAM,GACN,MAAM,GACN,SAAS,MAAM,GAAG,GAClB,SAAS,MAAM,GAAG,GAClB,aAAa,MAAM,IAAI,GACvB,aAAa,MAAM,IAAI,CAAC;CAC7B,QAkCF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/methods/stream.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/methods/stream.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAGvD,wBAA+B,MAAM,CACnC,WAAW,EAAE,WAAW,EACxB,OAAO,GAAE;IACP,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CAChB,GACL,cAAc,CAAC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAAE,IAAI,EAAE,SAAS,CAAC,CAsE7D"}
|
package/esm/methods/stream.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import cleanSQL from "../helpers/cleanSQL.js";
|
|
1
2
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
3
|
import { makeConverter } from "../helpers/runQuery.js";
|
|
3
4
|
import SDAError from "../class/SDAError.js";
|
|
@@ -17,7 +18,7 @@ export default async function* stream(simpleTable, options = {}) {
|
|
|
17
18
|
? [options.columns]
|
|
18
19
|
: options.columns)
|
|
19
20
|
: undefined;
|
|
20
|
-
const query = `SELECT ${columns ? columns.map((d) => `${quoteIdentifier(d)}`).join(", ") : "*"} FROM ${quoteIdentifier(simpleTable.name)}${options.conditions ? ` WHERE ${options.conditions}` : ""}
|
|
21
|
+
const query = cleanSQL(`SELECT ${columns ? columns.map((d) => `${quoteIdentifier(d)}`).join(", ") : "*"} FROM ${quoteIdentifier(simpleTable.name)}${options.conditions ? ` WHERE ${options.conditions}` : ""};`, simpleTable.sdb.expressionSyntax);
|
|
21
22
|
try {
|
|
22
23
|
await observeQuery(simpleTable.connection, query, [], {
|
|
23
24
|
logSQL: simpleTable.sdb.logSQL,
|
|
@@ -3,9 +3,7 @@ type Row = {
|
|
|
3
3
|
[key: string]: unknown;
|
|
4
4
|
};
|
|
5
5
|
type DataModifier = (rows: Row[]) => Row[] | Promise<Row[]>;
|
|
6
|
-
type UpdateWithJSOptions =
|
|
7
|
-
batchSize?: number;
|
|
8
|
-
};
|
|
6
|
+
type UpdateWithJSOptions = NonNullable<Parameters<SimpleTable["updateWithJS"]>[1]>;
|
|
9
7
|
export default function updateWithJS(simpleTable: SimpleTable, dataModifier: DataModifier, options?: UpdateWithJSOptions): void;
|
|
10
8
|
export {};
|
|
11
9
|
//# sourceMappingURL=updateWithJS.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateWithJS.d.ts","sourceRoot":"","sources":["../../src/methods/updateWithJS.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"updateWithJS.d.ts","sourceRoot":"","sources":["../../src/methods/updateWithJS.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAC;AAQvD,KAAK,GAAG,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAEtC,KAAK,YAAY,GAAG,CAClB,IAAI,EAAE,GAAG,EAAE,KACR,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAE5B,KAAK,mBAAmB,GAAG,WAAW,CACpC,UAAU,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAC3C,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAClC,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,YAAY,EAC1B,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAeN"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import prepareGeometry from "../helpers/prepareGeometry.js";
|
|
2
|
+
import geometryFromJSON from "../helpers/geometryFromJSON.js";
|
|
1
3
|
import quoteIdentifier from "../helpers/quoteIdentifier.js";
|
|
2
4
|
import { retainRegisteredTables } from "../helpers/tableRegistry.js";
|
|
3
5
|
import queueOp from "../helpers/queueOp.js";
|
|
4
|
-
import {
|
|
6
|
+
import { prepareArray } from "./loadArray.js";
|
|
7
|
+
import { DuckDBDataChunk } from "@duckdb/node-api";
|
|
8
|
+
import readMutationRows from "../helpers/readMutationRows.js";
|
|
9
|
+
import parseDuckDBType from "../helpers/parseDuckDBType.js";
|
|
5
10
|
export default function updateWithJS(simpleTable, dataModifier, options = {}) {
|
|
6
11
|
if (options.batchSize !== undefined &&
|
|
7
12
|
(!Number.isInteger(options.batchSize) || options.batchSize < 1)) {
|
|
@@ -17,39 +22,35 @@ export default function updateWithJS(simpleTable, dataModifier, options = {}) {
|
|
|
17
22
|
}
|
|
18
23
|
async function executeUpdateWithJS(simpleTable, dataModifier, options) {
|
|
19
24
|
const types = await simpleTable.getTypes();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (options.batchSize === undefined) {
|
|
25
|
-
const oldData = await simpleTable.getData();
|
|
26
|
-
if (!oldData) {
|
|
27
|
-
throw new Error("No data from getData.");
|
|
25
|
+
const geometryColumns = Object.keys(types).filter((key) => types[key].toUpperCase().startsWith("GEOMETRY"));
|
|
26
|
+
for (const column of geometryColumns) {
|
|
27
|
+
if (types[column].toUpperCase() !== "GEOMETRY('EPSG:4326')") {
|
|
28
|
+
throw new Error(`Column ${JSON.stringify(column)} has CRS ${types[column]}. Call .reproject("EPSG:4326", { column: ${JSON.stringify(column)} }) before updateWithJS().`);
|
|
28
29
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
const name = quoteIdentifier(column);
|
|
31
|
+
// Compare the actual binary representation, including dimensions and double
|
|
32
|
+
// precision, rather than assuming GeoJSON serialization is lossless.
|
|
33
|
+
const result = await simpleTable.connection.runAndReadAll(`SELECT count(*) AS invalid FROM ${quoteIdentifier(simpleTable.name)}
|
|
34
|
+
WHERE ${name} IS NOT NULL AND
|
|
35
|
+
ST_AsWKB(${name}) != ST_AsWKB(${geometryFromJSON(`ST_AsGeoJSON(${name})`)})`);
|
|
36
|
+
if (result.getRows()[0][0] !== 0n) {
|
|
37
|
+
throw new Error(`Column ${JSON.stringify(column)} contains geometry that cannot round-trip through GeoJSON without losing precision or dimensions; unsupported representations cannot be updated with updateWithJS().`);
|
|
36
38
|
}
|
|
37
|
-
await executePreparedArray(simpleTable, prepareArray(newData));
|
|
38
|
-
return;
|
|
39
39
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
for (const key of Object.keys(options.columnTypes ?? {})) {
|
|
41
|
+
if (Object.keys(types).some((name) => name.toLowerCase() === key.toLowerCase())) {
|
|
42
|
+
throw new Error(`updateWithJS() columnTypes may only declare new columns; ${JSON.stringify(key)} already exists.`);
|
|
43
|
+
}
|
|
43
44
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const batchSize = options.batchSize;
|
|
46
|
+
const geometryBatch = batchSize !== undefined && geometryColumns.length > 0;
|
|
47
|
+
if (batchSize !== undefined) {
|
|
48
|
+
// DuckDB resolves identifiers case-insensitively, and a real rowid column
|
|
49
|
+
// shadows its hidden row identifier used for pagination.
|
|
50
|
+
const conflictingColumn = Object.keys(types).find((column) => ["rowid", "__sda_rowid"].includes(column.toLowerCase()));
|
|
51
|
+
if (conflictingColumn !== undefined) {
|
|
52
|
+
throw new Error(`The table has a column named "${conflictingColumn}", which conflicts with the internal column used by the batchSize option. Rename it or run updateWithJS without batchSize.`);
|
|
50
53
|
}
|
|
51
|
-
await executePreparedArray(simpleTable, prepareArray(newData));
|
|
52
|
-
return;
|
|
53
54
|
}
|
|
54
55
|
// Rows are pulled in batches by rowid, passed through the modifier, and
|
|
55
56
|
// accumulated in a temporary table, so only one batch of rows is
|
|
@@ -60,27 +61,112 @@ async function executeUpdateWithJS(simpleTable, dataModifier, options) {
|
|
|
60
61
|
try {
|
|
61
62
|
let first = true;
|
|
62
63
|
let lastRowid = null;
|
|
64
|
+
let sawRows = false;
|
|
65
|
+
let outputOffset = 0;
|
|
66
|
+
let inputOffset = 0;
|
|
63
67
|
while (true) {
|
|
64
|
-
const
|
|
65
|
-
|
|
68
|
+
const page = `${quoteIdentifier(simpleTable.name)}${lastRowid === null ? "" : ` WHERE rowid > ${lastRowid}`}${batchSize === undefined ? "" : ` ORDER BY rowid LIMIT ${batchSize}`}`;
|
|
69
|
+
// Select the batch before serializing geometry. Otherwise DuckDB can
|
|
70
|
+
// evaluate ST_AsGeoJSON on remaining rows before applying the LIMIT,
|
|
71
|
+
// repeatedly converting polygons that belong to later batches.
|
|
72
|
+
const from = geometryBatch
|
|
73
|
+
? `(SELECT *, rowid AS __sda_rowid FROM ${page}) AS __sda_batch ORDER BY __sda_rowid`
|
|
74
|
+
: page;
|
|
75
|
+
const source = await readMutationRows(simpleTable.connection, `SELECT ${Object.keys(types).map((key) => geometryColumns.includes(key)
|
|
76
|
+
? `ST_AsGeoJSON(${quoteIdentifier(key)})::VARCHAR AS ${quoteIdentifier(key)}`
|
|
77
|
+
: quoteIdentifier(key)).join(", ")}${batchSize === undefined
|
|
78
|
+
? ""
|
|
79
|
+
: geometryBatch
|
|
80
|
+
? ", __sda_rowid"
|
|
81
|
+
: ", rowid AS __sda_rowid"} FROM ${from}`, batchSize === undefined ? undefined : "__sda_rowid");
|
|
82
|
+
const batch = source.rows;
|
|
83
|
+
const inputCount = batch.length;
|
|
84
|
+
if (inputCount === 0 && sawRows)
|
|
66
85
|
break;
|
|
86
|
+
sawRows ||= inputCount > 0;
|
|
87
|
+
if (batchSize !== undefined && batch.length > 0) {
|
|
88
|
+
// Keep the cursor separate: adding and deleting it on every row also
|
|
89
|
+
// makes ordinary callback operations such as object spread slower.
|
|
90
|
+
lastRowid = source.lastCursorValue;
|
|
67
91
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
for (const [index, row] of batch.entries()) {
|
|
93
|
+
for (const key of geometryColumns) {
|
|
94
|
+
row[key] = row[key] === null ? null : JSON.parse(row[key]);
|
|
95
|
+
prepareGeometry(row[key], key, inputOffset + index + 1);
|
|
96
|
+
}
|
|
71
97
|
}
|
|
98
|
+
inputOffset += inputCount;
|
|
72
99
|
const modified = await dataModifier(batch);
|
|
73
100
|
if (modified.length === 0) {
|
|
101
|
+
if (!sawRows)
|
|
102
|
+
return;
|
|
103
|
+
if (batchSize === undefined)
|
|
104
|
+
break;
|
|
74
105
|
continue;
|
|
75
106
|
}
|
|
76
|
-
|
|
107
|
+
const keys = Object.keys(modified[0]);
|
|
108
|
+
for (const key of Object.keys(options.columnTypes ?? {})) {
|
|
109
|
+
if (!keys.includes(key)) {
|
|
110
|
+
throw new Error(`Unknown column ${JSON.stringify(key)} in updateWithJS columnTypes.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const newKeys = keys.filter((key) => !source.types.has(key));
|
|
114
|
+
const added = newKeys.length === 0 ? undefined : prepareArray(modified.map((row) => Object.fromEntries(newKeys.map((key) => [key, row[key]]))), options.columnTypes);
|
|
115
|
+
const outputGeometry = keys.filter((key) => geometryColumns.includes(key) ||
|
|
116
|
+
(added && added.types[newKeys.indexOf(key)] === "GEOMETRY('EPSG:4326')"));
|
|
117
|
+
const outputJSON = keys.filter((key) => types[key] === "JSON" ||
|
|
118
|
+
(added && added.types[newKeys.indexOf(key)] === "JSON"));
|
|
119
|
+
if (outputGeometry.length && !simpleTable.sdb.spatialLoaded) {
|
|
120
|
+
await simpleTable.sdb.customQuery("INSTALL spatial; LOAD spatial; SET geometry_always_xy = true;");
|
|
121
|
+
simpleTable.sdb.spatialLoaded = true;
|
|
122
|
+
}
|
|
123
|
+
const geometryData = new Map(outputGeometry.map((key) => [
|
|
124
|
+
key,
|
|
125
|
+
geometryColumns.includes(key)
|
|
126
|
+
? modified.map((row, i) => prepareGeometry(row[key], key, outputOffset + i + 1))
|
|
127
|
+
: added.columnsData[newKeys.indexOf(key)],
|
|
128
|
+
]));
|
|
129
|
+
outputOffset += modified.length;
|
|
130
|
+
const columnTypes = keys.map((key) => outputGeometry.includes(key) || outputJSON.includes(key)
|
|
131
|
+
? parseDuckDBType("VARCHAR")
|
|
132
|
+
: source.types.get(key) ??
|
|
133
|
+
parseDuckDBType(added.types[newKeys.indexOf(key)]));
|
|
134
|
+
await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${quoteIdentifier(scratch.name)} (${keys.map((key, i) => `${quoteIdentifier(key)} ${columnTypes[i]}`)
|
|
135
|
+
.join(", ")})`);
|
|
136
|
+
const appender = await simpleTable.connection.createAppender(scratch.name);
|
|
137
|
+
try {
|
|
138
|
+
for (let start = 0; start < modified.length; start += 2000) {
|
|
139
|
+
const end = Math.min(start + 2000, modified.length);
|
|
140
|
+
const chunk = DuckDBDataChunk.create(columnTypes, end - start);
|
|
141
|
+
for (let i = 0; i < keys.length; i++) {
|
|
142
|
+
const key = keys[i];
|
|
143
|
+
chunk.setColumnValues(i, outputGeometry.includes(key)
|
|
144
|
+
? geometryData.get(key).slice(start, end)
|
|
145
|
+
: source.types.has(key)
|
|
146
|
+
? modified.slice(start, end).map((row) => source.toNative(row[key], columnTypes[i]))
|
|
147
|
+
: added.columnsData[newKeys.indexOf(key)].slice(start, end));
|
|
148
|
+
}
|
|
149
|
+
appender.appendDataChunk(chunk);
|
|
150
|
+
}
|
|
151
|
+
appender.flushSync();
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
appender.closeSync();
|
|
155
|
+
}
|
|
156
|
+
const projection = keys.map((key) => outputGeometry.includes(key)
|
|
157
|
+
? `${geometryFromJSON(quoteIdentifier(key))} AS ${quoteIdentifier(key)}`
|
|
158
|
+
: outputJSON.includes(key)
|
|
159
|
+
? `${quoteIdentifier(key)}::JSON AS ${quoteIdentifier(key)}`
|
|
160
|
+
: quoteIdentifier(key)).join(", ");
|
|
77
161
|
if (first) {
|
|
78
|
-
await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${quoteIdentifier(accumulator)} AS SELECT
|
|
162
|
+
await simpleTable.sdb.customQuery(`CREATE OR REPLACE TABLE ${quoteIdentifier(accumulator)} AS SELECT ${projection} FROM ${quoteIdentifier(scratch.name)}`);
|
|
79
163
|
first = false;
|
|
80
164
|
}
|
|
81
165
|
else {
|
|
82
|
-
await simpleTable.sdb.customQuery(`INSERT INTO ${quoteIdentifier(accumulator)} BY NAME SELECT
|
|
166
|
+
await simpleTable.sdb.customQuery(`INSERT INTO ${quoteIdentifier(accumulator)} BY NAME SELECT ${projection} FROM ${quoteIdentifier(scratch.name)}`);
|
|
83
167
|
}
|
|
168
|
+
if (batchSize === undefined || inputCount === 0)
|
|
169
|
+
break;
|
|
84
170
|
}
|
|
85
171
|
if (first) {
|
|
86
172
|
throw new Error("The dataModifier returned no rows. updateWithJS can't infer the table schema from zero rows.");
|