Skip to content

Commit 6a6aa6b

Browse files
Merge pull request #1 from Hayanesh/nested_sql
Added support for nested query in FROM clause
2 parents 8abde1d + 480a41d commit 6a6aa6b

6 files changed

Lines changed: 252 additions & 85 deletions

File tree

src/main/antlr4/io/github/mnesimiyilmaz/sql4json/generated/SQL4Json.g4

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
grammar SQL4Json;
22

33
sql4json
4-
: SELECT selectedColumns FROM rootNode (WHERE whereConditions)? (GROUP BY groupByColumns)? (HAVING havingConditions)? (ORDER BY orderByColumns)? (SEMI_COLON)? EOF
4+
: SELECT selectedColumns FROM rootNode (WHERE whereConditions)? (GROUP BY groupByColumns)? (HAVING havingConditions)? (ORDER BY orderByColumns)? (SEMI_COLON)? EOF?
55
;
66

77
rootNode
88
: ROOT (DOT IDENTIFIER (DOT IDENTIFIER)*)?
9+
| LPAREN sql4json RPAREN
910
;
1011

1112
jsonColumn

src/main/java/io/github/mnesimiyilmaz/sql4json/SQL4JsonListenerImpl.java

Lines changed: 26 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,41 @@
22

33
import com.fasterxml.jackson.databind.JsonNode;
44
import io.github.mnesimiyilmaz.sql4json.condition.ConditionProcessor;
5-
import io.github.mnesimiyilmaz.sql4json.condition.CriteriaNode;
65
import io.github.mnesimiyilmaz.sql4json.definitions.JsonColumnWithNonAggFunctionDefinion;
76
import io.github.mnesimiyilmaz.sql4json.definitions.OrderByColumnDefinion;
87
import io.github.mnesimiyilmaz.sql4json.definitions.SelectColumnDefinition;
9-
import io.github.mnesimiyilmaz.sql4json.grouping.GroupByInput;
10-
import io.github.mnesimiyilmaz.sql4json.grouping.GroupByProcessor;
11-
import io.github.mnesimiyilmaz.sql4json.sorting.SortProcessor;
12-
import io.github.mnesimiyilmaz.sql4json.utils.FieldKey;
13-
import io.github.mnesimiyilmaz.sql4json.utils.JsonUtils;
8+
import io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser;
9+
import io.github.mnesimiyilmaz.sql4json.processor.SQLBuilder;
10+
import io.github.mnesimiyilmaz.sql4json.processor.SQLProcessor;
1411
import io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonBaseListener;
12+
import org.antlr.v4.runtime.tree.ParseTree;
1513

1614
import java.util.*;
17-
import java.util.function.Function;
18-
import java.util.stream.Collectors;
1915

