Skip to content

Commit

Permalink
edgeql: Update how time and date is handled.
Browse files Browse the repository at this point in the history
The time and date scalars are:
- datetime
- naivedatetime
- naivedate
- naivetime
- timedelta

There are 3 different functions for obtaining "current" datetime:
- datetime_now
- datetime_of_statement
- datetime_of_transaction

There are also a suite of functions that extract specific date or time
parts:
- datetime_get
- date_get
- time_get
- timedelta_get
  • Loading branch information
vpetrovykh committed Nov 12, 2018
1 parent c2cb692 commit a3457f3
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 42 deletions.
13 changes: 9 additions & 4 deletions edb/lang/schema/scalars.py
Expand Up @@ -232,7 +232,9 @@ def _is_assignment_castable_impl(source: str, target: str) -> bool:
('std::str', 'std::str', 'std::str'),
('std::bytes', 'std::bytes', 'std::bytes'),
('std::datetime', 'std::timedelta', 'std::datetime'),
('std::time', 'std::timedelta', 'std::time'),
('std::naive_datetime', 'std::timedelta', 'std::naive_datetime'),
('std::naive_date', 'std::timedelta', 'std::naive_date'),
('std::naive_time', 'std::timedelta', 'std::naive_time'),
('std::timedelta', 'std::timedelta', 'std::timedelta'),
],

Expand All @@ -244,10 +246,13 @@ def _is_assignment_castable_impl(source: str, target: str) -> bool:
('std::float64', 'std::float64', 'std::float64'),
('std::decimal', 'std::decimal', 'std::decimal'),
('std::datetime', 'std::datetime', 'std::timedelta'),
('std::date', 'std::date', 'std::timedelta'),
('std::time', 'std::time', 'std::timedelta'),
('std::naive_datetime', 'std::naive_datetime', 'std::timedelta'),
('std::naive_date', 'std::naive_date', 'std::timedelta'),
('std::naive_time', 'std::naive_time', 'std::timedelta'),
('std::datetime', 'std::timedelta', 'std::datetime'),
('std::time', 'std::timedelta', 'std::time'),
('std::naive_datetime', 'std::timedelta', 'std::naive_datetime'),
('std::naive_date', 'std::timedelta', 'std::naive_date'),
('std::naive_time', 'std::timedelta', 'std::naive_time'),
('std::timedelta', 'std::timedelta', 'std::timedelta'),
],

Expand Down
12 changes: 7 additions & 5 deletions edb/lib/std/10-scalars.eql
Expand Up @@ -23,19 +23,21 @@ CREATE SCALAR TYPE std::bool EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::bytes EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::datetime EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::uuid EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::str EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::json EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::timedelta EXTENDING std::anyscalar;
CREATE SCALAR TYPE std::datetime EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::date EXTENDING std::anyscalar;
CREATE SCALAR TYPE std::naive_datetime EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::time EXTENDING std::anyscalar;
CREATE SCALAR TYPE std::naive_date EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::naive_time EXTENDING std::anyscalar;

CREATE SCALAR TYPE std::timedelta EXTENDING std::anyscalar;

CREATE ABSTRACT SCALAR TYPE std::anyreal EXTENDING std::anyscalar;

Expand Down
57 changes: 51 additions & 6 deletions edb/lib/std/30-datetimefuncs.eql
Expand Up @@ -21,21 +21,66 @@


CREATE FUNCTION
std::current_date() -> std::date
std::datetime_current() -> std::datetime
{
FROM SQL 'SELECT current_date';
FROM SQL FUNCTION 'clock_timestamp';
};


CREATE FUNCTION
std::current_time() -> std::time
std::datetime_of_transaction() -> std::datetime
{
FROM SQL 'SELECT current_time';
FROM SQL FUNCTION 'transaction_timestamp';
};


CREATE FUNCTION
std::current_datetime() -> std::datetime
std::datetime_of_statement() -> std::datetime
{
FROM SQL FUNCTION 'now';
FROM SQL FUNCTION 'statement_timestamp';
};


CREATE FUNCTION
std::datetime_get(dt: std::datetime, el: std::str) -> std::float64
{
FROM SQL $$
SELECT date_part("el", dt)
$$;
};


CREATE FUNCTION
std::datetime_get(dt: std::naive_datetime, el: std::str) -> std::float64
{
FROM SQL $$
SELECT date_part("el", dt)
$$;
};


CREATE FUNCTION
std::time_get(dt: std::naive_time, el: std::str) -> std::float64
{
FROM SQL $$
SELECT date_part("el", dt)
$$;
};


