Skip to content

Commit

Permalink
feat: declarative JVM databases (#4161)
Browse files Browse the repository at this point in the history
  • Loading branch information
stuartwdouglas authored Jan 23, 2025
1 parent c589e41 commit 8191669
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import jakarta.transaction.Transactional;

import xyz.block.ftl.SQLDatabaseType;
import xyz.block.ftl.SQLDatasource;
import xyz.block.ftl.Verb;

@SQLDatasource(name = "testdb", type = SQLDatabaseType.POSTGRESQL)
public class Database {

@Verb
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
@Table(name = "requests")
public class Request extends PanacheEntity {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

import jakarta.transaction.Transactional;

import xyz.block.ftl.SQLDatabaseType;
import xyz.block.ftl.SQLDatasource;
import xyz.block.ftl.Verb;

@SQLDatasource(name = "testdb", type = SQLDatabaseType.MYSQL)
public class Database {

@Verb
Expand Down

This file was deleted.

101 changes: 57 additions & 44 deletions docs/content/docs/reference/databases.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,67 +74,80 @@ func Query(ctx context.Context, db ftl.DatabaseHandle[TestDatasourceConfig]) ([]

```
<!-- kotlin -->
To declare a datasource in Kotlin you must create an `application.properties` file in the `src/main/resources`
directory. Datasources currently leverage the Quarkus Database extension, and so databases are configured
using Quarkus config. To define a MySQL database using the Quarkus Hibernate extension you would do the following:
To declare a datasource in Kotlin you must use the `@SQLDatasource` annotation. This annotations is used to define
the database name and type.

```properties
quarkus.datasource.testdb.db-kind=mysql
quarkus.hibernate-orm.datasource=testdb
```kotlin
@SQLDatasource(name = "testdb", type = SQLDatabaseType.POSTGRESQL)
```

To use this in your FTL code you can then just use [Hibernate directly](https://quarkus.io/guides/hibernate-orm) or using [Panache](https://quarkus.io/guides/hibernate-orm-panache).

Note that this will likely change significantly in future once FTL has SQL Verbs.
You must also include the appropriate depdencies in your `pom.xml` for the database you are using:

```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql</artifactId>
</dependency>
```

<!-- java -->
You can also use [Hibernate directly](https://quarkus.io/guides/hibernate-orm) or using [Panache](https://quarkus.io/guides/hibernate-orm-panache).

To declare a datasource in Java you must create an `application.properties` file in the `src/main/resources`
directory. Datasources currently leverage the Quarkus Database extension, and so databases are configured
using Quarkus config. To define a MySQL database using the Quarkus Hibernate extension you would do the following:
This will require adding one of the following dependencies:

```properties
quarkus.datasource.testdb.db-kind=mysql
quarkus.hibernate-orm.datasource=testdb
```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
```

To use this in your FTL code you can then just use [Hibernate directly](https://quarkus.io/guides/hibernate-orm) or using [Panache](https://quarkus.io/guides/hibernate-orm-panache).

Note that this will likely change significantly in future once FTL has SQL Verbs.

An example showing DB usage with Panache is shown below:
<!-- java -->
To declare a datasource in Java you must use the `@SQLDatasource` annotation. This annotations is used to define
the database name and type.

```java
package xyz.block.ftl.java.test.database;

import java.util.List;
import java.util.Map;

import jakarta.transaction.Transactional;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@SQLDatasource(name = "testdb", type = SQLDatabaseType.POSTGRESQL)
```

import xyz.block.ftl.Verb;
You must also include the appropriate depdencies in your `pom.xml` for the database you are using:

```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql</artifactId>
</dependency>
```

import io.quarkus.hibernate.orm.panache.PanacheEntity;
You can also use [Hibernate directly](https://quarkus.io/guides/hibernate-orm) or using [Panache](https://quarkus.io/guides/hibernate-orm-panache).

public class Database {

@Verb
@Transactional
public List<Request> query() {
List<Request> requests = Request.listAll();
return requests;
}
}
This will require adding one of the following dependencies:

```xml
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
```

@Entity
@Table(name = "requests")
public class Request extends PanacheEntity {
public String data;

}
Note that this will likely change significantly in future once FTL has SQL Verbs.

```
{% end %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package xyz.block.ftl.deployment;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class DatabaseConfigSource implements ConfigSource {

private final Map<String, String> properties;

public DatabaseConfigSource() {
properties = new HashMap<>();
var ds = Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/ftl-sql-databases.txt");
if (ds == null) {
return;
}
try (var in = ds) {
Properties p = new Properties();
p.load(in);
for (var name : p.stringPropertyNames()) {
properties.put("quarkus.datasource." + name + ".db-kind", p.getProperty(name));
}
if (properties.size() == 1) {
try {
// Temporary implicit support for hibernate orm
Thread.currentThread().getContextClassLoader()
.loadClass("io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder");
properties.put("quarkus.hibernate-orm.datasource", p.entrySet().iterator().next().getKey().toString());
} catch (ClassNotFoundException ignore) {

}
}
} catch (Exception e) {
throw new RuntimeException("Failed to load database properties", e);
}

}

@Override
public Set<String> getPropertyNames() {
return properties.keySet();
}

@Override
public String getValue(String propertyName) {
return properties.get(propertyName);
}

@Override
public String getName() {
return "FTL Database Config Source";
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
xyz.block.ftl.deployment.StaticConfigSource
xyz.block.ftl.deployment.StaticConfigSource
xyz.block.ftl.deployment.DatabaseConfigSource
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package xyz.block.ftl;

public enum SQLDatabaseType {
MYSQL,
POSTGRESQL,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package xyz.block.ftl;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to specify a SQL datasource.
*
* This can be added anywhere in your application, but it is recommended to add it to a package-info.java file
* at the root of your package hierarchy.
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PACKAGE, ElementType.TYPE })
public @interface SQLDatasource {
/**
* The name of the datasource.
*
* @return the name of the datasource
*/
String name();

/**
* The type of the SQL database.
*
* @return the type of the SQL database
*/
SQLDatabaseType type();

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import xyz.block.ftl.Config;
import xyz.block.ftl.Enum;
import xyz.block.ftl.Export;
import xyz.block.ftl.SQLDatasource;
import xyz.block.ftl.Secret;
import xyz.block.ftl.TypeAlias;
import xyz.block.ftl.Verb;
Expand All @@ -42,10 +43,12 @@ public class AnnotationProcessor implements Processor {
private static final Pattern REMOVE_JAVADOC_TAGS = Pattern.compile(
"^\\s*@(param|return|throws|exception|see|author)\\b[^\\n]*$\\n*",
Pattern.MULTILINE);
public static final String META_INF_FTL_SQL_DATABASES_TXT = "META-INF/ftl-sql-databases.txt";

private ProcessingEnvironment processingEnv;

final Map<String, String> saved = new HashMap<>();
final Map<String, String> databases = new HashMap<>();

@Override
public Set<String> getSupportedOptions() {
Expand All @@ -69,6 +72,11 @@ public void init(ProcessingEnvironment processingEnv) {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
roundEnv.getElementsAnnotatedWithAny(Set.of(SQLDatasource.class))
.forEach(element -> {
SQLDatasource ds = element.getAnnotation(SQLDatasource.class);
databases.put(ds.name(), ds.type().name());
});
//TODO: @VerbName, HTTP, CRON etc
roundEnv.getElementsAnnotatedWithAny(Set.of(Verb.class, Enum.class, Export.class, TypeAlias.class))
.forEach(element -> {
Expand Down Expand Up @@ -100,6 +108,10 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
write("META-INF/ftl-verbs.txt", saved.entrySet().stream().map(
e -> e.getKey() + "=" + Base64.getEncoder().encodeToString(e.getValue().getBytes(StandardCharsets.UTF_8)))
.collect(Collectors.toSet()));

write(META_INF_FTL_SQL_DATABASES_TXT, databases.entrySet().stream().map(
e -> e.getKey() + "=" + e.getValue().toLowerCase())
.collect(Collectors.toSet()));
}
return false;
}
Expand Down

0 comments on commit 8191669

Please sign in to comment.