Skip to content

Commit d581820

Browse files
authored
Merge 5f07771 into 68e4849
2 parents 68e4849 + 5f07771 commit d581820

File tree

4 files changed

+150
-4
lines changed

4 files changed

+150
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Thank you to all who have contributed!
2626
## [Unreleased]
2727

2828
### Added
29+
- Adds `PartiQLValueTextWriter` implementation of date, time, and timestamp values
2930

3031
### Changed
3132
- **Behavioral change**: The planner now does NOT support the NullType and MissingType variants of StaticType. The logic

partiql-ast/src/test/kotlin/org/partiql/ast/sql/SqlDialectTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import org.partiql.ast.builder.ast
2727
import org.partiql.ast.exprLit
2828
import org.partiql.value.PartiQLValueExperimental
2929
import org.partiql.value.boolValue
30+
import org.partiql.value.dateValue
31+
import org.partiql.value.datetime.DateTimeValue
32+
import org.partiql.value.datetime.TimeZone
3033
import org.partiql.value.decimalValue
3134
import org.partiql.value.float32Value
3235
import org.partiql.value.float64Value
@@ -39,6 +42,8 @@ import org.partiql.value.missingValue
3942
import org.partiql.value.nullValue
4043
import org.partiql.value.stringValue
4144
import org.partiql.value.symbolValue
45+
import org.partiql.value.timeValue
46+
import org.partiql.value.timestampValue
4247
import java.math.BigDecimal
4348
import java.math.BigInteger
4449
import kotlin.test.assertFails
@@ -406,6 +411,16 @@ class SqlDialectTest {
406411
expect("""hello""") {
407412
exprLit(symbolValue("hello"))
408413
},
414+
expect("DATE '0001-02-03'") {
415+
exprLit(dateValue(DateTimeValue.date(1, 2, 3)))
416+
},
417+
expect("TIME '01:02:03.456-00:30'") {
418+
exprLit(timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456), TimeZone.UtcOffset.of(-30))))
419+
},
420+
expect("TIMESTAMP '0001-02-03 04:05:06.78-00:30'") {
421+
exprLit(timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78), TimeZone.UtcOffset.of(-30))))
422+
},
423+
409424
// expect("""{{ '''Hello''' '''World''' }}""") {
410425
// exprLit(clobValue("HelloWorld".toByteArray()))
411426
// },

partiql-types/src/main/kotlin/org/partiql/value/io/PartiQLValueTextWriter.kt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.partiql.value.BagValue
1818
import org.partiql.value.BoolValue
1919
import org.partiql.value.CharValue
2020
import org.partiql.value.CollectionValue
21+
import org.partiql.value.DateValue
2122
import org.partiql.value.DecimalValue
2223
import org.partiql.value.Float32Value
2324
import org.partiql.value.Float64Value
@@ -35,9 +36,17 @@ import org.partiql.value.SexpValue
3536
import org.partiql.value.StringValue
3637
import org.partiql.value.StructValue
3738
import org.partiql.value.SymbolValue
39+
import org.partiql.value.TimeValue
40+
import org.partiql.value.TimestampValue
41+
import org.partiql.value.datetime.Date
42+
import org.partiql.value.datetime.Time
43+
import org.partiql.value.datetime.TimeZone
44+
import org.partiql.value.datetime.Timestamp
3845
import org.partiql.value.util.PartiQLValueBaseVisitor
3946
import java.io.OutputStream
4047
import java.io.PrintStream
48+
import java.math.BigDecimal
49+
import kotlin.math.abs
4150

