Skip to content

Commit fe67e1a

Browse files
merge with dev
2 parents a00d3b3 + cd6936a commit fe67e1a

File tree

4 files changed

+189
-3
lines changed

4 files changed

+189
-3
lines changed

src/foam/box/HTTPBox.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright 2017 The FOAM Authors. All Rights Reserved.
44
* http://www.apache.org/licenses/LICENSE-2.0
55
*/
6+
67
foam.CLASS({
78
package: 'foam.box',
89
name: 'HTTPException',
@@ -12,6 +13,7 @@ foam.CLASS({
1213
]
1314
});
1415

16+
1517
foam.CLASS({
1618
package: 'foam.box',
1719
name: 'HTTPBox',
@@ -181,7 +183,7 @@ foam.CLASS({
181183
message: envelope.message,
182184
replyBox: this.getReplyBox()
183185
}));
184-
186+
185187
var headers = {
186188
'Content-Type': 'application/json; charset=utf-8',
187189
'Origin': this.origin

src/foam/parse/QueryParser.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,28 @@ private Grammar getGrammar() {
357357
return predicate;
358358
});
359359

360-
grammar.addSymbol("VALUE", new GreedyAlt(grammar.sym("ME"),grammar.sym("NUMBER"),
361-
grammar.sym("DATE"),grammar.sym("STRING")));
360+
grammar.addSymbol("DECIMAL", new Seq(
361+
new Optional(Literal.create("-")),
362+
new Repeat(Range.create('0', '9'), 1),
363+
Literal.create("."),
364+
new Repeat(Range.create('0', '9'), 1)
365+
));
366+
grammar.addAction("DECIMAL", (val, x) -> {
367+
Object[] values = (Object[]) val;
368+
StringBuilder sb = new StringBuilder();
369+
for ( Object v : values ) {
370+
if ( v == null ) continue;
371+
if ( v instanceof Object[] ) {
372+
for ( Object c : (Object[]) v ) sb.append(c);
373+
} else {
374+
sb.append(v);
375+
}
376+
}
377+
return Double.parseDouble(sb.toString());
378+
});
379+
380+
grammar.addSymbol("VALUE", new GreedyAlt(grammar.sym("ME"),grammar.sym("DECIMAL"),
381+
grammar.sym("NUMBER"),grammar.sym("DATE"),grammar.sym("STRING")));
362382

363383
grammar.addSymbol("COMPOUND_VALUE", new Alt(grammar.sym("NEGATE_VALUE"),
364384
grammar.sym("OR_VALUE"), grammar.sym("AND_VALUE")));

src/foam/parse/test/QueryParserJSTest.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ foam.CLASS({
1313
{ class: 'String', name: 'firstName' },
1414
{ class: 'String', name: 'lastName' },
1515
{ class: 'String', name: 'email' },
16+
{ class: 'Double', name: 'amount' },
1617
{ class: 'foam.lang.Date', name: 'birthday' },
1718
{ class: 'foam.lang.DateTime', name: 'lastLogin' },
1819
{ class: 'Boolean', name: 'emailVerified' }
@@ -35,6 +36,7 @@ foam.CLASS({
3536
async function runTest(x) {
3637
this.testBasicQueries(x);
3738
this.testEmailPlusAddressing(x);
39+
this.testFloatParsing(x);
3840
this.testDateParsing(x);
3941
this.testDateParsingFixes(x);
4042
this.testTimezoneHandling(x);
@@ -92,6 +94,49 @@ foam.CLASS({
9294
"Multiple emails with + should parse correctly, got: " + JSON.stringify(values));
9395
},
9496

97+
function testFloatParsing(x) {
98+
var parser = this.QueryParser.create({ of: this.QueryParserTestUser });
99+
100+
// Decimal values in equality and comparisons
101+
x.test(this.isValidQuery(parser, 'amount=6.5'),
102+
'Float: should parse amount=6.5');
103+
x.test(this.isValidQuery(parser, 'amount=0.10000000009999999995'),
104+
'Float: should parse long decimal');
105+
x.test(this.isValidQuery(parser, 'amount=0.0'),
106+
'Float: should parse 0.0');
107+
x.test(this.isValidQuery(parser, 'amount>6.5'),
108+
'Float: should parse amount>6.5');
109+
x.test(this.isValidQuery(parser, 'amount>=6.5'),
110+
'Float: should parse amount>=6.5');
111+
x.test(this.isValidQuery(parser, 'amount<6.5'),
112+
'Float: should parse amount<6.5');
113+
x.test(this.isValidQuery(parser, 'amount<=6.5'),
114+
'Float: should parse amount<=6.5');
115+
116+
// Negative decimals
117+
x.test(this.isValidQuery(parser, 'amount>-0.10000000009999999995'),
118+
'Float: should parse negative decimal in gt');
119+
x.test(this.isValidQuery(parser, 'amount<-6.5'),
120+
'Float: should parse amount<-6.5');
121+
122+
// Verify parsed value is a Number, not a String
123+
var query = parser.parseString('amount>6.5');
124+
if ( query ) {
125+
var pred = query.args ? query.args[0] : query;
126+
var val = pred && pred.arg2 ? (pred.arg2.value !== undefined ? pred.arg2.value : pred.arg2) : null;
127+
x.test(typeof val === 'number',
128+
'Float: decimal value should parse as Number, not String');
129+
x.test(val === 6.5,
130+
'Float: parsed value should equal 6.5, got ' + val);
131+
}
132+
133+
// Combined with other predicates
134+
x.test(this.isValidQuery(parser, 'amount>=0.1 AND firstName=John'),
135+
'Float: decimal in AND expression');
136+
x.test(this.isValidQuery(parser, '(amount>=0.1 OR amount<-0.1) AND firstName=John'),
137+
'Float: decimal OR inside AND');
138+
},
139+
95140
function extractValue(query) {
96141
// Extract single value from Eq predicate
97142
if ( ! query ) return null;

src/foam/parse/test/QueryParserUserTest.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ foam.CLASS({
1010
extends: 'foam.core.test.Test',
1111

1212
javaImports: [
13+
'foam.dao.ArraySink',
14+
'foam.dao.DAO',
1315
'foam.lib.parse.PStream',
1416
'foam.lib.parse.ParserContext',
1517
'foam.lib.parse.ParserContextImpl',
@@ -103,6 +105,72 @@ foam.CLASS({
103105
104106
// test user's birthday is between two timestamps
105107
test(evaluate("birthday=" + dateFormat_.format(noon.toInstant()), user), user.getBirthday() + " = "+noon.toString());
108+
109+
// ── Float/Decimal parsing tests ──
110+
QueryParser floatParser = new QueryParser(
111+
foam.parse.test.QueryParserTestUser.getOwnClassInfo());
112+
113+
// Decimal values should parse
114+
test(canParseFloat(floatParser, "amount=6.5"), "Float: parse amount=6.5");
115+
test(canParseFloat(floatParser, "amount=0.10000000009999999995"), "Float: parse long decimal");
116+
test(canParseFloat(floatParser, "amount=0.0"), "Float: parse 0.0");
117+
118+
// Decimal comparisons
119+
test(canParseFloat(floatParser, "amount>6.5"), "Float: parse amount>6.5");
120+
test(canParseFloat(floatParser, "amount>=6.5"), "Float: parse amount>=6.5");
121+
test(canParseFloat(floatParser, "amount<6.5"), "Float: parse amount<6.5");
122+
test(canParseFloat(floatParser, "amount<=6.5"), "Float: parse amount<=6.5");
123+
124+
// Negative decimals
125+
test(canParseFloat(floatParser, "amount>-0.10000000009999999995"), "Float: parse negative decimal");
126+
test(canParseFloat(floatParser, "amount<-6.5"), "Float: parse amount<-6.5");
127+
128+
// Combined with other predicates
129+
test(canParseFloat(floatParser, "amount>=0.1 AND firstName=John"), "Float: decimal in AND");
130+
131+
// Evaluate predicates against objects with float values
132+
foam.parse.test.QueryParserTestUser m1 = new foam.parse.test.QueryParserTestUser();
133+
m1.setId(1);
134+
m1.setFirstName("positive");
135+
m1.setAmount(6.5);
136+
137+
foam.parse.test.QueryParserTestUser m2 = new foam.parse.test.QueryParserTestUser();
138+
m2.setId(2);
139+
m2.setFirstName("negative");
140+
m2.setAmount(-3.14);
141+
142+
foam.parse.test.QueryParserTestUser m3 = new foam.parse.test.QueryParserTestUser();
143+
m3.setId(3);
144+
m3.setFirstName("zero");
145+
m3.setAmount(0.0);
146+
147+
test(evaluateFloat(floatParser, "amount>6.0", m1), "Float: 6.5 > 6.0");
148+
test(!evaluateFloat(floatParser, "amount>6.0", m2), "Float: -3.14 !> 6.0");
149+
test(evaluateFloat(floatParser, "amount<0.0", m2), "Float: -3.14 < 0.0");
150+
test(!evaluateFloat(floatParser, "amount<0.0", m1), "Float: 6.5 !< 0.0");
151+
test(evaluateFloat(floatParser, "amount>=0.0", m3), "Float: 0.0 >= 0.0");
152+
test(evaluateFloat(floatParser, "amount<=0.0", m3), "Float: 0.0 <= 0.0");
153+
test(evaluateFloat(floatParser, "amount>=-3.15", m2), "Float: -3.14 >= -3.15");
154+
test(!evaluateFloat(floatParser, "amount>=-3.13", m2), "Float: -3.14 !>= -3.13");
155+
156+
// DAO select with float predicate
157+
DAO dao = new foam.dao.MapDAO(
158+
foam.parse.test.QueryParserTestUser.getOwnClassInfo());
159+
dao.put(m1);
160+
dao.put(m2);
161+
dao.put(m3);
162+
163+
Predicate floatPred = buildFloatPredicate(floatParser, "amount>0.0");
164+
if ( floatPred != null ) {
165+
ArraySink sink = (ArraySink) dao.where(floatPred).select(new ArraySink());
166+
test(sink.getArray().size() == 1, "Float DAO: amount>0.0 matches 1 record, got " + sink.getArray().size());
167+
}
168+
169+
floatPred = buildFloatPredicate(floatParser, "amount<0.0");
170+
if ( floatPred != null ) {
171+
ArraySink sink = (ArraySink) dao.where(floatPred).select(new ArraySink());
172+
test(sink.getArray().size() == 1, "Float DAO: amount<0.0 matches 1 record, got " + sink.getArray().size());
173+
}
106174
`
107175
},
108176
{
@@ -147,6 +215,57 @@ foam.CLASS({
147215
if (predicate == null) return false;
148216
return predicate.f(user);
149217
`
218+
},
219+
{
220+
name: 'buildFloatPredicate',
221+
type: 'foam.mlang.predicate.Predicate',
222+
args: [
223+
{ name: 'parser', javaType: 'foam.parse.QueryParser' },
224+
{ name: 'query', type: 'String' }
225+
],
226+
javaCode: `
227+
StringPStream sps = new StringPStream();
228+
sps.setString(query);
229+
PStream ps = sps;
230+
ParserContext px = new ParserContextImpl();
231+
ps = parser.parse(ps, px);
232+
if ( ps == null ) return null;
233+
Predicate pred = (Predicate) ps.value();
234+
return pred.partialEval();
235+
`
236+
},
237+
{
238+
name: 'canParseFloat',
239+
type: 'Boolean',
240+
args: [
241+
{ name: 'parser', javaType: 'foam.parse.QueryParser' },
242+
{ name: 'query', type: 'String' }
243+
],
244+
javaCode: `
245+
try {
246+
return buildFloatPredicate(parser, query) != null;
247+
} catch ( Exception e ) {
248+
return false;
249+
}
250+
`
251+
},
252+
{
253+
name: 'evaluateFloat',
254+
type: 'Boolean',
255+
args: [
256+
{ name: 'parser', javaType: 'foam.parse.QueryParser' },
257+
{ name: 'query', type: 'String' },
258+
{ name: 'obj', type: 'FObject' }
259+
],
260+
javaCode: `
261+
try {
262+
Predicate pred = buildFloatPredicate(parser, query);
263+
if ( pred == null ) return false;
264+
return pred.f(obj);
265+
} catch ( Exception e ) {
266+
return false;
267+
}
268+
`
150269
}
151270
]
152271
});

0 commit comments

Comments
 (0)