Skip to content

Commit 0037a3a

Browse files
Merge branch 'TimothyGillespie/enableMultipleOrderBy' into TimothyGillespie/addOrderByTests
2 parents e4d3fc0 + 17a7060 commit 0037a3a

File tree

21 files changed

+500
-183
lines changed

21 files changed

+500
-183
lines changed

.github/workflows/maven-test.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Maven Test
2+
on:
3+
push:
4+
branches:
5+
- '**'
6+
- '!master'
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
services:
12+
mysql:
13+
image: mariadb:latest
14+
env:
15+
MYSQL_ALLOW_EMPTY_PASSWORD: yes
16+
MYSQL_DATABASE: test
17+
MYSQL_USER: test
18+
MYSQL_PASSWORD: test
19+
MYSQL_RANDOM_ROOT_PASSWORD: yes
20+
ports:
21+
- 3306
22+
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
23+
steps:
24+
- uses: actions/checkout@v2
25+
- name: Set up JDK 1.8
26+
uses: actions/setup-java@v1
27+
with:
28+
java-version: 1.8
29+
- name: Test
30+
run: mvn -B test
31+
env:
32+
MYSQL_PORT: ${{ job.services.mysql.ports[3306] }}
33+
MYSQL_USERNAME: test
34+
MYSQL_PASSWORD: test

