From b527ee280a326b45be0e6d792dbd5316593eb202 Mon Sep 17 00:00:00 2001 From: Timothy Gillespie Date: Sun, 21 Mar 2021 10:42:12 +0100 Subject: [PATCH 01/45] Add ModelSetup to unify setup codes --- .../orm/test/shared/setup/ModelSetup.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/java/org/javawebstack/orm/test/shared/setup/ModelSetup.java diff --git a/src/test/java/org/javawebstack/orm/test/shared/setup/ModelSetup.java b/src/test/java/org/javawebstack/orm/test/shared/setup/ModelSetup.java new file mode 100644 index 0000000..563fbc4 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/setup/ModelSetup.java @@ -0,0 +1,22 @@ +package org.javawebstack.orm.test.shared.setup; + +import org.javawebstack.orm.Model; +import org.javawebstack.orm.ORM; +import org.javawebstack.orm.Repo; +import org.javawebstack.orm.exception.ORMConfigurationException; +import org.javawebstack.orm.test.shared.settings.MySQLConnectionContainer; + +public class ModelSetup extends MySQLConnectionContainer { + + public static Repo setUpModel(Class clazz) { + + // Converting to Runtime exception to avoid having to declare the thrown error which has no utility + try { + ORM.register(clazz, sql()); + } catch (ORMConfigurationException e) { + throw new RuntimeException(e); + } + + return Repo.get(clazz); + } +} From e2fc84bf00bc0d5adb386cc1aa687c57e1e4920e Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 17:17:11 +0200 Subject: [PATCH 02/45] Abstracted away QueryStringBuilder from query tree to allow implementing support for other dialects --- src/main/java/org/javawebstack/orm/Repo.java | 19 +- .../org/javawebstack/orm/query/Query.java | 134 ++++--------- .../orm/query/QueryCondition.java | 39 +--- .../orm/query/QueryConjunction.java | 8 +- .../javawebstack/orm/query/QueryElement.java | 4 - .../javawebstack/orm/query/QueryExists.java | 10 +- .../javawebstack/orm/query/QueryGroup.java | 15 +- .../org/javawebstack/orm/wrapper/MySQL.java | 6 + .../org/javawebstack/orm/wrapper/SQL.java | 4 + .../org/javawebstack/orm/wrapper/SQLite.java | 7 + .../builder/MySQLQueryStringBuilder.java | 179 ++++++++++++++++++ .../wrapper/builder/QueryStringBuilder.java | 15 ++ .../builder/SQLQueryString.java} | 10 +- 13 files changed, 271 insertions(+), 179 deletions(-) create mode 100644 src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java create mode 100644 src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java rename src/main/java/org/javawebstack/orm/{query/QueryString.java => wrapper/builder/SQLQueryString.java} (66%) diff --git a/src/main/java/org/javawebstack/orm/Repo.java b/src/main/java/org/javawebstack/orm/Repo.java index d05687f..d86859e 100644 --- a/src/main/java/org/javawebstack/orm/Repo.java +++ b/src/main/java/org/javawebstack/orm/Repo.java @@ -7,6 +7,7 @@ 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 java.lang.reflect.Field; import java.sql.SQLException; @@ -124,28 +125,14 @@ private void executeCreate(T entry) { if (field.get(entry) == null) field.set(entry, UUID.randomUUID()); } - List params = new ArrayList<>(); - StringBuilder sb = new StringBuilder("INSERT INTO `"); - sb.append(info.getTableName()); - sb.append("` ("); - List cols = new ArrayList<>(); - List values = new ArrayList<>(); Map map = SQLMapper.map(this, entry); if (info.isAutoIncrement()) { String idCol = info.getColumnName(info.getIdField()); if (map.containsKey(idCol) && map.get(idCol) == null) map.remove(idCol); } - for (String columnName : map.keySet()) { - cols.add("`" + columnName + "`"); - values.add("?"); - params.add(map.get(columnName)); - } - sb.append(String.join(",", cols)); - sb.append(") VALUES ("); - sb.append(String.join(",", values)); - sb.append(");"); - int id = connection.write(sb.toString(), params.toArray()); + SQLQueryString qs = getConnection().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); diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 54674b9..82f4705 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -4,6 +4,7 @@ import org.javawebstack.orm.Repo; import org.javawebstack.orm.SQLMapper; import org.javawebstack.orm.exception.ORMQueryException; +import org.javawebstack.orm.wrapper.builder.SQLQueryString; import java.sql.ResultSet; import java.sql.SQLException; @@ -26,7 +27,6 @@ public class Query { private QueryColumn order; private boolean desc = false; private boolean withDeleted = false; - private final Map, QueryCondition> leftJoins = new HashMap<>(); public Query(Class model) { this(Repo.get(model), model); @@ -38,13 +38,36 @@ public Query(Repo repo, Class model) { this.where = new QueryGroup<>(); } - public Class getModel() { - return model; + public boolean isWithDeleted() { + return withDeleted; } - public Query leftJoin(Class model, String self, String other) { - leftJoins.put(model, new QueryCondition(new QueryColumn(repo.getInfo().getTableName() + "." + self), "=", new QueryColumn(Repo.get(model).getInfo().getTableName() + "." + other))); - return this; + public QueryGroup getWhereGroup() { + return where; + } + + public Integer getLimit() { + return limit; + } + + public Integer getOffset() { + return offset; + } + + public QueryColumn getOrder() { + return order; + } + + public boolean isDescOrder() { + return desc; + } + + public Repo getRepo() { + return repo; + } + + public Class getModel() { + return model; } public Query and(Function, QueryGroup> group) { @@ -286,59 +309,10 @@ public Query withDeleted() { return this; } - public QueryString getQueryString() { - return getQueryString(false); - } - - public QueryString getQueryString(boolean count) { - List parameters = new ArrayList<>(); - StringBuilder sb = new StringBuilder("SELECT ") - .append(count ? "COUNT(*)" : "*") - .append(" FROM `") - .append(repo.getInfo().getTableName()) - .append('`'); - for (Class type : leftJoins.keySet()) { - sb.append(" LEFT JOIN `") - .append(Repo.get(type).getInfo().getTableName()) - .append("` ON ") - .append(leftJoins.get(type).getQueryString(repo.getInfo()).getQuery()); - } - considerSoftDelete(); - if (where.getQueryElements().size() > 0) { - QueryString qs = where.getQueryString(repo.getInfo()); - sb.append(" WHERE ").append(qs.getQuery()); - parameters.addAll(qs.getParameters()); - } - if (order != null) { - sb.append(" ORDER BY ").append(order.toString(repo.getInfo())); - if (desc) - sb.append(" DESC"); - } - if (offset != null && limit == null) - limit = Integer.MAX_VALUE; - if (limit != null) { - sb.append(" LIMIT ?"); - if (offset != null) { - sb.append(",?"); - parameters.add(offset); - } - parameters.add(limit); - } - return new QueryString(sb.toString(), SQLMapper.mapParams(repo, parameters)); - } - public void finalDelete() { - List parameters = new ArrayList<>(); - StringBuilder sb = new StringBuilder("DELETE FROM `") - .append(repo.getInfo().getTableName()) - .append('`'); - if (where.getQueryElements().size() > 0) { - QueryString qs = where.getQueryString(repo.getInfo()); - sb.append(" WHERE ").append(qs.getQuery()); - parameters = qs.getParameters(); - } + SQLQueryString qs = repo.getConnection().builder().buildDelete(this); try { - repo.getConnection().write(sb.toString(), SQLMapper.mapParams(repo, parameters).toArray()); + repo.getConnection().write(qs.getQuery(), qs.getParameters().toArray()); } catch (SQLException throwables) { throw new ORMQueryException(throwables); } @@ -364,18 +338,10 @@ public void restore() { withDeleted().update(values); } - private void considerSoftDelete() { - if (repo.getInfo().isSoftDelete() && !withDeleted) { - if (where.getQueryElements().size() > 0) - where.getQueryElements().add(0, QueryConjunction.AND); - where.getQueryElements().add(0, new QueryCondition(new QueryColumn(repo.getInfo().getColumnName(repo.getInfo().getSoftDeleteField())), "IS NULL", null)); - } - } - public T refresh(T entity) { - QueryString qs = getQueryString(false); + SQLQueryString qs = repo.getConnection().builder().buildQuery(this, false); try { - ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, SQLMapper.mapParams(repo, qs.getParameters())).toArray()); + ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray()); SQLMapper.mapBack(repo, rs, entity); repo.getConnection().close(rs); return entity; @@ -389,39 +355,19 @@ public void update(T entity) { } public void update(Map values) { - if (repo.getInfo().hasUpdated()) - values.put(repo.getInfo().getColumnName(repo.getInfo().getUpdatedField()), Timestamp.from(Instant.now())); - List parameters = new ArrayList<>(); - List sets = new ArrayList<>(); - values.forEach((key, value) -> { - sets.add("`" + key + "`=?"); - parameters.add(value); - }); - StringBuilder sb = new StringBuilder("UPDATE `") - .append(repo.getInfo().getTableName()) - .append("` SET ") - .append(String.join(",", sets)); - considerSoftDelete(); - if (where.getQueryElements().size() > 0) { - QueryString qs = where.getQueryString(repo.getInfo()); - sb.append(" WHERE ").append(qs.getQuery()); - parameters.addAll(qs.getParameters()); - } - sb.append(';'); + SQLQueryString queryString = repo.getConnection().builder().buildUpdate(this, values); try { - repo.getConnection().write(sb.toString(), SQLMapper.mapParams(repo, parameters).toArray()); + repo.getConnection().write(queryString.getQuery(), queryString.getParameters().toArray()); } catch (SQLException throwables) { throw new ORMQueryException(throwables); } } public List all() { - QueryString qs = getQueryString(false); + SQLQueryString qs = repo.getConnection().builder().buildQuery(this, false); try { - ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, qs.getParameters()).toArray()); - List> joinedModels = new ArrayList<>(); - joinedModels.addAll(leftJoins.keySet()); - List list = SQLMapper.map(repo, rs, joinedModels); + ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray()); + List list = SQLMapper.map(repo, rs, new ArrayList<>()); repo.getConnection().close(rs); return list; } catch (SQLException throwables) { @@ -445,9 +391,9 @@ public Stream stream() { } public int count() { - QueryString qs = getQueryString(true); + SQLQueryString qs = repo.getConnection().builder().buildQuery(this, true); try { - ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, qs.getParameters()).toArray()); + ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray()); int c = 0; if (rs.next()) c = rs.getInt(1); diff --git a/src/main/java/org/javawebstack/orm/query/QueryCondition.java b/src/main/java/org/javawebstack/orm/query/QueryCondition.java index c547452..1c2e587 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryCondition.java +++ b/src/main/java/org/javawebstack/orm/query/QueryCondition.java @@ -1,13 +1,5 @@ package org.javawebstack.orm.query; -import org.javawebstack.orm.TableInfo; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - public class QueryCondition implements QueryElement { private final Object left; @@ -38,37 +30,12 @@ public String getOperator() { return operator; } - private boolean hasRight() { + public boolean hasRight() { return !(operator.equalsIgnoreCase("IS NULL") || operator.equalsIgnoreCase("IS NOT NULL")); } - public QueryString getQueryString(TableInfo info) { - StringBuilder sb = new StringBuilder(); - if (not) - sb.append("NOT "); - List parameters = new ArrayList<>(); - if (left instanceof QueryColumn) { - sb.append(((QueryColumn) left).toString(info)); - } else { - sb.append('?'); - parameters.add(left); - } - sb.append(' '); - sb.append(operator); - if (hasRight()) { - sb.append(' '); - if (operator.endsWith("IN")) { - Object[] values = (Object[]) right; - sb.append("(").append(IntStream.range(0, values.length).mapToObj(i -> "?").collect(Collectors.joining(","))).append(")"); - parameters.addAll(Arrays.asList(values)); - } else if (right instanceof QueryColumn) { - sb.append(((QueryColumn) right).toString(info)); - } else { - sb.append('?'); - parameters.add(right); - } - } - return new QueryString(sb.toString(), parameters); + public boolean isNot() { + return not; } } diff --git a/src/main/java/org/javawebstack/orm/query/QueryConjunction.java b/src/main/java/org/javawebstack/orm/query/QueryConjunction.java index 5b4b1da..4edb308 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryConjunction.java +++ b/src/main/java/org/javawebstack/orm/query/QueryConjunction.java @@ -1,13 +1,7 @@ package org.javawebstack.orm.query; -import org.javawebstack.orm.TableInfo; - public enum QueryConjunction implements QueryElement { AND, OR, - XOR; - - public QueryString getQueryString(TableInfo info) { - return new QueryString(name()); - } + XOR } diff --git a/src/main/java/org/javawebstack/orm/query/QueryElement.java b/src/main/java/org/javawebstack/orm/query/QueryElement.java index 8b81bbe..8b960ff 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryElement.java +++ b/src/main/java/org/javawebstack/orm/query/QueryElement.java @@ -1,9 +1,5 @@ package org.javawebstack.orm.query; -import org.javawebstack.orm.TableInfo; - public interface QueryElement { - QueryString getQueryString(TableInfo info); - } diff --git a/src/main/java/org/javawebstack/orm/query/QueryExists.java b/src/main/java/org/javawebstack/orm/query/QueryExists.java index ad1529d..a175f51 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryExists.java +++ b/src/main/java/org/javawebstack/orm/query/QueryExists.java @@ -2,6 +2,7 @@ import org.javawebstack.orm.Model; import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.wrapper.builder.SQLQueryString; public class QueryExists implements QueryElement { @@ -13,9 +14,12 @@ public QueryExists(Query query, boolean not) { this.not = not; } - public QueryString getQueryString(TableInfo info) { - QueryString qs = query.getQueryString(); - return new QueryString((not ? "NOT " : "") + "EXISTS (" + qs.getQuery() + ")", qs.getParameters()); + public Query getQuery() { + return query; + } + + public boolean isNot() { + return not; } } diff --git a/src/main/java/org/javawebstack/orm/query/QueryGroup.java b/src/main/java/org/javawebstack/orm/query/QueryGroup.java index ebcd89c..1605fee 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryGroup.java +++ b/src/main/java/org/javawebstack/orm/query/QueryGroup.java @@ -3,6 +3,7 @@ import org.javawebstack.orm.Model; import org.javawebstack.orm.Repo; import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.wrapper.builder.SQLQueryString; import java.util.ArrayList; import java.util.Arrays; @@ -187,18 +188,4 @@ public QueryGroup orWhereNotIn(Object left, Object... values) { return orWhere(left, "NOT IN", values); } - public QueryString getQueryString(TableInfo info) { - StringBuilder sb = new StringBuilder("("); - List parameters = new ArrayList<>(); - for (QueryElement element : queryElements) { - if (sb.length() > 1) - sb.append(' '); - QueryString s = element.getQueryString(info); - sb.append(s.getQuery()); - parameters.addAll(s.getParameters()); - } - sb.append(')'); - return new QueryString(sb.toString(), parameters); - } - } diff --git a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java b/src/main/java/org/javawebstack/orm/wrapper/MySQL.java index c933b0a..21dda73 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/MySQL.java +++ b/src/main/java/org/javawebstack/orm/wrapper/MySQL.java @@ -1,6 +1,8 @@ package org.javawebstack.orm.wrapper; import org.javawebstack.orm.exception.ORMQueryException; +import org.javawebstack.orm.wrapper.builder.MySQLQueryStringBuilder; +import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; import java.sql.Connection; import java.sql.DriverManager; @@ -65,6 +67,10 @@ public Connection getConnection() { return c; } + public QueryStringBuilder builder() { + return MySQLQueryStringBuilder.INSTANCE; + } + } diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQL.java b/src/main/java/org/javawebstack/orm/wrapper/SQL.java index 5288dbf..04b37ad 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQL.java +++ b/src/main/java/org/javawebstack/orm/wrapper/SQL.java @@ -1,5 +1,7 @@ package org.javawebstack.orm.wrapper; +import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; + import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -14,6 +16,8 @@ public interface SQL { void close(ResultSet resultSet); + QueryStringBuilder builder(); + void addQueryLogger(QueryLogger logger); void removeQueryLogger(QueryLogger logger); diff --git a/src/main/java/org/javawebstack/orm/wrapper/SQLite.java b/src/main/java/org/javawebstack/orm/wrapper/SQLite.java index 9234b5f..1ae7a40 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/SQLite.java +++ b/src/main/java/org/javawebstack/orm/wrapper/SQLite.java @@ -1,5 +1,8 @@ package org.javawebstack.orm.wrapper; +import org.javawebstack.orm.wrapper.builder.MySQLQueryStringBuilder; +import org.javawebstack.orm.wrapper.builder.QueryStringBuilder; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -35,6 +38,10 @@ public Connection getConnection() { return c; } + public QueryStringBuilder builder() { + return MySQLQueryStringBuilder.INSTANCE; // TODO Build a custom one for SQLite + } + } diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java new file mode 100644 index 0000000..bfc1771 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -0,0 +1,179 @@ +package org.javawebstack.orm.wrapper.builder; + +import org.javawebstack.orm.Repo; +import org.javawebstack.orm.SQLMapper; +import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.query.*; + +import java.lang.reflect.Field; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class MySQLQueryStringBuilder implements QueryStringBuilder { + + public static final MySQLQueryStringBuilder INSTANCE = new MySQLQueryStringBuilder(); + + public SQLQueryString buildInsert(TableInfo info, Map values) { + List params = new ArrayList<>(); + StringBuilder sb = new StringBuilder("INSERT INTO `"); + sb.append(info.getTableName()); + sb.append("` ("); + List cols = new ArrayList<>(); + List vals = new ArrayList<>(); + for (String columnName : values.keySet()) { + cols.add("`" + columnName + "`"); + vals.add("?"); + params.add(values.get(columnName)); + } + sb.append(String.join(",", cols)); + sb.append(") VALUES ("); + sb.append(String.join(",", vals)); + sb.append(");"); + return new SQLQueryString(sb.toString(), params); + } + + public SQLQueryString buildQuery(Query query, boolean count) { + Repo repo = query.getRepo(); + List parameters = new ArrayList<>(); + StringBuilder sb = new StringBuilder("SELECT ") + .append(count ? "COUNT(*)" : "*") + .append(" FROM `") + .append(repo.getInfo().getTableName()) + .append('`'); + QueryGroup where = query.getWhereGroup(); + checkWithDeleted(repo, query.isWithDeleted(), where); + if (where.getQueryElements().size() > 0) { + SQLQueryString qs = convertGroup(repo.getInfo(), where); + sb.append(" WHERE ").append(qs.getQuery()); + parameters.addAll(qs.getParameters()); + } + if (query.getOrder() != null) { + sb.append(" ORDER BY ").append(query.getOrder().toString(repo.getInfo())); + if (query.isDescOrder()) + sb.append(" DESC"); + } + Integer offset = query.getOffset(); + Integer limit = query.getLimit(); + if (offset != null && limit == null) + limit = Integer.MAX_VALUE; + if (limit != null) { + sb.append(" LIMIT ?"); + if (offset != null) { + sb.append(",?"); + parameters.add(offset); + } + parameters.add(limit); + } + return new SQLQueryString(sb.toString(), SQLMapper.mapParams(repo, parameters)); + } + + public SQLQueryString buildUpdate(Query query, Map values) { + Repo repo = query.getRepo(); + if (repo.getInfo().hasUpdated()) + values.put(repo.getInfo().getColumnName(repo.getInfo().getUpdatedField()), Timestamp.from(Instant.now())); + List parameters = new ArrayList<>(); + List sets = new ArrayList<>(); + values.forEach((key, value) -> { + sets.add("`" + key + "`=?"); + parameters.add(value); + }); + StringBuilder sb = new StringBuilder("UPDATE `") + .append(repo.getInfo().getTableName()) + .append("` SET ") + .append(String.join(",", sets)); + QueryGroup where = query.getWhereGroup(); + checkWithDeleted(repo, query.isWithDeleted(), where); + if (where.getQueryElements().size() > 0) { + SQLQueryString qs = convertGroup(repo.getInfo(), where); + sb.append(" WHERE ").append(qs.getQuery()); + parameters.addAll(qs.getParameters()); + } + sb.append(';'); + return new SQLQueryString(sb.toString(), SQLMapper.mapParams(repo, parameters).toArray()); + } + + public SQLQueryString buildDelete(Query query) { + Repo repo = query.getRepo(); + QueryGroup where = query.getWhereGroup(); + List parameters = new ArrayList<>(); + StringBuilder sb = new StringBuilder("DELETE FROM `") + .append(repo.getInfo().getTableName()) + .append('`'); + if (where.getQueryElements().size() > 0) { + SQLQueryString qs = convertGroup(repo.getInfo(), where); + sb.append(" WHERE ").append(qs.getQuery()); + parameters = qs.getParameters(); + } + return new SQLQueryString(sb.toString(), SQLMapper.mapParams(repo, parameters)); + } + + private void checkWithDeleted(Repo repo, boolean withDeleted, QueryGroup where) { + if (repo.getInfo().isSoftDelete() && !withDeleted) { + if (where.getQueryElements().size() > 0) + where.getQueryElements().add(0, QueryConjunction.AND); + where.getQueryElements().add(0, new QueryCondition(new QueryColumn(repo.getInfo().getColumnName(repo.getInfo().getSoftDeleteField())), "IS NULL", null)); + } + } + + private SQLQueryString convertElement(TableInfo info, QueryElement element) { + if(element instanceof QueryCondition) + return convertCondition(info, (QueryCondition) element); + if(element instanceof QueryConjunction) + return new SQLQueryString(((QueryConjunction) element).name()); + if(element instanceof QueryExists) { + QueryExists queryExists = (QueryExists) element; + SQLQueryString qs = buildQuery(queryExists.getQuery(), false); + return new SQLQueryString((queryExists.isNot() ? "NOT " : "") + "EXISTS (" + qs.getQuery() + ")", qs.getParameters()); + } + if(element instanceof QueryGroup) + return convertGroup(info, (QueryGroup) element); + return null; + } + + private SQLQueryString convertGroup(TableInfo info, QueryGroup group) { + StringBuilder sb = new StringBuilder("("); + List parameters = new ArrayList<>(); + for (QueryElement element : group.getQueryElements()) { + if (sb.length() > 1) + sb.append(' '); + SQLQueryString s = convertElement(info, element); + sb.append(s.getQuery()); + parameters.addAll(s.getParameters()); + } + sb.append(')'); + return new SQLQueryString(sb.toString(), parameters); + } + + private SQLQueryString convertCondition(TableInfo info, QueryCondition condition) { + StringBuilder sb = new StringBuilder(); + if (condition.isNot()) + sb.append("NOT "); + List parameters = new ArrayList<>(); + if (condition.getLeft() instanceof QueryColumn) { + sb.append(((QueryColumn) condition.getLeft()).toString(info)); + } else { + sb.append('?'); + parameters.add(condition.getLeft()); + } + sb.append(' '); + sb.append(condition.getOperator()); + if (condition.hasRight()) { + sb.append(' '); + if (condition.getOperator().endsWith("IN")) { + Object[] values = (Object[]) condition.getRight(); + 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) { + sb.append(((QueryColumn) condition.getRight()).toString(info)); + } else { + sb.append('?'); + parameters.add(condition.getRight()); + } + } + return new SQLQueryString(sb.toString(), parameters); + } + +} diff --git a/src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java b/src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java new file mode 100644 index 0000000..46e93c5 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/QueryStringBuilder.java @@ -0,0 +1,15 @@ +package org.javawebstack.orm.wrapper.builder; + +import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.query.*; + +import java.util.Map; + +public interface QueryStringBuilder { + + SQLQueryString buildInsert(TableInfo info, Map values); + SQLQueryString buildQuery(Query query, boolean count); + SQLQueryString buildUpdate(Query query, Map values); + SQLQueryString buildDelete(Query query); + +} diff --git a/src/main/java/org/javawebstack/orm/query/QueryString.java b/src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java similarity index 66% rename from src/main/java/org/javawebstack/orm/query/QueryString.java rename to src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java index 069a17a..3c60a42 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryString.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/SQLQueryString.java @@ -1,24 +1,24 @@ -package org.javawebstack.orm.query; +package org.javawebstack.orm.wrapper.builder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class QueryString { +public class SQLQueryString { private final String query; private final List parameters; - public QueryString(String query, List parameters) { + public SQLQueryString(String query, List parameters) { this.query = query; this.parameters = parameters; } - public QueryString(String query, Object... parameters) { + public SQLQueryString(String query, Object... parameters) { this(query, new ArrayList<>(Arrays.asList(parameters))); } - public QueryString(String query) { + public SQLQueryString(String query) { this(query, new ArrayList<>()); } From a3f13873bf725da58436d1e66ab4cd40480d6cf4 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 17:21:16 +0200 Subject: [PATCH 03/45] Added test action for feature branches --- .github/workflows/maven-test.yml | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/maven-test.yml diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml new file mode 100644 index 0000000..95be220 --- /dev/null +++ b/.github/workflows/maven-test.yml @@ -0,0 +1,34 @@ +name: Maven Test +on: + push: + branches: + - '**' + - '!master' + +jobs: + test: + runs-on: ubuntu-latest + services: + mysql: + image: mariadb:latest + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + MYSQL_USER: test + MYSQL_PASSWORD: test + MYSQL_RANDOM_ROOT_PASSWORD: yes + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Test + run: mvn -B test + env: + MYSQL_PORT: ${{ job.services.mysql.ports[3306] }} + MYSQL_USERNAME: test + MYSQL_PASSWORD: test \ No newline at end of file From 057e32a5e9d3bb1920916a20a946e3f64f7174b4 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 17:48:40 +0200 Subject: [PATCH 04/45] Fixed the failing test --- .../org/javawebstack/orm/test/querybuilding/FromClauseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f03f754..c24faf9 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java @@ -69,6 +69,6 @@ void testOverwrittenTableName() throws ORMConfigurationException { */ private String getBaseQuery(Class clazz) throws ORMConfigurationException { ORM.register(clazz, sql()); - return Repo.get(clazz).query().getQueryString().getQuery(); + return Repo.get(clazz).getConnection().builder().buildQuery(Repo.get(clazz).query(), false).getQuery(); } } From bddf070837e3b3de23c1c4a196972df6c3e4c6b2 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 19:23:44 +0200 Subject: [PATCH 05/45] Merged master and fixed broken calls due to local changes --- .../orm/test/shared/verification/QueryVerification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0c2f5ec..a806819 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 @@ -48,7 +48,7 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr String sectionString; try { - sectionString = new QueryStringUtil(this.query.getQueryString().getQuery()) + sectionString = new QueryStringUtil(this.query.getRepo().getConnection().builder().buildQuery(this.query, false).getQuery()) .getTopLevelSectionsByKeyword(topLevelKeyword) .get(sectionIndex); } catch (IndexOutOfBoundsException ignored) { From 478ef588db13bd5bfdba73fe7bfc2a533463ff75 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 20:28:09 +0200 Subject: [PATCH 06/45] Added validation for operators and non raw column names --- .../javawebstack/orm/query/QueryColumn.java | 11 +++++++ .../orm/query/QueryCondition.java | 33 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryColumn.java b/src/main/java/org/javawebstack/orm/query/QueryColumn.java index 1cee6ed..ae056fc 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryColumn.java +++ b/src/main/java/org/javawebstack/orm/query/QueryColumn.java @@ -1,12 +1,16 @@ package org.javawebstack.orm.query; import org.javawebstack.orm.TableInfo; +import org.javawebstack.orm.exception.ORMQueryException; import java.util.Arrays; +import java.util.regex.Pattern; import java.util.stream.Collectors; public class QueryColumn { + private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9_-]+"); + private final String name; private final boolean raw; @@ -15,6 +19,8 @@ public QueryColumn(String name) { } public QueryColumn(String name, boolean raw) { + if(!raw) + validateName(name); this.name = name; this.raw = raw; } @@ -37,4 +43,9 @@ public String toString(TableInfo info) { return Arrays.stream((info != null ? info.getColumnName(name) : name).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining(".")); } + private static void validateName(String name) { + if(!NAME_PATTERN.matcher(name).matches()) + throw new ORMQueryException("Invalid column name '" + name + "' (Use raw in case you know what you're doing)"); + } + } diff --git a/src/main/java/org/javawebstack/orm/query/QueryCondition.java b/src/main/java/org/javawebstack/orm/query/QueryCondition.java index 1c2e587..28b0c67 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryCondition.java +++ b/src/main/java/org/javawebstack/orm/query/QueryCondition.java @@ -1,15 +1,41 @@ package org.javawebstack.orm.query; +import org.javawebstack.orm.exception.ORMQueryException; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + public class QueryCondition implements QueryElement { + private static final List VALID_OPERATORS = Arrays.asList( + "=", + "<=>", + "!=", + "<>", + "<=", + ">=", + "<", + ">", + "is null", + "is not null", + "is", + "is not", + "in", + "not in", + "like", + "not like" + ); + private final Object left; private final String operator; private final Object right; private final boolean not; public QueryCondition(Object left, String operator, Object right, boolean not) { + validateOperator(operator); this.left = left; - this.operator = operator; // TODO Validate and throw exception + this.operator = operator; this.right = right; this.not = not; } @@ -38,4 +64,9 @@ public boolean isNot() { return not; } + private static void validateOperator(String operator) { + if(!VALID_OPERATORS.contains(operator.toLowerCase(Locale.ROOT))) + throw new ORMQueryException("The given operator '" + operator + "' is invalid or not supported"); + } + } From b22b7e890f440c5997420b140388910e9550693e Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 30 Mar 2021 20:31:58 +0200 Subject: [PATCH 07/45] Added validation for operators and non raw column names --- src/main/java/org/javawebstack/orm/query/QueryColumn.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryColumn.java b/src/main/java/org/javawebstack/orm/query/QueryColumn.java index ae056fc..76ef519 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryColumn.java +++ b/src/main/java/org/javawebstack/orm/query/QueryColumn.java @@ -9,7 +9,7 @@ public class QueryColumn { - private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9_-]+"); + private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9_\\-.]+"); private final String name; private final boolean raw; From 49e4fa04b359b74ca8908478bf1d21d3048a4175 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 04:54:32 +0200 Subject: [PATCH 08/45] Add assertOrderByContains method --- .../test/shared/verification/QueryVerification.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 0c2f5ec..2fb3c16 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 @@ -22,7 +22,6 @@ public class QueryVerification { public QueryVerification(Query query) { this.query = query; - } /** @@ -36,6 +35,16 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr this.assertSectionContains(topLevelKeyword, containedSubstring, 0); } + /** + * Asserts that in ORDER BY section the given string is contained. + * This method uses the String.contains method internally and is therefore case sensitive. + * + * @param containedSubstring The substring which should be contained in ORDER BY section. + */ + public void assertOrderByContains(String containedSubstring) { + this.assertSectionContains("ORDER BY", containedSubstring); + } + /** * Asserts that in the i-th occurring section of a given top level keyword, the given string is contained. * This method uses the String.contains method internally and is therefore case sensitive. From 8aaedb258cdda939209f55aa3b386914df541330 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:11:58 +0200 Subject: [PATCH 09/45] Add SectionIndexOutOfBoundException --- .../SectionIndexOutOfBoundException.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/org/javawebstack/orm/test/exception/SectionIndexOutOfBoundException.java diff --git a/src/test/java/org/javawebstack/orm/test/exception/SectionIndexOutOfBoundException.java b/src/test/java/org/javawebstack/orm/test/exception/SectionIndexOutOfBoundException.java new file mode 100644 index 0000000..1a40ee7 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/exception/SectionIndexOutOfBoundException.java @@ -0,0 +1,17 @@ +package org.javawebstack.orm.test.exception; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +/** + * Only to be used for tests. + * This exception should be thrown when a SQL Query String is manually parsed and sections and section types are defined, and + * a type of section is attempted to be retrieved which does not exist in this number. + */ +public class SectionIndexOutOfBoundException extends Exception { + private int sectionCount; + private int attemptedIndex; + private String topLevelKeyword; +} From 1ae8ac89ea87dcb3cbb58eaa115f5cd810339c1b Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:18:26 +0200 Subject: [PATCH 10/45] Add retrieval methods for the sections --- .../verification/QueryVerification.java | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) 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 2fb3c16..af31a26 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,9 +1,11 @@ package org.javawebstack.orm.test.shared.verification; import org.javawebstack.orm.query.Query; +import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException; import org.javawebstack.orm.test.shared.util.QueryStringUtil; import java.util.HashSet; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -51,24 +53,19 @@ public void assertOrderByContains(String containedSubstring) { * * @param topLevelKeyword The top level keyword that prefaces the section. * @param containedSubstring The substring which should be contained in the first section of the given type. - * @param sectionIndex The index of the section to be checked, thusly 0 refers to the first occurrence etc. + * @param sectionIndex The index of the section to be checked, thus 0 refers to the first occurrence etc. */ public void assertSectionContains(String topLevelKeyword, String containedSubstring, int sectionIndex) { - String sectionString; - + String sectionString = null; try { - sectionString = new QueryStringUtil(this.query.getQueryString().getQuery()) - .getTopLevelSectionsByKeyword(topLevelKeyword) - .get(sectionIndex); - } catch (IndexOutOfBoundsException ignored) { + sectionString = getSection(topLevelKeyword, sectionIndex); + } catch (SectionIndexOutOfBoundException e) { fail(String.format( "A top level section of type %s and index %d was tested but only %d sections of that type existed.", - sectionIndex, - topLevelKeyword, - sectionIndex + 1 + e.getTopLevelKeyword(), + e.getAttemptedIndex(), + e.getSectionCount() )); - - return; } assertTrue( @@ -77,4 +74,47 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr ); } + /** + * Retrieves the inner part of a section by its keyword. With multiple occurrences it will only retrieve the first + * one. It does not include the keyword and one whitespaces at start and end. + * + * @param topLevelKeyword The top level keyword that prefaces the section. + * @return The inner part of the first section as specified. + * @throws SectionIndexOutOfBoundException if no section by that top level keyword exists. + */ + public String getSection(String topLevelKeyword) throws SectionIndexOutOfBoundException { + return this.getSection(topLevelKeyword, 0); + } + + /** + * Retrieves the inner part of a section by its keyword and index specifying which occurrence should be retrieved. + * It does not include the keyword and one whitespaces at start and end. + * + * @param topLevelKeyword The top level keyword that prefaces the section. + * @param sectionIndex The index of the section to be retrieved, thus 0 refers to the first occurrence etc. + * @return The inner part of the first section as specified. + * @throws SectionIndexOutOfBoundException if there are less than sectionIndex + 1 elements + */ + public String getSection(String topLevelKeyword, int sectionIndex) throws SectionIndexOutOfBoundException { + List sectionList = this.getSectionList(topLevelKeyword); + try { + return sectionList.get(sectionIndex); + } catch (IndexOutOfBoundsException converted) { + + SectionIndexOutOfBoundException exception = new SectionIndexOutOfBoundException(); + + exception.setSectionCount(sectionList.size()); + exception.setAttemptedIndex(sectionIndex); + exception.setTopLevelKeyword(topLevelKeyword); + + throw exception; + } + } + + + public List getSectionList(String topLevelKeyword) { + return new QueryStringUtil(this.query.getQueryString().getQuery()) + .getTopLevelSectionsByKeyword(topLevelKeyword); + } + } From 5d4e23331053a01974752c670be5b4e3f4604860 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:21:04 +0200 Subject: [PATCH 11/45] Add JavaDocs for getSectionList too --- .../orm/test/shared/verification/QueryVerification.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 af31a26..2fc4a6a 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 @@ -111,7 +111,13 @@ public String getSection(String topLevelKeyword, int sectionIndex) throws Sectio } } - + /** + * Retrieve list of all sections prefaced with the specified top level keyword. The list will have the same order + * as the occurrences of each section. + * + * @param topLevelKeyword The top level keyword that prefaces the sections. + * @return The order sensitive list of inner sections. + */ public List getSectionList(String topLevelKeyword) { return new QueryStringUtil(this.query.getQueryString().getQuery()) .getTopLevelSectionsByKeyword(topLevelKeyword); From 89c48dc82adbacb279da9d6372bf6355c24e83bb Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:21:04 +0200 Subject: [PATCH 12/45] Add JavaDocs for getSectionList too --- .../orm/test/shared/verification/QueryVerification.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 af31a26..27a4bda 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 @@ -111,7 +111,13 @@ public String getSection(String topLevelKeyword, int sectionIndex) throws Sectio } } - + /** + * Retrieve list of all sections prefaced with the specified top level keyword. The list will have the same order + * as the occurrences of each section. + * + * @param topLevelKeyword The top level keyword that prefaces the sections. + * @return The order sensitive string list of inner sections. + */ public List getSectionList(String topLevelKeyword) { return new QueryStringUtil(this.query.getQueryString().getQuery()) .getTopLevelSectionsByKeyword(topLevelKeyword); From 51907a4707def735c80528faa8fb8c1462092af8 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:25:44 +0200 Subject: [PATCH 13/45] Reorder methods more logically --- .../verification/QueryVerification.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 27a4bda..b712c0d 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 @@ -37,16 +37,6 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr this.assertSectionContains(topLevelKeyword, containedSubstring, 0); } - /** - * Asserts that in ORDER BY section the given string is contained. - * This method uses the String.contains method internally and is therefore case sensitive. - * - * @param containedSubstring The substring which should be contained in ORDER BY section. - */ - public void assertOrderByContains(String containedSubstring) { - this.assertSectionContains("ORDER BY", containedSubstring); - } - /** * Asserts that in the i-th occurring section of a given top level keyword, the given string is contained. * This method uses the String.contains method internally and is therefore case sensitive. @@ -61,19 +51,29 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr sectionString = getSection(topLevelKeyword, sectionIndex); } catch (SectionIndexOutOfBoundException e) { fail(String.format( - "A top level section of type %s and index %d was tested but only %d sections of that type existed.", - e.getTopLevelKeyword(), - e.getAttemptedIndex(), - e.getSectionCount() + "A top level section of type %s and index %d was tested but only %d sections of that type existed.", + e.getTopLevelKeyword(), + e.getAttemptedIndex(), + e.getSectionCount() )); } assertTrue( - sectionString.contains(containedSubstring), - String.format("The occurrence of index %d of %s section of the query did not contain a substring %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, containedSubstring, sectionString) + sectionString.contains(containedSubstring), + String.format("The occurrence of index %d of %s section of the query did not contain a substring %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, containedSubstring, sectionString) ); } + /** + * Asserts that in ORDER BY section the given string is contained. + * This method uses the String.contains method internally and is therefore case sensitive. + * + * @param containedSubstring The substring which should be contained in ORDER BY section. + */ + public void assertOrderByContains(String containedSubstring) { + this.assertSectionContains("ORDER BY", containedSubstring); + } + /** * Retrieves the inner part of a section by its keyword. With multiple occurrences it will only retrieve the first * one. It does not include the keyword and one whitespaces at start and end. From ac7140ef85e6376b5e37fa7ba45f1ddd8f7871d8 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:33:49 +0200 Subject: [PATCH 14/45] Add assertSectionEquals method --- .../verification/QueryVerification.java | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) 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 b712c0d..d0b8de4 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 @@ -50,12 +50,7 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr try { sectionString = getSection(topLevelKeyword, sectionIndex); } catch (SectionIndexOutOfBoundException e) { - fail(String.format( - "A top level section of type %s and index %d was tested but only %d sections of that type existed.", - e.getTopLevelKeyword(), - e.getAttemptedIndex(), - e.getSectionCount() - )); + this.failDueToSectionIndexOutOfBounds(e); } assertTrue( @@ -74,6 +69,39 @@ public void assertOrderByContains(String containedSubstring) { this.assertSectionContains("ORDER BY", containedSubstring); } + /** + * Asserts that the first occurring section of a given top level keyword is equal to the given string. + * This method uses the String.equals method internally and is therefore case sensitive. + * + * @param topLevelKeyword The top level keyword that prefaces the section. + * @param expectedString The substring which the first section of the given type should be equal to. + */ + public void assertSectionEquals(String topLevelKeyword, String expectedString) { + this.assertSectionEquals(topLevelKeyword, expectedString, 0); + } + + /** + * Asserts that the i-th occurring section of a given top level keyword equal to the given string. + * This method uses the String.equals method internally and is therefore case sensitive. + * + * @param topLevelKeyword The top level keyword that prefaces the section. + * @param expectedString The substring which the specified section of the given type should be equal to. + * @param sectionIndex The index of the section to be checked, thus 0 refers to the first occurrence etc. + */ + public void assertSectionEquals(String topLevelKeyword, String expectedString, int sectionIndex) { + String sectionString = null; + try { + sectionString = getSection(topLevelKeyword, sectionIndex); + } catch (SectionIndexOutOfBoundException e) { + this.failDueToSectionIndexOutOfBounds(e); + } + + assertTrue( + sectionString.equals(expectedString), + String.format("The occurrence of index %d of %s section of the query was not equal to the string %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, expectedString, sectionString) + ); + } + /** * Retrieves the inner part of a section by its keyword. With multiple occurrences it will only retrieve the first * one. It does not include the keyword and one whitespaces at start and end. @@ -123,4 +151,13 @@ public List getSectionList(String topLevelKeyword) { .getTopLevelSectionsByKeyword(topLevelKeyword); } + private void failDueToSectionIndexOutOfBounds(SectionIndexOutOfBoundException exception) { + fail(String.format( + "A top level section of type %s and index %d was tested but only %d sections of that type existed.", + exception.getTopLevelKeyword(), + exception.getAttemptedIndex(), + exception.getSectionCount() + )); + } + } From 5491a48a5755413f155d7c8b92ecd1ed6d710909 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 06:39:52 +0200 Subject: [PATCH 15/45] Remove OrderBy specific contains method --- .../test/shared/verification/QueryVerification.java | 10 ---------- 1 file changed, 10 deletions(-) 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 d0b8de4..8846069 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 @@ -59,16 +59,6 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr ); } - /** - * Asserts that in ORDER BY section the given string is contained. - * This method uses the String.contains method internally and is therefore case sensitive. - * - * @param containedSubstring The substring which should be contained in ORDER BY section. - */ - public void assertOrderByContains(String containedSubstring) { - this.assertSectionContains("ORDER BY", containedSubstring); - } - /** * Asserts that the first occurring section of a given top level keyword is equal to the given string. * This method uses the String.equals method internally and is therefore case sensitive. From 6f97433348aac85564ac8ca1fad4eef4b274a2d1 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 07:06:46 +0200 Subject: [PATCH 16/45] Make assertion methods chainable --- .../shared/verification/QueryVerification.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 8846069..e161491 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 @@ -33,8 +33,9 @@ public QueryVerification(Query query) { * @param topLevelKeyword The top level keyword that prefaces the section. * @param containedSubstring The substring which should be contained in the first section of the given type. */ - public void assertSectionContains(String topLevelKeyword, String containedSubstring) { + public QueryVerification assertSectionContains(String topLevelKeyword, String containedSubstring) { this.assertSectionContains(topLevelKeyword, containedSubstring, 0); + return this; } /** @@ -45,18 +46,21 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr * @param containedSubstring The substring which should be contained in the first section of the given type. * @param sectionIndex The index of the section to be checked, thus 0 refers to the first occurrence etc. */ - public void assertSectionContains(String topLevelKeyword, String containedSubstring, int sectionIndex) { + public QueryVerification assertSectionContains(String topLevelKeyword, String containedSubstring, int sectionIndex) { String sectionString = null; try { sectionString = getSection(topLevelKeyword, sectionIndex); } catch (SectionIndexOutOfBoundException e) { this.failDueToSectionIndexOutOfBounds(e); + return this; } assertTrue( sectionString.contains(containedSubstring), String.format("The occurrence of index %d of %s section of the query did not contain a substring %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, containedSubstring, sectionString) ); + + return this; } /** @@ -66,8 +70,9 @@ public void assertSectionContains(String topLevelKeyword, String containedSubstr * @param topLevelKeyword The top level keyword that prefaces the section. * @param expectedString The substring which the first section of the given type should be equal to. */ - public void assertSectionEquals(String topLevelKeyword, String expectedString) { + public QueryVerification assertSectionEquals(String topLevelKeyword, String expectedString) { this.assertSectionEquals(topLevelKeyword, expectedString, 0); + return this; } /** @@ -78,18 +83,21 @@ public void assertSectionEquals(String topLevelKeyword, String expectedString) { * @param expectedString The substring which the specified section of the given type should be equal to. * @param sectionIndex The index of the section to be checked, thus 0 refers to the first occurrence etc. */ - public void assertSectionEquals(String topLevelKeyword, String expectedString, int sectionIndex) { + public QueryVerification assertSectionEquals(String topLevelKeyword, String expectedString, int sectionIndex) { String sectionString = null; try { sectionString = getSection(topLevelKeyword, sectionIndex); } catch (SectionIndexOutOfBoundException e) { this.failDueToSectionIndexOutOfBounds(e); + return this; } assertTrue( sectionString.equals(expectedString), String.format("The occurrence of index %d of %s section of the query was not equal to the string %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, expectedString, sectionString) ); + + return this; } /** From b73d7dbf80fde8fb593ccdfc330cabe0714fe34a Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 07:12:50 +0200 Subject: [PATCH 17/45] Add better fitting internal assert for the equal assertion --- .../orm/test/shared/verification/QueryVerification.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 e161491..d9b6d05 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 @@ -7,8 +7,7 @@ import java.util.HashSet; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; /** * The QueryVerification class wraps an JWS Query Object and provides assertion methods regarding the raw query string. @@ -92,8 +91,9 @@ public QueryVerification assertSectionEquals(String topLevelKeyword, String expe return this; } - assertTrue( - sectionString.equals(expectedString), + assertEquals( + expectedString, + sectionString, String.format("The occurrence of index %d of %s section of the query was not equal to the string %s but looked like this: %s. Note that the match is case-sensitive.", sectionIndex, topLevelKeyword, expectedString, sectionString) ); From 710b6c5bb9602b6d0ad95e287be0e2edc39de62d Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 31 Mar 2021 07:14:34 +0200 Subject: [PATCH 18/45] Add tests for OrderBy Clauses --- .../test/querybuilding/OrderByClauseTest.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java new file mode 100644 index 0000000..f2108e9 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -0,0 +1,98 @@ +package org.javawebstack.orm.test.querybuilding; + +import org.javawebstack.orm.query.Query; +import org.javawebstack.orm.test.shared.models.Datatype; +import org.javawebstack.orm.test.shared.verification.QueryVerification; +import org.junit.jupiter.api.Test; + +import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel; + +public class OrderByClauseTest { + + @Test + void testOneExistingColumnDefaultOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` ASC"); + } + + @Test + void testOneNonExistingColumnDefaultOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("does_not_exist"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` ASC"); + } + + @Test + void testOneExistingColumnASCOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer", false); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` ASC"); + } + + @Test + void testOneNonExistingColumnASCOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("does_not_exist", false); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` ASC"); + } + + @Test + void testOneExistingColumnDESCOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer", true); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` DESC"); + } + + @Test + void testOneNonExistingColumnDESCOrderBy() { + Query query = setUpModel(Datatype.class).query() + .order("does_not_exist", true); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` DESC"); + } + + @Test + void testMultipleOrderByClausesOfASCOrder() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer") + .order("primitive_integer"); + + new QueryVerification(query) + .assertSectionContains("ORDER BY", "`wrapper_integer` ASC") + .assertSectionContains("ORDER BY", "`primitive_integer` ASC"); + } + + @Test + void testMultipleOrderByClausesOfDESCOrder() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer") + .order("primitive_integer"); + + new QueryVerification(query) + .assertSectionContains("ORDER BY", "`wrapper_integer` DESC") + .assertSectionContains("ORDER BY", "`primitive_integer` DESC"); + } + + @Test + void testMultipleOrderByClausesOfMixedOrder() { + Query query = setUpModel(Datatype.class).query() + .order("wrapper_integer", false) + .order("primitive_integer", true); + + new QueryVerification(query) + .assertSectionContains("ORDER BY", "`wrapper_integer` ASC") + .assertSectionContains("ORDER BY", "`primitive_integer` DESC"); + } + + @Test + void testMultipleOrderByClausesOfMixedOrderReversed() { + Query query = setUpModel(Datatype.class).query() + .order("primitive_integer", true) + .order("wrapper_integer", false); + + new QueryVerification(query) + .assertSectionContains("ORDER BY", "`primitive_integer` DESC") + .assertSectionContains("ORDER BY", "`wrapper_integer` ASC"); + } + +} From 0201c3d04aa865fcf232c53d31464c75db1ac22e Mon Sep 17 00:00:00 2001 From: JanHolger Date: Wed, 31 Mar 2021 22:22:51 +0200 Subject: [PATCH 19/45] Added extra fields to Model and added QueryWith but didn't yet implement it in the query builder as i'm way too tired (: --- src/main/java/org/javawebstack/orm/Model.java | 9 +++++++++ .../org/javawebstack/orm/query/Query.java | 14 +++++++++++++ .../org/javawebstack/orm/query/QueryWith.java | 20 +++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 src/main/java/org/javawebstack/orm/query/QueryWith.java diff --git a/src/main/java/org/javawebstack/orm/Model.java b/src/main/java/org/javawebstack/orm/Model.java index 4a8ef7c..e50de97 100644 --- a/src/main/java/org/javawebstack/orm/Model.java +++ b/src/main/java/org/javawebstack/orm/Model.java @@ -41,6 +41,7 @@ public class Model { private transient boolean internalEntryExists = false; private transient final Map, Object> internalJoinedModels = new HashMap<>(); private transient Map internalOriginalValues = new HashMap<>(); + private transient Map internalExtraFields = new HashMap<>(); void internalAddJoinedModel(Class type, Object entity) { internalJoinedModels.put(type, entity); @@ -62,6 +63,14 @@ public Map getFieldValues() { return values; } + public Map getExtraFields() { + return internalExtraFields; + } + + public T getExtraField(String key) { + return (T) internalExtraFields.get(key); + } + public Map getOriginalValues() { return internalOriginalValues; } diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index cb9c164..47fbfd2 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -27,6 +27,7 @@ public class Query { private QueryColumn order; private boolean desc = false; private boolean withDeleted = false; + private final List withs = new ArrayList<>(); public Query(Class model) { this(Repo.get(model), model); @@ -46,6 +47,10 @@ public QueryGroup getWhereGroup() { return where; } + public List getWiths() { + return withs; + } + public Integer getLimit() { return limit; } @@ -70,6 +75,15 @@ public Class getModel() { return model; } + public Query with(String extra) { + return with(extra, null); + } + + public Query with(String extra, String as) { + withs.add(new QueryWith(extra, as)); + return this; + } + public Query and(Function, QueryGroup> group) { where.and(group); return this; diff --git a/src/main/java/org/javawebstack/orm/query/QueryWith.java b/src/main/java/org/javawebstack/orm/query/QueryWith.java new file mode 100644 index 0000000..653fcbc --- /dev/null +++ b/src/main/java/org/javawebstack/orm/query/QueryWith.java @@ -0,0 +1,20 @@ +package org.javawebstack.orm.query; + +public class QueryWith { + + private final String expression; + private final String as; + + public QueryWith(String expression, String as) { + this.expression = expression; + this.as = as; + } + + public String getExpression() { + return expression; + } + + public String getAs() { + return as; + } +} From e4d3fc03c28e62a5e853d44130eb7fb3c4990119 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Thu, 1 Apr 2021 21:26:18 +0200 Subject: [PATCH 20/45] Remove explicit ASC as discussed --- .../test/querybuilding/OrderByClauseTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index f2108e9..c71516f 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -13,28 +13,28 @@ public class OrderByClauseTest { void testOneExistingColumnDefaultOrderBy() { Query query = setUpModel(Datatype.class).query() .order("wrapper_integer"); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` ASC"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`"); } @Test void testOneNonExistingColumnDefaultOrderBy() { Query query = setUpModel(Datatype.class).query() .order("does_not_exist"); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` ASC"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); } @Test void testOneExistingColumnASCOrderBy() { Query query = setUpModel(Datatype.class).query() .order("wrapper_integer", false); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` ASC"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`"); } @Test void testOneNonExistingColumnASCOrderBy() { Query query = setUpModel(Datatype.class).query() .order("does_not_exist", false); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` ASC"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); } @Test @@ -58,8 +58,8 @@ void testMultipleOrderByClausesOfASCOrder() { .order("primitive_integer"); new QueryVerification(query) - .assertSectionContains("ORDER BY", "`wrapper_integer` ASC") - .assertSectionContains("ORDER BY", "`primitive_integer` ASC"); + .assertSectionContains("ORDER BY", "`wrapper_integer`") + .assertSectionContains("ORDER BY", "`primitive_integer`"); } @Test @@ -80,7 +80,7 @@ void testMultipleOrderByClausesOfMixedOrder() { .order("primitive_integer", true); new QueryVerification(query) - .assertSectionContains("ORDER BY", "`wrapper_integer` ASC") + .assertSectionContains("ORDER BY", "`wrapper_integer`") .assertSectionContains("ORDER BY", "`primitive_integer` DESC"); } @@ -92,7 +92,7 @@ void testMultipleOrderByClausesOfMixedOrderReversed() { new QueryVerification(query) .assertSectionContains("ORDER BY", "`primitive_integer` DESC") - .assertSectionContains("ORDER BY", "`wrapper_integer` ASC"); + .assertSectionContains("ORDER BY", "`wrapper_integer`"); } } From abdab9e9cbaff88061834415c74005d7ddb568e9 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 13:44:21 +0200 Subject: [PATCH 21/45] Add JavaDocs for QueryGroup --- src/main/java/org/javawebstack/orm/query/QueryGroup.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/query/QueryGroup.java b/src/main/java/org/javawebstack/orm/query/QueryGroup.java index 7c7bd21..7111c11 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryGroup.java +++ b/src/main/java/org/javawebstack/orm/query/QueryGroup.java @@ -10,6 +10,15 @@ import java.util.List; import java.util.function.Function; +/** + * Queries grouped via the QueryGroup class will be put inside parenthesis. + * This makes expressions as the following possible (MySQL example): + * ... `column_a` = 'A' OR (`column_b` = 'B' AND `column_c´ = 'C') ... + * + * In the above example `column_b` = 'B' AND `column_c´ = 'C' would be in a QueryGroup. + * + * @param The model under which the QueryGroups functions. Currently purely semantic without functionality. + */ public class QueryGroup implements QueryElement { private final List queryElements = new ArrayList<>(); From 0a684f9eee6fbd3d353a1a719ebbad658a0e00f8 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 15:14:25 +0200 Subject: [PATCH 22/45] Add equals and hashcode overwrite to QueryColumn --- .../org/javawebstack/orm/query/QueryColumn.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/query/QueryColumn.java b/src/main/java/org/javawebstack/orm/query/QueryColumn.java index 76ef519..651c19d 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryColumn.java +++ b/src/main/java/org/javawebstack/orm/query/QueryColumn.java @@ -4,6 +4,7 @@ import org.javawebstack.orm.exception.ORMQueryException; import java.util.Arrays; +import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -48,4 +49,16 @@ private static void validateName(String name) { throw new ORMQueryException("Invalid column name '" + name + "' (Use raw in case you know what you're doing)"); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryColumn that = (QueryColumn) o; + return toString().equals(that.toString()); + } + + @Override + public int hashCode() { + return Objects.hash(toString()); + } } From 17a7060b030a00a596acea5da21b2eca6534a3e5 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 15:46:44 +0200 Subject: [PATCH 23/45] Add multiple order by capability --- .../org/javawebstack/orm/query/Query.java | 29 +++++----- .../javawebstack/orm/query/QueryOrderBy.java | 28 ++++++++++ .../orm/query/QueryOrderByElement.java | 53 +++++++++++++++++++ 3 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/javawebstack/orm/query/QueryOrderBy.java create mode 100644 src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index 47fbfd2..fac766c 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -24,8 +24,7 @@ public class Query { private final QueryGroup where; private Integer offset; private Integer limit; - private QueryColumn order; - private boolean desc = false; + private QueryOrderBy order; private boolean withDeleted = false; private final List withs = new ArrayList<>(); @@ -37,6 +36,7 @@ public Query(Repo repo, Class model) { this.repo = repo; this.model = model; this.where = new QueryGroup<>(); + this.order = new QueryOrderBy(); } public boolean isWithDeleted() { @@ -59,14 +59,10 @@ public Integer getOffset() { return offset; } - public QueryColumn getOrder() { + public QueryOrderBy getOrder() { return order; } - public boolean isDescOrder() { - return desc; - } - public Repo getRepo() { return repo; } @@ -300,18 +296,25 @@ public Query search(String search) { return this; } + public Query order(String orderBy) { + return order(orderBy, false); + } + public Query order(String orderBy, boolean desc) { return order(new QueryColumn(orderBy), desc); } public Query order(QueryColumn orderBy, boolean desc) { - this.order = orderBy; - this.desc = desc; - return this; - } + boolean success = this.order.add(orderBy, desc); + if(!success) { + throw new ORMQueryException(String.format( + "The column %s could not be ordered %s. This is probably caused by calling .order() on this column twice.", + orderBy.toString(), + desc ? "descendingly" : "ascendingly" + )); + } - public Query order(String orderBy) { - return order(orderBy, false); + return this; } public Query limit(int offset, int limit) { diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java new file mode 100644 index 0000000..d35529f --- /dev/null +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java @@ -0,0 +1,28 @@ +package org.javawebstack.orm.query; + +import java.util.LinkedList; +import java.util.List; + +public class QueryOrderBy extends LinkedList{ + + public boolean add(String columnName, boolean desc) { + return this.add(new QueryColumn(columnName), desc); + } + + public boolean add(QueryColumn column, boolean desc) { + return this.add(new QueryOrderByElement(column, desc)); + } + + @Override + public boolean add(QueryOrderByElement element) { + boolean hasBeenAdded = false; + if(!willOverwrite(element)) + hasBeenAdded = super.add(element); + + return hasBeenAdded; + } + + private boolean willOverwrite(QueryOrderByElement element) { + return this.stream().anyMatch(element::hasEqualColumn); + } +} diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java new file mode 100644 index 0000000..23bf426 --- /dev/null +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java @@ -0,0 +1,53 @@ +package org.javawebstack.orm.query; + +import java.util.Objects; + +public class QueryOrderByElement { + private QueryColumn queryColumn; + private boolean desc; + + QueryOrderByElement(String columnName, boolean desc) { + queryColumn = new QueryColumn(columnName); + this.desc = desc; + } + + QueryOrderByElement(QueryColumn column, boolean desc) { + this.queryColumn = column; + this.desc = desc; + } + + public QueryColumn getQueryColumn() { + return queryColumn; + } + + public boolean isDesc() { + return desc; + } + + public boolean hasEqualColumn(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryOrderByElement that = (QueryOrderByElement) o; + return getQueryColumn().equals(that.getQueryColumn()); + } + + public boolean hasEqualOrderDirection(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryOrderByElement that = (QueryOrderByElement) o; + return isDesc() == that.isDesc(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueryOrderByElement that = (QueryOrderByElement) o; + return isDesc() == that.isDesc() && getQueryColumn().equals(that.getQueryColumn()); + } + + @Override + public int hashCode() { + return Objects.hash(getQueryColumn(), isDesc()); + } +} From fb00e4d46402a6524898a6c547e8eaa6aa488deb Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 16:13:50 +0200 Subject: [PATCH 24/45] Adjust MySQL Query Building for the new OrderBy and fix OrderBy test --- .../javawebstack/orm/query/QueryOrderBy.java | 15 ++++++++++++++- .../orm/query/QueryOrderByElement.java | 19 +++++++++++++++++-- .../builder/MySQLQueryStringBuilder.java | 10 ++++++---- .../test/querybuilding/OrderByClauseTest.java | 4 ++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java index d35529f..fc05582 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java @@ -1,7 +1,9 @@ package org.javawebstack.orm.query; +import org.javawebstack.orm.TableInfo; + import java.util.LinkedList; -import java.util.List; +import java.util.stream.Collectors; public class QueryOrderBy extends LinkedList{ @@ -25,4 +27,15 @@ public boolean add(QueryOrderByElement element) { private boolean willOverwrite(QueryOrderByElement element) { return this.stream().anyMatch(element::hasEqualColumn); } + + @Override + public String toString() { + return toString(null); + } + + public String toString(TableInfo info) { + return this.stream() + .map(QueryOrderByElement::toString) + .collect(Collectors.joining(",")); + } } diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java index 23bf426..e90897b 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java @@ -1,10 +1,12 @@ package org.javawebstack.orm.query; +import org.javawebstack.orm.TableInfo; + import java.util.Objects; public class QueryOrderByElement { - private QueryColumn queryColumn; - private boolean desc; + private final QueryColumn queryColumn; + private final boolean desc; QueryOrderByElement(String columnName, boolean desc) { queryColumn = new QueryColumn(columnName); @@ -50,4 +52,17 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(getQueryColumn(), isDesc()); } + + @Override + public String toString() { + return this.toString(null); + } + + public String toString(TableInfo info) { + String stringifiedOrderBy = getQueryColumn().toString(info); + if (isDesc()) + stringifiedOrderBy += " DESC"; + + return stringifiedOrderBy; + } } 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 bfc1771..3b1faef 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -50,11 +50,13 @@ public SQLQueryString buildQuery(Query query, boolean count) { sb.append(" WHERE ").append(qs.getQuery()); parameters.addAll(qs.getParameters()); } - if (query.getOrder() != null) { - sb.append(" ORDER BY ").append(query.getOrder().toString(repo.getInfo())); - if (query.isDescOrder()) - sb.append(" DESC"); + + QueryOrderBy orderBy = query.getOrder(); + if (!orderBy.isEmpty()) { + sb.append(" ORDER BY ") + .append(orderBy.toString()); } + Integer offset = query.getOffset(); Integer limit = query.getLimit(); if (offset != null && limit == null) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index c71516f..1d16432 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -65,8 +65,8 @@ void testMultipleOrderByClausesOfASCOrder() { @Test void testMultipleOrderByClausesOfDESCOrder() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer") - .order("primitive_integer"); + .order("wrapper_integer", true) + .order("primitive_integer", true); new QueryVerification(query) .assertSectionContains("ORDER BY", "`wrapper_integer` DESC") From 018ce02873a5078ed6a792e9c15ac58c3afce217 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 16:56:02 +0200 Subject: [PATCH 25/45] Add test to assert the correct sort priority --- .../test/querybuilding/OrderByClauseTest.java | 46 ++++++++++++++++++- .../orm/test/shared/models/Datatype.java | 28 +++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index 1d16432..bb3a9d4 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -1,11 +1,19 @@ package org.javawebstack.orm.test.querybuilding; import org.javawebstack.orm.query.Query; +import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException; import org.javawebstack.orm.test.shared.models.Datatype; import org.javawebstack.orm.test.shared.verification.QueryVerification; import org.junit.jupiter.api.Test; +import javax.xml.crypto.Data; + +import java.util.*; +import java.util.stream.Collectors; + import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class OrderByClauseTest { @@ -84,6 +92,43 @@ void testMultipleOrderByClausesOfMixedOrder() { .assertSectionContains("ORDER BY", "`primitive_integer` DESC"); } + + @Test + // This test is important because putting the order by statements in different order is relevant (they set priorities) + void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionIndexOutOfBoundException { + Query query = setUpModel(Datatype.class).query(); + ArrayList columnNames = new ArrayList<>(Datatype.columnNames); + + LinkedList callOrder = new LinkedList<>(); + + Random r = new Random(); + columnNames.stream().unordered().forEach((singleColumn) -> { + query.order(singleColumn, r.nextBoolean()); + callOrder.add(singleColumn); + }); + + String queryString = new QueryVerification(query).getSection("ORDER BY"); + int lastIndex = 0; + int foundIndex = -1; + for (String nextInCallOrder : callOrder) { + foundIndex = queryString.indexOf("`" + nextInCallOrder + "`"); + if(foundIndex < lastIndex) { + if (foundIndex == -1) + fail("Not all columns occurred in the query string."); + else + fail("The columns did not appear an the correct order."); + + break; + } + + lastIndex = foundIndex; + } + + // If it came until here the test should count as passed. + assertTrue(true); + + } + @Test void testMultipleOrderByClausesOfMixedOrderReversed() { Query query = setUpModel(Datatype.class).query() @@ -94,5 +139,4 @@ void testMultipleOrderByClausesOfMixedOrderReversed() { .assertSectionContains("ORDER BY", "`primitive_integer` DESC") .assertSectionContains("ORDER BY", "`wrapper_integer`"); } - } diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/Datatype.java b/src/test/java/org/javawebstack/orm/test/shared/models/Datatype.java index 7b4aa9d..5d79a8d 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/models/Datatype.java +++ b/src/test/java/org/javawebstack/orm/test/shared/models/Datatype.java @@ -5,6 +5,8 @@ import java.sql.Date; import java.sql.Timestamp; +import java.util.Arrays; +import java.util.List; import java.util.UUID; /* @@ -84,4 +86,30 @@ public enum OptionEnum { OPTION1, OPTION2, } + + public static final List columnNames = Arrays.asList( + "id", + "primitive_boolean", + "wrapper_boolean", + "primitive_byte", + "wrapper_byte", + "primitive_short", + "wrapper_short", + "primitive_integer", + "wrapper_integer", + "primitive_long", + "wrapper_long", + "primitive_float", + "wrapper_float", + "primitive_double", + "wrapper_double", + "primitive_char", + "wrapper_string", + "char_array", + "byte_array", + "timestamp", + "date", + "uuid", + "option_enum" + ); } \ No newline at end of file From c8e9a408d51e3c63b277b3280dfb6ec7f1cf9ad6 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 16:59:19 +0200 Subject: [PATCH 26/45] Add error case for calling order on same column twice --- .../test/querybuilding/OrderByClauseTest.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index bb3a9d4..76d5b18 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -1,5 +1,6 @@ package org.javawebstack.orm.test.querybuilding; +import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.query.Query; import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException; import org.javawebstack.orm.test.shared.models.Datatype; @@ -12,8 +13,7 @@ import java.util.stream.Collectors; import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; public class OrderByClauseTest { @@ -92,6 +92,17 @@ void testMultipleOrderByClausesOfMixedOrder() { .assertSectionContains("ORDER BY", "`primitive_integer` DESC"); } + @Test + void testMultipleOrderByClausesOfMixedOrderReversed() { + Query query = setUpModel(Datatype.class).query() + .order("primitive_integer", true) + .order("wrapper_integer", false); + + new QueryVerification(query) + .assertSectionContains("ORDER BY", "`primitive_integer` DESC") + .assertSectionContains("ORDER BY", "`wrapper_integer`"); + } + @Test // This test is important because putting the order by statements in different order is relevant (they set priorities) @@ -129,14 +140,14 @@ void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionInde } + /* + * Error Cases + */ @Test - void testMultipleOrderByClausesOfMixedOrderReversed() { + void testCannotCallOrderOnSameColumnTwice() { Query query = setUpModel(Datatype.class).query() - .order("primitive_integer", true) - .order("wrapper_integer", false); + .order("primitive_integer", true); - new QueryVerification(query) - .assertSectionContains("ORDER BY", "`primitive_integer` DESC") - .assertSectionContains("ORDER BY", "`wrapper_integer`"); + assertThrows(ORMQueryException.class, () -> query.order("primitive_integer")); } } From 37ed3120ce176f5a6f43652ff7530ed407d7a803 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 17:04:55 +0200 Subject: [PATCH 27/45] Add note of purpose of OrderByClauseTest --- .../javawebstack/orm/test/querybuilding/OrderByClauseTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index 76d5b18..d411536 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -15,6 +15,7 @@ import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel; import static org.junit.jupiter.api.Assertions.*; +// This class tests the query generation for order by statements an MySQL public class OrderByClauseTest { @Test @@ -143,6 +144,8 @@ void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionInde /* * Error Cases */ + + // This test might not be correct here as it does not purely look at the query @Test void testCannotCallOrderOnSameColumnTwice() { Query query = setUpModel(Datatype.class).query() From 5a74b5380ce9b7d10cf772abd663e2dd8b305ab7 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 17:15:25 +0200 Subject: [PATCH 28/45] Add JavaDocs to QueryOrderBy --- .../javawebstack/orm/query/QueryOrderBy.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java index fc05582..7cc6e24 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java @@ -5,17 +5,44 @@ import java.util.LinkedList; import java.util.stream.Collectors; +/** + * The QueryOrderBy class serves as an aggregation of order by elements. It extends a list, because the order of the + * order by statements is of relevance. + */ public class QueryOrderBy extends LinkedList{ + /** + * Add a new order by statement. If a statement with the same column name already exists it will not add the + * statement. + * + * @param columnName The column name to order by. + * @param desc If the column should be order descendingly. + * @return True if adding the statement was successful. False otherwise. + */ public boolean add(String columnName, boolean desc) { return this.add(new QueryColumn(columnName), desc); } + /** + * Add a new order by statement. If a statement with the same column name already exists it will not add the + * statement. + * + * @param column The column to be ordered by. It will retrieve the name from the QueryColumn. + * @param desc If the column should be order descendingly. + * @return True if adding the statement was successful. False otherwise. + */ public boolean add(QueryColumn column, boolean desc) { return this.add(new QueryOrderByElement(column, desc)); } @Override + /** + * Add a new order by statement. If a statement with the same column name already exists it will not add the + * statement. + * + * @param element The direct QueryOrderByElement which encodes the order by statement. + * @return True if adding the statement was successful. False otherwise. + */ public boolean add(QueryOrderByElement element) { boolean hasBeenAdded = false; if(!willOverwrite(element)) @@ -28,6 +55,8 @@ private boolean willOverwrite(QueryOrderByElement element) { return this.stream().anyMatch(element::hasEqualColumn); } + + // The toString methods are specific to MySQL so they might have to be replaced later on. @Override public String toString() { return toString(null); From 5210447f3cd949e632abbc0e1c5f7a63d1b8eb9f Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 17:21:09 +0200 Subject: [PATCH 29/45] Add JavaDocs to QueryOrderByElement --- .../orm/query/QueryOrderByElement.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java index e90897b..915284f 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java @@ -4,6 +4,9 @@ import java.util.Objects; +/** + * The QueryOrderByElement class encodes an Order By Statement. + */ public class QueryOrderByElement { private final QueryColumn queryColumn; private final boolean desc; @@ -18,14 +21,30 @@ public class QueryOrderByElement { this.desc = desc; } + /** + * Retrieves the QueryColumn of the statement which encodes the column name. + * + * @return The encoding QueryColumn object. + */ public QueryColumn getQueryColumn() { return queryColumn; } + /** + * Retrieves the information if this column is ordered ascendingly or descendingly. + * + * @return false if ascending, true if descending. + */ public boolean isDesc() { return desc; } + /** + * Compares the encoded column name. + * + * @param o An object to compare to. + * @return True if the object is a QueryOrderByElement with a QueryColumn with generates the same identifier. + */ public boolean hasEqualColumn(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -33,13 +52,6 @@ public boolean hasEqualColumn(Object o) { return getQueryColumn().equals(that.getQueryColumn()); } - public boolean hasEqualOrderDirection(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - QueryOrderByElement that = (QueryOrderByElement) o; - return isDesc() == that.isDesc(); - } - @Override public boolean equals(Object o) { if (this == o) return true; From 3bff425c49f79e32f3ce5e4584a86be15856b3b9 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 2 Apr 2021 17:28:34 +0200 Subject: [PATCH 30/45] Add JavaDocs to the order method on Query --- .../org/javawebstack/orm/query/Query.java | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/Query.java b/src/main/java/org/javawebstack/orm/query/Query.java index fac766c..9bbd617 100644 --- a/src/main/java/org/javawebstack/orm/query/Query.java +++ b/src/main/java/org/javawebstack/orm/query/Query.java @@ -296,20 +296,43 @@ public Query search(String search) { return this; } - public Query order(String orderBy) { - return order(orderBy, false); - } - - public Query order(String orderBy, boolean desc) { - return order(new QueryColumn(orderBy), desc); - } - - public Query order(QueryColumn orderBy, boolean desc) { - boolean success = this.order.add(orderBy, desc); + /** + * Sorts the results by the given column name ascendingly. + * + * @param columnName The name of the column to sort ascendingly by. + * @return The Query object with the given order by information added. + * @throws ORMQueryException if the order operation is called twice on a column specification with the same name. + */ + public Query order(String columnName) throws ORMQueryException { + return order(columnName, false); + } + + /** + * Sorts the results by the given column name with the given order direction. + * + * @param columnName The name of the column to sort ascendingly by. + * @param desc If true it will order descendingly, if false it will order ascendingly. + * @return The Query object with the given order by information added. + * @throws ORMQueryException if the order operation is called twice on a column specification with the same name. + */ + public Query order(String columnName, boolean desc) throws ORMQueryException { + return order(new QueryColumn(columnName), desc); + } + + /** + * Sorts the results by the given column with the given order direction. + * + * @param column The column encoded as QueryColumn object. + * @param desc If true it will order descendingly, if false it will order ascendingly. + * @return The Query object with the given order by information added. + * @throws ORMQueryException if the order operation is called twice on a column specification with the same name. + */ + public Query order(QueryColumn column, boolean desc) throws ORMQueryException{ + boolean success = this.order.add(column, desc); if(!success) { throw new ORMQueryException(String.format( "The column %s could not be ordered %s. This is probably caused by calling .order() on this column twice.", - orderBy.toString(), + column.toString(), desc ? "descendingly" : "ascendingly" )); } From 289aa8ab84b4482d799eb5b17bb3e73c30b07275 Mon Sep 17 00:00:00 2001 From: JanHolger Date: Tue, 27 Apr 2021 12:36:05 +0200 Subject: [PATCH 31/45] Removed manual migrations --- .../org/javawebstack/orm/migration/DB.java | 282 ------------------ .../javawebstack/orm/migration/Migration.java | 25 -- .../orm/migration/MigrationState.java | 44 --- .../javawebstack/orm/migration/Migrator.java | 78 ----- .../orm/query/QueryCondition.java | 4 + 5 files changed, 4 insertions(+), 429 deletions(-) delete mode 100644 src/main/java/org/javawebstack/orm/migration/DB.java delete mode 100644 src/main/java/org/javawebstack/orm/migration/Migration.java delete mode 100644 src/main/java/org/javawebstack/orm/migration/MigrationState.java delete mode 100644 src/main/java/org/javawebstack/orm/migration/Migrator.java diff --git a/src/main/java/org/javawebstack/orm/migration/DB.java b/src/main/java/org/javawebstack/orm/migration/DB.java deleted file mode 100644 index e7892e6..0000000 --- a/src/main/java/org/javawebstack/orm/migration/DB.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.javawebstack.orm.migration; - -import org.javawebstack.orm.SQLType; -import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.wrapper.SQL; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -public class DB { - - private final SQL sql; - private final String tablePrefix; - - public DB(SQL sql, String tablePrefix) { - this.sql = sql; - this.tablePrefix = tablePrefix; - } - - public void table(String name, Consumer consumer) { - consumer.accept(new Table(name)); - } - - public class Table { - - private String name; - private final List columns = new ArrayList<>(); - - public Table(String name) { - this.name = name; - } - - public String fullName() { - return tablePrefix + name; - } - - public Column column(String name, SQLType type, String size) { - for (Column column : columns) { - if (column.name.equals(name)) - return column; - } - Column column = new Column(name, type, size); - columns.add(column); - return column; - } - - public Column column(String name, SQLType type) { - return column(name, type, null); - } - - public Column string(String name) { - return string(name, 255); - } - - public Column string(String name, int size) { - return column(name, SQLType.VARCHAR, String.valueOf(size)); - } - - public Column text(String name, int size) { - return column(name, SQLType.TEXT, String.valueOf(size)); - } - - public Column text(String name) { - return column(name, SQLType.TEXT); - } - - public Column id() { - return integer("id").autoIncrement().primary(); - } - - public Column integer(String name, int size) { - return column(name, SQLType.INT, String.valueOf(size)); - } - - public Column integer(String name) { - return column(name, SQLType.INT); - } - - public Column bool(String name) { - return column(name, SQLType.TINYINT, "1"); - } - - public Column uuid(String name) { - return column(name, SQLType.VARCHAR, "36"); - } - - public Column uuid() { - return uuid("uuid").primary(); - } - - public Column enums(String name, String... values) { - return column(name, SQLType.ENUM, String.join(",", values)); - } - - public Column timestamp(String name) { - return column(name, SQLType.TIMESTAMP); - } - - public void timestamps(String... names) { - for (String name : names) - timestamp(name); - } - - public void dates() { - timestamps("created_at", "updated_at"); - } - - public void softDelete() { - timestamp("deleted_at").nullable(); - } - - public void rename(String to) { - try { - sql.write("RENAME TABLE `" + fullName() + "` TO `" + tablePrefix + to + "`;"); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - this.name = to; - } - - public void drop() { - try { - sql.write("DROP TABLE `" + fullName() + "`;"); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - } - - public void create() { - create(false); - } - - public void create(boolean ifNotExists) { - StringBuilder sb = new StringBuilder("CREATE TABLE "); - if (ifNotExists) - sb.append("IF NOT EXISTS "); - sb.append('`'); - sb.append(fullName()); - sb.append("` ("); - List entries = new ArrayList<>(columns.stream().map(Column::definition).collect(Collectors.toList())); - columns.forEach(c -> entries.addAll(c.contraints())); - sb.append(String.join(",", entries)); - sb.append(") DEFAULT CHARSET=utf8mb4;"); - try { - sql.write(sb.toString()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - } - - public class Column { - private boolean primary = false; - private boolean unique = false; - private boolean autoIncrement = false; - private boolean nullable = false; - private String after; - private String name; - private final SQLType type; - private final String size; - - public Column(String name, SQLType type, String size) { - this.name = name; - this.type = type; - this.size = size; - } - - public Column primary() { - this.primary = true; - return this; - } - - public Column unique() { - this.unique = true; - return this; - } - - public Column autoIncrement() { - this.autoIncrement = true; - return this; - } - - public Column nullable() { - this.nullable = true; - return this; - } - - public Column after(String after) { - this.after = after; - return this; - } - - public Column first() { - this.after = ""; - return this; - } - - public void rename(String to) { - try { - sql.write("ALTER TABLE `" + fullName() + "` RENAME COLUMN `" + name + "` TO `" + to + "`;"); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - name = to; - } - - public void drop() { - try { - sql.write("ALTER TABLE `" + fullName() + "` DROP COLUMN `" + name + "`;"); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - columns.remove(this); - } - - public void add() { - StringBuilder sb = new StringBuilder("ALTER TABLE `"); - sb.append(fullName()); - sb.append("` ADD "); - sb.append(definition()); - sb.append(';'); - try { - sql.write(sb.toString()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - } - - public void modify() { - StringBuilder sb = new StringBuilder("ALTER TABLE `"); - sb.append(fullName()); - sb.append("` MODIFY "); - sb.append(definition()); - sb.append(';'); - try { - sql.write(sb.toString()); - } catch (SQLException throwables) { - throw new ORMQueryException(throwables); - } - } - - String definition() { - StringBuilder sb = new StringBuilder('`'); - sb.append(name); - sb.append("` "); - sb.append(type.name()); - if (size != null) { - sb.append('('); - sb.append(size); - sb.append(')'); - } - if (nullable) { - sb.append(" NULL"); - } else { - sb.append(" NOT NULL"); - } - if (autoIncrement) - sb.append(" AUTO_INCREMENT"); - if (after != null) { - if (after.length() != 0) { - sb.append(" AFTER `"); - sb.append(after); - sb.append('`'); - } else { - sb.append(" FIRST"); - } - } - return sb.toString(); - } - - List contraints() { - List constraints = new ArrayList<>(columns.stream().filter(c -> c.primary).map(c -> "PRIMARY KEY (`" + c.name + "`)").collect(Collectors.toList())); - contraints().addAll(columns.stream().filter(c -> c.unique).map(c -> "UNIQUE (`" + c.name + "`)").collect(Collectors.toList())); - return constraints; - } - } - - } - -} diff --git a/src/main/java/org/javawebstack/orm/migration/Migration.java b/src/main/java/org/javawebstack/orm/migration/Migration.java deleted file mode 100644 index e6da30f..0000000 --- a/src/main/java/org/javawebstack/orm/migration/Migration.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.javawebstack.orm.migration; - -public interface Migration { - - String version(); - - default String name() { - String className = getClass().getSimpleName(); - StringBuilder sb = new StringBuilder(Character.toLowerCase(className.charAt(0))); - for (int i = 1; i < className.length(); i++) { - if (Character.isUpperCase(className.charAt(i))) { - sb.append('-'); - sb.append(Character.toLowerCase(className.charAt(i))); - } else { - sb.append(className.charAt(i)); - } - } - return version().replace(" ", "-") + "-" + sb.toString(); - } - - void up(DB db); - - void down(DB db); - -} diff --git a/src/main/java/org/javawebstack/orm/migration/MigrationState.java b/src/main/java/org/javawebstack/orm/migration/MigrationState.java deleted file mode 100644 index 524d40e..0000000 --- a/src/main/java/org/javawebstack/orm/migration/MigrationState.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.javawebstack.orm.migration; - -import org.javawebstack.orm.Model; -import org.javawebstack.orm.annotation.Column; -import org.javawebstack.orm.annotation.Dates; -import org.javawebstack.orm.util.KeyType; - -import java.sql.Timestamp; - -@Dates -public class MigrationState extends Model { - - @Column(id = true, ai = false, key = KeyType.PRIMARY) - private String name; - @Column - private Timestamp createdAt; - @Column - private Timestamp updatedAt; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Timestamp getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Timestamp createdAt) { - this.createdAt = createdAt; - } - - public Timestamp getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Timestamp updatedAt) { - this.updatedAt = updatedAt; - } - -} diff --git a/src/main/java/org/javawebstack/orm/migration/Migrator.java b/src/main/java/org/javawebstack/orm/migration/Migrator.java deleted file mode 100644 index c35f456..0000000 --- a/src/main/java/org/javawebstack/orm/migration/Migrator.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.javawebstack.orm.migration; - -import org.javawebstack.orm.ORM; -import org.javawebstack.orm.ORMConfig; -import org.javawebstack.orm.Repo; -import org.javawebstack.orm.exception.ORMConfigurationException; -import org.javawebstack.orm.wrapper.SQL; -import org.reflections.Reflections; - -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; - -public class Migrator { - - private final DB db; - private final List migrations = new ArrayList<>(); - - public Migrator(SQL sql, ORMConfig config) { - this.db = new DB(sql, config.getTablePrefix()); - try { - ORM.register(MigrationState.class, sql, config); - } catch (ORMConfigurationException ignored) { - } - } - - public Migrator add(Migration... migrations) { - this.migrations.addAll(Arrays.asList(migrations)); - return this; - } - - public Migrator add(Package p) { - Reflections reflections = new Reflections(p.getName()); - reflections.getSubTypesOf(Migration.class).forEach(c -> { - try { - add(c.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { - } - }); - return this; - } - - public void migrate() { - migrate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(Date.from(Instant.now()))); - } - - public void migrate(String version) { - Map states = new HashMap<>(); - Repo.get(MigrationState.class).stream().forEach(m -> states.put(m.getName(), m)); - after(version).stream().sorted(Comparator.comparing(Migration::version).reversed()).forEach(m -> { - if (states.containsKey(m.name())) { - m.down(db); - states.get(m.name()).delete(); - states.remove(m.name()); - } - }); - before(version).stream().sorted(Comparator.comparing(Migration::version)).forEach(m -> { - if (!states.containsKey(m.name())) { - m.up(db); - MigrationState state = new MigrationState(); - state.setName(m.name()); - state.save(); - states.put(state.getName(), state); - } - }); - } - - private List before(String version) { - return new ArrayList<>(migrations.stream().filter(m -> m.version().compareTo(version) < 0).collect(Collectors.toList())); - } - - private List after(String version) { - return new ArrayList<>(migrations.stream().filter(m -> m.version().compareTo(version) >= 0).collect(Collectors.toList())); - } - -} diff --git a/src/main/java/org/javawebstack/orm/query/QueryCondition.java b/src/main/java/org/javawebstack/orm/query/QueryCondition.java index 28b0c67..4f3901c 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryCondition.java +++ b/src/main/java/org/javawebstack/orm/query/QueryCondition.java @@ -69,4 +69,8 @@ private static void validateOperator(String operator) { throw new ORMQueryException("The given operator '" + operator + "' is invalid or not supported"); } + public enum Operator { + + } + } From 54ad3d75546a4d165ae637ad0621a376408a7e7b Mon Sep 17 00:00:00 2001 From: JanHolger Date: Wed, 28 Apr 2021 03:04:07 +0200 Subject: [PATCH 32/45] Removed injector --- pom.xml | 5 ----- src/main/java/org/javawebstack/orm/Model.java | 8 -------- src/main/java/org/javawebstack/orm/ORMConfig.java | 11 ----------- src/main/java/org/javawebstack/orm/Repo.java | 4 ---- src/main/java/org/javawebstack/orm/SQLMapper.java | 6 ------ 5 files changed, 34 deletions(-) diff --git a/pom.xml b/pom.xml index 9b5ef29..7ccd700 100644 --- a/pom.xml +++ b/pom.xml @@ -63,11 +63,6 @@ reflections 0.9.12 - - org.javawebstack - Injector - 1.0-SNAPSHOT - org.javawebstack AbstractData diff --git a/src/main/java/org/javawebstack/orm/Model.java b/src/main/java/org/javawebstack/orm/Model.java index e50de97..9619dbd 100644 --- a/src/main/java/org/javawebstack/orm/Model.java +++ b/src/main/java/org/javawebstack/orm/Model.java @@ -1,6 +1,5 @@ package org.javawebstack.orm; -import org.javawebstack.injector.Injector; import org.javawebstack.orm.query.Query; import java.lang.reflect.Field; @@ -22,7 +21,6 @@ public class Model { private static final Method refreshMethod; { - inject(); updateOriginal(); } @@ -123,12 +121,6 @@ void setEntryExists(boolean exists) { this.internalEntryExists = exists; } - public void inject() { - Injector injector = Repo.get(getClass()).getInfo().getConfig().getInjector(); - if (injector != null) - injector.inject(this); - } - public void save() { try { saveMethod.invoke(ORM.repo(getClass()), this); diff --git a/src/main/java/org/javawebstack/orm/ORMConfig.java b/src/main/java/org/javawebstack/orm/ORMConfig.java index 1558d95..38c14a6 100644 --- a/src/main/java/org/javawebstack/orm/ORMConfig.java +++ b/src/main/java/org/javawebstack/orm/ORMConfig.java @@ -1,6 +1,5 @@ package org.javawebstack.orm; -import org.javawebstack.injector.Injector; import org.javawebstack.orm.exception.ORMConfigurationException; import org.javawebstack.orm.mapper.DefaultMapper; import org.javawebstack.orm.mapper.TypeMapper; @@ -15,7 +14,6 @@ public class ORMConfig { private boolean idPrimaryKey = true; private boolean idAutoIncrement = true; private final List typeMappers = new ArrayList<>(); - private Injector injector; private boolean preventUnnecessaryUpdates = true; public ORMConfig() { @@ -55,11 +53,6 @@ public ORMConfig setIdAutoIncrement(boolean idAutoIncrement) { return this; } - public ORMConfig setInjector(Injector injector) { - this.injector = injector; - return this; - } - public boolean isCamelToSnakeCase() { return camelToSnakeCase; } @@ -84,10 +77,6 @@ public boolean isIdAutoIncrement() { return idAutoIncrement; } - public Injector getInjector() { - return injector; - } - public TypeMapper getTypeMapper(Class type, int size) { for (TypeMapper mapper : getTypeMappers()) { SQLType sqlType = mapper.getType(type, size); diff --git a/src/main/java/org/javawebstack/orm/Repo.java b/src/main/java/org/javawebstack/orm/Repo.java index d86859e..018f7f9 100644 --- a/src/main/java/org/javawebstack/orm/Repo.java +++ b/src/main/java/org/javawebstack/orm/Repo.java @@ -101,8 +101,6 @@ public void save(T entry) { } public void create(T entry) { - if (info.getConfig().getInjector() != null) - info.getConfig().getInjector().inject(entry); observers.forEach(o -> o.saving(entry)); observers.forEach(o -> o.creating(entry)); executeCreate(entry); @@ -224,8 +222,6 @@ public Object getId(Object entity) { } public Repo observe(Observer observer) { - if (info.getConfig().getInjector() != null) - info.getConfig().getInjector().inject(observer); observers.add(observer); return this; } diff --git a/src/main/java/org/javawebstack/orm/SQLMapper.java b/src/main/java/org/javawebstack/orm/SQLMapper.java index 036ab20..10d841d 100644 --- a/src/main/java/org/javawebstack/orm/SQLMapper.java +++ b/src/main/java/org/javawebstack/orm/SQLMapper.java @@ -1,6 +1,5 @@ package org.javawebstack.orm; -import org.javawebstack.injector.Injector; import org.javawebstack.orm.exception.ORMQueryException; import org.javawebstack.orm.mapper.TypeMapper; @@ -28,14 +27,9 @@ public static List map(Repo repo, ResultSet rs, List model : joinedModels) { Repo r = Repo.get((Class) model); Model o = (Model) r.getInfo().getModelConstructor().newInstance(); - if (injector != null) - injector.inject(o); t.internalAddJoinedModel(model, mapBack(r, rs, o)); } list.add(mapBack(repo, rs, t)); From 6d2ed0c782cda2c69a459df65de83143b473082e Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 28 Apr 2021 23:32:14 +0200 Subject: [PATCH 33/45] Add script to start database docker container --- start_mariadb_test_server.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 start_mariadb_test_server.sh diff --git a/start_mariadb_test_server.sh b/start_mariadb_test_server.sh new file mode 100644 index 0000000..23e4b97 --- /dev/null +++ b/start_mariadb_test_server.sh @@ -0,0 +1 @@ +docker run -p 3306:3306 -e MYSQL_DATABASE=ormtest -e MYSQL_ROOT_PASSWORD=testpassword -d mariadb:10.5.9 \ No newline at end of file From 5c11dbe8281a29d273ff0e895fb072e594ed3628 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 28 Apr 2021 23:36:14 +0200 Subject: [PATCH 34/45] Fill OnlyIdModel --- .../orm/test/shared/models/OnlyIdModel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java b/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java index e69de29..efc4b35 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java +++ b/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java @@ -0,0 +1,11 @@ +package org.javawebstack.orm.test.shared.models; + +import org.javawebstack.orm.Model; +import org.javawebstack.orm.annotation.Column; + +public class OnlyIdModel extends Model { + + @Column + int id; + +} From a770745c8465177d0d4383eaf4e892ace60930a3 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 28 Apr 2021 23:46:52 +0200 Subject: [PATCH 35/45] Add Getter to OnlyIdModel --- .../org/javawebstack/orm/test/shared/models/OnlyIdModel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java b/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java index efc4b35..9579265 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java +++ b/src/test/java/org/javawebstack/orm/test/shared/models/OnlyIdModel.java @@ -1,8 +1,10 @@ package org.javawebstack.orm.test.shared.models; +import lombok.Getter; import org.javawebstack.orm.Model; import org.javawebstack.orm.annotation.Column; +@Getter public class OnlyIdModel extends Model { @Column From 2c5365c0e837c1e26cbda0bd0f64cc26a48209ca Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Wed, 28 Apr 2021 23:47:05 +0200 Subject: [PATCH 36/45] Add order by query execution test --- .../orm/test/queryexecution/OrderByTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/test/java/org/javawebstack/orm/test/queryexecution/OrderByTest.java diff --git a/src/test/java/org/javawebstack/orm/test/queryexecution/OrderByTest.java b/src/test/java/org/javawebstack/orm/test/queryexecution/OrderByTest.java new file mode 100644 index 0000000..cdc223a --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/queryexecution/OrderByTest.java @@ -0,0 +1,69 @@ +package org.javawebstack.orm.test.queryexecution; + +import org.javawebstack.orm.ORM; +import org.javawebstack.orm.Repo; +import org.javawebstack.orm.test.ORMTestCase; +import org.javawebstack.orm.test.shared.models.OnlyIdModel; +import org.javawebstack.orm.test.shared.setup.ModelSetup; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class OrderByTest extends ORMTestCase { + + @Test + void testOrderByCanPullResults() { + ModelSetup.setUpModel(OnlyIdModel.class); + ORM.autoMigrate(true); + + new OnlyIdModel().save(); + new OnlyIdModel().save(); + new OnlyIdModel().save(); + + assertDoesNotThrow(() -> Repo.get(OnlyIdModel.class) + .query() + .order("id") + .get() + ); + } + + @Test + void testOrderByWorksWithAsc() { + ModelSetup.setUpModel(OnlyIdModel.class); + ORM.autoMigrate(true); + + new OnlyIdModel().save(); + new OnlyIdModel().save(); + new OnlyIdModel().save(); + + List orderedList = Repo.get(OnlyIdModel.class) + .query() + .order("id", false) + .get(); + + for(int i = 1; i <= 3; i++) { + assertEquals(i, orderedList.get(i - 1).getId()); + } + } + + @Test + void testOrderByWorksWithDesc() { + ModelSetup.setUpModel(OnlyIdModel.class); + ORM.autoMigrate(true); + + new OnlyIdModel().save(); + new OnlyIdModel().save(); + new OnlyIdModel().save(); + + List orderedList = Repo.get(OnlyIdModel.class) + .query() + .order("id", true) + .get(); + + for(int i = 3; i >= 1; i--) { + assertEquals(i, orderedList.get(3 - i).getId()); + } + } +} From d464bc9d20da8d46559bae94acd199aba3b1f6e6 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Thu, 29 Apr 2021 00:22:42 +0200 Subject: [PATCH 37/45] Change query building tests appropriately --- .../test/querybuilding/OrderByClauseTest.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index d411536..1998364 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -21,50 +21,50 @@ public class OrderByClauseTest { @Test void testOneExistingColumnDefaultOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer"); + .order("wrapperInteger"); new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`"); } @Test void testOneNonExistingColumnDefaultOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("does_not_exist"); + .order("doesNotExist"); new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); } @Test void testOneExistingColumnASCOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer", false); + .order("wrapperInteger", false); new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`"); } @Test void testOneNonExistingColumnASCOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("does_not_exist", false); + .order("doesNotExist", false); new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); } @Test void testOneExistingColumnDESCOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer", true); + .order("wrapperInteger", true); new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` DESC"); } @Test void testOneNonExistingColumnDESCOrderBy() { Query query = setUpModel(Datatype.class).query() - .order("does_not_exist", true); + .order("doesNotExist", true); new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` DESC"); } @Test void testMultipleOrderByClausesOfASCOrder() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer") - .order("primitive_integer"); + .order("wrapperInteger") + .order("primitiveInteger"); new QueryVerification(query) .assertSectionContains("ORDER BY", "`wrapper_integer`") @@ -74,8 +74,8 @@ void testMultipleOrderByClausesOfASCOrder() { @Test void testMultipleOrderByClausesOfDESCOrder() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer", true) - .order("primitive_integer", true); + .order("wrapperInteger", true) + .order("primitiveInteger", true); new QueryVerification(query) .assertSectionContains("ORDER BY", "`wrapper_integer` DESC") @@ -85,8 +85,8 @@ void testMultipleOrderByClausesOfDESCOrder() { @Test void testMultipleOrderByClausesOfMixedOrder() { Query query = setUpModel(Datatype.class).query() - .order("wrapper_integer", false) - .order("primitive_integer", true); + .order("wrapperInteger", false) + .order("primitiveInteger", true); new QueryVerification(query) .assertSectionContains("ORDER BY", "`wrapper_integer`") @@ -96,8 +96,8 @@ void testMultipleOrderByClausesOfMixedOrder() { @Test void testMultipleOrderByClausesOfMixedOrderReversed() { Query query = setUpModel(Datatype.class).query() - .order("primitive_integer", true) - .order("wrapper_integer", false); + .order("primitiveInteger", true) + .order("wrapperInteger", false); new QueryVerification(query) .assertSectionContains("ORDER BY", "`primitive_integer` DESC") @@ -108,6 +108,7 @@ void testMultipleOrderByClausesOfMixedOrderReversed() { @Test // This test is important because putting the order by statements in different order is relevant (they set priorities) void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionIndexOutOfBoundException { + // This test does not use camel cases as input Query query = setUpModel(Datatype.class).query(); ArrayList columnNames = new ArrayList<>(Datatype.columnNames); @@ -149,8 +150,8 @@ void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionInde @Test void testCannotCallOrderOnSameColumnTwice() { Query query = setUpModel(Datatype.class).query() - .order("primitive_integer", true); + .order("primitiveInteger", true); - assertThrows(ORMQueryException.class, () -> query.order("primitive_integer")); + assertThrows(ORMQueryException.class, () -> query.order("primitiveInteger")); } } From 1e9baf49c0d95c7d7d58aa55bce3d453f674c4d9 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Thu, 29 Apr 2021 00:23:35 +0200 Subject: [PATCH 38/45] Fix minor issues --- .../orm/test/querybuilding/OrderByClauseTest.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index 1998364..c29f6c2 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -6,17 +6,12 @@ import org.javawebstack.orm.test.shared.models.Datatype; import org.javawebstack.orm.test.shared.verification.QueryVerification; import org.junit.jupiter.api.Test; - -import javax.xml.crypto.Data; - import java.util.*; -import java.util.stream.Collectors; - import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel; import static org.junit.jupiter.api.Assertions.*; // This class tests the query generation for order by statements an MySQL -public class OrderByClauseTest { +class OrderByClauseTest { @Test void testOneExistingColumnDefaultOrderBy() { @@ -122,7 +117,7 @@ void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionInde String queryString = new QueryVerification(query).getSection("ORDER BY"); int lastIndex = 0; - int foundIndex = -1; + int foundIndex; for (String nextInCallOrder : callOrder) { foundIndex = queryString.indexOf("`" + nextInCallOrder + "`"); if(foundIndex < lastIndex) { From 335f6335763985b35941a80b585a3e08ac12d51c Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Thu, 29 Apr 2021 00:38:24 +0200 Subject: [PATCH 39/45] Convert column names to snake case --- src/main/java/org/javawebstack/orm/query/QueryColumn.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryColumn.java b/src/main/java/org/javawebstack/orm/query/QueryColumn.java index 651c19d..9155683 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryColumn.java +++ b/src/main/java/org/javawebstack/orm/query/QueryColumn.java @@ -2,6 +2,7 @@ import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.exception.ORMQueryException; +import org.javawebstack.orm.util.Helper; import java.util.Arrays; import java.util.Objects; @@ -41,7 +42,7 @@ public String toString() { public String toString(TableInfo info) { if (raw) return name; - return Arrays.stream((info != null ? info.getColumnName(name) : name).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining(".")); + return Arrays.stream((info != null ? info.getColumnName(Helper.toSnakeCase(name)) : Helper.toSnakeCase(name)).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining(".")); } private static void validateName(String name) { From f52c9e334157ea1c139a2d671c39a4a137cd05c0 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 15:38:27 +0200 Subject: [PATCH 40/45] Revert "Convert column names to snake case" This reverts commit 335f6335763985b35941a80b585a3e08ac12d51c. --- src/main/java/org/javawebstack/orm/query/QueryColumn.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryColumn.java b/src/main/java/org/javawebstack/orm/query/QueryColumn.java index 9155683..651c19d 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryColumn.java +++ b/src/main/java/org/javawebstack/orm/query/QueryColumn.java @@ -2,7 +2,6 @@ import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.exception.ORMQueryException; -import org.javawebstack.orm.util.Helper; import java.util.Arrays; import java.util.Objects; @@ -42,7 +41,7 @@ public String toString() { public String toString(TableInfo info) { if (raw) return name; - return Arrays.stream((info != null ? info.getColumnName(Helper.toSnakeCase(name)) : Helper.toSnakeCase(name)).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining(".")); + return Arrays.stream((info != null ? info.getColumnName(name) : name).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining(".")); } private static void validateName(String name) { From 09dcc4c382f8d7d6b8805e54b3af7aacf736f7bd Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 15:45:21 +0200 Subject: [PATCH 41/45] Correct spelling mistake in class name --- .../javawebstack/orm/test/querybuilding/FromClauseTest.java | 3 +-- .../{OverwritteTableName.java => OverwrittenTableName.java} | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) rename src/test/java/org/javawebstack/orm/test/shared/models/tablenames/{OverwritteTableName.java => OverwrittenTableName.java} (87%) 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 c24faf9..0963e9b 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/FromClauseTest.java @@ -1,6 +1,5 @@ package org.javawebstack.orm.test.querybuilding; -import org.atteo.evo.inflector.English; import org.javawebstack.orm.Model; import org.javawebstack.orm.ORM; import org.javawebstack.orm.Repo; @@ -55,7 +54,7 @@ void testOneWordAlreadyInPluralDoesntWork() throws ORMConfigurationException { @Test void testOverwrittenTableName() throws ORMConfigurationException { - String query = getBaseQuery(OverwritteTableName.class); + String query = getBaseQuery(OverwrittenTableName.class); assertTrue(query.contains("FROM `oVer_writtenValue`")); } diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwritteTableName.java b/src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwrittenTableName.java similarity index 87% rename from src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwritteTableName.java rename to src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwrittenTableName.java index 7b4413b..a1d139e 100644 --- a/src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwritteTableName.java +++ b/src/test/java/org/javawebstack/orm/test/shared/models/tablenames/OverwrittenTableName.java @@ -8,7 +8,7 @@ * This class overwrites the model name to a seemingly random word to test multiple cases at once. */ @Table("oVer_writtenValue") -public class OverwritteTableName extends Model { +public class OverwrittenTableName extends Model { @Column int id; } From e625333b5beb44181d4d9a50e1508f23a7a68bb5 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 16:01:13 +0200 Subject: [PATCH 42/45] Add test for overwritten column names --- .../orm/test/querybuilding/OrderByClauseTest.java | 7 +++++++ .../models/columnnames/OverwrittenColumnName.java | 12 ++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/test/java/org/javawebstack/orm/test/shared/models/columnnames/OverwrittenColumnName.java diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index c29f6c2..2db6476 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -137,6 +137,13 @@ void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionInde } + @Test + void testWillUseOverwrittenColumnName() { + Query query = setUpModel(OverwrittenColumnName.class).query() + .order("dummyString"); + new QueryVerification(query).assertSectionEquals("ORDER BY", "`oVer_writtenColumn-name`"); + } + /* * Error Cases */ diff --git a/src/test/java/org/javawebstack/orm/test/shared/models/columnnames/OverwrittenColumnName.java b/src/test/java/org/javawebstack/orm/test/shared/models/columnnames/OverwrittenColumnName.java new file mode 100644 index 0000000..d4608e6 --- /dev/null +++ b/src/test/java/org/javawebstack/orm/test/shared/models/columnnames/OverwrittenColumnName.java @@ -0,0 +1,12 @@ +package org.javawebstack.orm.test.shared.models.columnnames; + +import org.javawebstack.orm.Model; +import org.javawebstack.orm.annotation.Column; + +public class OverwrittenColumnName extends Model { + @Column + int id; + + @Column(name = "oVer_writtenColumn-name") + String dummyString; +} From 2e3515569e128021ff1e5eabcd6324a4dcadeaf5 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 16:01:39 +0200 Subject: [PATCH 43/45] Adjust tests to reflect non snake case of non existant columns --- .../orm/test/querybuilding/OrderByClauseTest.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java index 2db6476..d3aadb2 100644 --- a/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java +++ b/src/test/java/org/javawebstack/orm/test/querybuilding/OrderByClauseTest.java @@ -4,6 +4,7 @@ import org.javawebstack.orm.query.Query; import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException; import org.javawebstack.orm.test.shared.models.Datatype; +import org.javawebstack.orm.test.shared.models.columnnames.OverwrittenColumnName; import org.javawebstack.orm.test.shared.verification.QueryVerification; import org.junit.jupiter.api.Test; import java.util.*; @@ -24,7 +25,9 @@ void testOneExistingColumnDefaultOrderBy() { void testOneNonExistingColumnDefaultOrderBy() { Query query = setUpModel(Datatype.class).query() .order("doesNotExist"); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); + + // Not in snake case as it is not in the mapping + new QueryVerification(query).assertSectionEquals("ORDER BY", "`doesNotExist`"); } @Test @@ -38,7 +41,9 @@ void testOneExistingColumnASCOrderBy() { void testOneNonExistingColumnASCOrderBy() { Query query = setUpModel(Datatype.class).query() .order("doesNotExist", false); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`"); + + // Not in snake case as it is not in the mapping + new QueryVerification(query).assertSectionEquals("ORDER BY", "`doesNotExist`"); } @Test @@ -52,7 +57,8 @@ void testOneExistingColumnDESCOrderBy() { void testOneNonExistingColumnDESCOrderBy() { Query query = setUpModel(Datatype.class).query() .order("doesNotExist", true); - new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` DESC"); + // Not in snake case as it is not in the mapping + new QueryVerification(query).assertSectionEquals("ORDER BY", "`doesNotExist` DESC"); } @Test From 15db24b432863cf4054f9e32b049e451884e26b9 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 16:02:52 +0200 Subject: [PATCH 44/45] Fix order by naming bug --- src/main/java/org/javawebstack/orm/query/QueryOrderBy.java | 2 +- .../orm/wrapper/builder/MySQLQueryStringBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java index 7cc6e24..f741993 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java @@ -64,7 +64,7 @@ public String toString() { public String toString(TableInfo info) { return this.stream() - .map(QueryOrderByElement::toString) + .map((singleOrderByElement) -> singleOrderByElement.toString(info)) .collect(Collectors.joining(",")); } } 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 3b1faef..6bc6588 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -54,7 +54,7 @@ public SQLQueryString buildQuery(Query query, boolean count) { QueryOrderBy orderBy = query.getOrder(); if (!orderBy.isEmpty()) { sb.append(" ORDER BY ") - .append(orderBy.toString()); + .append(orderBy.toString(repo.getInfo())); } Integer offset = query.getOffset(); From 912bd78c859f52209294564239399ac6e838fe40 Mon Sep 17 00:00:00 2001 From: TimothyGillespie Date: Fri, 30 Apr 2021 16:08:26 +0200 Subject: [PATCH 45/45] Address sonar cube concerns --- .../org/javawebstack/orm/query/QueryOrderBy.java | 2 +- .../wrapper/builder/MySQLQueryStringBuilder.java | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java index f741993..a58925c 100644 --- a/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java +++ b/src/main/java/org/javawebstack/orm/query/QueryOrderBy.java @@ -64,7 +64,7 @@ public String toString() { public String toString(TableInfo info) { return this.stream() - .map((singleOrderByElement) -> singleOrderByElement.toString(info)) + .map(singleOrderByElement -> singleOrderByElement.toString(info)) .collect(Collectors.joining(",")); } } 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 6bc6588..5e85191 100644 --- a/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java +++ b/src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java @@ -5,7 +5,6 @@ import org.javawebstack.orm.TableInfo; import org.javawebstack.orm.query.*; -import java.lang.reflect.Field; import java.sql.Timestamp; import java.time.Instant; import java.util.*; @@ -23,10 +22,10 @@ public SQLQueryString buildInsert(TableInfo info, Map values) { sb.append("` ("); List cols = new ArrayList<>(); List vals = new ArrayList<>(); - for (String columnName : values.keySet()) { - cols.add("`" + columnName + "`"); + for (Map.Entry columnValueMapping : values.entrySet()) { + cols.add("`" + columnValueMapping.getKey() + "`"); vals.add("?"); - params.add(values.get(columnName)); + params.add(columnValueMapping.getValue()); } sb.append(String.join(",", cols)); sb.append(") VALUES ("); @@ -45,7 +44,7 @@ public SQLQueryString buildQuery(Query query, boolean count) { .append('`'); QueryGroup where = query.getWhereGroup(); checkWithDeleted(repo, query.isWithDeleted(), where); - if (where.getQueryElements().size() > 0) { + if (!where.getQueryElements().isEmpty()) { SQLQueryString qs = convertGroup(repo.getInfo(), where); sb.append(" WHERE ").append(qs.getQuery()); parameters.addAll(qs.getParameters()); @@ -88,7 +87,7 @@ public SQLQueryString buildUpdate(Query query, Map values) { .append(String.join(",", sets)); QueryGroup where = query.getWhereGroup(); checkWithDeleted(repo, query.isWithDeleted(), where); - if (where.getQueryElements().size() > 0) { + if (!where.getQueryElements().isEmpty()) { SQLQueryString qs = convertGroup(repo.getInfo(), where); sb.append(" WHERE ").append(qs.getQuery()); parameters.addAll(qs.getParameters()); @@ -104,7 +103,7 @@ public SQLQueryString buildDelete(Query query) { StringBuilder sb = new StringBuilder("DELETE FROM `") .append(repo.getInfo().getTableName()) .append('`'); - if (where.getQueryElements().size() > 0) { + if (!where.getQueryElements().isEmpty()) { SQLQueryString qs = convertGroup(repo.getInfo(), where); sb.append(" WHERE ").append(qs.getQuery()); parameters = qs.getParameters(); @@ -114,7 +113,7 @@ public SQLQueryString buildDelete(Query query) { private void checkWithDeleted(Repo repo, boolean withDeleted, QueryGroup where) { if (repo.getInfo().isSoftDelete() && !withDeleted) { - if (where.getQueryElements().size() > 0) + if (!where.getQueryElements().isEmpty()) where.getQueryElements().add(0, QueryConjunction.AND); where.getQueryElements().add(0, new QueryCondition(new QueryColumn(repo.getInfo().getColumnName(repo.getInfo().getSoftDeleteField())), "IS NULL", null)); }