77
88package org .elasticsearch .xpack .esql .plan ;
99
10+ import org .elasticsearch .core .Nullable ;
1011import org .elasticsearch .transport .RemoteClusterService ;
12+ import org .elasticsearch .xpack .esql .core .QlIllegalArgumentException ;
13+ import org .elasticsearch .xpack .esql .core .expression .Expression ;
1114import org .elasticsearch .xpack .esql .core .type .DataType ;
15+ import org .elasticsearch .xpack .esql .expression .Foldables ;
1216import org .elasticsearch .xpack .esql .parser .ParsingException ;
1317
14- import java .util . function . Predicate ;
18+ import java .time . ZoneId ;
1519
16- public enum QuerySettings {
20+ public class QuerySettings {
1721 // TODO check cluster state and see if project routing is allowed
1822 // see https://github.com/elastic/elasticsearch/pull/134446
1923 // PROJECT_ROUTING(..., state -> state.getRemoteClusterNames().crossProjectEnabled());
20- PROJECT_ROUTING (
24+ public static final QuerySettingDef < String > PROJECT_ROUTING = new QuerySettingDef <> (
2125 "project_routing" ,
2226 DataType .KEYWORD ,
2327 true ,
2428 false ,
2529 true ,
2630 "A project routing expression, "
2731 + "used to define which projects to route the query to. "
28- + "Only supported if Cross-Project Search is enabled."
29- ),;
32+ + "Only supported if Cross-Project Search is enabled." ,
33+ (value , settings ) -> Foldables .stringLiteralValueOf (value , "Unexpected value" )
34+ );
3035
31- private String settingName ;
32- private DataType type ;
33- private final boolean serverlessOnly ;
34- private final boolean snapshotOnly ;
35- private final boolean preview ;
36- private final String description ;
37- private final Predicate <RemoteClusterService > validator ;
38-
39- QuerySettings (
40- String name ,
41- DataType type ,
42- boolean serverlessOnly ,
43- boolean preview ,
44- boolean snapshotOnly ,
45- String description ,
46- Predicate <RemoteClusterService > validator
47- ) {
48- this .settingName = name ;
49- this .type = type ;
50- this .serverlessOnly = serverlessOnly ;
51- this .preview = preview ;
52- this .snapshotOnly = snapshotOnly ;
53- this .description = description ;
54- this .validator = validator ;
55- }
56-
57- QuerySettings (String name , DataType type , boolean serverlessOnly , boolean preview , boolean snapshotOnly , String description ) {
58- this (name , type , serverlessOnly , preview , snapshotOnly , description , state -> true );
59- }
60-
61- public String settingName () {
62- return settingName ;
63- }
64-
65- public DataType type () {
66- return type ;
67- }
68-
69- public boolean serverlessOnly () {
70- return serverlessOnly ;
71- }
72-
73- public boolean snapshotOnly () {
74- return snapshotOnly ;
75- }
76-
77- public boolean preview () {
78- return preview ;
79- }
80-
81- public String description () {
82- return description ;
83- }
36+ public static final QuerySettingDef <ZoneId > TIME_ZONE = new QuerySettingDef <>(
37+ "time_zone" ,
38+ DataType .KEYWORD ,
39+ false ,
40+ true ,
41+ true ,
42+ "The default timezone to be used in the query, by the functions and commands that require it. Defaults to UTC" ,
43+ (value , _rcs ) -> {
44+ String timeZone = Foldables .stringLiteralValueOf (value , "Unexpected value" );
45+ try {
46+ return ZoneId .of (timeZone );
47+ } catch (Exception exc ) {
48+ throw new QlIllegalArgumentException ("Invalid time zone [" + timeZone + "]" );
49+ }
50+ }
51+ );
8452
85- public Predicate <RemoteClusterService > validator () {
86- return validator ;
87- }
53+ public static final QuerySettingDef <?>[] ALL_SETTINGS = { PROJECT_ROUTING , TIME_ZONE };
8854
8955 public static void validate (EsqlStatement statement , RemoteClusterService clusterService ) {
9056 for (QuerySetting setting : statement .settings ()) {
9157 boolean found = false ;
92- for (QuerySettings qs : values () ) {
93- if (qs . settingName ().equals (setting .name ())) {
58+ for (QuerySettingDef <?> def : ALL_SETTINGS ) {
59+ if (def . name ().equals (setting .name ())) {
9460 found = true ;
95- if (setting .value ().dataType () != qs .type ()) {
96- throw new ParsingException (setting .source (), "Setting [" + setting .name () + "] must be of type " + qs .type ());
61+ if (setting .value ().dataType () != def .type ()) {
62+ throw new ParsingException (setting .source (), "Setting [" + setting .name () + "] must be of type " + def .type ());
9763 }
98- if (qs .validator ().test (clusterService ) == false ) {
99- throw new ParsingException (setting .source (), "Setting [" + setting .name () + "] is not allowed" );
64+ String error = def .validator ().validate (setting .value (), clusterService );
65+ if (error != null ) {
66+ throw new ParsingException ("Error validating setting [" + setting .name () + "]: " + error );
10067 }
10168 break ;
10269 }
@@ -106,4 +73,69 @@ public static void validate(EsqlStatement statement, RemoteClusterService cluste
10673 }
10774 }
10875 }
76+
77+ /**
78+ * Definition of a query setting.
79+ *
80+ * @param name The name to be used when setting it in the query. E.g. {@code SET name=value}
81+ * @param type The allowed datatype of the setting.
82+ * @param serverlessOnly
83+ * @param preview
84+ * @param snapshotOnly
85+ * @param description The user-facing description of the setting.
86+ * @param validator A validation function to check the setting value.
87+ * Defaults to calling the {@link #parser} and returning the error message of any exception it throws.
88+ * @param parser A function to parse the setting value into the final object.
89+ * @param <T> The type of the setting value.
90+ */
91+ public record QuerySettingDef <T >(
92+ String name ,
93+ DataType type ,
94+ boolean serverlessOnly ,
95+ boolean preview ,
96+ boolean snapshotOnly ,
97+ String description ,
98+ Validator validator ,
99+ Parser <T > parser
100+ ) {
101+ public QuerySettingDef (
102+ String name ,
103+ DataType type ,
104+ boolean serverlessOnly ,
105+ boolean preview ,
106+ boolean snapshotOnly ,
107+ String description ,
108+ Parser <T > parser
109+ ) {
110+ this (name , type , serverlessOnly , preview , snapshotOnly , description , (value , rcs ) -> {
111+ try {
112+ parser .parse (value , rcs );
113+ return null ;
114+ } catch (Exception exc ) {
115+ return exc .getMessage ();
116+ }
117+ }, parser );
118+ }
119+
120+ public T get (Expression value , RemoteClusterService clusterService ) {
121+ return parser .parse (value , clusterService );
122+ }
123+
124+ @ FunctionalInterface
125+ public interface Validator {
126+ /**
127+ * Validates the setting value and returns the error message if there's an error, or null otherwise.
128+ */
129+ @ Nullable
130+ String validate (Expression value , RemoteClusterService clusterService );
131+ }
132+
133+ @ FunctionalInterface
134+ public interface Parser <T > {
135+ /**
136+ * Parses an already validated expression.
137+ */
138+ T parse (Expression value , RemoteClusterService clusterService );
139+ }
140+ }
109141}
0 commit comments