This is an automated email from the ASF dual-hosted git repository. rusackas pushed a commit to branch feat/database-troubleshooting-docs in repository https://gitbox.apache.org/repos/asf/superset.git
commit 7e514218328f4988578ec91266c17023798b4a6a Author: Evan Rusackas <[email protected]> AuthorDate: Wed Jan 21 15:06:26 2026 -0800 feat(docs): add auto-generated troubleshooting section to database pages Extract custom_errors from database engine specs and display them in a new "Troubleshooting" section on each database's documentation page. Changes: - Add extract_custom_errors.py script to parse custom_errors from engine specs - Integrate custom_errors extraction into generate-database-docs.mjs - Add CustomError types to types.ts - Add renderTroubleshooting() to DatabasePage.tsx with: - Errors grouped by category (Authentication, Connection, Query, etc.) - Human-readable error messages with solution hints - Links to related issue codes - Invalid field indicators This provides zero-maintenance troubleshooting documentation that stays in sync with the actual error handling code in the engine specs. Currently extracts 73 custom errors across 18 databases including: - PostgreSQL (9 errors) - MySQL (5 errors) - BigQuery (5 errors) - Presto/Trino (8 errors) - And more... Co-Authored-By: Claude Opus 4.5 <[email protected]> --- docs/scripts/extract_custom_errors.py | 296 ++++++++ docs/scripts/generate-database-docs.mjs | 73 ++ docs/src/components/databases/DatabasePage.tsx | 130 ++++ docs/src/components/databases/types.ts | 12 + docs/src/data/databases.json | 953 ++++++++++++++++++++++++- 5 files changed, 1455 insertions(+), 9 deletions(-) diff --git a/docs/scripts/extract_custom_errors.py b/docs/scripts/extract_custom_errors.py new file mode 100644 index 00000000000..bf84361b384 --- /dev/null +++ b/docs/scripts/extract_custom_errors.py @@ -0,0 +1,296 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Extract custom_errors from database engine specs for documentation. + +This script parses engine spec files to extract error handling information +that can be displayed on database documentation pages. + +Usage: python scripts/extract_custom_errors.py +Output: JSON mapping of engine spec module names to their custom errors +""" + +import ast +import json # noqa: TID251 - standalone docs script, not part of superset +import sys +from pathlib import Path +from typing import Any + +# Map SupersetErrorType values to human-readable categories and issue codes +ERROR_TYPE_INFO = { + "CONNECTION_INVALID_USERNAME_ERROR": { + "category": "Authentication", + "description": "Invalid username", + "issue_codes": [1012], + }, + "CONNECTION_INVALID_PASSWORD_ERROR": { + "category": "Authentication", + "description": "Invalid password", + "issue_codes": [1013], + }, + "CONNECTION_ACCESS_DENIED_ERROR": { + "category": "Authentication", + "description": "Access denied", + "issue_codes": [1014, 1015], + }, + "CONNECTION_INVALID_HOSTNAME_ERROR": { + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [1007], + }, + "CONNECTION_PORT_CLOSED_ERROR": { + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [1008], + }, + "CONNECTION_HOST_DOWN_ERROR": { + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [1009], + }, + "CONNECTION_UNKNOWN_DATABASE_ERROR": { + "category": "Connection", + "description": "Unknown database", + "issue_codes": [1015], + }, + "CONNECTION_DATABASE_PERMISSIONS_ERROR": { + "category": "Permissions", + "description": "Insufficient permissions", + "issue_codes": [1017], + }, + "CONNECTION_MISSING_PARAMETERS_ERROR": { + "category": "Configuration", + "description": "Missing parameters", + "issue_codes": [1018], + }, + "CONNECTION_DATABASE_TIMEOUT": { + "category": "Connection", + "description": "Connection timeout", + "issue_codes": [1001, 1009], + }, + "COLUMN_DOES_NOT_EXIST_ERROR": { + "category": "Query", + "description": "Column not found", + "issue_codes": [1003, 1004], + }, + "TABLE_DOES_NOT_EXIST_ERROR": { + "category": "Query", + "description": "Table not found", + "issue_codes": [1003, 1005], + }, + "SCHEMA_DOES_NOT_EXIST_ERROR": { + "category": "Query", + "description": "Schema not found", + "issue_codes": [1003, 1016], + }, + "SYNTAX_ERROR": { + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [1030], + }, + "OBJECT_DOES_NOT_EXIST_ERROR": { + "category": "Query", + "description": "Object not found", + "issue_codes": [1029], + }, + "GENERIC_DB_ENGINE_ERROR": { + "category": "General", + "description": "Database engine error", + "issue_codes": [1002], + }, +} + + +def extract_string_from_call(node: ast.Call) -> str | None: + """Extract string from __() or _() translation calls.""" + if not node.args: + return None + arg = node.args[0] + if isinstance(arg, ast.Constant) and isinstance(arg.value, str): + return arg.value + elif isinstance(arg, ast.JoinedStr): + # f-string - try to reconstruct + parts = [] + for value in arg.values: + if isinstance(value, ast.Constant): + parts.append(str(value.value)) + elif isinstance(value, ast.FormattedValue): + # Just use a placeholder + parts.append("{...}") + return "".join(parts) + return None + + +def extract_custom_errors_from_file(filepath: Path) -> dict[str, list[dict[str, Any]]]: + """ + Extract custom_errors definitions from a Python engine spec file. + + Returns a dict mapping class names to their custom errors list. + """ + results = {} + + try: + with open(filepath, "r", encoding="utf-8") as f: + source = f.read() + + tree = ast.parse(source) + + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef): + class_name = node.name + + for item in node.body: + # Look for custom_errors = { ... } + if ( + isinstance(item, ast.AnnAssign) + and isinstance(item.target, ast.Name) + and item.target.id == "custom_errors" + and isinstance(item.value, ast.Dict) + ): + errors = extract_errors_from_dict(item.value, source) + if errors: + results[class_name] = errors + + # Also handle simple assignment: custom_errors = { ... } + elif ( + isinstance(item, ast.Assign) + and len(item.targets) == 1 + and isinstance(item.targets[0], ast.Name) + and item.targets[0].id == "custom_errors" + and isinstance(item.value, ast.Dict) + ): + errors = extract_errors_from_dict(item.value, source) + if errors: + results[class_name] = errors + + except Exception as e: + print(f"Error parsing {filepath}: {e}", file=sys.stderr) + + return results + + +def extract_regex_info(key: ast.expr) -> dict[str, Any]: + """Extract regex pattern info from the dict key.""" + if isinstance(key, ast.Name): + return {"regex_name": key.id} + if isinstance(key, ast.Call): + if ( + isinstance(key.func, ast.Attribute) + and key.func.attr == "compile" + and key.args + and isinstance(key.args[0], ast.Constant) + ): + return {"regex_pattern": key.args[0].value} + return {} + + +def extract_invalid_fields(extra_node: ast.Dict) -> list[str]: + """Extract invalid fields from the extra dict.""" + for k, v in zip(extra_node.keys, extra_node.values, strict=False): + if ( + isinstance(k, ast.Constant) + and k.value == "invalid" + and isinstance(v, ast.List) + ): + return [elem.value for elem in v.elts if isinstance(elem, ast.Constant)] + return [] + + +def extract_error_tuple_info(value: ast.Tuple) -> dict[str, Any]: + """Extract error info from the (message, error_type, extra) tuple.""" + result: dict[str, Any] = {} + + # First element: message template + msg_node = value.elts[0] + if isinstance(msg_node, ast.Call): + message = extract_string_from_call(msg_node) + if message: + result["message_template"] = message + elif isinstance(msg_node, ast.Constant): + result["message_template"] = msg_node.value + + # Second element: SupersetErrorType.SOMETHING + type_node = value.elts[1] + if isinstance(type_node, ast.Attribute): + error_type = type_node.attr + result["error_type"] = error_type + if error_type in ERROR_TYPE_INFO: + type_info = ERROR_TYPE_INFO[error_type] + result["category"] = type_info["category"] + result["description"] = type_info["description"] + result["issue_codes"] = type_info["issue_codes"] + + # Third element: extra dict with invalid fields + if len(value.elts) >= 3 and isinstance(value.elts[2], ast.Dict): + invalid_fields = extract_invalid_fields(value.elts[2]) + if invalid_fields: + result["invalid_fields"] = invalid_fields + + return result + + +def extract_errors_from_dict(dict_node: ast.Dict, source: str) -> list[dict[str, Any]]: + """Extract error information from a custom_errors dict AST node.""" + errors = [] + + for key, value in zip(dict_node.keys, dict_node.values, strict=False): + if key is None or value is None: + continue + + error_info = extract_regex_info(key) + + if isinstance(value, ast.Tuple) and len(value.elts) >= 2: + error_info.update(extract_error_tuple_info(value)) + + if error_info.get("error_type") and error_info.get("message_template"): + errors.append(error_info) + + return errors + + +def main() -> None: + """Main function to extract custom_errors from all engine specs.""" + # Find the superset root directory + script_dir = Path(__file__).parent + root_dir = script_dir.parent.parent + specs_dir = root_dir / "superset" / "db_engine_specs" + + if not specs_dir.exists(): + print(f"Error: Engine specs directory not found: {specs_dir}", file=sys.stderr) + sys.exit(1) + + all_errors = {} + + # Process each Python file in the specs directory + for filepath in sorted(specs_dir.glob("*.py")): + if filepath.name.startswith("_"): + continue + + module_name = filepath.stem + class_errors = extract_custom_errors_from_file(filepath) + + if class_errors: + # Store errors by module and class + all_errors[module_name] = class_errors + + # Output as JSON + print(json.dumps(all_errors, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/docs/scripts/generate-database-docs.mjs b/docs/scripts/generate-database-docs.mjs index cde02d127dc..cd3bf9f5892 100644 --- a/docs/scripts/generate-database-docs.mjs +++ b/docs/scripts/generate-database-docs.mjs @@ -675,6 +675,75 @@ function updateReadme(databases) { return false; } +/** + * Extract custom_errors from engine specs for troubleshooting documentation + * Returns a map of module names to their custom errors + */ +function extractCustomErrors() { + console.log('Extracting custom_errors from engine specs...'); + + try { + const scriptPath = path.join(__dirname, 'extract_custom_errors.py'); + const result = spawnSync('python3', [scriptPath], { + cwd: ROOT_DIR, + encoding: 'utf-8', + timeout: 30000, + maxBuffer: 10 * 1024 * 1024, + }); + + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(result.stderr || 'Python script failed'); + } + + const customErrors = JSON.parse(result.stdout); + const moduleCount = Object.keys(customErrors).length; + const errorCount = Object.values(customErrors).reduce((sum, classes) => + sum + Object.values(classes).reduce((s, errs) => s + errs.length, 0), 0); + console.log(` Found ${errorCount} custom errors across ${moduleCount} modules`); + return customErrors; + } catch (err) { + console.log(' Could not extract custom_errors:', err.message); + return null; + } +} + +/** + * Merge custom_errors into database documentation + * Maps by module name since that's how both datasets are keyed + */ +function mergeCustomErrors(databases, customErrors) { + if (!customErrors) return; + + let mergedCount = 0; + + for (const [, db] of Object.entries(databases)) { + const moduleName = db.module; + if (!moduleName || !customErrors[moduleName]) continue; + + // Get all errors from all classes in this module + const moduleErrors = customErrors[moduleName]; + const allErrors = []; + + for (const classErrors of Object.values(moduleErrors)) { + allErrors.push(...classErrors); + } + + if (allErrors.length > 0) { + // Add to documentation + db.documentation = db.documentation || {}; + db.documentation.custom_errors = allErrors; + mergedCount++; + } + } + + if (mergedCount > 0) { + console.log(` Merged custom_errors into ${mergedCount} database docs`); + } +} + /** * Load existing database data if available */ @@ -768,6 +837,10 @@ async function main() { databases = mergeWithExistingDiagnostics(databases, existingData); } + // Extract and merge custom_errors for troubleshooting documentation + const customErrors = extractCustomErrors(); + mergeCustomErrors(databases, customErrors); + // Build statistics const statistics = buildStatistics(databases); diff --git a/docs/src/components/databases/DatabasePage.tsx b/docs/src/components/databases/DatabasePage.tsx index 836680b7462..c02d4a44a1c 100644 --- a/docs/src/components/databases/DatabasePage.tsx +++ b/docs/src/components/databases/DatabasePage.tsx @@ -39,6 +39,7 @@ import { BookOutlined, EditOutlined, GithubOutlined, + BugOutlined, } from '@ant-design/icons'; import type { DatabaseInfo } from './types'; @@ -414,6 +415,132 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => { ); }; + // Render troubleshooting / custom errors section + const renderTroubleshooting = () => { + if (!docs?.custom_errors?.length) return null; + + // Group errors by category + const errorsByCategory: Record<string, typeof docs.custom_errors> = {}; + for (const error of docs.custom_errors) { + const category = error.category || 'General'; + if (!errorsByCategory[category]) { + errorsByCategory[category] = []; + } + errorsByCategory[category].push(error); + } + + // Define category order for consistent display + const categoryOrder = [ + 'Authentication', + 'Connection', + 'Permissions', + 'Query', + 'Configuration', + 'General', + ]; + + const sortedCategories = Object.keys(errorsByCategory).sort((a, b) => { + const aIdx = categoryOrder.indexOf(a); + const bIdx = categoryOrder.indexOf(b); + if (aIdx === -1 && bIdx === -1) return a.localeCompare(b); + if (aIdx === -1) return 1; + if (bIdx === -1) return -1; + return aIdx - bIdx; + }); + + // Category colors + const categoryColors: Record<string, string> = { + Authentication: 'orange', + Connection: 'red', + Permissions: 'purple', + Query: 'blue', + Configuration: 'cyan', + General: 'default', + }; + + return ( + <Card + title={ + <> + <BugOutlined /> Troubleshooting + </> + } + style={{ marginBottom: 16 }} + > + <Paragraph type="secondary"> + Common error messages you may encounter when connecting to or querying{' '} + {name}, along with their causes and solutions. + </Paragraph> + <Collapse accordion> + {sortedCategories.map((category) => ( + <Panel + header={ + <span> + <Tag color={categoryColors[category] || 'default'}> + {category} + </Tag> + {errorsByCategory[category].length} error + {errorsByCategory[category].length !== 1 ? 's' : ''} + </span> + } + key={category} + > + {errorsByCategory[category].map((error, idx) => ( + <div + key={idx} + style={{ + marginBottom: + idx < errorsByCategory[category].length - 1 ? 16 : 0, + paddingBottom: + idx < errorsByCategory[category].length - 1 ? 16 : 0, + borderBottom: + idx < errorsByCategory[category].length - 1 + ? '1px solid var(--ifm-color-emphasis-200)' + : 'none', + }} + > + <div style={{ marginBottom: 8 }}> + <Text strong>{error.description || error.error_type}</Text> + </div> + <Alert + message={error.message_template} + type="error" + style={{ marginBottom: 8 }} + /> + {error.invalid_fields && error.invalid_fields.length > 0 && ( + <div style={{ marginBottom: 8 }}> + <Text type="secondary">Check these fields: </Text> + {error.invalid_fields.map((field) => ( + <Tag key={field} color="warning"> + {field} + </Tag> + ))} + </div> + )} + {error.issue_codes && error.issue_codes.length > 0 && ( + <div> + <Text type="secondary">Related issue codes: </Text> + {error.issue_codes.map((code) => ( + <Tag key={code}> + <a + href={`/docs/using-superset/issue-codes#issue-${code}`} + style={{ color: 'inherit' }} + > + Issue {code} + </a> + </Tag> + ))} + </div> + )} + </div> + ))} + </Panel> + ))} + </Collapse> + </Card> + ); + }; + return ( <div className="database-page" @@ -556,6 +683,9 @@ const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => { {/* Time Grains */} {renderTimeGrains()} + {/* Troubleshooting / Custom Errors */} + {renderTroubleshooting()} + {/* Compatible Databases */} {renderCompatibleDatabases()} diff --git a/docs/src/components/databases/types.ts b/docs/src/components/databases/types.ts index 698c93e2cca..d1ad59e74bd 100644 --- a/docs/src/components/databases/types.ts +++ b/docs/src/components/databases/types.ts @@ -86,6 +86,17 @@ export interface CompatibleDatabase { docs_url?: string; } +export interface CustomError { + error_type: string; // e.g., "CONNECTION_INVALID_USERNAME_ERROR" + message_template: string; // e.g., 'The username "%(username)s" does not exist.' + regex_pattern?: string; // The regex pattern that matches this error (optional, for reference) + regex_name?: string; // The name of the regex constant (e.g., "CONNECTION_INVALID_USERNAME_REGEX") + invalid_fields?: string[]; // Fields that are invalid, e.g., ["username", "password"] + issue_codes?: number[]; // Related issue codes from ISSUE_CODES mapping + category?: string; // Error category: "Authentication", "Connection", "Query", etc. + description?: string; // Human-readable short description of the error type +} + export interface DatabaseDocumentation { description?: string; logo?: string; @@ -111,6 +122,7 @@ export interface DatabaseDocumentation { sqlalchemy_docs_url?: string; advanced_features?: Record<string, string>; compatible_databases?: CompatibleDatabase[]; + custom_errors?: CustomError[]; // Database-specific error messages and troubleshooting info } export interface TimeGrains { diff --git a/docs/src/data/databases.json b/docs/src/data/databases.json index 88eb25777a7..25455e9d4c1 100644 --- a/docs/src/data/databases.json +++ b/docs/src/data/databases.json @@ -1,5 +1,5 @@ { - "generated": "2026-01-19T22:38:23.768Z", + "generated": "2026-01-21T23:00:45.237Z", "statistics": { "totalDatabases": 67, "withDocumentation": 67, @@ -333,7 +333,19 @@ } } ], - "notes": "URL-encode special characters in s3_staging_dir (e.g., s3:// becomes s3%3A//)." + "notes": "URL-encode special characters in s3_staging_dir (e.g., s3:// becomes s3%3A//).", + "custom_errors": [ + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors at or near \"%(syntax_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } + ] }, "time_grains": {}, "score": 0, @@ -516,7 +528,62 @@ "warnings": [ "Google BigQuery Python SDK is not compatible with gevent. Use a worker type other than gevent when deploying with gunicorn." ], - "docs_url": "https://github.com/googleapis/python-bigquery-sqlalchemy" + "docs_url": "https://github.com/googleapis/python-bigquery-sqlalchemy", + "custom_errors": [ + { + "regex_name": "CONNECTION_DATABASE_PERMISSIONS_REGEX", + "message_template": "Unable to connect. Verify that the following roles are set on the service account: \"BigQuery Data Viewer\", \"BigQuery Metadata Viewer\", \"BigQuery Job User\" and the following permissions are set \"bigquery.readsessions.create\", \"bigquery.readsessions.getData\"", + "error_type": "CONNECTION_DATABASE_PERMISSIONS_ERROR", + "category": "Permissions", + "description": "Insufficient permissions", + "issue_codes": [ + 1017 + ] + }, + { + "regex_name": "TABLE_DOES_NOT_EXIST_REGEX", + "message_template": "The table \"%(table)s\" does not exist. A valid table must be used to run this query.", + "error_type": "TABLE_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Table not found", + "issue_codes": [ + 1003, + 1005 + ] + }, + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve column \"%(column)s\" at line %(location)s.", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + }, + { + "regex_name": "SCHEMA_DOES_NOT_EXIST_REGEX", + "message_template": "The schema \"%(schema)s\" does not exist. A valid schema must be used to run this query.", + "error_type": "SCHEMA_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Schema not found", + "issue_codes": [ + 1003, + 1016 + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors at or near \"%(syntax_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } + ] }, "time_grains": {}, "score": 0, @@ -1214,6 +1281,120 @@ "is_recommended": true, "notes": "Uses PostgreSQL wire protocol." } + ], + "custom_errors": [ + { + "message_template": "Incorrect username or password.", + "error_type": "CONNECTION_INVALID_USERNAME_ERROR", + "category": "Authentication", + "description": "Invalid username", + "issue_codes": [ + 1012 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "message_template": "Please enter a password.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "password" + ] + }, + { + "message_template": "Hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "message_template": "Server refused the connection: check hostname and port.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "message_template": "Unable to connect to database \"%(database)s\"", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + }, + { + "message_template": "Unable to connect to database \"%(database)s\": database does not exist or insufficient permissions", + "error_type": "CONNECTION_DATABASE_PERMISSIONS_ERROR", + "category": "Permissions", + "description": "Insufficient permissions", + "issue_codes": [ + 1017 + ], + "invalid_fields": [ + "database" + ] + }, + { + "message_template": "Please check your query for syntax errors at or near \"%(err)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + }, + { + "message_template": "Column \"%(column)s\" not found in \"%(view)s\".", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + }, + { + "message_template": "Invalid aggregation expression.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + }, + { + "message_template": "\"%(exp)s\" is neither an aggregation function nor appears in the GROUP BY clause.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } ] }, "time_grains": { @@ -1313,6 +1494,73 @@ "APACHE_PROJECTS", "ANALYTICAL_DATABASES", "OPEN_SOURCE" + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "Unknown Doris server host \"%(hostname)s\".", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down and can't be reached.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Unable to connect to database \"%(database)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors near \"%(server_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } ] }, "time_grains": { @@ -1650,6 +1898,19 @@ "HOSTED_OPEN_SOURCE" ] } + ], + "custom_errors": [ + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve the column \"%(column_name)s\"", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + } ] }, "time_grains": { @@ -1727,7 +1988,20 @@ "database": "MotherDuck database name", "token": "Service token from MotherDuck dashboard" }, - "docs_url": "https://motherduck.com/docs/getting-started/" + "docs_url": "https://motherduck.com/docs/getting-started/", + "custom_errors": [ + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve the column \"%(column_name)s\"", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + } + ] }, "time_grains": {}, "score": 0, @@ -2159,7 +2433,19 @@ "CLOUD_GCP", "HOSTED_OPEN_SOURCE" ], - "install_instructions": "pip install \"apache-superset[gsheets]\"" + "install_instructions": "pip install \"apache-superset[gsheets]\"", + "custom_errors": [ + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors near \"%(server_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } + ] }, "time_grains": { "SECOND": true, @@ -2755,7 +3041,50 @@ "notes": "Connection string must be URL-encoded. Special characters like @ need encoding." } ], - "docs_url": "https://docs.sqlalchemy.org/en/20/core/engines.html#escaping-special-characters-such-as-signs-in-passwords" + "docs_url": "https://docs.sqlalchemy.org/en/20/core/engines.html#escaping-special-characters-such-as-signs-in-passwords", + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\", password, or database name \"%(database)s\" is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "The hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ] + }, + { + "regex_name": "CONNECTION_PORT_CLOSED_REGEX", + "message_template": "Port %(port)s on hostname \"%(hostname)s\" refused the connection.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down, and can't be reached on port %(port)s.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ] + } + ] }, "time_grains": { "SECOND": true, @@ -2828,6 +3157,49 @@ "CLOUD_DATA_WAREHOUSES", "ANALYTICAL_DATABASES", "PROPRIETARY" + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\", password, or database name \"%(database)s\" is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "The hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ] + }, + { + "regex_name": "CONNECTION_PORT_CLOSED_REGEX", + "message_template": "Port %(port)s on hostname \"%(hostname)s\" refused the connection.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down, and can't be reached on port %(port)s.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ] + } ] }, "time_grains": {}, @@ -2939,6 +3311,73 @@ "HOSTED_OPEN_SOURCE" ] } + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "Unknown MySQL server host \"%(hostname)s\".", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down and can't be reached.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Unable to connect to database \"%(database)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors near \"%(server_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } ] }, "time_grains": { @@ -3068,6 +3507,73 @@ "categories": [ "TRADITIONAL_RDBMS", "OPEN_SOURCE" + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "Unknown OceanBase server host \"%(hostname)s\".", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down and can't be reached.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Unable to connect to database \"%(database)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors near \"%(server_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } ] }, "time_grains": {}, @@ -3099,7 +3605,96 @@ "sqlalchemy-ocient" ], "connection_string": "ocient://{username}:{password}@{host}:{port}/{database}", - "install_instructions": "pip install sqlalchemy-ocient" + "install_instructions": "pip install sqlalchemy-ocient", + "custom_errors": [ + { + "regex_name": "CONNECTION_INVALID_USERNAME_REGEX", + "message_template": "The username \"%(username)s\" does not exist.", + "error_type": "CONNECTION_INVALID_USERNAME_ERROR", + "category": "Authentication", + "description": "Invalid username", + "issue_codes": [ + 1012 + ] + }, + { + "regex_name": "CONNECTION_INVALID_PASSWORD_REGEX", + "message_template": "The user/password combination is not valid (Incorrect password for user).", + "error_type": "CONNECTION_INVALID_PASSWORD_ERROR", + "category": "Authentication", + "description": "Invalid password", + "issue_codes": [ + 1013 + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Could not connect to database: \"%(database)s\"", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "Could not resolve hostname: \"%(host)s\".", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ] + }, + { + "regex_name": "CONNECTION_INVALID_PORT_ERROR", + "message_template": "Port out of range 0-65535", + "error_type": "CONNECTION_INVALID_PORT_ERROR" + }, + { + "regex_name": "INVALID_CONNECTION_STRING_REGEX", + "message_template": "Invalid Connection String: Expecting String of the form 'ocient://user:pass@host:port/database'.", + "error_type": "GENERIC_DB_ENGINE_ERROR", + "category": "General", + "description": "Database engine error", + "issue_codes": [ + 1002 + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Syntax Error: %(qualifier)s input \"%(input)s\" expecting \"%(expected)s", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + }, + { + "regex_name": "TABLE_DOES_NOT_EXIST_REGEX", + "message_template": "Table or View \"%(table)s\" does not exist.", + "error_type": "TABLE_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Table not found", + "issue_codes": [ + 1003, + 1005 + ] + }, + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "Invalid reference to column: \"%(column)s\"", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + } + ] }, "time_grains": { "SECOND": true, @@ -3446,6 +4041,124 @@ "HOSTED_OPEN_SOURCE" ] } + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_INVALID_USERNAME_REGEX", + "message_template": "The username \"%(username)s\" does not exist.", + "error_type": "CONNECTION_INVALID_USERNAME_ERROR", + "category": "Authentication", + "description": "Invalid username", + "issue_codes": [ + 1012 + ], + "invalid_fields": [ + "username" + ] + }, + { + "regex_name": "CONNECTION_INVALID_PASSWORD_REGEX", + "message_template": "The password provided for username \"%(username)s\" is incorrect.", + "error_type": "CONNECTION_INVALID_PASSWORD_ERROR", + "category": "Authentication", + "description": "Invalid password", + "issue_codes": [ + 1013 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_PASSWORD_NEEDED_REGEX", + "message_template": "Please re-enter the password.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "The hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "regex_name": "CONNECTION_PORT_CLOSED_REGEX", + "message_template": "Port %(port)s on hostname \"%(hostname)s\" refused the connection.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down, and can't be reached on port %(port)s.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Unable to connect to database \"%(database)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + }, + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve the column \"%(column_name)s\" at line %(location)s.", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors at or near \"%(syntax_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } ] }, "time_grains": { @@ -3515,6 +4228,92 @@ "connection_string": "presto://{hostname}:{port}/{database}", "is_recommended": true } + ], + "custom_errors": [ + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve the column \"%(column_name)s\" at line %(location)s.", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + }, + { + "regex_name": "TABLE_DOES_NOT_EXIST_REGEX", + "message_template": "The table \"%(table_name)s\" does not exist. A valid table must be used to run this query.", + "error_type": "TABLE_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Table not found", + "issue_codes": [ + 1003, + 1005 + ] + }, + { + "regex_name": "SCHEMA_DOES_NOT_EXIST_REGEX", + "message_template": "The schema \"%(schema_name)s\" does not exist. A valid schema must be used to run this query.", + "error_type": "SCHEMA_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Schema not found", + "issue_codes": [ + 1003, + 1016 + ] + }, + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "The hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down, and can't be reached on port %(port)s.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ] + }, + { + "regex_name": "CONNECTION_PORT_CLOSED_REGEX", + "message_template": "Port %(port)s on hostname \"%(hostname)s\" refused the connection.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "message_template": "Unable to connect to catalog named \"%(catalog_name)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ] + } ] }, "time_grains": { @@ -3625,6 +4424,77 @@ } } } + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_INVALID_HOSTNAME_REGEX", + "message_template": "The hostname \"%(hostname)s\" cannot be resolved.", + "error_type": "CONNECTION_INVALID_HOSTNAME_ERROR", + "category": "Connection", + "description": "Invalid hostname", + "issue_codes": [ + 1007 + ], + "invalid_fields": [ + "host" + ] + }, + { + "regex_name": "CONNECTION_PORT_CLOSED_REGEX", + "message_template": "Port %(port)s on hostname \"%(hostname)s\" refused the connection.", + "error_type": "CONNECTION_PORT_CLOSED_ERROR", + "category": "Connection", + "description": "Port closed or refused", + "issue_codes": [ + 1008 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_HOST_DOWN_REGEX", + "message_template": "The host \"%(hostname)s\" might be down, and can't be reached on port %(port)s.", + "error_type": "CONNECTION_HOST_DOWN_ERROR", + "category": "Connection", + "description": "Host unreachable", + "issue_codes": [ + 1009 + ], + "invalid_fields": [ + "host", + "port" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "We were unable to connect to your database named \"%(database)s\". Please verify your database name and try again.", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + } ] }, "time_grains": {}, @@ -3858,7 +4728,29 @@ } ], "notes": "Schema is not required in connection string. Ensure user has privileges for all databases/schemas/tables/views/warehouses.", - "docs_url": "https://docs.snowflake.com/en/user-guide/key-pair-auth.html" + "docs_url": "https://docs.snowflake.com/en/user-guide/key-pair-auth.html", + "custom_errors": [ + { + "regex_name": "OBJECT_DOES_NOT_EXIST_REGEX", + "message_template": "%(object)s does not exist in this database.", + "error_type": "OBJECT_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Object not found", + "issue_codes": [ + 1029 + ] + }, + { + "regex_name": "SYNTAX_ERROR_REGEX", + "message_template": "Please check your query for syntax errors at or near \"%(syntax_error)s\". Then, try running your query again.", + "error_type": "SYNTAX_ERROR", + "category": "Query", + "description": "SQL syntax error", + "issue_codes": [ + 1030 + ] + } + ] }, "time_grains": { "SECOND": true, @@ -4039,7 +4931,20 @@ ], "pypi_packages": [], "connection_string": "sqlite:///path/to/file.db?check_same_thread=false", - "notes": "No additional library needed. SQLite is bundled with Python." + "notes": "No additional library needed. SQLite is bundled with Python.", + "custom_errors": [ + { + "regex_name": "COLUMN_DOES_NOT_EXIST_REGEX", + "message_template": "We can't seem to resolve the column \"%(column_name)s\"", + "error_type": "COLUMN_DOES_NOT_EXIST_ERROR", + "category": "Query", + "description": "Column not found", + "issue_codes": [ + 1003, + 1004 + ] + } + ] }, "time_grains": { "SECOND": true, @@ -4183,6 +5088,36 @@ }, "docs_url": "https://docs.celerdata.com/" } + ], + "custom_errors": [ + { + "regex_name": "CONNECTION_ACCESS_DENIED_REGEX", + "message_template": "Either the username \"%(username)s\" or the password is incorrect.", + "error_type": "CONNECTION_ACCESS_DENIED_ERROR", + "category": "Authentication", + "description": "Access denied", + "issue_codes": [ + 1014, + 1015 + ], + "invalid_fields": [ + "username", + "password" + ] + }, + { + "regex_name": "CONNECTION_UNKNOWN_DATABASE_REGEX", + "message_template": "Unable to connect to database \"%(database)s\".", + "error_type": "CONNECTION_UNKNOWN_DATABASE_ERROR", + "category": "Connection", + "description": "Unknown database", + "issue_codes": [ + 1015 + ], + "invalid_fields": [ + "database" + ] + } ] }, "time_grains": {
