From b9f82b92bbc86fcf523987b877e151d7d3b2df47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 30 Mar 2026 07:08:22 +0200 Subject: [PATCH 1/4] feat: add SHOW CLUSTER NAME command and implement cluster name retrieval Closed Issue #60 --- build.sbt | 2 +- .../elastic/client/ClusterApi.scala | 58 +++++++++++++++++++ .../elastic/client/ElasticClientApi.scala | 1 + .../client/ElasticClientDelegator.scala | 11 ++++ .../elastic/client/GatewayApi.scala | 39 ++++++++++++- .../elastic/client/NopeClientApi.scala | 3 + .../client/metrics/MetricsElasticClient.scala | 7 +++ documentation/sql/dql_statements.md | 22 +++++++ .../elastic/client/jest/JestClientApi.scala | 1 + .../elastic/client/jest/JestClusterApi.scala | 38 ++++++++++++ .../client/rest/RestHighLevelClientApi.scala | 22 +++++++ .../client/rest/RestHighLevelClientApi.scala | 22 +++++++ .../elastic/client/java/JavaClientApi.scala | 15 +++++ .../elastic/client/java/JavaClientApi.scala | 15 +++++ .../elastic/sql/parser/Parser.scala | 8 ++- .../elastic/sql/query/package.scala | 10 ++++ .../elastic/sql/parser/ParserSpec.scala | 26 +++++++++ .../elastic/client/MockElasticClientApi.scala | 5 ++ .../repl/ReplGatewayIntegrationSpec.scala | 13 +++++ 19 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 core/src/main/scala/app/softnetwork/elastic/client/ClusterApi.scala create mode 100644 es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClusterApi.scala diff --git a/build.sbt b/build.sbt index f30ab0fd..0011f269 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ ThisBuild / organization := "app.softnetwork" name := "softclient4es" -ThisBuild / version := "0.19.2" +ThisBuild / version := "0.20.0" ThisBuild / scalaVersion := scala213 diff --git a/core/src/main/scala/app/softnetwork/elastic/client/ClusterApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/ClusterApi.scala new file mode 100644 index 00000000..a8d15663 --- /dev/null +++ b/core/src/main/scala/app/softnetwork/elastic/client/ClusterApi.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2025 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.softnetwork.elastic.client + +import app.softnetwork.elastic.client.result.{ElasticFailure, ElasticResult, ElasticSuccess} + +import java.util.concurrent.atomic.AtomicReference + +trait ClusterApi extends ElasticClientHelpers { + + // ======================================================================== + // PUBLIC METHODS + // ======================================================================== + + // Cache cluster name (avoids calling it every time) + private val cachedClusterName = new AtomicReference[Option[String]](None) + + /** Get Elasticsearch cluster name. + * @return + * the Elasticsearch cluster name + */ + def clusterName: ElasticResult[String] = { + cachedClusterName.get match { + case Some(name) => + ElasticSuccess(name) + case None => + executeGetClusterName() match { + case ElasticSuccess(name) => + logger.info(s"✅ Elasticsearch cluster name: $name") + cachedClusterName.compareAndSet(None, Some(name)) + ElasticSuccess(cachedClusterName.get.getOrElse(name)) + case failure @ ElasticFailure(error) => + logger.error(s"❌ Failed to get Elasticsearch cluster name: ${error.message}") + failure + } + } + } + + // ======================================================================== + // METHODS TO IMPLEMENT + // ======================================================================== + + private[client] def executeGetClusterName(): ElasticResult[String] +} diff --git a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala index 0fcb0219..f53f4e67 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientApi.scala @@ -41,6 +41,7 @@ trait ElasticClientApi with RefreshApi with FlushApi with VersionApi + with ClusterApi with PipelineApi with TemplateApi with EnrichPolicyApi diff --git a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientDelegator.scala b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientDelegator.scala index de97dce6..9e3c9088 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientDelegator.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/ElasticClientDelegator.scala @@ -85,6 +85,17 @@ trait ElasticClientDelegator extends ElasticClientApi with BulkTypes { override private[client] def executeVersion(): ElasticResult[String] = delegate.executeVersion() + /** Get Elasticsearch cluster name. + * + * @return + * the Elasticsearch cluster name + */ + override def clusterName: ElasticResult[String] = + delegate.clusterName + + override private[client] def executeGetClusterName(): ElasticResult[String] = + delegate.executeGetClusterName() + // ==================== IndicesApi ==================== /** Create an index with the provided name and settings. diff --git a/core/src/main/scala/app/softnetwork/elastic/client/GatewayApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/GatewayApi.scala index 29210b93..965ae9db 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/GatewayApi.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/GatewayApi.scala @@ -37,6 +37,7 @@ import app.softnetwork.elastic.sql.parser.Parser import app.softnetwork.elastic.sql.query.{ AlterTable, AlterTableSetting, + ClusterStatement, CopyInto, CreateEnrichPolicy, CreatePipeline, @@ -59,6 +60,7 @@ import app.softnetwork.elastic.sql.query.{ PipelineStatement, SearchStatement, SelectStatement, + ShowClusterName, ShowCreatePipeline, ShowCreateTable, ShowEnrichPolicies, @@ -1570,12 +1572,38 @@ class TableExecutor( } } +class ClusterExecutor( + api: ClusterApi, + logger: Logger +) extends Executor[ClusterStatement] { + override def execute( + statement: ClusterStatement + )(implicit system: ActorSystem): Future[ElasticResult[QueryResult]] = { + statement match { + case ShowClusterName => + api.clusterName match { + case ElasticSuccess(name) => + Future.successful( + ElasticResult.success(QueryRows(Seq(ListMap("name" -> name)))) + ) + case ElasticFailure(elasticError) => + Future.successful( + ElasticFailure( + elasticError.copy(operation = Some("cluster")) + ) + ) + } + } + } +} + class DqlRouterExecutor( searchExec: SearchExecutor, pipelineExec: PipelineExecutor, tableExec: TableExecutor, watcherExec: WatcherExecutor, - policyExec: EnrichPolicyExecutor + policyExec: EnrichPolicyExecutor, + clusterExec: ClusterExecutor ) extends Executor[DqlStatement] { override def execute( @@ -1587,6 +1615,7 @@ class DqlRouterExecutor( case t: TableStatement => tableExec.execute(t) case w: WatcherStatement => watcherExec.execute(w) case e: EnrichPolicyStatement => policyExec.execute(e) + case c: ClusterStatement => clusterExec.execute(c) case _ => Future.successful( @@ -1655,12 +1684,18 @@ trait GatewayApi extends IndicesApi with ElasticClientHelpers { logger = logger ) + lazy val clusterExecutor = new ClusterExecutor( + api = this, + logger = logger + ) + lazy val dqlExecutor = new DqlRouterExecutor( searchExec = searchExecutor, pipelineExec = pipelineExecutor, tableExec = tableExecutor, watcherExec = watcherExecutor, - policyExec = policyExecutor + policyExec = policyExecutor, + clusterExec = clusterExecutor ) lazy val ddlExecutor = new DdlRouterExecutor( diff --git a/core/src/main/scala/app/softnetwork/elastic/client/NopeClientApi.scala b/core/src/main/scala/app/softnetwork/elastic/client/NopeClientApi.scala index 38b4f2b6..cba9869c 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/NopeClientApi.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/NopeClientApi.scala @@ -263,6 +263,9 @@ trait NopeClientApi extends ElasticClientApi { override private[client] def executeVersion(): ElasticResult[String] = ElasticResult.success("0.0.0") + override private[client] def executeGetClusterName(): ElasticResult[String] = + ElasticResult.success("nope-cluster") + override private[client] def executeGetIndex(index: String): ElasticResult[Option[String]] = ElasticResult.success(None) diff --git a/core/src/main/scala/app/softnetwork/elastic/client/metrics/MetricsElasticClient.scala b/core/src/main/scala/app/softnetwork/elastic/client/metrics/MetricsElasticClient.scala index 5840a623..e9344964 100644 --- a/core/src/main/scala/app/softnetwork/elastic/client/metrics/MetricsElasticClient.scala +++ b/core/src/main/scala/app/softnetwork/elastic/client/metrics/MetricsElasticClient.scala @@ -97,6 +97,13 @@ class MetricsElasticClient( delegate.version } + // ==================== ClusterApi ==================== + + override def clusterName: ElasticResult[String] = + measureResult("cluster_name") { + delegate.clusterName + } + // ==================== IndicesApi ==================== override def createIndex( diff --git a/documentation/sql/dql_statements.md b/documentation/sql/dql_statements.md index 15e27517..a502e463 100644 --- a/documentation/sql/dql_statements.md +++ b/documentation/sql/dql_statements.md @@ -46,6 +46,7 @@ DQL supports: - [SHOW WATCHER STATUS](#show-watcher-status) - [SHOW ENRICH POLICIES](#show-enrich-policies) - [SHOW ENRICH POLICY](#show-enrich-policy) +- [SHOW CLUSTER NAME](#show-cluster-name) --- @@ -1282,4 +1283,25 @@ SHOW ENRICH POLICY my_policy; --- +## SHOW CLUSTER NAME + +```sql +SHOW CLUSTER NAME; +``` + +Returns the name of the Elasticsearch cluster. The cluster name is cached after the first call. + +**Example:** + +```sql +SHOW CLUSTER NAME; +``` + +| name | +|----------------| +| docker-cluster | +📊 1 row(s) (3ms) + +--- + [Back to index](README.md) diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala index c062dbc5..d2b8e62f 100644 --- a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClientApi.scala @@ -44,6 +44,7 @@ trait JestClientApi with JestScrollApi with JestBulkApi with JestVersionApi + with JestClusterApi with JestPipelineApi with JestTemplateApi with JestEnrichPolicyApi diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClusterApi.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClusterApi.scala new file mode 100644 index 00000000..a8d0dd65 --- /dev/null +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/JestClusterApi.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2025 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.softnetwork.elastic.client.jest + +import app.softnetwork.elastic.client.{result, ClusterApi} +import app.softnetwork.elastic.client.jest.actions.GetClusterInfo +import org.json4s.DefaultFormats +import org.json4s.jackson.JsonMethods + +trait JestClusterApi extends ClusterApi with JestClientHelpers { + _: JestClientCompanion => + override private[client] def executeGetClusterName(): result.ElasticResult[String] = + executeJestAction( + "cluster_name", + retryable = true + )( + new GetClusterInfo.Builder().build() + ) { result => + val jsonString = result.getJsonString + implicit val formats: DefaultFormats.type = DefaultFormats + val json = JsonMethods.parse(jsonString) + (json \ "cluster_name").extract[String] + } +} diff --git a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index ae0fe0d1..def0752a 100644 --- a/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es6/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -149,6 +149,7 @@ trait RestHighLevelClientApi with RestHighLevelClientScrollApi with RestHighLevelClientCompanion with RestHighLevelClientVersionApi + with RestHighLevelClientClusterApi with RestHighLevelClientPipelineApi with RestHighLevelClientTemplateApi with RestHighLevelClientEnrichPolicyApi @@ -179,6 +180,27 @@ trait RestHighLevelClientVersionApi extends VersionApi with RestHighLevelClientH ) } +trait RestHighLevelClientClusterApi extends ClusterApi with RestHighLevelClientHelpers { + _: RestHighLevelClientCompanion => + + override private[client] def executeGetClusterName(): ElasticResult[String] = + executeRestLowLevelAction[String]( + operation = "cluster_name", + index = None, + retryable = true + )( + request = new Request("GET", "/") + )( + transformer = resp => { + val jsonString = EntityUtils.toString(resp.getEntity) + implicit val formats: DefaultFormats.type = DefaultFormats + val json = JsonMethods.parse(jsonString) + (json \ "cluster_name").extract[String] + } + ) + +} + /** Indices management API for RestHighLevelClient * @see * [[IndicesApi]] for generic API documentation diff --git a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala index d5ab711d..a44e844e 100644 --- a/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala +++ b/es7/rest/src/main/scala/app/softnetwork/elastic/client/rest/RestHighLevelClientApi.scala @@ -205,6 +205,7 @@ trait RestHighLevelClientApi with RestHighLevelClientScrollApi with RestHighLevelClientCompanion with RestHighLevelClientVersionApi + with RestHighLevelClientClusterApi with RestHighLevelClientPipelineApi with RestHighLevelClientTemplateApi with RestHighLevelClientEnrichPolicyApi @@ -237,6 +238,27 @@ trait RestHighLevelClientVersionApi extends VersionApi with RestHighLevelClientH } +trait RestHighLevelClientClusterApi extends ClusterApi with RestHighLevelClientHelpers { + _: RestHighLevelClientCompanion => + + override private[client] def executeGetClusterName(): ElasticResult[String] = + executeRestLowLevelAction[String]( + operation = "cluster_name", + index = None, + retryable = true + )( + request = new Request("GET", "/") + )( + transformer = resp => { + val jsonString = EntityUtils.toString(resp.getEntity) + implicit val formats: DefaultFormats.type = DefaultFormats + val json = JsonMethods.parse(jsonString) + (json \ "cluster_name").extract[String] + } + ) + +} + /** Indices management API for RestHighLevelClient * @see * [[IndicesApi]] for generic API documentation diff --git a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala index 4a77b0a3..86488176 100644 --- a/es8/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala +++ b/es8/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala @@ -145,6 +145,7 @@ trait JavaClientApi with JavaClientScrollApi with JavaClientCompanion with JavaClientVersionApi + with JavaClientClusterApi with JavaClientPipelineApi with JavaClientTemplateApi with JavaClientEnrichPolicyApi @@ -170,6 +171,20 @@ trait JavaClientVersionApi extends VersionApi with JavaClientHelpers { } } +trait JavaClientClusterApi extends ClusterApi with JavaClientHelpers { + _: JavaClientCompanion => + override private[client] def executeGetClusterName(): ElasticResult[String] = + executeJavaAction( + operation = "cluster_name", + index = None, + retryable = true + )( + apply().info() + ) { response => + response.clusterName() + } +} + /** Elasticsearch client implementation of Indices API using the Java Client * @see * [[IndicesApi]] for index management operations diff --git a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala index 657103b1..7e94f2a2 100644 --- a/es9/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala +++ b/es9/java/src/main/scala/app/softnetwork/elastic/client/java/JavaClientApi.scala @@ -140,6 +140,7 @@ trait JavaClientApi with JavaClientScrollApi with JavaClientCompanion with JavaClientVersionApi + with JavaClientClusterApi with JavaClientPipelineApi with JavaClientTemplateApi with JavaClientEnrichPolicyApi @@ -165,6 +166,20 @@ trait JavaClientVersionApi extends VersionApi with JavaClientHelpers { } } +trait JavaClientClusterApi extends ClusterApi with JavaClientHelpers { + _: JavaClientCompanion => + override private[client] def executeGetClusterName(): ElasticResult[String] = + executeJavaAction( + operation = "cluster_name", + index = None, + retryable = true + )( + apply().info() + ) { response => + response.clusterName() + } +} + /** Elasticsearch client implementation of Indices API using the Java Client * @see * [[IndicesApi]] for index management operations diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala index b3ed0f56..a2646cec 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala @@ -902,6 +902,11 @@ object Parser ShowEnrichPolicies } + def showClusterName: PackratParser[ShowClusterName.type] = + ("(?i)SHOW\\b".r ~ "(?i)CLUSTER\\b".r ~ "(?i)NAME\\b".r) ^^ { _ => + ShowClusterName + } + def dqlStatement: PackratParser[DqlStatement] = { searchStatement | showTables | @@ -920,7 +925,8 @@ object Parser showWatchers | showWatcherStatus | showEnrichPolicy | - showEnrichPolicies + showEnrichPolicies | + showClusterName } def ddlStatement: PackratParser[DdlStatement] = diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/query/package.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/query/package.scala index b3d9084e..9b6b0248 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/query/package.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/query/package.scala @@ -1284,4 +1284,14 @@ package object query { case object ShowEnrichPolicies extends EnrichPolicyStatement with DqlStatement { override def sql: String = s"SHOW ENRICH POLICIES" } + + // ======================================================================== + // Cluster statements + // ======================================================================== + + sealed trait ClusterStatement extends Statement + + case object ShowClusterName extends ClusterStatement with DqlStatement { + override def sql: String = "SHOW CLUSTER NAME" + } } diff --git a/sql/src/test/scala/app/softnetwork/elastic/sql/parser/ParserSpec.scala b/sql/src/test/scala/app/softnetwork/elastic/sql/parser/ParserSpec.scala index 91e63442..b49f872b 100644 --- a/sql/src/test/scala/app/softnetwork/elastic/sql/parser/ParserSpec.scala +++ b/sql/src/test/scala/app/softnetwork/elastic/sql/parser/ParserSpec.scala @@ -3053,4 +3053,30 @@ class ParserSpec extends AnyFlatSpec with Matchers { } } + behavior of "Parser Cluster" + + it should "parse SHOW CLUSTER NAME" in { + val result = Parser("SHOW CLUSTER NAME") + result.isRight shouldBe true + result.toOption.get shouldBe ShowClusterName + result.toOption.get.sql shouldBe "SHOW CLUSTER NAME" + } + + it should "parse show cluster name (lowercase)" in { + val result = Parser("show cluster name") + result.isRight shouldBe true + result.toOption.get shouldBe ShowClusterName + } + + it should "parse Show Cluster Name (mixed case)" in { + val result = Parser("Show Cluster Name") + result.isRight shouldBe true + result.toOption.get shouldBe ShowClusterName + } + + it should "fail to parse SHOW CLUSTER alone" in { + val result = Parser("SHOW CLUSTER") + result.isLeft shouldBe true + } + } diff --git a/testkit/src/main/scala/app/softnetwork/elastic/client/MockElasticClientApi.scala b/testkit/src/main/scala/app/softnetwork/elastic/client/MockElasticClientApi.scala index 1b103862..b13019b8 100644 --- a/testkit/src/main/scala/app/softnetwork/elastic/client/MockElasticClientApi.scala +++ b/testkit/src/main/scala/app/softnetwork/elastic/client/MockElasticClientApi.scala @@ -89,6 +89,11 @@ trait MockElasticClientApi extends NopeClientApi { override private[client] def executeVersion(): ElasticResult[String] = ElasticResult.success(elasticVersion) + // ==================== ClusterApi ==================== + + override private[client] def executeGetClusterName(): ElasticResult[String] = + ElasticResult.success("test-cluster") + // ==================== IndicesApi ==================== override private[client] def executeCreateIndex( diff --git a/testkit/src/main/scala/app/softnetwork/elastic/client/repl/ReplGatewayIntegrationSpec.scala b/testkit/src/main/scala/app/softnetwork/elastic/client/repl/ReplGatewayIntegrationSpec.scala index 087289ef..24a0d879 100644 --- a/testkit/src/main/scala/app/softnetwork/elastic/client/repl/ReplGatewayIntegrationSpec.scala +++ b/testkit/src/main/scala/app/softnetwork/elastic/client/repl/ReplGatewayIntegrationSpec.scala @@ -1412,4 +1412,17 @@ trait ReplGatewayIntegrationSpec extends ReplIntegrationTestKit { // Reset to ASCII testRepl.setFormat(OutputFormat.Ascii) } + + // ========================================================================= + // 10. SHOW CLUSTER NAME tests + // ========================================================================= + + behavior of "REPL - SHOW CLUSTER NAME" + + it should "return the cluster name using SHOW CLUSTER NAME" in { + val rows = assertQueryRows(System.nanoTime(), executeSync("SHOW CLUSTER NAME")) + rows should have size 1 + rows.head should contain key "name" + rows.head("name") shouldBe "docker-cluster" + } } From 2ad1dcd3b0e7aee44313ec3c53fd406d55b0a5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 30 Mar 2026 07:58:01 +0200 Subject: [PATCH 2/4] refactor: standardize keyword usage in Parser.scala to fix case insensitivity Closed Issue #61 --- .../elastic/sql/parser/Parser.scala | 373 ++++++++++-------- 1 file changed, 212 insertions(+), 161 deletions(-) diff --git a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala index a2646cec..f7e72d90 100644 --- a/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala +++ b/sql/src/main/scala/app/softnetwork/elastic/sql/parser/Parser.scala @@ -124,7 +124,9 @@ object Parser } def createOrReplacePipeline: PackratParser[CreatePipeline] = - ("CREATE" ~ "OR" ~ "REPLACE" ~ "PIPELINE") ~ ident ~ ("WITH" ~ "PROCESSORS") ~ start ~ repsep( + (keyword("CREATE") ~ keyword("OR") ~ keyword("REPLACE") ~ keyword( + "PIPELINE" + )) ~ ident ~ (keyword("WITH") ~ keyword("PROCESSORS")) ~ start ~ repsep( processor, separator ) ~ end ^^ { case _ ~ name ~ _ ~ _ ~ proc ~ _ => @@ -132,7 +134,9 @@ object Parser } def createPipeline: PackratParser[CreatePipeline] = - ("CREATE" ~ "PIPELINE") ~ ifNotExists ~ ident ~ ("WITH" ~ "PROCESSORS" ~ start) ~ repsep( + (keyword("CREATE") ~ keyword("PIPELINE")) ~ ifNotExists ~ ident ~ (keyword("WITH") ~ keyword( + "PROCESSORS" + ) ~ start) ~ repsep( processor, separator ) <~ end ^^ { case _ ~ ine ~ name ~ _ ~ proc => @@ -140,45 +144,48 @@ object Parser } def dropPipeline: PackratParser[DropPipeline] = - ("DROP" ~ "PIPELINE") ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => + (keyword("DROP") ~ keyword("PIPELINE")) ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => DropPipeline(name, ifExists = ie) } def showPipeline: PackratParser[ShowPipeline] = - ("SHOW" ~ "PIPELINE") ~ ident ^^ { case _ ~ pipeline => + (keyword("SHOW") ~ keyword("PIPELINE")) ~ ident ^^ { case _ ~ pipeline => ShowPipeline(pipeline) } def showPipelines: PackratParser[ShowPipelines.type] = - ("SHOW" ~ "PIPELINES") ^^ { _ => + (keyword("SHOW") ~ keyword("PIPELINES")) ^^ { _ => ShowPipelines } def showCreatePipeline: PackratParser[ShowCreatePipeline] = - ("SHOW" ~ "CREATE" ~ "PIPELINE") ~ ident ^^ { case _ ~ _ ~ _ ~ pipeline => - ShowCreatePipeline(pipeline) + (keyword("SHOW") ~ keyword("CREATE") ~ keyword("PIPELINE")) ~ ident ^^ { + case _ ~ _ ~ _ ~ pipeline => + ShowCreatePipeline(pipeline) } def describePipeline: PackratParser[DescribePipeline] = - (("DESCRIBE" | "DESC") ~ "PIPELINE") ~ ident ^^ { case _ ~ pipeline => - DescribePipeline(pipeline) + ((keyword("DESCRIBE") | keyword("DESC")) ~ keyword("PIPELINE")) ~ ident ^^ { + case _ ~ pipeline => + DescribePipeline(pipeline) } def addProcessor: PackratParser[AddPipelineProcessor] = - ("ADD" ~ "PROCESSOR") ~ processor ^^ { case _ ~ proc => + (keyword("ADD") ~ keyword("PROCESSOR")) ~ processor ^^ { case _ ~ proc => AddPipelineProcessor(proc) } def dropProcessor: PackratParser[DropPipelineProcessor] = - ("DROP" ~ "PROCESSOR") ~ processorType ~ start ~ ident ~ end ^^ { case _ ~ pt ~ _ ~ name ~ _ => - DropPipelineProcessor(pt, name) + (keyword("DROP") ~ keyword("PROCESSOR")) ~ processorType ~ start ~ ident ~ end ^^ { + case _ ~ pt ~ _ ~ name ~ _ => + DropPipelineProcessor(pt, name) } def alterPipelineStatement: PackratParser[AlterPipelineStatement] = addProcessor | dropProcessor def alterPipeline: PackratParser[AlterPipeline] = - ("ALTER" ~ "PIPELINE") ~ ifExists ~ ident ~ start.? ~ repsep( + (keyword("ALTER") ~ keyword("PIPELINE")) ~ ifExists ~ ident ~ start.? ~ repsep( alterPipelineStatement, separator ) ~ end.? ^^ { case _ ~ ie ~ pipeline ~ s ~ stmts ~ e => @@ -193,22 +200,22 @@ object Parser } def multiFields: PackratParser[List[Column]] = - "FIELDS" ~ start ~> repsep(column, separator) <~ end ^^ (cols => cols) | success(Nil) + keyword("FIELDS") ~ start ~> repsep(column, separator) <~ end ^^ (cols => cols) | success(Nil) def ifExists: PackratParser[Boolean] = - opt("IF" ~ "EXISTS") ^^ { + opt(keyword("IF") ~ keyword("EXISTS")) ^^ { case Some(_) => true case None => false } def ifNotExists: PackratParser[Boolean] = - opt("IF" ~ "NOT" ~ "EXISTS") ^^ { + opt(keyword("IF") ~ keyword("NOT") ~ keyword("EXISTS")) ^^ { case Some(_) => true case None => false } def notNull: PackratParser[Boolean] = - opt("NOT" ~ "NULL") ^^ { + opt(keyword("NOT") ~ keyword("NULL")) ^^ { case Some(_) => true case None => false } @@ -218,13 +225,13 @@ object Parser def ingest_timestamp: PackratParser[Value[_]] = "_ingest.timestamp" ^^ (_ => IngestTimestampValue) def defaultVal: PackratParser[Option[Value[_]]] = - opt("DEFAULT" ~ (value | ingest_id | ingest_timestamp)) ^^ { + opt(keyword("DEFAULT") ~ (value | ingest_id | ingest_timestamp)) ^^ { case Some(_ ~ v) => Some(v) case None => None } def comment: PackratParser[Option[String]] = - opt("COMMENT" ~ literal) ^^ { + opt(keyword("COMMENT") ~ literal) ^^ { case Some(_ ~ v) => Some(v.value) case None => None } @@ -235,7 +242,7 @@ object Parser identifierWithFunction def script: PackratParser[PainlessScript] = - ("SCRIPT" ~ "AS") ~ start ~ scriptValue ~ end ^^ { case _ ~ _ ~ s ~ _ => s } + (keyword("SCRIPT") ~ keyword("AS")) ~ start ~ scriptValue ~ end ^^ { case _ ~ _ ~ s ~ _ => s } def column: PackratParser[Column] = ident ~ extension_type ~ (script | multiFields) ~ defaultVal ~ notNull ~ comment ~ (options | success( @@ -262,21 +269,21 @@ object Parser start ~ repsep(column, separator) ~ end ^^ { case _ ~ cols ~ _ => cols } def primaryKey: PackratParser[List[String]] = - separator ~ "PRIMARY" ~ "KEY" ~ start ~ repsep(ident, separator) ~ end ^^ { + separator ~ keyword("PRIMARY") ~ keyword("KEY") ~ start ~ repsep(ident, separator) ~ end ^^ { case _ ~ _ ~ _ ~ _ ~ keys ~ _ => keys } | success(Nil) def granularity: PackratParser[TimeUnit] = start ~ - (("YEAR" ^^^ TimeUnit.YEARS) | - ("MONTH" ^^^ TimeUnit.MONTHS) | - ("DAY" ^^^ TimeUnit.DAYS) | - ("HOUR" ^^^ TimeUnit.HOURS) | - ("MINUTE" ^^^ TimeUnit.MINUTES) | - ("SECOND" ^^^ TimeUnit.SECONDS)) ~ end ^^ { case _ ~ gf ~ _ => gf } + ((keyword("YEAR") ^^^ TimeUnit.YEARS) | + (keyword("MONTH") ^^^ TimeUnit.MONTHS) | + (keyword("DAY") ^^^ TimeUnit.DAYS) | + (keyword("HOUR") ^^^ TimeUnit.HOURS) | + (keyword("MINUTE") ^^^ TimeUnit.MINUTES) | + (keyword("SECOND") ^^^ TimeUnit.SECONDS)) ~ end ^^ { case _ ~ gf ~ _ => gf } def partitionBy: PackratParser[Option[PartitionDate]] = - opt("PARTITION" ~ "BY" ~ ident ~ opt(granularity)) ^^ { + opt(keyword("PARTITION") ~ keyword("BY") ~ ident ~ opt(granularity)) ^^ { case Some(_ ~ _ ~ pb ~ gf) => Some(PartitionDate(pb, gf.getOrElse(TimeUnit.DAYS))) case None => None } @@ -293,7 +300,9 @@ object Parser } def createOrReplaceTable: PackratParser[CreateTable] = - ("CREATE" ~ "OR" ~ "REPLACE" ~ "TABLE") ~ ident ~ (columnsWithPartitionBy | ("AS" ~> searchStatement)) ^^ { + (keyword("CREATE") ~ keyword("OR") ~ keyword("REPLACE") ~ keyword( + "TABLE" + )) ~ ident ~ (columnsWithPartitionBy | (keyword("AS") ~> searchStatement)) ^^ { case _ ~ name ~ lr => lr match { case ( @@ -317,7 +326,9 @@ object Parser } def createTable: PackratParser[CreateTable] = - ("CREATE" ~ "TABLE") ~ ifNotExists ~ ident ~ (columnsWithPartitionBy | ("AS" ~> searchStatement)) ^^ { + (keyword("CREATE") ~ keyword( + "TABLE" + )) ~ ifNotExists ~ ident ~ (columnsWithPartitionBy | (keyword("AS") ~> searchStatement)) ^^ { case _ ~ ine ~ name ~ lr => lr match { case ( @@ -331,56 +342,61 @@ object Parser } } - def patterns: PackratParser[List[String]] = "LIKE" ~> repsep(literal, comma) ^^ { patterns => - patterns.map(_.value) + def patterns: PackratParser[List[String]] = keyword("LIKE") ~> repsep(literal, comma) ^^ { + patterns => + patterns.map(_.value) } def showTables: PackratParser[ShowTables] = - ("SHOW" ~ "TABLES") ~> opt(patterns) ^^ { indices => + (keyword("SHOW") ~ keyword("TABLES")) ~> opt(patterns) ^^ { indices => ShowTables(indices.getOrElse(Seq.empty)) } def showTable: PackratParser[ShowTable] = - ("SHOW" ~ "TABLE") ~ ident ^^ { case _ ~ table => + (keyword("SHOW") ~ keyword("TABLE")) ~ ident ^^ { case _ ~ table => ShowTable(table) } def showCreateTable: PackratParser[ShowCreateTable] = - ("SHOW" ~ "CREATE" ~ "TABLE") ~ ident ^^ { case _ ~ _ ~ _ ~ table => + (keyword("SHOW") ~ keyword("CREATE") ~ keyword("TABLE")) ~ ident ^^ { case _ ~ _ ~ _ ~ table => ShowCreateTable(table) } def describeTable: PackratParser[DescribeTable] = - (("DESCRIBE" | "DESC") ~ opt("TABLE")) ~ ident ^^ { case _ ~ table => + ((keyword("DESCRIBE") | keyword("DESC")) ~ opt(keyword("TABLE"))) ~ ident ^^ { case _ ~ table => DescribeTable(table) } def dropTable: PackratParser[DropTable] = - ("DROP" ~ ("TABLE" | "INDEX")) ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => - DropTable(name, ifExists = ie) + (keyword("DROP") ~ (keyword("TABLE") | keyword("INDEX"))) ~ ifExists ~ ident ^^ { + case _ ~ ie ~ name => + DropTable(name, ifExists = ie) } def truncateTable: PackratParser[TruncateTable] = - ("TRUNCATE" ~ "TABLE") ~ ident ^^ { case _ ~ name => + (keyword("TRUNCATE") ~ keyword("TABLE")) ~ ident ^^ { case _ ~ name => TruncateTable(name) } def frequency: PackratParser[Frequency] = - ("REFRESH" ~ "EVERY") ~> """\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?""".r ^^ { - str => - val parts = str.trim.split("\\s+") - Frequency(TransformTimeUnit(parts(1)), parts(0).toLong) + (keyword("REFRESH") ~ keyword( + "EVERY" + )) ~> """\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?""".r ^^ { str => + val parts = str.trim.split("\\s+") + Frequency(TransformTimeUnit(parts(1)), parts(0).toLong) } def withOptions: PackratParser[ListMap[String, Value[_]]] = - ("WITH" ~ lparen) ~> repsep(option, separator) <~ rparen ^^ { opts => + (keyword("WITH") ~ lparen) ~> repsep(option, separator) <~ rparen ^^ { opts => ListMap(opts: _*) } def createOrReplaceMaterializedView: PackratParser[CreateMaterializedView] = - ("CREATE" ~ "OR" ~ "REPLACE" ~ "MATERIALIZED" ~ "VIEW") ~ ident ~ opt(frequency) ~ opt( + (keyword("CREATE") ~ keyword("OR") ~ keyword("REPLACE") ~ keyword("MATERIALIZED") ~ keyword( + "VIEW" + )) ~ ident ~ opt(frequency) ~ opt( withOptions - ) ~ ("AS" ~> searchStatement) ^^ { case _ ~ view ~ freq ~ opts ~ dql => + ) ~ (keyword("AS") ~> searchStatement) ^^ { case _ ~ view ~ freq ~ opts ~ dql => CreateMaterializedView( view, dql, @@ -392,11 +408,11 @@ object Parser } def createMaterializedView: PackratParser[CreateMaterializedView] = - ("CREATE" ~ "MATERIALIZED" ~ "VIEW") ~ ifNotExists ~ ident ~ opt( + (keyword("CREATE") ~ keyword("MATERIALIZED") ~ keyword("VIEW")) ~ ifNotExists ~ ident ~ opt( frequency ) ~ opt( withOptions - ) ~ ("AS" ~> searchStatement) ^^ { case _ ~ ine ~ view ~ freq ~ opts ~ dql => + ) ~ (keyword("AS") ~> searchStatement) ^^ { case _ ~ ine ~ view ~ freq ~ opts ~ dql => CreateMaterializedView( view, dql, @@ -408,100 +424,113 @@ object Parser } def dropMaterializedView: PackratParser[DropMaterializedView] = - ("DROP" ~ "MATERIALIZED" ~ "VIEW") ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => - DropMaterializedView(name, ifExists = ie) + (keyword("DROP") ~ keyword("MATERIALIZED") ~ keyword("VIEW")) ~ ifExists ~ ident ^^ { + case _ ~ ie ~ name => + DropMaterializedView(name, ifExists = ie) } def refreshMaterializedView: PackratParser[RefreshMaterializedView] = - ("REFRESH" ~ "MATERIALIZED" ~ "VIEW") ~ ifExists ~ ident ~ opt("WITH" ~ "SCHEDULE" ~ "NOW") ^^ { - case _ ~ ie ~ view ~ wn => - RefreshMaterializedView(view, ifExists = ie, scheduleNow = wn.isDefined) + (keyword("REFRESH") ~ keyword("MATERIALIZED") ~ keyword("VIEW")) ~ ifExists ~ ident ~ opt( + keyword("WITH") ~ keyword("SCHEDULE") ~ keyword("NOW") + ) ^^ { case _ ~ ie ~ view ~ wn => + RefreshMaterializedView(view, ifExists = ie, scheduleNow = wn.isDefined) } def showMaterializedViewStatus: PackratParser[ShowMaterializedViewStatus] = - ("SHOW" ~ "MATERIALIZED" ~ "VIEW" ~ "STATUS") ~ ident ^^ { case _ ~ _ ~ _ ~ _ ~ view => - ShowMaterializedViewStatus(view) + (keyword("SHOW") ~ keyword("MATERIALIZED") ~ keyword("VIEW") ~ keyword("STATUS")) ~ ident ^^ { + case _ ~ _ ~ _ ~ _ ~ view => + ShowMaterializedViewStatus(view) } def showCreateMaterializedView: PackratParser[ShowCreateMaterializedView] = - ("SHOW" ~ "CREATE" ~ "MATERIALIZED" ~ "VIEW") ~ ident ^^ { case _ ~ _ ~ _ ~ _ ~ view => - ShowCreateMaterializedView(view) + (keyword("SHOW") ~ keyword("CREATE") ~ keyword("MATERIALIZED") ~ keyword("VIEW")) ~ ident ^^ { + case _ ~ _ ~ _ ~ _ ~ view => + ShowCreateMaterializedView(view) } def showMaterializedView: PackratParser[ShowMaterializedView] = - ("SHOW" ~ "MATERIALIZED" ~ "VIEW") ~ ident ^^ { case _ ~ _ ~ view => + (keyword("SHOW") ~ keyword("MATERIALIZED") ~ keyword("VIEW")) ~ ident ^^ { case _ ~ _ ~ view => ShowMaterializedView(view) } def showMaterializedViews: PackratParser[ShowMaterializedViews.type] = - ("SHOW" ~ "MATERIALIZED" ~ "VIEWS") ^^ { _ => + (keyword("SHOW") ~ keyword("MATERIALIZED") ~ keyword("VIEWS")) ^^ { _ => ShowMaterializedViews } def describeMaterializedView: PackratParser[DescribeMaterializedView] = - (("DESCRIBE" | "DESC") ~ "MATERIALIZED" ~ "VIEW") ~ ident ^^ { case _ ~ _ ~ _ ~ view => + ((keyword("DESCRIBE") | keyword("DESC")) ~ keyword("MATERIALIZED") ~ keyword( + "VIEW" + )) ~ ident ^^ { case _ ~ _ ~ _ ~ view => DescribeMaterializedView(view) } def addColumn: PackratParser[AddColumn] = - ("ADD" ~ "COLUMN") ~ ifNotExists ~ column ^^ { case _ ~ ine ~ col => + (keyword("ADD") ~ keyword("COLUMN")) ~ ifNotExists ~ column ^^ { case _ ~ ine ~ col => AddColumn(col, ifNotExists = ine) } def dropColumn: PackratParser[DropColumn] = - ("DROP" ~ "COLUMN") ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => + (keyword("DROP") ~ keyword("COLUMN")) ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => DropColumn(name, ifExists = ie) } def renameColumn: PackratParser[RenameColumn] = - ("RENAME" ~ "COLUMN") ~ ident ~ ("TO" ~> ident) ^^ { case _ ~ oldName ~ newName => - RenameColumn(oldName, newName) + (keyword("RENAME") ~ keyword("COLUMN")) ~ ident ~ (keyword("TO") ~> ident) ^^ { + case _ ~ oldName ~ newName => + RenameColumn(oldName, newName) } def alterColumnIfExists: PackratParser[Boolean] = - ("ALTER" ~ "COLUMN") ~ ifExists ^^ { case _ ~ ie => + (keyword("ALTER") ~ keyword("COLUMN")) ~ ifExists ^^ { case _ ~ ie => ie } def alterColumnOptions: PackratParser[AlterColumnOptions] = - alterColumnIfExists ~ ident ~ "SET" ~ options ^^ { case ie ~ col ~ _ ~ opts => + alterColumnIfExists ~ ident ~ keyword("SET") ~ options ^^ { case ie ~ col ~ _ ~ opts => AlterColumnOptions(col, opts, ifExists = ie) } def alterColumnOption: PackratParser[AlterColumnOption] = - alterColumnIfExists ~ ident ~ (("SET" | "ADD") ~ "OPTION") ~ start ~ option ~ end ^^ { - case ie ~ col ~ _ ~ _ ~ opt ~ _ => - AlterColumnOption(col, opt._1, opt._2, ifExists = ie) + alterColumnIfExists ~ ident ~ ((keyword("SET") | keyword("ADD")) ~ keyword( + "OPTION" + )) ~ start ~ option ~ end ^^ { case ie ~ col ~ _ ~ _ ~ opt ~ _ => + AlterColumnOption(col, opt._1, opt._2, ifExists = ie) } def dropColumnOption: PackratParser[DropColumnOption] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "OPTION") ~ ident ^^ { case ie ~ col ~ _ ~ optionName => - DropColumnOption(col, optionName, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("OPTION")) ~ ident ^^ { + case ie ~ col ~ _ ~ optionName => + DropColumnOption(col, optionName, ifExists = ie) } def alterColumnFields: PackratParser[AlterColumnFields] = - alterColumnIfExists ~ ident ~ "SET" ~ multiFields ^^ { case ie ~ col ~ _ ~ fields => + alterColumnIfExists ~ ident ~ keyword("SET") ~ multiFields ^^ { case ie ~ col ~ _ ~ fields => AlterColumnFields(col, fields, ifExists = ie) } def alterColumnField: PackratParser[AlterColumnField] = - alterColumnIfExists ~ ident ~ (("SET" | "ADD") ~ "FIELD") ~ column ^^ { - case ie ~ col ~ _ ~ field => - AlterColumnField(col, field, ifExists = ie) + alterColumnIfExists ~ ident ~ ((keyword("SET") | keyword("ADD")) ~ keyword( + "FIELD" + )) ~ column ^^ { case ie ~ col ~ _ ~ field => + AlterColumnField(col, field, ifExists = ie) } def dropColumnField: PackratParser[DropColumnField] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "FIELD") ~ ident ^^ { case ie ~ col ~ _ ~ fieldName => - DropColumnField(col, fieldName, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("FIELD")) ~ ident ^^ { + case ie ~ col ~ _ ~ fieldName => + DropColumnField(col, fieldName, ifExists = ie) } def alterColumnType: PackratParser[AlterColumnType] = - alterColumnIfExists ~ ident ~ ("SET" ~ "DATA" ~ "TYPE") ~ extension_type ^^ { - case ie ~ name ~ _ ~ newType => AlterColumnType(name, newType, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("SET") ~ keyword("DATA") ~ keyword( + "TYPE" + )) ~ extension_type ^^ { case ie ~ name ~ _ ~ newType => + AlterColumnType(name, newType, ifExists = ie) } def alterColumnScript: PackratParser[AlterColumnScript] = - alterColumnIfExists ~ ident ~ "SET" ~ script ^^ { case ie ~ name ~ _ ~ ns => + alterColumnIfExists ~ ident ~ keyword("SET") ~ script ^^ { case ie ~ name ~ _ ~ ns => AlterColumnScript( name, ScriptProcessor.fromScript(name, ns, Some(ns.out)), @@ -510,63 +539,67 @@ object Parser } def dropColumnScript: PackratParser[DropColumnScript] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "SCRIPT") ^^ { case ie ~ name ~ _ => + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("SCRIPT")) ^^ { case ie ~ name ~ _ => DropColumnScript(name, ifExists = ie) } def alterColumnDefault: PackratParser[AlterColumnDefault] = - alterColumnIfExists ~ ident ~ ("SET" ~ "DEFAULT") ~ value ^^ { case ie ~ name ~ _ ~ dv => - AlterColumnDefault(name, dv, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("SET") ~ keyword("DEFAULT")) ~ value ^^ { + case ie ~ name ~ _ ~ dv => + AlterColumnDefault(name, dv, ifExists = ie) } def dropColumnDefault: PackratParser[DropColumnDefault] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "DEFAULT") ^^ { case ie ~ name ~ _ => + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("DEFAULT")) ^^ { case ie ~ name ~ _ => DropColumnDefault(name, ifExists = ie) } def alterColumnNotNull: PackratParser[AlterColumnNotNull] = - alterColumnIfExists ~ ident ~ ("SET" ~ "NOT" ~ "NULL") ^^ { case ie ~ name ~ _ => - AlterColumnNotNull(name, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("SET") ~ keyword("NOT") ~ keyword("NULL")) ^^ { + case ie ~ name ~ _ => + AlterColumnNotNull(name, ifExists = ie) } def dropColumnNotNull: PackratParser[DropColumnNotNull] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "NOT" ~ "NULL") ^^ { case ie ~ name ~ _ => - DropColumnNotNull(name, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("NOT") ~ keyword("NULL")) ^^ { + case ie ~ name ~ _ => + DropColumnNotNull(name, ifExists = ie) } def alterColumnComment: PackratParser[AlterColumnComment] = - alterColumnIfExists ~ ident ~ ("SET" ~ "COMMENT") ~ literal ^^ { case ie ~ name ~ _ ~ c => - AlterColumnComment(name, c.value, ifExists = ie) + alterColumnIfExists ~ ident ~ (keyword("SET") ~ keyword("COMMENT")) ~ literal ^^ { + case ie ~ name ~ _ ~ c => + AlterColumnComment(name, c.value, ifExists = ie) } def dropColumnComment: PackratParser[DropColumnComment] = - alterColumnIfExists ~ ident ~ ("DROP" ~ "COMMENT") ^^ { case ie ~ name ~ _ => + alterColumnIfExists ~ ident ~ (keyword("DROP") ~ keyword("COMMENT")) ^^ { case ie ~ name ~ _ => DropColumnComment(name, ifExists = ie) } def alterTableMapping: PackratParser[AlterTableMapping] = - (("SET" | "ADD") ~ "MAPPING") ~ option ^^ { case _ ~ opt => + ((keyword("SET") | keyword("ADD")) ~ keyword("MAPPING")) ~ option ^^ { case _ ~ opt => AlterTableMapping(opt._1, opt._2) } def dropTableMapping: PackratParser[DropTableMapping] = - ("DROP" ~ "MAPPING") ~> ident ^^ { m => DropTableMapping(m) } + (keyword("DROP") ~ keyword("MAPPING")) ~> ident ^^ { m => DropTableMapping(m) } def alterTableSetting: PackratParser[AlterTableSetting] = - (("SET" | "ADD") ~ "SETTING") ~ option ^^ { case _ ~ opt => + ((keyword("SET") | keyword("ADD")) ~ keyword("SETTING")) ~ option ^^ { case _ ~ opt => AlterTableSetting(opt._1, opt._2) } def dropTableSetting: PackratParser[DropTableSetting] = - ("DROP" ~ "SETTING") ~> ident ^^ { m => DropTableSetting(m) } + (keyword("DROP") ~ keyword("SETTING")) ~> ident ^^ { m => DropTableSetting(m) } def alterTableAlias: PackratParser[AlterTableAlias] = - (("SET" | "ADD") ~ "ALIAS") ~ option ^^ { case _ ~ opt => + ((keyword("SET") | keyword("ADD")) ~ keyword("ALIAS")) ~ option ^^ { case _ ~ opt => AlterTableAlias(opt._1, opt._2) } def dropTableAlias: PackratParser[DropTableAlias] = - ("DROP" ~ "ALIAS") ~> ident ^^ { m => DropTableAlias(m) } + (keyword("DROP") ~ keyword("ALIAS")) ~> ident ^^ { m => DropTableAlias(m) } def alterTableStatement: PackratParser[AlterTableStatement] = addColumn | @@ -595,7 +628,7 @@ object Parser dropTableAlias def alterTable: PackratParser[AlterTable] = - ("ALTER" ~ "TABLE") ~ ifExists ~ ident ~ start.? ~ repsep( + (keyword("ALTER") ~ keyword("TABLE")) ~ ifExists ~ ident ~ start.? ~ repsep( alterTableStatement, separator ) ~ end.? ^^ { case _ ~ ie ~ table ~ s ~ stmts ~ e => @@ -613,10 +646,10 @@ object Parser // Watcher condition parsers def alwaysWatcherCondition: PackratParser[AlwaysWatcherCondition.type] = - "ALWAYS" ^^ { _ => AlwaysWatcherCondition } + keyword("ALWAYS") ^^ { _ => AlwaysWatcherCondition } def neverWatcherCondition: PackratParser[NeverWatcherCondition.type] = - "NEVER" ^^ { _ => NeverWatcherCondition } + keyword("NEVER") ^^ { _ => NeverWatcherCondition } private def comparison_operator: PackratParser[ComparisonOperator] = eq | ne | diff | gt | ge | lt | le @@ -626,7 +659,7 @@ object Parser date_add | datetime_add | date_sub | datetime_sub def compareWatcherCondition: PackratParser[CompareWatcherCondition] = - "WHEN" ~> opt(not) ~ ident ~ comparison_operator ~ opt(value) ~ opt( + keyword("WHEN") ~> opt(not) ~ ident ~ comparison_operator ~ opt(value) ~ opt( dateMathScript ) ^^ { case n ~ field ~ op ~ v ~ fun => val target_op = @@ -658,14 +691,17 @@ object Parser } private def scriptParams: PackratParser[ListMap[String, Value[_]]] = - ("WITH" ~ "PARAMS") ~> lparen ~ repsep(option, comma) ~ rparen ^^ { case _ ~ opts ~ _ => - ListMap(opts: _*) + (keyword("WITH") ~ keyword("PARAMS")) ~> lparen ~ repsep(option, comma) ~ rparen ^^ { + case _ ~ opts ~ _ => + ListMap(opts: _*) } def scriptWatcherCondition: PackratParser[ScriptWatcherCondition] = - ("WHEN" ~ "SCRIPT") ~> literal ~ opt("USING" ~ "LANG" ~> literal) ~ opt( + (keyword("WHEN") ~ keyword("SCRIPT")) ~> literal ~ opt( + keyword("USING") ~ keyword("LANG") ~> literal + ) ~ opt( scriptParams - ) ~ opt("RETURNS" ~ "TRUE") ^^ { case scr ~ lang ~ p ~ _ => + ) ~ opt(keyword("RETURNS") ~ keyword("TRUE")) ^^ { case scr ~ lang ~ p ~ _ => ScriptWatcherCondition( scr.value, lang.map(_.value).getOrElse("painless"), @@ -678,13 +714,14 @@ object Parser // Watcher trigger parsers def triggerWatcherEveryInterval: PackratParser[IntervalWatcherTrigger] = - "EVERY" ~> """\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?""".r ^^ { str => - val parts = str.trim.split("\\s+") - IntervalWatcherTrigger(Delay(TransformTimeUnit(parts(1)), parts(0).toLong)) + keyword("EVERY") ~> """\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?""".r ^^ { + str => + val parts = str.trim.split("\\s+") + IntervalWatcherTrigger(Delay(TransformTimeUnit(parts(1)), parts(0).toLong)) } def triggerWatcherAtSchedule: PackratParser[CronWatcherTrigger] = - ("AT" ~ "SCHEDULE") ~> literal ^^ { cronExpr => + (keyword("AT") ~ keyword("SCHEDULE")) ~> literal ^^ { cronExpr => CronWatcherTrigger(cronExpr.value) } @@ -693,12 +730,15 @@ object Parser // Watcher input parsers def simpleWatcherInput: PackratParser[SimpleWatcherInput] = - opt("WITH" ~ "INPUT") ~> start ~ repsep(option, comma) ~ end ^^ { case _ ~ opts ~ _ => - SimpleWatcherInput(payload = ObjectValue(ListMap(opts: _*))) + opt(keyword("WITH") ~ keyword("INPUT")) ~> start ~ repsep(option, comma) ~ end ^^ { + case _ ~ opts ~ _ => + SimpleWatcherInput(payload = ObjectValue(ListMap(opts: _*))) } def withinTimeout: PackratParser[Option[Delay]] = - opt("WITHIN" ~> """(\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?)""".r) ^^ { + opt( + keyword("WITHIN") ~> """(\d+\s+(MILLISECOND|SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR)S?)""".r + ) ^^ { case Some(str) => val parts = str.trim.split("\\s+") Some(Delay(TransformTimeUnit(parts(1)), parts(0).toLong)) @@ -715,17 +755,17 @@ object Parser } def httpInput: PackratParser[HttpInput] = - opt("WITH" ~ "INPUT") ~> httpRequest ^^ { req => + opt(keyword("WITH") ~ keyword("INPUT")) ~> httpRequest ^^ { req => HttpInput(req) } def chainInput: PackratParser[(String, WatcherInput)] = - ident ~ opt("AS") ~ watcherInput ^^ { case name ~ _ ~ input => + ident ~ opt(keyword("AS")) ~ watcherInput ^^ { case name ~ _ ~ input => (name, input) } def chainInputs: PackratParser[WatcherInput] = - ("WITH" ~ "INPUTS") ~> rep1sep( + (keyword("WITH") ~ keyword("INPUTS")) ~> rep1sep( chainInput, comma ) ^^ { inputs => @@ -746,13 +786,13 @@ object Parser // action foreach limit parser def foreachWithLimit: PackratParser[(String, Int)] = - ("FOREACH" ~> literal) ~ ("LIMIT" ~> """\d+""".r) ^^ { case fe ~ l => + (keyword("FOREACH") ~> literal) ~ (keyword("LIMIT") ~> """\d+""".r) ^^ { case fe ~ l => (fe.value, l.toInt) } // simple logging action parser def loggingAction: PackratParser[Option[LoggingAction]] = - ("LOG" ~> literal) ~ opt("AT" ~> loggingLevel) ~ opt(foreachWithLimit) ^^ { + (keyword("LOG") ~> literal) ~ opt(keyword("AT") ~> loggingLevel) ~ opt(foreachWithLimit) ^^ { case text ~ levelOpt ~ feOpt => val foreach = feOpt.map(_._1) val limit = feOpt.map(_._2) @@ -761,14 +801,14 @@ object Parser // webhook action parser def webhookAction: PackratParser[Option[WebhookAction]] = - "WEBHOOK" ~> httpRequest ~ opt(foreachWithLimit) ^^ { case req ~ feOpt => + keyword("WEBHOOK") ~> httpRequest ~ opt(foreachWithLimit) ^^ { case req ~ feOpt => val foreach = feOpt.map(_._1) val limit = feOpt.map(_._2) Some(WebhookAction(req, foreach, limit)) } def watcherAction: PackratParser[(String, WatcherAction)] = - ident ~ opt("AS") ~ (loggingAction | webhookAction) ^^ { case name ~ _ ~ wa => + ident ~ opt(keyword("AS")) ~ (loggingAction | webhookAction) ^^ { case name ~ _ ~ wa => wa match { case Some(wa) => (name, wa) case _ => @@ -787,9 +827,11 @@ object Parser } def createOrReplaceWatcher: PackratParser[CreateWatcher] = - ("CREATE" ~ "OR" ~ "REPLACE" ~ "WATCHER") ~> ident ~ opt( - "AS" - ) ~ watcherTrigger ~ watcherInput ~ watcherCondition ~ ("DO" ~> watcherActions <~ "END") ^^ { + (keyword("CREATE") ~ keyword("OR") ~ keyword("REPLACE") ~ keyword("WATCHER")) ~> ident ~ opt( + keyword("AS") + ) ~ watcherTrigger ~ watcherInput ~ watcherCondition ~ (keyword( + "DO" + ) ~> watcherActions <~ keyword("END")) ^^ { case name ~ _ ~ trigger ~ input ~ condition ~ actions => CreateWatcher( name = name, @@ -803,9 +845,11 @@ object Parser } def createWatcher: PackratParser[CreateWatcher] = - ("CREATE" ~ "WATCHER") ~ ifNotExists ~ ident ~ opt( - "AS" - ) ~ watcherTrigger ~ watcherInput ~ watcherCondition ~ ("DO" ~> watcherActions <~ "END") ^^ { + (keyword("CREATE") ~ keyword("WATCHER")) ~ ifNotExists ~ ident ~ opt( + keyword("AS") + ) ~ watcherTrigger ~ watcherInput ~ watcherCondition ~ (keyword( + "DO" + ) ~> watcherActions <~ keyword("END")) ^^ { case _ ~ _ ~ ine ~ name ~ _ ~ trigger ~ input ~ condition ~ actions => CreateWatcher( name = name, @@ -819,28 +863,28 @@ object Parser } def showWatcherStatus: PackratParser[ShowWatcherStatus] = - ("SHOW" ~ "WATCHER" ~ "STATUS") ~> ident ^^ { name => + (keyword("SHOW") ~ keyword("WATCHER") ~ keyword("STATUS")) ~> ident ^^ { name => ShowWatcherStatus(name) } def showWatchers: PackratParser[ShowWatchers.type] = - ("SHOW" ~ "WATCHERS") ^^ { _ => + (keyword("SHOW") ~ keyword("WATCHERS")) ^^ { _ => ShowWatchers } def dropWatcher: PackratParser[DropWatcher] = - ("DROP" ~ "WATCHER") ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => + (keyword("DROP") ~ keyword("WATCHER")) ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => DropWatcher(name, ifExists = ie) } def createEnrichPolicy: PackratParser[CreateEnrichPolicy] = - ("CREATE" ~ "ENRICH" ~ "POLICY") ~ + (keyword("CREATE") ~ keyword("ENRICH") ~ keyword("POLICY")) ~ ifNotExists ~ ident ~ - opt("TYPE" ~> ("MATCH" | "GEO_MATCH" | "RANGE")) ~ - ("FROM" ~> repsep(ident, separator)) ~ - ("ON" ~> ident) ~ - ("ENRICH" ~> repsep(ident, separator)) ~ + opt(keyword("TYPE") ~> (keyword("MATCH") | keyword("GEO_MATCH") | keyword("RANGE"))) ~ + (keyword("FROM") ~> repsep(ident, separator)) ~ + (keyword("ON") ~> ident) ~ + (keyword("ENRICH") ~> repsep(ident, separator)) ~ opt(where) ^^ { case _ ~ ine ~ name ~ policyTypeOpt ~ sources ~ on ~ refreshFields ~ whereOpt => val policyType = policyTypeOpt match { case Some(value) => EnrichPolicyType(value) @@ -858,12 +902,14 @@ object Parser } def createOrReplaceEnrichPolicy: PackratParser[CreateEnrichPolicy] = - ("CREATE" ~ "OR" ~ "REPLACE" ~ "ENRICH" ~ "POLICY") ~ + (keyword("CREATE") ~ keyword("OR") ~ keyword("REPLACE") ~ keyword("ENRICH") ~ keyword( + "POLICY" + )) ~ ident ~ - opt("TYPE" ~> ("MATCH" | "GEO_MATCH" | "RANGE")) ~ - ("FROM" ~> repsep(ident, separator)) ~ - ("ON" ~> ident) ~ - ("ENRICH" ~> repsep(ident, separator)) ~ + opt(keyword("TYPE") ~> (keyword("MATCH") | keyword("GEO_MATCH") | keyword("RANGE"))) ~ + (keyword("FROM") ~> repsep(ident, separator)) ~ + (keyword("ON") ~> ident) ~ + (keyword("ENRICH") ~> repsep(ident, separator)) ~ opt(where) ^^ { case _ ~ name ~ policyTypeOpt ~ sources ~ on ~ refreshFields ~ whereOpt => val policyType = policyTypeOpt match { case Some("MATCH") => EnrichPolicyType.Match @@ -883,27 +929,28 @@ object Parser } def executeEnrichPolicy: PackratParser[ExecuteEnrichPolicy] = - ("EXECUTE" ~ "ENRICH" ~ "POLICY") ~> ident ^^ { name => + (keyword("EXECUTE") ~ keyword("ENRICH") ~ keyword("POLICY")) ~> ident ^^ { name => ExecuteEnrichPolicy(name) } def dropEnrichPolicy: PackratParser[DropEnrichPolicy] = - ("DROP" ~ "ENRICH" ~ "POLICY") ~ ifExists ~ ident ^^ { case _ ~ ie ~ name => - DropEnrichPolicy(name, ifExists = ie) + (keyword("DROP") ~ keyword("ENRICH") ~ keyword("POLICY")) ~ ifExists ~ ident ^^ { + case _ ~ ie ~ name => + DropEnrichPolicy(name, ifExists = ie) } def showEnrichPolicy: PackratParser[ShowEnrichPolicy] = - ("SHOW" ~ "ENRICH" ~ "POLICY") ~> ident ^^ { name => + (keyword("SHOW") ~ keyword("ENRICH") ~ keyword("POLICY")) ~> ident ^^ { name => ShowEnrichPolicy(name) } def showEnrichPolicies: PackratParser[ShowEnrichPolicies.type] = - ("SHOW" ~ "ENRICH" ~ "POLICIES") ^^ { _ => + (keyword("SHOW") ~ keyword("ENRICH") ~ keyword("POLICIES")) ^^ { _ => ShowEnrichPolicies } def showClusterName: PackratParser[ShowClusterName.type] = - ("(?i)SHOW\\b".r ~ "(?i)CLUSTER\\b".r ~ "(?i)NAME\\b".r) ^^ { _ => + (keyword("SHOW") ~ keyword("CLUSTER") ~ keyword("NAME")) ^^ { _ => ShowClusterName } @@ -952,9 +999,10 @@ object Parser dropEnrichPolicy def onConflict: PackratParser[OnConflict] = - ("ON" ~ "CONFLICT" ~> opt(conflictTarget) <~ "DO") ~ ("UPDATE" | "NOTHING") ^^ { - case target ~ action => - OnConflict(target, action == "UPDATE") + (keyword("ON") ~ keyword("CONFLICT") ~> opt(conflictTarget) <~ keyword("DO")) ~ (keyword( + "UPDATE" + ) | keyword("NOTHING")) ^^ { case target ~ action => + OnConflict(target, action == "UPDATE") } def conflictTarget: PackratParser[List[String]] = @@ -962,9 +1010,9 @@ object Parser /** INSERT INTO table [(col1, col2, ...)] VALUES (v1, v2, ...) */ def insert: PackratParser[Insert] = - ("INSERT" ~ "INTO") ~ ident ~ opt(lparen ~> repsep(ident, comma) <~ rparen) ~ - (("VALUES" ~> rows) ^^ { vs => Right(vs) } - | "AS".? ~> searchStatement ^^ { q => Left(q) }) ~ opt(onConflict) ^^ { + (keyword("INSERT") ~ keyword("INTO")) ~ ident ~ opt(lparen ~> repsep(ident, comma) <~ rparen) ~ + ((keyword("VALUES") ~> rows) ^^ { vs => Right(vs) } + | keyword("AS").? ~> searchStatement ^^ { q => Left(q) }) ~ opt(onConflict) ^^ { case _ ~ table ~ colsOpt ~ vals ~ conflict => conflict match { case Some(c) => Insert(table, colsOpt.getOrElse(Nil), vals, Some(c)) @@ -978,23 +1026,24 @@ object Parser } def fileFormat: PackratParser[FileFormat] = - ("FILE_FORMAT" ~> ( - ("PARQUET" ^^^ Parquet) | - ("JSON" ^^^ Json) | - ("JSON_ARRAY" ^^^ JsonArray) | - ("DELTA_LAKE" ^^^ Delta) + (keyword("FILE_FORMAT") ~> ( + (keyword("PARQUET") ^^^ Parquet) | + (keyword("JSON_ARRAY") ^^^ JsonArray) | + (keyword("JSON") ^^^ Json) | + (keyword("DELTA_LAKE") ^^^ Delta) )) ^^ { ff => ff } /** COPY INTO table FROM source */ def copy: PackratParser[CopyInto] = - ("COPY" ~ "INTO") ~ ident ~ ("FROM" ~> literal) ~ opt(fileFormat) ~ opt(onConflict) ^^ { - case _ ~ table ~ source ~ format ~ conflict => - CopyInto(source.value, table, fileFormat = format, onConflict = conflict) + (keyword("COPY") ~ keyword("INTO")) ~ ident ~ (keyword("FROM") ~> literal) ~ opt( + fileFormat + ) ~ opt(onConflict) ^^ { case _ ~ table ~ source ~ format ~ conflict => + CopyInto(source.value, table, fileFormat = format, onConflict = conflict) } /** UPDATE table SET col1 = v1, col2 = v2 [WHERE ...] */ def update: PackratParser[Update] = - ("UPDATE" ~> ident) ~ ("SET" ~> repsep( + (keyword("UPDATE") ~> ident) ~ (keyword("SET") ~> repsep( ident ~ "=" ~ (value | scriptValue), separator )) ~ where.? ^^ { case table ~ assigns ~ w => @@ -1004,7 +1053,7 @@ object Parser /** DELETE FROM table [WHERE ...] */ def delete: PackratParser[Delete] = - ("DELETE" ~ "FROM") ~> ident ~ where.? ^^ { case table ~ w => + (keyword("DELETE") ~ keyword("FROM")) ~> ident ~ where.? ^^ { case table ~ w => Delete(Table(table), w) } @@ -1054,6 +1103,8 @@ trait Parser with TypeParser with HttpParser { _: WhereParser with OrderByParser with LimitParser => + protected def keyword(word: String): Parser[String] = s"(?i)$word\\b".r ^^ (_ => word) + def ident: Parser[String] = """[a-zA-Z_][a-zA-Z0-9_.]*""".r val lparen: Parser[String] = "(" @@ -1083,7 +1134,7 @@ trait Parser } def options: PackratParser[ListMap[String, Value[_]]] = - "OPTIONS" ~ lparen ~ repsep(option, comma) ~ rparen ^^ { case _ ~ _ ~ opts ~ _ => + keyword("OPTIONS") ~ lparen ~ repsep(option, comma) ~ rparen ^^ { case _ ~ _ ~ opts ~ _ => ListMap(opts: _*) } From 85b759d3f61dc51f89127ddeb30421bb1bd945a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 30 Mar 2026 11:42:19 +0200 Subject: [PATCH 3/4] chore: update project version to 0.20-SNAPSHOT in build.sbt --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0011f269..dab63ba6 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ ThisBuild / organization := "app.softnetwork" name := "softclient4es" -ThisBuild / version := "0.20.0" +ThisBuild / version := "0.20-SNAPSHOT" ThisBuild / scalaVersion := scala213 From 1888c504afaed7b2196dc4418703418f1b7409b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Manciot?= Date: Mon, 30 Mar 2026 12:00:34 +0200 Subject: [PATCH 4/4] feat: add GetClusterInfo action to retrieve cluster information for Jest --- .../client/jest/actions/GetClusterInfo.scala | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/actions/GetClusterInfo.scala diff --git a/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/actions/GetClusterInfo.scala b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/actions/GetClusterInfo.scala new file mode 100644 index 00000000..f008d627 --- /dev/null +++ b/es6/jest/src/main/scala/app/softnetwork/elastic/client/jest/actions/GetClusterInfo.scala @@ -0,0 +1,34 @@ +/* + * Copyright 2025 SOFTNETWORK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app.softnetwork.elastic.client.jest.actions + +import io.searchbox.action.AbstractAction +import io.searchbox.action.GenericResultAbstractAction +import io.searchbox.client.config.ElasticsearchVersion + +object GetClusterInfo { + class Builder extends AbstractAction.Builder[GetClusterInfo, GetClusterInfo.Builder] { + override def build = new GetClusterInfo(this) + } +} + +class GetClusterInfo protected (builder: GetClusterInfo.Builder) + extends GenericResultAbstractAction(builder) { + override def getRestMethodName = "GET" + + override def buildURI(elasticsearchVersion: ElasticsearchVersion): String = "/" +}