src/main/java/org/javawebstack/orm/Model.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class Model {
4141
private transient boolean internalEntryExists = false;
4242
private transient final Map<Class<? extends Model>, Object> internalJoinedModels = new HashMap<>();
4343
private transient Map<String, Object> internalOriginalValues = new HashMap<>();
44+
private transient Map<String, Object> internalExtraFields = new HashMap<>();
4445

4546
void internalAddJoinedModel(Class<? extends Model> type, Object entity) {
4647
internalJoinedModels.put(type, entity);
@@ -62,6 +63,14 @@ public Map<String, Object> getFieldValues() {
6263
return values;
6364
}
6465

66+
public Map<String, Object> getExtraFields() {
67+
return internalExtraFields;
68+
}
69+
70+
public <T> T getExtraField(String key) {
71+
return (T) internalExtraFields.get(key);
72+
}
73+
6574
public Map<String, Object> getOriginalValues() {
6675
return internalOriginalValues;
6776
}

src/main/java/org/javawebstack/orm/Repo.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.javawebstack.orm.migration.AutoMigrator;
88
import org.javawebstack.orm.query.Query;
99
import org.javawebstack.orm.wrapper.SQL;
10+
import org.javawebstack.orm.wrapper.builder.SQLQueryString;
1011

1112
import java.lang.reflect.Field;
1213
import java.sql.SQLException;
@@ -124,28 +125,14 @@ private void executeCreate(T entry) {
124125
if (field.get(entry) == null)
125126
field.set(entry, UUID.randomUUID());
126127
}
127-
List<Object> params = new ArrayList<>();
128-
StringBuilder sb = new StringBuilder("INSERT INTO `");
129-
sb.append(info.getTableName());
130-
sb.append("` (");
131-
List<String> cols = new ArrayList<>();
132-
List<String> values = new ArrayList<>();
133128
Map<String, Object> map = SQLMapper.map(this, entry);
134129
if (info.isAutoIncrement()) {
135130
String idCol = info.getColumnName(info.getIdField());
136131
if (map.containsKey(idCol) && map.get(idCol) == null)
137132
map.remove(idCol);
138133
}
139-
for (String columnName : map.keySet()) {
140-
cols.add("`" + columnName + "`");
141-
values.add("?");
142-
params.add(map.get(columnName));
143-
}
144-
sb.append(String.join(",", cols));
145-
sb.append(") VALUES (");
146-
sb.append(String.join(",", values));
147-
sb.append(");");
148-
int id = connection.write(sb.toString(), params.toArray());
134+
SQLQueryString qs = getConnection().builder().buildInsert(info, map);
135+
int id = connection.write(qs.getQuery(), qs.getParameters().toArray());
149136
if (info.isAutoIncrement())
150137
info.getField(info.getIdField()).set(entry, id);
151138
entry.setEntryExists(true);

src/main/java/org/javawebstack/orm/query/Query.java

Lines changed: 62 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.javawebstack.orm.Repo;
55
import org.javawebstack.orm.SQLMapper;
66
import org.javawebstack.orm.exception.ORMQueryException;
7+
import org.javawebstack.orm.wrapper.builder.SQLQueryString;
78

89
import java.sql.ResultSet;
910
import java.sql.SQLException;
@@ -23,10 +24,9 @@ public class Query<T extends Model> {
2324
private final QueryGroup<T> where;
2425
private Integer offset;
2526
private Integer limit;
26-
private QueryColumn order;
27-
private boolean desc = false;
27+
private QueryOrderBy order;
2828
private boolean withDeleted = false;
29-
private final Map<Class<? extends Model>, QueryCondition> leftJoins = new HashMap<>();
29+
private final List<QueryWith> withs = new ArrayList<>();
3030

3131
public Query(Class<T> model) {
3232
this(Repo.get(model), model);
@@ -36,14 +36,47 @@ public Query(Repo<T> repo, Class<T> model) {
3636
this.repo = repo;
3737
this.model = model;
3838
this.where = new QueryGroup<>();
39+
this.order = new QueryOrderBy();
40+
}
41+
42+
public boolean isWithDeleted() {
43+
return withDeleted;
44+
}
45+
46+
public QueryGroup<T> getWhereGroup() {
47+
return where;
48+
}
49+
50+
public List<QueryWith> getWiths() {
51+
return withs;
52+
}
53+
54+
public Integer getLimit() {
55+
return limit;
56+
}
57+
58+
public Integer getOffset() {
59+
return offset;
60+
}
61+
62+
public QueryOrderBy getOrder() {
63+
return order;
64+
}
65+
66+
public Repo<T> getRepo() {
67+
return repo;
3968
}
4069

4170
public Class<T> getModel() {
4271
return model;
4372
}
4473

45-
public Query<T> leftJoin(Class<? extends Model> model, String self, String other) {
46-
leftJoins.put(model, new QueryCondition(new QueryColumn(repo.getInfo().getTableName() + "." + self), "=", new QueryColumn(Repo.get(model).getInfo().getTableName() + "." + other)));
74+
public Query<T> with(String extra) {
75+
return with(extra, null);
76+
}
77+
78+
public Query<T> with(String extra, String as) {
79+
withs.add(new QueryWith(extra, as));
4780
return this;
4881
}
4982

@@ -263,18 +296,25 @@ public Query<T> search(String search) {
263296
return this;
264297
}
265298

299+
public Query<T> order(String orderBy) {
300+
return order(orderBy, false);
301+
}
302+
266303
public Query<T> order(String orderBy, boolean desc) {
267304
return order(new QueryColumn(orderBy), desc);
268305
}
269306

270307
public Query<T> order(QueryColumn orderBy, boolean desc) {
271-
this.order = orderBy;
272-
this.desc = desc;
273-
return this;
274-
}
308+
boolean success = this.order.add(orderBy, desc);
309+
if(!success) {
310+
throw new ORMQueryException(String.format(
311+
"The column %s could not be ordered %s. This is probably caused by calling .order() on this column twice.",
312+
orderBy.toString(),
313+
desc ? "descendingly" : "ascendingly"
314+
));
315+
}
275316

276-
public Query<T> order(String orderBy) {
277-
return order(orderBy, false);
317+
return this;
278318
}
279319

280320
public Query<T> limit(int offset, int limit) {
@@ -296,59 +336,10 @@ public Query<T> withDeleted() {
296336
return this;
297337
}
298338

299-
public QueryString getQueryString() {
300-
return getQueryString(false);
301-
}
302-
303-
public QueryString getQueryString(boolean count) {
304-
List<Object> parameters = new ArrayList<>();
305-
StringBuilder sb = new StringBuilder("SELECT ")
306-
.append(count ? "COUNT(*)" : "*")
307-
.append(" FROM `")
308-
.append(repo.getInfo().getTableName())
309-
.append('`');
310-
for (Class<? extends Model> type : leftJoins.keySet()) {
311-
sb.append(" LEFT JOIN `")
312-
.append(Repo.get(type).getInfo().getTableName())
313-
.append("` ON ")
314-
.append(leftJoins.get(type).getQueryString(repo.getInfo()).getQuery());
315-
}
316-
considerSoftDelete();
317-
if (where.getQueryElements().size() > 0) {
318-
QueryString qs = where.getQueryString(repo.getInfo());
319-
sb.append(" WHERE ").append(qs.getQuery());
320-
parameters.addAll(qs.getParameters());
321-
}
322-
if (order != null) {
323-
sb.append(" ORDER BY ").append(order.toString(repo.getInfo()));
324-
if (desc)
325-
sb.append(" DESC");
326-
}
327-
if (offset != null && limit == null)
328-
limit = Integer.MAX_VALUE;
329-
if (limit != null) {
330-
sb.append(" LIMIT ?");
331-
if (offset != null) {
332-
sb.append(",?");
333-
parameters.add(offset);
334-
}
335-
parameters.add(limit);
336-
}
337-
return new QueryString(sb.toString(), SQLMapper.mapParams(repo, parameters));
338-
}
339-
340339
public void finalDelete() {
341-
List<Object> parameters = new ArrayList<>();
342-
StringBuilder sb = new StringBuilder("DELETE FROM `")
343-
.append(repo.getInfo().getTableName())
344-
.append('`');
345-
if (where.getQueryElements().size() > 0) {
346-
QueryString qs = where.getQueryString(repo.getInfo());
347-
sb.append(" WHERE ").append(qs.getQuery());
348-
parameters = qs.getParameters();
349-
}
340+
SQLQueryString qs = repo.getConnection().builder().buildDelete(this);
350341
try {
351-
repo.getConnection().write(sb.toString(), SQLMapper.mapParams(repo, parameters).toArray());
342+
repo.getConnection().write(qs.getQuery(), qs.getParameters().toArray());
352343
} catch (SQLException throwables) {
353344
throw new ORMQueryException(throwables);
354345
}
@@ -374,18 +365,10 @@ public void restore() {
374365
withDeleted().update(values);
375366
}
376367

377-
private void considerSoftDelete() {
378-
if (repo.getInfo().isSoftDelete() && !withDeleted) {
379-
if (where.getQueryElements().size() > 0)
380-
where.getQueryElements().add(0, QueryConjunction.AND);
381-
where.getQueryElements().add(0, new QueryCondition(new QueryColumn(repo.getInfo().getColumnName(repo.getInfo().getSoftDeleteField())), "IS NULL", null));
382-
}
383-
}
384-
385368
public T refresh(T entity) {
386-
QueryString qs = getQueryString(false);
369+
SQLQueryString qs = repo.getConnection().builder().buildQuery(this, false);
387370
try {
388-
ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, SQLMapper.mapParams(repo, qs.getParameters())).toArray());
371+
ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray());
389372
SQLMapper.mapBack(repo, rs, entity);
390373
repo.getConnection().close(rs);
391374
return entity;
@@ -399,39 +382,19 @@ public void update(T entity) {
399382
}
400383

401384
public void update(Map<String, Object> values) {
402-
if (repo.getInfo().hasUpdated())
403-
values.put(repo.getInfo().getColumnName(repo.getInfo().getUpdatedField()), Timestamp.from(Instant.now()));
404-
List<Object> parameters = new ArrayList<>();
405-
List<String> sets = new ArrayList<>();
406-
values.forEach((key, value) -> {
407-
sets.add("`" + key + "`=?");
408-
parameters.add(value);
409-
});
410-
StringBuilder sb = new StringBuilder("UPDATE `")
411-
.append(repo.getInfo().getTableName())
412-
.append("` SET ")
413-
.append(String.join(",", sets));
414-
considerSoftDelete();
415-
if (where.getQueryElements().size() > 0) {
416-
QueryString qs = where.getQueryString(repo.getInfo());
417-
sb.append(" WHERE ").append(qs.getQuery());
418-
parameters.addAll(qs.getParameters());
419-
}
420-
sb.append(';');
385+
SQLQueryString queryString = repo.getConnection().builder().buildUpdate(this, values);
421386
try {
422-
repo.getConnection().write(sb.toString(), SQLMapper.mapParams(repo, parameters).toArray());
387+
repo.getConnection().write(queryString.getQuery(), queryString.getParameters().toArray());
423388
} catch (SQLException throwables) {
424389
throw new ORMQueryException(throwables);
425390
}
426391
}
427392

428393
public List<T> all() {
429-
QueryString qs = getQueryString(false);
394+
SQLQueryString qs = repo.getConnection().builder().buildQuery(this, false);
430395
try {
431-
ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, qs.getParameters()).toArray());
432-
List<Class<? extends Model>> joinedModels = new ArrayList<>();
433-
joinedModels.addAll(leftJoins.keySet());
434-
List<T> list = SQLMapper.map(repo, rs, joinedModels);
396+
ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray());
397+
List<T> list = SQLMapper.map(repo, rs, new ArrayList<>());
435398
repo.getConnection().close(rs);
436399
return list;
437400
} catch (SQLException throwables) {
@@ -455,9 +418,9 @@ public Stream<T> stream() {
455418
}
456419

457420
public int count() {
458-
QueryString qs = getQueryString(true);
421+
SQLQueryString qs = repo.getConnection().builder().buildQuery(this, true);
459422
try {
460-
ResultSet rs = repo.getConnection().read(qs.getQuery(), SQLMapper.mapParams(repo, qs.getParameters()).toArray());
423+
ResultSet rs = repo.getConnection().read(qs.getQuery(), qs.getParameters().toArray());
461424
int c = 0;
462425
if (rs.next())
463426
c = rs.getInt(1);

src/main/java/org/javawebstack/orm/query/QueryColumn.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package org.javawebstack.orm.query;
22

33
import org.javawebstack.orm.TableInfo;
4+
import org.javawebstack.orm.exception.ORMQueryException;
45

56
import java.util.Arrays;
7+
import java.util.Objects;
8+
import java.util.regex.Pattern;
69
import java.util.stream.Collectors;
710

811
public class QueryColumn {
912

13+
private static final Pattern NAME_PATTERN = Pattern.compile("[A-Za-z0-9_\\-.]+");
14+
1015
private final String name;
1116
private final boolean raw;
1217

@@ -15,6 +20,8 @@ public QueryColumn(String name) {
1520
}
1621

1722
public QueryColumn(String name, boolean raw) {
23+
if(!raw)
24+
validateName(name);
1825
this.name = name;
1926
this.raw = raw;
2027
}
@@ -37,4 +44,21 @@ public String toString(TableInfo info) {
3744
return Arrays.stream((info != null ? info.getColumnName(name) : name).split("\\.")).map(s -> "`" + s + "`").collect(Collectors.joining("."));
3845
}
3946

47+
private static void validateName(String name) {
48+
if(!NAME_PATTERN.matcher(name).matches())
49+
throw new ORMQueryException("Invalid column name '" + name + "' (Use raw in case you know what you're doing)");
50+
}
51+
52+
@Override
53+
public boolean equals(Object o) {
54+
if (this == o) return true;
55+
if (o == null || getClass() != o.getClass()) return false;
56+
QueryColumn that = (QueryColumn) o;
57+
return toString().equals(that.toString());
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(toString());
63+
}
4064
}

0 commit comments

Comments
 (0)