@malloy-publisher/server 0.9.11 → 0.9.12

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.
Files changed (24) hide show
  1. package/dist/app/api-doc.yaml +73 -4
  2. package/dist/app/assets/EnvironmentPage-BFcCRfvE.js +1 -0
  3. package/dist/app/assets/HomePage-C0LnSonK.js +1 -0
  4. package/dist/app/assets/{MainPage-BpE_5WuW.js → MainPage-D3EM60u0.js} +1 -1
  5. package/dist/app/assets/{ModelPage-ftyDI1tw.js → ModelPage-B1wvZV7r.js} +2 -2
  6. package/dist/app/assets/{PackagePage-QGnFwlHu.js → PackagePage-QZ59dwip.js} +1 -1
  7. package/dist/app/assets/{RenderedResult-Cmx8sX8d.es-du-Qe1x0.js → RenderedResult-CDiXJAh9.es-C9BYYEfH.js} +1 -1
  8. package/dist/app/assets/{RouteError-zM4SSZaU.js → RouteError-v8LP_Q9x.js} +1 -1
  9. package/dist/app/assets/{ThemeEditorPage-DhZJXGJm.js → ThemeEditorPage-B5l2nzAq.js} +1 -1
  10. package/dist/app/assets/{ToggleButtonGroup-TKxhg0Mg.js → ToggleButtonGroup-C6Lespxy.js} +1 -1
  11. package/dist/app/assets/{documentRoutes-D4TbSIi5.js → documentRoutes-xv0fP4cS.js} +1 -1
  12. package/dist/app/assets/{editPageMounted-0kzl2sgg.js → editPageMounted-COiBq84S.js} +2 -2
  13. package/dist/app/assets/{index-gQEuz0Cg.js → index-5On9Gkpl.js} +87 -87
  14. package/dist/app/assets/{index-Bzi4YH8Y.js → index-BG7MWz9o.js} +1 -1
  15. package/dist/app/assets/{index-B-qJVEXn.js → index-Dynd6zeI.js} +1 -1
  16. package/dist/app/assets/{index-CS4mmuDD.js → index-mIetEOsB.js} +1 -1
  17. package/dist/app/assets/{index.es-B3P9tIm3.js → index.es-FZZk_rEa.js} +1 -1
  18. package/dist/app/index.html +1 -1
  19. package/dist/package_load_worker.mjs +105 -8
  20. package/dist/server.mjs +242 -23
  21. package/dist/sshcrypto-cqb52edz.node +0 -0
  22. package/package.json +2 -1
  23. package/dist/app/assets/EnvironmentPage-tIGzymgb.js +0 -1
  24. package/dist/app/assets/HomePage-7QkNvQ_3.js +0 -1
