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
24 changes: 22 additions & 2 deletions docs/src/main/asciidoc/assistant.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,17 @@ You can invoke the assistant from backend code using xref:dev-ui.adoc#communicat

==== JsonRPC against the Runtime classpath

To use the assistant in a xref:dev-ui.adoc#jsonrpc-against-the-runtime-classpath[`JsonRpcService`] on the runtime classpath, inject the `Assistant` like this:
To use the assistant in a xref:dev-ui.adoc#jsonrpc-against-the-runtime-classpath[`JsonRpcService`] on the runtime classpath, you need to make sure the `Assistant` interface is available:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-assistant-dev</artifactId>
</dependency>
----

Then you can inject the `Assistant` like this:

[source,java]
----
Expand All @@ -201,7 +211,17 @@ You can now use this assistant in any JsonRPC method, example:

==== JsonRPC against the Deployment classpath

In xref:dev-ui.adoc#jsonrpc-against-the-deployment-classpath[deployment-time] code, use the `BuildTimeActionBuildItem` and register assistant actions via `.addAssistantAction(...)`:
In xref:dev-ui.adoc#jsonrpc-against-the-deployment-classpath[deployment-time] code, you need to add the assistant-deployment-spi:

[source,xml]
----
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-assistant-deployment-spi</artifactId>
</dependency>
----

Then use the `BuildTimeActionBuildItem` and register assistant actions via `.addAssistantAction(...)`:

[source,java]
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,26 @@ void devUI(DataSourcesJdbcBuildTimeConfig config,
void createBuildTimeActions(BuildProducer<BuildTimeActionBuildItem> buildTimeActionProducer) {
BuildTimeActionBuildItem bta = new BuildTimeActionBuildItem();

// TODO: If currentInsertScript is empty, maybe send tables schema. This might mean we need to move this to runtime
bta.actionBuilder()
.methodName("generateMoreData")
.assistantFunction((a, p) -> {
Assistant assistant = (Assistant) a;
return assistant.assistBuilder()
.userMessage(ADD_DATA_MESSAGE)
.variables(p)
.assist();
}).build();

bta.addAssistantAction("generateMoreData", (a, p) -> {
Assistant assistant = (Assistant) a;
return assistant.assistBuilder()
.userMessage(USER_MESSAGE)
.variables(p)
.assist();
});
buildTimeActionProducer.produce(bta);

}

@BuildStep
JsonRPCProvidersBuildItem createJsonRPCService() {
return new JsonRPCProvidersBuildItem(DatabaseInspector.class);
}

private static final String USER_MESSAGE = """
private static final String ADD_DATA_MESSAGE = """
Given the provided sql script:
{{currentInsertScript}}
Can you add 10 more inserts into the script and return the result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
text-decoration: none;
color: var(--lumo-primary-text-color);
}
.generateTableDataButton {
align-self: center;
padding-top: 50px;
}
`;

static properties = {
Expand Down Expand Up @@ -365,6 +369,29 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
}
}

_generateInitialData(){
this._showBusyLoadingDialog = "Quarkus Assistant is generating data ... please wait";
this.jsonRpc.generateTableData({
datasource:this._selectedDataSource.name,
schema: this._selectedTable.tableSchema,
name: this._selectedTable.tableName,
rowCount: 5
}).then(jsonRpcResponse => {
const script = jsonRpcResponse.result.script;
if (Array.isArray(script)) {
this._currentSQL = script.join('\n');
} else {
this._currentSQL = script;
}
this._showBusyLoadingDialog = null;
this._showImportSQLDialog = true;
this._showAssistantWarning = true;



});
}

_saveInsertScript(){
try {
const blob = new Blob([this.value], { type: 'text/sql' });
Expand Down Expand Up @@ -525,13 +552,7 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
if(this._selectedTable && this._currentDataSet && this._currentDataSet.cols){
return html`<div class="data">
${this._renderSqlInput()}
<vaadin-grid .items="${this._currentDataSet.data}" theme="row-stripes no-border" class="fill" column-reordering-allowed>
${this._currentDataSet.cols.map((col) =>
this._renderTableHeader(col)
)}
<span slot="empty-state">No data.</span>
</vaadin-grid>
${this._renderPager()}
${this._renderTableDataGrid()}
</div>
`;
}else if(this._displaymessage){
Expand All @@ -544,6 +565,20 @@ export class QwcAgroalDatasource extends observeState(QwcHotReloadElement) {
}
}

_renderTableDataGrid(){
if((this._currentDataSet.data && this._currentDataSet.data.length>0)|| !assistantState.current.isConfigured){
return html`<vaadin-grid .items="${this._currentDataSet.data}" theme="row-stripes no-border" class="fill" column-reordering-allowed>
${this._currentDataSet.cols.map((col) =>
this._renderTableHeader(col)
)}
<span slot="empty-state">No data.</span>
</vaadin-grid>
${this._renderPager()}`;
}else {
return html`<qui-assistant-button class="generateTableDataButton" title="Use Quarkus Assistant to generate some data" @click="${this._generateInitialData}">Generate some data</qui-assistant-button>`;
}
}

_renderTableHeader(col){
let heading = col;
if(this._selectedTable.primaryKeys.includes(col)){
Expand Down
4 changes: 4 additions & 0 deletions extensions/agroal/runtime-dev/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@
<groupId>${project.groupId}</groupId>
<artifactId>quarkus-agroal</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-assistant-dev</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.inject.Instance;
Expand All @@ -34,6 +37,7 @@
import io.quarkus.agroal.runtime.AgroalDataSourceUtil;
import io.quarkus.arc.InactiveBeanException;
import io.quarkus.arc.InjectableInstance;
import io.quarkus.assistant.runtime.dev.Assistant;
import io.quarkus.datasource.common.runtime.DataSourceUtil;
import io.quarkus.runtime.LaunchMode;

Expand All @@ -44,6 +48,9 @@ public final class DatabaseInspector {
@Inject
Instance<AgroalDataSourceSupport> agroalDataSourceSupports;

@Inject
Optional<Assistant> assistant;

private final Map<String, AgroalDataSource> checkedDataSources = new HashMap<>();

private boolean isDev = false;
Expand Down Expand Up @@ -333,6 +340,22 @@ public String getInsertScript(String datasource) {
return null;
}

public CompletionStage<Map<String, String>> generateTableData(String datasource, String schema, String name, int rowCount) {
if (isDev && assistant.isPresent()) {
List<Table> tables = getTables(datasource);
Optional<Table> matchingTable = tables.stream()
.filter(t -> t.tableSchema().equals(schema) && t.tableName().equals(name))
.findFirst();

if (matchingTable.isPresent()) {
return assistant.get().assistBuilder()
.userMessage(generateInsertPrompt(matchingTable.get(), rowCount))
.assist();
}
}
return CompletableFuture.failedStage(new RuntimeException("Assistant is not available"));
}

private void exportTable(Connection conn, StringWriter writer, String tableName) throws SQLException, IOException {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName)) {
Expand Down Expand Up @@ -454,6 +477,58 @@ private boolean isBinary(int dataType) {
dataType == Types.OTHER;
}

private String generateInsertPrompt(Table table, int rowCount) {
StringBuilder sb = new StringBuilder();
sb.append("Generate a valid SQL script with ")
.append(rowCount)
.append(" INSERT statements for the following table:\n\n");

sb.append("Table name: ")
.append(table.tableSchema())
.append(".")
.append(table.tableName())
.append("\n\n");

sb.append("Columns:\n");
for (Column column : table.columns()) {
sb.append("- ")
.append(column.columnName())
.append(" (")
.append(column.columnType());
if (column.columnType().equalsIgnoreCase("varchar")) {
sb.append("(").append(column.columnSize()).append(")");
}
sb.append(", nullable: ").append(column.nullable());
sb.append(")\n");
}

if (!table.primaryKeys().isEmpty()) {
sb.append("\nPrimary key(s): ").append(String.join(", ", table.primaryKeys())).append("\n");
}

if (!table.foreignKeys().isEmpty()) {
sb.append("\nForeign keys:\n");
for (ForeignKey fk : table.foreignKeys()) {
sb.append("- ")
.append(fk.columnName())
.append(" references ")
.append(fk.referencedTable())
.append("(")
.append(fk.referencedColumn())
.append(")\n");
}
}

sb.append(
"\nReturn the output in a field called `script` with the contents being a SQL script with valid INSERT INTO statements for ")
.append(table.tableSchema())
.append(".")
.append(table.tableName())
.append(".\n");

return sb.toString();
}

private static record Column(String columnName, String columnType, int columnSize, String nullable, boolean binary) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,48 +173,39 @@ private void detectAndLogSpecificSpringPropertiesIfExist() {
Iterable<String> iterablePropertyNames = config.getPropertyNames();
List<String> propertyNames = new ArrayList<String>();
iterablePropertyNames.forEach(propertyNames::add);
List<String> springProperties = propertyNames.stream().filter(s -> pattern.matcher(s).matches()).collect(toList());
List<String> springProperties = propertyNames.stream().filter(s -> pattern.matcher(s).matches()).toList();
String notSupportedProperties = "";

if (!springProperties.isEmpty()) {
for (String sp : springProperties) {
switch (sp) {
case SPRING_JPA_SHOW_SQL:
notSupportedProperties = notSupportedProperties + "\t- " + SPRING_JPA_SHOW_SQL
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_LOG_SQL + "\n";
break;
case SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT:
notSupportedProperties = notSupportedProperties + "\t- " + SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT
notSupportedProperties = switch (sp) {
case SPRING_JPA_SHOW_SQL -> notSupportedProperties + "\t- " + SPRING_JPA_SHOW_SQL
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_LOG_SQL + "\n";
case SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT ->
notSupportedProperties + "\t- " + SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_DIALECT + "\n";
break;
case SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT_STORAGE_ENGINE:
notSupportedProperties = notSupportedProperties + "\t- "
+ SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT_STORAGE_ENGINE + " should be replaced by "
+ QUARKUS_HIBERNATE_ORM_DIALECT_STORAGE_ENGINE + "\n";
break;
case SPRING_JPA_GENERATE_DDL:
notSupportedProperties = notSupportedProperties + "\t- " + SPRING_JPA_GENERATE_DDL
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_SCHEMA_MANAGEMENT_STRATEGY + "\n";
break;
case SPRING_JPA_HIBERNATE_NAMING_PHYSICAL_STRATEGY:
notSupportedProperties = notSupportedProperties + "\t- " + SPRING_JPA_HIBERNATE_NAMING_PHYSICAL_STRATEGY
case SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT_STORAGE_ENGINE -> notSupportedProperties + "\t- "
+ SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT_STORAGE_ENGINE
+ " should be replaced by "
+ QUARKUS_HIBERNATE_ORM_DIALECT_STORAGE_ENGINE
+ "\n";
case SPRING_JPA_GENERATE_DDL -> notSupportedProperties + "\t- " + SPRING_JPA_GENERATE_DDL
+ " should be replaced by "
+ QUARKUS_HIBERNATE_ORM_SCHEMA_MANAGEMENT_STRATEGY + "\n";
case SPRING_JPA_HIBERNATE_NAMING_PHYSICAL_STRATEGY ->
notSupportedProperties + "\t- " + SPRING_JPA_HIBERNATE_NAMING_PHYSICAL_STRATEGY
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_PHYSICAL_NAMING_STRATEGY + "\n";
break;
case SPRING_JPA_HIBERNATE_NAMING_IMPLICIT_STRATEGY:
notSupportedProperties = notSupportedProperties + "\t- " + SPRING_JPA_HIBERNATE_NAMING_IMPLICIT_STRATEGY
case SPRING_JPA_HIBERNATE_NAMING_IMPLICIT_STRATEGY ->
notSupportedProperties + "\t- " + SPRING_JPA_HIBERNATE_NAMING_IMPLICIT_STRATEGY
+ " should be replaced by " + QUARKUS_HIBERNATE_ORM_IMPLICIT_NAMING_STRATEGY + "\n";
break;
case SPRING_DATASOURCE_DATA:
notSupportedProperties = notSupportedProperties + "\t- " + QUARKUS_HIBERNATE_ORM_SQL_LOAD_SCRIPT
case SPRING_DATASOURCE_DATA ->
notSupportedProperties + "\t- " + QUARKUS_HIBERNATE_ORM_SQL_LOAD_SCRIPT
+ " could be used to load data instead of " + SPRING_DATASOURCE_DATA
+ " but it does not support ant-style patterns as "
+ SPRING_DATASOURCE_DATA
+ " does, it accepts the name of files containing the SQL statements to execute when Hibernate ORM starts.\n";
break;
default:
notSupportedProperties = notSupportedProperties + "\t- " + sp + " does not have a Quarkus equivalent\n";
break;
}
default -> notSupportedProperties + "\t- " + sp + " does not have a Quarkus equivalent\n";
};
}
LOGGER.warnf(
"Quarkus does not support the following Spring Boot configuration properties: %n%s",
Expand Down
Loading
Loading