@malloy-publisher/server 0.0.191 → 0.0.193
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/api-doc.yaml +531 -3
- package/dist/app/assets/{HomePage-Dn3E4CuB.js → HomePage-Di9MU3lS.js} +1 -1
- package/dist/app/assets/{MainPage-BzB3yoqi.js → MainPage-yZQo2HSL.js} +1 -1
- package/dist/app/assets/{ModelPage-C9O_sAXT.js → ModelPage-Dx2mHWeT.js} +1 -1
- package/dist/app/assets/{PackagePage-DcxKEjBX.js → PackagePage-Q386Py9t.js} +1 -1
- package/dist/app/assets/{ProjectPage-BDj307rF.js → ProjectPage-WR7wPQB-.js} +1 -1
- package/dist/app/assets/{RouteError-DAShbVCG.js → RouteError-stRGU4aW.js} +1 -1
- package/dist/app/assets/{WorkbookPage-Cs_XYEaB.js → WorkbookPage-D3iX0djH.js} +1 -1
- package/dist/app/assets/{core-CjeTkq8O.es-BqRc6yhC.js → core-QH4HZQVz.es-CqlQLZdl.js} +1 -1
- package/dist/app/assets/{index-15BOvhp0.js → index-CVHzPJwN.js} +119 -119
- package/dist/app/assets/{index-D68X76-7.js → index-DavAceYD.js} +50 -50
- package/dist/app/assets/{index-Bb2jqquW.js → index-Y3Y-VRna.js} +1 -1
- package/dist/app/assets/{index.umd-DGBekgSu.js → index.umd-Bp8OIhfV.js} +46 -46
- package/dist/app/index.html +1 -1
- package/dist/server.mjs +1396 -985
- package/package.json +10 -10
- package/src/controller/connection.controller.ts +102 -27
- package/src/dto/connection.dto.spec.ts +4 -0
- package/src/dto/connection.dto.ts +46 -2
- package/src/server.ts +217 -9
- package/src/service/connection.spec.ts +250 -4
- package/src/service/connection.ts +326 -473
- package/src/service/connection_config.ts +514 -0
- package/src/service/connection_service.spec.ts +50 -0
- package/src/service/connection_service.ts +125 -32
- package/src/service/materialization_service.spec.ts +18 -12
- package/src/service/materialization_service.ts +54 -7
- package/src/service/model.ts +24 -27
- package/src/service/package.spec.ts +125 -1
- package/src/service/package.ts +86 -44
- package/src/service/project.ts +172 -94
- package/src/service/project_store.spec.ts +72 -0
- package/src/service/project_store.ts +98 -81
- package/tests/unit/duckdb/attached_databases.test.ts +1 -19
package/src/server.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
// Pre-load the instrumentation module; the instrumentation module must be loaded before the other imports.
|
|
2
|
-
import "./instrumentation";
|
|
3
|
-
import {
|
|
4
|
-
getPrometheusMetricsHandler,
|
|
5
|
-
httpMetricsMiddleware,
|
|
6
|
-
} from "./instrumentation";
|
|
7
|
-
|
|
8
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
3
|
import bodyParser from "body-parser";
|
|
10
4
|
import cors from "cors";
|
|
@@ -13,6 +7,7 @@ import * as http from "http";
|
|
|
13
7
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
14
8
|
import { AddressInfo } from "net";
|
|
15
9
|
import * as path from "path";
|
|
10
|
+
import { ParsedQs } from "qs";
|
|
16
11
|
import { fileURLToPath } from "url";
|
|
17
12
|
import { CompileController } from "./controller/compile.controller";
|
|
18
13
|
import { ConnectionController } from "./controller/connection.controller";
|
|
@@ -31,6 +26,11 @@ import {
|
|
|
31
26
|
registerHealthEndpoints,
|
|
32
27
|
registerSignalHandlers,
|
|
33
28
|
} from "./health";
|
|
29
|
+
import "./instrumentation";
|
|
30
|
+
import {
|
|
31
|
+
getPrometheusMetricsHandler,
|
|
32
|
+
httpMetricsMiddleware,
|
|
33
|
+
} from "./instrumentation";
|
|
34
34
|
import { logger, loggerMiddleware } from "./logger";
|
|
35
35
|
|
|
36
36
|
import { ManifestController } from "./controller/manifest.controller";
|
|
@@ -524,6 +524,70 @@ app.get(
|
|
|
524
524
|
},
|
|
525
525
|
);
|
|
526
526
|
|
|
527
|
+
// ── Per-package connection data routes ─────────────────────────────
|
|
528
|
+
// `duckdb` is per-package; non-`duckdb` names fall through to the
|
|
529
|
+
// project's connection registry via the package's MalloyConfig wrapper.
|
|
530
|
+
app.get(
|
|
531
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/schemas`,
|
|
532
|
+
async (req, res) => {
|
|
533
|
+
try {
|
|
534
|
+
res.status(200).json(
|
|
535
|
+
await connectionController.listSchemas(
|
|
536
|
+
req.params.projectName,
|
|
537
|
+
req.params.connectionName,
|
|
538
|
+
req.params.packageName,
|
|
539
|
+
),
|
|
540
|
+
);
|
|
541
|
+
} catch (error) {
|
|
542
|
+
logger.error(error);
|
|
543
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
544
|
+
res.status(status).json(json);
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
app.get(
|
|
550
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/schemas/:schemaName/tables`,
|
|
551
|
+
async (req, res) => {
|
|
552
|
+
try {
|
|
553
|
+
res.status(200).json(
|
|
554
|
+
await connectionController.listTables(
|
|
555
|
+
req.params.projectName,
|
|
556
|
+
req.params.connectionName,
|
|
557
|
+
req.params.schemaName,
|
|
558
|
+
normalizeQueryArray(req.query.tableNames),
|
|
559
|
+
req.params.packageName,
|
|
560
|
+
),
|
|
561
|
+
);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
logger.error(error);
|
|
564
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
565
|
+
res.status(status).json(json);
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
app.get(
|
|
571
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/schemas/:schemaName/tables/:tablePath`,
|
|
572
|
+
async (req, res) => {
|
|
573
|
+
try {
|
|
574
|
+
res.status(200).json(
|
|
575
|
+
await connectionController.getTable(
|
|
576
|
+
req.params.projectName,
|
|
577
|
+
req.params.connectionName,
|
|
578
|
+
req.params.schemaName,
|
|
579
|
+
req.params.tablePath,
|
|
580
|
+
req.params.packageName,
|
|
581
|
+
),
|
|
582
|
+
);
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.error(error);
|
|
585
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
586
|
+
res.status(status).json(json);
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
);
|
|
590
|
+
|
|
527
591
|
/**
|
|
528
592
|
* @deprecated Use /projects/:projectName/connections/:connectionName/sqlSource POST method instead
|
|
529
593
|
*/
|
|
@@ -565,8 +629,49 @@ app.post(
|
|
|
565
629
|
},
|
|
566
630
|
);
|
|
567
631
|
|
|
632
|
+
// Per-package versions
|
|
633
|
+
app.get(
|
|
634
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/sqlSource`,
|
|
635
|
+
async (req, res) => {
|
|
636
|
+
try {
|
|
637
|
+
res.status(200).json(
|
|
638
|
+
await connectionController.getConnectionSqlSource(
|
|
639
|
+
req.params.projectName,
|
|
640
|
+
req.params.connectionName,
|
|
641
|
+
req.query.sqlStatement as string,
|
|
642
|
+
req.params.packageName,
|
|
643
|
+
),
|
|
644
|
+
);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.error(error);
|
|
647
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
648
|
+
res.status(status).json(json);
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
app.post(
|
|
654
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/sqlSource`,
|
|
655
|
+
async (req, res) => {
|
|
656
|
+
try {
|
|
657
|
+
res.status(200).json(
|
|
658
|
+
await connectionController.getConnectionSqlSource(
|
|
659
|
+
req.params.projectName,
|
|
660
|
+
req.params.connectionName,
|
|
661
|
+
req.body.sqlStatement as string,
|
|
662
|
+
req.params.packageName,
|
|
663
|
+
),
|
|
664
|
+
);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
logger.error(error);
|
|
667
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
668
|
+
res.status(status).json(json);
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
);
|
|
672
|
+
|
|
568
673
|
/**
|
|
569
|
-
* @deprecated Use /projects/:projectName/connections/:connectionName/
|
|
674
|
+
* @deprecated Use /projects/:projectName/connections/:connectionName/sqlQuery POST method instead
|
|
570
675
|
*/
|
|
571
676
|
app.get(
|
|
572
677
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/queryData`,
|
|
@@ -588,16 +693,76 @@ app.get(
|
|
|
588
693
|
},
|
|
589
694
|
);
|
|
590
695
|
|
|
696
|
+
/**
|
|
697
|
+
* @deprecated Use /projects/:projectName/packages/:packageName/connections/:connectionName/sqlQuery
|
|
698
|
+
*/
|
|
699
|
+
app.get(
|
|
700
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/queryData`,
|
|
701
|
+
async (req, res) => {
|
|
702
|
+
try {
|
|
703
|
+
res.status(200).json(
|
|
704
|
+
await connectionController.getConnectionQueryData(
|
|
705
|
+
req.params.projectName,
|
|
706
|
+
req.params.connectionName,
|
|
707
|
+
req.query.sqlStatement as string,
|
|
708
|
+
req.query.options as string,
|
|
709
|
+
req.params.packageName,
|
|
710
|
+
),
|
|
711
|
+
);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
logger.error(error);
|
|
714
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
715
|
+
res.status(status).json(json);
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
);
|
|
719
|
+
|
|
591
720
|
app.post(
|
|
592
721
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlQuery`,
|
|
593
722
|
async (req, res) => {
|
|
594
723
|
try {
|
|
724
|
+
let options: string | ParsedQs | (string | ParsedQs)[] | undefined;
|
|
725
|
+
|
|
726
|
+
// Support both body and query parameters for options for backwards compatibility
|
|
727
|
+
// TODO: To be removed in the future
|
|
728
|
+
if (req.body?.options) {
|
|
729
|
+
options = req.body.options;
|
|
730
|
+
} else {
|
|
731
|
+
options = req.query.options;
|
|
732
|
+
}
|
|
595
733
|
res.status(200).json(
|
|
596
734
|
await connectionController.getConnectionQueryData(
|
|
597
735
|
req.params.projectName,
|
|
598
736
|
req.params.connectionName,
|
|
599
737
|
req.body.sqlStatement as string,
|
|
600
|
-
|
|
738
|
+
options as string,
|
|
739
|
+
),
|
|
740
|
+
);
|
|
741
|
+
} catch (error) {
|
|
742
|
+
logger.error(error);
|
|
743
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
744
|
+
res.status(status).json(json);
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
app.post(
|
|
750
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/sqlQuery`,
|
|
751
|
+
async (req, res) => {
|
|
752
|
+
try {
|
|
753
|
+
let options: string | ParsedQs | (string | ParsedQs)[] | undefined;
|
|
754
|
+
if (req.body?.options) {
|
|
755
|
+
options = req.body.options;
|
|
756
|
+
} else {
|
|
757
|
+
options = req.query.options;
|
|
758
|
+
}
|
|
759
|
+
res.status(200).json(
|
|
760
|
+
await connectionController.getConnectionQueryData(
|
|
761
|
+
req.params.projectName,
|
|
762
|
+
req.params.connectionName,
|
|
763
|
+
req.body.sqlStatement as string,
|
|
764
|
+
options as string,
|
|
765
|
+
req.params.packageName,
|
|
601
766
|
),
|
|
602
767
|
);
|
|
603
768
|
} catch (error) {
|
|
@@ -609,7 +774,7 @@ app.post(
|
|
|
609
774
|
);
|
|
610
775
|
|
|
611
776
|
/**
|
|
612
|
-
* @deprecated Use /projects/:projectName/connections/:connectionName/
|
|
777
|
+
* @deprecated Use /projects/:projectName/connections/:connectionName/sqlTemporaryTable POST method instead
|
|
613
778
|
*/
|
|
614
779
|
app.get(
|
|
615
780
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/temporaryTable`,
|
|
@@ -630,6 +795,29 @@ app.get(
|
|
|
630
795
|
},
|
|
631
796
|
);
|
|
632
797
|
|
|
798
|
+
/**
|
|
799
|
+
* @deprecated Use /projects/:projectName/packages/:packageName/connections/:connectionName/sqlTemporaryTable
|
|
800
|
+
*/
|
|
801
|
+
app.get(
|
|
802
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/temporaryTable`,
|
|
803
|
+
async (req, res) => {
|
|
804
|
+
try {
|
|
805
|
+
res.status(200).json(
|
|
806
|
+
await connectionController.getConnectionTemporaryTable(
|
|
807
|
+
req.params.projectName,
|
|
808
|
+
req.params.connectionName,
|
|
809
|
+
req.query.sqlStatement as string,
|
|
810
|
+
req.params.packageName,
|
|
811
|
+
),
|
|
812
|
+
);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
logger.error(error);
|
|
815
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
816
|
+
res.status(status).json(json);
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
);
|
|
820
|
+
|
|
633
821
|
app.post(
|
|
634
822
|
`${API_PREFIX}/projects/:projectName/connections/:connectionName/sqlTemporaryTable`,
|
|
635
823
|
async (req, res) => {
|
|
@@ -649,6 +837,26 @@ app.post(
|
|
|
649
837
|
},
|
|
650
838
|
);
|
|
651
839
|
|
|
840
|
+
app.post(
|
|
841
|
+
`${API_PREFIX}/projects/:projectName/packages/:packageName/connections/:connectionName/sqlTemporaryTable`,
|
|
842
|
+
async (req, res) => {
|
|
843
|
+
try {
|
|
844
|
+
res.status(200).json(
|
|
845
|
+
await connectionController.getConnectionTemporaryTable(
|
|
846
|
+
req.params.projectName,
|
|
847
|
+
req.params.connectionName,
|
|
848
|
+
req.body.sqlStatement as string,
|
|
849
|
+
req.params.packageName,
|
|
850
|
+
),
|
|
851
|
+
);
|
|
852
|
+
} catch (error) {
|
|
853
|
+
logger.error(error);
|
|
854
|
+
const { json, status } = internalErrorToHttpError(error as Error);
|
|
855
|
+
res.status(status).json(json);
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
);
|
|
859
|
+
|
|
652
860
|
app.get(`${API_PREFIX}/projects/:projectName/packages`, async (req, res) => {
|
|
653
861
|
if (req.query.versionId) {
|
|
654
862
|
setVersionIdError(res);
|
|
@@ -4,6 +4,7 @@ import path from "path";
|
|
|
4
4
|
import sinon from "sinon";
|
|
5
5
|
import { DuckDBConnection } from "@malloydata/db-duckdb";
|
|
6
6
|
import { createProjectConnections, testConnectionConfig } from "./connection";
|
|
7
|
+
import { assembleProjectConnections } from "./connection_config";
|
|
7
8
|
import { components } from "../api";
|
|
8
9
|
|
|
9
10
|
type ApiConnection = components["schemas"]["Connection"];
|
|
@@ -1121,19 +1122,264 @@ describe("connection integration tests", () => {
|
|
|
1121
1122
|
).rejects.toThrow(/cannot be 'duckdb'/);
|
|
1122
1123
|
});
|
|
1123
1124
|
|
|
1124
|
-
it("should
|
|
1125
|
+
it("should allow DuckDB connections with no attachments", async () => {
|
|
1126
|
+
const { malloyConnections } = await createProjectConnections(
|
|
1127
|
+
[
|
|
1128
|
+
{
|
|
1129
|
+
name: "empty_duckdb",
|
|
1130
|
+
type: "duckdb",
|
|
1131
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1132
|
+
},
|
|
1133
|
+
],
|
|
1134
|
+
testProjectPath,
|
|
1135
|
+
);
|
|
1136
|
+
|
|
1137
|
+
const connection = malloyConnections.get("empty_duckdb");
|
|
1138
|
+
expect(connection).toBeDefined();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it("should reject unsupported DuckDB connector fields", async () => {
|
|
1125
1142
|
await expect(
|
|
1126
1143
|
createProjectConnections(
|
|
1127
1144
|
[
|
|
1128
1145
|
{
|
|
1129
|
-
name: "
|
|
1146
|
+
name: "duckdb_with_setup_sql",
|
|
1130
1147
|
type: "duckdb",
|
|
1131
|
-
duckdbConnection: {
|
|
1148
|
+
duckdbConnection: {
|
|
1149
|
+
attachedDatabases: [],
|
|
1150
|
+
setupSQL: "INSTALL httpfs",
|
|
1151
|
+
},
|
|
1152
|
+
} as unknown as ApiConnection,
|
|
1153
|
+
],
|
|
1154
|
+
testProjectPath,
|
|
1155
|
+
),
|
|
1156
|
+
).rejects.toThrow(/Unsupported DuckDB connection field/);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it("should reject project-authored DuckDB policy fields", async () => {
|
|
1160
|
+
await expect(
|
|
1161
|
+
createProjectConnections(
|
|
1162
|
+
[
|
|
1163
|
+
{
|
|
1164
|
+
name: "duckdb_with_policy",
|
|
1165
|
+
type: "duckdb",
|
|
1166
|
+
duckdbConnection: {
|
|
1167
|
+
attachedDatabases: [],
|
|
1168
|
+
securityPolicy: "sandboxed",
|
|
1169
|
+
},
|
|
1170
|
+
} as unknown as ApiConnection,
|
|
1171
|
+
],
|
|
1172
|
+
testProjectPath,
|
|
1173
|
+
),
|
|
1174
|
+
).rejects.toThrow(/Unsupported DuckDB connection field/);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it("should preserve Snowflake private-key auth options", async () => {
|
|
1178
|
+
const { malloyConnections, releaseConnections } =
|
|
1179
|
+
await createProjectConnections(
|
|
1180
|
+
[
|
|
1181
|
+
{
|
|
1182
|
+
name: "sf_private_key",
|
|
1183
|
+
type: "snowflake",
|
|
1184
|
+
snowflakeConnection: {
|
|
1185
|
+
account: "test-account",
|
|
1186
|
+
username: "test-user",
|
|
1187
|
+
privateKey:
|
|
1188
|
+
"-----BEGIN PRIVATE KEY-----MIIB-----END PRIVATE KEY-----",
|
|
1189
|
+
warehouse: "test-warehouse",
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
],
|
|
1193
|
+
testProjectPath,
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
try {
|
|
1197
|
+
const connection = malloyConnections.get(
|
|
1198
|
+
"sf_private_key",
|
|
1199
|
+
) as unknown as { connOptions: Record<string, unknown> };
|
|
1200
|
+
expect(connection.connOptions.authenticator).toBe(
|
|
1201
|
+
"SNOWFLAKE_JWT",
|
|
1202
|
+
);
|
|
1203
|
+
expect(connection.connOptions.privateKey).toContain(
|
|
1204
|
+
"BEGIN PRIVATE KEY",
|
|
1205
|
+
);
|
|
1206
|
+
} finally {
|
|
1207
|
+
await releaseConnections();
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
it("should translate Trino Peaka credentials to core extraCredential", () => {
|
|
1212
|
+
const assembled = assembleProjectConnections(
|
|
1213
|
+
[
|
|
1214
|
+
{
|
|
1215
|
+
name: "trino_peaka",
|
|
1216
|
+
type: "trino",
|
|
1217
|
+
trinoConnection: {
|
|
1218
|
+
server: "https://example.com",
|
|
1219
|
+
port: 443,
|
|
1220
|
+
catalog: "catalog",
|
|
1221
|
+
schema: "schema",
|
|
1222
|
+
user: "user",
|
|
1223
|
+
peakaKey: "peaka-secret",
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
],
|
|
1227
|
+
testProjectPath,
|
|
1228
|
+
);
|
|
1229
|
+
|
|
1230
|
+
expect(
|
|
1231
|
+
assembled.pojo.connections.trino_peaka.extraCredential,
|
|
1232
|
+
).toEqual({ peakaKey: "peaka-secret" });
|
|
1233
|
+
expect(
|
|
1234
|
+
assembled.pojo.connections.trino_peaka.extraConfig,
|
|
1235
|
+
).toBeUndefined();
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
it("should validate project-level BigQuery service account keys", () => {
|
|
1239
|
+
expect(() =>
|
|
1240
|
+
assembleProjectConnections(
|
|
1241
|
+
[
|
|
1242
|
+
{
|
|
1243
|
+
name: "bq_invalid",
|
|
1244
|
+
type: "bigquery",
|
|
1245
|
+
bigqueryConnection: {
|
|
1246
|
+
defaultProjectId: "test-project",
|
|
1247
|
+
serviceAccountKeyJson: '{"invalid":"key"}',
|
|
1248
|
+
},
|
|
1132
1249
|
},
|
|
1133
1250
|
],
|
|
1134
1251
|
testProjectPath,
|
|
1135
1252
|
),
|
|
1136
|
-
).
|
|
1253
|
+
).toThrow(/missing "type" field/);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
it("should preserve PGSSLMODE for project-level Postgres", () => {
|
|
1257
|
+
const previousPgSslMode = process.env.PGSSLMODE;
|
|
1258
|
+
process.env.PGSSLMODE = "require";
|
|
1259
|
+
try {
|
|
1260
|
+
const assembled = assembleProjectConnections(
|
|
1261
|
+
[
|
|
1262
|
+
{
|
|
1263
|
+
name: "pg_ssl",
|
|
1264
|
+
type: "postgres",
|
|
1265
|
+
postgresConnection: {
|
|
1266
|
+
host: "localhost",
|
|
1267
|
+
port: 5432,
|
|
1268
|
+
userName: "user",
|
|
1269
|
+
password: "pass",
|
|
1270
|
+
databaseName: "db",
|
|
1271
|
+
},
|
|
1272
|
+
},
|
|
1273
|
+
],
|
|
1274
|
+
testProjectPath,
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
expect(
|
|
1278
|
+
assembled.pojo.connections.pg_ssl.connectionString,
|
|
1279
|
+
).toContain("sslmode=require");
|
|
1280
|
+
} finally {
|
|
1281
|
+
if (previousPgSslMode === undefined) {
|
|
1282
|
+
delete process.env.PGSSLMODE;
|
|
1283
|
+
} else {
|
|
1284
|
+
process.env.PGSSLMODE = previousPgSslMode;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it("should use project-root-relative file paths for project-level DuckDB", async () => {
|
|
1290
|
+
const insideCsvPath = path.join(testProjectPath, "inside.csv");
|
|
1291
|
+
await fs.writeFile(insideCsvPath, "id\n1\n");
|
|
1292
|
+
|
|
1293
|
+
const { malloyConnections } = await createProjectConnections(
|
|
1294
|
+
[
|
|
1295
|
+
{
|
|
1296
|
+
name: "project_scoped_duckdb",
|
|
1297
|
+
type: "duckdb",
|
|
1298
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1299
|
+
},
|
|
1300
|
+
],
|
|
1301
|
+
testProjectPath,
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
const connection = malloyConnections.get(
|
|
1305
|
+
"project_scoped_duckdb",
|
|
1306
|
+
) as DuckDBConnection;
|
|
1307
|
+
createdConnections.push(connection);
|
|
1308
|
+
|
|
1309
|
+
const assembled = assembleProjectConnections(
|
|
1310
|
+
[
|
|
1311
|
+
{
|
|
1312
|
+
name: "project_scoped_duckdb",
|
|
1313
|
+
type: "duckdb",
|
|
1314
|
+
duckdbConnection: { attachedDatabases: [] },
|
|
1315
|
+
},
|
|
1316
|
+
],
|
|
1317
|
+
testProjectPath,
|
|
1318
|
+
);
|
|
1319
|
+
expect(
|
|
1320
|
+
assembled.pojo.connections.project_scoped_duckdb
|
|
1321
|
+
.workingDirectory,
|
|
1322
|
+
).toBeUndefined();
|
|
1323
|
+
expect(
|
|
1324
|
+
assembled.pojo.connections.project_scoped_duckdb.securityPolicy,
|
|
1325
|
+
).toBeUndefined();
|
|
1326
|
+
|
|
1327
|
+
await expect(
|
|
1328
|
+
connection.runSQL("SELECT * FROM read_csv_auto('inside.csv')"),
|
|
1329
|
+
).resolves.toBeDefined();
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it("should keep external access available for federated DuckDB entries", () => {
|
|
1333
|
+
const assembled = assembleProjectConnections(
|
|
1334
|
+
[
|
|
1335
|
+
{
|
|
1336
|
+
name: "federated_duckdb",
|
|
1337
|
+
type: "duckdb",
|
|
1338
|
+
duckdbConnection: {
|
|
1339
|
+
attachedDatabases: [
|
|
1340
|
+
{
|
|
1341
|
+
name: "pg",
|
|
1342
|
+
type: "postgres",
|
|
1343
|
+
postgresConnection: {
|
|
1344
|
+
host: "localhost",
|
|
1345
|
+
port: 5432,
|
|
1346
|
+
userName: "user",
|
|
1347
|
+
password: "pass",
|
|
1348
|
+
databaseName: "db",
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1351
|
+
],
|
|
1352
|
+
},
|
|
1353
|
+
},
|
|
1354
|
+
],
|
|
1355
|
+
testProjectPath,
|
|
1356
|
+
);
|
|
1357
|
+
|
|
1358
|
+
const entry = assembled.pojo.connections.federated_duckdb;
|
|
1359
|
+
expect(entry.securityPolicy).toBeUndefined();
|
|
1360
|
+
expect(entry.enableExternalAccess).toBeUndefined();
|
|
1361
|
+
expect(entry.allowedDirectories).toBeUndefined();
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
it("should keep external access available for MotherDuck entries", () => {
|
|
1365
|
+
const assembled = assembleProjectConnections(
|
|
1366
|
+
[
|
|
1367
|
+
{
|
|
1368
|
+
name: "md",
|
|
1369
|
+
type: "motherduck",
|
|
1370
|
+
motherduckConnection: {
|
|
1371
|
+
accessToken: "token",
|
|
1372
|
+
database: "db",
|
|
1373
|
+
},
|
|
1374
|
+
},
|
|
1375
|
+
],
|
|
1376
|
+
testProjectPath,
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
const entry = assembled.pojo.connections.md;
|
|
1380
|
+
expect(entry.securityPolicy).toBeUndefined();
|
|
1381
|
+
expect(entry.enableExternalAccess).toBeUndefined();
|
|
1382
|
+
expect(entry.allowedDirectories).toBeUndefined();
|
|
1137
1383
|
});
|
|
1138
1384
|
|
|
1139
1385
|
it("should handle already attached database gracefully", async () => {
|