package/dist/server.mjs CHANGED
@@ -205154,15 +205154,36 @@ function filesystemAccessFailure(error2) {
205154
205154
  function isMysqlConfigFault(code) {
205155
205155
  return typeof code === "string" && (MYSQL_CONFIG_CODES.has(code) || code.startsWith("HANDSHAKE_"));
205156
205156
  }
205157
- function isConnectionFailure(error2) {
205158
- let current = error2;
205157
+ function isSqlserverConnectTimeout(error2, code) {
205158
+ return code === "ETIMEOUT" && error2.name === "ConnectionError";
205159
+ }
205160
+ function wrappedError(error2) {
205161
+ if (error2.cause instanceof Error)
205162
+ return error2.cause;
205163
+ return error2.originalError;
205164
+ }
205165
+ function causeChainHasTlsVerificationFailure(error2) {
205166
+ let current = wrappedError(error2);
205159
205167
  for (let depth = 0;depth < 8 && current instanceof Error; depth++) {
205168
+ const { code } = current;
205169
+ if (typeof code === "string" && TLS_VERIFICATION_CODES.has(code)) {
205170
+ return true;
205171
+ }
205172
+ current = wrappedError(current);
205173
+ }
205174
+ return false;
205175
+ }
205176
+ function isConnectionFailure(error2) {
205177
+ for (const current of causeChain(error2)) {
205160
205178
  const { code, fatal } = current;
205161
205179
  if (typeof code === "string") {
205162
205180
  if (NODE_CONNECTION_CODES.has(code))
205163
205181
  return true;
205164
205182
  if (POSTGRES_CONNECTION_SQLSTATE.test(code))
205165
205183
  return true;
205184
+ if (SQLSERVER_CONNECTION_CODES.has(code) || isSqlserverConnectTimeout(current, code)) {
205185
+ return !causeChainHasTlsVerificationFailure(current);
205186
+ }
205166
205187
  }
205167
205188
  if (fatal === true && !isMysqlConfigFault(code))
205168
205189
  return true;
@@ -205170,13 +205191,18 @@ function isConnectionFailure(error2) {
205170
205191
  if (CODELESS_CONNECTION_MESSAGES.some((re) => re.test(message))) {
205171
205192
  return true;
205172
205193
  }
205173
- current = current.cause;
205174
205194
  }
205175
205195
  return false;
205176
205196
  }
205177
- function isCredentialRejection(error2) {
205197
+ function* causeChain(error2) {
205178
205198
  let current = error2;
205179
205199
  for (let depth = 0;depth < 8 && current instanceof Error; depth++) {
205200
+ yield current;
205201
+ current = wrappedError(current);
205202
+ }
205203
+ }
205204
+ function isCredentialRejection(error2) {
205205
+ for (const current of causeChain(error2)) {
205180
205206
  const { code } = current;
205181
205207
  if (typeof code === "string" || typeof code === "number") {
205182
205208
  const value = String(code);
@@ -205184,12 +205210,13 @@ function isCredentialRejection(error2) {
205184
205210
  return true;
205185
205211
  if (MYSQL_AUTH_CODES.has(value))
205186
205212
  return true;
205213
+ if (SQLSERVER_AUTH_CODES.has(value))
205214
+ return true;
205187
205215
  if (SNOWFLAKE_AUTH_CODES.has(value))
205188
205216
  return true;
205189
205217
  }
205190
205218
  if (current.name === "BigQueryAuthenticationError")
205191
205219
  return true;
205192
- current = current.cause;
205193
205220
  }
205194
205221
  return false;
205195
205222
  }
@@ -205204,6 +205231,19 @@ function databaseAccessFailure(error2) {
205204
205231
  return new ConnectionFailedError(message);
205205
205232
  return;
205206
205233
  }
205234
+ function statementRejection(error2) {
205235
+ for (const current of causeChain(error2)) {
205236
+ const { code, severity } = current;
205237
+ if (severity === "FATAL" || severity === "PANIC")
205238
+ return;
205239
+ if (typeof code !== "string" || !POSTGRES_STATEMENT_SQLSTATES.has(code)) {
205240
+ continue;
205241
+ }
205242
+ const message = current.message;
205243
+ return code === POSTGRES_UNDEFINED_TABLE && POSTGRES_RELATION_MISSING.test(message) ? new TableNotFoundError(message) : new InvalidArgumentError(message);
205244
+ }
205245
+ return;
205246
+ }
205207
205247
  function describeFilesystemAccessFailure(errno) {
205208
205248
  const code = errno.code ?? "";
205209
205249
  const target = errno.path ? ` '${errno.path}'` : "";
@@ -205343,7 +205383,7 @@ function toQueryTextProblem(problem) {
205343
205383
  ...problem.at ? { at: { range: problem.at.range } } : {}
205344
205384
  };
205345
205385
  }
205346
- var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", GENERIC_UNREACHABLE_MESSAGE = "The database connection is down: the database could not be reached, so the query did not run.", GENERIC_AUTH_MESSAGE = "The database rejected the connection's credentials. Check the connection's user, password, key or token.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NODE_CONNECTION_CODES, POSTGRES_CONNECTION_SQLSTATE, CODELESS_CONNECTION_MESSAGES, MYSQL_CONFIG_CODES, POSTGRES_AUTH_SQLSTATE, MYSQL_AUTH_CODES, SNOWFLAKE_AUTH_CODES, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, InvalidModelPathError, RenderTagRefusedError, UnparseableTextError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, SkillNotFoundError, AgentNotFoundError, ConflictError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, ConnectionFailedError, UnconfiguredConnectionError, ConnectionPoolExhaustedError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, ChainedUpstreamMissingError, ChainedShapeNotCarriedError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteRollbackFailedError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PackageAdmissionRefusedError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
205386
+ var GENERIC_INTERNAL_MESSAGE = "Internal server error.", GENERIC_UPSTREAM_MESSAGE = "Upstream connection error.", GENERIC_UNREACHABLE_MESSAGE = "The database connection is down: the database could not be reached, so the query did not run.", GENERIC_AUTH_MESSAGE = "The database rejected the connection's credentials. Check the connection's user, password, key or token.", MAX_LOGGED_DETAIL_CHARS = 2000, FILESYSTEM_ACCESS_DESCRIPTIONS, NODE_CONNECTION_CODES, POSTGRES_CONNECTION_SQLSTATE, CODELESS_CONNECTION_MESSAGES, MYSQL_CONFIG_CODES, SQLSERVER_CONNECTION_CODES, TLS_VERIFICATION_CODES, POSTGRES_AUTH_SQLSTATE, MYSQL_AUTH_CODES, SQLSERVER_AUTH_CODES, SNOWFLAKE_AUTH_CODES, POSTGRES_STATEMENT_SQLSTATES, POSTGRES_UNDEFINED_TABLE = "42P01", POSTGRES_RELATION_MISSING, NotImplementedError, BadRequestError, InvalidArgumentError, CompileRefusedError, InvalidModelPathError, RenderTagRefusedError, UnparseableTextError, EnvironmentNotFoundError, PackageNotFoundError, ModelNotFoundError, DashboardNotFoundError, SkillNotFoundError, AgentNotFoundError, ConflictError, ConnectionNotFoundError, TableNotFoundError, ConnectionError, ConnectionFailedError, UnconfiguredConnectionError, ConnectionPoolExhaustedError, DestinationNotFoundError, ConnectionAuthError, UnsupportedCatalogFormatError, ModelCompilationError, PackageManifestError, MaterializationEligibilityError, ChainedUpstreamMissingError, ChainedShapeNotCarriedError, PublisherConfigError, ServerConfigurationError, FrozenConfigError, AccessDeniedError, QueryCompileError, NotQueryableError, OffSurfaceError, MaterializationNotFoundError, MaterializationConflictError, WriteConflictError, WriteRolledBackError, WriteRollbackFailedError, WriteVerifyError, InvalidStateTransitionError, ServiceUnavailableError, PackageAdmissionRefusedError, PayloadTooLargeError, ResponseUnserializableError, QueryTimeoutError;
205347
205387
  var init_errors = __esm(() => {
205348
205388
  init_constants();
205349
205389
  init_logger();
@@ -205369,7 +205409,8 @@ var init_errors = __esm(() => {
205369
205409
  /^(Error: )?Connection lost: The server closed the connection\.$/,
205370
205410
  new RegExp(`^(Error: )?(connect|read|write|getaddrinfo) (${[
205371
205411
  ...NODE_CONNECTION_CODES
205372
- ].join("|")})\\b`)
205412
+ ].join("|")})\\b`),
205413
+ /^Failed to connect to \S+( \(redirected from \S+\))?( in \d+ms| - (?!.*certificate).+)$/i
205373
205414
  ];
205374
205415
  MYSQL_CONFIG_CODES = new Set([
205375
205416
  "ER_ACCESS_DENIED_ERROR",
@@ -205378,9 +205419,46 @@ var init_errors = __esm(() => {
205378
205419
  "AUTH_SWITCH_PLUGIN_ERROR",
205379
205420
  "MYSQL_CLEAR_PASSWORD_NOT_ENABLED"
205380
205421
  ]);
205422
+ SQLSERVER_CONNECTION_CODES = new Set(["ESOCKET", "EINSTLOOKUP"]);
205423
+ TLS_VERIFICATION_CODES = new Set([
205424
+ "CERT_HAS_EXPIRED",
205425
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
205426
+ "ERR_TLS_CERT_ALTNAME_INVALID",
205427
+ "SELF_SIGNED_CERT_IN_CHAIN",
205428
+ "UNABLE_TO_GET_ISSUER_CERT",
205429
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
205430
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE"
205431
+ ]);
205381
205432
  POSTGRES_AUTH_SQLSTATE = /^28[0-9A-Z]{3}$/;
205382
205433
  MYSQL_AUTH_CODES = new Set(["ER_ACCESS_DENIED_ERROR"]);
205434
+ SQLSERVER_AUTH_CODES = new Set(["ELOGIN"]);
205383
205435
  SNOWFLAKE_AUTH_CODES = new Set(["390100", "390144", "390195", "390318"]);
205436
+ POSTGRES_STATEMENT_SQLSTATES = new Set([
205437
+ "42601",
205438
+ "42703",
205439
+ "42P01",
205440
+ "42883",
205441
+ "42704",
205442
+ "42P02",
205443
+ "42702",
205444
+ "42725",
205445
+ "42P09",
205446
+ "42P10",
205447
+ "42804",
205448
+ "42846",
205449
+ "42803",
205450
+ "42P20",
205451
+ "42P18",
205452
+ "42P19",
205453
+ "42712",
205454
+ "42701",
205455
+ "42602",
205456
+ "42622",
205457
+ "42809",
205458
+ "42P21",
205459
+ "42P22"
205460
+ ]);
205461
+ POSTGRES_RELATION_MISSING = /^relation ".*" does not exist$/;
205384
205462
  NotImplementedError = class NotImplementedError extends Error {
205385
205463
  constructor(message) {
205386
205464
  super(message);
@@ -262101,6 +262179,11 @@ var require_utils93 = __commonJS((exports, module) => {
262101
262179
  };
262102
262180
  });
262103
262181
 
262182
+ // ../../node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
262183
+ var require_sshcrypto = __commonJS((exports, module) => {
262184
+ module.exports = __require("./sshcrypto-cqb52edz.node");
262185
+ });
262186
+
262104
262187
  // ../../node_modules/ssh2/lib/protocol/crypto/poly1305.js
262105
262188
  var require_poly1305 = __commonJS((exports, module) => {
262106
262189
  var __dirname = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto", __filename = "/home/runner/work/publisher/publisher/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
@@ -262587,7 +262670,7 @@ var require_crypto = __commonJS((exports, module) => {
262587
262670
  var ChaChaPolyDecipher;
262588
262671
  var GenericDecipher;
262589
262672
  try {
262590
- binding = (()=>{throw new Error("Cannot require module "+"./crypto/build/Release/sshcrypto.node");})();
262673
+ binding = require_sshcrypto();
262591
262674
  ({
262592
262675
  AESGCMCipher,
262593
262676
  ChaChaPolyCipher,
@@ -338983,6 +339066,7 @@ import {
338983
339066
  buildPoolOptions,
338984
339067
  SnowflakeConnection
338985
339068
  } from "@malloydata/db-snowflake";
339069
+ import"@malloydata/db-sqlserver";
338986
339070
  import"@malloydata/db-trino";
338987
339071
  import {
338988
339072
  contextOverlay,
@@ -344267,6 +344351,7 @@ var DOUBLED_ESCAPE_DIALECTS = new Set([
344267
344351
  "motherduck",
344268
344352
  "postgres",
344269
344353
  "publisher",
344354
+ "sqlserver",
344270
344355
  "trino"
344271
344356
  ]);
344272
344357
  function sqlLiteral(value, connectionType) {
@@ -345119,6 +345204,13 @@ function getStaticConnectionAttributes(type) {
345119
345204
  canPersist: true,
345120
345205
  canStream: false
345121
345206
  };
345207
+ case "sqlserver":
345208
+ return {
345209
+ dialectName: "sqlserver",
345210
+ isPool: true,
345211
+ canPersist: false,
345212
+ canStream: true
345213
+ };
345122
345214
  case "duckdb":
345123
345215
  case "motherduck":
345124
345216
  case "ducklake":
@@ -345265,6 +345357,20 @@ function validateConnectionShape(connection) {
345265
345357
  case "postgres":
345266
345358
  case "mysql":
345267
345359
  break;
345360
+ case "sqlserver": {
345361
+ const sqlserver = connection.sqlserverConnection;
345362
+ if (!sqlserver) {
345363
+ throw new Error("SQL Server connection configuration is missing.");
345364
+ }
345365
+ const port = sqlserver.port;
345366
+ if (port !== undefined && port !== null && (typeof port !== "number" || !Number.isInteger(port) || port < 1 || port > 65535)) {
345367
+ throw new Error(`Connection '${connection.name}' has an invalid sqlserverConnection.port ` + `${JSON.stringify(port)} (expected an integer from 1 to 65535).`);
345368
+ }
345369
+ if (sqlserver.instanceName && sqlserver.port) {
345370
+ logger.warn(`Connection '${connection.name}' sets both sqlserverConnection.instanceName ` + `and port; the instance is located through SQL Browser and the port is not used.`);
345371
+ }
345372
+ break;
345373
+ }
345268
345374
  case "bigquery": {
345269
345375
  const bigquery = connection.bigqueryConnection;
345270
345376
  if (bigquery?.impersonateServiceAccount) {
@@ -345512,6 +345618,9 @@ function canonicalizeJsonValue(value) {
345512
345618
  }
345513
345619
  return sorted;
345514
345620
  }
345621
+ function blankAsUnset(value) {
345622
+ return value === "" ? undefined : value;
345623
+ }
345515
345624
  function assembleEnvironmentConnections(connections = [], environmentPath = "") {
345516
345625
  const pojo = { connections: {} };
345517
345626
  const metadata = new Map;
@@ -345574,6 +345683,22 @@ function assembleEnvironmentConnections(connections = [], environmentPath = "")
345574
345683
  };
345575
345684
  break;
345576
345685
  }
345686
+ case "sqlserver": {
345687
+ const sqlserver = connection.sqlserverConnection;
345688
+ pojo.connections[connection.name] = {
345689
+ is: "sqlserver",
345690
+ server: sqlserver?.server,
345691
+ port: sqlserver?.port,
345692
+ database: sqlserver?.database,
345693
+ user: sqlserver?.user,
345694
+ password: sqlserver?.password,
345695
+ encrypt: sqlserver?.encrypt,
345696
+ trustServerCertificate: sqlserver?.trustServerCertificate,
345697
+ hostNameInCertificate: blankAsUnset(sqlserver?.hostNameInCertificate),
345698
+ instanceName: blankAsUnset(sqlserver?.instanceName)
345699
+ };
345700
+ break;
345701
+ }
345577
345702
  case "bigquery": {
345578
345703
  const serviceAccountKey = parseServiceAccountKey(connection.bigqueryConnection?.serviceAccountKeyJson);
345579
345704
  const impersonateServiceAccount = connection.bigqueryConnection?.impersonateServiceAccount;
@@ -347507,6 +347632,16 @@ var MYSQL = {
347507
347632
  database: "scalar",
347508
347633
  user: "scalar"
347509
347634
  };
347635
+ var SQLSERVER = {
347636
+ server: "scalar",
347637
+ port: "scalar",
347638
+ database: "scalar",
347639
+ user: "scalar",
347640
+ encrypt: "scalar",
347641
+ trustServerCertificate: "scalar",
347642
+ hostNameInCertificate: "scalar",
347643
+ instanceName: "scalar"
347644
+ };
347510
347645
  var MOTHERDUCK = { database: "scalar" };
347511
347646
  var PUBLISHER = { connectionUri: "scalar" };
347512
347647
  var S3 = {
@@ -347572,6 +347707,7 @@ var PUBLIC_CONNECTION = {
347572
347707
  trinoConnection: TRINO,
347573
347708
  databricksConnection: DATABRICKS,
347574
347709
  mysqlConnection: MYSQL,
347710
+ sqlserverConnection: SQLSERVER,
347575
347711
  duckdbConnection: DUCKDB,
347576
347712
  motherduckConnection: MOTHERDUCK,
347577
347713
  ducklakeConnection: DUCKLAKE,
@@ -347585,6 +347721,7 @@ var PUBLIC_FIELDS_BY_SCHEMA = {
347585
347721
  TrinoConnection: Object.keys(TRINO),
347586
347722
  DatabricksConnection: Object.keys(DATABRICKS),
347587
347723
  MysqlConnection: Object.keys(MYSQL),
347724
+ SqlserverConnection: Object.keys(SQLSERVER),
347588
347725
  DuckdbConnection: Object.keys(DUCKDB),
347589
347726
  MotherDuckConnection: Object.keys(MOTHERDUCK),
347590
347727
  DucklakeConnection: Object.keys(DUCKLAKE),
@@ -348077,6 +348214,27 @@ async function getSchemasForPostgres(connection, malloyConnection) {
348077
348214
  throw new Error(`Failed to get schemas for Postgres connection ${connection.name}: ${error2.message}`, { cause: error2 });
348078
348215
  }
348079
348216
  }
348217
+ async function getSchemasForSQLServer(connection, malloyConnection) {
348218
+ if (!connection.sqlserverConnection) {
348219
+ throw new Error("SQL Server connection is required");
348220
+ }
348221
+ try {
348222
+ const result = await runIntrospectionSQL(malloyConnection, "SELECT DISTINCT table_schema AS schema_name FROM information_schema.tables ORDER BY table_schema");
348223
+ const rows = standardizeRunSQLResult2(result);
348224
+ return rows.map((row) => {
348225
+ const typedRow = row;
348226
+ const schemaName = String(typedRow.schema_name ?? typedRow.SCHEMA_NAME ?? "");
348227
+ return {
348228
+ name: schemaName,
348229
+ isHidden: false,
348230
+ isDefault: schemaName === "dbo"
348231
+ };
348232
+ });
348233
+ } catch (error2) {
348234
+ logger.error(`Error getting schemas for SQL Server connection ${connection.name}`, { error: error2 });
348235
+ throw new Error(`Failed to get schemas for SQL Server connection ${connection.name}: ${error2.message}`, { cause: error2 });
348236
+ }
348237
+ }
348080
348238
  async function getSchemasForMySQL(connection) {
348081
348239
  if (!connection.mysqlConnection) {
348082
348240
  throw new Error("Mysql connection is required");
@@ -348426,6 +348584,8 @@ async function getSchemasForConnection(connection, malloyConnection) {
348426
348584
  return getSchemasForPostgres(connection, malloyConnection);
348427
348585
  case "mysql":
348428
348586
  return getSchemasForMySQL(connection);
348587
+ case "sqlserver":
348588
+ return getSchemasForSQLServer(connection, malloyConnection);
348429
348589
  case "snowflake":
348430
348590
  return getSchemasForSnowflake(connection, malloyConnection);
348431
348591
  case "trino":
@@ -348613,6 +348773,8 @@ async function listTablesForSchema(connection, schemaName, malloyConnection, tab
348613
348773
  return listTablesForMySQL(connection, schemaName, malloyConnection, tableNames);
348614
348774
  case "postgres":
348615
348775
  return listTablesForPostgres(connection, schemaName, malloyConnection, tableNames);
348776
+ case "sqlserver":
348777
+ return listTablesForSQLServer(connection, schemaName, malloyConnection, tableNames);
348616
348778
  case "snowflake":
348617
348779
  return listTablesForSnowflake(connection, schemaName, malloyConnection, tableNames);
348618
348780
  case "trino":
@@ -348681,6 +348843,27 @@ async function listTablesForMySQL(connection, schemaName, malloyConnection, tabl
348681
348843
  throw new Error(`Failed to get tables for MySQL schema ${schemaName} in connection ${connection.name}: ${error2.message}`, { cause: error2 });
348682
348844
  }
348683
348845
  }
348846
+ function sqlserverNLiteral(value) {
348847
+ return `N'${sqlLiteral(value, "sqlserver")}'`;
348848
+ }
348849
+ function sqlserverInFilter(columnName, values) {
348850
+ if (!values || values.length === 0)
348851
+ return "";
348852
+ return `AND ${columnName} IN (${values.map(sqlserverNLiteral).join(", ")})`;
348853
+ }
348854
+ async function listTablesForSQLServer(connection, schemaName, malloyConnection, tableNames) {
348855
+ if (!connection.sqlserverConnection) {
348856
+ throw new Error("SQL Server connection is required");
348857
+ }
348858
+ try {
348859
+ const result = await runIntrospectionSQL(malloyConnection, `SELECT table_name, column_name, data_type FROM information_schema.columns WHERE table_schema = ${sqlserverNLiteral(schemaName)} ${sqlserverInFilter("table_name", tableNames)} ORDER BY table_name, ordinal_position`);
348860
+ const rows = standardizeRunSQLResult2(result);
348861
+ return groupColumnRowsIntoTables(rows, (t2) => `${schemaName}.${t2}`);
348862
+ } catch (error2) {
348863
+ logger.error(`Error getting tables for SQL Server schema ${schemaName} in connection ${connection.name}`, { error: error2 });
348864
+ throw new Error(`Failed to get tables for SQL Server schema ${schemaName} in connection ${connection.name}: ${error2.message}`, { cause: error2 });
348865
+ }
348866
+ }
348684
348867
  async function listTablesForPostgres(connection, schemaName, malloyConnection, tableNames) {
348685
348868
  if (!connection.postgresConnection) {
348686
348869
  throw new Error("Postgres connection is required");
@@ -349001,12 +349184,12 @@ function classifyDriverFailure(error2) {
349001
349184
  return error2;
349002
349185
  }
349003
349186
  const message = error2 instanceof Error ? error2.message : typeof error2 === "string" ? error2 : JSON.stringify(error2);
349004
- return databaseAccessFailure(error2) ?? driverErrorToPublisherError(message);
349187
+ return databaseAccessFailure(error2) ?? statementRejection(error2) ?? driverErrorToPublisherError(message);
349005
349188
  }
349006
349189
  function sqlRunFailure(error2) {
349007
349190
  if (error2 instanceof ConnectionError)
349008
349191
  return error2;
349009
- return databaseAccessFailure(error2) ?? new ConnectionError(error2.message);
349192
+ return databaseAccessFailure(error2) ?? statementRejection(error2) ?? new ConnectionError(error2.message);
349010
349193
  }
349011
349194
  async function withDatabaseAccessClassified(list) {
349012
349195
  try {
@@ -349096,8 +349279,11 @@ class ConnectionController {
349096
349279
  throw new TableNotFoundError(`Table ${tablePath} not found`);
349097
349280
  }
349098
349281
  if (typeof source === "string") {
349099
- const prefix = `Error fetching schema for ${tablePath}: `;
349100
- const accessFailure = source.startsWith(prefix) ? databaseAccessFailure(new Error(source.slice(prefix.length))) : undefined;
349282
+ if (source === `Table ${tablePath} not found`) {
349283
+ throw new TableNotFoundError(source);
349284
+ }
349285
+ const driverMessage = lookupFailureDriverMessage(source, tablePath);
349286
+ const accessFailure = driverMessage === undefined ? undefined : databaseAccessFailure(new Error(driverMessage));
349101
349287
  throw accessFailure ?? driverErrorToPublisherError(source);
349102
349288
  }
349103
349289
  return {
@@ -349376,6 +349562,16 @@ class ConnectionController {
349376
349562
  };
349377
349563
  }
349378
349564
  }
349565
+ function lookupFailureDriverMessage(source, tablePath) {
349566
+ const prefix = `Error fetching schema for ${tablePath}: `;
349567
+ if (source.startsWith(prefix))
349568
+ return source.slice(prefix.length);
349569
+ const sqlserverPrefix = `Error fetching schema for table ${tablePath}: `;
349570
+ if (source.startsWith(sqlserverPrefix)) {
349571
+ return source.slice(sqlserverPrefix.length).replace(/^\d+: /, "");
349572
+ }
349573
+ return;
349574
+ }
349379
349575
 
349380
349576
  // src/controller/report.controller.ts
349381
349577
  init_errors();
@@ -351499,7 +351695,7 @@ with both queries. Otherwise it is still \`NOT-RETURNED\` / \`LOW-RANK\`.
351499
351695
  Use when the entity was found and used, and the definition or the data behind it
351500
351696
  is wrong (bad grain, wrong join key, inverted filter). Owner: model. A doc whose
351501
351697
  factual claim the data contradicts (a population statement, a grain claim) is
351502
- also model-definition: the SQL may be right while the stated contract is false,
351698
+ also model-definition: the field may compute correctly while its doc is false,
351503
351699
  and an agent that trusts the doc answers wrongly without ever failing a query.
351504
351700
  Probe the claim before writing the issue.
351505
351701
 
@@ -352213,7 +352409,7 @@ trusting a name that looks familiar, and write down the mapping you used in
352213
352409
  |---|---|---|
352214
352410
  | \`prompt\`, \`query\`, \`input\`, \`nl_question\` | the question | \`query\` is the trap: in half of these it is the SQL or Malloy they ran, not the question |
352215
352411
  | \`answer\`, \`expected\`, \`output\`, \`ground_truth\` | a golden value, prose, or both mixed in one string | a paragraph under \`answer\` is criteria, not a value. Classify it, do not store it as a number |
352216
- | \`sql\`, \`malloy\`, \`gold_query\` | their derivation | run it (\`SKILL.md\` step 3). This is the field that upgrades a typed number into something checkable |
352412
+ | \`sql\`, \`malloy\`, \`gold_query\` | their derivation | Malloy: run it (\`SKILL.md\` step 3). SQL: keep it as evidence of how they got the number, but do not run it; derive the number in Malloy over the truth package instead. Either way, this is the field that upgrades a typed number into something checkable |
352217
352413
  | \`criteria\`, \`rubric\`, \`assertions\`, \`must_include\` | prose criteria | apply the query test in \`SKILL.md\` step 4 to EACH one, not to the field |
352218
352414
  | \`id\`, \`case_id\`, \`uuid\` | their id | keep it, prefixed. Two sets from one customer collide otherwise |
352219
352415
  | \`difficulty\`, \`category\`, \`tags\` | their own labels | keep as-is on the case for slicing. Never let one become \`split\` |
@@ -352376,7 +352572,7 @@ data with no question in front of them?* Reject:
352376
352572
 
352377
352573
  - a field that hard-codes this question's filter and serves no other question
352378
352574
  - this question's text, qid, or expected numbers in a doc, comment, or name
352379
- - a join copied from gold SQL that you have not probed as a real relationship
352575
+ - a join copied from a golden's query that you have not probed as a real relationship
352380
352576
 
352381
352577
  The golden is a hypothesis source. The data is still the verifier.
352382
352578
 
@@ -352385,12 +352581,16 @@ The golden is a hypothesis source. The data is still the verifier.
352385
352581
  Every structural claim needs a query you ran: join key, primary key, filter
352386
352582
  value existence, snapshot assumption, value space, cardinality.
352387
352583
 
352388
- \`\`\`sql
352389
- SELECT COUNT(*), COUNT(DISTINCT col) FROM t;
352390
- SELECT a.k, COUNT(*) FROM a JOIN b ON … GROUP BY 1 ORDER BY 2 DESC;
352391
- SELECT col, COUNT(*) FROM t GROUP BY 1 ORDER BY 2 DESC LIMIT 5;
352584
+ \`\`\`malloy
352585
+ run: t -> { aggregate: row_count is count(), distinct_keys is count(col) }
352586
+ run: b -> { group_by: k; aggregate: n is count(); order_by: n desc; limit: 5 }
352587
+ run: t -> { group_by: col; aggregate: n is count(); order_by: n desc; limit: 5 }
352392
352588
  \`\`\`
352393
352589
 
352590
+ Run probes in Malloy, against the raw tables (the truth package, or a bare
352591
+ \`connection.table()\` source), never as SQL. The second line is the join probe:
352592
+ any \`n\` above 1 means \`k\` is not a key on \`b\`, so a \`join_one\` on it fans out.
352593
+
352394
352594
  A false \`primary_key\` compiles and silently corrupts every aggregate. Of one
352395
352595
  pilot's 11 accepted edits, 4 of 5 wrong ones died to a single
352396
352596
  \`COUNT(*)\` vs \`COUNT(DISTINCT …)\` probe that was never run.
@@ -352735,7 +352935,7 @@ Output, exactly this shape:
352735
352935
  plainly the answer explained itself, and however the rubric is worded. Say
352736
352936
  in \`why\` that you are overriding a rubric clause, so the clause gets fixed.
352737
352937
  This rule exists because a set shipped one: a question asked for sales over
352738
- the company's season, the answer gave the meteorological window 25% lower
352938
+ the company's season, the answer gave the meteorological window 20% lower
352739
352939
  and said which window it used, and a clause granting \`near_match\` for a
352740
352940
  stated window kept a materially wrong answer out of the pass rate
352741
352941
  entirely -- the arm reported 100%.
@@ -381478,7 +381678,7 @@ var storageServeRoutingCounter = lazyCounter4("publisher_storage_serve_routing_t
381478
381678
  var storageTableRetainedCounter = lazyCounter4("publisher_storage_tables_retained_total", "Tables a FAILED run left in a storage= destination and deliberately did not " + "reclaim, because the source is refreshed incrementally and the name may be " + "the one it serves from. Label: destination. Not all of these are orphans — " + "a rebuild at a fresh generational name is, a seed on the live serving name " + "is not, and the manifest entry cannot separate them — so read this as an " + "upper bound on what is accumulating rather than a leak count. It is the " + "only accounting there is until reclaiming a destination exists, which is " + "why it is a counter and not just a log line: the question is a rate, not " + "whether it ever happened.");
381479
381679
  var storageBuildFailureCounter = lazyCounter4("publisher_storage_build_failures_total", "storage= build failures (federation/passthrough/attach/CTAS), distinct from " + "in-warehouse build failures. Labels: destination (connection name), " + "reason ('build_failed'|'billed_read_not_captured'). The second is the " + "expensive one: the warehouse read ran and was charged, and the rows could " + "not be captured — so a re-drive pays for it again. Worth alerting on " + "separately from a failure that costs only a retry.");
381480
381680
  var attributionSkippedCounter = lazyCounter4("publisher_storage_build_attribution_skipped_total", "storage= builds whose warehouse read went out UNATTRIBUTED while tagging was " + "on. Label: reason ('job_listing_unavailable'|'tag_failed'|" + "'read_row_not_found'|'read_row_ambiguous'|'cost_query_failed'). " + "The read still ran and the " + "build still succeeded — what was lost is the label in the customer's own " + "query history, and the cost on this side. Without this an operator who " + "turns tagging on and sees nothing has a single log line to go on.");
381481
- var eligibilityRefusedCounter = lazyCounter4("publisher_materialization_eligibility_refused_total", "materialization-eligibility refusals, both tiers. Label: reason " + "('free_parameter'|'given_in_persisted_query'|'dynamic_projection'|" + "'dynamic_join'|'dynamic_joined_where'|'partition_without_storage'|" + "'partition_column_unknown'|'partition_column_not_public'|" + "'merge_key_scope_unresolved'|'preaggregate_over_dynamic_source'|" + "'authorize'|'partition'|'not_duckdb_portable'|" + "'public_surface_unknown'). 'given' is retained on the enum for records " + "that carry it and is no longer produced: where a given sits decides the " + "outcome, so one reason covering every placement cannot be raised.");
381681
+ var eligibilityRefusedCounter = lazyCounter4("publisher_materialization_eligibility_refused_total", "materialization-eligibility refusals, both tiers. Label: reason " + "('free_parameter'|'given_in_persisted_query'|'dynamic_projection'|" + "'dynamic_join'|'dynamic_joined_where'|'partition_without_storage'|" + "'partition_column_unknown'|'partition_column_not_public'|" + "'merge_key_scope_unresolved'|'preaggregate_over_dynamic_source'|" + "'authorize'|'partition'|'not_duckdb_portable'|" + "'public_surface_unknown'|'read_only_dialect'). 'given' is retained on the enum for records " + "that carry it and is no longer produced: where a given sits decides the " + "outcome, so one reason covering every placement cannot be raised.");
381482
381682
  var serveShapeTierDropCounter = lazyCounter4("publisher_storage_serve_shape_tier_drop_total", "storage serve-shape compile escalations: a refinement tier failed to " + "compile and the riskiest category was dropped, or one or more lifted " + "entry points failed to compile and were dropped alone. Label: tier (the " + "failed tier index, 0=full; 'lifts' for dropped lifts).");
381483
381683
  var serveShapeTypeFallbackCounter = lazyCounter4("publisher_storage_serve_shape_type_fallback_total", "Captured DuckDB column types mapped to json in the serve shape (type " + "fidelity loss). Label: kind ('array'|'unrecognized').");
381484
381684
  var chainedStorageBuildCounter = lazyCounter4("publisher_storage_chained_build_total", "Chained storage= source builds (a source reading a storage-materialized " + "upstream). Label: outcome ('parent_reuse'|'inline_fallback'|" + "'strict_shape_fallback'|'strict_refused'|'infra_failure'). The " + "parent_reuse share is the headline signal for how far the " + "stack-on-the-parent path gets us vs recompute-from-raw.");
@@ -384511,7 +384711,16 @@ function assertMergeKeyScopeResolvable(persistSource, terms, annotationFields, w
384511
384711
  message: `${what} cannot be materialized: it declares 'merge_key=' and is ` + `scoped by a term this pass cannot express as a predicate over the ` + `stored table's own columns (${unscopable.map((term) => `'${term.code}'`).join(", ")}). An incremental refresh matches rows by the merge ` + `key, and the stored table holds every caller's rows, so the match ` + `must also carry the scoping columns or it would update rows ` + `belonging to other callers. Scope this source with a term over its ` + `own columns, or drop 'merge_key=' to refresh by watermark range.`
384512
384712
  });
384513
384713
  }
384714
+ var READ_ONLY_DIALECTS = new Set(["sqlserver"]);
384514
384715
  function assertColocatedPersistNotAuthorizeGated(persistSource, sourceName = persistSource.name, origin2 = "persist", gateOutcome, annotationFields = {}) {
384716
+ if (!annotationFields.storage && READ_ONLY_DIALECTS.has(persistSource.dialectName)) {
384717
+ recordEligibilityRefused("read_only_dialect");
384718
+ const what2 = origin2 === "preaggregate" ? `Pre-aggregation rollup '${sourceName}'` : `Source '${sourceName}'`;
384719
+ throw new MaterializationEligibilityError({
384720
+ reason: "read_only_dialect",
384721
+ message: `${what2} cannot be materialized (colocated '#@ persist'): its ` + `connection '${persistSource.connectionName}' is on the ` + `'${persistSource.dialectName}' dialect, whose connection is ` + `read-only and creates no tables, so there is nowhere in the ` + `source warehouse to write the result. The source serves live. ` + `Remove the annotation, or persist a source on a connection that ` + `can write.`
384722
+ });
384723
+ }
384515
384724
  const partition = resolvePartitionColumns(persistSource, annotationFields);
384516
384725
  if (!partition.ok) {
384517
384726
  recordEligibilityRefused(partition.reason);
@@ -394266,7 +394475,7 @@ ${query}`);
394266
394475
  }
394267
394476
  }
394268
394477
  // package.json
394269
- var version2 = "0.9.11";
394478
+ var version2 = "0.9.12";
394270
394479
 
394271
394480
  // src/version.ts
394272
394481
  var SERVER_VERSION = version2;
@@ -400837,7 +401046,7 @@ function registerSearchDatabaseSchemaTool(mcpServer, environmentStore) {
400837
401046
  if (n === 0)
400838
401047
  return [];
400839
401048
  return [
400840
- `${n} table(s) have a path this server will not vouch for across every dialect it serves, so malloySource is omitted for them rather than risk a line that does not compile. Build it from tablePath: on your own dialect the path may work as-is, or may need the offending segment quoted. Double quotes on DuckDB, Postgres, Snowflake and Trino; backticks on MySQL, BigQuery and Databricks.`
401049
+ `${n} table(s) have a path this server will not vouch for across every dialect it serves, so malloySource is omitted for them rather than risk a line that does not compile. Build it from tablePath: on your own dialect the path may work as-is, or may need the offending segment quoted. Double quotes on DuckDB, Postgres, Snowflake, Trino and SQL Server; backticks on MySQL, BigQuery and Databricks.`
400841
401050
  ];
400842
401051
  };
400843
401052
  const assertCanAdmit = async (environmentName) => {
@@ -406083,6 +406292,15 @@ function parseFrameAncestors(raw) {
406083
406292
  return raw.trim();
406084
406293
  }
406085
406294
 
406295
+ // src/service/experimental_dialects.ts
406296
+ import { getDialect } from "@malloydata/malloy";
406297
+ var BUNDLED_EXPERIMENTAL_DIALECTS = ["sqlserver"];
406298
+ function acceptBundledExperimentalDialects() {
406299
+ for (const name of BUNDLED_EXPERIMENTAL_DIALECTS) {
406300
+ getDialect(name).acceptExperimental();
406301
+ }
406302
+ }
406303
+
406086
406304
  // src/server.ts
406087
406305
  assertSupportedNodeVersion();
406088
406306
  function parseArgs() {
@@ -406158,6 +406376,7 @@ parseArgs();
406158
406376
  getPersistStorageMode();
406159
406377
  getPersistCollisionEnforce();
406160
406378
  getQueryMetadataMode();
406379
+ acceptBundledExperimentalDialects();
406161
406380
  var PUBLISHER_PORT = Number(process.env.PUBLISHER_PORT || 4000);
406162
406381
  var PUBLISHER_HOST = process.env.PUBLISHER_HOST || "0.0.0.0";
406163
406382
  var MCP_PORT = Number(process.env.MCP_PORT || 4040);
Binary file
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "snowflake"
20
20
  ],
21
21
  "homepage": "https://docs.malloydata.dev/documentation/user_guides/publishing/mcp_agents",
22
- "version": "0.9.11",
22
+ "version": "0.9.12",
23
23
  "main": "dist/server.mjs",
24
24
  "bin": {
25
25
  "malloy-publisher": "dist/server.mjs"
@@ -70,6 +70,7 @@
70
70
  "@malloydata/db-postgres": "^0.0.435",
71
71
  "@malloydata/db-publisher": "^0.0.435",
72
72
  "@malloydata/db-snowflake": "^0.0.435",
73
+ "@malloydata/db-sqlserver": "^0.0.435",
73
74
  "@malloydata/db-trino": "^0.0.435",
74
75
  "@malloydata/malloy": "^0.0.435",
75
76
  "@malloydata/malloy-filter": "^0.0.435",
@@ -1 +0,0 @@
1
- import{a as r,D as t,j as e,ab as i,aA as o}from"./index-gQEuz0Cg.js";function m(){const a=r(),{environmentName:n}=t();if(n){const s=i({environmentName:n});return e.jsx(o,{onSelectPackage:a,resourceUri:s})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing environment name"})})}export{m as default};
@@ -1 +0,0 @@
1
- import{a as n,j as t,v as o}from"./index-gQEuz0Cg.js";function s(){const a=n();return t.jsx(o,{onClickEnvironment:a})}export{s as default};