@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.
@@ -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: Snowflake database name
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: Snowflake schema name
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 required = {
266663
+ for (const [field, value] of Object.entries({
266661
266664
  account: sf.account,
266662
- username: sf.username,
266663
- password: sf.password
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
- ` PASSWORD '${params.password}'`,
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 filters = [];
267916
- if (database) {
267917
- filters.push(`CATALOG_NAME = '${sqlLiteral(database, connection.type)}'`);
267918
- }
267919
- if (schema) {
267920
- filters.push(`SCHEMA_NAME = '${sqlLiteral(schema, connection.type)}'`);
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@malloy-publisher/server",
3
3
  "description": "Malloy Publisher Server",
4
- "version": "0.0.242",
4
+ "version": "0.0.243",
5
5
  "main": "dist/server.mjs",
6
6
  "bin": {
7
7
  "malloy-publisher": "dist/server.mjs"