1+ /*
2+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+ * or more contributor license agreements. Licensed under the Elastic License;
4+ * you may not use this file except in compliance with the Elastic License.
5+ */
6+
7+ package org .elasticsearch .xpack .eql .expression .function .scalar .string ;
8+
9+ import org .elasticsearch .xpack .ql .expression .Expression ;
10+ import org .elasticsearch .xpack .ql .expression .Expressions ;
11+ import org .elasticsearch .xpack .ql .expression .Expressions .ParamOrdinal ;
12+ import org .elasticsearch .xpack .ql .expression .FieldAttribute ;
13+ import org .elasticsearch .xpack .ql .expression .Literal ;
14+ import org .elasticsearch .xpack .ql .expression .function .OptionalArgument ;
15+ import org .elasticsearch .xpack .ql .expression .function .scalar .ScalarFunction ;
16+ import org .elasticsearch .xpack .ql .expression .gen .pipeline .Pipe ;
17+ import org .elasticsearch .xpack .ql .expression .gen .script .ScriptTemplate ;
18+ import org .elasticsearch .xpack .ql .expression .gen .script .Scripts ;
19+ import org .elasticsearch .xpack .ql .tree .NodeInfo ;
20+ import org .elasticsearch .xpack .ql .tree .Source ;
21+ import org .elasticsearch .xpack .ql .type .DataType ;
22+ import org .elasticsearch .xpack .ql .type .DataTypes ;
23+
24+ import java .util .Arrays ;
25+ import java .util .List ;
26+ import java .util .Locale ;
27+
28+ import static java .lang .String .format ;
29+ import static org .elasticsearch .xpack .eql .expression .function .scalar .string .IndexOfFunctionProcessor .doProcess ;
30+ import static org .elasticsearch .xpack .ql .expression .TypeResolutions .isInteger ;
31+ import static org .elasticsearch .xpack .ql .expression .TypeResolutions .isStringAndExact ;
32+ import static org .elasticsearch .xpack .ql .expression .gen .script .ParamsBuilder .paramsBuilder ;
33+
34+ /**
35+ * Find the first position (zero-indexed) of a string where a substring is found.
36+ * If the optional parameter start is provided, then this will find the first occurrence at or after the start position.
37+ */
38+ public class IndexOf extends ScalarFunction implements OptionalArgument {
39+
40+ private final Expression source , substring , start ;
41+
42+ public IndexOf (Source source , Expression src , Expression substring , Expression start ) {
43+ super (source , Arrays .asList (src , substring , start != null ? start : new Literal (source , null , DataTypes .NULL )));
44+ this .source = src ;
45+ this .substring = substring ;
46+ this .start = arguments ().get (2 );
47+ }
48+
49+ @ Override
50+ protected TypeResolution resolveType () {
51+ if (!childrenResolved ()) {
52+ return new TypeResolution ("Unresolved children" );
53+ }
54+
55+ TypeResolution resolution = isStringAndExact (source , sourceText (), ParamOrdinal .FIRST );
56+ if (resolution .unresolved ()) {
57+ return resolution ;
58+ }
59+
60+ resolution = isStringAndExact (substring , sourceText (), ParamOrdinal .SECOND );
61+ if (resolution .unresolved ()) {
62+ return resolution ;
63+ }
64+
65+ return isInteger (start , sourceText (), ParamOrdinal .THIRD );
66+ }
67+
68+ @ Override
69+ protected Pipe makePipe () {
70+ return new IndexOfFunctionPipe (source (), this , Expressions .pipe (source ), Expressions .pipe (substring ), Expressions .pipe (start ));
71+ }
72+
73+ @ Override
74+ public boolean foldable () {
75+ return source .foldable () && substring .foldable () && start .foldable ();
76+ }
77+
78+ @ Override
79+ public Object fold () {
80+ return doProcess (source .fold (), substring .fold (), start .fold ());
81+ }
82+
83+ @ Override
84+ protected NodeInfo <? extends Expression > info () {
85+ return NodeInfo .create (this , IndexOf ::new , source , substring , start );
86+ }
87+
88+ @ Override
89+ public ScriptTemplate asScript () {
90+ ScriptTemplate sourceScript = asScript (source );
91+ ScriptTemplate substringScript = asScript (substring );
92+ ScriptTemplate startScript = asScript (start );
93+
94+ return asScriptFrom (sourceScript , substringScript , startScript );
95+ }
96+
97+ protected ScriptTemplate asScriptFrom (ScriptTemplate sourceScript , ScriptTemplate substringScript , ScriptTemplate startScript ) {
98+ return new ScriptTemplate (format (Locale .ROOT , formatTemplate ("{eql}.%s(%s,%s,%s)" ),
99+ "indexOf" ,
100+ sourceScript .template (),
101+ substringScript .template (),
102+ startScript .template ()),
103+ paramsBuilder ()
104+ .script (sourceScript .params ())
105+ .script (substringScript .params ())
106+ .script (startScript .params ())
107+ .build (), dataType ());
108+ }
109+
110+ @ Override
111+ public ScriptTemplate scriptWithField (FieldAttribute field ) {
112+ return new ScriptTemplate (processScript (Scripts .DOC_VALUE ),
113+ paramsBuilder ().variable (field .exactAttribute ().name ()).build (),
114+ dataType ());
115+ }
116+
117+ @ Override
118+ public DataType dataType () {
119+ return DataTypes .INTEGER ;
120+ }
121+
122+ @ Override
123+ public Expression replaceChildren (List <Expression > newChildren ) {
124+ if (newChildren .size () != 3 ) {
125+ throw new IllegalArgumentException ("expected [3] children but received [" + newChildren .size () + "]" );
126+ }
127+
128+ return new IndexOf (source (), newChildren .get (0 ), newChildren .get (1 ), newChildren .get (2 ));
129+ }
130+
131+ }
0 commit comments