Skip to content

Commit

Permalink
ES|QL: Improve support for TEXT fields in functions (elastic#106810)
Browse files Browse the repository at this point in the history
Re-submitting elastic#106688 after
a revert due to a conflict after merge
  • Loading branch information
luigidellaquila authored Mar 27, 2024
1 parent 2b6b7ae commit 3e406e2
Show file tree
Hide file tree
Showing 24 changed files with 383 additions and 36 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/106810.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 106810
summary: "ES|QL: Improve support for TEXT fields in functions"
area: ES|QL
type: bug
issues: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Description*

Returns a string representation of a date, in the provided format.
14 changes: 14 additions & 0 deletions docs/reference/esql/functions/layout/date_format.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

[discrete]
[[esql-date_format]]
=== `DATE_FORMAT`

*Syntax*

[.text-center]
image::esql/functions/signature/date_format.svg[Embedded,opts=inline]

include::../parameters/date_format.asciidoc[]
include::../description/date_format.asciidoc[]
include::../types/date_format.asciidoc[]
7 changes: 7 additions & 0 deletions docs/reference/esql/functions/parameters/date_format.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*Parameters*

`dateFormat`::
A valid date pattern

`date`::
Date expression
1 change: 1 addition & 0 deletions docs/reference/esql/functions/signature/date_format.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/reference/esql/functions/types/date_extract.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
|===
datePart | date | result
keyword | datetime | long
text | datetime | long
|===
10 changes: 10 additions & 0 deletions docs/reference/esql/functions/types/date_format.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.

*Supported types*

[%header.monospaced.styled,format=dsv,separator=|]
|===
dateFormat | date | result
keyword | datetime | keyword
text | datetime | keyword
|===
1 change: 1 addition & 0 deletions docs/reference/esql/functions/types/date_parse.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
datePattern | dateString | result
keyword | keyword | datetime
keyword | text | datetime
text | text | datetime
|===
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ auto_bucket |"double|date auto_bucket(field:integer|long|double|dat
avg |"double avg(number:double|integer|long)" |number |"double|integer|long" | "" |double | "The average of a numeric field." | false | false | true
case |"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version case(condition:boolean, trueValue...:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)" |[condition, trueValue] |["boolean", "boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version"] |["", ""] |"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version" | "Accepts pairs of conditions and values. The function returns the value that belongs to the first condition that evaluates to true." | [false, false] | true | false
ceil |"double|integer|long|unsigned_long ceil(number:double|integer|long|unsigned_long)" |number |"double|integer|long|unsigned_long" | "Numeric expression. If `null`, the function returns `null`." | "double|integer|long|unsigned_long" | "Round a number up to the nearest integer." | false | false | false
cidr_match |boolean cidr_match(ip:ip, blockX...:keyword) |[ip, blockX] |[ip, keyword] |["", "CIDR block to test the IP against."] |boolean | "Returns true if the provided IP is contained in one of the provided CIDR blocks." | [false, false] | true | false
cidr_match |"boolean cidr_match(ip:ip, blockX...:keyword|text)" |[ip, blockX] |[ip, "keyword|text"] |["", "CIDR block to test the IP against."] |boolean | "Returns true if the provided IP is contained in one of the provided CIDR blocks." | [false, false] | true | false
coalesce |"boolean|text|integer|keyword|long coalesce(first:boolean|text|integer|keyword|long, ?rest...:boolean|text|integer|keyword|long)" |first | "boolean|text|integer|keyword|long" | "Expression to evaluate" |"boolean|text|integer|keyword|long" | "Returns the first of its arguments that is not null. If all arguments are null, it returns `null`." | false | true | false
concat |"keyword concat(string1:keyword|text, string2...:keyword|text)" |[string1, string2] |["keyword|text", "keyword|text"] |["", ""] |keyword | "Concatenates two or more strings." | [false, false] | true | false
cos |"double cos(number:double|integer|long|unsigned_long)" |number |"double|integer|long|unsigned_long" | "An angle, in radians" |double | "Returns the trigonometric cosine of an angle" | false | false | false
cosh |"double cosh(number:double|integer|long|unsigned_long)" |number |"double|integer|long|unsigned_long" | "The number who's hyperbolic cosine is to be returned" |double | "Returns the hyperbolic cosine of a number" | false | false | false
count |"long count(?field:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)" |field |"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version" | "Column or literal for which to count the number of values." |long | "Returns the total number (count) of input values." | true | false | true
count_distinct |"long count_distinct(field:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|version, ?precision:integer)" |[field, precision] |["boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|version, integer"] |["Column or literal for which to count the number of distinct values.", ""] |long | "Returns the approximate number of distinct values." | [false, true] | false | true
date_diff |"integer date_diff(unit:keyword|text, startTimestamp:date, endTimestamp:date)"|[unit, startTimestamp, endTimestamp] |["keyword|text", "date", "date"] |["A valid date unit", "A string representing a start timestamp", "A string representing an end timestamp"] |integer | "Subtract 2 dates and return their difference in multiples of a unit specified in the 1st argument" | [false, false, false] | false | false
date_extract |long date_extract(datePart:keyword, date:date) |[datePart, date] |[keyword, date] |["Part of the date to extract. Can be: aligned_day_of_week_in_month; aligned_day_of_week_in_year; aligned_week_of_month; aligned_week_of_year; ampm_of_day; clock_hour_of_ampm; clock_hour_of_day; day_of_month; day_of_week; day_of_year; epoch_day; era; hour_of_ampm; hour_of_day; instant_seconds; micro_of_day; micro_of_second; milli_of_day; milli_of_second; minute_of_day; minute_of_hour; month_of_year; nano_of_day; nano_of_second; offset_seconds; proleptic_month; second_of_day; second_of_minute; year; or year_of_era.", "Date expression"] |long | "Extracts parts of a date, like year, month, day, hour." | [false, false] | false | false
date_format |keyword date_format(?dateFormat:keyword, date:date) |[dateFormat, date] |[keyword, date] |["A valid date pattern", "Date expression"] |keyword | "Returns a string representation of a date, in the provided format." | [true, false] | false | false
date_parse |"date date_parse(?datePattern:keyword, dateString:keyword|text)"|[datePattern, dateString]|["keyword", "keyword|text"]|["A valid date pattern", "A string representing a date"]|date |Parses a string into a date value | [true, false] | false | false
date_trunc |"date date_trunc(interval:keyword, date:date)" |[interval, date] |[keyword, date] |["Interval; expressed using the timespan literal syntax.", "Date expression"] |date | "Rounds down a date to the closest interval." | [false, false] | false | false
date_extract |"long date_extract(datePart:keyword|text, date:date)" |[datePart, date] |["keyword|text", date] |["Part of the date to extract. Can be: aligned_day_of_week_in_month; aligned_day_of_week_in_year; aligned_week_of_month; aligned_week_of_year; ampm_of_day; clock_hour_of_ampm; clock_hour_of_day; day_of_month; day_of_week; day_of_year; epoch_day; era; hour_of_ampm; hour_of_day; instant_seconds; micro_of_day; micro_of_second; milli_of_day; milli_of_second; minute_of_day; minute_of_hour; month_of_year; nano_of_day; nano_of_second; offset_seconds; proleptic_month; second_of_day; second_of_minute; year; or year_of_era.", "Date expression"] |long | "Extracts parts of a date, like year, month, day, hour." | [false, false] | false | false
date_format |"keyword date_format(?dateFormat:keyword|text, date:date)" |[dateFormat, date] |["keyword|text", date] |["A valid date pattern", "Date expression"] |keyword | "Returns a string representation of a date, in the provided format." | [true, false] | false | false
date_parse |"date date_parse(?datePattern:keyword|text, dateString:keyword|text)"|[datePattern, dateString]|["keyword|text", "keyword|text"]|["A valid date pattern", "A string representing a date"]|date |Parses a string into a date value | [true, false] | false | false
date_trunc |"date date_trunc(interval:keyword, date:date)" |[interval, date] |["keyword", date] |["Interval; expressed using the timespan literal syntax.", "Date expression"] |date | "Rounds down a date to the closest interval." | [false, false] | false | false
e |double e() | null | null | null |double | "Euler’s number." | null | false | false
ends_with |"boolean ends_with(str:keyword|text, suffix:keyword|text)" |[str, suffix] |["keyword|text", "keyword|text"] |["", ""] |boolean | "Returns a boolean that indicates whether a keyword string ends with another string" | [false, false] | false | false
floor |"double|integer|long|unsigned_long floor(number:double|integer|long|unsigned_long)" |number |"double|integer|long|unsigned_long" | "" |"double|integer|long|unsigned_long" | "Round a number down to the nearest integer." | false | false | false
Expand Down Expand Up @@ -116,17 +116,17 @@ synopsis:keyword
"double avg(number:double|integer|long)"
"boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version case(condition:boolean, trueValue...:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)"
"double|integer|long|unsigned_long ceil(number:double|integer|long|unsigned_long)"
boolean cidr_match(ip:ip, blockX...:keyword)
"boolean cidr_match(ip:ip, blockX...:keyword|text)"
"boolean|text|integer|keyword|long coalesce(first:boolean|text|integer|keyword|long, ?rest...:boolean|text|integer|keyword|long)"
"keyword concat(string1:keyword|text, string2...:keyword|text)"
"double cos(number:double|integer|long|unsigned_long)"
"double cosh(number:double|integer|long|unsigned_long)"
"long count(?field:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|unsigned_long|version)"
"long count_distinct(field:boolean|cartesian_point|date|double|geo_point|integer|ip|keyword|long|text|version, ?precision:integer)"
"integer date_diff(unit:keyword|text, startTimestamp:date, endTimestamp:date)"
long date_extract(datePart:keyword, date:date)
keyword date_format(?dateFormat:keyword, date:date)
"date date_parse(?datePattern:keyword, dateString:keyword|text)"
"long date_extract(datePart:keyword|text, date:date)"
"keyword date_format(?dateFormat:keyword|text, date:date)"
"date date_parse(?datePattern:keyword|text, dateString:keyword|text)"
"date date_trunc(interval:keyword, date:date)"
double e()
"boolean ends_with(str:keyword|text, suffix:keyword|text)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1160,3 +1160,11 @@ required_feature: esql.agg_values
null | null
// end::values-grouped-result[]
;


splitBasedOnField
from employees | where emp_no == 10001 | eval split = split("fooMbar", gender) | keep gender, split;

gender:keyword | split:keyword
M | [foo, bar]
;
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@

public class EsqlTypeResolutions {

public static Expression.TypeResolution isStringAndExact(Expression e, String operationName, TypeResolutions.ParamOrdinal paramOrd) {
Expression.TypeResolution resolution = TypeResolutions.isString(e, operationName, paramOrd);
if (resolution.unresolved()) {
return resolution;
}

return isExact(e, operationName, paramOrd);
}

public static Expression.TypeResolution isExact(Expression e, String operationName, TypeResolutions.ParamOrdinal paramOrd) {
if (e instanceof FieldAttribute fa) {
if (DataTypes.isString(fa.dataType())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlConfigurationFunction;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
import org.elasticsearch.xpack.ql.InvalidArgumentException;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.TypeResolutions;
Expand All @@ -28,10 +29,10 @@
import java.util.List;
import java.util.function.Function;

import static org.elasticsearch.xpack.esql.expression.EsqlTypeResolutions.isStringAndExact;
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.EsqlConverter.STRING_TO_CHRONO_FIELD;
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.chronoToLong;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isDate;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact;

public class DateExtract extends EsqlConfigurationFunction {

Expand All @@ -42,7 +43,7 @@ public DateExtract(
Source source,
// Need to replace the commas in the description here with semi-colon as there's a bug in the CSV parser
// used in the CSVTests and fixing it is not trivial
@Param(name = "datePart", type = { "keyword" }, description = """
@Param(name = "datePart", type = { "keyword", "text" }, description = """
Part of the date to extract.
Can be: aligned_day_of_week_in_month; aligned_day_of_week_in_year; aligned_week_of_month;
aligned_week_of_year; ampm_of_day; clock_hour_of_ampm; clock_hour_of_day; day_of_month; day_of_week;
Expand Down Expand Up @@ -76,7 +77,7 @@ private ChronoField chronoField() {
if (chronoField == null) {
Expression field = children().get(0);
try {
if (field.foldable() && field.dataType() == DataTypes.KEYWORD) {
if (field.foldable() && EsqlDataTypes.isString(field.dataType())) {
chronoField = (ChronoField) STRING_TO_CHRONO_FIELD.convert(field.fold());
}
} catch (Exception e) {
Expand Down
Loading

0 comments on commit 3e406e2

Please sign in to comment.