@sqlanvil/cli 1.17.0 → 1.18.0
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/bundle.js +1551 -1384
- package/package.json +1 -1
- package/worker_bundle.js +46 -0
package/bundle.js
CHANGED
|
@@ -27044,6 +27044,7 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27044
27044
|
* @property {string|null} [billingProject] Extract billingProject
|
|
27045
27045
|
* @property {Object.<string,string>|null} [columnTypes] Extract columnTypes
|
|
27046
27046
|
* @property {string|null} [fileName] Extract fileName
|
|
27047
|
+
* @property {string|null} [database] Extract database
|
|
27047
27048
|
*/
|
|
27048
27049
|
|
|
27049
27050
|
/**
|
|
@@ -27184,6 +27185,14 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27184
27185
|
*/
|
|
27185
27186
|
Extract.prototype.fileName = "";
|
|
27186
27187
|
|
|
27188
|
+
/**
|
|
27189
|
+
* Extract database.
|
|
27190
|
+
* @member {string} database
|
|
27191
|
+
* @memberof sqlanvil.Extract
|
|
27192
|
+
* @instance
|
|
27193
|
+
*/
|
|
27194
|
+
Extract.prototype.database = "";
|
|
27195
|
+
|
|
27187
27196
|
/**
|
|
27188
27197
|
* Creates a new Extract instance using the specified properties.
|
|
27189
27198
|
* @function create
|
|
@@ -27245,6 +27254,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27245
27254
|
writer.uint32(/* id 14, wireType 2 =*/114).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]).uint32(/* id 2, wireType 2 =*/18).string(message.columnTypes[keys[i]]).ldelim();
|
|
27246
27255
|
if (message.fileName != null && Object.hasOwnProperty.call(message, "fileName"))
|
|
27247
27256
|
writer.uint32(/* id 15, wireType 2 =*/122).string(message.fileName);
|
|
27257
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
27258
|
+
writer.uint32(/* id 16, wireType 2 =*/130).string(message.database);
|
|
27248
27259
|
return writer;
|
|
27249
27260
|
};
|
|
27250
27261
|
|
|
@@ -27370,6 +27381,10 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27370
27381
|
message.fileName = reader.string();
|
|
27371
27382
|
break;
|
|
27372
27383
|
}
|
|
27384
|
+
case 16: {
|
|
27385
|
+
message.database = reader.string();
|
|
27386
|
+
break;
|
|
27387
|
+
}
|
|
27373
27388
|
default:
|
|
27374
27389
|
reader.skipType(tag & 7, long);
|
|
27375
27390
|
break;
|
|
@@ -27481,6 +27496,9 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27481
27496
|
if (message.fileName != null && Object.hasOwnProperty.call(message, "fileName"))
|
|
27482
27497
|
if (!$util.isString(message.fileName))
|
|
27483
27498
|
return "fileName: string expected";
|
|
27499
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
27500
|
+
if (!$util.isString(message.database))
|
|
27501
|
+
return "database: string expected";
|
|
27484
27502
|
return null;
|
|
27485
27503
|
};
|
|
27486
27504
|
|
|
@@ -27580,6 +27598,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27580
27598
|
}
|
|
27581
27599
|
if (object.fileName != null)
|
|
27582
27600
|
message.fileName = String(object.fileName);
|
|
27601
|
+
if (object.database != null)
|
|
27602
|
+
message.database = String(object.database);
|
|
27583
27603
|
return message;
|
|
27584
27604
|
};
|
|
27585
27605
|
|
|
@@ -27619,6 +27639,7 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27619
27639
|
object.sourceName = "";
|
|
27620
27640
|
object.billingProject = "";
|
|
27621
27641
|
object.fileName = "";
|
|
27642
|
+
object.database = "";
|
|
27622
27643
|
}
|
|
27623
27644
|
if (message.target != null && Object.hasOwnProperty.call(message, "target"))
|
|
27624
27645
|
object.target = $root.sqlanvil.Target.toObject(message.target, options, q + 1);
|
|
@@ -27663,6 +27684,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
27663
27684
|
}
|
|
27664
27685
|
if (message.fileName != null && Object.hasOwnProperty.call(message, "fileName"))
|
|
27665
27686
|
object.fileName = message.fileName;
|
|
27687
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
27688
|
+
object.database = message.database;
|
|
27666
27689
|
return object;
|
|
27667
27690
|
};
|
|
27668
27691
|
|
|
@@ -37844,6 +37867,7 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
37844
37867
|
* @property {string|null} [sourceName] ExtractSpec sourceName
|
|
37845
37868
|
* @property {string|null} [billingProject] ExtractSpec billingProject
|
|
37846
37869
|
* @property {Object.<string,string>|null} [columnTypes] ExtractSpec columnTypes
|
|
37870
|
+
* @property {string|null} [database] ExtractSpec database
|
|
37847
37871
|
*/
|
|
37848
37872
|
|
|
37849
37873
|
/**
|
|
@@ -37918,6 +37942,14 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
37918
37942
|
*/
|
|
37919
37943
|
ExtractSpec.prototype.columnTypes = $util.emptyObject;
|
|
37920
37944
|
|
|
37945
|
+
/**
|
|
37946
|
+
* ExtractSpec database.
|
|
37947
|
+
* @member {string} database
|
|
37948
|
+
* @memberof sqlanvil.ExtractSpec
|
|
37949
|
+
* @instance
|
|
37950
|
+
*/
|
|
37951
|
+
ExtractSpec.prototype.database = "";
|
|
37952
|
+
|
|
37921
37953
|
/**
|
|
37922
37954
|
* Creates a new ExtractSpec instance using the specified properties.
|
|
37923
37955
|
* @function create
|
|
@@ -37961,6 +37993,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
37961
37993
|
if (message.columnTypes != null && Object.hasOwnProperty.call(message, "columnTypes"))
|
|
37962
37994
|
for (let keys = Object.keys(message.columnTypes), i = 0; i < keys.length; ++i)
|
|
37963
37995
|
writer.uint32(/* id 7, wireType 2 =*/58).fork().uint32(/* id 1, wireType 2 =*/10).string(keys[i]).uint32(/* id 2, wireType 2 =*/18).string(message.columnTypes[keys[i]]).ldelim();
|
|
37996
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
37997
|
+
writer.uint32(/* id 8, wireType 2 =*/66).string(message.database);
|
|
37964
37998
|
return writer;
|
|
37965
37999
|
};
|
|
37966
38000
|
|
|
@@ -38050,6 +38084,10 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
38050
38084
|
message.columnTypes[key] = value;
|
|
38051
38085
|
break;
|
|
38052
38086
|
}
|
|
38087
|
+
case 8: {
|
|
38088
|
+
message.database = reader.string();
|
|
38089
|
+
break;
|
|
38090
|
+
}
|
|
38053
38091
|
default:
|
|
38054
38092
|
reader.skipType(tag & 7, long);
|
|
38055
38093
|
break;
|
|
@@ -38115,6 +38153,9 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
38115
38153
|
if (!$util.isString(message.columnTypes[key[i]]))
|
|
38116
38154
|
return "columnTypes: string{k:string} expected";
|
|
38117
38155
|
}
|
|
38156
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
38157
|
+
if (!$util.isString(message.database))
|
|
38158
|
+
return "database: string expected";
|
|
38118
38159
|
return null;
|
|
38119
38160
|
};
|
|
38120
38161
|
|
|
@@ -38158,6 +38199,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
38158
38199
|
message.columnTypes[keys[i]] = String(object.columnTypes[keys[i]]);
|
|
38159
38200
|
}
|
|
38160
38201
|
}
|
|
38202
|
+
if (object.database != null)
|
|
38203
|
+
message.database = String(object.database);
|
|
38161
38204
|
return message;
|
|
38162
38205
|
};
|
|
38163
38206
|
|
|
@@ -38187,6 +38230,7 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
38187
38230
|
object.dataset = "";
|
|
38188
38231
|
object.sourceName = "";
|
|
38189
38232
|
object.billingProject = "";
|
|
38233
|
+
object.database = "";
|
|
38190
38234
|
}
|
|
38191
38235
|
if (message.connectionName != null && Object.hasOwnProperty.call(message, "connectionName"))
|
|
38192
38236
|
object.connectionName = message.connectionName;
|
|
@@ -38209,6 +38253,8 @@ const sqlanvil = $root.sqlanvil = (() => {
|
|
|
38209
38253
|
object.columnTypes[keys2[j]] = message.columnTypes[keys2[j]];
|
|
38210
38254
|
}
|
|
38211
38255
|
}
|
|
38256
|
+
if (message.database != null && Object.hasOwnProperty.call(message, "database"))
|
|
38257
|
+
object.database = message.database;
|
|
38212
38258
|
return object;
|
|
38213
38259
|
};
|
|
38214
38260
|
|
|
@@ -48751,7 +48797,7 @@ function collectEvaluationQueries(queryOrAction, concatenate, queryModifier = (q
|
|
|
48751
48797
|
.filter(validationQuery => !!validationQuery.query);
|
|
48752
48798
|
}
|
|
48753
48799
|
|
|
48754
|
-
const version = "1.
|
|
48800
|
+
const version = "1.18.0";
|
|
48755
48801
|
const dataformVersion = "3.0.61";
|
|
48756
48802
|
|
|
48757
48803
|
async function build(compiledGraph, runConfig, dbadapter) {
|
|
@@ -48817,6 +48863,7 @@ class Builder {
|
|
|
48817
48863
|
platform: ext.platform,
|
|
48818
48864
|
project: ext.project,
|
|
48819
48865
|
dataset: ext.dataset,
|
|
48866
|
+
database: ext.database,
|
|
48820
48867
|
sourceName: ext.sourceName,
|
|
48821
48868
|
billingProject: ext.billingProject,
|
|
48822
48869
|
columnTypes: ext.columnTypes
|
|
@@ -49269,8 +49316,36 @@ function mapBigQueryType(bqType) {
|
|
|
49269
49316
|
function mapPostgresType(pgType) {
|
|
49270
49317
|
return pgType.trim().toLowerCase();
|
|
49271
49318
|
}
|
|
49319
|
+
const MYSQL_TYPE_MAP = {
|
|
49320
|
+
tinyint: "smallint",
|
|
49321
|
+
mediumint: "integer",
|
|
49322
|
+
int: "integer",
|
|
49323
|
+
double: "double precision",
|
|
49324
|
+
float: "real",
|
|
49325
|
+
decimal: "numeric",
|
|
49326
|
+
bit: "smallint",
|
|
49327
|
+
year: "smallint",
|
|
49328
|
+
datetime: "timestamp",
|
|
49329
|
+
timestamp: "timestamptz",
|
|
49330
|
+
char: "text",
|
|
49331
|
+
varchar: "text",
|
|
49332
|
+
tinytext: "text",
|
|
49333
|
+
mediumtext: "text",
|
|
49334
|
+
longtext: "text",
|
|
49335
|
+
enum: "text",
|
|
49336
|
+
set: "text",
|
|
49337
|
+
binary: "bytea",
|
|
49338
|
+
varbinary: "bytea",
|
|
49339
|
+
tinyblob: "bytea",
|
|
49340
|
+
blob: "bytea",
|
|
49341
|
+
mediumblob: "bytea",
|
|
49342
|
+
longblob: "bytea",
|
|
49343
|
+
json: "jsonb",
|
|
49344
|
+
geometry: "text"
|
|
49345
|
+
};
|
|
49272
49346
|
function mapMysqlType(mysqlType) {
|
|
49273
|
-
|
|
49347
|
+
const key = mysqlType.trim().toLowerCase();
|
|
49348
|
+
return MYSQL_TYPE_MAP[key] || key;
|
|
49274
49349
|
}
|
|
49275
49350
|
function keyToken(name) {
|
|
49276
49351
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `"${name}"`;
|
|
@@ -50358,10 +50433,55 @@ class PostgresDbAdapter {
|
|
|
50358
50433
|
}
|
|
50359
50434
|
}
|
|
50360
50435
|
|
|
50361
|
-
const DEFAULT_ROW_CAP = 1000000;
|
|
50362
|
-
const DEFAULT_BYTE_CAP = 512 * 1024 * 1024;
|
|
50363
50436
|
const MAX_PARAMS_PER_INSERT = 60000;
|
|
50364
50437
|
const quoteIdent = (id) => `"${String(id).replace(/"/g, '""')}"`;
|
|
50438
|
+
async function loadRowsIntoPostgres(args) {
|
|
50439
|
+
var _a;
|
|
50440
|
+
const { target, columnTypes, rows } = args;
|
|
50441
|
+
const cols = Object.keys(columnTypes);
|
|
50442
|
+
const coerce = args.coerce || ((v) => (v === undefined ? null : v));
|
|
50443
|
+
const pg = await PostgresDbAdapter.create(args.pg, {
|
|
50444
|
+
disableSslForTestsOnly: args.disableSslForTestsOnly
|
|
50445
|
+
});
|
|
50446
|
+
try {
|
|
50447
|
+
const qualified = `${quoteIdent(target.schema)}.${quoteIdent(target.name)}`;
|
|
50448
|
+
const colDefs = cols.map(c => `${quoteIdent(c)} ${columnTypes[c]}`).join(", ");
|
|
50449
|
+
await pg.execute(`create schema if not exists ${quoteIdent(target.schema)}`);
|
|
50450
|
+
const { rows: existing } = await pg.execute(`select c.relkind from pg_class c join pg_namespace n on n.oid = c.relnamespace ` +
|
|
50451
|
+
`where n.nspname = $1 and c.relname = $2`, { params: [target.schema, target.name] });
|
|
50452
|
+
const relkind = (_a = existing[0]) === null || _a === void 0 ? void 0 : _a.relkind;
|
|
50453
|
+
if (relkind === "f") {
|
|
50454
|
+
await pg.execute(`drop foreign table ${qualified} cascade`);
|
|
50455
|
+
}
|
|
50456
|
+
else if (relkind === "v") {
|
|
50457
|
+
await pg.execute(`drop view ${qualified} cascade`);
|
|
50458
|
+
}
|
|
50459
|
+
else if (relkind) {
|
|
50460
|
+
await pg.execute(`drop table ${qualified} cascade`);
|
|
50461
|
+
}
|
|
50462
|
+
await pg.execute(`create table ${qualified} (${colDefs})`);
|
|
50463
|
+
const colIdents = cols.map(quoteIdent).join(", ");
|
|
50464
|
+
const batchRows = Math.max(1, Math.min(1000, Math.floor(MAX_PARAMS_PER_INSERT / cols.length)));
|
|
50465
|
+
for (let i = 0; i < rows.length; i += batchRows) {
|
|
50466
|
+
const batch = rows.slice(i, i + batchRows);
|
|
50467
|
+
const params = [];
|
|
50468
|
+
const tuples = batch.map((row, r) => {
|
|
50469
|
+
const placeholders = cols.map((c, ci) => `$${r * cols.length + ci + 1}`);
|
|
50470
|
+
cols.forEach(c => params.push(coerce(row[c])));
|
|
50471
|
+
return `(${placeholders.join(", ")})`;
|
|
50472
|
+
});
|
|
50473
|
+
await pg.execute(`insert into ${qualified} (${colIdents}) values ${tuples.join(", ")}`, {
|
|
50474
|
+
params
|
|
50475
|
+
});
|
|
50476
|
+
}
|
|
50477
|
+
}
|
|
50478
|
+
finally {
|
|
50479
|
+
await pg.close();
|
|
50480
|
+
}
|
|
50481
|
+
}
|
|
50482
|
+
|
|
50483
|
+
const DEFAULT_ROW_CAP$1 = 1000000;
|
|
50484
|
+
const DEFAULT_BYTE_CAP$1 = 512 * 1024 * 1024;
|
|
50365
50485
|
function coerce(v) {
|
|
50366
50486
|
if (v === null || v === undefined) {
|
|
50367
50487
|
return null;
|
|
@@ -50375,7 +50495,7 @@ async function runBigQueryExtract(args) {
|
|
|
50375
50495
|
var _a, _b;
|
|
50376
50496
|
const { spec, target, connectionCredentials } = args;
|
|
50377
50497
|
if ((spec.platform || "bigquery") !== "bigquery") {
|
|
50378
|
-
throw new Error(`runner-extract
|
|
50498
|
+
throw new Error(`bigquery runner-extract got a "${spec.platform}" source.`);
|
|
50379
50499
|
}
|
|
50380
50500
|
const cols = Object.keys(spec.columnTypes || {});
|
|
50381
50501
|
if (cols.length === 0) {
|
|
@@ -50391,8 +50511,8 @@ async function runBigQueryExtract(args) {
|
|
|
50391
50511
|
accessToken: conn.accessToken,
|
|
50392
50512
|
credentials: conn.credentials
|
|
50393
50513
|
}));
|
|
50394
|
-
const rowCap = (_a = args.rowCap) !== null && _a !== void 0 ? _a : DEFAULT_ROW_CAP;
|
|
50395
|
-
const byteCap = (_b = args.byteCap) !== null && _b !== void 0 ? _b : DEFAULT_BYTE_CAP;
|
|
50514
|
+
const rowCap = (_a = args.rowCap) !== null && _a !== void 0 ? _a : DEFAULT_ROW_CAP$1;
|
|
50515
|
+
const byteCap = (_b = args.byteCap) !== null && _b !== void 0 ? _b : DEFAULT_BYTE_CAP$1;
|
|
50396
50516
|
const colList = cols.map(c => "`" + c + "`").join(", ");
|
|
50397
50517
|
const fqn = "`" + `${spec.project}.${spec.dataset}.${spec.sourceName}` + "`";
|
|
50398
50518
|
const { rows } = await bq.execute(`select ${colList} from ${fqn}`, {
|
|
@@ -50402,471 +50522,868 @@ async function runBigQueryExtract(args) {
|
|
|
50402
50522
|
if (rows.length >= rowCap) {
|
|
50403
50523
|
console.warn(`runner-extract: ${target.schema}.${target.name} truncated at ${rowCap} rows (source is larger).`);
|
|
50404
50524
|
}
|
|
50405
|
-
|
|
50525
|
+
await loadRowsIntoPostgres({
|
|
50526
|
+
pg: args.pg,
|
|
50527
|
+
target,
|
|
50528
|
+
columnTypes: spec.columnTypes,
|
|
50529
|
+
rows,
|
|
50530
|
+
coerce,
|
|
50406
50531
|
disableSslForTestsOnly: args.disableSslForTestsOnly
|
|
50407
50532
|
});
|
|
50408
|
-
|
|
50409
|
-
const qualified = `${quoteIdent(target.schema)}.${quoteIdent(target.name)}`;
|
|
50410
|
-
const colDefs = cols.map(c => `${quoteIdent(c)} ${spec.columnTypes[c]}`).join(", ");
|
|
50411
|
-
await pg.execute(`create schema if not exists ${quoteIdent(target.schema)}`);
|
|
50412
|
-
await pg.execute(`drop foreign table if exists ${qualified} cascade`);
|
|
50413
|
-
await pg.execute(`drop table if exists ${qualified} cascade`);
|
|
50414
|
-
await pg.execute(`create table ${qualified} (${colDefs})`);
|
|
50415
|
-
const colIdents = cols.map(quoteIdent).join(", ");
|
|
50416
|
-
const batchRows = Math.max(1, Math.min(1000, Math.floor(MAX_PARAMS_PER_INSERT / cols.length)));
|
|
50417
|
-
for (let i = 0; i < rows.length; i += batchRows) {
|
|
50418
|
-
const batch = rows.slice(i, i + batchRows);
|
|
50419
|
-
const params = [];
|
|
50420
|
-
const tuples = batch.map((row, r) => {
|
|
50421
|
-
const placeholders = cols.map((c, ci) => `$${r * cols.length + ci + 1}`);
|
|
50422
|
-
cols.forEach(c => params.push(coerce(row[c])));
|
|
50423
|
-
return `(${placeholders.join(", ")})`;
|
|
50424
|
-
});
|
|
50425
|
-
await pg.execute(`insert into ${qualified} (${colIdents}) values ${tuples.join(", ")}`, {
|
|
50426
|
-
params
|
|
50427
|
-
});
|
|
50428
|
-
}
|
|
50429
|
-
return { rowCount: rows.length };
|
|
50430
|
-
}
|
|
50431
|
-
finally {
|
|
50432
|
-
await pg.close();
|
|
50433
|
-
}
|
|
50533
|
+
return { rowCount: rows.length };
|
|
50434
50534
|
}
|
|
50435
50535
|
|
|
50436
|
-
|
|
50437
|
-
|
|
50438
|
-
|
|
50439
|
-
}
|
|
50440
|
-
catch (e) {
|
|
50441
|
-
throw new Error(`This feature requires the optional "@duckdb/node-api" dependency, which failed to load: ` +
|
|
50442
|
-
`${e.message}`);
|
|
50536
|
+
class MySqlPoolExecutor {
|
|
50537
|
+
constructor(config, options) {
|
|
50538
|
+
this.pool = mysql__namespace.createPool(Object.assign(Object.assign({}, config), { connectionLimit: (options === null || options === void 0 ? void 0 : options.concurrencyLimit) || 10, waitForConnections: true, multipleStatements: false }));
|
|
50443
50539
|
}
|
|
50444
|
-
|
|
50445
|
-
|
|
50446
|
-
await conn.run(sql);
|
|
50447
|
-
}
|
|
50448
|
-
async function allAsync(conn, sql) {
|
|
50449
|
-
const reader = await conn.runAndReadAll(sql);
|
|
50450
|
-
return reader.getRowObjects();
|
|
50451
|
-
}
|
|
50452
|
-
async function withDuckdb(fn, dbPath = ":memory:") {
|
|
50453
|
-
const { DuckDBInstance } = loadDuckdb();
|
|
50454
|
-
const instance = await DuckDBInstance.create(dbPath);
|
|
50455
|
-
const conn = await instance.connect();
|
|
50456
|
-
const done = () => {
|
|
50457
|
-
var _a, _b;
|
|
50540
|
+
async verifyConnection() {
|
|
50541
|
+
const conn = await this.pool.getConnection();
|
|
50458
50542
|
try {
|
|
50459
|
-
|
|
50543
|
+
await conn.query("select 1");
|
|
50460
50544
|
}
|
|
50461
|
-
|
|
50545
|
+
finally {
|
|
50546
|
+
conn.release();
|
|
50462
50547
|
}
|
|
50548
|
+
}
|
|
50549
|
+
async withClientLock(callback) {
|
|
50550
|
+
const conn = await this.pool.getConnection();
|
|
50551
|
+
let released = false;
|
|
50552
|
+
const releaseOnce = () => {
|
|
50553
|
+
if (released) {
|
|
50554
|
+
return;
|
|
50555
|
+
}
|
|
50556
|
+
released = true;
|
|
50557
|
+
conn.release();
|
|
50558
|
+
};
|
|
50463
50559
|
try {
|
|
50464
|
-
|
|
50465
|
-
|
|
50466
|
-
|
|
50560
|
+
return await callback({
|
|
50561
|
+
execute: async (statement, options = { rowLimit: 1000 }) => {
|
|
50562
|
+
const [rows] = await conn.query(statement, options.params || []);
|
|
50563
|
+
const arr = Array.isArray(rows) ? rows : [];
|
|
50564
|
+
return options.rowLimit && arr.length > options.rowLimit
|
|
50565
|
+
? arr.slice(0, options.rowLimit)
|
|
50566
|
+
: arr;
|
|
50567
|
+
}
|
|
50568
|
+
});
|
|
50467
50569
|
}
|
|
50468
|
-
|
|
50469
|
-
|
|
50470
|
-
if (!process.env.HOME) {
|
|
50471
|
-
await runAsync(conn, `SET home_directory='${os__namespace.tmpdir()}'`);
|
|
50570
|
+
finally {
|
|
50571
|
+
releaseOnce();
|
|
50472
50572
|
}
|
|
50473
|
-
const result = await fn(conn);
|
|
50474
|
-
done();
|
|
50475
|
-
return result;
|
|
50476
50573
|
}
|
|
50477
|
-
|
|
50478
|
-
|
|
50479
|
-
throw e;
|
|
50574
|
+
async close() {
|
|
50575
|
+
await this.pool.end();
|
|
50480
50576
|
}
|
|
50481
50577
|
}
|
|
50482
|
-
|
|
50483
|
-
|
|
50484
|
-
|
|
50485
|
-
|
|
50486
|
-
|
|
50487
|
-
|
|
50488
|
-
|
|
50489
|
-
|
|
50490
|
-
|
|
50491
|
-
|
|
50492
|
-
|
|
50493
|
-
|
|
50494
|
-
|
|
50495
|
-
|
|
50496
|
-
|
|
50497
|
-
|
|
50498
|
-
|
|
50499
|
-
|
|
50500
|
-
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
|
|
50505
|
-
|
|
50506
|
-
|
|
50507
|
-
|
|
50508
|
-
|
|
50509
|
-
|
|
50510
|
-
|
|
50511
|
-
|
|
50512
|
-
|
|
50513
|
-
|
|
50514
|
-
|
|
50515
|
-
|
|
50516
|
-
|
|
50517
|
-
|
|
50518
|
-
|
|
50519
|
-
|
|
50520
|
-
.
|
|
50521
|
-
|
|
50522
|
-
|
|
50578
|
+
function convertFieldType(type) {
|
|
50579
|
+
switch (String(type).toUpperCase()) {
|
|
50580
|
+
case "FLOAT":
|
|
50581
|
+
case "DOUBLE":
|
|
50582
|
+
case "REAL":
|
|
50583
|
+
return sqlanvil.Field.Primitive.FLOAT;
|
|
50584
|
+
case "TINYINT":
|
|
50585
|
+
case "SMALLINT":
|
|
50586
|
+
case "MEDIUMINT":
|
|
50587
|
+
case "INT":
|
|
50588
|
+
case "INTEGER":
|
|
50589
|
+
case "BIGINT":
|
|
50590
|
+
case "YEAR":
|
|
50591
|
+
case "BIT":
|
|
50592
|
+
return sqlanvil.Field.Primitive.INTEGER;
|
|
50593
|
+
case "DECIMAL":
|
|
50594
|
+
case "DEC":
|
|
50595
|
+
case "NUMERIC":
|
|
50596
|
+
case "FIXED":
|
|
50597
|
+
return sqlanvil.Field.Primitive.NUMERIC;
|
|
50598
|
+
case "BOOL":
|
|
50599
|
+
case "BOOLEAN":
|
|
50600
|
+
return sqlanvil.Field.Primitive.BOOLEAN;
|
|
50601
|
+
case "CHAR":
|
|
50602
|
+
case "VARCHAR":
|
|
50603
|
+
case "TINYTEXT":
|
|
50604
|
+
case "TEXT":
|
|
50605
|
+
case "MEDIUMTEXT":
|
|
50606
|
+
case "LONGTEXT":
|
|
50607
|
+
case "ENUM":
|
|
50608
|
+
case "SET":
|
|
50609
|
+
case "JSON":
|
|
50610
|
+
case "TIME":
|
|
50611
|
+
return sqlanvil.Field.Primitive.STRING;
|
|
50612
|
+
case "DATE":
|
|
50613
|
+
return sqlanvil.Field.Primitive.DATE;
|
|
50614
|
+
case "DATETIME":
|
|
50615
|
+
case "TIMESTAMP":
|
|
50616
|
+
return sqlanvil.Field.Primitive.TIMESTAMP;
|
|
50617
|
+
default:
|
|
50618
|
+
return sqlanvil.Field.Primitive.UNKNOWN;
|
|
50523
50619
|
}
|
|
50524
|
-
return null;
|
|
50525
50620
|
}
|
|
50526
|
-
function
|
|
50527
|
-
|
|
50528
|
-
return uri.slice("local://".length);
|
|
50529
|
-
}
|
|
50530
|
-
return uri;
|
|
50621
|
+
function escapeMysqlString(s) {
|
|
50622
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
50531
50623
|
}
|
|
50532
|
-
function
|
|
50533
|
-
|
|
50534
|
-
|
|
50624
|
+
function reconstructColumnDef(col) {
|
|
50625
|
+
const extra = (col.extra || "").toLowerCase();
|
|
50626
|
+
const notNull = col.isNullable === "NO";
|
|
50627
|
+
if (extra.includes("generated") && col.generationExpression) {
|
|
50628
|
+
const storage = extra.includes("stored") ? "STORED" : "VIRTUAL";
|
|
50629
|
+
return `${col.columnType} GENERATED ALWAYS AS (${col.generationExpression}) ${storage}${notNull ? " NOT NULL" : ""}`;
|
|
50535
50630
|
}
|
|
50536
|
-
|
|
50537
|
-
|
|
50631
|
+
let def = col.columnType;
|
|
50632
|
+
if (col.collationName) {
|
|
50633
|
+
def += ` COLLATE ${col.collationName}`;
|
|
50538
50634
|
}
|
|
50539
|
-
|
|
50540
|
-
|
|
50541
|
-
|
|
50542
|
-
|
|
50543
|
-
const fmt = (format || "").toLowerCase();
|
|
50544
|
-
const extraOptions = Object.entries(options || {}).map(([key, value]) => `${key.toUpperCase()} ${value}`);
|
|
50545
|
-
const optionList = [`FORMAT ${fmt}`, ...extraOptions].join(", ");
|
|
50546
|
-
return `COPY (SELECT * FROM postgres_query('${PG_ATTACH_ALIAS}', $sa$${selectSql}$sa$)) TO '${target}' (${optionList})`;
|
|
50547
|
-
}
|
|
50548
|
-
async function runDuckdbExport(args) {
|
|
50549
|
-
const { spec, selectSql, pg, storage, actionName } = args;
|
|
50550
|
-
const uri = resolveExportUri(spec, actionName, { wildcard: false });
|
|
50551
|
-
const scheme = schemeOf(uri);
|
|
50552
|
-
if (scheme !== "local" && !(storage === null || storage === void 0 ? void 0 : storage[scheme])) {
|
|
50553
|
-
throw new Error(`No "${scheme}" storage credentials found in .df-credentials.json (storage.${scheme}) for ` +
|
|
50554
|
-
`export to ${uri}.`);
|
|
50635
|
+
def += notNull ? " NOT NULL" : " NULL";
|
|
50636
|
+
def += defaultClause(col, extra);
|
|
50637
|
+
if (extra.includes("on update current_timestamp")) {
|
|
50638
|
+
def += " ON UPDATE CURRENT_TIMESTAMP";
|
|
50555
50639
|
}
|
|
50556
|
-
|
|
50557
|
-
|
|
50558
|
-
await runAsync(conn, buildAttachSql(pg));
|
|
50559
|
-
if (scheme !== "local") {
|
|
50560
|
-
const secret = buildSecretSql(scheme, storage[scheme]);
|
|
50561
|
-
if (secret) {
|
|
50562
|
-
await runAsync(conn, secret);
|
|
50563
|
-
}
|
|
50564
|
-
}
|
|
50565
|
-
await runAsync(conn, buildCopySql(selectSql, uri, spec.format, spec.options || {}));
|
|
50566
|
-
return { destination: toCopyTarget(uri) };
|
|
50567
|
-
});
|
|
50568
|
-
}
|
|
50569
|
-
|
|
50570
|
-
function readerForFormat(format) {
|
|
50571
|
-
switch ((format || "").toLowerCase()) {
|
|
50572
|
-
case "csv":
|
|
50573
|
-
return "read_csv_auto";
|
|
50574
|
-
case "json":
|
|
50575
|
-
return "read_json_auto";
|
|
50576
|
-
case "parquet":
|
|
50577
|
-
default:
|
|
50578
|
-
return "read_parquet";
|
|
50640
|
+
if (extra.includes("auto_increment")) {
|
|
50641
|
+
def += " AUTO_INCREMENT";
|
|
50579
50642
|
}
|
|
50643
|
+
return def;
|
|
50580
50644
|
}
|
|
50581
|
-
function
|
|
50582
|
-
|
|
50583
|
-
|
|
50584
|
-
|
|
50585
|
-
}
|
|
50586
|
-
function buildImportSql(target, location, format, overwrite) {
|
|
50587
|
-
const reader = readerForFormat(format);
|
|
50588
|
-
const source = toCopyTarget(location);
|
|
50589
|
-
const dest = importTargetSql(target);
|
|
50590
|
-
const select = `SELECT * FROM ${reader}('${source}')`;
|
|
50591
|
-
if (overwrite) {
|
|
50592
|
-
return [`DROP TABLE IF EXISTS ${dest}`, `CREATE TABLE ${dest} AS ${select}`];
|
|
50645
|
+
function defaultClause(col, extra) {
|
|
50646
|
+
if (extra.includes("default_generated")) {
|
|
50647
|
+
const d = col.columnDefault || "";
|
|
50648
|
+
return /^current_timestamp/i.test(d) ? ` DEFAULT ${d}` : ` DEFAULT (${d})`;
|
|
50593
50649
|
}
|
|
50594
|
-
|
|
50595
|
-
|
|
50596
|
-
async function runDuckdbImport(args) {
|
|
50597
|
-
const { spec, target, pg, storage } = args;
|
|
50598
|
-
const location = spec.location || "";
|
|
50599
|
-
const scheme = schemeOf(location);
|
|
50600
|
-
if (scheme !== "local" && !(storage === null || storage === void 0 ? void 0 : storage[scheme])) {
|
|
50601
|
-
throw new Error(`No "${scheme}" storage credentials found in .df-credentials.json (storage.${scheme}) for ` +
|
|
50602
|
-
`import from ${location}.`);
|
|
50650
|
+
if (col.columnDefault === null || col.columnDefault === undefined) {
|
|
50651
|
+
return "";
|
|
50603
50652
|
}
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50607
|
-
|
|
50608
|
-
|
|
50609
|
-
if (secret) {
|
|
50610
|
-
await runAsync(conn, secret);
|
|
50611
|
-
}
|
|
50612
|
-
}
|
|
50613
|
-
for (const statement of buildImportSql(target, location, spec.format, spec.overwrite)) {
|
|
50614
|
-
await runAsync(conn, statement);
|
|
50615
|
-
}
|
|
50616
|
-
return { source: toCopyTarget(location) };
|
|
50617
|
-
});
|
|
50653
|
+
const d = col.columnDefault;
|
|
50654
|
+
if (d.toUpperCase() === "NULL" || /^-?\d+(\.\d+)?$/.test(d)) {
|
|
50655
|
+
return ` DEFAULT ${d}`;
|
|
50656
|
+
}
|
|
50657
|
+
return ` DEFAULT '${escapeMysqlString(d)}'`;
|
|
50618
50658
|
}
|
|
50619
50659
|
|
|
50620
|
-
|
|
50621
|
-
|
|
50622
|
-
|
|
50623
|
-
|
|
50624
|
-
|
|
50625
|
-
|
|
50626
|
-
|
|
50627
|
-
|
|
50628
|
-
|
|
50629
|
-
throw Flags.invalidFlagValueError(name);
|
|
50630
|
-
});
|
|
50631
|
-
}
|
|
50632
|
-
static string(name, defaultValue = "") {
|
|
50633
|
-
return new SingleValueFlag(name, defaultValue, (stringValue) => stringValue);
|
|
50634
|
-
}
|
|
50635
|
-
static number(name, defaultValue = 0) {
|
|
50636
|
-
return new SingleValueFlag(name, defaultValue, (stringValue) => parseFloat(stringValue));
|
|
50637
|
-
}
|
|
50638
|
-
static long(name, defaultValue = Long__default["default"].ZERO) {
|
|
50639
|
-
return new SingleValueFlag(name, defaultValue, Long__default["default"].fromString);
|
|
50660
|
+
const INTERNAL_SCHEMAS = new Set([
|
|
50661
|
+
"information_schema",
|
|
50662
|
+
"mysql",
|
|
50663
|
+
"performance_schema",
|
|
50664
|
+
"sys"
|
|
50665
|
+
]);
|
|
50666
|
+
class MySqlDbAdapter {
|
|
50667
|
+
constructor(queryExecutor) {
|
|
50668
|
+
this.queryExecutor = queryExecutor;
|
|
50640
50669
|
}
|
|
50641
|
-
static
|
|
50642
|
-
|
|
50670
|
+
static async create(credentials, options) {
|
|
50671
|
+
const sslMode = (credentials.sslMode || "").toLowerCase();
|
|
50672
|
+
const ssl = !(options === null || options === void 0 ? void 0 : options.disableSslForTestsOnly) && sslMode && sslMode !== "disable"
|
|
50673
|
+
?
|
|
50674
|
+
{ rejectUnauthorized: false }
|
|
50675
|
+
: undefined;
|
|
50676
|
+
const queryExecutor = new MySqlPoolExecutor({
|
|
50677
|
+
host: credentials.host,
|
|
50678
|
+
port: credentials.port || 3306,
|
|
50679
|
+
user: credentials.user,
|
|
50680
|
+
password: credentials.password,
|
|
50681
|
+
database: credentials.database || undefined,
|
|
50682
|
+
ssl
|
|
50683
|
+
}, options);
|
|
50684
|
+
try {
|
|
50685
|
+
await queryExecutor.verifyConnection();
|
|
50686
|
+
}
|
|
50687
|
+
catch (e) {
|
|
50688
|
+
await queryExecutor.close().catch(() => undefined);
|
|
50689
|
+
throw new ErrorWithCause(`Could not connect to MySQL at ${credentials.host}:${credentials.port || 3306} ` +
|
|
50690
|
+
`as "${credentials.user}": ${e.message}`, e);
|
|
50691
|
+
}
|
|
50692
|
+
return new MySqlDbAdapter(queryExecutor);
|
|
50643
50693
|
}
|
|
50644
|
-
|
|
50645
|
-
return
|
|
50694
|
+
async execute(statement, options = { rowLimit: 1000, byteLimit: 1024 * 1024 }) {
|
|
50695
|
+
return await this.withClientLock(client => client.execute(statement, options));
|
|
50646
50696
|
}
|
|
50647
|
-
|
|
50648
|
-
|
|
50697
|
+
async executeRaw(statement, options = { rowLimit: 1000 }) {
|
|
50698
|
+
const result = await this.execute(statement, options);
|
|
50699
|
+
return Object.assign(Object.assign({}, result), { schema: [] });
|
|
50649
50700
|
}
|
|
50650
|
-
|
|
50651
|
-
return
|
|
50701
|
+
async withClientLock(callback) {
|
|
50702
|
+
return await this.queryExecutor.withClientLock(client => callback({
|
|
50703
|
+
execute: async (stmt, opts = { rowLimit: 1000, byteLimit: 1024 * 1024 }) => {
|
|
50704
|
+
try {
|
|
50705
|
+
const rows = await client.execute(stmt, { params: opts.params, rowLimit: opts.rowLimit });
|
|
50706
|
+
return { rows, metadata: {} };
|
|
50707
|
+
}
|
|
50708
|
+
catch (e) {
|
|
50709
|
+
if (opts.includeQueryInError) {
|
|
50710
|
+
throw new Error(`Error encountered while running "${stmt}": ${e.message}`);
|
|
50711
|
+
}
|
|
50712
|
+
throw new ErrorWithCause(`Error executing mysql query: ${e.message}`, e);
|
|
50713
|
+
}
|
|
50714
|
+
},
|
|
50715
|
+
executeRaw: async (stmt, opts = { rowLimit: 1000 }) => {
|
|
50716
|
+
const positional = opts.params ? Object.values(opts.params) : undefined;
|
|
50717
|
+
const rows = await client.execute(stmt, { params: positional, rowLimit: opts.rowLimit });
|
|
50718
|
+
return { rows, schema: [], metadata: {} };
|
|
50719
|
+
}
|
|
50720
|
+
}));
|
|
50652
50721
|
}
|
|
50653
|
-
|
|
50654
|
-
|
|
50655
|
-
|
|
50656
|
-
|
|
50657
|
-
|
|
50658
|
-
|
|
50659
|
-
|
|
50660
|
-
|
|
50661
|
-
|
|
50662
|
-
|
|
50663
|
-
|
|
50664
|
-
|
|
50665
|
-
|
|
50666
|
-
|
|
50667
|
-
|
|
50722
|
+
async evaluate(queryOrAction) {
|
|
50723
|
+
const validationQueries = collectEvaluationQueries(queryOrAction, false, (query) => !!query ? `explain ${query}` : "").map((validationQuery, index) => ({ index, validationQuery }));
|
|
50724
|
+
const validationQueriesWithoutWrappers = collectEvaluationQueries(queryOrAction, false);
|
|
50725
|
+
const queryEvaluations = new Array();
|
|
50726
|
+
for (const { index, validationQuery } of validationQueries) {
|
|
50727
|
+
let evaluationResponse = {
|
|
50728
|
+
status: sqlanvil.QueryEvaluation.QueryEvaluationStatus.SUCCESS
|
|
50729
|
+
};
|
|
50730
|
+
try {
|
|
50731
|
+
await this.execute(validationQuery.query);
|
|
50732
|
+
}
|
|
50733
|
+
catch (e) {
|
|
50734
|
+
evaluationResponse = {
|
|
50735
|
+
status: sqlanvil.QueryEvaluation.QueryEvaluationStatus.FAILURE,
|
|
50736
|
+
error: sqlanvil.QueryEvaluationError.create({
|
|
50737
|
+
message: (e === null || e === void 0 ? void 0 : e.message) ? String(e.message) : String(e)
|
|
50738
|
+
})
|
|
50739
|
+
};
|
|
50740
|
+
}
|
|
50741
|
+
queryEvaluations.push(sqlanvil.QueryEvaluation.create(Object.assign(Object.assign({}, evaluationResponse), { incremental: validationQuery.incremental, query: validationQueriesWithoutWrappers[index].query })));
|
|
50668
50742
|
}
|
|
50743
|
+
return queryEvaluations;
|
|
50669
50744
|
}
|
|
50670
|
-
|
|
50671
|
-
|
|
50672
|
-
|
|
50673
|
-
|
|
50674
|
-
|
|
50675
|
-
|
|
50676
|
-
else if (currentFlagName) {
|
|
50677
|
-
parsedArgv[currentFlagName] = splitArg;
|
|
50678
|
-
currentFlagName = "";
|
|
50745
|
+
async tables(_database, schema) {
|
|
50746
|
+
const params = [];
|
|
50747
|
+
let schemaClause = "";
|
|
50748
|
+
if (schema) {
|
|
50749
|
+
schemaClause = "and table_schema = ?";
|
|
50750
|
+
params.push(schema);
|
|
50679
50751
|
}
|
|
50752
|
+
const queryResult = await this.execute(`select table_name, table_schema
|
|
50753
|
+
from information_schema.tables
|
|
50754
|
+
where table_schema not in ('information_schema', 'mysql', 'performance_schema', 'sys')
|
|
50755
|
+
${schemaClause}`, { params, rowLimit: 10000, includeQueryInError: true });
|
|
50756
|
+
const targets = queryResult.rows.map(row => ({
|
|
50757
|
+
schema: row.table_schema,
|
|
50758
|
+
name: row.table_name
|
|
50759
|
+
}));
|
|
50760
|
+
return await Promise.all(targets.map(target => this.table(target)));
|
|
50680
50761
|
}
|
|
50681
|
-
|
|
50682
|
-
|
|
50683
|
-
|
|
50684
|
-
|
|
50685
|
-
|
|
50686
|
-
|
|
50687
|
-
|
|
50762
|
+
async search(searchText, options = { limit: 1000 }) {
|
|
50763
|
+
const results = await this.execute(`select tables.table_schema as table_schema, tables.table_name as table_name
|
|
50764
|
+
from information_schema.tables as tables
|
|
50765
|
+
left join information_schema.columns columns
|
|
50766
|
+
on tables.table_schema = columns.table_schema
|
|
50767
|
+
and tables.table_name = columns.table_name
|
|
50768
|
+
where tables.table_schema like ?
|
|
50769
|
+
or tables.table_name like ?
|
|
50770
|
+
or columns.column_name like ?
|
|
50771
|
+
group by 1, 2`, {
|
|
50772
|
+
params: [`%${searchText}%`, `%${searchText}%`, `%${searchText}%`],
|
|
50773
|
+
rowLimit: options.limit
|
|
50774
|
+
});
|
|
50775
|
+
return await Promise.all(results.rows.map(row => this.table({
|
|
50776
|
+
schema: row.table_schema,
|
|
50777
|
+
name: row.table_name
|
|
50778
|
+
})));
|
|
50688
50779
|
}
|
|
50689
|
-
|
|
50690
|
-
|
|
50691
|
-
|
|
50780
|
+
async table(target) {
|
|
50781
|
+
var _a, _b, _c;
|
|
50782
|
+
const params = [target.schema, target.name];
|
|
50783
|
+
const [tableResults, columnResults] = await Promise.all([
|
|
50784
|
+
this.execute(`select table_type, table_comment from information_schema.tables
|
|
50785
|
+
where table_schema = ? and table_name = ?`, { params, includeQueryInError: true }),
|
|
50786
|
+
this.execute(`select column_name, data_type, ordinal_position, column_comment
|
|
50787
|
+
from information_schema.columns
|
|
50788
|
+
where table_schema = ? and table_name = ?
|
|
50789
|
+
order by ordinal_position`, { params, includeQueryInError: true })
|
|
50790
|
+
]);
|
|
50791
|
+
if (tableResults.rows.length === 0) {
|
|
50792
|
+
return null;
|
|
50692
50793
|
}
|
|
50693
|
-
const
|
|
50694
|
-
|
|
50695
|
-
|
|
50794
|
+
const tableType = String((_a = tableResults.rows[0].table_type) !== null && _a !== void 0 ? _a : tableResults.rows[0].TABLE_TYPE).toUpperCase();
|
|
50795
|
+
const tableComment = String((_c = (_b = tableResults.rows[0].table_comment) !== null && _b !== void 0 ? _b : tableResults.rows[0].TABLE_COMMENT) !== null && _c !== void 0 ? _c : "");
|
|
50796
|
+
return sqlanvil.TableMetadata.create({
|
|
50797
|
+
target,
|
|
50798
|
+
type: tableType === "VIEW" ? sqlanvil.TableMetadata.Type.VIEW : sqlanvil.TableMetadata.Type.TABLE,
|
|
50799
|
+
description: tableComment || undefined,
|
|
50800
|
+
fields: columnResults.rows.map(row => {
|
|
50801
|
+
var _a, _b, _c, _d;
|
|
50802
|
+
const comment = String((_b = (_a = row.column_comment) !== null && _a !== void 0 ? _a : row.COLUMN_COMMENT) !== null && _b !== void 0 ? _b : "");
|
|
50803
|
+
return sqlanvil.Field.create({
|
|
50804
|
+
name: ((_c = row.column_name) !== null && _c !== void 0 ? _c : row.COLUMN_NAME),
|
|
50805
|
+
primitive: convertFieldType(((_d = row.data_type) !== null && _d !== void 0 ? _d : row.DATA_TYPE)),
|
|
50806
|
+
description: comment || undefined
|
|
50807
|
+
});
|
|
50808
|
+
})
|
|
50809
|
+
});
|
|
50810
|
+
}
|
|
50811
|
+
async deleteTable(target) {
|
|
50812
|
+
const metadata = await this.table(target);
|
|
50813
|
+
if (!metadata) {
|
|
50814
|
+
return;
|
|
50696
50815
|
}
|
|
50697
|
-
|
|
50816
|
+
const kind = metadata.type === sqlanvil.TableMetadata.Type.VIEW ? "view" : "table";
|
|
50817
|
+
await this.execute(`drop ${kind} if exists \`${target.schema}\`.\`${target.name}\``, {
|
|
50818
|
+
includeQueryInError: true
|
|
50819
|
+
});
|
|
50698
50820
|
}
|
|
50699
|
-
|
|
50700
|
-
|
|
50701
|
-
|
|
50702
|
-
|
|
50703
|
-
|
|
50821
|
+
async schemas(_database) {
|
|
50822
|
+
const result = await this.execute(`select schema_name from information_schema.schemata`, {
|
|
50823
|
+
includeQueryInError: true
|
|
50824
|
+
});
|
|
50825
|
+
return result.rows
|
|
50826
|
+
.map(row => { var _a; return ((_a = row.schema_name) !== null && _a !== void 0 ? _a : row.SCHEMA_NAME); })
|
|
50827
|
+
.filter(name => !INTERNAL_SCHEMAS.has(name));
|
|
50704
50828
|
}
|
|
50705
|
-
|
|
50706
|
-
|
|
50829
|
+
async createSchema(_database, schema) {
|
|
50830
|
+
await this.execute(`create database if not exists \`${schema}\``, {
|
|
50831
|
+
includeQueryInError: true
|
|
50832
|
+
});
|
|
50707
50833
|
}
|
|
50708
|
-
|
|
50709
|
-
|
|
50710
|
-
|
|
50711
|
-
|
|
50712
|
-
|
|
50834
|
+
async setMetadata(action) {
|
|
50835
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
50836
|
+
const { target, actionDescriptor } = action;
|
|
50837
|
+
const actualMetadata = await this.table(target);
|
|
50838
|
+
if (!actualMetadata) {
|
|
50839
|
+
return;
|
|
50840
|
+
}
|
|
50841
|
+
if (actualMetadata.type === sqlanvil.TableMetadata.Type.VIEW) {
|
|
50842
|
+
return;
|
|
50843
|
+
}
|
|
50844
|
+
const resolved = `\`${target.schema}\`.\`${target.name}\``;
|
|
50845
|
+
if (actionDescriptor === null || actionDescriptor === void 0 ? void 0 : actionDescriptor.description) {
|
|
50846
|
+
await this.execute(`alter table ${resolved} comment = '${escapeMysqlString(actionDescriptor.description)}'`, { includeQueryInError: true });
|
|
50847
|
+
}
|
|
50848
|
+
const columnComments = ((actionDescriptor === null || actionDescriptor === void 0 ? void 0 : actionDescriptor.columns) || []).filter(column => {
|
|
50849
|
+
var _a;
|
|
50850
|
+
return ((_a = column.path) === null || _a === void 0 ? void 0 : _a.length) === 1 &&
|
|
50851
|
+
column.description != null &&
|
|
50852
|
+
actualMetadata.fields.some(f => f.name === column.path[0]);
|
|
50853
|
+
});
|
|
50854
|
+
if (columnComments.length === 0) {
|
|
50855
|
+
return;
|
|
50856
|
+
}
|
|
50857
|
+
const defResult = await this.execute(`select column_name, column_type, is_nullable, column_default, extra,
|
|
50858
|
+
collation_name, generation_expression
|
|
50859
|
+
from information_schema.columns
|
|
50860
|
+
where table_schema = ? and table_name = ?`, { params: [target.schema, target.name], includeQueryInError: true });
|
|
50861
|
+
const defByName = new Map();
|
|
50862
|
+
defResult.rows.forEach(row => { var _a; return defByName.set(String((_a = row.column_name) !== null && _a !== void 0 ? _a : row.COLUMN_NAME), row); });
|
|
50863
|
+
for (const column of columnComments) {
|
|
50864
|
+
const name = column.path[0];
|
|
50865
|
+
const row = defByName.get(name);
|
|
50866
|
+
if (!row) {
|
|
50867
|
+
continue;
|
|
50868
|
+
}
|
|
50869
|
+
const def = reconstructColumnDef({
|
|
50870
|
+
columnType: String((_a = row.column_type) !== null && _a !== void 0 ? _a : row.COLUMN_TYPE),
|
|
50871
|
+
isNullable: String((_b = row.is_nullable) !== null && _b !== void 0 ? _b : row.IS_NULLABLE),
|
|
50872
|
+
columnDefault: ((_d = (_c = row.column_default) !== null && _c !== void 0 ? _c : row.COLUMN_DEFAULT) !== null && _d !== void 0 ? _d : null) === null
|
|
50873
|
+
? null
|
|
50874
|
+
: String((_e = row.column_default) !== null && _e !== void 0 ? _e : row.COLUMN_DEFAULT),
|
|
50875
|
+
extra: String((_g = (_f = row.extra) !== null && _f !== void 0 ? _f : row.EXTRA) !== null && _g !== void 0 ? _g : ""),
|
|
50876
|
+
collationName: ((_j = (_h = row.collation_name) !== null && _h !== void 0 ? _h : row.COLLATION_NAME) !== null && _j !== void 0 ? _j : null) === null
|
|
50877
|
+
? null
|
|
50878
|
+
: String((_k = row.collation_name) !== null && _k !== void 0 ? _k : row.COLLATION_NAME),
|
|
50879
|
+
generationExpression: ((_m = (_l = row.generation_expression) !== null && _l !== void 0 ? _l : row.GENERATION_EXPRESSION) !== null && _m !== void 0 ? _m : null) === null
|
|
50880
|
+
? null
|
|
50881
|
+
: String((_o = row.generation_expression) !== null && _o !== void 0 ? _o : row.GENERATION_EXPRESSION)
|
|
50882
|
+
});
|
|
50883
|
+
await this.execute(`alter table ${resolved} modify column \`${name}\` ${def} comment '${escapeMysqlString(column.description)}'`, { includeQueryInError: true });
|
|
50884
|
+
}
|
|
50713
50885
|
}
|
|
50714
|
-
|
|
50715
|
-
|
|
50716
|
-
.split(",")
|
|
50717
|
-
.filter(singleValue => singleValue.length > 0)
|
|
50718
|
-
.map(this.parser));
|
|
50886
|
+
async close() {
|
|
50887
|
+
await this.queryExecutor.close();
|
|
50719
50888
|
}
|
|
50720
50889
|
}
|
|
50721
50890
|
|
|
50722
|
-
const
|
|
50723
|
-
const
|
|
50724
|
-
|
|
50725
|
-
|
|
50726
|
-
|
|
50727
|
-
|
|
50728
|
-
|
|
50729
|
-
|
|
50730
|
-
}
|
|
50731
|
-
class Runner {
|
|
50732
|
-
constructor(dbadapter, graph, executionOptions = {}, partiallyExecutedRunResult = {}, runnerNotificationPeriodMillis = flags.runnerNotificationPeriodMillis.get()) {
|
|
50733
|
-
var _a;
|
|
50734
|
-
this.dbadapter = dbadapter;
|
|
50735
|
-
this.graph = graph;
|
|
50736
|
-
this.executionOptions = executionOptions;
|
|
50737
|
-
this.runnerNotificationPeriodMillis = runnerNotificationPeriodMillis;
|
|
50738
|
-
this.changeListeners = [];
|
|
50739
|
-
this.lastNotificationTimestampMillis = 0;
|
|
50740
|
-
this.stopped = false;
|
|
50741
|
-
this.cancelled = false;
|
|
50742
|
-
this.timedOut = false;
|
|
50743
|
-
this.allActionTargets = new Set(graph.actions.map(action => targetStringifier.stringify(action.target)));
|
|
50744
|
-
this.runResult = Object.assign({ actions: [] }, partiallyExecutedRunResult);
|
|
50745
|
-
this.warehouseStateByTarget = new Map();
|
|
50746
|
-
(_a = graph.warehouseState.tables) === null || _a === void 0 ? void 0 : _a.forEach(tableMetadata => this.warehouseStateByTarget.set(targetStringifier.stringify(tableMetadata.target), tableMetadata));
|
|
50747
|
-
this.executedActionTargets = new Set(this.runResult.actions
|
|
50748
|
-
.filter(action => action.status !== sqlanvil.ActionResult.ExecutionStatus.RUNNING)
|
|
50749
|
-
.map(action => targetStringifier.stringify(action.target)));
|
|
50750
|
-
this.successfullyExecutedActionTargets = new Set(this.runResult.actions
|
|
50751
|
-
.filter(isSuccessfulAction)
|
|
50752
|
-
.map(action => targetStringifier.stringify(action.target)));
|
|
50753
|
-
this.pendingActions = graph.actions.filter(action => !this.executedActionTargets.has(targetStringifier.stringify(action.target)));
|
|
50754
|
-
this.eEmitter = new EventEmitter__default["default"]();
|
|
50755
|
-
this.eEmitter.setMaxListeners(0);
|
|
50891
|
+
const DEFAULT_ROW_CAP = 1000000;
|
|
50892
|
+
const DEFAULT_BYTE_CAP = 512 * 1024 * 1024;
|
|
50893
|
+
const backquote = (id) => "`" + String(id).replace(/`/g, "``") + "`";
|
|
50894
|
+
async function runMysqlExtract(args) {
|
|
50895
|
+
var _a, _b;
|
|
50896
|
+
const { spec, target, connectionCredentials } = args;
|
|
50897
|
+
if (spec.platform !== "mysql") {
|
|
50898
|
+
throw new Error(`mysql runner-extract got a "${spec.platform}" source.`);
|
|
50756
50899
|
}
|
|
50757
|
-
|
|
50758
|
-
|
|
50759
|
-
|
|
50900
|
+
const cols = Object.keys(spec.columnTypes || {});
|
|
50901
|
+
if (cols.length === 0) {
|
|
50902
|
+
throw new Error(`Extract "${target.schema}.${target.name}" has no columnTypes; run \`sqlanvil introspect\`.`);
|
|
50760
50903
|
}
|
|
50761
|
-
|
|
50762
|
-
|
|
50763
|
-
if (!!this.executionTask) {
|
|
50764
|
-
throw new Error("Executor already started.");
|
|
50765
|
-
}
|
|
50766
|
-
this.executionTask = this.executeGraph();
|
|
50767
|
-
if (!!this.graph.runConfig && !!this.graph.runConfig.timeoutMillis) {
|
|
50768
|
-
const now = Date.now();
|
|
50769
|
-
const runStartMillis = ((_c = (_b = (_a = this.runResult.timing) === null || _a === void 0 ? void 0 : _a.startTimeMillis) === null || _b === void 0 ? void 0 : _b.toNumber) === null || _c === void 0 ? void 0 : _c.call(_b)) || now;
|
|
50770
|
-
const elapsedTimeMillis = now - runStartMillis;
|
|
50771
|
-
const timeoutMillis = this.graph.runConfig.timeoutMillis - elapsedTimeMillis;
|
|
50772
|
-
this.timeout = setTimeout(() => {
|
|
50773
|
-
this.timedOut = true;
|
|
50774
|
-
this.cancel();
|
|
50775
|
-
}, timeoutMillis);
|
|
50776
|
-
}
|
|
50777
|
-
return this;
|
|
50904
|
+
if (!args.pg) {
|
|
50905
|
+
throw new Error("runner-extract needs the write-warehouse connection (warehouseConnection).");
|
|
50778
50906
|
}
|
|
50779
|
-
|
|
50780
|
-
|
|
50907
|
+
const conn = (connectionCredentials === null || connectionCredentials === void 0 ? void 0 : connectionCredentials[spec.connectionName]) || {};
|
|
50908
|
+
const database = spec.database || conn.database;
|
|
50909
|
+
if (!database) {
|
|
50910
|
+
throw new Error(`Extract "${target.schema}.${target.name}": no source database — set \`database:\` on ` +
|
|
50911
|
+
`connection "${spec.connectionName}" (workflow_settings.yaml) or \`schema:\` on the declaration.`);
|
|
50781
50912
|
}
|
|
50782
|
-
|
|
50783
|
-
|
|
50784
|
-
|
|
50913
|
+
if (!conn.host && !conn.user) {
|
|
50914
|
+
throw new Error(`No credentials for connection "${spec.connectionName}" — add it to .df-credentials.json's ` +
|
|
50915
|
+
`"connections" map ({ host, port, user, password }).`);
|
|
50785
50916
|
}
|
|
50786
|
-
|
|
50917
|
+
const rowCap = (_a = args.rowCap) !== null && _a !== void 0 ? _a : DEFAULT_ROW_CAP;
|
|
50918
|
+
const byteCap = (_b = args.byteCap) !== null && _b !== void 0 ? _b : DEFAULT_BYTE_CAP;
|
|
50919
|
+
const my = await MySqlDbAdapter.create(sqlanvil.MysqlConnection.create({
|
|
50920
|
+
host: conn.host,
|
|
50921
|
+
port: conn.port || 3306,
|
|
50922
|
+
user: conn.user,
|
|
50923
|
+
password: conn.password,
|
|
50924
|
+
database,
|
|
50925
|
+
sslMode: conn.sslMode
|
|
50926
|
+
}), { disableSslForTestsOnly: args.disableSslForTestsOnly });
|
|
50927
|
+
let rows;
|
|
50928
|
+
try {
|
|
50929
|
+
const colList = cols.map(backquote).join(", ");
|
|
50930
|
+
const source = `${backquote(database)}.${backquote(spec.sourceName)}`;
|
|
50931
|
+
({ rows } = await my.execute(`select ${colList} from ${source} limit ${rowCap + 1}`, {
|
|
50932
|
+
rowLimit: rowCap + 1,
|
|
50933
|
+
byteLimit: byteCap
|
|
50934
|
+
}));
|
|
50935
|
+
}
|
|
50936
|
+
finally {
|
|
50937
|
+
await my.close().catch(() => undefined);
|
|
50938
|
+
}
|
|
50939
|
+
if (rows.length > rowCap) {
|
|
50940
|
+
rows = rows.slice(0, rowCap);
|
|
50941
|
+
console.warn(`runner-extract: ${target.schema}.${target.name} truncated at ${rowCap} rows (source is larger).`);
|
|
50942
|
+
}
|
|
50943
|
+
await loadRowsIntoPostgres({
|
|
50944
|
+
pg: args.pg,
|
|
50945
|
+
target,
|
|
50946
|
+
columnTypes: spec.columnTypes,
|
|
50947
|
+
rows,
|
|
50948
|
+
disableSslForTestsOnly: args.disableSslForTestsOnly
|
|
50949
|
+
});
|
|
50950
|
+
return { rowCount: rows.length };
|
|
50951
|
+
}
|
|
50952
|
+
|
|
50953
|
+
function loadDuckdb() {
|
|
50954
|
+
try {
|
|
50955
|
+
return nativeRequire("@duckdb/node-api");
|
|
50956
|
+
}
|
|
50957
|
+
catch (e) {
|
|
50958
|
+
throw new Error(`This feature requires the optional "@duckdb/node-api" dependency, which failed to load: ` +
|
|
50959
|
+
`${e.message}`);
|
|
50960
|
+
}
|
|
50961
|
+
}
|
|
50962
|
+
async function runAsync(conn, sql) {
|
|
50963
|
+
await conn.run(sql);
|
|
50964
|
+
}
|
|
50965
|
+
async function allAsync(conn, sql) {
|
|
50966
|
+
const reader = await conn.runAndReadAll(sql);
|
|
50967
|
+
return reader.getRowObjects();
|
|
50968
|
+
}
|
|
50969
|
+
async function withDuckdb(fn, dbPath = ":memory:") {
|
|
50970
|
+
const { DuckDBInstance } = loadDuckdb();
|
|
50971
|
+
const instance = await DuckDBInstance.create(dbPath);
|
|
50972
|
+
const conn = await instance.connect();
|
|
50973
|
+
const done = () => {
|
|
50974
|
+
var _a, _b;
|
|
50787
50975
|
try {
|
|
50788
|
-
|
|
50976
|
+
(_a = conn.closeSync) === null || _a === void 0 ? void 0 : _a.call(conn);
|
|
50789
50977
|
}
|
|
50790
|
-
|
|
50791
|
-
if (!!this.timeout) {
|
|
50792
|
-
clearTimeout(this.timeout);
|
|
50793
|
-
}
|
|
50978
|
+
catch (e) {
|
|
50794
50979
|
}
|
|
50795
|
-
|
|
50796
|
-
|
|
50797
|
-
if (Date.now() - this.runnerNotificationPeriodMillis < this.lastNotificationTimestampMillis) {
|
|
50798
|
-
return;
|
|
50980
|
+
try {
|
|
50981
|
+
(_b = instance.closeSync) === null || _b === void 0 ? void 0 : _b.call(instance);
|
|
50799
50982
|
}
|
|
50800
|
-
|
|
50801
|
-
this.lastNotificationTimestampMillis = Date.now();
|
|
50802
|
-
this.changeListeners.forEach(listener => listener(runResultClone));
|
|
50803
|
-
}
|
|
50804
|
-
async executeGraph() {
|
|
50805
|
-
const timer = Timer.start(this.runResult.timing);
|
|
50806
|
-
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.RUNNING;
|
|
50807
|
-
this.runResult.timing = timer.current();
|
|
50808
|
-
this.notifyListeners();
|
|
50809
|
-
if (this.runResult.actions.length === 0) {
|
|
50810
|
-
await this.prepareAllSchemas();
|
|
50983
|
+
catch (e) {
|
|
50811
50984
|
}
|
|
50812
|
-
|
|
50813
|
-
|
|
50814
|
-
|
|
50985
|
+
};
|
|
50986
|
+
try {
|
|
50987
|
+
if (!process.env.HOME) {
|
|
50988
|
+
await runAsync(conn, `SET home_directory='${os__namespace.tmpdir()}'`);
|
|
50815
50989
|
}
|
|
50816
|
-
|
|
50817
|
-
|
|
50818
|
-
|
|
50819
|
-
|
|
50990
|
+
const result = await fn(conn);
|
|
50991
|
+
done();
|
|
50992
|
+
return result;
|
|
50993
|
+
}
|
|
50994
|
+
catch (e) {
|
|
50995
|
+
done();
|
|
50996
|
+
throw e;
|
|
50997
|
+
}
|
|
50998
|
+
}
|
|
50999
|
+
|
|
51000
|
+
const PG_ATTACH_ALIAS = "pg";
|
|
51001
|
+
const SECRET_NAME = "sa_export";
|
|
51002
|
+
function buildAttachSql(pg, opts = {}) {
|
|
51003
|
+
const readOnly = opts.readOnly !== false;
|
|
51004
|
+
const dsn = [
|
|
51005
|
+
pg.host && `host=${pg.host}`,
|
|
51006
|
+
pg.port && `port=${pg.port}`,
|
|
51007
|
+
pg.database && `dbname=${pg.database}`,
|
|
51008
|
+
pg.user && `user=${pg.user}`,
|
|
51009
|
+
pg.password && `password=${pg.password}`
|
|
51010
|
+
]
|
|
51011
|
+
.filter(Boolean)
|
|
51012
|
+
.join(" ");
|
|
51013
|
+
const mode = readOnly ? ", READ_ONLY" : "";
|
|
51014
|
+
return `ATTACH '${dsn}' AS ${PG_ATTACH_ALIAS} (TYPE postgres${mode})`;
|
|
51015
|
+
}
|
|
51016
|
+
function buildSecretSql(scheme, creds) {
|
|
51017
|
+
if (scheme === "s3") {
|
|
51018
|
+
const fields = [
|
|
51019
|
+
"TYPE s3",
|
|
51020
|
+
creds.accessKeyId && `KEY_ID '${creds.accessKeyId}'`,
|
|
51021
|
+
creds.secretAccessKey && `SECRET '${creds.secretAccessKey}'`,
|
|
51022
|
+
creds.region && `REGION '${creds.region}'`,
|
|
51023
|
+
creds.endpoint && `ENDPOINT '${creds.endpoint}'`,
|
|
51024
|
+
creds.endpoint && `URL_STYLE 'path'`,
|
|
51025
|
+
creds.endpoint && `USE_SSL true`
|
|
51026
|
+
]
|
|
51027
|
+
.filter(Boolean)
|
|
51028
|
+
.join(", ");
|
|
51029
|
+
return `CREATE OR REPLACE SECRET ${SECRET_NAME} (${fields})`;
|
|
51030
|
+
}
|
|
51031
|
+
if (scheme === "gcs") {
|
|
51032
|
+
const fields = [
|
|
51033
|
+
"TYPE gcs",
|
|
51034
|
+
creds.keyId && `KEY_ID '${creds.keyId}'`,
|
|
51035
|
+
creds.secret && `SECRET '${creds.secret}'`
|
|
51036
|
+
]
|
|
51037
|
+
.filter(Boolean)
|
|
51038
|
+
.join(", ");
|
|
51039
|
+
return `CREATE OR REPLACE SECRET ${SECRET_NAME} (${fields})`;
|
|
51040
|
+
}
|
|
51041
|
+
return null;
|
|
51042
|
+
}
|
|
51043
|
+
function toCopyTarget(uri) {
|
|
51044
|
+
if (uri.startsWith("local://")) {
|
|
51045
|
+
return uri.slice("local://".length);
|
|
51046
|
+
}
|
|
51047
|
+
return uri;
|
|
51048
|
+
}
|
|
51049
|
+
function schemeOf(uri) {
|
|
51050
|
+
if (uri.startsWith("s3://")) {
|
|
51051
|
+
return "s3";
|
|
51052
|
+
}
|
|
51053
|
+
if (uri.startsWith("gs://") || uri.startsWith("gcs://")) {
|
|
51054
|
+
return "gcs";
|
|
51055
|
+
}
|
|
51056
|
+
return "local";
|
|
51057
|
+
}
|
|
51058
|
+
function buildCopySql(selectSql, uri, format, options = {}) {
|
|
51059
|
+
const target = toCopyTarget(uri);
|
|
51060
|
+
const fmt = (format || "").toLowerCase();
|
|
51061
|
+
const extraOptions = Object.entries(options || {}).map(([key, value]) => `${key.toUpperCase()} ${value}`);
|
|
51062
|
+
const optionList = [`FORMAT ${fmt}`, ...extraOptions].join(", ");
|
|
51063
|
+
return `COPY (SELECT * FROM postgres_query('${PG_ATTACH_ALIAS}', $sa$${selectSql}$sa$)) TO '${target}' (${optionList})`;
|
|
51064
|
+
}
|
|
51065
|
+
async function runDuckdbExport(args) {
|
|
51066
|
+
const { spec, selectSql, pg, storage, actionName } = args;
|
|
51067
|
+
const uri = resolveExportUri(spec, actionName, { wildcard: false });
|
|
51068
|
+
const scheme = schemeOf(uri);
|
|
51069
|
+
if (scheme !== "local" && !(storage === null || storage === void 0 ? void 0 : storage[scheme])) {
|
|
51070
|
+
throw new Error(`No "${scheme}" storage credentials found in .df-credentials.json (storage.${scheme}) for ` +
|
|
51071
|
+
`export to ${uri}.`);
|
|
51072
|
+
}
|
|
51073
|
+
return withDuckdb(async (conn) => {
|
|
51074
|
+
await runAsync(conn, "INSTALL postgres; LOAD postgres; INSTALL httpfs; LOAD httpfs;");
|
|
51075
|
+
await runAsync(conn, buildAttachSql(pg));
|
|
51076
|
+
if (scheme !== "local") {
|
|
51077
|
+
const secret = buildSecretSql(scheme, storage[scheme]);
|
|
51078
|
+
if (secret) {
|
|
51079
|
+
await runAsync(conn, secret);
|
|
51080
|
+
}
|
|
50820
51081
|
}
|
|
50821
|
-
|
|
50822
|
-
|
|
51082
|
+
await runAsync(conn, buildCopySql(selectSql, uri, spec.format, spec.options || {}));
|
|
51083
|
+
return { destination: toCopyTarget(uri) };
|
|
51084
|
+
});
|
|
51085
|
+
}
|
|
51086
|
+
|
|
51087
|
+
function readerForFormat(format) {
|
|
51088
|
+
switch ((format || "").toLowerCase()) {
|
|
51089
|
+
case "csv":
|
|
51090
|
+
return "read_csv_auto";
|
|
51091
|
+
case "json":
|
|
51092
|
+
return "read_json_auto";
|
|
51093
|
+
case "parquet":
|
|
51094
|
+
default:
|
|
51095
|
+
return "read_parquet";
|
|
51096
|
+
}
|
|
51097
|
+
}
|
|
51098
|
+
function importTargetSql(target) {
|
|
51099
|
+
return target.schema
|
|
51100
|
+
? `${PG_ATTACH_ALIAS}."${target.schema}"."${target.name}"`
|
|
51101
|
+
: `${PG_ATTACH_ALIAS}."${target.name}"`;
|
|
51102
|
+
}
|
|
51103
|
+
function buildImportSql(target, location, format, overwrite) {
|
|
51104
|
+
const reader = readerForFormat(format);
|
|
51105
|
+
const source = toCopyTarget(location);
|
|
51106
|
+
const dest = importTargetSql(target);
|
|
51107
|
+
const select = `SELECT * FROM ${reader}('${source}')`;
|
|
51108
|
+
if (overwrite) {
|
|
51109
|
+
return [`DROP TABLE IF EXISTS ${dest}`, `CREATE TABLE ${dest} AS ${select}`];
|
|
51110
|
+
}
|
|
51111
|
+
return [`INSERT INTO ${dest} ${select}`];
|
|
51112
|
+
}
|
|
51113
|
+
async function runDuckdbImport(args) {
|
|
51114
|
+
const { spec, target, pg, storage } = args;
|
|
51115
|
+
const location = spec.location || "";
|
|
51116
|
+
const scheme = schemeOf(location);
|
|
51117
|
+
if (scheme !== "local" && !(storage === null || storage === void 0 ? void 0 : storage[scheme])) {
|
|
51118
|
+
throw new Error(`No "${scheme}" storage credentials found in .df-credentials.json (storage.${scheme}) for ` +
|
|
51119
|
+
`import from ${location}.`);
|
|
51120
|
+
}
|
|
51121
|
+
return withDuckdb(async (conn) => {
|
|
51122
|
+
await runAsync(conn, "INSTALL postgres; LOAD postgres; INSTALL httpfs; LOAD httpfs;");
|
|
51123
|
+
await runAsync(conn, buildAttachSql(pg, { readOnly: false }));
|
|
51124
|
+
if (scheme !== "local") {
|
|
51125
|
+
const secret = buildSecretSql(scheme, storage[scheme]);
|
|
51126
|
+
if (secret) {
|
|
51127
|
+
await runAsync(conn, secret);
|
|
51128
|
+
}
|
|
50823
51129
|
}
|
|
50824
|
-
|
|
50825
|
-
|
|
51130
|
+
for (const statement of buildImportSql(target, location, spec.format, spec.overwrite)) {
|
|
51131
|
+
await runAsync(conn, statement);
|
|
50826
51132
|
}
|
|
50827
|
-
return
|
|
50828
|
-
}
|
|
50829
|
-
|
|
50830
|
-
|
|
50831
|
-
|
|
50832
|
-
|
|
50833
|
-
|
|
50834
|
-
|
|
50835
|
-
|
|
50836
|
-
databaseSchemas.set(trueDatabase, new Set());
|
|
51133
|
+
return { source: toCopyTarget(location) };
|
|
51134
|
+
});
|
|
51135
|
+
}
|
|
51136
|
+
|
|
51137
|
+
class Flags {
|
|
51138
|
+
static boolean(name, defaultValue = false) {
|
|
51139
|
+
return new SingleValueFlag(name, defaultValue, (stringValue) => {
|
|
51140
|
+
if (stringValue === "" || stringValue === "true") {
|
|
51141
|
+
return true;
|
|
50837
51142
|
}
|
|
50838
|
-
|
|
51143
|
+
if (stringValue === "false") {
|
|
51144
|
+
return false;
|
|
51145
|
+
}
|
|
51146
|
+
throw Flags.invalidFlagValueError(name);
|
|
50839
51147
|
});
|
|
50840
|
-
await Promise.all(Array.from(databaseSchemas.entries()).map(async ([database, schemas]) => {
|
|
50841
|
-
const existingSchemas = new Set(await this.dbadapter.schemas(database));
|
|
50842
|
-
await Promise.all(Array.from(schemas)
|
|
50843
|
-
.filter(schema => !existingSchemas.has(schema))
|
|
50844
|
-
.map(schema => this.dbadapter.createSchema(database, schema)));
|
|
50845
|
-
}));
|
|
50846
51148
|
}
|
|
50847
|
-
|
|
50848
|
-
|
|
50849
|
-
|
|
51149
|
+
static string(name, defaultValue = "") {
|
|
51150
|
+
return new SingleValueFlag(name, defaultValue, (stringValue) => stringValue);
|
|
51151
|
+
}
|
|
51152
|
+
static number(name, defaultValue = 0) {
|
|
51153
|
+
return new SingleValueFlag(name, defaultValue, (stringValue) => parseFloat(stringValue));
|
|
51154
|
+
}
|
|
51155
|
+
static long(name, defaultValue = Long__default["default"].ZERO) {
|
|
51156
|
+
return new SingleValueFlag(name, defaultValue, Long__default["default"].fromString);
|
|
51157
|
+
}
|
|
51158
|
+
static stringSet(name, defaultValue = new Set()) {
|
|
51159
|
+
return new SetFlag(name, defaultValue, (singleValue) => singleValue);
|
|
51160
|
+
}
|
|
51161
|
+
static getRawFlagValue(flagName) {
|
|
51162
|
+
return Flags.parsedArgv[flagName];
|
|
51163
|
+
}
|
|
51164
|
+
static setRawFlagValueForTesting(flagName, value) {
|
|
51165
|
+
Flags.parsedArgv[flagName] = value;
|
|
51166
|
+
}
|
|
51167
|
+
static invalidFlagValueError(flagName) {
|
|
51168
|
+
return new Error(`Invalid flag value: ${Flags.getRawFlagValue(flagName)} [${flagName}]`);
|
|
51169
|
+
}
|
|
51170
|
+
}
|
|
51171
|
+
Flags.parsedArgv = parseArgv(process.argv);
|
|
51172
|
+
function parseArgv(argv) {
|
|
51173
|
+
const parsedArgv = {};
|
|
51174
|
+
const splitArgv = [];
|
|
51175
|
+
for (let arg of argv) {
|
|
51176
|
+
if (arg.startsWith("--no-")) {
|
|
51177
|
+
arg = `--${arg.slice(5)}=false`;
|
|
50850
51178
|
}
|
|
50851
|
-
if (
|
|
50852
|
-
|
|
50853
|
-
|
|
50854
|
-
allPendingActions.forEach(pendingAction => this.runResult.actions.push({
|
|
50855
|
-
target: pendingAction.target,
|
|
50856
|
-
status: sqlanvil.ActionResult.ExecutionStatus.SKIPPED,
|
|
50857
|
-
tasks: pendingAction.tasks.map(() => ({
|
|
50858
|
-
status: sqlanvil.TaskResult.ExecutionStatus.SKIPPED
|
|
50859
|
-
}))
|
|
50860
|
-
}));
|
|
50861
|
-
this.notifyListeners();
|
|
50862
|
-
return;
|
|
51179
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
51180
|
+
splitArgv.push(arg.slice(0, arg.indexOf("=")));
|
|
51181
|
+
splitArgv.push(arg.slice(arg.indexOf("=") + 1));
|
|
50863
51182
|
}
|
|
50864
|
-
|
|
50865
|
-
|
|
50866
|
-
|
|
50867
|
-
|
|
50868
|
-
|
|
50869
|
-
|
|
51183
|
+
else {
|
|
51184
|
+
splitArgv.push(arg);
|
|
51185
|
+
}
|
|
51186
|
+
}
|
|
51187
|
+
let currentFlagName = "";
|
|
51188
|
+
for (const splitArg of splitArgv) {
|
|
51189
|
+
if (splitArg.startsWith("--")) {
|
|
51190
|
+
currentFlagName = splitArg.slice(2);
|
|
51191
|
+
parsedArgv[currentFlagName] = "";
|
|
51192
|
+
}
|
|
51193
|
+
else if (currentFlagName) {
|
|
51194
|
+
parsedArgv[currentFlagName] = splitArg;
|
|
51195
|
+
currentFlagName = "";
|
|
51196
|
+
}
|
|
51197
|
+
}
|
|
51198
|
+
return parsedArgv;
|
|
51199
|
+
}
|
|
51200
|
+
class AbstractFlag {
|
|
51201
|
+
constructor(name, defaultValue) {
|
|
51202
|
+
this.name = name;
|
|
51203
|
+
this.defaultValue = defaultValue;
|
|
51204
|
+
this.parsed = {};
|
|
51205
|
+
}
|
|
51206
|
+
get() {
|
|
51207
|
+
if (Flags.getRawFlagValue(this.name) === undefined) {
|
|
51208
|
+
return this.defaultValue;
|
|
51209
|
+
}
|
|
51210
|
+
const stringValue = Flags.getRawFlagValue(this.name);
|
|
51211
|
+
if (!(stringValue in this.parsed)) {
|
|
51212
|
+
this.parsed[stringValue] = this.parse(stringValue);
|
|
51213
|
+
}
|
|
51214
|
+
return this.parsed[stringValue];
|
|
51215
|
+
}
|
|
51216
|
+
}
|
|
51217
|
+
class SingleValueFlag extends AbstractFlag {
|
|
51218
|
+
constructor(name, defaultValue, parser) {
|
|
51219
|
+
super(name, defaultValue);
|
|
51220
|
+
this.parser = parser;
|
|
51221
|
+
}
|
|
51222
|
+
parse(stringValue) {
|
|
51223
|
+
return this.parser(stringValue);
|
|
51224
|
+
}
|
|
51225
|
+
}
|
|
51226
|
+
class SetFlag extends AbstractFlag {
|
|
51227
|
+
constructor(name, defaultValue, parser) {
|
|
51228
|
+
super(name, defaultValue);
|
|
51229
|
+
this.parser = parser;
|
|
51230
|
+
}
|
|
51231
|
+
parse(stringValue) {
|
|
51232
|
+
return new Set(stringValue
|
|
51233
|
+
.split(",")
|
|
51234
|
+
.filter(singleValue => singleValue.length > 0)
|
|
51235
|
+
.map(this.parser));
|
|
51236
|
+
}
|
|
51237
|
+
}
|
|
51238
|
+
|
|
51239
|
+
const CANCEL_EVENT = "jobCancel";
|
|
51240
|
+
const flags = {
|
|
51241
|
+
runnerNotificationPeriodMillis: Flags.number("runner-notification-period-millis", 5000)
|
|
51242
|
+
};
|
|
51243
|
+
const isSuccessfulAction = (actionResult) => actionResult.status === sqlanvil.ActionResult.ExecutionStatus.SUCCESSFUL ||
|
|
51244
|
+
actionResult.status === sqlanvil.ActionResult.ExecutionStatus.DISABLED;
|
|
51245
|
+
function run(dbadapter, graph, executionOptions, partiallyExecutedRunResult = {}, runnerNotificationPeriodMillis = flags.runnerNotificationPeriodMillis.get()) {
|
|
51246
|
+
return new Runner(dbadapter, graph, executionOptions, partiallyExecutedRunResult, runnerNotificationPeriodMillis).execute();
|
|
51247
|
+
}
|
|
51248
|
+
class Runner {
|
|
51249
|
+
constructor(dbadapter, graph, executionOptions = {}, partiallyExecutedRunResult = {}, runnerNotificationPeriodMillis = flags.runnerNotificationPeriodMillis.get()) {
|
|
51250
|
+
var _a;
|
|
51251
|
+
this.dbadapter = dbadapter;
|
|
51252
|
+
this.graph = graph;
|
|
51253
|
+
this.executionOptions = executionOptions;
|
|
51254
|
+
this.runnerNotificationPeriodMillis = runnerNotificationPeriodMillis;
|
|
51255
|
+
this.changeListeners = [];
|
|
51256
|
+
this.lastNotificationTimestampMillis = 0;
|
|
51257
|
+
this.stopped = false;
|
|
51258
|
+
this.cancelled = false;
|
|
51259
|
+
this.timedOut = false;
|
|
51260
|
+
this.allActionTargets = new Set(graph.actions.map(action => targetStringifier.stringify(action.target)));
|
|
51261
|
+
this.runResult = Object.assign({ actions: [] }, partiallyExecutedRunResult);
|
|
51262
|
+
this.warehouseStateByTarget = new Map();
|
|
51263
|
+
(_a = graph.warehouseState.tables) === null || _a === void 0 ? void 0 : _a.forEach(tableMetadata => this.warehouseStateByTarget.set(targetStringifier.stringify(tableMetadata.target), tableMetadata));
|
|
51264
|
+
this.executedActionTargets = new Set(this.runResult.actions
|
|
51265
|
+
.filter(action => action.status !== sqlanvil.ActionResult.ExecutionStatus.RUNNING)
|
|
51266
|
+
.map(action => targetStringifier.stringify(action.target)));
|
|
51267
|
+
this.successfullyExecutedActionTargets = new Set(this.runResult.actions
|
|
51268
|
+
.filter(isSuccessfulAction)
|
|
51269
|
+
.map(action => targetStringifier.stringify(action.target)));
|
|
51270
|
+
this.pendingActions = graph.actions.filter(action => !this.executedActionTargets.has(targetStringifier.stringify(action.target)));
|
|
51271
|
+
this.eEmitter = new EventEmitter__default["default"]();
|
|
51272
|
+
this.eEmitter.setMaxListeners(0);
|
|
51273
|
+
}
|
|
51274
|
+
onChange(listener) {
|
|
51275
|
+
this.changeListeners.push(listener);
|
|
51276
|
+
return this;
|
|
51277
|
+
}
|
|
51278
|
+
execute() {
|
|
51279
|
+
var _a, _b, _c;
|
|
51280
|
+
if (!!this.executionTask) {
|
|
51281
|
+
throw new Error("Executor already started.");
|
|
51282
|
+
}
|
|
51283
|
+
this.executionTask = this.executeGraph();
|
|
51284
|
+
if (!!this.graph.runConfig && !!this.graph.runConfig.timeoutMillis) {
|
|
51285
|
+
const now = Date.now();
|
|
51286
|
+
const runStartMillis = ((_c = (_b = (_a = this.runResult.timing) === null || _a === void 0 ? void 0 : _a.startTimeMillis) === null || _b === void 0 ? void 0 : _b.toNumber) === null || _c === void 0 ? void 0 : _c.call(_b)) || now;
|
|
51287
|
+
const elapsedTimeMillis = now - runStartMillis;
|
|
51288
|
+
const timeoutMillis = this.graph.runConfig.timeoutMillis - elapsedTimeMillis;
|
|
51289
|
+
this.timeout = setTimeout(() => {
|
|
51290
|
+
this.timedOut = true;
|
|
51291
|
+
this.cancel();
|
|
51292
|
+
}, timeoutMillis);
|
|
51293
|
+
}
|
|
51294
|
+
return this;
|
|
51295
|
+
}
|
|
51296
|
+
stop() {
|
|
51297
|
+
this.stopped = true;
|
|
51298
|
+
}
|
|
51299
|
+
cancel() {
|
|
51300
|
+
this.cancelled = true;
|
|
51301
|
+
this.eEmitter.emit(CANCEL_EVENT, undefined, undefined);
|
|
51302
|
+
}
|
|
51303
|
+
async result() {
|
|
51304
|
+
try {
|
|
51305
|
+
return await this.executionTask;
|
|
51306
|
+
}
|
|
51307
|
+
finally {
|
|
51308
|
+
if (!!this.timeout) {
|
|
51309
|
+
clearTimeout(this.timeout);
|
|
51310
|
+
}
|
|
51311
|
+
}
|
|
51312
|
+
}
|
|
51313
|
+
notifyListeners() {
|
|
51314
|
+
if (Date.now() - this.runnerNotificationPeriodMillis < this.lastNotificationTimestampMillis) {
|
|
51315
|
+
return;
|
|
51316
|
+
}
|
|
51317
|
+
const runResultClone = deepClone(sqlanvil.RunResult, this.runResult);
|
|
51318
|
+
this.lastNotificationTimestampMillis = Date.now();
|
|
51319
|
+
this.changeListeners.forEach(listener => listener(runResultClone));
|
|
51320
|
+
}
|
|
51321
|
+
async executeGraph() {
|
|
51322
|
+
const timer = Timer.start(this.runResult.timing);
|
|
51323
|
+
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.RUNNING;
|
|
51324
|
+
this.runResult.timing = timer.current();
|
|
51325
|
+
this.notifyListeners();
|
|
51326
|
+
if (this.runResult.actions.length === 0) {
|
|
51327
|
+
await this.prepareAllSchemas();
|
|
51328
|
+
}
|
|
51329
|
+
await this.executeAllActionsReadyForExecution();
|
|
51330
|
+
if (this.stopped) {
|
|
51331
|
+
return this.runResult;
|
|
51332
|
+
}
|
|
51333
|
+
this.runResult.timing = timer.end();
|
|
51334
|
+
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.SUCCESSFUL;
|
|
51335
|
+
if (this.timedOut) {
|
|
51336
|
+
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.TIMED_OUT;
|
|
51337
|
+
}
|
|
51338
|
+
else if (this.cancelled) {
|
|
51339
|
+
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.CANCELLED;
|
|
51340
|
+
}
|
|
51341
|
+
else if (this.runResult.actions.some(action => action.status === sqlanvil.ActionResult.ExecutionStatus.FAILED)) {
|
|
51342
|
+
this.runResult.status = sqlanvil.RunResult.ExecutionStatus.FAILED;
|
|
51343
|
+
}
|
|
51344
|
+
return this.runResult;
|
|
51345
|
+
}
|
|
51346
|
+
async prepareAllSchemas() {
|
|
51347
|
+
const databaseSchemas = new Map();
|
|
51348
|
+
this.graph.actions
|
|
51349
|
+
.filter(action => !!action.target && !!action.target.schema)
|
|
51350
|
+
.forEach(({ target }) => {
|
|
51351
|
+
const trueDatabase = target.database || this.graph.projectConfig.defaultDatabase;
|
|
51352
|
+
if (!databaseSchemas.has(trueDatabase)) {
|
|
51353
|
+
databaseSchemas.set(trueDatabase, new Set());
|
|
51354
|
+
}
|
|
51355
|
+
databaseSchemas.get(trueDatabase).add(target.schema);
|
|
51356
|
+
});
|
|
51357
|
+
await Promise.all(Array.from(databaseSchemas.entries()).map(async ([database, schemas]) => {
|
|
51358
|
+
const existingSchemas = new Set(await this.dbadapter.schemas(database));
|
|
51359
|
+
await Promise.all(Array.from(schemas)
|
|
51360
|
+
.filter(schema => !existingSchemas.has(schema))
|
|
51361
|
+
.map(schema => this.dbadapter.createSchema(database, schema)));
|
|
51362
|
+
}));
|
|
51363
|
+
}
|
|
51364
|
+
async executeAllActionsReadyForExecution() {
|
|
51365
|
+
if (this.stopped) {
|
|
51366
|
+
return;
|
|
51367
|
+
}
|
|
51368
|
+
if (this.cancelled) {
|
|
51369
|
+
const allPendingActions = this.pendingActions;
|
|
51370
|
+
this.pendingActions = [];
|
|
51371
|
+
allPendingActions.forEach(pendingAction => this.runResult.actions.push({
|
|
51372
|
+
target: pendingAction.target,
|
|
51373
|
+
status: sqlanvil.ActionResult.ExecutionStatus.SKIPPED,
|
|
51374
|
+
tasks: pendingAction.tasks.map(() => ({
|
|
51375
|
+
status: sqlanvil.TaskResult.ExecutionStatus.SKIPPED
|
|
51376
|
+
}))
|
|
51377
|
+
}));
|
|
51378
|
+
this.notifyListeners();
|
|
51379
|
+
return;
|
|
51380
|
+
}
|
|
51381
|
+
const executableActions = [];
|
|
51382
|
+
const skippableActions = [];
|
|
51383
|
+
const stillPendingActions = [];
|
|
51384
|
+
for (const pendingAction of this.pendingActions) {
|
|
51385
|
+
if (pendingAction.dependencyTargets.every(dependency => !this.allActionTargets.has(targetStringifier.stringify(dependency)) ||
|
|
51386
|
+
this.successfullyExecutedActionTargets.has(targetStringifier.stringify(dependency)))) {
|
|
50870
51387
|
executableActions.push(pendingAction);
|
|
50871
51388
|
}
|
|
50872
51389
|
else if (pendingAction.dependencyTargets.every(dependency => !this.allActionTargets.has(targetStringifier.stringify(dependency)) ||
|
|
@@ -50987,7 +51504,7 @@ class Runner {
|
|
|
50987
51504
|
return actionResult;
|
|
50988
51505
|
}
|
|
50989
51506
|
async executeTask(client, task, parentAction, options, action) {
|
|
50990
|
-
var _a, _b, _c, _d, _e;
|
|
51507
|
+
var _a, _b, _c, _d, _e, _f;
|
|
50991
51508
|
const timer = Timer.start();
|
|
50992
51509
|
const taskResult = {
|
|
50993
51510
|
status: sqlanvil.TaskResult.ExecutionStatus.RUNNING,
|
|
@@ -51038,7 +51555,9 @@ class Runner {
|
|
|
51038
51555
|
}
|
|
51039
51556
|
else if (task.type === "extract") {
|
|
51040
51557
|
try {
|
|
51041
|
-
const extractor =
|
|
51558
|
+
const extractor = ((_c = action === null || action === void 0 ? void 0 : action.extract) === null || _c === void 0 ? void 0 : _c.platform) === "mysql"
|
|
51559
|
+
? this.executionOptions.mysqlExtract || runMysqlExtract
|
|
51560
|
+
: this.executionOptions.bigQueryExtract || runBigQueryExtract;
|
|
51042
51561
|
await extractor({
|
|
51043
51562
|
spec: action === null || action === void 0 ? void 0 : action.extract,
|
|
51044
51563
|
target: action === null || action === void 0 ? void 0 : action.target,
|
|
@@ -51066,7 +51585,7 @@ class Runner {
|
|
|
51066
51585
|
}), task.type === "operation" ? 1 : options.bigquery.actionRetryLimit + 1 || 1);
|
|
51067
51586
|
taskResult.metadata = metadata;
|
|
51068
51587
|
if (task.type === "assertion") {
|
|
51069
|
-
const rowCount = (
|
|
51588
|
+
const rowCount = (_d = rows[0]) === null || _d === void 0 ? void 0 : _d[Object.keys(rows[0])[0]];
|
|
51070
51589
|
if (rowCount > 0) {
|
|
51071
51590
|
throw new Error(`Assertion failed: query returned ${rowCount} row(s).`);
|
|
51072
51591
|
}
|
|
@@ -51078,1066 +51597,711 @@ class Runner {
|
|
|
51078
51597
|
? sqlanvil.TaskResult.ExecutionStatus.CANCELLED
|
|
51079
51598
|
: sqlanvil.TaskResult.ExecutionStatus.FAILED;
|
|
51080
51599
|
taskResult.errorMessage = `${this.graph.projectConfig.warehouse} error: ${e.message}`;
|
|
51081
|
-
if ((
|
|
51600
|
+
if ((_f = (_e = e.metadata) === null || _e === void 0 ? void 0 : _e.bigquery) === null || _f === void 0 ? void 0 : _f.jobId) {
|
|
51082
51601
|
taskResult.metadata = {
|
|
51083
51602
|
bigquery: {
|
|
51084
|
-
jobId: e.metadata.bigquery.jobId
|
|
51085
|
-
}
|
|
51086
|
-
};
|
|
51087
|
-
}
|
|
51088
|
-
}
|
|
51089
|
-
}
|
|
51090
|
-
taskResult.timing = timer.end();
|
|
51091
|
-
this.notifyListeners();
|
|
51092
|
-
return taskResult.status;
|
|
51093
|
-
}
|
|
51094
|
-
}
|
|
51095
|
-
class Timer {
|
|
51096
|
-
constructor(startTimeMillis) {
|
|
51097
|
-
this.startTimeMillis = startTimeMillis;
|
|
51098
|
-
}
|
|
51099
|
-
static start(existingTiming) {
|
|
51100
|
-
return new Timer((existingTiming === null || existingTiming === void 0 ? void 0 : existingTiming.startTimeMillis.toNumber()) || new Date().valueOf());
|
|
51101
|
-
}
|
|
51102
|
-
current() {
|
|
51103
|
-
return {
|
|
51104
|
-
startTimeMillis: Long__default["default"].fromNumber(this.startTimeMillis)
|
|
51105
|
-
};
|
|
51106
|
-
}
|
|
51107
|
-
end() {
|
|
51108
|
-
return {
|
|
51109
|
-
startTimeMillis: Long__default["default"].fromNumber(this.startTimeMillis),
|
|
51110
|
-
endTimeMillis: Long__default["default"].fromNumber(new Date().valueOf())
|
|
51111
|
-
};
|
|
51112
|
-
}
|
|
51113
|
-
}
|
|
51114
|
-
|
|
51115
|
-
async function test(dbadapter, tests) {
|
|
51116
|
-
return await Promise.all(tests.map(testCase => runTest(dbadapter, testCase)));
|
|
51117
|
-
}
|
|
51118
|
-
async function runTest(dbadapter, testCase) {
|
|
51119
|
-
const noLimit = {};
|
|
51120
|
-
let actualResults;
|
|
51121
|
-
let expectedResults;
|
|
51122
|
-
try {
|
|
51123
|
-
[actualResults, expectedResults] = await Promise.all([
|
|
51124
|
-
dbadapter.execute(testCase.testQuery, noLimit),
|
|
51125
|
-
dbadapter.execute(testCase.expectedOutputQuery, noLimit)
|
|
51126
|
-
]);
|
|
51127
|
-
}
|
|
51128
|
-
catch (e) {
|
|
51129
|
-
return {
|
|
51130
|
-
name: testCase.name,
|
|
51131
|
-
successful: false,
|
|
51132
|
-
messages: [`Error thrown: ${e.message}.`]
|
|
51133
|
-
};
|
|
51134
|
-
}
|
|
51135
|
-
if (actualResults.rows.length !== expectedResults.rows.length) {
|
|
51136
|
-
return {
|
|
51137
|
-
name: testCase.name,
|
|
51138
|
-
successful: false,
|
|
51139
|
-
messages: [
|
|
51140
|
-
`Expected ${expectedResults.rows.length} rows, but saw ${actualResults.rows.length} rows.`
|
|
51141
|
-
]
|
|
51142
|
-
};
|
|
51143
|
-
}
|
|
51144
|
-
if (actualResults.rows.length === 0) {
|
|
51145
|
-
return {
|
|
51146
|
-
name: testCase.name,
|
|
51147
|
-
successful: true
|
|
51148
|
-
};
|
|
51149
|
-
}
|
|
51150
|
-
const actualColumns = Object.keys(actualResults.rows[0]);
|
|
51151
|
-
const expectedColumns = Object.keys(expectedResults.rows[0]);
|
|
51152
|
-
if (actualColumns.length !== expectedColumns.length) {
|
|
51153
|
-
return {
|
|
51154
|
-
name: testCase.name,
|
|
51155
|
-
successful: false,
|
|
51156
|
-
messages: [`Expected columns "${expectedColumns}", but saw "${actualColumns}".`]
|
|
51157
|
-
};
|
|
51158
|
-
}
|
|
51159
|
-
for (const expectedColumn of expectedColumns) {
|
|
51160
|
-
if (!actualColumns.some(actualColumn => normalizeColumnName(actualColumn) === normalizeColumnName(expectedColumn))) {
|
|
51161
|
-
return {
|
|
51162
|
-
name: testCase.name,
|
|
51163
|
-
successful: false,
|
|
51164
|
-
messages: [`Expected columns "${expectedColumns}", but saw "${actualColumns}".`]
|
|
51165
|
-
};
|
|
51166
|
-
}
|
|
51167
|
-
}
|
|
51168
|
-
const rowMessages = [];
|
|
51169
|
-
for (let i = 0; i < actualResults.rows.length; i++) {
|
|
51170
|
-
const actualResultRow = normalizeRow(actualResults.rows[i]);
|
|
51171
|
-
const expectedResultRow = normalizeRow(expectedResults.rows[i]);
|
|
51172
|
-
for (const column of actualColumns) {
|
|
51173
|
-
const normalizedColumn = normalizeColumnName(column);
|
|
51174
|
-
const expectedValue = expectedResultRow[normalizedColumn];
|
|
51175
|
-
const actualValue = actualResultRow[normalizedColumn];
|
|
51176
|
-
if (expectedValue === null && actualValue !== null) {
|
|
51177
|
-
rowMessages.push(`For row ${i} and column "${column}": expected null, but saw "${actualValue}".`);
|
|
51178
|
-
break;
|
|
51179
|
-
}
|
|
51180
|
-
if (expectedValue !== null && actualValue === null) {
|
|
51181
|
-
rowMessages.push(`For row ${i} and column "${column}": expected "${expectedValue}", but saw null.`);
|
|
51182
|
-
break;
|
|
51183
|
-
}
|
|
51184
|
-
if (typeof expectedValue !== typeof actualValue) {
|
|
51185
|
-
rowMessages.push(`For row ${i} and column "${column}": expected type "${typeof expectedValue}", but saw type "${typeof actualValue}".`);
|
|
51186
|
-
break;
|
|
51187
|
-
}
|
|
51188
|
-
const comparableExpectedValue = typeof expectedValue === "object" ? JSON.stringify(expectedValue) : expectedValue;
|
|
51189
|
-
const comparableActualValue = typeof actualValue === "object" ? JSON.stringify(actualValue) : actualValue;
|
|
51190
|
-
if (comparableExpectedValue !== comparableActualValue) {
|
|
51191
|
-
rowMessages.push(`For row ${i} and column "${column}": expected "${comparableExpectedValue}", but saw "${comparableActualValue}".`);
|
|
51192
|
-
}
|
|
51193
|
-
}
|
|
51194
|
-
}
|
|
51195
|
-
if (rowMessages.length > 0) {
|
|
51196
|
-
return {
|
|
51197
|
-
name: testCase.name,
|
|
51198
|
-
successful: false,
|
|
51199
|
-
messages: rowMessages
|
|
51200
|
-
};
|
|
51201
|
-
}
|
|
51202
|
-
return {
|
|
51203
|
-
name: testCase.name,
|
|
51204
|
-
successful: true
|
|
51205
|
-
};
|
|
51206
|
-
}
|
|
51207
|
-
function normalizeColumnName(name) {
|
|
51208
|
-
return name.toUpperCase();
|
|
51209
|
-
}
|
|
51210
|
-
function normalizeRow(row) {
|
|
51211
|
-
const newRow = {};
|
|
51212
|
-
Object.keys(row).forEach(colName => {
|
|
51213
|
-
newRow[normalizeColumnName(colName)] = row[colName];
|
|
51214
|
-
});
|
|
51215
|
-
return newRow;
|
|
51216
|
-
}
|
|
51217
|
-
|
|
51218
|
-
function resolveEnvironment(projectDir, envName) {
|
|
51219
|
-
const settings = readConfigFromWorkflowSettings(projectDir);
|
|
51220
|
-
const environments = (settings === null || settings === void 0 ? void 0 : settings.environments) || {};
|
|
51221
|
-
const env = environments[envName];
|
|
51222
|
-
if (!env) {
|
|
51223
|
-
const available = Object.keys(environments);
|
|
51224
|
-
throw new Error(`Environment "${envName}" not found. ` +
|
|
51225
|
-
(available.length
|
|
51226
|
-
? `Available environments: ${available.join(", ")}.`
|
|
51227
|
-
: `No environments defined in workflow_settings.yaml.`));
|
|
51228
|
-
}
|
|
51229
|
-
const configOverride = {};
|
|
51230
|
-
if (env.schemaSuffix) {
|
|
51231
|
-
configOverride.schemaSuffix = env.schemaSuffix;
|
|
51232
|
-
}
|
|
51233
|
-
if (env.vars && Object.keys(env.vars).length > 0) {
|
|
51234
|
-
configOverride.vars = env.vars;
|
|
51235
|
-
}
|
|
51236
|
-
if (env.defaultDatabase) {
|
|
51237
|
-
configOverride.defaultDatabase = env.defaultDatabase;
|
|
51238
|
-
}
|
|
51239
|
-
if (env.defaultLocation) {
|
|
51240
|
-
configOverride.defaultLocation = env.defaultLocation;
|
|
51241
|
-
}
|
|
51242
|
-
return { configOverride, credentials: env.credentials || undefined };
|
|
51243
|
-
}
|
|
51244
|
-
function mergeProjectConfigOverride(envOverride, cliOverride) {
|
|
51245
|
-
const merged = Object.assign(Object.assign({}, envOverride), cliOverride);
|
|
51246
|
-
if (envOverride.vars || cliOverride.vars) {
|
|
51247
|
-
merged.vars = Object.assign(Object.assign({}, envOverride.vars), cliOverride.vars);
|
|
51248
|
-
}
|
|
51249
|
-
return merged;
|
|
51250
|
-
}
|
|
51251
|
-
function resolveCredentials(envCredentials, cliCredentials, defaultFilename) {
|
|
51252
|
-
if (cliCredentials && cliCredentials !== defaultFilename) {
|
|
51253
|
-
return cliCredentials;
|
|
51254
|
-
}
|
|
51255
|
-
return envCredentials || defaultFilename;
|
|
51256
|
-
}
|
|
51257
|
-
|
|
51258
|
-
function key(target) {
|
|
51259
|
-
return targetStringifier.stringify(target);
|
|
51260
|
-
}
|
|
51261
|
-
function enumName(enumObject, value) {
|
|
51262
|
-
if (value === undefined || value === null) {
|
|
51263
|
-
return "UNKNOWN";
|
|
51264
|
-
}
|
|
51265
|
-
const match = Object.keys(enumObject).find(k => enumObject[k] === value);
|
|
51266
|
-
return match || String(value);
|
|
51267
|
-
}
|
|
51268
|
-
function tableType$1(enumType) {
|
|
51269
|
-
switch (enumType) {
|
|
51270
|
-
case sqlanvil.TableType.VIEW:
|
|
51271
|
-
return "view";
|
|
51272
|
-
case sqlanvil.TableType.INCREMENTAL:
|
|
51273
|
-
return "incremental";
|
|
51274
|
-
default:
|
|
51275
|
-
return "table";
|
|
51276
|
-
}
|
|
51277
|
-
}
|
|
51278
|
-
function toMillis(value) {
|
|
51279
|
-
if (value === undefined || value === null) {
|
|
51280
|
-
return 0;
|
|
51281
|
-
}
|
|
51282
|
-
if (typeof value === "number") {
|
|
51283
|
-
return value;
|
|
51284
|
-
}
|
|
51285
|
-
if (typeof value.toNumber === "function") {
|
|
51286
|
-
return value.toNumber();
|
|
51287
|
-
}
|
|
51288
|
-
return Number(value) || 0;
|
|
51289
|
-
}
|
|
51290
|
-
function actionRow(action, type) {
|
|
51291
|
-
const descriptor = action.actionDescriptor || {};
|
|
51292
|
-
return {
|
|
51293
|
-
target_key: key(action.target),
|
|
51294
|
-
database: action.target.database || "",
|
|
51295
|
-
schema: action.target.schema || "",
|
|
51296
|
-
name: action.target.name || "",
|
|
51297
|
-
readable_name: targetAsReadableString(action.target),
|
|
51298
|
-
type,
|
|
51299
|
-
tags: JSON.stringify(action.tags || []),
|
|
51300
|
-
disabled: !!action.disabled,
|
|
51301
|
-
file_name: action.fileName || "",
|
|
51302
|
-
description: descriptor.description || ""
|
|
51303
|
-
};
|
|
51304
|
-
}
|
|
51305
|
-
function pushDeps(action, out) {
|
|
51306
|
-
for (const dep of action.dependencyTargets || []) {
|
|
51307
|
-
out.push({
|
|
51308
|
-
from_target_key: key(action.target),
|
|
51309
|
-
to_target_key: key(dep),
|
|
51310
|
-
from_readable: targetAsReadableString(action.target),
|
|
51311
|
-
to_readable: targetAsReadableString(dep)
|
|
51312
|
-
});
|
|
51313
|
-
}
|
|
51314
|
-
}
|
|
51315
|
-
function pushColumns(action, out) {
|
|
51316
|
-
const columns = (action.actionDescriptor && action.actionDescriptor.columns) || [];
|
|
51317
|
-
for (const column of columns) {
|
|
51318
|
-
out.push({
|
|
51319
|
-
target_key: key(action.target),
|
|
51320
|
-
readable_name: targetAsReadableString(action.target),
|
|
51321
|
-
column_name: (column.path || []).join("."),
|
|
51322
|
-
description: column.description || ""
|
|
51323
|
-
});
|
|
51324
|
-
}
|
|
51325
|
-
}
|
|
51326
|
-
function catalogRows(compiledGraph) {
|
|
51327
|
-
const actions = [];
|
|
51328
|
-
const dependencies = [];
|
|
51329
|
-
const columns = [];
|
|
51330
|
-
const add = (action, type) => {
|
|
51331
|
-
actions.push(actionRow(action, type));
|
|
51332
|
-
pushDeps(action, dependencies);
|
|
51333
|
-
pushColumns(action, columns);
|
|
51334
|
-
};
|
|
51335
|
-
for (const table of compiledGraph.tables || []) {
|
|
51336
|
-
add(table, tableType$1(table.enumType));
|
|
51337
|
-
}
|
|
51338
|
-
for (const operation of compiledGraph.operations || []) {
|
|
51339
|
-
add(operation, "operation");
|
|
51340
|
-
}
|
|
51341
|
-
for (const assertion of compiledGraph.assertions || []) {
|
|
51342
|
-
add(assertion, "assertion");
|
|
51343
|
-
}
|
|
51344
|
-
for (const exp of compiledGraph.exports || []) {
|
|
51345
|
-
add(exp, "export");
|
|
51346
|
-
}
|
|
51347
|
-
for (const imp of compiledGraph.imports || []) {
|
|
51348
|
-
add(imp, "import");
|
|
51349
|
-
}
|
|
51350
|
-
for (const declaration of compiledGraph.declarations || []) {
|
|
51351
|
-
add(declaration, "declaration");
|
|
51352
|
-
}
|
|
51353
|
-
return { actions, dependencies, columns };
|
|
51354
|
-
}
|
|
51355
|
-
function runRows(runResult, runId) {
|
|
51356
|
-
const runStatus = enumName(sqlanvil.RunResult.ExecutionStatus, runResult.status);
|
|
51357
|
-
return (runResult.actions || []).map(action => {
|
|
51358
|
-
const start = toMillis(action.timing && action.timing.startTimeMillis);
|
|
51359
|
-
const end = toMillis(action.timing && action.timing.endTimeMillis);
|
|
51360
|
-
const failedTask = (action.tasks || []).find(t => !!t.errorMessage);
|
|
51361
|
-
return {
|
|
51362
|
-
run_id: runId,
|
|
51363
|
-
run_status: runStatus,
|
|
51364
|
-
target_key: key(action.target),
|
|
51365
|
-
readable_name: targetAsReadableString(action.target),
|
|
51366
|
-
status: enumName(sqlanvil.ActionResult.ExecutionStatus, action.status),
|
|
51367
|
-
start_millis: start,
|
|
51368
|
-
end_millis: end,
|
|
51369
|
-
duration_millis: start && end ? end - start : 0,
|
|
51370
|
-
error_message: (failedTask && failedTask.errorMessage) || ""
|
|
51371
|
-
};
|
|
51372
|
-
});
|
|
51373
|
-
}
|
|
51374
|
-
|
|
51375
|
-
let tmpCounter = 0;
|
|
51376
|
-
async function writeParquet(rows, outPath, columns) {
|
|
51377
|
-
await fs__namespace.ensureDir(path__namespace.dirname(outPath));
|
|
51378
|
-
const tmp = path__namespace.join(os__namespace.tmpdir(), `sa_artifact_${process.pid}_${Date.now()}_${tmpCounter++}.json`);
|
|
51379
|
-
const payload = rows.length > 0 ? rows : [columns.reduce((o, c) => ((o[c] = null), o), {})];
|
|
51380
|
-
const whereFalse = rows.length > 0 ? "" : " WHERE false";
|
|
51381
|
-
await fs__namespace.writeFile(tmp, JSON.stringify(payload));
|
|
51382
|
-
try {
|
|
51383
|
-
await withDuckdb(async (conn) => {
|
|
51384
|
-
await runAsync(conn, `COPY (SELECT * FROM read_json_auto('${tmp}')${whereFalse}) TO '${outPath}' (FORMAT parquet)`);
|
|
51385
|
-
});
|
|
51386
|
-
}
|
|
51387
|
-
finally {
|
|
51388
|
-
await fs__namespace.remove(tmp).catch(() => undefined);
|
|
51389
|
-
}
|
|
51390
|
-
}
|
|
51391
|
-
async function queryParquet(sql, views) {
|
|
51392
|
-
return withDuckdb(async (conn) => {
|
|
51393
|
-
for (const view of views) {
|
|
51394
|
-
await runAsync(conn, `create view ${view.name} as select * from read_parquet('${view.glob}')`);
|
|
51603
|
+
jobId: e.metadata.bigquery.jobId
|
|
51604
|
+
}
|
|
51605
|
+
};
|
|
51606
|
+
}
|
|
51607
|
+
}
|
|
51395
51608
|
}
|
|
51396
|
-
|
|
51397
|
-
|
|
51398
|
-
|
|
51399
|
-
|
|
51400
|
-
const TARGET_DIR = "target";
|
|
51401
|
-
const ACTION_COLUMNS = [
|
|
51402
|
-
"target_key",
|
|
51403
|
-
"database",
|
|
51404
|
-
"schema",
|
|
51405
|
-
"name",
|
|
51406
|
-
"readable_name",
|
|
51407
|
-
"type",
|
|
51408
|
-
"tags",
|
|
51409
|
-
"disabled",
|
|
51410
|
-
"file_name",
|
|
51411
|
-
"description"
|
|
51412
|
-
];
|
|
51413
|
-
const DEPENDENCY_COLUMNS = ["from_target_key", "to_target_key", "from_readable", "to_readable"];
|
|
51414
|
-
const COLUMN_COLUMNS = ["target_key", "readable_name", "column_name", "description"];
|
|
51415
|
-
const RUN_COLUMNS = [
|
|
51416
|
-
"run_id",
|
|
51417
|
-
"run_status",
|
|
51418
|
-
"target_key",
|
|
51419
|
-
"readable_name",
|
|
51420
|
-
"status",
|
|
51421
|
-
"start_millis",
|
|
51422
|
-
"end_millis",
|
|
51423
|
-
"duration_millis",
|
|
51424
|
-
"error_message"
|
|
51425
|
-
];
|
|
51426
|
-
async function writeArtifacts(compiledGraph, projectDir, options = {}) {
|
|
51427
|
-
const targetDir = path__namespace.join(projectDir, TARGET_DIR);
|
|
51428
|
-
const catalogDir = path__namespace.join(targetDir, "catalog");
|
|
51429
|
-
const { actions, dependencies, columns } = catalogRows(compiledGraph);
|
|
51430
|
-
await writeParquet(actions, path__namespace.join(catalogDir, "actions.parquet"), ACTION_COLUMNS);
|
|
51431
|
-
await writeParquet(dependencies, path__namespace.join(catalogDir, "dependencies.parquet"), DEPENDENCY_COLUMNS);
|
|
51432
|
-
await writeParquet(columns, path__namespace.join(catalogDir, "columns.parquet"), COLUMN_COLUMNS);
|
|
51433
|
-
if (options.runResult) {
|
|
51434
|
-
const runId = options.runId !== undefined ? options.runId : Date.now();
|
|
51435
|
-
await writeParquet(runRows(options.runResult, runId), path__namespace.join(targetDir, "runs", `run_${runId}.parquet`), RUN_COLUMNS);
|
|
51609
|
+
taskResult.timing = timer.end();
|
|
51610
|
+
this.notifyListeners();
|
|
51611
|
+
return taskResult.status;
|
|
51436
51612
|
}
|
|
51437
|
-
return { targetDir };
|
|
51438
51613
|
}
|
|
51439
|
-
|
|
51440
|
-
|
|
51441
|
-
|
|
51614
|
+
class Timer {
|
|
51615
|
+
constructor(startTimeMillis) {
|
|
51616
|
+
this.startTimeMillis = startTimeMillis;
|
|
51442
51617
|
}
|
|
51443
|
-
|
|
51444
|
-
|
|
51445
|
-
|
|
51446
|
-
|
|
51618
|
+
static start(existingTiming) {
|
|
51619
|
+
return new Timer((existingTiming === null || existingTiming === void 0 ? void 0 : existingTiming.startTimeMillis.toNumber()) || new Date().valueOf());
|
|
51620
|
+
}
|
|
51621
|
+
current() {
|
|
51622
|
+
return {
|
|
51623
|
+
startTimeMillis: Long__default["default"].fromNumber(this.startTimeMillis)
|
|
51624
|
+
};
|
|
51625
|
+
}
|
|
51626
|
+
end() {
|
|
51627
|
+
return {
|
|
51628
|
+
startTimeMillis: Long__default["default"].fromNumber(this.startTimeMillis),
|
|
51629
|
+
endTimeMillis: Long__default["default"].fromNumber(new Date().valueOf())
|
|
51630
|
+
};
|
|
51447
51631
|
}
|
|
51448
51632
|
}
|
|
51449
51633
|
|
|
51450
|
-
function
|
|
51451
|
-
return
|
|
51452
|
-
.replace(/&/g, "&")
|
|
51453
|
-
.replace(/</g, "<")
|
|
51454
|
-
.replace(/>/g, ">")
|
|
51455
|
-
.replace(/"/g, """);
|
|
51634
|
+
async function test(dbadapter, tests) {
|
|
51635
|
+
return await Promise.all(tests.map(testCase => runTest(dbadapter, testCase)));
|
|
51456
51636
|
}
|
|
51457
|
-
async function
|
|
51458
|
-
const
|
|
51459
|
-
|
|
51460
|
-
|
|
51461
|
-
|
|
51462
|
-
|
|
51463
|
-
|
|
51464
|
-
|
|
51465
|
-
|
|
51466
|
-
const runId = head[0] && head[0].run_id !== null ? Number(head[0].run_id) : undefined;
|
|
51467
|
-
if (runId !== undefined) {
|
|
51468
|
-
const overall = await queryParquet(`select run_status from runs where run_id = ${runId} limit 1`, views);
|
|
51469
|
-
latestRun = { runId, status: overall[0] ? overall[0].run_status : "UNKNOWN" };
|
|
51470
|
-
const statuses = await queryParquet(`select readable_name, status from runs where run_id = ${runId}`, views);
|
|
51471
|
-
for (const row of statuses) {
|
|
51472
|
-
statusByModel.set(row.readable_name, row.status);
|
|
51473
|
-
}
|
|
51474
|
-
}
|
|
51637
|
+
async function runTest(dbadapter, testCase) {
|
|
51638
|
+
const noLimit = {};
|
|
51639
|
+
let actualResults;
|
|
51640
|
+
let expectedResults;
|
|
51641
|
+
try {
|
|
51642
|
+
[actualResults, expectedResults] = await Promise.all([
|
|
51643
|
+
dbadapter.execute(testCase.testQuery, noLimit),
|
|
51644
|
+
dbadapter.execute(testCase.expectedOutputQuery, noLimit)
|
|
51645
|
+
]);
|
|
51475
51646
|
}
|
|
51476
|
-
|
|
51477
|
-
|
|
51478
|
-
|
|
51647
|
+
catch (e) {
|
|
51648
|
+
return {
|
|
51649
|
+
name: testCase.name,
|
|
51650
|
+
successful: false,
|
|
51651
|
+
messages: [`Error thrown: ${e.message}.`]
|
|
51652
|
+
};
|
|
51479
51653
|
}
|
|
51480
|
-
|
|
51481
|
-
const models = actions.map(a => {
|
|
51482
|
-
byTypeMap.set(a.type, (byTypeMap.get(a.type) || 0) + 1);
|
|
51483
|
-
let tags = [];
|
|
51484
|
-
try {
|
|
51485
|
-
tags = JSON.parse(a.tags || "[]");
|
|
51486
|
-
}
|
|
51487
|
-
catch (e) {
|
|
51488
|
-
tags = [];
|
|
51489
|
-
}
|
|
51654
|
+
if (actualResults.rows.length !== expectedResults.rows.length) {
|
|
51490
51655
|
return {
|
|
51491
|
-
|
|
51492
|
-
|
|
51493
|
-
|
|
51494
|
-
|
|
51495
|
-
|
|
51496
|
-
dependsOn: dependsOn.get(a.readable_name) || []
|
|
51656
|
+
name: testCase.name,
|
|
51657
|
+
successful: false,
|
|
51658
|
+
messages: [
|
|
51659
|
+
`Expected ${expectedResults.rows.length} rows, but saw ${actualResults.rows.length} rows.`
|
|
51660
|
+
]
|
|
51497
51661
|
};
|
|
51498
|
-
});
|
|
51499
|
-
return {
|
|
51500
|
-
generatedAt,
|
|
51501
|
-
summary: {
|
|
51502
|
-
total: models.length,
|
|
51503
|
-
byType: Array.from(byTypeMap.entries())
|
|
51504
|
-
.map(([type, n]) => ({ type, n }))
|
|
51505
|
-
.sort((x, y) => x.type.localeCompare(y.type))
|
|
51506
|
-
},
|
|
51507
|
-
latestRun,
|
|
51508
|
-
models,
|
|
51509
|
-
columns: columns.map(c => ({
|
|
51510
|
-
readable: c.readable_name,
|
|
51511
|
-
column: c.column_name,
|
|
51512
|
-
description: c.description || ""
|
|
51513
|
-
}))
|
|
51514
|
-
};
|
|
51515
|
-
}
|
|
51516
|
-
function renderDocsHtml(model) {
|
|
51517
|
-
const summaryLine = `${model.summary.total} models — ` +
|
|
51518
|
-
model.summary.byType.map(t => `${t.n} ${t.type}`).join(", ");
|
|
51519
|
-
const runLine = model.latestRun
|
|
51520
|
-
? `Last run: <strong>${escapeHtml(model.latestRun.status)}</strong> (run ${model.latestRun.runId})`
|
|
51521
|
-
: "No runs recorded yet.";
|
|
51522
|
-
const statusBadge = (status) => {
|
|
51523
|
-
if (!status) {
|
|
51524
|
-
return "";
|
|
51525
|
-
}
|
|
51526
|
-
const cls = status === "SUCCESSFUL" ? "ok" : status === "FAILED" ? "fail" : "muted";
|
|
51527
|
-
return `<span class="badge ${cls}">${escapeHtml(status)}</span>`;
|
|
51528
|
-
};
|
|
51529
|
-
const modelRows = model.models
|
|
51530
|
-
.map(m => `<tr data-search="${escapeHtml((m.readable + " " + m.type + " " + m.tags.join(" ")).toLowerCase())}">
|
|
51531
|
-
<td><code>${escapeHtml(m.readable)}</code></td>
|
|
51532
|
-
<td>${escapeHtml(m.type)}</td>
|
|
51533
|
-
<td>${m.tags.map(t => `<span class="tag">${escapeHtml(t)}</span>`).join(" ")}</td>
|
|
51534
|
-
<td>${statusBadge(m.status)}</td>
|
|
51535
|
-
<td>${m.dependsOn.map(d => `<code>${escapeHtml(d)}</code>`).join("<br>")}</td>
|
|
51536
|
-
<td>${escapeHtml(m.description)}</td>
|
|
51537
|
-
</tr>`)
|
|
51538
|
-
.join("\n");
|
|
51539
|
-
const columnRows = model.columns
|
|
51540
|
-
.map(c => `<tr><td><code>${escapeHtml(c.readable)}</code></td><td><code>${escapeHtml(c.column)}</code></td><td>${escapeHtml(c.description)}</td></tr>`)
|
|
51541
|
-
.join("\n");
|
|
51542
|
-
return `<!doctype html>
|
|
51543
|
-
<html lang="en">
|
|
51544
|
-
<head>
|
|
51545
|
-
<meta charset="utf-8">
|
|
51546
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
51547
|
-
<title>SQLAnvil catalog</title>
|
|
51548
|
-
<style>
|
|
51549
|
-
:root { color-scheme: light dark; }
|
|
51550
|
-
body { font: 14px/1.5 -apple-system, system-ui, sans-serif; margin: 2rem; max-width: 1100px; }
|
|
51551
|
-
h1 { margin: 0 0 .25rem; }
|
|
51552
|
-
.meta { color: #888; margin-bottom: 1.5rem; }
|
|
51553
|
-
table { border-collapse: collapse; width: 100%; margin: 1rem 0 2rem; }
|
|
51554
|
-
th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid #8884; vertical-align: top; }
|
|
51555
|
-
th { font-weight: 600; }
|
|
51556
|
-
code { font-size: 12px; }
|
|
51557
|
-
.tag { background: #8883; border-radius: 4px; padding: 0 .35rem; font-size: 12px; }
|
|
51558
|
-
.badge { border-radius: 4px; padding: 0 .4rem; font-size: 12px; font-weight: 600; }
|
|
51559
|
-
.badge.ok { background: #1a7f37; color: #fff; }
|
|
51560
|
-
.badge.fail { background: #b62324; color: #fff; }
|
|
51561
|
-
.badge.muted { background: #8884; }
|
|
51562
|
-
#q { padding: .4rem .6rem; width: 320px; max-width: 100%; margin-bottom: .5rem; }
|
|
51563
|
-
</style>
|
|
51564
|
-
</head>
|
|
51565
|
-
<body>
|
|
51566
|
-
<h1>SQLAnvil catalog</h1>
|
|
51567
|
-
<div class="meta">${escapeHtml(summaryLine)} · ${runLine} · generated ${escapeHtml(model.generatedAt)}</div>
|
|
51568
|
-
|
|
51569
|
-
<input id="q" type="search" placeholder="Filter models…" oninput="filterModels(this.value)">
|
|
51570
|
-
<table id="models">
|
|
51571
|
-
<thead><tr><th>Model</th><th>Type</th><th>Tags</th><th>Last run</th><th>Depends on</th><th>Description</th></tr></thead>
|
|
51572
|
-
<tbody>
|
|
51573
|
-
${modelRows}
|
|
51574
|
-
</tbody>
|
|
51575
|
-
</table>
|
|
51576
|
-
|
|
51577
|
-
<h2>Columns</h2>
|
|
51578
|
-
<table>
|
|
51579
|
-
<thead><tr><th>Model</th><th>Column</th><th>Description</th></tr></thead>
|
|
51580
|
-
<tbody>
|
|
51581
|
-
${columnRows || '<tr><td colspan="3" class="meta">No documented columns.</td></tr>'}
|
|
51582
|
-
</tbody>
|
|
51583
|
-
</table>
|
|
51584
|
-
|
|
51585
|
-
<script>
|
|
51586
|
-
function filterModels(q) {
|
|
51587
|
-
q = q.toLowerCase();
|
|
51588
|
-
for (const tr of document.querySelectorAll('#models tbody tr')) {
|
|
51589
|
-
tr.style.display = tr.getAttribute('data-search').includes(q) ? '' : 'none';
|
|
51590
51662
|
}
|
|
51591
|
-
|
|
51592
|
-
|
|
51593
|
-
|
|
51594
|
-
|
|
51595
|
-
|
|
51596
|
-
}
|
|
51597
|
-
|
|
51598
|
-
|
|
51599
|
-
|
|
51600
|
-
|
|
51601
|
-
|
|
51602
|
-
|
|
51603
|
-
|
|
51604
|
-
|
|
51605
|
-
for (const n of nodes) {
|
|
51606
|
-
const inGraphDeps = Array.from(new Set(n.dependencyKeys.filter(d => byKey.has(d) && d !== n.key)));
|
|
51607
|
-
indegree.set(n.key, inGraphDeps.length);
|
|
51608
|
-
for (const dep of inGraphDeps) {
|
|
51609
|
-
dependents.set(dep, (dependents.get(dep) || []).concat(n.key));
|
|
51610
|
-
}
|
|
51663
|
+
if (actualResults.rows.length === 0) {
|
|
51664
|
+
return {
|
|
51665
|
+
name: testCase.name,
|
|
51666
|
+
successful: true
|
|
51667
|
+
};
|
|
51668
|
+
}
|
|
51669
|
+
const actualColumns = Object.keys(actualResults.rows[0]);
|
|
51670
|
+
const expectedColumns = Object.keys(expectedResults.rows[0]);
|
|
51671
|
+
if (actualColumns.length !== expectedColumns.length) {
|
|
51672
|
+
return {
|
|
51673
|
+
name: testCase.name,
|
|
51674
|
+
successful: false,
|
|
51675
|
+
messages: [`Expected columns "${expectedColumns}", but saw "${actualColumns}".`]
|
|
51676
|
+
};
|
|
51611
51677
|
}
|
|
51612
|
-
const
|
|
51613
|
-
.
|
|
51614
|
-
|
|
51615
|
-
|
|
51616
|
-
|
|
51617
|
-
|
|
51618
|
-
|
|
51619
|
-
const key = ready.shift();
|
|
51620
|
-
if (emitted.has(key)) {
|
|
51621
|
-
continue;
|
|
51622
|
-
}
|
|
51623
|
-
emitted.add(key);
|
|
51624
|
-
ordered.push(byKey.get(key));
|
|
51625
|
-
for (const dep of (dependents.get(key) || []).slice().sort()) {
|
|
51626
|
-
indegree.set(dep, (indegree.get(dep) || 0) - 1);
|
|
51627
|
-
if ((indegree.get(dep) || 0) <= 0 && !emitted.has(dep)) {
|
|
51628
|
-
ready.push(dep);
|
|
51629
|
-
ready.sort();
|
|
51630
|
-
}
|
|
51678
|
+
for (const expectedColumn of expectedColumns) {
|
|
51679
|
+
if (!actualColumns.some(actualColumn => normalizeColumnName(actualColumn) === normalizeColumnName(expectedColumn))) {
|
|
51680
|
+
return {
|
|
51681
|
+
name: testCase.name,
|
|
51682
|
+
successful: false,
|
|
51683
|
+
messages: [`Expected columns "${expectedColumns}", but saw "${actualColumns}".`]
|
|
51684
|
+
};
|
|
51631
51685
|
}
|
|
51632
51686
|
}
|
|
51633
|
-
|
|
51634
|
-
|
|
51635
|
-
|
|
51636
|
-
|
|
51637
|
-
|
|
51687
|
+
const rowMessages = [];
|
|
51688
|
+
for (let i = 0; i < actualResults.rows.length; i++) {
|
|
51689
|
+
const actualResultRow = normalizeRow(actualResults.rows[i]);
|
|
51690
|
+
const expectedResultRow = normalizeRow(expectedResults.rows[i]);
|
|
51691
|
+
for (const column of actualColumns) {
|
|
51692
|
+
const normalizedColumn = normalizeColumnName(column);
|
|
51693
|
+
const expectedValue = expectedResultRow[normalizedColumn];
|
|
51694
|
+
const actualValue = actualResultRow[normalizedColumn];
|
|
51695
|
+
if (expectedValue === null && actualValue !== null) {
|
|
51696
|
+
rowMessages.push(`For row ${i} and column "${column}": expected null, but saw "${actualValue}".`);
|
|
51697
|
+
break;
|
|
51698
|
+
}
|
|
51699
|
+
if (expectedValue !== null && actualValue === null) {
|
|
51700
|
+
rowMessages.push(`For row ${i} and column "${column}": expected "${expectedValue}", but saw null.`);
|
|
51701
|
+
break;
|
|
51702
|
+
}
|
|
51703
|
+
if (typeof expectedValue !== typeof actualValue) {
|
|
51704
|
+
rowMessages.push(`For row ${i} and column "${column}": expected type "${typeof expectedValue}", but saw type "${typeof actualValue}".`);
|
|
51705
|
+
break;
|
|
51706
|
+
}
|
|
51707
|
+
const comparableExpectedValue = typeof expectedValue === "object" ? JSON.stringify(expectedValue) : expectedValue;
|
|
51708
|
+
const comparableActualValue = typeof actualValue === "object" ? JSON.stringify(actualValue) : actualValue;
|
|
51709
|
+
if (comparableExpectedValue !== comparableActualValue) {
|
|
51710
|
+
rowMessages.push(`For row ${i} and column "${column}": expected "${comparableExpectedValue}", but saw "${comparableActualValue}".`);
|
|
51638
51711
|
}
|
|
51639
51712
|
}
|
|
51640
51713
|
}
|
|
51641
|
-
|
|
51714
|
+
if (rowMessages.length > 0) {
|
|
51715
|
+
return {
|
|
51716
|
+
name: testCase.name,
|
|
51717
|
+
successful: false,
|
|
51718
|
+
messages: rowMessages
|
|
51719
|
+
};
|
|
51720
|
+
}
|
|
51721
|
+
return {
|
|
51722
|
+
name: testCase.name,
|
|
51723
|
+
successful: true
|
|
51724
|
+
};
|
|
51642
51725
|
}
|
|
51643
|
-
function
|
|
51644
|
-
return
|
|
51645
|
-
|
|
51646
|
-
|
|
51726
|
+
function normalizeColumnName(name) {
|
|
51727
|
+
return name.toUpperCase();
|
|
51728
|
+
}
|
|
51729
|
+
function normalizeRow(row) {
|
|
51730
|
+
const newRow = {};
|
|
51731
|
+
Object.keys(row).forEach(colName => {
|
|
51732
|
+
newRow[normalizeColumnName(colName)] = row[colName];
|
|
51647
51733
|
});
|
|
51734
|
+
return newRow;
|
|
51648
51735
|
}
|
|
51649
|
-
|
|
51650
|
-
function
|
|
51651
|
-
|
|
51736
|
+
|
|
51737
|
+
function resolveEnvironment(projectDir, envName) {
|
|
51738
|
+
const settings = readConfigFromWorkflowSettings(projectDir);
|
|
51739
|
+
const environments = (settings === null || settings === void 0 ? void 0 : settings.environments) || {};
|
|
51740
|
+
const env = environments[envName];
|
|
51741
|
+
if (!env) {
|
|
51742
|
+
const available = Object.keys(environments);
|
|
51743
|
+
throw new Error(`Environment "${envName}" not found. ` +
|
|
51744
|
+
(available.length
|
|
51745
|
+
? `Available environments: ${available.join(", ")}.`
|
|
51746
|
+
: `No environments defined in workflow_settings.yaml.`));
|
|
51747
|
+
}
|
|
51748
|
+
const configOverride = {};
|
|
51749
|
+
if (env.schemaSuffix) {
|
|
51750
|
+
configOverride.schemaSuffix = env.schemaSuffix;
|
|
51751
|
+
}
|
|
51752
|
+
if (env.vars && Object.keys(env.vars).length > 0) {
|
|
51753
|
+
configOverride.vars = env.vars;
|
|
51754
|
+
}
|
|
51755
|
+
if (env.defaultDatabase) {
|
|
51756
|
+
configOverride.defaultDatabase = env.defaultDatabase;
|
|
51757
|
+
}
|
|
51758
|
+
if (env.defaultLocation) {
|
|
51759
|
+
configOverride.defaultLocation = env.defaultLocation;
|
|
51760
|
+
}
|
|
51761
|
+
return { configOverride, credentials: env.credentials || undefined };
|
|
51652
51762
|
}
|
|
51653
|
-
function
|
|
51654
|
-
const
|
|
51655
|
-
|
|
51763
|
+
function mergeProjectConfigOverride(envOverride, cliOverride) {
|
|
51764
|
+
const merged = Object.assign(Object.assign({}, envOverride), cliOverride);
|
|
51765
|
+
if (envOverride.vars || cliOverride.vars) {
|
|
51766
|
+
merged.vars = Object.assign(Object.assign({}, envOverride.vars), cliOverride.vars);
|
|
51767
|
+
}
|
|
51768
|
+
return merged;
|
|
51656
51769
|
}
|
|
51657
|
-
function
|
|
51658
|
-
|
|
51659
|
-
|
|
51660
|
-
|
|
51661
|
-
|
|
51770
|
+
function resolveCredentials(envCredentials, cliCredentials, defaultFilename) {
|
|
51771
|
+
if (cliCredentials && cliCredentials !== defaultFilename) {
|
|
51772
|
+
return cliCredentials;
|
|
51773
|
+
}
|
|
51774
|
+
return envCredentials || defaultFilename;
|
|
51662
51775
|
}
|
|
51663
51776
|
|
|
51664
|
-
|
|
51665
|
-
|
|
51666
|
-
|
|
51667
|
-
|
|
51668
|
-
|
|
51669
|
-
|
|
51670
|
-
await deps.execute(deps.dropSchemaCascadeSql(schema));
|
|
51671
|
-
}
|
|
51672
|
-
catch (e) {
|
|
51673
|
-
}
|
|
51674
|
-
}
|
|
51675
|
-
}
|
|
51676
|
-
catch (e) {
|
|
51777
|
+
function key(target) {
|
|
51778
|
+
return targetStringifier.stringify(target);
|
|
51779
|
+
}
|
|
51780
|
+
function enumName(enumObject, value) {
|
|
51781
|
+
if (value === undefined || value === null) {
|
|
51782
|
+
return "UNKNOWN";
|
|
51677
51783
|
}
|
|
51784
|
+
const match = Object.keys(enumObject).find(k => enumObject[k] === value);
|
|
51785
|
+
return match || String(value);
|
|
51678
51786
|
}
|
|
51679
|
-
function tableType(enumType) {
|
|
51787
|
+
function tableType$1(enumType) {
|
|
51680
51788
|
switch (enumType) {
|
|
51681
51789
|
case sqlanvil.TableType.VIEW:
|
|
51682
51790
|
return "view";
|
|
51683
51791
|
case sqlanvil.TableType.INCREMENTAL:
|
|
51684
51792
|
return "incremental";
|
|
51685
51793
|
default:
|
|
51686
|
-
return "table";
|
|
51687
|
-
}
|
|
51688
|
-
}
|
|
51689
|
-
function depKeys(deps) {
|
|
51690
|
-
return (deps || []).map(targetKey);
|
|
51691
|
-
}
|
|
51692
|
-
async function validate(compiledGraph, deps, options = {}) {
|
|
51693
|
-
const nodes = [];
|
|
51694
|
-
for (const table of compiledGraph.tables || []) {
|
|
51695
|
-
nodes.push({
|
|
51696
|
-
key: targetKey(table.target),
|
|
51697
|
-
dependencyKeys: depKeys(table.dependencyTargets),
|
|
51698
|
-
kind: "table",
|
|
51699
|
-
type: tableType(table.enumType),
|
|
51700
|
-
target: table.target,
|
|
51701
|
-
table,
|
|
51702
|
-
action: table
|
|
51703
|
-
});
|
|
51704
|
-
}
|
|
51705
|
-
for (const assertion of compiledGraph.assertions || []) {
|
|
51706
|
-
nodes.push({
|
|
51707
|
-
key: targetKey(assertion.target),
|
|
51708
|
-
dependencyKeys: depKeys(assertion.dependencyTargets),
|
|
51709
|
-
kind: "assertion",
|
|
51710
|
-
type: "assertion",
|
|
51711
|
-
target: assertion.target,
|
|
51712
|
-
action: assertion
|
|
51713
|
-
});
|
|
51714
|
-
}
|
|
51715
|
-
for (const operation of compiledGraph.operations || []) {
|
|
51716
|
-
if (!operation.target) {
|
|
51717
|
-
continue;
|
|
51718
|
-
}
|
|
51719
|
-
nodes.push({
|
|
51720
|
-
key: targetKey(operation.target),
|
|
51721
|
-
dependencyKeys: depKeys(operation.dependencyTargets),
|
|
51722
|
-
kind: "operation",
|
|
51723
|
-
type: "operation",
|
|
51724
|
-
target: operation.target,
|
|
51725
|
-
action: undefined
|
|
51726
|
-
});
|
|
51727
|
-
}
|
|
51728
|
-
for (const imp of compiledGraph.imports || []) {
|
|
51729
|
-
if (!imp.target) {
|
|
51730
|
-
continue;
|
|
51731
|
-
}
|
|
51732
|
-
nodes.push({
|
|
51733
|
-
key: targetKey(imp.target),
|
|
51734
|
-
dependencyKeys: depKeys(imp.dependencyTargets),
|
|
51735
|
-
kind: "import",
|
|
51736
|
-
type: "import",
|
|
51737
|
-
target: imp.target,
|
|
51738
|
-
action: undefined
|
|
51739
|
-
});
|
|
51740
|
-
}
|
|
51741
|
-
const ordered = topoOrder(nodes);
|
|
51742
|
-
const shadowSchemas = Array.from(new Set(nodes.filter(n => n.kind === "table").map(n => n.target.schema)));
|
|
51743
|
-
const statusByKey = new Map();
|
|
51744
|
-
const results = [];
|
|
51745
|
-
try {
|
|
51746
|
-
for (const schema of shadowSchemas) {
|
|
51747
|
-
await deps.execute(deps.createSchemaSql(schema));
|
|
51748
|
-
}
|
|
51749
|
-
for (const node of ordered) {
|
|
51750
|
-
if (node.kind === "operation" || node.kind === "import") {
|
|
51751
|
-
statusByKey.set(node.key, "SKIPPED");
|
|
51752
|
-
results.push({ target: node.target, type: node.type, status: "SKIPPED", errors: [] });
|
|
51753
|
-
continue;
|
|
51754
|
-
}
|
|
51755
|
-
if (dependencyBlocked(node.dependencyKeys, statusByKey)) {
|
|
51756
|
-
statusByKey.set(node.key, "BLOCKED");
|
|
51757
|
-
results.push({ target: node.target, type: node.type, status: "BLOCKED", errors: [] });
|
|
51758
|
-
continue;
|
|
51759
|
-
}
|
|
51760
|
-
const evaluations = await deps.evaluate(node.action);
|
|
51761
|
-
const failed = evaluations.some(e => e.status === sqlanvil.QueryEvaluation.QueryEvaluationStatus.FAILURE);
|
|
51762
|
-
const status = failed ? "FAILURE" : "PASS";
|
|
51763
|
-
statusByKey.set(node.key, status);
|
|
51764
|
-
results.push({ target: node.target, type: node.type, status, errors: evaluations });
|
|
51765
|
-
if (status === "PASS" && node.kind === "table") {
|
|
51766
|
-
try {
|
|
51767
|
-
await deps.execute(deps.validationStubSql(node.table));
|
|
51768
|
-
}
|
|
51769
|
-
catch (e) {
|
|
51770
|
-
}
|
|
51771
|
-
}
|
|
51772
|
-
}
|
|
51773
|
-
}
|
|
51774
|
-
finally {
|
|
51775
|
-
if (!options.keepShadow) {
|
|
51776
|
-
for (const schema of shadowSchemas.slice().reverse()) {
|
|
51777
|
-
try {
|
|
51778
|
-
await deps.execute(deps.dropSchemaCascadeSql(schema));
|
|
51779
|
-
}
|
|
51780
|
-
catch (e) {
|
|
51781
|
-
}
|
|
51782
|
-
}
|
|
51783
|
-
}
|
|
51794
|
+
return "table";
|
|
51784
51795
|
}
|
|
51785
|
-
return results;
|
|
51786
51796
|
}
|
|
51787
|
-
|
|
51788
|
-
|
|
51789
|
-
|
|
51790
|
-
this.pool = mysql__namespace.createPool(Object.assign(Object.assign({}, config), { connectionLimit: (options === null || options === void 0 ? void 0 : options.concurrencyLimit) || 10, waitForConnections: true, multipleStatements: false }));
|
|
51791
|
-
}
|
|
51792
|
-
async verifyConnection() {
|
|
51793
|
-
const conn = await this.pool.getConnection();
|
|
51794
|
-
try {
|
|
51795
|
-
await conn.query("select 1");
|
|
51796
|
-
}
|
|
51797
|
-
finally {
|
|
51798
|
-
conn.release();
|
|
51799
|
-
}
|
|
51797
|
+
function toMillis(value) {
|
|
51798
|
+
if (value === undefined || value === null) {
|
|
51799
|
+
return 0;
|
|
51800
51800
|
}
|
|
51801
|
-
|
|
51802
|
-
|
|
51803
|
-
let released = false;
|
|
51804
|
-
const releaseOnce = () => {
|
|
51805
|
-
if (released) {
|
|
51806
|
-
return;
|
|
51807
|
-
}
|
|
51808
|
-
released = true;
|
|
51809
|
-
conn.release();
|
|
51810
|
-
};
|
|
51811
|
-
try {
|
|
51812
|
-
return await callback({
|
|
51813
|
-
execute: async (statement, options = { rowLimit: 1000 }) => {
|
|
51814
|
-
const [rows] = await conn.query(statement, options.params || []);
|
|
51815
|
-
const arr = Array.isArray(rows) ? rows : [];
|
|
51816
|
-
return options.rowLimit && arr.length > options.rowLimit
|
|
51817
|
-
? arr.slice(0, options.rowLimit)
|
|
51818
|
-
: arr;
|
|
51819
|
-
}
|
|
51820
|
-
});
|
|
51821
|
-
}
|
|
51822
|
-
finally {
|
|
51823
|
-
releaseOnce();
|
|
51824
|
-
}
|
|
51801
|
+
if (typeof value === "number") {
|
|
51802
|
+
return value;
|
|
51825
51803
|
}
|
|
51826
|
-
|
|
51827
|
-
|
|
51804
|
+
if (typeof value.toNumber === "function") {
|
|
51805
|
+
return value.toNumber();
|
|
51828
51806
|
}
|
|
51807
|
+
return Number(value) || 0;
|
|
51829
51808
|
}
|
|
51830
|
-
function
|
|
51831
|
-
|
|
51832
|
-
|
|
51833
|
-
|
|
51834
|
-
|
|
51835
|
-
|
|
51836
|
-
|
|
51837
|
-
|
|
51838
|
-
|
|
51839
|
-
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
|
|
51843
|
-
|
|
51844
|
-
|
|
51845
|
-
|
|
51846
|
-
|
|
51847
|
-
|
|
51848
|
-
|
|
51849
|
-
|
|
51850
|
-
|
|
51851
|
-
|
|
51852
|
-
|
|
51853
|
-
case "CHAR":
|
|
51854
|
-
case "VARCHAR":
|
|
51855
|
-
case "TINYTEXT":
|
|
51856
|
-
case "TEXT":
|
|
51857
|
-
case "MEDIUMTEXT":
|
|
51858
|
-
case "LONGTEXT":
|
|
51859
|
-
case "ENUM":
|
|
51860
|
-
case "SET":
|
|
51861
|
-
case "JSON":
|
|
51862
|
-
case "TIME":
|
|
51863
|
-
return sqlanvil.Field.Primitive.STRING;
|
|
51864
|
-
case "DATE":
|
|
51865
|
-
return sqlanvil.Field.Primitive.DATE;
|
|
51866
|
-
case "DATETIME":
|
|
51867
|
-
case "TIMESTAMP":
|
|
51868
|
-
return sqlanvil.Field.Primitive.TIMESTAMP;
|
|
51869
|
-
default:
|
|
51870
|
-
return sqlanvil.Field.Primitive.UNKNOWN;
|
|
51809
|
+
function actionRow(action, type) {
|
|
51810
|
+
const descriptor = action.actionDescriptor || {};
|
|
51811
|
+
return {
|
|
51812
|
+
target_key: key(action.target),
|
|
51813
|
+
database: action.target.database || "",
|
|
51814
|
+
schema: action.target.schema || "",
|
|
51815
|
+
name: action.target.name || "",
|
|
51816
|
+
readable_name: targetAsReadableString(action.target),
|
|
51817
|
+
type,
|
|
51818
|
+
tags: JSON.stringify(action.tags || []),
|
|
51819
|
+
disabled: !!action.disabled,
|
|
51820
|
+
file_name: action.fileName || "",
|
|
51821
|
+
description: descriptor.description || ""
|
|
51822
|
+
};
|
|
51823
|
+
}
|
|
51824
|
+
function pushDeps(action, out) {
|
|
51825
|
+
for (const dep of action.dependencyTargets || []) {
|
|
51826
|
+
out.push({
|
|
51827
|
+
from_target_key: key(action.target),
|
|
51828
|
+
to_target_key: key(dep),
|
|
51829
|
+
from_readable: targetAsReadableString(action.target),
|
|
51830
|
+
to_readable: targetAsReadableString(dep)
|
|
51831
|
+
});
|
|
51871
51832
|
}
|
|
51872
51833
|
}
|
|
51873
|
-
function
|
|
51874
|
-
|
|
51834
|
+
function pushColumns(action, out) {
|
|
51835
|
+
const columns = (action.actionDescriptor && action.actionDescriptor.columns) || [];
|
|
51836
|
+
for (const column of columns) {
|
|
51837
|
+
out.push({
|
|
51838
|
+
target_key: key(action.target),
|
|
51839
|
+
readable_name: targetAsReadableString(action.target),
|
|
51840
|
+
column_name: (column.path || []).join("."),
|
|
51841
|
+
description: column.description || ""
|
|
51842
|
+
});
|
|
51843
|
+
}
|
|
51875
51844
|
}
|
|
51876
|
-
function
|
|
51877
|
-
const
|
|
51878
|
-
const
|
|
51879
|
-
|
|
51880
|
-
|
|
51881
|
-
|
|
51845
|
+
function catalogRows(compiledGraph) {
|
|
51846
|
+
const actions = [];
|
|
51847
|
+
const dependencies = [];
|
|
51848
|
+
const columns = [];
|
|
51849
|
+
const add = (action, type) => {
|
|
51850
|
+
actions.push(actionRow(action, type));
|
|
51851
|
+
pushDeps(action, dependencies);
|
|
51852
|
+
pushColumns(action, columns);
|
|
51853
|
+
};
|
|
51854
|
+
for (const table of compiledGraph.tables || []) {
|
|
51855
|
+
add(table, tableType$1(table.enumType));
|
|
51882
51856
|
}
|
|
51883
|
-
|
|
51884
|
-
|
|
51885
|
-
def += ` COLLATE ${col.collationName}`;
|
|
51857
|
+
for (const operation of compiledGraph.operations || []) {
|
|
51858
|
+
add(operation, "operation");
|
|
51886
51859
|
}
|
|
51887
|
-
|
|
51888
|
-
|
|
51889
|
-
if (extra.includes("on update current_timestamp")) {
|
|
51890
|
-
def += " ON UPDATE CURRENT_TIMESTAMP";
|
|
51860
|
+
for (const assertion of compiledGraph.assertions || []) {
|
|
51861
|
+
add(assertion, "assertion");
|
|
51891
51862
|
}
|
|
51892
|
-
|
|
51893
|
-
|
|
51863
|
+
for (const exp of compiledGraph.exports || []) {
|
|
51864
|
+
add(exp, "export");
|
|
51894
51865
|
}
|
|
51895
|
-
|
|
51866
|
+
for (const imp of compiledGraph.imports || []) {
|
|
51867
|
+
add(imp, "import");
|
|
51868
|
+
}
|
|
51869
|
+
for (const declaration of compiledGraph.declarations || []) {
|
|
51870
|
+
add(declaration, "declaration");
|
|
51871
|
+
}
|
|
51872
|
+
return { actions, dependencies, columns };
|
|
51896
51873
|
}
|
|
51897
|
-
function
|
|
51898
|
-
|
|
51899
|
-
|
|
51900
|
-
|
|
51874
|
+
function runRows(runResult, runId) {
|
|
51875
|
+
const runStatus = enumName(sqlanvil.RunResult.ExecutionStatus, runResult.status);
|
|
51876
|
+
return (runResult.actions || []).map(action => {
|
|
51877
|
+
const start = toMillis(action.timing && action.timing.startTimeMillis);
|
|
51878
|
+
const end = toMillis(action.timing && action.timing.endTimeMillis);
|
|
51879
|
+
const failedTask = (action.tasks || []).find(t => !!t.errorMessage);
|
|
51880
|
+
return {
|
|
51881
|
+
run_id: runId,
|
|
51882
|
+
run_status: runStatus,
|
|
51883
|
+
target_key: key(action.target),
|
|
51884
|
+
readable_name: targetAsReadableString(action.target),
|
|
51885
|
+
status: enumName(sqlanvil.ActionResult.ExecutionStatus, action.status),
|
|
51886
|
+
start_millis: start,
|
|
51887
|
+
end_millis: end,
|
|
51888
|
+
duration_millis: start && end ? end - start : 0,
|
|
51889
|
+
error_message: (failedTask && failedTask.errorMessage) || ""
|
|
51890
|
+
};
|
|
51891
|
+
});
|
|
51892
|
+
}
|
|
51893
|
+
|
|
51894
|
+
let tmpCounter = 0;
|
|
51895
|
+
async function writeParquet(rows, outPath, columns) {
|
|
51896
|
+
await fs__namespace.ensureDir(path__namespace.dirname(outPath));
|
|
51897
|
+
const tmp = path__namespace.join(os__namespace.tmpdir(), `sa_artifact_${process.pid}_${Date.now()}_${tmpCounter++}.json`);
|
|
51898
|
+
const payload = rows.length > 0 ? rows : [columns.reduce((o, c) => ((o[c] = null), o), {})];
|
|
51899
|
+
const whereFalse = rows.length > 0 ? "" : " WHERE false";
|
|
51900
|
+
await fs__namespace.writeFile(tmp, JSON.stringify(payload));
|
|
51901
|
+
try {
|
|
51902
|
+
await withDuckdb(async (conn) => {
|
|
51903
|
+
await runAsync(conn, `COPY (SELECT * FROM read_json_auto('${tmp}')${whereFalse}) TO '${outPath}' (FORMAT parquet)`);
|
|
51904
|
+
});
|
|
51901
51905
|
}
|
|
51902
|
-
|
|
51903
|
-
|
|
51906
|
+
finally {
|
|
51907
|
+
await fs__namespace.remove(tmp).catch(() => undefined);
|
|
51908
|
+
}
|
|
51909
|
+
}
|
|
51910
|
+
async function queryParquet(sql, views) {
|
|
51911
|
+
return withDuckdb(async (conn) => {
|
|
51912
|
+
for (const view of views) {
|
|
51913
|
+
await runAsync(conn, `create view ${view.name} as select * from read_parquet('${view.glob}')`);
|
|
51914
|
+
}
|
|
51915
|
+
return allAsync(conn, sql);
|
|
51916
|
+
});
|
|
51917
|
+
}
|
|
51918
|
+
|
|
51919
|
+
const TARGET_DIR = "target";
|
|
51920
|
+
const ACTION_COLUMNS = [
|
|
51921
|
+
"target_key",
|
|
51922
|
+
"database",
|
|
51923
|
+
"schema",
|
|
51924
|
+
"name",
|
|
51925
|
+
"readable_name",
|
|
51926
|
+
"type",
|
|
51927
|
+
"tags",
|
|
51928
|
+
"disabled",
|
|
51929
|
+
"file_name",
|
|
51930
|
+
"description"
|
|
51931
|
+
];
|
|
51932
|
+
const DEPENDENCY_COLUMNS = ["from_target_key", "to_target_key", "from_readable", "to_readable"];
|
|
51933
|
+
const COLUMN_COLUMNS = ["target_key", "readable_name", "column_name", "description"];
|
|
51934
|
+
const RUN_COLUMNS = [
|
|
51935
|
+
"run_id",
|
|
51936
|
+
"run_status",
|
|
51937
|
+
"target_key",
|
|
51938
|
+
"readable_name",
|
|
51939
|
+
"status",
|
|
51940
|
+
"start_millis",
|
|
51941
|
+
"end_millis",
|
|
51942
|
+
"duration_millis",
|
|
51943
|
+
"error_message"
|
|
51944
|
+
];
|
|
51945
|
+
async function writeArtifacts(compiledGraph, projectDir, options = {}) {
|
|
51946
|
+
const targetDir = path__namespace.join(projectDir, TARGET_DIR);
|
|
51947
|
+
const catalogDir = path__namespace.join(targetDir, "catalog");
|
|
51948
|
+
const { actions, dependencies, columns } = catalogRows(compiledGraph);
|
|
51949
|
+
await writeParquet(actions, path__namespace.join(catalogDir, "actions.parquet"), ACTION_COLUMNS);
|
|
51950
|
+
await writeParquet(dependencies, path__namespace.join(catalogDir, "dependencies.parquet"), DEPENDENCY_COLUMNS);
|
|
51951
|
+
await writeParquet(columns, path__namespace.join(catalogDir, "columns.parquet"), COLUMN_COLUMNS);
|
|
51952
|
+
if (options.runResult) {
|
|
51953
|
+
const runId = options.runId !== undefined ? options.runId : Date.now();
|
|
51954
|
+
await writeParquet(runRows(options.runResult, runId), path__namespace.join(targetDir, "runs", `run_${runId}.parquet`), RUN_COLUMNS);
|
|
51955
|
+
}
|
|
51956
|
+
return { targetDir };
|
|
51957
|
+
}
|
|
51958
|
+
async function safeWriteArtifacts(compiledGraph, projectDir, options = {}) {
|
|
51959
|
+
try {
|
|
51960
|
+
await writeArtifacts(compiledGraph, projectDir, options);
|
|
51904
51961
|
}
|
|
51905
|
-
|
|
51906
|
-
|
|
51907
|
-
|
|
51962
|
+
catch (e) {
|
|
51963
|
+
if (options.warn) {
|
|
51964
|
+
options.warn(`Artifacts skipped: ${e.message}`);
|
|
51965
|
+
}
|
|
51908
51966
|
}
|
|
51909
|
-
return ` DEFAULT '${escapeMysqlString(d)}'`;
|
|
51910
51967
|
}
|
|
51911
51968
|
|
|
51912
|
-
|
|
51913
|
-
"
|
|
51914
|
-
|
|
51915
|
-
|
|
51916
|
-
|
|
51917
|
-
|
|
51918
|
-
|
|
51919
|
-
|
|
51920
|
-
|
|
51969
|
+
function escapeHtml(value) {
|
|
51970
|
+
return String(value === null || value === undefined ? "" : value)
|
|
51971
|
+
.replace(/&/g, "&")
|
|
51972
|
+
.replace(/</g, "<")
|
|
51973
|
+
.replace(/>/g, ">")
|
|
51974
|
+
.replace(/"/g, """);
|
|
51975
|
+
}
|
|
51976
|
+
async function buildDocsModel(views, generatedAt) {
|
|
51977
|
+
const hasRuns = views.some(v => v.name === "runs");
|
|
51978
|
+
const actions = await queryParquet("select readable_name, type, tags, description from actions order by type, readable_name", views);
|
|
51979
|
+
const dependencies = await queryParquet("select from_readable, to_readable from dependencies", views);
|
|
51980
|
+
const columns = await queryParquet("select readable_name, column_name, description from columns order by readable_name, column_name", views);
|
|
51981
|
+
let latestRun;
|
|
51982
|
+
const statusByModel = new Map();
|
|
51983
|
+
if (hasRuns) {
|
|
51984
|
+
const head = await queryParquet("select max(run_id) as run_id from runs", views);
|
|
51985
|
+
const runId = head[0] && head[0].run_id !== null ? Number(head[0].run_id) : undefined;
|
|
51986
|
+
if (runId !== undefined) {
|
|
51987
|
+
const overall = await queryParquet(`select run_status from runs where run_id = ${runId} limit 1`, views);
|
|
51988
|
+
latestRun = { runId, status: overall[0] ? overall[0].run_status : "UNKNOWN" };
|
|
51989
|
+
const statuses = await queryParquet(`select readable_name, status from runs where run_id = ${runId}`, views);
|
|
51990
|
+
for (const row of statuses) {
|
|
51991
|
+
statusByModel.set(row.readable_name, row.status);
|
|
51992
|
+
}
|
|
51993
|
+
}
|
|
51921
51994
|
}
|
|
51922
|
-
|
|
51923
|
-
|
|
51924
|
-
|
|
51925
|
-
|
|
51926
|
-
|
|
51927
|
-
|
|
51928
|
-
|
|
51929
|
-
|
|
51930
|
-
port: credentials.port || 3306,
|
|
51931
|
-
user: credentials.user,
|
|
51932
|
-
password: credentials.password,
|
|
51933
|
-
database: credentials.database || undefined,
|
|
51934
|
-
ssl
|
|
51935
|
-
}, options);
|
|
51995
|
+
const dependsOn = new Map();
|
|
51996
|
+
for (const dep of dependencies) {
|
|
51997
|
+
dependsOn.set(dep.from_readable, (dependsOn.get(dep.from_readable) || []).concat(dep.to_readable));
|
|
51998
|
+
}
|
|
51999
|
+
const byTypeMap = new Map();
|
|
52000
|
+
const models = actions.map(a => {
|
|
52001
|
+
byTypeMap.set(a.type, (byTypeMap.get(a.type) || 0) + 1);
|
|
52002
|
+
let tags = [];
|
|
51936
52003
|
try {
|
|
51937
|
-
|
|
52004
|
+
tags = JSON.parse(a.tags || "[]");
|
|
51938
52005
|
}
|
|
51939
52006
|
catch (e) {
|
|
51940
|
-
|
|
51941
|
-
throw new ErrorWithCause(`Could not connect to MySQL at ${credentials.host}:${credentials.port || 3306} ` +
|
|
51942
|
-
`as "${credentials.user}": ${e.message}`, e);
|
|
52007
|
+
tags = [];
|
|
51943
52008
|
}
|
|
51944
|
-
return
|
|
52009
|
+
return {
|
|
52010
|
+
readable: a.readable_name,
|
|
52011
|
+
type: a.type,
|
|
52012
|
+
tags,
|
|
52013
|
+
description: a.description || "",
|
|
52014
|
+
status: statusByModel.get(a.readable_name),
|
|
52015
|
+
dependsOn: dependsOn.get(a.readable_name) || []
|
|
52016
|
+
};
|
|
52017
|
+
});
|
|
52018
|
+
return {
|
|
52019
|
+
generatedAt,
|
|
52020
|
+
summary: {
|
|
52021
|
+
total: models.length,
|
|
52022
|
+
byType: Array.from(byTypeMap.entries())
|
|
52023
|
+
.map(([type, n]) => ({ type, n }))
|
|
52024
|
+
.sort((x, y) => x.type.localeCompare(y.type))
|
|
52025
|
+
},
|
|
52026
|
+
latestRun,
|
|
52027
|
+
models,
|
|
52028
|
+
columns: columns.map(c => ({
|
|
52029
|
+
readable: c.readable_name,
|
|
52030
|
+
column: c.column_name,
|
|
52031
|
+
description: c.description || ""
|
|
52032
|
+
}))
|
|
52033
|
+
};
|
|
52034
|
+
}
|
|
52035
|
+
function renderDocsHtml(model) {
|
|
52036
|
+
const summaryLine = `${model.summary.total} models — ` +
|
|
52037
|
+
model.summary.byType.map(t => `${t.n} ${t.type}`).join(", ");
|
|
52038
|
+
const runLine = model.latestRun
|
|
52039
|
+
? `Last run: <strong>${escapeHtml(model.latestRun.status)}</strong> (run ${model.latestRun.runId})`
|
|
52040
|
+
: "No runs recorded yet.";
|
|
52041
|
+
const statusBadge = (status) => {
|
|
52042
|
+
if (!status) {
|
|
52043
|
+
return "";
|
|
52044
|
+
}
|
|
52045
|
+
const cls = status === "SUCCESSFUL" ? "ok" : status === "FAILED" ? "fail" : "muted";
|
|
52046
|
+
return `<span class="badge ${cls}">${escapeHtml(status)}</span>`;
|
|
52047
|
+
};
|
|
52048
|
+
const modelRows = model.models
|
|
52049
|
+
.map(m => `<tr data-search="${escapeHtml((m.readable + " " + m.type + " " + m.tags.join(" ")).toLowerCase())}">
|
|
52050
|
+
<td><code>${escapeHtml(m.readable)}</code></td>
|
|
52051
|
+
<td>${escapeHtml(m.type)}</td>
|
|
52052
|
+
<td>${m.tags.map(t => `<span class="tag">${escapeHtml(t)}</span>`).join(" ")}</td>
|
|
52053
|
+
<td>${statusBadge(m.status)}</td>
|
|
52054
|
+
<td>${m.dependsOn.map(d => `<code>${escapeHtml(d)}</code>`).join("<br>")}</td>
|
|
52055
|
+
<td>${escapeHtml(m.description)}</td>
|
|
52056
|
+
</tr>`)
|
|
52057
|
+
.join("\n");
|
|
52058
|
+
const columnRows = model.columns
|
|
52059
|
+
.map(c => `<tr><td><code>${escapeHtml(c.readable)}</code></td><td><code>${escapeHtml(c.column)}</code></td><td>${escapeHtml(c.description)}</td></tr>`)
|
|
52060
|
+
.join("\n");
|
|
52061
|
+
return `<!doctype html>
|
|
52062
|
+
<html lang="en">
|
|
52063
|
+
<head>
|
|
52064
|
+
<meta charset="utf-8">
|
|
52065
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
52066
|
+
<title>SQLAnvil catalog</title>
|
|
52067
|
+
<style>
|
|
52068
|
+
:root { color-scheme: light dark; }
|
|
52069
|
+
body { font: 14px/1.5 -apple-system, system-ui, sans-serif; margin: 2rem; max-width: 1100px; }
|
|
52070
|
+
h1 { margin: 0 0 .25rem; }
|
|
52071
|
+
.meta { color: #888; margin-bottom: 1.5rem; }
|
|
52072
|
+
table { border-collapse: collapse; width: 100%; margin: 1rem 0 2rem; }
|
|
52073
|
+
th, td { text-align: left; padding: .4rem .6rem; border-bottom: 1px solid #8884; vertical-align: top; }
|
|
52074
|
+
th { font-weight: 600; }
|
|
52075
|
+
code { font-size: 12px; }
|
|
52076
|
+
.tag { background: #8883; border-radius: 4px; padding: 0 .35rem; font-size: 12px; }
|
|
52077
|
+
.badge { border-radius: 4px; padding: 0 .4rem; font-size: 12px; font-weight: 600; }
|
|
52078
|
+
.badge.ok { background: #1a7f37; color: #fff; }
|
|
52079
|
+
.badge.fail { background: #b62324; color: #fff; }
|
|
52080
|
+
.badge.muted { background: #8884; }
|
|
52081
|
+
#q { padding: .4rem .6rem; width: 320px; max-width: 100%; margin-bottom: .5rem; }
|
|
52082
|
+
</style>
|
|
52083
|
+
</head>
|
|
52084
|
+
<body>
|
|
52085
|
+
<h1>SQLAnvil catalog</h1>
|
|
52086
|
+
<div class="meta">${escapeHtml(summaryLine)} · ${runLine} · generated ${escapeHtml(model.generatedAt)}</div>
|
|
52087
|
+
|
|
52088
|
+
<input id="q" type="search" placeholder="Filter models…" oninput="filterModels(this.value)">
|
|
52089
|
+
<table id="models">
|
|
52090
|
+
<thead><tr><th>Model</th><th>Type</th><th>Tags</th><th>Last run</th><th>Depends on</th><th>Description</th></tr></thead>
|
|
52091
|
+
<tbody>
|
|
52092
|
+
${modelRows}
|
|
52093
|
+
</tbody>
|
|
52094
|
+
</table>
|
|
52095
|
+
|
|
52096
|
+
<h2>Columns</h2>
|
|
52097
|
+
<table>
|
|
52098
|
+
<thead><tr><th>Model</th><th>Column</th><th>Description</th></tr></thead>
|
|
52099
|
+
<tbody>
|
|
52100
|
+
${columnRows || '<tr><td colspan="3" class="meta">No documented columns.</td></tr>'}
|
|
52101
|
+
</tbody>
|
|
52102
|
+
</table>
|
|
52103
|
+
|
|
52104
|
+
<script>
|
|
52105
|
+
function filterModels(q) {
|
|
52106
|
+
q = q.toLowerCase();
|
|
52107
|
+
for (const tr of document.querySelectorAll('#models tbody tr')) {
|
|
52108
|
+
tr.style.display = tr.getAttribute('data-search').includes(q) ? '' : 'none';
|
|
51945
52109
|
}
|
|
51946
|
-
|
|
51947
|
-
|
|
52110
|
+
}
|
|
52111
|
+
</script>
|
|
52112
|
+
</body>
|
|
52113
|
+
</html>
|
|
52114
|
+
`;
|
|
52115
|
+
}
|
|
52116
|
+
|
|
52117
|
+
function targetKey(target) {
|
|
52118
|
+
return [target.database, target.schema, target.name].filter(Boolean).join(".");
|
|
52119
|
+
}
|
|
52120
|
+
function topoOrder(nodes) {
|
|
52121
|
+
const byKey = new Map(nodes.map(n => [n.key, n]));
|
|
52122
|
+
const indegree = new Map();
|
|
52123
|
+
const dependents = new Map();
|
|
52124
|
+
for (const n of nodes) {
|
|
52125
|
+
const inGraphDeps = Array.from(new Set(n.dependencyKeys.filter(d => byKey.has(d) && d !== n.key)));
|
|
52126
|
+
indegree.set(n.key, inGraphDeps.length);
|
|
52127
|
+
for (const dep of inGraphDeps) {
|
|
52128
|
+
dependents.set(dep, (dependents.get(dep) || []).concat(n.key));
|
|
52129
|
+
}
|
|
51948
52130
|
}
|
|
51949
|
-
|
|
51950
|
-
|
|
51951
|
-
|
|
52131
|
+
const ready = nodes
|
|
52132
|
+
.filter(n => (indegree.get(n.key) || 0) === 0)
|
|
52133
|
+
.map(n => n.key)
|
|
52134
|
+
.sort();
|
|
52135
|
+
const ordered = [];
|
|
52136
|
+
const emitted = new Set();
|
|
52137
|
+
while (ready.length) {
|
|
52138
|
+
const key = ready.shift();
|
|
52139
|
+
if (emitted.has(key)) {
|
|
52140
|
+
continue;
|
|
52141
|
+
}
|
|
52142
|
+
emitted.add(key);
|
|
52143
|
+
ordered.push(byKey.get(key));
|
|
52144
|
+
for (const dep of (dependents.get(key) || []).slice().sort()) {
|
|
52145
|
+
indegree.set(dep, (indegree.get(dep) || 0) - 1);
|
|
52146
|
+
if ((indegree.get(dep) || 0) <= 0 && !emitted.has(dep)) {
|
|
52147
|
+
ready.push(dep);
|
|
52148
|
+
ready.sort();
|
|
52149
|
+
}
|
|
52150
|
+
}
|
|
51952
52151
|
}
|
|
51953
|
-
|
|
51954
|
-
|
|
51955
|
-
|
|
51956
|
-
|
|
51957
|
-
|
|
51958
|
-
return { rows, metadata: {} };
|
|
51959
|
-
}
|
|
51960
|
-
catch (e) {
|
|
51961
|
-
if (opts.includeQueryInError) {
|
|
51962
|
-
throw new Error(`Error encountered while running "${stmt}": ${e.message}`);
|
|
51963
|
-
}
|
|
51964
|
-
throw new ErrorWithCause(`Error executing mysql query: ${e.message}`, e);
|
|
51965
|
-
}
|
|
51966
|
-
},
|
|
51967
|
-
executeRaw: async (stmt, opts = { rowLimit: 1000 }) => {
|
|
51968
|
-
const positional = opts.params ? Object.values(opts.params) : undefined;
|
|
51969
|
-
const rows = await client.execute(stmt, { params: positional, rowLimit: opts.rowLimit });
|
|
51970
|
-
return { rows, schema: [], metadata: {} };
|
|
52152
|
+
if (ordered.length < nodes.length) {
|
|
52153
|
+
for (const n of nodes.slice().sort((a, b) => a.key.localeCompare(b.key))) {
|
|
52154
|
+
if (!emitted.has(n.key)) {
|
|
52155
|
+
emitted.add(n.key);
|
|
52156
|
+
ordered.push(n);
|
|
51971
52157
|
}
|
|
51972
|
-
}
|
|
52158
|
+
}
|
|
51973
52159
|
}
|
|
51974
|
-
|
|
51975
|
-
|
|
51976
|
-
|
|
51977
|
-
|
|
51978
|
-
|
|
51979
|
-
|
|
51980
|
-
|
|
51981
|
-
|
|
52160
|
+
return ordered;
|
|
52161
|
+
}
|
|
52162
|
+
function dependencyBlocked(dependencyKeys, statusByKey) {
|
|
52163
|
+
return dependencyKeys.some(dep => {
|
|
52164
|
+
const status = statusByKey.get(dep);
|
|
52165
|
+
return status !== undefined && status !== "PASS";
|
|
52166
|
+
});
|
|
52167
|
+
}
|
|
52168
|
+
const VALIDATE_SHADOW_PREFIX = "sqlanvil_validate_";
|
|
52169
|
+
function validateShadowSuffix(nowMs) {
|
|
52170
|
+
return `${VALIDATE_SHADOW_PREFIX}${nowMs}`;
|
|
52171
|
+
}
|
|
52172
|
+
function parseShadowTimestamp(schemaName) {
|
|
52173
|
+
const match = schemaName.match(/sqlanvil_validate_(\d+)/);
|
|
52174
|
+
return match ? Number(match[1]) : null;
|
|
52175
|
+
}
|
|
52176
|
+
function shadowSchemasToSweep(schemaNames, nowMs, maxAgeMs) {
|
|
52177
|
+
return schemaNames.filter(name => {
|
|
52178
|
+
const ts = parseShadowTimestamp(name);
|
|
52179
|
+
return ts !== null && nowMs - ts > maxAgeMs;
|
|
52180
|
+
});
|
|
52181
|
+
}
|
|
52182
|
+
|
|
52183
|
+
const SHADOW_MAX_AGE_MS = 60 * 60 * 1000;
|
|
52184
|
+
async function sweepOrphanShadows(deps, nowMs, maxAgeMs = SHADOW_MAX_AGE_MS) {
|
|
52185
|
+
try {
|
|
52186
|
+
const schemas = await deps.listSchemas();
|
|
52187
|
+
for (const schema of shadowSchemasToSweep(schemas, nowMs, maxAgeMs)) {
|
|
51982
52188
|
try {
|
|
51983
|
-
await
|
|
52189
|
+
await deps.execute(deps.dropSchemaCascadeSql(schema));
|
|
51984
52190
|
}
|
|
51985
52191
|
catch (e) {
|
|
51986
|
-
evaluationResponse = {
|
|
51987
|
-
status: sqlanvil.QueryEvaluation.QueryEvaluationStatus.FAILURE,
|
|
51988
|
-
error: sqlanvil.QueryEvaluationError.create({
|
|
51989
|
-
message: (e === null || e === void 0 ? void 0 : e.message) ? String(e.message) : String(e)
|
|
51990
|
-
})
|
|
51991
|
-
};
|
|
51992
52192
|
}
|
|
51993
|
-
queryEvaluations.push(sqlanvil.QueryEvaluation.create(Object.assign(Object.assign({}, evaluationResponse), { incremental: validationQuery.incremental, query: validationQueriesWithoutWrappers[index].query })));
|
|
51994
|
-
}
|
|
51995
|
-
return queryEvaluations;
|
|
51996
|
-
}
|
|
51997
|
-
async tables(_database, schema) {
|
|
51998
|
-
const params = [];
|
|
51999
|
-
let schemaClause = "";
|
|
52000
|
-
if (schema) {
|
|
52001
|
-
schemaClause = "and table_schema = ?";
|
|
52002
|
-
params.push(schema);
|
|
52003
52193
|
}
|
|
52004
|
-
const queryResult = await this.execute(`select table_name, table_schema
|
|
52005
|
-
from information_schema.tables
|
|
52006
|
-
where table_schema not in ('information_schema', 'mysql', 'performance_schema', 'sys')
|
|
52007
|
-
${schemaClause}`, { params, rowLimit: 10000, includeQueryInError: true });
|
|
52008
|
-
const targets = queryResult.rows.map(row => ({
|
|
52009
|
-
schema: row.table_schema,
|
|
52010
|
-
name: row.table_name
|
|
52011
|
-
}));
|
|
52012
|
-
return await Promise.all(targets.map(target => this.table(target)));
|
|
52013
52194
|
}
|
|
52014
|
-
|
|
52015
|
-
const results = await this.execute(`select tables.table_schema as table_schema, tables.table_name as table_name
|
|
52016
|
-
from information_schema.tables as tables
|
|
52017
|
-
left join information_schema.columns columns
|
|
52018
|
-
on tables.table_schema = columns.table_schema
|
|
52019
|
-
and tables.table_name = columns.table_name
|
|
52020
|
-
where tables.table_schema like ?
|
|
52021
|
-
or tables.table_name like ?
|
|
52022
|
-
or columns.column_name like ?
|
|
52023
|
-
group by 1, 2`, {
|
|
52024
|
-
params: [`%${searchText}%`, `%${searchText}%`, `%${searchText}%`],
|
|
52025
|
-
rowLimit: options.limit
|
|
52026
|
-
});
|
|
52027
|
-
return await Promise.all(results.rows.map(row => this.table({
|
|
52028
|
-
schema: row.table_schema,
|
|
52029
|
-
name: row.table_name
|
|
52030
|
-
})));
|
|
52195
|
+
catch (e) {
|
|
52031
52196
|
}
|
|
52032
|
-
|
|
52033
|
-
|
|
52034
|
-
|
|
52035
|
-
|
|
52036
|
-
|
|
52037
|
-
|
|
52038
|
-
|
|
52039
|
-
|
|
52040
|
-
|
|
52041
|
-
order by ordinal_position`, { params, includeQueryInError: true })
|
|
52042
|
-
]);
|
|
52043
|
-
if (tableResults.rows.length === 0) {
|
|
52044
|
-
return null;
|
|
52045
|
-
}
|
|
52046
|
-
const tableType = String((_a = tableResults.rows[0].table_type) !== null && _a !== void 0 ? _a : tableResults.rows[0].TABLE_TYPE).toUpperCase();
|
|
52047
|
-
const tableComment = String((_c = (_b = tableResults.rows[0].table_comment) !== null && _b !== void 0 ? _b : tableResults.rows[0].TABLE_COMMENT) !== null && _c !== void 0 ? _c : "");
|
|
52048
|
-
return sqlanvil.TableMetadata.create({
|
|
52049
|
-
target,
|
|
52050
|
-
type: tableType === "VIEW" ? sqlanvil.TableMetadata.Type.VIEW : sqlanvil.TableMetadata.Type.TABLE,
|
|
52051
|
-
description: tableComment || undefined,
|
|
52052
|
-
fields: columnResults.rows.map(row => {
|
|
52053
|
-
var _a, _b, _c, _d;
|
|
52054
|
-
const comment = String((_b = (_a = row.column_comment) !== null && _a !== void 0 ? _a : row.COLUMN_COMMENT) !== null && _b !== void 0 ? _b : "");
|
|
52055
|
-
return sqlanvil.Field.create({
|
|
52056
|
-
name: ((_c = row.column_name) !== null && _c !== void 0 ? _c : row.COLUMN_NAME),
|
|
52057
|
-
primitive: convertFieldType(((_d = row.data_type) !== null && _d !== void 0 ? _d : row.DATA_TYPE)),
|
|
52058
|
-
description: comment || undefined
|
|
52059
|
-
});
|
|
52060
|
-
})
|
|
52061
|
-
});
|
|
52197
|
+
}
|
|
52198
|
+
function tableType(enumType) {
|
|
52199
|
+
switch (enumType) {
|
|
52200
|
+
case sqlanvil.TableType.VIEW:
|
|
52201
|
+
return "view";
|
|
52202
|
+
case sqlanvil.TableType.INCREMENTAL:
|
|
52203
|
+
return "incremental";
|
|
52204
|
+
default:
|
|
52205
|
+
return "table";
|
|
52062
52206
|
}
|
|
52063
|
-
|
|
52064
|
-
|
|
52065
|
-
|
|
52066
|
-
|
|
52067
|
-
|
|
52068
|
-
|
|
52069
|
-
|
|
52070
|
-
|
|
52207
|
+
}
|
|
52208
|
+
function depKeys(deps) {
|
|
52209
|
+
return (deps || []).map(targetKey);
|
|
52210
|
+
}
|
|
52211
|
+
async function validate(compiledGraph, deps, options = {}) {
|
|
52212
|
+
const nodes = [];
|
|
52213
|
+
for (const table of compiledGraph.tables || []) {
|
|
52214
|
+
nodes.push({
|
|
52215
|
+
key: targetKey(table.target),
|
|
52216
|
+
dependencyKeys: depKeys(table.dependencyTargets),
|
|
52217
|
+
kind: "table",
|
|
52218
|
+
type: tableType(table.enumType),
|
|
52219
|
+
target: table.target,
|
|
52220
|
+
table,
|
|
52221
|
+
action: table
|
|
52071
52222
|
});
|
|
52072
52223
|
}
|
|
52073
|
-
|
|
52074
|
-
|
|
52075
|
-
|
|
52224
|
+
for (const assertion of compiledGraph.assertions || []) {
|
|
52225
|
+
nodes.push({
|
|
52226
|
+
key: targetKey(assertion.target),
|
|
52227
|
+
dependencyKeys: depKeys(assertion.dependencyTargets),
|
|
52228
|
+
kind: "assertion",
|
|
52229
|
+
type: "assertion",
|
|
52230
|
+
target: assertion.target,
|
|
52231
|
+
action: assertion
|
|
52076
52232
|
});
|
|
52077
|
-
return result.rows
|
|
52078
|
-
.map(row => { var _a; return ((_a = row.schema_name) !== null && _a !== void 0 ? _a : row.SCHEMA_NAME); })
|
|
52079
|
-
.filter(name => !INTERNAL_SCHEMAS.has(name));
|
|
52080
52233
|
}
|
|
52081
|
-
|
|
52082
|
-
|
|
52083
|
-
|
|
52234
|
+
for (const operation of compiledGraph.operations || []) {
|
|
52235
|
+
if (!operation.target) {
|
|
52236
|
+
continue;
|
|
52237
|
+
}
|
|
52238
|
+
nodes.push({
|
|
52239
|
+
key: targetKey(operation.target),
|
|
52240
|
+
dependencyKeys: depKeys(operation.dependencyTargets),
|
|
52241
|
+
kind: "operation",
|
|
52242
|
+
type: "operation",
|
|
52243
|
+
target: operation.target,
|
|
52244
|
+
action: undefined
|
|
52084
52245
|
});
|
|
52085
52246
|
}
|
|
52086
|
-
|
|
52087
|
-
|
|
52088
|
-
|
|
52089
|
-
const actualMetadata = await this.table(target);
|
|
52090
|
-
if (!actualMetadata) {
|
|
52091
|
-
return;
|
|
52092
|
-
}
|
|
52093
|
-
if (actualMetadata.type === sqlanvil.TableMetadata.Type.VIEW) {
|
|
52094
|
-
return;
|
|
52095
|
-
}
|
|
52096
|
-
const resolved = `\`${target.schema}\`.\`${target.name}\``;
|
|
52097
|
-
if (actionDescriptor === null || actionDescriptor === void 0 ? void 0 : actionDescriptor.description) {
|
|
52098
|
-
await this.execute(`alter table ${resolved} comment = '${escapeMysqlString(actionDescriptor.description)}'`, { includeQueryInError: true });
|
|
52247
|
+
for (const imp of compiledGraph.imports || []) {
|
|
52248
|
+
if (!imp.target) {
|
|
52249
|
+
continue;
|
|
52099
52250
|
}
|
|
52100
|
-
|
|
52101
|
-
|
|
52102
|
-
|
|
52103
|
-
|
|
52104
|
-
|
|
52251
|
+
nodes.push({
|
|
52252
|
+
key: targetKey(imp.target),
|
|
52253
|
+
dependencyKeys: depKeys(imp.dependencyTargets),
|
|
52254
|
+
kind: "import",
|
|
52255
|
+
type: "import",
|
|
52256
|
+
target: imp.target,
|
|
52257
|
+
action: undefined
|
|
52105
52258
|
});
|
|
52106
|
-
|
|
52107
|
-
|
|
52259
|
+
}
|
|
52260
|
+
const ordered = topoOrder(nodes);
|
|
52261
|
+
const shadowSchemas = Array.from(new Set(nodes.filter(n => n.kind === "table").map(n => n.target.schema)));
|
|
52262
|
+
const statusByKey = new Map();
|
|
52263
|
+
const results = [];
|
|
52264
|
+
try {
|
|
52265
|
+
for (const schema of shadowSchemas) {
|
|
52266
|
+
await deps.execute(deps.createSchemaSql(schema));
|
|
52108
52267
|
}
|
|
52109
|
-
|
|
52110
|
-
|
|
52111
|
-
|
|
52112
|
-
|
|
52113
|
-
const defByName = new Map();
|
|
52114
|
-
defResult.rows.forEach(row => { var _a; return defByName.set(String((_a = row.column_name) !== null && _a !== void 0 ? _a : row.COLUMN_NAME), row); });
|
|
52115
|
-
for (const column of columnComments) {
|
|
52116
|
-
const name = column.path[0];
|
|
52117
|
-
const row = defByName.get(name);
|
|
52118
|
-
if (!row) {
|
|
52268
|
+
for (const node of ordered) {
|
|
52269
|
+
if (node.kind === "operation" || node.kind === "import") {
|
|
52270
|
+
statusByKey.set(node.key, "SKIPPED");
|
|
52271
|
+
results.push({ target: node.target, type: node.type, status: "SKIPPED", errors: [] });
|
|
52119
52272
|
continue;
|
|
52120
52273
|
}
|
|
52121
|
-
|
|
52122
|
-
|
|
52123
|
-
|
|
52124
|
-
|
|
52125
|
-
|
|
52126
|
-
|
|
52127
|
-
|
|
52128
|
-
|
|
52129
|
-
|
|
52130
|
-
|
|
52131
|
-
|
|
52132
|
-
|
|
52133
|
-
|
|
52134
|
-
|
|
52135
|
-
|
|
52274
|
+
if (dependencyBlocked(node.dependencyKeys, statusByKey)) {
|
|
52275
|
+
statusByKey.set(node.key, "BLOCKED");
|
|
52276
|
+
results.push({ target: node.target, type: node.type, status: "BLOCKED", errors: [] });
|
|
52277
|
+
continue;
|
|
52278
|
+
}
|
|
52279
|
+
const evaluations = await deps.evaluate(node.action);
|
|
52280
|
+
const failed = evaluations.some(e => e.status === sqlanvil.QueryEvaluation.QueryEvaluationStatus.FAILURE);
|
|
52281
|
+
const status = failed ? "FAILURE" : "PASS";
|
|
52282
|
+
statusByKey.set(node.key, status);
|
|
52283
|
+
results.push({ target: node.target, type: node.type, status, errors: evaluations });
|
|
52284
|
+
if (status === "PASS" && node.kind === "table") {
|
|
52285
|
+
try {
|
|
52286
|
+
await deps.execute(deps.validationStubSql(node.table));
|
|
52287
|
+
}
|
|
52288
|
+
catch (e) {
|
|
52289
|
+
}
|
|
52290
|
+
}
|
|
52136
52291
|
}
|
|
52137
52292
|
}
|
|
52138
|
-
|
|
52139
|
-
|
|
52293
|
+
finally {
|
|
52294
|
+
if (!options.keepShadow) {
|
|
52295
|
+
for (const schema of shadowSchemas.slice().reverse()) {
|
|
52296
|
+
try {
|
|
52297
|
+
await deps.execute(deps.dropSchemaCascadeSql(schema));
|
|
52298
|
+
}
|
|
52299
|
+
catch (e) {
|
|
52300
|
+
}
|
|
52301
|
+
}
|
|
52302
|
+
}
|
|
52140
52303
|
}
|
|
52304
|
+
return results;
|
|
52141
52305
|
}
|
|
52142
52306
|
|
|
52143
52307
|
class SupabaseDbAdapter extends PostgresDbAdapter {
|
|
@@ -53453,6 +53617,9 @@ const projectDirOption = positionalOption("project-dir", {
|
|
|
53453
53617
|
});
|
|
53454
53618
|
const projectDirMustExistOption = Object.assign(Object.assign({}, projectDirOption), { check: (argv) => {
|
|
53455
53619
|
assertPathExists(argv[projectDirOption.name]);
|
|
53620
|
+
if (argv.graph) {
|
|
53621
|
+
return;
|
|
53622
|
+
}
|
|
53456
53623
|
const workflowSettingsYamlPath = path__namespace.resolve(argv[projectDirOption.name], "workflow_settings.yaml");
|
|
53457
53624
|
if (!fs__namespace$1.existsSync(workflowSettingsYamlPath)) {
|
|
53458
53625
|
throw new Error(`${argv[projectDirOption.name]} does not appear to be a sqlanvil directory (missing workflow_settings.yaml file).`);
|