Skip to content

Commit 480b883

Browse files
Merge branch 'master' into TimothyGillespie/addOrderByTests
2 parents b527ee2 + cfb3beb commit 480b883

File tree

8 files changed

+471
-21
lines changed

8 files changed

+471
-21
lines changed

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,18 @@
7979
<version>5.4.2</version>
8080
<scope>test</scope>
8181
</dependency>
82+
<dependency>
83+
<groupId>org.apache.commons</groupId>
84+
<artifactId>commons-lang3</artifactId>
85+
<version>3.5</version>
86+
<scope>test</scope>
87+
</dependency>
88+
<dependency>
89+
<groupId>org.projectlombok</groupId>
90+
<artifactId>lombok</artifactId>
91+
<version>1.18.16</version>
92+
<scope>test</scope>
93+
</dependency>
8294
</dependencies>
8395

8496
<distributionManagement>

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

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.lang.reflect.Field;
77
import java.lang.reflect.InvocationTargetException;
88
import java.lang.reflect.Method;
9+
import java.security.InvalidParameterException;
910
import java.util.ArrayList;
1011
import java.util.HashMap;
1112
import java.util.List;
@@ -165,26 +166,40 @@ public <T extends Model> Query<T> belongsTo(Class<T> parent) {
165166
}
166167

167168
public <T extends Model> Query<T> belongsTo(Class<T> parent, String fieldName) {
169+
return belongsTo(parent, fieldName, Repo.get(parent).getInfo().getIdField());
170+
}
171+
172+
public <T extends Model> Query<T> belongsTo(Class<T> parent, String fieldName, String otherFieldName) {
168173
try {
169174
Object id = Repo.get(getClass()).getInfo().getField(fieldName).get(this);
170-
return Repo.get(parent).whereId(id);
175+
return Repo.get(parent).where(otherFieldName, id);
171176
} catch (IllegalAccessException e) {
172177
throw new RuntimeException(e);
173178
}
174179
}
175180

181+
public <T extends Model> void assignTo(T value) {
182+
if(value == null)
183+
throw new InvalidParameterException("You need to specify a parent type if the value is null");
184+
assignTo((Class<T>) value.getClass(), value);
185+
}
186+
176187
public <T extends Model> void assignTo(Class<T> parent, T value) {
177188
assignTo(parent, value, Repo.get(parent).getInfo().getRelationField());
178189
}
179190

