Skip to content

Commit

Permalink
Fixed RD-10971 (#439)
Browse files Browse the repository at this point in the history
Also added tests for RD-14684 and RD-14685.
  • Loading branch information
bgaidioz committed Sep 5, 2024
1 parent 0d1f981 commit c705a88
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2024 RAW Labs S.A.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.txt.
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0, included in the file
* licenses/APL.txt.
*/

package com.rawlabs.snapi.compiler.tests.regressions
import com.rawlabs.snapi.compiler.tests.SnapiTestContext

class RD10971Test extends SnapiTestContext {

test("Json.Parse(\" \", type int)")(
// Jackson returns an internal error.
_ should runErrorAs("""Internal error: _parseNumericValue called when parser instance closed
| at [Source: (String)" "; line: 1, column: 2]""".stripMargin)
)

test("[Json.Parse(\" \", type int)]")(
_ should run // it doesn't fail because it's in a list.
)

test("Json.Parse(\" \", type record(a: int, b: string))")(
// unexpected token.
_ should runErrorAs("expected { but token null found")
)

test("[Json.Parse(\" \", type record(a: int, b: string))]")(
// Same as above, the tryable should be left in the list. The query doesn't fail.
_ should run // it doesn't fail because it's in a list.
)

test("Json.Parse(\" \", type list(record(a: int, b: string))) // RD-10971")(
// unexpected token.
_ should runErrorAs("expected [ but token null found")
)

test("[Json.Parse(\" \", type list(record(a: int, b: string)))] // RD-10971")(
_ should run // it doesn't fail because it's in a list.
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2024 RAW Labs S.A.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.txt.
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0, included in the file
* licenses/APL.txt.
*/

package com.rawlabs.snapi.compiler.tests.regressions

import com.rawlabs.snapi.compiler.tests.SnapiTestContext
import com.rawlabs.snapi.frontend.snapi.SnapiInterpolator

class RD14684Test extends SnapiTestContext {

private val missing_field = tempFile("""[
|{"name": "Benjamin", "birthYear": 1978},
|{"name": "X"},
|{"name": "Jane", "birthYear": 200}
|]
""".stripMargin)

private val missing_field2 = tempFile("""[
|{"name": "Benjamin", "birthYear": 1978},
|{"name": "X"},
|{"name": "Jane", "birthYear": 200},
|{"name": "Tarzan", "birthYear": 201}
|]
""".stripMargin)

private val totallyNotRecord = tempFile("""[
|{"name": "Benjamin", "birthYear": 1978},
|14,
|{"name": "Jane", "birthYear": 200}
|]
""".stripMargin)

ignore(
snapi"""Json.InferAndRead("$missing_field", sampleSize = 1, preferNulls = false)"""
) { it =>
// Because we specified a sampleSize of 1, sampling didn't infer a birthyear could miss,
// and we also don't make fields nullable, therefore, the expected behavior is that
// we fail to build the middle record as a whole.
// TODO: RD-14684: the record is indeed parsed as a failed record, but we skip the following one (Jane)
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("'birthYear': not found"),
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

ignore(
snapi"""Json.InferAndRead("$missing_field2", sampleSize = 1, preferNulls = false)"""
) { it =>
// TODO: RD-14684: the record is indeed parsed as a failed record, but we skip the following one (Jane)
// Same as in the test above. That test confirms we're skipping the following record, not the remaining one.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("'birthYear': not found"),
|{name: "Jane", birthYear: 200},
|{name: "Tarzan", birthYear: 201}]""".stripMargin
)
}

test(
snapi"""Json.InferAndRead("$missing_field", sampleSize = 1, preferNulls = true)"""
) { it =>
// because sampling didn't infer a birthyear could miss but we make fields
// nullable, we get a null birthYear.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|{name: "X", birthYear: null},
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

test(
snapi"""Json.InferAndRead("$missing_field2", sampleSize = 1, preferNulls = true)"""
) { it =>
// because sampling didn't infer a birthyear could miss but we make fields
// nullable, we get a null birthYear.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|{name: "X", birthYear: null},
|{name: "Jane", birthYear: 200},
|{name: "Tarzan", birthYear: 201}]""".stripMargin
)
}

test(
snapi"""Json.InferAndRead("$totallyNotRecord", sampleSize = 1, preferNulls = false)"""
) { it =>
// because sampling didn't infer a birthyear could miss and we don't make fields
// nullable, we fail to build the middle record. This doesn't fail like in the first
// tests likely because the parser doesn't start to read the record, it immediately fails
// when it gets an integer in place of the opening brace.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("expected { but token VALUE_NUMBER_INT found"),
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

test(
snapi"""Json.InferAndRead("$totallyNotRecord", sampleSize = 1, preferNulls = true)"""
) { it =>
// Same as above. Using preferNulls shouldn't change anything.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("expected { but token VALUE_NUMBER_INT found"),
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

test(
snapi"""Json.Read("$totallyNotRecord", type list(record(name: string, birthYear: int)))"""
) { it =>
// Same as above with an explicit type (which means it's tryable/nullable).
// The parser doesn't even get to start to parse a record. The whole record is failed.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("expected { but token VALUE_NUMBER_INT found"),
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

test(
snapi"""Json.Read("$totallyNotRecord", type collection(record(name: string, birthYear: int)))"""
) { it =>
// Same as above with a collection.
it should evaluateTo(
"""[{name: "Benjamin", birthYear: 1978},
|Error.Build("expected { but token VALUE_NUMBER_INT found"),
|{name: "Jane", birthYear: 200}]""".stripMargin
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2024 RAW Labs S.A.
*
* Use of this software is governed by the Business Source License
* included in the file licenses/BSL.txt.
*
* As of the Change Date specified in that file, in accordance with
* the Business Source License, use of this software will be governed
* by the Apache License, Version 2.0, included in the file
* licenses/APL.txt.
*/

package com.rawlabs.snapi.compiler.tests.regressions

import com.rawlabs.snapi.compiler.tests.SnapiTestContext

class RD14685Test extends SnapiTestContext {

test("Json.Parse(\"12\", type collection(record(a: int, b: string)))")(
_ should runErrorAs("expected [ but token VALUE_NUMBER_INT found")
)

test("Json.Parse(\" \", type collection(record(a: int, b: string)))")(
// Same error as above. The collection is OK but when rendering to JSON we get a failure.
_ should runErrorAs("expected [ but token null found")
)

ignore("[Json.Parse(\"12\", type collection(record(a: int, b: string)))]")(
// the collection is consumed when writing JSON, it fails in the middle.
_ should run // TODO if it runs, check the error message in Error.Build
)

ignore("[Json.Parse(\" \", type collection(record(a: int, b: string)))]")(
// the collection is consumed when writing JSON, it fails in the middle.
_ should run // TODO if it runs, check the error message in Error.Build
)

ignore("[Collection.Count(Json.Parse(\" \", type collection(record(a: int, b: string))))]")(
// the collection is consumed when running Count, it leads to a failed int in the list.
_ should run // TODO if it runs, check the error message in Error.Build
)

ignore("[Collection.Count(Json.Parse(\"12\", type collection(record(a: int, b: string))))]")(
// the collection is consumed when running Count, it leads to a failed int in the list.
_ should run // TODO if it runs, check the error message in Error.Build
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,10 @@ protected static TruffleArrayList doParseList(
@Cached @Cached.Shared("currentToken")
JsonParserNodes.CurrentTokenJsonParserNode currentToken,
@Cached @Cached.Shared("nextToken") JsonParserNodes.NextTokenJsonParserNode nextToken) {
if (currentToken.execute(thisNode, parser) != JsonToken.START_ARRAY) {
JsonToken token = currentToken.execute(thisNode, parser);
if (token != JsonToken.START_ARRAY) {
throw new JsonUnexpectedTokenException(
JsonToken.START_ARRAY.asString(),
currentToken.execute(thisNode, parser).toString(),
thisNode);
JsonToken.START_ARRAY.asString(), String.valueOf(token), thisNode);
}
nextToken.execute(thisNode, parser);

Expand All @@ -640,11 +639,10 @@ protected static Object doParse(
JsonParserNodes.CurrentTokenJsonParserNode currentToken,
@Cached JsonParserNodes.CurrentFieldJsonParserNode currentField,
@Cached RecordNodes.AddPropNode addPropNode) {
if (currentToken.execute(thisNode, parser) != JsonToken.START_OBJECT) {
JsonToken token = currentToken.execute(thisNode, parser);
if (token != JsonToken.START_OBJECT) {
throw new JsonUnexpectedTokenException(
JsonToken.START_OBJECT.asString(),
currentToken.execute(thisNode, parser).toString(),
thisNode);
JsonToken.START_OBJECT.asString(), String.valueOf(token), thisNode);
}

nextToken.execute(thisNode, parser);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ public ListParseJsonNode(
public Object executeGeneric(VirtualFrame frame) {
Object[] args = frame.getArguments();
JsonParser parser = (JsonParser) args[0];

if (currentToken.execute(this, parser) != JsonToken.START_ARRAY) {
JsonToken token = currentToken.execute(this, parser);
if (token != JsonToken.START_ARRAY) {
throw new JsonUnexpectedTokenException(
JsonToken.START_ARRAY.asString(), currentToken.execute(this, parser).toString(), this);
JsonToken.START_ARRAY.asString(), String.valueOf(token), this);
}
nextToken.execute(this, parser);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,10 @@ public Object executeGeneric(VirtualFrame frame) {
JsonParser parser = (JsonParser) args[0];
BitSet currentBitSet = new BitSet(this.fieldsSize);

if (currentTokenNode.execute(this, parser) != JsonToken.START_OBJECT) {
JsonToken token = currentTokenNode.execute(this, parser);
if (token != JsonToken.START_OBJECT) {
throw new JsonUnexpectedTokenException(
JsonToken.START_OBJECT.asString(),
currentTokenNode.execute(this, parser).toString(),
this);
JsonToken.START_OBJECT.asString(), String.valueOf(token), this);
}
nextTokenNode.execute(this, parser);

Expand Down

0 comments on commit c705a88

Please sign in to comment.