4251
/**
4352
* [PartiQLValueWriter] which outputs PartiQL text.
@@ -192,6 +201,74 @@ public class PartiQLValueTextWriter(
192201
}
193202
}
194203

204+
override fun visitDate(v: DateValue, format: Format?) = v.toString(format) {
205+
when (val value = v.value) {
206+
null -> "null" // null.date
207+
else -> sqlString(value)
208+
}
209+
}
210+
211+
private fun padZeros(v: Int, totalDigits: Int): String = String.format("%0${totalDigits}d", v)
212+
213+
private fun sqlString(d: Date): String {
214+
val yyyy = padZeros(d.year, 4)
215+
val mm = padZeros(d.month, 2)
216+
val dd = padZeros(d.day, 2)
217+
return "DATE '$yyyy-$mm-$dd'"
218+
}
219+
220+
override fun visitTime(v: TimeValue, format: Format?) = v.toString(format) {
221+
when (val value = v.value) {
222+
null -> "null" // null.time
223+
else -> sqlString(value)
224+
}
225+
}
226+
227+
private fun sqlString(tz: TimeZone?): String {
228+
return when (tz) {
229+
null -> ""
230+
is TimeZone.UnknownTimeZone -> "-00:00"
231+
is TimeZone.UtcOffset -> {
232+
val sign = if (tz.totalOffsetMinutes < 0) {
233+
"-"
234+
} else {
235+
"+"
236+
}
237+
val hh = padZeros(abs(tz.tzHour), 2)
238+
val mm = padZeros(abs(tz.tzMinute), 2)
239+
"$sign$hh:$mm"
240+
}
241+
}
242+
}
243+
244+
private fun sqlString(t: Time): String {
245+
val hh = padZeros(t.hour, 2)
246+
val mm = padZeros(t.minute, 2)
247+
val ss = padZeros(t.decimalSecond.toInt(), 2)
248+
val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0
249+
val timeZone = sqlString(t.timeZone)
250+
return "TIME '$hh:$mm:$ss$frac$timeZone'"
251+
}
252+
253+
override fun visitTimestamp(v: TimestampValue, format: Format?) = v.toString(format) {
254+
when (val value = v.value) {
255+
null -> "null" // null.timestamp
256+
else -> sqlString(value)
257+
}
258+
}
259+
260+
private fun sqlString(t: Timestamp): String {
261+
val yyyy = padZeros(t.year, 4)
262+
val mon = padZeros(t.month, 2)
263+
val dd = padZeros(t.day, 2)
264+
val hh = padZeros(t.hour, 2)
265+
val min = padZeros(t.minute, 2)
266+
val ss = padZeros(t.decimalSecond.toInt(), 2)
267+
val frac = t.decimalSecond.remainder(BigDecimal.ONE).toString().substring(1) // drop leading 0
268+
val timeZone = sqlString(t.timeZone)
269+
return "TIMESTAMP '$yyyy-$mon-$dd $hh:$min:$ss$frac$timeZone'"
270+
}
271+
195272
override fun visitBag(v: BagValue<*>, format: Format?) = collection(v, format, "<<" to ">>")
196273

197274
override fun visitList(v: ListValue<*>, format: Format?) = collection(v, format, "[" to "]")

partiql-types/src/test/kotlin/org/partiql/value/io/PartiQLValueTextWriterTest.kt

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import org.partiql.value.PartiQLValueExperimental
1313
import org.partiql.value.bagValue
1414
import org.partiql.value.boolValue
1515
import org.partiql.value.charValue
16+
import org.partiql.value.dateValue
17+
import org.partiql.value.datetime.DateTimeValue
18+
import org.partiql.value.datetime.TimeZone
1619
import org.partiql.value.decimalValue
1720
import org.partiql.value.float32Value
1821
import org.partiql.value.float64Value
@@ -28,6 +31,8 @@ import org.partiql.value.sexpValue
2831
import org.partiql.value.stringValue
2932
import org.partiql.value.structValue
3033
import org.partiql.value.symbolValue
34+
import org.partiql.value.timeValue
35+
import org.partiql.value.timestampValue
3136
import java.io.ByteArrayOutputStream
3237
import java.io.PrintStream
3338
import java.math.BigDecimal
@@ -37,7 +42,6 @@ import java.math.BigInteger
3742
* Basic text writing test.
3843
*
3944
* TODOs
40-
* - Dates and times
4145
* - String/Symbol escapes
4246
*/
4347
class PartiQLValueTextWriterTest {
@@ -173,13 +177,62 @@ class PartiQLValueTextWriterTest {
173177
value = symbolValue("f.x"),
174178
expected = "f.x",
175179
),
180+
case(
181+
value = dateValue(DateTimeValue.date(1, 2, 3)),
182+
expected = "DATE '0001-02-03'",
183+
),
184+
case(
185+
value = dateValue(DateTimeValue.date(2020, 2, 3)),
186+
expected = "DATE '2020-02-03'",
187+
),
188+
case(
189+
value = timeValue(DateTimeValue.time(1, 2, 3)),
190+
expected = "TIME '01:02:03'",
191+
),
192+
case(
193+
value = timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456))),
194+
expected = "TIME '01:02:03.456'",
195+
),
196+
case(
197+
value = timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456), TimeZone.UnknownTimeZone)),
198+
expected = "TIME '01:02:03.456-00:00'",
199+
),
200+
case(
201+
value = timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456), TimeZone.UtcOffset.of(0))),
202+
expected = "TIME '01:02:03.456+00:00'",
203+
),
204+
case(
205+
value = timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456), TimeZone.UtcOffset.of(+30))),
206+
expected = "TIME '01:02:03.456+00:30'",
207+
),
208+
case(
209+
value = timeValue(DateTimeValue.time(1, 2, BigDecimal.valueOf(3.456), TimeZone.UtcOffset.of(-30))),
210+
expected = "TIME '01:02:03.456-00:30'",
211+
),
212+
case(
213+
value = timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78))),
214+
expected = "TIMESTAMP '0001-02-03 04:05:06.78'",
215+
),
216+
case(
217+
value = timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78), TimeZone.UnknownTimeZone)),
218+
expected = "TIMESTAMP '0001-02-03 04:05:06.78-00:00'",
219+
),
220+
case(
221+
value = timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78), TimeZone.UtcOffset.of(0))),
222+
expected = "TIMESTAMP '0001-02-03 04:05:06.78+00:00'",
223+
),
224+
case(
225+
value = timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78), TimeZone.UtcOffset.of(+30))),
226+
expected = "TIMESTAMP '0001-02-03 04:05:06.78+00:30'",
227+
),
228+
case(
229+
value = timestampValue(DateTimeValue.timestamp(1, 2, 3, 4, 5, BigDecimal.valueOf(6.78), TimeZone.UtcOffset.of(-30))),
230+
expected = "TIMESTAMP '0001-02-03 04:05:06.78-00:30'",
231+
),
176232
// TODO CLOB
177233
// TODO BINARY
178234
// TODO BYTE
179235
// TODO BLOB
180-
// TODO DATE
181-
// TODO TIME
182-
// TODO TIMESTAMP
183236
// TODO INTERVAL
184237
)
185238

0 commit comments

Comments
 (0)