3030import org .junit .ClassRule ;
3131
3232import java .io .IOException ;
33+ import java .util .Arrays ;
3334import java .util .List ;
3435import java .util .Locale ;
3536import java .util .Map ;
@@ -87,9 +88,11 @@ private void indexDocument(String index, int id, double value, String org) throw
8788
8889 @ Before
8990 public void indexDocuments () throws IOException {
91+ Settings lookupSettings = Settings .builder ().put ("index.mode" , "lookup" ).build ();
9092 String mapping = """
9193 "properties":{"value": {"type": "double"}, "org": {"type": "keyword"}}
9294 """ ;
95+
9396 createIndex ("index" , Settings .EMPTY , mapping );
9497 indexDocument ("index" , 1 , 10.0 , "sales" );
9598 indexDocument ("index" , 2 , 20.0 , "engineering" );
@@ -110,6 +113,16 @@ public void indexDocuments() throws IOException {
110113 indexDocument ("indexpartial" , 2 , 40.0 , "sales" );
111114 refresh ("indexpartial" );
112115
116+ createIndex ("lookup-user1" , lookupSettings , mapping );
117+ indexDocument ("lookup-user1" , 1 , 12.0 , "engineering" );
118+ indexDocument ("lookup-user1" , 2 , 31.0 , "sales" );
119+ refresh ("lookup-user1" );
120+
121+ createIndex ("lookup-user2" , lookupSettings , mapping );
122+ indexDocument ("lookup-user2" , 1 , 32.0 , "marketing" );
123+ indexDocument ("lookup-user2" , 2 , 40.0 , "sales" );
124+ refresh ("lookup-user2" );
125+
113126 if (aliasExists ("second-alias" ) == false ) {
114127 Request aliasRequest = new Request ("POST" , "_aliases" );
115128 aliasRequest .setJsonEntity ("""
@@ -126,6 +139,17 @@ public void indexDocuments() throws IOException {
126139 }
127140 }
128141 },
142+ {
143+ "add": {
144+ "alias": "lookup-first-alias",
145+ "index": "lookup-user1",
146+ "filter": {
147+ "term": {
148+ "org": "sales"
149+ }
150+ }
151+ }
152+ },
129153 {
130154 "add": {
131155 "alias": "second-alias",
@@ -229,22 +253,30 @@ public void testAliasFilter() throws Exception {
229253 public void testUnauthorizedIndices () throws IOException {
230254 ResponseException error ;
231255 error = expectThrows (ResponseException .class , () -> runESQLCommand ("user1" , "from index-user2 | stats sum(value)" ));
256+ assertThat (error .getMessage (), containsString ("Unknown index [index-user2]" ));
232257 assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (400 ));
233258
234259 error = expectThrows (ResponseException .class , () -> runESQLCommand ("user2" , "from index-user1 | stats sum(value)" ));
260+ assertThat (error .getMessage (), containsString ("Unknown index [index-user1]" ));
235261 assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (400 ));
236262
237263 error = expectThrows (ResponseException .class , () -> runESQLCommand ("alias_user2" , "from index-user2 | stats sum(value)" ));
264+ assertThat (error .getMessage (), containsString ("Unknown index [index-user2]" ));
238265 assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (400 ));
239266
240267 error = expectThrows (ResponseException .class , () -> runESQLCommand ("metadata1_read2" , "from index-user1 | stats sum(value)" ));
268+ assertThat (error .getMessage (), containsString ("Unknown index [index-user1]" ));
241269 assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (400 ));
242270 }
243271
244272 public void testInsufficientPrivilege () {
245- Exception error = expectThrows (Exception .class , () -> runESQLCommand ("metadata1_read2" , "FROM index-user1 | STATS sum=sum(value)" ));
273+ ResponseException error = expectThrows (
274+ ResponseException .class ,
275+ () -> runESQLCommand ("metadata1_read2" , "FROM index-user1 | STATS sum=sum(value)" )
276+ );
246277 logger .info ("error" , error );
247278 assertThat (error .getMessage (), containsString ("Unknown index [index-user1]" ));
279+ assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
248280 }
249281
250282 public void testIndexPatternErrorMessageComparison_ESQL_SearchDSL () throws Exception {
@@ -511,6 +543,63 @@ record Listen(long timestamp, String songId, double duration) {
511543 }
512544 }
513545
546+ public void testLookupJoinIndexAllowed () throws Exception {
547+ Response resp = runESQLCommand (
548+ "metadata1_read2" ,
549+ "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN `lookup-user2` ON value | KEEP x, org"
550+ );
551+ assertOK (resp );
552+ Map <String , Object > respMap = entityAsMap (resp );
553+ assertThat (
554+ respMap .get ("columns" ),
555+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
556+ );
557+ assertThat (respMap .get ("values" ), equalTo (List .of (List .of (40.0 , "sales" ))));
558+
559+ // Alias, should find the index and the row
560+ resp = runESQLCommand ("alias_user1" , "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN `lookup-first-alias` ON value | KEEP x, org" );
561+ assertOK (resp );
562+ respMap = entityAsMap (resp );
563+ assertThat (
564+ respMap .get ("columns" ),
565+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
566+ );
567+ assertThat (respMap .get ("values" ), equalTo (List .of (List .of (31.0 , "sales" ))));
568+
569+ // Alias, for a row that's filtered out
570+ resp = runESQLCommand ("alias_user1" , "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN `lookup-first-alias` ON value | KEEP x, org" );
571+ assertOK (resp );
572+ respMap = entityAsMap (resp );
573+ assertThat (
574+ respMap .get ("columns" ),
575+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
576+ );
577+ assertThat (respMap .get ("values" ), equalTo (List .of (Arrays .asList (123.0 , null ))));
578+ }
579+
580+ public void testLookupJoinIndexForbidden () {
581+ var resp = expectThrows (
582+ ResponseException .class ,
583+ () -> runESQLCommand ("metadata1_read2" , "FROM lookup-user2 | EVAL value = 10.0 | LOOKUP JOIN `lookup-user1` ON value | KEEP x" )
584+ );
585+ assertThat (resp .getMessage (), containsString ("Unknown index [lookup-user1]" ));
586+ assertThat (resp .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
587+
588+ resp = expectThrows (
589+ ResponseException .class ,
590+ () -> runESQLCommand ("metadata1_read2" , "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN `lookup-user1` ON value | KEEP x" )
591+ );
592+ assertThat (resp .getMessage (), containsString ("Unknown index [lookup-user1]" ));
593+ assertThat (resp .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
594+
595+ resp = expectThrows (
596+ ResponseException .class ,
597+ () -> runESQLCommand ("alias_user1" , "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN `lookup-user1` ON value | KEEP x" )
598+ );
599+ assertThat (resp .getMessage (), containsString ("Unknown index [lookup-user1]" ));
600+ assertThat (resp .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
601+ }
602+
514603 private void createEnrichPolicy () throws Exception {
515604 createIndex ("songs" , Settings .EMPTY , """
516605 "properties":{"song_id": {"type": "keyword"}, "title": {"type": "keyword"}, "artist": {"type": "keyword"} }
0 commit comments