feat(docs): auto-generate database documentation from lib.py (#36805)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Evan Rusackas
2026-01-21 10:54:01 -08:00
committed by GitHub
parent 2c1a33fd32
commit b460ca94c6
133 changed files with 11531 additions and 2123 deletions

7
docs/.gitignore vendored
View File

@@ -26,3 +26,10 @@ docs/intro.md
# Generated badge images (downloaded at build time by remark-localize-badges plugin)
static/badges/
# Generated database documentation MDX files (regenerated at build time)
# Source of truth is in superset/db_engine_specs/*.py metadata attributes
docs/databases/
# Note: src/data/databases.json is COMMITTED (not ignored) to preserve feature diagnostics
# that require Flask context to generate. Update it locally with: npm run gen-db-docs

File diff suppressed because it is too large Load Diff

View File

@@ -222,7 +222,7 @@ const config: Config = {
from: '/gallery.html',
},
{
to: '/docs/configuration/databases',
to: '/docs/databases',
from: '/druid.html',
},
{
@@ -274,7 +274,7 @@ const config: Config = {
from: '/docs/contributing/contribution-page',
},
{
to: '/docs/configuration/databases',
to: '/docs/databases',
from: '/docs/databases/yugabyte/',
},
{
@@ -410,6 +410,11 @@ const config: Config = {
docId: 'intro',
label: 'Getting Started',
},
{
type: 'doc',
docId: 'databases/index',
label: 'Databases',
},
{
type: 'doc',
docId: 'faq',

View File

@@ -6,17 +6,22 @@
"scripts": {
"docusaurus": "docusaurus",
"_init": "cat src/intro_header.txt ../README.md > docs/intro.md",
"start": "yarn run _init && yarn run generate:extension-components && NODE_ENV=development docusaurus start",
"start": "yarn run _init && yarn run generate:extension-components && yarn run generate:database-docs && NODE_ENV=development docusaurus start",
"stop": "pkill -f 'docusaurus start' || pkill -f 'docusaurus serve' || echo 'No docusaurus server running'",
"build": "yarn run _init && yarn run generate:extension-components && DEBUG=docusaurus:* docusaurus build",
"build": "yarn run _init && yarn run generate:extension-components && yarn run generate:database-docs && DEBUG=docusaurus:* docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "yarn run _init && docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "yarn run generate:extension-components && tsc",
"typecheck": "yarn run generate:extension-components && yarn run generate:database-docs && tsc",
"generate:extension-components": "node scripts/generate-extension-components.mjs",
"generate:database-docs": "node scripts/generate-database-docs.mjs",
"gen-db-docs": "node scripts/generate-database-docs.mjs",
"lint:db-metadata": "python3 ../superset/db_engine_specs/lint_metadata.py",
"lint:db-metadata:report": "python3 ../superset/db_engine_specs/lint_metadata.py --markdown -o ../superset/db_engine_specs/METADATA_STATUS.md",
"update:readme-db-logos": "node scripts/generate-database-docs.mjs --update-readme",
"eslint": "eslint .",
"version:add": "node scripts/manage-versions.mjs add",
"version:remove": "node scripts/manage-versions.mjs remove",

View File

@@ -0,0 +1,867 @@
/**
* 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.
*/
/**
* This script generates database documentation data from engine spec metadata.
* It outputs a JSON file that can be imported by React components for rendering.
*
* Usage: node scripts/generate-database-docs.mjs
*
* The script can run in two modes:
* 1. With Flask app (full diagnostics) - requires superset to be installed
* 2. Fallback mode (documentation only) - parses engine spec `metadata` attributes via AST
*/
import { spawnSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const ROOT_DIR = path.resolve(__dirname, '../..');
const DOCS_DIR = path.resolve(__dirname, '..');
const DATA_OUTPUT_DIR = path.join(DOCS_DIR, 'src/data');
const DATA_OUTPUT_FILE = path.join(DATA_OUTPUT_DIR, 'databases.json');
const MDX_OUTPUT_DIR = path.join(DOCS_DIR, 'docs/databases');
const MDX_SUPPORTED_DIR = path.join(MDX_OUTPUT_DIR, 'supported');
/**
* Try to run the full lib.py script with Flask context
*/
function tryRunFullScript() {
try {
console.log('Attempting to run lib.py with Flask context...');
const pythonCode = `
import sys
import json
sys.path.insert(0, '.')
from superset.app import create_app
from superset.db_engine_specs.lib import generate_yaml_docs
app = create_app()
with app.app_context():
docs = generate_yaml_docs()
print(json.dumps(docs, default=str))
`;
const result = spawnSync('python', ['-c', pythonCode], {
cwd: ROOT_DIR,
encoding: 'utf-8',
timeout: 60000,
maxBuffer: 10 * 1024 * 1024,
env: { ...process.env, SUPERSET_SECRET_KEY: 'docs-build-key' },
});
if (result.error) {
throw result.error;
}
if (result.status !== 0) {
throw new Error(result.stderr || 'Python script failed');
}
return JSON.parse(result.stdout);
} catch (error) {
console.log('Full script execution failed, using fallback mode...');
console.log(' Reason:', error.message?.split('\n')[0] || 'Unknown error');
return null;
}
}
/**
* Extract metadata from individual engine spec files using AST parsing
* This is the preferred approach - reads directly from spec.metadata attributes
* Supports metadata inheritance - child classes inherit and merge with parent metadata
*/
function extractEngineSpecMetadata() {
console.log('Extracting metadata from engine spec files...');
console.log(` ROOT_DIR: ${ROOT_DIR}`);
try {
const pythonCode = `
import sys
import json
import ast
import os
def eval_node(node):
"""Safely evaluate an AST node as a Python literal."""
if node is None:
return None
if isinstance(node, ast.Constant):
return node.value
elif isinstance(node, ast.List):
return [eval_node(e) for e in node.elts]
elif isinstance(node, ast.Dict):
result = {}
for k, v in zip(node.keys, node.values):
if k is not None:
key = eval_node(k)
if key is not None:
result[key] = eval_node(v)
return result
elif isinstance(node, ast.Name):
# Handle True, False, None constants
if node.id == 'True':
return True
elif node.id == 'False':
return False
elif node.id == 'None':
return None
return node.id
elif isinstance(node, ast.Attribute):
# Handle DatabaseCategory.SOMETHING - return just the attribute name
return node.attr
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
left, right = eval_node(node.left), eval_node(node.right)
if isinstance(left, str) and isinstance(right, str):
return left + right
return None
elif isinstance(node, ast.Tuple):
return tuple(eval_node(e) for e in node.elts)
elif isinstance(node, ast.JoinedStr):
# f-strings - just return a placeholder
return "<f-string>"
return None
def deep_merge(base, override):
"""Deep merge two dictionaries. Override values take precedence."""
if base is None:
return override
if override is None:
return base
if not isinstance(base, dict) or not isinstance(override, dict):
return override
# Fields that should NOT be inherited from parent classes
# - compatible_databases: Each class defines its own compatible DBs
# - categories: Each class defines its own categories (not extended from parent)
NON_INHERITABLE_FIELDS = {'compatible_databases', 'categories'}
result = base.copy()
# Remove non-inheritable fields from base (they should only come from the class that defines them)
for field in NON_INHERITABLE_FIELDS:
result.pop(field, None)
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
elif key in result and isinstance(result[key], list) and isinstance(value, list):
# Extend lists from parent (e.g., drivers)
result[key] = result[key] + value
else:
result[key] = value
return result
databases = {}
specs_dir = 'superset/db_engine_specs'
errors = []
debug_info = {
"cwd": os.getcwd(),
"specs_dir_exists": os.path.isdir(specs_dir),
"files_checked": 0,
"classes_found": 0,
"classes_with_metadata": 0,
"inherited_metadata": 0,
}
if not os.path.isdir(specs_dir):
print(json.dumps({"error": f"Directory not found: {specs_dir}", "cwd": os.getcwd()}))
sys.exit(1)
# First pass: collect all class info (name, bases, metadata)
class_info = {} # class_name -> {bases: [], metadata: {}, engine_name: str, filename: str}
for filename in sorted(os.listdir(specs_dir)):
if not filename.endswith('.py') or filename in ('__init__.py', 'lib.py', 'lint_metadata.py'):
continue
debug_info["files_checked"] += 1
filepath = os.path.join(specs_dir, filename)
try:
with open(filepath) as f:
source = f.read()
tree = ast.parse(source)
for node in ast.walk(tree):
if not isinstance(node, ast.ClassDef):
continue
# Get base class names
base_names = []
for b in node.bases:
if isinstance(b, ast.Name):
base_names.append(b.id)
elif isinstance(b, ast.Attribute):
base_names.append(b.attr)
is_engine_spec = any('EngineSpec' in name or 'Mixin' in name for name in base_names)
if not is_engine_spec:
continue
# Extract class attributes
engine_name = None
metadata = None
for item in node.body:
if isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name):
if target.id == 'engine_name':
val = eval_node(item.value)
if isinstance(val, str):
engine_name = val
elif target.id == 'metadata':
metadata = eval_node(item.value)
# Check for engine attribute with non-empty value to distinguish
# true base classes from product classes like OceanBaseEngineSpec
has_non_empty_engine = False
for item in node.body:
if isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name) and target.id == 'engine':
# Check if engine value is non-empty string
if isinstance(item.value, ast.Constant):
has_non_empty_engine = bool(item.value.value)
break
# True base classes: end with BaseEngineSpec AND don't define engine
# or have empty engine (like PostgresBaseEngineSpec with engine = "")
is_true_base = (
node.name.endswith('BaseEngineSpec') and not has_non_empty_engine
) or 'Mixin' in node.name
# Store class info for inheritance resolution
class_info[node.name] = {
'bases': base_names,
'metadata': metadata,
'engine_name': engine_name,
'filename': filename,
'is_base_or_mixin': is_true_base,
}
except Exception as e:
errors.append(f"{filename}: {str(e)}")
# Second pass: resolve inheritance and build final metadata
def get_inherited_metadata(class_name, visited=None):
"""Recursively get metadata from parent classes."""
if visited is None:
visited = set()
if class_name in visited:
return {} # Prevent circular inheritance
visited.add(class_name)
info = class_info.get(class_name)
if not info:
return {}
# Start with parent metadata
inherited = {}
for base_name in info['bases']:
parent_metadata = get_inherited_metadata(base_name, visited.copy())
if parent_metadata:
inherited = deep_merge(inherited, parent_metadata)
# Merge with own metadata (own takes precedence)
if info['metadata']:
inherited = deep_merge(inherited, info['metadata'])
return inherited
for class_name, info in class_info.items():
# Skip base classes and mixins
if info['is_base_or_mixin']:
continue
debug_info["classes_found"] += 1
# Get final metadata with inheritance
final_metadata = get_inherited_metadata(class_name)
# Remove compatible_databases if not defined by this class (it's not inheritable)
own_metadata = info['metadata'] or {}
if 'compatible_databases' not in own_metadata and 'compatible_databases' in final_metadata:
del final_metadata['compatible_databases']
# Track if we inherited anything
if final_metadata and final_metadata != own_metadata:
debug_info["inherited_metadata"] += 1
# Use class name as fallback for engine_name
display_name = info['engine_name'] or class_name.replace('EngineSpec', '').replace('_', ' ')
if final_metadata and isinstance(final_metadata, dict) and display_name:
debug_info["classes_with_metadata"] += 1
databases[display_name] = {
'engine': display_name.lower().replace(' ', '_'),
'engine_name': display_name,
'module': info['filename'][:-3], # Remove .py extension
'documentation': final_metadata,
'time_grains': {},
'score': 0,
'max_score': 0,
'joins': True,
'subqueries': True,
'supports_dynamic_schema': False,
'supports_catalog': False,
'supports_dynamic_catalog': False,
'ssh_tunneling': False,
'query_cancelation': False,
'supports_file_upload': False,
'user_impersonation': False,
'query_cost_estimation': False,
'sql_validation': False,
}
if errors and not databases:
print(json.dumps({"error": "Parse errors", "details": errors, "debug": debug_info}), file=sys.stderr)
# Print debug info to stderr for troubleshooting
print(json.dumps(debug_info), file=sys.stderr)
print(json.dumps(databases, default=str))
`;
const result = spawnSync('python3', ['-c', pythonCode], {
cwd: ROOT_DIR,
encoding: 'utf-8',
timeout: 30000,
maxBuffer: 10 * 1024 * 1024,
});
if (result.error) {
throw result.error;
}
// Log debug info from stderr
if (result.stderr) {
console.log('Python debug info:', result.stderr.trim());
}
if (result.status !== 0) {
throw new Error(result.stderr || 'Python script failed');
}
const databases = JSON.parse(result.stdout);
if (Object.keys(databases).length === 0) {
throw new Error('No metadata found in engine specs');
}
console.log(`Extracted metadata from ${Object.keys(databases).length} engine specs`);
return databases;
} catch (err) {
console.log('Engine spec metadata extraction failed:', err.message);
return null;
}
}
/**
* Build statistics from the database data
*/
function buildStatistics(databases) {
const stats = {
totalDatabases: Object.keys(databases).length,
withDocumentation: 0,
withConnectionString: 0,
withDrivers: 0,
withAuthMethods: 0,
supportsJoins: 0,
supportsSubqueries: 0,
supportsDynamicSchema: 0,
supportsCatalog: 0,
averageScore: 0,
maxScore: 0,
byCategory: {},
};
let totalScore = 0;
for (const [name, db] of Object.entries(databases)) {
const docs = db.documentation || {};
if (Object.keys(docs).length > 0) stats.withDocumentation++;
if (docs.connection_string || docs.drivers?.length > 0)
stats.withConnectionString++;
if (docs.drivers?.length > 0) stats.withDrivers++;
if (docs.authentication_methods?.length > 0) stats.withAuthMethods++;
if (db.joins) stats.supportsJoins++;
if (db.subqueries) stats.supportsSubqueries++;
if (db.supports_dynamic_schema) stats.supportsDynamicSchema++;
if (db.supports_catalog) stats.supportsCatalog++;
totalScore += db.score || 0;
if (db.max_score > stats.maxScore) stats.maxScore = db.max_score;
// Use categories from documentation metadata (computed by Python)
// Each database can belong to multiple categories
const categories = docs.categories || ['OTHER'];
for (const cat of categories) {
// Map category constant names to display names
const categoryDisplayNames = {
'CLOUD_AWS': 'Cloud - AWS',
'CLOUD_GCP': 'Cloud - Google',
'CLOUD_AZURE': 'Cloud - Azure',
'CLOUD_DATA_WAREHOUSES': 'Cloud Data Warehouses',
'APACHE_PROJECTS': 'Apache Projects',
'TRADITIONAL_RDBMS': 'Traditional RDBMS',
'ANALYTICAL_DATABASES': 'Analytical Databases',
'SEARCH_NOSQL': 'Search & NoSQL',
'QUERY_ENGINES': 'Query Engines',
'TIME_SERIES': 'Time Series Databases',
'OTHER': 'Other Databases',
'OPEN_SOURCE': 'Open Source',
'HOSTED_OPEN_SOURCE': 'Hosted Open Source',
'PROPRIETARY': 'Proprietary',
};
const displayName = categoryDisplayNames[cat] || cat;
if (!stats.byCategory[displayName]) {
stats.byCategory[displayName] = [];
}
stats.byCategory[displayName].push(name);
}
}
stats.averageScore = Math.round(totalScore / stats.totalDatabases);
return stats;
}
/**
* Convert database name to a URL-friendly slug
*/
function toSlug(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
/**
* Generate MDX content for a single database page
*/
function generateDatabaseMDX(name, db) {
const description = db.documentation?.description || `Documentation for ${name} database connection.`;
const shortDesc = description
.slice(0, 160)
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
return `---
title: ${name}
sidebar_label: ${name}
description: "${shortDesc}"
hide_title: true
---
{/*
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.
*/}
import { DatabasePage } from '@site/src/components/databases';
import databaseData from '@site/src/data/databases.json';
<DatabasePage name="${name}" database={databaseData.databases["${name}"]} />
`;
}
/**
* Generate the index MDX for the databases overview
*/
function generateIndexMDX(statistics, usedFlaskContext = true) {
const fallbackNotice = usedFlaskContext ? '' : `
:::info Developer Note
This documentation was built without Flask context, so feature diagnostics (scores, time grain support, etc.)
may not reflect actual database capabilities. For full diagnostics, build docs locally with:
\`\`\`bash
cd docs && npm run gen-db-docs
\`\`\`
This requires a working Superset development environment.
:::
`;
return `---
title: Connecting to Databases
sidebar_label: Overview
sidebar_position: 1
---
{/*
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.
*/}
import { DatabaseIndex } from '@site/src/components/databases';
import databaseData from '@site/src/data/databases.json';
# Connecting to Databases
Superset does not ship bundled with connectivity to databases. The main step in connecting
Superset to a database is to **install the proper database driver(s)** in your environment.
:::note
You'll need to install the required packages for the database you want to use as your metadata database
as well as the packages needed to connect to the databases you want to access through Superset.
For information about setting up Superset's metadata database, please refer to
installation documentations ([Docker Compose](/docs/installation/docker-compose), [Kubernetes](/docs/installation/kubernetes))
:::
## Supported Databases
Superset supports **${statistics.totalDatabases} databases** with varying levels of feature support.
Click on any database name to see detailed documentation including connection strings,
authentication methods, and configuration options.
<DatabaseIndex data={databaseData} />
## Installing Database Drivers
Superset requires a Python [DB-API database driver](https://peps.python.org/pep-0249/)
and a [SQLAlchemy dialect](https://docs.sqlalchemy.org/en/20/dialects/) to be installed for
each database engine you want to connect to.
### Installing Drivers in Docker
For Docker deployments, create a \`requirements-local.txt\` file in the \`docker\` directory:
\`\`\`bash
# Create the requirements file
touch ./docker/requirements-local.txt
# Add your driver (e.g., for PostgreSQL)
echo "psycopg2-binary" >> ./docker/requirements-local.txt
\`\`\`
Then restart your containers. The drivers will be installed automatically.
### Installing Drivers with pip
For non-Docker installations:
\`\`\`bash
pip install <driver-package>
\`\`\`
See individual database pages for the specific driver packages needed.
## Connecting Through the UI
1. Go to **Settings → Data: Database Connections**
2. Click **+ DATABASE**
3. Select your database type or enter a SQLAlchemy URI
4. Click **Test Connection** to verify
5. Click **Connect** to save
## Contributing
To add or update database documentation, add a \`metadata\` attribute to your engine spec class in
\`superset/db_engine_specs/\`. Documentation is auto-generated from these metadata attributes.
See [METADATA_STATUS.md](https://github.com/apache/superset/blob/master/superset/db_engine_specs/METADATA_STATUS.md)
for the current status of database documentation and the [README](https://github.com/apache/superset/blob/master/superset/db_engine_specs/README.md) for the metadata schema.
${fallbackNotice}`;
}
const README_PATH = path.join(ROOT_DIR, 'README.md');
const README_START_MARKER = '<!-- SUPPORTED_DATABASES_START -->';
const README_END_MARKER = '<!-- SUPPORTED_DATABASES_END -->';
/**
* Generate the database logos HTML for README.md
* Only includes databases that have logos defined
*/
function generateReadmeLogos(databases) {
// Get databases with logos, sorted alphabetically
const dbsWithLogos = Object.entries(databases)
.filter(([, db]) => db.documentation?.logo)
.sort(([a], [b]) => a.localeCompare(b));
if (dbsWithLogos.length === 0) {
return '';
}
// Generate HTML img tags
const logoTags = dbsWithLogos.map(([name, db]) => {
const logo = db.documentation.logo;
const alt = name.toLowerCase().replace(/\s+/g, '-');
// Use docs site URL for logos
return ` <img src="https://superset.apache.org/img/databases/${logo}" alt="${alt}" border="0" width="80" height="40" class="database-logo" />`;
});
return `<p align="center">
${logoTags.join('\n')}
</p>`;
}
/**
* Update the README.md with generated database logos
*/
function updateReadme(databases) {
if (!fs.existsSync(README_PATH)) {
console.log('README.md not found, skipping update');
return false;
}
const content = fs.readFileSync(README_PATH, 'utf-8');
// Check if markers exist
if (!content.includes(README_START_MARKER) || !content.includes(README_END_MARKER)) {
console.log('README.md missing database markers, skipping update');
console.log(` Add ${README_START_MARKER} and ${README_END_MARKER} to enable auto-generation`);
return false;
}
// Generate new logos section
const logosHtml = generateReadmeLogos(databases);
// Replace content between markers
const pattern = new RegExp(
`${README_START_MARKER}[\\s\\S]*?${README_END_MARKER}`,
'g'
);
const newContent = content.replace(
pattern,
`${README_START_MARKER}\n${logosHtml}\n${README_END_MARKER}`
);
if (newContent !== content) {
fs.writeFileSync(README_PATH, newContent);
console.log('Updated README.md database logos');
return true;
}
console.log('README.md database logos unchanged');
return false;
}
/**
* Load existing database data if available
*/
function loadExistingData() {
if (!fs.existsSync(DATA_OUTPUT_FILE)) {
return null;
}
try {
const content = fs.readFileSync(DATA_OUTPUT_FILE, 'utf-8');
return JSON.parse(content);
} catch (error) {
console.log('Could not load existing data:', error.message);
return null;
}
}
/**
* Merge new documentation with existing diagnostics
* Preserves score, time_grains, and feature flags from existing data
*/
function mergeWithExistingDiagnostics(newDatabases, existingData) {
if (!existingData?.databases) return newDatabases;
const diagnosticFields = [
'score', 'max_score', 'time_grains', 'joins', 'subqueries',
'supports_dynamic_schema', 'supports_catalog', 'supports_dynamic_catalog',
'ssh_tunneling', 'query_cancelation', 'supports_file_upload',
'user_impersonation', 'query_cost_estimation', 'sql_validation'
];
for (const [name, db] of Object.entries(newDatabases)) {
const existingDb = existingData.databases[name];
if (existingDb && existingDb.score > 0) {
// Preserve diagnostics from existing data
for (const field of diagnosticFields) {
if (existingDb[field] !== undefined) {
db[field] = existingDb[field];
}
}
}
}
const preserved = Object.values(newDatabases).filter(d => d.score > 0).length;
if (preserved > 0) {
console.log(`Preserved diagnostics for ${preserved} databases from existing data`);
}
return newDatabases;
}
/**
* Main function
*/
async function main() {
console.log('Generating database documentation...\n');
// Ensure output directories exist
if (!fs.existsSync(DATA_OUTPUT_DIR)) {
fs.mkdirSync(DATA_OUTPUT_DIR, { recursive: true });
}
if (!fs.existsSync(MDX_OUTPUT_DIR)) {
fs.mkdirSync(MDX_OUTPUT_DIR, { recursive: true });
}
// Load existing data for potential merge
const existingData = loadExistingData();
// Try sources in order of preference:
// 1. Full script with Flask context (richest data with diagnostics)
// 2. Engine spec metadata files (works in CI without Flask)
let databases = tryRunFullScript();
let usedFlaskContext = !!databases;
if (!databases) {
// Extract from engine spec metadata (preferred for CI)
databases = extractEngineSpecMetadata();
}
if (!databases || Object.keys(databases).length === 0) {
console.error('Failed to generate database documentation data.');
console.error('Could not extract from Flask app or engine spec metadata.');
process.exit(1);
}
console.log(`Processed ${Object.keys(databases).length} databases\n`);
// Check if new data has scores; if not, preserve existing diagnostics
const hasNewScores = Object.values(databases).some((db) => db.score > 0);
if (!hasNewScores && existingData) {
databases = mergeWithExistingDiagnostics(databases, existingData);
}
// Build statistics
const statistics = buildStatistics(databases);
// Create the final output structure
const output = {
generated: new Date().toISOString(),
statistics,
databases,
};
// Write the JSON file (with trailing newline for POSIX compliance)
fs.writeFileSync(DATA_OUTPUT_FILE, JSON.stringify(output, null, 2) + '\n');
console.log(`Generated: ${path.relative(DOCS_DIR, DATA_OUTPUT_FILE)}`);
// Ensure supported directory exists
if (!fs.existsSync(MDX_SUPPORTED_DIR)) {
fs.mkdirSync(MDX_SUPPORTED_DIR, { recursive: true });
}
// Clean up old MDX files that are no longer in the database list
console.log(`\nCleaning up old MDX files in ${path.relative(DOCS_DIR, MDX_SUPPORTED_DIR)}/`);
const existingMdxFiles = fs.readdirSync(MDX_SUPPORTED_DIR).filter(f => f.endsWith('.mdx'));
const validSlugs = new Set(Object.keys(databases).map(name => `${toSlug(name)}.mdx`));
let removedCount = 0;
for (const file of existingMdxFiles) {
if (!validSlugs.has(file)) {
fs.unlinkSync(path.join(MDX_SUPPORTED_DIR, file));
removedCount++;
}
}
if (removedCount > 0) {
console.log(` Removed ${removedCount} outdated MDX files`);
}
// Generate individual MDX files for each database in supported/ subdirectory
console.log(`\nGenerating MDX files in ${path.relative(DOCS_DIR, MDX_SUPPORTED_DIR)}/`);
let mdxCount = 0;
for (const [name, db] of Object.entries(databases)) {
const slug = toSlug(name);
const mdxContent = generateDatabaseMDX(name, db);
const mdxPath = path.join(MDX_SUPPORTED_DIR, `${slug}.mdx`);
fs.writeFileSync(mdxPath, mdxContent);
mdxCount++;
}
console.log(` Generated ${mdxCount} database pages`);
// Generate index page in parent databases/ directory
const indexContent = generateIndexMDX(statistics, usedFlaskContext);
const indexPath = path.join(MDX_OUTPUT_DIR, 'index.mdx');
fs.writeFileSync(indexPath, indexContent);
console.log(` Generated index page`);
// Generate _category_.json for databases/ directory
const categoryJson = {
label: 'Databases',
position: 1,
link: {
type: 'doc',
id: 'databases/index',
},
};
fs.writeFileSync(
path.join(MDX_OUTPUT_DIR, '_category_.json'),
JSON.stringify(categoryJson, null, 2) + '\n'
);
// Generate _category_.json for supported/ subdirectory (collapsible)
const supportedCategoryJson = {
label: 'Supported Databases',
position: 2,
collapsed: true,
collapsible: true,
};
fs.writeFileSync(
path.join(MDX_SUPPORTED_DIR, '_category_.json'),
JSON.stringify(supportedCategoryJson, null, 2) + '\n'
);
console.log(` Generated _category_.json files`);
// Update README.md database logos (only when explicitly requested)
if (process.env.UPDATE_README === 'true' || process.argv.includes('--update-readme')) {
console.log('');
updateReadme(databases);
}
console.log(`\nStatistics:`);
console.log(` Total databases: ${statistics.totalDatabases}`);
console.log(` With documentation: ${statistics.withDocumentation}`);
console.log(` With connection strings: ${statistics.withConnectionString}`);
console.log(` Categories: ${Object.keys(statistics.byCategory).length}`);
console.log('\nDone!');
}
main().catch(console.error);

View File

@@ -57,6 +57,20 @@ const sidebars = {
},
],
},
{
type: 'category',
label: 'Databases',
link: {
type: 'doc',
id: 'databases/index',
},
items: [
{
type: 'autogenerated',
dirName: 'databases',
},
],
},
{
type: 'category',
label: 'Using Superset',

View File

@@ -98,6 +98,7 @@ interface SectionHeaderProps {
title: string;
subtitle?: string | ReactNode;
dark?: boolean;
link?: string;
}
const SectionHeader = ({
@@ -105,15 +106,24 @@ const SectionHeader = ({
title,
subtitle,
dark,
link,
}: SectionHeaderProps) => {
const Heading = level;
const StyledRoot =
level === 'h1' ? StyledSectionHeaderH1 : StyledSectionHeaderH2;
const titleContent = link ? (
<a href={link} style={{ color: 'inherit', textDecoration: 'none' }}>
{title}
</a>
) : (
title
);
return (
<StyledRoot dark={!!dark}>
<Heading className="title">{title}</Heading>
<Heading className="title">{titleContent}</Heading>
<img className="line" src="/img/community/line.png" alt="line" />
{subtitle && <div className="subtitle">{subtitle}</div>}
</StyledRoot>

View File

@@ -0,0 +1,578 @@
/**
* 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.
*/
import React, { useState, useMemo } from 'react';
import { Card, Row, Col, Statistic, Table, Tag, Input, Select, Tooltip } from 'antd';
import {
DatabaseOutlined,
CheckCircleOutlined,
ApiOutlined,
KeyOutlined,
SearchOutlined,
LinkOutlined,
} from '@ant-design/icons';
import type { DatabaseData, DatabaseInfo, TimeGrains } from './types';
interface DatabaseIndexProps {
data: DatabaseData;
}
// Type for table entries (includes both regular DBs and compatible DBs)
interface TableEntry {
name: string;
categories: string[]; // Multiple categories supported
score: number;
max_score: number;
timeGrainCount: number;
time_grains?: TimeGrains;
hasDrivers: boolean;
hasAuthMethods: boolean;
hasConnectionString: boolean;
joins?: boolean;
subqueries?: boolean;
supports_dynamic_schema?: boolean;
supports_catalog?: boolean;
ssh_tunneling?: boolean;
supports_file_upload?: boolean;
query_cancelation?: boolean;
query_cost_estimation?: boolean;
user_impersonation?: boolean;
sql_validation?: boolean;
documentation?: DatabaseInfo['documentation'];
// For compatible databases
isCompatible?: boolean;
compatibleWith?: string;
compatibleDescription?: string;
}
// Map category constant names to display names
const CATEGORY_DISPLAY_NAMES: Record<string, string> = {
'CLOUD_AWS': 'Cloud - AWS',
'CLOUD_GCP': 'Cloud - Google',
'CLOUD_AZURE': 'Cloud - Azure',
'CLOUD_DATA_WAREHOUSES': 'Cloud Data Warehouses',
'APACHE_PROJECTS': 'Apache Projects',
'TRADITIONAL_RDBMS': 'Traditional RDBMS',
'ANALYTICAL_DATABASES': 'Analytical Databases',
'SEARCH_NOSQL': 'Search & NoSQL',
'QUERY_ENGINES': 'Query Engines',
'TIME_SERIES': 'Time Series Databases',
'OTHER': 'Other Databases',
'OPEN_SOURCE': 'Open Source',
'HOSTED_OPEN_SOURCE': 'Hosted Open Source',
'PROPRIETARY': 'Proprietary',
};
// Category colors for visual distinction
const CATEGORY_COLORS: Record<string, string> = {
'Cloud - AWS': 'orange',
'Cloud - Google': 'blue',
'Cloud - Azure': 'cyan',
'Cloud Data Warehouses': 'purple',
'Apache Projects': 'red',
'Traditional RDBMS': 'green',
'Analytical Databases': 'magenta',
'Search & NoSQL': 'gold',
'Query Engines': 'lime',
'Time Series Databases': 'volcano',
'Other Databases': 'default',
// Licensing categories
'Open Source': 'geekblue',
'Hosted Open Source': 'cyan',
'Proprietary': 'default',
};
// Convert category constant to display name
function getCategoryDisplayName(cat: string): string {
return CATEGORY_DISPLAY_NAMES[cat] || cat;
}
// Get categories for a database - uses categories from metadata when available
// Falls back to name-based inference for compatible databases without categories
function getCategories(
name: string,
documentationCategories?: string[]
): string[] {
// Prefer categories from documentation metadata (computed by Python)
if (documentationCategories && documentationCategories.length > 0) {
return documentationCategories.map(getCategoryDisplayName);
}
// Fallback: infer from name (for compatible databases without categories)
const nameLower = name.toLowerCase();
if (nameLower.includes('aws') || nameLower.includes('amazon'))
return ['Cloud - AWS'];
if (nameLower.includes('google') || nameLower.includes('bigquery'))
return ['Cloud - Google'];
if (nameLower.includes('azure') || nameLower.includes('microsoft'))
return ['Cloud - Azure'];
if (nameLower.includes('snowflake') || nameLower.includes('databricks'))
return ['Cloud Data Warehouses'];
if (
nameLower.includes('apache') ||
nameLower.includes('druid') ||
nameLower.includes('hive') ||
nameLower.includes('spark')
)
return ['Apache Projects'];
if (
nameLower.includes('postgres') ||
nameLower.includes('mysql') ||
nameLower.includes('sqlite') ||
nameLower.includes('mariadb')
)
return ['Traditional RDBMS'];
if (
nameLower.includes('clickhouse') ||
nameLower.includes('vertica') ||
nameLower.includes('starrocks')
)
return ['Analytical Databases'];
if (
nameLower.includes('elastic') ||
nameLower.includes('solr') ||
nameLower.includes('couchbase')
)
return ['Search & NoSQL'];
if (nameLower.includes('trino') || nameLower.includes('presto'))
return ['Query Engines'];
return ['Other Databases'];
}
// Count supported time grains
function countTimeGrains(db: DatabaseInfo): number {
if (!db.time_grains) return 0;
return Object.values(db.time_grains).filter(Boolean).length;
}
// Format time grain name for display (e.g., FIVE_MINUTES -> "5 min")
function formatTimeGrain(grain: string): string {
const mapping: Record<string, string> = {
SECOND: 'Second',
FIVE_SECONDS: '5 sec',
THIRTY_SECONDS: '30 sec',
MINUTE: 'Minute',
FIVE_MINUTES: '5 min',
TEN_MINUTES: '10 min',
FIFTEEN_MINUTES: '15 min',
THIRTY_MINUTES: '30 min',
HALF_HOUR: '30 min',
HOUR: 'Hour',
SIX_HOURS: '6 hours',
DAY: 'Day',
WEEK: 'Week',
WEEK_STARTING_SUNDAY: 'Week (Sun)',
WEEK_STARTING_MONDAY: 'Week (Mon)',
WEEK_ENDING_SATURDAY: 'Week (→Sat)',
WEEK_ENDING_SUNDAY: 'Week (→Sun)',
MONTH: 'Month',
QUARTER: 'Quarter',
QUARTER_YEAR: 'Quarter',
YEAR: 'Year',
};
return mapping[grain] || grain;
}
// Get list of supported time grains for tooltip
function getSupportedTimeGrains(timeGrains?: TimeGrains): string[] {
if (!timeGrains) return [];
return Object.entries(timeGrains)
.filter(([, supported]) => supported)
.map(([grain]) => formatTimeGrain(grain));
}
const DatabaseIndex: React.FC<DatabaseIndexProps> = ({ data }) => {
const [searchText, setSearchText] = useState('');
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
const { statistics, databases } = data;
// Convert databases object to array, including compatible databases
const databaseList = useMemo(() => {
const entries: TableEntry[] = [];
Object.entries(databases).forEach(([name, db]) => {
// Add the main database
// Use categories from documentation metadata (computed by Python) when available
entries.push({
...db,
name,
categories: getCategories(name, db.documentation?.categories),
timeGrainCount: countTimeGrains(db),
hasDrivers: (db.documentation?.drivers?.length ?? 0) > 0,
hasAuthMethods: (db.documentation?.authentication_methods?.length ?? 0) > 0,
hasConnectionString: Boolean(
db.documentation?.connection_string ||
(db.documentation?.drivers?.length ?? 0) > 0
),
isCompatible: false,
});
// Add compatible databases from this database's documentation
const compatibleDbs = db.documentation?.compatible_databases ?? [];
compatibleDbs.forEach((compat) => {
// Check if this compatible DB already exists as a main entry
const existsAsMain = Object.keys(databases).some(
(dbName) => dbName.toLowerCase() === compat.name.toLowerCase()
);
if (!existsAsMain) {
// Compatible databases: use their categories if defined, or infer from name
entries.push({
name: compat.name,
categories: getCategories(compat.name, compat.categories),
// Compatible DBs inherit scores from parent
score: db.score,
max_score: db.max_score,
timeGrainCount: countTimeGrains(db),
hasDrivers: false,
hasAuthMethods: false,
hasConnectionString: Boolean(compat.connection_string),
joins: db.joins,
subqueries: db.subqueries,
supports_dynamic_schema: db.supports_dynamic_schema,
supports_catalog: db.supports_catalog,
ssh_tunneling: db.ssh_tunneling,
documentation: {
description: compat.description,
connection_string: compat.connection_string,
pypi_packages: compat.pypi_packages,
},
isCompatible: true,
compatibleWith: name,
compatibleDescription: `Uses ${name} driver`,
});
}
});
});
return entries;
}, [databases]);
// Filter and sort databases
const filteredDatabases = useMemo(() => {
return databaseList
.filter((db) => {
const matchesSearch =
!searchText ||
db.name.toLowerCase().includes(searchText.toLowerCase()) ||
db.documentation?.description
?.toLowerCase()
.includes(searchText.toLowerCase());
const matchesCategory = !categoryFilter || db.categories.includes(categoryFilter);
return matchesSearch && matchesCategory;
})
.sort((a, b) => b.score - a.score);
}, [databaseList, searchText, categoryFilter]);
// Get unique categories and counts for filter
const { categories, categoryCounts } = useMemo(() => {
const counts: Record<string, number> = {};
databaseList.forEach((db) => {
// Count each category the database belongs to
db.categories.forEach((cat) => {
counts[cat] = (counts[cat] || 0) + 1;
});
});
return {
categories: Object.keys(counts).sort(),
categoryCounts: counts,
};
}, [databaseList]);
// Table columns
const columns = [
{
title: 'Database',
dataIndex: 'name',
key: 'name',
sorter: (a: TableEntry, b: TableEntry) => a.name.localeCompare(b.name),
render: (name: string, record: TableEntry) => {
// Convert name to URL slug
const toSlug = (n: string) => n.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
// Link to parent for compatible DBs, otherwise to own page
const linkTarget = record.isCompatible && record.compatibleWith
? `/docs/databases/supported/${toSlug(record.compatibleWith)}`
: `/docs/databases/supported/${toSlug(name)}`;
return (
<div>
<a href={linkTarget}>
<strong>{name}</strong>
</a>
{record.isCompatible && record.compatibleWith && (
<Tag
icon={<LinkOutlined />}
color="geekblue"
style={{ marginLeft: 8, fontSize: '11px' }}
>
{record.compatibleWith} compatible
</Tag>
)}
<div style={{ fontSize: '12px', color: '#666' }}>
{record.documentation?.description?.slice(0, 80)}
{(record.documentation?.description?.length ?? 0) > 80 ? '...' : ''}
</div>
</div>
);
},
},
{
title: 'Categories',
dataIndex: 'categories',
key: 'categories',
width: 220,
filters: categories.map((cat) => ({ text: cat, value: cat })),
onFilter: (value: React.Key | boolean, record: TableEntry) =>
record.categories.includes(value as string),
render: (cats: string[]) => (
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{cats.map((cat) => (
<Tag key={cat} color={CATEGORY_COLORS[cat] || 'default'}>{cat}</Tag>
))}
</div>
),
},
{
title: 'Score',
dataIndex: 'score',
key: 'score',
width: 80,
sorter: (a: TableEntry, b: TableEntry) => a.score - b.score,
defaultSortOrder: 'descend' as const,
render: (score: number, record: TableEntry) => (
<span
style={{
color: score > 150 ? '#52c41a' : score > 100 ? '#1890ff' : '#666',
fontWeight: score > 150 ? 'bold' : 'normal',
}}
>
{score}/{record.max_score}
</span>
),
},
{
title: 'Time Grains',
dataIndex: 'timeGrainCount',
key: 'timeGrainCount',
width: 100,
sorter: (a: TableEntry, b: TableEntry) => a.timeGrainCount - b.timeGrainCount,
render: (count: number, record: TableEntry) => {
if (count === 0) return <span>-</span>;
const grains = getSupportedTimeGrains(record.time_grains);
return (
<Tooltip
title={
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px', maxWidth: 280 }}>
{grains.map((grain) => (
<Tag key={grain} style={{ margin: 0 }}>{grain}</Tag>
))}
</div>
}
placement="top"
>
<span style={{ cursor: 'help', borderBottom: '1px dotted #999' }}>
{count} grains
</span>
</Tooltip>
);
},
},
{
title: 'Features',
key: 'features',
width: 280,
filters: [
{ text: 'JOINs', value: 'joins' },
{ text: 'Subqueries', value: 'subqueries' },
{ text: 'Dynamic Schema', value: 'dynamic_schema' },
{ text: 'Catalog', value: 'catalog' },
{ text: 'SSH Tunneling', value: 'ssh' },
{ text: 'File Upload', value: 'file_upload' },
{ text: 'Query Cancel', value: 'query_cancel' },
{ text: 'Cost Estimation', value: 'cost_estimation' },
{ text: 'User Impersonation', value: 'impersonation' },
{ text: 'SQL Validation', value: 'sql_validation' },
],
onFilter: (value: React.Key | boolean, record: TableEntry) => {
switch (value) {
case 'joins':
return Boolean(record.joins);
case 'subqueries':
return Boolean(record.subqueries);
case 'dynamic_schema':
return Boolean(record.supports_dynamic_schema);
case 'catalog':
return Boolean(record.supports_catalog);
case 'ssh':
return Boolean(record.ssh_tunneling);
case 'file_upload':
return Boolean(record.supports_file_upload);
case 'query_cancel':
return Boolean(record.query_cancelation);
case 'cost_estimation':
return Boolean(record.query_cost_estimation);
case 'impersonation':
return Boolean(record.user_impersonation);
case 'sql_validation':
return Boolean(record.sql_validation);
default:
return true;
}
},
render: (_: unknown, record: TableEntry) => (
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{record.joins && <Tag color="green">JOINs</Tag>}
{record.subqueries && <Tag color="green">Subqueries</Tag>}
{record.supports_dynamic_schema && <Tag color="blue">Dynamic Schema</Tag>}
{record.supports_catalog && <Tag color="purple">Catalog</Tag>}
{record.ssh_tunneling && <Tag color="cyan">SSH</Tag>}
{record.supports_file_upload && <Tag color="orange">File Upload</Tag>}
{record.query_cancelation && <Tag color="volcano">Query Cancel</Tag>}
{record.query_cost_estimation && <Tag color="gold">Cost Est.</Tag>}
{record.user_impersonation && <Tag color="magenta">Impersonation</Tag>}
{record.sql_validation && <Tag color="lime">SQL Validation</Tag>}
</div>
),
},
{
title: 'Documentation',
key: 'docs',
width: 150,
render: (_: unknown, record: TableEntry) => (
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
{record.hasConnectionString && (
<Tag icon={<ApiOutlined />} color="default">
Connection
</Tag>
)}
{record.hasDrivers && (
<Tag icon={<DatabaseOutlined />} color="default">
Drivers
</Tag>
)}
{record.hasAuthMethods && (
<Tag icon={<KeyOutlined />} color="default">
Auth
</Tag>
)}
</div>
),
},
];
return (
<div className="database-index">
{/* Statistics Cards */}
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
<Col xs={12} sm={6}>
<Card>
<Statistic
title="Total Databases"
value={statistics.totalDatabases}
prefix={<DatabaseOutlined />}
/>
</Card>
</Col>
<Col xs={12} sm={6}>
<Card>
<Statistic
title="With Documentation"
value={statistics.withDocumentation}
prefix={<CheckCircleOutlined />}
suffix={`/ ${statistics.totalDatabases}`}
/>
</Card>
</Col>
<Col xs={12} sm={6}>
<Card>
<Statistic
title="Multiple Drivers"
value={statistics.withDrivers}
prefix={<ApiOutlined />}
/>
</Card>
</Col>
<Col xs={12} sm={6}>
<Card>
<Statistic
title="Auth Methods"
value={statistics.withAuthMethods}
prefix={<KeyOutlined />}
/>
</Card>
</Col>
</Row>
{/* Filters */}
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col xs={24} sm={12}>
<Input
placeholder="Search databases..."
prefix={<SearchOutlined />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
allowClear
/>
</Col>
<Col xs={24} sm={12}>
<Select
placeholder="Filter by category"
style={{ width: '100%' }}
value={categoryFilter}
onChange={setCategoryFilter}
allowClear
options={categories.map((cat) => ({
label: (
<span>
<Tag
color={CATEGORY_COLORS[cat] || 'default'}
style={{ marginRight: 8 }}
>
{categoryCounts[cat] || 0}
</Tag>
{cat}
</span>
),
value: cat,
}))}
/>
</Col>
</Row>
{/* Database Table */}
<Table
dataSource={filteredDatabases}
columns={columns}
rowKey={(record) => record.isCompatible ? `${record.compatibleWith}-${record.name}` : record.name}
pagination={{
pageSize: 20,
showSizeChanger: true,
showTotal: (total) => `${total} databases`,
}}
size="middle"
/>
</div>
);
};
export default DatabaseIndex;

View File

@@ -0,0 +1,634 @@
/**
* 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.
*/
import React from 'react';
import {
Card,
Collapse,
Table,
Tag,
Typography,
Alert,
Space,
Divider,
Tabs,
} from 'antd';
import {
CheckCircleOutlined,
CloseCircleOutlined,
WarningOutlined,
LinkOutlined,
KeyOutlined,
SettingOutlined,
BookOutlined,
EditOutlined,
GithubOutlined,
} from '@ant-design/icons';
import type { DatabaseInfo } from './types';
// Simple code block component for connection strings
const CodeBlock: React.FC<{ children: React.ReactNode }> = ({ children }) => (
<pre
style={{
background: 'var(--ifm-code-background)',
padding: '12px 16px',
borderRadius: '4px',
overflow: 'auto',
fontSize: '13px',
fontFamily: 'var(--ifm-font-family-monospace)',
}}
>
<code>{children}</code>
</pre>
);
const { Title, Paragraph, Text } = Typography;
const { Panel } = Collapse;
const { TabPane } = Tabs;
interface DatabasePageProps {
database: DatabaseInfo;
name: string;
}
// Feature badge component
const FeatureBadge: React.FC<{ supported: boolean; label: string }> = ({
supported,
label,
}) => (
<Tag
icon={supported ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
color={supported ? 'success' : 'default'}
>
{label}
</Tag>
);
// Time grain badge
const TimeGrainBadge: React.FC<{ supported: boolean; grain: string }> = ({
supported,
grain,
}) => (
<Tag color={supported ? 'blue' : 'default'} style={{ margin: '2px' }}>
{grain}
</Tag>
);
const DatabasePage: React.FC<DatabasePageProps> = ({ database, name }) => {
const { documentation: docs } = database;
// Helper to render connection string with copy button
const renderConnectionString = (connStr: string, description?: string) => (
<div style={{ marginBottom: 16 }}>
{description && (
<Text type="secondary" style={{ display: 'block', marginBottom: 4 }}>
{description}
</Text>
)}
<CodeBlock>{connStr}</CodeBlock>
</div>
);
// Render driver information
const renderDrivers = () => {
if (!docs?.drivers?.length) return null;
return (
<Card title="Drivers" style={{ marginBottom: 16 }}>
<Tabs>
{docs.drivers.map((driver, idx) => (
<TabPane
tab={
<span>
{driver.name}
{driver.is_recommended && (
<Tag color="green" style={{ marginLeft: 8 }}>
Recommended
</Tag>
)}
</span>
}
key={idx}
>
<Space direction="vertical" style={{ width: '100%' }}>
{driver.pypi_package && (
<div>
<Text strong>PyPI Package: </Text>
<code>{driver.pypi_package}</code>
</div>
)}
{driver.connection_string &&
renderConnectionString(driver.connection_string)}
{driver.notes && (
<Alert message={driver.notes} type="info" showIcon />
)}
{driver.docs_url && (
<a href={driver.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Documentation
</a>
)}
</Space>
</TabPane>
))}
</Tabs>
</Card>
);
};
// Render authentication methods
const renderAuthMethods = () => {
if (!docs?.authentication_methods?.length) return null;
return (
<Card
title={
<>
<KeyOutlined /> Authentication Methods
</>
}
style={{ marginBottom: 16 }}
>
<Collapse accordion>
{docs.authentication_methods.map((auth, idx) => (
<Panel header={auth.name} key={idx}>
{auth.description && <Paragraph>{auth.description}</Paragraph>}
{auth.requirements && (
<Alert
message="Requirements"
description={auth.requirements}
type="warning"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{auth.connection_string &&
renderConnectionString(
auth.connection_string,
'Connection String'
)}
{auth.secure_extra && (
<div>
<Text strong>Secure Extra Configuration:</Text>
<CodeBlock>
{JSON.stringify(auth.secure_extra, null, 2)}
</CodeBlock>
</div>
)}
{auth.engine_parameters && (
<div>
<Text strong>Engine Parameters:</Text>
<CodeBlock>
{JSON.stringify(auth.engine_parameters, null, 2)}
</CodeBlock>
</div>
)}
{auth.notes && (
<Alert message={auth.notes} type="info" showIcon />
)}
</Panel>
))}
</Collapse>
</Card>
);
};
// Render engine parameters
const renderEngineParams = () => {
if (!docs?.engine_parameters?.length) return null;
return (
<Card
title={
<>
<SettingOutlined /> Engine Parameters
</>
}
style={{ marginBottom: 16 }}
>
<Collapse>
{docs.engine_parameters.map((param, idx) => (
<Panel header={param.name} key={idx}>
{param.description && <Paragraph>{param.description}</Paragraph>}
{param.json && (
<CodeBlock>
{JSON.stringify(param.json, null, 2)}
</CodeBlock>
)}
{param.docs_url && (
<a href={param.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Learn more
</a>
)}
</Panel>
))}
</Collapse>
</Card>
);
};
// Render compatible databases (for PostgreSQL, etc.)
const renderCompatibleDatabases = () => {
if (!docs?.compatible_databases?.length) return null;
// Create array of all panel keys to expand by default
const allPanelKeys = docs.compatible_databases.map((_, idx) => idx);
return (
<Card title="Compatible Databases" style={{ marginBottom: 16 }}>
<Paragraph>
The following databases are compatible with the {name} driver:
</Paragraph>
<Collapse defaultActiveKey={allPanelKeys}>
{docs.compatible_databases.map((compat, idx) => (
<Panel
header={
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{compat.logo && (
<img
src={`/img/databases/${compat.logo}`}
alt={compat.name}
style={{
width: 28,
height: 28,
objectFit: 'contain',
}}
/>
)}
<span>{compat.name}</span>
</div>
}
key={idx}
>
{compat.description && (
<Paragraph>{compat.description}</Paragraph>
)}
{compat.connection_string &&
renderConnectionString(compat.connection_string)}
{compat.parameters && (
<div>
<Text strong>Parameters:</Text>
<Table
dataSource={Object.entries(compat.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
})
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
{
title: 'Description',
dataIndex: 'description',
key: 'd',
},
]}
pagination={false}
size="small"
/>
</div>
)}
{compat.notes && (
<Alert
message={compat.notes}
type="info"
showIcon
style={{ marginTop: 16 }}
/>
)}
</Panel>
))}
</Collapse>
</Card>
);
};
// Render feature matrix
const renderFeatures = () => {
const features: Array<{ key: keyof DatabaseInfo; label: string }> = [
{ key: 'joins', label: 'JOINs' },
{ key: 'subqueries', label: 'Subqueries' },
{ key: 'supports_dynamic_schema', label: 'Dynamic Schema' },
{ key: 'supports_catalog', label: 'Catalog Support' },
{ key: 'supports_dynamic_catalog', label: 'Dynamic Catalog' },
{ key: 'ssh_tunneling', label: 'SSH Tunneling' },
{ key: 'query_cancelation', label: 'Query Cancellation' },
{ key: 'supports_file_upload', label: 'File Upload' },
{ key: 'user_impersonation', label: 'User Impersonation' },
{ key: 'query_cost_estimation', label: 'Cost Estimation' },
{ key: 'sql_validation', label: 'SQL Validation' },
];
return (
<Card title="Supported Features" style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{features.map(({ key, label }) => (
<FeatureBadge
key={key}
supported={Boolean(database[key])}
label={label}
/>
))}
</div>
{database.score > 0 && (
<div style={{ marginTop: 16 }}>
<Text>
Feature Score:{' '}
<Text strong>
{database.score}/{database.max_score}
</Text>
</Text>
</div>
)}
</Card>
);
};
// Render time grains
const renderTimeGrains = () => {
if (!database.time_grains) return null;
const commonGrains = [
'SECOND',
'MINUTE',
'HOUR',
'DAY',
'WEEK',
'MONTH',
'QUARTER',
'YEAR',
];
const extendedGrains = Object.keys(database.time_grains).filter(
(g) => !commonGrains.includes(g)
);
return (
<Card title="Time Grains" style={{ marginBottom: 16 }}>
<div style={{ marginBottom: 16 }}>
<Text strong>Common Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{commonGrains.map((grain) => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[grain as keyof typeof database.time_grains]
)}
/>
))}
</div>
</div>
{extendedGrains.length > 0 && (
<div>
<Text strong>Extended Time Grains:</Text>
<div style={{ marginTop: 8 }}>
{extendedGrains.map((grain) => (
<TimeGrainBadge
key={grain}
grain={grain}
supported={Boolean(
database.time_grains[grain as keyof typeof database.time_grains]
)}
/>
))}
</div>
</div>
)}
</Card>
);
};
return (
<div
className="database-page"
id={name.toLowerCase().replace(/\s+/g, '-')}
>
<div style={{ marginBottom: 16 }}>
{docs?.logo && (
<img
src={`/img/databases/${docs.logo}`}
alt={name}
style={{
height: 120,
objectFit: 'contain',
marginBottom: 12,
}}
/>
)}
<Title level={1} style={{ margin: 0 }}>{name}</Title>
{docs?.homepage_url && (
<a
href={docs.homepage_url}
target="_blank"
rel="noreferrer"
style={{ fontSize: 14 }}
>
<LinkOutlined /> {docs.homepage_url}
</a>
)}
</div>
{docs?.description && <Paragraph>{docs.description}</Paragraph>}
{/* Warnings */}
{docs?.warnings?.map((warning, idx) => (
<Alert
key={idx}
message={warning}
type="warning"
icon={<WarningOutlined />}
showIcon
style={{ marginBottom: 16 }}
/>
))}
{/* Known Limitations */}
{docs?.limitations?.length > 0 && (
<Card
title="Known Limitations"
style={{ marginBottom: 16 }}
type="inner"
>
<ul style={{ margin: 0, paddingLeft: 20 }}>
{docs.limitations.map((limitation, idx) => (
<li key={idx}>{limitation}</li>
))}
</ul>
</Card>
)}
{/* Installation */}
{(docs?.pypi_packages?.length || docs?.install_instructions) && (
<Card title="Installation" style={{ marginBottom: 16 }}>
{docs.pypi_packages?.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Text strong>Required packages: </Text>
{docs.pypi_packages.map((pkg) => (
<Tag key={pkg} color="blue">
{pkg}
</Tag>
))}
</div>
)}
{docs.version_requirements && (
<Alert
message={`Version requirement: ${docs.version_requirements}`}
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{docs.install_instructions && (
<CodeBlock>{docs.install_instructions}</CodeBlock>
)}
</Card>
)}
{/* Basic Connection */}
{docs?.connection_string && !docs?.drivers?.length && (
<Card title="Connection String" style={{ marginBottom: 16 }}>
{renderConnectionString(docs.connection_string)}
{docs.parameters && (
<Table
dataSource={Object.entries(docs.parameters).map(
([key, value]) => ({
key,
parameter: key,
description: value,
})
)}
columns={[
{ title: 'Parameter', dataIndex: 'parameter', key: 'p' },
{ title: 'Description', dataIndex: 'description', key: 'd' },
]}
pagination={false}
size="small"
/>
)}
{docs.default_port && (
<Text type="secondary">Default port: {docs.default_port}</Text>
)}
</Card>
)}
{/* Drivers */}
{renderDrivers()}
{/* Connection Examples */}
{docs?.connection_examples?.length > 0 && (
<Card title="Connection Examples" style={{ marginBottom: 16 }}>
{docs.connection_examples.map((example, idx) => (
<div key={idx}>
{renderConnectionString(
example.connection_string,
example.description
)}
</div>
))}
</Card>
)}
{/* Authentication Methods */}
{renderAuthMethods()}
{/* Engine Parameters */}
{renderEngineParams()}
{/* Features */}
{renderFeatures()}
{/* Time Grains */}
{renderTimeGrains()}
{/* Compatible Databases */}
{renderCompatibleDatabases()}
{/* Notes */}
{docs?.notes && (
<Alert
message="Notes"
description={docs.notes}
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
)}
{/* External Links */}
{(docs?.docs_url || docs?.tutorials?.length) && (
<Card
title={
<>
<BookOutlined /> Resources
</>
}
style={{ marginBottom: 16 }}
>
<Space direction="vertical">
{docs.docs_url && (
<a href={docs.docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> Official Documentation
</a>
)}
{docs.sqlalchemy_docs_url && (
<a href={docs.sqlalchemy_docs_url} target="_blank" rel="noreferrer">
<LinkOutlined /> SQLAlchemy Dialect Documentation
</a>
)}
{docs.tutorials?.map((tutorial, idx) => (
<a key={idx} href={tutorial} target="_blank" rel="noreferrer">
<LinkOutlined /> Tutorial {idx + 1}
</a>
))}
</Space>
</Card>
)}
{/* Edit link */}
{database.module && (
<Card
style={{
marginBottom: 16,
background: 'var(--ifm-background-surface-color)',
borderStyle: 'dashed',
}}
size="small"
>
<Space>
<GithubOutlined />
<Text type="secondary">
Help improve this documentation by editing the engine spec:
</Text>
<a
href={`https://github.com/apache/superset/edit/master/superset/db_engine_specs/${database.module}.py`}
target="_blank"
rel="noreferrer"
>
<EditOutlined /> Edit {database.module}.py
</a>
</Space>
</Card>
)}
<Divider />
</div>
);
};
export default DatabasePage;

View File

@@ -0,0 +1,22 @@
/**
* 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.
*/
export { default as DatabaseIndex } from './DatabaseIndex';
export { default as DatabasePage } from './DatabasePage';
export * from './types';

View File

@@ -0,0 +1,243 @@
/**
* 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.
*/
/**
* TypeScript types for database documentation data
* Generated from superset/db_engine_specs/lib.py
*/
export interface Driver {
name: string;
pypi_package?: string;
connection_string?: string;
is_recommended?: boolean;
notes?: string;
docs_url?: string;
default_port?: number;
odbc_driver_paths?: Record<string, string>;
environment_variables?: Record<string, string>;
}
export interface ConnectionExample {
description: string;
connection_string: string;
}
export interface HostExample {
platform: string;
host: string;
}
export interface AuthenticationMethod {
name: string;
description?: string;
requirements?: string;
connection_string?: string;
secure_extra?: Record<string, unknown>;
secure_extra_body?: Record<string, unknown>;
secure_extra_path?: Record<string, unknown>;
engine_parameters?: Record<string, unknown>;
config_example?: Record<string, unknown>;
notes?: string;
}
export interface EngineParameter {
name: string;
description?: string;
json?: Record<string, unknown>;
secure_extra?: Record<string, unknown>;
docs_url?: string;
}
export interface SSLConfiguration {
custom_certificate?: string;
disable_ssl_verification?: {
engine_params?: Record<string, unknown>;
};
}
export interface CompatibleDatabase {
name: string;
description?: string;
logo?: string;
homepage_url?: string;
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
pypi_packages?: string[];
connection_string?: string;
parameters?: Record<string, string>;
connection_examples?: ConnectionExample[];
notes?: string;
docs_url?: string;
}
export interface DatabaseDocumentation {
description?: string;
logo?: string;
homepage_url?: string;
categories?: string[]; // Category classifications (e.g., ["TRADITIONAL_RDBMS", "OPEN_SOURCE"])
pypi_packages?: string[];
connection_string?: string;
default_port?: number;
parameters?: Record<string, string>;
notes?: string;
limitations?: string[]; // Known limitations or caveats
connection_examples?: ConnectionExample[];
host_examples?: HostExample[];
drivers?: Driver[];
authentication_methods?: AuthenticationMethod[];
engine_parameters?: EngineParameter[];
ssl_configuration?: SSLConfiguration;
version_requirements?: string;
install_instructions?: string;
warnings?: string[];
tutorials?: string[];
docs_url?: string;
sqlalchemy_docs_url?: string;
advanced_features?: Record<string, string>;
compatible_databases?: CompatibleDatabase[];
}
export interface TimeGrains {
SECOND?: boolean;
MINUTE?: boolean;
HOUR?: boolean;
DAY?: boolean;
WEEK?: boolean;
MONTH?: boolean;
QUARTER?: boolean;
YEAR?: boolean;
FIVE_SECONDS?: boolean;
THIRTY_SECONDS?: boolean;
FIVE_MINUTES?: boolean;
TEN_MINUTES?: boolean;
FIFTEEN_MINUTES?: boolean;
THIRTY_MINUTES?: boolean;
HALF_HOUR?: boolean;
SIX_HOURS?: boolean;
WEEK_STARTING_SUNDAY?: boolean;
WEEK_STARTING_MONDAY?: boolean;
WEEK_ENDING_SATURDAY?: boolean;
WEEK_ENDING_SUNDAY?: boolean;
QUARTER_YEAR?: boolean;
}
export interface DatabaseInfo {
engine: string;
engine_name: string;
engine_aliases?: string[];
default_driver?: string;
module?: string;
documentation: DatabaseDocumentation;
// Diagnostics from lib.py diagnose() function
time_grains: TimeGrains;
score: number;
max_score: number;
// SQL capabilities
joins: boolean;
subqueries: boolean;
alias_in_select?: boolean;
alias_in_orderby?: boolean;
cte_in_subquery?: boolean;
sql_comments?: boolean;
escaped_colons?: boolean;
time_groupby_inline?: boolean;
alias_to_source_column?: boolean;
order_by_not_in_select?: boolean;
expressions_in_orderby?: boolean;
// Platform features
limit_method?: string;
limit_clause?: boolean;
max_column_name?: number;
supports_file_upload?: boolean;
supports_dynamic_schema?: boolean;
supports_catalog?: boolean;
supports_dynamic_catalog?: boolean;
// Advanced features
user_impersonation?: boolean;
ssh_tunneling?: boolean;
query_cancelation?: boolean;
expand_data?: boolean;
query_cost_estimation?: boolean;
sql_validation?: boolean;
get_metrics?: boolean;
where_latest_partition?: boolean;
get_extra_table_metadata?: boolean;
dbapi_exception_mapping?: boolean;
custom_errors?: boolean;
masked_encrypted_extra?: boolean;
column_type_mapping?: boolean;
function_names?: boolean;
}
export interface Statistics {
totalDatabases: number;
withDocumentation: number;
withConnectionString: number;
withDrivers: number;
withAuthMethods: number;
supportsJoins: number;
supportsSubqueries: number;
supportsDynamicSchema: number;
supportsCatalog: number;
averageScore: number;
maxScore: number;
byCategory: Record<string, string[]>;
}
export interface DatabaseData {
generated: string;
statistics: Statistics;
databases: Record<string, DatabaseInfo>;
}
// Helper type for sorting databases
export type SortField = 'name' | 'score' | 'category';
export type SortDirection = 'asc' | 'desc';
// Helper to get common time grains
export const COMMON_TIME_GRAINS = [
'SECOND',
'MINUTE',
'HOUR',
'DAY',
'WEEK',
'MONTH',
'QUARTER',
'YEAR',
] as const;
export const EXTENDED_TIME_GRAINS = [
'FIVE_SECONDS',
'THIRTY_SECONDS',
'FIVE_MINUTES',
'TEN_MINUTES',
'FIFTEEN_MINUTES',
'THIRTY_MINUTES',
'HALF_HOUR',
'SIX_HOURS',
'WEEK_STARTING_SUNDAY',
'WEEK_STARTING_MONDAY',
'WEEK_ENDING_SATURDAY',
'WEEK_ENDING_SUNDAY',
'QUARTER_YEAR',
] as const;

4799
docs/src/data/databases.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,32 @@ import { Card, Carousel, Flex } from 'antd';
import styled from '@emotion/styled';
import GitHubButton from 'react-github-btn';
import { mq } from '../utils';
import { Databases } from '../resources/data';
import SectionHeader from '../components/SectionHeader';
import databaseData from '../data/databases.json';
import BlurredSection from '../components/BlurredSection';
import DataSet from '../../../RESOURCES/INTHEWILD.yaml';
import type { DatabaseData } from '../components/databases/types';
import '../styles/main.less';
// Build database list from databases.json (databases with logos)
// Deduplicate by logo filename to avoid showing the same logo twice
const typedDatabaseData = databaseData as DatabaseData;
const seenLogos = new Set<string>();
const Databases = Object.entries(typedDatabaseData.databases)
.filter(([, db]) => db.documentation?.logo && db.documentation?.homepage_url)
.map(([name, db]) => ({
title: name,
href: db.documentation?.homepage_url,
imgName: db.documentation?.logo,
docPath: `/docs/databases/supported/${name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`,
}))
.sort((a, b) => a.title.localeCompare(b.title))
.filter((db) => {
if (seenLogos.has(db.imgName!)) return false;
seenLogos.add(db.imgName!);
return true;
});
interface Organization {
name: string;
url: string;
@@ -440,22 +460,22 @@ const StyledIntegrations = styled('div')`
padding: 0 20px;
.database-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 14px;
max-width: 1160px;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: 10px;
max-width: 1200px;
margin: 25px auto 0;
${mq[1]} {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
}
${mq[0]} {
grid-template-columns: repeat(1, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
}
& > .item {
border: 1px solid var(--ifm-border-color);
border-radius: 10px;
border-radius: 8px;
overflow: hidden;
height: 120px;
padding: 25px;
height: 80px;
padding: 14px;
display: flex;
align-items: center;
justify-content: center;
@@ -759,23 +779,19 @@ export default function Home(): JSX.Element {
</BlurredSection>
<BlurredSection>
<StyledIntegrations>
<SectionHeader level="h2" title="Supported Databases" />
<SectionHeader level="h2" title="Supported Databases" link="/docs/databases" />
<div className="database-grid">
{Databases.map(({ title, href, imgName }) => (
{Databases.map(({ title, imgName, docPath }) => (
<div className="item" key={title}>
{href ? (
<a href={href} aria-label={`Go to ${title} page`}>
<img src={`/img/databases/${imgName}`} title={title} />
</a>
) : (
<a href={docPath} aria-label={`${title} documentation`}>
<img src={`/img/databases/${imgName}`} title={title} />
)}
</a>
</div>
))}
</div>
<span className="database-sub">
...and many other{' '}
<a href="/docs/configuration/databases#installing-database-drivers">
<a href="/docs/databases#installing-database-drivers">
compatible databases
</a>
</span>

View File

@@ -123,6 +123,11 @@ ul.dropdown__menu svg {
--ifm-code-padding-horizontal: 5px;
}
/* Database logo images in intro/README */
.database-logo {
object-fit: contain;
}
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;

BIN
docs/static/img/databases/altinity.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 207.3 107.8" enable-background="new 0 0 207.3 107.8" xml:space="preserve">
<g>
<path fill="#FFFFFF" d="M43.1,73.3c-2.3-1.2-5-2.1-7.9-2.6c-2.8-0.5-5.7-0.7-8.6-0.7c-2.3,0-4.7-0.2-7-0.6
c-2.3-0.4-4.3-1.1-6.1-2.1c-1.7-1-3.2-2.4-4.3-4.1c-1.1-1.7-1.6-4-1.6-6.7C7.8,54,8.3,52,9.4,50.3c1.1-1.7,2.5-3.1,4.1-4.1
c1.7-1,3.7-1.8,5.9-2.3c3.8-0.8,7.7-0.9,11.7-0.4c1.6,0.2,3.1,0.6,4.6,1.2c1.5,0.5,2.9,1.3,4.1,2.2c1.2,0.9,2.3,2.1,3.2,3.4
l0.5,0.8l3.8-1.6l-0.8-1.1c-1-1.5-2.2-2.8-3.4-3.9c-1.2-1.2-2.7-2.1-4.4-2.9c-1.6-0.8-3.5-1.4-5.5-1.8c-2-0.4-4.4-0.6-7-0.6
c-2.5,0-5.1,0.3-7.7,0.9c-2.6,0.6-5.1,1.5-7.3,2.8c-2.2,1.3-4.1,3.1-5.5,5.3c-1.4,2.2-2.1,5-2.1,8.1c0,3.3,0.7,6.2,2,8.4
c1.3,2.3,3.1,4.1,5.2,5.4c2.1,1.3,4.6,2.3,7.3,2.8c2.7,0.5,5.5,0.8,8.4,0.8c2.3,0,4.7,0.2,7.2,0.5c2.4,0.3,4.7,1,6.7,2
c2,1,3.6,2.3,4.9,4c1.2,1.6,1.8,3.8,1.8,6.6c0,2.4-0.6,4.5-1.8,6.2c-1.2,1.7-2.8,3.1-4.7,4.2c-1.9,1.1-4.1,1.9-6.5,2.4
c-2.4,0.5-4.8,0.8-7.1,0.8c-3.9,0-7.7-0.7-11.4-2.2c-3.7-1.5-6.9-3.6-9.6-6.5l-0.7-0.8l-3,2.6l0.8,0.8c2.7,2.7,6.1,5.1,10,7
c4,2,8.7,3,13.9,3c2.5,0,5.2-0.3,7.9-0.9c2.8-0.6,5.4-1.6,7.8-2.9c2.4-1.4,4.4-3.2,6-5.4c1.6-2.3,2.4-5.1,2.4-8.4
c0-3.4-0.8-6.2-2.2-8.5C47.4,76.3,45.5,74.6,43.1,73.3z"/>
<path fill="#FFFFFF" d="M95.3,63.7c-2-2.3-4.4-4.2-7.2-5.7c-2.8-1.5-6-2.2-9.4-2.2c-3.1,0-6.1,0.7-8.9,2c-2.7,1.3-5.2,3.1-7.2,5.4
c-2,2.2-3.7,4.9-4.9,7.9c-1.2,3-1.8,6.2-1.8,9.4c0,3.1,0.6,6.1,1.6,9c1.1,2.9,2.6,5.5,4.6,7.8c2,2.3,4.4,4.2,7.2,5.6
c2.8,1.4,5.9,2.2,9.3,2.2h0h0c3.2-0.1,6.2-0.8,9-2.1c2.7-1.3,5.2-3.2,7.2-5.4c2-2.2,3.7-4.9,4.8-7.8c1.2-2.9,1.7-6,1.7-9.3
c0-2.9-0.5-5.9-1.6-8.8C98.8,68.7,97.3,66,95.3,63.7z M78.7,101.1c-2.7,0-5.2-0.6-7.4-1.7c-2.2-1.1-4.2-2.7-5.8-4.6
c-1.7-1.9-3-4.1-3.9-6.6c-0.9-2.5-1.5-5.1-1.6-7.8c0-2.4,0.4-4.9,1.3-7.4c0.9-2.5,2.2-4.7,3.8-6.7c1.7-2,3.7-3.6,6-4.8
c2.3-1.2,4.8-1.8,7.6-1.8c2.6,0,5.1,0.6,7.3,1.7c2.3,1.2,4.3,2.7,6,4.6c1.7,1.9,3,4.1,4,6.6c1,2.5,1.5,5.1,1.5,7.7
c0,2.4-0.4,4.9-1.3,7.4c-0.9,2.5-2.2,4.7-3.8,6.7c-1.7,2-3.7,3.6-6,4.8C84.1,100.5,81.5,101.1,78.7,101.1z"/>
<path fill="#FFFFFF" d="M122.1,100c-0.7,0.2-1.3,0.3-1.8,0.4c-0.5,0.1-1.1,0.2-1.7,0.3c-0.6,0.1-1.1,0.1-1.6,0.1
c-1.3,0-2.3-0.4-3-1.4c-0.8-1-1.1-2-1.1-3V38h-4v58.3c0,2.2,0.7,4.1,2.1,5.8c1.4,1.7,3.4,2.5,6,2.5c0.8,0,1.6,0,2.4-0.1
c0.7-0.1,1.4-0.2,2-0.3c0.6-0.1,1.3-0.2,2.1-0.4l1.4-0.3l-1.8-3.7L122.1,100z"/>
<path fill="#FFFFFF" d="M139.3,59.8c-2.1,1.5-3.9,3.3-5.5,5.5v-8.8h-4v47.7h4V74.5c0.6-1.9,1.3-3.8,2.3-5.4c1-1.7,2.1-3.1,3.5-4.4
c1.4-1.2,3-2.2,4.8-3c1.8-0.7,3.8-1.2,6-1.3l1.1-0.1v-3.9h-1.1C146.2,56.5,142.4,57.6,139.3,59.8z"/>
</g>
<g>
<path fill="#262130" d="M196.2,13l-33,35.7l41.5-19.1C203.5,23.2,200.5,17.5,196.2,13z"/>
<path fill="#262130" d="M171.7,2.4c-4.6,0-8.9,0.9-12.9,2.6l-4.4,37.3l22.1-39.5C175,2.5,173.4,2.4,171.7,2.4z"/>
<path fill="#262130" d="M205,31.6l-39.3,22l36.9-4.4c1.7-4.1,2.7-8.5,2.7-13.2C205.3,34.5,205.2,33,205,31.6z"/>
<path fill="#262130" d="M190.5,63.8c4.4-3,8-7,10.6-11.6L166.6,59L190.5,63.8z"/>
<path fill="#262130" d="M178.6,3.1l-19.2,41.7l35.7-33C190.5,7.5,184.9,4.4,178.6,3.1z"/>
<path fill="#262130" d="M174.6,69.4c3.7-0.3,7.3-1.2,10.5-2.6l-19.4-2.3L174.6,69.4z"/>
<path fill="#262130" d="M141.1,22.2c-1.5,3.3-2.5,7-2.8,10.8l5.2,9.3L141.1,22.2z"/>
<path fill="#262130" d="M155.9,6.3c-4.7,2.5-8.8,6.2-11.8,10.6l4.9,24.5L155.9,6.3z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/static/img/databases/ascend.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/static/img/databases/aws-aurora.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
docs/static/img/databases/aws.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

1
docs/static/img/databases/azure.svg vendored Normal file
View File

@@ -0,0 +1 @@
<svg id="f2f5701e-cb3b-4d6f-b407-5866ec5b7784" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a891901b-79ae-490a-8568-9c4334417d35" x1="9" y1="5.38" x2="9" gradientUnits="userSpaceOnUse"><stop offset="0.199" stop-color="#005ba1"/><stop offset="1" stop-color="#0078d4"/></linearGradient><linearGradient id="bbdaa009-2281-4da8-9e89-6f41689e91a7" x1="9" y1="12.713" x2="9" y2="5.287" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#198ab3"/><stop offset="0.172" stop-color="#32bedd"/><stop offset="0.5" stop-color="#50e6ff"/><stop offset="0.5" stop-color="#4fe4fd"/><stop offset="0.5" stop-color="#4bddf8"/><stop offset="0.5" stop-color="#44d2ee"/><stop offset="0.5" stop-color="#3ac1e0"/><stop offset="0.5" stop-color="#2dabce"/><stop offset="0.5" stop-color="#1d90b8"/><stop offset="0.5" stop-color="#198ab3"/><stop offset="0.662" stop-color="#32bedd"/><stop offset="0.975" stop-color="#50e6ff"/></linearGradient></defs><path d="M9,0,1.15,4.49v8.97L9,18l7.85-4.49v-9Zm6.4,12.57L9,16.27,2.6,12.609V5.38L9,1.68l6.4,3.71Z" fill="#0078d4"/><polygon points="9 0 9 0 1.15 4.49 2.6 5.38 9 1.68 9 1.68 15.4 5.38 16.85 4.49 9 0" fill="url(#a891901b-79ae-490a-8568-9c4334417d35)"/><path d="M12.74,10.475a.73.73,0,0,0-.323-.286A5.835,5.835,0,0,0,7.939,6.843L14.416,3.1,12.91,2.236,5.534,6.5A.75.75,0,0,0,5.91,7.9.684.684,0,0,0,6,7.877l.125.523a4.319,4.319,0,0,1,4.837,2.238L3.613,14.885l1.5.866L12.466,11.5a.729.729,0,0,0,.242-.236l.075-.018c-.007-.029-.018-.055-.025-.084A.735.735,0,0,0,12.74,10.475Z" fill="#50e6ff"/><path d="M12.091,9.013a1.85,1.85,0,1,0,1.85,1.85A1.85,1.85,0,0,0,12.091,9.013ZM5.909,5.267a1.85,1.85,0,1,0,1.85,1.85A1.85,1.85,0,0,0,5.909,5.267Z" fill="url(#bbdaa009-2281-4da8-9e89-6f41689e91a7)"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
docs/static/img/databases/celerdata.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
docs/static/img/databases/cloudflare.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

BIN
docs/static/img/databases/cratedb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

BIN
docs/static/img/databases/duckdb.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
docs/static/img/databases/imply.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/static/img/databases/kusto.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/static/img/databases/motherduck.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/static/img/databases/risingwave.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/static/img/databases/shillelagh.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/static/img/databases/starburst.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

43
docs/static/img/databases/superset.svg vendored Normal file
View File

@@ -0,0 +1,43 @@
<!--
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.
-->
<svg width="100%" height="100%" viewBox="0 0 266 69" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M73.79,15.23C67.32,15.23 61.36,18.87 55.6,25.23C49.94,18.77 43.88,15.23 37.11,15.23C25.9,15.23 17.72,23.23 17.72,34C17.72,44.77 25.9,52.67 37.11,52.67C44,52.67 49.34,49.44 55.3,43C61.06,49.46 66.92,52.69 73.79,52.69C85,52.67 93.18,44.8 93.18,34C93.18,23.2 85,15.23 73.79,15.23ZM37.19,41.37C32.44,41.37 29.61,38.24 29.61,34.1C29.61,29.96 32.44,26.74 37.19,26.74C41.19,26.74 44.46,29.96 48,34.3C44.66,38.34 41.13,41.37 37.19,41.37ZM73.45,41.37C69.51,41.37 66.18,38.24 62.64,34.1C66.28,29.76 69.41,26.74 73.45,26.74C78.2,26.74 81,30 81,34.1C81,38.2 78.2,41.37 73.45,41.37Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M63.74,50L71.28,41C68.28,40.1 65.51,37.4 62.64,34.05L55.3,43C57.703,45.788 60.556,48.154 63.74,50Z" style="fill:rgb(32,167,201);fill-rule:nonzero;"/>
<g id="Main">
<g id="Superset">
<g id="Full-Lockup-With-Text">
<g id="Group-7">
<g id="Group-17">
<g id="Superset-Copy">
<g>
<path d="M116.72,40.39C116.751,39.474 116.36,38.592 115.66,38C114.539,37.193 113.272,36.609 111.93,36.28C109.421,35.66 107.048,34.582 104.93,33.1C103.37,31.922 102.481,30.053 102.55,28.1C102.528,26.015 103.555,24.052 105.28,22.88C107.327,21.458 109.79,20.754 112.28,20.88C114.812,20.767 117.301,21.577 119.28,23.16C120.994,24.509 121.961,26.601 121.88,28.78L121.88,28.88L116.82,28.88C116.861,27.778 116.419,26.71 115.61,25.96C114.667,25.171 113.457,24.773 112.23,24.85C111.077,24.779 109.934,25.104 108.99,25.77C108.263,26.344 107.842,27.224 107.85,28.15C107.867,28.99 108.298,29.769 109,30.23C110.313,31.008 111.726,31.603 113.2,32C115.582,32.553 117.81,33.633 119.72,35.16C121.197,36.462 122.013,38.362 121.94,40.33C122.008,42.418 121.013,44.404 119.3,45.6C117.238,46.985 114.78,47.662 112.3,47.53C109.663,47.589 107.072,46.823 104.89,45.34C102.838,43.996 101.66,41.648 101.81,39.2L101.81,39.09L107,39.09C106.889,40.389 107.42,41.664 108.42,42.5C109.597,43.291 111.004,43.671 112.42,43.58C113.571,43.658 114.716,43.348 115.67,42.7C116.371,42.144 116.762,41.283 116.72,40.39Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M137,44.4C136.453,45.359 135.672,46.164 134.73,46.74C132.116,48.188 128.835,47.72 126.73,45.6C125.583,44.267 125.01,42.24 125.01,39.52L125.01,27.85L130.21,27.85L130.21,39.58C130.131,40.629 130.379,41.678 130.92,42.58C131.434,43.208 132.22,43.551 133.03,43.5C133.767,43.516 134.498,43.38 135.18,43.1C135.764,42.836 136.268,42.422 136.64,41.9L136.64,27.85L141.86,27.85L141.86,47.18L137.41,47.18L137,44.4Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M162.87,38.05C162.99,40.508 162.286,42.937 160.87,44.95C159.569,46.68 157.492,47.658 155.33,47.56C154.4,47.575 153.478,47.384 152.63,47C151.843,46.61 151.158,46.042 150.63,45.34L150.63,54.62L145.43,54.62L145.43,27.85L150.13,27.85L150.44,30.13C150.968,29.331 151.673,28.664 152.5,28.18C153.363,27.707 154.336,27.469 155.32,27.49C157.535,27.403 159.644,28.467 160.89,30.3C162.313,32.49 163.013,35.072 162.89,37.68L162.87,38.05ZM157.65,37.65C157.71,36.118 157.397,34.595 156.74,33.21C156.228,32.144 155.132,31.476 153.95,31.51C153.253,31.49 152.562,31.656 151.95,31.99C151.393,32.322 150.937,32.799 150.63,33.37L150.63,41.86C150.942,42.394 151.4,42.828 151.95,43.11C152.573,43.411 153.259,43.558 153.95,43.54C155.082,43.61 156.161,43.032 156.73,42.05C157.376,40.819 157.684,39.439 157.62,38.05L157.65,37.65Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M174.21,47.56C171.699,47.674 169.258,46.696 167.52,44.88C165.828,43.026 164.93,40.579 165.02,38.07L165.02,37.36C164.918,34.784 165.761,32.258 167.39,30.26C170.696,26.757 176.29,26.572 179.82,29.85C181.338,31.617 182.119,33.903 182,36.23L182,39.07L170.43,39.07L170.43,39.18C170.48,40.34 170.933,41.447 171.71,42.31C172.51,43.146 173.634,43.595 174.79,43.54C175.762,43.562 176.732,43.444 177.67,43.19C178.539,42.91 179.377,42.542 180.17,42.09L181.58,45.32C180.656,46.037 179.609,46.579 178.49,46.92C177.108,47.366 175.662,47.582 174.21,47.56ZM173.74,31.56C172.841,31.53 171.983,31.946 171.45,32.67C170.859,33.531 170.513,34.537 170.45,35.58L170.5,35.67L176.9,35.67L176.9,35.21C176.949,34.261 176.674,33.322 176.12,32.55C175.546,31.835 174.655,31.446 173.74,31.51L173.74,31.56Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M195.3,32.33L193.38,32.33C192.711,32.303 192.047,32.47 191.47,32.81C190.964,33.141 190.567,33.614 190.33,34.17L190.33,47.18L185.13,47.18L185.13,27.85L190,27.85L190.23,30.71C190.616,29.787 191.224,28.972 192,28.34C192.71,27.776 193.594,27.476 194.5,27.49C194.741,27.488 194.982,27.508 195.22,27.55L195.89,27.7L195.3,32.33Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M208.32,41.86C208.308,41.257 207.996,40.698 207.49,40.37C206.544,39.809 205.498,39.435 204.41,39.27C202.553,38.979 200.785,38.271 199.24,37.2C198.087,36.32 197.433,34.93 197.49,33.48C197.487,31.814 198.265,30.24 199.59,29.23C201.198,28.003 203.19,27.386 205.21,27.49C207.312,27.38 209.391,27.991 211.1,29.22C212.489,30.234 213.279,31.882 213.2,33.6L213.2,33.71L208.2,33.71C208.226,33.002 207.958,32.314 207.46,31.81C206.859,31.287 206.074,31.024 205.28,31.08C204.561,31.04 203.85,31.26 203.28,31.7C202.816,32.075 202.55,32.644 202.56,33.24C202.551,33.826 202.837,34.379 203.32,34.71C204.271,35.243 205.318,35.582 206.4,35.71C208.308,35.991 210.126,36.71 211.71,37.81C212.862,38.729 213.506,40.148 213.44,41.62C213.458,43.325 212.62,44.93 211.21,45.89C209.473,47.062 207.403,47.641 205.31,47.54C203.1,47.652 200.925,46.939 199.21,45.54C197.817,44.508 196.996,42.873 197,41.14L197,41.04L201.77,41.04C201.72,41.907 202.093,42.746 202.77,43.29C203.515,43.784 204.397,44.029 205.29,43.99C206.067,44.039 206.838,43.835 207.49,43.41C208.012,43.069 208.326,42.484 208.32,41.86Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M224.86,47.56C222.352,47.674 219.914,46.696 218.18,44.88C216.488,43.026 215.59,40.579 215.68,38.07L215.68,37.36C215.579,34.786 216.419,32.261 218.04,30.26C221.346,26.757 226.94,26.572 230.47,29.85C231.992,31.615 232.77,33.903 232.64,36.23L232.64,39.07L221.09,39.07L221.09,39.18C221.137,40.339 221.587,41.446 222.36,42.31C223.162,43.149 224.291,43.598 225.45,43.54C226.419,43.562 227.385,43.444 228.32,43.19C229.193,42.912 230.034,42.544 230.83,42.09L232.24,45.32C231.315,46.035 230.268,46.577 229.15,46.92C227.765,47.366 226.315,47.582 224.86,47.56ZM224.4,31.56C223.5,31.526 222.641,31.943 222.11,32.67C221.519,33.532 221.174,34.537 221.11,35.58L221.17,35.67L227.57,35.67L227.57,35.21C227.619,34.261 227.344,33.322 226.79,32.55C226.214,31.832 225.318,31.442 224.4,31.51L224.4,31.56Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
<path d="M242.35,23.11L242.35,27.85L245.61,27.85L245.61,31.51L242.35,31.51L242.35,41.36C242.296,41.937 242.465,42.513 242.82,42.97C243.15,43.299 243.604,43.474 244.07,43.45C244.304,43.451 244.538,43.435 244.77,43.4C245.003,43.363 245.233,43.313 245.46,43.25L245.91,47.02C245.408,47.195 244.893,47.332 244.37,47.43C243.834,47.516 243.293,47.56 242.75,47.56C241.219,47.662 239.712,47.126 238.59,46.08C237.508,44.765 236.984,43.077 237.13,41.38L237.13,31.51L234.31,31.51L234.31,27.85L237.13,27.85L237.13,23.11L242.35,23.11Z" style="fill:rgb(72,72,72);fill-rule:nonzero;"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
<path d="M55.6,25.22C53.213,22.392 50.378,19.973 47.21,18.06L39.66,27.16C42.53,28.16 45.07,30.74 47.77,34.03L48.07,34.24L55.6,25.22Z" style="fill:rgb(32,167,201);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -8,6 +8,8 @@
"strict": false,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"types": ["@docusaurus/module-type-aliases"],
"paths": {
"@superset-ui/core": ["../superset-frontend/packages/superset-ui-core/src"],

View File

@@ -1,7 +1,7 @@
{
"docs": {
"disabled": false,
"lastVersion": "6.0.0",
"lastVersion": "current",
"includeCurrentVersion": true,
"onlyIncludeVersions": [
"current",
@@ -11,7 +11,7 @@
"current": {
"label": "Next",
"path": "",
"banner": "unreleased"
"banner": "none"
},
"6.0.0": {
"label": "6.0.0",