From b077bcb945ab67f4d26380deb64e77724a5a73b3 Mon Sep 17 00:00:00 2001 From: "j.bebendorf" Date: Fri, 7 Oct 2022 22:04:50 +0200 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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 26d84e4160f64d983c49b7f5e4ae340252c5a2bd Mon Sep 17 00:00:00 2001 From: JanHolger Date: Mon, 11 Dec 2023 19:43:53 +0100 Subject: [PATCH 23/37] Added fix for race condition --- .../orm/connection/pool/SQLPool.java | 22 ++++++++++--------- 1 file changed, 12 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..dcaebb8 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -68,16 +68,18 @@ 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.isEmpty()) { + SQL sql = connectionQueue.poll(); + sql.close(); + connections.remove(sql); + } } } From bffe472e9e515d28c61ec7a785ce9bc65d9795a8 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Tue, 12 Mar 2024 18:52:35 +0100 Subject: [PATCH 24/37] Made dates fields not nullable --- .../org/javawebstack/orm/migration/AutoMigrator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index e97ab25..a0228b1 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -63,7 +63,7 @@ private static void migrateTable(SQLPool pool, TableInfo info, boolean tableExis sb.append('(') .append(parameterTypes) .append(')'); - sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); + sb.append(info.isNotNull(fieldName) || isDatesField(info, fieldName) ? " NOT NULL" : " NULL"); if (info.isAutoIncrement() && info.getIdField().equals(fieldName)) sb.append(" AUTO_INCREMENT"); if (columnKeys.containsKey(columnName)) { @@ -194,4 +194,11 @@ private static List getTables(SQLPool pool) { } } + private static boolean isDatesField(TableInfo info, String fieldName) { + if (!info.hasDates()) + return false; + + return fieldName.equals(info.getCreatedField()) || fieldName.equals(info.getUpdatedField()); + } + } From b3146d6c94a0236e3ba1a663396adc43e282375b Mon Sep 17 00:00:00 2001 From: x7airworker Date: Wed, 13 Mar 2024 18:27:52 +0100 Subject: [PATCH 25/37] Changed implementation to isNotNull method --- src/main/java/org/javawebstack/orm/TableInfo.java | 9 +++++++++ .../org/javawebstack/orm/migration/AutoMigrator.java | 9 +-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/TableInfo.java b/src/main/java/org/javawebstack/orm/TableInfo.java index b9c1c59..79471ee 100644 --- a/src/main/java/org/javawebstack/orm/TableInfo.java +++ b/src/main/java/org/javawebstack/orm/TableInfo.java @@ -289,6 +289,15 @@ public Object getDefault(String fieldName) { public boolean isNotNull(String fieldName) { if (idField.equals(fieldName)) return true; + + if (hasDates()) { + if (fieldName.equals(getCreatedField())) + return true; + if (fieldName.equals(getUpdatedField())) + return true; + } + + return false; } diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index a0228b1..4f2eb0b 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -63,7 +63,7 @@ private static void migrateTable(SQLPool pool, TableInfo info, boolean tableExis sb.append('(') .append(parameterTypes) .append(')'); - sb.append(info.isNotNull(fieldName) || isDatesField(info, fieldName) ? " NOT NULL" : " NULL"); + sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); if (info.isAutoIncrement() && info.getIdField().equals(fieldName)) sb.append(" AUTO_INCREMENT"); if (columnKeys.containsKey(columnName)) { @@ -194,11 +194,4 @@ private static List getTables(SQLPool pool) { } } - private static boolean isDatesField(TableInfo info, String fieldName) { - if (!info.hasDates()) - return false; - - return fieldName.equals(info.getCreatedField()) || fieldName.equals(info.getUpdatedField()); - } - } From d361a2fe1400422f8b30575eea7b43a2b754ceed Mon Sep 17 00:00:00 2001 From: x7airworker Date: Wed, 13 Mar 2024 18:28:25 +0100 Subject: [PATCH 26/37] Removed space --- src/main/java/org/javawebstack/orm/migration/AutoMigrator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java index 4f2eb0b..e97ab25 100644 --- a/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java +++ b/src/main/java/org/javawebstack/orm/migration/AutoMigrator.java @@ -63,7 +63,7 @@ private static void migrateTable(SQLPool pool, TableInfo info, boolean tableExis sb.append('(') .append(parameterTypes) .append(')'); - sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); + sb.append(info.isNotNull(fieldName) ? " NOT NULL" : " NULL"); if (info.isAutoIncrement() && info.getIdField().equals(fieldName)) sb.append(" AUTO_INCREMENT"); if (columnKeys.containsKey(columnName)) { From 831d5dc1a634ecba9cd5a527966f181e25004422 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 24 May 2024 14:24:07 +0200 Subject: [PATCH 27/37] Fixed IllegalArgumentException --- .../java/org/javawebstack/orm/connection/pool/SQLPool.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 dcaebb8..d5052f4 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -47,9 +47,8 @@ public void release(SQL sql) { sql.close(); return; } - if(!connections.contains(sql)) - throw new IllegalArgumentException("Not a connection of this pool"); - connectionQueue.add(sql); + if(!connectionQueue.contains(sql)) + connectionQueue.add(sql); scale(); } } From 27b8cf8991e4e1b5831058ea38164eee94bc2a63 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 24 May 2024 14:37:00 +0200 Subject: [PATCH 28/37] Updated gpg key name --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3de70e..71f6bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,7 @@ sign - A313520526A8DFE1C2A30399C35A3D43C557B112 + 6802C125C9069802B97A080910CE6063CEE9C47B gpg --no-tty From f6b3a7758b13d698953c1baa65ba6429b022dcb8 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 24 May 2024 14:39:44 +0200 Subject: [PATCH 29/37] Fixed double closing of connections --- .../java/org/javawebstack/orm/connection/pool/PooledSQL.java | 3 +++ 1 file changed, 3 insertions(+) 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 d391e8f..8e4dd44 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/PooledSQL.java @@ -48,6 +48,9 @@ public void close(ResultSet resultSet) { } public void close() { + if (closed) + return; + pool.release(connection); closed = true; } From 799658e1f3d66b72c563750014d1a1e6f9f15213 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 24 May 2024 15:11:55 +0200 Subject: [PATCH 30/37] Added null-check in release --- .../java/org/javawebstack/orm/connection/pool/SQLPool.java | 3 +++ 1 file changed, 3 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 d5052f4..fda3991 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -42,6 +42,9 @@ public PooledSQL get() { } public void release(SQL sql) { + if(sql == null) { + throw new IllegalArgumentException("sql must not be null"); + } if(!closing) { if(sql instanceof PooledSQL) { sql.close(); From cf46fe58d839a674d061dce68a75ea897c749b91 Mon Sep 17 00:00:00 2001 From: x7airworker Date: Wed, 10 Jul 2024 13:43:38 +0200 Subject: [PATCH 31/37] Implemented whereNotExists and orWhereNotExists on Query --- src/main/java/org/javawebstack/orm/query/Query.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 658650d..4e9cb10 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -249,11 +249,21 @@ public Query whereExists(Class model, Function, return this; } + public Query whereNotExists(Class model, Function, Query> consumer) { + where.whereNotExists(model, consumer); + return this; + } + public Query orWhereExists(Class model, Function, Query> consumer) { where.orWhereExists(model, consumer); return this; } + public Query orWhereNotExists(Class model, Function, Query> consumer) { + where.orWhereNotExists(model, consumer); + return this; + } + public Query whereIn(Object left, Object... values) { where.whereIn(left, values); return this; From 21aaf6de1e4757ec17ef9c94bada8943e51e969f Mon Sep 17 00:00:00 2001 From: x7airworker Date: Fri, 12 Jul 2024 14:52:47 +0200 Subject: [PATCH 32/37] Replaced hard coded username with variable --- build/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/settings.xml b/build/settings.xml index 07e7726..20e240a 100644 --- a/build/settings.xml +++ b/build/settings.xml @@ -16,7 +16,7 @@ ossrh - JavaWebStack + ${env.OSSRH_USERNAME} ${env.OSSRH_PASSWORD} From 499333a087dd3ba395320dd87858fd2395cae3e4 Mon Sep 17 00:00:00 2001 From: Jake Esser Date: Mon, 29 Jul 2024 11:48:24 +0200 Subject: [PATCH 33/37] Passed username to Maven --- .github/workflows/maven-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 59d57de..aac76eb 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -43,4 +43,5 @@ jobs: DEPLOYMENT_USERNAME: ${{ secrets.DEPLOYMENT_USERNAME }} DEPLOYMENT_PASSWORD: ${{ secrets.DEPLOYMENT_PASSWORD }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} From 620fbd6deadeef94b45c809fd153d362a838ac67 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 11 Jul 2025 15:47:11 +0200 Subject: [PATCH 34/37] Fix connection pool issue --- .../org/javawebstack/orm/connection/pool/SQLPool.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 fda3991..417e9e2 100644 --- a/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java +++ b/src/main/java/org/javawebstack/orm/connection/pool/SQLPool.java @@ -50,8 +50,10 @@ public void release(SQL sql) { sql.close(); return; } - if(!connectionQueue.contains(sql)) - connectionQueue.add(sql); + synchronized (connectionQueue) { + if(!connectionQueue.contains(sql)) + connectionQueue.add(sql); + } scale(); } } @@ -70,7 +72,7 @@ public void scale() { int newScale = scaling.scale(connections.size(), connections.size() - connectionQueue.size()); if(newScale == connections.size()) return; - synchronized (this) { + synchronized (connectionQueue) { while (newScale > connections.size()) { SQL sql = supplier.get(); sql.addQueryLogger(queryLogger); From 2df36e8fe28f8650bf3a54a42556e0b4b45d6613 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Fri, 11 Jul 2025 16:02:38 +0200 Subject: [PATCH 35/37] Started to implement support for DuckDB --- pom.xml | 6 ++ .../javawebstack/orm/connection/DuckDB.java | 93 +++++++++++++++++++ .../javawebstack/orm/connection/MySQL.java | 17 +--- 3 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/javawebstack/orm/connection/DuckDB.java diff --git a/pom.xml b/pom.xml index 71f6bb5..4736006 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,12 @@ 8.0.33 test + + org.duckdb + duckdb_jdbc + 1.3.2.0 + test + diff --git a/src/main/java/org/javawebstack/orm/connection/DuckDB.java b/src/main/java/org/javawebstack/orm/connection/DuckDB.java new file mode 100644 index 0000000..03b3696 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/connection/DuckDB.java @@ -0,0 +1,93 @@ +package org.javawebstack.orm.connection; + +import org.javawebstack.orm.exception.ORMQueryException; +import org.javawebstack.orm.renderer.MySQLQueryStringRenderer; +import org.javawebstack.orm.renderer.QueryStringRenderer; + +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.Properties; +import java.util.stream.Collectors; + +public class DuckDB extends BaseSQL { + + private Connection c = null; + private final String path; + private final boolean readOnly; + private Map customParams = new HashMap<>(); + + public DuckDB() { + this(null); + } + + public DuckDB(String path) { + this(path, false); + } + + public DuckDB(String path, boolean readOnly) { + this.path = path; + this.readOnly = readOnly; + } + + public DuckDB setCustomParam(String key, String value) { + customParams.put(key, value); + return this; + } + + public SQL fork() { + if(!readOnly) { + throw new ORMQueryException("Forking is only supported for read-only connections"); + } + DuckDB sql = new DuckDB(path, readOnly); + sql.customParams = customParams; + return sql; + } + + public Connection getConnection() { + try { + if (c == null || c.isClosed()) { + try { + Class.forName("org.duckdb.DuckDBDriver"); + Properties params = new Properties(); + params.put("duckdb.read_only", this.readOnly ? "true" : "false"); + params.putAll(customParams); + c = DriverManager.getConnection("jdbc:duckdb:" + (this.path != null ? this.path : ""), params); + } catch (SQLException e) { + System.out.println("Error: at getConnection()[DuckDB.java] SQLException " + e.getMessage()); + } catch (ClassNotFoundException e) { + System.out.println("Error: at getConnection()[DuckDB.java] ClassNotFoundException"); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + try { + if (c == null || c.isClosed()) + throw new ORMQueryException("Connection failed!"); + } catch (SQLException e) { + e.printStackTrace(); + } + return c; + } + + public void close() { + if(c != null) { + try { + if(!c.isClosed()) + c.close(); + } catch (SQLException ignored) {} + c = null; + } + } + + public QueryStringRenderer builder() { + return MySQLQueryStringRenderer.INSTANCE; + } + +} + diff --git a/src/main/java/org/javawebstack/orm/connection/MySQL.java b/src/main/java/org/javawebstack/orm/connection/MySQL.java index f311ac6..a7b0266 100644 --- a/src/main/java/org/javawebstack/orm/connection/MySQL.java +++ b/src/main/java/org/javawebstack/orm/connection/MySQL.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; public class MySQL extends BaseSQL { @@ -65,7 +66,7 @@ public Connection getConnection() { if (c == null || c.isClosed()) { try { Class.forName("com.mysql.cj.jdbc.Driver"); - Map params = new HashMap<>(); + Properties params = new Properties(); params.put("user", this.username); params.put("password", this.password); params.put("autoReconnect", "true"); @@ -74,7 +75,7 @@ public Connection getConnection() { 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)); + c = DriverManager.getConnection("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database, params); } catch (SQLException e) { System.out.println("Error: at getConnection()[MySQL.java] SQLException " + e.getMessage()); } catch (ClassNotFoundException e) { @@ -103,18 +104,6 @@ public void close() { } } - 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 QueryStringRenderer builder() { return MySQLQueryStringRenderer.INSTANCE; } From 0c0618f0ddc69251a553edc1a7f00dd1c4bb8b74 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Wed, 4 Feb 2026 16:46:21 +0100 Subject: [PATCH 36/37] Switch from OSSRH to new central --- .github/workflows/maven-deploy-release.yml | 5 ++-- .github/workflows/maven-deploy.yml | 6 ++-- build/settings.xml | 16 ++-------- pom.xml | 34 +++++++++------------- 4 files changed, 20 insertions(+), 41 deletions(-) diff --git a/.github/workflows/maven-deploy-release.yml b/.github/workflows/maven-deploy-release.yml index ed702c9..946804f 100644 --- a/.github/workflows/maven-deploy-release.yml +++ b/.github/workflows/maven-deploy-release.yml @@ -39,7 +39,6 @@ jobs: - name: Deploy to JavaWebStack Repository run: mvn deploy -B -DbuildVersion=${{ github.event.release.tag_name }} -s build/settings.xml -Dmaven.test.skip=true env: - DEPLOYMENT_USERNAME: ${{ secrets.DEPLOYMENT_USERNAME }} - DEPLOYMENT_PASSWORD: ${{ secrets.DEPLOYMENT_PASSWORD }} + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index aac76eb..707ac7b 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -40,8 +40,6 @@ jobs: - name: Deploy to JavaWebStack Repository run: mvn deploy -B -s build/settings.xml -Dmaven.test.skip=true env: - DEPLOYMENT_USERNAME: ${{ secrets.DEPLOYMENT_USERNAME }} - DEPLOYMENT_PASSWORD: ${{ secrets.DEPLOYMENT_PASSWORD }} + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} - OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} diff --git a/build/settings.xml b/build/settings.xml index 20e240a..88571fa 100644 --- a/build/settings.xml +++ b/build/settings.xml @@ -5,19 +5,9 @@ > - javawebstack-snapshots - ${env.DEPLOYMENT_USERNAME} - ${env.DEPLOYMENT_PASSWORD} - - - javawebstack-releases - ${env.DEPLOYMENT_USERNAME} - ${env.DEPLOYMENT_PASSWORD} - - - ossrh - ${env.OSSRH_USERNAME} - ${env.OSSRH_PASSWORD} + central + ${env.CENTRAL_USERNAME} + ${env.CENTRAL_PASSWORD} gpg diff --git a/pom.xml b/pom.xml index 4736006..28d6f36 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,8 @@ - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots + central-snapshots + https://central.sonatype.com/repository/maven-snapshots/ @@ -68,7 +68,7 @@ org.javawebstack abstract-data - 1.0.6 + 1.0.7-SNAPSHOT org.atteo @@ -115,17 +115,13 @@ - maven-deploy-plugin - 3.0.0-M1 - - - default-deploy - deploy - - deploy - - - + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 + true + + central + org.apache.maven.plugins @@ -170,7 +166,7 @@ sign - 6802C125C9069802B97A080910CE6063CEE9C47B + EC9CCFF8901F0AA22191DCEDD619376246C066D0 gpg --no-tty @@ -186,13 +182,9 @@ - ossrh - https://s01.oss.sonatype.org/content/repositories/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ - - ossrh - https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - From 881db6e48201cb42d7c2b5fb7b792b49624d2a2b Mon Sep 17 00:00:00 2001 From: Julian Gojani Date: Sat, 30 May 2026 22:22:30 +0200 Subject: [PATCH 37/37] Update pom.xml