From 3e954ae55a0d9d71458f8c03e11092cc2b574457 Mon Sep 17 00:00:00 2001 From: jclausen Date: Sun, 26 Nov 2023 12:05:24 -0500 Subject: [PATCH 01/14] bump snapshot [ci-skip] --- box.json | 2 +- changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/box.json b/box.json index e41a3cc..767253a 100644 --- a/box.json +++ b/box.json @@ -2,7 +2,7 @@ "name":"Elasticsearch for the Coldbox Framework", "author":"Ortus Solutions Date: Tue, 20 Feb 2024 15:13:29 -0500 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Add=20support?= =?UTF-8?q?=20for=20missing=20'reason'=20key=20in=20error=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/io/HyperClient.cfc | 6 +++--- models/util/Util.cfc | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index dc9e1d2..283c42d 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -453,7 +453,7 @@ component accessors="true" threadSafe singleton { } else if ( reindexResult.keyExists( "error" ) ) { throw( type = "cbElasticsearch.HyperClient.ReindexFailedException", - message = "The reindex action failed with response code [#reindexResult.status#]. The cause of this exception was #reindexResult.error.reason#", + message = "The reindex action failed with response code [#reindexResult.status#]. The cause of this exception was #reindexResult.error.reason ?: 'None'#", extendedInfo = getUtil().toJSON( reindexResult ) ); } @@ -1206,10 +1206,10 @@ component accessors="true" threadSafe singleton { item.update.keyExists( "error" ) && item.update.error.keyExists( "root_cause" ) ) - ? " Reason: #isArray( item.update.error.root_cause ) ? item.update.error.root_cause[ 1 ].reason : item.update.error.root_cause.reason#" + ? " Reason: #isArray( item.update.error.root_cause ) ? ( item.update.error.root_cause[ 1 ].reason ?: 'None' ) : ( item.update.error.root_cause.reason ?: 'None' )#" : ( structKeyExists( item.update, "error" ) - ? " Reason: #item.update.error.reason#" + ? " Reason: #item.update.error.reason ?: 'None'#" : "" ); throw( diff --git a/models/util/Util.cfc b/models/util/Util.cfc index abeb366..65e854e 100644 --- a/models/util/Util.cfc +++ b/models/util/Util.cfc @@ -95,13 +95,13 @@ component accessors="true" singleton { && !isSimpleValue( errorPayload.error ) && errorPayload.error.keyExists( "root_cause" ) ) - ? " Reason: #isArray( errorPayload.error.root_cause ) ? errorPayload.error.root_cause[ 1 ].reason : errorPayload.error.root_cause.reason#" + ? " Reason: #isArray( errorPayload.error.root_cause ) ? ( errorPayload.error.root_cause[ 1 ].reason ?: 'None' ) : ( errorPayload.error.root_cause.reason ?: 'None' )#" : ( structKeyExists( errorPayload, "error" ) ? ( isSimpleValue( errorPayload.error ) ? " Reason: #errorPayload.error# " - : " Reason: #errorPayload.error.reason#" + : " Reason: #errorPayload.error.reason ?: 'None'#" ) : "" ); From 823f41523ca19be97c3b641ee32e462569976584 Mon Sep 17 00:00:00 2001 From: michaelborn Date: Tue, 20 Feb 2024 20:17:55 +0000 Subject: [PATCH 03/14] Apply cfformat changes --- models/SearchBuilder.cfc | 20 ++++++++------ models/io/HyperClient.cfc | 23 ++++++++++------ models/logging/LogstashAppender.cfc | 42 ++++++++++++++--------------- models/util/Util.cfc | 6 +++-- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index 8af1dbc..a496cab 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -177,8 +177,12 @@ component accessors="true" { * @fields Array or list of fields to pull term vectors on * @options Any custom parameters to send with the request. */ - struct function getTermVectors( string id = "", any fields = "", struct options = {} ){ - var args = arguments; + struct function getTermVectors( + string id = "", + any fields = "", + struct options = {} + ){ + var args = arguments; args.indexName = variables.index; return getClient().getTermVectors( argumentCollection = args ); @@ -196,13 +200,13 @@ component accessors="true" { return this; } - /** + /** * Backwards compatible getter for max result size - * + * * @deprecated */ any function getMaxRows(){ - return getSize(); + return getSize(); } /** @@ -217,13 +221,13 @@ component accessors="true" { return this; } - /** + /** * Backwards compatible getter for start row - * + * * @deprecated */ any function getStartRow(){ - return getFrom(); + return getFrom(); } /** diff --git a/models/io/HyperClient.cfc b/models/io/HyperClient.cfc index 283c42d..08638f6 100644 --- a/models/io/HyperClient.cfc +++ b/models/io/HyperClient.cfc @@ -453,7 +453,7 @@ component accessors="true" threadSafe singleton { } else if ( reindexResult.keyExists( "error" ) ) { throw( type = "cbElasticsearch.HyperClient.ReindexFailedException", - message = "The reindex action failed with response code [#reindexResult.status#]. The cause of this exception was #reindexResult.error.reason ?: 'None'#", + message = "The reindex action failed with response code [#reindexResult.status#]. The cause of this exception was #reindexResult.error.reason ?: "None"#", extendedInfo = getUtil().toJSON( reindexResult ) ); } @@ -538,13 +538,18 @@ component accessors="true" threadSafe singleton { * @params struct Struct of query parameters to influence the request. For example: `"offsets": false }` * @options struct Body payload to send. For example: `{ "filter": { "max_num_terms": 3 } }` */ - struct function getTermVectors( required string indexName, string id = "", any fields = [], struct options = {} ){ + struct function getTermVectors( + required string indexName, + string id = "", + any fields = [], + struct options = {} + ){ arguments.options[ "fields" ] = arguments.fields; - if ( !isArray( arguments.options["fields"] ) ) { - arguments.options["fields"] = listToArray( arguments.options["fields"] ); + if ( !isArray( arguments.options[ "fields" ] ) ) { + arguments.options[ "fields" ] = listToArray( arguments.options[ "fields" ] ); } - var endpoint = [arguments.indexName, "_termvectors" ]; + var endpoint = [ arguments.indexName, "_termvectors" ]; if ( arguments.id != "" ) { endpoint.append( arguments.id ); } @@ -1031,7 +1036,7 @@ component accessors="true" threadSafe singleton { deleteRequest.setQueryParam( param.name, param.value ); } ); - var response = deleteRequest.send(); + var response = deleteRequest.send(); var deleteResult = response.json(); if ( arguments.throwOnError && structKeyExists( deleteResult, "error" ) ) { @@ -1206,10 +1211,12 @@ component accessors="true" threadSafe singleton { item.update.keyExists( "error" ) && item.update.error.keyExists( "root_cause" ) ) - ? " Reason: #isArray( item.update.error.root_cause ) ? ( item.update.error.root_cause[ 1 ].reason ?: 'None' ) : ( item.update.error.root_cause.reason ?: 'None' )#" + ? " Reason: #isArray( item.update.error.root_cause ) ? ( item.update.error.root_cause[ 1 ].reason ?: "None" ) : ( + item.update.error.root_cause.reason ?: "None" + )#" : ( structKeyExists( item.update, "error" ) - ? " Reason: #item.update.error.reason ?: 'None'#" + ? " Reason: #item.update.error.reason ?: "None"#" : "" ); throw( diff --git a/models/logging/LogstashAppender.cfc b/models/logging/LogstashAppender.cfc index b13265c..f41c492 100644 --- a/models/logging/LogstashAppender.cfc +++ b/models/logging/LogstashAppender.cfc @@ -123,10 +123,10 @@ component * Runs on registration */ public LogstashAppender function onRegistration() output=false{ - try{ + try { ensureDataStream(); variables._dataStreamAssured = true; - } catch( any e ){ + } catch ( any e ) { createObject( "java", "java.lang.System" ).err.println( "Unable to create data stream. The attempt to communicate with the Elasticsearch server returned: #e.message# - #e.detail#. Your ability to log messages with this appender may be compromised." ); @@ -138,11 +138,10 @@ component * Write an entry into the appender. */ public void function logMessage( required any logEvent ) output=false{ - - if( !variables._dataStreamAssured ){ + if ( !variables._dataStreamAssured ) { this.onRegistration(); // skip out if there was a communication failure - if( !variables._dataStreamAssured ){ + if ( !variables._dataStreamAssured ) { return; } } @@ -151,8 +150,11 @@ component try { var document = newDocument().new( index = getProperty( "dataStream" ), properties = logObj ); - if( getProperty( "async" ) ){ - variables.asyncManager.newFuture().withTimeout( getProperty( "asyncTimeout" ) ).run( () => document.create() ); + if ( getProperty( "async" ) ) { + variables.asyncManager + .newFuture() + .withTimeout( getProperty( "asyncTimeout" ) ) + .run( () => document.create() ); } else { document.create(); } @@ -166,12 +168,12 @@ component extraInfo = { "logData" : logObj, "exception" : e }, category = e.type ); - var appendersMap = application.wirebox.getLogbox().getAppenderRegistry(); + var appendersMap = application.wirebox.getLogbox().getAppenderRegistry(); // Log errors out to other appenders besides this one appendersMap .keyArray() .filter( function( key ){ - return lcase( key ) != lcase( getName() ); + return lCase( key ) != lCase( getName() ); } ) .each( function( appenderName ){ appendersMap[ appenderName ].logMessage( eLogEvent ); @@ -366,7 +368,9 @@ component if ( propertyExists( "lifecyclePolicy" ) ) { policyBuilder.setPhases( getProperty( "lifecyclePolicy" ) ); } else { - policyBuilder.hotPhase( rollover=getProperty( "rolloverSize" ) ).withDeletion( age = getProperty( "retentionDays" ) ); + policyBuilder + .hotPhase( rollover = getProperty( "rolloverSize" ) ) + .withDeletion( age = getProperty( "retentionDays" ) ); } policyBuilder.save(); @@ -407,7 +411,9 @@ component ], "data_stream" : {}, "priority" : getProperty( "indexTemplatePriority" ), - "_meta" : { "description" : "Index Template for cbElasticsearch Logs ( DataStream: #dataStreamName# )" } + "_meta" : { + "description" : "Index Template for cbElasticsearch Logs ( DataStream: #dataStreamName# )" + } } ); @@ -645,7 +651,7 @@ component "dynamic_templates" : [ { "user_info_fields" : { - "path_match" : "user.info.*", + "path_match" : "user.info.*", "match_mapping_type" : "string", "mapping" : { "type" : "text", @@ -682,17 +688,11 @@ component }, "error" : { "type" : "object", - "properties" : { - "extrainfo" : { "type" : "text" } - } + "properties" : { "extrainfo" : { "type" : "text" } } }, "message" : { "type" : "text", - "fields" : { - "keyword" : { - "type" : "keyword", "ignore_above" : 512 - } - } + "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 512 } } }, "event" : { "type" : "object", @@ -709,7 +709,7 @@ component "properties" : { "signature" : { "type" : "keyword" }, "isSuppressed" : { "type" : "boolean" }, - "assignment" : { "type" : "keyword" } + "assignment" : { "type" : "keyword" } } } } diff --git a/models/util/Util.cfc b/models/util/Util.cfc index 65e854e..46d0040 100644 --- a/models/util/Util.cfc +++ b/models/util/Util.cfc @@ -95,13 +95,15 @@ component accessors="true" singleton { && !isSimpleValue( errorPayload.error ) && errorPayload.error.keyExists( "root_cause" ) ) - ? " Reason: #isArray( errorPayload.error.root_cause ) ? ( errorPayload.error.root_cause[ 1 ].reason ?: 'None' ) : ( errorPayload.error.root_cause.reason ?: 'None' )#" + ? " Reason: #isArray( errorPayload.error.root_cause ) ? ( errorPayload.error.root_cause[ 1 ].reason ?: "None" ) : ( + errorPayload.error.root_cause.reason ?: "None" + )#" : ( structKeyExists( errorPayload, "error" ) ? ( isSimpleValue( errorPayload.error ) ? " Reason: #errorPayload.error# " - : " Reason: #errorPayload.error.reason ?: 'None'#" + : " Reason: #errorPayload.error.reason ?: "None"#" ) : "" ); From 363eec41fd551be2002a801c97614d9da2734307 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Wed, 21 Feb 2024 07:42:26 -0500 Subject: [PATCH 04/14] =?UTF-8?q?=F0=9F=93=96=20DOC:=20Add=20changelog=20n?= =?UTF-8?q?ote=20for=20improved=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index 3c4e931..dad7c96 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ---- ## [Unreleased] + +### Fixed +* Improved error handling for rare cases where ElasticSearch exceptions are missing a `reason` key. + ## [3.2.5] - 11-26-2023 ### Added * Added `asyncTimeout` setting with default of 5000ms From 4ccd485682e272eb5834cf13775df8c517b7d1e5 Mon Sep 17 00:00:00 2001 From: Michael Born <8106227+michaelborn@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:12:51 -0400 Subject: [PATCH 05/14] Minor cleanup in Pipelines doc --- docs/Pipelines.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/Pipelines.md b/docs/Pipelines.md index f86288a..ee66076 100644 --- a/docs/Pipelines.md +++ b/docs/Pipelines.md @@ -4,7 +4,7 @@ description: Learn how to transform incoming data in an Elasticsearch Ingest Pip # Ingest Pipelines -Elasticsearch allows you to create pipelines which pre-process inbound documents and data. Methods are available to create, read, update and delete pipelines. For more information on defining processors, conditionals and options see the (PUT Pipeline)[https://www.elastic.co/guide/en/elasticsearch/reference/master/put-pipeline-api.html] and (Processor)[https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-processors.html] documentation. +Elasticsearch allows you to create pipelines which pre-process inbound documents and data. Methods are available to create, read, update and delete pipelines. For more information on defining processors, conditionals and options see the [PUT Pipeline](https://www.elastic.co/guide/en/elasticsearch/reference/master/put-pipeline-api.html) and [Processor](https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-processors.html) documentation. ## Creating a Pipeline @@ -13,19 +13,19 @@ Let's say we want to automatically set a field on a document when we save it. We ```js var myPipeline = getInstance( "Pipeline@cbelasticsearch" ).new( { - "id" : "foo-pipeline", - "description" : "A test pipeline", - "version" : 1, - "processors" : [ - { - "set" : { - "if" : "ctx.foo == null", - "field" : "foo", - "value" : "bar" - } - } - ] - } ); + "id" : "foo-pipeline", + "description" : "A test pipeline", + "version" : 1, + "processors" : [ + { + "set" : { + "if" : "ctx.foo == null", + "field" : "foo", + "value" : "bar" + } + } + ] + } ); ``` With this pipeline, if a value of `foo` is not defined ( note that `ctx` is the document reference in the `if` conditional ) in the inbound document, then the value of that field will automatically be set to `'bar'`. @@ -67,8 +67,8 @@ We can modify pipelines using the pipeline object, as well. Let's do this by ret ```js var pipeline = getInstance( "Pipeline@cbElasticsearch" ) - .new( getInstance( "Client@cbElasticsearch" ) - .getPipeline( "foo-pipeline" ) ); + .new( getInstance( "Client@cbElasticsearch" ) + .getPipeline( "foo-pipeline" ) ); pipeline.addProcessor( { @@ -107,4 +107,4 @@ For multiple documents, the pipeline may be set in the document, prior to the `s ```js getInstance( "Client@cbElasticsearch" ) .saveAll( documents=myDocuments, params={ "pipeline" : "foo-pipeline" } ); -``` \ No newline at end of file +``` From 5b1f38db3059e2ac1e3377906165162ce11ea025 Mon Sep 17 00:00:00 2001 From: Michael Born <8106227+michaelborn@users.noreply.github.com> Date: Mon, 18 Mar 2024 06:13:31 -0400 Subject: [PATCH 06/14] Fix header casing in Pipelines doc --- docs/Pipelines.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Pipelines.md b/docs/Pipelines.md index ee66076..53bad62 100644 --- a/docs/Pipelines.md +++ b/docs/Pipelines.md @@ -46,7 +46,7 @@ getInstance( "Client@cbElasticsearch" ).applyPipeline( myPipeline ); Note that if you are using a [secondary cluster](Configuration.md), you will need to perform your CRUD operations through the client, as the `save` method in the pipeline object will route through the top level client. -## Retrieving pipeline definitions +## Retrieving Pipeline Definitions If we know the name of our pipeline, we can retreive the definition from Elasticsearch by using the `getPipeline` method of the client: @@ -92,7 +92,7 @@ getInstance( "Client@cbElastisearch" ) .deletePipeline( "foo-pipeline" ); ``` -## Using pipelines When Saving Documents +## Using Pipelines When Saving Documents Pipelines may be used when saving individual or multiple documents. See the [Documents](Documents.md) section for more information on document creation. From 02c4129cc68724e5b8b5de9ec8762ab5a878998d Mon Sep 17 00:00:00 2001 From: Michael Born Date: Thu, 28 Mar 2024 15:34:12 -0400 Subject: [PATCH 07/14] WIP: Add runtime mappings support via addRuntimeMapping() --- docs/Searching/Search.md | 22 ++++++++++++++ models/SearchBuilder.cfc | 30 +++++++++++++++++++ .../tests/specs/unit/SearchBuilderTest.cfc | 14 +++++++++ 3 files changed, 66 insertions(+) diff --git a/docs/Searching/Search.md b/docs/Searching/Search.md index ce3e21b..586cdcb 100644 --- a/docs/Searching/Search.md +++ b/docs/Searching/Search.md @@ -203,6 +203,28 @@ for( hit in result.getHits() ){ } ``` + +### Runtime Mappings + +SearchBuilder also supports [Elasticsearch runtime mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html), which allow you to define a field in the index mapping which is generated or computed at runtime, but unlike [script fields](#script-fields) are available to use in aggregations, search queries, and so forth. + +```js +searchBuilder.addRuntimeMapping( "hasPricing", { + "type" : "boolean", + "script": { + "source": "doc.containsKey( 'price' )" + } +} ); +``` + +This will result in an `"hasPricing"` field in the `fields` property on the `Document` object: + +```js +var documentsWithPricing = searchBuilder.execute() + .getHits() + .filter( (document) => document.getFields()["hasPricing"] ); +``` + ### Advanced Query DSL The SearchBuilder also allows full use of the [Elasticsearch query language](https://www.elastic.co/guide/en/elasticsearch/reference/current/_introducing_the_query_language.html), allowing full configuration of your search queries. There are several methods to provide the raw query language to the Search Builder. One is during instantiation. diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index a496cab..47f323a 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -57,6 +57,13 @@ component accessors="true" { */ property name="scriptFields" type="struct"; + /** + * Property containing elasticsearch "runtime_mappings" definition for runtime scripted fields + * + * https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html + */ + property name="runtimeMappings" type="struct"; + /** * Property containing "fields" array of fields to return for each hit * @@ -1247,6 +1254,10 @@ component accessors="true" { dsl[ "script_fields" ] = variables.scriptFields; } + if ( !isNull( variables.runtimeMappings ) ) { + dsl[ "runtime_mappings" ] = variables.runtimeMappings; + } + if ( !isNull( variables.fields ) ) { dsl[ "fields" ] = variables.fields; } @@ -1362,4 +1373,23 @@ component accessors="true" { return this; } + /** + * Append a search-time (runtime) mapping to the search. + * + * @name Name of the runtime mapping field + * + * @script Script to use. `{ "script" : { "lang": "painless", "source" : } }` + */ + public SearchBuilder function addRuntimeMapping( + required string name, + struct script, + any source = true + ){ + if ( isNull( variables.runtimeMappings ) ) { + variables.runtimeMappings = {}; + } + variables.runtimeMappings[ arguments.name ] = arguments.script; + return this; + } + } diff --git a/test-harness/tests/specs/unit/SearchBuilderTest.cfc b/test-harness/tests/specs/unit/SearchBuilderTest.cfc index 2cd82e4..eea2f4f 100644 --- a/test-harness/tests/specs/unit/SearchBuilderTest.cfc +++ b/test-harness/tests/specs/unit/SearchBuilderTest.cfc @@ -830,6 +830,20 @@ component extends="coldbox.system.testing.BaseTestCase" { expect( searchBuilder.getDSL()[ "script_fields" ] ).toHaveKey( "with5PercentDiscount" ); } ); + it( "Tests the addRuntimeMapping() method", function(){ + var searchBuilder = variables.model.new( variables.testIndexName, "testdocs" ); + + searchBuilder.addRuntimeMapping( "hasPricing", { + "type" : "boolean", + "script": { + "source": "doc.containsKey( 'price' )" + } + } ); + + expect( searchBuilder.getDSL() ).toBeStruct().toHaveKey( "runtime_mappings" ); + expect( searchBuilder.getDSL()[ "runtime_mappings" ] ).toHaveKey( "hasPricing" ); + } ); + it( "Tests the addField() method for retrieving runtime or other fields", function(){ var search = variables.model.new( variables.testIndexName, "testdocs" ); From d0af284ad4e6f8713dacdfcc1542def09e4322a2 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 29 Mar 2024 06:59:30 -0400 Subject: [PATCH 08/14] Improve documentation for runtime mapping fields --- docs/Searching/Search.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Searching/Search.md b/docs/Searching/Search.md index 586cdcb..1fa0e1b 100644 --- a/docs/Searching/Search.md +++ b/docs/Searching/Search.md @@ -162,7 +162,7 @@ var interest = searchBuilder.execute().getHits().map( (document) => document.get ### Runtime Fields -Elasticsearch also allows the creation of runtime fields, which are fields defined in the index mapping but populated at search time via a script. +Elasticsearch also supports defining runtime fields, which are fields defined in the index mapping but populated at search time via a script. You can [define these in the index mapping](../Indices/Managing-Indices.md#creating-runtime-fields), or [define them at search time](#define-runtime-fields-at-search-time). {% hint style="info" %} See [Managing-Indices](../Indices/Managing-Indices.md#creating-runtime-fields) for more information on creating runtime fields. @@ -204,9 +204,9 @@ for( hit in result.getHits() ){ ``` -### Runtime Mappings +### Define Runtime Fields At Search Time -SearchBuilder also supports [Elasticsearch runtime mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-mapping-fields.html), which allow you to define a field in the index mapping which is generated or computed at runtime, but unlike [script fields](#script-fields) are available to use in aggregations, search queries, and so forth. +Elasticsearch also allows you to [define runtime fields at search time](https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-search-request.html), and unlike [script fields](#script-fields) these runtime fields are available to use in aggregations, search queries, and so forth. ```js searchBuilder.addRuntimeMapping( "hasPricing", { From b11e4d2f7ae49db149f845112a1c029c77ca22b0 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Thu, 18 Apr 2024 10:25:53 -0400 Subject: [PATCH 09/14] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Minor=20improve?= =?UTF-8?q?ments=20for=20the=20addRuntimeMapping()=20method=20and=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Searching/Search.md | 10 +++++++++- models/SearchBuilder.cfc | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/Searching/Search.md b/docs/Searching/Search.md index 1fa0e1b..3a19af6 100644 --- a/docs/Searching/Search.md +++ b/docs/Searching/Search.md @@ -217,7 +217,13 @@ searchBuilder.addRuntimeMapping( "hasPricing", { } ); ``` -This will result in an `"hasPricing"` field in the `fields` property on the `Document` object: +Using `.addField()` ensures the field is returned with the document upon query completion: + +```js +searchBuilder.addRuntimeMapping( "hasPricing", ... ).addField( "hasPricing" ); +``` + +We can then retrieve the result field via the `getFields()` method: ```js var documentsWithPricing = searchBuilder.execute() @@ -225,6 +231,8 @@ var documentsWithPricing = searchBuilder.execute() .filter( (document) => document.getFields()["hasPricing"] ); ``` +or inlined with the document mento using `hit.getDocument( includeFields = true )`. + ### Advanced Query DSL The SearchBuilder also allows full use of the [Elasticsearch query language](https://www.elastic.co/guide/en/elasticsearch/reference/current/_introducing_the_query_language.html), allowing full configuration of your search queries. There are several methods to provide the raw query language to the Search Builder. One is during instantiation. diff --git a/models/SearchBuilder.cfc b/models/SearchBuilder.cfc index 47f323a..40f86f1 100644 --- a/models/SearchBuilder.cfc +++ b/models/SearchBuilder.cfc @@ -1373,6 +1373,7 @@ component accessors="true" { return this; } + /** * Append a search-time (runtime) mapping to the search. * @@ -1382,8 +1383,7 @@ component accessors="true" { */ public SearchBuilder function addRuntimeMapping( required string name, - struct script, - any source = true + struct script ){ if ( isNull( variables.runtimeMappings ) ) { variables.runtimeMappings = {}; From 659ec490f2f703588f3c5f490fca3b7b5256c809 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Thu, 18 Apr 2024 11:13:12 -0400 Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=93=96=20DOC:=20Add=20changelog=20i?= =?UTF-8?q?tem=20for=20addRuntimeMapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index dad7c96..f968f24 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ---- ## [Unreleased] +### Added +* Added `addRuntimeMapping()` method for mapping runtime fields at search time + ### Fixed * Improved error handling for rare cases where ElasticSearch exceptions are missing a `reason` key. From 9807727deef422f2d2ca890257353d4ac8b98d6a Mon Sep 17 00:00:00 2001 From: Michael Born Date: Thu, 18 Apr 2024 11:14:42 -0400 Subject: [PATCH 11/14] Bump minor version for new feature --- box.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/box.json b/box.json index 767253a..7e5259a 100644 --- a/box.json +++ b/box.json @@ -2,7 +2,7 @@ "name":"Elasticsearch for the Coldbox Framework", "author":"Ortus Solutions Date: Thu, 18 Apr 2024 12:04:33 -0400 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=9A=80=20RELEASE:=20v3.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index f968f24..4a6d529 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ---- ## [Unreleased] +## [3.3.0] - 04-18-2024 + ### Added * Added `addRuntimeMapping()` method for mapping runtime fields at search time From 3c90161d209e3a58a73a9db7580bb8683260f305 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Thu, 18 Apr 2024 12:09:30 -0400 Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=93=96=20DOC:=20Add=20release=20doc?= =?UTF-8?q?s=20for=20my=20own=20sanity's=20sake?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Contributing.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/Contributing.md b/docs/Contributing.md index e427b58..05605a6 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -22,4 +22,13 @@ If you would prefer to set up Elasticsearch yourself, make sure you start this a ELASTICSEARCH_PROTOCOL=http ELASTICSEARCH_HOST=127.0.0.1 ELASTICSEARCH_PORT=9200 -``` \ No newline at end of file +``` + +## Releases + +To issue a new release: + +1. Update (and commit) `changelog.md` with each addition, bugfix, or security issue. + 1. These should be placed under the version number heading: `## [x.y.z] - dd-mm-yyyy`. Later this will be automated to use the `## Unreleased` section. +2. Set and commit the new version number in `box.json`, following semantic versioning format. +3. Run the release script: `box recipe build/release.boxr` \ No newline at end of file From 86103e23c260e3ab943d76e154046d81fc41bdf3 Mon Sep 17 00:00:00 2001 From: jclausen Date: Thu, 18 Apr 2024 12:23:53 -0400 Subject: [PATCH 14/14] fix changelog for 3.3.0 release --- changelog.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/changelog.md b/changelog.md index 4a6d529..c498193 100644 --- a/changelog.md +++ b/changelog.md @@ -6,8 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ---- -## [Unreleased] - ## [3.3.0] - 04-18-2024 ### Added