1010import org .elasticsearch .ResourceNotFoundException ;
1111import org .elasticsearch .action .ActionListener ;
1212import org .elasticsearch .common .Strings ;
13+ import org .elasticsearch .common .settings .Settings ;
1314import org .elasticsearch .xpack .esql .VerificationException ;
1415import org .elasticsearch .xpack .esql .expression .function .EsqlFunctionRegistry ;
1516import org .elasticsearch .xpack .esql .parser .EsqlParser ;
2728import java .util .function .Function ;
2829
2930public abstract class ViewService {
30- /**
31- * Maximum number of views referencing views referencing views.
32- */
33- private static final int MAX_VIEW_DEPTH = 10 ;
31+ private final ViewServiceConfig config ;
3432 private final EsqlFunctionRegistry functionRegistry ;
3533
36- public ViewService (EsqlFunctionRegistry functionRegistry ) {
34+ public record ViewServiceConfig (int maxViews , int maxViewSize , int maxViewDepth ) {
35+
36+ public static final String MAX_VIEWS_COUNT_SETTING = "esql.views.max_count" ;
37+ public static final String MAX_VIEWS_SIZE_SETTING = "esql.views.max_size" ;
38+ public static final String MAX_VIEWS_DEPTH_SETTING = "esql.views.max_depth" ;
39+ public static final ViewServiceConfig DEFAULT = new ViewServiceConfig (100 , 10_000 , 10 );
40+
41+ public static ViewServiceConfig fromSettings (Settings settings ) {
42+ return new ViewServiceConfig (
43+ settings .getAsInt (MAX_VIEWS_COUNT_SETTING , DEFAULT .maxViews ),
44+ settings .getAsInt (MAX_VIEWS_SIZE_SETTING , DEFAULT .maxViewSize ),
45+ settings .getAsInt (MAX_VIEWS_DEPTH_SETTING , DEFAULT .maxViewDepth )
46+ );
47+ }
48+ }
49+
50+ public ViewService (EsqlFunctionRegistry functionRegistry , ViewServiceConfig config ) {
3751 this .functionRegistry = functionRegistry ;
52+ this .config = config ;
3853 }
3954
4055 protected abstract ViewMetadata getMetadata ();
@@ -51,14 +66,14 @@ public LogicalPlan replaceViews(LogicalPlan plan, PlanTelemetry telemetry, Confi
5166 return ur ;
5267 }
5368 View view = views .views ().get (name );
54- if (seen .size () > MAX_VIEW_DEPTH ) {
55- throw viewError ("too many views referencing views " , seen );
56- }
5769 boolean alreadySeen = seen .contains (name );
5870 seen .add (name );
5971 if (alreadySeen ) {
6072 throw viewError ("circular view reference " , seen );
6173 }
74+ if (seen .size () > config .maxViewDepth ) {
75+ throw viewError ("The maximum allowed view depth of " + config .maxViewDepth + " has been exceeded: " , seen );
76+ }
6277 return resolve (view , telemetry , configuration );
6378 });
6479 if (plan .equals (prev )) {
@@ -95,10 +110,7 @@ private VerificationException viewError(String type, List<String> seen) {
95110 */
96111 public void put (String name , View view , ActionListener <Void > callback , Configuration configuration ) {
97112 assertMasterNode ();
98- new EsqlParser ().createStatement (view .query (), new QueryParams (), new PlanTelemetry (functionRegistry ), configuration );
99- // TODO should we validate this in the transport action and make it async? like plan like a query
100- // TODO postgresql does.
101-
113+ validatePutView (name , view , configuration );
102114 updateViewMetadata (callback , current -> {
103115 Map <String , View > original = getMetadata ().views ();
104116 Map <String , View > updated = new HashMap <>(original );
@@ -107,6 +119,29 @@ public void put(String name, View view, ActionListener<Void> callback, Configura
107119 });
108120 }
109121
122+ private void validatePutView (String name , View view , Configuration configuration ) {
123+ if (Strings .isNullOrEmpty (name )) {
124+ throw new IllegalArgumentException ("name is missing or empty" );
125+ }
126+ if (view == null ) {
127+ throw new IllegalArgumentException ("view is missing" );
128+ }
129+ if (Strings .isNullOrEmpty (view .query ())) {
130+ throw new IllegalArgumentException ("view query is missing or empty" );
131+ }
132+ if (view .query ().length () > config .maxViewSize ) {
133+ throw new IllegalArgumentException (
134+ "view query is too large: " + view .query ().length () + " characters, the maximum allowed is " + config .maxViewSize
135+ );
136+ }
137+ if (getMetadata ().views ().containsKey (name ) == false && getMetadata ().views ().size () >= config .maxViews ) {
138+ throw new IllegalArgumentException ("cannot add view, the maximum number of views is reached: " + config .maxViews );
139+ }
140+ new EsqlParser ().createStatement (view .query (), new QueryParams (), new PlanTelemetry (functionRegistry ), configuration );
141+ // TODO should we validate this in the transport action and make it async? like plan like a query
142+ // TODO postgresql does.
143+ }
144+
110145 /**
111146 * Gets the view by name.
112147 */
0 commit comments