Skip to content

Commit

Permalink
F051: Basic date and time (#109)
Browse files Browse the repository at this point in the history
In a nutshell, this adds storage support for date/time types and values.
Although, we do not yet support other features such as constants,
functions, comparisons and casting. They can come as a separate features
in the near future.

More specifically, this adds support for:

1. F051-01: `DATE` data type (including support of `DATE` literal).
2. F051-02: `TIME` data type (including support of `TIME` literal) with
fractional seconds precision of at least 0.
3. F051-03: `TIMESTAMP` data type (including support of `TIMESTAMP`
literal) with fractional seconds precision of at least 0 and 6.

The five new distinct data types and their storage requirements are:

1. `DATE` (8 bytes)
2. `TIME(n) WITH TIME ZONE` (10 bytes)
3. `TIME(n) WITHOUT TIME ZONE` or `TIME(n)` (8 bytes)
4. `TIMESTAMP(n) WITH TIME ZONE` (10 bytes)
5. `TIMESTAMP(n) WITHOUT TIME ZONE` or `TIMESTAMP(n)` (8 bytes)
  • Loading branch information
elliotchance authored Jul 3, 2022
1 parent dc4b549 commit 126fe94
Show file tree
Hide file tree
Showing 19 changed files with 1,779 additions and 120 deletions.
106 changes: 99 additions & 7 deletions docs/data-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,105 @@ is an alias for ``CHARACTER(1)``.
1. The *n* limit is not yet enforced.
2. Values are not actually space padded.

Date and Time Types
-------------------

``DATE``
^^^^^^^^

A ``DATE`` holds a year-month-day value, such as ``2010-10-25``.

A ``DATE`` value can be created with the ``DATE '2010-10-25'`` literal
expression.

Valid date ranges are between ``0000-01-01`` and ``9999-12-31``.

A ``DATE`` is stored as 8 bytes.

``TIME(n) WITH TIME ZONE``
^^^^^^^^^^^^^^^^^^^^^^^^^^

Holds a time as hour-minute-second-timezone (without respect to a date),
for example: ``15:12:47+05:30``.

The ``(n)`` describes the sub-second resolution to be stored. It must be
inclusively between 0 (whole seconds) and 6 (microseconds). If omitted, 0 is
used.

A ``TIME(n) WITH TIME ZONE`` value is created with the ``TIME 'VALUE'`` literal
expression. The ``VALUE`` itself will determine whether the time has a time zone
and its precision. For example:

.. list-table::
:header-rows: 1

* - Expr
- Type

* - ``TIME '15:12:47'``
- ``TIME(0) WITHOUT TIME ZONE``

* - ``TIME '15:12:47.123'``
- ``TIME(3) WITHOUT TIME ZONE``

* - ``TIME '15:12:47+05:30'``
- ``TIME(0) WITH TIME ZONE``

* - ``TIME '15:12:47.000000+05:30'``
- ``TIME(6) WITH TIME ZONE``

A ``TIME WITH TIME ZONE`` (with any precision) is stored as 10 bytes.

``TIME(n) WITHOUT TIME ZONE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This works the same way as ``TIME(n) WITH TIME ZONE`` except there is no
time zone component.

A ``TIME WITHOUT TIME ZONE`` (with any precision) is stored as 8 bytes.

``TIMESTAMP(n) WITH TIME ZONE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Holds a timestamp as year-month-day-hour-minute-second-timezone, for example:
``2010-10-25 15:12:47+05:30``.

The ``(n)`` describes the sub-second resolution to be stored. It must be
inclusively between 0 (whole seconds) and 6 (microseconds). If omitted, 6 is
used. This is different from the behavior of ``TIME`` that uses 0 by default.

A ``TIMESTAMP(n) WITH TIME ZONE`` value is created with the
``TIMESTAMP 'VALUE'`` literal expression. The ``VALUE`` itself will determine
whether the timestamp has a time zone and its precision. For example:

.. list-table::
:header-rows: 1

* - Expr
- Type

* - ``TIMESTAMP '2010-10-25 15:12:47'``
- ``TIMESTAMP(0) WITHOUT TIME ZONE``

* - ``TIMESTAMP '2010-10-25 15:12:47.123'``
- ``TIMESTAMP(3) WITHOUT TIME ZONE``

* - ``TIMESTAMP '2010-10-25 15:12:47+05:30'``
- ``TIMESTAMP(0) WITH TIME ZONE``

* - ``TIMESTAMP '2010-10-25 15:12:47.000000+05:30'``
- ``TIMESTAMP(6) WITH TIME ZONE``

A ``TIMESTAMP WITH TIME ZONE`` (with any precision) is stored as 10 bytes.

``TIMESTAMP(n) WITHOUT TIME ZONE``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This works the same way as ``TIMESTAMP(n) WITH TIME ZONE`` except there is no
time zone component.

A ``TIMESTAMP WITHOUT TIME ZONE`` (with any precision) is stored as 8 bytes.

Unsupported Data Types
----------------------

Expand Down Expand Up @@ -136,13 +235,6 @@ Some are supported, but the remaining ones that are not supported:

1. ``DECFLOAT``

<datetime type>
^^^^^^^^^^^^^^^

1. ``DATE``
2. ``TIME``
3. ``TIMESTAMP``

<interval type>
^^^^^^^^^^^^^^^

Expand Down
34 changes: 33 additions & 1 deletion docs/sql-compliance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,41 @@ The following table is **not complete** and will be filled in as time goes on.
- All comparison operators are supported (rather than just =)

* - **F051**
- **Unknown**
- **Partial**
- **Basic date and time**

* - F051-01
- Yes
- ``DATE`` data type (including support of ``DATE`` literal)

* - F051-02
- Yes
- ``TIME`` data type (including support of ``TIME`` literal) with fractional seconds precision of at least 0.

* - F051-03
- Yes
- ``TIMESTAMP`` data type (including support of ``TIMESTAMP`` literal) with fractional seconds precision of at least 0 and 6.

* - F051-04
- No
- Comparison predicate on ``DATE``, ``TIME``, and ``TIMESTAMP`` data types

* - F051-05
- No
- Explicit ``CAST`` between date-time types and character string types

* - F051-06
- No
- ``CURRENT_DATE``

* - F051-07
- No
- ``LOCALTIME``

* - F051-08
- No
- ``LOCALTIMESTAMP``

* - **F081**
- **Unknown**
- **UNION and EXCEPT in views**
Expand Down
4 changes: 3 additions & 1 deletion generate-grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ def var_name(name):
if node.children.len == 0 {
match node.value.name {
'^integer' {
return [EarleyValue(new_integer_value(node.value.end_column.value.int()))]
return [EarleyValue(node.value.end_column.value.int())]
}
'^identifier' {
return [EarleyValue(new_identifier(node.value.end_column.value))]
Expand Down Expand Up @@ -513,6 +513,8 @@ def parse_tree(text):
# parse_tree("SELECT * FROM ( VALUES ROW ( 123 ) , ROW ( 456 ) )")
# parse_tree("SELECT x FROM ( SELECT y FROM t2 )")
# parse_tree("SELECT x , y FROM ( SELECT x , y FROM t1 )")
# parse_tree("VALUES TIMESTAMP '2022-06-30'")
# parse_tree("INSERT INTO foo ( f1 ) VALUES ( TIMESTAMP '2022-06-30' )")

for arg in sys.argv[1:]:
print(arg)
Expand Down
81 changes: 75 additions & 6 deletions grammar.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<character string type>
| <numeric type>
| <boolean type>
| <datetime type>

<character string type> /* Type */ ::=
CHARACTER -> character
Expand All @@ -95,13 +96,13 @@

<right paren> ::= ")"

<character length> /* Value */ ::=
<character length> /* int */ ::=
<length>

<length> /* Value */ ::=
<length> /* int */ ::=
<unsigned integer>

<unsigned integer> /* Value */ ::=
<unsigned integer> /* int */ ::=
^integer

<comma> ::= ","
Expand All @@ -122,7 +123,7 @@
| REAL -> real
| DOUBLE PRECISION -> double_precision

<precision> /* Value */ ::=
<precision> /* int */ ::=
<unsigned integer>

<boolean type> /* Type */ ::=
Expand Down Expand Up @@ -182,8 +183,8 @@
<SQL schema statement>

<exact numeric literal> /* Value */ ::=
<unsigned integer>
| <unsigned integer> <period> -> value
<unsigned integer> -> int_value
| <unsigned integer> <period> -> int_value
| <unsigned integer> <period> <unsigned integer> -> exact_numeric_literal1
| <period> <unsigned integer> -> exact_numeric_literal2

Expand Down Expand Up @@ -245,6 +246,19 @@
<common value expression> /* Expr */ ::=
<numeric value expression>
| <string value expression>
| <datetime value expression>

<datetime value expression> /* Expr */ ::=
<datetime term>

<datetime term> /* Expr */ ::=
<datetime factor>

<datetime factor> /* Expr */ ::=
<datetime primary>

<datetime primary> /* Expr */ ::=
<value expression primary>

<value expression> /* Expr */ ::=
<common value expression>
Expand Down Expand Up @@ -365,8 +379,32 @@

<general literal> /* Value */ ::=
<character string literal>
| <datetime literal>
| <boolean literal>

<datetime literal> /* Value */ ::=
<date literal>
| <time literal>
| <timestamp literal>

<date literal> /* Value */ ::=
DATE <date string> -> date_literal

<date string> /* Value */ ::=
^string

<time literal> /* Value */ ::=
TIME <time string> -> time_literal

<time string> /* Value */ ::=
^string

<timestamp literal> /* Value */ ::=
TIMESTAMP <timestamp string> -> timestamp_literal

<timestamp string> /* Value */ ::=
^string

<boolean literal> /* Value */ ::=
TRUE -> true
| FALSE -> false
Expand Down Expand Up @@ -914,3 +952,34 @@
INNER
| <outer join type>
| <outer join type> OUTER -> string

<datetime type> /* Type */ ::=
DATE -> date_type
| TIME -> time_type
| TIME <left paren> <time precision> <right paren> -> time_prec_type
| TIME <with or without time zone> -> time_tz_type
| TIME <left paren> <time precision> <right paren>
<with or without time zone> -> time_prec_tz_type
| TIMESTAMP -> timestamp_type
| TIMESTAMP
<left paren> <timestamp precision> <right paren> -> timestamp_prec_type
| TIMESTAMP <with or without time zone> -> timestamp_tz_type
| TIMESTAMP
<left paren> <timestamp precision> <right paren>
<with or without time zone> -> timestamp_prec_tz_type

<time precision> /* int */ ::=
<time fractional seconds precision>

<time fractional seconds precision> /* int */ ::=
<unsigned integer>

<with or without time zone> /* bool */ ::=
WITH TIME ZONE -> yes
| WITHOUT TIME ZONE -> no

<timestamp precision> /* int */ ::=
<time fractional seconds precision>

<time fractional seconds precision> /* int */ ::=
<unsigned integer>
39 changes: 39 additions & 0 deletions tests/create-table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,42 @@ CREATE TABLE "Foo" (baz CHARACTER VARYING(10));

CREATE TABLE t1 (f1 CHARACTER VARYING(10), f2 FLOAT NOT NULL);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar DATE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME(3));
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME WITHOUT TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME(3) WITHOUT TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME WITH TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIME(3) WITH TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP(3));
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP WITHOUT TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP(3) WITHOUT TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP WITH TIME ZONE);
-- msg: CREATE TABLE 1

CREATE TABLE foo (bar TIMESTAMP(3) WITH TIME ZONE);
-- msg: CREATE TABLE 1
Loading

0 comments on commit 126fe94

Please sign in to comment.