diff --git a/CHANGELOG b/CHANGELOG index 3c675dbcc6..4682b2ebc9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v2.1.0 (XXXX-XX-XX) ------------------- +* added AQL date functions + * added index memory statistics to `db..figures()` function The `figures` function will now return a sub-document `indexes`, which lists diff --git a/Documentation/UserManual/Aql.md b/Documentation/UserManual/Aql.md index fbf9452140..0cb5ed8e72 100644 --- a/Documentation/UserManual/Aql.md +++ b/Documentation/UserManual/Aql.md @@ -1046,6 +1046,117 @@ supported: - @FN{RAND()}: Returns a pseudo-random number between 0 and 1 +@subsubsection AqlFunctionsDate Date functions + +AQL offers functionality to work with dates. Dates are no datatypes of their own in +AQL (neither they are in JSON, which is often used as a format to ship data into and +out of ArangoDB). Instead, dates in AQL are internally represented by either numbers +(timestamps) or strings. The date functions in AQL provide mechanisms to convert from +a numeric timestamp to a string representation and vice versa. + +There are two date functions in AQL to create dates for further use: + +- @FN{DATE_TIMESTAMP(@FA{date})}: Creates a UTC timestamp value from @FA{date}. + +- @FN{DATE_TIMESTAMP(@FA{year}, @FA{month}, @FA{day}, @FA{hour}, @FA{minute}, @FA{second}, @FA{millisecond})}: + Same as before, but allows specifying the individual date components separately. + All parameters after @FA{day} are optional. + +- @FN{DATE_ISO8601(@FA{date})}: Returns an ISO8601 datetime string from @FA{date}. + The datetime string will always use UTC time, indicated by the `Z` at its end. + +- @FN{DATE_ISO8601(@FA{year}, @FA{month}, @FA{day}, @FA{hour}, @FA{minute}, @FA{second}, @FA{millisecond})}: + same as before, but allows specifying the individual date components separately. + All parameters after @FA{day} are optional. + +These two above date functions accept the following input values: + +- numeric timestamps, indicating the number of milliseconds elapsed since the UNIX + epoch (i.e. January 1st 1970 00:00:00 UTC). + An example timestamp value is `1399472349522`, which translates to + `2014-05-07T14:19:09.522Z`. + +- datetime strings in formats @LIT{YYYY-MM-DDTHH:MM:SS.MMM}, + @LIT{YYYY-MM-DD HH:MM:SS.MMM}, or @LIT{YYYY-MM-DD}. Milliseconds are always optional. + A timezone difference may optionally be added at the end of the string, e.g.: + `2014-05-07T14:19:09+01:00` for a one hour offset. Alternatively, a `Z` can be + specified at the end of the string to indicate UTC / Zulu time. + + An example value is `2014-05-07T14:19:09.522Z` meaning May 7th 2014, 14:19:09 and + 522 milliseconds, Zulu time. Another example value without time component is + `2014-05-07Z`. + +- individual date components as separate function arguments, in the following order: + - year + - month + - day + - hour + - minute + - second + - millisecond + + All components following `day` are optional and can be omitted. + +Please note that if no timezone offset is specified in a datestring, ArangoDB will +assume UTC time automatically. This is done to ensure portability of queries across +servers with different timezone settings, and because timestamps will always be +UTC-based. + +The following calls to `DATE_TIMESTAMP` are equivalent and will all return +`1399472349522`: + + DATE_TIMESTAMP("2014-05-07T14:19:09.522") + DATE_TIMESTAMP("2014-05-07T14:19:09.522Z") + DATE_TIMESTAMP("2014-05-07 14:19:09.522") + DATE_TIMESTAMP("2014-05-07 14:19:09.522Z") + DATE_TIMESTAMP(2014, 5, 7, 14, 19, 9, 522) + DATE_TIMESTAMP(1399472349522) + +The same is true for calls to `DATE_ISO8601` that also accepts variable input +formats: + + DATE_ISO8601("2014-05-07T14:19:09.522Z") + DATE_ISO8601("2014-05-07 14:19:09.522Z") + DATE_ISO8601(2014, 5, 7, 14, 19, 9, 522) + DATE_ISO8601(1399472349522) + +The above functions are all equivalent and will return `"2014-05-07T14:19:09.522Z"`. + +The following date functions can be used with dates created by `DATE_TIMESTAMP` and +`DATE_ISO8601`: + +- @FN{DATE_DAYOFWEEK(@FA{date})}: Returns the weekday number of @FA{date}. The + return values have the following meanings: + - 0: Sunday + - 1: Monday + - 2: Tuesday + - 3: Wednesday + - 4: Thursday + - 5: Friday + - 6: Saturday + +- @FN{DATE_YEAR(@FA{date})}: Returns the year part of @FA{date} as a number. + +- @FN{DATE_MONTH(@FA{date})}: Returns the month part of @FA{date} as a number. + +- @FN{DATE_DAY(@FA{date})}: Returns the day part of @FA{date} as a number. + +- @FN{DATE_HOUR(@FA{date})}: Returns the hour part of @FA{date} as a number. + +- @FN{DATE_MINUTE(@FA{date})}: Returns the minute part of @FA{date} as a number. + +- @FN{DATE_SECOND(@FA{date})}: Returns the seconds part of @FA{date} as a number. + +- @FN{DATE_MILLISECOND(@FA{date})}: Returns the milliseconds part of @FA{date} as a number. + +The following other date functions are also available: + +- @FN{DATE_NOW()}: Returns the current time as a timestamp. + + Note that this function is evaluated on every invocation and may return different + values when invoked multiple times in the same query. + + @subsubsection AqlFunctionsList List functions AQL supports the following functions to operate on list values: diff --git a/Documentation/UserManual/AqlTOC.md b/Documentation/UserManual/AqlTOC.md index 7cf508e920..ff210c1a4d 100644 --- a/Documentation/UserManual/AqlTOC.md +++ b/Documentation/UserManual/AqlTOC.md @@ -20,6 +20,7 @@ TOC {#AqlTOC} - @ref AqlFunctionsChecking - @ref AqlFunctionsString - @ref AqlFunctionsNumeric + - @ref AqlFunctionsDate - @ref AqlFunctionsList - @ref AqlFunctionsDocument - @ref AqlFunctionsGeo diff --git a/arangod/Ahuacatl/ahuacatl-functions.c b/arangod/Ahuacatl/ahuacatl-functions.c index 5690bb5133..ddfd1b5178 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.c +++ b/arangod/Ahuacatl/ahuacatl-functions.c @@ -708,6 +708,19 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s|a", NULL); REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL); REGISTER_FUNCTION("NEIGHBORS", "GRAPH_NEIGHBORS", false, false, "h,h,s,s|l", NULL); + + // date functions + REGISTER_FUNCTION("DATE_NOW", "DATE_NOW", false, false, "", NULL); // NOW is non-deterministic + REGISTER_FUNCTION("DATE_TIMESTAMP", "DATE_TIMESTAMP", true, false, "ns|ns,ns,ns,ns,ns,ns", NULL); + REGISTER_FUNCTION("DATE_ISO8601", "DATE_ISO8601", true, false, "ns|ns,ns,ns,ns,ns,ns", NULL); + REGISTER_FUNCTION("DATE_DAYOFWEEK", "DATE_DAYOFWEEK", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_YEAR", "DATE_YEAR", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_MONTH", "DATE_MONTH", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_DAY", "DATE_DAY", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_HOUR", "DATE_HOUR", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_MINUTE", "DATE_MINUTE", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_SECOND", "DATE_SECOND", true, false, "ns", NULL); + REGISTER_FUNCTION("DATE_MILLISECOND", "DATE_MILLISECOND", true, false, "ns", NULL); // misc functions REGISTER_FUNCTION("FAIL", "FAIL", false, false, "|s", NULL); // FAIL is non-deterministic, otherwise query optimisation will fail! diff --git a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js index 3477937b60..da0f25b2ee 100644 --- a/js/apps/system/aardvark/frontend/js/bootstrap/errors.js +++ b/js/apps/system/aardvark/frontend/js/bootstrap/errors.js @@ -162,6 +162,7 @@ "ERROR_QUERY_FAIL_CALLED" : { "code" : 1569, "message" : "FAIL(%s) called" }, "ERROR_QUERY_GEO_INDEX_MISSING" : { "code" : 1570, "message" : "no suitable geo index found for geo restriction on '%s'" }, "ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" }, + "ERROR_QUERY_INVALID_DATE_VALUE" : { "code" : 1572, "message" : "invalid date value" }, "ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" }, "ERROR_QUERY_FUNCTION_INVALID_CODE" : { "code" : 1581, "message" : "invalid user function code" }, "ERROR_QUERY_FUNCTION_NOT_FOUND" : { "code" : 1582, "message" : "user function '%s()' not found" }, diff --git a/js/common/bootstrap/errors.js b/js/common/bootstrap/errors.js index 3477937b60..da0f25b2ee 100644 --- a/js/common/bootstrap/errors.js +++ b/js/common/bootstrap/errors.js @@ -162,6 +162,7 @@ "ERROR_QUERY_FAIL_CALLED" : { "code" : 1569, "message" : "FAIL(%s) called" }, "ERROR_QUERY_GEO_INDEX_MISSING" : { "code" : 1570, "message" : "no suitable geo index found for geo restriction on '%s'" }, "ERROR_QUERY_FULLTEXT_INDEX_MISSING" : { "code" : 1571, "message" : "no suitable fulltext index found for fulltext query on '%s'" }, + "ERROR_QUERY_INVALID_DATE_VALUE" : { "code" : 1572, "message" : "invalid date value" }, "ERROR_QUERY_FUNCTION_INVALID_NAME" : { "code" : 1580, "message" : "invalid user function name" }, "ERROR_QUERY_FUNCTION_INVALID_CODE" : { "code" : 1581, "message" : "invalid user function code" }, "ERROR_QUERY_FUNCTION_NOT_FOUND" : { "code" : 1582, "message" : "user function '%s()' not found" }, diff --git a/js/server/modules/org/arangodb/ahuacatl.js b/js/server/modules/org/arangodb/ahuacatl.js index 93b0722518..63f27b949b 100644 --- a/js/server/modules/org/arangodb/ahuacatl.js +++ b/js/server/modules/org/arangodb/ahuacatl.js @@ -3724,6 +3724,238 @@ function FAIL (message) { THROW(INTERNAL.errors.ERROR_QUERY_FAIL_CALLED, ""); } +// ----------------------------------------------------------------------------- +// --SECTION-- date functions +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief helper function for date creation +//////////////////////////////////////////////////////////////////////////////// + +function MAKE_DATE (args, func) { + "use strict"; + + var weight; + var i, n = args.length; + + if (n === 1) { + // called with one argument only + weight = TYPEWEIGHT(args[0]); + + if (weight !== TYPEWEIGHT_NUMBER) { + if (weight !== TYPEWEIGHT_STRING) { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func); + } + + // argument is a string + + // append zulu time specifier if no other present + if (! args[0].match(/([zZ]|[+\-]\d+(:\d+)?)$/) || + (args[0].match(/-\d+(:\d+)?$/) && ! args[0].match(/[tT ]/))) { + args[0] += 'Z'; + } + } + + return new Date(args[0]); + } + + // called with more than one argument + + if (n < 3) { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH, func); + } + + for (i = 0; i < n; ++i) { + weight = TYPEWEIGHT(args[i]); + + if (weight === TYPEWEIGHT_NULL) { + args[i] = 0; + } + else { + if (weight === TYPEWEIGHT_STRING) { + args[i] = parseInt(args[i], 10); + } + else if (weight !== TYPEWEIGHT_NUMBER) { + THROW(INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH, func); + } + + if (args[i] < 0) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE, func); + } + + if (i === 1) { + // an exception for handling months: months are 0-based in JavaScript, + // but 1-based in AQL + args[i]--; + } + } + } + + return new Date(Date.UTC.apply(null, args)); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the number of milliseconds since the Unix epoch +/// +/// this function is evaluated on every call +//////////////////////////////////////////////////////////////////////////////// + +function DATE_NOW () { + "use strict"; + + return Date.now(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the timestamp of the date passed (in milliseconds) +//////////////////////////////////////////////////////////////////////////////// + +function DATE_TIMESTAMP () { + "use strict"; + + try { + return MAKE_DATE(arguments, "DATE_TIMESTAMP").getTime(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the ISO string representation of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_ISO8601 () { + "use strict"; + + try { + return MAKE_DATE(arguments, "DATE_ISO8601").toISOString(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the weekday of the date passed (0 = Sunday, 1 = Monday etc.) +//////////////////////////////////////////////////////////////////////////////// + +function DATE_DAYOFWEEK (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_DAYOFWEEK").getUTCDay(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the year of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_YEAR (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_YEAR").getUTCFullYear(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the month of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_MONTH (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_MONTH").getUTCMonth() + 1; + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the day of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_DAY (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_DAY").getUTCDate(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the hours of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_HOUR (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_HOUR").getUTCHours(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the minutes of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_MINUTE (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_MINUTE").getUTCMinutes(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the seconds of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_SECOND (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_SECOND").getUTCSeconds(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief return the milliseconds of the date passed +//////////////////////////////////////////////////////////////////////////////// + +function DATE_MILLISECOND (value) { + "use strict"; + + try { + return MAKE_DATE([ value ], "DATE_MILLISECOND").getUTCMilliseconds(); + } + catch (err) { + THROW(INTERNAL.errors.ERROR_QUERY_INVALID_DATE_VALUE); + } +} + // ----------------------------------------------------------------------------- // --SECTION-- graph functions // ----------------------------------------------------------------------------- @@ -4414,6 +4646,17 @@ exports.SLEEP = SLEEP; exports.CURRENT_DATABASE = CURRENT_DATABASE; exports.CURRENT_USER = CURRENT_USER; exports.FAIL = FAIL; +exports.DATE_NOW = DATE_NOW; +exports.DATE_TIMESTAMP = DATE_TIMESTAMP; +exports.DATE_ISO8601 = DATE_ISO8601; +exports.DATE_DAYOFWEEK = DATE_DAYOFWEEK; +exports.DATE_YEAR = DATE_YEAR; +exports.DATE_MONTH = DATE_MONTH; +exports.DATE_DAY = DATE_DAY; +exports.DATE_HOUR = DATE_HOUR; +exports.DATE_MINUTE = DATE_MINUTE; +exports.DATE_SECOND = DATE_SECOND; +exports.DATE_MILLISECOND = DATE_MILLISECOND; exports.reload = reloadUserFunctions; diff --git a/js/server/tests/ahuacatl-functions.js b/js/server/tests/ahuacatl-functions.js index 0e0f347f3b..34872a7d50 100644 --- a/js/server/tests/ahuacatl-functions.js +++ b/js/server/tests/ahuacatl-functions.js @@ -3788,6 +3788,904 @@ function ahuacatlFunctionsTestSuite () { assertTrue(diff >= 1.5 && diff <= 2.5); }, +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_now function +//////////////////////////////////////////////////////////////////////////////// + + testDateNowInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_NOW(1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_NOW(1, 1)"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_now function +//////////////////////////////////////////////////////////////////////////////// + + testDateNow : function () { + var time = require("internal").time() * 1000; + var actual = getQueryResults("RETURN DATE_NOW()")[0]; + + // allow for some small tolerance + assertTrue(Math.abs(time - actual) <= 1500); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_dayofweek function +//////////////////////////////////////////////////////////////////////////////// + + testDateDayOfWeekInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_DAYOFWEEK()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_DAYOFWEEK(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAYOFWEEK(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAYOFWEEK(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAYOFWEEK([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAYOFWEEK({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_dayofweek function +//////////////////////////////////////////////////////////////////////////////// + + testDateDayOfWeek : function () { + var values = [ + [ "2000-04-29", 6 ], + [ "2000-04-29Z", 6 ], + [ "2012-02-12 13:24:12", 0 ], + [ "2012-02-12 13:24:12Z", 0 ], + [ "2012-02-12 23:59:59.991", 0 ], + [ "2012-02-12 23:59:59.991Z", 0 ], + [ "2012-02-12", 0 ], + [ "2012-02-12Z", 0 ], + [ "2012-02-12T13:24:12Z", 0 ], + [ "2012-02-12Z", 0 ], + [ "2012-2-12Z", 0 ], + [ "1910-01-02T03:04:05Z", 0 ], + [ "1910-01-02 03:04:05Z", 0 ], + [ "1910-01-02", 0 ], + [ "1910-01-02Z", 0 ], + [ "1970-01-01T01:05:27", 4 ], + [ "1970-01-01T01:05:27Z", 4 ], + [ "1970-01-01 01:05:27Z", 4 ], + [ "1970-1-1Z", 4 ], + [ "1221-02-28T23:59:59Z", 0 ], + [ "1221-02-28 23:59:59Z", 0 ], + [ "1221-02-28Z", 0 ], + [ "1221-2-28Z", 0 ], + [ "1000-12-24T04:12:00Z", 3 ], + [ "1000-12-24Z", 3 ], + [ "1000-12-24 04:12:00Z", 3 ], + [ "6789-12-31T23:59:58.99Z", 0 ], + [ "6789-12-31Z", 0 ], + [ "9999-12-31T23:59:59.999Z", 5 ], + [ "9999-12-31Z", 5 ], + [ "9999-12-31z", 5 ], + [ "9999-12-31", 5 ], + [ "2012Z", 0 ], + [ "2012z", 0 ], + [ "2012", 0 ], + [ "2012-1Z", 0 ], + [ "2012-1z", 0 ], + [ "2012-1-1z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-01Z", 0 ], + [ " 2012-01-01Z", 0 ], + [ " 2012-01-01z", 0 ], + [ 1399395674000, 2 ], + [ 60123, 4 ], + [ 1, 4 ], + [ 0, 4 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_DAYOFWEEK(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_year function +//////////////////////////////////////////////////////////////////////////////// + + testDateDayOfWeekInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_YEAR()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_YEAR(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_YEAR(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_YEAR(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_YEAR([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_YEAR({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_year function +//////////////////////////////////////////////////////////////////////////////// + + testDateYear : function () { + var values = [ + [ "2000-04-29Z", 2000 ], + [ "2012-02-12 13:24:12Z", 2012 ], + [ "2012-02-12 23:59:59.991Z", 2012 ], + [ "2012-02-12Z", 2012 ], + [ "2012-02-12T13:24:12Z", 2012 ], + [ "2012-02-12Z", 2012 ], + [ "2012-2-12Z", 2012 ], + [ "1910-01-02T03:04:05Z", 1910 ], + [ "1910-01-02 03:04:05Z", 1910 ], + [ "1910-01-02Z", 1910 ], + [ "1970-01-01T01:05:27Z", 1970 ], + [ "1970-01-01 01:05:27Z", 1970 ], + [ "1970-1-1Z", 1970 ], + [ "1221-02-28T23:59:59Z", 1221 ], + [ "1221-02-28 23:59:59Z", 1221 ], + [ "1221-02-28Z", 1221 ], + [ "1221-2-28Z", 1221 ], + [ "1000-12-24T04:12:00Z", 1000 ], + [ "1000-12-24Z", 1000 ], + [ "1000-12-24 04:12:00Z", 1000 ], + [ "6789-12-31T23:59:58.99Z", 6789 ], + [ "6789-12-31Z", 6789 ], + [ "9999-12-31T23:59:59.999Z", 9999 ], + [ "9999-12-31Z", 9999 ], + [ "9999-12-31z", 9999 ], + [ "9999-12-31", 9999 ], + [ "2012Z", 2012 ], + [ "2012z", 2012 ], + [ "2012", 2012 ], + [ "2012-1Z", 2012 ], + [ "2012-1z", 2012 ], + [ "2012-1-1z", 2012 ], + [ "2012-01-01Z", 2012 ], + [ "2012-01-01Z", 2012 ], + [ " 2012-01-01Z", 2012 ], + [ " 2012-01-01z", 2012 ], + [ 1399395674000, 2014 ], + [ 60123, 1970 ], + [ 1, 1970 ], + [ 0, 1970 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_YEAR(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_month function +//////////////////////////////////////////////////////////////////////////////// + + testDateMonthInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MONTH()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MONTH(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MONTH(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MONTH(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MONTH([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MONTH({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_month function +//////////////////////////////////////////////////////////////////////////////// + + testDateMonth : function () { + var values = [ + [ "2000-04-29Z", 4 ], + [ "2012-02-12 13:24:12Z", 2 ], + [ "2012-02-12 23:59:59.991Z", 2 ], + [ "2012-02-12Z", 2 ], + [ "2012-02-12T13:24:12Z", 2 ], + [ "2012-02-12Z", 2 ], + [ "2012-2-12Z", 2 ], + [ "1910-01-02T03:04:05Z", 1 ], + [ "1910-01-02 03:04:05Z", 1 ], + [ "1910-01-02Z", 1 ], + [ "1970-01-01T01:05:27Z", 1 ], + [ "1970-01-01 01:05:27Z", 1 ], + [ "1970-1-1Z", 1 ], + [ "1221-02-28T23:59:59Z", 2 ], + [ "1221-02-28 23:59:59Z", 2 ], + [ "1221-02-28Z", 2 ], + [ "1221-2-28Z", 2 ], + [ "1000-12-24T04:12:00Z", 12 ], + [ "1000-12-24Z", 12 ], + [ "1000-12-24 04:12:00Z", 12 ], + [ "6789-12-31T23:59:58.99Z", 12 ], + [ "6789-12-31Z", 12 ], + [ "9999-12-31T23:59:59.999Z", 12 ], + [ "9999-12-31Z", 12 ], + [ "9999-12-31z", 12 ], + [ "9999-12-31", 12 ], + [ "2012Z", 1 ], + [ "2012z", 1 ], + [ "2012", 1 ], + [ "2012-2Z", 2 ], + [ "2012-1Z", 1 ], + [ "2012-1z", 1 ], + [ "2012-1-1z", 1 ], + [ "2012-01-01Z", 1 ], + [ "2012-01-01Z", 1 ], + [ " 2012-01-01Z", 1 ], + [ " 2012-01-01z", 1 ], + [ 1399395674000, 5 ], + [ 60123, 1 ], + [ 1, 1 ], + [ 0, 1 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_MONTH(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_day function +//////////////////////////////////////////////////////////////////////////////// + + testDateDayInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_DAY()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_DAY(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAY(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAY(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAY([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_DAY({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_day function +//////////////////////////////////////////////////////////////////////////////// + + testDateDay : function () { + var values = [ + [ "2000-04-29Z", 29 ], + [ "2012-02-12 13:24:12Z", 12 ], + [ "2012-02-12 23:59:59.991Z", 12 ], + [ "2012-02-12Z", 12 ], + [ "2012-02-12T13:24:12Z", 12 ], + [ "2012-02-12Z", 12 ], + [ "2012-2-12Z", 12 ], + [ "1910-01-02T03:04:05Z", 2 ], + [ "1910-01-02 03:04:05Z", 2 ], + [ "1910-01-02Z", 2 ], + [ "1970-01-01T01:05:27Z", 1 ], + [ "1970-01-01 01:05:27Z", 1 ], + [ "1970-1-1Z", 1 ], + [ "1221-02-28T23:59:59Z", 28 ], + [ "1221-02-28 23:59:59Z", 28 ], + [ "1221-02-28Z", 28 ], + [ "1221-2-28Z", 28 ], + [ "1000-12-24T04:12:00Z", 24 ], + [ "1000-12-24Z", 24 ], + [ "1000-12-24 04:12:00Z", 24 ], + [ "6789-12-31T23:59:58.99Z", 31 ], + [ "6789-12-31Z", 31 ], + [ "9999-12-31T23:59:59.999Z", 31 ], + [ "9999-12-31Z", 31 ], + [ "9999-12-31z", 31 ], + [ "9999-12-31", 31 ], + [ "2012Z", 1 ], + [ "2012z", 1 ], + [ "2012", 1 ], + [ "2012-2Z", 1 ], + [ "2012-1Z", 1 ], + [ "2012-1z", 1 ], + [ "2012-1-1z", 1 ], + [ "2012-01-01Z", 1 ], + [ "2012-01-01Z", 1 ], + [ "2012-01-02Z", 2 ], + [ "2012-01-2Z", 2 ], + [ " 2012-01-01Z", 1 ], + [ " 2012-01-01z", 1 ], + [ 1399395674000, 6 ], + [ 60123, 1 ], + [ 1, 1 ], + [ 0, 1 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_DAY(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_hour function +//////////////////////////////////////////////////////////////////////////////// + + testDateHourInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_HOUR()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_HOUR(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_HOUR(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_HOUR(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_HOUR([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_HOUR({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_hour function +//////////////////////////////////////////////////////////////////////////////// + + testDateHour : function () { + var values = [ + [ "2000-04-29", 0 ], + [ "2000-04-29Z", 0 ], + [ "2012-02-12 13:24:12", 13 ], + [ "2012-02-12 13:24:12Z", 13 ], + [ "2012-02-12 23:59:59.991Z", 23 ], + [ "2012-02-12", 0 ], + [ "2012-02-12Z", 0 ], + [ "2012-02-12T13:24:12", 13 ], + [ "2012-02-12T13:24:12Z", 13 ], + [ "2012-02-12", 0 ], + [ "2012-02-12Z", 0 ], + [ "2012-2-12Z", 0 ], + [ "1910-01-02T03:04:05", 3 ], + [ "1910-01-02T03:04:05Z", 3 ], + [ "1910-01-02 03:04:05Z", 3 ], + [ "1910-01-02Z", 0 ], + [ "1970-01-01T01:05:27Z", 1 ], + [ "1970-01-01 01:05:27Z", 1 ], + [ "1970-01-01T12:05:27Z", 12 ], + [ "1970-01-01 12:05:27Z", 12 ], + [ "1970-1-1Z", 0 ], + [ "1221-02-28T23:59:59Z", 23 ], + [ "1221-02-28 23:59:59Z", 23 ], + [ "1221-02-28Z", 0 ], + [ "1221-2-28Z", 0 ], + [ "1000-12-24T04:12:00Z", 4 ], + [ "1000-12-24Z", 0 ], + [ "1000-12-24 04:12:00Z", 4 ], + [ "6789-12-31T23:59:58.99Z", 23 ], + [ "6789-12-31Z", 0 ], + [ "9999-12-31T23:59:59.999Z", 23 ], + [ "9999-12-31Z", 0 ], + [ "9999-12-31z", 0 ], + [ "9999-12-31", 0 ], + [ "2012Z", 0 ], + [ "2012z", 0 ], + [ "2012", 0 ], + [ "2012-2Z", 0 ], + [ "2012-1Z", 0 ], + [ "2012-1z", 0 ], + [ "2012-1-1z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-02Z", 0 ], + [ "2012-01-2Z", 0 ], + [ " 2012-01-01Z", 0 ], + [ " 2012-01-01z", 0 ], + [ 1399395674000, 17 ], + [ 3600000, 1 ], + [ 7200000, 2 ], + [ 8200000, 2 ], + [ 60123, 0 ], + [ 1, 0 ], + [ 0, 0 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_HOUR(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_minute function +//////////////////////////////////////////////////////////////////////////////// + + testDateMinuteInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MINUTE()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MINUTE(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MINUTE(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MINUTE(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MINUTE([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MINUTE({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_minute function +//////////////////////////////////////////////////////////////////////////////// + + testDateMinute : function () { + var values = [ + [ "2000-04-29Z", 0 ], + [ "2012-02-12 13:24:12Z", 24 ], + [ "2012-02-12 23:59:59.991Z", 59 ], + [ "2012-02-12Z", 0 ], + [ "2012-02-12T13:24:12Z", 24 ], + [ "2012-02-12Z", 0 ], + [ "2012-2-12Z", 0 ], + [ "1910-01-02T03:04:05Z", 4 ], + [ "1910-01-02 03:04:05Z", 4 ], + [ "1910-01-02Z", 0 ], + [ "1970-01-01T01:05:27Z", 5 ], + [ "1970-01-01 01:05:27Z", 5 ], + [ "1970-01-01T12:05:27Z", 5 ], + [ "1970-01-01 12:05:27Z", 5 ], + [ "1970-1-1Z", 0 ], + [ "1221-02-28T23:59:59Z", 59 ], + [ "1221-02-28 23:59:59Z", 59 ], + [ "1221-02-28Z", 0 ], + [ "1221-2-28Z", 0 ], + [ "1000-12-24T04:12:00Z", 12 ], + [ "1000-12-24Z", 0 ], + [ "1000-12-24 04:12:00Z", 12 ], + [ "6789-12-31T23:59:58.99Z", 59 ], + [ "6789-12-31Z", 0 ], + [ "9999-12-31T23:59:59.999Z", 59 ], + [ "9999-12-31Z", 0 ], + [ "9999-12-31z", 0 ], + [ "9999-12-31", 0 ], + [ "2012Z", 0 ], + [ "2012z", 0 ], + [ "2012", 0 ], + [ "2012-2Z", 0 ], + [ "2012-1Z", 0 ], + [ "2012-1z", 0 ], + [ "2012-1-1z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-02Z", 0 ], + [ "2012-01-2Z", 0 ], + [ " 2012-01-01Z", 0 ], + [ " 2012-01-01z", 0 ], + [ 1399395674000, 1 ], + [ 3600000, 0 ], + [ 7200000, 0 ], + [ 8200000, 16 ], + [ 60123, 1 ], + [ 1, 0 ], + [ 0, 0 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_MINUTE(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_second function +//////////////////////////////////////////////////////////////////////////////// + + testDateSecondInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_SECOND()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_SECOND(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_SECOND(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_SECOND(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_SECOND([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_SECOND({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_second function +//////////////////////////////////////////////////////////////////////////////// + + testDateSecond : function () { + var values = [ + [ "2000-04-29Z", 0 ], + [ "2012-02-12 13:24:12Z", 12 ], + [ "2012-02-12 23:59:59.991Z", 59 ], + [ "2012-02-12Z", 0 ], + [ "2012-02-12T13:24:12Z", 12 ], + [ "2012-02-12Z", 0 ], + [ "2012-2-12Z", 0 ], + [ "1910-01-02T03:04:05Z", 5 ], + [ "1910-01-02 03:04:05Z", 5 ], + [ "1910-01-02Z", 0 ], + [ "1970-01-01T01:05:27Z", 27 ], + [ "1970-01-01 01:05:27Z", 27 ], + [ "1970-01-01T12:05:27Z", 27 ], + [ "1970-01-01 12:05:27Z", 27 ], + [ "1970-1-1Z", 0 ], + [ "1221-02-28T23:59:59Z", 59 ], + [ "1221-02-28 23:59:59Z", 59 ], + [ "1221-02-28Z", 0 ], + [ "1221-2-28Z", 0 ], + [ "1000-12-24T04:12:00Z", 0 ], + [ "1000-12-24Z", 0 ], + [ "1000-12-24 04:12:00Z", 0 ], + [ "6789-12-31T23:59:58.99Z", 58 ], + [ "6789-12-31Z", 0 ], + [ "9999-12-31T23:59:59.999Z", 59 ], + [ "9999-12-31Z", 0 ], + [ "9999-12-31z", 0 ], + [ "9999-12-31", 0 ], + [ "2012Z", 0 ], + [ "2012z", 0 ], + [ "2012", 0 ], + [ "2012-2Z", 0 ], + [ "2012-1Z", 0 ], + [ "2012-1z", 0 ], + [ "2012-1-1z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-02Z", 0 ], + [ "2012-01-2Z", 0 ], + [ " 2012-01-01Z", 0 ], + [ " 2012-01-01z", 0 ], + [ 1399395674000, 14 ], + [ 3600000, 0 ], + [ 7200000, 0 ], + [ 8200000, 40 ], + [ 61123, 1 ], + [ 60123, 0 ], + [ 2001, 2 ], + [ 2000, 2 ], + [ 1000, 1 ], + [ 1, 0 ], + [ 0, 0 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_SECOND(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_millisecond function +//////////////////////////////////////////////////////////////////////////////// + + testDateMillisecondInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MILLISECOND()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_MILLISECOND(1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MILLISECOND(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MILLISECOND(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MILLISECOND([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_MILLISECOND({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_millisecond function +//////////////////////////////////////////////////////////////////////////////// + + testDateMillisecond : function () { + var values = [ + [ "2000-04-29Z", 0 ], + [ "2012-02-12 13:24:12Z", 0 ], + [ "2012-02-12 23:59:59.991Z", 991 ], + [ "2012-02-12Z", 0 ], + [ "2012-02-12T13:24:12Z", 0 ], + [ "2012-02-12Z", 0 ], + [ "2012-2-12Z", 0 ], + [ "1910-01-02T03:04:05Z", 0 ], + [ "1910-01-02 03:04:05Z", 0 ], + [ "1910-01-02Z", 0 ], + [ "1970-01-01T01:05:27Z", 0 ], + [ "1970-01-01 01:05:27Z", 0 ], + [ "1970-01-01T12:05:27Z", 0 ], + [ "1970-01-01 12:05:27Z", 0 ], + [ "1970-1-1Z", 0 ], + [ "1221-02-28T23:59:59Z", 0 ], + [ "1221-02-28 23:59:59Z", 0 ], + [ "1221-02-28Z", 0 ], + [ "1221-2-28Z", 0 ], + [ "1000-12-24T04:12:00Z", 0 ], + [ "1000-12-24Z", 0 ], + [ "1000-12-24 04:12:00Z", 0 ], + [ "6789-12-31T23:59:58.99Z", 990 ], + [ "6789-12-31Z", 0 ], + [ "9999-12-31T23:59:59.999Z", 999 ], + [ "9999-12-31Z", 0 ], + [ "9999-12-31z", 0 ], + [ "9999-12-31", 0 ], + [ "2012Z", 0 ], + [ "2012z", 0 ], + [ "2012", 0 ], + [ "2012-2Z", 0 ], + [ "2012-1Z", 0 ], + [ "2012-1z", 0 ], + [ "2012-1-1z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-01Z", 0 ], + [ "2012-01-02Z", 0 ], + [ "2012-01-2Z", 0 ], + [ " 2012-01-01Z", 0 ], + [ " 2012-01-01z", 0 ], + [ 1399395674000, 0 ], + [ 1399395674123, 123 ], + [ 3600000, 0 ], + [ 7200000, 0 ], + [ 8200000, 0 ], + [ 8200040, 40 ], + [ 61123, 123 ], + [ 60123, 123 ], + [ 2001, 1 ], + [ 2000, 0 ], + [ 1000, 0 ], + [ 753, 753 ], + [ 53, 53 ], + [ 1, 1 ], + [ 0, 0 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_MILLISECOND(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_timestamp function +//////////////////////////////////////////////////////////////////////////////// + + testDateTimestampInvalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_TIMESTAMP()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_TIMESTAMP(1, 1, 1, 1, 1, 1, 1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_TIMESTAMP(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_TIMESTAMP(1, null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_TIMESTAMP(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_TIMESTAMP([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_TIMESTAMP({})"); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_timestamp function +//////////////////////////////////////////////////////////////////////////////// + + testDateTimestamp : function () { + var values = [ + [ "2000-04-29", 956966400000 ], + [ "2000-04-29Z", 956966400000 ], + [ "2012-02-12 13:24:12", 1329053052000 ], + [ "2012-02-12 13:24:12Z", 1329053052000 ], + [ "2012-02-12 23:59:59.991", 1329091199991 ], + [ "2012-02-12 23:59:59.991Z", 1329091199991 ], + [ "2012-02-12", 1329004800000 ], + [ "2012-02-12Z", 1329004800000 ], + [ "2012-02-12T13:24:12", 1329053052000 ], + [ "2012-02-12T13:24:12Z", 1329053052000 ], + [ "2012-02-12Z", 1329004800000 ], + [ "2012-2-12Z", 1329004800000 ], + [ "1910-01-02T03:04:05", -1893358555000 ], + [ "1910-01-02T03:04:05Z", -1893358555000 ], + [ "1910-01-02 03:04:05Z", -1893358555000 ], + [ "1910-01-02", -1893369600000 ], + [ "1910-01-02Z", -1893369600000 ], + [ "1970-01-01T01:05:27Z", 3927000 ], + [ "1970-01-01 01:05:27Z", 3927000 ], + [ "1970-01-01T12:05:27Z", 43527000 ], + [ "1970-01-01 12:05:27Z", 43527000 ], + [ "1970-1-1Z", 0 ], + [ "1221-02-28T23:59:59Z", -23631004801000 ], + [ "1221-02-28 23:59:59Z", -23631004801000 ], + [ "1221-02-28Z", -23631091200000 ], + [ "1221-2-28Z", -23631091200000 ], + [ "1000-12-24T04:12:00Z", -30579364080000 ], + [ "1000-12-24Z", -30579379200000 ], + [ "1000-12-24 04:12:00Z", -30579364080000 ], + [ "6789-12-31T23:59:58.99Z", 152104521598990 ], + [ "6789-12-31Z", 152104435200000 ], + [ "9999-12-31T23:59:59.999Z", 253402300799999 ], + [ "9999-12-31Z", 253402214400000 ], + [ "9999-12-31z", 253402214400000 ], + [ "9999-12-31", 253402214400000 ], + [ "2012Z", 1325376000000 ], + [ "2012z", 1325376000000 ], + [ "2012", 1325376000000 ], + [ "2012-2Z", 1328054400000 ], + [ "2012-1Z", 1325376000000 ], + [ "2012-1z", 1325376000000 ], + [ "2012-1-1z", 1325376000000 ], + [ "2012-01-01Z", 1325376000000 ], + [ "2012-01-01Z", 1325376000000 ], + [ "2012-01-02Z", 1325462400000 ], + [ "2012-01-2Z", 1325462400000 ], + [ " 2012-01-01Z", 1325376000000 ], + [ " 2012-01-01z", 1325376000000 ], + [ 1399395674000, 1399395674000 ], + [ 3600000, 3600000 ], + [ 7200000, 7200000 ], + [ 8200000, 8200000 ], + [ 61123, 61123 ], + [ 60123, 60123 ], + [ 2001, 2001 ], + [ 2000, 2000 ], + [ 1000, 1000 ], + [ 121, 121 ], + [ 1, 1 ], + [ 0, 0 ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_TIMESTAMP(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_iso8601 function +//////////////////////////////////////////////////////////////////////////////// + + testDateIso8601Invalid : function () { + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_ISO8601()"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_NUMBER_MISMATCH.code, "RETURN DATE_ISO8601(1, 1, 1, 1, 1, 1, 1, 1)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_ISO8601(1, null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_ISO8601(null)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_ISO8601(false)"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_ISO8601([])"); + assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN DATE_ISO8601({})"); + + var values = [ + "foobar", + "2012fjh", + " 2012tjjgg", + "", + " ", + "abc2012-01-01", + "2000-00-00", + "2001-02-11 24:00:00", + "2001-02-11 24:01:01", + "2001-02-11 25:01:01", + "2001-02-11 23:60:00", + "2001-02-11 23:01:60", + "2001-02-11 23:01:99", + "2001-00-11", + "2001-0-11", + "2001-13-11", + "2001-13-32", + "2001-01-0", + "2001-01-00", + "2001-01-32", + "2001-1-32" + ]; + + values.forEach(function(value) { + assertQueryError(errors.ERROR_QUERY_INVALID_DATE_VALUE.code, "RETURN DATE_ISO8601(@value)", { value: value }); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_iso8601 function +//////////////////////////////////////////////////////////////////////////////// + + testDateIso8601 : function () { + var values = [ + [ "2000-04-29", "2000-04-29T00:00:00.000Z" ], + [ "2000-04-29Z", "2000-04-29T00:00:00.000Z" ], + [ "2012-02-12 13:24:12", "2012-02-12T13:24:12.000Z" ], + [ "2012-02-12 13:24:12Z", "2012-02-12T13:24:12.000Z" ], + [ "2012-02-12 23:59:59.991", "2012-02-12T23:59:59.991Z" ], + [ "2012-02-12 23:59:59.991Z", "2012-02-12T23:59:59.991Z" ], + [ "2012-02-12", "2012-02-12T00:00:00.000Z" ], + [ "2012-02-12Z", "2012-02-12T00:00:00.000Z" ], + [ "2012-02-12T13:24:12", "2012-02-12T13:24:12.000Z" ], + [ "2012-02-12T13:24:12Z", "2012-02-12T13:24:12.000Z" ], + [ "2012-02-12Z", "2012-02-12T00:00:00.000Z" ], + [ "2012-2-12Z", "2012-02-12T00:00:00.000Z" ], + [ "1910-01-02T03:04:05", "1910-01-02T03:04:05.000Z" ], + [ "1910-01-02T03:04:05Z", "1910-01-02T03:04:05.000Z" ], + [ "1910-01-02 03:04:05", "1910-01-02T03:04:05.000Z" ], + [ "1910-01-02 03:04:05Z", "1910-01-02T03:04:05.000Z" ], + [ "1910-01-02", "1910-01-02T00:00:00.000Z" ], + [ "1910-01-02Z", "1910-01-02T00:00:00.000Z" ], + [ "1970-01-01T01:05:27+01:00", "1970-01-01T00:05:27.000Z" ], + [ "1970-01-01T01:05:27-01:00", "1970-01-01T02:05:27.000Z" ], + [ "1970-01-01T01:05:27", "1970-01-01T01:05:27.000Z" ], + [ "1970-01-01T01:05:27Z", "1970-01-01T01:05:27.000Z" ], + [ "1970-01-01 01:05:27Z", "1970-01-01T01:05:27.000Z" ], + [ "1970-01-01T12:05:27Z", "1970-01-01T12:05:27.000Z" ], + [ "1970-01-01 12:05:27Z", "1970-01-01T12:05:27.000Z" ], + [ "1970-1-1Z", "1970-01-01T00:00:00.000Z" ], + [ "1221-02-28T23:59:59", "1221-02-28T23:59:59.000Z" ], + [ "1221-02-28T23:59:59Z", "1221-02-28T23:59:59.000Z" ], + [ "1221-02-28 23:59:59Z", "1221-02-28T23:59:59.000Z" ], + [ "1221-02-28", "1221-02-28T00:00:00.000Z" ], + [ "1221-02-28Z", "1221-02-28T00:00:00.000Z" ], + [ "1221-2-28Z", "1221-02-28T00:00:00.000Z" ], + [ "1000-12-24T04:12:00Z", "1000-12-24T04:12:00.000Z" ], + [ "1000-12-24Z", "1000-12-24T00:00:00.000Z" ], + [ "1000-12-24 04:12:00Z", "1000-12-24T04:12:00.000Z" ], + [ "6789-12-31T23:59:58.99Z", "6789-12-31T23:59:58.990Z" ], + [ "6789-12-31Z", "6789-12-31T00:00:00.000Z" ], + [ "9999-12-31T23:59:59.999Z", "9999-12-31T23:59:59.999Z" ], + [ "9999-12-31Z", "9999-12-31T00:00:00.000Z" ], + [ "9999-12-31z", "9999-12-31T00:00:00.000Z" ], + [ "9999-12-31", "9999-12-31T00:00:00.000Z" ], + [ "2012Z", "2012-01-01T00:00:00.000Z" ], + [ "2012z", "2012-01-01T00:00:00.000Z" ], + [ "2012", "2012-01-01T00:00:00.000Z" ], + [ "2012-2Z", "2012-02-01T00:00:00.000Z" ], + [ "2012-1Z", "2012-01-01T00:00:00.000Z" ], + [ "2012-1z", "2012-01-01T00:00:00.000Z" ], + [ "2012-1-1z", "2012-01-01T00:00:00.000Z" ], + [ "2012-01-01", "2012-01-01T00:00:00.000Z" ], + [ "2012-01-01Z", "2012-01-01T00:00:00.000Z" ], + [ "2012-01-01Z", "2012-01-01T00:00:00.000Z" ], + [ "2012-01-02Z", "2012-01-02T00:00:00.000Z" ], + [ "2012-01-2Z", "2012-01-02T00:00:00.000Z" ], + [ " 2012-01-01Z", "2012-01-01T00:00:00.000Z" ], + [ " 2012-01-01z", "2012-01-01T00:00:00.000Z" ], + [ 1399395674000, "2014-05-06T17:01:14.000Z" ], + [ 3600000, "1970-01-01T01:00:00.000Z" ], + [ 7200000, "1970-01-01T02:00:00.000Z" ], + [ 8200000, "1970-01-01T02:16:40.000Z" ], + [ 61123, "1970-01-01T00:01:01.123Z" ], + [ 60123, "1970-01-01T00:01:00.123Z" ], + [ 2001, "1970-01-01T00:00:02.001Z" ], + [ 2000, "1970-01-01T00:00:02.000Z" ], + [ 1000, "1970-01-01T00:00:01.000Z" ], + [ 121, "1970-01-01T00:00:00.121Z" ], + [ 1, "1970-01-01T00:00:00.001Z" ], + [ 0, "1970-01-01T00:00:00.000Z" ] + ]; + + values.forEach(function (value) { + var actual = getQueryResults("RETURN DATE_ISO8601(@value)", { value: value[0] }); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date_iso8601 function +//////////////////////////////////////////////////////////////////////////////// + + testDateIso8601Alternative : function () { + var values = [ + [ [ 1000, 1, 1, 0, 0, 0, 0 ], "1000-01-01T00:00:00.000Z" ], + [ [ 9999, 12, 31, 23, 59, 59, 999 ], "9999-12-31T23:59:59.999Z" ], + [ [ 2012, 1, 1, 13, 12, 14, 95 ], "2012-01-01T13:12:14.095Z" ], + [ [ 1234, 12, 31, 23, 59, 59, 477 ], "1234-12-31T23:59:59.477Z" ], + [ [ 2014, 5, 7, 12, 6 ], "2014-05-07T12:06:00.000Z" ], + [ [ 2014, 5, 7, 12 ], "2014-05-07T12:00:00.000Z" ], + [ [ 2014, 5, 7 ], "2014-05-07T00:00:00.000Z" ], + [ [ 2014, 5, 7 ], "2014-05-07T00:00:00.000Z" ], + [ [ 1000, 1, 1 ], "1000-01-01T00:00:00.000Z" ], + [ [ "1000", "01", "01", "00", "00", "00", "00" ], "1000-01-01T00:00:00.000Z" ], + [ [ "1000", "1", "1", "0", "0", "0", "0" ], "1000-01-01T00:00:00.000Z" ], + [ [ "9999", "12", "31", "23", "59", "59", "999" ], "9999-12-31T23:59:59.999Z" ], + [ [ "2012", "1", "1", "13", "12", "14", "95" ], "2012-01-01T13:12:14.095Z" ], + [ [ "1234", "12", "31", "23", "59", "59", "477" ], "1234-12-31T23:59:59.477Z" ], + [ [ "2014", "05", "07", "12", "06" ], "2014-05-07T12:06:00.000Z" ], + [ [ "2014", "5", "7", "12", "6" ], "2014-05-07T12:06:00.000Z" ], + [ [ "2014", "5", "7", "12" ], "2014-05-07T12:00:00.000Z" ], + [ [ "2014", "5", "7" ], "2014-05-07T00:00:00.000Z" ], + [ [ "2014", "5", "7" ], "2014-05-07T00:00:00.000Z" ], + [ [ "1000", "01", "01" ], "1000-01-01T00:00:00.000Z" ], + [ [ "1000", "1", "1" ], "1000-01-01T00:00:00.000Z" ], + ]; + + values.forEach(function (value) { + var query = "RETURN DATE_ISO8601(" + + value[0].map(function(v) { + return JSON.stringify(v) + }).join(", ") + ")"; + + var actual = getQueryResults(query); + assertEqual([ value[1] ], actual); + }); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date transformations +//////////////////////////////////////////////////////////////////////////////// + + testDateTransformations1 : function () { + var dt = "2014-05-07T15:23:21.446"; + var actual; + + actual = getQueryResults("RETURN DATE_TIMESTAMP(DATE_YEAR(@value), DATE_MONTH(@value), DATE_DAY(@value), DATE_HOUR(@value), DATE_MINUTE(@value), DATE_SECOND(@value), DATE_MILLISECOND(@value))", { value: dt }); + assertEqual([ new Date(dt).getTime() ], actual); + + actual = getQueryResults("RETURN DATE_TIMESTAMP(DATE_YEAR(@value), DATE_MONTH(@value), DATE_DAY(@value), DATE_HOUR(@value), DATE_MINUTE(@value), DATE_SECOND(@value), DATE_MILLISECOND(@value))", { value: dt + "Z" }); + assertEqual([ new Date(dt).getTime() ], actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test date transformations +//////////////////////////////////////////////////////////////////////////////// + + testDateTransformations2 : function () { + var dt = "2014-05-07T15:23:21.446"; + var actual; + + actual = getQueryResults("RETURN DATE_ISO8601(DATE_TIMESTAMP(DATE_YEAR(@value), DATE_MONTH(@value), DATE_DAY(@value), DATE_HOUR(@value), DATE_MINUTE(@value), DATE_SECOND(@value), DATE_MILLISECOND(@value)))", { value: dt }); + assertEqual([ dt + "Z" ], actual); + + actual = getQueryResults("RETURN DATE_ISO8601(DATE_TIMESTAMP(DATE_YEAR(@value), DATE_MONTH(@value), DATE_DAY(@value), DATE_HOUR(@value), DATE_MINUTE(@value), DATE_SECOND(@value), DATE_MILLISECOND(@value)))", { value: dt + "Z" }); + assertEqual([ dt + "Z" ], actual); + }, + //////////////////////////////////////////////////////////////////////////////// /// @brief test non-existing functions //////////////////////////////////////////////////////////////////////////////// diff --git a/lib/BasicsC/errors.dat b/lib/BasicsC/errors.dat index d8e27d90ca..5e558ce599 100755 --- a/lib/BasicsC/errors.dat +++ b/lib/BasicsC/errors.dat @@ -202,6 +202,7 @@ ERROR_QUERY_LIST_EXPECTED,1563,"list expected","Will be raised when a non-list o ERROR_QUERY_FAIL_CALLED,1569,"FAIL(%s) called","Will be raised when the function FAIL() is called from inside a query." ERROR_QUERY_GEO_INDEX_MISSING,1570,"no suitable geo index found for geo restriction on '%s'","Will be raised when a geo restriction was specified but no suitable geo index is found to resolve it." ERROR_QUERY_FULLTEXT_INDEX_MISSING,1571,"no suitable fulltext index found for fulltext query on '%s'","Will be raised when a fulltext query is performed on a collection without a suitable fulltext index." +ERROR_QUERY_INVALID_DATE_VALUE,1572,"invalid date value","Will be raised when a value cannot be converted to a date." ################################################################################ ## AQL user functions diff --git a/lib/BasicsC/voc-errors.c b/lib/BasicsC/voc-errors.c index 17d4c451af..8d982de77d 100644 --- a/lib/BasicsC/voc-errors.c +++ b/lib/BasicsC/voc-errors.c @@ -158,6 +158,7 @@ void TRI_InitialiseErrorMessages (void) { REG_ERROR(ERROR_QUERY_FAIL_CALLED, "FAIL(%s) called"); REG_ERROR(ERROR_QUERY_GEO_INDEX_MISSING, "no suitable geo index found for geo restriction on '%s'"); REG_ERROR(ERROR_QUERY_FULLTEXT_INDEX_MISSING, "no suitable fulltext index found for fulltext query on '%s'"); + REG_ERROR(ERROR_QUERY_INVALID_DATE_VALUE, "invalid date value"); REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_NAME, "invalid user function name"); REG_ERROR(ERROR_QUERY_FUNCTION_INVALID_CODE, "invalid user function code"); REG_ERROR(ERROR_QUERY_FUNCTION_NOT_FOUND, "user function '%s()' not found"); diff --git a/lib/BasicsC/voc-errors.h b/lib/BasicsC/voc-errors.h index 1921c39cc7..c45b934662 100644 --- a/lib/BasicsC/voc-errors.h +++ b/lib/BasicsC/voc-errors.h @@ -376,6 +376,8 @@ extern "C" { /// - 1571: @LIT{no suitable fulltext index found for fulltext query on '\%s'} /// Will be raised when a fulltext query is performed on a collection without /// a suitable fulltext index. +/// - 1572: @LIT{invalid date value} +/// Will be raised when a value cannot be converted to a date. /// - 1580: @LIT{invalid user function name} /// Will be raised when a user function with an invalid name is registered. /// - 1581: @LIT{invalid user function code} @@ -2040,6 +2042,16 @@ void TRI_InitialiseErrorMessages (void); #define TRI_ERROR_QUERY_FULLTEXT_INDEX_MISSING (1571) +//////////////////////////////////////////////////////////////////////////////// +/// @brief 1572: ERROR_QUERY_INVALID_DATE_VALUE +/// +/// invalid date value +/// +/// Will be raised when a value cannot be converted to a date. +//////////////////////////////////////////////////////////////////////////////// + +#define TRI_ERROR_QUERY_INVALID_DATE_VALUE (1572) + //////////////////////////////////////////////////////////////////////////////// /// @brief 1580: ERROR_QUERY_FUNCTION_INVALID_NAME ///