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
18 changes: 18 additions & 0 deletions vertx-sql-client-templates/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ When you need to perform an insert or update operation, and you do not care of t
{@link examples.TemplateExamples#insertExample}
----

[TIP]
====
Creating a template instance with {@link io.vertx.sqlclient.templates.SqlTemplate#forQuery} or {@link io.vertx.sqlclient.templates.SqlTemplate#forUpdate} involves parsing the query, and that doesn't come for free.

To avoid paying the price of computing the actual query repeatedly, consider reusing your template instances.
Typically, you will store an instance as a verticle field.
====

[TIP]
====
When your template must be executed inside a transaction, you might create a temporary instance using {@link io.vertx.sqlclient.templates.SqlTemplate#withClient}:

[source,$lang]
----
{@link examples.TemplateExamples#templateInTransaction}
----
====

== Template syntax

The template syntax uses `#{XXX}` syntax where `XXX` is a valid https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8[java identifier] string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package examples;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.format.QualifiedCase;
import io.vertx.codegen.format.SnakeCase;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.docgen.Source;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.SqlClient;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.*;
import io.vertx.sqlclient.templates.RowMapper;
import io.vertx.sqlclient.templates.SqlTemplate;
import io.vertx.sqlclient.templates.TupleMapper;
Expand Down Expand Up @@ -424,4 +423,19 @@ public Tuple map(Function<Integer, String> mapping, int size, UserDataObject par
throw new UnsupportedOperationException();
}
}

public void templateInTransaction(Pool pool) {
SqlTemplate<Map<String, Object>, RowSet<UserDataObject>> template = SqlTemplate
.forQuery(pool, "SELECT * FROM users WHERE id=#{id}")
.mapTo(UserDataObjectRowMapper.INSTANCE);

// Store the resolved template instance for reuse (typically in a verticle field).

// Then create an ephemeral instance for execution inside a transaction

Future<RowSet<UserDataObject>> future = pool.withTransaction(conn -> {
return template.withClient(conn) // Create an ephemeral instance, don't reuse it
.execute(Map.of("id", 1));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ public interface SqlTemplate<I, R> {
* @return the template
*/
static SqlTemplate<Map<String, Object>, RowSet<Row>> forQuery(SqlClient client, String template) {
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create((SqlClientInternal) client, template);
return new SqlTemplateImpl<>(client, sqlTemplate, Function.identity(), sqlTemplate::mapTuple);
SqlClientInternal clientInternal = (SqlClientInternal) client;
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create(clientInternal, template);
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, Function.identity(), sqlTemplate::mapTuple);
}

/**
Expand All @@ -63,8 +64,9 @@ static SqlTemplate<Map<String, Object>, RowSet<Row>> forQuery(SqlClient client,
* @return the template
*/
static SqlTemplate<Map<String, Object>, SqlResult<Void>> forUpdate(SqlClient client, String template) {
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create((SqlClientInternal) client, template);
return new SqlTemplateImpl<>(client, sqlTemplate, query -> query.collecting(SqlTemplateImpl.NULL_COLLECTOR), sqlTemplate::mapTuple);
SqlClientInternal clientInternal = (SqlClientInternal) client;
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create(clientInternal, template);
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, query -> query.collecting(SqlTemplateImpl.NULL_COLLECTOR), sqlTemplate::mapTuple);
}


Expand Down Expand Up @@ -140,6 +142,28 @@ default <T> SqlTemplate<T, R> mapFrom(Class<T> type) {
@GenIgnore
<U> SqlTemplate<I, SqlResult<U>> collecting(Collector<Row, ?, U> collector);

/**
* Returns a new template, using the specified {@code client}.
* <p>
* This method does not compute the template query again, so it can be useful to execute a template on a specific {@link io.vertx.sqlclient.SqlConnection}.
* For example, after starting a transaction:
*
* <pre>
* // Typically stored as a verticle field
* // So that heavy computation of the template happens once
* SqlTemplate<Map<String, Object>, RowSet<World>> template = SqlTemplate
* .forQuery(pool, "SELECT id, randomnumber FROM tmp_world")
* .mapTo(World.class);
*
* // Executing the template inside a transaction
* Future<RowSet<World>> future = pool.withTransaction(conn -> template.withClient(conn).execute(Map.of()));
* </pre>
*
* @param client the client that will execute requests
* @return a new template
*/
SqlTemplate<I, R> withClient(SqlClient client);

/**
* Execute the query with the {@code parameters}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.*;
import io.vertx.sqlclient.impl.SqlClientInternal;
import io.vertx.sqlclient.templates.RowMapper;
import io.vertx.sqlclient.templates.TupleMapper;

import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
Expand All @@ -16,12 +18,12 @@ public class SqlTemplateImpl<I, R> implements io.vertx.sqlclient.templates.SqlTe
//
public static final Collector<Row, Void, Void> NULL_COLLECTOR = Collector.of(() -> null, (v, row) -> {}, (a, b) -> null);

protected final SqlClient client;
protected final SqlClientInternal client;
protected final SqlTemplate sqlTemplate;
protected final Function<I, Tuple> tupleMapper;
protected Function<PreparedQuery<RowSet<Row>>, PreparedQuery<R>> queryMapper;

public SqlTemplateImpl(SqlClient client,
public SqlTemplateImpl(SqlClientInternal client,
SqlTemplate sqlTemplate,
Function<PreparedQuery<RowSet<Row>>,
PreparedQuery<R>> queryMapper,
Expand All @@ -47,6 +49,12 @@ public <U> io.vertx.sqlclient.templates.SqlTemplate<I, SqlResult<U>> collecting(
return new SqlTemplateImpl<>(client, sqlTemplate, query -> query.collecting(collector), tupleMapper);
}

@Override
public io.vertx.sqlclient.templates.SqlTemplate<I, R> withClient(SqlClient client) {
SqlClientInternal clientInternal = (SqlClientInternal) Objects.requireNonNull(client, "client is null");
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, queryMapper, tupleMapper);
}

@Override
public <U> io.vertx.sqlclient.templates.SqlTemplate<I, RowSet<U>> mapTo(Class<U> type) {
return mapTo(row -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.vertx.mysqlclient.MySQLBuilder;
import io.vertx.mysqlclient.MySQLConnectOptions;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.templates.SqlTemplate;
import org.junit.AfterClass;
import org.junit.Before;
Expand All @@ -15,15 +16,17 @@
import org.testcontainers.containers.GenericContainer;

import java.time.Duration;
import java.util.Collections;
import java.util.Map;

@RunWith(VertxUnitRunner.class)
public class MySQLTest {

private static GenericContainer server;
private static GenericContainer<?> server;

@BeforeClass
public static void startDatabase() {
server = new GenericContainer("mysql:8.0")
server = new GenericContainer<>("mysql:8.0")
.withEnv("MYSQL_USER", "mysql")
.withEnv("MYSQL_PASSWORD", "password")
.withEnv("MYSQL_ROOT_PASSWORD", "password")
Expand Down Expand Up @@ -76,4 +79,36 @@ public void testDurationMapping(TestContext ctx) {
ctx.assertEquals(duration, row.getDuration());
}));
}

@Test
public void executeWithOtherClient(TestContext ctx) {
SqlTemplate<Map<String, Object>, RowSet<World>> template = SqlTemplate
.forQuery(pool, "SELECT id, randomnumber FROM tmp_world")
.mapTo(World.class);

pool.withTransaction(conn -> {
// Create a table visible only within the current session
return conn.query("CREATE TEMPORARY TABLE tmp_world (" +
"id int(10) unsigned NOT NULL auto_increment, " +
"randomnumber int NOT NULL default 0, " +
"PRIMARY KEY (id)) " +
"ENGINE=INNODB")
.execute()
.compose(v -> {
return conn.query("INSERT INTO tmp_world (randomnumber) VALUES " +
"(floor(0 + (rand() * 10000))), " +
"(floor(0 + (rand() * 10000))), " +
"(floor(0 + (rand() * 10000)))")
.execute();
})
.compose(v -> {
return template.withClient(conn).execute(Collections.emptyMap());
});
}).onComplete(ctx.asyncAssertSuccess(rows -> {
ctx.assertEquals(3, rows.size());
for (World world : rows) {
ctx.assertNotNull(world.id);
}
}));
}
}