CREATE FUNCTION
std::date_get(dt: std::naive_date, el: std::str) -> std::float64
{
FROM SQL $$
SELECT date_part("el", dt)
$$;
};


CREATE FUNCTION
std::timedelta_get(dt: std::timedelta, el: std::str) -> std::float64
{
FROM SQL $$
SELECT date_part("el", dt)
$$;
};
11 changes: 6 additions & 5 deletions edb/server/pgsql/types.py
Expand Up @@ -47,8 +47,9 @@
sn.Name('std::float32'): 'float4',
sn.Name('std::uuid'): 'uuid',
sn.Name('std::datetime'): 'timestamptz',
sn.Name('std::date'): 'date',
sn.Name('std::time'): 'timetz',
sn.Name('std::naive_datetime'): 'timestamp',
sn.Name('std::naive_date'): 'date',
sn.Name('std::naive_time'): 'time',
sn.Name('std::timedelta'): 'interval',
sn.Name('std::bytes'): 'bytea',
sn.Name('std::json'): 'jsonb',
Expand All @@ -74,9 +75,9 @@
'uuid': sn.Name('std::uuid'),
'timestamp with time zone': sn.Name('std::datetime'),
'timestamptz': sn.Name('std::datetime'),
'date': sn.Name('std::date'),
'timetz': sn.Name('std::time'),
'time': sn.Name('std::time'),
'timestamp': sn.Name('std::naive_datetime'),
'date': sn.Name('std::naive_date'),
'time': sn.Name('std::naive_time'),
'interval': sn.Name('std::timedelta'),
'bytea': sn.Name('std::bytes'),
'jsonb': sn.Name('std::json'),
Expand Down
2 changes: 1 addition & 1 deletion tests/schemas/issues.eschema
Expand Up @@ -86,7 +86,7 @@ type Issue extending Named, Owned, Text:
multi link time_spent_log -> LogEntry

property start_date -> datetime:
default := SELECT current_datetime()
default := SELECT datetime_current()
# The default value of start_date will be a
# result of the EdgeQL expression above.

Expand Down
2 changes: 1 addition & 1 deletion tests/schemas/tutorial.eschema
Expand Up @@ -31,7 +31,7 @@ abstract Type Text:

abstract Type Timestamped:
required property created_on -> datetime:
default := current_datetime()
default := datetime_current()


abstract link todo:
Expand Down
38 changes: 36 additions & 2 deletions tests/test_edgeql_datatypes.py
Expand Up @@ -71,17 +71,51 @@ async def test_edgeql_dt_datetime_02(self):
['2017-10-09T00:00:00+00:00'],
])

@unittest.expectedFailure
async def test_edgeql_dt_datetime_03(self):
await self.assert_query_result('''
SELECT <tuple<str,datetime>>('foo', '2020-10-10');
SELECT (<tuple<str,datetime>>('foo', '2020-10-10')).1 +
<timedelta>'1 month';
''', [
[{'foo': '2020-10-10T00:00:00+00:00'}],
[['foo', '2020-10-10T00:00:00+00:00']],
['2020-11-10T00:00:00+00:00'],
])

async def test_edgeql_dt_naive_datetime_01(self):
await self.assert_query_result('''
SELECT <naive_datetime>'2017-10-10T13:11' + <timedelta>'1 day';
SELECT <timedelta>'1 day' + <naive_datetime>'2017-10-10T13:11';
SELECT <naive_datetime>'2017-10-10T13:11' - <timedelta>'1 day';
''', [
['2017-10-11T13:11:00'],
['2017-10-11T13:11:00'],
['2017-10-09T13:11:00'],
])

@unittest.expectedFailure
# FIXME: naive_date becomes naive_datetime after arithmetic with timedelta
async def test_edgeql_dt_naive_date_01(self):
await self.assert_query_result('''
SELECT <naive_date>'2017-10-10' + <timedelta>'1 day';
SELECT <timedelta>'1 day' + <naive_date>'2017-10-10';
SELECT <naive_date>'2017-10-10' - <timedelta>'1 day';
''', [
['2017-10-11'],
['2017-10-11'],
['2017-10-09'],
])

async def test_edgeql_dt_naive_time_01(self):
await self.assert_query_result('''
SELECT <naive_time>'10:01:01' + <timedelta>'1 hour';
SELECT <timedelta>'1 hour' + <naive_time>'10:01:01';
SELECT <naive_time>'10:01:01' - <timedelta>'1 hour';
''', [
['11:01:01'],
['11:01:01'],
['09:01:01'],
])

