Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ tree.txt
_ai-distrib/
.mcpregistry*
**/Playground.java
**/dice.png

# Created by https://www.toptal.com/developers/gitignore/api/java,eclipse,intellij+all,visualstudiocode,maven,gradle
# Edit at https://www.toptal.com/developers/gitignore?templates=java,eclipse,intellij+all,visualstudiocode,maven,gradle
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SchemaCrawler AI
* http://www.schemacrawler.com
* Copyright (c) 2000-2025, Sualeh Fatehi <sualeh@hotmail.com>.
* All rights reserved.
* SPDX-License-Identifier: CC-BY-NC-4.0
*/

package schemacrawler.tools.ai.functions;

import schemacrawler.tools.ai.tools.base.AbstractFunctionDefinition;

public final class DiagramFunctionDefinition
extends AbstractFunctionDefinition<DiagramFunctionParameters> {

@Override
public String getDescription() {
return """
Generates a database diagram in the specified format.
"""
.stripIndent()
.replace("\n", " ")
.trim();
}

@Override
public Class<DiagramFunctionParameters> getParametersClass() {
return DiagramFunctionParameters.class;
}

@Override
public String getTitle() {
return "Generate database diagram";
}

@Override
public DiagramFunctionExecutor newExecutor() {
return new DiagramFunctionExecutor(getFunctionName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SchemaCrawler AI
* http://www.schemacrawler.com
* Copyright (c) 2000-2025, Sualeh Fatehi <sualeh@hotmail.com>.
* All rights reserved.
* SPDX-License-Identifier: CC-BY-NC-4.0
*/

package schemacrawler.tools.ai.functions;

import schemacrawler.inclusionrule.InclusionRule;
import schemacrawler.tools.ai.functions.DiagramFunctionParameters.DiagramType;
import schemacrawler.tools.ai.tools.base.AbstractExecutableFunctionExecutor;
import schemacrawler.tools.options.Config;
import us.fatehi.utility.property.PropertyName;

public final class DiagramFunctionExecutor
extends AbstractExecutableFunctionExecutor<DiagramFunctionParameters> {

protected DiagramFunctionExecutor(final PropertyName functionName) {
super(functionName);
}

@Override
protected String getCommand() {
return "script";
}

@Override
protected InclusionRule grepTablesInclusionRule() {
return makeInclusionRule(commandOptions.tableName());
}

@Override
protected Config createAdditionalConfig() {
final Config additionalConfig = super.createAdditionalConfig();

final DiagramType diagramType = commandOptions.diagramType();
additionalConfig.put("script-language", "python");
additionalConfig.put("script", diagramType.script());
return additionalConfig;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* SchemaCrawler AI
* http://www.schemacrawler.com
* Copyright (c) 2000-2025, Sualeh Fatehi <sualeh@hotmail.com>.
* All rights reserved.
* SPDX-License-Identifier: CC-BY-NC-4.0
*/

package schemacrawler.tools.ai.functions;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import schemacrawler.tools.ai.tools.FunctionParameters;
import schemacrawler.tools.ai.tools.FunctionReturnType;
import schemacrawler.tools.ai.tools.base.ParameterUtility;

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public record DiagramFunctionParameters(
@JsonPropertyDescription(
"""
Name of database table or view to describe.
May be specified as a regular expression, matching the fully qualified
table name (including the schema).
Use an empty string if all tables are requested.
If not specified, all tables will be returned, but the results
could be large.
""")
@JsonProperty(required = false)
String tableName,
@JsonPropertyDescription(
"""
Indicates database schema diagram format - Graphviz, PlantUML, Mermaid
or DBML from dbdiagram.io.
""")
@JsonProperty(required = true)
DiagramType diagramType)
implements FunctionParameters {

public enum DiagramType {
PLANTUML("/scripts/plantuml.py", "https://editor.plantuml.com/"),
MERMAID("/scripts/mermaid.py", "https://mermaid.live/"),
DBML("/scripts/dbml.py", "https://dbdiagram.io/d"),
;

private final String script;
private final String onlineEditorUrl;

private DiagramType(final String script, final String url) {
this.script = script;
onlineEditorUrl = url;
}

public String getOnlineEditorUrl() {
return onlineEditorUrl;
}

public String script() {
return script;
}
}

public DiagramFunctionParameters {
if (tableName == null || tableName.isBlank()) {
tableName = "";
}
if (diagramType == null) {
diagramType = DiagramType.PLANTUML;
}
}

@Override
public String toString() {
return ParameterUtility.parametersToString(this);
}

@JsonIgnore
@Override
public final FunctionReturnType getFunctionReturnType() {
return FunctionReturnType.TEXT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@

package schemacrawler.tools.ai.tools;

import static java.util.Objects.requireNonNull;
import static schemacrawler.tools.ai.utility.JsonUtility.mapper;
import static us.fatehi.utility.Utility.isBlank;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.sql.Connection;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static java.util.Objects.requireNonNull;
import static us.fatehi.utility.Utility.isBlank;
import schemacrawler.schema.Catalog;
import schemacrawler.schemacrawler.exceptions.InternalRuntimeException;
import us.fatehi.utility.property.PropertyName;
import us.fatehi.utility.string.StringFormat;

Expand Down Expand Up @@ -72,8 +72,12 @@ public FunctionReturn execute(final String argumentsString, final Connection con
Level.INFO,
e,
new StringFormat(
"Exception executing: %s%n%s", toCallObject(argumentsString), e.getMessage()));
return new ExceptionFunctionReturn(e);
"Exception executing: <%s>%n%s", toCallObject(argumentsString), e.getMessage()));
if (e instanceof final RuntimeException runex) {
throw runex;
}
throw new InternalRuntimeException(
"Exception executing <%s>" + getFunctionName().getName(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@
import java.util.function.Supplier;

public sealed interface FunctionReturn extends Supplier<String>
permits TextFunctionReturn, JsonFunctionReturn, ExceptionFunctionReturn {}
permits NoResultsFunctionReturn,
TextFunctionReturn,
JsonFunctionReturn,
ExceptionFunctionReturn {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* SchemaCrawler AI
* http://www.schemacrawler.com
* Copyright (c) 2000-2025, Sualeh Fatehi <sualeh@hotmail.com>.
* All rights reserved.
* SPDX-License-Identifier: CC-BY-NC-4.0
*/

package schemacrawler.tools.ai.tools;

public record NoResultsFunctionReturn() implements FunctionReturn {

@Override
public String get() {
return "No results returned from tool call.";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import static schemacrawler.tools.ai.utility.JsonUtility.mapper;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.StringWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand All @@ -25,6 +24,7 @@
import schemacrawler.tools.ai.tools.FunctionReturn;
import schemacrawler.tools.ai.tools.FunctionReturnType;
import schemacrawler.tools.ai.tools.JsonFunctionReturn;
import schemacrawler.tools.ai.tools.NoResultsFunctionReturn;
import schemacrawler.tools.ai.tools.TextFunctionReturn;
import schemacrawler.tools.command.text.schema.options.SchemaTextOptionsBuilder;
import schemacrawler.tools.executable.SchemaCrawlerExecutable;
Expand Down Expand Up @@ -67,15 +67,7 @@ public final FunctionReturn call() {
executable.execute();

if (!hasResults()) {
switch (functionReturnType) {
case JSON:
final ObjectNode objectNode = mapper.createObjectNode();
objectNode.put("message", "No results");
return new JsonFunctionReturn(objectNode);
case TEXT:
default:
return new TextFunctionReturn("There were no matching results for your query.");
}
return new NoResultsFunctionReturn();
}

final String results = writer.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,27 @@

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder({"error-message", "error-type", "stack-trace"})
@JsonPropertyOrder({"error-message", "error-type"})
public class ExceptionInfo {

public static String getStackTraceHead(final Throwable ex, final int maxLines) {
final StringBuilder sb = new StringBuilder();
sb.append(ex.toString()).append("\n");

final StackTraceElement[] elements = ex.getStackTrace();
for (int i = 0; i < Math.min(maxLines, elements.length); i++) {
sb.append("\tat ").append(elements[i].toString()).append("\n");
}

return sb.toString();
}

public final String type;
public final String message;
public final String stackTrace;

public ExceptionInfo(final Exception ex) {
if (ex == null) {
type = "";
message = "An unknown error occurred";
stackTrace = "";
return;
} else {
type = ex.getClass().getName();
message = ex.getMessage();
}
type = ex.getClass().getName();
message = ex.getMessage();
stackTrace = getStackTraceHead(ex, 8);
}

@JsonProperty("error-message")
public String getMessage() {
return message;
}

@JsonProperty("stack-trace")
public String getStackTrace() {
return stackTrace;
}

@JsonProperty("error-type")
public String getType() {
return type;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
schemacrawler.tools.ai.functions.DescribeRoutinesFunctionDefinition
schemacrawler.tools.ai.functions.DescribeTablesFunctionDefinition
schemacrawler.tools.ai.functions.DiagramFunctionDefinition
schemacrawler.tools.ai.functions.LintFunctionDefinition
schemacrawler.tools.ai.functions.ListAcrossTablesFunctionDefinition
schemacrawler.tools.ai.functions.ListFunctionDefinition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.junit.jupiter.api.Test;
import schemacrawler.tools.ai.functions.DescribeRoutinesFunctionDefinition;
import schemacrawler.tools.ai.functions.DescribeTablesFunctionDefinition;
import schemacrawler.tools.ai.functions.DiagramFunctionDefinition;
import schemacrawler.tools.ai.functions.LintFunctionDefinition;
import schemacrawler.tools.ai.functions.ListAcrossTablesFunctionDefinition;
import schemacrawler.tools.ai.functions.ListFunctionDefinition;
Expand All @@ -32,7 +33,7 @@

public class FunctionDefinitionRegistryTest {

private static final int NUM_FUNCTIONS = 7;
private static final int NUM_FUNCTIONS = 8;

@Test
public void name() {
Expand Down Expand Up @@ -60,6 +61,7 @@ public void registeredPlugins() {
"lint",
"list",
"list-across-tables",
"diagram",
"server-information",
"table-sample"));
}
Expand All @@ -80,6 +82,7 @@ public void testCommandPlugin() throws Exception {
LintFunctionDefinition.class.getSimpleName(),
ListFunctionDefinition.class.getSimpleName(),
ListAcrossTablesFunctionDefinition.class.getSimpleName(),
DiagramFunctionDefinition.class.getSimpleName(),
ServerInformationFunctionDefinition.class.getSimpleName(),
TableSampleFunctionDefinition.class.getSimpleName()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static java.util.Objects.requireNonNull;
import static schemacrawler.tools.ai.mcpserver.utility.LoggingUtility.log;
import static schemacrawler.tools.ai.mcpserver.utility.LoggingUtility.logExceptionToClient;
import static schemacrawler.tools.ai.utility.JsonUtility.mapper;

import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -57,6 +58,10 @@ public CallToolResult apply(
final Connection connection = ConnectionService.getConnection();
functionReturn = functionCallback.execute(arguments, connection);
} catch (final Exception e) {
logExceptionToClient(
exchange,
functionCallback.getFunctionName().getName() + ":\n" + request.arguments(),
e);
functionReturn = new ExceptionFunctionReturn(e);
}
final List<Content> content = createContent(functionReturn);
Expand Down
Loading
Loading