2016
/**
2117
* @author mnesimiyilmaz
2218
*/
2319
class SQL4JsonListenerImpl extends SQL4JsonBaseListener {
2420

2521
private final List<SelectColumnDefinition> selectedColumns;
26-
27-
private JsonNode dataNode;
28-
private List<Map<FieldKey, Object>> flattenedData;
29-
private CriteriaNode whereClause;
30-
private List<JsonColumnWithNonAggFunctionDefinion> groupByColumns;
31-
private CriteriaNode havingClause;
32-
private List<OrderByColumnDefinion> orderByColumns;
22+
private final JsonNode dataNode;
23+
private final SQLProcessor sqlProcessor;
24+
private String rootPath;
3325

3426
public SQL4JsonListenerImpl(JsonNode jsonNode) {
3527
this.dataNode = jsonNode;
28+
this.sqlProcessor = new SQLProcessor();
3629
this.selectedColumns = new ArrayList<>();
3730
}
3831

3932
@Override
4033
public void enterRootNode(io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser.RootNodeContext ctx) {
41-
String rootPath = ctx.getText();
42-
if (rootPath.equalsIgnoreCase("$r")) {
43-
rootPath = "";
44-
} else {
45-
rootPath = rootPath.substring(3);
34+
String path = ctx.getText();
35+
if(path.startsWith("$r")){
36+
this.rootPath = path.equalsIgnoreCase("$r") ? "" : path.substring(3);
4637
}
47-
this.dataNode = JsonUtils.peelJsonNode(dataNode, rootPath);
48-
this.flattenedData = JsonUtils.convertJsonToFlattenedListOfKeyValue(dataNode);
38+
getBuilder(ctx).setSelectedColumns(new ArrayList<>(selectedColumns));
39+
selectedColumns.clear();
4940
}
5041

5142
@Override
@@ -62,88 +53,39 @@ public void enterSelectedColumns(io.github.mnesimiyilmaz.sql4json.generated.SQL4
6253

6354
@Override
6455
public void enterWhereConditions(io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser.WhereConditionsContext ctx) {
65-
this.whereClause = new ConditionProcessor(ctx.conditions()).process();
56+
getBuilder(ctx).setWhereClause(new ConditionProcessor(ctx.conditions()).process());
6657
}
6758

6859
@Override
6960
public void enterHavingConditions(io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser.HavingConditionsContext ctx) {
70-
this.havingClause = new ConditionProcessor(ctx.conditions()).process();
61+
getBuilder(ctx).setHavingClause(new ConditionProcessor(ctx.conditions()).process());
7162
}
7263

7364
@Override
7465
public void enterGroupByColumn(io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser.GroupByColumnContext ctx) {
75-
if (this.groupByColumns == null) {
76-
this.groupByColumns = new ArrayList<>();
77-
}
78-
this.groupByColumns.add(new JsonColumnWithNonAggFunctionDefinion(ctx.jsonColumnWithNonAggFunction()));
66+
getBuilder(ctx).addGroupByColumn(new JsonColumnWithNonAggFunctionDefinion(ctx.jsonColumnWithNonAggFunction()));
7967
}
8068

8169
@Override
8270
public void enterOrderByColumn(io.github.mnesimiyilmaz.sql4json.generated.SQL4JsonParser.OrderByColumnContext ctx) {
83-
if (this.orderByColumns == null) {
84-
this.orderByColumns = new ArrayList<>();
85-
}
86-
this.orderByColumns.add(new OrderByColumnDefinion(ctx));
71+
getBuilder(ctx).addOrderByColumn(new OrderByColumnDefinion(ctx));
8772
}
8873

89-
public JsonNode getResult() {
90-
if (whereClause != null) {
91-
this.flattenedData.removeIf(x -> !whereClause.test(x));
92-
}
93-
if (groupByColumns != null) {
94-
this.flattenedData = new GroupByProcessor(new GroupByInput(
95-
flattenedData, selectedColumns, groupByColumns, havingClause)).process();
96-
}
97-
if (orderByColumns != null) {
98-
flattenedData.sort(new SortProcessor(flattenedData, orderByColumns).buildComparator());
99-
}
100-
if (groupByColumns == null && !selectedColumns.get(0).isAsterisk()) {
101-
flattenedData = flattenedData.stream().map(this::getSelectedRowData).collect(Collectors.toList());
102-
}
103-
return JsonUtils.convertStructuredMapToJsonNode(
104-
flattenedData.stream().map(JsonUtils::convertFlatMapToStructuredMap).collect(Collectors.toList()));
74+
public JsonNode getResult(){
75+
return sqlProcessor.process(dataNode, rootPath);
10576
}
10677

107-
private Map<FieldKey, Object> getSelectedRowData(Map<FieldKey, Object> row) {
108-
Map<FieldKey, Object> result = new HashMap<>();
109-
for (SelectColumnDefinition selectedColumn : selectedColumns) {
110-
FieldKey fieldKey = FieldKey.of(selectedColumn.getColumnDefinition().getColumnDefinition().getColumnName());
111-
if (isObjectField(row, fieldKey.getKey())) {
112-
result.putAll(getObjectFieldValues(row, fieldKey.getKey(), selectedColumn.getAlias()));
113-
} else {
114-
Object value = row.get(fieldKey);
115-
Optional<Function<Object, Object>> decorator = selectedColumn.getColumnDefinition().getColumnDefinition().getValueDecorator();
116-
if (decorator.isPresent()) {
117-
value = decorator.get().apply(value);
118-
}
119-
if (selectedColumn.getAlias() != null) {
120-
result.put(FieldKey.of(selectedColumn.getAlias()), value);
121-
} else {
122-
result.put(fieldKey, value);
123-
}
124-
}
125-
}
126-
return result;
78+
private SQLBuilder getBuilder(ParseTree tree){
79+
return sqlProcessor.getBuilder(sql4JsonContextNameOf(tree));
12780
}
12881

129-
private boolean isObjectField(Map<FieldKey, Object> row, String selectPath) {
130-
return row.keySet().stream().filter(x -> x.getKey().startsWith(selectPath)).count() > 1L;
131-
}
82+
private static String sql4JsonContextNameOf(ParseTree tree){
83+
ParseTree parent = tree.getParent();
13284

133-
private Map<FieldKey, Object> getObjectFieldValues(Map<FieldKey, Object> row, String selectPath, String alias) {
134-
if (alias == null) {
135-
return row.entrySet().stream()
136-
.filter(x -> x.getKey().getKey().startsWith(selectPath))
137-
.collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
138-
} else {
139-
Map<FieldKey, Object> result = new HashMap<>();
140-
for (Map.Entry<FieldKey, Object> entry : row.entrySet()) {
141-
if (entry.getKey().getKey().startsWith(selectPath)) {
142-
result.put(FieldKey.of(alias + entry.getKey().getKey().substring(selectPath.length())), entry.getValue());
143-
}
144-
}
145-
return result;
85+
while (!(parent instanceof SQL4JsonParser.Sql4jsonContext)){
86+
parent = parent.getParent();
14687
}
88+
return parent.toString();
14789
}
14890

14991
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.github.mnesimiyilmaz.sql4json.processor;
2+
3+
import io.github.mnesimiyilmaz.sql4json.condition.CriteriaNode;
4+
import io.github.mnesimiyilmaz.sql4json.definitions.JsonColumnWithNonAggFunctionDefinion;
5+
import io.github.mnesimiyilmaz.sql4json.definitions.OrderByColumnDefinion;
6+
import io.github.mnesimiyilmaz.sql4json.definitions.SelectColumnDefinition;
7+
import lombok.Getter;
8+
import lombok.Setter;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
@Getter
14+
public class SQLBuilder {
15+
16+
@Setter
17+
private List<SelectColumnDefinition> selectedColumns;
18+
@Setter
19+
private CriteriaNode whereClause;
20+
private List<JsonColumnWithNonAggFunctionDefinion> groupByColumns;
21+
@Setter
22+
private CriteriaNode havingClause;
23+
private List<OrderByColumnDefinion> orderByColumns;
24+
25+
public void addGroupByColumn(JsonColumnWithNonAggFunctionDefinion groupByColumn){
26+
if(this.groupByColumns == null)
27+
this.groupByColumns = new ArrayList<>();
28+
29+
this.groupByColumns.add(groupByColumn);
30+
}
31+
32+
public void addOrderByColumn(OrderByColumnDefinion orderByColumn){
33+
if(this.orderByColumns == null)
34+
this.orderByColumns = new ArrayList<>();
35+
36+
this.orderByColumns.add(orderByColumn);
37+
}
38+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package io.github.mnesimiyilmaz.sql4json.processor;
2+
3+
import io.github.mnesimiyilmaz.sql4json.condition.CriteriaNode;
4+
import io.github.mnesimiyilmaz.sql4json.definitions.JsonColumnWithNonAggFunctionDefinion;
5+
import io.github.mnesimiyilmaz.sql4json.definitions.OrderByColumnDefinion;
6+
import io.github.mnesimiyilmaz.sql4json.definitions.SelectColumnDefinition;
7+
import io.github.mnesimiyilmaz.sql4json.grouping.GroupByInput;
8+
import io.github.mnesimiyilmaz.sql4json.grouping.GroupByProcessor;
9+
import io.github.mnesimiyilmaz.sql4json.sorting.SortProcessor;
10+
import io.github.mnesimiyilmaz.sql4json.utils.FieldKey;
11+
import lombok.AccessLevel;
12+
import lombok.RequiredArgsConstructor;
13+
14+
import java.util.*;
15+
import java.util.function.Function;
16+
import java.util.stream.Collectors;
17+
18+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
19+
class SQLConstruct {
20+
private List<SelectColumnDefinition> selectedColumns;
21+
private CriteriaNode whereClause;
22+
private List<JsonColumnWithNonAggFunctionDefinion> groupByColumns;
23+
private CriteriaNode havingClause;
24+
private List<OrderByColumnDefinion> orderByColumns;
25+
26+
public static SQLConstruct newInstance(SQLBuilder builder){
27+
SQLConstruct sqlConstruct = new SQLConstruct();
28+
sqlConstruct.selectedColumns = builder.getSelectedColumns();
29+
sqlConstruct.whereClause = builder.getWhereClause();
30+
sqlConstruct.groupByColumns = builder.getGroupByColumns();
31+
sqlConstruct.havingClause = builder.getHavingClause();
32+
sqlConstruct.orderByColumns = builder.getOrderByColumns();
33+
return sqlConstruct;
34+
}
35+
36+
public List<Map<FieldKey, Object>> apply(List<Map<FieldKey, Object>> flattenedData) {
37+
if (whereClause != null) {
38+
flattenedData.removeIf(x -> !whereClause.test(x));
39+
}
40+
if (groupByColumns != null) {
41+
flattenedData = new GroupByProcessor(new GroupByInput(
42+
flattenedData, selectedColumns, groupByColumns, havingClause)).process();
43+
}
44+
if (orderByColumns != null) {
45+
flattenedData.sort(new SortProcessor(flattenedData, orderByColumns).buildComparator());
46+
}
47+
if (groupByColumns == null && !selectedColumns.get(0).isAsterisk()) {
48+
flattenedData = flattenedData.stream().map(this::getSelectedRowData).collect(Collectors.toList());
49+
}
50+
return flattenedData;
51+
}
52+
53+
private Map<FieldKey, Object> getSelectedRowData(Map<FieldKey, Object> row) {
54+
Map<FieldKey, Object> result = new HashMap<>();
55+
for (SelectColumnDefinition selectedColumn : selectedColumns) {
56+
FieldKey fieldKey = FieldKey.of(selectedColumn.getColumnDefinition().getColumnDefinition().getColumnName());
57+
if (isObjectField(row, fieldKey.getKey())) {
58+
result.putAll(getObjectFieldValues(row, fieldKey.getKey(), selectedColumn.getAlias()));
59+
} else {
60+
Object value = row.get(fieldKey);
61+
Optional<Function<Object, Object>> decorator = selectedColumn.getColumnDefinition().getColumnDefinition().getValueDecorator();
62+
if (decorator.isPresent()) {
63+
value = decorator.get().apply(value);
64+
}
65+
if (selectedColumn.getAlias() != null) {
66+
result.put(FieldKey.of(selectedColumn.getAlias()), value);
67+
} else {
68+
result.put(fieldKey, value);
69+
}
70+
}
71+
}
72+
return result;
73+
}
74+
75+
private boolean isObjectField(Map<FieldKey, Object> row, String selectPath) {
76+
return row.keySet().stream().filter(x -> x.getKey().startsWith(selectPath)).count() > 1L;
77+
}
78+
79+
private Map<FieldKey, Object> getObjectFieldValues(Map<FieldKey, Object> row, String selectPath, String alias) {
80+
if (alias == null) {
81+
return row.entrySet().stream()
82+
.filter(x -> x.getKey().getKey().startsWith(selectPath))
83+
.collect(HashMap::new, (m, v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
84+
} else {
85+
Map<FieldKey, Object> result = new HashMap<>();
86+
for (Map.Entry<FieldKey, Object> entry : row.entrySet()) {
87+
if (entry.getKey().getKey().startsWith(selectPath)) {
88+
result.put(FieldKey.of(alias + entry.getKey().getKey().substring(selectPath.length())), entry.getValue());
89+
}
90+
}
91+
return result;
92+
}
93+
}
94+
95+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.github.mnesimiyilmaz.sql4json.processor;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import io.github.mnesimiyilmaz.sql4json.utils.FieldKey;
5+
import io.github.mnesimiyilmaz.sql4json.utils.JsonUtils;
6+
7+
import java.util.*;
8+
import java.util.stream.Collectors;
9+
10+
public class SQLProcessor {
11+
private final Map<String, SQLBuilder> mapOfSQLBuilders;
12+
13+
public SQLProcessor() {
14+
this.mapOfSQLBuilders = new LinkedHashMap<>();
15+
}
16+
17+
public SQLBuilder getBuilder(String name){
18+
return mapOfSQLBuilders.computeIfAbsent(name, n -> new SQLBuilder());
19+
}
20+
21+
22+
public JsonNode process(JsonNode dataNode, String rootPath){
23+
dataNode = JsonUtils.peelJsonNode(dataNode, rootPath);
24+
List<Map<FieldKey, Object>> flattenedData = JsonUtils.convertJsonToFlattenedListOfKeyValue(dataNode);
25+
26+
Iterator<String> names = new LinkedList<>(mapOfSQLBuilders.keySet()).descendingIterator();
27+
28+
while (names.hasNext()){
29+
SQLConstruct sqlConstruct = SQLConstruct.newInstance(mapOfSQLBuilders.get(names.next()));
30+
flattenedData = sqlConstruct.apply(flattenedData);
31+
}
32+
33+
return JsonUtils.convertStructuredMapToJsonNode(
34+
flattenedData.stream().map(JsonUtils::convertFlatMapToStructuredMap).collect(Collectors.toList()));
35+
36+
}
37+
}

0 commit comments

Comments
 (0)