@malloy-publisher/server 0.0.242 → 0.0.243
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 +13 -7
- package/dist/server.mjs +84 -36
- package/package.json +1 -1
package/dist/app/api-doc.yaml
CHANGED
|
@@ -4141,25 +4141,31 @@ components:
|
|
|
4141
4141
|
type: string
|
|
4142
4142
|
description: Snowflake username for authentication
|
|
4143
4143
|
password:
|
|
4144
|
-
type: string
|
|
4144
|
+
type: ["string", "null"]
|
|
4145
4145
|
description: Snowflake password for authentication
|
|
4146
4146
|
privateKey:
|
|
4147
4147
|
type: string
|
|
4148
4148
|
description: Snowflake private key for authentication
|
|
4149
4149
|
privateKeyPass:
|
|
4150
|
-
type: string
|
|
4150
|
+
type: ["string", "null"]
|
|
4151
4151
|
description: Passphrase for the Snowflake private key
|
|
4152
4152
|
warehouse:
|
|
4153
4153
|
type: string
|
|
4154
4154
|
description: Snowflake warehouse name
|
|
4155
4155
|
database:
|
|
4156
|
-
type: string
|
|
4157
|
-
description:
|
|
4156
|
+
type: ["string", "null"]
|
|
4157
|
+
description:
|
|
4158
|
+
Snowflake database name. Omitted or null connects with no current
|
|
4159
|
+
database, so names must be fully qualified as
|
|
4160
|
+
DATABASE.SCHEMA.TABLE and schema discovery spans the account.
|
|
4158
4161
|
schema:
|
|
4159
|
-
type: string
|
|
4160
|
-
description:
|
|
4162
|
+
type: ["string", "null"]
|
|
4163
|
+
description:
|
|
4164
|
+
Snowflake schema name. Sets the session default schema and forms part
|
|
4165
|
+
of the connection's identity, so it determines what a materialization
|
|
4166
|
+
build runs as. It does not restrict which schemas are discoverable.
|
|
4161
4167
|
role:
|
|
4162
|
-
type: string
|
|
4168
|
+
type: ["string", "null"]
|
|
4163
4169
|
description: Snowflake role name
|
|
4164
4170
|
responseTimeoutMilliseconds:
|
|
4165
4171
|
type: integer
|
package/dist/server.mjs
CHANGED
|
@@ -265609,6 +265609,9 @@ var PROXIED_SSLMODES = [
|
|
|
265609
265609
|
"verify-full"
|
|
265610
265610
|
];
|
|
265611
265611
|
var PUBLISHER_DUCKDB_API_FIELDS = new Set(["attachedDatabases"]);
|
|
265612
|
+
function nullToUndefined(value) {
|
|
265613
|
+
return value ?? undefined;
|
|
265614
|
+
}
|
|
265612
265615
|
function normalizeSnowflakePrivateKey(privateKey) {
|
|
265613
265616
|
let privateKeyContent = privateKey.trim();
|
|
265614
265617
|
if (!privateKeyContent.includes(`
|
|
@@ -266129,13 +266132,13 @@ function assembleEnvironmentConnections(connections = [], environmentPath = "")
|
|
|
266129
266132
|
is: "snowflake",
|
|
266130
266133
|
account: connection.snowflakeConnection?.account,
|
|
266131
266134
|
username: connection.snowflakeConnection?.username,
|
|
266132
|
-
password: connection.snowflakeConnection?.password,
|
|
266135
|
+
password: nullToUndefined(connection.snowflakeConnection?.password),
|
|
266133
266136
|
privateKey: connection.snowflakeConnection?.privateKey ? normalizeSnowflakePrivateKey(connection.snowflakeConnection.privateKey) : undefined,
|
|
266134
|
-
privateKeyPass: connection.snowflakeConnection?.privateKeyPass,
|
|
266137
|
+
privateKeyPass: nullToUndefined(connection.snowflakeConnection?.privateKeyPass),
|
|
266135
266138
|
warehouse: connection.snowflakeConnection?.warehouse,
|
|
266136
|
-
database: connection.snowflakeConnection?.database,
|
|
266137
|
-
schema: connection.snowflakeConnection?.schema,
|
|
266138
|
-
role: connection.snowflakeConnection?.role,
|
|
266139
|
+
database: nullToUndefined(connection.snowflakeConnection?.database),
|
|
266140
|
+
schema: nullToUndefined(connection.snowflakeConnection?.schema),
|
|
266141
|
+
role: nullToUndefined(connection.snowflakeConnection?.role),
|
|
266139
266142
|
timeoutMs: connection.snowflakeConnection?.responseTimeoutMilliseconds,
|
|
266140
266143
|
poolMin: 1,
|
|
266141
266144
|
poolMax: 20
|
|
@@ -266657,32 +266660,44 @@ async function federateSnowflake(connection, config) {
|
|
|
266657
266660
|
if (!sf) {
|
|
266658
266661
|
throw new Error(`Snowflake connection configuration missing for: ${config.name}`);
|
|
266659
266662
|
}
|
|
266660
|
-
const
|
|
266663
|
+
for (const [field, value] of Object.entries({
|
|
266661
266664
|
account: sf.account,
|
|
266662
|
-
username: sf.username
|
|
266663
|
-
|
|
266664
|
-
};
|
|
266665
|
-
for (const [field, value] of Object.entries(required)) {
|
|
266665
|
+
username: sf.username
|
|
266666
|
+
})) {
|
|
266666
266667
|
if (!value) {
|
|
266667
266668
|
throw new Error(`Snowflake ${field} is required for: ${config.name}`);
|
|
266668
266669
|
}
|
|
266669
266670
|
}
|
|
266671
|
+
const usesKeyPair = !!sf.privateKey;
|
|
266672
|
+
if (!usesKeyPair && !sf.password) {
|
|
266673
|
+
throw new Error(`Snowflake privateKey or password is required for: ${config.name}`);
|
|
266674
|
+
}
|
|
266670
266675
|
await installAndLoadExtension(connection, "snowflake", true);
|
|
266671
266676
|
const params = {
|
|
266672
266677
|
account: escapeSQL(sf.account || ""),
|
|
266673
266678
|
user: escapeSQL(sf.username || ""),
|
|
266674
|
-
password: escapeSQL(sf.password
|
|
266679
|
+
password: sf.password ? escapeSQL(sf.password) : undefined,
|
|
266680
|
+
privateKey: sf.privateKey ? escapeSQL(normalizeSnowflakePrivateKey(sf.privateKey)) : undefined,
|
|
266681
|
+
privateKeyPass: sf.privateKeyPass ? escapeSQL(sf.privateKeyPass) : undefined,
|
|
266675
266682
|
database: sf.database ? escapeSQL(sf.database) : undefined,
|
|
266676
|
-
warehouse: sf.warehouse ? escapeSQL(sf.warehouse) : undefined
|
|
266683
|
+
warehouse: sf.warehouse ? escapeSQL(sf.warehouse) : undefined,
|
|
266684
|
+
schema: sf.schema ? escapeSQL(sf.schema) : undefined,
|
|
266685
|
+
role: sf.role ? escapeSQL(sf.role) : undefined
|
|
266677
266686
|
};
|
|
266678
266687
|
const secretName = sanitizeSecretName(`snowflake_${config.name}`);
|
|
266679
266688
|
const secretLines = [
|
|
266680
266689
|
` TYPE snowflake`,
|
|
266681
266690
|
` ACCOUNT '${params.account}'`,
|
|
266682
266691
|
` USER '${params.user}'`,
|
|
266683
|
-
|
|
266692
|
+
...usesKeyPair ? [
|
|
266693
|
+
` AUTH_TYPE 'key_pair'`,
|
|
266694
|
+
` PRIVATE_KEY '${params.privateKey}'`,
|
|
266695
|
+
...params.privateKeyPass ? [` PRIVATE_KEY_PASSWORD '${params.privateKeyPass}'`] : []
|
|
266696
|
+
] : [` PASSWORD '${params.password}'`],
|
|
266684
266697
|
...params.database ? [` DATABASE '${params.database}'`] : [],
|
|
266685
|
-
...params.warehouse ? [` WAREHOUSE '${params.warehouse}'`] : []
|
|
266698
|
+
...params.warehouse ? [` WAREHOUSE '${params.warehouse}'`] : [],
|
|
266699
|
+
...params.schema ? [` SCHEMA '${params.schema}'`] : [],
|
|
266700
|
+
...params.role ? [` ROLE '${params.role}'`] : []
|
|
266686
266701
|
];
|
|
266687
266702
|
await connection.runSQL(`CREATE OR REPLACE SECRET ${secretName} (
|
|
266688
266703
|
${secretLines.join(`,
|
|
@@ -267004,7 +267019,7 @@ function entryToDuckDBOptions(name, entry, workingDirectory) {
|
|
|
267004
267019
|
return { ...removeUndefined(rest), name };
|
|
267005
267020
|
}
|
|
267006
267021
|
function removeUndefined(value) {
|
|
267007
|
-
return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined));
|
|
267022
|
+
return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined && fieldValue !== null));
|
|
267008
267023
|
}
|
|
267009
267024
|
function buildSnowflakePrivateKeyConnection(metadata) {
|
|
267010
267025
|
const name = metadata.apiConnection.name;
|
|
@@ -267905,6 +267920,52 @@ async function getSchemasForMySQL(connection) {
|
|
|
267905
267920
|
}
|
|
267906
267921
|
];
|
|
267907
267922
|
}
|
|
267923
|
+
var SNOWFLAKE_SYSTEM_DATABASES = new Set([
|
|
267924
|
+
"SNOWFLAKE",
|
|
267925
|
+
"SNOWFLAKE_SAMPLE_DATA"
|
|
267926
|
+
]);
|
|
267927
|
+
async function listSnowflakeSchemasInDatabase(connection, malloyConnection, database, schema) {
|
|
267928
|
+
const filters = [
|
|
267929
|
+
`CATALOG_NAME = '${sqlLiteral(database, connection.type)}'`
|
|
267930
|
+
];
|
|
267931
|
+
if (schema) {
|
|
267932
|
+
filters.push(`SCHEMA_NAME = '${sqlLiteral(schema, connection.type)}'`);
|
|
267933
|
+
}
|
|
267934
|
+
assertSafeSqlIdentifier(database, "database name");
|
|
267935
|
+
const result = await runIntrospectionSQL(malloyConnection, `SELECT CATALOG_NAME, SCHEMA_NAME, SCHEMA_OWNER FROM ${database}.INFORMATION_SCHEMA.SCHEMATA WHERE ${filters.join(" AND ")} ORDER BY SCHEMA_NAME`);
|
|
267936
|
+
return standardizeRunSQLResult2(result).map((row) => {
|
|
267937
|
+
const r = row;
|
|
267938
|
+
return {
|
|
267939
|
+
catalogName: String(r.CATALOG_NAME ?? r.catalog_name ?? ""),
|
|
267940
|
+
schemaName: String(r.SCHEMA_NAME ?? r.schema_name ?? ""),
|
|
267941
|
+
owner: String(r.SCHEMA_OWNER ?? r.schema_owner ?? "")
|
|
267942
|
+
};
|
|
267943
|
+
});
|
|
267944
|
+
}
|
|
267945
|
+
var SNOWFLAKE_SHOW_ROW_LIMIT = 1e4;
|
|
267946
|
+
async function listSnowflakeSchemasInAccount(malloyConnection) {
|
|
267947
|
+
const result = await runIntrospectionSQL(malloyConnection, `SHOW SCHEMAS IN ACCOUNT LIMIT ${SNOWFLAKE_SHOW_ROW_LIMIT}`);
|
|
267948
|
+
const returnedRows = standardizeRunSQLResult2(result);
|
|
267949
|
+
if (returnedRows.length >= SNOWFLAKE_SHOW_ROW_LIMIT) {
|
|
267950
|
+
logger.warn("Snowflake account-wide schema listing hit the SHOW row limit; the schema list is incomplete and some tables will not be discoverable", {
|
|
267951
|
+
rowLimit: SNOWFLAKE_SHOW_ROW_LIMIT,
|
|
267952
|
+
returnedRows: returnedRows.length
|
|
267953
|
+
});
|
|
267954
|
+
}
|
|
267955
|
+
const parsed = returnedRows.map((row) => {
|
|
267956
|
+
const r = row;
|
|
267957
|
+
return {
|
|
267958
|
+
catalogName: String(r.database_name ?? r.DATABASE_NAME ?? ""),
|
|
267959
|
+
schemaName: String(r.name ?? r.NAME ?? ""),
|
|
267960
|
+
owner: String(r.owner ?? r.OWNER ?? "")
|
|
267961
|
+
};
|
|
267962
|
+
});
|
|
267963
|
+
const usable = parsed.filter((r) => r.catalogName && r.schemaName);
|
|
267964
|
+
if (usable.length < parsed.length) {
|
|
267965
|
+
logger.warn("Dropped Snowflake schema rows missing a database or schema name; the schema list is incomplete", { dropped: parsed.length - usable.length, returned: parsed.length });
|
|
267966
|
+
}
|
|
267967
|
+
return usable.sort((a, b) => a.catalogName.localeCompare(b.catalogName) || a.schemaName.localeCompare(b.schemaName));
|
|
267968
|
+
}
|
|
267908
267969
|
async function getSchemasForSnowflake(connection, malloyConnection) {
|
|
267909
267970
|
if (!connection.snowflakeConnection) {
|
|
267910
267971
|
throw new Error("Snowflake connection is required");
|
|
@@ -267912,28 +267973,15 @@ async function getSchemasForSnowflake(connection, malloyConnection) {
|
|
|
267912
267973
|
try {
|
|
267913
267974
|
const database = connection.snowflakeConnection.database;
|
|
267914
267975
|
const schema = connection.snowflakeConnection.schema;
|
|
267915
|
-
const
|
|
267916
|
-
|
|
267917
|
-
|
|
267918
|
-
|
|
267919
|
-
|
|
267920
|
-
|
|
267921
|
-
}
|
|
267922
|
-
const whereClause = filters.length > 0 ? `WHERE ${filters.join(" AND ")}` : "";
|
|
267923
|
-
const result = await runIntrospectionSQL(malloyConnection, `SELECT CATALOG_NAME, SCHEMA_NAME, SCHEMA_OWNER FROM ${database ? `${database}.` : ""}INFORMATION_SCHEMA.SCHEMATA ${whereClause} ORDER BY SCHEMA_NAME`);
|
|
267924
|
-
const rows = standardizeRunSQLResult2(result);
|
|
267925
|
-
return rows.map((row) => {
|
|
267926
|
-
const typedRow = row;
|
|
267927
|
-
const catalogName = String(typedRow.CATALOG_NAME ?? typedRow.catalog_name ?? "");
|
|
267928
|
-
const schemaName = String(typedRow.SCHEMA_NAME ?? typedRow.schema_name ?? "");
|
|
267929
|
-
const owner = String(typedRow.SCHEMA_OWNER ?? typedRow.schema_owner ?? "");
|
|
267930
|
-
return {
|
|
267931
|
-
name: `${catalogName}.${schemaName}`,
|
|
267932
|
-
isHidden: ["SNOWFLAKE", ""].includes(owner) || schemaName === "INFORMATION_SCHEMA",
|
|
267933
|
-
isDefault: schema ? schemaName === schema : false
|
|
267934
|
-
};
|
|
267935
|
-
});
|
|
267976
|
+
const rows = database ? await listSnowflakeSchemasInDatabase(connection, malloyConnection, database, schema) : await listSnowflakeSchemasInAccount(malloyConnection);
|
|
267977
|
+
return rows.map(({ catalogName, schemaName, owner }) => ({
|
|
267978
|
+
name: `${catalogName}.${schemaName}`,
|
|
267979
|
+
isHidden: owner === "SNOWFLAKE" || Boolean(database) && owner === "" || schemaName === "INFORMATION_SCHEMA" || !database && SNOWFLAKE_SYSTEM_DATABASES.has(catalogName),
|
|
267980
|
+
isDefault: Boolean(database) && Boolean(schema) && schemaName === schema
|
|
267981
|
+
}));
|
|
267936
267982
|
} catch (error) {
|
|
267983
|
+
if (error instanceof BadRequestError)
|
|
267984
|
+
throw error;
|
|
267937
267985
|
logger.error(`Error getting schemas for Snowflake connection ${connection.name}`, { error });
|
|
267938
267986
|
throw new Error(`Failed to get schemas for Snowflake connection ${connection.name}: ${error.message}`);
|
|
267939
267987
|
}
|