@xeplr/actions 2.2.5 → 2.2.6
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 +2 -0
- package/index.js +1 -0
- package/lib/drivers/db/internalTables.js +15 -0
- package/lib/uploader/index.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -569,6 +569,8 @@ On Postgres, MySQL and SQL Server the staging table is shaped like the target (`
|
|
|
569
569
|
|
|
570
570
|
Beside the tables it loads, the uploader makes three of its own: `<table>_import_errors` (refused rows), `<table>__xeplr_staging` (a staged load in progress) and, in a DuckDB file, `__xeplr_loads` (its ledger). `isInternalTable(name)` (also accepts a listing row `{ name }`) says whether a table is one of these, so an app listing a database's tables for people to pick from can leave them out. They still exist and are read where they belong.
|
|
571
571
|
|
|
572
|
+
It also adds its own columns to every table it loads: `__xeplr_id__` (Postgres, MySQL, SQL Server) and `__xeplr_movement_id__` (every target), the row's id and the load that wrote it. `isInternalColumn(name)` (or `{ name }`) says whether a column is one of these. **They are never carried into another table:** when a loaded table is loaded somewhere else (an import copied into a cube's warehouse, a move between databases), the uploader drops them from the target's columns, whether inferred from the rows or declared by the caller, and the new load stamps its own. Carried along, they clashed with the target's own column (`Column with name __xeplr_movement_id__ already exists`) and would have marked rows with a load that never wrote them.
|
|
573
|
+
|
|
572
574
|
### A written DuckDB file keeps no index
|
|
573
575
|
|
|
574
576
|
DuckDB 1.5.4–1.5.6 has a bug ([duckdb/duckdb#26106](https://github.com/duckdb/duckdb/issues/26106), open upstream): when a process dies with rows still only in the WAL, the next open replays them into the table and the next checkpoint drops them from every index on it. The first `DELETE` that reaches one fails with *"Failed to delete all rows from index"*, which is **FATAL**: the whole file is unusable until the process restarts. Any index, unique or not. A table with no index is unaffected.
|
package/index.js
CHANGED
|
@@ -48,6 +48,7 @@ module.exports = {
|
|
|
48
48
|
uploader: uploader,
|
|
49
49
|
// The uploader's own tables (_import_errors, __xeplr_staging, __xeplr_loads), to leave out of table pickers.
|
|
50
50
|
isInternalTable: require('./lib/drivers/db/internalTables').isInternalTable,
|
|
51
|
+
isInternalColumn: require('./lib/drivers/db/internalTables').isInternalColumn,
|
|
51
52
|
inferColumns: uploader.inferColumns,
|
|
52
53
|
streaming: streaming,
|
|
53
54
|
drivers: { db: dbDrivers, file: fileDrivers },
|
|
@@ -13,6 +13,13 @@ var ERRORS_SUFFIX = '_import_errors';
|
|
|
13
13
|
var STAGING_SUFFIX = require('./staging').SUFFIX;
|
|
14
14
|
var LOADS_LEDGER = '__xeplr_loads';
|
|
15
15
|
|
|
16
|
+
// THE COLUMNS IT ADDS TO EVERY TABLE IT LOADS: a row's own id and the
|
|
17
|
+
// movement that wrote it. They describe a load into THAT table, so they are
|
|
18
|
+
// never carried into another one — loading a loaded table elsewhere (an
|
|
19
|
+
// import copied into a cube's warehouse, a move between databases) leaves
|
|
20
|
+
// them behind and the new load stamps its own (uploader/index.js).
|
|
21
|
+
var INTERNAL_COLUMNS = ['__xeplr_id__', '__xeplr_movement_id__'];
|
|
22
|
+
|
|
16
23
|
function endsWith(s, suffix) {
|
|
17
24
|
return s.length > suffix.length && s.slice(-suffix.length) === suffix;
|
|
18
25
|
}
|
|
@@ -23,8 +30,16 @@ function isInternalTable(name) {
|
|
|
23
30
|
return endsWith(s, ERRORS_SUFFIX) || endsWith(s, STAGING_SUFFIX) || s === LOADS_LEDGER;
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
/** Whether a column is one the uploader adds itself. A name, or { name }. */
|
|
34
|
+
function isInternalColumn(column) {
|
|
35
|
+
var s = String(column == null ? '' : (typeof column === 'object' ? column.name || '' : column));
|
|
36
|
+
return INTERNAL_COLUMNS.indexOf(s) !== -1;
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
module.exports = {
|
|
27
40
|
isInternalTable: isInternalTable,
|
|
41
|
+
isInternalColumn: isInternalColumn,
|
|
42
|
+
INTERNAL_COLUMNS: INTERNAL_COLUMNS,
|
|
28
43
|
ERRORS_SUFFIX: ERRORS_SUFFIX,
|
|
29
44
|
STAGING_SUFFIX: STAGING_SUFFIX,
|
|
30
45
|
LOADS_LEDGER: LOADS_LEDGER
|
package/lib/uploader/index.js
CHANGED
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
//
|
|
60
60
|
// See project_xeplr_actions_streaming.md for the full contract.
|
|
61
61
|
|
|
62
|
+
var { isInternalColumn } = require('../drivers/db/internalTables');
|
|
62
63
|
var path = require('path');
|
|
63
64
|
var fsp = require('fs/promises');
|
|
64
65
|
|
|
@@ -149,6 +150,12 @@ async function upload(opts) {
|
|
|
149
150
|
var scanLimit = Math.min(sample.length, opts.firstBatchScanRows);
|
|
150
151
|
declared = inferColumns(sample.slice(0, scanLimit));
|
|
151
152
|
}
|
|
153
|
+
// OUR OWN COLUMNS NEVER COME ALONG. A source that was itself loaded
|
|
154
|
+
// by the uploader (an import's table, a replica) has its own
|
|
155
|
+
// __xeplr_id__ and __xeplr_movement_id__; carried here they clashed
|
|
156
|
+
// with this table's ("already exists") and would have claimed rows
|
|
157
|
+
// for a load that never wrote them. This load stamps its own.
|
|
158
|
+
declared = declared.filter(function(c) { return !isInternalColumn(c); });
|
|
152
159
|
columns = await runBootstrap(driver, pool, opts.targetTable, declared, opts.primaryKeys, opts.staged);
|
|
153
160
|
if (opts.staged) {
|
|
154
161
|
opts.writeTable = await driver.prepareStaging(pool, opts.targetTable);
|
package/package.json
CHANGED