async def test_edgeql_dt_sequence_01(self):
await self.assert_query_result('''
INSERT Obj;
Expand Down
156 changes: 156 additions & 0 deletions tests/test_edgeql_functions.py
Expand Up @@ -689,3 +689,159 @@ async def test_edgeql_functions_sum_03(self):
''', [
[6.8],
])

async def test_edgeql_functions_datetime_current_01(self):
dt = (await self.con.execute('SELECT datetime_current();'))[0][0]
self.assertRegex(dt, r'\d+-\d+-\d+T\d+:\d+:\d+\.\d+.*')

async def test_edgeql_functions_datetime_current_02(self):
res = await self.con.execute(r'''
START TRANSACTION;
WITH MODULE schema
SELECT Type {
dt_t := datetime_of_transaction(),
dt_s := datetime_of_statement(),
dt_n := datetime_current(),
};
# NOTE: this test assumes that there's at least 1 microsecond
# time difference between statements
WITH MODULE schema
SELECT Type {
dt_t := datetime_of_transaction(),
dt_s := datetime_of_statement(),
dt_n := datetime_current(),
};
COMMIT;
''')

batch1 = res[1]
batch2 = res[2]
batches = batch1 + batch2

# all of the dt_t should be the same
set_dt_t = {t['dt_t'] for t in batches}
self.assertTrue(len(set_dt_t) == 1)

# all of the dt_s should be the same in each batch
set_dt_s1 = {t['dt_s'] for t in batch1}
set_dt_s2 = {t['dt_s'] for t in batch2}
self.assertTrue(len(set_dt_s1) == 1)
self.assertTrue(len(set_dt_s1) == 1)

# the transaction and statement datetimes should be in
# chronological order
dt_t = set_dt_t.pop()
dt_s1 = set_dt_s1.pop()
dt_s2 = set_dt_s2.pop()
self.assertTrue(dt_t <= dt_s1 < dt_s2)

# the first "now" datetime is no earlier than the statement
# for each batch
self.assertTrue(dt_s1 <= batch1[0]['dt_n'])
self.assertTrue(dt_s2 <= batch2[0]['dt_n'])

# every dt_n is already in chronological order
self.assertEqual(
[t['dt_n'] for t in batches],
sorted([t['dt_n'] for t in batches])
)
# the first dt_n is strictly earlier than the last
self.assertTrue(batches[0]['dt_n'] < batches[-1]['dt_n'])

async def test_edgeql_functions_datetime_get_01(self):
await self.assert_query_result(r'''
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'year');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'month');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'day');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'hour');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'minute');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'second');
SELECT datetime_get(
<datetime>'2018-05-07T15:01:22.306916-05', 'timezone_hour');
''', [
{2018},
{5},
{7},
{20},
{1},
{22.306916},
{0},
])

async def test_edgeql_functions_datetime_get_02(self):
await self.assert_query_result(r'''
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'year');
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'month');
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'day');
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'hour');
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'minute');
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916', 'second');
''', [
{2018},
{5},
{7},
{15},
{1},
{22.306916},
])

async def test_edgeql_functions_datetime_get_03(self):
with self.assertRaisesRegex(
exc.UnknownEdgeDBError,
'timestamp units "timezone_hour" not supported'):
await self.con.execute('''
SELECT datetime_get(
<naive_datetime>'2018-05-07T15:01:22.306916',
'timezone_hour'
);
''')

async def test_edgeql_functions_date_get_01(self):
await self.assert_query_result(r'''
SELECT date_get(<naive_date>'2018-05-07', 'year');
SELECT date_get(<naive_date>'2018-05-07', 'month');
SELECT date_get(<naive_date>'2018-05-07', 'day');
''', [
{2018},
{5},
{7},
])

async def test_edgeql_functions_time_get_01(self):
await self.assert_query_result(r'''
SELECT time_get(<naive_time>'15:01:22.306916', 'hour');
SELECT time_get(<naive_time>'15:01:22.306916', 'minute');
SELECT time_get(<naive_time>'15:01:22.306916', 'second');
''', [
{15},
{1},
{22.306916},
])

async def test_edgeql_functions_timedelta_get_01(self):
await self.assert_query_result(r'''
SELECT timedelta_get(<timedelta>'15:01:22.306916', 'hour');
SELECT timedelta_get(<timedelta>'15:01:22.306916', 'minute');
SELECT timedelta_get(<timedelta>'15:01:22.306916', 'second');
SELECT timedelta_get(<timedelta>'3 days 15:01:22', 'day');
''', [
{15},
{1},
{22.306916},
{3},
])

0 comments on commit a3457f3

Please sign in to comment.