datera 0.1.0 → 0.2.1
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/CHANGELOG.md +74 -0
- package/README.md +18 -7
- package/dist/api/formats.d.ts +4 -0
- package/dist/api/formats.js +9 -1
- package/dist/api/inspect.js +2 -1
- package/dist/cells.d.ts +5 -0
- package/dist/cells.js +24 -0
- package/dist/cli.js +2 -0
- package/dist/commands.js +9 -2
- package/dist/config/check.js +4 -0
- package/dist/config/ioSchema.d.ts +38 -0
- package/dist/config/ioSchema.js +30 -0
- package/dist/config/load.js +34 -6
- package/dist/config/schema.d.ts +38 -0
- package/dist/env.d.ts +0 -1
- package/dist/env.js +0 -9
- package/dist/filters/combine.js +7 -6
- package/dist/filters/dedupe.js +2 -1
- package/dist/filters/fillEmpty.js +5 -4
- package/dist/filters/merge.js +4 -3
- package/dist/filters/mergeStrategies.js +12 -11
- package/dist/filters/mergeUnkeyed.js +2 -3
- package/dist/filters/validate.js +3 -2
- package/dist/filters/validateTypes.d.ts +0 -5
- package/dist/io/csv/csvFormat.d.ts +1 -0
- package/dist/io/csv/csvFormat.js +8 -0
- package/dist/io/csv/csvSink.d.ts +1 -0
- package/dist/io/csv/csvSink.js +9 -5
- package/dist/io/csv/csvSource.js +2 -2
- package/dist/io/excel/excelSink.js +3 -2
- package/dist/io/excel/excelSource.js +2 -1
- package/dist/io/factory.js +57 -6
- package/dist/io/json/jsonSource.js +6 -5
- package/dist/io/mysql/client.d.ts +1 -0
- package/dist/io/mysql/client.js +1 -0
- package/dist/io/mysql/mysqlSink.d.ts +20 -0
- package/dist/io/mysql/mysqlSink.js +89 -0
- package/dist/io/parquet/parquetSink.js +2 -1
- package/dist/io/parquet/parquetSource.js +3 -4
- package/dist/io/postgres/postgresSink.d.ts +16 -0
- package/dist/io/postgres/postgresSink.js +82 -0
- package/dist/io/postgres/postgresSource.d.ts +1 -1
- package/dist/io/safety.d.ts +1 -1
- package/dist/io/safety.js +105 -6
- package/dist/io/servers.d.ts +14 -0
- package/dist/io/servers.js +72 -0
- package/dist/io/sheets/client.js +2 -1
- package/dist/io/sheets/sheetsSource.js +2 -1
- package/dist/io/sql/cell.js +2 -1
- package/dist/io/sql/target.d.ts +8 -0
- package/dist/io/sql/target.js +14 -0
- package/dist/io/sql/writer.d.ts +46 -0
- package/dist/io/sql/writer.js +264 -0
- package/dist/io/sqlite/sqlite.d.ts +3 -0
- package/dist/io/sqlite/sqlite.js +11 -0
- package/dist/io/sqlite/sqliteSink.d.ts +17 -0
- package/dist/io/sqlite/sqliteSink.js +69 -0
- package/dist/io/sqlite/sqliteSource.js +2 -9
- package/dist/io/sqlserver/sqlServerSink.d.ts +20 -0
- package/dist/io/sqlserver/sqlServerSink.js +107 -0
- package/dist/io/xml/xmlParse.js +4 -2
- package/dist/io/xml/xmlSource.js +7 -6
- package/dist/pipeline/runEtl.js +2 -1
- package/package.json +10 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FillEmptyFilter = void 0;
|
|
4
|
+
const cells_1 = require("../cells");
|
|
4
5
|
class FillEmptyFilter {
|
|
5
6
|
rules;
|
|
6
7
|
constructor(rules) {
|
|
@@ -10,16 +11,16 @@ class FillEmptyFilter {
|
|
|
10
11
|
return rows.map((row) => {
|
|
11
12
|
const result = { ...row };
|
|
12
13
|
for (const { column, fallbackColumns = [], default: fallbackValue, } of this.rules) {
|
|
13
|
-
if (!this.isEmpty(row
|
|
14
|
+
if (!this.isEmpty((0, cells_1.cellOf)(row, column)))
|
|
14
15
|
continue;
|
|
15
16
|
const fromColumn = fallbackColumns
|
|
16
|
-
.map((fallback) => row
|
|
17
|
+
.map((fallback) => (0, cells_1.cellOf)(row, fallback))
|
|
17
18
|
.find((value) => !this.isEmpty(value));
|
|
18
19
|
if (fromColumn !== undefined) {
|
|
19
|
-
result
|
|
20
|
+
(0, cells_1.setCell)(result, column, fromColumn);
|
|
20
21
|
}
|
|
21
22
|
else if (fallbackValue !== undefined) {
|
|
22
|
-
result
|
|
23
|
+
(0, cells_1.setCell)(result, column, fallbackValue);
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
return result;
|
package/dist/filters/merge.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.MergeFilter = void 0;
|
|
|
4
4
|
const mergeStrategies_1 = require("./mergeStrategies");
|
|
5
5
|
const mergeUnkeyed_1 = require("./mergeUnkeyed");
|
|
6
6
|
const registry_1 = require("../normalizers/registry");
|
|
7
|
+
const cells_1 = require("../cells");
|
|
7
8
|
const identityNormalizer = (raw) => ({
|
|
8
9
|
key: typeof raw === "string" ? raw.trim() : raw,
|
|
9
10
|
});
|
|
@@ -30,7 +31,7 @@ class MergeFilter {
|
|
|
30
31
|
const emptyKeyRows = [];
|
|
31
32
|
const rejectedKeyRows = [];
|
|
32
33
|
for (const row of rows) {
|
|
33
|
-
const rawKey = row
|
|
34
|
+
const rawKey = (0, cells_1.cellOf)(row, this.keyColumn);
|
|
34
35
|
if (rawKey === null ||
|
|
35
36
|
rawKey === undefined ||
|
|
36
37
|
(typeof rawKey === "string" && rawKey.trim() === "")) {
|
|
@@ -95,10 +96,10 @@ class MergeFilter {
|
|
|
95
96
|
}
|
|
96
97
|
switch (resolvedStrategy) {
|
|
97
98
|
case "overwrite":
|
|
98
|
-
merged
|
|
99
|
+
(0, cells_1.setCell)(merged, target, (0, mergeStrategies_1.mergeOverwrite)(group, column));
|
|
99
100
|
break;
|
|
100
101
|
case "concat":
|
|
101
|
-
merged
|
|
102
|
+
(0, cells_1.setCell)(merged, target, (0, mergeStrategies_1.mergeConcat)(group, column, finalSeparator));
|
|
102
103
|
break;
|
|
103
104
|
case "extra-column":
|
|
104
105
|
(0, mergeStrategies_1.mergeExtraColumns)(group, column, merged, target);
|
|
@@ -7,16 +7,17 @@ exports.mergeConcat = mergeConcat;
|
|
|
7
7
|
exports.mergeConcatDistributed = mergeConcatDistributed;
|
|
8
8
|
exports.findFreeOverflowColumnName = findFreeOverflowColumnName;
|
|
9
9
|
exports.mergeExtraColumns = mergeExtraColumns;
|
|
10
|
+
const cells_1 = require("../cells");
|
|
10
11
|
exports.DEFAULT_SEPARATOR = "; ";
|
|
11
12
|
function presentValues(rows, column) {
|
|
12
13
|
return rows
|
|
13
|
-
.map((row) => row
|
|
14
|
+
.map((row) => (0, cells_1.cellOf)(row, column))
|
|
14
15
|
.filter((v) => v !== null && v !== undefined && v !== "");
|
|
15
16
|
}
|
|
16
17
|
function mergeOverwrite(group, column) {
|
|
17
|
-
let result = group[0]
|
|
18
|
+
let result = (group[0] ? (0, cells_1.cellOf)(group[0], column) : null) ?? null;
|
|
18
19
|
for (const row of group) {
|
|
19
|
-
const value = row
|
|
20
|
+
const value = (0, cells_1.cellOf)(row, column);
|
|
20
21
|
if (value !== null && value !== undefined) {
|
|
21
22
|
result = value;
|
|
22
23
|
}
|
|
@@ -32,18 +33,18 @@ function mergeConcatDistributed(group, column, merged, distribute, separator) {
|
|
|
32
33
|
const sourceColumns = [column, ...sources];
|
|
33
34
|
const uniqueValues = Array.from(new Set(group.flatMap((row) => sourceColumns.flatMap((source) => presentValues([row], source).map(String)))));
|
|
34
35
|
for (const destination of columns) {
|
|
35
|
-
if (
|
|
36
|
-
merged
|
|
36
|
+
if ((0, cells_1.hasCell)(merged, destination))
|
|
37
|
+
(0, cells_1.setCell)(merged, destination, null);
|
|
37
38
|
}
|
|
38
39
|
uniqueValues.forEach((value, index) => {
|
|
39
40
|
if (index < columns.length) {
|
|
40
|
-
merged
|
|
41
|
+
(0, cells_1.setCell)(merged, columns[index], value);
|
|
41
42
|
}
|
|
42
43
|
});
|
|
43
44
|
const overflowValues = uniqueValues.slice(columns.length);
|
|
44
45
|
if (overflowValues.length > 0) {
|
|
45
46
|
const overflowColumn = overflowInto ?? findFreeOverflowColumnName(column, merged);
|
|
46
|
-
merged
|
|
47
|
+
(0, cells_1.setCell)(merged, overflowColumn, overflowValues.join(separator));
|
|
47
48
|
}
|
|
48
49
|
for (const source of sourceColumns) {
|
|
49
50
|
if (!columns.includes(source))
|
|
@@ -52,18 +53,18 @@ function mergeConcatDistributed(group, column, merged, distribute, separator) {
|
|
|
52
53
|
}
|
|
53
54
|
function findFreeOverflowColumnName(column, merged) {
|
|
54
55
|
const base = `${column}_overflow`;
|
|
55
|
-
if (merged
|
|
56
|
+
if (!(0, cells_1.hasCell)(merged, base))
|
|
56
57
|
return base;
|
|
57
58
|
let suffix = 2;
|
|
58
|
-
while (merged
|
|
59
|
+
while ((0, cells_1.hasCell)(merged, `${base}_${suffix}`)) {
|
|
59
60
|
suffix++;
|
|
60
61
|
}
|
|
61
62
|
return `${base}_${suffix}`;
|
|
62
63
|
}
|
|
63
64
|
function mergeExtraColumns(group, column, merged, target = column) {
|
|
64
65
|
const values = presentValues(group, column);
|
|
65
|
-
merged
|
|
66
|
+
(0, cells_1.setCell)(merged, target, values[0] ?? null);
|
|
66
67
|
for (let i = 1; i < values.length; i++) {
|
|
67
|
-
merged
|
|
68
|
+
(0, cells_1.setCell)(merged, `${target}_${i + 1}`, values[i] ?? null);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildUnkeyedRows = buildUnkeyedRows;
|
|
4
4
|
const mergeStrategies_1 = require("./mergeStrategies");
|
|
5
|
+
const cells_1 = require("../cells");
|
|
5
6
|
function buildUnkeyedRows(rows, label, keyColumn, columns) {
|
|
6
7
|
if (rows.length === 0)
|
|
7
8
|
return [];
|
|
@@ -13,9 +14,7 @@ function buildUnkeyedRows(rows, label, keyColumn, columns) {
|
|
|
13
14
|
for (const { column, unkeyed } of collapsing) {
|
|
14
15
|
const target = unkeyed?.into ?? `${column} ${label}`;
|
|
15
16
|
const separator = unkeyed?.separator ?? mergeStrategies_1.DEFAULT_SEPARATOR;
|
|
16
|
-
collapsed
|
|
17
|
-
.map(String)
|
|
18
|
-
.join(separator);
|
|
17
|
+
(0, cells_1.setCell)(collapsed, target, (0, mergeStrategies_1.presentValues)(rows, column).map(String).join(separator));
|
|
19
18
|
}
|
|
20
19
|
return [collapsed];
|
|
21
20
|
}
|
package/dist/filters/validate.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RowValidator = exports.DEFAULT_REASON_COLUMN = exports.DEFAULT_PENDING_SHEET = void 0;
|
|
4
4
|
const registry_1 = require("../normalizers/registry");
|
|
5
|
+
const cells_1 = require("../cells");
|
|
5
6
|
exports.DEFAULT_PENDING_SHEET = "Pendências";
|
|
6
7
|
exports.DEFAULT_REASON_COLUMN = "Motivo";
|
|
7
8
|
class RowValidator {
|
|
@@ -16,14 +17,14 @@ class RowValidator {
|
|
|
16
17
|
const pending = [];
|
|
17
18
|
for (const row of rows) {
|
|
18
19
|
const reasons = this.checks
|
|
19
|
-
.filter((check) => !this.passes(check, row
|
|
20
|
+
.filter((check) => !this.passes(check, (0, cells_1.cellOf)(row, check.column)))
|
|
20
21
|
.map((check) => check.message);
|
|
21
22
|
if (reasons.length === 0) {
|
|
22
23
|
valid.push(row);
|
|
23
24
|
continue;
|
|
24
25
|
}
|
|
25
26
|
const withReason = { [this.reasonColumn]: null, ...row };
|
|
26
|
-
withReason
|
|
27
|
+
(0, cells_1.setCell)(withReason, this.reasonColumn, Array.from(new Set(reasons)).join("; "));
|
|
27
28
|
pending.push(withReason);
|
|
28
29
|
}
|
|
29
30
|
return { valid, pending };
|
|
@@ -20,9 +20,4 @@ export interface NormalizerRule extends BaseRule {
|
|
|
20
20
|
normalizer: string;
|
|
21
21
|
}
|
|
22
22
|
export type ValidationRule = RequiredRule | PatternRule | OneOfRule | NormalizerRule;
|
|
23
|
-
export interface ValidationConfig {
|
|
24
|
-
rules: ValidationRule[];
|
|
25
|
-
pendingSheet?: string | undefined;
|
|
26
|
-
reasonColumn?: string | undefined;
|
|
27
|
-
}
|
|
28
23
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function stripBom(text: string): string;
|
|
2
2
|
export declare function detectDelimiter(text: string): string;
|
|
3
3
|
export declare function parseCsv(text: string, delimiter: string): string[][];
|
|
4
|
+
export declare function escapeFormula(value: string): string;
|
|
4
5
|
export declare function toCsv(rows: string[][], delimiter: string): string;
|
package/dist/io/csv/csvFormat.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.stripBom = stripBom;
|
|
4
4
|
exports.detectDelimiter = detectDelimiter;
|
|
5
5
|
exports.parseCsv = parseCsv;
|
|
6
|
+
exports.escapeFormula = escapeFormula;
|
|
6
7
|
exports.toCsv = toCsv;
|
|
7
8
|
const BOM = "\uFEFF";
|
|
8
9
|
const CANDIDATE_DELIMITERS = [",", ";", "\t", "|"];
|
|
@@ -79,6 +80,13 @@ function parseCsv(text, delimiter) {
|
|
|
79
80
|
}
|
|
80
81
|
return rows.filter((r) => !(r.length === 1 && r[0] === ""));
|
|
81
82
|
}
|
|
83
|
+
const FORMULA_START = /^\s*[=+\-@\t\r\uFF1D\uFF0B\uFF0D\uFF20]/;
|
|
84
|
+
const PLAIN_NUMBER = /^\s*[+-]?\d+([.,]\d+)*\s*$/;
|
|
85
|
+
function escapeFormula(value) {
|
|
86
|
+
return FORMULA_START.test(value) && !PLAIN_NUMBER.test(value)
|
|
87
|
+
? `'${value}`
|
|
88
|
+
: value;
|
|
89
|
+
}
|
|
82
90
|
function escapeField(value, delimiter) {
|
|
83
91
|
const needsQuotes = value.includes(delimiter) ||
|
|
84
92
|
value.includes('"') ||
|
package/dist/io/csv/csvSink.d.ts
CHANGED
package/dist/io/csv/csvSink.js
CHANGED
|
@@ -5,6 +5,7 @@ const header_1 = require("../header");
|
|
|
5
5
|
const csvFormat_1 = require("./csvFormat");
|
|
6
6
|
const files_1 = require("../files");
|
|
7
7
|
const logger_1 = require("../../logger");
|
|
8
|
+
const cells_1 = require("../../cells");
|
|
8
9
|
class CsvSink {
|
|
9
10
|
options;
|
|
10
11
|
logger;
|
|
@@ -14,16 +15,19 @@ class CsvSink {
|
|
|
14
15
|
}
|
|
15
16
|
async write(rows, options = {}) {
|
|
16
17
|
const filePath = (0, files_1.extraOutputPath)(this.options.path, options.name);
|
|
18
|
+
const safe = (this.options.escapeFormulas ?? true)
|
|
19
|
+
? csvFormat_1.escapeFormula
|
|
20
|
+
: (value) => value;
|
|
17
21
|
const header = (0, header_1.buildHeader)(rows);
|
|
18
22
|
const lines = rows.map((row) => header.map((column) => {
|
|
19
|
-
const value = row
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
const value = (0, cells_1.cellOf)(row, column);
|
|
24
|
+
if (value === null || value === undefined)
|
|
25
|
+
return "";
|
|
26
|
+
return typeof value === "number" ? String(value) : safe(value);
|
|
23
27
|
}));
|
|
24
28
|
const content = rows.length === 0
|
|
25
29
|
? ""
|
|
26
|
-
: (0, csvFormat_1.toCsv)([header, ...lines], this.options.delimiter ?? ",");
|
|
30
|
+
: (0, csvFormat_1.toCsv)([header.map(safe), ...lines], this.options.delimiter ?? ",");
|
|
27
31
|
const bom = (this.options.bom ?? true) ? "\uFEFF" : "";
|
|
28
32
|
(0, files_1.writeTextFile)(filePath, bom + content + (content ? "\r\n" : ""));
|
|
29
33
|
this.logger.info(`${rows.length} linhas escritas em ${filePath}.`);
|
package/dist/io/csv/csvSource.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CsvSource = void 0;
|
|
4
4
|
const csvFormat_1 = require("./csvFormat");
|
|
5
5
|
const files_1 = require("../files");
|
|
6
|
+
const cells_1 = require("../../cells");
|
|
6
7
|
class CsvSource {
|
|
7
8
|
options;
|
|
8
9
|
constructor(options) {
|
|
@@ -19,8 +20,7 @@ class CsvSource {
|
|
|
19
20
|
const row = {};
|
|
20
21
|
columns.forEach((column, index) => {
|
|
21
22
|
const value = values[index];
|
|
22
|
-
row
|
|
23
|
-
value === undefined || value === "" ? null : value;
|
|
23
|
+
(0, cells_1.setCell)(row, column, value === undefined || value === "" ? null : value);
|
|
24
24
|
});
|
|
25
25
|
return row;
|
|
26
26
|
});
|
|
@@ -9,6 +9,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const header_1 = require("../header");
|
|
10
10
|
const workbook_1 = require("./workbook");
|
|
11
11
|
const logger_1 = require("../../logger");
|
|
12
|
+
const cells_1 = require("../../cells");
|
|
12
13
|
const DEFAULT_SHEET = "Dados";
|
|
13
14
|
const MAX_SHEET_NAME = 31;
|
|
14
15
|
function safeSheetName(name) {
|
|
@@ -43,10 +44,10 @@ class ExcelSink {
|
|
|
43
44
|
if (header.length > 0) {
|
|
44
45
|
worksheet.addRow(header).font = { bold: true };
|
|
45
46
|
for (const row of rows) {
|
|
46
|
-
worksheet.addRow(header.map((column) => row
|
|
47
|
+
worksheet.addRow(header.map((column) => (0, cells_1.cellOf)(row, column) ?? null));
|
|
47
48
|
}
|
|
48
49
|
header.forEach((column, index) => {
|
|
49
|
-
const longest = Math.max(column.length, ...rows.map((row) => String(row
|
|
50
|
+
const longest = Math.max(column.length, ...rows.map((row) => String((0, cells_1.cellOf)(row, column) ?? "").length));
|
|
50
51
|
worksheet.getColumn(index + 1).width = Math.min(Math.max(longest + 2, 10), 50);
|
|
51
52
|
});
|
|
52
53
|
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ExcelSource = void 0;
|
|
4
4
|
const workbook_1 = require("./workbook");
|
|
5
5
|
const excelCell_1 = require("./excelCell");
|
|
6
|
+
const cells_1 = require("../../cells");
|
|
6
7
|
class ExcelSource {
|
|
7
8
|
options;
|
|
8
9
|
constructor(options) {
|
|
@@ -34,7 +35,7 @@ class ExcelSource {
|
|
|
34
35
|
let hasValue = false;
|
|
35
36
|
for (const [column, name] of header) {
|
|
36
37
|
const value = (0, excelCell_1.fromExcelCell)(excelRow.getCell(column).value);
|
|
37
|
-
row
|
|
38
|
+
(0, cells_1.setCell)(row, name, value);
|
|
38
39
|
if (value !== null)
|
|
39
40
|
hasValue = true;
|
|
40
41
|
}
|
package/dist/io/factory.js
CHANGED
|
@@ -13,6 +13,10 @@ const xmlSource_1 = require("./xml/xmlSource");
|
|
|
13
13
|
const postgresSource_1 = require("./postgres/postgresSource");
|
|
14
14
|
const sqlServerSource_1 = require("./sqlserver/sqlServerSource");
|
|
15
15
|
const sqliteSource_1 = require("./sqlite/sqliteSource");
|
|
16
|
+
const sqliteSink_1 = require("./sqlite/sqliteSink");
|
|
17
|
+
const postgresSink_1 = require("./postgres/postgresSink");
|
|
18
|
+
const sqlServerSink_1 = require("./sqlserver/sqlServerSink");
|
|
19
|
+
const mysqlSink_1 = require("./mysql/mysqlSink");
|
|
16
20
|
const xmlSink_1 = require("./xml/xmlSink");
|
|
17
21
|
const parquetSource_1 = require("./parquet/parquetSource");
|
|
18
22
|
const parquetSink_1 = require("./parquet/parquetSink");
|
|
@@ -22,9 +26,12 @@ const sheetsSource_1 = require("./sheets/sheetsSource");
|
|
|
22
26
|
const registry_1 = require("./custom/registry");
|
|
23
27
|
const logger_1 = require("../logger");
|
|
24
28
|
const safety_1 = require("./safety");
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
29
|
+
const servers_1 = require("./servers");
|
|
30
|
+
const SERVER_NAMES = {
|
|
31
|
+
mysql: "MySQL",
|
|
32
|
+
postgres: "PostgreSQL",
|
|
33
|
+
sqlserver: "SQL Server",
|
|
34
|
+
};
|
|
28
35
|
function required(value, what) {
|
|
29
36
|
if (value === undefined || value === "") {
|
|
30
37
|
throw new Error(`Faltou configurar ${what}.`);
|
|
@@ -37,15 +44,16 @@ function createSource(config, logger = logger_1.consoleLogger) {
|
|
|
37
44
|
case "mysql":
|
|
38
45
|
return new mysqlSource_1.MysqlSource({
|
|
39
46
|
host: required(source.host ?? config.dbHost, "o host do MySQL"),
|
|
40
|
-
port: source.port
|
|
47
|
+
port: (0, servers_1.serverOf)(config, "source").port,
|
|
41
48
|
user: required(source.user ?? config.dbUser, "o usuário do MySQL"),
|
|
42
49
|
password: source.password ?? config.dbPassword,
|
|
43
50
|
database: required(source.database ?? config.dbName, "o banco do MySQL"),
|
|
51
|
+
ssl: source.ssl,
|
|
44
52
|
}, required(source.table ?? config.tableName, "a tabela do MySQL"), logger);
|
|
45
53
|
case "postgres":
|
|
46
54
|
return new postgresSource_1.PostgresSource({
|
|
47
55
|
host: required(source.host, "o host do PostgreSQL"),
|
|
48
|
-
port: source.port
|
|
56
|
+
port: (0, servers_1.serverOf)(config, "source").port,
|
|
49
57
|
user: required(source.user, "o usuário do PostgreSQL"),
|
|
50
58
|
password: source.password,
|
|
51
59
|
database: required(source.database, "o banco do PostgreSQL"),
|
|
@@ -54,7 +62,7 @@ function createSource(config, logger = logger_1.consoleLogger) {
|
|
|
54
62
|
case "sqlserver":
|
|
55
63
|
return new sqlServerSource_1.SqlServerSource({
|
|
56
64
|
host: required(source.host, "o servidor do SQL Server"),
|
|
57
|
-
port: source.port
|
|
65
|
+
port: (0, servers_1.serverOf)(config, "source").port,
|
|
58
66
|
user: required(source.user, "o usuário do SQL Server"),
|
|
59
67
|
password: source.password,
|
|
60
68
|
database: required(source.database, "o banco do SQL Server"),
|
|
@@ -85,6 +93,49 @@ function createSink(config, logger = logger_1.consoleLogger) {
|
|
|
85
93
|
(0, safety_1.assertSourceIsSafe)(config);
|
|
86
94
|
const destination = config.destination ?? { type: "sheets" };
|
|
87
95
|
switch (destination.type) {
|
|
96
|
+
case "mysql":
|
|
97
|
+
case "postgres":
|
|
98
|
+
case "sqlserver": {
|
|
99
|
+
const server = (0, servers_1.serverOf)(config, "destination");
|
|
100
|
+
const what = SERVER_NAMES[destination.type];
|
|
101
|
+
const connection = {
|
|
102
|
+
host: required(server.host, `o host do ${what} de destino`),
|
|
103
|
+
port: server.port,
|
|
104
|
+
user: required(server.user, `o usuário do ${what} de destino`),
|
|
105
|
+
password: server.password,
|
|
106
|
+
database: required(server.database, `o banco do ${what} de destino`),
|
|
107
|
+
};
|
|
108
|
+
const target = {
|
|
109
|
+
table: required(destination.table, `a tabela do ${what} onde gravar`),
|
|
110
|
+
pendingTable: destination.pendingTable,
|
|
111
|
+
};
|
|
112
|
+
if (destination.type === "mysql") {
|
|
113
|
+
const same = config.source?.type === "mysql" &&
|
|
114
|
+
(0, servers_1.destinationSharesSource)(config)
|
|
115
|
+
? config.source
|
|
116
|
+
: undefined;
|
|
117
|
+
return new mysqlSink_1.MysqlSink({ ...connection, ssl: destination.ssl ?? same?.ssl }, target, logger);
|
|
118
|
+
}
|
|
119
|
+
if (destination.type === "postgres") {
|
|
120
|
+
const same = config.source?.type === "postgres" &&
|
|
121
|
+
(0, servers_1.destinationSharesSource)(config)
|
|
122
|
+
? config.source
|
|
123
|
+
: undefined;
|
|
124
|
+
return new postgresSink_1.PostgresSink({ ...connection, ssl: destination.ssl ?? same?.ssl }, target, logger);
|
|
125
|
+
}
|
|
126
|
+
const same = config.source?.type === "sqlserver" &&
|
|
127
|
+
(0, servers_1.destinationSharesSource)(config)
|
|
128
|
+
? config.source
|
|
129
|
+
: undefined;
|
|
130
|
+
return new sqlServerSink_1.SqlServerSink({
|
|
131
|
+
...connection,
|
|
132
|
+
encrypt: destination.encrypt ?? same?.encrypt,
|
|
133
|
+
trustServerCertificate: destination.trustServerCertificate ??
|
|
134
|
+
same?.trustServerCertificate,
|
|
135
|
+
}, target, logger);
|
|
136
|
+
}
|
|
137
|
+
case "sqlite":
|
|
138
|
+
return new sqliteSink_1.SqliteSink(destination, logger);
|
|
88
139
|
case "sheets": {
|
|
89
140
|
const credentialsPath = required(destination.credentialsPath ?? config.credentialsPath, "o caminho das credenciais do Google");
|
|
90
141
|
const spreadsheetId = required(destination.spreadsheetId ?? config.spreadsheetId, "o id da planilha");
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.JsonSource = void 0;
|
|
4
4
|
const files_1 = require("../files");
|
|
5
|
+
const cells_1 = require("../../cells");
|
|
5
6
|
function flatten(value, prefix, row) {
|
|
6
7
|
if (value === null || value === undefined) {
|
|
7
|
-
row
|
|
8
|
+
(0, cells_1.setCell)(row, prefix, null);
|
|
8
9
|
}
|
|
9
10
|
else if (Array.isArray(value)) {
|
|
10
|
-
row
|
|
11
|
+
(0, cells_1.setCell)(row, prefix, value.every((item) => typeof item !== "object" || item === null)
|
|
11
12
|
? value.map((item) => String(item ?? "")).join(", ")
|
|
12
|
-
: JSON.stringify(value);
|
|
13
|
+
: JSON.stringify(value));
|
|
13
14
|
}
|
|
14
15
|
else if (typeof value === "object") {
|
|
15
16
|
for (const [key, inner] of Object.entries(value)) {
|
|
@@ -17,10 +18,10 @@ function flatten(value, prefix, row) {
|
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
else if (typeof value === "number" || typeof value === "string") {
|
|
20
|
-
row
|
|
21
|
+
(0, cells_1.setCell)(row, prefix, value);
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
|
-
row
|
|
24
|
+
(0, cells_1.setCell)(row, prefix, String(value));
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
class JsonSource {
|
|
@@ -7,6 +7,7 @@ export interface MysqlConnection {
|
|
|
7
7
|
user: string;
|
|
8
8
|
password?: string | undefined;
|
|
9
9
|
database: string;
|
|
10
|
+
ssl?: boolean | undefined;
|
|
10
11
|
}
|
|
11
12
|
export declare function createPool(connection: MysqlConnection): Pool;
|
|
12
13
|
export declare function readTableInBatches(pool: Pool, tableName: string, batchSize?: number, logger?: Logger): AsyncGenerator<TableRow[]>;
|
package/dist/io/mysql/client.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TableRow } from "../../types";
|
|
2
|
+
import { Sink, SinkWriteOptions } from "../types";
|
|
3
|
+
import { Logger } from "../../logger";
|
|
4
|
+
import { SqlDialect, SqlSession } from "../sql/writer";
|
|
5
|
+
import { TableTarget } from "../sql/target";
|
|
6
|
+
import { MysqlConnection } from "./client";
|
|
7
|
+
export declare const MYSQL_DIALECT: SqlDialect;
|
|
8
|
+
export interface MysqlSession extends SqlSession {
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export type MysqlSessionFactory = (connection: MysqlConnection) => Promise<MysqlSession>;
|
|
12
|
+
export declare const createMysqlSession: MysqlSessionFactory;
|
|
13
|
+
export declare class MysqlSink implements Sink {
|
|
14
|
+
private readonly connection;
|
|
15
|
+
private readonly target;
|
|
16
|
+
private readonly logger;
|
|
17
|
+
private readonly createSession;
|
|
18
|
+
constructor(connection: MysqlConnection, target: TableTarget, logger?: Logger, createSession?: MysqlSessionFactory);
|
|
19
|
+
write(rows: TableRow[], options?: SinkWriteOptions): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MysqlSink = exports.createMysqlSession = exports.MYSQL_DIALECT = void 0;
|
|
4
|
+
const logger_1 = require("../../logger");
|
|
5
|
+
const writer_1 = require("../sql/writer");
|
|
6
|
+
const target_1 = require("../sql/target");
|
|
7
|
+
const client_1 = require("./client");
|
|
8
|
+
exports.MYSQL_DIALECT = {
|
|
9
|
+
label: "MySQL",
|
|
10
|
+
open: "`",
|
|
11
|
+
close: "`",
|
|
12
|
+
schemas: true,
|
|
13
|
+
maxNameLength: 64,
|
|
14
|
+
nameLength: (name) => name.length,
|
|
15
|
+
maxParams: 60000,
|
|
16
|
+
maxRowsPerInsert: 1000,
|
|
17
|
+
transactionalDdl: false,
|
|
18
|
+
types: { integer: "BIGINT", number: "DOUBLE", text: "LONGTEXT" },
|
|
19
|
+
keyType: "VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin",
|
|
20
|
+
tableSuffix: " DEFAULT CHARSET=utf8mb4",
|
|
21
|
+
param: () => "?",
|
|
22
|
+
tableMark: (token) => ({ inside: "", after: ` COMMENT='${token}'` }),
|
|
23
|
+
tableId: async (session, table) => {
|
|
24
|
+
const parts = (0, writer_1.tableParts)(exports.MYSQL_DIALECT, table);
|
|
25
|
+
const name = parts[parts.length - 1];
|
|
26
|
+
const schema = parts.length > 1 ? parts[0] : null;
|
|
27
|
+
const [found] = await session.all("SELECT TABLE_COMMENT AS id FROM information_schema.tables WHERE table_schema = COALESCE(?, DATABASE()) AND table_name = ? AND table_type = 'BASE TABLE'", [schema, name]);
|
|
28
|
+
const id = found?.id;
|
|
29
|
+
return typeof id === "string" && id.startsWith(writer_1.MARK_PREFIX) ? id : null;
|
|
30
|
+
},
|
|
31
|
+
tableExists: async (session, table) => {
|
|
32
|
+
const parts = (0, writer_1.tableParts)(exports.MYSQL_DIALECT, table);
|
|
33
|
+
const name = parts[parts.length - 1];
|
|
34
|
+
const schema = parts.length > 1 ? parts[0] : null;
|
|
35
|
+
const [found] = await session.all("SELECT COUNT(*) AS total FROM information_schema.tables WHERE table_schema = COALESCE(?, DATABASE()) AND table_name = ?", [schema, name]);
|
|
36
|
+
return Number(found?.total ?? 0) > 0;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const createMysqlSession = async (connection) => {
|
|
40
|
+
const pool = (0, client_1.createPool)(connection);
|
|
41
|
+
let handle;
|
|
42
|
+
try {
|
|
43
|
+
handle =
|
|
44
|
+
(await pool.getConnection());
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
await pool.end();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
run: async (sql, values) => {
|
|
52
|
+
await handle.query(sql, values);
|
|
53
|
+
},
|
|
54
|
+
all: async (sql, values) => {
|
|
55
|
+
const [rows] = await handle.query(sql, values);
|
|
56
|
+
return rows;
|
|
57
|
+
},
|
|
58
|
+
begin: () => handle.beginTransaction(),
|
|
59
|
+
commit: () => handle.commit(),
|
|
60
|
+
rollback: () => handle.rollback(),
|
|
61
|
+
close: async () => {
|
|
62
|
+
handle.release();
|
|
63
|
+
await pool.end();
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
exports.createMysqlSession = createMysqlSession;
|
|
68
|
+
class MysqlSink {
|
|
69
|
+
connection;
|
|
70
|
+
target;
|
|
71
|
+
logger;
|
|
72
|
+
createSession;
|
|
73
|
+
constructor(connection, target, logger = logger_1.consoleLogger, createSession = exports.createMysqlSession) {
|
|
74
|
+
this.connection = connection;
|
|
75
|
+
this.target = target;
|
|
76
|
+
this.logger = logger;
|
|
77
|
+
this.createSession = createSession;
|
|
78
|
+
}
|
|
79
|
+
async write(rows, options = {}) {
|
|
80
|
+
const session = await this.createSession(this.connection);
|
|
81
|
+
try {
|
|
82
|
+
await (0, writer_1.writeTable)(session, exports.MYSQL_DIALECT, (0, target_1.tableFor)(this.target, options), rows, this.logger);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await session.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.MysqlSink = MysqlSink;
|
|
@@ -10,6 +10,7 @@ const files_1 = require("../files");
|
|
|
10
10
|
const header_1 = require("../header");
|
|
11
11
|
const logger_1 = require("../../logger");
|
|
12
12
|
const parquetLibrary_1 = require("./parquetLibrary");
|
|
13
|
+
const cells_1 = require("../../cells");
|
|
13
14
|
const INT32_MIN = -(2 ** 31);
|
|
14
15
|
const INT32_MAX = 2 ** 31 - 1;
|
|
15
16
|
function typeOf(values) {
|
|
@@ -23,7 +24,7 @@ function typeOf(values) {
|
|
|
23
24
|
}
|
|
24
25
|
function parquetColumns(rows) {
|
|
25
26
|
return (0, header_1.buildHeader)(rows).map((name) => {
|
|
26
|
-
const values = rows.map((row) => row
|
|
27
|
+
const values = rows.map((row) => (0, cells_1.cellOf)(row, name) ?? null);
|
|
27
28
|
const type = typeOf(values);
|
|
28
29
|
return {
|
|
29
30
|
name,
|
|
@@ -8,6 +8,7 @@ exports.parquetRow = parquetRow;
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const cell_1 = require("../sql/cell");
|
|
10
10
|
const parquetLibrary_1 = require("./parquetLibrary");
|
|
11
|
+
const cells_1 = require("../../cells");
|
|
11
12
|
function join(prefix, name) {
|
|
12
13
|
return prefix ? `${prefix}.${name}` : name;
|
|
13
14
|
}
|
|
@@ -28,12 +29,10 @@ function flatten(value, prefix, row) {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
else if (Array.isArray(value) && value.every(isSimple)) {
|
|
31
|
-
row
|
|
32
|
-
.map((item) => String((0, cell_1.toCell)(item) ?? ""))
|
|
33
|
-
.join(", ");
|
|
32
|
+
(0, cells_1.setCell)(row, prefix, value.map((item) => String((0, cell_1.toCell)(item) ?? "")).join(", "));
|
|
34
33
|
}
|
|
35
34
|
else {
|
|
36
|
-
row
|
|
35
|
+
(0, cells_1.setCell)(row, prefix, (0, cell_1.toCell)(value));
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
function parquetRow(record) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TableRow } from "../../types";
|
|
2
|
+
import { Sink, SinkWriteOptions } from "../types";
|
|
3
|
+
import { Logger } from "../../logger";
|
|
4
|
+
import { SqlDialect, SqlSession } from "../sql/writer";
|
|
5
|
+
import { TableTarget } from "../sql/target";
|
|
6
|
+
import { PostgresClient, PostgresClientFactory, PostgresConnection } from "./postgresSource";
|
|
7
|
+
export declare const POSTGRES_DIALECT: SqlDialect;
|
|
8
|
+
export declare function postgresSession(client: PostgresClient): SqlSession;
|
|
9
|
+
export declare class PostgresSink implements Sink {
|
|
10
|
+
private readonly connection;
|
|
11
|
+
private readonly target;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
private readonly createClient;
|
|
14
|
+
constructor(connection: PostgresConnection, target: TableTarget, logger?: Logger, createClient?: PostgresClientFactory);
|
|
15
|
+
write(rows: TableRow[], options?: SinkWriteOptions): Promise<void>;
|
|
16
|
+
}
|