Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializes PartiQLData and PartiQLValueLoader #1443

Merged
merged 3 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
package org.partiql.cli

import com.amazon.ion.system.IonReaderBuilder
import com.amazon.ion.system.IonTextWriterBuilder
import com.amazon.ionelement.api.ionListOf
import com.amazon.ionelement.api.ionNull
import com.amazon.ionelement.api.loadAllElements
import org.partiql.cli.io.Format
import org.partiql.cli.io.PartiQLCursorWriter
import org.partiql.cli.pipeline.Pipeline
import org.partiql.cli.shell.Shell
import org.partiql.eval.PartiQLEngine
Expand All @@ -32,7 +32,6 @@ import org.partiql.spi.connector.Connector
import org.partiql.spi.connector.sql.info.InfoSchema
import org.partiql.types.StaticType
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.toIon
import picocli.CommandLine
import java.io.File
import java.io.InputStream
Expand Down Expand Up @@ -77,7 +76,8 @@ internal class Version : CommandLine.IVersionProvider {
],
showDefaultValues = true
)
internal class MainCommand() : Runnable {
internal class MainCommand : Runnable {
// TODO: Need to add tests to CLI. All tests were removed in the same commit as this TODO. See Git blame.

internal companion object {
private const val SHEBANG_PREFIX = "#!"
Expand All @@ -95,6 +95,13 @@ internal class MainCommand() : Runnable {
)
var strict: Boolean = false

@CommandLine.Option(
names = ["--debug"],
description = ["THIS IS FOR INTERNAL DEVELOPMENT USE ONLY. Shows typing information in the output."],
hidden = true
)
var debug: Boolean = false

@CommandLine.Option(
names = ["-f", "--format"],
description = ["The data format, using the form <input>[:<output>]."],
Expand Down Expand Up @@ -150,7 +157,7 @@ internal class MainCommand() : Runnable {
true -> Pipeline.strict()
else -> Pipeline.default()
}
Shell(pipeline, session()).start()
Shell(pipeline, session(), debug).start()
}

@OptIn(PartiQLValueExperimental::class)
Expand All @@ -167,10 +174,8 @@ internal class MainCommand() : Runnable {
error(result.cause.stackTrace)
}
is PartiQLResult.Value -> {
// TODO handle output format
val ion = result.value.toIon()
val writer = IonTextWriterBuilder.pretty().build(System.out as Appendable)
ion.writeTo(writer)
val writer = PartiQLCursorWriter(System.out, debug)
writer.append(result.value)
println()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

package org.partiql.cli.format


// internal object ExplainFormatter {
//
// internal fun format(result: PartiQLResult.Explain.Domain): String {
Expand Down
213 changes: 213 additions & 0 deletions partiql-cli/src/main/kotlin/org/partiql/cli/io/PartiQLCursorWriter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package org.partiql.cli.io

import org.partiql.value.PartiQLCursor
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.PartiQLValueType
import org.partiql.value.datetime.Date
import org.partiql.value.datetime.Time
import org.partiql.value.datetime.TimeZone
import org.partiql.value.datetime.Timestamp
import kotlin.math.abs

/**
* Aids in appending [PartiQLCursor] to [out].
*
* Prints in a human-readable fashion. Indents appropriately. Example output:
* ```
* partiql ▶ SELECT VALUE { 'a': { 'b': t } } FROM <<1, 2>> AS t
* |
* <<
* {
* 'a': {
* 'b': 1
* }
* },
* {
* 'a': {
* 'b': 2
* }
* }
* >>
* ```
*
* @param debug specifies whether to also output typing information; if set to true, values will have their types prefixed
* to the output; for example: `int32::512`, `string::'hello, world!'`, and `null.int64`; if set to false, values will
* be printed as-is; for example: `512`, `'hello, world!'`, and `null`.
*/
class PartiQLCursorWriter(
private val out: Appendable,
private val debug: Boolean = false
) {

/**
* Determines how much to indent
*/
private var indent = 0

@OptIn(PartiQLValueExperimental::class)
fun append(data: PartiQLCursor) {
for (element in data) {
writeValue(data, element)
}
}

@OptIn(PartiQLValueExperimental::class)
private fun writeValue(data: PartiQLCursor, element: PartiQLValueType) {
when (element) {
PartiQLValueType.ANY -> error("This shouldn't have happened.")
PartiQLValueType.BOOL -> writeScalar(data, "bool", PartiQLCursor::getBoolValue)
PartiQLValueType.INT8 -> writeScalar(data, "int8", PartiQLCursor::getInt8Value)
PartiQLValueType.INT16 -> writeScalar(data, "int16", PartiQLCursor::getInt16Value)
PartiQLValueType.INT32 -> writeScalar(data, "int32", PartiQLCursor::getInt32Value)
PartiQLValueType.INT64 -> writeScalar(data, "int64", PartiQLCursor::getInt64Value)
PartiQLValueType.INT -> writeScalar(data, "int", PartiQLCursor::getIntValue)
PartiQLValueType.DECIMAL -> writeScalar(data, "decimal", PartiQLCursor::getDecimalValue)
PartiQLValueType.DECIMAL_ARBITRARY -> writeScalar(data, "decimal_arbitrary", PartiQLCursor::getDecimalArbitraryValue)
PartiQLValueType.FLOAT32 -> writeScalar(data, "float32", PartiQLCursor::getFloat32Value)
PartiQLValueType.FLOAT64 -> writeScalar(data, "float64", PartiQLCursor::getFloat64Value)
PartiQLValueType.CHAR -> writeScalar(data, "char", PartiQLCursor::getCharValue)
PartiQLValueType.STRING -> writeScalar(data, "string", PartiQLCursor::getStringValue)
PartiQLValueType.SYMBOL -> writeScalar(data, "symbol", PartiQLCursor::getSymbolValue)
PartiQLValueType.BINARY -> writeScalar(data, "binary", PartiQLCursor::getBinaryValue)
PartiQLValueType.BYTE -> writeScalar(data, "byte", PartiQLCursor::getByteValue)
PartiQLValueType.BLOB -> writeScalar(data, "blob", PartiQLCursor::getBlobValue)
PartiQLValueType.CLOB -> writeScalar(data, "clob", PartiQLCursor::getClobValue)
PartiQLValueType.DATE -> writeScalar(data, "date") { it.dateValue.getLiteralString() }
PartiQLValueType.TIME -> writeScalar(data, "time") { it.timeValue.getLiteralString() }
PartiQLValueType.TIMESTAMP -> writeScalar(data, "timestamp") { it.timestampValue.getLiteralString() }
PartiQLValueType.INTERVAL -> writeScalar(data, "interval", PartiQLCursor::getIntervalValue)
PartiQLValueType.BAG -> writeCollection(data, "bag", "<<", ">>", named = false)
PartiQLValueType.LIST -> writeCollection(data, "list", "[", "]", named = false)
PartiQLValueType.SEXP -> writeCollection(data, "sexp", "(", ")", named = false)
PartiQLValueType.STRUCT -> writeCollection(data, "struct", "{", "}", named = true)
PartiQLValueType.NULL -> writeScalar(data, "null") { d -> "null".also { assert(d.isNullValue) } }
PartiQLValueType.MISSING -> writeScalar(data, "missing") { d -> "missing".also { assert(d.isMissingValue) } }
}
}

@OptIn(PartiQLValueExperimental::class)
private fun writeCollection(data: PartiQLCursor, type: String, prefix: String, postfix: String, named: Boolean) {
if (appendPotentialNullValue(data, type)) {
return
}
// Print value prefix (AKA: << for bag, [ for list, or ( for s-exp)
appendTypePrefixIfDebugEnabled(type)
out.appendLine(prefix)

// Print children
stepIn(data)
for (child in data) {
out.append(buildIndent())
if (named) {
val fieldName = data.fieldName
out.append('\'')
out.append(fieldName)
out.append('\'')
out.append(": ")
}
writeValue(data, child)
when (data.hasNext()) {
true -> out.appendLine(",")
false -> out.appendLine()
}
}
stepOut(data)

// Print value postfix
out.append(buildIndent())
out.append(postfix)
}

private fun writeScalar(data: PartiQLCursor, type: String, transform: (PartiQLCursor) -> Any) {
if (appendPotentialNullValue(data, type)) {
return
}
appendTypePrefixIfDebugEnabled(type)
out.append(transform(data).toString())
}

private fun appendTypePrefixIfDebugEnabled(type: String) {
if (debug) {
out.append(type)
out.append("::")
}
}

/**
* @return true if the value was null and [out] was appended to; false if the value was not null and [out] was
* not appended to.
*/
private fun appendPotentialNullValue(data: PartiQLCursor, type: String): Boolean {
if (data.isNullValue) {
out.append("null")
// Print out the type of the null. AKA: null.bag
if (debug) {
out.append('.')
out.append(type)
}
return true
}
return false
}

private fun stepIn(data: PartiQLCursor) {
data.stepIn()
indent++
}

private fun stepOut(data: PartiQLCursor) {
data.stepOut()
indent--
}

private fun buildIndent(): String {
var prefix = ""
for (i in 1..indent) {
prefix += " "
}
return prefix
}

private fun Time.getLiteralString(): String {
val tz = this.timeZone.getTypeString()
return "TIME $tz '${this.hour}:${this.minute}:${this.decimalSecond}'"
}

private fun Date.getLiteralString(): String {
val dateString = "${this.year.pad(4)}-${this.month.pad()}-${this.day.pad()}"
return "DATE '$dateString'"
}

private fun Timestamp.getLiteralString(): String {
val tz = this.timeZone.getTypeString()
val dateString = "${this.year.pad(4)}-${this.month.pad()}-${this.day.pad()}"
val timeString = "${this.hour.pad()}:${this.minute.pad()}:${this.decimalSecond}"
val tzLiteral = this.timeZone.getLiteralTimeZoneString()
return "TIMESTAMP $tz '$dateString $timeString$tzLiteral'"
}

private fun TimeZone?.getTypeString() = when (this) {
null -> "WITHOUT TIME ZONE"
is TimeZone.UnknownTimeZone -> "WITH UNKNOWN TIME ZONE"
is TimeZone.UtcOffset -> "WITH TIME ZONE"
}

private fun TimeZone?.getLiteralTimeZoneString() = when (this) {
null -> ""
is TimeZone.UnknownTimeZone -> "-00:00"
is TimeZone.UtcOffset -> {
val sign = when (this.totalOffsetMinutes >= 0) {
true -> "+"
false -> "-"
}
val offset = abs(this.totalOffsetMinutes)
val hours = offset.div(60)
val minutes = offset - (hours * 60)
"$sign${hours.pad()}:${minutes.pad()}"
}
}

private fun Int.pad(length: Int = 2): String {
return this.toString().padStart(length, '0')
}
}
10 changes: 6 additions & 4 deletions partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.partiql.cli.shell

import com.amazon.ion.system.IonSystemBuilder
import com.amazon.ion.system.IonTextWriterBuilder
import com.amazon.ionelement.api.toIonValue
import com.google.common.util.concurrent.Uninterruptibles
import org.fusesource.jansi.AnsiConsole
Expand All @@ -32,6 +31,7 @@ import org.jline.utils.AttributedStyle
import org.jline.utils.AttributedStyle.BOLD
import org.jline.utils.InfoCmp
import org.joda.time.Duration
import org.partiql.cli.io.PartiQLCursorWriter
import org.partiql.cli.pipeline.Pipeline
import org.partiql.eval.PartiQLResult
import org.partiql.plugins.fs.toIon
Expand All @@ -40,8 +40,6 @@ import org.partiql.spi.BindingName
import org.partiql.spi.BindingPath
import org.partiql.spi.connector.ConnectorHandle
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.io.PartiQLValueTextWriter
import software.amazon.ion.IonSystem
import java.io.Closeable
import java.io.PrintStream
import java.nio.file.Path
Expand Down Expand Up @@ -119,9 +117,13 @@ val exiting = AtomicBoolean(false)
val doneCompiling = AtomicBoolean(true)
val donePrinting = AtomicBoolean(true)

/**
* @param debug specifies whether to print typing information or not.
*/
internal class Shell(
private val pipeline: Pipeline,
private val session: Pipeline.Session,
private val debug: Boolean
) {

private var state: State = State(false)
Expand Down Expand Up @@ -308,7 +310,7 @@ internal class Shell(
when (result) {
is PartiQLResult.Error -> throw result.cause
is PartiQLResult.Value -> {
val writer = PartiQLValueTextWriter(out)
val writer = PartiQLCursorWriter(out, debug)
writer.append(result.value)
out.appendLine()
out.appendLine()
Expand Down
Loading
Loading