Skip to content

Commit

Permalink
Better SqlParserTestBase (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
lziq authored Nov 2, 2021
1 parent c88b3cf commit c6e3fc6
Showing 1 changed file with 101 additions and 99 deletions.
200 changes: 101 additions & 99 deletions lang/test/org/partiql/lang/syntax/SqlParserTestBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
}

Expand All @@ -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 <expr>) wrapping, so extract <expr>
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 <expr>), so extract <expr> 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()
Expand Down

0 comments on commit c6e3fc6

Please sign in to comment.