From b077bcb945ab67f4d26380deb64e77724a5a73b3 Mon Sep 17 00:00:00 2001 From: "j.bebendorf" Date: Fri, 7 Oct 2022 22:04:50 +0200 Subject: [PATCH 01/24] Incremented version --- README.md | 4 ++-- pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b621a7..d1112ce 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ for(User user : Repo.get(User.class).all()){ org.javawebstack orm - 1.0.1 + 1.0.3 ``` @@ -72,7 +72,7 @@ You also need to add the driver you want to use. JavaWebStack ORM comes with a w mysql mysql-connector-java - 8.0.27 + 8.0.30 compile ``` diff --git a/pom.xml b/pom.xml index d102ba0..1a7f09f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 8 8 - 1.0.2-SNAPSHOT + 1.0.3-SNAPSHOT org.javawebstack From da69237671f2322d05e2fadab8af74fce7e37e6a Mon Sep 17 00:00:00 2001 From: "j.bebendorf" Date: Sun, 26 Mar 2023 04:51:57 +0200 Subject: [PATCH 02/24] Added support for custom jdbc parameters --- .../org/javawebstack/orm/wrapper/MySQL.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java b/src/main/java/org/javawebstack/orm/wrapper/MySQL.java index 3d71818..676b8db 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java +++ b/src/main/java/org/javawebstack/orm/wrapper/MySQL.java @@ -4,9 +4,14 @@ import org.javawebstack.orm.wrapper.builder.MySQLQueryStringBuilder; import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; public class MySQL extends BaseSQL { @@ -18,6 +23,7 @@ public class MySQL extends BaseSQL { private final String password; private final long timeout; private long lastQuery = 0; + private Map customParams = new HashMap<>(); public MySQL(String host, int port, String database, String username, String password) { this(host, port, database, username, password, 60); @@ -32,8 +38,15 @@ public MySQL(String host, int port, String database, String username, String pas this.timeout = timeout * 1000L; } + public MySQL setCustomParam(String key, String value) { + customParams.put(key, value); + return this; + } + public SQL fork() { - return new MySQL(host, port, database, username, password, (int) (timeout / 1000L)); + MySQL sql = new MySQL(host, port, database, username, password, (int) (timeout / 1000L)); + sql.customParams = customParams; + return sql; } public Connection getConnection() { @@ -52,7 +65,16 @@ public Connection getConnection() { if (c == null || c.isClosed()) { try { Class.forName("com.mysql.cj.jdbc.Driver"); - c = DriverManager.getConnection("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database + "?user=" + this.username + "&password=" + this.password + "&autoReconnect=" + true + "&failOverReadOnly=false&maxReconnects=" + 5 + "&UseUnicode=yes&characterEncoding=UTF-8"); + Map params = new HashMap<>(); + params.put("user", this.username); + params.put("password", this.password); + params.put("autoReconnect", "true"); + params.put("failOverReadOnly", "false"); + params.put("maxReconnects", "5"); + params.put("UseUnicode", "yes"); + params.put("characterEncoding", "UTF-8"); + params.putAll(customParams); + c = DriverManager.getConnection("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database + "?" + buildQuery(params)); } catch (SQLException e) { System.out.println("Error: at getConnection()[MySQL.java] SQLException " + e.getMessage()); } catch (ClassNotFoundException e) { @@ -71,6 +93,18 @@ public Connection getConnection() { return c; } + private static String buildQuery(Map params) { + return params.entrySet().stream().map(e -> urlEncode(e.getKey()) + "=" + urlEncode(e.getValue())).collect(Collectors.joining("&")); + } + + private static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + } + return s; + } + public QueryStringBuilder builder() { return MySQLQueryStringBuilder.INSTANCE; } From ea7aa716f4031e1c21e73b5c28065e9b0b39092d Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 18:02:36 +0200 Subject: [PATCH 03/24] Implemented support for Indices --- .../java/org/javawebstack/orm/TableInfo.java | 12 ++++++ .../javawebstack/orm/annotation/Index.java | 12 ++++++ .../javawebstack/orm/annotation/Indices.java | 12 ++++++ .../orm/migration/AutoMigrator.java | 40 +++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 src/main/java/org/javawebstack/orm/annotation/Index.java create mode 100644 src/main/java/org/javawebstack/orm/annotation/Indices.java diff --git a/src/main/java/org/javawebstack/orm/TableInfo.java b/src/main/java/org/javawebstack/orm/TableInfo.java index e144417..0ec7e55 100644 --- a/src/main/java/org/javawebstack/orm/TableInfo.java +++ b/src/main/java/org/javawebstack/orm/TableInfo.java @@ -32,6 +32,7 @@ public class TableInfo { private String relationField; private final Map filterable = new HashMap<>(); private final List searchable = new ArrayList<>(); + private final List indices = new ArrayList<>(); private static final Class[] appliesDefaultSize = { String.class, @@ -105,6 +106,14 @@ private void analyzeTable(Class model) throws ORMConfigurationE if (!fields.containsKey(dates.update())) throw new ORMConfigurationException("Missing dates field '" + dates.update() + "'"); } + if (model.isAnnotationPresent(Index.class)) { + Index[] unfilteredIndices = model.getDeclaredAnnotationsByType(Index.class); + for (Index index : unfilteredIndices) { + if (index.value().length == 0) + continue; + indices.add(index); + } + } } private void analyzeColumns(Class model) throws ORMConfigurationException { @@ -292,4 +301,7 @@ public String getRelationField() { return relationField; } + public List getIndices() { + return indices; + } } diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java new file mode 100644 index 0000000..10fe4f8 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -0,0 +1,12 @@ +package org.javawebstack.orm.annotation; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Repeatable(Indices.class) +public @interface Index { + boolean unique() default false; + String id() default ""; + String[] value() default {}; +} diff --git a/src/main/java/org/javawebstack/orm/annotation/Indices.java b/src/main/java/org/javawebstack/orm/annotation/Indices.java new file mode 100644 index 0000000..eb894ba --- /dev/null +++ b/src/main/java/org/javawebstack/orm/annotation/Indices.java @@ -0,0 +1,12 @@ +package org.javawebstack.orm.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Indices { + Index[] value(); +} diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 0d4044c..5ef4520 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -1,7 +1,9 @@ package org.javawebstack.orm.migration; +import jdk.internal.joptsimple.internal.Strings; import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.annotation.Index; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.wrapper.SQL; @@ -12,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; public class AutoMigrator { @@ -122,6 +125,30 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { } } } + + List existingIndices = getIndices(sql, info.getTableName()); + for (Index index : info.getIndices()) { + String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); + String id = index.id().length() > 0 ? index.id() : "idx_" + Strings.join(index.value(), "_"); + if (existingIndices.contains(id)) + continue; + + StringBuilder sb = new StringBuilder("CREATE "); + if (index.unique()) + sb.append("UNIQUE "); + sb.append("INDEX `") + .append(id) + .append("` ON `") + .append(info.getTableName()) + .append("` (") + .append(columns) + .append(");"); + try { + sql.write(sb.toString()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } } private static Map getColumnKeys(SQL sql, String tableName) { @@ -137,6 +164,19 @@ private static Map getColumnKeys(SQL sql, String tableName) { } } + private static List getIndices(SQL sql, String tableName) { + try { + List indices = new ArrayList<>(); + ResultSet rs = sql.read("SHOW INDEX FROM `" + tableName + "`;"); + while (rs.next()) { + indices.add(rs.getString(1)); + } + return indices; + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } + private static List getTables(SQL sql) { try { List tables = new ArrayList<>(); From 29cac9e45b8d120e3cc2e5101404b92bf4e0f127 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 20:29:33 +0200 Subject: [PATCH 04/24] Added option to specify BTREE or HASH --- src/main/java/org/javawebstack/orm/annotation/Index.java | 7 +++++++ .../java/org/javawebstack/orm/migration/AutoMigrator.java | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java index 10fe4f8..9e4d9e9 100644 --- a/src/main/java/org/javawebstack/orm/annotation/Index.java +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -6,7 +6,14 @@ @Target(ElementType.TYPE) @Repeatable(Indices.class) public @interface Index { + Type type() default Type.AUTO; boolean unique() default false; String id() default ""; String[] value() default {}; + + enum Type { + AUTO, + BTREE, + HASH + } } diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 5ef4520..888ff40 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -136,6 +136,8 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { StringBuilder sb = new StringBuilder("CREATE "); if (index.unique()) sb.append("UNIQUE "); + if (index.type() != Index.Type.AUTO) + sb.append("USING ").append(index.type().name()).append(" "); sb.append("INDEX `") .append(id) .append("` ON `") From c276e881cac62ac2bac395bc580b43f1055db522 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Thu, 30 Mar 2023 20:32:29 +0200 Subject: [PATCH 05/24] Added option to specify BTREE or HASH --- .../java/org/javawebstack/orm/migration/AutoMigrator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 888ff40..4117da5 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -136,11 +136,10 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { StringBuilder sb = new StringBuilder("CREATE "); if (index.unique()) sb.append("UNIQUE "); + sb.append("INDEX `").append(id).append("` "); if (index.type() != Index.Type.AUTO) sb.append("USING ").append(index.type().name()).append(" "); - sb.append("INDEX `") - .append(id) - .append("` ON `") + sb.append("ON `") .append(info.getTableName()) .append("` (") .append(columns) From a025318f1780b31013cb902389731dec807d081d Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 31 Mar 2023 10:51:37 +0200 Subject: [PATCH 06/24] Replace Strings class with String class --- src/main/java/org/javawebstack/orm/migration/AutoMigrator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 4117da5..e88041a 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -1,6 +1,5 @@ package org.javawebstack.orm.migration; -import jdk.internal.joptsimple.internal.Strings; import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.annotation.Index; @@ -129,7 +128,7 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { List existingIndices = getIndices(sql, info.getTableName()); for (Index index : info.getIndices()) { String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); - String id = index.id().length() > 0 ? index.id() : "idx_" + Strings.join(index.value(), "_"); + String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); if (existingIndices.contains(id)) continue; From 6e4dec247341c21f2047b847554b0fbeb9f1aeb6 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 31 Mar 2023 18:09:49 +0200 Subject: [PATCH 07/24] Moved Indices class to subclass of Index annotation --- .../java/org/javawebstack/orm/annotation/Index.java | 8 +++++++- .../org/javawebstack/orm/annotation/Indices.java | 12 ------------ 2 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 src/main/java/org/javawebstack/orm/annotation/Indices.java diff --git a/src/main/java/org/javawebstack/orm/annotation/Index.java b/src/main/java/org/javawebstack/orm/annotation/Index.java index 9e4d9e9..0e77653 100644 --- a/src/main/java/org/javawebstack/orm/annotation/Index.java +++ b/src/main/java/org/javawebstack/orm/annotation/Index.java @@ -4,7 +4,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) -@Repeatable(Indices.class) +@Repeatable(Index.Indices.class) public @interface Index { Type type() default Type.AUTO; boolean unique() default false; @@ -16,4 +16,10 @@ enum Type { BTREE, HASH } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @interface Indices { + Index[] value(); + } } diff --git a/src/main/java/org/javawebstack/orm/annotation/Indices.java b/src/main/java/org/javawebstack/orm/annotation/Indices.java deleted file mode 100644 index eb894ba..0000000 --- a/src/main/java/org/javawebstack/orm/annotation/Indices.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.javawebstack.orm.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Indices { - Index[] value(); -} From d11b81160ed5ee93053fcbe0d0eaac66de09fb2e Mon Sep 17 00:00:00 2001 From: x7airworker Date: Tue, 18 Apr 2023 15:07:10 +0200 Subject: [PATCH 08/24] Added support for Lists and Streams in whereIn / whereNotIn --- .../wrapper/builder/MySQLQueryStringBuilder.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java index 3c706e1..a97bb4f 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -1,5 +1,6 @@ package org.javawebstack.orm.wrapper.builder; +import org.javawebstack.abstractdata.AbstractArray; import org.javawebstack.orm.*; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.query.*; @@ -8,12 +9,10 @@ import java.lang.reflect.Method; import java.sql.Timestamp; import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; public class MySQLQueryStringBuilder implements QueryStringBuilder { @@ -213,6 +212,12 @@ private SQLQueryString convertCondition(TableInfo info, QueryCondition condition sb.append(' '); if (condition.getOperator().endsWith("IN")) { Object[] values = (Object[]) condition.getRight(); + if (values.length == 1) { + if (values[0] instanceof Collection) + values = ((Collection) values[0]).toArray(); + else if (values[0] instanceof Stream) + values = ((Stream) values[0]).toArray(); + } sb.append("(").append(IntStream.range(0, values.length).mapToObj(i -> "?").collect(Collectors.joining(","))).append(")"); parameters.addAll(Arrays.asList(values)); } else if (condition.getRight() instanceof QueryColumn) { From 01d81f3cfe3d5467f73f6f0844c4d603cf462b6a Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 21 Apr 2023 11:47:16 +0200 Subject: [PATCH 09/24] Added tests, fixed syntax errors and verified implementation --- .../java/org/javawebstack/orm/TableInfo.java | 13 ++--- .../orm/migration/AutoMigrator.java | 2 +- .../orm/test/automigrate/IndexTest.java | 36 ++++++++++++ .../orm/test/shared/models/IndexType.java | 19 +++++++ .../orm/test/shared/verification/Indexes.java | 55 +++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java create mode 100644 src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java create mode 100644 src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java diff --git a/src/main/java/org/javawebstack/orm/TableInfo.java b/src/main/java/org/javawebstack/orm/TableInfo.java index 0ec7e55..b9c1c59 100644 --- a/src/main/java/org/javawebstack/orm/TableInfo.java +++ b/src/main/java/org/javawebstack/orm/TableInfo.java @@ -106,13 +106,12 @@ private void analyzeTable(Class model) throws ORMConfigurationE if (!fields.containsKey(dates.update())) throw new ORMConfigurationException("Missing dates field '" + dates.update() + "'"); } - if (model.isAnnotationPresent(Index.class)) { - Index[] unfilteredIndices = model.getDeclaredAnnotationsByType(Index.class); - for (Index index : unfilteredIndices) { - if (index.value().length == 0) - continue; - indices.add(index); - } + + Index[] unfilteredIndices = model.getAnnotationsByType(Index.class); + for (Index index : unfilteredIndices) { + if (index.value().length == 0) + continue; + indices.add(index); } } diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index e88041a..a1b32b6 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -127,7 +127,7 @@ private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { List existingIndices = getIndices(sql, info.getTableName()); for (Index index : info.getIndices()) { - String columns = Stream.of(index.value()).map(info::getColumnName).collect(Collectors.joining(",")); + String columns = Stream.of(index.value()).map(info::getColumnName).map(s -> "`" + s + "`").collect(Collectors.joining(",")); String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); if (existingIndices.contains(id)) continue; diff --git a/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java b/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java new file mode 100644 index 0000000..b86956e --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/automigrate/IndexTest.java @@ -0,0 +1,36 @@ +package org.javawebstack.orm.test.automigrate; + +import org.javawebstack.orm.ORM; +import org.javawebstack.orm.ORMConfig; +import org.javawebstack.orm.exception.ORMConfigurationException; +import org.javawebstack.orm.test.ORMTestCase; +import org.javawebstack.orm.test.shared.models.IndexType; +import org.javawebstack.orm.test.shared.verification.Indexes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; + +public class IndexTest extends ORMTestCase { + + @BeforeEach + public void setUp() throws ORMConfigurationException { + ORMConfig config = new ORMConfig() + .setDefaultSize(255); + ORM.register(IndexType.class, sql(), config); + ORM.autoMigrate(true); + } + + @Test + public void hasIndexes() throws SQLException { + Indexes indexes = new Indexes("index_types"); + indexes.assertHasIndex("idx_key_value"); // auto generated + indexes.assertHasIndex("idx_custom"); // explicit set + } + + @Test + public void testUnique() throws SQLException { + Indexes indexes = new Indexes("index_types"); + indexes.assertIsUnique("idx_key"); + } +} diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java b/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java new file mode 100644 index 0000000..e9a5085 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/models/IndexType.java @@ -0,0 +1,19 @@ +package org.javawebstack.orm.test.shared.models; + +import org.javawebstack.orm.Model; +import org.javawebstack.orm.annotation.Column; +import org.javawebstack.orm.annotation.Index; + +import java.util.UUID; + +@Index({"key", "value"}) +@Index(value = {"id", "key"}, id = "idx_custom") +@Index(value = {"key"}, unique = true) +public class IndexType extends Model { + @Column + UUID id; + @Column + String key; + @Column + String value; +} diff --git a/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java b/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java new file mode 100644 index 0000000..3ca806e --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/verification/Indexes.java @@ -0,0 +1,55 @@ +package org.javawebstack.orm.test.shared.verification; + +import lombok.Builder; +import lombok.ToString; +import org.javawebstack.orm.test.shared.settings.MySQLConnectionContainer; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class Indexes extends MySQLConnectionContainer { + String tableName; + List indices = new ArrayList<>(); + + public Indexes(String tableName) throws SQLException { + this.tableName = tableName; + String query = String.format("SHOW INDEXES FROM %s WHERE `key_name` != 'PRIMARY'", tableName); + ResultSet resultSet = sql().read(query); + while (resultSet.next()) { + indices.add(IndexInfo.builder() + .name(resultSet.getString("key_name")) + .type(resultSet.getString("index_type")) + .unique(resultSet.getInt("non_unique") != 1) + .build() + ); + } + } + + public void assertHasIndex(String indexName) { + assertTrue( + indices.stream().anyMatch(i -> i.name.equals(indexName)), + String.format("Index %s.%s doesn't exist on table.", tableName, indexName) + ); + } + + public void assertIsUnique(String indexName) { + IndexInfo indexInfo = indices.stream().filter(i -> i.name.equals(indexName)).findFirst().orElse(null); + assertNotNull(indexInfo); + assertTrue( + indexInfo.unique, + String.format("Index %s.%s is not unique.", tableName, indexInfo) + ); + } + + @Builder + @ToString + public static class IndexInfo { + String name; + String type; + boolean unique; + } +} From 5674f8d5a3113590622bffdb6b45fd5eb67399bc Mon Sep 17 00:00:00 2001 From: x7airworker Date: Tue, 4 Jul 2023 16:59:22 +0200 Subject: [PATCH 10/24] Implemented onlyDeleted method on query builder --- src/main/java/org/javawebstack/orm/query/Query.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 24ba5c8..689fc85 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -361,6 +361,10 @@ public Query withDeleted() { return this; } + public Query onlyDeleted() { + return withDeleted().whereNotNull(repo.getInfo().getSoftDeleteField()); + } + public void finalDelete() { SQLQueryString qs = connection.builder().buildDelete(this); try { From 7db179e9f0226946acaa6fb3acd11dcec5257b6b Mon Sep 17 00:00:00 2001 From: maher Date: Tue, 15 Aug 2023 16:59:13 +0200 Subject: [PATCH 11/24] Fixed the whereIn condition for empty streams and collections --- .../javawebstack/orm/query/QueryGroup.java | 40 ++++++++++++------- .../builder/MySQLQueryStringBuilder.java | 6 --- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryGroup.java b/src/main/java/org/javawebstack/orm/query/QueryGroup.java index 446b6f1..46c2288 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryGroup.java +++ b/src/main/java/org/javawebstack/orm/query/QueryGroup.java @@ -6,8 +6,10 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.function.Function; +import java.util.stream.Stream; /** * Queries grouped via the QueryGroup class will be put inside parenthesis. @@ -37,9 +39,9 @@ public QueryGroup and(Function, QueryGroup> group) { public QueryGroup where(Function, QueryGroup> group) { QueryGroup innerGroup = group.apply(new QueryGroup<>()); - if(innerGroup.queryElements.size() == 0) + if(innerGroup.queryElements.isEmpty()) return this; - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.AND); queryElements.add(innerGroup); return this; @@ -52,9 +54,9 @@ public QueryGroup or(Function, QueryGroup> group) { public QueryGroup orWhere(Function, QueryGroup> group) { QueryGroup innerGroup = group.apply(new QueryGroup<>()); - if(innerGroup.queryElements.size() == 0) + if(innerGroup.queryElements.isEmpty()) return this; - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.OR); queryElements.add(innerGroup); return this; @@ -65,12 +67,22 @@ public QueryGroup where(Object left, String condition, Object right) { return whereNull(left); if(condition.equalsIgnoreCase("!=") && right == null) return whereNotNull(left); - if((condition.equalsIgnoreCase("IN") || condition.equalsIgnoreCase("NOT IN")) && (right == null || Array.getLength(right) == 0)) { - left = 1; - condition = "="; - right = 2; + + if(condition.equalsIgnoreCase("IN") || condition.equalsIgnoreCase("NOT IN")) { + Object[] values = (Object[]) right; + if (values != null && values.length == 1) { + if (values[0] instanceof Collection) + values = ((Collection) values[0]).toArray(); + else if (values[0] instanceof Stream) + values = ((Stream) values[0]).toArray(); + } + if(values == null || values.length == 0) { + left = 1; + condition = "="; + right = 2; + } } - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.AND); queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); return this; @@ -120,7 +132,7 @@ public QueryGroup orWhere(Object left, String condition, Object right) { condition = "="; right = 2; } - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.OR); queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); return this; @@ -173,7 +185,7 @@ public QueryGroup orWhereNotNull(Object left) { } public QueryGroup whereExists(Class model, Function, Query> consumer) { - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.AND); Query query = consumer.apply(new Query<>(model).limit(1)); queryElements.add(new QueryExists<>(query, false)); @@ -181,7 +193,7 @@ public QueryGroup whereExists(Class model, Function QueryGroup orWhereExists(Class model, Function, Query> consumer) { - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.OR); Query query = consumer.apply(new Query<>(model).limit(1)); queryElements.add(new QueryExists<>(query, false)); @@ -189,7 +201,7 @@ public QueryGroup orWhereExists(Class model, Function QueryGroup whereNotExists(Class model, Function, Query> consumer) { - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.AND); Query query = consumer.apply(new Query<>(model).limit(1)); queryElements.add(new QueryExists<>(query, true)); @@ -197,7 +209,7 @@ public QueryGroup whereNotExists(Class model, Function QueryGroup orWhereNotExists(Class model, Function, Query> consumer) { - if (queryElements.size() > 0) + if (!queryElements.isEmpty()) queryElements.add(QueryConjunction.OR); Query query = consumer.apply(new Query<>(model).limit(1)); queryElements.add(new QueryExists<>(query, true)); diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java index a97bb4f..dc9f40f 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -212,12 +212,6 @@ private SQLQueryString convertCondition(TableInfo info, QueryCondition condition sb.append(' '); if (condition.getOperator().endsWith("IN")) { Object[] values = (Object[]) condition.getRight(); - if (values.length == 1) { - if (values[0] instanceof Collection) - values = ((Collection) values[0]).toArray(); - else if (values[0] instanceof Stream) - values = ((Stream) values[0]).toArray(); - } sb.append("(").append(IntStream.range(0, values.length).mapToObj(i -> "?").collect(Collectors.joining(","))).append(")"); parameters.addAll(Arrays.asList(values)); } else if (condition.getRight() instanceof QueryColumn) { From 9e989b426612f59f022983450dc1503f267e94c6 Mon Sep 17 00:00:00 2001 From: Jan Bebendorf Date: Tue, 15 Aug 2023 17:05:27 +0200 Subject: [PATCH 12/24] Update maven-deploy.yml --- .github/workflows/maven-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 122197d..59d57de 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest services: mysql: - image: mariadb:latest + image: 'mariadb:10' env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: test From 4b127043ee03e634eea2ce81ada5988cc3ac51f7 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Mon, 4 Sep 2023 15:27:29 +0200 Subject: [PATCH 13/24] Upgraded abstract-data --- pom.xml | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 1a7f09f..dfd9929 100644 --- a/pom.xml +++ b/pom.xml @@ -56,15 +56,8 @@ org.javawebstack abstract-data - 1.0.4 + 1.0.6 - - - org.yaml - snakeyaml - 1.33 - - org.atteo evo-inflector @@ -78,25 +71,25 @@ org.junit.jupiter junit-jupiter-engine - 5.9.0 + 5.10.0 test org.apache.commons commons-lang3 - 3.12.0 + 3.13.0 test org.projectlombok lombok - 1.18.24 + 1.18.28 test mysql mysql-connector-java - 8.0.30 + 8.0.33 test From 6aacf970e11f2c6e95305402f94d5befea600df1 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 15:37:24 +0200 Subject: [PATCH 14/24] Fixed NPE when no accessible is set but .accessible is called --- .../orm/wrapper/builder/MySQLQueryStringBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java index dc9f40f..d773466 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -62,6 +62,8 @@ public SQLQueryString buildQuery(Query query) { QueryGroup where = (QueryGroup) query.getWhereGroup(); checkWithDeleted(repo, query.isWithDeleted(), where); if(query.shouldApplyAccessible()) { + if(repo.getAccessible() == null) + throw new ORMQueryException("No accessible was set on the repository for " + repo.getInfo().getModelClass().getName()); QueryGroup accessChecks; try { accessChecks = (QueryGroup) accessibleAccessMethod.invoke(repo.getAccessible(), query, new QueryGroup<>(), query.getAccessor()); From bcc0e9dc6f9ee8be70c14eba7746ab6e6599d963 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 16:24:41 +0200 Subject: [PATCH 15/24] Deprecated hasRecords, added isEmpty and isNotEmpty and fixed performance issues with them by setting limit(1) --- src/main/java/org/javawebstack/orm/query/Query.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 689fc85..e536e98 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -464,8 +464,17 @@ public int count() { } } + @Deprecated public boolean hasRecords() { - return count() > 0; + return isNotEmpty(); + } + + public boolean isNotEmpty() { + return limit(1).count() > 0; + } + + public boolean isEmpty() { + return !isNotEmpty(); } } From adefe66570fc4ae9434cf3485a08a78bf92bd147 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 18:32:30 +0200 Subject: [PATCH 16/24] Implemented dependency injection --- pom.xml | 12 ++++++++++++ src/main/java/org/javawebstack/orm/Model.java | 4 ++++ src/main/java/org/javawebstack/orm/ORM.java | 3 --- .../java/org/javawebstack/orm/ORMConfig.java | 16 ++++++++++++++++ .../java/org/javawebstack/orm/SQLMapper.java | 1 + 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index dfd9929..c3de70e 100644 --- a/pom.xml +++ b/pom.xml @@ -52,7 +52,19 @@ https://github.com/JavaWebStack/orm/tree/master + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + + + org.javawebstack + commons + 1.0.0-SNAPSHOT + org.javawebstack abstract-data diff --git a/src/main/java/org/javawebstack/orm/Model.java b/src/main/java/org/javawebstack/orm/Model.java index 7a60a73..8c46c34 100644 --- a/src/main/java/org/javawebstack/orm/Model.java +++ b/src/main/java/org/javawebstack/orm/Model.java @@ -1,5 +1,6 @@ package org.javawebstack.orm; +import org.javawebstack.commons.inject.Injector; import org.javawebstack.orm.query.Query; import java.lang.reflect.Field; @@ -22,6 +23,9 @@ public class Model { { updateOriginal(); + Injector injector = Repo.get(getClass()).getInfo().getConfig().getInjector(); + if(injector != null) + injector.inject(this); } static { diff --git a/src/main/java/org/javawebstack/orm/ORM.java b/src/main/java/org/javawebstack/orm/ORM.java index 20e42c0..60cb056 100644 --- a/src/main/java/org/javawebstack/orm/ORM.java +++ b/src/main/java/org/javawebstack/orm/ORM.java @@ -10,12 +10,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Logger; public class ORM { - public static final Logger LOGGER = Logger.getLogger("ORM"); - private static final Map, Repo> repositories = new HashMap<>(); public static Repo repo(Class model) { diff --git a/src/main/java/org/javawebstack/orm/ORMConfig.java b/src/main/java/org/javawebstack/orm/ORMConfig.java index 38c14a6..ab88807 100644 --- a/src/main/java/org/javawebstack/orm/ORMConfig.java +++ b/src/main/java/org/javawebstack/orm/ORMConfig.java @@ -1,5 +1,6 @@ package org.javawebstack.orm; +import org.javawebstack.commons.inject.Injector; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.mapper.DefaultMapper; import org.javawebstack.orm.mapper.TypeMapper; @@ -15,6 +16,7 @@ public class ORMConfig { private boolean idAutoIncrement = true; private final List typeMappers = new ArrayList<>(); private boolean preventUnnecessaryUpdates = true; + private Injector injector; public ORMConfig() { typeMappers.add(new DefaultMapper()); @@ -39,6 +41,8 @@ public ORMConfig setDefaultSize(int defaultSize) throws ORMConfigurationExceptio } public ORMConfig addTypeMapper(TypeMapper typeMapper) { + if(injector != null) + injector.inject(typeMapper); typeMappers.add(typeMapper); return this; } @@ -112,4 +116,16 @@ public ORMConfig setPreventUnnecessaryUpdates(boolean preventUnnecessaryUpdates) this.preventUnnecessaryUpdates = preventUnnecessaryUpdates; return this; } + + public ORMConfig setInjector(Injector injector) { + this.injector = injector; + if(injector != null) + typeMappers.forEach(injector::inject); + return this; + } + + public Injector getInjector() { + return injector; + } + } diff --git a/src/main/java/org/javawebstack/orm/SQLMapper.java b/src/main/java/org/javawebstack/orm/SQLMapper.java index dd3ebc8..efb3ea8 100644 --- a/src/main/java/org/javawebstack/orm/SQLMapper.java +++ b/src/main/java/org/javawebstack/orm/SQLMapper.java @@ -1,5 +1,6 @@ package org.javawebstack.orm; +import org.javawebstack.commons.inject.Injector; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.mapper.DefaultMapper; import org.javawebstack.orm.mapper.TypeMapper; From 085b29396e842e4f7a4b7fbc4bbf11acc14b7f7c Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 21:43:31 +0200 Subject: [PATCH 17/24] Implemented connection pooling --- src/main/java/org/javawebstack/orm/ORM.java | 18 +- src/main/java/org/javawebstack/orm/Repo.java | 28 +-- .../java/org/javawebstack/orm/Session.java | 46 ---- .../orm/{wrapper => connection}/BaseSQL.java | 2 +- .../orm/{wrapper => connection}/MySQL.java | 21 +- .../{wrapper => connection}/QueryLogger.java | 2 +- .../orm/{wrapper => connection}/SQL.java | 8 +- .../orm/{wrapper => connection}/SQLite.java | 20 +- .../orm/connection/pool/MinMaxScaler.java | 18 ++ .../orm/connection/pool/PoolScaling.java | 7 + .../orm/connection/pool/PooledSQL.java | 70 ++++++ .../orm/connection/pool/SQLPool.java | 93 ++++++++ .../orm/connection/pool/SingletonPool.java | 11 + .../orm/migration/AutoMigrator.java | 200 +++++++++--------- .../org/javawebstack/orm/query/Query.java | 104 ++++----- .../MySQLQueryStringRenderer.java} | 8 +- .../QueryStringRenderer.java} | 4 +- .../builder => renderer}/SQLQueryString.java | 2 +- .../{wrapper => util}/SQLDriverFactory.java | 6 +- .../SQLDriverNotFoundException.java | 5 +- .../orm/test/SQLDriverFactoryTest.java | 6 +- .../orm/test/UpdateOnlyIfIsDirtyTest.java | 2 +- .../settings/MySQLConnectionContainer.java | 4 +- 23 files changed, 434 insertions(+), 251 deletions(-) delete mode 100644 src/main/java/org/javawebstack/orm/Session.java rename src/main/java/org/javawebstack/orm/{wrapper => connection}/BaseSQL.java (98%) rename src/main/java/org/javawebstack/orm/{wrapper => connection}/MySQL.java (88%) rename src/main/java/org/javawebstack/orm/{wrapper => connection}/QueryLogger.java (67%) rename src/main/java/org/javawebstack/orm/{wrapper => connection}/SQL.java (73%) rename src/main/java/org/javawebstack/orm/{wrapper => connection}/SQLite.java (65%) create mode 100644 src/main/java/org/javawebstack/orm/connection/pool/MinMaxScaler.java create mode 100644 src/main/java/org/javawebstack/orm/connection/pool/PoolScaling.java create mode 100644 src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java create mode 100644 src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java create mode 100644 src/main/java/org/javawebstack/orm/connection/pool/SingletonPool.java rename src/main/java/org/javawebstack/orm/{wrapper/builder/MySQLQueryStringBuilder.java => renderer/MySQLQueryStringRenderer.java} (97%) rename src/main/java/org/javawebstack/orm/{wrapper/builder/QueryStringBuilder.java => renderer/QueryStringRenderer.java} (80%) rename src/main/java/org/javawebstack/orm/{wrapper/builder => renderer}/SQLQueryString.java (93%) rename src/main/java/org/javawebstack/orm/{wrapper => util}/SQLDriverFactory.java (86%) rename src/main/java/org/javawebstack/orm/{wrapper => util}/SQLDriverNotFoundException.java (87%) diff --git a/src/main/java/org/javawebstack/orm/ORM.java b/src/main/java/org/javawebstack/orm/ORM.java index 60cb056..4f37984 100644 --- a/src/main/java/org/javawebstack/orm/ORM.java +++ b/src/main/java/org/javawebstack/orm/ORM.java @@ -1,8 +1,8 @@ package org.javawebstack.orm; +import org.javawebstack.orm.connection.pool.SQLPool; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.migration.AutoMigrator; -import org.javawebstack.orm.wrapper.SQL; import org.reflections.Reflections; import java.lang.reflect.Modifier; @@ -19,25 +19,25 @@ public static Repo repo(Class model) { return (Repo) repositories.get(model); } - public static Repo register(Class model, SQL sql, ORMConfig config) throws ORMConfigurationException { - Repo repo = new Repo<>(model, sql, config); + public static Repo register(Class model, SQLPool pool, ORMConfig config) throws ORMConfigurationException { + Repo repo = new Repo<>(model, pool, config); repositories.put(model, repo); return repo; } - public static Repo register(Class model, SQL sql) throws ORMConfigurationException { - return register(model, sql, new ORMConfig()); + public static Repo register(Class model, SQLPool pool) throws ORMConfigurationException { + return register(model, pool, new ORMConfig()); } - public static void register(Package p, SQL sql, ORMConfig config) throws ORMConfigurationException { + public static void register(Package p, SQLPool pool, ORMConfig config) throws ORMConfigurationException { for (Class model : new Reflections(p.getName()).getSubTypesOf(Model.class)) { if (!Modifier.isAbstract(model.getModifiers())) - ORM.register(model, sql, config); + ORM.register(model, pool, config); } } - public static void register(Package p, SQL sql) throws ORMConfigurationException { - register(p, sql, new ORMConfig()); + public static void register(Package p, SQLPool pool) throws ORMConfigurationException { + register(p, pool, new ORMConfig()); } public static void unregister(Class model) { diff --git a/src/main/java/org/javawebstack/orm/Repo.java b/src/main/java/org/javawebstack/orm/Repo.java index f831184..3530a1a 100644 --- a/src/main/java/org/javawebstack/orm/Repo.java +++ b/src/main/java/org/javawebstack/orm/Repo.java @@ -1,13 +1,14 @@ package org.javawebstack.orm; +import org.javawebstack.orm.connection.pool.PooledSQL; +import org.javawebstack.orm.connection.pool.SQLPool; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.filter.DefaultQueryFilter; import org.javawebstack.orm.filter.QueryFilter; import org.javawebstack.orm.migration.AutoMigrator; import org.javawebstack.orm.query.Query; -import org.javawebstack.orm.wrapper.SQL; -import org.javawebstack.orm.wrapper.builder.SQLQueryString; +import org.javawebstack.orm.renderer.SQLQueryString; import java.lang.reflect.Field; import java.sql.SQLException; @@ -25,14 +26,14 @@ public static Repo get(Class model) { } private final TableInfo info; - private final SQL connection; + private final SQLPool pool; private final List> observers = new ArrayList<>(); private Accessible accessible; private QueryFilter filter; - public Repo(Class clazz, SQL connection, ORMConfig config) throws ORMConfigurationException { + public Repo(Class clazz, SQLPool pool, ORMConfig config) throws ORMConfigurationException { this.info = new TableInfo(clazz, config); - this.connection = connection; + this.pool = pool; filter = new DefaultQueryFilter(info.getFilterable(), info.getSearchable()); } @@ -119,12 +120,13 @@ private void executeCreate(T entry) { if (map.containsKey(idCol) && map.get(idCol) == null) map.remove(idCol); } - SQLQueryString qs = getConnection().builder().buildInsert(info, map); - SQL connection = Session.current() != null ? Session.current().getConnection() : this.connection; - int id = connection.write(qs.getQuery(), qs.getParameters().toArray()); - if (info.isAutoIncrement()) - info.getField(info.getIdField()).set(entry, id); - entry.setEntryExists(true); + try(PooledSQL connection = pool.get()) { + SQLQueryString qs = connection.builder().buildInsert(info, map); + int id = connection.write(qs.getQuery(), qs.getParameters().toArray()); + if (info.isAutoIncrement()) + info.getField(info.getIdField()).set(entry, id); + entry.setEntryExists(true); + } } catch (SQLException | IllegalAccessException throwables) { throw new ORMQueryException(throwables); } @@ -217,8 +219,8 @@ public void autoMigrate() { AutoMigrator.migrate(this); } - public SQL getConnection() { - return connection; + public SQLPool getPool() { + return pool; } public TableInfo getInfo() { diff --git a/src/main/java/org/javawebstack/orm/Session.java b/src/main/java/org/javawebstack/orm/Session.java deleted file mode 100644 index 2204d22..0000000 --- a/src/main/java/org/javawebstack/orm/Session.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.javawebstack.orm; - -import org.javawebstack.orm.wrapper.SQL; - -import java.util.function.Consumer; - -public class Session { - - private static ThreadLocal sessions = new ThreadLocal<>(); - - public static Session current() { - return sessions.get(); - } - - private SQL connection; - - private Session() { - - } - - public Session via(SQL connection) { - this.connection = connection; - return this; - } - - public SQL getConnection() { - return connection; - } - - public static void session(Consumer consumer) { - Session session = begin(); - consumer.accept(session); - end(); - } - - public static Session begin() { - Session session = new Session(); - sessions.set(session); - return session; - } - - public static void end() { - sessions.remove(); - } - -} diff --git a/src/main/java/org/javawebstack/orm/wrapper/BaseSQL.java b/src/main/java/org/javawebstack/orm/connection/BaseSQL.java similarity index 98% rename from src/main/java/org/javawebstack/orm/wrapper/BaseSQL.java rename to src/main/java/org/javawebstack/orm/connection/BaseSQL.java index ad0e0b5..63a5ff2 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/BaseSQL.java +++ b/src/main/java/org/javawebstack/orm/connection/BaseSQL.java @@ -1,4 +1,4 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.connection; import org.javawebstack.orm.exception.ORMQueryException; diff --git a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java b/src/main/java/org/javawebstack/orm/connection/MySQL.java similarity index 88% rename from src/main/java/org/javawebstack/orm/wrapper/MySQL.java rename to src/main/java/org/javawebstack/orm/connection/MySQL.java index 676b8db..f311ac6 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java +++ b/src/main/java/org/javawebstack/orm/connection/MySQL.java @@ -1,8 +1,8 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.connection; import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.wrapper.builder.MySQLQueryStringBuilder; -import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; +import org.javawebstack.orm.renderer.MySQLQueryStringRenderer; +import org.javawebstack.orm.renderer.QueryStringRenderer; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -93,6 +93,16 @@ public Connection getConnection() { return c; } + public void close() { + if(c != null) { + try { + if(!c.isClosed()) + c.close(); + } catch (SQLException ignored) {} + c = null; + } + } + private static String buildQuery(Map params) { return params.entrySet().stream().map(e -> urlEncode(e.getKey()) + "=" + urlEncode(e.getValue())).collect(Collectors.joining("&")); } @@ -105,10 +115,9 @@ private static String urlEncode(String s) { return s; } - public QueryStringBuilder builder() { - return MySQLQueryStringBuilder.INSTANCE; + public QueryStringRenderer builder() { + return MySQLQueryStringRenderer.INSTANCE; } - } diff --git a/src/main/java/org/javawebstack/orm/wrapper/QueryLogger.java b/src/main/java/org/javawebstack/orm/connection/QueryLogger.java similarity index 67% rename from src/main/java/org/javawebstack/orm/wrapper/QueryLogger.java rename to src/main/java/org/javawebstack/orm/connection/QueryLogger.java index 21852ae..0e8e315 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/QueryLogger.java +++ b/src/main/java/org/javawebstack/orm/connection/QueryLogger.java @@ -1,4 +1,4 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.connection; public interface QueryLogger { diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQL.java b/src/main/java/org/javawebstack/orm/connection/SQL.java similarity index 73% rename from src/main/java/org/javawebstack/orm/wrapper/SQL.java rename to src/main/java/org/javawebstack/orm/connection/SQL.java index 35a0a3b..ed47c6c 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQL.java +++ b/src/main/java/org/javawebstack/orm/connection/SQL.java @@ -1,6 +1,6 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.connection; -import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; +import org.javawebstack.orm.renderer.QueryStringRenderer; import java.sql.Connection; import java.sql.ResultSet; @@ -15,13 +15,15 @@ public interface SQL { int write(String queryString, Object... parameters) throws SQLException; void close(ResultSet resultSet); + void close(); - QueryStringBuilder builder(); + QueryStringRenderer builder(); void addQueryLogger(QueryLogger logger); void removeQueryLogger(QueryLogger logger); + @Deprecated SQL fork(); } diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQLite.java b/src/main/java/org/javawebstack/orm/connection/SQLite.java similarity index 65% rename from src/main/java/org/javawebstack/orm/wrapper/SQLite.java rename to src/main/java/org/javawebstack/orm/connection/SQLite.java index c07c4d4..ee9a4c2 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQLite.java +++ b/src/main/java/org/javawebstack/orm/connection/SQLite.java @@ -1,7 +1,7 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.connection; -import org.javawebstack.orm.wrapper.builder.MySQLQueryStringBuilder; -import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; +import org.javawebstack.orm.renderer.MySQLQueryStringRenderer; +import org.javawebstack.orm.renderer.QueryStringRenderer; import java.sql.Connection; import java.sql.DriverManager; @@ -42,8 +42,18 @@ public Connection getConnection() { return c; } - public QueryStringBuilder builder() { - return MySQLQueryStringBuilder.INSTANCE; // TODO Build a custom one for SQLite + public void close() { + if(c != null) { + try { + if(!c.isClosed()) + c.close(); + } catch (SQLException ignored) {} + c = null; + } + } + + public QueryStringRenderer builder() { + return MySQLQueryStringRenderer.INSTANCE; // TODO Build a custom one for SQLite } diff --git a/src/main/java/org/javawebstack/orm/connection/pool/MinMaxScaler.java b/src/main/java/org/javawebstack/orm/connection/pool/MinMaxScaler.java new file mode 100644 index 0000000..3fd3dfd --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/pool/MinMaxScaler.java @@ -0,0 +1,18 @@ +package org.javawebstack.orm.connection.pool; + +public class MinMaxScaler implements PoolScaling { + + private final int min; + private final int max; + + public MinMaxScaler(int min, int max) { + this.min = min; + this.max = max; + } + + public int scale(int total, int used) { + int needed = used + 1; + return Math.max(min, Math.min(max, needed)); + } + +} diff --git a/src/main/java/org/javawebstack/orm/connection/pool/PoolScaling.java b/src/main/java/org/javawebstack/orm/connection/pool/PoolScaling.java new file mode 100644 index 0000000..8a582c6 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/pool/PoolScaling.java @@ -0,0 +1,7 @@ +package org.javawebstack.orm.connection.pool; + +public interface PoolScaling { + + int scale(int total, int used); + +} diff --git a/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java b/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java new file mode 100644 index 0000000..52c1fc7 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java @@ -0,0 +1,70 @@ +package org.javawebstack.orm.connection.pool; + +import org.javawebstack.orm.connection.QueryLogger; +import org.javawebstack.orm.connection.SQL; +import org.javawebstack.orm.exception.ORMQueryException; +import org.javawebstack.orm.renderer.QueryStringRenderer; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class PooledSQL implements SQL, AutoCloseable { + + private final SQLPool pool; + private final SQL connection; + private boolean closed; + + public PooledSQL(SQLPool pool, SQL connection) { + this.pool = pool; + this.connection = connection; + } + + private void assureOpen() { + if(closed) + throw new ORMQueryException("Pooled connection has already been returned"); + } + + public Connection getConnection() { + assureOpen(); + return null; + } + + public ResultSet read(String queryString, Object... parameters) throws SQLException { + assureOpen(); + return connection.read(queryString, parameters); + } + + public int write(String queryString, Object... parameters) throws SQLException { + assureOpen(); + return connection.write(queryString, parameters); + } + + public void close(ResultSet resultSet) { + assureOpen(); + connection.close(resultSet); + } + + public void close() { + pool.release(connection); + closed = true; + } + + public QueryStringRenderer builder() { + assureOpen(); + return connection.builder(); + } + + public void addQueryLogger(QueryLogger logger) { + throw new RuntimeException("addQueryLogger can not be executed on a pooled connection, call it on the pool instead"); + } + + public void removeQueryLogger(QueryLogger logger) { + throw new RuntimeException("removeQueryLogger can not be executed on a pooled connection, call it on the pool instead"); + } + + public SQL fork() { + throw new ORMQueryException("Forking is not supported by pooled connections"); + } + +} diff --git a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java new file mode 100644 index 0000000..aca777a --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -0,0 +1,93 @@ +package org.javawebstack.orm.connection.pool; + +import org.javawebstack.orm.connection.QueryLogger; +import org.javawebstack.orm.connection.SQL; + +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Supplier; + +public class SQLPool { + + private final PoolScaling scaling; + private final Supplier supplier; + private final List connections = new ArrayList<>(); + private final Queue connectionQueue = new LinkedBlockingQueue<>(); + private final Set loggers = new HashSet<>(); + private boolean closing; + + public SQLPool(PoolScaling scaling, Supplier supplier) { + this.scaling = scaling; + this.supplier = supplier; + this.scale(); + } + + public SQLPool(int min, int max, Supplier supplier) { + this(new MinMaxScaler(min, max), supplier); + } + + public PooledSQL get() { + if(closing) + throw new IllegalStateException("Pool has already been closed"); + scale(); + SQL sql = connectionQueue.poll(); + scale(); + return new PooledSQL(this, sql); + } + + public void release(SQL sql) { + if(!closing) { + if(sql instanceof PooledSQL) { + sql.close(); + return; + } + if(!connections.contains(sql)) + throw new IllegalArgumentException("Not a connection of this pool"); + connectionQueue.add(sql); + scale(); + } + } + + public void addQueryLogger(QueryLogger logger) { + loggers.add(logger); + } + + public void removeQueryLogger(QueryLogger logger) { + loggers.remove(logger); + } + + public void scale() { + if(closing) + throw new IllegalStateException("Pool has already been closed"); + int newScale = scaling.scale(connections.size(), connections.size() - connectionQueue.size()); + if(newScale == connections.size()) + return; + while (newScale > connections.size()) { + SQL sql = supplier.get(); + connections.add(sql); + connectionQueue.add(sql); + } + while (newScale < connections.size() && connectionQueue.size() > 0) { + SQL sql = connectionQueue.poll(); + sql.close(); + connections.remove(sql); + } + } + + public void close() { + closing = true; + for(SQL connection : connections) { + connection.close(); + } + } + + private class PoolQueryLogger implements QueryLogger { + + public void log(String query, Object[] parameters) { + for(QueryLogger logger : loggers) + logger.log(query, parameters); + } + + } + +} diff --git a/src/main/java/org/javawebstack/orm/connection/pool/SingletonPool.java b/src/main/java/org/javawebstack/orm/connection/pool/SingletonPool.java new file mode 100644 index 0000000..f1ff6de --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/pool/SingletonPool.java @@ -0,0 +1,11 @@ +package org.javawebstack.orm.connection.pool; + +import org.javawebstack.orm.connection.SQL; + +public class SingletonPool extends SQLPool { + + public SingletonPool(SQL sql) { + super(new MinMaxScaler(1,1), () -> sql); + } + +} diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index a1b32b6..e97ab25 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -3,8 +3,10 @@ import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.annotation.Index; +import org.javawebstack.orm.connection.pool.PooledSQL; +import org.javawebstack.orm.connection.pool.SQLPool; import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.wrapper.SQL; +import org.javawebstack.orm.connection.SQL; import java.sql.ResultSet; import java.sql.SQLException; @@ -24,131 +26,133 @@ public static void migrate(Repo... repos) { public static void migrate(boolean fresh, Repo... repos) { if (fresh) drop(repos); - Map> tables = new HashMap<>(); + Map> tables = new HashMap<>(); for (Repo repo : repos) { - if (!tables.containsKey(repo.getConnection())) { - tables.put(repo.getConnection(), getTables(repo.getConnection())); + if (!tables.containsKey(repo.getPool())) { + tables.put(repo.getPool(), getTables(repo.getPool())); } - migrateTable(repo.getConnection(), repo.getInfo(), tables.get(repo.getConnection()).contains(repo.getInfo().getTableName())); + migrateTable(repo.getPool(), repo.getInfo(), tables.get(repo.getPool()).contains(repo.getInfo().getTableName())); } } public static void drop(Repo... repos) { for (Repo repo : repos) { - try { - repo.getConnection().write("DROP TABLE `" + repo.getInfo().getTableName() + "`;"); + try(PooledSQL connection = repo.getPool().get()) { + connection.write("DROP TABLE `" + repo.getInfo().getTableName() + "`;"); } catch (SQLException ignored) { } } } - private static void migrateTable(SQL sql, TableInfo info, boolean tableExists) { - List addColumns = new ArrayList<>(); - List updateColumns = new ArrayList<>(); - Map columnKeys = tableExists ? getColumnKeys(sql, info.getTableName()) : new HashMap<>(); - List addValues = new ArrayList<>(); - List updateValues = new ArrayList<>(); - for (String fieldName : info.getFields()) { - String columnName = info.getColumnName(fieldName); - StringBuilder sb = new StringBuilder("`") - .append(columnName) - .append("` "); - sb.append(info.getType(fieldName).name()); - String parameterTypes = info.getTypeParameters(fieldName); + private static void migrateTable(SQLPool pool, TableInfo info, boolean tableExists) { + try(PooledSQL sql = pool.get()) { + List addColumns = new ArrayList<>(); + List updateColumns = new ArrayList<>(); + Map columnKeys = tableExists ? getColumnKeys(sql, info.getTableName()) : new HashMap<>(); + List addValues = new ArrayList<>(); + List updateValues = new ArrayList<>(); + for (String fieldName : info.getFields()) { + String columnName = info.getColumnName(fieldName); + StringBuilder sb = new StringBuilder("`") + .append(columnName) + .append("` "); + sb.append(info.getType(fieldName).name()); + String parameterTypes = info.getTypeParameters(fieldName); - if (parameterTypes != null) - sb.append('(') - .append(parameterTypes) - .append(')'); - sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); - if (info.isAutoIncrement() && info.getIdField().equals(fieldName)) - sb.append(" AUTO_INCREMENT"); - if (columnKeys.containsKey(columnName)) { - if (info.getDefault(fieldName) != null) { - sb.append(" DEFAULT(?)"); - updateValues.add(info.getDefault(fieldName)); + if (parameterTypes != null) + sb.append('(') + .append(parameterTypes) + .append(')'); + sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); + if (info.isAutoIncrement() && info.getIdField().equals(fieldName)) + sb.append(" AUTO_INCREMENT"); + if (columnKeys.containsKey(columnName)) { + if (info.getDefault(fieldName) != null) { + sb.append(" DEFAULT(?)"); + updateValues.add(info.getDefault(fieldName)); + } + updateColumns.add(sb.toString()); + } else { + if (info.getDefault(fieldName) != null) { + sb.append(" DEFAULT(?)"); + addValues.add(info.getDefault(fieldName)); + } + addColumns.add(sb.toString()); } - updateColumns.add(sb.toString()); - } else { - if (info.getDefault(fieldName) != null) { - sb.append(" DEFAULT(?)"); - addValues.add(info.getDefault(fieldName)); - } - addColumns.add(sb.toString()); } - } - if (info.getPrimaryKey() != null) { - String columnName = info.getColumnName(info.getPrimaryKey()); - if (!columnKeys.containsKey(columnName) || !columnKeys.get(columnName).contains("PRI")) - addColumns.add("PRIMARY KEY (`" + columnName + "`)"); - } - for (String uniqueField : info.getUniqueKeys()) { - String columnName = info.getColumnName(uniqueField); - if (!columnKeys.containsKey(columnName) || !columnKeys.get(columnName).contains("UNI")) - addColumns.add("UNIQUE (`" + columnName + "`)"); - } - if (!tableExists) { - try { - sql.write(new StringBuilder("CREATE TABLE `") - .append(info.getTableName()) - .append("` (") - .append(String.join(",", addColumns)) - .append(") DEFAULT CHARSET=utf8mb4;").toString() - , addValues.toArray()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); + if (info.getPrimaryKey() != null) { + String columnName = info.getColumnName(info.getPrimaryKey()); + if (!columnKeys.containsKey(columnName) || !columnKeys.get(columnName).contains("PRI")) + addColumns.add("PRIMARY KEY (`" + columnName + "`)"); } - } else { - if (addColumns.size() > 0) { + for (String uniqueField : info.getUniqueKeys()) { + String columnName = info.getColumnName(uniqueField); + if (!columnKeys.containsKey(columnName) || !columnKeys.get(columnName).contains("UNI")) + addColumns.add("UNIQUE (`" + columnName + "`)"); + } + if (!tableExists) { try { - sql.write(new StringBuilder("ALTER TABLE `") + sql.write(new StringBuilder("CREATE TABLE `") .append(info.getTableName()) - .append("` ADD (") + .append("` (") .append(String.join(",", addColumns)) - .append(");").toString() + .append(") DEFAULT CHARSET=utf8mb4;").toString() , addValues.toArray()); } catch (SQLException throwables) { throw new ORMQueryException(throwables); } + } else { + if (addColumns.size() > 0) { + try { + sql.write(new StringBuilder("ALTER TABLE `") + .append(info.getTableName()) + .append("` ADD (") + .append(String.join(",", addColumns)) + .append(");").toString() + , addValues.toArray()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } + if (updateColumns.size() > 0) { + try { + sql.write(new StringBuilder("ALTER TABLE `") + .append(info.getTableName()) + .append("` ") + .append(updateColumns.stream().map(c -> "MODIFY COLUMN " + c).collect(Collectors.joining(","))) + .append(";").toString() + , updateValues.toArray()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } + } } - if (updateColumns.size() > 0) { + + List existingIndices = getIndices(sql, info.getTableName()); + for (Index index : info.getIndices()) { + String columns = Stream.of(index.value()).map(info::getColumnName).map(s -> "`" + s + "`").collect(Collectors.joining(",")); + String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); + if (existingIndices.contains(id)) + continue; + + StringBuilder sb = new StringBuilder("CREATE "); + if (index.unique()) + sb.append("UNIQUE "); + sb.append("INDEX `").append(id).append("` "); + if (index.type() != Index.Type.AUTO) + sb.append("USING ").append(index.type().name()).append(" "); + sb.append("ON `") + .append(info.getTableName()) + .append("` (") + .append(columns) + .append(");"); try { - sql.write(new StringBuilder("ALTER TABLE `") - .append(info.getTableName()) - .append("` ") - .append(updateColumns.stream().map(c -> "MODIFY COLUMN " + c).collect(Collectors.joining(","))) - .append(";").toString() - , updateValues.toArray()); + sql.write(sb.toString()); } catch (SQLException throwables) { throw new ORMQueryException(throwables); } } } - - List existingIndices = getIndices(sql, info.getTableName()); - for (Index index : info.getIndices()) { - String columns = Stream.of(index.value()).map(info::getColumnName).map(s -> "`" + s + "`").collect(Collectors.joining(",")); - String id = index.id().length() > 0 ? index.id() : "idx_" + String.join("_", index.value()); - if (existingIndices.contains(id)) - continue; - - StringBuilder sb = new StringBuilder("CREATE "); - if (index.unique()) - sb.append("UNIQUE "); - sb.append("INDEX `").append(id).append("` "); - if (index.type() != Index.Type.AUTO) - sb.append("USING ").append(index.type().name()).append(" "); - sb.append("ON `") - .append(info.getTableName()) - .append("` (") - .append(columns) - .append(");"); - try { - sql.write(sb.toString()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - } } private static Map getColumnKeys(SQL sql, String tableName) { @@ -177,8 +181,8 @@ private static List getIndices(SQL sql, String tableName) { } } - private static List getTables(SQL sql) { - try { + private static List getTables(SQLPool pool) { + try(PooledSQL sql = pool.get()) { List tables = new ArrayList<>(); ResultSet rs = sql.read("SHOW TABLES;"); while (rs.next()) { diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index e536e98..658650d 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -3,10 +3,10 @@ import org.javawebstack.orm.Model; import org.javawebstack.orm.Repo; import org.javawebstack.orm.SQLMapper; -import org.javawebstack.orm.Session; +import org.javawebstack.orm.connection.pool.PooledSQL; import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.wrapper.SQL; -import org.javawebstack.orm.wrapper.builder.SQLQueryString; +import org.javawebstack.orm.connection.SQL; +import org.javawebstack.orm.renderer.SQLQueryString; import java.sql.ResultSet; import java.sql.SQLException; @@ -21,7 +21,6 @@ public class Query { private final Repo repo; private final Class model; - private SQL connection; private List select = new ArrayList<>(); private final QueryGroup where = new QueryGroup<>(); private Integer offset; @@ -40,15 +39,6 @@ public Query(Class model) { public Query(Repo repo, Class model) { this.repo = repo; this.model = model; - Session session = Session.current(); - this.connection = repo.getConnection(); - if(session != null && session.getConnection() != null) - this.connection = session.getConnection(); - } - - public Query via(SQL connection) { - this.connection = connection; - return this; } public boolean isWithDeleted() { @@ -366,11 +356,13 @@ public Query onlyDeleted() { } public void finalDelete() { - SQLQueryString qs = connection.builder().buildDelete(this); - try { - connection.write(qs.getQuery(), qs.getParameters().toArray()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); + try(PooledSQL connection = repo.getPool().get()) { + SQLQueryString qs = connection.builder().buildDelete(this); + try { + connection.write(qs.getQuery(), qs.getParameters().toArray()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } } } @@ -395,18 +387,20 @@ public void restore() { } public T refresh(T entity) { - SQLQueryString qs = connection.builder().buildQuery(this); - try { - ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); - if(rs.next()) { - SQLMapper.mapBack(repo, rs, entity); - connection.close(rs); - return entity; - } else { - return null; + try(PooledSQL connection = repo.getPool().get()) { + SQLQueryString qs = connection.builder().buildQuery(this); + try { + ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); + if(rs.next()) { + SQLMapper.mapBack(repo, rs, entity); + connection.close(rs); + return entity; + } else { + return null; + } + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); } - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); } } @@ -415,23 +409,27 @@ public void update(T entity) { } public void update(Map values) { - SQLQueryString queryString = connection.builder().buildUpdate(this, values); - try { - connection.write(queryString.getQuery(), queryString.getParameters().toArray()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); + try(PooledSQL connection = repo.getPool().get()) { + SQLQueryString queryString = connection.builder().buildUpdate(this, values); + try { + connection.write(queryString.getQuery(), queryString.getParameters().toArray()); + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } } } public List all() { - SQLQueryString qs = connection.builder().buildQuery(this); - try { - ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); - List list = SQLMapper.map(repo, rs, new ArrayList<>()); - connection.close(rs); - return list; - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); + try(PooledSQL connection = repo.getPool().get()) { + SQLQueryString qs = connection.builder().buildQuery(this); + try { + ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); + List list = SQLMapper.map(repo, rs, new ArrayList<>()); + connection.close(rs); + return list; + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } } } @@ -451,16 +449,18 @@ public Stream stream() { } public int count() { - SQLQueryString qs = connection.builder().buildQuery(this.select("count(*)")); - try { - ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); - int c = 0; - if (rs.next()) - c = rs.getInt(1); - connection.close(rs); - return c; - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); + try(PooledSQL connection = repo.getPool().get()) { + SQLQueryString qs = connection.builder().buildQuery(this.select("count(*)")); + try { + ResultSet rs = connection.read(qs.getQuery(), qs.getParameters().toArray()); + int c = 0; + if (rs.next()) + c = rs.getInt(1); + connection.close(rs); + return c; + } catch (SQLException throwables) { + throw new ORMQueryException(throwables); + } } } diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java b/src/main/java/org/javawebstack/orm/renderer/MySQLQueryStringRenderer.java similarity index 97% rename from src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java rename to src/main/java/org/javawebstack/orm/renderer/MySQLQueryStringRenderer.java index d773466..44ee5c8 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/renderer/MySQLQueryStringRenderer.java @@ -1,6 +1,5 @@ -package org.javawebstack.orm.wrapper.builder; +package org.javawebstack.orm.renderer; -import org.javawebstack.abstractdata.AbstractArray; import org.javawebstack.orm.*; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.query.*; @@ -12,11 +11,10 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; -public class MySQLQueryStringBuilder implements QueryStringBuilder { +public class MySQLQueryStringRenderer implements QueryStringRenderer { - public static final MySQLQueryStringBuilder INSTANCE = new MySQLQueryStringBuilder(); + public static final MySQLQueryStringRenderer INSTANCE = new MySQLQueryStringRenderer(); private static Method accessibleAccessMethod; diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java b/src/main/java/org/javawebstack/orm/renderer/QueryStringRenderer.java similarity index 80% rename from src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java rename to src/main/java/org/javawebstack/orm/renderer/QueryStringRenderer.java index 0b48378..b0b61a5 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/renderer/QueryStringRenderer.java @@ -1,11 +1,11 @@ -package org.javawebstack.orm.wrapper.builder; +package org.javawebstack.orm.renderer; import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.query.Query; import java.util.Map; -public interface QueryStringBuilder { +public interface QueryStringRenderer { SQLQueryString buildInsert(TableInfo info, Map values); SQLQueryString buildQuery(Query query); diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java b/src/main/java/org/javawebstack/orm/renderer/SQLQueryString.java similarity index 93% rename from src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java rename to src/main/java/org/javawebstack/orm/renderer/SQLQueryString.java index 3c60a42..1947c8a 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java +++ b/src/main/java/org/javawebstack/orm/renderer/SQLQueryString.java @@ -1,4 +1,4 @@ -package org.javawebstack.orm.wrapper.builder; +package org.javawebstack.orm.renderer; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQLDriverFactory.java b/src/main/java/org/javawebstack/orm/util/SQLDriverFactory.java similarity index 86% rename from src/main/java/org/javawebstack/orm/wrapper/SQLDriverFactory.java rename to src/main/java/org/javawebstack/orm/util/SQLDriverFactory.java index a07bb82..64a695a 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQLDriverFactory.java +++ b/src/main/java/org/javawebstack/orm/util/SQLDriverFactory.java @@ -1,4 +1,8 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.util; + +import org.javawebstack.orm.connection.MySQL; +import org.javawebstack.orm.connection.SQL; +import org.javawebstack.orm.connection.SQLite; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQLDriverNotFoundException.java b/src/main/java/org/javawebstack/orm/util/SQLDriverNotFoundException.java similarity index 87% rename from src/main/java/org/javawebstack/orm/wrapper/SQLDriverNotFoundException.java rename to src/main/java/org/javawebstack/orm/util/SQLDriverNotFoundException.java index 2466d78..789f35b 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQLDriverNotFoundException.java +++ b/src/main/java/org/javawebstack/orm/util/SQLDriverNotFoundException.java @@ -1,14 +1,15 @@ -package org.javawebstack.orm.wrapper; +package org.javawebstack.orm.util; public class SQLDriverNotFoundException extends Exception { + private String name; public SQLDriverNotFoundException (String name) { this.name = name; } - public String getMessage() { return "SQL Driver " + name + " not found!"; } + } diff --git a/src/test/java/org/javawebstack/orm/test/SQLDriverFactoryTest.java b/src/test/java/org/javawebstack/orm/test/SQLDriverFactoryTest.java index d6b1299..9438c16 100644 --- a/src/test/java/org/javawebstack/orm/test/SQLDriverFactoryTest.java +++ b/src/test/java/org/javawebstack/orm/test/SQLDriverFactoryTest.java @@ -1,8 +1,8 @@ package org.javawebstack.orm.test; -import org.javawebstack.orm.wrapper.MySQL; -import org.javawebstack.orm.wrapper.SQLDriverFactory; -import org.javawebstack.orm.wrapper.SQLDriverNotFoundException; +import org.javawebstack.orm.connection.MySQL; +import org.javawebstack.orm.util.SQLDriverFactory; +import org.javawebstack.orm.util.SQLDriverNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/javawebstack/orm/test/UpdateOnlyIfIsDirtyTest.java b/src/test/java/org/javawebstack/orm/test/UpdateOnlyIfIsDirtyTest.java index 44076f2..e3edb18 100644 --- a/src/test/java/org/javawebstack/orm/test/UpdateOnlyIfIsDirtyTest.java +++ b/src/test/java/org/javawebstack/orm/test/UpdateOnlyIfIsDirtyTest.java @@ -4,7 +4,7 @@ import org.javawebstack.orm.ORMConfig; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.test.shared.models.JustString; -import org.javawebstack.orm.wrapper.QueryLogger; +import org.javawebstack.orm.connection.QueryLogger; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/org/javawebstack/orm/test/shared/settings/MySQLConnectionContainer.java b/src/test/java/org/javawebstack/orm/test/shared/settings/MySQLConnectionContainer.java index c8472d9..e9a0fbe 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/settings/MySQLConnectionContainer.java +++ b/src/test/java/org/javawebstack/orm/test/shared/settings/MySQLConnectionContainer.java @@ -1,7 +1,7 @@ package org.javawebstack.orm.test.shared.settings; -import org.javawebstack.orm.wrapper.MySQL; -import org.javawebstack.orm.wrapper.SQL; +import org.javawebstack.orm.connection.MySQL; +import org.javawebstack.orm.connection.SQL; import java.io.File; import java.io.FileInputStream; From e9647d6f10bf712f0f60f5616c3e75b420a61fff Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 21:59:19 +0200 Subject: [PATCH 18/24] Added deprecated legacy register methods --- src/main/java/org/javawebstack/orm/ORM.java | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/ORM.java b/src/main/java/org/javawebstack/orm/ORM.java index 4f37984..cf7f4f4 100644 --- a/src/main/java/org/javawebstack/orm/ORM.java +++ b/src/main/java/org/javawebstack/orm/ORM.java @@ -1,6 +1,8 @@ package org.javawebstack.orm; +import org.javawebstack.orm.connection.SQL; import org.javawebstack.orm.connection.pool.SQLPool; +import org.javawebstack.orm.connection.pool.SingletonPool; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.migration.AutoMigrator; import org.reflections.Reflections; @@ -40,6 +42,26 @@ public static void register(Package p, SQLPool pool) throws ORMConfigurationExce register(p, pool, new ORMConfig()); } + @Deprecated + public static Repo register(Class model, SQL sql, ORMConfig config) throws ORMConfigurationException { + return register(model, new SingletonPool(sql), config); + } + + @Deprecated + public static Repo register(Class model, SQL sql) throws ORMConfigurationException { + return register(model, new SingletonPool(sql)); + } + + @Deprecated + public static void register(Package p, SQL sql, ORMConfig config) throws ORMConfigurationException { + register(p, new SingletonPool(sql), config); + } + + @Deprecated + public static void register(Package p, SQL sql) throws ORMConfigurationException { + register(p, new SingletonPool(sql)); + } + public static void unregister(Class model) { repositories.remove(model); } From ffbc74370161810caccd8058870e32a1bf8e83d0 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 8 Sep 2023 23:36:10 +0200 Subject: [PATCH 19/24] Add withDeleted to refresh --- src/main/java/org/javawebstack/orm/Repo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/Repo.java b/src/main/java/org/javawebstack/orm/Repo.java index 3530a1a..02413fc 100644 --- a/src/main/java/org/javawebstack/orm/Repo.java +++ b/src/main/java/org/javawebstack/orm/Repo.java @@ -175,7 +175,7 @@ public void finalDelete(T entry) { } public T refresh(T entry) { - where(info.getIdField(), getId(entry)).refresh(entry); + where(info.getIdField(), getId(entry)).withDeleted().refresh(entry); entry.updateOriginal(); return entry; } From 05ea936f526cb9e1bd967e21d3d19a870791a115 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 12 Sep 2023 22:46:45 +0200 Subject: [PATCH 20/24] Fixed tests --- .../orm/test/querybuilding/FromClauseTest.java | 5 ++++- .../orm/test/shared/verification/QueryVerification.java | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java index 227d733..cc51855 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java @@ -3,6 +3,7 @@ import org.javawebstack.orm.Model; import org.javawebstack.orm.ORM; import org.javawebstack.orm.Repo; +import org.javawebstack.orm.connection.pool.PooledSQL; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.test.ORMTestCase; import org.javawebstack.orm.test.shared.models.tablenames.*; @@ -68,6 +69,8 @@ void testOverwrittenTableName() throws ORMConfigurationException { */ private String getBaseQuery(Class clazz) throws ORMConfigurationException { ORM.register(clazz, sql()); - return Repo.get(clazz).getConnection().builder().buildQuery(Repo.get(clazz).query()).getQuery(); + try(PooledSQL sql = Repo.get(clazz).getPool().get()) { + return sql.builder().buildQuery(Repo.get(clazz).query()).getQuery(); + } } } diff --git a/src/test/java/org/javawebstack/orm/test/shared/verification/QueryVerification.java b/src/test/java/org/javawebstack/orm/test/shared/verification/QueryVerification.java index 3911d99..6ace800 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/verification/QueryVerification.java +++ b/src/test/java/org/javawebstack/orm/test/shared/verification/QueryVerification.java @@ -1,5 +1,6 @@ package org.javawebstack.orm.test.shared.verification; +import org.javawebstack.orm.connection.pool.PooledSQL; import org.javawebstack.orm.query.Query; import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException; import org.javawebstack.orm.test.shared.util.QueryStringUtil; @@ -145,8 +146,10 @@ public String getSection(String topLevelKeyword, int sectionIndex) throws Sectio * @return The order sensitive string list of inner sections. */ public List getSectionList(String topLevelKeyword) { - return new QueryStringUtil(this.query.getRepo().getConnection().builder().buildQuery(this.query).getQuery()) - .getTopLevelSectionsByKeyword(topLevelKeyword); + try(PooledSQL sql = this.query.getRepo().getPool().get()) { + return new QueryStringUtil(sql.builder().buildQuery(this.query).getQuery()) + .getTopLevelSectionsByKeyword(topLevelKeyword); + } } private void failDueToSectionIndexOutOfBounds(SectionIndexOutOfBoundException exception) { From 467f48d1dce2ca1e88caa25593ad3b37b56d8931 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 12 Sep 2023 22:59:03 +0200 Subject: [PATCH 21/24] Added query logger --- src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java index aca777a..5575be1 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -15,6 +15,7 @@ public class SQLPool { private final Queue connectionQueue = new LinkedBlockingQueue<>(); private final Set loggers = new HashSet<>(); private boolean closing; + private PoolQueryLogger queryLogger = new PoolQueryLogger(); public SQLPool(PoolScaling scaling, Supplier supplier) { this.scaling = scaling; @@ -64,6 +65,7 @@ public void scale() { return; while (newScale > connections.size()) { SQL sql = supplier.get(); + sql.addQueryLogger(queryLogger); connections.add(sql); connectionQueue.add(sql); } From 7787b9e257bb063612e7f73843d5786799cc0a53 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Wed, 13 Sep 2023 13:57:03 +0200 Subject: [PATCH 22/24] Fixed NPE in PooledSQL --- .../javawebstack/orm/connection/pool/PooledSQL.java | 2 ++ .../javawebstack/orm/connection/pool/SQLPool.java | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java b/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java index 52c1fc7..d391e8f 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java @@ -16,6 +16,8 @@ public class PooledSQL implements SQL, AutoCloseable { private boolean closed; public PooledSQL(SQLPool pool, SQL connection) { + if(connection == null) + throw new IllegalArgumentException("connection can not be null"); this.pool = pool; this.connection = connection; } diff --git a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java index 5575be1..ccd8306 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -4,6 +4,7 @@ import org.javawebstack.orm.connection.SQL; import java.util.*; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Supplier; @@ -12,7 +13,7 @@ public class SQLPool { private final PoolScaling scaling; private final Supplier supplier; private final List connections = new ArrayList<>(); - private final Queue connectionQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue connectionQueue = new LinkedBlockingQueue<>(); private final Set loggers = new HashSet<>(); private boolean closing; private PoolQueryLogger queryLogger = new PoolQueryLogger(); @@ -31,9 +32,13 @@ public PooledSQL get() { if(closing) throw new IllegalStateException("Pool has already been closed"); scale(); - SQL sql = connectionQueue.poll(); - scale(); - return new PooledSQL(this, sql); + try { + SQL sql = connectionQueue.take(); + scale(); + return new PooledSQL(this, sql); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } public void release(SQL sql) { From 75df5202bd4ca9eac9ce64653c50afc7364eed66 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Wed, 13 Sep 2023 16:58:22 +0200 Subject: [PATCH 23/24] Support for immutable query builder --- .../java/org/javawebstack/orm/ORMConfig.java | 10 + src/main/java/org/javawebstack/orm/Repo.java | 2 +- .../org/javawebstack/orm/query/Query.java | 219 ++++++++++++------ .../javawebstack/orm/query/QueryGroup.java | 114 ++++++--- 4 files changed, 239 insertions(+), 106 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/ORMConfig.java b/src/main/java/org/javawebstack/orm/ORMConfig.java index ab88807..61043dc 100644 --- a/src/main/java/org/javawebstack/orm/ORMConfig.java +++ b/src/main/java/org/javawebstack/orm/ORMConfig.java @@ -17,6 +17,7 @@ public class ORMConfig { private final List typeMappers = new ArrayList<>(); private boolean preventUnnecessaryUpdates = true; private Injector injector; + private boolean immutableQuery = false; public ORMConfig() { typeMappers.add(new DefaultMapper()); @@ -57,6 +58,11 @@ public ORMConfig setIdAutoIncrement(boolean idAutoIncrement) { return this; } + public ORMConfig setImmutableQuery(boolean immutableQuery) { + this.immutableQuery = immutableQuery; + return this; + } + public boolean isCamelToSnakeCase() { return camelToSnakeCase; } @@ -81,6 +87,10 @@ public boolean isIdAutoIncrement() { return idAutoIncrement; } + public boolean isImmutableQuery() { + return immutableQuery; + } + public TypeMapper getTypeMapper(Class type, int size) { for (TypeMapper mapper : getTypeMappers()) { SQLType sqlType = mapper.getType(type, size); diff --git a/src/main/java/org/javawebstack/orm/Repo.java b/src/main/java/org/javawebstack/orm/Repo.java index 02413fc..fdfcc47 100644 --- a/src/main/java/org/javawebstack/orm/Repo.java +++ b/src/main/java/org/javawebstack/orm/Repo.java @@ -52,7 +52,7 @@ public QueryFilter getFilter() { } public Query query() { - return new Query<>((Class) info.getModelClass()); + return new Query<>((Class) info.getModelClass(), info.getConfig().isImmutableQuery()); } public Query where(Object left, String operator, Object right) { diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 658650d..cb35c4e 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -5,7 +5,6 @@ import org.javawebstack.orm.SQLMapper; import org.javawebstack.orm.connection.pool.PooledSQL; import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.connection.SQL; import org.javawebstack.orm.renderer.SQLQueryString; import java.sql.ResultSet; @@ -22,7 +21,7 @@ public class Query { private final Repo repo; private final Class model; private List select = new ArrayList<>(); - private final QueryGroup where = new QueryGroup<>(); + private QueryGroup where; private Integer offset; private Integer limit; private final List order = new ArrayList<>(); @@ -31,14 +30,60 @@ public class Query { private QueryGroup having; private boolean applyAccessible = false; private Object accessor; + private boolean immutable; public Query(Class model) { this(Repo.get(model), model); } + public Query(Class model, boolean immutable) { + this(Repo.get(model), model, immutable); + } + public Query(Repo repo, Class model) { + this(repo, model, false); + } + + public Query(Repo repo, Class model, boolean immutable) { this.repo = repo; this.model = model; + this.immutable = immutable; + this.where = new QueryGroup<>(immutable); + } + + public Query clone() { + Query cloned = new Query<>(repo, model, immutable); + cloned.select.addAll(select); + cloned.where = where.clone(); + cloned.offset = offset; + cloned.limit = limit; + cloned.order.addAll(order); + cloned.withDeleted = withDeleted; + cloned.groupBy.addAll(groupBy); + cloned.having = having.clone(); + cloned.applyAccessible = applyAccessible; + cloned.accessor = accessor; + return cloned; + } + + public Query mutable() { + if(!immutable) + return this; + Query cloned = clone(); + cloned.immutable = false; + return cloned; + } + + public Query immutable() { + if(immutable) + return this; + Query cloned = clone(); + cloned.immutable = true; + return cloned; + } + + public boolean isImmutable() { + return immutable; } public boolean isWithDeleted() { @@ -90,28 +135,33 @@ public Class getModel() { } public Query select(String... columns) { - this.select = Arrays.asList(columns); - return this; + Query q = immutable ? clone() : this; + q.select = Arrays.asList(columns); + return q; } public Query and(Function, QueryGroup> group) { - where.and(group); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.and(group); + return q; } public Query or(Function, QueryGroup> group) { - where.or(group); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.or(group); + return q; } public Query where(Object left, String condition, Object right) { - where.where(left, condition, right); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.where(left, condition, right); + return q; } public Query where(Object left, Object right) { - where.where(left, right); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.where(left, right); + return q; } public Query where(Class leftTable, String left, String operator, Class rightTable, String right) { @@ -146,16 +196,22 @@ public Query orWhere(Class leftTable, String left, String op return orWhere(left, operator, right); } - public QueryGroup orWhereMorph(String name, Class type) { - return where.orWhereMorph(name, type); + public Query orWhereMorph(String name, Class type) { + Query q = immutable ? clone() : this; + q.where = q.where.orWhereMorph(name, type); + return q; } - public QueryGroup orWhereMorph(String name, Class type, Object id) { - return where.orWhereMorph(name, type, id); + public Query orWhereMorph(String name, Class type, Object id) { + Query q = immutable ? clone() : this; + q.where = q.where.orWhereMorph(name, type, id); + return q; } - public QueryGroup orWhereMorph(String name, Model entity) { - return where.orWhereMorph(name, entity); + public Query orWhereMorph(String name, Model entity) { + Query q = immutable ? clone() : this; + q.where = q.where.orWhereMorph(name, entity); + return q; } public Query whereId(String operator, Object right) { @@ -192,107 +248,127 @@ public Query orWhereId(String operator, Class other, String @Deprecated public Query isNull(Object left) { - where.isNull(left); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.isNull(left); + return q; } @Deprecated public Query notNull(Object left) { - where.notNull(left); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.notNull(left); + return q; } public Query whereNull(Object left) { - where.whereNull(left); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.whereNull(left); + return q; } public Query whereNotNull(Object left) { - where.whereNotNull(left); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.whereNotNull(left); + return q; } public Query orWhere(Object left, String condition, Object right) { - where.orWhere(left, condition, right); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.orWhere(left, condition, right); + return q; } public Query orWhere(Object left, Object right) { - where.orWhere(left, right); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.orWhere(left, right); + return q; } @Deprecated public Query orIsNull(Object left) { - where.orIsNull(left); + Query q = immutable ? clone() : this; + q.where = q.where.orIsNull(left); return this; } @Deprecated public Query orNotNull(Object left) { - where.orNotNull(left); + Query q = immutable ? clone() : this; + q.where = q.where.orNotNull(left); return this; } public Query orWhereNull(Object left) { - where.orWhereNull(left); + Query q = immutable ? clone() : this; + q.where = q.where.orWhereNull(left); return this; } public Query orWhereNotNull(Object left) { - where.orWhereNotNull(left); + Query q = immutable ? clone() : this; + q.where = q.where.orWhereNotNull(left); return this; } public Query whereExists(Class model, Function, Query> consumer) { - where.whereExists(model, consumer); + Query q = immutable ? clone() : this; + q.where = q.where.whereExists(model, consumer); return this; } public Query orWhereExists(Class model, Function, Query> consumer) { - where.orWhereExists(model, consumer); + Query q = immutable ? clone() : this; + q.where = q.where.orWhereExists(model, consumer); return this; } public Query whereIn(Object left, Object... values) { - where.whereIn(left, values); + Query q = immutable ? clone() : this; + q.where = q.where.whereIn(left, values); return this; } public Query whereNotIn(Object left, Object... values) { - where.whereNotIn(left, values); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.whereNotIn(left, values); + return q; } public Query orWhereIn(Object left, Object... values) { - where.orWhereIn(left, values); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.orWhereIn(left, values); + return q; } public Query orWhereNotIn(Object left, Object... values) { - where.orWhereNotIn(left, values); + Query q = immutable ? clone() : this; + q.where = q.where.orWhereNotIn(left, values); return this; } public Query groupBy(String column) { - groupBy.add(new QueryColumn(column)); + Query q = immutable ? clone() : this; + q.groupBy.add(new QueryColumn(column)); return this; } public Query groupBy(QueryColumn column) { - groupBy.add(column); - return this; + Query q = immutable ? clone() : this; + q.groupBy.add(column); + return q; } public Query having(Consumer> consumer) { - having = new QueryGroup<>(); + Query q = immutable ? clone() : this; + q.having = new QueryGroup<>(immutable); consumer.accept(having); - return this; + return q; } public Query has(Query relation, String operator, int count) { - where.has(relation, operator, count); - return this; + Query q = immutable ? clone() : this; + q.where = q.where.has(relation, operator, count); + return q; } public Query has(Query relation) { @@ -300,23 +376,28 @@ public Query has(Query relation) { } public Query accessible(Object accessor) { - this.applyAccessible = true; - this.accessor = accessor; - return this; + Query q = immutable ? clone() : this; + q.applyAccessible = true; + q.accessor = accessor; + return q; } public Query filter(Map filter) { + Query q = immutable ? clone() : this; if (filter == null) - return this; - repo.getFilter().filter(this, filter); - return this; + return q; + q = q.mutable(); + repo.getFilter().filter(q, filter); + return immutable ? q.immutable() : q; } public Query search(String search) { + Query q = immutable ? clone() : this; if (search == null || search.length() == 0) return this; - repo.getFilter().search(this, search); - return this; + q = q.mutable(); + repo.getFilter().search(q, search); + return immutable ? q.immutable() : q; } public Query order(String columnName) throws ORMQueryException { @@ -327,9 +408,10 @@ public Query order(String columnName, boolean desc) throws ORMQueryException return order(new QueryColumn(columnName), desc); } - public Query order(QueryColumn column, boolean desc) throws ORMQueryException{ - this.order.add(new QueryOrderBy(column, desc)); - return this; + public Query order(QueryColumn column, boolean desc) throws ORMQueryException { + Query q = immutable ? clone() : this; + q.order.add(new QueryOrderBy(column, desc)); + return q; } public Query limit(int offset, int limit) { @@ -337,18 +419,21 @@ public Query limit(int offset, int limit) { } public Query limit(int limit) { - this.limit = limit; - return this; + Query q = immutable ? clone() : this; + q.limit = limit; + return q; } public Query offset(int offset) { - this.offset = offset; - return this; + Query q = immutable ? clone() : this; + q.offset = offset; + return q; } public Query withDeleted() { - withDeleted = true; - return this; + Query q = immutable ? clone() : this; + q.withDeleted = true; + return q; } public Query onlyDeleted() { @@ -383,7 +468,7 @@ public void restore() { return; Map values = new HashMap<>(); values.put(repo.getInfo().getColumnName(repo.getInfo().getSoftDeleteField()), null); - withDeleted().update(values); + clone().withDeleted().update(values); } public T refresh(T entity) { diff --git a/src/main/java/org/javawebstack/orm/query/QueryGroup.java b/src/main/java/org/javawebstack/orm/query/QueryGroup.java index 46c2288..aac4a0a 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryGroup.java +++ b/src/main/java/org/javawebstack/orm/query/QueryGroup.java @@ -23,9 +23,39 @@ public class QueryGroup implements QueryElement { private final List queryElements = new ArrayList<>(); + private boolean immutable; public QueryGroup(QueryElement... queryElements) { + this(false, queryElements); + } + + public QueryGroup(boolean immutable, QueryElement... queryElements) { this.queryElements.addAll(Arrays.asList(queryElements)); + this.immutable = immutable; + } + + public QueryGroup clone() { + return new QueryGroup(immutable, queryElements.toArray(new QueryElement[0])); + } + + public QueryGroup mutable() { + if(!this.immutable) + return this; + QueryGroup cloned = clone(); + cloned.immutable = false; + return cloned; + } + + public QueryGroup immutable() { + if(this.immutable) + return this; + QueryGroup cloned = clone(); + cloned.immutable = true; + return cloned; + } + + public boolean isImmutable() { + return immutable; } public List getQueryElements() { @@ -38,13 +68,14 @@ public QueryGroup and(Function, QueryGroup> group) { } public QueryGroup where(Function, QueryGroup> group) { + QueryGroup q = immutable ? clone() : this; QueryGroup innerGroup = group.apply(new QueryGroup<>()); if(innerGroup.queryElements.isEmpty()) - return this; - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.AND); - queryElements.add(innerGroup); - return this; + return q; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.AND); + q.queryElements.add(innerGroup); + return q; } @Deprecated @@ -53,13 +84,14 @@ public QueryGroup or(Function, QueryGroup> group) { } public QueryGroup orWhere(Function, QueryGroup> group) { + QueryGroup q = immutable ? clone() : this; QueryGroup innerGroup = group.apply(new QueryGroup<>()); if(innerGroup.queryElements.isEmpty()) - return this; - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.OR); - queryElements.add(innerGroup); - return this; + return q; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.OR); + q.queryElements.add(innerGroup); + return q; } public QueryGroup where(Object left, String condition, Object right) { @@ -82,10 +114,11 @@ else if (values[0] instanceof Stream) right = 2; } } - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.AND); - queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); - return this; + QueryGroup q = immutable ? clone() : this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.AND); + q.queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); + return q; } public QueryGroup where(Class leftTable, String left, String operator, Class rightTable, String right) { @@ -123,6 +156,7 @@ public QueryGroup whereNotNull(Object left) { } public QueryGroup orWhere(Object left, String condition, Object right) { + QueryGroup q = immutable ? clone() : this; if(condition.equalsIgnoreCase("=") && right == null) return orIsNull(left); if(condition.equalsIgnoreCase("!=") && right == null) @@ -132,10 +166,10 @@ public QueryGroup orWhere(Object left, String condition, Object right) { condition = "="; right = 2; } - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.OR); - queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); - return this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.OR); + q.queryElements.add(new QueryCondition(left instanceof String ? new QueryColumn((String) left) : left, condition, right)); + return q; } public QueryGroup orWhere(Object left, Object right) { @@ -185,35 +219,39 @@ public QueryGroup orWhereNotNull(Object left) { } public QueryGroup whereExists(Class model, Function, Query> consumer) { - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.AND); - Query query = consumer.apply(new Query<>(model).limit(1)); - queryElements.add(new QueryExists<>(query, false)); - return this; + QueryGroup q = immutable ? clone() : this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.AND); + Query query = consumer.apply(new Query<>(model, immutable).limit(1)); + q.queryElements.add(new QueryExists<>(query, false)); + return q; } public QueryGroup orWhereExists(Class model, Function, Query> consumer) { - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.OR); - Query query = consumer.apply(new Query<>(model).limit(1)); - queryElements.add(new QueryExists<>(query, false)); - return this; + QueryGroup q = immutable ? clone() : this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.OR); + Query query = consumer.apply(new Query<>(model, immutable).limit(1)); + q.queryElements.add(new QueryExists<>(query, false)); + return q; } public QueryGroup whereNotExists(Class model, Function, Query> consumer) { - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.AND); - Query query = consumer.apply(new Query<>(model).limit(1)); - queryElements.add(new QueryExists<>(query, true)); - return this; + QueryGroup q = immutable ? clone() : this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.AND); + Query query = consumer.apply(new Query<>(model, immutable).limit(1)); + q.queryElements.add(new QueryExists<>(query, true)); + return q; } public QueryGroup orWhereNotExists(Class model, Function, Query> consumer) { - if (!queryElements.isEmpty()) - queryElements.add(QueryConjunction.OR); - Query query = consumer.apply(new Query<>(model).limit(1)); - queryElements.add(new QueryExists<>(query, true)); - return this; + QueryGroup q = immutable ? clone() : this; + if (!q.queryElements.isEmpty()) + q.queryElements.add(QueryConjunction.OR); + Query query = consumer.apply(new Query<>(model, immutable).limit(1)); + q.queryElements.add(new QueryExists<>(query, true)); + return q; } public QueryGroup has(Query relation, String operator, int count) { From b2d67083ca2b16f8b4f48df1952c34221e40d7ff Mon Sep 17 00:00:00 2001 From: JanHolger Date: Mon, 11 Dec 2023 19:41:06 +0100 Subject: [PATCH 24/24] Added fix for race condition --- .../orm/connection/pool/SQLPool.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java index ccd8306..469feeb 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -68,17 +68,20 @@ public void scale() { int newScale = scaling.scale(connections.size(), connections.size() - connectionQueue.size()); if(newScale == connections.size()) return; - while (newScale > connections.size()) { - SQL sql = supplier.get(); - sql.addQueryLogger(queryLogger); - connections.add(sql); - connectionQueue.add(sql); - } - while (newScale < connections.size() && connectionQueue.size() > 0) { - SQL sql = connectionQueue.poll(); - sql.close(); - connections.remove(sql); + synchronized(this) { + while (newScale > connections.size()) { + SQL sql = supplier.get(); + sql.addQueryLogger(queryLogger); + connections.add(sql); + connectionQueue.add(sql); + } + while (newScale < connections.size() && connectionQueue.size() > 0) { + SQL sql = connectionQueue.poll(); + sql.close(); + connections.remove(sql); + } } + } public void close() {