Skip to content

Commit 67437dd

Browse files
authored
Support multiple entity selection via cartesian product πŸ“ (#116)
* support multiple entity selection via cartesian product πŸ«πŸ‘©β€πŸ«πŸ“šπŸ”’πŸ“ * fixed testcase πŸ› * fixed lint report πŸ› * more test :test: * support multiple entity selection via cartesian product πŸ«πŸ‘©β€πŸ«πŸ“šπŸ”’πŸ“ * fixed testcase πŸ› * fixed lint report πŸ› * more test :test: * Optimized for caretesian product generation
1 parent 48564aa commit 67437dd

File tree

5 files changed

+203
-20
lines changed

5 files changed

+203
-20
lines changed

β€Žsourcecode-parser/antlr/listener_impl.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import (
99
type Query struct {
1010
SelectList []SelectList
1111
Expression string
12+
Condition []string
1213
}
1314

1415
type CustomQueryListener struct {
1516
BaseQueryListener
1617
expression strings.Builder
1718
selectList []SelectList
19+
condition []string
1820
}
1921

2022
type SelectList struct {
@@ -41,6 +43,12 @@ func (l *CustomQueryListener) EnterSelect_list(ctx *Select_listContext) {
4143
}
4244
}
4345

46+
func (l *CustomQueryListener) EnterCondition(ctx *ConditionContext) {
47+
if ctx.GetChildCount() > 1 {
48+
l.condition = append(l.condition, ctx.GetText())
49+
}
50+
}
51+
4452
func (l *CustomQueryListener) EnterExpression(ctx *ExpressionContext) {
4553
if l.expression.Len() > 0 {
4654
l.expression.WriteString(" ")
@@ -89,5 +97,5 @@ func ParseQuery(inputQuery string) Query {
8997
listener := NewCustomQueryListener()
9098
antlr.ParseTreeWalkerDefault.Walk(listener, p.Query())
9199

92-
return Query{SelectList: listener.selectList, Expression: listener.expression.String()}
100+
return Query{SelectList: listener.selectList, Expression: listener.expression.String(), Condition: listener.condition}
93101
}

β€Žsourcecode-parser/antlr/listener_impl_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func TestParseQuery(t *testing.T) {
1919
expectedQuery: Query{
2020
SelectList: []SelectList{{Entity: "class_declaration", Alias: "cd"}},
2121
Expression: "cd.GetName()==\"test\"",
22+
Condition: []string{"cd.GetName()==\"test\""},
2223
},
2324
},
2425
{
@@ -30,6 +31,7 @@ func TestParseQuery(t *testing.T) {
3031
{Entity: "entity2", Alias: "e2"},
3132
},
3233
Expression: "e1.GetName()==\"test\"",
34+
Condition: []string{"e1.GetName()==\"test\""},
3335
},
3436
},
3537
{
@@ -41,6 +43,7 @@ func TestParseQuery(t *testing.T) {
4143
{Entity: "entity2", Alias: "e2"},
4244
},
4345
Expression: "e1.GetName()==\"test\" || e2.GetName()==\"test\"",
46+
Condition: []string{"e1.GetName()==\"test\"", "e2.GetName()==\"test\""},
4447
},
4548
},
4649
{
@@ -52,6 +55,7 @@ func TestParseQuery(t *testing.T) {
5255
{Entity: "entity2", Alias: "e2"},
5356
},
5457
Expression: "e1.GetName()==\"test\" && e2.GetName()==\"test\"",
58+
Condition: []string{"e1.GetName()==\"test\"", "e2.GetName()==\"test\""},
5559
},
5660
},
5761
}

β€Žsourcecode-parser/graph/query.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package graph
22

33
import (
44
"fmt"
5-
6-
"github.com/shivasurya/code-pathfinder/sourcecode-parser/analytics"
7-
"github.com/shivasurya/code-pathfinder/sourcecode-parser/model"
5+
"log"
86

97
"github.com/expr-lang/expr"
8+
"github.com/shivasurya/code-pathfinder/sourcecode-parser/analytics"
109
parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr"
10+
"github.com/shivasurya/code-pathfinder/sourcecode-parser/model"
1111
)
1212