180191
public <T extends Model> void assignTo(Class<T> parent, T value, String fieldName) {
192+
assignTo(parent, value, fieldName, Repo.get(parent).getInfo().getIdField());
193+
}
194+
195+
public <T extends Model> void assignTo(Class<T> parent, T value, String fieldName, String otherFieldName) {
181196
try {
182197
Field f = Repo.get(getClass()).getInfo().getField(fieldName);
183198
if (value == null) {
184199
f.set(this, null);
185200
} else {
186201
Repo<T> repo = Repo.get(parent);
187-
Object id = repo.getInfo().getField(repo.getInfo().getIdField()).get(value);
202+
Object id = repo.getInfo().getField(otherFieldName).get(value);
188203
f.set(this, id);
189204
}
190205
} catch (IllegalAccessException e) {
@@ -197,9 +212,13 @@ public <T extends Model> Query<T> hasMany(Class<T> child) {
197212
}
198213

199214
public <T extends Model> Query<T> hasMany(Class<T> child, String fieldName) {
215+
return hasMany(child, fieldName, Repo.get(getClass()).getInfo().getIdField());
216+
}
217+
218+
public <T extends Model> Query<T> hasMany(Class<T> child, String fieldName, String ownFieldName) {
200219
try {
201220
Repo<?> ownRepo = Repo.get(getClass());
202-
Object id = ownRepo.getInfo().getField(ownRepo.getInfo().getIdField()).get(this);
221+
Object id = ownRepo.getInfo().getField(ownFieldName).get(this);
203222
return Repo.get(child).where(fieldName, id);
204223
} catch (IllegalAccessException e) {
205224
throw new RuntimeException(e);
@@ -214,17 +233,25 @@ public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other,
214233
return belongsToMany(other, pivot, Repo.get(getClass()).getInfo().getRelationField(), Repo.get(other).getInfo().getRelationField(), pivotFilter);
215234
}
216235

217-
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfFieldName, String otherFieldName) {
218-
return belongsToMany(other, pivot, selfFieldName, otherFieldName, null);
236+
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfPivotFieldName, String otherPivotFieldName) {
237+
return belongsToMany(other, pivot, selfPivotFieldName, otherPivotFieldName, null);
238+
}
239+
240+
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfPivotFieldName, String otherPivotFieldName, Function<Query<P>, Query<P>> pivotFilter) {
241+
return belongsToMany(other, pivot, selfPivotFieldName, otherPivotFieldName, Repo.get(getClass()).getInfo().getIdField(), Repo.get(other).getInfo().getIdField(), pivotFilter);
219242
}
220243

221-
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfFieldName, String otherFieldName, Function<Query<P>, Query<P>> pivotFilter) {
244+
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfPivotFieldName, String otherPivotFieldName, String selfFieldName, String otherFieldName) {
245+
return belongsToMany(other, pivot, selfPivotFieldName, otherPivotFieldName, selfFieldName, otherFieldName, null);
246+
}
247+
248+
public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other, Class<P> pivot, String selfPivotFieldName, String otherPivotFieldName, String selfFieldName, String otherFieldName, Function<Query<P>, Query<P>> pivotFilter) {
222249
try {
223250
Repo<?> selfRepo = Repo.get(getClass());
224251
Repo<T> otherRepo = Repo.get(other);
225-
Object id = selfRepo.getInfo().getField(selfRepo.getInfo().getIdField()).get(this);
252+
Object id = selfRepo.getInfo().getField(selfFieldName).get(this);
226253
return otherRepo.whereExists(pivot, q -> {
227-
q.where(pivot, selfFieldName, "=", id).where(pivot, otherFieldName, "=", other, otherRepo.getInfo().getIdColumn());
254+
q.where(pivot, selfPivotFieldName, "=", id).where(pivot, otherPivotFieldName, "=", other, otherFieldName);
228255
if (pivotFilter != null)
229256
q = pivotFilter.apply(q);
230257
return q;
@@ -234,17 +261,4 @@ public <T extends Model, P extends Model> Query<T> belongsToMany(Class<T> other,
234261
}
235262
}
236263

237-
public void setMorph(String name, Class<? extends Model> type, Object id) {
238-
TableInfo info = Repo.get(getClass()).getInfo();
239-
try {
240-
info.getField(name + "Id").set(this, id);
241-
info.getField(name + "Type").set(this, Repo.get(type).getInfo().getMorphType());
242-
} catch (IllegalAccessException ignored) {
243-
}
244-
}
245-
246-
public void setMorph(String name, Model model) {
247-
setMorph(name, model.getClass(), Repo.get(model.getClass()).getId(model));
248-
}
249-
250264
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ public Query<T> notNull(Object left) {
164164
where.notNull(left);
165165
return this;
166166
}
167+
168+
public Query<T> whereNull(Object left) {
169+
where.isNull(left);
170+
return this;
171+
}
172+
173+
public Query<T> whereNotNull(Object left) {
174+
where.notNull(left);
175+
return this;
176+
}
167177

168178
public Query<T> lessThan(Object left, Object right) {
169179
where.lessThan(left, right);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ public QueryGroup<T> isNull(Object left) {
7979
public QueryGroup<T> notNull(Object left) {
8080
return where(left, "IS NOT NULL", null);
8181
}
82+
83+
public QueryGroup<T> whereNull(Object left) {
84+
return where(left, "IS NULL", null);
85+
}
86+
87+
public QueryGroup<T> whereNotNull(Object left) {
88+
return where(left, "IS NOT NULL", null);
89+
}
8290

8391
public QueryGroup<T> lessThan(Object left, Object right) {
8492
return where(left, "<", right);
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package org.javawebstack.orm.test.other;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import org.apache.commons.lang3.RandomStringUtils;
6+
import org.javawebstack.orm.test.shared.util.QueryStringUtil;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.util.*;
10+
11+
import static org.junit.jupiter.api.Assertions.assertEquals;
12+
13+
14+
/*
15+
* The scope of this class is to test the QueryUtility as it will be used to write other tests.
16+
*/
17+
class QueryUtilTest {
18+
19+
@Test
20+
void testGetSectionSimple() {
21+
List<SectionRecord> list = new LinkedList<>(Arrays.asList(
22+
new SectionRecord("SELECT", RandomStringUtils.randomAlphanumeric(3, 12)),
23+
new SectionRecord("FROM", RandomStringUtils.randomAlphanumeric(3, 10)),
24+
new SectionRecord("WHERE", RandomStringUtils.randomAlphanumeric(3, 10)),
25+
new SectionRecord("ORDER BY", RandomStringUtils.randomAlphanumeric(3, 10)),
26+
new SectionRecord("GROUP BY", RandomStringUtils.randomAlphanumeric(3, 10)),
27+
new SectionRecord("HAVING", RandomStringUtils.randomAlphanumeric(3, 10)),
28+
new SectionRecord("LIMIT", RandomStringUtils.randomNumeric(1, 100)),
29+
new SectionRecord("OFFSET", RandomStringUtils.randomNumeric(1, 100))
30+
));
31+
32+
this.performStandardTestOnList(list);
33+
34+
}
35+
36+
@Test
37+
void testCasingDoesNotMatter() {
38+
List<SectionRecord> list = new LinkedList<>(Arrays.asList(
39+
new SectionRecord("select", RandomStringUtils.randomAlphanumeric(3, 12)),
40+
new SectionRecord("fRom", RandomStringUtils.randomAlphanumeric(3, 10)),
41+
new SectionRecord("where", RandomStringUtils.randomAlphanumeric(3, 10)),
42+
new SectionRecord("ordEr by", RandomStringUtils.randomAlphanumeric(3, 10)),
43+
new SectionRecord("Group By", RandomStringUtils.randomAlphanumeric(3, 10)),
44+
new SectionRecord("hAvIng", RandomStringUtils.randomAlphanumeric(3, 10)),
45+
new SectionRecord("limit", RandomStringUtils.randomNumeric(1, 100)),
46+
new SectionRecord("offset", RandomStringUtils.randomNumeric(1, 100))
47+
));
48+
49+
this.performStandardTestOnList(list);
50+
}
51+
52+
// Example from here: https://www.freecodecamp.org/news/sql-example/
53+
@Test
54+
void testGetUsualCase() {
55+
List<SectionRecord> list = new LinkedList<>(Arrays.asList(
56+
new SectionRecord("SELECT", "`Customers`.`CustomerName`, `Orders`.`OrderID`"),
57+
new SectionRecord("FROM", "`Customers`"),
58+
new SectionRecord("FULL OUTER JOIN", "`Orders` ON `Customers`.`CustomerID`=`Orders`.`CustomerID`"),
59+
new SectionRecord("ORDER BY", "`Customers`.`CustomerName`")
60+
));
61+
62+
this.performStandardTestOnList(list);
63+
}
64+
65+
@Test
66+
void testTrapExpressionsWhichAreEscaped() {
67+
List<SectionRecord> list = new LinkedList<>(Arrays.asList(
68+
new SectionRecord("SELECT", "`FROM`.`SELECT`, `GROUP BY`.`having`"),
69+
new SectionRecord("FROM", "`order by`"),
70+
new SectionRecord("WHERE", "`from` LIKE `where` AND 'from' = 'where'"),
71+
new SectionRecord("FULL OUTER JOIN", "`from` ON `FROM`.`SELECT`=`select`.`from`"),
72+
new SectionRecord("ORDER BY", "`limit`.`where`")
73+
));
74+
75+
this.performStandardTestOnList(list);
76+
}
77+
78+
@Test
79+
void testMultipleOccurrences() {
80+
List<SectionRecord> list = new LinkedList<>(Arrays.asList(
81+
new SectionRecord("SELECT", RandomStringUtils.randomAlphanumeric(3, 12)),
82+
new SectionRecord("FROM", RandomStringUtils.randomAlphanumeric(3, 12)),
83+
new SectionRecord("JOIN", RandomStringUtils.randomAlphanumeric(3, 12)),
84+
new SectionRecord("JOIN", RandomStringUtils.randomAlphanumeric(3, 12)),
85+
new SectionRecord("JOIN", RandomStringUtils.randomAlphanumeric(3, 12))
86+
));
87+
88+
String queryString = this.getQueryStringFromList(list);
89+
QueryStringUtil util = new QueryStringUtil(queryString);
90+
91+
SectionRecord currentRecord = list.get(0);
92+
assertEquals(currentRecord.getValue(), util.getTopLevelSectionsByKeyword(currentRecord.getKey()).get(0));
93+
94+
currentRecord = list.get(1);
95+
assertEquals(currentRecord.getValue(), util.getTopLevelSectionsByKeyword(currentRecord.getKey()).get(0));
96+
97+
currentRecord = list.get(2);
98+
assertEquals(currentRecord.getValue(), util.getTopLevelSectionsByKeyword(currentRecord.getKey()).get(0));
99+
100+
currentRecord = list.get(3);
101+
assertEquals(currentRecord.getValue(), util.getTopLevelSectionsByKeyword(currentRecord.getKey()).get(1));
102+
103+
currentRecord = list.get(4);
104+
assertEquals(currentRecord.getValue(), util.getTopLevelSectionsByKeyword(currentRecord.getKey()).get(2));
105+
106+
}
107+
/*
108+
* Boilerplate Code Reduction Methods
109+
*/
110+
111+
private String getQueryStringFromList(List<SectionRecord> list) {
112+
StringBuilder builder = new StringBuilder();
113+
114+
for(SectionRecord entry : list)
115+
builder
116+
.append(entry.getKey())
117+
.append(" ")
118+
.append(entry.getValue())
119+
.append(" ");
120+
121+
return builder.toString().trim();
122+
}
123+
124+
/*
125+
* The standard test in this case will be that each section cnly exists once and as defined per list.
126+
*/
127+
private void performStandardTestOnList(List<SectionRecord> list) {
128+
String query = this.getQueryStringFromList(list);
129+
QueryStringUtil verification = new QueryStringUtil(query);
130+
131+
for (SectionRecord entry : list) {
132+
List<String> foundSections = verification.getTopLevelSectionsByKeyword(entry.getKey());
133+
String firstSection = foundSections.get(0);
134+
assertEquals(1, foundSections.size(), "More than one section or no section was found, but only one unique section was expected.");
135+
assertEquals(
136+
entry.getValue(),
137+
firstSection,
138+
String.format(
139+
"The section name %s has been %s instead of %s.",
140+
entry.getKey(),
141+
firstSection,
142+
entry.getValue()
143+
)
144+
);
145+
}
146+
}
147+
148+
@Getter
149+
@AllArgsConstructor
150+
static class SectionRecord {
151+
String key;
152+
String value;
153+
}
154+
155+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.javawebstack.orm.test.shared.knowledge;
2+
3+
import java.util.Arrays;
4+
import java.util.HashSet;
5+
6+
/**
7+
* The QueryKnowledgeBase serves as a decentralized information container around raw query terms.
8+
*/
9+
public class QueryKnowledgeBase {
10+
11+
/**
12+
* Top level select keyword are SQL keywords which occur in SELECT statements and do not depend on another keyword
13+
* except for SELECT and FROM (which are both included in this set as well).
14+
* For example JOIN can appear after FROM statement so it is included. The ON keyword depends on a JOIN keyword though
15+
* which we view as a sub keyword of JOIN and therefore not as a top level keyword.
16+
*/
17+
public static final HashSet<String> TOP_LEVEL_SELECT_KEYWORDS;
18+
19+
/**
20+
* Quote characters are characters which prevents an SQL parser from picking up on a keyword, if the
21+
* wrap the keyword.
22+
*/
23+
public static final HashSet<Character> QUOTE_CHARACTERS;
24+
25+
26+
static {
27+
TOP_LEVEL_SELECT_KEYWORDS = new HashSet<>(Arrays.asList(
28+
"SELECT",
29+
"FROM",
30+
"WHERE",
31+
"ORDER BY",
32+
"JOIN",
33+
"JOIN LEFT",
34+
"JOIN RIGHT",
35+
"INNER JOIN",
36+
"FULL JOIN",
37+
"FULL OUTER JOIN",
38+
"OUTER JOIN",
39+
"GROUP BY",
40+
"HAVING",
41+
"LIMIT",
42+
"OFFSET"
43+
));
44+
45+
QUOTE_CHARACTERS = new HashSet<>(Arrays.asList(
46+
'`',
47+
'\''
48+
));
49+
}
50+
}

0 commit comments

Comments
 (0)