diff --git a/lang/test/org/partiql/lang/syntax/SqlParserTestBase.kt b/lang/test/org/partiql/lang/syntax/SqlParserTestBase.kt index ac363a7b4c..f97216628b 100644 --- a/lang/test/org/partiql/lang/syntax/SqlParserTestBase.kt +++ b/lang/test/org/partiql/lang/syntax/SqlParserTestBase.kt @@ -16,19 +16,15 @@ package org.partiql.lang.syntax import com.amazon.ion.IonSexp import com.amazon.ionelement.api.IonElement -import com.amazon.ionelement.api.IonElementLoaderOptions import com.amazon.ionelement.api.SexpElement import com.amazon.ionelement.api.toIonElement -import com.amazon.ionelement.api.loadSingleElement import com.amazon.ionelement.api.toIonValue import org.partiql.lang.TestBase import org.partiql.lang.ast.AstDeserializerBuilder import org.partiql.lang.ast.AstSerializer import org.partiql.lang.ast.AstVersion -import org.partiql.lang.ast.DataManipulation import org.partiql.lang.ast.ExprNode import org.partiql.lang.ast.passes.MetaStrippingRewriter -import org.partiql.lang.ast.toAstExpr import org.partiql.lang.ast.toAstStatement import org.partiql.lang.ast.toExprNode import org.partiql.lang.domains.PartiqlAst @@ -45,82 +41,95 @@ abstract class SqlParserTestBase : TestBase() { protected fun parse(source: String): ExprNode = parser.parseExprNode(source) protected fun parseToAst(source: String): PartiqlAst.Statement = parser.parseAstStatement(source) + /** + * This method is used by test cases for parsing a string. + * The test are performed with only PIG AST. + * The expected PIG AST is a PIG builder. + */ protected fun assertExpression( source: String, - pigBuilder: PartiqlAst.Builder.() -> PartiqlAst.PartiqlAstNode + expectedPigBuilder: PartiqlAst.Builder.() -> PartiqlAst.PartiqlAstNode ) { - val parsedExprNode = parse(source) - - val expectedPartiQlAst = PartiqlAst.build { pigBuilder() }.toIonElement().toString() - // Convert the query to ExprNode + val expectedPigAst = PartiqlAst.build { expectedPigBuilder() }.toIonElement().toString() - val partiqlAst = loadIonSexp(expectedPartiQlAst) - partiqlAssert(parsedExprNode, partiqlAst, source) - - pigDomainAssert(parsedExprNode, partiqlAst.toIonElement().asSexp()) - pigExprNodeTransformAsserts(parsedExprNode) + // Refer to comments inside the main body of the following function to see what checks are performed. + assertExpression(source, expectedPigAst) } - // TODO: refactor the signature with pig builder + /** + * This method is used by test cases for parsing a string. + * The test are performed with only PIG AST. + * The expected PIG AST is a string. + */ protected fun assertExpression( - source: String, - expectedSexpAstAsString: String + source: String, + expectedPigAst: String ) { - val parsedExprNode = parse(source) - val expectedSexpAst = loadSingleElement( - expectedSexpAstAsString, - IonElementLoaderOptions(includeLocationMeta = false) - ).asSexp() - - val parsedExprNodeIonElement = when (parsedExprNode) { - is DataManipulation -> parsedExprNode.toAstStatement().toIonElement() - else -> parsedExprNode.toAstExpr().toIonElement() - } - assertRoundTripIonElementToPartiQlAst(parsedExprNodeIonElement, expectedSexpAst) - assertRoundTripPartiQlAstToExprNode(parsedExprNode.toAstStatement(), expectedSexpAst, parsedExprNode) - pigExprNodeTransformAsserts(parsedExprNode) + val actualExprNode = parse(source) + val expectedIonSexp = loadIonSexp(expectedPigAst) + + // Check equals for actual value and expected value in IonSexp format + checkEqualInIonSexp(actualExprNode, expectedIonSexp, source) + + val expectedElement = expectedIonSexp.toIonElement().asSexp() + + // See the comments inside the function to see what checks are performed. + pigDomainAssert(actualExprNode, expectedElement) + + // Check equals for actual value after round trip transformation: astStatement -> ExprNode -> astStatement + assertRoundTripPigAstToExprNode(actualExprNode) } + /** + * This method is used by test cases for parsing a string. + * The test are performed with both PIG AST and V0 AST. + * The expected PIG AST is a PIG builder. + */ protected fun assertExpression( source: String, - expectedSexpAstV0String: String, - pigBuilder: PartiqlAst.Builder.() -> PartiqlAst.PartiqlAstNode + expectedSexpAstV0: String, + expectedPigBuilder: PartiqlAst.Builder.() -> PartiqlAst.PartiqlAstNode ) { - val expectedPartiQlAst = PartiqlAst.build { pigBuilder() } - assertExpression(source, expectedSexpAstV0String, expectedPartiQlAst.toIonElement().toString()) + val expectedPigAst = PartiqlAst.build { expectedPigBuilder() }.toIonElement().toString() + + // Refer to comments inside the main body of the following function to see what checks are performed. + assertExpression(source, expectedSexpAstV0, expectedPigAst) } + /** + * This method is used by test cases for parsing a string. + * The test are performed with both PIG AST and V0 AST. + * The expected PIG AST is a string. + */ protected fun assertExpression( source: String, - expectedSexpAstV0String: String, - expectedPartiqlAstString: String = expectedSexpAstV0String + expectedV0Ast: String, + expectedPigAst: String ) { - // Convert the query to ExprNode - val parsedExprNode = parse(source) - - val v0SexpAst = loadIonSexp(expectedSexpAstV0String) - serializeAssert(AstVersion.V0, parsedExprNode, v0SexpAst, source) + // Check for V0 Ast + val actualExprNode = parse(source) + val expectedV0AstSexp = loadIonSexp(expectedV0Ast) - val partiqlAst = loadIonSexp(expectedPartiqlAstString) - partiqlAssert(parsedExprNode, partiqlAst, source) + // Check equals for actual value and expected value after transformation: EpxrNode -> IonSexp + // Check equals for actual value and expected value after transformation: IonSexp -> EpxrNode + serializeAssert(AstVersion.V0, actualExprNode, expectedV0AstSexp, source) - pigDomainAssert(parsedExprNode, partiqlAst.toIonElement().asSexp()) - - pigExprNodeTransformAsserts(parsedExprNode) + // Check for PIG Ast + assertExpression(source, expectedPigAst) } - private fun serializeAssert(astVersion: AstVersion, parsedExprNode: ExprNode, expectedSexpAst: IonSexp, source: String) { + private fun serializeAssert(astVersion: AstVersion, actualExprNode: ExprNode, expectedIonSexp: IonSexp, source: String) { - val actualSexpAstWithoutMetas = AstSerializer.serialize(parsedExprNode, astVersion, ion).filterMetaNodes() + val actualSexpAstWithoutMetas = AstSerializer.serialize(actualExprNode, astVersion, ion).filterMetaNodes() val deserializer = AstDeserializerBuilder(ion).build() - assertSexpEquals(expectedSexpAst, actualSexpAstWithoutMetas, "$astVersion AST, $source") + assertSexpEquals(expectedIonSexp, actualSexpAstWithoutMetas, "$astVersion AST, $source") - val deserializedExprNodeFromSexp = deserializer.deserialize(expectedSexpAst, astVersion) + val deserializedExprNodeFromSexp = deserializer.deserialize(expectedIonSexp, astVersion) assertEquals( - "Parsed ExprNodes must match deserialized s-exp $astVersion AST", - parsedExprNode.stripMetas(), + "actual ExprNodes must match deserialized s-exp $astVersion AST", + actualExprNode.stripMetas(), deserializedExprNodeFromSexp.stripMetas()) } @@ -139,83 +148,76 @@ abstract class SqlParserTestBase : TestBase() { /** * Performs checks similar to that of [serializeAssert]. First checks that parsing the [source] query string to - * a [PartiqlAst] and to an IonValue Sexp equals the [expectedSexpAst]. Next checks that converting this IonValue - * Sexp to an ExprNode equals the [parsedExprNode]. + * a [PartiqlAst] and to an IonValue Sexp equals the [expectedIonSexp]. */ - private fun partiqlAssert(parsedExprNode: ExprNode, expectedSexpAst: IonSexp, source: String) { - val actualSexpAstStatment = parseToAst(source) - val actualSexpQuery = unwrapQuery(actualSexpAstStatment) - val actualSexpAst = actualSexpQuery.toIonElement().asAnyElement().toIonValue(ion) - - assertSexpEquals(expectedSexpAst, actualSexpAst, "AST, $source") - - val exprNodeFromSexp = actualSexpAstStatment.toExprNode(ion) + private fun checkEqualInIonSexp(actualExprNode: ExprNode, expectedIonSexp: IonSexp, source: String) { + val actualStatment = actualExprNode.toAstStatement() + val actualElement = unwrapQuery(actualStatment) + val actualIonSexp = actualElement.toIonElement().asAnyElement().toIonValue(ion) - assertEquals( - "Parsed ExprNodes must match the expected PartiqlAst", - parsedExprNode.stripMetas(), - exprNodeFromSexp.stripMetas()) + assertSexpEquals(expectedIonSexp, actualIonSexp, "AST, $source") } - private fun pigDomainAssert(parsedExprNode: ExprNode, expectedSexpAst: SexpElement) { - // Convert ExprNode into a PartiqlAst statement - val statement = parsedExprNode.toAstStatement() - - // Test cases are missing (query ) wrapping, so extract - val parsedElement = unwrapQuery(statement) + private fun pigDomainAssert(actualExprNode: ExprNode, expectedSexpAst: SexpElement) { + val actualStatement = actualExprNode.toAstStatement() + val actualElement = unwrapQuery(actualStatement) - assertRoundTripIonElementToPartiQlAst(parsedElement, expectedSexpAst) + // Check equal for actual and expected in transformed astStatement: astStatment -> SexpElement -> astStatement + // Check round trip for actual: SexpElement -> astStatement -> SexpElement + assertRoundTripIonElementToPartiQlAst(actualElement, expectedSexpAst) - // Run the parsedExprNode through the conversion to PIG domain instance for all tests - // to detect conditions which will cause the conversion to throw an exception. - assertRoundTripPartiQlAstToExprNode(statement, expectedSexpAst, parsedExprNode) + // Check equal for actual and expected in SexpElement format + // Check round trip for actual: astStatement -> SexpElement -> astStatement + // Check equals for actual value after round trip transformation: ExprNode -> astStatement -> SexpElement -> astStatement -> ExprNode + assertRoundTripPartiQlAstToExprNode(actualStatement, expectedSexpAst, actualExprNode) } - private fun assertRoundTripPartiQlAstToExprNode(statement: PartiqlAst.Statement, expectedSexpAst: IonElement, parsedExprNode: ExprNode) { + private fun assertRoundTripPartiQlAstToExprNode(actualStatement: PartiqlAst.Statement, expectedSexpAst: IonElement, actualExprNode: ExprNode) { // Run additional checks on the resulting PartiqlAst instance // None of our test cases are wrapped in (query ), so extract from that out - val element = unwrapQuery(statement) - assertEquals(expectedSexpAst, element) + val actualElement = unwrapQuery(actualStatement) + assertEquals(expectedSexpAst, actualElement) // Convert the the IonElement back to the PartiqlAst instance and assert equivalence - val transformedPig = PartiqlAst.transform(statement.toIonElement()) as PartiqlAst.Statement - assertEquals(statement, transformedPig) + val transformedActualStatement = PartiqlAst.transform(actualStatement.toIonElement()) as PartiqlAst.Statement + assertEquals(actualStatement, transformedActualStatement) - // Convert from the PIG instance back to ExprNode and assert the result is the same as parsedExprNode. - val exprNode2 = MetaStrippingRewriter.stripMetas(transformedPig.toExprNode(ion)) - assertEquals(MetaStrippingRewriter.stripMetas(parsedExprNode), exprNode2) + // Convert from the PIG instance back to ExprNode and assert the result is the same as actualExprNode. + val transformedActualExprNode = MetaStrippingRewriter.stripMetas(transformedActualStatement.toExprNode(ion)) + assertEquals(MetaStrippingRewriter.stripMetas(actualExprNode), transformedActualExprNode) } - private fun assertRoundTripIonElementToPartiQlAst(parsedElement: SexpElement, expectedSexpAst: SexpElement) { - // #1 We can transform the parsed PartiqlAst element. - val transformedParsedElement = PartiqlAst.transform(parsedElement) + private fun assertRoundTripIonElementToPartiQlAst(actualElement: SexpElement, expectedElement: SexpElement) { + // #1 We can transform the actual PartiqlAst element. + val transformedActualStatement = PartiqlAst.transform(actualElement) // #2 We can transform the expected PartiqlAst element. - val transformedExpectedElement = PartiqlAst.transform(expectedSexpAst) + val transformedExpectedStatement = PartiqlAst.transform(expectedElement) // #3 The results of both transformations match. - assertEquals(transformedExpectedElement, transformedParsedElement) + assertEquals(transformedExpectedStatement, transformedActualStatement) - // #4 Re-transforming the parsed PartiqlAst element and check if it matches the expected AST. - val reserializedAst = transformedParsedElement.toIonElement() - assertEquals(expectedSexpAst, reserializedAst) + // #4 Re-transforming the actual PartiqlAst element and check if it matches the expected AST. + val reserializedActualElement = transformedActualStatement.toIonElement() + assertEquals(expectedElement, reserializedActualElement) // #5 Re-serializing the expected PartiqlAst element matches the expected AST. // Note: because of #3 above, no need for #5. } /** - * Strips metas from the [parsedExprNode] so they are not included in equivalence checks - * and round-trip the resulting [parsedExprNode] AST through [toAstStatement] and [toExprNode]. + * Strips metas from the [actualExprNode] so they are not included in equivalence checks + * and round-trip the resulting [actualExprNode] AST through [toAstStatement] and [toExprNode]. * * Verify that the result matches the original without metas. */ - private fun pigExprNodeTransformAsserts(parsedExprNode: ExprNode) { - val parsedExprNodeNoMetas = MetaStrippingRewriter.stripMetas(parsedExprNode) - val statement = parsedExprNodeNoMetas.toAstStatement() - val exprNode2 = statement.toExprNode(ion) - assertEquals(parsedExprNodeNoMetas, exprNode2) + private fun assertRoundTripPigAstToExprNode(actualExprNode: ExprNode) { + val actualExprNodeNoMetas = MetaStrippingRewriter.stripMetas(actualExprNode) + val actualStatement = actualExprNodeNoMetas.toAstStatement() + val roundTripActualExprNode = actualStatement.toExprNode(ion) + + assertEquals(actualExprNodeNoMetas, roundTripActualExprNode) } private fun loadIonSexp(expectedSexpAst: String) = ion.singleValue(expectedSexpAst).asIonSexp()