1313
type Env struct {
@@ -97,13 +97,92 @@ func QueryEntities(graph *CodeGraph, query parser.Query) []*Node {
9797
analytics.ReportEvent(entity.Entity)
9898
}
9999

100-
for _, node := range graph.Nodes {
101-
for _, entity := range query.SelectList {
102-
if entity.Entity == node.Type && FilterEntities(node, query) {
103-
result = append(result, node)
100+
cartesianProduct := generateCartesianProduct(graph, query.SelectList, query.Condition)
101+
102+
for _, nodeSet := range cartesianProduct {
103+
if FilterEntities(nodeSet, query) {
104+
result = append(result, nodeSet...)
105+
}
106+
}
107+
return result
108+
}
109+
110+
func generateCartesianProduct(graph *CodeGraph, selectList []parser.SelectList, conditions []string) [][]*Node {
111+
typeIndex := make(map[string][]*Node)
112+
113+
// value and reference based reducing search space
114+
for _, condition := range conditions {
115+
// this code helps to reduce search space
116+
// if there is single entity in select list, the condition is easy to reduce the search space
117+
// if there are multiple entities in select list, the condition is hard to reduce the search space,
118+
// but I have tried my best using O(n^2) time complexity to reduce the search space
119+
if len(selectList) > 1 {
120+
lhsNodes := graph.FindNodesByType(selectList[0].Entity)
121+
rhsNodes := graph.FindNodesByType(selectList[1].Entity)
122+
for _, lhsNode := range lhsNodes {
123+
for _, rhsNode := range rhsNodes {
124+
if FilterEntities([]*Node{lhsNode, rhsNode}, parser.Query{Expression: condition, SelectList: selectList}) {
125+
typeIndex[lhsNode.Type] = append(typeIndex[lhsNode.Type], lhsNode)
126+
typeIndex[rhsNode.Type] = append(typeIndex[rhsNode.Type], rhsNode)
127+
}
128+
}
129+
}
130+
} else {
131+
for _, node := range graph.Nodes {
132+
query := parser.Query{Expression: condition, SelectList: selectList}
133+
if FilterEntities([]*Node{node}, query) {
134+
typeIndex[node.Type] = append(typeIndex[node.Type], node)
135+
}
104136
}
105137
}
106138
}
139+
140+
sets := make([][]interface{}, 0, len(selectList))
141+
142+
for _, entity := range selectList {
143+
set := make([]interface{}, 0)
144+
if nodes, ok := typeIndex[entity.Entity]; ok {
145+
for _, node := range nodes {
146+
set = append(set, node)
147+
}
148+
}
149+
sets = append(sets, set)
150+
}
151+
152+
product := cartesianProduct(sets)
153+
154+
result := make([][]*Node, len(product))
155+
for i, p := range product {
156+
result[i] = make([]*Node, len(p))
157+
for j, node := range p {
158+
if n, ok := node.(*Node); ok {
159+
result[i][j] = n
160+
} else {
161+
// Handle the error case, e.g., skip this node or log an error
162+
// You might want to customize this part based on your error handling strategy
163+
log.Printf("Warning: Expected *Node type, got %T", node)
164+
}
165+
}
166+
}
167+
168+
return result
169+
}
170+
171+
func cartesianProduct(sets [][]interface{}) [][]interface{} {
172+
result := [][]interface{}{{}}
173+
for _, set := range sets {
174+
var newResult [][]interface{}
175+
for _, item := range set {
176+
for _, subResult := range result {
177+
newSubResult := make([]interface{}, len(subResult), len(subResult)+1)
178+
copy(newSubResult, subResult)
179+
newSubResult = append(newSubResult, item)
180+
newResult = append(newResult, newSubResult)
181+
}
182+
}
183+
result = newResult
184+
}
185+
107186
return result
108187
}
109188

@@ -283,13 +362,13 @@ func generateProxyEnv(node *Node, query parser.Query) map[string]interface{} {
283362
return env
284363
}
285364

286-
func FilterEntities(node *Node, query parser.Query) bool {
365+
func FilterEntities(node []*Node, query parser.Query) bool {
287366
expression := query.Expression
288367
if expression == "" {
289368
return true
290369
}
291370

292-
env := generateProxyEnv(node, query)
371+
env := generateProxyEnvForSet(node, query)
293372

294373
program, err := expr.Compile(expression, expr.Env(env))
295374
if err != nil {
@@ -306,3 +385,14 @@ func FilterEntities(node *Node, query parser.Query) bool {
306385
}
307386
return false
308387
}
388+
389+
func generateProxyEnvForSet(nodeSet []*Node, query parser.Query) map[string]interface{} {
390+
env := make(map[string]interface{})
391+
392+
for i, entity := range query.SelectList {
393+
proxyEnv := generateProxyEnv(nodeSet[i], query)
394+
env[entity.Alias] = proxyEnv[entity.Alias]
395+
}
396+
397+
return env
398+
}

β€Žsourcecode-parser/graph/query_test.go

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package graph
33
import (
44
"fmt"
55
"testing"
6+
"time"
67

78
parser "github.com/shivasurya/code-pathfinder/sourcecode-parser/antlr"
89
"github.com/shivasurya/code-pathfinder/sourcecode-parser/model"
@@ -25,15 +26,21 @@ func TestQueryEntities(t *testing.T) {
2526
name: "Query with expression",
2627
query: parser.Query{
2728
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
28-
Expression: "md.getVisibility() == 'public'",
29+
Expression: "md.getVisibility() == \"public\"",
30+
Condition: []string{
31+
"md.getVisibility()==\"public\"",
32+
},
2933
},
3034
expected: 1,
3135
},
3236
{
3337
name: "Query with no results",
3438
query: parser.Query{
3539
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
36-
Expression: "md.getVisibility() == 'private'",
40+
Expression: "md.getVisibility() == \"private\"",
41+
Condition: []string{
42+
"md.getVisibility()==\"private\"",
43+
},
3744
},
3845
expected: 0,
3946
},
@@ -60,7 +67,7 @@ func TestFilterEntities(t *testing.T) {
6067
node: &Node{Type: "method_declaration", Modifier: "public"},
6168
query: parser.Query{
6269
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
63-
Expression: "md.getVisibility() == 'public'",
70+
Expression: "md.getVisibility() == \"public\"",
6471
},
6572
expected: true,
6673
},
@@ -69,7 +76,7 @@ func TestFilterEntities(t *testing.T) {
6976
node: &Node{Type: "class_declaration", Name: "TestClass"},
7077
query: parser.Query{
7178
SelectList: []parser.SelectList{{Entity: "class_declaration", Alias: "cd"}},
72-
Expression: "cd.getName() == 'TestClass'",
79+
Expression: "cd.getName() == \"TestClass\"",
7380
},
7481
expected: true,
7582
},
@@ -78,7 +85,7 @@ func TestFilterEntities(t *testing.T) {
7885
node: &Node{Type: "method_declaration", ReturnType: "void"},
7986
query: parser.Query{
8087
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
81-
Expression: "md.getReturnType() == 'void'",
88+
Expression: "md.getReturnType() == \"void\"",
8289
},
8390
expected: true,
8491
},
@@ -87,7 +94,7 @@ func TestFilterEntities(t *testing.T) {
8794
node: &Node{Type: "variable_declaration", DataType: "int"},
8895
query: parser.Query{
8996
SelectList: []parser.SelectList{{Entity: "variable_declaration", Alias: "vd"}},
90-
Expression: "vd.getVariableDataType() == 'int'",
97+
Expression: "vd.getVariableDataType() == \"int\"",
9198
},
9299
expected: true,
93100
},
@@ -96,7 +103,7 @@ func TestFilterEntities(t *testing.T) {
96103
node: &Node{Type: "method_declaration", Modifier: "public", ReturnType: "String", Name: "getName"},
97104
query: parser.Query{
98105
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
99-
Expression: "md.getVisibility() == 'public' && md.getReturnType() == 'String' && md.getName() == 'getName'",
106+
Expression: "md.getVisibility() == \"public\" && md.getReturnType() == \"String\" && md.getName() == \"getName\"",
100107
},
101108
expected: true,
102109
},
@@ -105,15 +112,15 @@ func TestFilterEntities(t *testing.T) {
105112
node: &Node{Type: "method_declaration", Modifier: "private"},
106113
query: parser.Query{
107114
SelectList: []parser.SelectList{{Entity: "method_declaration", Alias: "md"}},
108-
Expression: "md.getVisibility() == 'public'",
115+
Expression: "md.getVisibility() == \"public\"",
109116
},
110117
expected: false,
111118
},
112119
}
113120

114121
for _, tt := range tests {
115122
t.Run(tt.name, func(t *testing.T) {
116-
result := FilterEntities(tt.node, tt.query)
123+
result := FilterEntities([]*Node{tt.node}, tt.query)
117124
assert.Equal(t, tt.expected, result)
118125
})
119126
}
@@ -167,3 +174,77 @@ func TestGenerateProxyEnv(t *testing.T) {
167174
throwsTypes := methodEnv["getThrowsType"].(func() []string)()
168175
assert.Equal(t, []string{"IOException"}, throwsTypes)
169176
}
177+
178+
func TestCartesianProduct(t *testing.T) {
179+
tests := []struct {
180+
name string
181+
input [][]interface{}
182+
expected [][]interface{}
183+
}{
184+
{
185+
name: "Empty input",
186+
input: [][]interface{}{},
187+
expected: [][]interface{}{{}},
188+
},
189+
{
190+
name: "Single set",
191+
input: [][]interface{}{{1, 2, 3}},
192+
expected: [][]interface{}{{1}, {2}, {3}},
193+
},
194+
{
195+
name: "Two sets",
196+
input: [][]interface{}{{1, 2}, {"a", "b"}},
197+
expected: [][]interface{}{{1, "a"}, {2, "a"}, {1, "b"}, {2, "b"}},
198+
},
199+
{
200+
name: "Three sets",
201+
input: [][]interface{}{{1, 2}, {"a", "b"}, {true, false}},
202+
expected: [][]interface{}{
203+
{1, "a", true}, {2, "a", true},
204+
{1, "b", true}, {2, "b", true},
205+
{1, "a", false}, {2, "a", false},
206+
{1, "b", false}, {2, "b", false},
207+
},
208+
},
209+
{
210+
name: "Mixed types",
211+
input: [][]interface{}{{1, "x"}, {true, 3.14}},
212+
expected: [][]interface{}{{1, true}, {"x", true}, {1, 3.14}, {"x", 3.14}},
213+
},
214+
}
215+
216+
for _, tt := range tests {
217+
t.Run(tt.name, func(t *testing.T) {
218+
result := cartesianProduct(tt.input)
219+
assert.Equal(t, tt.expected, result)
220+
})
221+
}
222+
}
223+
224+
func TestCartesianProductLargeInput(t *testing.T) {
225+
input := [][]interface{}{
226+
{1, 2, 3, 4, 5},
227+
{"a", "b", "c", "d", "e"},
228+
{true, false},
229+
}
230+
result := cartesianProduct(input)
231+
assert.Equal(t, 50, len(result))
232+
assert.Equal(t, 3, len(result[0]))
233+
}
234+
235+
func TestCartesianProductPerformance(t *testing.T) {
236+
input := make([][]interface{}, 10)
237+
for i := range input {
238+
input[i] = make([]interface{}, 5)
239+
for j := range input[i] {
240+
input[i][j] = j
241+
}
242+
}
243+
244+
start := time.Now()
245+
result := cartesianProduct(input)
246+
duration := time.Since(start)
247+
248+
assert.Equal(t, 9765625, len(result))
249+
assert.Less(t, duration, 10*time.Second)
250+
}

β€Žtest-src/android/app/src/test/java/com/ivb/udacity/ExampleUnitTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*/
1010
public class ExampleUnitTest {
1111
@Test
12-
public void addition_isCorrect() throws Exception {
12+
public void is_addition_correct() throws Exception {
1313
assertEquals(4, 2 + 2);
1414
}
1515
}

0 commit comments

Comments
Β (0)