Skip to content

Commit

Permalink
Fix write_file function (#608)
Browse files Browse the repository at this point in the history
 Fix the WriteFile CLI Command to properly output query results to a file (See #606) and update the README
  • Loading branch information
johnedquinn authored May 25, 2022
1 parent b633476 commit ba62b66
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 31 deletions.
39 changes: 16 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@
[![Build Status](https://travis-ci.org/partiql/partiql-lang-kotlin.svg?branch=master)](https://travis-ci.org/partiql/partiql-lang-kotlin)
[![codecov](https://codecov.io/gh/partiql/partiql-lang-kotlin/branch/master/graph/badge.svg)](https://codecov.io/gh/partiql/partiql-lang-kotlin)



# PartiQL Lang Kotlin

This is a Kotlin implementation of the [PartiQL specification](https://partiql.org/assets/PartiQL-Specification.pdf).
PartiQL is based on SQL-92 and has added support for working with schemaless hierarchical data.
PartiQL’s extensions to SQL are easy to understand, treat nested data as first class citizens and
compose seamlessly with each other and SQL.


This repository contains an embeddable reference interpreter, test framework, and tests for PartiQL in Kotlin.

The easiest way to get started with PartiQL is to clone this repository locally, build, then
[run the REPL](./docs/user/CLI.md).

## Status

PartiQL should be considered to be in "preview" status. It has been in use within a number of Amazon internal
systems and an AWS product for over one year. The behavior of the language itself is mostly stable however
Users of PartiQL should consider PartiQL to be in "preview" status. It has been leveraged within a number of Amazon internal
systems and AWS products for over a year. The behavior of the language itself is mostly stable, however,
the public API of the interpreter is slated to undergo significant improvements in the near term. (See the
GitHub issues list for details.)

Expand All @@ -34,41 +31,41 @@ This project is published to [Maven Central](https://search.maven.org/artifact/o
| `org.partiql` | `partiql-lang-kotlin` | `0.6.0`|


For Maven builds, add this to your `pom.xml`:
For Maven builds, add the following to your `pom.xml`:

```
```xml
<dependency>
<groupId>org.partiql</groupId>
<artifactId>partiql-lang-kotlin</artifactId>
<version>{version}</version>
<version>${version}</version>
</dependency>
```

For Gradle 5 and later, add this to your `build.gradle`:
For Gradle 5+, add the following to your `build.gradle`:

```
```groovy
repositories {
mavenCentral()
}
dependencies {
implementation "org.partiql:partiql-lang-kotlin:{version}"
implementation "org.partiql:partiql-lang-kotlin:${version}"
}
```

Be sure to replace `{version}` with the desired version.
Be sure to replace `${version}` with the desired version.

## Building

To build this project, clone this repository and from its root directory execute:

```
$./gradlew build
```shell
./gradlew build
```

This will build the reference interpreter and test framework, then run all unit and integration tests.

### Building the documentation
### Building the Documentation

[Instructions on how to build PartiQL's documentation](docs/Docker/README.md)

Expand All @@ -79,18 +76,14 @@ This will build the reference interpreter and test framework, then run all unit
- `lang` contains the source code of the library containing the interpreter.
- `lang/jmh` contains the JMH benchmarks for PartiQL.
- `cli` contains the source code of the command-line interface and interactive prompt. (CLI/REPL)
- `testframework` contains the source code of the integration test framework.
- `integration-test/test-scripts` contains the test scripts executed by the test framework as part of the
Gradle build.
- `integration-test/test-scripts-ignored` contains test scripts which cannot be executed during the Gradle build.

### Running JMH benchmarks
### Running JMH Benchmarks

To run JMH benchmarks located in `lang/jmh`, build the entire project first and then run
the following command:

```
$./gradlew jmh
```shell
./gradlew jmh
```

### Examples
Expand All @@ -104,6 +97,6 @@ See [CONTRIBUTING](CONTRIBUTING.md)

## License

This the works contained within this repository are licensed under the Apache 2.0 License.
The works contained within this repository are licensed under the Apache 2.0 License.

See the [LICENSE](LICENSE) file.
2 changes: 1 addition & 1 deletion cli/src/org/partiql/cli/functions/WriteFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import java.io.OutputStreamWriter
internal class WriteFile(valueFactory: ExprValueFactory) : BaseFunction(valueFactory) {
override val signature = FunctionSignature(
name = "write_file",
requiredParameters = listOf(StaticType.STRING, StaticType.STRING),
requiredParameters = listOf(StaticType.STRING, StaticType.ANY),
optionalParameter = StaticType.STRUCT,
returnType = StaticType.BOOL
)
Expand Down
51 changes: 51 additions & 0 deletions cli/test/org/partiql/cli/CliTestUtility.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.partiql.cli

import com.amazon.ion.IonSystem
import com.amazon.ion.system.IonSystemBuilder
import org.junit.Assert
import org.partiql.lang.CompilerPipeline
import org.partiql.lang.eval.Bindings
import org.partiql.lang.eval.ExprValue
import org.partiql.lang.eval.ExprValueFactory
import java.io.ByteArrayOutputStream
import java.io.OutputStream

/**
* Initializes a CLI and runs the passed-in query
*/
fun makeCliAndGetResult(
query: String,
input: String? = null,
bindings: Bindings<ExprValue> = Bindings.empty(),
outputFormat: OutputFormat = OutputFormat.ION_TEXT,
output: OutputStream = ByteArrayOutputStream(),
ion: IonSystem = IonSystemBuilder.standard().build(),
compilerPipeline: CompilerPipeline = CompilerPipeline.standard(ion),
valueFactory: ExprValueFactory = ExprValueFactory.standard(ion)
): String {
val cli = Cli(
valueFactory,
input?.byteInputStream(Charsets.UTF_8) ?: EmptyInputStream(),
output,
outputFormat,
compilerPipeline,
bindings,
query
)
cli.run()
return output.toString()
}

/**
* An assertion helper
*/
fun assertAsIon(expected: String, actual: String) {
val ion = IonSystemBuilder.standard().build()
assertAsIon(ion, expected, actual)
}

/**
* An assertion helper
*/
fun assertAsIon(ion: IonSystem, expected: String, actual: String) =
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(actual))
173 changes: 166 additions & 7 deletions cli/test/org/partiql/cli/functions/WriteFileTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,47 @@ import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.partiql.cli.assertAsIon
import org.partiql.cli.makeCliAndGetResult
import org.partiql.lang.CompilerPipeline
import org.partiql.lang.eval.EvaluationSession
import org.partiql.lang.eval.ExprValueFactory
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.OutputStream
import java.util.UUID

class WriteFileTest {
private val ion = IonSystemBuilder.standard().build()
private val valueFactory = ExprValueFactory.standard(ion)
private val function = WriteFile(valueFactory)
private val session = EvaluationSession.standard()
private val pipeline: CompilerPipeline = CompilerPipeline.build(ion) {
addFunction(function)
}

private fun String.exprValue() = valueFactory.newFromIonValue(ion.singleValue(this))
private fun readFile(path: String) = File(dirPath(path)).readText()

private fun dirPath(fname: String = "") = "/tmp/partiqltest/$fname"
private fun readFileFromPath(path: String) = File(path).readText()
private fun createRandomTmpFilePath(extension: String? = "ion"): String {
val prefix: UUID = UUID.randomUUID()
val name = if (extension != null) "$prefix.$extension" else prefix.toString()
return dirPath(name)
}

private val outputStream: OutputStream = ByteArrayOutputStream()

companion object {
private const val TRUE_STRING: String = "true"
private const val FALSE_STRING: String = "false"
}

/**
*
* CONFIG
*
*/

@Before
fun setUp() {
Expand All @@ -44,24 +71,156 @@ class WriteFileTest {
File(dirPath()).deleteRecursively()
}

/**
*
* UNIT TESTS
*
*/

@Test
fun writeIonAsDefault() {
val args = listOf(""" "${dirPath("data.ion")}" """, "[1, 2]").map { it.exprValue() }
fun unit_success_writeIonAsDefault() {
val filePath = createRandomTmpFilePath()
val args = listOf("\"$filePath\"", "[1, 2]").map { it.exprValue() }
function.callWithRequired(session, args).ionValue

val expected = "1 2"

Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFile("data.ion")))
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun readIon() {
val args = listOf(""" "${dirPath("data1.ion")}" """, "[1, 2]").map { it.exprValue() }
fun unit_success_readIon() {
val filePath = createRandomTmpFilePath()
val args = listOf("\"$filePath\"", "[1, 2]").map { it.exprValue() }
val additionalOptions = """{type: "ion"}""".exprValue()
function.callWithOptional(session, args, additionalOptions).ionValue

val expected = "1 2"

Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFile("data1.ion")))
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

/**
*
* INTEGRATION TESTS
*
*/

@Test
fun integration_success_singleValueStruct() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT * FROM input_data)"
val input = "{a: 1}"
val expected = "{a: 1}"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_success_nestedValueStruct() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT a.b FROM input_data)"
val input = "{a: {b: 1}}"
val expected = "{b: 1}"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_success_nestedValue() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT VALUE a FROM input_data)"
val input = "{a: {b: 1}}"
val expected = "{b: 1}"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_success_nestedValueInt() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT VALUE a.b FROM input_data)"
val input = "{a: {b: 1}}"
val expected = "1"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_success_nestedValueList() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT VALUE a.b FROM input_data)"
val input = "{a: {b: [ 1, 2 ]}}"
val expected = "[ 1, 2 ]"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_success_int() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT VALUE a FROM input_data)"
val input = "{a : 5}"
val expected = "5"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(TRUE_STRING, cliResponse)
Assert.assertEquals(ion.loader.load(expected), ion.loader.load(readFileFromPath(filePath)))
}

@Test
fun integration_fail_nestedValueBag() {
// Arrange
val filePath = createRandomTmpFilePath()
val query = "write_file('$filePath', SELECT VALUE a.b FROM input_data)"
val input = "{a: {b: << 1, 2 >>}}"
val expected = "<< 1, 2 >>"

// Act
val cliResponse =
makeCliAndGetResult(query = query, input = input, output = outputStream, compilerPipeline = pipeline)

// Assert
assertAsIon(FALSE_STRING, cliResponse)
}
}

0 comments on commit ba62b66

Please sign in to comment.